@geekmidas/cli 0.10.0 → 0.13.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 (146) hide show
  1. package/README.md +525 -0
  2. package/dist/bundler-B1qy9b-j.cjs +112 -0
  3. package/dist/bundler-B1qy9b-j.cjs.map +1 -0
  4. package/dist/bundler-DskIqW2t.mjs +111 -0
  5. package/dist/bundler-DskIqW2t.mjs.map +1 -0
  6. package/dist/{config-C9aXOHBe.cjs → config-AmInkU7k.cjs} +8 -8
  7. package/dist/config-AmInkU7k.cjs.map +1 -0
  8. package/dist/{config-BrkUalUh.mjs → config-DYULeEv8.mjs} +3 -3
  9. package/dist/config-DYULeEv8.mjs.map +1 -0
  10. package/dist/config.cjs +1 -1
  11. package/dist/config.d.cts +1 -1
  12. package/dist/config.d.mts +1 -1
  13. package/dist/config.mjs +1 -1
  14. package/dist/encryption-C8H-38Yy.mjs +42 -0
  15. package/dist/encryption-C8H-38Yy.mjs.map +1 -0
  16. package/dist/encryption-Dyf_r1h-.cjs +44 -0
  17. package/dist/encryption-Dyf_r1h-.cjs.map +1 -0
  18. package/dist/index.cjs +2123 -179
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.mjs +2141 -192
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/{openapi-CZLI4QTr.mjs → openapi-BfFlOBCG.mjs} +801 -38
  23. package/dist/openapi-BfFlOBCG.mjs.map +1 -0
  24. package/dist/{openapi-BeHLKcwP.cjs → openapi-Bt_1FDpT.cjs} +794 -31
  25. package/dist/openapi-Bt_1FDpT.cjs.map +1 -0
  26. package/dist/{openapi-react-query-o5iMi8tz.cjs → openapi-react-query-B-sNWHFU.cjs} +5 -5
  27. package/dist/openapi-react-query-B-sNWHFU.cjs.map +1 -0
  28. package/dist/{openapi-react-query-CcciaVu5.mjs → openapi-react-query-B6XTeGqS.mjs} +5 -5
  29. package/dist/openapi-react-query-B6XTeGqS.mjs.map +1 -0
  30. package/dist/openapi-react-query.cjs +1 -1
  31. package/dist/openapi-react-query.d.cts.map +1 -1
  32. package/dist/openapi-react-query.d.mts.map +1 -1
  33. package/dist/openapi-react-query.mjs +1 -1
  34. package/dist/openapi.cjs +2 -2
  35. package/dist/openapi.d.cts +1 -1
  36. package/dist/openapi.d.cts.map +1 -1
  37. package/dist/openapi.d.mts +1 -1
  38. package/dist/openapi.d.mts.map +1 -1
  39. package/dist/openapi.mjs +2 -2
  40. package/dist/storage-BOOpAF8N.cjs +5 -0
  41. package/dist/storage-Bj1E26lU.cjs +187 -0
  42. package/dist/storage-Bj1E26lU.cjs.map +1 -0
  43. package/dist/storage-kSxTjkNb.mjs +133 -0
  44. package/dist/storage-kSxTjkNb.mjs.map +1 -0
  45. package/dist/storage-tgZSUnKl.mjs +3 -0
  46. package/dist/{types-b-vwGpqc.d.cts → types-BR0M2v_c.d.mts} +100 -1
  47. package/dist/types-BR0M2v_c.d.mts.map +1 -0
  48. package/dist/{types-DXgiA1sF.d.mts → types-BhkZc-vm.d.cts} +100 -1
  49. package/dist/types-BhkZc-vm.d.cts.map +1 -0
  50. package/examples/cron-example.ts +27 -27
  51. package/examples/env.ts +27 -27
  52. package/examples/function-example.ts +31 -31
  53. package/examples/gkm.config.json +20 -20
  54. package/examples/gkm.config.ts +8 -8
  55. package/examples/gkm.minimal.config.json +5 -5
  56. package/examples/gkm.production.config.json +25 -25
  57. package/examples/logger.ts +2 -2
  58. package/package.json +6 -6
  59. package/src/__tests__/EndpointGenerator.hooks.spec.ts +191 -191
  60. package/src/__tests__/config.spec.ts +55 -55
  61. package/src/__tests__/loadEnvFiles.spec.ts +93 -93
  62. package/src/__tests__/normalizeHooksConfig.spec.ts +58 -58
  63. package/src/__tests__/openapi-react-query.spec.ts +497 -497
  64. package/src/__tests__/openapi.spec.ts +428 -428
  65. package/src/__tests__/test-helpers.ts +76 -76
  66. package/src/auth/__tests__/credentials.spec.ts +204 -0
  67. package/src/auth/__tests__/index.spec.ts +168 -0
  68. package/src/auth/credentials.ts +187 -0
  69. package/src/auth/index.ts +226 -0
  70. package/src/build/__tests__/bundler.spec.ts +444 -0
  71. package/src/build/__tests__/index-new.spec.ts +474 -474
  72. package/src/build/__tests__/manifests.spec.ts +333 -333
  73. package/src/build/bundler.ts +210 -0
  74. package/src/build/endpoint-analyzer.ts +236 -0
  75. package/src/build/handler-templates.ts +1253 -0
  76. package/src/build/index.ts +260 -179
  77. package/src/build/manifests.ts +52 -52
  78. package/src/build/providerResolver.ts +145 -145
  79. package/src/build/types.ts +64 -43
  80. package/src/config.ts +39 -39
  81. package/src/deploy/__tests__/docker.spec.ts +111 -0
  82. package/src/deploy/__tests__/dokploy.spec.ts +245 -0
  83. package/src/deploy/__tests__/init.spec.ts +662 -0
  84. package/src/deploy/docker.ts +128 -0
  85. package/src/deploy/dokploy.ts +204 -0
  86. package/src/deploy/index.ts +136 -0
  87. package/src/deploy/init.ts +484 -0
  88. package/src/deploy/types.ts +48 -0
  89. package/src/dev/__tests__/index.spec.ts +266 -266
  90. package/src/dev/index.ts +647 -601
  91. package/src/docker/__tests__/compose.spec.ts +531 -0
  92. package/src/docker/__tests__/templates.spec.ts +280 -0
  93. package/src/docker/compose.ts +273 -0
  94. package/src/docker/index.ts +230 -0
  95. package/src/docker/templates.ts +446 -0
  96. package/src/generators/CronGenerator.ts +72 -72
  97. package/src/generators/EndpointGenerator.ts +699 -398
  98. package/src/generators/FunctionGenerator.ts +84 -84
  99. package/src/generators/Generator.ts +72 -72
  100. package/src/generators/OpenApiTsGenerator.ts +577 -577
  101. package/src/generators/SubscriberGenerator.ts +124 -124
  102. package/src/generators/__tests__/CronGenerator.spec.ts +433 -433
  103. package/src/generators/__tests__/EndpointGenerator.spec.ts +532 -382
  104. package/src/generators/__tests__/FunctionGenerator.spec.ts +244 -244
  105. package/src/generators/__tests__/SubscriberGenerator.spec.ts +397 -382
  106. package/src/generators/index.ts +4 -4
  107. package/src/index.ts +623 -201
  108. package/src/init/__tests__/generators.spec.ts +334 -334
  109. package/src/init/__tests__/init.spec.ts +332 -332
  110. package/src/init/__tests__/utils.spec.ts +89 -89
  111. package/src/init/generators/config.ts +175 -175
  112. package/src/init/generators/docker.ts +41 -41
  113. package/src/init/generators/env.ts +72 -72
  114. package/src/init/generators/index.ts +1 -1
  115. package/src/init/generators/models.ts +64 -64
  116. package/src/init/generators/monorepo.ts +161 -161
  117. package/src/init/generators/package.ts +71 -71
  118. package/src/init/generators/source.ts +6 -6
  119. package/src/init/index.ts +203 -208
  120. package/src/init/templates/api.ts +115 -115
  121. package/src/init/templates/index.ts +75 -75
  122. package/src/init/templates/minimal.ts +98 -98
  123. package/src/init/templates/serverless.ts +89 -89
  124. package/src/init/templates/worker.ts +98 -98
  125. package/src/init/utils.ts +54 -56
  126. package/src/openapi-react-query.ts +194 -194
  127. package/src/openapi.ts +63 -63
  128. package/src/secrets/__tests__/encryption.spec.ts +226 -0
  129. package/src/secrets/__tests__/generator.spec.ts +319 -0
  130. package/src/secrets/__tests__/index.spec.ts +91 -0
  131. package/src/secrets/__tests__/storage.spec.ts +611 -0
  132. package/src/secrets/encryption.ts +91 -0
  133. package/src/secrets/generator.ts +164 -0
  134. package/src/secrets/index.ts +383 -0
  135. package/src/secrets/storage.ts +192 -0
  136. package/src/secrets/types.ts +53 -0
  137. package/src/types.ts +295 -176
  138. package/tsdown.config.ts +11 -8
  139. package/dist/config-BrkUalUh.mjs.map +0 -1
  140. package/dist/config-C9aXOHBe.cjs.map +0 -1
  141. package/dist/openapi-BeHLKcwP.cjs.map +0 -1
  142. package/dist/openapi-CZLI4QTr.mjs.map +0 -1
  143. package/dist/openapi-react-query-CcciaVu5.mjs.map +0 -1
  144. package/dist/openapi-react-query-o5iMi8tz.cjs.map +0 -1
  145. package/dist/types-DXgiA1sF.d.mts.map +0 -1
  146. package/dist/types-b-vwGpqc.d.cts.map +0 -1
