@g1cloud/api-gen 1.0.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.
Files changed (86) hide show
  1. package/.claude/settings.local.json +22 -0
  2. package/CLAUDE.md +63 -0
  3. package/README.md +379 -0
  4. package/dist/analyzer/controllerAnalyzer.d.ts +20 -0
  5. package/dist/analyzer/controllerAnalyzer.d.ts.map +1 -0
  6. package/dist/analyzer/controllerAnalyzer.js +101 -0
  7. package/dist/analyzer/controllerAnalyzer.js.map +1 -0
  8. package/dist/analyzer/parameterAnalyzer.d.ts +19 -0
  9. package/dist/analyzer/parameterAnalyzer.d.ts.map +1 -0
  10. package/dist/analyzer/parameterAnalyzer.js +207 -0
  11. package/dist/analyzer/parameterAnalyzer.js.map +1 -0
  12. package/dist/analyzer/responseAnalyzer.d.ts +12 -0
  13. package/dist/analyzer/responseAnalyzer.d.ts.map +1 -0
  14. package/dist/analyzer/responseAnalyzer.js +116 -0
  15. package/dist/analyzer/responseAnalyzer.js.map +1 -0
  16. package/dist/analyzer/schemaGenerator.d.ts +6 -0
  17. package/dist/analyzer/schemaGenerator.d.ts.map +1 -0
  18. package/dist/analyzer/schemaGenerator.js +347 -0
  19. package/dist/analyzer/schemaGenerator.js.map +1 -0
  20. package/dist/analyzer/securityAnalyzer.d.ts +6 -0
  21. package/dist/analyzer/securityAnalyzer.d.ts.map +1 -0
  22. package/dist/analyzer/securityAnalyzer.js +177 -0
  23. package/dist/analyzer/securityAnalyzer.js.map +1 -0
  24. package/dist/generator/openapiGenerator.d.ts +14 -0
  25. package/dist/generator/openapiGenerator.d.ts.map +1 -0
  26. package/dist/generator/openapiGenerator.js +340 -0
  27. package/dist/generator/openapiGenerator.js.map +1 -0
  28. package/dist/index.d.ts +3 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +218 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/lib.d.ts +61 -0
  33. package/dist/lib.d.ts.map +1 -0
  34. package/dist/lib.js +199 -0
  35. package/dist/lib.js.map +1 -0
  36. package/dist/mcp-server.d.ts +9 -0
  37. package/dist/mcp-server.d.ts.map +1 -0
  38. package/dist/mcp-server.js +257 -0
  39. package/dist/mcp-server.js.map +1 -0
  40. package/dist/mcp-server.mjs +45586 -0
  41. package/dist/parser/astAnalyzer.d.ts +87 -0
  42. package/dist/parser/astAnalyzer.d.ts.map +1 -0
  43. package/dist/parser/astAnalyzer.js +321 -0
  44. package/dist/parser/astAnalyzer.js.map +1 -0
  45. package/dist/parser/javaParser.d.ts +10 -0
  46. package/dist/parser/javaParser.d.ts.map +1 -0
  47. package/dist/parser/javaParser.js +805 -0
  48. package/dist/parser/javaParser.js.map +1 -0
  49. package/dist/types/index.d.ts +217 -0
  50. package/dist/types/index.d.ts.map +1 -0
  51. package/dist/types/index.js +3 -0
  52. package/dist/types/index.js.map +1 -0
  53. package/examples/CreateUserRequest.java +80 -0
  54. package/examples/DepartmentDTO.java +45 -0
  55. package/examples/Filter.java +39 -0
  56. package/examples/PaginatedList.java +71 -0
  57. package/examples/ProductController.java +136 -0
  58. package/examples/ProductDTO.java +129 -0
  59. package/examples/RoleDTO.java +47 -0
  60. package/examples/SearchParam.java +55 -0
  61. package/examples/Sort.java +70 -0
  62. package/examples/UpdateUserRequest.java +74 -0
  63. package/examples/UserController.java +98 -0
  64. package/examples/UserDTO.java +119 -0
  65. package/package.json +51 -0
  66. package/prompt/01_Initial.md +358 -0
  67. package/prompt/02_/354/266/224/352/260/200.md +31 -0
  68. package/src/analyzer/controllerAnalyzer.ts +125 -0
  69. package/src/analyzer/parameterAnalyzer.ts +259 -0
  70. package/src/analyzer/responseAnalyzer.ts +142 -0
  71. package/src/analyzer/schemaGenerator.ts +412 -0
  72. package/src/analyzer/securityAnalyzer.ts +200 -0
  73. package/src/generator/openapiGenerator.ts +378 -0
  74. package/src/index.ts +212 -0
  75. package/src/lib.ts +240 -0
  76. package/src/mcp-server.ts +310 -0
  77. package/src/parser/astAnalyzer.ts +373 -0
  78. package/src/parser/javaParser.ts +901 -0
  79. package/src/types/index.ts +238 -0
  80. package/test-boolean.yaml +607 -0
  81. package/test-filter.yaml +576 -0
  82. package/test-inner.ts +59 -0
  83. package/test-output.yaml +650 -0
  84. package/test-paginated.yaml +585 -0
  85. package/tsconfig.json +20 -0
  86. package/tsup.config.ts +30 -0
