@geekmidas/cli 0.0.25 → 0.1.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 (142) hide show
  1. package/FUNCTION_CRON_SUPPORT.md +266 -0
  2. package/README.md +21 -4
  3. package/dist/CronGenerator-ClbRcmz_.mjs +53 -0
  4. package/dist/CronGenerator-ClbRcmz_.mjs.map +1 -0
  5. package/dist/CronGenerator-Ctl4USy4.cjs +59 -0
  6. package/dist/CronGenerator-Ctl4USy4.cjs.map +1 -0
  7. package/dist/EndpointGenerator-Dj7AumHi.cjs +164 -0
  8. package/dist/EndpointGenerator-Dj7AumHi.cjs.map +1 -0
  9. package/dist/EndpointGenerator-uBA1ixUw.mjs +158 -0
  10. package/dist/EndpointGenerator-uBA1ixUw.mjs.map +1 -0
  11. package/dist/FunctionGenerator-DN681IUn.cjs +58 -0
  12. package/dist/FunctionGenerator-DN681IUn.cjs.map +1 -0
  13. package/dist/FunctionGenerator-crAa-JC7.mjs +52 -0
  14. package/dist/FunctionGenerator-crAa-JC7.mjs.map +1 -0
  15. package/dist/Generator-C3tYSTQY.cjs +47 -0
  16. package/dist/Generator-C3tYSTQY.cjs.map +1 -0
  17. package/dist/Generator-CDt4pB3W.mjs +41 -0
  18. package/dist/Generator-CDt4pB3W.mjs.map +1 -0
  19. package/dist/__tests__/config.spec.cjs +98 -0
  20. package/dist/__tests__/config.spec.cjs.map +1 -0
  21. package/dist/__tests__/config.spec.mjs +97 -0
  22. package/dist/__tests__/config.spec.mjs.map +1 -0
  23. package/dist/__tests__/test-helpers.cjs +14 -0
  24. package/dist/__tests__/test-helpers.mjs +4 -0
  25. package/dist/build/__tests__/index-new.spec.cjs +286 -0
  26. package/dist/build/__tests__/index-new.spec.cjs.map +1 -0
  27. package/dist/build/__tests__/index-new.spec.mjs +285 -0
  28. package/dist/build/__tests__/index-new.spec.mjs.map +1 -0
  29. package/dist/build/index.cjs +11 -0
  30. package/dist/build/index.mjs +11 -0
  31. package/dist/build/manifests.cjs +3 -0
  32. package/dist/build/manifests.mjs +3 -0
  33. package/dist/build/providerResolver.cjs +5 -0
  34. package/dist/build/providerResolver.mjs +3 -0
  35. package/dist/build/types.cjs +0 -0
  36. package/dist/build/types.mjs +0 -0
  37. package/dist/build-BZdwxCLW.mjs +64 -0
  38. package/dist/build-BZdwxCLW.mjs.map +1 -0
  39. package/dist/build-BfQFnU5-.cjs +70 -0
  40. package/dist/build-BfQFnU5-.cjs.map +1 -0
  41. package/dist/{chunk-CUT6urMc.cjs → chunk-CsX-DzYB.cjs} +12 -0
  42. package/dist/config-CXxYmz_o.mjs +30 -0
  43. package/dist/config-CXxYmz_o.mjs.map +1 -0
  44. package/dist/config-RcNESK0T.cjs +36 -0
  45. package/dist/config-RcNESK0T.cjs.map +1 -0
  46. package/dist/config.cjs +1 -1
  47. package/dist/config.mjs +1 -1
  48. package/dist/esm-9eeZntth.mjs +3777 -0
  49. package/dist/esm-9eeZntth.mjs.map +1 -0
  50. package/dist/esm-Crmo4h9t.cjs +4392 -0
  51. package/dist/esm-Crmo4h9t.cjs.map +1 -0
  52. package/dist/esm-CsJbr7gi.mjs +3 -0
  53. package/dist/esm-w09tAC4l.cjs +8 -0
  54. package/dist/generators/CronGenerator.cjs +4 -0
  55. package/dist/generators/CronGenerator.mjs +4 -0
  56. package/dist/generators/EndpointGenerator.cjs +4 -0
  57. package/dist/generators/EndpointGenerator.mjs +4 -0
  58. package/dist/generators/FunctionGenerator.cjs +4 -0
  59. package/dist/generators/FunctionGenerator.mjs +4 -0
  60. package/dist/generators/Generator.cjs +3 -0
  61. package/dist/generators/Generator.mjs +3 -0
  62. package/dist/generators/__tests__/CronGenerator.spec.cjs +216 -0
  63. package/dist/generators/__tests__/CronGenerator.spec.cjs.map +1 -0
  64. package/dist/generators/__tests__/CronGenerator.spec.mjs +215 -0
  65. package/dist/generators/__tests__/CronGenerator.spec.mjs.map +1 -0
  66. package/dist/generators/__tests__/EndpointGenerator.spec.cjs +182 -0
  67. package/dist/generators/__tests__/EndpointGenerator.spec.cjs.map +1 -0
  68. package/dist/generators/__tests__/EndpointGenerator.spec.mjs +181 -0
  69. package/dist/generators/__tests__/EndpointGenerator.spec.mjs.map +1 -0
  70. package/dist/generators/__tests__/FunctionGenerator.spec.cjs +152 -0
  71. package/dist/generators/__tests__/FunctionGenerator.spec.cjs.map +1 -0
  72. package/dist/generators/__tests__/FunctionGenerator.spec.mjs +151 -0
  73. package/dist/generators/__tests__/FunctionGenerator.spec.mjs.map +1 -0
  74. package/dist/generators/index.cjs +10 -0
  75. package/dist/generators/index.mjs +7 -0
  76. package/dist/generators-CsLujGXs.mjs +0 -0
  77. package/dist/generators-_pY7sHy1.cjs +0 -0
  78. package/dist/index.cjs +68 -26
  79. package/dist/index.cjs.map +1 -0
  80. package/dist/index.mjs +67 -25
  81. package/dist/index.mjs.map +1 -0
  82. package/dist/manifests-BTtfDMX8.cjs +26 -0
  83. package/dist/manifests-BTtfDMX8.cjs.map +1 -0
  84. package/dist/manifests-HX4z4kkz.mjs +20 -0
  85. package/dist/manifests-HX4z4kkz.mjs.map +1 -0
  86. package/dist/{openapi-CksVdkh2.mjs → openapi-BivnatiC.mjs} +8 -6
  87. package/dist/openapi-BivnatiC.mjs.map +1 -0
  88. package/dist/{openapi-D4QQJUPY.cjs → openapi-DW-qF3oW.cjs} +9 -7
  89. package/dist/openapi-DW-qF3oW.cjs.map +1 -0
  90. package/dist/{openapi-react-query-C1JLYUOs.cjs → openapi-react-query-J0BzBHhN.cjs} +4 -3
  91. package/dist/openapi-react-query-J0BzBHhN.cjs.map +1 -0
  92. package/dist/{openapi-react-query-DpT3XHFC.mjs → openapi-react-query-lgS7AVEz.mjs} +3 -2
  93. package/dist/openapi-react-query-lgS7AVEz.mjs.map +1 -0
  94. package/dist/openapi-react-query.cjs +1 -1
  95. package/dist/openapi-react-query.mjs +1 -1
  96. package/dist/openapi.cjs +4 -3
  97. package/dist/openapi.mjs +4 -3
  98. package/dist/providerResolver-B_TjNF0_.mjs +96 -0
  99. package/dist/providerResolver-B_TjNF0_.mjs.map +1 -0
  100. package/dist/providerResolver-Cs-0YCaP.cjs +114 -0
  101. package/dist/providerResolver-Cs-0YCaP.cjs.map +1 -0
  102. package/dist/test-helpers-ARd8GDgx.cjs +199 -0
  103. package/dist/test-helpers-ARd8GDgx.cjs.map +1 -0
  104. package/dist/test-helpers-DdVBk23F.mjs +133 -0
  105. package/dist/test-helpers-DdVBk23F.mjs.map +1 -0
  106. package/examples/cron-example.ts +45 -0
  107. package/examples/function-example.ts +40 -0
  108. package/examples/gkm.config.json +22 -0
  109. package/examples/gkm.minimal.config.json +7 -0
  110. package/examples/gkm.production.config.json +27 -0
  111. package/package.json +35 -14
  112. package/src/__tests__/config.spec.ts +110 -0
  113. package/src/__tests__/test-helpers.ts +178 -0
  114. package/src/build/__tests__/index-new.spec.ts +578 -0
  115. package/src/build/index.ts +136 -0
  116. package/src/build/manifests.ts +32 -0
  117. package/src/build/providerResolver.ts +184 -0
  118. package/src/build/types.ts +37 -0
  119. package/src/config.ts +14 -6
  120. package/src/generators/CronGenerator.ts +97 -0
  121. package/src/generators/EndpointGenerator.ts +290 -0
  122. package/src/generators/FunctionGenerator.ts +96 -0
  123. package/src/generators/Generator.ts +95 -0
  124. package/src/generators/__tests__/CronGenerator.spec.ts +445 -0
  125. package/src/generators/__tests__/EndpointGenerator.spec.ts +372 -0
  126. package/src/generators/__tests__/FunctionGenerator.spec.ts +257 -0
  127. package/src/generators/index.ts +8 -0
  128. package/src/index.ts +57 -22
  129. package/src/openapi.ts +4 -3
  130. package/src/types.ts +73 -2
  131. package/dist/build-BTggTCYL.cjs +0 -176
  132. package/dist/build-Ca4P6_lY.mjs +0 -170
  133. package/dist/build.cjs +0 -5
  134. package/dist/build.mjs +0 -5
  135. package/dist/config-BNqUMsvc.cjs +0 -24
  136. package/dist/config-BciAdY6_.mjs +0 -18
  137. package/dist/loadEndpoints-BBIavB9h.cjs +0 -37
  138. package/dist/loadEndpoints-DAZ53Og2.mjs +0 -31
  139. package/dist/loadEndpoints.cjs +0 -3
  140. package/dist/loadEndpoints.mjs +0 -3
  141. package/src/build.ts +0 -305
  142. package/src/loadEndpoints.ts +0 -48
