@geekmidas/cli 1.10.29 → 1.10.31

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 (138) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/{CachedStateProvider-37pG1HBx.cjs → CachedStateProvider-3couMAQk.cjs} +1 -1
  3. package/dist/CachedStateProvider-C4QqvlPE.mjs +3 -0
  4. package/dist/{CachedStateProvider-C8A-cCm5.mjs → CachedStateProvider-CQr31ups.mjs} +1 -1
  5. package/dist/{CachedStateProvider-C8A-cCm5.mjs.map → CachedStateProvider-CQr31ups.mjs.map} +1 -1
  6. package/dist/{CachedStateProvider-CxsXOKkg.cjs → CachedStateProvider-Cyn17x3x.cjs} +1 -1
  7. package/dist/{CachedStateProvider-CxsXOKkg.cjs.map → CachedStateProvider-Cyn17x3x.cjs.map} +1 -1
  8. package/dist/{HostingerProvider-5KYmwoK2.cjs → HostingerProvider-C5oLa7N7.cjs} +1 -1
  9. package/dist/{HostingerProvider-5KYmwoK2.cjs.map → HostingerProvider-C5oLa7N7.cjs.map} +1 -1
  10. package/dist/{HostingerProvider-ANWchdiK.mjs → HostingerProvider-YvCg3Mus.mjs} +1 -1
  11. package/dist/{HostingerProvider-ANWchdiK.mjs.map → HostingerProvider-YvCg3Mus.mjs.map} +1 -1
  12. package/dist/{LocalStateProvider-Dp0KkRcw.mjs → LocalStateProvider-BciOhGR0.mjs} +1 -1
  13. package/dist/{LocalStateProvider-Dp0KkRcw.mjs.map → LocalStateProvider-BciOhGR0.mjs.map} +1 -1
  14. package/dist/{LocalStateProvider-CLifRC0Y.cjs → LocalStateProvider-DI6n11UM.cjs} +1 -1
  15. package/dist/{LocalStateProvider-CLifRC0Y.cjs.map → LocalStateProvider-DI6n11UM.cjs.map} +1 -1
  16. package/dist/{Route53Provider-QoPgcXxn.mjs → Route53Provider-CYlDOrYO.mjs} +1 -1
  17. package/dist/{Route53Provider-QoPgcXxn.mjs.map → Route53Provider-CYlDOrYO.mjs.map} +1 -1
  18. package/dist/{Route53Provider-owQQ4pn6.cjs → Route53Provider-DISZ6E8f.cjs} +1 -1
  19. package/dist/{Route53Provider-owQQ4pn6.cjs.map → Route53Provider-DISZ6E8f.cjs.map} +1 -1
  20. package/dist/{SSMStateProvider-CT8tjl9o.cjs → SSMStateProvider-BljH5efc.cjs} +1 -1
  21. package/dist/{SSMStateProvider-CT8tjl9o.cjs.map → SSMStateProvider-BljH5efc.cjs.map} +1 -1
  22. package/dist/{SSMStateProvider-CksOTB8M.mjs → SSMStateProvider-DXT1dIdI.mjs} +1 -1
  23. package/dist/{SSMStateProvider-CksOTB8M.mjs.map → SSMStateProvider-DXT1dIdI.mjs.map} +1 -1
  24. package/dist/{backup-provisioner-BEXoHTuC.mjs → backup-provisioner-CCUcj28C.mjs} +1 -1
  25. package/dist/{backup-provisioner-BEXoHTuC.mjs.map → backup-provisioner-CCUcj28C.mjs.map} +1 -1
  26. package/dist/{backup-provisioner-C4noe75O.cjs → backup-provisioner-CpirEvO1.cjs} +1 -1
  27. package/dist/{backup-provisioner-C4noe75O.cjs.map → backup-provisioner-CpirEvO1.cjs.map} +1 -1
  28. package/dist/{bundler-BG6P249p.cjs → bundler-CLOenWgi.cjs} +2 -2
  29. package/dist/{bundler-BG6P249p.cjs.map → bundler-CLOenWgi.cjs.map} +1 -1
  30. package/dist/{bundler-c6QvGUBM.mjs → bundler-E5VcXsPH.mjs} +2 -2
  31. package/dist/{bundler-c6QvGUBM.mjs.map → bundler-E5VcXsPH.mjs.map} +1 -1
  32. package/dist/client-generator-CUVVtAOo.mjs +23 -0
  33. package/dist/client-generator-CUVVtAOo.mjs.map +1 -0
  34. package/dist/client-generator-D-T4ypQo.cjs +35 -0
  35. package/dist/client-generator-D-T4ypQo.cjs.map +1 -0
  36. package/dist/{config-jsRYHOHU.mjs → config-B62g483e.mjs} +2 -2
  37. package/dist/{config-jsRYHOHU.mjs.map → config-B62g483e.mjs.map} +1 -1
  38. package/dist/{config-D3ORuiUs.cjs → config-Cuo8vFsp.cjs} +2 -2
  39. package/dist/{config-D3ORuiUs.cjs.map → config-Cuo8vFsp.cjs.map} +1 -1
  40. package/dist/config.cjs +2 -2
  41. package/dist/config.d.cts +1 -1
  42. package/dist/config.d.mts +2 -2
  43. package/dist/config.mjs +2 -2
  44. package/dist/deploy/sniffer-worker.cjs +2 -0
  45. package/dist/deploy/sniffer-worker.cjs.map +1 -1
  46. package/dist/deploy/sniffer-worker.mjs +2 -0
  47. package/dist/deploy/sniffer-worker.mjs.map +1 -1
  48. package/dist/dokploy-api-BPYkG-ZZ.cjs +3 -0
  49. package/dist/dokploy-api-BiSzWM4v.mjs +3 -0
  50. package/dist/{dokploy-api-Dvyq-LsM.cjs → dokploy-api-CqOIKHme.cjs} +1 -1
  51. package/dist/{dokploy-api-Dvyq-LsM.cjs.map → dokploy-api-CqOIKHme.cjs.map} +1 -1
  52. package/dist/{dokploy-api-BIT0683o.mjs → dokploy-api-sfZFlWuE.mjs} +1 -1
  53. package/dist/{dokploy-api-BIT0683o.mjs.map → dokploy-api-sfZFlWuE.mjs.map} +1 -1
  54. package/dist/{encryption-CipsS0KW.cjs → encryption-BVS7Vmzs.cjs} +1 -1
  55. package/dist/encryption-Bd_m4q3g.mjs +3 -0
  56. package/dist/{encryption-DSkIlEZ6.mjs → encryption-CO5Q4Bhf.mjs} +1 -1
  57. package/dist/{encryption-DSkIlEZ6.mjs.map → encryption-CO5Q4Bhf.mjs.map} +1 -1
  58. package/dist/{encryption-DgKS-Dv9.cjs → encryption-MfXAiFTm.cjs} +1 -1
  59. package/dist/{encryption-DgKS-Dv9.cjs.map → encryption-MfXAiFTm.cjs.map} +1 -1
  60. package/dist/{index-CY-ieuRp.d.mts → index-C-_rW4KF.d.mts} +3 -33
  61. package/dist/index-C-_rW4KF.d.mts.map +1 -0
  62. package/dist/{index-BkibYzso.d.cts → index-C3t5VL4R.d.cts} +2 -32
  63. package/dist/index-C3t5VL4R.d.cts.map +1 -0
  64. package/dist/index.cjs +102 -380
  65. package/dist/index.cjs.map +1 -1
  66. package/dist/index.mjs +78 -356
  67. package/dist/index.mjs.map +1 -1
  68. package/dist/{openapi-CQCoLuzh.mjs → openapi-DHq5r17U.mjs} +7 -42
  69. package/dist/openapi-DHq5r17U.mjs.map +1 -0
  70. package/dist/{openapi-react-query-C4UdILaI.mjs → openapi-react-query-COpH7Mmz.mjs} +1 -1
  71. package/dist/{openapi-react-query-C4UdILaI.mjs.map → openapi-react-query-COpH7Mmz.mjs.map} +1 -1
  72. package/dist/{openapi-react-query-DYbBq-WJ.cjs → openapi-react-query-CxyQTTdF.cjs} +1 -1
  73. package/dist/{openapi-react-query-DYbBq-WJ.cjs.map → openapi-react-query-CxyQTTdF.cjs.map} +1 -1
  74. package/dist/openapi-react-query.cjs +1 -1
  75. package/dist/openapi-react-query.mjs +1 -1
  76. package/dist/{openapi-CBU4TEi-.cjs → openapi-w-_smBTO.cjs} +7 -54
  77. package/dist/openapi-w-_smBTO.cjs.map +1 -0
  78. package/dist/openapi.cjs +4 -3
  79. package/dist/openapi.d.mts +1 -1
  80. package/dist/openapi.mjs +4 -3
  81. package/dist/{reconcile-CZtzdrnc.cjs → reconcile-C8KIy4Uy.cjs} +1 -1
  82. package/dist/{reconcile-CZtzdrnc.cjs.map → reconcile-C8KIy4Uy.cjs.map} +1 -1
  83. package/dist/{reconcile-DAAKa0YR.mjs → reconcile-D423q2QW.mjs} +1 -1
  84. package/dist/{reconcile-DAAKa0YR.mjs.map → reconcile-D423q2QW.mjs.map} +1 -1
  85. package/dist/sniffer-BqSVVrWA.mjs +356 -0
  86. package/dist/sniffer-BqSVVrWA.mjs.map +1 -0
  87. package/dist/sniffer-CBhzByiY.cjs +386 -0
  88. package/dist/sniffer-CBhzByiY.cjs.map +1 -0
  89. package/dist/sniffer-Cpq_cbyz.mjs +4 -0
  90. package/dist/sniffer-eZ13G10M.cjs +4 -0
  91. package/dist/sync-B2CN_KS5.cjs +4 -0
  92. package/dist/{sync-DGXXSk2v.cjs → sync-BWY6yoUG.cjs} +1 -1
  93. package/dist/{sync-DGXXSk2v.cjs.map → sync-BWY6yoUG.cjs.map} +1 -1
  94. package/dist/{sync-D_NowTkZ.mjs → sync-Dc-QA4Mx.mjs} +1 -1
  95. package/dist/{sync-D_NowTkZ.mjs.map → sync-Dc-QA4Mx.mjs.map} +1 -1
  96. package/dist/{sync-COnAugP-.mjs → sync-fzT5WX_y.mjs} +1 -1
  97. package/dist/{types-OszPdw9m.d.mts → types-BqAbYt5a.d.mts} +1 -1
  98. package/dist/{types-OszPdw9m.d.mts.map → types-BqAbYt5a.d.mts.map} +1 -1
  99. package/dist/workspace/index.cjs +1 -1
  100. package/dist/workspace/index.d.cts +2 -2
  101. package/dist/workspace/index.d.mts +3 -3
  102. package/dist/workspace/index.mjs +1 -1
  103. package/dist/{workspace-D4z4A4cq.mjs → workspace-Bi4X7Yzy.mjs} +1 -6
  104. package/dist/{workspace-D4z4A4cq.mjs.map → workspace-Bi4X7Yzy.mjs.map} +1 -1
  105. package/dist/{workspace-4SP3Gx4Y.cjs → workspace-CGYykWfn.cjs} +1 -6
  106. package/dist/{workspace-4SP3Gx4Y.cjs.map → workspace-CGYykWfn.cjs.map} +1 -1
  107. package/package.json +3 -3
  108. package/src/__tests__/openapi.spec.ts +0 -315
  109. package/src/build/__tests__/manifests.spec.ts +66 -0
  110. package/src/build/index.ts +54 -1
  111. package/src/deploy/__tests__/__fixtures__/env-parsers/optional-env-parser.ts +21 -0
  112. package/src/deploy/__tests__/sniffer.spec.ts +123 -0
  113. package/src/deploy/sniffer-worker.ts +2 -1
  114. package/src/deploy/sniffer.ts +86 -12
  115. package/src/docker/compose.ts +2 -0
  116. package/src/generators/EndpointGenerator.ts +1 -1
  117. package/src/index.ts +8 -0
  118. package/src/init/generators/monorepo.ts +15 -1
  119. package/src/init/generators/package.ts +2 -0
  120. package/src/init/index.ts +2 -4
  121. package/src/init/templates/index.ts +5 -0
  122. package/src/init/versions.ts +1 -1
  123. package/src/openapi.ts +1 -35
  124. package/src/types.ts +7 -0
  125. package/src/workspace/__tests__/client-generator.spec.ts +1 -485
  126. package/src/workspace/client-generator.ts +1 -145
  127. package/src/workspace/index.ts +0 -1
  128. package/src/workspace/schema.ts +0 -9
  129. package/src/workspace/types.ts +0 -29
  130. package/dist/CachedStateProvider-CC0DyO09.mjs +0 -3
  131. package/dist/dokploy-api-BOx71j6F.cjs +0 -3
  132. package/dist/dokploy-api-BZGk-lkD.mjs +0 -3
  133. package/dist/encryption-Db2zvGqM.mjs +0 -3
  134. package/dist/index-BkibYzso.d.cts.map +0 -1
  135. package/dist/index-CY-ieuRp.d.mts.map +0 -1
  136. package/dist/openapi-CBU4TEi-.cjs.map +0 -1
  137. package/dist/openapi-CQCoLuzh.mjs.map +0 -1
  138. package/dist/sync-D1Pa30oV.cjs +0 -4
