@geekmidas/cli 10.0.0-alpha.2 → 10.0.0-alpha.20

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 (241) hide show
  1. package/README.md +25 -48
  2. package/bin/adjacent-tsconfig.mjs +59 -0
  3. package/bin/gkm.mjs +7 -1
  4. package/dist/{HostingerProvider-Cft7lEYr.mjs → HostingerProvider-BY26bWwB.mjs} +20 -21
  5. package/dist/HostingerProvider-BY26bWwB.mjs.map +1 -0
  6. package/dist/{HostingerProvider-h3EbEp-Z.cjs → HostingerProvider-BZFGYYVA.cjs} +21 -22
  7. package/dist/HostingerProvider-BZFGYYVA.cjs.map +1 -0
  8. package/dist/{LocalStateProvider-Db9SEjyH.cjs → LocalStateProvider-7HR6SS2t.cjs} +11 -6
  9. package/dist/LocalStateProvider-7HR6SS2t.cjs.map +1 -0
  10. package/dist/{LocalStateProvider-Bn6tS-oL.mjs → LocalStateProvider-D7QyJoLM.mjs} +10 -4
  11. package/dist/LocalStateProvider-D7QyJoLM.mjs.map +1 -0
  12. package/dist/{Route53Provider-RdpUDJQk.mjs → Route53Provider-Cvj40A8I.mjs} +12 -12
  13. package/dist/Route53Provider-Cvj40A8I.mjs.map +1 -0
  14. package/dist/{Route53Provider-di2fMM5N.cjs → Route53Provider-DVILBsvV.cjs} +20 -21
  15. package/dist/Route53Provider-DVILBsvV.cjs.map +1 -0
  16. package/dist/{SSMStateProvider-BRt2py-A.mjs → SSMStateProvider-B0Hu1MKZ.mjs} +13 -4
  17. package/dist/SSMStateProvider-B0Hu1MKZ.mjs.map +1 -0
  18. package/dist/{SSMStateProvider-DyX8s9HI.cjs → SSMStateProvider-VrQ-yeqd.cjs} +17 -9
  19. package/dist/SSMStateProvider-VrQ-yeqd.cjs.map +1 -0
  20. package/dist/{backup-provisioner-BrsHwYG0.cjs → backup-provisioner-BmhIO_Yr.cjs} +21 -19
  21. package/dist/backup-provisioner-BmhIO_Yr.cjs.map +1 -0
  22. package/dist/{backup-provisioner-Bgj5Vi_L.mjs → backup-provisioner-DIVCbfee.mjs} +10 -7
  23. package/dist/backup-provisioner-DIVCbfee.mjs.map +1 -0
  24. package/dist/{bundler-BY1OmV1e.cjs → bundler-mjqGAvu1.cjs} +10 -28
  25. package/dist/bundler-mjqGAvu1.cjs.map +1 -0
  26. package/dist/{bundler-Be8Zg4Wd.mjs → bundler-mwH7WOb1.mjs} +9 -26
  27. package/dist/bundler-mwH7WOb1.mjs.map +1 -0
  28. package/dist/{config-BholeD-0.cjs → config-DSXI480r.cjs} +80 -148
  29. package/dist/config-DSXI480r.cjs.map +1 -0
  30. package/dist/{config-CzWnfcXM.mjs → config-DdKkoUE1.mjs} +33 -88
  31. package/dist/config-DdKkoUE1.mjs.map +1 -0
  32. package/dist/config.cjs +4 -5
  33. package/dist/config.d.cts +14 -17
  34. package/dist/config.d.cts.map +1 -1
  35. package/dist/config.d.mts +14 -17
  36. package/dist/config.d.mts.map +1 -1
  37. package/dist/config.mjs +3 -5
  38. package/dist/{credentials-DQmbRnr1.cjs → credentials-C6Pfn6GY.cjs} +1502 -1243
  39. package/dist/credentials-C6Pfn6GY.cjs.map +1 -0
  40. package/dist/{credentials-BZcp3CGx.mjs → credentials-lbv-j0Be.mjs} +1370 -1116
  41. package/dist/credentials-lbv-j0Be.mjs.map +1 -0
  42. package/dist/deploy/sniffer-envkit-patch.cjs +18 -10
  43. package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
  44. package/dist/deploy/sniffer-envkit-patch.d.cts +0 -2
  45. package/dist/deploy/sniffer-envkit-patch.d.cts.map +1 -1
  46. package/dist/deploy/sniffer-envkit-patch.d.mts +0 -2
  47. package/dist/deploy/sniffer-envkit-patch.d.mts.map +1 -1
  48. package/dist/deploy/sniffer-envkit-patch.mjs +10 -2
  49. package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
  50. package/dist/deploy/sniffer-hooks.cjs +13 -7
  51. package/dist/deploy/sniffer-hooks.cjs.map +1 -1
  52. package/dist/deploy/sniffer-hooks.d.cts +1 -2
  53. package/dist/deploy/sniffer-hooks.d.cts.map +1 -1
  54. package/dist/deploy/sniffer-hooks.d.mts +1 -2
  55. package/dist/deploy/sniffer-hooks.d.mts.map +1 -1
  56. package/dist/deploy/sniffer-hooks.mjs +9 -2
  57. package/dist/deploy/sniffer-hooks.mjs.map +1 -1
  58. package/dist/deploy/sniffer-loader.cjs +16 -7
  59. package/dist/deploy/sniffer-loader.cjs.map +1 -1
  60. package/dist/deploy/sniffer-loader.d.cts +1 -1
  61. package/dist/deploy/sniffer-loader.d.mts +1 -1
  62. package/dist/deploy/sniffer-loader.mjs +13 -2
  63. package/dist/deploy/sniffer-loader.mjs.map +1 -1
  64. package/dist/deploy/sniffer-routes-worker.cjs +11 -11
  65. package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
  66. package/dist/deploy/sniffer-routes-worker.d.cts +1 -1
  67. package/dist/deploy/sniffer-routes-worker.d.mts +1 -1
  68. package/dist/deploy/sniffer-routes-worker.mjs +2 -2
  69. package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
  70. package/dist/deploy/sniffer-worker.cjs +15 -6
  71. package/dist/deploy/sniffer-worker.cjs.map +1 -1
  72. package/dist/deploy/sniffer-worker.d.cts +1 -3
  73. package/dist/deploy/sniffer-worker.d.cts.map +1 -1
  74. package/dist/deploy/sniffer-worker.d.mts +1 -3
  75. package/dist/deploy/sniffer-worker.d.mts.map +1 -1
  76. package/dist/deploy/sniffer-worker.mjs +15 -4
  77. package/dist/deploy/sniffer-worker.mjs.map +1 -1
  78. package/dist/{discover-DTmFeloL.cjs → discover-BERGFg8v.cjs} +81 -64
  79. package/dist/discover-BERGFg8v.cjs.map +1 -0
  80. package/dist/{discover-CAlFgYec.mjs → discover-CK-n3w6b.mjs} +35 -20
  81. package/dist/discover-CK-n3w6b.mjs.map +1 -0
  82. package/dist/{emit-CbcRolsa.cjs → emit-BdoPFjZf.cjs} +53 -36
  83. package/dist/emit-BdoPFjZf.cjs.map +1 -0
  84. package/dist/{emit-CiZS_5Ub.mjs → emit-Bzq7JsMP.mjs} +24 -6
  85. package/dist/emit-Bzq7JsMP.mjs.map +1 -0
  86. package/dist/{generator-VE2JrFlY.mjs → generator-LfZU7NlU.mjs} +8 -14
  87. package/dist/generator-LfZU7NlU.mjs.map +1 -0
  88. package/dist/{generator-CNzBw7g5.cjs → generator-Wdfp0jCS.cjs} +38 -45
  89. package/dist/generator-Wdfp0jCS.cjs.map +1 -0
  90. package/dist/github-BvfpvBcW.cjs +216 -0
  91. package/dist/github-BvfpvBcW.cjs.map +1 -0
  92. package/dist/github-DW3CP-4D.mjs +216 -0
  93. package/dist/github-DW3CP-4D.mjs.map +1 -0
  94. package/dist/{index-CHB4tSzX.d.cts → index-B4H4XF7A.d.mts} +4 -13
  95. package/dist/index-B4H4XF7A.d.mts.map +1 -0
  96. package/dist/{index-BThUzEIp.d.mts → index-RCUizGxL.d.cts} +4 -13
  97. package/dist/index-RCUizGxL.d.cts.map +1 -0
  98. package/dist/index.cjs +5108 -3381
  99. package/dist/index.cjs.map +1 -1
  100. package/dist/index.d.cts +1 -1
  101. package/dist/index.d.mts +1 -1
  102. package/dist/index.mjs +5025 -3315
  103. package/dist/index.mjs.map +1 -1
  104. package/dist/{openapi-c9bpieI5.cjs → openapi-B8bu_OXN.cjs} +228 -188
  105. package/dist/openapi-B8bu_OXN.cjs.map +1 -0
  106. package/dist/{openapi-CY9xFkTH.mjs → openapi-DfjbhrsM.mjs} +172 -143
  107. package/dist/openapi-DfjbhrsM.mjs.map +1 -0
  108. package/dist/openapi.cjs +5 -6
  109. package/dist/openapi.d.cts +47 -14
  110. package/dist/openapi.d.cts.map +1 -1
  111. package/dist/openapi.d.mts +47 -14
  112. package/dist/openapi.d.mts.map +1 -1
  113. package/dist/openapi.mjs +2 -6
  114. package/dist/reconcile/public.cjs +4 -4
  115. package/dist/reconcile/public.d.cts +8 -26
  116. package/dist/reconcile/public.d.cts.map +1 -1
  117. package/dist/reconcile/public.d.mts +8 -26
  118. package/dist/reconcile/public.d.mts.map +1 -1
  119. package/dist/reconcile/public.mjs +3 -4
  120. package/dist/{fullstack-secrets-D1o9-G5l.mjs → reconcile-gb1lz1Dk.mjs} +43 -17
  121. package/dist/reconcile-gb1lz1Dk.mjs.map +1 -0
  122. package/dist/{fullstack-secrets-C-GGohke.cjs → reconcile-hx_vBvux.cjs} +63 -38
  123. package/dist/reconcile-hx_vBvux.cjs.map +1 -0
  124. package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
  125. package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
  126. package/dist/ssm-BKN1Cg4z.mjs +60 -0
  127. package/dist/ssm-BKN1Cg4z.mjs.map +1 -0
  128. package/dist/ssm-FxeSAt35.cjs +60 -0
  129. package/dist/ssm-FxeSAt35.cjs.map +1 -0
  130. package/dist/{storage-BJmzYxt8.cjs → storage-DgypfSD6.cjs} +65 -77
  131. package/dist/storage-DgypfSD6.cjs.map +1 -0
  132. package/dist/{storage-Bi24WfoW.mjs → storage-TP0x118M.mjs} +22 -15
  133. package/dist/storage-TP0x118M.mjs.map +1 -0
  134. package/dist/transfer-DjFZRgub.mjs +122 -0
  135. package/dist/transfer-DjFZRgub.mjs.map +1 -0
  136. package/dist/transfer-SeiSeKEF.cjs +151 -0
  137. package/dist/transfer-SeiSeKEF.cjs.map +1 -0
  138. package/dist/types-7R81hu0i.d.mts +6302 -0
  139. package/dist/types-7R81hu0i.d.mts.map +1 -0
  140. package/dist/types-N7c2O2m7.d.cts +6303 -0
  141. package/dist/types-N7c2O2m7.d.cts.map +1 -0
  142. package/dist/workspace/index.cjs +3 -3
  143. package/dist/workspace/index.d.cts +3 -3
  144. package/dist/workspace/index.d.mts +3 -3
  145. package/dist/workspace/index.mjs +2 -3
  146. package/dist/workspace-Bu3xNCej.mjs +6563 -0
  147. package/dist/workspace-Bu3xNCej.mjs.map +1 -0
  148. package/dist/workspace-CRD9YinV.cjs +6741 -0
  149. package/dist/workspace-CRD9YinV.cjs.map +1 -0
  150. package/package.json +33 -43
  151. package/dist/CachedStateProvider-BLo7zv7S.cjs +0 -3
  152. package/dist/CachedStateProvider-BxoAm-B2.mjs +0 -3
  153. package/dist/CachedStateProvider-CqPdW7CB.cjs +0 -60
  154. package/dist/CachedStateProvider-CqPdW7CB.cjs.map +0 -1
  155. package/dist/CachedStateProvider-DTniehMP.mjs +0 -54
  156. package/dist/CachedStateProvider-DTniehMP.mjs.map +0 -1
  157. package/dist/HostingerProvider-Cft7lEYr.mjs.map +0 -1
  158. package/dist/HostingerProvider-h3EbEp-Z.cjs.map +0 -1
  159. package/dist/LocalStateProvider-Bn6tS-oL.mjs.map +0 -1
  160. package/dist/LocalStateProvider-Db9SEjyH.cjs.map +0 -1
  161. package/dist/Route53Provider-RdpUDJQk.mjs.map +0 -1
  162. package/dist/Route53Provider-di2fMM5N.cjs.map +0 -1
  163. package/dist/SSMStateProvider-BRt2py-A.mjs.map +0 -1
  164. package/dist/SSMStateProvider-DyX8s9HI.cjs.map +0 -1
  165. package/dist/backup-provisioner-Bgj5Vi_L.mjs.map +0 -1
  166. package/dist/backup-provisioner-BrsHwYG0.cjs.map +0 -1
  167. package/dist/bundler-BY1OmV1e.cjs.map +0 -1
  168. package/dist/bundler-Be8Zg4Wd.mjs.map +0 -1
  169. package/dist/chunk-Duj1WY3L.mjs +0 -7
  170. package/dist/config-BholeD-0.cjs.map +0 -1
  171. package/dist/config-CzWnfcXM.mjs.map +0 -1
  172. package/dist/credentials-B5zRHdAl.mjs +0 -141
  173. package/dist/credentials-B5zRHdAl.mjs.map +0 -1
  174. package/dist/credentials-BZcp3CGx.mjs.map +0 -1
  175. package/dist/credentials-BapzjyV5.cjs +0 -195
  176. package/dist/credentials-BapzjyV5.cjs.map +0 -1
  177. package/dist/credentials-DQmbRnr1.cjs.map +0 -1
  178. package/dist/discover-CAlFgYec.mjs.map +0 -1
  179. package/dist/discover-DTmFeloL.cjs.map +0 -1
  180. package/dist/dokploy-api-BS2xnJLZ.mjs +0 -3
  181. package/dist/dokploy-api-BsVH0y_s.cjs +0 -718
  182. package/dist/dokploy-api-BsVH0y_s.cjs.map +0 -1
  183. package/dist/dokploy-api-BuxDRo3l.cjs +0 -3
  184. package/dist/dokploy-api-aTFPz7_4.mjs +0 -706
  185. package/dist/dokploy-api-aTFPz7_4.mjs.map +0 -1
  186. package/dist/emit-CbcRolsa.cjs.map +0 -1
  187. package/dist/emit-CiZS_5Ub.mjs.map +0 -1
  188. package/dist/encryption-C6jaU0hE.cjs +0 -54
  189. package/dist/encryption-C6jaU0hE.cjs.map +0 -1
  190. package/dist/encryption-CHzzeR8a.cjs +0 -4
  191. package/dist/encryption-CQUncu7v.mjs +0 -3
  192. package/dist/encryption-D-abyhZx.mjs +0 -42
  193. package/dist/encryption-D-abyhZx.mjs.map +0 -1
  194. package/dist/fullstack-secrets-C-GGohke.cjs.map +0 -1
  195. package/dist/fullstack-secrets-D1o9-G5l.mjs.map +0 -1
  196. package/dist/generator-CNzBw7g5.cjs.map +0 -1
  197. package/dist/generator-VE2JrFlY.mjs.map +0 -1
  198. package/dist/index-BThUzEIp.d.mts.map +0 -1
  199. package/dist/index-CHB4tSzX.d.cts.map +0 -1
  200. package/dist/openapi-CY9xFkTH.mjs.map +0 -1
  201. package/dist/openapi-c9bpieI5.cjs.map +0 -1
  202. package/dist/openapi-react-query-Byltvm_7.mjs +0 -167
  203. package/dist/openapi-react-query-Byltvm_7.mjs.map +0 -1
  204. package/dist/openapi-react-query-CmJ1xDbn.cjs +0 -173
  205. package/dist/openapi-react-query-CmJ1xDbn.cjs.map +0 -1
  206. package/dist/openapi-react-query.cjs +0 -4
  207. package/dist/openapi-react-query.d.cts +0 -11
  208. package/dist/openapi-react-query.d.cts.map +0 -1
  209. package/dist/openapi-react-query.d.mts +0 -11
  210. package/dist/openapi-react-query.d.mts.map +0 -1
  211. package/dist/openapi-react-query.mjs +0 -4
  212. package/dist/reconcile-CFs8C_1a.mjs +0 -37
  213. package/dist/reconcile-CFs8C_1a.mjs.map +0 -1
  214. package/dist/reconcile-D0pimNiy.cjs +0 -37
  215. package/dist/reconcile-D0pimNiy.cjs.map +0 -1
  216. package/dist/setup-Bc73t0ve.mjs +0 -443
  217. package/dist/setup-Bc73t0ve.mjs.map +0 -1
  218. package/dist/setup-C48C9hto.cjs +0 -473
  219. package/dist/setup-C48C9hto.cjs.map +0 -1
  220. package/dist/setup-DCuudnCl.mjs +0 -11
  221. package/dist/setup-fG_ivuxO.cjs +0 -11
  222. package/dist/storage-BJmzYxt8.cjs.map +0 -1
  223. package/dist/storage-BM9hGpgv.cjs +0 -7
  224. package/dist/storage-Bi24WfoW.mjs.map +0 -1
  225. package/dist/storage-CYlGwWTj.mjs +0 -3
  226. package/dist/sync-CmEuNZsB.mjs +0 -76
  227. package/dist/sync-CmEuNZsB.mjs.map +0 -1
  228. package/dist/sync-D306yOWu.cjs +0 -4
  229. package/dist/sync-SizaChmC.cjs +0 -93
  230. package/dist/sync-SizaChmC.cjs.map +0 -1
  231. package/dist/sync-hOqAWHR6.mjs +0 -3
  232. package/dist/types-C-arnQ1r.d.mts +0 -2184
  233. package/dist/types-C-arnQ1r.d.mts.map +0 -1
  234. package/dist/types-abJQuk7m.d.cts +0 -2184
  235. package/dist/types-abJQuk7m.d.cts.map +0 -1
  236. package/dist/workspace-7FqprxuJ.mjs +0 -4323
  237. package/dist/workspace-7FqprxuJ.mjs.map +0 -1
  238. package/dist/workspace-BO_KWohI.cjs +0 -9
  239. package/dist/workspace-DK4WWJIn.mjs +0 -8
  240. package/dist/workspace-XvqO9jIv.cjs +0 -4472
  241. package/dist/workspace-XvqO9jIv.cjs.map +0 -1
