@geekmidas/cli 0.0.26 → 0.2.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 (121) hide show
  1. package/FUNCTION_CRON_SUPPORT.md +266 -0
  2. package/README.md +84 -17
  3. package/dist/CronGenerator-1PflEYe2.cjs +60 -0
  4. package/dist/CronGenerator-1PflEYe2.cjs.map +1 -0
  5. package/dist/CronGenerator-DXRfHQcV.mjs +54 -0
  6. package/dist/CronGenerator-DXRfHQcV.mjs.map +1 -0
  7. package/dist/EndpointGenerator-BbGrDiCP.cjs +264 -0
  8. package/dist/EndpointGenerator-BbGrDiCP.cjs.map +1 -0
  9. package/dist/EndpointGenerator-BmZ9BxbO.mjs +258 -0
  10. package/dist/EndpointGenerator-BmZ9BxbO.mjs.map +1 -0
  11. package/dist/FunctionGenerator-Clw64SwQ.cjs +59 -0
  12. package/dist/FunctionGenerator-Clw64SwQ.cjs.map +1 -0
  13. package/dist/FunctionGenerator-DOEB_yPh.mjs +53 -0
  14. package/dist/FunctionGenerator-DOEB_yPh.mjs.map +1 -0
  15. package/dist/Generator-CDoEXCDg.cjs +47 -0
  16. package/dist/Generator-CDoEXCDg.cjs.map +1 -0
  17. package/dist/Generator-UanJW0_V.mjs +41 -0
  18. package/dist/Generator-UanJW0_V.mjs.map +1 -0
  19. package/dist/SubscriberGenerator-BfMZCVNy.cjs +204 -0
  20. package/dist/SubscriberGenerator-BfMZCVNy.cjs.map +1 -0
  21. package/dist/SubscriberGenerator-D2u00NI3.mjs +198 -0
  22. package/dist/SubscriberGenerator-D2u00NI3.mjs.map +1 -0
  23. package/dist/build/index.cjs +12 -0
  24. package/dist/build/index.mjs +12 -0
  25. package/dist/build/manifests.cjs +3 -0
  26. package/dist/build/manifests.mjs +3 -0
  27. package/dist/build/providerResolver.cjs +5 -0
  28. package/dist/build/providerResolver.mjs +3 -0
  29. package/dist/build/types.cjs +0 -0
  30. package/dist/build/types.mjs +0 -0
  31. package/dist/build-BBhlEjf5.cjs +89 -0
  32. package/dist/build-BBhlEjf5.cjs.map +1 -0
  33. package/dist/build-kY-lG30Q.mjs +83 -0
  34. package/dist/build-kY-lG30Q.mjs.map +1 -0
  35. package/dist/config-D1EpSGk6.cjs +36 -0
  36. package/dist/config-D1EpSGk6.cjs.map +1 -0
  37. package/dist/config-U-mdW-7Y.mjs +30 -0
  38. package/dist/config-U-mdW-7Y.mjs.map +1 -0
  39. package/dist/config.cjs +1 -1
  40. package/dist/config.mjs +1 -1
  41. package/dist/generators/CronGenerator.cjs +4 -0
  42. package/dist/generators/CronGenerator.mjs +4 -0
  43. package/dist/generators/EndpointGenerator.cjs +4 -0
  44. package/dist/generators/EndpointGenerator.mjs +4 -0
  45. package/dist/generators/FunctionGenerator.cjs +4 -0
  46. package/dist/generators/FunctionGenerator.mjs +4 -0
  47. package/dist/generators/Generator.cjs +3 -0
  48. package/dist/generators/Generator.mjs +3 -0
  49. package/dist/generators/SubscriberGenerator.cjs +4 -0
  50. package/dist/generators/SubscriberGenerator.mjs +4 -0
  51. package/dist/generators/index.cjs +12 -0
  52. package/dist/generators/index.mjs +8 -0
  53. package/dist/generators-CEKtVh81.cjs +0 -0
  54. package/dist/generators-CsLujGXs.mjs +0 -0
  55. package/dist/index.cjs +71 -25
  56. package/dist/index.cjs.map +1 -0
  57. package/dist/index.mjs +71 -25
  58. package/dist/index.mjs.map +1 -0
  59. package/dist/manifests-BrJXpHrf.mjs +21 -0
  60. package/dist/manifests-BrJXpHrf.mjs.map +1 -0
  61. package/dist/manifests-D0saShvH.cjs +27 -0
  62. package/dist/manifests-D0saShvH.cjs.map +1 -0
  63. package/dist/{openapi-CksVdkh2.mjs → openapi-BQx3_JbM.mjs} +8 -6
  64. package/dist/openapi-BQx3_JbM.mjs.map +1 -0
  65. package/dist/{openapi-D4QQJUPY.cjs → openapi-CMLr04cz.cjs} +9 -7
  66. package/dist/openapi-CMLr04cz.cjs.map +1 -0
  67. package/dist/{openapi-react-query-DpT3XHFC.mjs → openapi-react-query-DbrWwQzb.mjs} +5 -3
  68. package/dist/openapi-react-query-DbrWwQzb.mjs.map +1 -0
  69. package/dist/{openapi-react-query-C1JLYUOs.cjs → openapi-react-query-Dvjqx_Eo.cjs} +5 -3
  70. package/dist/openapi-react-query-Dvjqx_Eo.cjs.map +1 -0
  71. package/dist/openapi-react-query.cjs +1 -1
  72. package/dist/openapi-react-query.mjs +1 -1
  73. package/dist/openapi.cjs +4 -3
  74. package/dist/openapi.mjs +4 -3
  75. package/dist/providerResolver-B_TjNF0_.mjs +96 -0
  76. package/dist/providerResolver-B_TjNF0_.mjs.map +1 -0
  77. package/dist/providerResolver-DgvzNfP4.cjs +114 -0
  78. package/dist/providerResolver-DgvzNfP4.cjs.map +1 -0
  79. package/examples/cron-example.ts +45 -0
  80. package/examples/function-example.ts +40 -0
  81. package/examples/gkm.config.json +22 -0
  82. package/examples/gkm.minimal.config.json +7 -0
  83. package/examples/gkm.production.config.json +27 -0
  84. package/examples/logger.ts +1 -1
  85. package/package.json +38 -14
  86. package/src/__tests__/config.spec.ts +110 -0
  87. package/src/__tests__/openapi-react-query.spec.ts +506 -0
  88. package/src/__tests__/openapi.spec.ts +362 -0
  89. package/src/__tests__/test-helpers.ts +180 -0
  90. package/src/build/__tests__/index-new.spec.ts +577 -0
  91. package/src/build/index.ts +197 -0
  92. package/src/build/manifests.ts +35 -0
  93. package/src/build/providerResolver.ts +184 -0
  94. package/src/build/types.ts +37 -0
  95. package/src/config.ts +14 -6
  96. package/src/generators/CronGenerator.ts +98 -0
  97. package/src/generators/EndpointGenerator.ts +389 -0
  98. package/src/generators/FunctionGenerator.ts +97 -0
  99. package/src/generators/Generator.ts +95 -0
  100. package/src/generators/SubscriberGenerator.ts +271 -0
  101. package/src/generators/__tests__/CronGenerator.spec.ts +445 -0
  102. package/src/generators/__tests__/EndpointGenerator.spec.ts +394 -0
  103. package/src/generators/__tests__/FunctionGenerator.spec.ts +256 -0
  104. package/src/generators/__tests__/SubscriberGenerator.spec.ts +341 -0
  105. package/src/generators/index.ts +9 -0
  106. package/src/index.ts +57 -22
  107. package/src/openapi-react-query.ts +2 -1
  108. package/src/openapi.ts +5 -4
  109. package/src/types.ts +91 -2
  110. package/dist/build-BTggTCYL.cjs +0 -176
  111. package/dist/build-Ca4P6_lY.mjs +0 -170
  112. package/dist/build.cjs +0 -5
  113. package/dist/build.mjs +0 -5
  114. package/dist/config-BNqUMsvc.cjs +0 -24
  115. package/dist/config-BciAdY6_.mjs +0 -18
  116. package/dist/loadEndpoints-BBIavB9h.cjs +0 -37
  117. package/dist/loadEndpoints-DAZ53Og2.mjs +0 -31
  118. package/dist/loadEndpoints.cjs +0 -3
  119. package/dist/loadEndpoints.mjs +0 -3
  120. package/src/build.ts +0 -305
  121. package/src/loadEndpoints.ts +0 -48