@@ -1,16 +1,5 @@
1
+ import { describe, expect, it } from 'vitest';
1
2
  import {
2
- existsSync,
3
- mkdirSync,
4
- readFileSync,
5
- rmSync,
6
- writeFileSync,
7
- } from 'node:fs';
8
- import { tmpdir } from 'node:os';
9
- import { join } from 'node:path';
10
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
11
- import {
12
- copyAllClients,
13
- copyClientToFrontends,
14
3
  getBackendDependencies,
15
4
  getBackendOpenApiPath,
16
5
  getDependentFrontends,
@@ -334,477 +323,4 @@ describe('Client Generator', () => {
334
323
  expect(path).toBeNull();
335
324
  });
336
325
  });
337
-
338
- describe('copyClientToFrontends', () => {
339
- let testDir: string;
340
-
341
- beforeEach(() => {
342
- testDir = join(
343
- tmpdir(),
344
- `gkm-client-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
345
- );
346
- mkdirSync(testDir, { recursive: true });
347
- });
348
-
349
- afterEach(() => {
350
- if (existsSync(testDir)) {
351
- rmSync(testDir, { recursive: true, force: true });
352
- }
353
- });
354
-
355
- function createOpenApiFile(
356
- appDir: string,
357
- endpoints: Array<{ method: string; path: string }>,
358
- ): void {
359
- const endpointAuthEntries = endpoints
360
- .map((ep) => ` '${ep.method.toUpperCase()} ${ep.path}': null,`)
361
- .join('\n');
362
-
363
- const content = `// Auto-generated by @geekmidas/cli - DO NOT EDIT
364
-
365
- export const endpointAuth = {
366
- ${endpointAuthEntries}
367
- } as const;
368
-
369
- export const paths = {};
370
- export function createApi() { return {}; }
371
- `;
372
-
373
- const gkmDir = join(appDir, '.gkm');
374
- mkdirSync(gkmDir, { recursive: true });
375
- writeFileSync(join(gkmDir, 'openapi.ts'), content);
376
- }
377
-
378
- it('should copy client to dependent frontends', async () => {
379
- const apiDir = join(testDir, 'apps/api');
380
- const webDir = join(testDir, 'apps/web');
381
- mkdirSync(webDir, { recursive: true });
382
-
383
- createOpenApiFile(apiDir, [
384
- { method: 'GET', path: '/users' },
385
- { method: 'POST', path: '/users' },
386
- ]);
387
-
388
- const workspace: NormalizedWorkspace = {
389
- name: 'test',
390
- root: testDir,
391
- apps: {
392
- api: {
393
- type: 'backend',
394
- path: 'apps/api',
395
- port: 3000,
396
- dependencies: [],
397
- routes: './src/endpoints/**/*.ts',
398
- resolvedDeployTarget: 'dokploy',
399
- },
400
- web: {
401
- type: 'frontend',
402
- path: 'apps/web',
403
- port: 3001,
404
- dependencies: ['api'],
405
- client: { output: 'src/api' },
406
- resolvedDeployTarget: 'dokploy',
407
- },
408
- },
409
- services: {},
410
- deploy: { default: 'dokploy' },
411
- shared: { packages: [] },
412
- secrets: {},
413
- };
414
-
415
- const results = await copyClientToFrontends(workspace, 'api', {
416
- silent: true,
417
- });
418
-
419
- expect(results).toHaveLength(1);
420
- expect(results[0]?.success).toBe(true);
421
- expect(results[0]?.frontendApp).toBe('web');
422
- expect(results[0]?.backendApp).toBe('api');
423
- expect(results[0]?.endpointCount).toBe(2);
424
- expect(existsSync(join(webDir, 'src/api/api.ts'))).toBe(true);
425
- });
426
-
427
- it('should skip frontends without client.output configured', async () => {
428
- const apiDir = join(testDir, 'apps/api');
429
- const webDir = join(testDir, 'apps/web');
430
- mkdirSync(webDir, { recursive: true });
431
-
432
- createOpenApiFile(apiDir, [{ method: 'GET', path: '/users' }]);
433
-
434
- const workspace: NormalizedWorkspace = {
435
- name: 'test',
436
- root: testDir,
437
- apps: {
438
- api: {
439
- type: 'backend',
440
- path: 'apps/api',
441
- port: 3000,
442
- dependencies: [],
443
- routes: './src/endpoints/**/*.ts',
444
- resolvedDeployTarget: 'dokploy',
445
- },
446
- web: {
447
- type: 'frontend',
448
- path: 'apps/web',
449
- port: 3001,
450
- dependencies: ['api'],
451
- // No client.output configured
452
- resolvedDeployTarget: 'dokploy',
453
- },
454
- },
455
- services: {},
456
- deploy: { default: 'dokploy' },
457
- shared: { packages: [] },
458
- secrets: {},
459
- };
460
-
461
- const results = await copyClientToFrontends(workspace, 'api', {
462
- silent: true,
463
- });
464
-
465
- expect(results).toHaveLength(0);
466
- });
467
-
468
- it('should return empty results for non-backend app', async () => {
469
- const workspace: NormalizedWorkspace = {
470
- name: 'test',
471
- root: testDir,
472
- apps: {
473
- web: {
474
- type: 'frontend',
475
- path: 'apps/web',
476
- port: 3001,
477
- dependencies: [],
478
- resolvedDeployTarget: 'dokploy',
479
- },
480
- },
481
- services: {},
482
- deploy: { default: 'dokploy' },
483
- shared: { packages: [] },
484
- secrets: {},
485
- };
486
-
487
- const results = await copyClientToFrontends(workspace, 'web', {
488
- silent: true,
489
- });
490
-
491
- expect(results).toEqual([]);
492
- });
493
-
494
- it('should return empty results when openapi file does not exist', async () => {
495
- const apiDir = join(testDir, 'apps/api');
496
- const webDir = join(testDir, 'apps/web');
497
- mkdirSync(apiDir, { recursive: true });
498
- mkdirSync(webDir, { recursive: true });
499
- // Don't create openapi file
500
-
501
- const workspace: NormalizedWorkspace = {
502
- name: 'test',
503
- root: testDir,
504
- apps: {
505
- api: {
506
- type: 'backend',
507
- path: 'apps/api',
508
- port: 3000,
509
- dependencies: [],
510
- routes: './src/endpoints/**/*.ts',
511
- resolvedDeployTarget: 'dokploy',
512
- },
513
- web: {
514
- type: 'frontend',
515
- path: 'apps/web',
516
- port: 3001,
517
- dependencies: ['api'],
518
- client: { output: 'src/api' },
519
- resolvedDeployTarget: 'dokploy',
520
- },
521
- },
522
- services: {},
523
- deploy: { default: 'dokploy' },
524
- shared: { packages: [] },
525
- secrets: {},
526
- };
527
-
528
- const results = await copyClientToFrontends(workspace, 'api', {
529
- silent: true,
530
- });
531
-
532
- expect(results).toEqual([]);
533
- });
534
-
535
- it('should copy to multiple frontends', async () => {
536
- const apiDir = join(testDir, 'apps/api');
537
- const webDir = join(testDir, 'apps/web');
538
- const adminDir = join(testDir, 'apps/admin');
539
- mkdirSync(webDir, { recursive: true });
540
- mkdirSync(adminDir, { recursive: true });
541
-
542
- createOpenApiFile(apiDir, [{ method: 'GET', path: '/users' }]);
543
-
544
- const workspace: NormalizedWorkspace = {
545
- name: 'test',
546
- root: testDir,
547
- apps: {
548
- api: {
549
- type: 'backend',
550
- path: 'apps/api',
551
- port: 3000,
552
- dependencies: [],
553
- routes: './src/endpoints/**/*.ts',
554
- resolvedDeployTarget: 'dokploy',
555
- },
556
- web: {
557
- type: 'frontend',
558
- path: 'apps/web',
559
- port: 3001,
560
- dependencies: ['api'],
561
- client: { output: 'src/api' },
562
- resolvedDeployTarget: 'dokploy',
563
- },
564
- admin: {
565
- type: 'frontend',
566
- path: 'apps/admin',
567
- port: 3002,
568
- dependencies: ['api'],
569
- client: { output: 'lib/client' },
570
- resolvedDeployTarget: 'dokploy',
571
- },
572
- },
573
- services: {},
574
- deploy: { default: 'dokploy' },
575
- shared: { packages: [] },
576
- secrets: {},
577
- };
578
-
579
- const results = await copyClientToFrontends(workspace, 'api', {
580
- silent: true,
581
- });
582
-
583
- expect(results).toHaveLength(2);
584
- expect(results.every((r) => r.success)).toBe(true);
585
- expect(existsSync(join(webDir, 'src/api/api.ts'))).toBe(true);
586
- expect(existsSync(join(adminDir, 'lib/client/api.ts'))).toBe(true);
587
- });
588
-
589
- it('should add header comment to copied file', async () => {
590
- const apiDir = join(testDir, 'apps/api');
591
- const webDir = join(testDir, 'apps/web');
592
- mkdirSync(webDir, { recursive: true });
593
-
594
- createOpenApiFile(apiDir, [{ method: 'GET', path: '/users' }]);
595
-
596
- const workspace: NormalizedWorkspace = {
597
- name: 'test',
598
- root: testDir,
599
- apps: {
600
- api: {
601
- type: 'backend',
602
- path: 'apps/api',
603
- port: 3000,
604
- dependencies: [],
605
- routes: './src/endpoints/**/*.ts',
606
- resolvedDeployTarget: 'dokploy',
607
- },
608
- web: {
609
- type: 'frontend',
610
- path: 'apps/web',
611
- port: 3001,
612
- dependencies: ['api'],
613
- client: { output: 'src/api' },
614
- resolvedDeployTarget: 'dokploy',
615
- },
616
- },
617
- services: {},
618
- deploy: { default: 'dokploy' },
619
- shared: { packages: [] },
620
- secrets: {},
621
- };
622
-
623
- await copyClientToFrontends(workspace, 'api', { silent: true });
624
-
625
- const content = readFileSync(join(webDir, 'src/api/api.ts'), 'utf-8');
626
- expect(content).toContain('Auto-generated API client for api');
627
- expect(content).toContain('DO NOT EDIT');
628
- });
629
-
630
- it('should use backend name in filename when frontend has multiple backends', async () => {
631
- const apiDir = join(testDir, 'apps/api');
632
- const authDir = join(testDir, 'apps/auth');
633
- const webDir = join(testDir, 'apps/web');
634
- mkdirSync(webDir, { recursive: true });
635
-
636
- createOpenApiFile(apiDir, [{ method: 'GET', path: '/users' }]);
637
- createOpenApiFile(authDir, [{ method: 'POST', path: '/login' }]);
638
-
639
- const workspace: NormalizedWorkspace = {
640
- name: 'test',
641
- root: testDir,
642
- apps: {
643
- api: {
644
- type: 'backend',
645
- path: 'apps/api',
646
- port: 3000,
647
- dependencies: [],
648
- routes: './src/endpoints/**/*.ts',
649
- resolvedDeployTarget: 'dokploy',
650
- },
651
- auth: {
652
- type: 'backend',
653
- path: 'apps/auth',
654
- port: 3001,
655
- dependencies: [],
656
- routes: './src/endpoints/**/*.ts',
657
- resolvedDeployTarget: 'dokploy',
658
- },
659
- web: {
660
- type: 'frontend',
661
- path: 'apps/web',
662
- port: 3002,
663
- dependencies: ['api', 'auth'], // Multiple backends
664
- client: { output: 'src/api' },
665
- resolvedDeployTarget: 'dokploy',
666
- },
667
- },
668
- services: {},
669
- deploy: { default: 'dokploy' },
670
- shared: { packages: [] },
671
- secrets: {},
672
- };
673
-
674
- await copyClientToFrontends(workspace, 'api', { silent: true });
675
- await copyClientToFrontends(workspace, 'auth', { silent: true });
676
-
677
- // Should use {backend}.ts naming
678
- expect(existsSync(join(webDir, 'src/api/api.ts'))).toBe(true);
679
- expect(existsSync(join(webDir, 'src/api/auth.ts'))).toBe(true);
680
- });
681
- });
682
-
683
- describe('copyAllClients', () => {
684
- let testDir: string;
685
-
686
- beforeEach(() => {
687
- testDir = join(
688
- tmpdir(),
689
- `gkm-client-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
690
- );
691
- mkdirSync(testDir, { recursive: true });
692
- });
693
-
694
- afterEach(() => {
695
- if (existsSync(testDir)) {
696
- rmSync(testDir, { recursive: true, force: true });
697
- }
698
- });
699
-
700
- function createOpenApiFile(
701
- appDir: string,
702
- endpoints: Array<{ method: string; path: string }>,
703
- ): void {
704
- const endpointAuthEntries = endpoints
705
- .map((ep) => ` '${ep.method.toUpperCase()} ${ep.path}': null,`)
706
- .join('\n');
707
-
708
- const content = `// Auto-generated by @geekmidas/cli - DO NOT EDIT
709
-
710
- export const endpointAuth = {
711
- ${endpointAuthEntries}
712
- } as const;
713
-
714
- export const paths = {};
715
- export function createApi() { return {}; }
716
- `;
717
-
718
- const gkmDir = join(appDir, '.gkm');
719
- mkdirSync(gkmDir, { recursive: true });
720
- writeFileSync(join(gkmDir, 'openapi.ts'), content);
721
- }
722
-
723
- it('should copy from all backends to their dependent frontends', async () => {
724
- const apiDir = join(testDir, 'apps/api');
725
- const authDir = join(testDir, 'apps/auth');
726
- const webDir = join(testDir, 'apps/web');
727
- const adminDir = join(testDir, 'apps/admin');
728
- mkdirSync(webDir, { recursive: true });
729
- mkdirSync(adminDir, { recursive: true });
730
-
731
- createOpenApiFile(apiDir, [{ method: 'GET', path: '/users' }]);
732
- createOpenApiFile(authDir, [{ method: 'POST', path: '/login' }]);
733
-
734
- const workspace: NormalizedWorkspace = {
735
- name: 'test',
736
- root: testDir,
737
- apps: {
738
- api: {
739
- type: 'backend',
740
- path: 'apps/api',
741
- port: 3000,
742
- dependencies: [],
743
- routes: './src/endpoints/**/*.ts',
744
- resolvedDeployTarget: 'dokploy',
745
- },
746
- auth: {
747
- type: 'backend',
748
- path: 'apps/auth',
749
- port: 3001,
750
- dependencies: [],
751
- routes: './src/endpoints/**/*.ts',
752
- resolvedDeployTarget: 'dokploy',
753
- },
754
- web: {
755
- type: 'frontend',
756
- path: 'apps/web',
757
- port: 3002,
758
- dependencies: ['api'],
759
- client: { output: 'src/api' },
760
- resolvedDeployTarget: 'dokploy',
761
- },
762
- admin: {
763
- type: 'frontend',
764
- path: 'apps/admin',
765
- port: 3003,
766
- dependencies: ['auth'],
767
- client: { output: 'lib/client' },
768
- resolvedDeployTarget: 'dokploy',
769
- },
770
- },
771
- services: {},
772
- deploy: { default: 'dokploy' },
773
- shared: { packages: [] },
774
- secrets: {},
775
- };
776
-
777
- const results = await copyAllClients(workspace, { silent: true });
778
-
779
- expect(results).toHaveLength(2);
780
- expect(results.every((r) => r.success)).toBe(true);
781
- expect(existsSync(join(webDir, 'src/api/api.ts'))).toBe(true);
782
- expect(existsSync(join(adminDir, 'lib/client/auth.ts'))).toBe(true);
783
- });
784
-
785
- it('should return empty array when no backends have routes', async () => {
786
- const workspace: NormalizedWorkspace = {
787
- name: 'test',
788
- root: testDir,
789
- apps: {
790
- worker: {
791
- type: 'backend',
792
- path: 'apps/worker',
793
- port: 3000,
794
- dependencies: [],
795
- // No routes
796
- resolvedDeployTarget: 'dokploy',
797
- },
798
- },
799
- services: {},
800
- deploy: { default: 'dokploy' },
801
- shared: { packages: [] },
802
- secrets: {},
803
- };
804
-
805
- const results = await copyAllClients(workspace, { silent: true });
806
-
807
- expect(results).toEqual([]);
808
- });
809
- });
810
326
  });
