@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.
- package/FUNCTION_CRON_SUPPORT.md +266 -0
- package/README.md +84 -17
- package/dist/CronGenerator-1PflEYe2.cjs +60 -0
- package/dist/CronGenerator-1PflEYe2.cjs.map +1 -0
- package/dist/CronGenerator-DXRfHQcV.mjs +54 -0
- package/dist/CronGenerator-DXRfHQcV.mjs.map +1 -0
- package/dist/EndpointGenerator-BbGrDiCP.cjs +264 -0
- package/dist/EndpointGenerator-BbGrDiCP.cjs.map +1 -0
- package/dist/EndpointGenerator-BmZ9BxbO.mjs +258 -0
- package/dist/EndpointGenerator-BmZ9BxbO.mjs.map +1 -0
- package/dist/FunctionGenerator-Clw64SwQ.cjs +59 -0
- package/dist/FunctionGenerator-Clw64SwQ.cjs.map +1 -0
- package/dist/FunctionGenerator-DOEB_yPh.mjs +53 -0
- package/dist/FunctionGenerator-DOEB_yPh.mjs.map +1 -0
- package/dist/Generator-CDoEXCDg.cjs +47 -0
- package/dist/Generator-CDoEXCDg.cjs.map +1 -0
- package/dist/Generator-UanJW0_V.mjs +41 -0
- package/dist/Generator-UanJW0_V.mjs.map +1 -0
- package/dist/SubscriberGenerator-BfMZCVNy.cjs +204 -0
- package/dist/SubscriberGenerator-BfMZCVNy.cjs.map +1 -0
- package/dist/SubscriberGenerator-D2u00NI3.mjs +198 -0
- package/dist/SubscriberGenerator-D2u00NI3.mjs.map +1 -0
- package/dist/build/index.cjs +12 -0
- package/dist/build/index.mjs +12 -0
- package/dist/build/manifests.cjs +3 -0
- package/dist/build/manifests.mjs +3 -0
- package/dist/build/providerResolver.cjs +5 -0
- package/dist/build/providerResolver.mjs +3 -0
- package/dist/build/types.cjs +0 -0
- package/dist/build/types.mjs +0 -0
- package/dist/build-BBhlEjf5.cjs +89 -0
- package/dist/build-BBhlEjf5.cjs.map +1 -0
- package/dist/build-kY-lG30Q.mjs +83 -0
- package/dist/build-kY-lG30Q.mjs.map +1 -0
- package/dist/config-D1EpSGk6.cjs +36 -0
- package/dist/config-D1EpSGk6.cjs.map +1 -0
- package/dist/config-U-mdW-7Y.mjs +30 -0
- package/dist/config-U-mdW-7Y.mjs.map +1 -0
- package/dist/config.cjs +1 -1
- package/dist/config.mjs +1 -1
- package/dist/generators/CronGenerator.cjs +4 -0
- package/dist/generators/CronGenerator.mjs +4 -0
- package/dist/generators/EndpointGenerator.cjs +4 -0
- package/dist/generators/EndpointGenerator.mjs +4 -0
- package/dist/generators/FunctionGenerator.cjs +4 -0
- package/dist/generators/FunctionGenerator.mjs +4 -0
- package/dist/generators/Generator.cjs +3 -0
- package/dist/generators/Generator.mjs +3 -0
- package/dist/generators/SubscriberGenerator.cjs +4 -0
- package/dist/generators/SubscriberGenerator.mjs +4 -0
- package/dist/generators/index.cjs +12 -0
- package/dist/generators/index.mjs +8 -0
- package/dist/generators-CEKtVh81.cjs +0 -0
- package/dist/generators-CsLujGXs.mjs +0 -0
- package/dist/index.cjs +71 -25
- package/dist/index.cjs.map +1 -0
- package/dist/index.mjs +71 -25
- package/dist/index.mjs.map +1 -0
- package/dist/manifests-BrJXpHrf.mjs +21 -0
- package/dist/manifests-BrJXpHrf.mjs.map +1 -0
- package/dist/manifests-D0saShvH.cjs +27 -0
- package/dist/manifests-D0saShvH.cjs.map +1 -0
- package/dist/{openapi-CksVdkh2.mjs → openapi-BQx3_JbM.mjs} +8 -6
- package/dist/openapi-BQx3_JbM.mjs.map +1 -0
- package/dist/{openapi-D4QQJUPY.cjs → openapi-CMLr04cz.cjs} +9 -7
- package/dist/openapi-CMLr04cz.cjs.map +1 -0
- package/dist/{openapi-react-query-DpT3XHFC.mjs → openapi-react-query-DbrWwQzb.mjs} +5 -3
- package/dist/openapi-react-query-DbrWwQzb.mjs.map +1 -0
- package/dist/{openapi-react-query-C1JLYUOs.cjs → openapi-react-query-Dvjqx_Eo.cjs} +5 -3
- package/dist/openapi-react-query-Dvjqx_Eo.cjs.map +1 -0
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +4 -3
- package/dist/openapi.mjs +4 -3
- package/dist/providerResolver-B_TjNF0_.mjs +96 -0
- package/dist/providerResolver-B_TjNF0_.mjs.map +1 -0
- package/dist/providerResolver-DgvzNfP4.cjs +114 -0
- package/dist/providerResolver-DgvzNfP4.cjs.map +1 -0
- package/examples/cron-example.ts +45 -0
- package/examples/function-example.ts +40 -0
- package/examples/gkm.config.json +22 -0
- package/examples/gkm.minimal.config.json +7 -0
- package/examples/gkm.production.config.json +27 -0
- package/examples/logger.ts +1 -1
- package/package.json +38 -14
- package/src/__tests__/config.spec.ts +110 -0
- package/src/__tests__/openapi-react-query.spec.ts +506 -0
- package/src/__tests__/openapi.spec.ts +362 -0
- package/src/__tests__/test-helpers.ts +180 -0
- package/src/build/__tests__/index-new.spec.ts +577 -0
- package/src/build/index.ts +197 -0
- package/src/build/manifests.ts +35 -0
- package/src/build/providerResolver.ts +184 -0
- package/src/build/types.ts +37 -0
- package/src/config.ts +14 -6
- package/src/generators/CronGenerator.ts +98 -0
- package/src/generators/EndpointGenerator.ts +389 -0
- package/src/generators/FunctionGenerator.ts +97 -0
- package/src/generators/Generator.ts +95 -0
- package/src/generators/SubscriberGenerator.ts +271 -0
- package/src/generators/__tests__/CronGenerator.spec.ts +445 -0
- package/src/generators/__tests__/EndpointGenerator.spec.ts +394 -0
- package/src/generators/__tests__/FunctionGenerator.spec.ts +256 -0
- package/src/generators/__tests__/SubscriberGenerator.spec.ts +341 -0
- package/src/generators/index.ts +9 -0
- package/src/index.ts +57 -22
- package/src/openapi-react-query.ts +2 -1
- package/src/openapi.ts +5 -4
- package/src/types.ts +91 -2
- package/dist/build-BTggTCYL.cjs +0 -176
- package/dist/build-Ca4P6_lY.mjs +0 -170
- package/dist/build.cjs +0 -5
- package/dist/build.mjs +0 -5
- package/dist/config-BNqUMsvc.cjs +0 -24
- package/dist/config-BciAdY6_.mjs +0 -18
- package/dist/loadEndpoints-BBIavB9h.cjs +0 -37
- package/dist/loadEndpoints-DAZ53Og2.mjs +0 -31
- package/dist/loadEndpoints.cjs +0 -3
- package/dist/loadEndpoints.mjs +0 -3
- package/src/build.ts +0 -305
- 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';
|