@@ -0,0 +1,32 @@
1
+ import { writeFile } from 'node:fs/promises';
2
+ import { join, relative } from 'path';
3
+ import type {
4
+ BuildManifest,
5
+ CronInfo,
6
+ FunctionInfo,
7
+ RouteInfo,
8
+ } from '../types';
9
+
10
+ const logger = console;
11
+
12
+ export async function generateManifests(
13
+ outputDir: string,
14
+ routes: RouteInfo[],
15
+ functions: FunctionInfo[],
16
+ crons: CronInfo[],
17
+ ): Promise<void> {
18
+ // Generate unified manifest for all providers
19
+ const manifest: BuildManifest = {
20
+ routes,
21
+ functions,
22
+ crons,
23
+ };
24
+
25
+ const manifestPath = join(outputDir, 'manifest.json');
26
+ await writeFile(manifestPath, JSON.stringify(manifest, null, 2));
27
+
28
+ logger.log(
29
+ `Generated ${routes.length} routes, ${functions.length} functions, ${crons.length} crons in ${relative(process.cwd(), outputDir)}`,
30
+ );
31
+ logger.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
32
+ }
@@ -0,0 +1,184 @@
1
+ import type {
2
+ AWSApiGatewayConfig,
3
+ AWSLambdaConfig,
4
+ BuildOptions,
5
+ GkmConfig,
6
+ LegacyProvider,
7
+ MainProvider,
8
+ ProvidersConfig,
9
+ ServerConfig,
10
+ } from '../types';
11
+
12
+ export interface ResolvedProviders {
13
+ providers: LegacyProvider[];
14
+ enableOpenApi: boolean;
15
+ }
16
+
17
+ /**
18
+ * Resolves provider configuration from the new simplified system
19
+ * to the internal legacy format for backward compatibility
20
+ */
21
+ export function resolveProviders(
22
+ config: GkmConfig,
23
+ options: BuildOptions,
24
+ ): ResolvedProviders {
25
+ const providers: LegacyProvider[] = [];
26
+ let enableOpenApi = options.enableOpenApi || false;
27
+
28
+ // Handle legacy providers option (deprecated)
29
+ if (options.providers) {
30
+ return {
31
+ providers: options.providers,
32
+ enableOpenApi,
33
+ };
34
+ }
35
+
36
+ // Handle new provider option
37
+ if (options.provider) {
38
+ const resolvedProviders = resolveMainProvider(
39
+ options.provider,
40
+ config.providers,
41
+ );
42
+ providers.push(...resolvedProviders.providers);
43
+ enableOpenApi = resolvedProviders.enableOpenApi || enableOpenApi;
44
+ }
45
+ // Default: build all configured providers
46
+ else if (config.providers) {
47
+ const resolvedProviders = resolveAllConfiguredProviders(config.providers);
48
+ providers.push(...resolvedProviders.providers);
49
+ enableOpenApi = resolvedProviders.enableOpenApi || enableOpenApi;
50
+ }
51
+ // Fallback: use default AWS configuration
52
+ else {
53
+ providers.push('aws-apigatewayv2', 'aws-lambda');
54
+ }
55
+
56
+ return {
57
+ providers: [...new Set(providers)], // Remove duplicates
58
+ enableOpenApi,
59
+ };
60
+ }
61
+
62
+ function resolveMainProvider(
63
+ mainProvider: MainProvider,
64
+ providersConfig?: ProvidersConfig,
65
+ ): ResolvedProviders {
66
+ const providers: LegacyProvider[] = [];
67
+ let enableOpenApi = false;
68
+
69
+ if (mainProvider === 'aws') {
70
+ const awsConfig = providersConfig?.aws;
71
+
72
+ // Resolve API Gateway providers
73
+ if (awsConfig?.apiGateway) {
74
+ if (isEnabled(awsConfig.apiGateway.v1)) {
75
+ providers.push('aws-apigatewayv1');
76
+ }
77
+ if (isEnabled(awsConfig.apiGateway.v2)) {
78
+ providers.push('aws-apigatewayv2');
79
+ }
80
+ } else {
81
+ // Default: enable v2 if no specific config
82
+ providers.push('aws-apigatewayv2');
83
+ }
84
+
85
+ // Resolve Lambda providers
86
+ if (awsConfig?.lambda) {
87
+ if (
88
+ isEnabled(awsConfig.lambda.functions) ||
89
+ isEnabled(awsConfig.lambda.crons)
90
+ ) {
91
+ providers.push('aws-lambda');
92
+ }
93
+ } else {
94
+ // Default: enable lambda if no specific config
95
+ providers.push('aws-lambda');
96
+ }
97
+ } else if (mainProvider === 'server') {
98
+ providers.push('server');
99
+ const serverConfig = providersConfig?.server;
100
+
101
+ if (typeof serverConfig === 'object' && serverConfig?.enableOpenApi) {
102
+ enableOpenApi = true;
103
+ }
104
+ }
105
+
106
+ return { providers, enableOpenApi };
107
+ }
108
+
109
+ function resolveAllConfiguredProviders(
110
+ providersConfig: ProvidersConfig,
111
+ ): ResolvedProviders {
112
+ const providers: LegacyProvider[] = [];
113
+ let enableOpenApi = false;
114
+
115
+ // AWS providers
116
+ if (providersConfig.aws) {
117
+ const awsProviders = resolveMainProvider('aws', providersConfig);
118
+ providers.push(...awsProviders.providers);
119
+ }
120
+
121
+ // Server provider
122
+ if (providersConfig.server && isEnabled(providersConfig.server)) {
123
+ providers.push('server');
124
+ if (
125
+ typeof providersConfig.server === 'object' &&
126
+ providersConfig.server.enableOpenApi
127
+ ) {
128
+ enableOpenApi = true;
129
+ }
130
+ }
131
+
132
+ return { providers, enableOpenApi };
133
+ }
134
+
135
+ function isEnabled(
136
+ config:
137
+ | boolean
138
+ | AWSApiGatewayConfig
139
+ | AWSLambdaConfig
140
+ | ServerConfig
141
+ | undefined,
142
+ ): boolean {
143
+ if (config === undefined) return false;
144
+ if (typeof config === 'boolean') return config;
145
+ return config.enabled !== false; // Default to true if enabled is not explicitly false
146
+ }
147
+
148
+ /**
149
+ * Gets configuration for a specific AWS service
150
+ */
151
+ export function getAWSServiceConfig<
152
+ T extends AWSApiGatewayConfig | AWSLambdaConfig,
153
+ >(
154
+ config: GkmConfig,
155
+ service: 'apiGateway' | 'lambda',
156
+ subService?: 'v1' | 'v2' | 'functions' | 'crons',
157
+ ): T | undefined {
158
+ const awsConfig = config.providers?.aws;
159
+ if (!awsConfig) return undefined;
160
+
161
+ if (service === 'apiGateway' && awsConfig.apiGateway) {
162
+ const apiConfig = subService
163
+ ? awsConfig.apiGateway[subService as 'v1' | 'v2']
164
+ : undefined;
165
+ return typeof apiConfig === 'object' ? (apiConfig as T) : undefined;
166
+ }
167
+
168
+ if (service === 'lambda' && awsConfig.lambda) {
169
+ const lambdaConfig = subService
170
+ ? awsConfig.lambda[subService as 'functions' | 'crons']
171
+ : undefined;
172
+ return typeof lambdaConfig === 'object' ? (lambdaConfig as T) : undefined;
173
+ }
174
+
175
+ return undefined;
176
+ }
177
+
178
+ /**
179
+ * Gets server configuration
180
+ */
181
+ export function getServerConfig(config: GkmConfig): ServerConfig | undefined {
182
+ const serverConfig = config.providers?.server;
183
+ return typeof serverConfig === 'object' ? serverConfig : undefined;
184
+ }
@@ -0,0 +1,37 @@
1
+ import type { Cron, Function } from '@geekmidas/api/constructs';
2
+ import type { Endpoint } from '@geekmidas/api/server';
3
+
4
+ import type { CronInfo, FunctionInfo, RouteInfo } from '../types';
5
+
6
+ export interface ProcessedEndpoint {
7
+ file: string;
8
+ exportName: string;
9
+ endpoint: Endpoint<any, any, any, any, any, any>;
10
+ routeInfo: RouteInfo;
11
+ }
12
+
13
+ export interface ProcessedFunction {
14
+ file: string;
15
+ exportName: string;
16
+ fn: Function<any, any, any, any, any>;
17
+ }
18
+
19
+ export interface ProcessedCron {
20
+ file: string;
21
+ exportName: string;
22
+ cron: Cron<any, any, any, any>;
23
+ schedule?: string;
24
+ }
25
+
26
+ export interface BuildContext {
27
+ envParserPath: string;
28
+ envParserImportPattern: string;
29
+ loggerPath: string;
30
+ loggerImportPattern: string;
31
+ }
32
+
33
+ export interface ProviderBuildResult {
34
+ routes: RouteInfo[];
35
+ functions: FunctionInfo[];
36
+ crons: CronInfo[];
37
+ }
package/src/config.ts CHANGED
@@ -1,20 +1,28 @@
1
1
  import { existsSync } from 'fs';
