@geekmidas/cli 0.18.0 → 0.19.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 (118) hide show
  1. package/dist/{bundler-C74EKlNa.cjs → bundler-CyHg1v_T.cjs} +3 -3
  2. package/dist/{bundler-C74EKlNa.cjs.map → bundler-CyHg1v_T.cjs.map} +1 -1
  3. package/dist/{bundler-B6z6HEeh.mjs → bundler-DQIuE3Kn.mjs} +3 -3
  4. package/dist/{bundler-B6z6HEeh.mjs.map → bundler-DQIuE3Kn.mjs.map} +1 -1
  5. package/dist/{config-DYULeEv8.mjs → config-BaYqrF3n.mjs} +48 -10
  6. package/dist/config-BaYqrF3n.mjs.map +1 -0
  7. package/dist/{config-AmInkU7k.cjs → config-CxrLu8ia.cjs} +53 -9
  8. package/dist/config-CxrLu8ia.cjs.map +1 -0
  9. package/dist/config.cjs +4 -1
  10. package/dist/config.d.cts +27 -2
  11. package/dist/config.d.cts.map +1 -1
  12. package/dist/config.d.mts +27 -2
  13. package/dist/config.d.mts.map +1 -1
  14. package/dist/config.mjs +3 -2
  15. package/dist/dokploy-api-B0w17y4_.mjs +3 -0
  16. package/dist/{dokploy-api-CaETb2L6.mjs → dokploy-api-B9qR2Yn1.mjs} +1 -1
  17. package/dist/{dokploy-api-CaETb2L6.mjs.map → dokploy-api-B9qR2Yn1.mjs.map} +1 -1
  18. package/dist/dokploy-api-BnGeUqN4.cjs +3 -0
  19. package/dist/{dokploy-api-C7F9VykY.cjs → dokploy-api-C5czOZoc.cjs} +1 -1
  20. package/dist/{dokploy-api-C7F9VykY.cjs.map → dokploy-api-C5czOZoc.cjs.map} +1 -1
  21. package/dist/{encryption-D7Efcdi9.cjs → encryption-BAz0xQ1Q.cjs} +1 -1
  22. package/dist/{encryption-D7Efcdi9.cjs.map → encryption-BAz0xQ1Q.cjs.map} +1 -1
  23. package/dist/{encryption-h4Nb6W-M.mjs → encryption-JtMsiGNp.mjs} +2 -2
  24. package/dist/{encryption-h4Nb6W-M.mjs.map → encryption-JtMsiGNp.mjs.map} +1 -1
  25. package/dist/index-CWN-bgrO.d.mts +495 -0
  26. package/dist/index-CWN-bgrO.d.mts.map +1 -0
  27. package/dist/index-DEWYvYvg.d.cts +495 -0
  28. package/dist/index-DEWYvYvg.d.cts.map +1 -0
  29. package/dist/index.cjs +2639 -563
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.mjs +2634 -563
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/{openapi-CZVcfxk-.mjs → openapi-CgqR6Jkw.mjs} +3 -3
  34. package/dist/{openapi-CZVcfxk-.mjs.map → openapi-CgqR6Jkw.mjs.map} +1 -1
  35. package/dist/{openapi-C89hhkZC.cjs → openapi-DfpxS0xv.cjs} +8 -2
  36. package/dist/{openapi-C89hhkZC.cjs.map → openapi-DfpxS0xv.cjs.map} +1 -1
  37. package/dist/{openapi-react-query-CM2_qlW9.mjs → openapi-react-query-5rSortLH.mjs} +1 -1
  38. package/dist/{openapi-react-query-CM2_qlW9.mjs.map → openapi-react-query-5rSortLH.mjs.map} +1 -1
  39. package/dist/{openapi-react-query-iKjfLzff.cjs → openapi-react-query-DvNpdDpM.cjs} +1 -1
  40. package/dist/{openapi-react-query-iKjfLzff.cjs.map → openapi-react-query-DvNpdDpM.cjs.map} +1 -1
  41. package/dist/openapi-react-query.cjs +1 -1
  42. package/dist/openapi-react-query.mjs +1 -1
  43. package/dist/openapi.cjs +3 -2
  44. package/dist/openapi.d.cts +1 -1
  45. package/dist/openapi.d.mts +1 -1
  46. package/dist/openapi.mjs +3 -2
  47. package/dist/{storage-Bn3K9Ccu.cjs → storage-BPRgh3DU.cjs} +136 -5
  48. package/dist/storage-BPRgh3DU.cjs.map +1 -0
  49. package/dist/{storage-nkGIjeXt.mjs → storage-DNj_I11J.mjs} +1 -1
  50. package/dist/storage-Dhst7BhI.mjs +272 -0
  51. package/dist/storage-Dhst7BhI.mjs.map +1 -0
  52. package/dist/{storage-UfyTn7Zm.cjs → storage-fOR8dMu5.cjs} +1 -1
  53. package/dist/{types-iFk5ms7y.d.mts → types-K2uQJ-FO.d.mts} +2 -2
  54. package/dist/{types-BgaMXsUa.d.cts.map → types-K2uQJ-FO.d.mts.map} +1 -1
  55. package/dist/{types-BgaMXsUa.d.cts → types-l53qUmGt.d.cts} +2 -2
  56. package/dist/{types-iFk5ms7y.d.mts.map → types-l53qUmGt.d.cts.map} +1 -1
  57. package/dist/workspace/index.cjs +19 -0
  58. package/dist/workspace/index.d.cts +3 -0
  59. package/dist/workspace/index.d.mts +3 -0
  60. package/dist/workspace/index.mjs +3 -0
  61. package/dist/workspace-CPLEZDZf.mjs +3788 -0
  62. package/dist/workspace-CPLEZDZf.mjs.map +1 -0
  63. package/dist/workspace-iWgBlX6h.cjs +3885 -0
  64. package/dist/workspace-iWgBlX6h.cjs.map +1 -0
  65. package/package.json +8 -3
  66. package/src/build/__tests__/workspace-build.spec.ts +215 -0
  67. package/src/build/index.ts +189 -1
  68. package/src/config.ts +71 -14
  69. package/src/deploy/__tests__/docker.spec.ts +1 -1
  70. package/src/deploy/__tests__/index.spec.ts +305 -1
  71. package/src/deploy/index.ts +426 -4
  72. package/src/deploy/types.ts +32 -0
  73. package/src/dev/__tests__/index.spec.ts +572 -1
  74. package/src/dev/index.ts +582 -2
  75. package/src/docker/__tests__/compose.spec.ts +425 -0
  76. package/src/docker/__tests__/templates.spec.ts +145 -0
  77. package/src/docker/compose.ts +248 -0
  78. package/src/docker/index.ts +159 -3
  79. package/src/docker/templates.ts +219 -4
  80. package/src/index.ts +24 -0
  81. package/src/init/__tests__/generators.spec.ts +17 -24
  82. package/src/init/__tests__/init.spec.ts +157 -5
  83. package/src/init/generators/auth.ts +220 -0
  84. package/src/init/generators/config.ts +61 -4
  85. package/src/init/generators/docker.ts +115 -8
  86. package/src/init/generators/env.ts +7 -127
  87. package/src/init/generators/index.ts +1 -0
  88. package/src/init/generators/models.ts +3 -1
  89. package/src/init/generators/monorepo.ts +154 -10
  90. package/src/init/generators/package.ts +5 -3
  91. package/src/init/generators/web.ts +213 -0
  92. package/src/init/index.ts +290 -58
  93. package/src/init/templates/api.ts +38 -29
  94. package/src/init/templates/index.ts +132 -4
  95. package/src/init/templates/minimal.ts +33 -35
  96. package/src/init/templates/serverless.ts +16 -19
  97. package/src/init/templates/worker.ts +50 -25
  98. package/src/init/versions.ts +47 -0
  99. package/src/secrets/keystore.ts +144 -0
  100. package/src/secrets/storage.ts +109 -6
  101. package/src/test/index.ts +97 -0
  102. package/src/workspace/__tests__/client-generator.spec.ts +357 -0
  103. package/src/workspace/__tests__/index.spec.ts +543 -0
  104. package/src/workspace/__tests__/schema.spec.ts +519 -0
  105. package/src/workspace/__tests__/type-inference.spec.ts +251 -0
  106. package/src/workspace/client-generator.ts +307 -0
  107. package/src/workspace/index.ts +372 -0
  108. package/src/workspace/schema.ts +368 -0
  109. package/src/workspace/types.ts +336 -0
  110. package/tsconfig.tsbuildinfo +1 -1
  111. package/tsdown.config.ts +1 -0
  112. package/dist/config-AmInkU7k.cjs.map +0 -1
  113. package/dist/config-DYULeEv8.mjs.map +0 -1
  114. package/dist/dokploy-api-B7KxOQr3.cjs +0 -3
  115. package/dist/dokploy-api-DHvfmWbi.mjs +0 -3
  116. package/dist/storage-BaOP55oq.mjs +0 -147
  117. package/dist/storage-BaOP55oq.mjs.map +0 -1
  118. package/dist/storage-Bn3K9Ccu.cjs.map +0 -1
