@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,378 @@
1
+ import * as fs from 'fs';
2
+ import * as yaml from 'js-yaml';
3
+ import {
4
+ CLIOptions,
5
+ OpenAPISpec,
6
+ ControllerInfo,
7
+ EndpointInfo,
8
+ ProcessingContext,
9
+ PathItem,
10
+ OperationObject,
11
+ ParameterObject,
12
+ RequestBodyObject,
13
+ ResponseObject,
14
+ SchemaInfo,
15
+ TagInfo,
16
+ } from '../types';
17
+ import { generateSchemas } from '../analyzer/schemaGenerator';
18
+
19
+ /**
20
+ * Generate OpenAPI YAML from analyzed controllers
21
+ */
22
+ export function generateOpenAPISpec(
23
+ controllers: ControllerInfo[],
24
+ context: ProcessingContext,
25
+ options: CLIOptions
26
+ ): OpenAPISpec {
27
+ // Generate schemas for all referenced types
28
+ const schemas = generateSchemas(context);
29
+
30
+ // Build tags from controllers
31
+ const tags: TagInfo[] = controllers.map(controller => ({
32
+ name: controller.className,
33
+ description: controller.javaClass.javadoc || `Endpoints for ${controller.className}`,
34
+ }));
35
+
36
+ // Build paths
37
+ const paths: Record<string, PathItem> = {};
38
+
39
+ for (const controller of controllers) {
40
+ for (const endpoint of controller.endpoints) {
41
+ const path = endpoint.path;
42
+
43
+ if (!paths[path]) {
44
+ paths[path] = {};
45
+ }
46
+
47
+ paths[path][endpoint.method] = buildOperation(endpoint, controller.className);
48
+ }
49
+ }
50
+
51
+ // Convert schema Map to object
52
+ const schemaObject: Record<string, SchemaInfo> = {};
53
+ for (const [name, schema] of schemas) {
54
+ schemaObject[name] = schema;
55
+ }
56
+
57
+ // Build the spec
58
+ const spec: OpenAPISpec = {
59
+ openapi: '3.0.0',
60
+ info: {
61
+ title: options.title,
62
+ version: options.version,
63
+ },
64
+ tags,
65
+ paths,
66
+ components: {
67
+ schemas: schemaObject,
68
+ securitySchemes: {
69
+ bearerAuth: {
70
+ type: 'http',
71
+ scheme: 'bearer',
72
+ bearerFormat: 'JWT',
73
+ },
74
+ },
75
+ },
76
+ };
77
+
78
+ // Add servers if base path is specified
79
+ if (options.basePath) {
80
+ spec.servers = [{ url: `http://localhost:8080${options.basePath}` }];
81
+ }
82
+
83
+ return spec;
84
+ }
85
+
86
+ /**
87
+ * Build operation object for an endpoint
88
+ */
89
+ function buildOperation(endpoint: EndpointInfo, controllerName?: string): OperationObject {
90
+ const operation: OperationObject = {
91
+ summary: endpoint.summary,
92
+ operationId: endpoint.operationId,
93
+ responses: {},
94
+ };
95
+
96
+ // Add tag for controller grouping
97
+ if (controllerName) {
98
+ operation.tags = [controllerName];
99
+ }
100
+
101
+ // Build description with javadoc, @return, and security info
102
+ const descriptionParts: string[] = [];
103
+
104
+ if (endpoint.description) {
105
+ descriptionParts.push(endpoint.description);
106
+ }
107
+
108
+ // Add @return description from javadoc
109
+ if (endpoint.returnDescription) {
110
+ descriptionParts.push(`\n\n**Returns:** ${endpoint.returnDescription}`);
111
+ }
112
+
113
+ // Add security info to description
114
+ if (endpoint.security.roles.length > 0) {
115
+ descriptionParts.push(`\n\n**Required Roles:** ${endpoint.security.roles.join(', ')}`);
116
+ }
117
+
118
+ if (endpoint.security.securityExpression) {
119
+ descriptionParts.push(`\n\n**Security Expression:** \`${endpoint.security.securityExpression}\``);
120
+ }
121
+
122
+ if (descriptionParts.length > 0) {
123
+ operation.description = descriptionParts.join('');
124
+ }
125
+
126
+ // Add parameters
127
+ if (endpoint.parameters.length > 0) {
128
+ operation.parameters = endpoint.parameters.map(buildParameter);
129
+ }
130
+
131
+ // Add request body
132
+ if (endpoint.requestBody) {
133
+ operation.requestBody = buildRequestBody(endpoint.requestBody);
134
+ }
135
+
136
+ // Add responses
137
+ for (const response of endpoint.responses) {
138
+ operation.responses[response.statusCode] = buildResponse(response);
139
+ }
140
+
141
+ // Add security
142
+ if (endpoint.security.roles.length > 0 || endpoint.security.authorities.length > 0) {
143
+ operation.security = [{ bearerAuth: [] }];
144
+ }
145
+
146
+ // Add x-required-roles
147
+ if (endpoint.security.roles.length > 0) {
148
+ operation['x-required-roles'] = endpoint.security.roles;
149
+ }
150
+
151
+ // Add x-security-expression for complex expressions
152
+ if (endpoint.security.hasComplexExpression && endpoint.security.securityExpression) {
153
+ operation['x-security-expression'] = endpoint.security.securityExpression;
154
+ }
155
+
156
+ return operation;
157
+ }
158
+
159
+ /**
160
+ * Build parameter object
161
+ */
162
+ function buildParameter(param: import('../types').ParameterInfo): ParameterObject {
163
+ const parameter: ParameterObject = {
164
+ name: param.name,
165
+ in: param.in,
166
+ schema: param.schema || { type: param.type },
167
+ };
168
+
169
+ if (param.required) {
170
+ parameter.required = true;
171
+ }
172
+
173
+ if (param.description) {
174
+ parameter.description = param.description;
175
+ }
176
+
177
+ return parameter;
178
+ }
179
+
180
+ /**
181
+ * Build request body object
182
+ */
183
+ function buildRequestBody(requestBody: import('../types').RequestBodyInfo): RequestBodyObject {
184
+ const body: RequestBodyObject = {
185
+ required: requestBody.required,
186
+ content: {
187
+ [requestBody.contentType]: {
188
+ schema: requestBody.schema,
189
+ },
190
+ },
191
+ };
192
+
193
+ // Add description from @param javadoc if available
194
+ if (requestBody.description) {
195
+ body.description = requestBody.description;
196
+ }
197
+
198
+ return body;
199
+ }
200
+
201
+ /**
202
+ * Build response object
203
+ */
204
+ function buildResponse(response: import('../types').ResponseInfo): ResponseObject {
205
+ const responseObj: ResponseObject = {
206
+ description: response.description,
207
+ };
208
+
209
+ // Add headers
210
+ if (response.headers) {
211
+ responseObj.headers = {};
212
+ for (const [name, header] of Object.entries(response.headers)) {
213
+ responseObj.headers[name] = {
214
+ schema: header.schema,
215
+ description: header.description,
216
+ };
217
+ }
218
+ }
219
+
220
+ // Add content
221
+ if (response.contentType && response.schema) {
222
+ responseObj.content = {
223
+ [response.contentType]: {
224
+ schema: response.schema,
225
+ },
226
+ };
227
+ }
228
+
229
+ return responseObj;
230
+ }
231
+
232
+ /**
233
+ * Recursively extract all $ref types from a schema
234
+ */
235
+ function extractRefsFromSchema(schema: any, refs: Set<string>): void {
236
+ if (!schema || typeof schema !== 'object') {
237
+ return;
238
+ }
239
+
240
+ // Direct $ref
241
+ if (schema.$ref && typeof schema.$ref === 'string') {
242
+ const typeName = schema.$ref.replace('#/components/schemas/', '');
243
+ refs.add(typeName);
244
+ }
245
+
246
+ // Array items
247
+ if (schema.items) {
248
+ extractRefsFromSchema(schema.items, refs);
249
+ }
250
+
251
+ // Object properties
252
+ if (schema.properties) {
253
+ for (const prop of Object.values(schema.properties)) {
254
+ extractRefsFromSchema(prop, refs);
255
+ }
256
+ }
257
+
258
+ // additionalProperties
259
+ if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
260
+ extractRefsFromSchema(schema.additionalProperties, refs);
261
+ }
262
+
263
+ // allOf, anyOf, oneOf
264
+ for (const key of ['allOf', 'anyOf', 'oneOf']) {
265
+ if (Array.isArray(schema[key])) {
266
+ for (const subSchema of schema[key]) {
267
+ extractRefsFromSchema(subSchema, refs);
268
+ }
269
+ }
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Generate OpenAPI spec for a single controller
275
+ */
276
+ export function generateOpenAPISpecForController(
277
+ controller: ControllerInfo,
278
+ context: ProcessingContext,
279
+ options: CLIOptions
280
+ ): OpenAPISpec {
281
+ // Create a temporary context with only this controller to generate relevant schemas
282
+ const singleControllerContext: ProcessingContext = {
283
+ javaClasses: context.javaClasses,
284
+ controllers: [controller],
285
+ dtoSchemas: new Map(),
286
+ referencedTypes: new Set(),
287
+ };
288
+
289
+ // Collect referenced types from this controller's endpoints
290
+ for (const endpoint of controller.endpoints) {
291
+ // Extract refs from request body schema (including nested refs like array items)
292
+ if (endpoint.requestBody?.schema) {
293
+ extractRefsFromSchema(endpoint.requestBody.schema, singleControllerContext.referencedTypes);
294
+ }
295
+
296
+ // Extract refs from response schemas
297
+ for (const response of endpoint.responses) {
298
+ if (response.schema) {
299
+ extractRefsFromSchema(response.schema, singleControllerContext.referencedTypes);
300
+ }
301
+ }
302
+
303
+ // Extract refs from parameter schemas
304
+ for (const param of endpoint.parameters) {
305
+ if (param.schema) {
306
+ extractRefsFromSchema(param.schema, singleControllerContext.referencedTypes);
307
+ }
308
+ }
309
+ }
310
+
311
+ // Generate schemas for referenced types
312
+ const schemas = generateSchemas(singleControllerContext);
313
+
314
+ // Build paths for this controller only
315
+ const paths: Record<string, PathItem> = {};
316
+ for (const endpoint of controller.endpoints) {
317
+ const path = endpoint.path;
318
+ if (!paths[path]) {
319
+ paths[path] = {};
320
+ }
321
+ paths[path][endpoint.method] = buildOperation(endpoint, controller.className);
322
+ }
323
+
324
+ // Build tag for this controller
325
+ const tags: TagInfo[] = [{
326
+ name: controller.className,
327
+ description: controller.javaClass.javadoc || `Endpoints for ${controller.className}`,
328
+ }];
329
+
330
+ // Convert schema Map to object
331
+ const schemaObject: Record<string, SchemaInfo> = {};
332
+ for (const [name, schema] of schemas) {
333
+ schemaObject[name] = schema;
334
+ }
335
+
336
+ // Build the spec with controller name as title
337
+ const spec: OpenAPISpec = {
338
+ openapi: '3.0.0',
339
+ info: {
340
+ title: controller.className,
341
+ version: options.version,
342
+ },
343
+ tags,
344
+ paths,
345
+ components: {
346
+ schemas: schemaObject,
347
+ securitySchemes: {
348
+ bearerAuth: {
349
+ type: 'http',
350
+ scheme: 'bearer',
351
+ bearerFormat: 'JWT',
352
+ },
353
+ },
354
+ },
355
+ };
356
+
357
+ // Add servers if base path is specified
358
+ if (options.basePath) {
359
+ spec.servers = [{ url: `http://localhost:8080${options.basePath}` }];
360
+ }
361
+
362
+ return spec;
363
+ }
364
+
365
+ /**
366
+ * Write OpenAPI spec to YAML file
367
+ */
368
+ export function writeOpenAPISpec(spec: OpenAPISpec, outputPath: string): void {
369
+ const yamlContent = yaml.dump(spec, {
370
+ indent: 2,
371
+ lineWidth: -1,
372
+ noRefs: true,
373
+ sortKeys: false,
374
+ });
375
+
376
+ fs.writeFileSync(outputPath, yamlContent, 'utf-8');
377
+ console.log(` Written: ${outputPath}`);
378
+ }
package/src/index.ts ADDED
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import * as fs from 'fs';
5
+ import * as path from 'path';
6
+ import { CLIOptions, ProcessingContext } from './types';
7
+ import { parseJavaSource } from './parser/javaParser';
8
+ import { analyzeControllers } from './analyzer/controllerAnalyzer';
9
+ import { generateOpenAPISpec, generateOpenAPISpecForController, writeOpenAPISpec } from './generator/openapiGenerator';
10
+
11
+ const program = new Command();
12
+
13
+ // Helper to collect multiple values for an option
14
+ function collect(value: string, previous: string[]): string[] {
15
+ return previous.concat([value]);
16
+ }
17
+
18
+ program
19
+ .name('spring-to-openapi')
20
+ .description('Generate OpenAPI v3 YAML from Spring Boot Java source code')
21
+ .version('1.0.0')
22
+ .requiredOption('-s, --source <path...>', 'Java source code directory path(s) (can be specified multiple times)', collect, [])
23
+ .option('-a, --api-source <path>', 'Directory to search for RestControllers (defaults to first --source)')
24
+ .option('-o, --output <path>', 'Output YAML file path (generates single combined file)')
25
+ .option('-d, --out-dir <path>', 'Output directory path (generates separate YAML per controller)')
26
+ .option('-t, --title <title>', 'API title', 'API Documentation')
27
+ .option('--api-version <version>', 'API version', '1.0.0')
28
+ .option('-b, --base-path <path>', 'API base path')
29
+ .action(async (options: any) => {
30
+ try {
31
+ await run(options);
32
+ } catch (error) {
33
+ console.error('Error:', error instanceof Error ? error.message : error);
34
+ process.exit(1);
35
+ }
36
+ });
37
+
38
+ async function run(options: any): Promise<void> {
39
+ const startTime = Date.now();
40
+
41
+ // Validate output options
42
+ if (!options.output && !options.outDir) {
43
+ options.output = 'openapi.yaml'; // default to single file
44
+ }
45
+
46
+ if (options.output && options.outDir) {
47
+ console.error('Error: Cannot use both --output and --out-dir. Choose one output mode.');
48
+ process.exit(1);
49
+ }
50
+
51
+ // Normalize source to array
52
+ const sources: string[] = Array.isArray(options.source) ? options.source : [options.source];
53
+
54
+ if (sources.length === 0) {
55
+ console.error('Error: At least one --source path is required.');
56
+ process.exit(1);
57
+ }
58
+
59
+ // Normalize options
60
+ const cliOptions: CLIOptions = {
61
+ source: sources,
62
+ apiSource: options.apiSource, // Optional: specific directory for RestController search
63
+ output: options.output,
64
+ outDir: options.outDir,
65
+ title: options.title,
66
+ version: options.apiVersion,
67
+ basePath: options.basePath,
68
+ };
69
+
70
+ const isDirectoryMode = !!cliOptions.outDir;
71
+
72
+ console.log('='.repeat(60));
73
+ console.log('Spring Boot to OpenAPI Generator');
74
+ console.log('='.repeat(60));
75
+ if (cliOptions.source.length === 1) {
76
+ console.log(`\nSource directory: ${cliOptions.source[0]}`);
77
+ } else {
78
+ console.log(`\nSource directories:`);
79
+ for (const src of cliOptions.source) {
80
+ console.log(` - ${src}`);
81
+ }
82
+ }
83
+ if (cliOptions.apiSource) {
84
+ console.log(`API source directory: ${cliOptions.apiSource} (RestController search)`);
85
+ }
86
+ if (isDirectoryMode) {
87
+ console.log(`Output directory: ${cliOptions.outDir} (per-controller mode)`);
88
+ } else {
89
+ console.log(`Output file: ${cliOptions.output}`);
90
+ }
91
+ console.log(`API title: ${cliOptions.title}`);
92
+ console.log(`API version: ${cliOptions.version}`);
93
+ if (cliOptions.basePath) {
94
+ console.log(`Base path: ${cliOptions.basePath}`);
95
+ }
96
+ console.log();
97
+
98
+ // Resolve paths
99
+ const sourcePaths = cliOptions.source.map(s => path.resolve(s));
100
+ const apiSourcePath = cliOptions.apiSource ? path.resolve(cliOptions.apiSource) : undefined;
101
+
102
+ // Step 1: Parse Java files from all source directories
103
+ console.log('Step 1: Parsing Java source files...');
104
+ const javaClasses = new Map<string, any>();
105
+
106
+ for (const sourcePath of sourcePaths) {
107
+ const classes = await parseJavaSource(sourcePath);
108
+ console.log(` Found ${classes.size} class(es) in ${sourcePath}`);
109
+ // Merge classes (later sources override earlier ones for same class name)
110
+ for (const [name, cls] of classes) {
111
+ javaClasses.set(name, cls);
112
+ }
113
+ }
114
+ console.log(` Total: ${javaClasses.size} Java class(es)`);
115
+
116
+ if (javaClasses.size === 0) {
117
+ console.warn('Warning: No Java files found in the specified directory.');
118
+ return;
119
+ }
120
+
121
+ // Step 2: Initialize processing context
122
+ const context: ProcessingContext = {
123
+ javaClasses,
124
+ controllers: [],
125
+ dtoSchemas: new Map(),
126
+ referencedTypes: new Set(),
127
+ apiSourcePath, // Pass apiSourcePath to filter RestControllers
128
+ };
129
+
130
+ // Step 3: Analyze controllers
131
+ console.log('\nStep 2: Analyzing REST controllers...');
132
+ const controllers = analyzeControllers(context);
133
+ context.controllers = controllers;
134
+
135
+ const totalEndpoints = controllers.reduce((sum, c) => sum + c.endpoints.length, 0);
136
+ console.log(`\n Found ${controllers.length} controller(s) with ${totalEndpoints} endpoint(s)`);
137
+
138
+ if (controllers.length === 0) {
139
+ console.warn('Warning: No REST controllers found in the source files.');
140
+ return;
141
+ }
142
+
143
+ // Step 4: Generate and write OpenAPI spec(s)
144
+ console.log('\nStep 3: Generating OpenAPI specification...');
145
+
146
+ if (isDirectoryMode) {
147
+ // Directory mode: generate separate YAML file per controller
148
+ const outDirPath = path.resolve(cliOptions.outDir!);
149
+
150
+ // Create output directory if it doesn't exist
151
+ if (!fs.existsSync(outDirPath)) {
152
+ fs.mkdirSync(outDirPath, { recursive: true });
153
+ }
154
+
155
+ console.log('\nStep 4: Writing YAML files (per controller)...');
156
+ let totalSchemas = 0;
157
+
158
+ for (const controller of controllers) {
159
+ const spec = generateOpenAPISpecForController(controller, context, cliOptions);
160
+ const fileName = `${controller.className}.yaml`;
161
+ const outputPath = path.join(outDirPath, fileName);
162
+ writeOpenAPISpec(spec, outputPath);
163
+ totalSchemas += Object.keys(spec.components.schemas).length;
164
+ }
165
+
166
+ const endTime = Date.now();
167
+ console.log(`\nCompleted in ${endTime - startTime}ms`);
168
+ console.log('='.repeat(60));
169
+
170
+ // Print summary
171
+ console.log('\nSummary:');
172
+ console.log(` Controllers: ${controllers.length}`);
173
+ console.log(` Endpoints: ${totalEndpoints}`);
174
+ console.log(` Files generated: ${controllers.length}`);
175
+
176
+ // Print generated files
177
+ console.log('\nGenerated files:');
178
+ for (const controller of controllers) {
179
+ const endpointCount = controller.endpoints.length;
180
+ console.log(` ${controller.className}.yaml (${endpointCount} endpoint${endpointCount !== 1 ? 's' : ''})`);
181
+ }
182
+ } else {
183
+ // Single file mode: generate combined YAML
184
+ const spec = generateOpenAPISpec(controllers, context, cliOptions);
185
+ const outputPath = path.resolve(cliOptions.output!);
186
+
187
+ console.log('\nStep 4: Writing YAML output...');
188
+ writeOpenAPISpec(spec, outputPath);
189
+
190
+ const endTime = Date.now();
191
+ console.log(`\nCompleted in ${endTime - startTime}ms`);
192
+ console.log('='.repeat(60));
193
+
194
+ // Print summary
195
+ console.log('\nSummary:');
196
+ console.log(` Controllers: ${controllers.length}`);
197
+ console.log(` Endpoints: ${totalEndpoints}`);
198
+ console.log(` Schemas: ${Object.keys(spec.components.schemas).length}`);
199
+
200
+ // Print endpoints by path
201
+ console.log('\nEndpoints:');
202
+ const paths = Object.keys(spec.paths).sort();
203
+ for (const p of paths) {
204
+ const methods = Object.keys(spec.paths[p]).sort();
205
+ for (const method of methods) {
206
+ console.log(` ${method.toUpperCase().padEnd(7)} ${p}`);
207
+ }
208
+ }
209
+ }
210
+ }
211
+
212
+ program.parse();