@apiposture/cli 1.0.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 (110) hide show
  1. package/.apiposture.json.example +56 -0
  2. package/.github/workflows/publish.yml +38 -0
  3. package/.github/workflows/test.yml +42 -0
  4. package/LICENSE +21 -0
  5. package/README.md +156 -0
  6. package/dist/cli/commands/license/activate.d.ts +3 -0
  7. package/dist/cli/commands/license/activate.js +35 -0
  8. package/dist/cli/commands/license/deactivate.d.ts +3 -0
  9. package/dist/cli/commands/license/deactivate.js +28 -0
  10. package/dist/cli/commands/license/status.d.ts +3 -0
  11. package/dist/cli/commands/license/status.js +36 -0
  12. package/dist/cli/commands/scan.d.ts +3 -0
  13. package/dist/cli/commands/scan.js +211 -0
  14. package/dist/cli/options.d.ts +27 -0
  15. package/dist/cli/options.js +30 -0
  16. package/dist/core/analysis/project-analyzer.d.ts +16 -0
  17. package/dist/core/analysis/project-analyzer.js +54 -0
  18. package/dist/core/analysis/source-file-loader.d.ts +32 -0
  19. package/dist/core/analysis/source-file-loader.js +155 -0
  20. package/dist/core/authorization/authorization-extractor.d.ts +11 -0
  21. package/dist/core/authorization/authorization-extractor.js +2 -0
  22. package/dist/core/authorization/express-auth-extractor.d.ts +10 -0
  23. package/dist/core/authorization/express-auth-extractor.js +106 -0
  24. package/dist/core/authorization/global-auth-analyzer.d.ts +12 -0
  25. package/dist/core/authorization/global-auth-analyzer.js +74 -0
  26. package/dist/core/authorization/nestjs-auth-extractor.d.ts +13 -0
  27. package/dist/core/authorization/nestjs-auth-extractor.js +142 -0
  28. package/dist/core/configuration/config-loader.d.ts +27 -0
  29. package/dist/core/configuration/config-loader.js +72 -0
  30. package/dist/core/configuration/suppression-matcher.d.ts +14 -0
  31. package/dist/core/configuration/suppression-matcher.js +79 -0
  32. package/dist/core/discovery/discoverer-interface.d.ts +7 -0
  33. package/dist/core/discovery/discoverer-interface.js +2 -0
  34. package/dist/core/discovery/express-discoverer.d.ts +20 -0
  35. package/dist/core/discovery/express-discoverer.js +223 -0
  36. package/dist/core/discovery/fastify-discoverer.d.ts +19 -0
  37. package/dist/core/discovery/fastify-discoverer.js +249 -0
  38. package/dist/core/discovery/framework-detector.d.ts +9 -0
  39. package/dist/core/discovery/framework-detector.js +61 -0
  40. package/dist/core/discovery/index.d.ts +8 -0
  41. package/dist/core/discovery/index.js +8 -0
  42. package/dist/core/discovery/koa-discoverer.d.ts +16 -0
  43. package/dist/core/discovery/koa-discoverer.js +151 -0
  44. package/dist/core/discovery/nestjs-discoverer.d.ts +16 -0
  45. package/dist/core/discovery/nestjs-discoverer.js +180 -0
  46. package/dist/core/discovery/route-group-registry.d.ts +18 -0
  47. package/dist/core/discovery/route-group-registry.js +50 -0
  48. package/dist/core/licensing/license-context.d.ts +17 -0
  49. package/dist/core/licensing/license-context.js +15 -0
  50. package/dist/core/licensing/license-features.d.ts +14 -0
  51. package/dist/core/licensing/license-features.js +47 -0
  52. package/dist/core/models/authorization-info.d.ts +13 -0
  53. package/dist/core/models/authorization-info.js +25 -0
  54. package/dist/core/models/endpoint-type.d.ts +8 -0
  55. package/dist/core/models/endpoint-type.js +12 -0
  56. package/dist/core/models/endpoint.d.ts +16 -0
  57. package/dist/core/models/endpoint.js +16 -0
  58. package/dist/core/models/finding.d.ts +19 -0
  59. package/dist/core/models/finding.js +8 -0
  60. package/dist/core/models/http-method.d.ts +14 -0
  61. package/dist/core/models/http-method.js +25 -0
  62. package/dist/core/models/index.d.ts +10 -0
  63. package/dist/core/models/index.js +10 -0
  64. package/dist/core/models/scan-result.d.ts +21 -0
  65. package/dist/core/models/scan-result.js +35 -0
  66. package/dist/core/models/security-classification.d.ts +8 -0
  67. package/dist/core/models/security-classification.js +12 -0
  68. package/dist/core/models/severity.d.ts +11 -0
  69. package/dist/core/models/severity.js +23 -0
  70. package/dist/core/models/source-location.d.ts +7 -0
  71. package/dist/core/models/source-location.js +4 -0
  72. package/dist/index.d.ts +3 -0
  73. package/dist/index.js +23 -0
  74. package/dist/licensing/license-manager.d.ts +38 -0
  75. package/dist/licensing/license-manager.js +184 -0
  76. package/dist/output/accessibility-helper.d.ts +22 -0
  77. package/dist/output/accessibility-helper.js +98 -0
  78. package/dist/output/formatter-interface.d.ts +11 -0
  79. package/dist/output/formatter-interface.js +2 -0
  80. package/dist/output/index.d.ts +6 -0
  81. package/dist/output/index.js +6 -0
  82. package/dist/output/json-formatter.d.ts +7 -0
  83. package/dist/output/json-formatter.js +72 -0
  84. package/dist/output/markdown-formatter.d.ts +10 -0
  85. package/dist/output/markdown-formatter.js +114 -0
  86. package/dist/output/terminal-formatter.d.ts +12 -0
  87. package/dist/output/terminal-formatter.js +82 -0
  88. package/dist/rules/consistency/controller-action-conflict.d.ts +19 -0
  89. package/dist/rules/consistency/controller-action-conflict.js +40 -0
  90. package/dist/rules/consistency/missing-auth-on-writes.d.ts +21 -0
  91. package/dist/rules/consistency/missing-auth-on-writes.js +59 -0
  92. package/dist/rules/exposure/allow-anonymous-on-write.d.ts +20 -0
  93. package/dist/rules/exposure/allow-anonymous-on-write.js +42 -0
  94. package/dist/rules/exposure/public-without-explicit-intent.d.ts +20 -0
  95. package/dist/rules/exposure/public-without-explicit-intent.js +58 -0
  96. package/dist/rules/index.d.ts +11 -0
  97. package/dist/rules/index.js +11 -0
  98. package/dist/rules/privilege/excessive-role-access.d.ts +20 -0
  99. package/dist/rules/privilege/excessive-role-access.js +36 -0
  100. package/dist/rules/privilege/weak-role-naming.d.ts +20 -0
  101. package/dist/rules/privilege/weak-role-naming.js +50 -0
  102. package/dist/rules/rule-engine.d.ts +15 -0
  103. package/dist/rules/rule-engine.js +52 -0
  104. package/dist/rules/rule-interface.d.ts +16 -0
  105. package/dist/rules/rule-interface.js +2 -0
  106. package/dist/rules/surface/sensitive-route-keywords.d.ts +20 -0
  107. package/dist/rules/surface/sensitive-route-keywords.js +63 -0
  108. package/dist/rules/surface/unprotected-endpoint.d.ts +20 -0
  109. package/dist/rules/surface/unprotected-endpoint.js +61 -0
  110. package/package.json +60 -0