2
2
  import { join } from 'path';
3
- import { readFile } from 'fs/promises';
4
3
  import type { GkmConfig } from './types.ts';
5
4
 
6
5
  export async function loadConfig(): Promise<GkmConfig> {
7
- const configPath = join(process.cwd(), 'gkm.config.json');
6
+ const files = ['gkm.config.json', 'gkm.config.ts', 'gkm.config.js'];
7
+ let configPath = '';
8
8
 
9
- if (!existsSync(configPath)) {
9
+ for (const file of files) {
10
+ const path = join(process.cwd(), file);
11
+ if (existsSync(path)) {
12
+ configPath = path;
13
+ break;
14
+ }
15
+ }
16
+
17
+ if (!configPath) {
10
18
  throw new Error(
11
- 'gkm.config.json not found. Please create a configuration file.',
19
+ 'Configuration file not found. Please create gkm.config.json, gkm.config.ts, or gkm.config.js in the project root.',
12
20
  );
13
21
  }
14
22
 
15
23
  try {
16
- const config = await readFile(configPath, 'utf-8');
17
- return JSON.parse(config);
24
+ const config = await import(configPath);
25
+ return config.default;
18
26
  } catch (error) {
19
27
  throw new Error(
20
28
  `Failed to load gkm.config.json: ${(error as Error).message}`,
@@ -0,0 +1,97 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { dirname, join, relative } from 'node:path';
3
+ import { Cron } from '@geekmidas/api/constructs';
4
+ import type { BuildContext } from '../build/types';
5
+ import type { CronInfo } from '../types';
6
+ import {
7
+ ConstructGenerator,
8
+ type GeneratedConstruct,
9
+ type GeneratorOptions,
10
+ } from './Generator';
11
+
12
+ export class CronGenerator extends ConstructGenerator<
13
+ Cron<any, any, any, any>,
14
+ CronInfo[]
15
+ > {
16
+ async build(
17
+ context: BuildContext,
18
+ constructs: GeneratedConstruct<Cron<any, any, any, any>>[],
19
+ outputDir: string,
20
+ options?: GeneratorOptions,
21
+ ): Promise<CronInfo[]> {
22
+ const provider = options?.provider || 'aws-lambda';
23
+ const logger = console;
24
+ const cronInfos: CronInfo[] = [];
25
+
26
+ if (constructs.length === 0 || provider !== 'aws-lambda') {
27
+ return cronInfos;
28
+ }
29
+
30
+ // Create crons subdirectory
31
+ const cronsDir = join(outputDir, 'crons');
32
+ await mkdir(cronsDir, { recursive: true });
33
+
34
+ // Generate cron handlers
35
+ for (const { key, construct, path } of constructs) {
36
+ const handlerFile = await this.generateCronHandler(
37
+ cronsDir,
38
+ path.relative,
39
+ key,
40
+ context,
41
+ );
42
+
43
+ cronInfos.push({
44
+ name: key,
45
+ handler: relative(process.cwd(), handlerFile).replace(
46
+ /\.ts$/,
47
+ '.handler',
48
+ ),
49
+ schedule: construct.schedule || 'rate(1 hour)',
50
+ timeout: construct.timeout,
51
+ });
52
+
53
+ logger.log(`Generated cron handler: ${key}`);
54
+ }
55
+
56
+ return cronInfos;
57
+ }
58
+
59
+ isConstruct(value: any): value is Cron<any, any, any, any> {
60
+ return Cron.isCron(value);
61
+ }
62
+
63
+ private async generateCronHandler(
64
+ outputDir: string,
65
+ sourceFile: string,
66
+ exportName: string,
67
+ context: BuildContext,
68
+ ): Promise<string> {
69
+ const handlerFileName = `${exportName}.ts`;
70
+ const handlerPath = join(outputDir, handlerFileName);
71
+
72
+ const relativePath = relative(dirname(handlerPath), sourceFile);
73
+ const importPath = relativePath.replace(/\.ts$/, '.js');
74
+
75
+ const relativeEnvParserPath = relative(
76
+ dirname(handlerPath),
77
+ context.envParserPath,
78
+ );
79
+ const relativeLoggerPath = relative(
80
+ dirname(handlerPath),
81
+ context.loggerPath,
82
+ );
83
+
84
+ const content = `import { AWSScheduledFunction } from '@geekmidas/api/aws-lambda';
85
+ import { ${exportName} } from '${importPath}';
86
+ import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
87
+ import ${context.loggerImportPattern} from '${relativeLoggerPath}';
88
+
89
+ const adapter = new AWSScheduledFunction(envParser, ${exportName});
90
+
91
+ export const handler = adapter.handler;
92
+ `;
93
+
94
+ await writeFile(handlerPath, content);
95
+ return handlerPath;
96
+ }
97
+ }
@@ -0,0 +1,290 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { dirname, join, relative } from 'node:path';
3
+ import { Endpoint } from '@geekmidas/api/server';
4
+ import type { BuildContext } from '../build/types';
5
+ import type { LegacyProvider, RouteInfo } from '../types';
6
+ import {
7
+ ConstructGenerator,
8
+ type GeneratedConstruct,
9
+ type GeneratorOptions,
10
+ } from './Generator';
11
+
12
+ export class EndpointGenerator extends ConstructGenerator<
13
+ Endpoint<any, any, any, any, any, any>,
14
+ RouteInfo[]
15
+ > {
16
+ isConstruct(value: any): value is Endpoint<any, any, any, any, any, any> {
17
+ return Endpoint.isEndpoint(value);
18
+ }
19
+
20
+ async build(
21
+ context: BuildContext,
22
+ constructs: GeneratedConstruct<Endpoint<any, any, any, any, any, any>>[],
23
+ outputDir: string,
24
+ options?: GeneratorOptions,
25
+ ): Promise<RouteInfo[]> {
26
+ const provider = options?.provider || 'aws-apigatewayv2';
27
+ const enableOpenApi = options?.enableOpenApi || false;
28
+ const logger = console;
29
+ const routes: RouteInfo[] = [];
30
+
31
+ if (constructs.length === 0) {
32
+ return routes;
33
+ }
34
+
35
+ if (provider === 'server') {
36
+ // Generate single server file with all endpoints
37
+ const serverFile = await this.generateServerFile(
38
+ outputDir,
39
+ constructs,
40
+ context,
41
+ enableOpenApi,
42
+ );
43
+
44
+ routes.push({
45
+ path: '*',
46
+ method: 'ALL',
47
+ handler: relative(process.cwd(), serverFile),
48
+ });
49
+
50
+ logger.log(
51
+ `Generated server app with ${constructs.length} endpoints${enableOpenApi ? ' (OpenAPI enabled)' : ''}`,
52
+ );
53
+ } else if (provider === 'aws-lambda') {
54
+ // For aws-lambda, create routes subdirectory
55
+ const routesDir = join(outputDir, 'routes');
56
+ await mkdir(routesDir, { recursive: true });
57
+
58
+ // Generate individual handlers for API Gateway routes
59
+ for (const { key, construct, path } of constructs) {
60
+ const handlerFile = await this.generateHandlerFile(
61
+ routesDir,
62
+ path.relative,
63
+ key,
64
+ 'aws-apigatewayv2',
65
+ construct,
66
+ context,
67
+ );
68
+
69
+ const routeInfo: RouteInfo = {
70
+ path: construct._path,
71
+ method: construct.method,
72
+ handler: relative(process.cwd(), handlerFile).replace(
73
+ /\.ts$/,
74
+ '.handler',
75
+ ),
76
+ };
77
+
78
+ routes.push(routeInfo);
79
+ logger.log(
80
+ `Generated handler for ${routeInfo.method} ${routeInfo.path}`,
81
+ );
82
+ }
83
+ } else {
84
+ // Generate individual handler files for AWS API Gateway providers
85
+ for (const { key, construct, path } of constructs) {
86
+ const handlerFile = await this.generateHandlerFile(
87
+ outputDir,
88
+ path.relative,
89
+ key,
90
+ provider,
91
+ construct,
92
+ context,
93
+ );
94
+
95
+ const routeInfo: RouteInfo = {
96
+ path: construct._path,
97
+ method: construct.method,
98
+ handler: relative(process.cwd(), handlerFile).replace(
99
+ /\.ts$/,
100
+ '.handler',
101
+ ),
102
+ };
103
+
104
+ routes.push(routeInfo);
105
+ logger.log(
106
+ `Generated handler for ${routeInfo.method} ${routeInfo.path}`,
107
+ );
108
+ }
109
+ }
110
+
111
+ return routes;
112
+ }
113
+
114
+ private async generateHandlerFile(
115
+ outputDir: string,
116
+ sourceFile: string,
117
+ exportName: string,
118
+ provider: LegacyProvider,
119
+ _endpoint: Endpoint<any, any, any, any, any, any>,
120
+ context: BuildContext,
121
+ ): Promise<string> {
122
+ const handlerFileName = `${exportName}.ts`;
123
+ const handlerPath = join(outputDir, handlerFileName);
124
+
125
+ const relativePath = relative(dirname(handlerPath), sourceFile);
126
+ const importPath = relativePath.replace(/\.ts$/, '.js');
127
+
128
+ const relativeEnvParserPath = relative(
129
+ dirname(handlerPath),
130
+ context.envParserPath,
131
+ );
132
+
133
+ let content: string;
134
+
135
+ switch (provider) {
136
+ case 'aws-apigatewayv1':
137
+ content = this.generateAWSApiGatewayV1Handler(
138
+ importPath,
139
+ exportName,
140
+ relativeEnvParserPath,
141
+ context.envParserImportPattern,
142
+ );
143
+ break;
144
+ case 'aws-apigatewayv2':
145
+ content = this.generateAWSApiGatewayV2Handler(
146
+ importPath,
147
+ exportName,
148
+ relativeEnvParserPath,
149
+ context.envParserImportPattern,
150
+ );
151
+ break;
152
+ case 'server':
153
+ content = this.generateServerHandler(importPath, exportName);
154
+ break;
155
+ default:
156
+ throw new Error(`Unsupported provider: ${provider}`);
157
+ }
158
+
159
+ await writeFile(handlerPath, content);
160
+ return handlerPath;
161
+ }
162
+
163
+ private async generateServerFile(
164
+ outputDir: string,
165
+ endpoints: GeneratedConstruct<Endpoint<any, any, any, any, any, any>>[],
166
+ context: BuildContext,
167
+ enableOpenApi: boolean,
168
+ ): Promise<string> {
169
+ const serverFileName = 'app.ts';
170
+ const serverPath = join(outputDir, serverFileName);
171
+
172
+ // Group imports by file
173
+ const importsByFile = new Map<string, string[]>();
174
+
175
+ for (const { path, key } of endpoints) {
176
+ const relativePath = relative(dirname(serverPath), path.relative);
177
+ const importPath = relativePath.replace(/\.ts$/, '.js');
178
+
179
+ if (!importsByFile.has(importPath)) {
180
+ importsByFile.set(importPath, []);
181
+ }
182
+ importsByFile.get(importPath)!.push(key);
183
+ }
184
+
185
+ const relativeEnvParserPath = relative(
186
+ dirname(serverPath),
187
+ context.envParserPath,
188
+ );
189
+ const relativeLoggerPath = relative(
190
+ dirname(serverPath),
191
+ context.loggerPath,
192
+ );
193
+
194
+ // Generate import statements
195
+ const imports = Array.from(importsByFile.entries())
196
+ .map(
197
+ ([importPath, exports]) =>
198
+ `import { ${exports.join(', ')} } from '${importPath}';`,
199
+ )
200
+ .join('\n');
201
+
202
+ const allExportNames = endpoints.map(({ key }) => key);
203
+
204
+ const content = `import { HonoEndpoint } from '@geekmidas/api/hono';
205
+ import { Endpoint } from '@geekmidas/api/server';
206
+ import { ServiceDiscovery } from '@geekmidas/api/services';
207
+ import { Hono } from 'hono';
208
+ import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
209
+ import ${context.loggerImportPattern} from '${relativeLoggerPath}';
210
+ ${imports}
211
+
212
+ export function createApp(app?: Hono, enableOpenApi: boolean = ${enableOpenApi}): Hono {
213
+ const honoApp = app || new Hono();
214
+
215
+ const endpoints: Endpoint<any, any, any, any, any, any, any>[] = [
216
+ ${allExportNames.join(',\n ')}
217
+ ];
218
+
219
+ const serviceDiscovery = ServiceDiscovery.getInstance(
220
+ logger,
221
+ envParser
222
+ );
223
+
224
+ // Configure OpenAPI options based on enableOpenApi flag
225
+ const openApiOptions: any = enableOpenApi ? {
226
+ docsPath: '/docs',
227
+ openApiOptions: {
228
+ title: 'API Documentation',
229
+ version: '1.0.0',
230
+ description: 'Generated API documentation'
231
+ }
232
+ } : { docsPath: false };
233
+
234
+ HonoEndpoint.addRoutes(endpoints, serviceDiscovery, honoApp, openApiOptions);
235
+
236
+ return honoApp;
237
+ }
238
+
239
+ // Default export for convenience
240
+ export default createApp;
241
+ `;
242
+
243
+ await writeFile(serverPath, content);
244
+
245
+ return serverPath;
246
+ }
247
+
248
+ private generateAWSApiGatewayV1Handler(
249
+ importPath: string,
250
+ exportName: string,
251
+ envParserPath: string,
252
+ envParserImportPattern: string,
253
+ ): string {
254
+ return `import { AmazonApiGatewayV1Endpoint } from '@geekmidas/api/aws-apigateway';
255
+ import { ${exportName} } from '${importPath}';
256
+ import ${envParserImportPattern} from '${envParserPath}';
257
+
258
+ const adapter = new AmazonApiGatewayV1Endpoint(envParser, ${exportName});
259
+
260
+ export const handler = adapter.handler;
261
+ `;
262
+ }
263
+
264
+ private generateAWSApiGatewayV2Handler(
265
+ importPath: string,
266
+ exportName: string,
267
+ envParserPath: string,
268
+ envParserImportPattern: string,
269
+ ): string {
270
+ return `import { AmazonApiGatewayV2Endpoint } from '@geekmidas/api/aws-apigateway';
271
+ import { ${exportName} } from '${importPath}';
272
+ import ${envParserImportPattern} from '${envParserPath}';
273
+
274
+ const adapter = new AmazonApiGatewayV2Endpoint(envParser, ${exportName});
275
+
276
+ export const handler = adapter.handler;
277
+ `;
278
+ }
279
+
280
+ private generateServerHandler(
281
+ importPath: string,
282
+ exportName: string,
283
+ ): string {
284
+ return `import { ${exportName} } from '${importPath}';
285
+
286
+ // Server handler - implement based on your server framework
287
+ export const handler = ${exportName};
288
+ `;
289
+ }
290
+ }