package/README.md CHANGED
@@ -54,6 +54,7 @@ Create a `gkm.config.ts` file in your project root:
54
54
  import { defineConfig } from '@geekmidas/cli/config';
55
55
 
56
56
  export default defineConfig({
57
+ stages: { local: 'dev', deployed: ['prod'] },
57
58
  // Glob pattern to find endpoint files
58
59
  routes: 'src/routes/**/*.ts',
59
60
 
@@ -443,7 +444,7 @@ gkm docker [options]
443
444
 
444
445
  **Generated Files:**
445
446
  - `.gkm/docker/Dockerfile` - Multi-stage or slim Dockerfile
446
- - `.gkm/docker/docker-compose.yml` - Docker Compose configuration
447
+ - `docker-compose.constructs.yml` - the containers the constructs imply, and the app (project root)
447
448
  - `.gkm/docker/docker-entrypoint.sh` - Entrypoint script
448
449
  - `.dockerignore` - Docker ignore file (project root)
449
450
 
@@ -490,6 +491,7 @@ Configure Docker settings in `gkm.config.ts`:
490
491
  import { defineConfig } from '@geekmidas/cli/config';
491
492
 
492
493
  export default defineConfig({
494
+ stages: { local: 'dev', deployed: ['prod'] },
493
495
  routes: 'src/routes/**/*.ts',
494
496
  envParser: './src/env.ts',
495
497
  logger: './src/logger.ts',
@@ -503,38 +505,20 @@ export default defineConfig({
503
505
  baseImage: 'node:22-alpine',
504
506
  // Port to expose (default: 3000)
505
507
  port: 3000,
506
- // Docker Compose services
507
- compose: {
508
- services: {
509
- postgres: { image: 'postgis/postgis:16-3.4-alpine' }, // Custom image
510
- redis: true, // Default version
511
- rabbitmq: { version: '3.12-management-alpine' }, // Custom version
512
- },
513
- },
514
508
  },
515
509
  });
516
510
  ```
517
511
 
518
- **Docker Compose Services:**
519
-
520
- Services can be configured with custom versions, custom images, or use defaults:
521
-
522
- ```typescript
523
- // Object format (recommended)
524
- services: {
525
- postgres: { version: '15-alpine' }, // Custom version
526
- redis: true, // Default: redis:7-alpine
527
- rabbitmq: { version: '3.12-management-alpine' },
528
- }
529
-
530
- // Custom images (e.g., PostGIS, Redis Stack)
531
- services: {
532
- postgres: { image: 'postgis/postgis:16-3.4-alpine' }, // Full image reference
533
- redis: { image: 'redis/redis-stack:latest' },
534
- }
512
+ **Compose:** there is no `compose` option. `gkm docker` writes
513
+ `docker-compose.constructs.yml` at the project root from the declared
514
+ constructs; an image pin or an extra service goes in your own
515
+ `docker-compose.yml`, which is merged over it:
535
516
 
536
- // Legacy array format - uses default versions
537
- services: ['postgres', 'redis', 'rabbitmq']
517
+ ```yaml
518
+ # docker-compose.yml
519
+ services:
520
+ postgres:
521
+ image: postgis/postgis:16-3.4-alpine
538
522
  ```
539
523
 
540
524
  **Service Configuration Options:**
@@ -682,8 +666,8 @@ gkm secrets:init --stage production --force
682
666
  ```
683
667
 
684
668
  **Generated:**
685
- - Secure passwords for postgres, redis, rabbitmq (based on `docker.compose.services` config)
686
- - Connection URLs (`DATABASE_URL`, `REDIS_URL`, `RABBITMQ_URL`)
669
+ - Custom secrets for the stage; the containers' credentials are provisioned by
670
+ reconcile from the declared constructs, not stored here
687
671
  - Stored in `.gkm/secrets/<stage>.json` (gitignored)
688
672
 
689
673
  ### `gkm secrets:set`
@@ -929,6 +913,7 @@ This allows the CLI to validate that all required secrets are configured before
929
913
  ```typescript
930
914
  // gkm.config.ts
931
915
  export default defineConfig({
916
+ stages: { local: 'dev', deployed: ['prod'] },
932
917
  routes: 'src/endpoints/**/*.ts',
933
918
  envParser: './src/env.ts',
934
919
  logger: './src/logger.ts',
@@ -1216,6 +1201,7 @@ import { defineWorkspace } from '@geekmidas/cli/config';
1216
1201
 
1217
1202
  export default defineWorkspace({
1218
1203
  name: 'my-project',
1204
+ stages: { local: 'dev', deployed: ['prod'] },
1219
1205
  constructs: './constructs/**/*.ts',
1220
1206
  shared: {
1221
1207
  packages: ['packages/*'],
@@ -1239,7 +1225,7 @@ them out of the manifest; none of them is listed in config.
1239
1225
 
1240
1226
  ```typescript
1241
1227
  // constructs/api.ts
1242
- export const api = new RestApi('Api', { defaultAuthorizer: 'none', logger });
1228
+ export const api = new RestApi('Api', { path: 'apps/api', defaultAuthorizer: 'none', logger });
1243
1229
  ```
1244
1230
 
1245
1231
  `gkm build --provider server` generates a Hono server from the endpoints built
@@ -1253,6 +1239,7 @@ An auth server mounts a wildcard, so there is nothing for a glob to find:
1253
1239
  ```typescript
1254
1240
  // constructs/auth.ts
1255
1241
  export const auth = new BetterAuth('Auth', {
1242
+ path: 'apps/auth',
1256
1243
  database: authDb,
1257
1244
  basePath: '/api/auth',
1258
1245
  });
@@ -1271,8 +1258,8 @@ from being read by it.
1271
1258
 
1272
1259
  ```typescript
1273
1260
  // constructs/site.ts
1274
- export const web = new StaticSite('Web').dependsOn([api, auth]);
1275
- export const admin = new StaticSite('Admin', { variant: 'next' }).dependsOn([api]);
1261
+ export const web = new StaticSite('Web', { path: 'apps/web' }).dependsOn([api, auth]);
1262
+ export const admin = new StaticSite('Admin', { path: 'apps/admin', variant: 'next' }).dependsOn([api]);
1276
1263
  ```
1277
1264
 
1278
1265
  `.dependsOn()` is the single fact behind four things that are hand-maintained
@@ -1291,7 +1278,7 @@ gkm docker
1291
1278
  - `.gkm/docker/Dockerfile.api` - Routes-based backend (uses `gkm build`)
1292
1279
  - `.gkm/docker/Dockerfile.auth` - Entry-based backend (uses esbuild bundling)
1293
1280
  - `.gkm/docker/Dockerfile.web` - Next.js standalone output
1294
- - `.gkm/docker/docker-compose.yml` - Full stack with all apps and services
1281
+ - `docker-compose.constructs.yml` - every container the constructs imply, and each app behind the `apps` profile (project root)
1295
1282
  - `.dockerignore` - Optimized ignore patterns
1296
1283
 
1297
1284
  **Dockerfile types by app:**
@@ -1358,12 +1345,10 @@ interface WorkspaceConfig {
1358
1345
  What an app is comes from the declaration, not from here:
1359
1346
 
1360
1347
  ```typescript
1361
- /** Overrides, all optional. A surface normally has none. */
1348
+ /** How a site is built and run. A surface declares only its `path`. */
1362
1349
  interface AppSpec {
1363
- /** Default: `apps/<kebab-id>` if present, else the project root. */
1364
- path?: string;
1365
- /** Default: the conventional directories under `path`. */
1366
- code?: string;
1350
+ /** Required: where the app lives, relative to the workspace root. */
1351
+ path: string;
1367
1352
  /** Default: assigned in a stable order, so adding an app renumbers nothing. */
1368
1353
  port?: number;
1369
1354
  entry?: string;
@@ -1883,6 +1868,7 @@ Configure multiple patterns for complex project structures:
1883
1868
  import { defineConfig } from '@geekmidas/cli/config';
1884
1869
 
1885
1870
  export default defineConfig({
1871
+ stages: { local: 'dev', deployed: ['prod'] },
1886
1872
  routes: [
1887
1873
  'src/routes/**/*.ts',
1888
1874
  'src/api/v1/**/*.ts',
@@ -2054,17 +2040,8 @@ interface DockerConfig {
2054
2040
  imageName?: string; // Image name (defaults to package.json name)
2055
2041
  baseImage?: string; // Base image (default: node:22-alpine)
2056
2042
  port?: number; // Port to expose (default: 3000)
2057
- compose?: {
2058
- services?: ComposeServicesConfig | ComposeServiceName[];
2059
- };
2060
2043
  }
2061
2044
 
2062
- // Service configuration for docker-compose
2063
- type ComposeServiceName = 'postgres' | 'redis' | 'rabbitmq';
2064
- type ComposeServicesConfig = {
2065
- [K in ComposeServiceName]?: boolean | { version?: string };
2066
- };
2067
-
2068
2045
  // Telescope configuration
2069
2046
  interface TelescopeConfig {
2070
2047
  enabled?: boolean;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Resolve a module's path aliases through the tsconfig beside it.
3
+ *
4
+ * tsx applies one tsconfig to the whole process — the one in the directory the
5
+ * command ran from. The workspace's `constructs` glob loads every app's code
6
+ * from wherever a command started, so `~/router.ts` in `apps/api` was being
7
+ * read with `apps/web`'s `~` when the command ran there, and resolved to
8
+ * nothing. TypeScript itself resolves an import through the tsconfig that owns
9
+ * the importing file; this does the same at runtime.
10
+ *
11
+ * Registered after tsx, so it runs first. It only answers a non-relative
12
+ * specifier from TypeScript source outside `node_modules` that the adjacent
13
+ * tsconfig's `paths` actually map — everything else, and anything a mapped
14
+ * candidate fails to resolve to, goes to the next resolver untouched.
15
+ */
16
+
17
+ import { dirname } from 'node:path';
18
+ import { fileURLToPath, pathToFileURL } from 'node:url';
19
+ import { createPathsMatcher, getTsconfig } from 'get-tsconfig';
20
+
21
+ /** Directory → the paths matcher of the tsconfig that owns it, if any. */
22
+ const matchers = new Map();
23
+ /** Shared with `get-tsconfig`, so a config `extends` is read once. */
24
+ const tsconfigCache = new Map();
25
+
26
+ const TS_SOURCE = /\.(?:[cm]?ts|tsx)$/;
27
+ /** Relative, absolute, or carrying a scheme (`node:`, `file:`, `data:`). */
28
+ const NOT_BARE = /^(?:\.{1,2}(?:\/|$)|\/|[a-zA-Z][a-zA-Z\d+.-]*:)/;
29
+
30
+ function matcherFor(directory) {
31
+ if (!matchers.has(directory)) {
32
+ const tsconfig = getTsconfig(directory, 'tsconfig.json', tsconfigCache);
33
+ matchers.set(directory, tsconfig ? createPathsMatcher(tsconfig) : null);
34
+ }
35
+ return matchers.get(directory);
36
+ }
37
+
38
+ export async function resolve(specifier, context, nextResolve) {
39
+ const parent = context.parentURL;
40
+ if (!parent?.startsWith('file:') || NOT_BARE.test(specifier)) {
41
+ return nextResolve(specifier, context);
42
+ }
43
+
44
+ const parentPath = fileURLToPath(parent);
45
+ if (!TS_SOURCE.test(parentPath) || parentPath.includes('/node_modules/')) {
46
+ return nextResolve(specifier, context);
47
+ }
48
+
49
+ const match = matcherFor(dirname(parentPath));
50
+ for (const candidate of match?.(specifier) ?? []) {
51
+ try {
52
+ return await nextResolve(pathToFileURL(candidate).href, context);
53
+ } catch {
54
+ // The next mapping, as TypeScript tries them in order.
55
+ }
56
+ }
57
+
58
+ return nextResolve(specifier, context);
59
+ }
package/bin/gkm.mjs CHANGED
@@ -35,5 +35,11 @@ if (
35
35
  process.exit(0);
36
36
  }
37
37
 
38
- // tsx is loaded — run the CLI
38
+ // tsx is loaded. Resolve each module's path aliases through its own tsconfig
39
+ // rather than the one tsx took from the cwd — registered after tsx, so it is
40
+ // asked first. See `adjacent-tsconfig.mjs`.
41
+ const { register } = await import('node:module');
42
+ register('./adjacent-tsconfig.mjs', import.meta.url);
43
+
44
+ // Run the CLI
39
45
  await import('../dist/index.mjs');
@@ -1,5 +1,4 @@
1
- import { getHostingerToken } from "./credentials-B5zRHdAl.mjs";
2
-
1
+ import { n as getHostingerToken } from "./index.mjs";
3
2
  //#region src/deploy/dns/hostinger-api.ts
4
3
  /**
5
4
  * Hostinger DNS API client
@@ -12,6 +11,9 @@ const HOSTINGER_API_BASE = "https://developers.hostinger.com";
12
11
  * Hostinger API error
13
12
  */
14
13
  var HostingerApiError = class extends Error {
14
+ status;
15
+ statusText;
16
+ errors;
15
17
  constructor(message, status, statusText, errors) {
16
18
  super(message);
17
19
  this.status = status;
@@ -65,7 +67,7 @@ var HostingerApi = class {
65
67
  throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
66
68
  }
67
69
  const text = await response.text();
68
- if (!text || text.trim() === "") return void 0;
70
+ if (!text || text.trim() === "") return;
69
71
  return JSON.parse(text);
70
72
  }
71
73
  /**
@@ -122,8 +124,7 @@ var HostingerApi = class {
122
124
  * @param type - Record type (e.g., 'A')
123
125
  */
124
126
  async recordExists(domain, name, type = "A") {
125
- const records = await this.getRecords(domain);
126
- return records.some((r) => r.name === name && r.type === type);
127
+ return (await this.getRecords(domain)).some((r) => r.name === name && r.type === type);
127
128
  }
128
129
  /**
129
130
  * Create a single A record if it doesn't exist
@@ -135,8 +136,7 @@ var HostingerApi = class {
135
136
  * @returns true if created, false if already exists
136
137
  */
137
138
  async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
138
- const exists = await this.recordExists(domain, subdomain, "A");
139
- if (exists) return false;
139
+ if (await this.recordExists(domain, subdomain, "A")) return false;
140
140
  await this.upsertRecords(domain, [{
141
141
  name: subdomain,
142
142
  type: "A",
@@ -146,10 +146,14 @@ var HostingerApi = class {
146
146
  return true;
147
147
  }
148
148
  };
149
-
150
149
  //#endregion
151
150
  //#region src/deploy/dns/HostingerProvider.ts
152
151
  /**
152
+ * Hostinger DNS Provider
153
+ *
154
+ * Implements DnsProvider interface using the Hostinger DNS API.
155
+ */
156
+ /**
153
157
  * Hostinger DNS provider implementation.
154
158
  */
155
159
  var HostingerProvider = class {
@@ -166,9 +170,7 @@ var HostingerProvider = class {
166
170
  return this.api;
167
171
  }
168
172
  async getRecords(domain) {
169
- const api = await this.getApi();
170
- const records = await api.getRecords(domain);
171
- return records.map((r) => ({
173
+ return (await (await this.getApi()).getRecords(domain)).map((r) => ({
172
174
  name: r.name,
173
175
  type: r.type,
174
176
  ttl: r.ttl,
@@ -227,18 +229,15 @@ var HostingerProvider = class {
227
229
  error: String(error)
228
230
  });
229
231
  }
230
- for (const record of records) {
231
- const existing = existingRecords.find((r) => r.name === record.name && r.type === record.type);
232
- if (!existing) results.push({
233
- record,
234
- deleted: false,
235
- notFound: true
236
- });
237
- }
232
+ for (const record of records) if (!existingRecords.find((r) => r.name === record.name && r.type === record.type)) results.push({
233
+ record,
234
+ deleted: false,
235
+ notFound: true
236
+ });
238
237
  return results;
239
238
  }
240
239
  };
241
-
242
240
  //#endregion
243
241
  export { HostingerProvider };
244
- //# sourceMappingURL=HostingerProvider-Cft7lEYr.mjs.map
242
+
243
+ //# sourceMappingURL=HostingerProvider-BY26bWwB.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HostingerProvider-BY26bWwB.mjs","names":[],"sources":["../src/deploy/dns/hostinger-api.ts","../src/deploy/dns/HostingerProvider.ts"],"sourcesContent":["/**\n * Hostinger DNS API client\n *\n * API Documentation: https://developers.hostinger.com/\n * Authentication: Bearer token from hpanel.hostinger.com/profile/api\n */\n\nconst HOSTINGER_API_BASE = 'https://developers.hostinger.com';\n\n/**\n * DNS record types supported by Hostinger\n */\nexport type DnsRecordType =\n\t| 'A'\n\t| 'AAAA'\n\t| 'CNAME'\n\t| 'MX'\n\t| 'TXT'\n\t| 'NS'\n\t| 'SRV'\n\t| 'CAA';\n\n/**\n * A single DNS record\n */\nexport interface DnsRecord {\n\t/** Subdomain name (e.g., 'api.joemoer' for api.joemoer.traflabs.io) */\n\tname: string;\n\t/** Record type */\n\ttype: DnsRecordType;\n\t/** TTL in seconds */\n\tttl: number;\n\t/** Record values */\n\trecords: Array<{ content: string }>;\n}\n\n/**\n * Filter for deleting specific records\n */\nexport interface DnsRecordFilter {\n\tname: string;\n\ttype: DnsRecordType;\n}\n\n/**\n * API error response\n */\nexport interface HostingerErrorResponse {\n\tmessage?: string;\n\terrors?: Record<string, string[]>;\n}\n\n/**\n * Hostinger API error\n */\nexport class HostingerApiError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic errors?: Record<string, string[]>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'HostingerApiError';\n\t}\n}\n\n/**\n * Hostinger DNS API client\n *\n * @example\n * ```ts\n * const api = new HostingerApi(token);\n *\n * // Get all records for a domain\n * const records = await api.getRecords('traflabs.io');\n *\n * // Create/update records\n * await api.upsertRecords('traflabs.io', [\n * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }\n * ]);\n * ```\n */\nexport class HostingerApi {\n\tprivate token: string;\n\n\tconstructor(token: string) {\n\t\tthis.token = token;\n\t}\n\n\t/**\n\t * Make a request to the Hostinger API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst url = `${HOSTINGER_API_BASE}${endpoint}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\tAuthorization: `Bearer ${this.token}`,\n\t\t\t},\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;\n\t\t\tlet errors: Record<string, string[]> | undefined;\n\n\t\t\ttry {\n\t\t\t\tconst errorBody = (await response.json()) as HostingerErrorResponse;\n\t\t\t\tif (errorBody.message) {\n\t\t\t\t\terrorMessage = `Hostinger API error: ${errorBody.message}`;\n\t\t\t\t}\n\t\t\t\terrors = errorBody.errors;\n\t\t\t} catch {\n\t\t\t\t// Ignore JSON parse errors\n\t\t\t}\n\n\t\t\tthrow new HostingerApiError(\n\t\t\t\terrorMessage,\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\terrors,\n\t\t\t);\n\t\t}\n\n\t\t// Handle empty responses\n\t\tconst text = await response.text();\n\t\tif (!text || text.trim() === '') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn JSON.parse(text) as T;\n\t}\n\n\t/**\n\t * Get all DNS records for a domain\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t */\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\t// The zone comes back as a **bare array**, not `{ data: [...] }`.\n\t\t//\n\t\t// This read `response.data || []`, so every call returned zero records\n\t\t// against the real API while every mocked test passed. Silently: no error,\n\t\t// no warning, just an empty zone. `upsertRecords` uses this to decide what\n\t\t// already exists, so everything looked new and every write was reported as\n\t\t// a creation — harmless only because the PUT merges rather than replaces.\n\t\t//\n\t\t// Both shapes are accepted because one of them is a guess: the wrapper was\n\t\t// written against documentation and the array is what the API actually\n\t\t// sends, so tolerating an envelope costs a line and survives it appearing.\n\t\tconst response = await this.request<DnsRecord[] | { data?: DnsRecord[] }>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\tif (Array.isArray(response)) return response;\n\n\t\treturn response?.data ?? [];\n\t}\n\n\t/**\n\t * Create or update DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to create/update\n\t * @param overwrite - If true, replaces all existing records. If false, merges with existing.\n\t */\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t\toverwrite = false,\n\t): Promise<void> {\n\t\tawait this.request('PUT', `/api/dns/v1/zones/${domain}`, {\n\t\t\toverwrite,\n\t\t\tzone: records,\n\t\t});\n\t}\n\n\t/**\n\t * Validate DNS records before applying\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to validate\n\t * @returns true if valid, throws if invalid\n\t */\n\tasync validateRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t): Promise<boolean> {\n\t\tawait this.request('POST', `/api/dns/v1/zones/${domain}/validate`, {\n\t\t\toverwrite: false,\n\t\t\tzone: records,\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * Delete specific DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param filters - Filters to match records for deletion\n\t */\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\tfilters: DnsRecordFilter[],\n\t): Promise<void> {\n\t\tawait this.request('DELETE', `/api/dns/v1/zones/${domain}`, {\n\t\t\tfilters,\n\t\t});\n\t}\n\n\t/**\n\t * Check if a specific record exists\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param name - Subdomain name (e.g., 'api.joemoer')\n\t * @param type - Record type (e.g., 'A')\n\t */\n\tasync recordExists(\n\t\tdomain: string,\n\t\tname: string,\n\t\ttype: DnsRecordType = 'A',\n\t): Promise<boolean> {\n\t\tconst records = await this.getRecords(domain);\n\t\treturn records.some((r) => r.name === name && r.type === type);\n\t}\n\n\t/**\n\t * Create a single A record if it doesn't exist\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param subdomain - Subdomain name (e.g., 'api.joemoer')\n\t * @param ip - IP address to point to\n\t * @param ttl - TTL in seconds (default: 300)\n\t * @returns true if created, false if already exists\n\t */\n\tasync createARecordIfNotExists(\n\t\tdomain: string,\n\t\tsubdomain: string,\n\t\tip: string,\n\t\tttl = 300,\n\t): Promise<boolean> {\n\t\tconst exists = await this.recordExists(domain, subdomain, 'A');\n\t\tif (exists) {\n\t\t\treturn false;\n\t\t}\n\n\t\tawait this.upsertRecords(domain, [\n\t\t\t{\n\t\t\t\tname: subdomain,\n\t\t\t\ttype: 'A',\n\t\t\t\tttl,\n\t\t\t\trecords: [{ content: ip }],\n\t\t\t},\n\t\t]);\n\n\t\treturn true;\n\t}\n}\n","/**\n * Hostinger DNS Provider\n *\n * Implements DnsProvider interface using the Hostinger DNS API.\n */\n\nimport { getHostingerToken } from '../../auth/credentials';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\nimport { HostingerApi } from './hostinger-api';\n\n/**\n * Hostinger DNS provider implementation.\n */\nexport class HostingerProvider implements DnsProvider {\n\treadonly name = 'hostinger';\n\tprivate api: HostingerApi | null = null;\n\n\t/**\n\t * Get or create the Hostinger API client.\n\t */\n\tprivate async getApi(): Promise<HostingerApi> {\n\t\tif (this.api) {\n\t\t\treturn this.api;\n\t\t}\n\n\t\tconst token = await getHostingerToken();\n\t\tif (!token) {\n\t\t\tthrow new Error(\n\t\t\t\t'Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api',\n\t\t\t);\n\t\t}\n\n\t\tthis.api = new HostingerApi(token);\n\t\treturn this.api;\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst api = await this.getApi();\n\t\tconst records = await api.getRecords(domain);\n\n\t\treturn records.map((r) => ({\n\t\t\tname: r.name,\n\t\t\ttype: r.type,\n\t\t\tttl: r.ttl,\n\t\t\tvalues: r.records.map((rec) => rec.content),\n\t\t}));\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to check what already exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\n\t\t\tconst existingValue = existing?.records?.[0]?.content;\n\n\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: false,\n\t\t\t\t\tunchanged: true,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Create or update the record\n\t\t\tawait api.upsertRecords(domain, [\n\t\t\t\t{\n\t\t\t\t\tname: record.name,\n\t\t\t\t\ttype: record.type,\n\t\t\t\t\tttl: record.ttl,\n\t\t\t\t\trecords: [{ content: record.value }],\n\t\t\t\t},\n\t\t\t]);\n\n\t\t\tresults.push({\n\t\t\t\trecord,\n\t\t\t\tcreated: !existing,\n\t\t\t\tunchanged: false,\n\t\t\t});\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to check what exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\t// Filter to only records that exist\n\t\tconst recordsToDelete = records.filter((record) =>\n\t\t\texistingRecords.some(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t),\n\t\t);\n\n\t\t// Delete existing records\n\t\tif (recordsToDelete.length > 0) {\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecords(\n\t\t\t\t\tdomain,\n\t\t\t\t\trecordsToDelete.map((r) => ({\n\t\t\t\t\t\tname: r.name,\n\t\t\t\t\t\ttype: r.type as\n\t\t\t\t\t\t\t| 'A'\n\t\t\t\t\t\t\t| 'AAAA'\n\t\t\t\t\t\t\t| 'CNAME'\n\t\t\t\t\t\t\t| 'MX'\n\t\t\t\t\t\t\t| 'TXT'\n\t\t\t\t\t\t\t| 'SRV'\n\t\t\t\t\t\t\t| 'CAA',\n\t\t\t\t\t})),\n\t\t\t\t);\n\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: true,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// If batch delete fails, report error for all records\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t\terror: String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Mark non-existent records as not found\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\t\t\tif (!existing) {\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: false,\n\t\t\t\t\tnotFound: true,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
- const require_credentials = require('./credentials-BapzjyV5.cjs');
2
-
1
+ const require_index = require("./index.cjs");
3
2
  //#region src/deploy/dns/hostinger-api.ts
4
3
  /**
5
4
  * Hostinger DNS API client
@@ -12,6 +11,9 @@ const HOSTINGER_API_BASE = "https://developers.hostinger.com";
12
11
  * Hostinger API error
13
12
  */
14
13
  var HostingerApiError = class extends Error {
14
+ status;
15
+ statusText;
16
+ errors;
15
17
  constructor(message, status, statusText, errors) {
16
18
  super(message);
17
19
  this.status = status;
@@ -65,7 +67,7 @@ var HostingerApi = class {
65
67
  throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
66
68
  }
67
69
  const text = await response.text();
68
- if (!text || text.trim() === "") return void 0;
70
+ if (!text || text.trim() === "") return;
69
71
  return JSON.parse(text);
70
72
  }
71
73
  /**
@@ -122,8 +124,7 @@ var HostingerApi = class {
122
124
  * @param type - Record type (e.g., 'A')
123
125
  */
124
126
  async recordExists(domain, name, type = "A") {
125
- const records = await this.getRecords(domain);
126
- return records.some((r) => r.name === name && r.type === type);
127
+ return (await this.getRecords(domain)).some((r) => r.name === name && r.type === type);
127
128
  }
128
129
  /**
129
130
  * Create a single A record if it doesn't exist
@@ -135,8 +136,7 @@ var HostingerApi = class {
135
136
  * @returns true if created, false if already exists
136
137
  */
137
138
  async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
138
- const exists = await this.recordExists(domain, subdomain, "A");
139
- if (exists) return false;
139
+ if (await this.recordExists(domain, subdomain, "A")) return false;
140
140
  await this.upsertRecords(domain, [{
141
141
  name: subdomain,
142
142
  type: "A",
@@ -146,10 +146,14 @@ var HostingerApi = class {
146
146
  return true;
147
147
  }
148
148
  };
149
-
150
149
  //#endregion
151
150
  //#region src/deploy/dns/HostingerProvider.ts
152
151
  /**
152
+ * Hostinger DNS Provider
153
+ *
154
+ * Implements DnsProvider interface using the Hostinger DNS API.
155
+ */
156
+ /**
153
157
  * Hostinger DNS provider implementation.
154
158
  */
155
159
  var HostingerProvider = class {
@@ -160,15 +164,13 @@ var HostingerProvider = class {
160
164
  */
161
165
  async getApi() {
162
166
  if (this.api) return this.api;
163
- const token = await require_credentials.getHostingerToken();
167
+ const token = await require_index.getHostingerToken();
164
168
  if (!token) throw new Error("Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api");
165
169
  this.api = new HostingerApi(token);
166
170
  return this.api;
167
171
  }
168
172
  async getRecords(domain) {
169
- const api = await this.getApi();
170
- const records = await api.getRecords(domain);
171
- return records.map((r) => ({
173
+ return (await (await this.getApi()).getRecords(domain)).map((r) => ({
172
174
  name: r.name,
173
175
  type: r.type,
174
176
  ttl: r.ttl,
@@ -227,18 +229,15 @@ var HostingerProvider = class {
227
229
  error: String(error)
228
230
  });
229
231
  }
230
- for (const record of records) {
231
- const existing = existingRecords.find((r) => r.name === record.name && r.type === record.type);
232
- if (!existing) results.push({
233
- record,
234
- deleted: false,
235
- notFound: true
236
- });
237
- }
232
+ for (const record of records) if (!existingRecords.find((r) => r.name === record.name && r.type === record.type)) results.push({
233
+ record,
234
+ deleted: false,
235
+ notFound: true
236
+ });
238
237
  return results;
239
238
  }
240
239
  };
241
-
242
240
  //#endregion
243
241
  exports.HostingerProvider = HostingerProvider;
244
- //# sourceMappingURL=HostingerProvider-h3EbEp-Z.cjs.map
242
+
243
+ //# sourceMappingURL=HostingerProvider-BZFGYYVA.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HostingerProvider-BZFGYYVA.cjs","names":["getHostingerToken"],"sources":["../src/deploy/dns/hostinger-api.ts","../src/deploy/dns/HostingerProvider.ts"],"sourcesContent":["/**\n * Hostinger DNS API client\n *\n * API Documentation: https://developers.hostinger.com/\n * Authentication: Bearer token from hpanel.hostinger.com/profile/api\n */\n\nconst HOSTINGER_API_BASE = 'https://developers.hostinger.com';\n\n/**\n * DNS record types supported by Hostinger\n */\nexport type DnsRecordType =\n\t| 'A'\n\t| 'AAAA'\n\t| 'CNAME'\n\t| 'MX'\n\t| 'TXT'\n\t| 'NS'\n\t| 'SRV'\n\t| 'CAA';\n\n/**\n * A single DNS record\n */\nexport interface DnsRecord {\n\t/** Subdomain name (e.g., 'api.joemoer' for api.joemoer.traflabs.io) */\n\tname: string;\n\t/** Record type */\n\ttype: DnsRecordType;\n\t/** TTL in seconds */\n\tttl: number;\n\t/** Record values */\n\trecords: Array<{ content: string }>;\n}\n\n/**\n * Filter for deleting specific records\n */\nexport interface DnsRecordFilter {\n\tname: string;\n\ttype: DnsRecordType;\n}\n\n/**\n * API error response\n */\nexport interface HostingerErrorResponse {\n\tmessage?: string;\n\terrors?: Record<string, string[]>;\n}\n\n/**\n * Hostinger API error\n */\nexport class HostingerApiError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic errors?: Record<string, string[]>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'HostingerApiError';\n\t}\n}\n\n/**\n * Hostinger DNS API client\n *\n * @example\n * ```ts\n * const api = new HostingerApi(token);\n *\n * // Get all records for a domain\n * const records = await api.getRecords('traflabs.io');\n *\n * // Create/update records\n * await api.upsertRecords('traflabs.io', [\n * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }\n * ]);\n * ```\n */\nexport class HostingerApi {\n\tprivate token: string;\n\n\tconstructor(token: string) {\n\t\tthis.token = token;\n\t}\n\n\t/**\n\t * Make a request to the Hostinger API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst url = `${HOSTINGER_API_BASE}${endpoint}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\tAuthorization: `Bearer ${this.token}`,\n\t\t\t},\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;\n\t\t\tlet errors: Record<string, string[]> | undefined;\n\n\t\t\ttry {\n\t\t\t\tconst errorBody = (await response.json()) as HostingerErrorResponse;\n\t\t\t\tif (errorBody.message) {\n\t\t\t\t\terrorMessage = `Hostinger API error: ${errorBody.message}`;\n\t\t\t\t}\n\t\t\t\terrors = errorBody.errors;\n\t\t\t} catch {\n\t\t\t\t// Ignore JSON parse errors\n\t\t\t}\n\n\t\t\tthrow new HostingerApiError(\n\t\t\t\terrorMessage,\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\terrors,\n\t\t\t);\n\t\t}\n\n\t\t// Handle empty responses\n\t\tconst text = await response.text();\n\t\tif (!text || text.trim() === '') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn JSON.parse(text) as T;\n\t}\n\n\t/**\n\t * Get all DNS records for a domain\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t */\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\t// The zone comes back as a **bare array**, not `{ data: [...] }`.\n\t\t//\n\t\t// This read `response.data || []`, so every call returned zero records\n\t\t// against the real API while every mocked test passed. Silently: no error,\n\t\t// no warning, just an empty zone. `upsertRecords` uses this to decide what\n\t\t// already exists, so everything looked new and every write was reported as\n\t\t// a creation — harmless only because the PUT merges rather than replaces.\n\t\t//\n\t\t// Both shapes are accepted because one of them is a guess: the wrapper was\n\t\t// written against documentation and the array is what the API actually\n\t\t// sends, so tolerating an envelope costs a line and survives it appearing.\n\t\tconst response = await this.request<DnsRecord[] | { data?: DnsRecord[] }>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\tif (Array.isArray(response)) return response;\n\n\t\treturn response?.data ?? [];\n\t}\n\n\t/**\n\t * Create or update DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to create/update\n\t * @param overwrite - If true, replaces all existing records. If false, merges with existing.\n\t */\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t\toverwrite = false,\n\t): Promise<void> {\n\t\tawait this.request('PUT', `/api/dns/v1/zones/${domain}`, {\n\t\t\toverwrite,\n\t\t\tzone: records,\n\t\t});\n\t}\n\n\t/**\n\t * Validate DNS records before applying\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to validate\n\t * @returns true if valid, throws if invalid\n\t */\n\tasync validateRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t): Promise<boolean> {\n\t\tawait this.request('POST', `/api/dns/v1/zones/${domain}/validate`, {\n\t\t\toverwrite: false,\n\t\t\tzone: records,\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * Delete specific DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param filters - Filters to match records for deletion\n\t */\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\tfilters: DnsRecordFilter[],\n\t): Promise<void> {\n\t\tawait this.request('DELETE', `/api/dns/v1/zones/${domain}`, {\n\t\t\tfilters,\n\t\t});\n\t}\n\n\t/**\n\t * Check if a specific record exists\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param name - Subdomain name (e.g., 'api.joemoer')\n\t * @param type - Record type (e.g., 'A')\n\t */\n\tasync recordExists(\n\t\tdomain: string,\n\t\tname: string,\n\t\ttype: DnsRecordType = 'A',\n\t): Promise<boolean> {\n\t\tconst records = await this.getRecords(domain);\n\t\treturn records.some((r) => r.name === name && r.type === type);\n\t}\n\n\t/**\n\t * Create a single A record if it doesn't exist\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param subdomain - Subdomain name (e.g., 'api.joemoer')\n\t * @param ip - IP address to point to\n\t * @param ttl - TTL in seconds (default: 300)\n\t * @returns true if created, false if already exists\n\t */\n\tasync createARecordIfNotExists(\n\t\tdomain: string,\n\t\tsubdomain: string,\n\t\tip: string,\n\t\tttl = 300,\n\t): Promise<boolean> {\n\t\tconst exists = await this.recordExists(domain, subdomain, 'A');\n\t\tif (exists) {\n\t\t\treturn false;\n\t\t}\n\n\t\tawait this.upsertRecords(domain, [\n\t\t\t{\n\t\t\t\tname: subdomain,\n\t\t\t\ttype: 'A',\n\t\t\t\tttl,\n\t\t\t\trecords: [{ content: ip }],\n\t\t\t},\n\t\t]);\n\n\t\treturn true;\n\t}\n}\n","/**\n * Hostinger DNS Provider\n *\n * Implements DnsProvider interface using the Hostinger DNS API.\n */\n\nimport { getHostingerToken } from '../../auth/credentials';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\nimport { HostingerApi } from './hostinger-api';\n\n/**\n * Hostinger DNS provider implementation.\n */\nexport class HostingerProvider implements DnsProvider {\n\treadonly name = 'hostinger';\n\tprivate api: HostingerApi | null = null;\n\n\t/**\n\t * Get or create the Hostinger API client.\n\t */\n\tprivate async getApi(): Promise<HostingerApi> {\n\t\tif (this.api) {\n\t\t\treturn this.api;\n\t\t}\n\n\t\tconst token = await getHostingerToken();\n\t\tif (!token) {\n\t\t\tthrow new Error(\n\t\t\t\t'Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api',\n\t\t\t);\n\t\t}\n\n\t\tthis.api = new HostingerApi(token);\n\t\treturn this.api;\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst api = await this.getApi();\n\t\tconst records = await api.getRecords(domain);\n\n\t\treturn records.map((r) => ({\n\t\t\tname: r.name,\n\t\t\ttype: r.type,\n\t\t\tttl: r.ttl,\n\t\t\tvalues: r.records.map((rec) => rec.content),\n\t\t}));\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to check what already exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\n\t\t\tconst existingValue = existing?.records?.[0]?.content;\n\n\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: false,\n\t\t\t\t\tunchanged: true,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Create or update the record\n\t\t\tawait api.upsertRecords(domain, [\n\t\t\t\t{\n\t\t\t\t\tname: record.name,\n\t\t\t\t\ttype: record.type,\n\t\t\t\t\tttl: record.ttl,\n\t\t\t\t\trecords: [{ content: record.value }],\n\t\t\t\t},\n\t\t\t]);\n\n\t\t\tresults.push({\n\t\t\t\trecord,\n\t\t\t\tcreated: !existing,\n\t\t\t\tunchanged: false,\n\t\t\t});\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to check what exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\t// Filter to only records that exist\n\t\tconst recordsToDelete = records.filter((record) =>\n\t\t\texistingRecords.some(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t),\n\t\t);\n\n\t\t// Delete existing records\n\t\tif (recordsToDelete.length > 0) {\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecords(\n\t\t\t\t\tdomain,\n\t\t\t\t\trecordsToDelete.map((r) => ({\n\t\t\t\t\t\tname: r.name,\n\t\t\t\t\t\ttype: r.type as\n\t\t\t\t\t\t\t| 'A'\n\t\t\t\t\t\t\t| 'AAAA'\n\t\t\t\t\t\t\t| 'CNAME'\n\t\t\t\t\t\t\t| 'MX'\n\t\t\t\t\t\t\t| 'TXT'\n\t\t\t\t\t\t\t| 'SRV'\n\t\t\t\t\t\t\t| 'CAA',\n\t\t\t\t\t})),\n\t\t\t\t);\n\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: true,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// If batch delete fails, report error for all records\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t\terror: String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Mark non-existent records as not found\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\t\t\tif (!existing) {\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: false,\n\t\t\t\t\tnotFound: true,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":";;;;;;;;AAOA,MAAM,qBAAqB;;;;AAgD3B,IAAa,oBAAb,cAAuC,MAAM;CAGpC;CACA;CACA;CAJR,YACC,SACA,QACA,YACA,QACC;EACD,MAAM,OAAO;EAJN,KAAA,SAAA;EACA,KAAA,aAAA;EACA,KAAA,SAAA;EAGP,KAAK,OAAO;CACb;AACD;;;;;;;;;;;;;;;;;AAkBA,IAAa,eAAb,MAA0B;CACzB;CAEA,YAAY,OAAe;EAC1B,KAAK,QAAQ;CACd;;;;CAKA,MAAc,QACb,QACA,UACA,MACa;EACb,MAAM,MAAM,GAAG,qBAAqB;EAEpC,MAAM,WAAW,MAAM,MAAM,KAAK;GACjC;GACA,SAAS;IACR,gBAAgB;IAChB,eAAe,UAAU,KAAK;GAC/B;GACA,MAAM,OAAO,KAAK,UAAU,IAAI,IAAI,KAAA;EACrC,CAAC;EAED,IAAI,CAAC,SAAS,IAAI;GACjB,IAAI,eAAe,wBAAwB,SAAS,OAAO,GAAG,SAAS;GACvE,IAAI;GAEJ,IAAI;IACH,MAAM,YAAa,MAAM,SAAS,KAAK;IACvC,IAAI,UAAU,SACb,eAAe,wBAAwB,UAAU;IAElD,SAAS,UAAU;GACpB,QAAQ,CAER;GAEA,MAAM,IAAI,kBACT,cACA,SAAS,QACT,SAAS,YACT,MACD;EACD;EAGA,MAAM,OAAO,MAAM,SAAS,KAAK;EACjC,IAAI,CAAC,QAAQ,KAAK,KAAK,MAAM,IAC5B;EAED,OAAO,KAAK,MAAM,IAAI;CACvB;;;;;;CAOA,MAAM,WAAW,QAAsC;EAYtD,MAAM,WAAW,MAAM,KAAK,QAC3B,OACA,qBAAqB,QACtB;EAEA,IAAI,MAAM,QAAQ,QAAQ,GAAG,OAAO;EAEpC,OAAO,UAAU,QAAQ,CAAC;CAC3B;;;;;;;;CASA,MAAM,cACL,QACA,SACA,YAAY,OACI;EAChB,MAAM,KAAK,QAAQ,OAAO,qBAAqB,UAAU;GACxD;GACA,MAAM;EACP,CAAC;CACF;;;;;;;;CASA,MAAM,gBACL,QACA,SACmB;EACnB,MAAM,KAAK,QAAQ,QAAQ,qBAAqB,OAAO,YAAY;GAClE,WAAW;GACX,MAAM;EACP,CAAC;EACD,OAAO;CACR;;;;;;;CAQA,MAAM,cACL,QACA,SACgB;EAChB,MAAM,KAAK,QAAQ,UAAU,qBAAqB,UAAU,EAC3D,QACD,CAAC;CACF;;;;;;;;CASA,MAAM,aACL,QACA,MACA,OAAsB,KACH;EAEnB,QAAO,MADe,KAAK,WAAW,MAAM,EAAA,CAC7B,MAAM,MAAM,EAAE,SAAS,QAAQ,EAAE,SAAS,IAAI;CAC9D;;;;;;;;;;CAWA,MAAM,yBACL,QACA,WACA,IACA,MAAM,KACa;EAEnB,IAAI,MADiB,KAAK,aAAa,QAAQ,WAAW,GAAG,GAE5D,OAAO;EAGR,MAAM,KAAK,cAAc,QAAQ,CAChC;GACC,MAAM;GACN,MAAM;GACN;GACA,SAAS,CAAC,EAAE,SAAS,GAAG,CAAC;EAC1B,CACD,CAAC;EAED,OAAO;CACR;AACD;;;;;;;;;;;ACpPA,IAAa,oBAAb,MAAsD;CACrD,OAAgB;CAChB,MAAmC;;;;CAKnC,MAAc,SAAgC;EAC7C,IAAI,KAAK,KACR,OAAO,KAAK;EAGb,MAAM,QAAQ,MAAMA,cAAAA,kBAAkB;EACtC,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,yIACD;EAGD,KAAK,MAAM,IAAI,aAAa,KAAK;EACjC,OAAO,KAAK;CACb;CAEA,MAAM,WAAW,QAAsC;EAItD,QAAO,OAFe,MADJ,KAAK,OAAO,EAAA,CACJ,WAAW,MAAM,EAAA,CAE5B,KAAK,OAAO;GAC1B,MAAM,EAAE;GACR,MAAM,EAAE;GACR,KAAK,EAAE;GACP,QAAQ,EAAE,QAAQ,KAAK,QAAQ,IAAI,OAAO;EAC3C,EAAE;CACH;CAEA,MAAM,cACL,QACA,SAC0B;EAC1B,MAAM,MAAM,MAAM,KAAK,OAAO;EAC9B,MAAM,UAA0B,CAAC;EAGjC,MAAM,kBAAkB,MAAM,IAAI,WAAW,MAAM;EAEnD,KAAK,MAAM,UAAU,SAAS;GAC7B,MAAM,WAAW,gBAAgB,MAC/B,MAAM,EAAE,SAAS,OAAO,QAAQ,EAAE,SAAS,OAAO,IACpD;GAEA,MAAM,gBAAgB,UAAU,UAAU,EAAE,EAAE;GAE9C,IAAI,YAAY,kBAAkB,OAAO,OAAO;IAE/C,QAAQ,KAAK;KACZ;KACA,SAAS;KACT,WAAW;IACZ,CAAC;IACD;GACD;GAGA,MAAM,IAAI,cAAc,QAAQ,CAC/B;IACC,MAAM,OAAO;IACb,MAAM,OAAO;IACb,KAAK,OAAO;IACZ,SAAS,CAAC,EAAE,SAAS,OAAO,MAAM,CAAC;GACpC,CACD,CAAC;GAED,QAAQ,KAAK;IACZ;IACA,SAAS,CAAC;IACV,WAAW;GACZ,CAAC;EACF;EAEA,OAAO;CACR;CAEA,MAAM,cACL,QACA,SAC0B;EAC1B,MAAM,MAAM,MAAM,KAAK,OAAO;EAC9B,MAAM,UAA0B,CAAC;EAGjC,MAAM,kBAAkB,MAAM,IAAI,WAAW,MAAM;EAGnD,MAAM,kBAAkB,QAAQ,QAAQ,WACvC,gBAAgB,MACd,MAAM,EAAE,SAAS,OAAO,QAAQ,EAAE,SAAS,OAAO,IACpD,CACD;EAGA,IAAI,gBAAgB,SAAS,GAC5B,IAAI;GACH,MAAM,IAAI,cACT,QACA,gBAAgB,KAAK,OAAO;IAC3B,MAAM,EAAE;IACR,MAAM,EAAE;GAQT,EAAE,CACH;GAEA,KAAK,MAAM,UAAU,iBACpB,QAAQ,KAAK;IACZ;IACA,SAAS;IACT,UAAU;GACX,CAAC;EAEH,SAAS,OAAO;GAEf,KAAK,MAAM,UAAU,iBACpB,QAAQ,KAAK;IACZ;IACA,SAAS;IACT,UAAU;IACV,OAAO,OAAO,KAAK;GACpB,CAAC;EAEH;EAID,KAAK,MAAM,UAAU,SAIpB,IAAI,CAHa,gBAAgB,MAC/B,MAAM,EAAE,SAAS,OAAO,QAAQ,EAAE,SAAS,OAAO,IAExC,GACX,QAAQ,KAAK;GACZ;GACA,SAAS;GACT,UAAU;EACX,CAAC;EAIH,OAAO;CACR;AACD"}
@@ -1,9 +1,13 @@
1
- const require_chunk = require('./chunk-CUT6urMc.cjs');
2
- const node_path = require_chunk.__toESM(require("node:path"));
3
- const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
4
-
1
+ let node_fs_promises = require("node:fs/promises");
2
+ let node_path = require("node:path");
5
3
  //#region src/deploy/LocalStateProvider.ts
6
4
  /**
5
+ * Local Filesystem State Provider
6
+ *
7
+ * Stores deployment state in .gkm/deploy-{stage}.json files.
8
+ * This is the default provider for local development.
9
+ */
10
+ /**
7
11
  * Get the state file path for a stage.
8
12
  */
9
13
  function getStateFilePath(workspaceRoot, stage) {
@@ -15,6 +19,7 @@ function getStateFilePath(workspaceRoot, stage) {
15
19
  * Stores state in .gkm/deploy-{stage}.json files in the workspace root.
16
20
  */
17
21
  var LocalStateProvider = class {
22
+ workspaceRoot;
18
23
  constructor(workspaceRoot) {
19
24
  this.workspaceRoot = workspaceRoot;
20
25
  }
@@ -37,7 +42,7 @@ var LocalStateProvider = class {
37
42
  await (0, node_fs_promises.writeFile)(filePath, JSON.stringify(state, null, 2));
38
43
  }
39
44
  };
40
-
41
45
  //#endregion
42
46
  exports.LocalStateProvider = LocalStateProvider;
43
- //# sourceMappingURL=LocalStateProvider-Db9SEjyH.cjs.map
47
+
48
+ //# sourceMappingURL=LocalStateProvider-7HR6SS2t.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalStateProvider-7HR6SS2t.cjs","names":["join","readFile","mkdir","writeFile"],"sources":["../src/deploy/LocalStateProvider.ts"],"sourcesContent":["/**\n * Local Filesystem State Provider\n *\n * Stores deployment state in .gkm/deploy-{stage}.json files.\n * This is the default provider for local development.\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/**\n * Get the state file path for a stage.\n */\nfunction getStateFilePath(workspaceRoot: string, stage: string): string {\n\treturn join(workspaceRoot, '.gkm', `deploy-${stage}.json`);\n}\n\n/**\n * Local filesystem state provider.\n *\n * Stores state in .gkm/deploy-{stage}.json files in the workspace root.\n */\nexport class LocalStateProvider implements StateProvider {\n\tconstructor(private readonly workspaceRoot: string) {}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\n\t\ttry {\n\t\t\tconst content = await readFile(filePath, 'utf-8');\n\t\t\treturn JSON.parse(content) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// File doesn't exist - return null\n\t\t\tif ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\t// Log other errors but don't fail\n\t\t\tconsole.warn(`Warning: Could not read deploy state: ${error}`);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\t\tconst dir = join(this.workspaceRoot, '.gkm');\n\n\t\t// Ensure .gkm directory exists\n\t\tawait mkdir(dir, { recursive: true });\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait writeFile(filePath, JSON.stringify(state, null, 2));\n\t}\n}\n"],"mappings":";;;;;;;;;;;;AAeA,SAAS,iBAAiB,eAAuB,OAAuB;CACvE,QAAA,GAAOA,UAAAA,KAAAA,CAAK,eAAe,QAAQ,UAAU,MAAM,MAAM;AAC1D;;;;;;AAOA,IAAa,qBAAb,MAAyD;CAC3B;CAA7B,YAAY,eAAwC;EAAvB,KAAA,gBAAA;CAAwB;CAErD,MAAM,KAAK,OAAkD;EAC5D,MAAM,WAAW,iBAAiB,KAAK,eAAe,KAAK;EAE3D,IAAI;GACH,MAAM,UAAU,OAAA,GAAMC,iBAAAA,SAAAA,CAAS,UAAU,OAAO;GAChD,OAAO,KAAK,MAAM,OAAO;EAC1B,SAAS,OAAO;GAEf,IAAK,MAAgC,SAAS,UAC7C,OAAO;GAGR,QAAQ,KAAK,yCAAyC,OAAO;GAC7D,OAAO;EACR;CACD;CAEA,MAAM,MAAM,OAAe,OAAyC;EACnE,MAAM,WAAW,iBAAiB,KAAK,eAAe,KAAK;EAC3D,MAAM,OAAA,GAAMD,UAAAA,KAAAA,CAAK,KAAK,eAAe,MAAM;EAG3C,OAAA,GAAME,iBAAAA,MAAAA,CAAM,KAAK,EAAE,WAAW,KAAK,CAAC;EAGpC,MAAM,kCAAiB,IAAI,KAAK,EAAA,CAAE,YAAY;EAE9C,OAAA,GAAMC,iBAAAA,UAAAA,CAAU,UAAU,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;CACzD;AACD"}
@@ -1,8 +1,13 @@
1
- import { join } from "node:path";
2
1
  import { mkdir, readFile, writeFile } from "node:fs/promises";
3
-
2
+ import { join } from "node:path";
4
3
  //#region src/deploy/LocalStateProvider.ts
5
4
  /**
5
+ * Local Filesystem State Provider
6
+ *
7
+ * Stores deployment state in .gkm/deploy-{stage}.json files.
8
+ * This is the default provider for local development.
9
+ */
10
+ /**
6
11
  * Get the state file path for a stage.
7
12
  */
8
13
  function getStateFilePath(workspaceRoot, stage) {
@@ -14,6 +19,7 @@ function getStateFilePath(workspaceRoot, stage) {
14
19
  * Stores state in .gkm/deploy-{stage}.json files in the workspace root.
15
20
  */
16
21
  var LocalStateProvider = class {
22
+ workspaceRoot;
17
23
  constructor(workspaceRoot) {
18
24
  this.workspaceRoot = workspaceRoot;
19
25
  }
@@ -36,7 +42,7 @@ var LocalStateProvider = class {
36
42
  await writeFile(filePath, JSON.stringify(state, null, 2));
37
43
  }
38
44
  };
39
-
40
45
  //#endregion
41
46
  export { LocalStateProvider };
42
- //# sourceMappingURL=LocalStateProvider-Bn6tS-oL.mjs.map
47
+
48
+ //# sourceMappingURL=LocalStateProvider-D7QyJoLM.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalStateProvider-D7QyJoLM.mjs","names":[],"sources":["../src/deploy/LocalStateProvider.ts"],"sourcesContent":["/**\n * Local Filesystem State Provider\n *\n * Stores deployment state in .gkm/deploy-{stage}.json files.\n * This is the default provider for local development.\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/**\n * Get the state file path for a stage.\n */\nfunction getStateFilePath(workspaceRoot: string, stage: string): string {\n\treturn join(workspaceRoot, '.gkm', `deploy-${stage}.json`);\n}\n\n/**\n * Local filesystem state provider.\n *\n * Stores state in .gkm/deploy-{stage}.json files in the workspace root.\n */\nexport class LocalStateProvider implements StateProvider {\n\tconstructor(private readonly workspaceRoot: string) {}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\n\t\ttry {\n\t\t\tconst content = await readFile(filePath, 'utf-8');\n\t\t\treturn JSON.parse(content) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// File doesn't exist - return null\n\t\t\tif ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\t// Log other errors but don't fail\n\t\t\tconsole.warn(`Warning: Could not read deploy state: ${error}`);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\t\tconst dir = join(this.workspaceRoot, '.gkm');\n\n\t\t// Ensure .gkm directory exists\n\t\tawait mkdir(dir, { recursive: true });\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait writeFile(filePath, JSON.stringify(state, null, 2));\n\t}\n}\n"],"mappings":";;;;;;;;;;;;AAeA,SAAS,iBAAiB,eAAuB,OAAuB;CACvE,OAAO,KAAK,eAAe,QAAQ,UAAU,MAAM,MAAM;AAC1D;;;;;;AAOA,IAAa,qBAAb,MAAyD;CAC3B;CAA7B,YAAY,eAAwC;EAAvB,KAAA,gBAAA;CAAwB;CAErD,MAAM,KAAK,OAAkD;EAC5D,MAAM,WAAW,iBAAiB,KAAK,eAAe,KAAK;EAE3D,IAAI;GACH,MAAM,UAAU,MAAM,SAAS,UAAU,OAAO;GAChD,OAAO,KAAK,MAAM,OAAO;EAC1B,SAAS,OAAO;GAEf,IAAK,MAAgC,SAAS,UAC7C,OAAO;GAGR,QAAQ,KAAK,yCAAyC,OAAO;GAC7D,OAAO;EACR;CACD;CAEA,MAAM,MAAM,OAAe,OAAyC;EACnE,MAAM,WAAW,iBAAiB,KAAK,eAAe,KAAK;EAC3D,MAAM,MAAM,KAAK,KAAK,eAAe,MAAM;EAG3C,MAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;EAGpC,MAAM,kCAAiB,IAAI,KAAK,EAAA,CAAE,YAAY;EAE9C,MAAM,UAAU,UAAU,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;CACzD;AACD"}