@@ -0,0 +1,362 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
5
+ import { openapiCommand } from '../openapi';
6
+ import {
7
+ cleanupDir,
8
+ createMockEndpointFile,
9
+ createTempDir,
10
+ createTestFile,
11
+ } from './test-helpers';
12
+
13
+ describe('OpenAPI Generation', () => {
14
+ let tempDir: string;
15
+
16
+ beforeEach(async () => {
17
+ tempDir = await createTempDir('openapi-test-');
18
+ });
19
+
20
+ afterEach(async () => {
21
+ await cleanupDir(tempDir);
22
+ vi.restoreAllMocks();
23
+ });
24
+
25
+ describe('openapiCommand', () => {
26
+ it('should generate OpenAPI spec for endpoints', async () => {
27
+ // Create endpoint file
28
+ await createMockEndpointFile(
29
+ tempDir,
30
+ 'getUser.ts',
31
+ 'getUser',
32
+ '/users/:id',
33
+ 'GET',
34
+ );
35
+
36
+ // Create config file
37
+ await createTestFile(
38
+ tempDir,
39
+ 'gkm.config.json',
40
+ JSON.stringify({
41
+ routes: [`${tempDir}/**/*.ts`],
42
+ }),
43
+ );
44
+
45
+ const outputPath = join(tempDir, 'openapi.json');
46
+
47
+ // Mock process.cwd
48
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
49
+
50
+ await openapiCommand({ output: outputPath });
51
+
52
+ // Verify file was created
53
+ expect(existsSync(outputPath)).toBe(true);
54
+
55
+ // Verify content
56
+ const content = await readFile(outputPath, 'utf-8');
57
+ const spec = JSON.parse(content);
58
+
59
+ expect(spec).toHaveProperty('openapi');
60
+ expect(spec).toHaveProperty('info');
61
+ expect(spec.info.title).toBe('API Documentation');
62
+ expect(spec).toHaveProperty('paths');
63
+ expect(Object.keys(spec.paths).length).toBeGreaterThan(0);
64
+ });
65
+
66
+ it('should handle no endpoints found', async () => {
67
+ // Create config with no matching files
68
+ await createTestFile(
69
+ tempDir,
70
+ 'gkm.config.json',
71
+ JSON.stringify({
72
+ routes: [`${tempDir}/nonexistent/**/*.ts`],
73
+ }),
74
+ );
75
+
76
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
77
+ const consoleSpy = vi.spyOn(console, 'log');
78
+
79
+ await openapiCommand({ output: join(tempDir, 'openapi.json') });
80
+
81
+ expect(consoleSpy).toHaveBeenCalledWith('No valid endpoints found');
82
+ });
83
+
84
+ it('should use default output path when not specified', async () => {
85
+ // Create endpoint file
86
+ await createMockEndpointFile(
87
+ tempDir,
88
+ 'endpoint.ts',
89
+ 'testEndpoint',
90
+ '/test',
91
+ 'GET',
92
+ );
93
+
94
+ // Create config
95
+ await createTestFile(
96
+ tempDir,
97
+ 'gkm.config.json',
98
+ JSON.stringify({
99
+ routes: [`${tempDir}/**/*.ts`],
100
+ }),
101
+ );
102
+
103
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
104
+
105
+ await openapiCommand();
106
+
107
+ // Should create openapi.json in current directory
108
+ expect(existsSync(join(tempDir, 'openapi.json'))).toBe(true);
109
+ });
110
+
111
+ it('should generate spec with multiple endpoints', async () => {
112
+ // Create multiple endpoint files
113
+ await createMockEndpointFile(
114
+ tempDir,
115
+ 'getUsers.ts',
116
+ 'getUsers',
117
+ '/users',
118
+ 'GET',
119
+ );
120
+ await createMockEndpointFile(
121
+ tempDir,
122
+ 'createUser.ts',
123
+ 'createUser',
124
+ '/users',
125
+ 'POST',
126
+ );
127
+ await createMockEndpointFile(
128
+ tempDir,
129
+ 'deleteUser.ts',
130
+ 'deleteUser',
131
+ '/users/:id',
132
+ 'DELETE',
133
+ );
134
+
135
+ // Create config
136
+ await createTestFile(
137
+ tempDir,
138
+ 'gkm.config.json',
139
+ JSON.stringify({
140
+ routes: [`${tempDir}/**/*.ts`],
141
+ }),
142
+ );
143
+
144
+ const outputPath = join(tempDir, 'openapi.json');
145
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
146
+
147
+ await openapiCommand({ output: outputPath });
148
+
149
+ const content = await readFile(outputPath, 'utf-8');
150
+ const spec = JSON.parse(content);
151
+
152
+ // Should have multiple paths
153
+ expect(Object.keys(spec.paths).length).toBeGreaterThanOrEqual(1);
154
+ });
155
+
156
+ it('should create output directory if it does not exist', async () => {
157
+ // Create endpoint file
158
+ await createMockEndpointFile(
159
+ tempDir,
160
+ 'endpoint.ts',
161
+ 'testEndpoint',
162
+ '/test',
163
+ 'GET',
164
+ );
165
+
166
+ // Create config
167
+ await createTestFile(
168
+ tempDir,
169
+ 'gkm.config.json',
170
+ JSON.stringify({
171
+ routes: [`${tempDir}/**/*.ts`],
172
+ }),
173
+ );
174
+
175
+ const outputPath = join(tempDir, 'nested', 'dir', 'openapi.json');
176
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
177
+
178
+ await openapiCommand({ output: outputPath });
179
+
180
+ expect(existsSync(outputPath)).toBe(true);
181
+ });
182
+
183
+ it('should include API metadata in spec', async () => {
184
+ // Create endpoint
185
+ await createMockEndpointFile(
186
+ tempDir,
187
+ 'endpoint.ts',
188
+ 'testEndpoint',
189
+ '/test',
190
+ 'GET',
191
+ );
192
+
193
+ // Create config
194
+ await createTestFile(
195
+ tempDir,
196
+ 'gkm.config.json',
197
+ JSON.stringify({
198
+ routes: [`${tempDir}/**/*.ts`],
199
+ }),
200
+ );
201
+
202
+ const outputPath = join(tempDir, 'openapi.json');
203
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
204
+
205
+ await openapiCommand({ output: outputPath });
206
+
207
+ const content = await readFile(outputPath, 'utf-8');
208
+ const spec = JSON.parse(content);
209
+
210
+ expect(spec.info).toEqual({
211
+ title: 'API Documentation',
212
+ version: '1.0.0',
213
+ description: 'Auto-generated API documentation from endpoints',
214
+ });
215
+ });
216
+
217
+ it('should log generation success', async () => {
218
+ // Create endpoint
219
+ await createMockEndpointFile(
220
+ tempDir,
221
+ 'endpoint.ts',
222
+ 'testEndpoint',
223
+ '/test',
224
+ 'GET',
225
+ );
226
+
227
+ // Create config
228
+ await createTestFile(
229
+ tempDir,
230
+ 'gkm.config.json',
231
+ JSON.stringify({
232
+ routes: [`${tempDir}/**/*.ts`],
233
+ }),
234
+ );
235
+
236
+ const outputPath = join(tempDir, 'openapi.json');
237
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
238
+ const consoleSpy = vi.spyOn(console, 'log');
239
+
240
+ await openapiCommand({ output: outputPath });
241
+
242
+ expect(consoleSpy).toHaveBeenCalledWith(
243
+ expect.stringContaining('OpenAPI spec generated'),
244
+ );
245
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Found'));
246
+ expect(consoleSpy).toHaveBeenCalledWith(
247
+ expect.stringContaining('endpoints'),
248
+ );
249
+ });
250
+
251
+ it('should throw error when config loading fails', async () => {
252
+ // No config file created
253
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
254
+
255
+ await expect(openapiCommand()).rejects.toThrow(
256
+ /OpenAPI generation failed/,
257
+ );
258
+ });
259
+
260
+ it('should throw error for invalid TypeScript files', async () => {
261
+ // Create invalid TS file
262
+ await createTestFile(
263
+ tempDir,
264
+ 'invalid.ts',
265
+ 'this is not valid typescript {[}]',
266
+ );
267
+
268
+ // Create config
269
+ await createTestFile(
270
+ tempDir,
271
+ 'gkm.config.json',
272
+ JSON.stringify({
273
+ routes: [`${tempDir}/**/*.ts`],
274
+ }),
275
+ );
276
+
277
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
278
+
279
+ // Should throw error for syntax errors
280
+ await expect(
281
+ openapiCommand({ output: join(tempDir, 'openapi.json') }),
282
+ ).rejects.toThrow(/OpenAPI generation failed/);
283
+ });
284
+
285
+ it('should generate valid JSON format', async () => {
286
+ // Create endpoint
287
+ await createMockEndpointFile(
288
+ tempDir,
289
+ 'endpoint.ts',
290
+ 'testEndpoint',
291
+ '/test',
292
+ 'GET',
293
+ );
294
+
295
+ // Create config
296
+ await createTestFile(
297
+ tempDir,
298
+ 'gkm.config.json',
299
+ JSON.stringify({
300
+ routes: [`${tempDir}/**/*.ts`],
301
+ }),
302
+ );
303
+
304
+ const outputPath = join(tempDir, 'openapi.json');
305
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
306
+
307
+ await openapiCommand({ output: outputPath });
308
+
309
+ const content = await readFile(outputPath, 'utf-8');
310
+
311
+ // Should be valid JSON and properly formatted
312
+ expect(() => JSON.parse(content)).not.toThrow();
313
+ expect(content).toContain('\n'); // Formatted with indentation
314
+ });
315
+
316
+ it('should handle endpoints with complex schemas', async () => {
317
+ // Create endpoint with complex schema
318
+ const complexEndpointContent = `
319
+ import { e } from '@geekmidas/constructs/endpoints';
320
+ import { z } from 'zod';
321
+
322
+ export const complexEndpoint = e
323
+ .post('/complex')
324
+ .body(z.object({
325
+ user: z.object({
326
+ name: z.string(),
327
+ email: z.string().email(),
328
+ age: z.number().optional(),
329
+ }),
330
+ tags: z.array(z.string()),
331
+ }))
332
+ .output(z.object({
333
+ id: z.string(),
334
+ status: z.enum(['active', 'inactive']),
335
+ }))
336
+ .handle(async () => ({ id: '123', status: 'active' as const }));
337
+ `;
338
+
339
+ await createTestFile(tempDir, 'complex.ts', complexEndpointContent);
340
+
341
+ // Create config
342
+ await createTestFile(
343
+ tempDir,
344
+ 'gkm.config.json',
345
+ JSON.stringify({
346
+ routes: [`${tempDir}/**/*.ts`],
347
+ }),
348
+ );
349
+
350
+ const outputPath = join(tempDir, 'openapi.json');
351
+ vi.spyOn(process, 'cwd').mockReturnValue(tempDir);
352
+
353
+ await openapiCommand({ output: outputPath });
354
+
355
+ const content = await readFile(outputPath, 'utf-8');
356
+ const spec = JSON.parse(content);
357
+
358
+ // Should have generated schema for complex types
359
+ expect(spec.paths).toBeDefined();
360
+ });
361
+ });
362
+ });
@@ -0,0 +1,180 @@
1
+ import { mkdir, rm, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import {
5
+ CronBuilder,
6
+ type ScheduleExpression,
7
+ } from '@geekmidas/constructs/crons';
8
+ import { e } from '@geekmidas/constructs/endpoints';
9
+ import { z } from 'zod';
10
+
11
+ /**
12
+ * Creates a temporary directory for testing
13
+ */
14
+ export async function createTempDir(prefix = 'cli-test-'): Promise<string> {
15
+ const tempPath = join(
16
+ tmpdir(),
17
+ `${prefix}${Date.now()}-${Math.random().toString(36).slice(2)}`,
18
+ );
19
+ await mkdir(tempPath, { recursive: true });
20
+ return tempPath;
21
+ }
22
+
23
+ /**
24
+ * Cleans up a directory
25
+ */
26
+ export async function cleanupDir(path: string): Promise<void> {
27
+ try {
28
+ await rm(path, { recursive: true, force: true });
29
+ } catch (error) {
30
+ // Ignore errors during cleanup
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Creates a test file with content
36
+ */
37
+ export async function createTestFile(
38
+ dir: string,
39
+ filename: string,
40
+ content: string,
41
+ ): Promise<string> {
42
+ const filePath = join(dir, filename);
43
+ await mkdir(dirname(filePath), { recursive: true });
44
+ await writeFile(filePath, content);
45
+ return filePath;
46
+ }
47
+
48
+ /**
49
+ * Creates a mock endpoint file with real endpoint construct
50
+ */
51
+ export async function createMockEndpointFile(
52
+ dir: string,
53
+ filename: string,
54
+ exportName: string,
55
+ path: string = '/test',
56
+ method: string = 'GET',
57
+ ): Promise<string> {
58
+ const content = `
59
+ import { e } from '@geekmidas/constructs/endpoints';
60
+ import { z } from 'zod';
61
+
62
+ export const ${exportName} = e
63
+ .${method.toLowerCase()}('${path}')
64
+ .output(z.object({ message: z.string() }))
65
+ .handle(async () => ({ message: 'Hello from ${exportName}' }));
66
+ `;
67
+ return createTestFile(dir, filename, content);
68
+ }
69
+
70
+ /**
71
+ * Creates a mock function file with real function construct
72
+ */
73
+ export async function createMockFunctionFile(
74
+ dir: string,
75
+ filename: string,
76
+ exportName: string,
77
+ timeout = 30,
78
+ ): Promise<string> {
79
+ const content = `
80
+ import { f } from '@geekmidas/constructs/functions';
81
+ import { z } from 'zod';
82
+
83
+ export const ${exportName} = f
84
+ .input(z.object({ name: z.string() }))
85
+ .output(z.object({ greeting: z.string() }))
86
+ .timeout(${timeout})
87
+ .handle(async ({ input }) => ({ greeting: \`Hello, \${input.name}!\` }));
88
+ `;
89
+ return createTestFile(dir, filename, content);
90
+ }
91
+
92
+ /**
93
+ * Creates a mock cron file with real cron construct
94
+ */
95
+ export async function createMockCronFile(
96
+ dir: string,
97
+ filename: string,
98
+ exportName: string,
99
+ schedule = 'rate(1 hour)',
100
+ ): Promise<string> {
101
+ const content = `
102
+ import { CronBuilder } from '@geekmidas/constructs/crons';
103
+ import { z } from 'zod';
104
+
105
+ export const ${exportName} = new CronBuilder()
106
+ .schedule('${schedule}')
107
+ .output(z.object({ processed: z.number() }))
108
+ .handle(async () => {
109
+ console.log('Running cron job: ${exportName}');
110
+ return { processed: 10 };
111
+ });
112
+ `;
113
+ return createTestFile(dir, filename, content);
114
+ }
115
+
116
+ /**
117
+ * Helper functions to create real constructs for testing
118
+ */
119
+ export function createTestEndpoint(path: string, method: HttpMethod = 'GET') {
120
+ const m = method.toLowerCase() as Lowercase<HttpMethod>;
121
+ const builder = e[m](path);
122
+ builder.output(z.object({ message: z.string() }));
123
+ return builder.handle(async () => ({ message: `Hello from ${path}` }));
124
+ }
125
+
126
+ export function createTestFunction(timeout: number = 30) {
127
+ const builder = new FunctionBuilder();
128
+ builder.input(z.object({ name: z.string() }));
129
+ builder.output(z.object({ greeting: z.string() }));
130
+ builder.timeout(timeout);
131
+ return builder.handle(async ({ input }: any) => ({
132
+ greeting: `Hello, ${input.name}!`,
133
+ }));
134
+ }
135
+
136
+ export function createTestCron(
137
+ schedule: ScheduleExpression = 'rate(1 hour)',
138
+ timeout: number = 30,
139
+ ) {
140
+ const builder = new CronBuilder();
141
+ builder.schedule(schedule);
142
+ builder.output(z.object({ processed: z.number() }));
143
+ builder.timeout(timeout);
144
+ return builder.handle(async () => {
145
+ return { processed: 10 };
146
+ });
147
+ }
148
+
149
+ /**
150
+ * Creates a mock build context
151
+ */
152
+ export function createMockBuildContext() {
153
+ return {
154
+ envParserPath: './env',
155
+ envParserImportPattern: 'envParser',
156
+ loggerPath: './logger',
157
+ loggerImportPattern: 'logger',
158
+ };
159
+ }
160
+
161
+ /**
162
+ * Waits for a condition to be true
163
+ */
164
+ export async function waitFor(
165
+ condition: () => boolean,
166
+ timeout = 5000,
167
+ interval = 100,
168
+ ): Promise<void> {
169
+ const start = Date.now();
170
+ while (!condition() && Date.now() - start < timeout) {
171
+ await new Promise((resolve) => setTimeout(resolve, interval));
172
+ }
173
+ if (!condition()) {
174
+ throw new Error('Timeout waiting for condition');
175
+ }
176
+ }
177
+
178
+ import { dirname } from 'node:path';
179
+ import { FunctionBuilder } from '@geekmidas/constructs/functions';
180
+ import type { HttpMethod } from '../../../api/src/constructs/types';