@@ -0,0 +1,249 @@
1
+ import * as ts from 'typescript';
2
+ import { getLineAndColumn, findNodes } from '../analysis/source-file-loader.js';
3
+ import { createEndpoint } from '../models/endpoint.js';
4
+ import { EndpointType } from '../models/endpoint-type.js';
5
+ import { HttpMethod, parseHttpMethod } from '../models/http-method.js';
6
+ import { createDefaultAuthorizationInfo, determineClassification, } from '../models/authorization-info.js';
7
+ const FASTIFY_HTTP_METHODS = new Set([
8
+ 'get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'all'
9
+ ]);
10
+ const FASTIFY_IDENTIFIERS = new Set(['fastify', 'server', 'app', 'instance']);
11
+ const AUTH_HOOK_PATTERNS = [
12
+ /authenticate/i,
13
+ /verify/i,
14
+ /jwt/i,
15
+ /auth/i,
16
+ /guard/i,
17
+ /protected/i,
18
+ ];
19
+ export class FastifyDiscoverer {
20
+ name = 'Fastify';
21
+ async discover(file) {
22
+ const endpoints = [];
23
+ const { sourceFile, filePath } = file;
24
+ const callExpressions = findNodes(sourceFile, ts.isCallExpression);
25
+ for (const callExpr of callExpressions) {
26
+ // Check for fastify.get(), fastify.post(), etc.
27
+ const shorthandEndpoint = this.processShorthandRoute(callExpr, sourceFile, filePath);
28
+ if (shorthandEndpoint) {
29
+ endpoints.push(shorthandEndpoint);
30
+ continue;
31
+ }
32
+ // Check for fastify.route({ method, url, ... })
33
+ const routeEndpoint = this.processRouteMethod(callExpr, sourceFile, filePath);
34
+ if (routeEndpoint) {
35
+ endpoints.push(routeEndpoint);
36
+ }
37
+ }
38
+ return endpoints;
39
+ }
40
+ processShorthandRoute(callExpr, sourceFile, filePath) {
41
+ if (!ts.isPropertyAccessExpression(callExpr.expression)) {
42
+ return null;
43
+ }
44
+ const propAccess = callExpr.expression;
45
+ const methodName = propAccess.name.text.toLowerCase();
46
+ if (!FASTIFY_HTTP_METHODS.has(methodName)) {
47
+ return null;
48
+ }
49
+ const callerName = this.getCallerName(propAccess.expression);
50
+ if (!callerName || !this.isFastifyIdentifier(callerName)) {
51
+ return null;
52
+ }
53
+ const args = callExpr.arguments;
54
+ if (args.length === 0) {
55
+ return null;
56
+ }
57
+ // First arg is route path
58
+ const routePath = this.extractStringValue(args[0]);
59
+ if (!routePath) {
60
+ return null;
61
+ }
62
+ // Extract options/handler
63
+ let handlerName = 'anonymous';
64
+ let authorization = createDefaultAuthorizationInfo();
65
+ // Check for options object: fastify.get('/path', { preHandler: [] }, handler)
66
+ if (args.length >= 2 && ts.isObjectLiteralExpression(args[1])) {
67
+ const options = args[1];
68
+ authorization = this.extractAuthFromOptions(options, sourceFile);
69
+ if (args.length >= 3) {
70
+ handlerName = this.extractHandlerName(args[2], sourceFile);
71
+ }
72
+ }
73
+ else if (args.length >= 2) {
74
+ handlerName = this.extractHandlerName(args[1], sourceFile);
75
+ }
76
+ authorization.classification = determineClassification(authorization);
77
+ const location = getLineAndColumn(sourceFile, callExpr.getStart(sourceFile));
78
+ return createEndpoint({
79
+ route: this.normalizePath(routePath),
80
+ method: parseHttpMethod(methodName.toUpperCase()) ?? HttpMethod.GET,
81
+ handlerName,
82
+ type: EndpointType.Fastify,
83
+ location: {
84
+ filePath,
85
+ line: location.line,
86
+ column: location.column,
87
+ },
88
+ authorization,
89
+ });
90
+ }
91
+ processRouteMethod(callExpr, sourceFile, filePath) {
92
+ if (!ts.isPropertyAccessExpression(callExpr.expression)) {
93
+ return null;
94
+ }
95
+ const propAccess = callExpr.expression;
96
+ if (propAccess.name.text !== 'route') {
97
+ return null;
98
+ }
99
+ const callerName = this.getCallerName(propAccess.expression);
100
+ if (!callerName || !this.isFastifyIdentifier(callerName)) {
101
+ return null;
102
+ }
103
+ const args = callExpr.arguments;
104
+ if (args.length === 0 || !ts.isObjectLiteralExpression(args[0])) {
105
+ return null;
106
+ }
107
+ const options = args[0];
108
+ let method = null;
109
+ let url = null;
110
+ let handlerName = 'handler';
111
+ const authorization = createDefaultAuthorizationInfo();
112
+ for (const prop of options.properties) {
113
+ if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) {
114
+ continue;
115
+ }
116
+ const propName = prop.name.text;
117
+ if (propName === 'method') {
118
+ const methodStr = this.extractStringValue(prop.initializer);
119
+ if (methodStr) {
120
+ method = parseHttpMethod(methodStr.toUpperCase()) ?? null;
121
+ }
122
+ }
123
+ if (propName === 'url') {
124
+ url = this.extractStringValue(prop.initializer);
125
+ }
126
+ if (propName === 'handler') {
127
+ handlerName = this.extractHandlerName(prop.initializer, sourceFile);
128
+ }
129
+ if (propName === 'preHandler' || propName === 'onRequest') {
130
+ this.extractHooksAuth(prop.initializer, sourceFile, authorization);
131
+ }
132
+ }
133
+ if (!method || !url) {
134
+ return null;
135
+ }
136
+ authorization.classification = determineClassification(authorization);
137
+ const location = getLineAndColumn(sourceFile, callExpr.getStart(sourceFile));
138
+ return createEndpoint({
139
+ route: this.normalizePath(url),
140
+ method,
141
+ handlerName,
142
+ type: EndpointType.Fastify,
143
+ location: {
144
+ filePath,
145
+ line: location.line,
146
+ column: location.column,
147
+ },
148
+ authorization,
149
+ });
150
+ }
151
+ extractAuthFromOptions(options, sourceFile) {
152
+ const auth = createDefaultAuthorizationInfo();
153
+ for (const prop of options.properties) {
154
+ if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name)) {
155
+ continue;
156
+ }
157
+ const propName = prop.name.text;
158
+ if (propName === 'preHandler' || propName === 'onRequest') {
159
+ this.extractHooksAuth(prop.initializer, sourceFile, auth);
160
+ }
161
+ }
162
+ return auth;
163
+ }
164
+ extractHooksAuth(node, sourceFile, auth) {
165
+ const hooks = [];
166
+ if (ts.isArrayLiteralExpression(node)) {
167
+ for (const elem of node.elements) {
168
+ const hookName = this.extractHookName(elem, sourceFile);
169
+ if (hookName) {
170
+ hooks.push(hookName);
171
+ }
172
+ }
173
+ }
174
+ else {
175
+ const hookName = this.extractHookName(node, sourceFile);
176
+ if (hookName) {
177
+ hooks.push(hookName);
178
+ }
179
+ }
180
+ auth.middlewareChain.push(...hooks);
181
+ for (const hook of hooks) {
182
+ if (this.isAuthHook(hook)) {
183
+ auth.isAuthenticated = true;
184
+ auth.guardNames.push(hook);
185
+ }
186
+ }
187
+ }
188
+ extractHookName(node, sourceFile) {
189
+ if (ts.isIdentifier(node)) {
190
+ return node.text;
191
+ }
192
+ if (ts.isPropertyAccessExpression(node)) {
193
+ return node.getText(sourceFile);
194
+ }
195
+ if (ts.isCallExpression(node)) {
196
+ if (ts.isIdentifier(node.expression)) {
197
+ return node.expression.text;
198
+ }
199
+ if (ts.isPropertyAccessExpression(node.expression)) {
200
+ return node.expression.getText(sourceFile);
201
+ }
202
+ }
203
+ return null;
204
+ }
205
+ isAuthHook(hookName) {
206
+ return AUTH_HOOK_PATTERNS.some((pattern) => pattern.test(hookName));
207
+ }
208
+ getCallerName(expression) {
209
+ if (ts.isIdentifier(expression)) {
210
+ return expression.text;
211
+ }
212
+ return null;
213
+ }
214
+ isFastifyIdentifier(name) {
215
+ if (FASTIFY_IDENTIFIERS.has(name.toLowerCase())) {
216
+ return true;
217
+ }
218
+ return name.toLowerCase().includes('fastify');
219
+ }
220
+ extractStringValue(node) {
221
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
222
+ return node.text;
223
+ }
224
+ return null;
225
+ }
226
+ extractHandlerName(node, sourceFile) {
227
+ if (ts.isIdentifier(node)) {
228
+ return node.text;
229
+ }
230
+ if (ts.isPropertyAccessExpression(node)) {
231
+ return node.getText(sourceFile);
232
+ }
233
+ if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
234
+ return 'anonymous';
235
+ }
236
+ return 'unknown';
237
+ }
238
+ normalizePath(path) {
239
+ if (!path.startsWith('/')) {
240
+ path = '/' + path;
241
+ }
242
+ path = path.replace(/\/+/g, '/');
243
+ if (path.length > 1 && path.endsWith('/')) {
244
+ path = path.slice(0, -1);
245
+ }
246
+ return path;
247
+ }
248
+ }
249
+ //# sourceMappingURL=fastify-discoverer.js.map
@@ -0,0 +1,9 @@
1
+ import { EndpointType } from '../models/endpoint-type.js';
2
+ export interface DetectedFrameworks {
3
+ frameworks: EndpointType[];
4
+ confidence: Record<EndpointType, number>;
5
+ }
6
+ export declare class FrameworkDetector {
7
+ detect(projectPath: string): DetectedFrameworks;
8
+ }
9
+ //# sourceMappingURL=framework-detector.d.ts.map
@@ -0,0 +1,61 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { EndpointType } from '../models/endpoint-type.js';
4
+ export class FrameworkDetector {
5
+ detect(projectPath) {
6
+ const result = {
7
+ frameworks: [],
8
+ confidence: {
9
+ [EndpointType.Express]: 0,
10
+ [EndpointType.NestJS]: 0,
11
+ [EndpointType.Fastify]: 0,
12
+ [EndpointType.Koa]: 0,
13
+ },
14
+ };
15
+ // Read package.json
16
+ const packageJsonPath = path.join(projectPath, 'package.json');
17
+ if (!fs.existsSync(packageJsonPath)) {
18
+ return result;
19
+ }
20
+ let packageJson;
21
+ try {
22
+ const content = fs.readFileSync(packageJsonPath, 'utf-8');
23
+ packageJson = JSON.parse(content);
24
+ }
25
+ catch {
26
+ return result;
27
+ }
28
+ const deps = {
29
+ ...packageJson.dependencies,
30
+ ...packageJson.devDependencies,
31
+ };
32
+ // Check for Express
33
+ if (deps.express) {
34
+ result.confidence[EndpointType.Express] = 100;
35
+ }
36
+ // Check for NestJS
37
+ if (deps['@nestjs/core'] || deps['@nestjs/common']) {
38
+ result.confidence[EndpointType.NestJS] = 100;
39
+ }
40
+ // Check for Fastify
41
+ if (deps.fastify) {
42
+ result.confidence[EndpointType.Fastify] = 100;
43
+ }
44
+ // Check for Koa
45
+ if (deps.koa) {
46
+ result.confidence[EndpointType.Koa] = 100;
47
+ // If koa-router is present, increase confidence
48
+ if (deps['koa-router'] || deps['@koa/router']) {
49
+ result.confidence[EndpointType.Koa] = 100;
50
+ }
51
+ }
52
+ // Build frameworks list (those with confidence > 0)
53
+ for (const [framework, confidence] of Object.entries(result.confidence)) {
54
+ if (confidence > 0) {
55
+ result.frameworks.push(framework);
56
+ }
57
+ }
58
+ return result;
59
+ }
60
+ }
61
+ //# sourceMappingURL=framework-detector.js.map
@@ -0,0 +1,8 @@
1
+ export * from './discoverer-interface.js';
2
+ export * from './express-discoverer.js';
3
+ export * from './nestjs-discoverer.js';
4
+ export * from './fastify-discoverer.js';
5
+ export * from './koa-discoverer.js';
6
+ export * from './route-group-registry.js';
7
+ export * from './framework-detector.js';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,8 @@
1
+ export * from './discoverer-interface.js';
2
+ export * from './express-discoverer.js';
3
+ export * from './nestjs-discoverer.js';
4
+ export * from './fastify-discoverer.js';
5
+ export * from './koa-discoverer.js';
6
+ export * from './route-group-registry.js';
7
+ export * from './framework-detector.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,16 @@
1
+ import { EndpointDiscoverer } from './discoverer-interface.js';
2
+ import { LoadedSourceFile } from '../analysis/source-file-loader.js';
3
+ import { Endpoint } from '../models/endpoint.js';
4
+ export declare class KoaDiscoverer implements EndpointDiscoverer {
5
+ readonly name = "Koa";
6
+ discover(file: LoadedSourceFile): Promise<Endpoint[]>;
7
+ private processRouteCall;
8
+ private buildAuthorizationInfo;
9
+ private isAuthMiddleware;
10
+ private getCallerName;
11
+ private isKoaRouterIdentifier;
12
+ private extractStringValue;
13
+ private extractMiddlewareName;
14
+ private normalizePath;
15
+ }
16
+ //# sourceMappingURL=koa-discoverer.d.ts.map
@@ -0,0 +1,151 @@
1
+ import * as ts from 'typescript';
2
+ import { getLineAndColumn, findNodes } from '../analysis/source-file-loader.js';
3
+ import { createEndpoint } from '../models/endpoint.js';
4
+ import { EndpointType } from '../models/endpoint-type.js';
5
+ import { HttpMethod, parseHttpMethod } from '../models/http-method.js';
6
+ import { createDefaultAuthorizationInfo, determineClassification, } from '../models/authorization-info.js';
7
+ const KOA_HTTP_METHODS = new Set([
8
+ 'get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'all'
9
+ ]);
10
+ const KOA_ROUTER_IDENTIFIERS = new Set(['router', 'koaRouter', 'apiRouter']);
11
+ const AUTH_MIDDLEWARE_PATTERNS = [
12
+ /authenticate/i,
13
+ /auth/i,
14
+ /jwt/i,
15
+ /passport/i,
16
+ /protect/i,
17
+ /guard/i,
18
+ /verify/i,
19
+ ];
20
+ export class KoaDiscoverer {
21
+ name = 'Koa';
22
+ async discover(file) {
23
+ const endpoints = [];
24
+ const { sourceFile, filePath } = file;
25
+ const callExpressions = findNodes(sourceFile, ts.isCallExpression);
26
+ for (const callExpr of callExpressions) {
27
+ const endpoint = this.processRouteCall(callExpr, sourceFile, filePath);
28
+ if (endpoint) {
29
+ endpoints.push(endpoint);
30
+ }
31
+ }
32
+ return endpoints;
33
+ }
34
+ processRouteCall(callExpr, sourceFile, filePath) {
35
+ if (!ts.isPropertyAccessExpression(callExpr.expression)) {
36
+ return null;
37
+ }
38
+ const propAccess = callExpr.expression;
39
+ const methodName = propAccess.name.text.toLowerCase();
40
+ if (!KOA_HTTP_METHODS.has(methodName)) {
41
+ return null;
42
+ }
43
+ const callerName = this.getCallerName(propAccess.expression);
44
+ if (!callerName || !this.isKoaRouterIdentifier(callerName)) {
45
+ return null;
46
+ }
47
+ const args = callExpr.arguments;
48
+ if (args.length === 0) {
49
+ return null;
50
+ }
51
+ // First arg is route path
52
+ const routePath = this.extractStringValue(args[0]);
53
+ if (!routePath) {
54
+ return null;
55
+ }
56
+ // Extract middleware chain and handler
57
+ const middlewares = [];
58
+ let handlerName = 'anonymous';
59
+ for (let i = 1; i < args.length; i++) {
60
+ const arg = args[i];
61
+ const name = this.extractMiddlewareName(arg, sourceFile);
62
+ if (i === args.length - 1) {
63
+ // Last argument is the handler
64
+ handlerName = name ?? 'anonymous';
65
+ }
66
+ else if (name) {
67
+ middlewares.push(name);
68
+ }
69
+ }
70
+ // Build authorization info
71
+ const authorization = this.buildAuthorizationInfo(middlewares);
72
+ const location = getLineAndColumn(sourceFile, callExpr.getStart(sourceFile));
73
+ return createEndpoint({
74
+ route: this.normalizePath(routePath),
75
+ method: parseHttpMethod(methodName.toUpperCase()) ?? HttpMethod.GET,
76
+ handlerName,
77
+ type: EndpointType.Koa,
78
+ location: {
79
+ filePath,
80
+ line: location.line,
81
+ column: location.column,
82
+ },
83
+ authorization,
84
+ });
85
+ }
86
+ buildAuthorizationInfo(middlewares) {
87
+ const auth = createDefaultAuthorizationInfo();
88
+ auth.middlewareChain = middlewares;
89
+ for (const middleware of middlewares) {
90
+ if (this.isAuthMiddleware(middleware)) {
91
+ auth.isAuthenticated = true;
92
+ auth.guardNames.push(middleware);
93
+ }
94
+ }
95
+ auth.classification = determineClassification(auth);
96
+ return auth;
97
+ }
98
+ isAuthMiddleware(name) {
99
+ return AUTH_MIDDLEWARE_PATTERNS.some((pattern) => pattern.test(name));
100
+ }
101
+ getCallerName(expression) {
102
+ if (ts.isIdentifier(expression)) {
103
+ return expression.text;
104
+ }
105
+ return null;
106
+ }
107
+ isKoaRouterIdentifier(name) {
108
+ if (KOA_ROUTER_IDENTIFIERS.has(name)) {
109
+ return true;
110
+ }
111
+ const lower = name.toLowerCase();
112
+ return lower.includes('router') || lower.includes('koa');
113
+ }
114
+ extractStringValue(node) {
115
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
116
+ return node.text;
117
+ }
118
+ return null;
119
+ }
120
+ extractMiddlewareName(node, sourceFile) {
121
+ if (ts.isIdentifier(node)) {
122
+ return node.text;
123
+ }
124
+ if (ts.isPropertyAccessExpression(node)) {
125
+ return node.getText(sourceFile);
126
+ }
127
+ if (ts.isCallExpression(node)) {
128
+ if (ts.isIdentifier(node.expression)) {
129
+ return node.expression.text;
130
+ }
131
+ if (ts.isPropertyAccessExpression(node.expression)) {
132
+ return node.expression.getText(sourceFile);
133
+ }
134
+ }
135
+ if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) {
136
+ return null; // Anonymous function
137
+ }
138
+ return null;
139
+ }
140
+ normalizePath(path) {
141
+ if (!path.startsWith('/')) {
142
+ path = '/' + path;
143
+ }
144
+ path = path.replace(/\/+/g, '/');
145
+ if (path.length > 1 && path.endsWith('/')) {
146
+ path = path.slice(0, -1);
147
+ }
148
+ return path;
149
+ }
150
+ }
151
+ //# sourceMappingURL=koa-discoverer.js.map
@@ -0,0 +1,16 @@
1
+ import { EndpointDiscoverer } from './discoverer-interface.js';
2
+ import { LoadedSourceFile } from '../analysis/source-file-loader.js';
3
+ import { Endpoint } from '../models/endpoint.js';
4
+ export declare class NestJSDiscoverer implements EndpointDiscoverer {
5
+ readonly name = "NestJS";
6
+ private authExtractor;
7
+ constructor();
8
+ discover(file: LoadedSourceFile): Promise<Endpoint[]>;
9
+ private processClass;
10
+ private processMethod;
11
+ private extractControllerPath;
12
+ private extractRoutePath;
13
+ private buildRoute;
14
+ private extractClassAuthInfo;
15
+ }
16
+ //# sourceMappingURL=nestjs-discoverer.d.ts.map