@@ -0,0 +1,259 @@
1
+ import {
2
+ JavaMethod,
3
+ JavaParameter,
4
+ JavaAnnotation,
5
+ ParameterInfo,
6
+ RequestBodyInfo,
7
+ ProcessingContext,
8
+ SchemaInfo,
9
+ } from '../types';
10
+ import { ASTAnalyzer } from '../parser/astAnalyzer';
11
+
12
+ interface ParameterAnalysisResult {
13
+ parameters: ParameterInfo[];
14
+ requestBody?: RequestBodyInfo;
15
+ hasSearchParam: boolean;
16
+ }
17
+
18
+ /**
19
+ * Analyze method parameters and extract OpenAPI parameter/request body info
20
+ * @param method The Java method to analyze
21
+ * @param context Processing context
22
+ * @param paramDescriptions Javadoc @param descriptions (param name -> description)
23
+ */
24
+ export function analyzeParameters(
25
+ method: JavaMethod,
26
+ context: ProcessingContext,
27
+ paramDescriptions: Record<string, string> = {}
28
+ ): ParameterAnalysisResult {
29
+ const parameters: ParameterInfo[] = [];
30
+ let requestBody: RequestBodyInfo | undefined;
31
+ let hasSearchParam = false;
32
+
33
+ for (const param of method.parameters) {
34
+ // Check if it's a SearchParam
35
+ if (ASTAnalyzer.isSearchParam(param.type)) {
36
+ hasSearchParam = true;
37
+ // Add SearchParam query parameters
38
+ parameters.push(...getSearchParamParameters());
39
+ continue;
40
+ }
41
+
42
+ // Get parameter type from annotations
43
+ const paramType = ASTAnalyzer.getParameterType(param.annotations);
44
+
45
+ // Get @param description from javadoc
46
+ const javadocDescription = paramDescriptions[param.name];
47
+
48
+ if (paramType === 'body') {
49
+ requestBody = createRequestBody(param, context, javadocDescription);
50
+ } else if (paramType) {
51
+ const paramInfo = createParameterInfo(param, paramType, javadocDescription);
52
+ if (paramInfo) {
53
+ parameters.push(paramInfo);
54
+ }
55
+ }
56
+ // Parameters without REST annotations are typically handled by Spring automatically
57
+ // but we skip them as they're not part of the API contract
58
+ }
59
+
60
+ return { parameters, requestBody, hasSearchParam };
61
+ }
62
+
63
+ /**
64
+ * Get SearchParam query parameters
65
+ */
66
+ function getSearchParamParameters(): ParameterInfo[] {
67
+ return [
68
+ {
69
+ name: 'offset',
70
+ in: 'query',
71
+ required: false,
72
+ type: 'integer',
73
+ description: 'Pagination offset',
74
+ schema: { type: 'integer' },
75
+ },
76
+ {
77
+ name: 'limit',
78
+ in: 'query',
79
+ required: false,
80
+ type: 'integer',
81
+ description: 'Pagination limit',
82
+ schema: { type: 'integer' },
83
+ },
84
+ {
85
+ name: 'filter',
86
+ in: 'query',
87
+ required: false,
88
+ type: 'object',
89
+ description: 'Filter conditions',
90
+ schema: { $ref: '#/components/schemas/Filter' },
91
+ },
92
+ {
93
+ name: 'sort',
94
+ in: 'query',
95
+ required: false,
96
+ type: 'object',
97
+ description: 'Sort conditions',
98
+ schema: { $ref: '#/components/schemas/Sort' },
99
+ },
100
+ ];
101
+ }
102
+
103
+ /**
104
+ * Create ParameterInfo from a Java parameter
105
+ */
106
+ function createParameterInfo(
107
+ param: JavaParameter,
108
+ paramType: 'query' | 'path' | 'header',
109
+ javadocDescription?: string
110
+ ): ParameterInfo | null {
111
+ // Find the relevant annotation
112
+ const annotation = param.annotations.find(
113
+ (a) => a.name === 'RequestParam' || a.name === 'PathVariable' || a.name === 'RequestHeader'
114
+ );
115
+
116
+ if (!annotation) {
117
+ return null;
118
+ }
119
+
120
+ // Get parameter name (from annotation or Java parameter name)
121
+ const paramName = getAnnotationStringValue(annotation, 'value') ||
122
+ getAnnotationStringValue(annotation, 'name') ||
123
+ param.name;
124
+
125
+ // Determine if required
126
+ let required = paramType === 'path'; // Path variables are always required
127
+ if (paramType !== 'path') {
128
+ const requiredValue = annotation.values['required'];
129
+ if (requiredValue !== undefined) {
130
+ required = requiredValue === 'true' || requiredValue === true;
131
+ } else {
132
+ // Default is true for @RequestParam
133
+ required = paramType === 'query';
134
+ }
135
+ }
136
+
137
+ // Get default value
138
+ const defaultValue = getAnnotationStringValue(annotation, 'defaultValue');
139
+ if (defaultValue) {
140
+ required = false; // If there's a default value, it's not required
141
+ }
142
+
143
+ // Convert Java type to OpenAPI type
144
+ const openApiType = ASTAnalyzer.javaTypeToOpenAPI(param.type);
145
+
146
+ return {
147
+ name: paramName,
148
+ in: paramType,
149
+ required,
150
+ type: openApiType.type,
151
+ description: javadocDescription,
152
+ defaultValue,
153
+ schema: {
154
+ type: openApiType.type,
155
+ format: openApiType.format,
156
+ },
157
+ };
158
+ }
159
+
160
+ /**
161
+ * Create RequestBodyInfo from a Java parameter
162
+ */
163
+ function createRequestBody(
164
+ param: JavaParameter,
165
+ context: ProcessingContext,
166
+ javadocDescription?: string
167
+ ): RequestBodyInfo {
168
+ // Find @RequestBody annotation
169
+ const annotation = param.annotations.find((a) => a.name === 'RequestBody');
170
+
171
+ // Determine if required
172
+ let required = true;
173
+ if (annotation) {
174
+ const requiredValue = annotation.values['required'];
175
+ if (requiredValue !== undefined) {
176
+ required = requiredValue === 'true' || requiredValue === true;
177
+ }
178
+ }
179
+
180
+ // Create schema reference
181
+ const schema = createSchemaForType(param.type, param.genericType, context);
182
+
183
+ return {
184
+ required,
185
+ contentType: 'application/json',
186
+ schema,
187
+ description: javadocDescription,
188
+ };
189
+ }
190
+
191
+ /**
192
+ * Create a schema for a Java type
193
+ */
194
+ export function createSchemaForType(
195
+ javaType: string,
196
+ genericType: string | undefined,
197
+ context: ProcessingContext
198
+ ): SchemaInfo {
199
+ // Check if it's a primitive or wrapper type
200
+ if (ASTAnalyzer.isPrimitiveOrWrapper(javaType)) {
201
+ return ASTAnalyzer.javaTypeToOpenAPI(javaType);
202
+ }
203
+
204
+ // Check if it's an array
205
+ if (javaType.endsWith('[]')) {
206
+ const elementType = javaType.slice(0, -2);
207
+ return {
208
+ type: 'array',
209
+ items: createSchemaForType(elementType, undefined, context),
210
+ };
211
+ }
212
+
213
+ // Check if it's a collection type
214
+ if (ASTAnalyzer.isCollectionType(javaType)) {
215
+ const itemType = genericType ? ASTAnalyzer.extractGenericType(genericType) : 'Object';
216
+ return {
217
+ type: 'array',
218
+ items: itemType ? createSchemaForType(itemType, undefined, context) : { type: 'object' },
219
+ };
220
+ }
221
+
222
+ // Check if it's a Map type
223
+ if (ASTAnalyzer.isMapType(javaType)) {
224
+ return {
225
+ type: 'object',
226
+ additionalProperties: true,
227
+ };
228
+ }
229
+
230
+ // Check if it's ResponseEntity - extract the generic type
231
+ if (ASTAnalyzer.isResponseEntity(javaType) && genericType) {
232
+ return createSchemaForType(genericType, undefined, context);
233
+ }
234
+
235
+ // It's a custom type (DTO) - add to referenced types and return a reference
236
+ const simpleName = javaType.split('.').pop() || javaType;
237
+ context.referencedTypes.add(simpleName);
238
+
239
+ return {
240
+ $ref: `#/components/schemas/${simpleName}`,
241
+ };
242
+ }
243
+
244
+ /**
245
+ * Get string value from annotation
246
+ */
247
+ function getAnnotationStringValue(annotation: JavaAnnotation, key: string): string | undefined {
248
+ const value = annotation.values[key];
249
+ if (value === undefined) {
250
+ return undefined;
251
+ }
252
+ if (Array.isArray(value)) {
253
+ return value[0];
254
+ }
255
+ if (typeof value === 'string') {
256
+ return value;
257
+ }
258
+ return String(value);
259
+ }
@@ -0,0 +1,142 @@
1
+ import {
2
+ JavaMethod,
3
+ ResponseInfo,
4
+ ProcessingContext,
5
+ HeaderInfo,
6
+ SchemaInfo,
7
+ } from '../types';
8
+ import { ASTAnalyzer } from '../parser/astAnalyzer';
9
+ import { createSchemaForType } from './parameterAnalyzer';
10
+
11
+ interface ResponseAnalysisResult {
12
+ responses: ResponseInfo[];
13
+ hasPaginatedList: boolean;
14
+ paginatedListItemType?: string;
15
+ }
16
+
17
+ /**
18
+ * Analyze method return type and extract response information
19
+ */
20
+ export function analyzeResponse(
21
+ method: JavaMethod,
22
+ context: ProcessingContext
23
+ ): ResponseAnalysisResult {
24
+ const responses: ResponseInfo[] = [];
25
+ let hasPaginatedList = false;
26
+ let paginatedListItemType: string | undefined;
27
+
28
+ let returnType = method.returnType;
29
+ let genericType = method.returnGenericType;
30
+
31
+ // Handle ResponseEntity<T>
32
+ if (ASTAnalyzer.isResponseEntity(returnType)) {
33
+ // Extract the generic type from ResponseEntity
34
+ if (genericType) {
35
+ returnType = extractMainType(genericType);
36
+ genericType = extractNestedGenericType(genericType);
37
+ } else {
38
+ returnType = 'Object';
39
+ }
40
+ }
41
+
42
+ // Check for PaginatedList
43
+ if (ASTAnalyzer.isPaginatedList(returnType)) {
44
+ hasPaginatedList = true;
45
+ paginatedListItemType = genericType ? extractMainType(genericType) : undefined;
46
+
47
+ // Create PaginatedList response with headers
48
+ const response = createPaginatedListResponse(returnType, genericType, context);
49
+ responses.push(response);
50
+ } else if (returnType === 'void' || returnType === 'Void') {
51
+ // Void response
52
+ responses.push({
53
+ statusCode: '200',
54
+ description: 'Successful response',
55
+ });
56
+ } else {
57
+ // Regular response
58
+ const schema = createSchemaForType(returnType, genericType, context);
59
+ responses.push({
60
+ statusCode: '200',
61
+ description: 'Successful response',
62
+ contentType: 'application/json',
63
+ schema,
64
+ });
65
+ }
66
+
67
+ return { responses, hasPaginatedList, paginatedListItemType };
68
+ }
69
+
70
+ /**
71
+ * Create response for PaginatedList type
72
+ * PaginatedList<T> is treated as an array of T, with pagination info in headers
73
+ */
74
+ function createPaginatedListResponse(
75
+ returnType: string,
76
+ genericType: string | undefined,
77
+ context: ProcessingContext
78
+ ): ResponseInfo {
79
+ const itemType = genericType ? extractMainType(genericType) : 'Object';
80
+
81
+ // Add the item type to referenced types
82
+ if (itemType !== 'Object' && !ASTAnalyzer.isPrimitiveOrWrapper(itemType)) {
83
+ context.referencedTypes.add(itemType);
84
+ }
85
+
86
+ // Create array schema directly (treat PaginatedList<T> as List<T>)
87
+ const arraySchema: SchemaInfo = {
88
+ type: 'array',
89
+ items: itemType !== 'Object' && !ASTAnalyzer.isPrimitiveOrWrapper(itemType)
90
+ ? { $ref: `#/components/schemas/${itemType}` }
91
+ : ASTAnalyzer.javaTypeToOpenAPI(itemType),
92
+ };
93
+
94
+ // Create pagination headers (pagination info is passed via headers)
95
+ const headers: Record<string, HeaderInfo> = {
96
+ 'X-Total-Count': {
97
+ schema: { type: 'integer' },
98
+ description: 'Total number of items',
99
+ },
100
+ 'X-Offset': {
101
+ schema: { type: 'integer' },
102
+ description: 'Current offset',
103
+ },
104
+ 'X-Limit': {
105
+ schema: { type: 'integer' },
106
+ description: 'Current limit',
107
+ },
108
+ };
109
+
110
+ return {
111
+ statusCode: '200',
112
+ description: 'Successful response',
113
+ contentType: 'application/json',
114
+ schema: arraySchema,
115
+ headers,
116
+ };
117
+ }
118
+
119
+ /**
120
+ * Extract the main type from a generic type string
121
+ * e.g., "UserDTO" from "UserDTO" or "List<String>" -> "List"
122
+ */
123
+ function extractMainType(genericType: string): string {
124
+ // Remove any nested generics to get the main type
125
+ const match = genericType.match(/^([^<]+)/);
126
+ if (match) {
127
+ return match[1].trim();
128
+ }
129
+ return genericType;
130
+ }
131
+
132
+ /**
133
+ * Extract nested generic type
134
+ * e.g., "List<String>" -> "String", "PaginatedList<UserDTO>" -> "UserDTO"
135
+ */
136
+ function extractNestedGenericType(genericType: string): string | undefined {
137
+ const match = genericType.match(/<(.+)>$/);
138
+ if (match) {
139
+ return match[1].trim();
140
+ }
141
+ return undefined;
142
+ }