@@ -0,0 +1,444 @@
1
+ import { mkdir, writeFile } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import type { Construct } from '@geekmidas/constructs';
4
+ import { itWithDir } from '@geekmidas/testkit/os';
5
+ import { beforeEach, describe, expect, vi } from 'vitest';
6
+ import type { StageSecrets } from '../../secrets/types';
7
+ import { bundleServer } from '../bundler';
8
+
9
+ // Mock child_process to avoid actually running tsdown
10
+ vi.mock('node:child_process', () => ({
11
+ execSync: vi.fn(),
12
+ }));
13
+
14
+ // Mock construct that returns specific environment variables
15
+ function createMockConstruct(envVars: string[]): Construct {
16
+ return {
17
+ getEnvironment: vi.fn().mockResolvedValue(envVars),
18
+ } as unknown as Construct;
19
+ }
20
+
21
+ // Helper to create a minimal secrets file
22
+ async function createSecretsFile(
23
+ dir: string,
24
+ stage: string,
25
+ secrets: Partial<StageSecrets>,
26
+ ): Promise<void> {
27
+ const secretsDir = join(dir, '.gkm', 'secrets');
28
+ await mkdir(secretsDir, { recursive: true });
29
+
30
+ const fullSecrets: StageSecrets = {
31
+ stage,
32
+ createdAt: new Date().toISOString(),
33
+ updatedAt: new Date().toISOString(),
34
+ services: {},
35
+ urls: {},
36
+ custom: {},
37
+ ...secrets,
38
+ };
39
+
40
+ await writeFile(
41
+ join(secretsDir, `${stage}.json`),
42
+ JSON.stringify(fullSecrets, null, 2),
43
+ );
44
+ }
45
+
46
+ // Helper to create a minimal entry point file and mock the bundle output
47
+ async function createEntryPoint(dir: string): Promise<string> {
48
+ const outputDir = join(dir, '.gkm', 'server');
49
+ const distDir = join(outputDir, 'dist');
50
+ await mkdir(outputDir, { recursive: true });
51
+ await mkdir(distDir, { recursive: true });
52
+
53
+ const entryPoint = join(outputDir, 'server.ts');
54
+ await writeFile(entryPoint, 'console.log("hello");');
55
+
56
+ // Create the output file that tsdown would normally create
57
+ // (since we're mocking execSync, the file won't be created automatically)
58
+ await writeFile(join(distDir, 'server.js'), 'console.log("bundled");');
59
+
60
+ return entryPoint;
61
+ }
62
+
63
+ describe('bundleServer environment validation', () => {
64
+ beforeEach(() => {
65
+ vi.clearAllMocks();
66
+ });
67
+
68
+ itWithDir(
69
+ 'should pass validation when all required env vars are present',
70
+ async ({ dir }) => {
71
+ const entryPoint = await createEntryPoint(dir);
72
+ await createSecretsFile(dir, 'production', {
73
+ urls: { DATABASE_URL: 'postgresql://localhost/db' },
74
+ custom: { API_KEY: 'sk_test_123' },
75
+ });
76
+
77
+ const constructs = [
78
+ createMockConstruct(['DATABASE_URL']),
79
+ createMockConstruct(['API_KEY']),
80
+ ];
81
+
82
+ const originalCwd = process.cwd();
83
+ process.chdir(dir);
84
+
85
+ try {
86
+ // Should not throw
87
+ const result = await bundleServer({
88
+ entryPoint,
89
+ outputDir: join(dir, '.gkm', 'server', 'dist'),
90
+ minify: false,
91
+ sourcemap: false,
92
+ external: [],
93
+ stage: 'production',
94
+ constructs,
95
+ });
96
+
97
+ expect(result.masterKey).toBeDefined();
98
+ expect(constructs[0].getEnvironment).toHaveBeenCalled();
99
+ expect(constructs[1].getEnvironment).toHaveBeenCalled();
100
+ } finally {
101
+ process.chdir(originalCwd);
102
+ }
103
+ },
104
+ );
105
+
106
+ itWithDir(
107
+ 'should throw error when required env vars are missing',
108
+ async ({ dir }) => {
109
+ const entryPoint = await createEntryPoint(dir);
110
+ await createSecretsFile(dir, 'production', {
111
+ urls: { DATABASE_URL: 'postgresql://localhost/db' },
112
+ custom: {},
113
+ });
114
+
115
+ const constructs = [
116
+ createMockConstruct(['DATABASE_URL', 'API_KEY', 'JWT_SECRET']),
117
+ ];
118
+
119
+ const originalCwd = process.cwd();
120
+ process.chdir(dir);
121
+
122
+ try {
123
+ await expect(
124
+ bundleServer({
125
+ entryPoint,
126
+ outputDir: join(dir, '.gkm', 'server', 'dist'),
127
+ minify: false,
128
+ sourcemap: false,
129
+ external: [],
130
+ stage: 'production',
131
+ constructs,
132
+ }),
133
+ ).rejects.toThrow('Missing environment variables');
134
+ } finally {
135
+ process.chdir(originalCwd);
136
+ }
137
+ },
138
+ );
139
+
140
+ itWithDir(
141
+ 'should include missing variables in error message',
142
+ async ({ dir }) => {
143
+ const entryPoint = await createEntryPoint(dir);
144
+ await createSecretsFile(dir, 'staging', {
145
+ custom: { EXISTING_VAR: 'value' },
146
+ });
147
+
148
+ const constructs = [
149
+ createMockConstruct(['EXISTING_VAR', 'MISSING_VAR_1', 'MISSING_VAR_2']),
150
+ ];
151
+
152
+ const originalCwd = process.cwd();
153
+ process.chdir(dir);
154
+
155
+ try {
156
+ await expect(
157
+ bundleServer({
158
+ entryPoint,
159
+ outputDir: join(dir, '.gkm', 'server', 'dist'),
160
+ minify: false,
161
+ sourcemap: false,
162
+ external: [],
163
+ stage: 'staging',
164
+ constructs,
165
+ }),
166
+ ).rejects.toThrow(/MISSING_VAR_1/);
167
+
168
+ await expect(
169
+ bundleServer({
170
+ entryPoint,
171
+ outputDir: join(dir, '.gkm', 'server', 'dist'),
172
+ minify: false,
173
+ sourcemap: false,
174
+ external: [],
175
+ stage: 'staging',
176
+ constructs,
177
+ }),
178
+ ).rejects.toThrow(/MISSING_VAR_2/);
179
+ } finally {
180
+ process.chdir(originalCwd);
181
+ }
182
+ },
183
+ );
184
+
185
+ itWithDir(
186
+ 'should collect env vars from multiple constructs',
187
+ async ({ dir }) => {
188
+ const entryPoint = await createEntryPoint(dir);
189
+ await createSecretsFile(dir, 'production', {
190
+ urls: { DATABASE_URL: 'postgresql://localhost/db' },
191
+ custom: {
192
+ API_KEY: 'key',
193
+ REDIS_URL: 'redis://localhost',
194
+ JWT_SECRET: 'secret',
195
+ },
196
+ });
197
+
198
+ const constructs = [
199
+ createMockConstruct(['DATABASE_URL']),
200
+ createMockConstruct(['API_KEY', 'REDIS_URL']),
201
+ createMockConstruct(['JWT_SECRET']),
202
+ ];
203
+
204
+ const originalCwd = process.cwd();
205
+ process.chdir(dir);
206
+
207
+ try {
208
+ const result = await bundleServer({
209
+ entryPoint,
210
+ outputDir: join(dir, '.gkm', 'server', 'dist'),
211
+ minify: false,
212
+ sourcemap: false,
213
+ external: [],
214
+ stage: 'production',
215
+ constructs,
216
+ });
217
+
218
+ expect(result.masterKey).toBeDefined();
219
+
220
+ // All constructs should have been checked
221
+ for (const construct of constructs) {
222
+ expect(construct.getEnvironment).toHaveBeenCalled();
223
+ }
224
+ } finally {
225
+ process.chdir(originalCwd);
226
+ }
227
+ },
228
+ );
229
+
230
+ itWithDir(
231
+ 'should deduplicate env vars from multiple constructs',
232
+ async ({ dir }) => {
233
+ const entryPoint = await createEntryPoint(dir);
234
+ await createSecretsFile(dir, 'production', {
235
+ custom: { SHARED_VAR: 'value' },
236
+ });
237
+
238
+ // Multiple constructs requiring the same variable
239
+ const constructs = [
240
+ createMockConstruct(['SHARED_VAR']),
241
+ createMockConstruct(['SHARED_VAR']),
242
+ createMockConstruct(['SHARED_VAR']),
243
+ ];
244
+
245
+ const originalCwd = process.cwd();
246
+ process.chdir(dir);
247
+
248
+ try {
249
+ // Should pass since SHARED_VAR is provided once
250
+ const result = await bundleServer({
251
+ entryPoint,
252
+ outputDir: join(dir, '.gkm', 'server', 'dist'),
253
+ minify: false,
254
+ sourcemap: false,
255
+ external: [],
256
+ stage: 'production',
257
+ constructs,
258
+ });
259
+
260
+ expect(result.masterKey).toBeDefined();
261
+ } finally {
262
+ process.chdir(originalCwd);
263
+ }
264
+ },
265
+ );
266
+
267
+ itWithDir(
268
+ 'should skip validation when no constructs provided',
269
+ async ({ dir }) => {
270
+ const entryPoint = await createEntryPoint(dir);
271
+ await createSecretsFile(dir, 'production', {
272
+ custom: {},
273
+ });
274
+
275
+ const originalCwd = process.cwd();
276
+ process.chdir(dir);
277
+
278
+ try {
279
+ // Should not throw even with empty secrets
280
+ const result = await bundleServer({
281
+ entryPoint,
282
+ outputDir: join(dir, '.gkm', 'server', 'dist'),
283
+ minify: false,
284
+ sourcemap: false,
285
+ external: [],
286
+ stage: 'production',
287
+ constructs: [],
288
+ });
289
+
290
+ expect(result.masterKey).toBeDefined();
291
+ } finally {
292
+ process.chdir(originalCwd);
293
+ }
294
+ },
295
+ );
296
+
297
+ itWithDir(
298
+ 'should skip validation when constructs is undefined',
299
+ async ({ dir }) => {
300
+ const entryPoint = await createEntryPoint(dir);
301
+ await createSecretsFile(dir, 'production', {
302
+ custom: {},
303
+ });
304
+
305
+ const originalCwd = process.cwd();
306
+ process.chdir(dir);
307
+
308
+ try {
309
+ const result = await bundleServer({
310
+ entryPoint,
311
+ outputDir: join(dir, '.gkm', 'server', 'dist'),
312
+ minify: false,
313
+ sourcemap: false,
314
+ external: [],
315
+ stage: 'production',
316
+ // No constructs provided
317
+ });
318
+
319
+ expect(result.masterKey).toBeDefined();
320
+ } finally {
321
+ process.chdir(originalCwd);
322
+ }
323
+ },
324
+ );
325
+
326
+ itWithDir(
327
+ 'should recognize service credentials as provided',
328
+ async ({ dir }) => {
329
+ const entryPoint = await createEntryPoint(dir);
330
+ await createSecretsFile(dir, 'production', {
331
+ services: {
332
+ postgres: {
333
+ host: 'localhost',
334
+ port: 5432,
335
+ username: 'app',
336
+ password: 'secret',
337
+ database: 'mydb',
338
+ },
339
+ },
340
+ });
341
+
342
+ const constructs = [
343
+ createMockConstruct([
344
+ 'POSTGRES_HOST',
345
+ 'POSTGRES_PORT',
346
+ 'POSTGRES_USER',
347
+ 'POSTGRES_PASSWORD',
348
+ 'POSTGRES_DB',
349
+ ]),
350
+ ];
351
+
352
+ const originalCwd = process.cwd();
353
+ process.chdir(dir);
354
+
355
+ try {
356
+ const result = await bundleServer({
357
+ entryPoint,
358
+ outputDir: join(dir, '.gkm', 'server', 'dist'),
359
+ minify: false,
360
+ sourcemap: false,
361
+ external: [],
362
+ stage: 'production',
363
+ constructs,
364
+ });
365
+
366
+ expect(result.masterKey).toBeDefined();
367
+ } finally {
368
+ process.chdir(originalCwd);
369
+ }
370
+ },
371
+ );
372
+
373
+ itWithDir(
374
+ 'should throw when secrets file does not exist',
375
+ async ({ dir }) => {
376
+ const entryPoint = await createEntryPoint(dir);
377
+ // Don't create secrets file
378
+
379
+ const constructs = [createMockConstruct(['DATABASE_URL'])];
380
+
381
+ const originalCwd = process.cwd();
382
+ process.chdir(dir);
383
+
384
+ try {
385
+ await expect(
386
+ bundleServer({
387
+ entryPoint,
388
+ outputDir: join(dir, '.gkm', 'server', 'dist'),
389
+ minify: false,
390
+ sourcemap: false,
391
+ external: [],
392
+ stage: 'production',
393
+ constructs,
394
+ }),
395
+ ).rejects.toThrow('No secrets found for stage "production"');
396
+ } finally {
397
+ process.chdir(originalCwd);
398
+ }
399
+ },
400
+ );
401
+
402
+ itWithDir(
403
+ 'should include helpful instructions in error message',
404
+ async ({ dir }) => {
405
+ const entryPoint = await createEntryPoint(dir);
406
+ await createSecretsFile(dir, 'myapp', {
407
+ custom: {},
408
+ });
409
+
410
+ const constructs = [createMockConstruct(['MISSING_VAR'])];
411
+
412
+ const originalCwd = process.cwd();
413
+ process.chdir(dir);
414
+
415
+ try {
416
+ await expect(
417
+ bundleServer({
418
+ entryPoint,
419
+ outputDir: join(dir, '.gkm', 'server', 'dist'),
420
+ minify: false,
421
+ sourcemap: false,
422
+ external: [],
423
+ stage: 'myapp',
424
+ constructs,
425
+ }),
426
+ ).rejects.toThrow(/gkm secrets:set/);
427
+
428
+ await expect(
429
+ bundleServer({
430
+ entryPoint,
431
+ outputDir: join(dir, '.gkm', 'server', 'dist'),
432
+ minify: false,
433
+ sourcemap: false,
434
+ external: [],
435
+ stage: 'myapp',
436
+ constructs,
437
+ }),
438
+ ).rejects.toThrow(/gkm secrets:import/);
439
+ } finally {
440
+ process.chdir(originalCwd);
441
+ }
442
+ },
443
+ );
444
+ });