@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,531 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ type ComposeOptions,
4
+ DEFAULT_SERVICE_IMAGES,
5
+ DEFAULT_SERVICE_VERSIONS,
6
+ generateDockerCompose,
7
+ generateMinimalDockerCompose,
8
+ } from '../compose';
9
+
10
+ /** Helper to get full default image reference */
11
+ function getDefaultImage(service: 'postgres' | 'redis' | 'rabbitmq'): string {
12
+ return `${DEFAULT_SERVICE_IMAGES[service]}:${DEFAULT_SERVICE_VERSIONS[service]}`;
13
+ }
14
+
15
+ describe('generateDockerCompose', () => {
16
+ const baseOptions: ComposeOptions = {
17
+ imageName: 'my-api',
18
+ registry: 'ghcr.io/myorg',
19
+ port: 3000,
20
+ healthCheckPath: '/health',
21
+ services: {},
22
+ };
23
+
24
+ describe('api service', () => {
25
+ it('should generate valid docker-compose version', () => {
26
+ const yaml = generateDockerCompose(baseOptions);
27
+
28
+ expect(yaml).toContain("version: '3.8'");
29
+ });
30
+
31
+ it('should include api service with correct image reference', () => {
32
+ const yaml = generateDockerCompose(baseOptions);
33
+
34
+ expect(yaml).toContain('services:');
35
+ expect(yaml).toContain('api:');
36
+ expect(yaml).toContain(
37
+ 'image: ${REGISTRY:-ghcr.io/myorg}/${IMAGE_NAME:-my-api}:${TAG:-latest}',
38
+ );
39
+ });
40
+
41
+ it('should set container name from imageName', () => {
42
+ const yaml = generateDockerCompose(baseOptions);
43
+
44
+ expect(yaml).toContain('container_name: my-api');
45
+ });
46
+
47
+ it('should configure port mapping', () => {
48
+ const yaml = generateDockerCompose(baseOptions);
49
+
50
+ expect(yaml).toContain('- "${PORT:-3000}:3000"');
51
+ });
52
+
53
+ it('should set NODE_ENV to production', () => {
54
+ const yaml = generateDockerCompose(baseOptions);
55
+
56
+ expect(yaml).toContain('- NODE_ENV=production');
57
+ });
58
+
59
+ it('should include health check with configured path', () => {
60
+ const yaml = generateDockerCompose(baseOptions);
61
+
62
+ expect(yaml).toContain('healthcheck:');
63
+ expect(yaml).toContain(
64
+ 'test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/health"]',
65
+ );
66
+ expect(yaml).toContain('interval: 30s');
67
+ expect(yaml).toContain('timeout: 3s');
68
+ expect(yaml).toContain('retries: 3');
69
+ });
70
+
71
+ it('should use custom health check path', () => {
72
+ const yaml = generateDockerCompose({
73
+ ...baseOptions,
74
+ healthCheckPath: '/api/status',
75
+ });
76
+
77
+ expect(yaml).toContain('http://localhost:3000/api/status');
78
+ });
79
+
80
+ it('should use custom port', () => {
81
+ const yaml = generateDockerCompose({
82
+ ...baseOptions,
83
+ port: 8080,
84
+ });
85
+
86
+ expect(yaml).toContain('- "${PORT:-8080}:8080"');
87
+ expect(yaml).toContain('http://localhost:8080/health');
88
+ });
89
+
90
+ it('should handle empty registry', () => {
91
+ const yaml = generateDockerCompose({
92
+ ...baseOptions,
93
+ registry: '',
94
+ });
95
+
96
+ expect(yaml).toContain('image: ${IMAGE_NAME:-my-api}:${TAG:-latest}');
97
+ expect(yaml).not.toContain('${REGISTRY:-}');
98
+ });
99
+
100
+ it('should include build context and dockerfile', () => {
101
+ const yaml = generateDockerCompose(baseOptions);
102
+
103
+ expect(yaml).toContain('build:');
104
+ expect(yaml).toContain('context: ../..');
105
+ expect(yaml).toContain('dockerfile: .gkm/docker/Dockerfile');
106
+ });
107
+
108
+ it('should configure restart policy', () => {
109
+ const yaml = generateDockerCompose(baseOptions);
110
+
111
+ expect(yaml).toContain('restart: unless-stopped');
112
+ });
113
+
114
+ it('should attach to app-network', () => {
115
+ const yaml = generateDockerCompose(baseOptions);
116
+
117
+ expect(yaml).toContain('networks:');
118
+ expect(yaml).toContain('- app-network');
119
+ });
120
+ });
121
+
122
+ describe('postgres service', () => {
123
+ it('should add DATABASE_URL environment variable', () => {
124
+ const yaml = generateDockerCompose({
125
+ ...baseOptions,
126
+ services: { postgres: true },
127
+ });
128
+
129
+ expect(yaml).toContain(
130
+ '- DATABASE_URL=${DATABASE_URL:-postgresql://postgres:postgres@postgres:5432/app}',
131
+ );
132
+ });
133
+
134
+ it('should add postgres service definition with default version', () => {
135
+ const yaml = generateDockerCompose({
136
+ ...baseOptions,
137
+ services: { postgres: true },
138
+ });
139
+
140
+ expect(yaml).toContain('postgres:');
141
+ expect(yaml).toContain(`image: ${getDefaultImage('postgres')}`);
142
+ expect(yaml).toContain('container_name: postgres');
143
+ });
144
+
145
+ it('should use custom postgres version', () => {
146
+ const yaml = generateDockerCompose({
147
+ ...baseOptions,
148
+ services: { postgres: { version: '15-alpine' } },
149
+ });
150
+
151
+ expect(yaml).toContain('image: postgres:15-alpine');
152
+ });
153
+
154
+ it('should use custom postgres image (e.g., PostGIS)', () => {
155
+ const yaml = generateDockerCompose({
156
+ ...baseOptions,
157
+ services: { postgres: { image: 'postgis/postgis:16-3.4-alpine' } },
158
+ });
159
+
160
+ expect(yaml).toContain('image: postgis/postgis:16-3.4-alpine');
161
+ });
162
+
163
+ it('should configure postgres environment variables', () => {
164
+ const yaml = generateDockerCompose({
165
+ ...baseOptions,
166
+ services: { postgres: true },
167
+ });
168
+
169
+ expect(yaml).toContain('POSTGRES_USER: ${POSTGRES_USER:-postgres}');
170
+ expect(yaml).toContain(
171
+ 'POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}',
172
+ );
173
+ expect(yaml).toContain('POSTGRES_DB: ${POSTGRES_DB:-app}');
174
+ });
175
+
176
+ it('should add postgres volume', () => {
177
+ const yaml = generateDockerCompose({
178
+ ...baseOptions,
179
+ services: { postgres: true },
180
+ });
181
+
182
+ expect(yaml).toContain('- postgres_data:/var/lib/postgresql/data');
183
+ expect(yaml).toContain('postgres_data:');
184
+ });
185
+
186
+ it('should include postgres healthcheck', () => {
187
+ const yaml = generateDockerCompose({
188
+ ...baseOptions,
189
+ services: { postgres: true },
190
+ });
191
+
192
+ expect(yaml).toContain('test: ["CMD-SHELL", "pg_isready -U postgres"]');
193
+ });
194
+
195
+ it('should add depends_on for postgres', () => {
196
+ const yaml = generateDockerCompose({
197
+ ...baseOptions,
198
+ services: { postgres: true },
199
+ });
200
+
201
+ expect(yaml).toContain('depends_on:');
202
+ expect(yaml).toContain('postgres:');
203
+ expect(yaml).toContain('condition: service_healthy');
204
+ });
205
+ });
206
+
207
+ describe('redis service', () => {
208
+ it('should add REDIS_URL environment variable', () => {
209
+ const yaml = generateDockerCompose({
210
+ ...baseOptions,
211
+ services: { redis: true },
212
+ });
213
+
214
+ expect(yaml).toContain('- REDIS_URL=${REDIS_URL:-redis://redis:6379}');
215
+ });
216
+
217
+ it('should add redis service definition with default version', () => {
218
+ const yaml = generateDockerCompose({
219
+ ...baseOptions,
220
+ services: { redis: true },
221
+ });
222
+
223
+ expect(yaml).toContain('redis:');
224
+ expect(yaml).toContain(`image: ${getDefaultImage('redis')}`);
225
+ expect(yaml).toContain('container_name: redis');
226
+ });
227
+
228
+ it('should use custom redis version', () => {
229
+ const yaml = generateDockerCompose({
230
+ ...baseOptions,
231
+ services: { redis: { version: '6-alpine' } },
232
+ });
233
+
234
+ expect(yaml).toContain('image: redis:6-alpine');
235
+ });
236
+
237
+ it('should use custom redis image (e.g., Redis Stack)', () => {
238
+ const yaml = generateDockerCompose({
239
+ ...baseOptions,
240
+ services: { redis: { image: 'redis/redis-stack:latest' } },
241
+ });
242
+
243
+ expect(yaml).toContain('image: redis/redis-stack:latest');
244
+ });
245
+
246
+ it('should add redis volume', () => {
247
+ const yaml = generateDockerCompose({
248
+ ...baseOptions,
249
+ services: { redis: true },
250
+ });
251
+
252
+ expect(yaml).toContain('- redis_data:/data');
253
+ expect(yaml).toContain('redis_data:');
254
+ });
255
+
256
+ it('should include redis healthcheck', () => {
257
+ const yaml = generateDockerCompose({
258
+ ...baseOptions,
259
+ services: { redis: true },
260
+ });
261
+
262
+ expect(yaml).toContain('test: ["CMD", "redis-cli", "ping"]');
263
+ });
264
+ });
265
+
266
+ describe('rabbitmq service', () => {
267
+ it('should add RABBITMQ_URL environment variable', () => {
268
+ const yaml = generateDockerCompose({
269
+ ...baseOptions,
270
+ services: { rabbitmq: true },
271
+ });
272
+
273
+ expect(yaml).toContain(
274
+ '- RABBITMQ_URL=${RABBITMQ_URL:-amqp://rabbitmq:5672}',
275
+ );
276
+ });
277
+
278
+ it('should add rabbitmq service definition with default version', () => {
279
+ const yaml = generateDockerCompose({
280
+ ...baseOptions,
281
+ services: { rabbitmq: true },
282
+ });
283
+
284
+ expect(yaml).toContain('rabbitmq:');
285
+ expect(yaml).toContain(
286
+ `image: rabbitmq:${DEFAULT_SERVICE_VERSIONS.rabbitmq}`,
287
+ );
288
+ expect(yaml).toContain('container_name: rabbitmq');
289
+ });
290
+
291
+ it('should use custom rabbitmq version', () => {
292
+ const yaml = generateDockerCompose({
293
+ ...baseOptions,
294
+ services: { rabbitmq: { version: '3.12-management-alpine' } },
295
+ });
296
+
297
+ expect(yaml).toContain('image: rabbitmq:3.12-management-alpine');
298
+ });
299
+
300
+ it('should configure rabbitmq credentials', () => {
301
+ const yaml = generateDockerCompose({
302
+ ...baseOptions,
303
+ services: { rabbitmq: true },
304
+ });
305
+
306
+ expect(yaml).toContain('RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER:-guest}');
307
+ expect(yaml).toContain(
308
+ 'RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD:-guest}',
309
+ );
310
+ });
311
+
312
+ it('should expose management UI port', () => {
313
+ const yaml = generateDockerCompose({
314
+ ...baseOptions,
315
+ services: { rabbitmq: true },
316
+ });
317
+
318
+ expect(yaml).toContain('- "15672:15672"');
319
+ });
320
+
321
+ it('should add rabbitmq volume', () => {
322
+ const yaml = generateDockerCompose({
323
+ ...baseOptions,
324
+ services: { rabbitmq: true },
325
+ });
326
+
327
+ expect(yaml).toContain('- rabbitmq_data:/var/lib/rabbitmq');
328
+ expect(yaml).toContain('rabbitmq_data:');
329
+ });
330
+
331
+ it('should include rabbitmq healthcheck', () => {
332
+ const yaml = generateDockerCompose({
333
+ ...baseOptions,
334
+ services: { rabbitmq: true },
335
+ });
336
+
337
+ expect(yaml).toContain(
338
+ 'test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]',
339
+ );
340
+ });
341
+ });
342
+
343
+ describe('multiple services', () => {
344
+ it('should include all services when all specified', () => {
345
+ const yaml = generateDockerCompose({
346
+ ...baseOptions,
347
+ services: { postgres: true, redis: true, rabbitmq: true },
348
+ });
349
+
350
+ expect(yaml).toContain('postgres:');
351
+ expect(yaml).toContain('redis:');
352
+ expect(yaml).toContain('rabbitmq:');
353
+ });
354
+
355
+ it('should add all environment variables', () => {
356
+ const yaml = generateDockerCompose({
357
+ ...baseOptions,
358
+ services: { postgres: true, redis: true, rabbitmq: true },
359
+ });
360
+
361
+ expect(yaml).toContain('DATABASE_URL=');
362
+ expect(yaml).toContain('REDIS_URL=');
363
+ expect(yaml).toContain('RABBITMQ_URL=');
364
+ });
365
+
366
+ it('should add all volumes', () => {
367
+ const yaml = generateDockerCompose({
368
+ ...baseOptions,
369
+ services: { postgres: true, redis: true, rabbitmq: true },
370
+ });
371
+
372
+ expect(yaml).toContain('postgres_data:');
373
+ expect(yaml).toContain('redis_data:');
374
+ expect(yaml).toContain('rabbitmq_data:');
375
+ });
376
+
377
+ it('should add depends_on for all services', () => {
378
+ const yaml = generateDockerCompose({
379
+ ...baseOptions,
380
+ services: { postgres: true, redis: true, rabbitmq: true },
381
+ });
382
+
383
+ // Count occurrences of 'condition: service_healthy'
384
+ const matches = yaml.match(/condition: service_healthy/g);
385
+ expect(matches?.length).toBe(3);
386
+ });
387
+
388
+ it('should support mixed custom and default versions', () => {
389
+ const yaml = generateDockerCompose({
390
+ ...baseOptions,
391
+ services: {
392
+ postgres: { version: '15-alpine' },
393
+ redis: true,
394
+ rabbitmq: { version: '3.12-management-alpine' },
395
+ },
396
+ });
397
+
398
+ expect(yaml).toContain('image: postgres:15-alpine');
399
+ expect(yaml).toContain(`image: redis:${DEFAULT_SERVICE_VERSIONS.redis}`);
400
+ expect(yaml).toContain('image: rabbitmq:3.12-management-alpine');
401
+ });
402
+ });
403
+
404
+ describe('legacy array format', () => {
405
+ it('should support legacy array format with default versions', () => {
406
+ const yaml = generateDockerCompose({
407
+ ...baseOptions,
408
+ services: ['postgres', 'redis'],
409
+ });
410
+
411
+ expect(yaml).toContain('postgres:');
412
+ expect(yaml).toContain('redis:');
413
+ expect(yaml).toContain(
414
+ `image: postgres:${DEFAULT_SERVICE_VERSIONS.postgres}`,
415
+ );
416
+ expect(yaml).toContain(`image: redis:${DEFAULT_SERVICE_VERSIONS.redis}`);
417
+ });
418
+ });
419
+
420
+ describe('network configuration', () => {
421
+ it('should define app-network with bridge driver', () => {
422
+ const yaml = generateDockerCompose(baseOptions);
423
+
424
+ expect(yaml).toContain('networks:');
425
+ expect(yaml).toContain('app-network:');
426
+ expect(yaml).toContain('driver: bridge');
427
+ });
428
+
429
+ it('should attach all services to app-network', () => {
430
+ const yaml = generateDockerCompose({
431
+ ...baseOptions,
432
+ services: { postgres: true, redis: true },
433
+ });
434
+
435
+ // Should appear multiple times (api + postgres + redis)
436
+ const networkMatches = yaml.match(/- app-network/g);
437
+ expect(networkMatches?.length).toBeGreaterThanOrEqual(1);
438
+ });
439
+ });
440
+
441
+ describe('service exclusion', () => {
442
+ it('should not include services set to false', () => {
443
+ const yaml = generateDockerCompose({
444
+ ...baseOptions,
445
+ services: { postgres: true, redis: false },
446
+ });
447
+
448
+ expect(yaml).toContain('postgres:');
449
+ expect(yaml).not.toContain('image: redis:');
450
+ });
451
+ });
452
+ });
453
+
454
+ describe('generateMinimalDockerCompose', () => {
455
+ const baseOptions = {
456
+ imageName: 'minimal-api',
457
+ registry: 'docker.io/myorg',
458
+ port: 8080,
459
+ healthCheckPath: '/status',
460
+ };
461
+
462
+ it('should generate valid docker-compose version', () => {
463
+ const yaml = generateMinimalDockerCompose(baseOptions);
464
+
465
+ expect(yaml).toContain("version: '3.8'");
466
+ });
467
+
468
+ it('should include only api service', () => {
469
+ const yaml = generateMinimalDockerCompose(baseOptions);
470
+
471
+ expect(yaml).toContain('api:');
472
+ expect(yaml).not.toContain('postgres:');
473
+ expect(yaml).not.toContain('redis:');
474
+ expect(yaml).not.toContain('rabbitmq:');
475
+ });
476
+
477
+ it('should include correct image reference', () => {
478
+ const yaml = generateMinimalDockerCompose(baseOptions);
479
+
480
+ expect(yaml).toContain(
481
+ 'image: ${REGISTRY:-docker.io/myorg}/${IMAGE_NAME:-minimal-api}:${TAG:-latest}',
482
+ );
483
+ });
484
+
485
+ it('should configure correct port', () => {
486
+ const yaml = generateMinimalDockerCompose(baseOptions);
487
+
488
+ expect(yaml).toContain('- "${PORT:-8080}:8080"');
489
+ });
490
+
491
+ it('should include health check with configured path', () => {
492
+ const yaml = generateMinimalDockerCompose(baseOptions);
493
+
494
+ expect(yaml).toContain('http://localhost:8080/status');
495
+ });
496
+
497
+ it('should not include volumes section', () => {
498
+ const yaml = generateMinimalDockerCompose(baseOptions);
499
+
500
+ expect(yaml).not.toContain('volumes:');
501
+ });
502
+
503
+ it('should not include depends_on', () => {
504
+ const yaml = generateMinimalDockerCompose(baseOptions);
505
+
506
+ expect(yaml).not.toContain('depends_on:');
507
+ });
508
+
509
+ it('should include network configuration', () => {
510
+ const yaml = generateMinimalDockerCompose(baseOptions);
511
+
512
+ expect(yaml).toContain('networks:');
513
+ expect(yaml).toContain('app-network:');
514
+ expect(yaml).toContain('driver: bridge');
515
+ });
516
+
517
+ it('should set NODE_ENV to production', () => {
518
+ const yaml = generateMinimalDockerCompose(baseOptions);
519
+
520
+ expect(yaml).toContain('- NODE_ENV=production');
521
+ });
522
+
523
+ it('should handle empty registry', () => {
524
+ const yaml = generateMinimalDockerCompose({
525
+ ...baseOptions,
526
+ registry: '',
527
+ });
528
+
529
+ expect(yaml).toContain('image: ${IMAGE_NAME:-minimal-api}:${TAG:-latest}');
530
+ });
531
+ });