@@ -1,23 +1,7 @@
1
- import { existsSync } from 'node:fs';
2
- import { mkdir, readFile, writeFile } from 'node:fs/promises';
3
- import { dirname, join, relative } from 'node:path';
1
+ import { join } from 'node:path';
4
2
  import { isPartitionedRoutes, type Routes } from '../types.js';
5
3
  import type { NormalizedWorkspace } from './types.js';
6
4
 
7
- const logger = console;
8
-
9
- /**
10
- * Result of copying a client to a frontend app.
11
- */
12
- export interface ClientCopyResult {
13
- frontendApp: string;
14
- backendApp: string;
15
- outputPath: string;
16
- endpointCount: number;
17
- success: boolean;
18
- error?: string;
19
- }
20
-
21
5
  /**
22
6
  * Normalize routes to an array of patterns.
23
7
  * Handles string, string[], and PartitionedRoutes (extracts paths).
@@ -120,131 +104,3 @@ export function getBackendOpenApiPath(
120
104
 
121
105
  return join(workspace.root, app.path, '.gkm', 'openapi.ts');
122
106
  }
123
-
124
- /**
125
- * Count endpoints in an OpenAPI spec content.
126
- */
127
- function countEndpoints(content: string): number {
128
- const endpointMatches = content.match(
129
- /'(GET|POST|PUT|PATCH|DELETE)\s+\/[^']+'/g,
130
- );
131
- return endpointMatches?.length ?? 0;
132
- }
133
-
134
- /**
135
- * Copy the OpenAPI client from a backend to all dependent frontend apps.
136
- * Called when the backend's .gkm/openapi.ts file changes.
137
- */
138
- export async function copyClientToFrontends(
139
- workspace: NormalizedWorkspace,
140
- backendAppName: string,
141
- options: { silent?: boolean } = {},
142
- ): Promise<ClientCopyResult[]> {
143
- const log = options.silent ? () => {} : logger.log.bind(logger);
144
- const results: ClientCopyResult[] = [];
145
-
146
- const backendApp = workspace.apps[backendAppName];
147
- if (!backendApp || backendApp.type !== 'backend') {
148
- return results;
149
- }
150
-
151
- // Get the backend's OpenAPI spec
152
- const openApiPath = join(
153
- workspace.root,
154
- backendApp.path,
155
- '.gkm',
156
- 'openapi.ts',
157
- );
158
-
159
- if (!existsSync(openApiPath)) {
160
- return results;
161
- }
162
-
163
- const content = await readFile(openApiPath, 'utf-8');
164
- const endpointCount = countEndpoints(content);
165
-
166
- // Get all frontends that depend on this backend
167
- const dependentFrontends = getDependentFrontends(workspace, backendAppName);
168
-
169
- for (const frontendAppName of dependentFrontends) {
170
- const frontendApp = workspace.apps[frontendAppName];
171
- if (!frontendApp || frontendApp.type !== 'frontend') {
172
- continue;
173
- }
174
-
175
- // Check if frontend has client output configured
176
- const clientOutput = frontendApp.client?.output;
177
- if (!clientOutput) {
178
- continue;
179
- }
180
-
181
- const result: ClientCopyResult = {
182
- frontendApp: frontendAppName,
183
- backendApp: backendAppName,
184
- outputPath: '',
185
- endpointCount,
186
- success: false,
187
- };
188
-
189
- try {
190
- const frontendPath = join(workspace.root, frontendApp.path);
191
- const outputDir = join(frontendPath, clientOutput);
192
- await mkdir(outputDir, { recursive: true });
193
-
194
- // Use backend app name as filename
195
- const fileName = `${backendAppName}.ts`;
196
- const outputPath = join(outputDir, fileName);
197
-
198
- // Add header comment with backend reference
199
- const backendRelPath = relative(
200
- dirname(outputPath),
201
- join(workspace.root, backendApp.path),
202
- );
203
-
204
- const clientContent = `/**
205
- * Auto-generated API client for ${backendAppName}
206
- * Generated from: ${backendRelPath}
207
- *
208
- * DO NOT EDIT - This file is automatically regenerated when backend schemas change.
209
- */
210
-
211
- ${content}
212
- `;
213
-
214
- await writeFile(outputPath, clientContent);
215
-
216
- result.outputPath = outputPath;
217
- result.success = true;
218
-
219
- log(
220
- `📦 Copied client to ${frontendAppName} from ${backendAppName} (${endpointCount} endpoints)`,
221
- );
222
- } catch (error) {
223
- result.error = (error as Error).message;
224
- }
225
-
226
- results.push(result);
227
- }
228
-
229
- return results;
230
- }
231
-
232
- /**
233
- * Copy clients from all backends to their dependent frontends.
234
- * Useful for initial setup or force refresh.
235
- */
236
- export async function copyAllClients(
237
- workspace: NormalizedWorkspace,
238
- options: { silent?: boolean } = {},
239
- ): Promise<ClientCopyResult[]> {
240
- const allResults: ClientCopyResult[] = [];
241
-
242
- for (const [appName, app] of Object.entries(workspace.apps)) {
243
- if (app.type === 'backend' && app.routes) {
244
- const results = await copyClientToFrontends(workspace, appName, options);
245
- allResults.push(...results);
246
- }
247
- }
248
-
249
- return allResults;
250
- }
@@ -35,7 +35,6 @@ export type {
35
35
  AppInput,
36
36
  AppsRecord,
37
37
  BackendFramework,
38
- ClientConfig,
39
38
  ConstrainedApps,
40
39
  DeployConfig,
41
40
  DeployTarget,
@@ -54,13 +54,6 @@ const HooksConfigSchema = z.object({
54
54
  server: z.string().optional(),
55
55
  });
56
56
 
57
- /**
58
- * Client generation configuration schema.
59
- */
60
- const ClientConfigSchema = z.object({
61
- output: z.string().optional(),
62
- });
63
-
64
57
  /**
65
58
  * Auth provider schema.
66
59
  * Currently only 'better-auth' is supported.
@@ -577,8 +570,6 @@ const AppConfigSchema = z
577
570
 
578
571
  // Framework (backend or frontend)
579
572
  framework: FrameworkSchema.optional(),
580
- client: ClientConfigSchema.optional(),
581
-
582
573
  // Frontend-specific: config file paths for env sniffing (calls .parse() at import)
583
574
  config: z
584
575
  .object({