@@ -0,0 +1,519 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import {
3
+ formatValidationErrors,
4
+ getDeployTargetError,
5
+ isDeployTargetSupported,
6
+ isPhase2DeployTarget,
7
+ safeValidateWorkspaceConfig,
8
+ validateWorkspaceConfig,
9
+ } from '../schema.ts';
10
+
11
+ describe('WorkspaceConfigSchema', () => {
12
+ describe('validateWorkspaceConfig', () => {
13
+ it('should validate a minimal valid workspace config', () => {
14
+ const config = {
15
+ apps: {
16
+ api: {
17
+ type: 'backend' as const,
18
+ path: 'apps/api',
19
+ port: 3000,
20
+ routes: './src/endpoints/**/*.ts',
21
+ },
22
+ },
23
+ };
24
+
25
+ const result = validateWorkspaceConfig(config);
26
+
27
+ expect(result.apps.api).toMatchObject({
28
+ type: 'backend',
29
+ path: 'apps/api',
30
+ port: 3000,
31
+ routes: './src/endpoints/**/*.ts',
32
+ });
33
+ });
34
+
35
+ it('should validate a complete workspace config', () => {
36
+ const config = {
37
+ name: 'my-saas',
38
+ apps: {
39
+ api: {
40
+ type: 'backend' as const,
41
+ path: 'apps/api',
42
+ port: 3000,
43
+ routes: './src/endpoints/**/*.ts',
44
+ envParser: './src/config/env',
45
+ logger: './src/logger',
46
+ telescope: true,
47
+ openapi: { enabled: true, title: 'My API' },
48
+ },
49
+ web: {
50
+ type: 'frontend' as const,
51
+ path: 'apps/web',
52
+ port: 3001,
53
+ framework: 'nextjs' as const,
54
+ dependencies: ['api'],
55
+ client: { output: './src/api' },
56
+ },
57
+ },
58
+ shared: {
59
+ packages: ['packages/*'],
60
+ models: { path: 'packages/models', schema: 'zod' as const },
61
+ },
62
+ deploy: {
63
+ default: 'dokploy' as const,
64
+ dokploy: {
65
+ endpoint: 'https://dokploy.example.com',
66
+ projectId: 'proj-123',
67
+ },
68
+ },
69
+ services: {
70
+ db: true,
71
+ cache: { version: '7.2', image: 'redis:7.2-alpine' },
72
+ mail: { smtp: { host: 'smtp.example.com', port: 587 } },
73
+ },
74
+ secrets: {
75
+ enabled: true,
76
+ algorithm: 'aes-256-gcm',
77
+ kdf: 'scrypt' as const,
78
+ },
79
+ };
80
+
81
+ const result = validateWorkspaceConfig(config);
82
+
83
+ expect(result.name).toBe('my-saas');
84
+ expect(result.apps.api.type).toBe('backend');
85
+ expect(result.apps.web.type).toBe('frontend');
86
+ expect(result.apps.web.dependencies).toEqual(['api']);
87
+ expect(result.shared?.packages).toEqual(['packages/*']);
88
+ expect(result.deploy?.default).toBe('dokploy');
89
+ expect(result.services?.db).toBe(true);
90
+ });
91
+
92
+ it('should default app type to backend', () => {
93
+ const config = {
94
+ apps: {
95
+ api: {
96
+ path: 'apps/api',
97
+ port: 3000,
98
+ routes: './src/endpoints/**/*.ts',
99
+ },
100
+ },
101
+ };
102
+
103
+ const result = validateWorkspaceConfig(config);
104
+
105
+ expect(result.apps.api.type).toBe('backend');
106
+ });
107
+
108
+ it('should accept array of route globs', () => {
109
+ const config = {
110
+ apps: {
111
+ api: {
112
+ type: 'backend' as const,
113
+ path: 'apps/api',
114
+ port: 3000,
115
+ routes: ['./src/endpoints/**/*.ts', './src/routes/**/*.ts'],
116
+ },
117
+ },
118
+ };
119
+
120
+ const result = validateWorkspaceConfig(config);
121
+
122
+ expect(result.apps.api.routes).toEqual([
123
+ './src/endpoints/**/*.ts',
124
+ './src/routes/**/*.ts',
125
+ ]);
126
+ });
127
+ });
128
+
129
+ describe('validation errors', () => {
130
+ it('should reject config without apps', () => {
131
+ const config = { name: 'test' };
132
+
133
+ expect(() => validateWorkspaceConfig(config)).toThrow();
134
+ });
135
+
136
+ it('should reject empty apps object', () => {
137
+ const config = { apps: {} };
138
+
139
+ const result = safeValidateWorkspaceConfig(config);
140
+
141
+ expect(result.success).toBe(false);
142
+ expect(result.error).toBeDefined();
143
+ });
144
+
145
+ it('should reject backend app without routes', () => {
146
+ const config = {
147
+ apps: {
148
+ api: {
149
+ type: 'backend' as const,
150
+ path: 'apps/api',
151
+ port: 3000,
152
+ // Missing routes
153
+ },
154
+ },
155
+ };
156
+
157
+ const result = safeValidateWorkspaceConfig(config);
158
+
159
+ expect(result.success).toBe(false);
160
+ });
161
+
162
+ it('should reject frontend app without framework', () => {
163
+ const config = {
164
+ apps: {
165
+ web: {
166
+ type: 'frontend' as const,
167
+ path: 'apps/web',
168
+ port: 3001,
169
+ // Missing framework
170
+ },
171
+ },
172
+ };
173
+
174
+ const result = safeValidateWorkspaceConfig(config);
175
+
176
+ expect(result.success).toBe(false);
177
+ });
178
+
179
+ it('should reject invalid port', () => {
180
+ const config = {
181
+ apps: {
182
+ api: {
183
+ type: 'backend' as const,
184
+ path: 'apps/api',
185
+ port: -1,
186
+ routes: './src/**/*.ts',
187
+ },
188
+ },
189
+ };
190
+
191
+ const result = safeValidateWorkspaceConfig(config);
192
+
193
+ expect(result.success).toBe(false);
194
+ });
195
+
196
+ it('should reject dependency referencing non-existent app', () => {
197
+ const config = {
198
+ apps: {
199
+ web: {
200
+ type: 'frontend' as const,
201
+ path: 'apps/web',
202
+ port: 3001,
203
+ framework: 'nextjs' as const,
204
+ dependencies: ['nonexistent'],
205
+ },
206
+ },
207
+ };
208
+
209
+ const result = safeValidateWorkspaceConfig(config);
210
+
211
+ expect(result.success).toBe(false);
212
+ });
213
+
214
+ it('should reject self-referential dependency', () => {
215
+ const config = {
216
+ apps: {
217
+ api: {
218
+ type: 'backend' as const,
219
+ path: 'apps/api',
220
+ port: 3000,
221
+ routes: './src/**/*.ts',
222
+ dependencies: ['api'],
223
+ },
224
+ },
225
+ };
226
+
227
+ const result = safeValidateWorkspaceConfig(config);
228
+
229
+ expect(result.success).toBe(false);
230
+ });
231
+
232
+ it('should reject circular dependencies', () => {
233
+ const config = {
234
+ apps: {
235
+ api: {
236
+ type: 'backend' as const,
237
+ path: 'apps/api',
238
+ port: 3000,
239
+ routes: './src/**/*.ts',
240
+ dependencies: ['worker'],
241
+ },
242
+ worker: {
243
+ type: 'backend' as const,
244
+ path: 'apps/worker',
245
+ port: 3001,
246
+ routes: './src/**/*.ts',
247
+ dependencies: ['api'],
248
+ },
249
+ },
250
+ };
251
+
252
+ const result = safeValidateWorkspaceConfig(config);
253
+
254
+ expect(result.success).toBe(false);
255
+ });
256
+
257
+ it('should reject invalid dokploy endpoint URL', () => {
258
+ const config = {
259
+ apps: {
260
+ api: {
261
+ type: 'backend' as const,
262
+ path: 'apps/api',
263
+ port: 3000,
264
+ routes: './src/**/*.ts',
265
+ },
266
+ },
267
+ deploy: {
268
+ dokploy: {
269
+ endpoint: 'not-a-url',
270
+ projectId: 'proj-123',
271
+ },
272
+ },
273
+ };
274
+
275
+ const result = safeValidateWorkspaceConfig(config);
276
+
277
+ expect(result.success).toBe(false);
278
+ });
279
+ });
280
+
281
+ describe('formatValidationErrors', () => {
282
+ it('should format errors with paths', () => {
283
+ const config = {
284
+ apps: {
285
+ api: {
286
+ type: 'backend' as const,
287
+ path: '',
288
+ port: 3000,
289
+ routes: './src/**/*.ts',
290
+ },
291
+ },
292
+ };
293
+
294
+ const result = safeValidateWorkspaceConfig(config);
295
+
296
+ expect(result.success).toBe(false);
297
+ if (result.error) {
298
+ const formatted = formatValidationErrors(result.error);
299
+ expect(formatted).toContain(
300
+ 'Workspace configuration validation failed',
301
+ );
302
+ }
303
+ });
304
+
305
+ it('should format root-level errors', () => {
306
+ const config = { apps: {} };
307
+
308
+ const result = safeValidateWorkspaceConfig(config);
309
+
310
+ expect(result.success).toBe(false);
311
+ if (result.error) {
312
+ const formatted = formatValidationErrors(result.error);
313
+ expect(formatted).toContain('At least one app must be defined');
314
+ }
315
+ });
316
+ });
317
+
318
+ describe('telescope configuration', () => {
319
+ it('should accept boolean telescope config', () => {
320
+ const config = {
321
+ apps: {
322
+ api: {
323
+ type: 'backend' as const,
324
+ path: 'apps/api',
325
+ port: 3000,
326
+ routes: './src/**/*.ts',
327
+ telescope: true,
328
+ },
329
+ },
330
+ };
331
+
332
+ const result = validateWorkspaceConfig(config);
333
+
334
+ expect(result.apps.api.telescope).toBe(true);
335
+ });
336
+
337
+ it('should accept string telescope config', () => {
338
+ const config = {
339
+ apps: {
340
+ api: {
341
+ type: 'backend' as const,
342
+ path: 'apps/api',
343
+ port: 3000,
344
+ routes: './src/**/*.ts',
345
+ telescope: './src/telescope',
346
+ },
347
+ },
348
+ };
349
+
350
+ const result = validateWorkspaceConfig(config);
351
+
352
+ expect(result.apps.api.telescope).toBe('./src/telescope');
353
+ });
354
+
355
+ it('should accept object telescope config', () => {
356
+ const config = {
357
+ apps: {
358
+ api: {
359
+ type: 'backend' as const,
360
+ path: 'apps/api',
361
+ port: 3000,
362
+ routes: './src/**/*.ts',
363
+ telescope: {
364
+ enabled: true,
365
+ port: 9000,
366
+ path: '/__debug',
367
+ ignore: ['/health'],
368
+ recordBody: false,
369
+ maxEntries: 500,
370
+ websocket: true,
371
+ },
372
+ },
373
+ },
374
+ };
375
+
376
+ const result = validateWorkspaceConfig(config);
377
+
378
+ expect(result.apps.api.telescope).toEqual({
379
+ enabled: true,
380
+ port: 9000,
381
+ path: '/__debug',
382
+ ignore: ['/health'],
383
+ recordBody: false,
384
+ maxEntries: 500,
385
+ websocket: true,
386
+ });
387
+ });
388
+ });
389
+
390
+ describe('deploy configuration', () => {
391
+ it('should accept dokploy as per-app deploy target', () => {
392
+ const config = {
393
+ apps: {
394
+ api: {
395
+ type: 'backend' as const,
396
+ path: 'apps/api',
397
+ port: 3000,
398
+ routes: './src/**/*.ts',
399
+ deploy: 'dokploy' as const,
400
+ },
401
+ },
402
+ deploy: {
403
+ default: 'dokploy' as const,
404
+ },
405
+ };
406
+
407
+ const result = validateWorkspaceConfig(config);
408
+
409
+ expect(result.apps.api.deploy).toBe('dokploy');
410
+ expect(result.deploy?.default).toBe('dokploy');
411
+ });
412
+
413
+ it('should reject Phase 2 deploy target in deploy.default', () => {
414
+ const config = {
415
+ apps: {
416
+ api: {
417
+ type: 'backend' as const,
418
+ path: 'apps/api',
419
+ port: 3000,
420
+ routes: './src/**/*.ts',
421
+ },
422
+ },
423
+ deploy: {
424
+ default: 'vercel' as const,
425
+ },
426
+ };
427
+
428
+ const result = safeValidateWorkspaceConfig(config);
429
+
430
+ expect(result.success).toBe(false);
431
+ expect(result.error).toBeDefined();
432
+ if (result.error) {
433
+ const formatted = formatValidationErrors(result.error);
434
+ expect(formatted).toContain('coming in Phase 2');
435
+ }
436
+ });
437
+
438
+ it('should reject Phase 2 deploy target in per-app deploy', () => {
439
+ const config = {
440
+ apps: {
441
+ api: {
442
+ type: 'backend' as const,
443
+ path: 'apps/api',
444
+ port: 3000,
445
+ routes: './src/**/*.ts',
446
+ deploy: 'cloudflare' as const,
447
+ },
448
+ },
449
+ };
450
+
451
+ const result = safeValidateWorkspaceConfig(config);
452
+
453
+ expect(result.success).toBe(false);
454
+ expect(result.error).toBeDefined();
455
+ if (result.error) {
456
+ const formatted = formatValidationErrors(result.error);
457
+ expect(formatted).toContain('coming in Phase 2');
458
+ expect(formatted).toContain('api');
459
+ }
460
+ });
461
+
462
+ it('should reject unknown deploy target', () => {
463
+ const config = {
464
+ apps: {
465
+ api: {
466
+ type: 'backend' as const,
467
+ path: 'apps/api',
468
+ port: 3000,
469
+ routes: './src/**/*.ts',
470
+ deploy: 'kubernetes' as const,
471
+ },
472
+ },
473
+ };
474
+
475
+ const result = safeValidateWorkspaceConfig(config);
476
+
477
+ expect(result.success).toBe(false);
478
+ });
479
+ });
480
+
481
+ describe('deploy target helpers', () => {
482
+ it('isDeployTargetSupported should return true for dokploy', () => {
483
+ expect(isDeployTargetSupported('dokploy')).toBe(true);
484
+ });
485
+
486
+ it('isDeployTargetSupported should return false for Phase 2 targets', () => {
487
+ expect(isDeployTargetSupported('vercel')).toBe(false);
488
+ expect(isDeployTargetSupported('cloudflare')).toBe(false);
489
+ });
490
+
491
+ it('isPhase2DeployTarget should identify Phase 2 targets', () => {
492
+ expect(isPhase2DeployTarget('vercel')).toBe(true);
493
+ expect(isPhase2DeployTarget('cloudflare')).toBe(true);
494
+ expect(isPhase2DeployTarget('dokploy')).toBe(false);
495
+ expect(isPhase2DeployTarget('unknown')).toBe(false);
496
+ });
497
+
498
+ it('getDeployTargetError should return Phase 2 message', () => {
499
+ const error = getDeployTargetError('vercel');
500
+ expect(error).toContain('coming in Phase 2');
501
+ expect(error).toContain('vercel');
502
+ expect(error).toContain('dokploy');
503
+ });
504
+
505
+ it('getDeployTargetError should include app name when provided', () => {
506
+ const error = getDeployTargetError('cloudflare', 'web');
507
+ expect(error).toContain('coming in Phase 2');
508
+ expect(error).toContain('cloudflare');
509
+ expect(error).toContain('web');
510
+ });
511
+
512
+ it('getDeployTargetError should handle unknown targets', () => {
513
+ const error = getDeployTargetError('kubernetes');
514
+ expect(error).toContain('Unknown deploy target');
515
+ expect(error).toContain('kubernetes');
516
+ expect(error).toContain('Coming in Phase 2');
517
+ });
518
+ });
519
+ });