@geekmidas/cli 0.0.26 → 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,578 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { itWithDir } from '@geekmidas/testkit/os';
4
+ import { describe, expect, vi } from 'vitest';
5
+ import {
6
+ createMockCronFile,
7
+ createMockEndpointFile,
8
+ createMockFunctionFile,
9
+ createTestFile,
10
+ } from '../../__tests__/test-helpers';
11
+ import { buildCommand } from '../index';
12
+
13
+ describe('buildCommand', () => {
14
+ itWithDir(
15
+ 'should build endpoints, functions, and crons for multiple providers',
16
+ async ({ dir }) => {
17
+ // Create test files that will be discovered
18
+ await createMockEndpointFile(
19
+ dir,
20
+ 'src/endpoints/users.ts',
21
+ 'getUsersEndpoint',
22
+ '/users',
23
+ 'GET',
24
+ );
25
+ await createMockEndpointFile(
26
+ dir,
27
+ 'src/endpoints/posts.ts',
28
+ 'getPostsEndpoint',
29
+ '/posts',
30
+ 'GET',
31
+ );
32
+ await createMockFunctionFile(
33
+ dir,
34
+ 'src/functions/process.ts',
35
+ 'processDataFunction',
36
+ 60,
37
+ );
38
+ await createMockCronFile(
39
+ dir,
40
+ 'src/crons/cleanup.ts',
41
+ 'cleanupCron',
42
+ 'rate(1 day)',
43
+ );
44
+
45
+ // Create a basic config file
46
+ await createTestFile(
47
+ dir,
48
+ 'gkm.config.ts',
49
+ `
50
+ export default {
51
+ routes: './src/endpoints/**/*.ts',
52
+ functions: './src/functions/**/*.ts',
53
+ crons: './src/crons/**/*.ts',
54
+ envParser: './config/env',
55
+ logger: './config/logger',
56
+ };
57
+ `,
58
+ );
59
+
60
+ // Create env and logger files
61
+ await createTestFile(dir, 'config/env.ts', 'export default {}');
62
+ await createTestFile(dir, 'config/logger.ts', 'export default {}');
63
+
64
+ const originalCwd = process.cwd();
65
+ process.chdir(dir);
66
+
67
+ try {
68
+ await buildCommand({ provider: 'server' });
69
+
70
+ // Check that output directories were created
71
+ const serverDir = join(dir, '.gkm', 'server');
72
+ expect(await readFile(join(serverDir, 'app.ts'), 'utf-8')).toContain(
73
+ 'HonoEndpoint',
74
+ );
75
+ } finally {
76
+ process.chdir(originalCwd);
77
+ }
78
+ },
79
+ );
80
+
81
+ itWithDir(
82
+ 'should perform complete build with all construct types for AWS Lambda',
83
+ async ({ dir }) => {
84
+ // Create comprehensive test setup with all construct types
85
+ await createMockEndpointFile(
86
+ dir,
87
+ 'src/endpoints/users.ts',
88
+ 'getUsersEndpoint',
89
+ '/users',
90
+ 'GET',
91
+ );
92
+ await createMockEndpointFile(
93
+ dir,
94
+ 'src/endpoints/posts.ts',
95
+ 'getPostsEndpoint',
96
+ '/posts',
97
+ 'POST',
98
+ );
99
+ await createMockFunctionFile(
100
+ dir,
101
+ 'src/functions/processData.ts',
102
+ 'processDataFunction',
103
+ 300,
104
+ );
105
+ await createMockFunctionFile(
106
+ dir,
107
+ 'src/functions/sendEmail.ts',
108
+ 'sendEmailFunction',
109
+ 30,
110
+ );
111
+ await createMockCronFile(
112
+ dir,
113
+ 'src/crons/dailyCleanup.ts',
114
+ 'dailyCleanupCron',
115
+ 'rate(1 day)',
116
+ );
117
+ await createMockCronFile(
118
+ dir,
119
+ 'src/crons/hourlyReport.ts',
120
+ 'hourlyReportCron',
121
+ 'cron(0 * * * ? *)',
122
+ );
123
+
124
+ // Create config
125
+ await createTestFile(
126
+ dir,
127
+ 'gkm.config.ts',
128
+ `
129
+ export default {
130
+ routes: './src/endpoints/**/*.ts',
131
+ functions: './src/functions/**/*.ts',
132
+ crons: './src/crons/**/*.ts',
133
+ envParser: './config/env',
134
+ logger: './config/logger',
135
+ };
136
+ `,
137
+ );
138
+
139
+ // Create env and logger files
140
+ await createTestFile(dir, 'config/env.ts', 'export default {}');
141
+ await createTestFile(dir, 'config/logger.ts', 'export default {}');
142
+
143
+ const originalCwd = process.cwd();
144
+ process.chdir(dir);
145
+
146
+ try {
147
+ // Build for AWS Lambda
148
+ await buildCommand({ provider: 'aws' });
149
+
150
+ const awsLambdaDir = join(dir, '.gkm', 'aws-lambda');
151
+ const awsApiGatewayV2Dir = join(dir, '.gkm', 'aws-apigatewayv2');
152
+
153
+ // Verify Lambda handlers were created
154
+ expect(
155
+ await readFile(
156
+ join(awsLambdaDir, 'functions', 'processDataFunction.ts'),
157
+ 'utf-8',
158
+ ),
159
+ ).toContain('AWSLambdaFunction');
160
+ expect(
161
+ await readFile(
162
+ join(awsLambdaDir, 'functions', 'sendEmailFunction.ts'),
163
+ 'utf-8',
164
+ ),
165
+ ).toContain('AWSLambdaFunction');
166
+
167
+ // Verify Cron handlers were created
168
+ expect(
169
+ await readFile(
170
+ join(awsLambdaDir, 'crons', 'dailyCleanupCron.ts'),
171
+ 'utf-8',
172
+ ),
173
+ ).toContain('AWSScheduledFunction');
174
+ expect(
175
+ await readFile(
176
+ join(awsLambdaDir, 'crons', 'hourlyReportCron.ts'),
177
+ 'utf-8',
178
+ ),
179
+ ).toContain('AWSScheduledFunction');
180
+
181
+ // Verify API Gateway handlers were created
182
+ expect(
183
+ await readFile(
184
+ join(awsApiGatewayV2Dir, 'getUsersEndpoint.ts'),
185
+ 'utf-8',
186
+ ),
187
+ ).toContain('AmazonApiGatewayV2Endpoint');
188
+ expect(
189
+ await readFile(
190
+ join(awsApiGatewayV2Dir, 'getPostsEndpoint.ts'),
191
+ 'utf-8',
192
+ ),
193
+ ).toContain('AmazonApiGatewayV2Endpoint');
194
+
195
+ // Verify unified manifests were created with all construct types
196
+ const lambdaManifestPath = join(awsLambdaDir, 'manifest.json');
197
+ const apiGatewayManifestPath = join(awsApiGatewayV2Dir, 'manifest.json');
198
+
199
+ const lambdaManifest = JSON.parse(
200
+ await readFile(lambdaManifestPath, 'utf-8'),
201
+ );
202
+ const apiGatewayManifest = JSON.parse(
203
+ await readFile(apiGatewayManifestPath, 'utf-8'),
204
+ );
205
+
206
+ // Verify Lambda manifest structure
207
+ expect(lambdaManifest).toMatchObject({
208
+ routes: expect.arrayContaining([
209
+ expect.objectContaining({
210
+ path: '/users',
211
+ method: 'GET',
212
+ handler: expect.stringContaining('routes/getUsersEndpoint.handler'),
213
+ }),
214
+ expect.objectContaining({
215
+ path: '/posts',
216
+ method: 'POST',
217
+ handler: expect.stringContaining('routes/getPostsEndpoint.handler'),
218
+ }),
219
+ ]),
220
+ functions: expect.arrayContaining([
221
+ expect.objectContaining({
222
+ name: 'processDataFunction',
223
+ handler: expect.stringContaining(
224
+ 'functions/processDataFunction.handler',
225
+ ),
226
+ timeout: 300,
227
+ }),
228
+ expect.objectContaining({
229
+ name: 'sendEmailFunction',
230
+ handler: expect.stringContaining(
231
+ 'functions/sendEmailFunction.handler',
232
+ ),
233
+ timeout: 30,
234
+ }),
235
+ ]),
236
+ crons: expect.arrayContaining([
237
+ expect.objectContaining({
238
+ name: 'dailyCleanupCron',
239
+ handler: expect.stringContaining(
240
+ 'crons/dailyCleanupCron.handler',
241
+ ),
242
+ schedule: 'rate(1 day)',
243
+ }),
244
+ expect.objectContaining({
245
+ name: 'hourlyReportCron',
246
+ handler: expect.stringContaining(
247
+ 'crons/hourlyReportCron.handler',
248
+ ),
249
+ schedule: 'cron(0 * * * ? *)',
250
+ }),
251
+ ]),
252
+ });
253
+
254
+ // Verify API Gateway manifest structure
255
+ expect(apiGatewayManifest).toMatchObject({
256
+ routes: expect.arrayContaining([
257
+ expect.objectContaining({
258
+ path: '/users',
259
+ method: 'GET',
260
+ handler: expect.stringContaining('getUsersEndpoint.handler'),
261
+ }),
262
+ expect.objectContaining({
263
+ path: '/posts',
264
+ method: 'POST',
265
+ handler: expect.stringContaining('getPostsEndpoint.handler'),
266
+ }),
267
+ ]),
268
+ functions: [],
269
+ crons: [],
270
+ });
271
+
272
+ // Verify counts
273
+ expect(lambdaManifest.routes).toHaveLength(2);
274
+ expect(lambdaManifest.functions).toHaveLength(2);
275
+ expect(lambdaManifest.crons).toHaveLength(2);
276
+ expect(apiGatewayManifest.routes).toHaveLength(2);
277
+ expect(apiGatewayManifest.functions).toHaveLength(0);
278
+ expect(apiGatewayManifest.crons).toHaveLength(0);
279
+ } finally {
280
+ process.chdir(originalCwd);
281
+ }
282
+ },
283
+ );
284
+
285
+ itWithDir('should handle case with no constructs found', async ({ dir }) => {
286
+ // Create a basic config file with no actual construct files
287
+ await createTestFile(
288
+ dir,
289
+ 'gkm.config.ts',
290
+ `
291
+ export default {
292
+ routes: './src/endpoints/**/*.ts',
293
+ functions: './src/functions/**/*.ts',
294
+ crons: './src/crons/**/*.ts',
295
+ envParser: './config/env',
296
+ logger: './config/logger',
297
+ };
298
+ `,
299
+ );
300
+
301
+ // Create env and logger files
302
+ await createTestFile(dir, 'config/env.ts', 'export default {}');
303
+ await createTestFile(dir, 'config/logger.ts', 'export default {}');
304
+
305
+ const originalCwd = process.cwd();
306
+ process.chdir(dir);
307
+
308
+ const logSpy = vi.spyOn(console, 'log');
309
+
310
+ try {
311
+ await buildCommand({ provider: 'server' });
312
+
313
+ expect(logSpy).toHaveBeenCalledWith('Found 0 endpoints');
314
+ expect(logSpy).toHaveBeenCalledWith('Found 0 functions');
315
+ expect(logSpy).toHaveBeenCalledWith('Found 0 crons');
316
+ expect(logSpy).toHaveBeenCalledWith(
317
+ 'No endpoints, functions, or crons found to process',
318
+ );
319
+ } finally {
320
+ process.chdir(originalCwd);
321
+ logSpy.mockRestore();
322
+ }
323
+ });
324
+
325
+ itWithDir(
326
+ 'should handle optional functions and crons config',
327
+ async ({ dir }) => {
328
+ // Create config with undefined functions and crons
329
+ await createTestFile(
330
+ dir,
331
+ 'gkm.config.ts',
332
+ `
333
+ export default {
334
+ routes: './src/endpoints/**/*.ts',
335
+ functions: undefined,
336
+ crons: undefined,
337
+ envParser: './config/env',
338
+ logger: './config/logger',
339
+ };
340
+ `,
341
+ );
342
+
343
+ await createMockEndpointFile(
344
+ dir,
345
+ 'src/endpoints/test.ts',
346
+ 'testEndpoint',
347
+ '/test',
348
+ 'GET',
349
+ );
350
+
351
+ // Create env and logger files
352
+ await createTestFile(dir, 'config/env.ts', 'export default {}');
353
+ await createTestFile(dir, 'config/logger.ts', 'export default {}');
354
+
355
+ const originalCwd = process.cwd();
356
+ process.chdir(dir);
357
+
358
+ const logSpy = vi.spyOn(console, 'log');
359
+
360
+ try {
361
+ await buildCommand({ provider: 'server' });
362
+
363
+ expect(logSpy).toHaveBeenCalledWith('Found 1 endpoints');
364
+ expect(logSpy).toHaveBeenCalledWith('Found 0 functions');
365
+ expect(logSpy).toHaveBeenCalledWith('Found 0 crons');
366
+
367
+ // Should not log functions or crons loading messages
368
+ expect(logSpy).not.toHaveBeenCalledWith(
369
+ expect.stringContaining('Loading functions'),
370
+ );
371
+ expect(logSpy).not.toHaveBeenCalledWith(
372
+ expect.stringContaining('Loading crons'),
373
+ );
374
+ } finally {
375
+ process.chdir(originalCwd);
376
+ logSpy.mockRestore();
377
+ }
378
+ },
379
+ );
380
+
381
+ itWithDir(
382
+ 'should parse envParser configuration correctly',
383
+ async ({ dir }) => {
384
+ // Create config with custom named exports
385
+ await createTestFile(
386
+ dir,
387
+ 'gkm.config.ts',
388
+ `
389
+ export default {
390
+ routes: './src/endpoints/**/*.ts',
391
+ functions: undefined,
392
+ crons: undefined,
393
+ envParser: './config/env#customEnvParser',
394
+ logger: './config/logger#customLogger',
395
+ };
396
+ `,
397
+ );
398
+
399
+ await createMockEndpointFile(
400
+ dir,
401
+ 'src/endpoints/test.ts',
402
+ 'testEndpoint',
403
+ '/test',
404
+ 'GET',
405
+ );
406
+
407
+ // Create env and logger files with named exports
408
+ await createTestFile(
409
+ dir,
410
+ 'config/env.ts',
411
+ 'export const customEnvParser = {}',
412
+ );
413
+ await createTestFile(
414
+ dir,
415
+ 'config/logger.ts',
416
+ 'export const customLogger = {}',
417
+ );
418
+
419
+ const originalCwd = process.cwd();
420
+ process.chdir(dir);
421
+
422
+ try {
423
+ await buildCommand({ provider: 'aws' });
424
+
425
+ // Verify that a handler file was generated with correct imports
426
+ const handlerFile = join(dir, '.gkm/aws-apigatewayv2/testEndpoint.ts');
427
+ const handlerContent = await readFile(handlerFile, 'utf-8');
428
+ expect(handlerContent).toContain('{ customEnvParser as envParser }');
429
+ } finally {
430
+ process.chdir(originalCwd);
431
+ }
432
+ },
433
+ );
434
+
435
+ itWithDir(
436
+ 'should create output directories for each provider',
437
+ async ({ dir }) => {
438
+ // Create config with multiple providers
439
+ await createTestFile(
440
+ dir,
441
+ 'gkm.config.ts',
442
+ `
443
+ export default {
444
+ routes: './src/endpoints/**/*.ts',
445
+ functions: undefined,
446
+ crons: undefined,
447
+ envParser: './config/env',
448
+ logger: './config/logger',
449
+ };
450
+ `,
451
+ );
452
+
453
+ await createMockEndpointFile(
454
+ dir,
455
+ 'src/endpoints/test.ts',
456
+ 'testEndpoint',
457
+ '/test',
458
+ 'GET',
459
+ );
460
+
461
+ // Create env and logger files
462
+ await createTestFile(dir, 'config/env.ts', 'export default {}');
463
+ await createTestFile(dir, 'config/logger.ts', 'export default {}');
464
+
465
+ const originalCwd = process.cwd();
466
+ process.chdir(dir);
467
+
468
+ try {
469
+ await buildCommand({ provider: 'aws' });
470
+
471
+ const v2HandlerFile = join(
472
+ dir,
473
+ '.gkm/aws-apigatewayv2/testEndpoint.ts',
474
+ );
475
+
476
+ const v2Content = await readFile(v2HandlerFile, 'utf-8');
477
+
478
+ expect(v2Content).toContain('AmazonApiGatewayV2Endpoint');
479
+ } finally {
480
+ process.chdir(originalCwd);
481
+ }
482
+ },
483
+ );
484
+
485
+ itWithDir(
486
+ 'should handle default import patterns for envParser and logger',
487
+ async ({ dir }) => {
488
+ // Create config with default import patterns
489
+ await createTestFile(
490
+ dir,
491
+ 'gkm.config.ts',
492
+ `
493
+ export default {
494
+ routes: './src/endpoints/**/*.ts',
495
+ functions: undefined,
496
+ crons: undefined,
497
+ envParser: './config/env',
498
+ logger: './config/logger',
499
+ };
500
+ `,
501
+ );
502
+
503
+ await createMockEndpointFile(
504
+ dir,
505
+ 'src/endpoints/test.ts',
506
+ 'testEndpoint',
507
+ '/test',
508
+ 'GET',
509
+ );
510
+
511
+ // Create env and logger files with default exports
512
+ await createTestFile(dir, 'config/env.ts', 'export default {}');
513
+ await createTestFile(dir, 'config/logger.ts', 'export default {}');
514
+
515
+ const originalCwd = process.cwd();
516
+ process.chdir(dir);
517
+
518
+ try {
519
+ await buildCommand({ provider: 'aws' });
520
+
521
+ // Verify that a handler file was generated with default imports
522
+ const handlerFile = join(dir, '.gkm/aws-apigatewayv2/testEndpoint.ts');
523
+ const handlerContent = await readFile(handlerFile, 'utf-8');
524
+ expect(handlerContent).toContain('import envParser');
525
+ expect(handlerContent).not.toContain('{ envParser }');
526
+ } finally {
527
+ process.chdir(originalCwd);
528
+ }
529
+ },
530
+ );
531
+
532
+ itWithDir(
533
+ 'should handle envParser pattern with same name as expected',
534
+ async ({ dir }) => {
535
+ // Create config with named exports that match expected names
536
+ await createTestFile(
537
+ dir,
538
+ 'gkm.config.ts',
539
+ `
540
+ export default {
541
+ routes: './src/endpoints/**/*.ts',
542
+ functions: undefined,
543
+ crons: undefined,
544
+ envParser: './config/env#envParser',
545
+ logger: './config/logger#logger',
546
+ };
547
+ `,
548
+ );
549
+
550
+ await createMockEndpointFile(
551
+ dir,
552
+ 'src/endpoints/test.ts',
553
+ 'testEndpoint',
554
+ '/test',
555
+ 'GET',
556
+ );
557
+
558
+ // Create env and logger files with named exports
559
+ await createTestFile(dir, 'config/env.ts', 'export const envParser = {}');
560
+ await createTestFile(dir, 'config/logger.ts', 'export const logger = {}');
561
+
562
+ const originalCwd = process.cwd();
563
+ process.chdir(dir);
564
+
565
+ try {
566
+ await buildCommand({ provider: 'aws' });
567
+
568
+ // Verify that a handler file was generated with named imports
569
+ const handlerFile = join(dir, '.gkm/aws-apigatewayv2/testEndpoint.ts');
570
+ const handlerContent = await readFile(handlerFile, 'utf-8');
571
+
572
+ expect(handlerContent).toContain('{ envParser }');
573
+ } finally {
574
+ process.chdir(originalCwd);
575
+ }
576
+ },
577
+ );
578
+ });
@@ -0,0 +1,136 @@
1
+ import { mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import type { Cron, Function } from '@geekmidas/api/constructs';
4
+ import type { Endpoint } from '@geekmidas/api/server';
5
+ import { loadConfig } from '../config';
6
+ import {
7
+ CronGenerator,
8
+ EndpointGenerator,
9
+ FunctionGenerator,
10
+ type GeneratedConstruct,
11
+ } from '../generators';
12
+ import type { BuildOptions, LegacyProvider } from '../types';
13
+ import { generateManifests } from './manifests';
14
+ import { resolveProviders } from './providerResolver';
15
+ import type { BuildContext } from './types';
16
+
17
+ const logger = console;
18
+
19
+ export async function buildCommand(options: BuildOptions): Promise<void> {
20
+ const config = await loadConfig();
21
+
22
+ // Resolve providers from new config format
23
+ const resolved = resolveProviders(config, options);
24
+
25
+ logger.log(`Building with providers: ${resolved.providers.join(', ')}`);
26
+ logger.log(`Loading routes from: ${config.routes}`);
27
+ if (config.functions) {
28
+ logger.log(`Loading functions from: ${config.functions}`);
29
+ }
30
+ if (config.crons) {
31
+ logger.log(`Loading crons from: ${config.crons}`);
32
+ }
33
+ logger.log(`Using envParser: ${config.envParser}`);
34
+
35
+ // Parse envParser configuration
36
+ const [envParserPath, envParserName] = config.envParser.split('#');
37
+ const envParserImportPattern = !envParserName
38
+ ? 'envParser'
39
+ : envParserName === 'envParser'
40
+ ? '{ envParser }'
41
+ : `{ ${envParserName} as envParser }`;
42
+
43
+ // Parse logger configuration
44
+ const [loggerPath, loggerName] = config.logger.split('#');
45
+ const loggerImportPattern = !loggerName
46
+ ? 'logger'
47
+ : loggerName === 'logger'
48
+ ? '{ logger }'
49
+ : `{ ${loggerName} as logger }`;
50
+
51
+ const buildContext: BuildContext = {
52
+ envParserPath,
53
+ envParserImportPattern,
54
+ loggerPath,
55
+ loggerImportPattern,
56
+ };
57
+
58
+ // Initialize generators
59
+ const endpointGenerator = new EndpointGenerator();
60
+ const functionGenerator = new FunctionGenerator();
61
+ const cronGenerator = new CronGenerator();
62
+
63
+ // Load all constructs in parallel
64
+ const [allEndpoints, allFunctions, allCrons] = await Promise.all([
65
+ endpointGenerator.load(config.routes),
66
+ config.functions ? functionGenerator.load(config.functions) : [],
67
+ config.crons ? cronGenerator.load(config.crons) : [],
68
+ ]);
69
+
70
+ logger.log(`Found ${allEndpoints.length} endpoints`);
71
+ logger.log(`Found ${allFunctions.length} functions`);
72
+ logger.log(`Found ${allCrons.length} crons`);
73
+
74
+ if (
75
+ allEndpoints.length === 0 &&
76
+ allFunctions.length === 0 &&
77
+ allCrons.length === 0
78
+ ) {
79
+ logger.log('No endpoints, functions, or crons found to process');
80
+ return;
81
+ }
82
+
83
+ // Build for each provider in parallel
84
+ await Promise.all(
85
+ resolved.providers.map((provider) =>
86
+ buildForProvider(
87
+ provider,
88
+ buildContext,
89
+ endpointGenerator,
90
+ functionGenerator,
91
+ cronGenerator,
92
+ allEndpoints,
93
+ allFunctions,
94
+ allCrons,
95
+ resolved.enableOpenApi,
96
+ ),
97
+ ),
98
+ );
99
+ }
100
+
101
+ async function buildForProvider(
102
+ provider: LegacyProvider,
103
+ context: BuildContext,
104
+ endpointGenerator: EndpointGenerator,
105
+ functionGenerator: FunctionGenerator,
106
+ cronGenerator: CronGenerator,
107
+ endpoints: GeneratedConstruct<Endpoint<any, any, any, any, any, any>>[],
108
+ functions: GeneratedConstruct<Function<any, any, any, any>>[],
109
+ crons: GeneratedConstruct<Cron<any, any, any, any>>[],
110
+ enableOpenApi: boolean,
111
+ ): Promise<void> {
112
+ const outputDir = join(process.cwd(), '.gkm', provider);
113
+
114
+ // Ensure output directory exists
115
+ await mkdir(outputDir, { recursive: true });
116
+
117
+ logger.log(`\nGenerating handlers for provider: ${provider}`);
118
+
119
+ // Build all constructs in parallel
120
+ const [routes, functionInfos, cronInfos] = await Promise.all([
121
+ endpointGenerator.build(context, endpoints, outputDir, {
122
+ provider,
123
+ enableOpenApi,
124
+ }),
125
+ functionGenerator.build(context, functions, outputDir, { provider }),
126
+ cronGenerator.build(context, crons, outputDir, { provider }),
127
+ ]);
128
+
129
+ // Generate manifests
130
+ await generateManifests(
131
+ outputDir,
132
+ routes,
133
+ functionInfos,
134
+ cronInfos,
135
+ );
136
+ }