@geekmidas/cli 9.0.2 → 10.0.0-alpha.1

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 (390) hide show
  1. package/README.md +66 -122
  2. package/dist/{CachedStateProvider-BdX7klRc.cjs → CachedStateProvider-BLo7zv7S.cjs} +1 -1
  3. package/dist/CachedStateProvider-BxoAm-B2.mjs +3 -0
  4. package/dist/{CachedStateProvider-klLWZXWF.cjs → CachedStateProvider-CqPdW7CB.cjs} +1 -1
  5. package/dist/{CachedStateProvider-klLWZXWF.cjs.map → CachedStateProvider-CqPdW7CB.cjs.map} +1 -1
  6. package/dist/{CachedStateProvider-CzyFct42.mjs → CachedStateProvider-DTniehMP.mjs} +1 -1
  7. package/dist/{CachedStateProvider-CzyFct42.mjs.map → CachedStateProvider-DTniehMP.mjs.map} +1 -1
  8. package/dist/{HostingerProvider-B2ZYz7Yc.mjs → HostingerProvider-Cft7lEYr.mjs} +4 -3
  9. package/dist/HostingerProvider-Cft7lEYr.mjs.map +1 -0
  10. package/dist/{HostingerProvider-CrHfsgz9.cjs → HostingerProvider-h3EbEp-Z.cjs} +4 -3
  11. package/dist/HostingerProvider-h3EbEp-Z.cjs.map +1 -0
  12. package/dist/{LocalStateProvider-CP49rjzt.mjs → LocalStateProvider-Bn6tS-oL.mjs} +1 -1
  13. package/dist/{LocalStateProvider-CP49rjzt.mjs.map → LocalStateProvider-Bn6tS-oL.mjs.map} +1 -1
  14. package/dist/{LocalStateProvider-CalzNfNA.cjs → LocalStateProvider-Db9SEjyH.cjs} +1 -1
  15. package/dist/{LocalStateProvider-CalzNfNA.cjs.map → LocalStateProvider-Db9SEjyH.cjs.map} +1 -1
  16. package/dist/{Route53Provider-tlgBzrRQ.mjs → Route53Provider-RdpUDJQk.mjs} +1 -1
  17. package/dist/{Route53Provider-tlgBzrRQ.mjs.map → Route53Provider-RdpUDJQk.mjs.map} +1 -1
  18. package/dist/{Route53Provider-DwgXiLxt.cjs → Route53Provider-di2fMM5N.cjs} +1 -1
  19. package/dist/{Route53Provider-DwgXiLxt.cjs.map → Route53Provider-di2fMM5N.cjs.map} +1 -1
  20. package/dist/{SSMStateProvider-BlrTiV5m.mjs → SSMStateProvider-BRt2py-A.mjs} +1 -1
  21. package/dist/{SSMStateProvider-BlrTiV5m.mjs.map → SSMStateProvider-BRt2py-A.mjs.map} +1 -1
  22. package/dist/{SSMStateProvider-Bt6r8CTL.cjs → SSMStateProvider-DyX8s9HI.cjs} +1 -1
  23. package/dist/{SSMStateProvider-Bt6r8CTL.cjs.map → SSMStateProvider-DyX8s9HI.cjs.map} +1 -1
  24. package/dist/{backup-provisioner-Xunq-p_8.mjs → backup-provisioner-Bgj5Vi_L.mjs} +2 -2
  25. package/dist/{backup-provisioner-Xunq-p_8.mjs.map → backup-provisioner-Bgj5Vi_L.mjs.map} +1 -1
  26. package/dist/{backup-provisioner-B3HBN-2k.cjs → backup-provisioner-BrsHwYG0.cjs} +2 -2
  27. package/dist/{backup-provisioner-B3HBN-2k.cjs.map → backup-provisioner-BrsHwYG0.cjs.map} +1 -1
  28. package/dist/{bundler-Dufq3uvI.cjs → bundler-BY1OmV1e.cjs} +44 -14
  29. package/dist/bundler-BY1OmV1e.cjs.map +1 -0
  30. package/dist/{bundler-DkA4qzUD.mjs → bundler-Be8Zg4Wd.mjs} +45 -15
  31. package/dist/bundler-Be8Zg4Wd.mjs.map +1 -0
  32. package/dist/config-D8o6I_7Z.mjs +469 -0
  33. package/dist/config-D8o6I_7Z.mjs.map +1 -0
  34. package/dist/config-DbPE1KXF.cjs +535 -0
  35. package/dist/config-DbPE1KXF.cjs.map +1 -0
  36. package/dist/config.cjs +3 -2
  37. package/dist/config.d.cts +2 -2
  38. package/dist/config.d.cts.map +1 -1
  39. package/dist/config.d.mts +2 -2
  40. package/dist/config.d.mts.map +1 -1
  41. package/dist/config.mjs +3 -2
  42. package/dist/{credentials-s1kLcIzK.mjs → credentials-B5zRHdAl.mjs} +21 -6
  43. package/dist/credentials-B5zRHdAl.mjs.map +1 -0
  44. package/dist/{credentials-C8DWtnMY.cjs → credentials-BapzjyV5.cjs} +26 -5
  45. package/dist/credentials-BapzjyV5.cjs.map +1 -0
  46. package/dist/credentials-Dh0Q6HzY.mjs +2548 -0
  47. package/dist/credentials-Dh0Q6HzY.mjs.map +1 -0
  48. package/dist/credentials-K961ThG3.cjs +2680 -0
  49. package/dist/credentials-K961ThG3.cjs.map +1 -0
  50. package/dist/deploy/sniffer-routes-worker.cjs +6 -2
  51. package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
  52. package/dist/deploy/sniffer-routes-worker.mjs +6 -2
  53. package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
  54. package/dist/discover-CAlFgYec.mjs +229 -0
  55. package/dist/discover-CAlFgYec.mjs.map +1 -0
  56. package/dist/discover-DTmFeloL.cjs +271 -0
  57. package/dist/discover-DTmFeloL.cjs.map +1 -0
  58. package/dist/dokploy-api-BS2xnJLZ.mjs +3 -0
  59. package/dist/{dokploy-api-CqOIKHme.cjs → dokploy-api-BsVH0y_s.cjs} +141 -32
  60. package/dist/dokploy-api-BsVH0y_s.cjs.map +1 -0
  61. package/dist/dokploy-api-BuxDRo3l.cjs +3 -0
  62. package/dist/{dokploy-api-sfZFlWuE.mjs → dokploy-api-aTFPz7_4.mjs} +141 -32
  63. package/dist/dokploy-api-aTFPz7_4.mjs.map +1 -0
  64. package/dist/emit-CbcRolsa.cjs +236 -0
  65. package/dist/emit-CbcRolsa.cjs.map +1 -0
  66. package/dist/emit-CiZS_5Ub.mjs +206 -0
  67. package/dist/emit-CiZS_5Ub.mjs.map +1 -0
  68. package/dist/{encryption-MfXAiFTm.cjs → encryption-C6jaU0hE.cjs} +1 -1
  69. package/dist/{encryption-MfXAiFTm.cjs.map → encryption-C6jaU0hE.cjs.map} +1 -1
  70. package/dist/{encryption-BVS7Vmzs.cjs → encryption-CHzzeR8a.cjs} +1 -1
  71. package/dist/encryption-CQUncu7v.mjs +3 -0
  72. package/dist/{encryption-CO5Q4Bhf.mjs → encryption-D-abyhZx.mjs} +1 -1
  73. package/dist/{encryption-CO5Q4Bhf.mjs.map → encryption-D-abyhZx.mjs.map} +1 -1
  74. package/dist/fullstack-secrets-C-GGohke.cjs +111 -0
  75. package/dist/fullstack-secrets-C-GGohke.cjs.map +1 -0
  76. package/dist/fullstack-secrets-D1o9-G5l.mjs +87 -0
  77. package/dist/fullstack-secrets-D1o9-G5l.mjs.map +1 -0
  78. package/dist/{fullstack-secrets-BYzi6Hwl.cjs → generator-CNzBw7g5.cjs} +1 -114
  79. package/dist/generator-CNzBw7g5.cjs.map +1 -0
  80. package/dist/{fullstack-secrets-l8s3II_H.mjs → generator-VE2JrFlY.mjs} +2 -85
  81. package/dist/generator-VE2JrFlY.mjs.map +1 -0
  82. package/dist/index-BThUzEIp.d.mts +175 -0
  83. package/dist/index-BThUzEIp.d.mts.map +1 -0
  84. package/dist/index-CHB4tSzX.d.cts +175 -0
  85. package/dist/index-CHB4tSzX.d.cts.map +1 -0
  86. package/dist/index.cjs +2530 -1810
  87. package/dist/index.cjs.map +1 -1
  88. package/dist/index.mjs +2506 -1786
  89. package/dist/index.mjs.map +1 -1
  90. package/dist/{openapi-Cfwi0fqF.mjs → openapi-CgpMWMmM.mjs} +174 -95
  91. package/dist/openapi-CgpMWMmM.mjs.map +1 -0
  92. package/dist/{openapi-BzNOC7_i.cjs → openapi-CljONd_M.cjs} +175 -108
  93. package/dist/openapi-CljONd_M.cjs.map +1 -0
  94. package/dist/{openapi-react-query-C4iIvpPf.mjs → openapi-react-query-Byltvm_7.mjs} +2 -2
  95. package/dist/openapi-react-query-Byltvm_7.mjs.map +1 -0
  96. package/dist/{openapi-react-query-CYWik72n.cjs → openapi-react-query-CmJ1xDbn.cjs} +2 -2
  97. package/dist/openapi-react-query-CmJ1xDbn.cjs.map +1 -0
  98. package/dist/openapi-react-query.cjs +1 -1
  99. package/dist/openapi-react-query.mjs +1 -1
  100. package/dist/openapi.cjs +4 -3
  101. package/dist/openapi.d.cts +1 -1
  102. package/dist/openapi.d.mts +1 -1
  103. package/dist/openapi.mjs +4 -3
  104. package/dist/reconcile/public.cjs +9 -0
  105. package/dist/reconcile/public.d.cts +125 -0
  106. package/dist/reconcile/public.d.cts.map +1 -0
  107. package/dist/reconcile/public.d.mts +125 -0
  108. package/dist/reconcile/public.d.mts.map +1 -0
  109. package/dist/reconcile/public.mjs +4 -0
  110. package/dist/{reconcile-B2FoJE0a.mjs → reconcile-CFs8C_1a.mjs} +5 -4
  111. package/dist/reconcile-CFs8C_1a.mjs.map +1 -0
  112. package/dist/{reconcile-BzqKJ2Rt.cjs → reconcile-D0pimNiy.cjs} +5 -4
  113. package/dist/reconcile-D0pimNiy.cjs.map +1 -0
  114. package/dist/setup-CJiQM2pX.cjs +473 -0
  115. package/dist/setup-CJiQM2pX.cjs.map +1 -0
  116. package/dist/setup-DMtU3p41.cjs +11 -0
  117. package/dist/setup-DOsYueeQ.mjs +443 -0
  118. package/dist/setup-DOsYueeQ.mjs.map +1 -0
  119. package/dist/setup-DSutDAlt.mjs +11 -0
  120. package/dist/{storage-B1wvztiJ.cjs → storage-BJmzYxt8.cjs} +1 -1
  121. package/dist/{storage-B1wvztiJ.cjs.map → storage-BJmzYxt8.cjs.map} +1 -1
  122. package/dist/{storage-DOEtT2Hr.cjs → storage-BM9hGpgv.cjs} +1 -1
  123. package/dist/{storage-dbb9RyBl.mjs → storage-Bi24WfoW.mjs} +1 -1
  124. package/dist/{storage-dbb9RyBl.mjs.map → storage-Bi24WfoW.mjs.map} +1 -1
  125. package/dist/{storage-Cs4WBsc4.mjs → storage-CYlGwWTj.mjs} +1 -1
  126. package/dist/{sync-CSooqRQ8.mjs → sync-CmEuNZsB.mjs} +2 -2
  127. package/dist/{sync-CSooqRQ8.mjs.map → sync-CmEuNZsB.mjs.map} +1 -1
  128. package/dist/sync-D306yOWu.cjs +4 -0
  129. package/dist/{sync-o_CtEbvR.cjs → sync-SizaChmC.cjs} +2 -2
  130. package/dist/{sync-o_CtEbvR.cjs.map → sync-SizaChmC.cjs.map} +1 -1
  131. package/dist/{sync-B5UHXwRM.mjs → sync-hOqAWHR6.mjs} +1 -1
  132. package/dist/{index-Bw548Bta.d.mts → types-C-arnQ1r.d.mts} +590 -143
  133. package/dist/types-C-arnQ1r.d.mts.map +1 -0
  134. package/dist/{index-DTpkeFb8.d.cts → types-abJQuk7m.d.cts} +590 -143
  135. package/dist/types-abJQuk7m.d.cts.map +1 -0
  136. package/dist/workspace/index.cjs +5 -1
  137. package/dist/workspace/index.d.cts +3 -3
  138. package/dist/workspace/index.d.mts +3 -3
  139. package/dist/workspace/index.mjs +2 -2
  140. package/dist/{workspace-D-37Ylqo.mjs → workspace-7FqprxuJ.mjs} +117 -30
  141. package/dist/workspace-7FqprxuJ.mjs.map +1 -0
  142. package/dist/workspace-8acupeaI.cjs +9 -0
  143. package/dist/{workspace-CQTT7HJQ.cjs → workspace-XvqO9jIv.cjs} +139 -28
  144. package/dist/workspace-XvqO9jIv.cjs.map +1 -0
  145. package/dist/workspace-yP5AHnrn.mjs +8 -0
  146. package/package.json +26 -9
  147. package/CHANGELOG.md +0 -650
  148. package/FUNCTION_CRON_SUPPORT.md +0 -266
  149. package/dist/CachedStateProvider-GcGziXh1.mjs +0 -3
  150. package/dist/HostingerProvider-B2ZYz7Yc.mjs.map +0 -1
  151. package/dist/HostingerProvider-CrHfsgz9.cjs.map +0 -1
  152. package/dist/bundler-DkA4qzUD.mjs.map +0 -1
  153. package/dist/bundler-Dufq3uvI.cjs.map +0 -1
  154. package/dist/config-Bbu1JKvf.mjs +0 -211
  155. package/dist/config-Bbu1JKvf.mjs.map +0 -1
  156. package/dist/config-DujV8DbR.cjs +0 -253
  157. package/dist/config-DujV8DbR.cjs.map +0 -1
  158. package/dist/credentials-C8DWtnMY.cjs.map +0 -1
  159. package/dist/credentials-s1kLcIzK.mjs.map +0 -1
  160. package/dist/dokploy-api-BPYkG-ZZ.cjs +0 -3
  161. package/dist/dokploy-api-BiSzWM4v.mjs +0 -3
  162. package/dist/dokploy-api-CqOIKHme.cjs.map +0 -1
  163. package/dist/dokploy-api-sfZFlWuE.mjs.map +0 -1
  164. package/dist/encryption-Bd_m4q3g.mjs +0 -3
  165. package/dist/fullstack-secrets-BYzi6Hwl.cjs.map +0 -1
  166. package/dist/fullstack-secrets-l8s3II_H.mjs.map +0 -1
  167. package/dist/index-Bw548Bta.d.mts.map +0 -1
  168. package/dist/index-DTpkeFb8.d.cts.map +0 -1
  169. package/dist/openapi-BzNOC7_i.cjs.map +0 -1
  170. package/dist/openapi-Cfwi0fqF.mjs.map +0 -1
  171. package/dist/openapi-react-query-C4iIvpPf.mjs.map +0 -1
  172. package/dist/openapi-react-query-CYWik72n.cjs.map +0 -1
  173. package/dist/reconcile-B2FoJE0a.mjs.map +0 -1
  174. package/dist/reconcile-BzqKJ2Rt.cjs.map +0 -1
  175. package/dist/setup-DDXUXzzl.mjs +0 -765
  176. package/dist/setup-DDXUXzzl.mjs.map +0 -1
  177. package/dist/setup-DLwhfyt-.cjs +0 -8
  178. package/dist/setup-Dh-B7YTX.mjs +0 -8
  179. package/dist/setup-WZ-xqAQO.cjs +0 -849
  180. package/dist/setup-WZ-xqAQO.cjs.map +0 -1
  181. package/dist/sync-BLA94rgC.cjs +0 -4
  182. package/dist/types-B3GQUg3k.d.mts +0 -280
  183. package/dist/types-B3GQUg3k.d.mts.map +0 -1
  184. package/dist/types-BWQDiooe.d.cts +0 -280
  185. package/dist/types-BWQDiooe.d.cts.map +0 -1
  186. package/dist/workspace-CQTT7HJQ.cjs.map +0 -1
  187. package/dist/workspace-D-37Ylqo.mjs.map +0 -1
  188. package/docs/OPENAPI_TYPESCRIPT_DESIGN.md +0 -408
  189. package/docs/dev-server-resilience-design.md +0 -189
  190. package/docs/manifest-refactor-design.md +0 -287
  191. package/examples/cron-example.ts +0 -45
  192. package/examples/env.ts +0 -33
  193. package/examples/function-example.ts +0 -40
  194. package/examples/gkm.config.json +0 -22
  195. package/examples/gkm.config.ts +0 -14
  196. package/examples/gkm.minimal.config.json +0 -7
  197. package/examples/gkm.production.config.json +0 -27
  198. package/examples/logger.ts +0 -10
  199. package/scripts/sync-versions.ts +0 -108
  200. package/src/__tests__/EndpointGenerator.hooks.spec.ts +0 -204
  201. package/src/__tests__/config.spec.ts +0 -422
  202. package/src/__tests__/loadEnvFiles.spec.ts +0 -131
  203. package/src/__tests__/normalizeHooksConfig.spec.ts +0 -63
  204. package/src/__tests__/openapi-react-query.spec.ts +0 -506
  205. package/src/__tests__/openapi.spec.ts +0 -625
  206. package/src/__tests__/test-helpers.ts +0 -181
  207. package/src/auth/__tests__/credentials.spec.ts +0 -331
  208. package/src/auth/__tests__/index.spec.ts +0 -237
  209. package/src/auth/credentials.ts +0 -286
  210. package/src/auth/index.ts +0 -233
  211. package/src/build/__tests__/bundler.spec.ts +0 -446
  212. package/src/build/__tests__/endpoint-analyzer.spec.ts +0 -623
  213. package/src/build/__tests__/handler-templates.spec.ts +0 -1287
  214. package/src/build/__tests__/index-new.spec.ts +0 -550
  215. package/src/build/__tests__/manifests.spec.ts +0 -583
  216. package/src/build/__tests__/partitions.spec.ts +0 -110
  217. package/src/build/__tests__/workspace-build.spec.ts +0 -215
  218. package/src/build/bundler.ts +0 -256
  219. package/src/build/endpoint-analyzer.ts +0 -236
  220. package/src/build/handler-templates.ts +0 -1253
  221. package/src/build/index.ts +0 -616
  222. package/src/build/manifests.ts +0 -285
  223. package/src/build/partitions.ts +0 -58
  224. package/src/build/providerResolver.ts +0 -184
  225. package/src/build/types.ts +0 -104
  226. package/src/config.ts +0 -330
  227. package/src/credentials/__tests__/fullDockerPorts.spec.ts +0 -144
  228. package/src/credentials/__tests__/helpers.ts +0 -112
  229. package/src/credentials/__tests__/prepareEntryCredentials.spec.ts +0 -125
  230. package/src/credentials/__tests__/readonlyDockerPorts.spec.ts +0 -190
  231. package/src/credentials/__tests__/workspaceCredentials.spec.ts +0 -209
  232. package/src/credentials/index.ts +0 -843
  233. package/src/debug.ts +0 -68
  234. package/src/deploy/CachedStateProvider.ts +0 -86
  235. package/src/deploy/LocalStateProvider.ts +0 -57
  236. package/src/deploy/SSMStateProvider.ts +0 -117
  237. package/src/deploy/StateProvider.ts +0 -175
  238. package/src/deploy/__tests__/CachedStateProvider.spec.ts +0 -235
  239. package/src/deploy/__tests__/HostingerProvider.spec.ts +0 -347
  240. package/src/deploy/__tests__/LocalStateProvider.spec.ts +0 -130
  241. package/src/deploy/__tests__/Route53Provider.spec.ts +0 -425
  242. package/src/deploy/__tests__/SSMStateProvider.spec.ts +0 -201
  243. package/src/deploy/__tests__/__fixtures__/entry-apps/async-entry.ts +0 -24
  244. package/src/deploy/__tests__/__fixtures__/entry-apps/nested-config-entry.ts +0 -24
  245. package/src/deploy/__tests__/__fixtures__/entry-apps/no-env-entry.ts +0 -12
  246. package/src/deploy/__tests__/__fixtures__/entry-apps/simple-entry.ts +0 -14
  247. package/src/deploy/__tests__/__fixtures__/entry-apps/throwing-entry.ts +0 -16
  248. package/src/deploy/__tests__/__fixtures__/env-parsers/non-function-export.ts +0 -10
  249. package/src/deploy/__tests__/__fixtures__/env-parsers/optional-env-parser.ts +0 -21
  250. package/src/deploy/__tests__/__fixtures__/env-parsers/parseable-env-parser.ts +0 -18
  251. package/src/deploy/__tests__/__fixtures__/env-parsers/throwing-env-parser.ts +0 -16
  252. package/src/deploy/__tests__/__fixtures__/env-parsers/valid-env-parser.ts +0 -16
  253. package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/auth.ts +0 -16
  254. package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/health.ts +0 -13
  255. package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/users.ts +0 -15
  256. package/src/deploy/__tests__/__fixtures__/route-apps/services.ts +0 -55
  257. package/src/deploy/__tests__/backup-provisioner.spec.ts +0 -428
  258. package/src/deploy/__tests__/createDnsProvider.spec.ts +0 -195
  259. package/src/deploy/__tests__/createStateProvider.spec.ts +0 -126
  260. package/src/deploy/__tests__/dns-orchestration.spec.ts +0 -192
  261. package/src/deploy/__tests__/dns-verification.spec.ts +0 -229
  262. package/src/deploy/__tests__/docker.spec.ts +0 -149
  263. package/src/deploy/__tests__/dokploy-api.spec.ts +0 -697
  264. package/src/deploy/__tests__/dokploy.spec.ts +0 -435
  265. package/src/deploy/__tests__/domain.spec.ts +0 -235
  266. package/src/deploy/__tests__/env-resolver.spec.ts +0 -903
  267. package/src/deploy/__tests__/index.spec.ts +0 -1091
  268. package/src/deploy/__tests__/init.spec.ts +0 -793
  269. package/src/deploy/__tests__/secrets.spec.ts +0 -303
  270. package/src/deploy/__tests__/sniffer.spec.ts +0 -838
  271. package/src/deploy/__tests__/state-e2e.spec.ts +0 -387
  272. package/src/deploy/__tests__/state.spec.ts +0 -876
  273. package/src/deploy/__tests__/undeploy.spec.ts +0 -758
  274. package/src/deploy/backup-provisioner.ts +0 -316
  275. package/src/deploy/dns/DnsProvider.ts +0 -201
  276. package/src/deploy/dns/HostingerProvider.ts +0 -174
  277. package/src/deploy/dns/Route53Provider.ts +0 -340
  278. package/src/deploy/dns/hostinger-api.ts +0 -261
  279. package/src/deploy/dns/index.ts +0 -627
  280. package/src/deploy/docker.ts +0 -260
  281. package/src/deploy/dokploy-api.ts +0 -1101
  282. package/src/deploy/dokploy.ts +0 -177
  283. package/src/deploy/domain.ts +0 -134
  284. package/src/deploy/env-resolver.ts +0 -305
  285. package/src/deploy/index.ts +0 -2094
  286. package/src/deploy/init.ts +0 -427
  287. package/src/deploy/secrets.ts +0 -187
  288. package/src/deploy/sniffer-envkit-patch.ts +0 -61
  289. package/src/deploy/sniffer-hooks.ts +0 -57
  290. package/src/deploy/sniffer-loader.ts +0 -28
  291. package/src/deploy/sniffer-routes-worker.ts +0 -104
  292. package/src/deploy/sniffer-worker.ts +0 -75
  293. package/src/deploy/sniffer.ts +0 -578
  294. package/src/deploy/state-commands.ts +0 -274
  295. package/src/deploy/state.ts +0 -482
  296. package/src/deploy/types.ts +0 -102
  297. package/src/deploy/undeploy.ts +0 -407
  298. package/src/dev/__tests__/entry-integration.spec.ts +0 -347
  299. package/src/dev/__tests__/entry.spec.ts +0 -144
  300. package/src/dev/__tests__/index.spec.ts +0 -2084
  301. package/src/dev/index.ts +0 -1649
  302. package/src/docker/__tests__/compose.spec.ts +0 -1142
  303. package/src/docker/__tests__/templates.spec.ts +0 -694
  304. package/src/docker/compose.ts +0 -808
  305. package/src/docker/index.ts +0 -523
  306. package/src/docker/templates.ts +0 -1067
  307. package/src/exec/index.ts +0 -118
  308. package/src/generators/CronGenerator.ts +0 -105
  309. package/src/generators/EndpointGenerator.ts +0 -954
  310. package/src/generators/FunctionGenerator.ts +0 -117
  311. package/src/generators/Generator.ts +0 -147
  312. package/src/generators/OpenApiTsGenerator.ts +0 -821
  313. package/src/generators/QueueGenerator.ts +0 -245
  314. package/src/generators/SubscriberGenerator.ts +0 -282
  315. package/src/generators/TopicGenerator.ts +0 -45
  316. package/src/generators/__tests__/CronGenerator.spec.ts +0 -445
  317. package/src/generators/__tests__/EndpointGenerator.spec.ts +0 -544
  318. package/src/generators/__tests__/FunctionGenerator.spec.ts +0 -256
  319. package/src/generators/__tests__/Generator.bustCache.spec.ts +0 -72
  320. package/src/generators/__tests__/OpenApiTsGenerator.registry.spec.ts +0 -98
  321. package/src/generators/__tests__/OpenApiTsGenerator.responseType.spec.ts +0 -56
  322. package/src/generators/__tests__/QueueGenerator.spec.ts +0 -141
  323. package/src/generators/__tests__/SubscriberGenerator.spec.ts +0 -410
  324. package/src/generators/index.ts +0 -11
  325. package/src/index.ts +0 -972
  326. package/src/init/__tests__/generators.spec.ts +0 -1070
  327. package/src/init/__tests__/init.spec.ts +0 -577
  328. package/src/init/__tests__/utils.spec.ts +0 -104
  329. package/src/init/generators/auth.ts +0 -225
  330. package/src/init/generators/config.ts +0 -316
  331. package/src/init/generators/docker.ts +0 -388
  332. package/src/init/generators/env.ts +0 -62
  333. package/src/init/generators/index.ts +0 -6
  334. package/src/init/generators/mobile-expo.ts +0 -576
  335. package/src/init/generators/models.ts +0 -153
  336. package/src/init/generators/monorepo.ts +0 -475
  337. package/src/init/generators/package.ts +0 -95
  338. package/src/init/generators/source.ts +0 -15
  339. package/src/init/generators/test.ts +0 -178
  340. package/src/init/generators/ui.ts +0 -1473
  341. package/src/init/generators/web-tanstack.ts +0 -348
  342. package/src/init/generators/web.ts +0 -363
  343. package/src/init/index.ts +0 -542
  344. package/src/init/templates/api.ts +0 -404
  345. package/src/init/templates/index.ts +0 -342
  346. package/src/init/templates/minimal.ts +0 -207
  347. package/src/init/templates/serverless.ts +0 -148
  348. package/src/init/templates/worker.ts +0 -216
  349. package/src/init/utils.ts +0 -197
  350. package/src/init/versions.ts +0 -52
  351. package/src/openapi-react-query.ts +0 -288
  352. package/src/openapi.ts +0 -259
  353. package/src/secrets/__tests__/encryption.spec.ts +0 -226
  354. package/src/secrets/__tests__/generator.spec.ts +0 -575
  355. package/src/secrets/__tests__/index.spec.ts +0 -91
  356. package/src/secrets/__tests__/reconcile.spec.ts +0 -123
  357. package/src/secrets/__tests__/storage.spec.ts +0 -645
  358. package/src/secrets/encryption.ts +0 -91
  359. package/src/secrets/generator.ts +0 -329
  360. package/src/secrets/index.ts +0 -417
  361. package/src/secrets/keystore.ts +0 -144
  362. package/src/secrets/reconcile.ts +0 -53
  363. package/src/secrets/storage.ts +0 -337
  364. package/src/secrets/sync.ts +0 -136
  365. package/src/secrets/types.ts +0 -70
  366. package/src/setup/__tests__/auto-setup.spec.ts +0 -147
  367. package/src/setup/__tests__/reconcile-secrets.spec.ts +0 -439
  368. package/src/setup/fullstack-secrets.ts +0 -140
  369. package/src/setup/index.ts +0 -411
  370. package/src/test/__tests__/__fixtures__/workspace.ts +0 -104
  371. package/src/test/__tests__/api.spec.ts +0 -199
  372. package/src/test/__tests__/auth.spec.ts +0 -162
  373. package/src/test/__tests__/index.spec.ts +0 -502
  374. package/src/test/__tests__/web.spec.ts +0 -210
  375. package/src/test/index.ts +0 -198
  376. package/src/types.ts +0 -363
  377. package/src/upgrade/__tests__/index.spec.ts +0 -354
  378. package/src/upgrade/index.ts +0 -253
  379. package/src/workspace/__tests__/client-generator.spec.ts +0 -326
  380. package/src/workspace/__tests__/index.spec.ts +0 -766
  381. package/src/workspace/__tests__/publicEnv.spec.ts +0 -64
  382. package/src/workspace/__tests__/schema.spec.ts +0 -737
  383. package/src/workspace/__tests__/type-inference.spec.ts +0 -251
  384. package/src/workspace/client-generator.ts +0 -109
  385. package/src/workspace/index.ts +0 -433
  386. package/src/workspace/publicEnv.ts +0 -57
  387. package/src/workspace/schema.ts +0 -861
  388. package/src/workspace/types.ts +0 -943
  389. package/tsconfig.json +0 -9
  390. package/tsdown.config.ts +0 -22
@@ -1,2084 +0,0 @@
1
- import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
2
- import type { AddressInfo } from 'node:net';
3
- import { createServer } from 'node:net';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
- import { afterEach, beforeEach, describe, expect, it } from 'vitest';
7
- import type {
8
- NormalizedAppConfig,
9
- NormalizedWorkspace,
10
- } from '../../workspace/index.js';
11
- import {
12
- buildDockerComposeEnv,
13
- checkPortConflicts,
14
- findAvailablePort,
15
- generateAllDependencyEnvVars,
16
- generateServerEntryContent,
17
- isPortAvailable,
18
- loadPortState,
19
- loadSecretsForApp,
20
- normalizeHooksConfig,
21
- normalizeProductionConfig,
22
- normalizeStudioConfig,
23
- normalizeTelescopeConfig,
24
- parseComposePortMappings,
25
- parseComposeServiceNames,
26
- replacePortInUrl,
27
- rewriteUrlsWithPorts,
28
- savePortState,
29
- validateFrontendApp,
30
- validateFrontendApps,
31
- } from '../index';
32
-
33
- // Skip port-related tests in CI due to flaky port binding issues
34
- const describePortTests = process.env.CI ? describe.skip : describe;
35
-
36
- // Track servers to clean up after each test
37
- const activeServers: ReturnType<typeof createServer>[] = [];
38
-
39
- afterEach(async () => {
40
- // Close all servers and wait for them to fully close
41
- await Promise.all(
42
- activeServers.map(
43
- (server) =>
44
- new Promise<void>((resolve) => {
45
- server.close(() => resolve());
46
- }),
47
- ),
48
- );
49
- activeServers.length = 0;
50
- // Give OS time to release ports
51
- await new Promise((resolve) => setTimeout(resolve, 50));
52
- });
53
-
54
- /**
55
- * Helper to occupy a port for testing.
56
- * Pass port 0 to get a random available port.
57
- */
58
- function occupyPort(
59
- port: number,
60
- ): Promise<{ server: ReturnType<typeof createServer>; port: number }> {
61
- return new Promise((resolve, reject) => {
62
- const server = createServer();
63
-
64
- server.once('error', (err) => {
65
- reject(err);
66
- });
67
-
68
- server.once('listening', () => {
69
- activeServers.push(server);
70
- const actualPort = (server.address() as AddressInfo).port;
71
- resolve({ server, port: actualPort });
72
- });
73
-
74
- server.listen(port);
75
- });
76
- }
77
-
78
- describePortTests('Port Availability Functions', () => {
79
- describe('isPortAvailable', () => {
80
- it('should return true for an available port', async () => {
81
- // Get a random port, close it, then check availability
82
- const { server, port } = await occupyPort(0);
83
- server.close();
84
- await new Promise((resolve) => setTimeout(resolve, 50));
85
-
86
- const available = await isPortAvailable(port);
87
- expect(available).toBe(true);
88
- });
89
-
90
- it('should return false for a port in use', async () => {
91
- const { port } = await occupyPort(0);
92
-
93
- const available = await isPortAvailable(port);
94
- expect(available).toBe(false);
95
- // Server cleanup handled by afterEach
96
- });
97
-
98
- it('should handle multiple sequential checks correctly', async () => {
99
- // Get a port to test with
100
- const { server: tempServer, port } = await occupyPort(0);
101
- tempServer.close();
102
- await new Promise((resolve) => setTimeout(resolve, 50));
103
-
104
- // First check - port should be available
105
- const firstCheck = await isPortAvailable(port);
106
- expect(firstCheck).toBe(true);
107
-
108
- // Occupy the port
109
- await occupyPort(port);
110
-
111
- // Second check - port should be unavailable
112
- const secondCheck = await isPortAvailable(port);
113
- expect(secondCheck).toBe(false);
114
- // Server cleanup and third check handled by afterEach
115
- });
116
- });
117
-
118
- describe('findAvailablePort', () => {
119
- it('should return the preferred port if available', async () => {
120
- // Get a random port, close it, then use as preferred
121
- const { server, port: preferredPort } = await occupyPort(0);
122
- server.close();
123
- await new Promise((resolve) => setTimeout(resolve, 50));
124
-
125
- const foundPort = await findAvailablePort(preferredPort);
126
- expect(foundPort).toBe(preferredPort);
127
- });
128
-
129
- it('should return the next available port if preferred is in use', async () => {
130
- const { port: preferredPort } = await occupyPort(0);
131
-
132
- const foundPort = await findAvailablePort(preferredPort);
133
- expect(foundPort).toBe(preferredPort + 1);
134
- });
135
-
136
- it('should skip multiple occupied ports', async () => {
137
- // Get a base port
138
- const { port: basePort } = await occupyPort(0);
139
- // Occupy consecutive ports
140
- await occupyPort(basePort + 1);
141
- await occupyPort(basePort + 2);
142
-
143
- const foundPort = await findAvailablePort(basePort);
144
- expect(foundPort).toBe(basePort + 3);
145
- });
146
-
147
- it('should throw error if no available port found within max attempts', async () => {
148
- const { port: preferredPort } = await occupyPort(0);
149
- const maxAttempts = 3;
150
-
151
- // Occupy consecutive ports
152
- await occupyPort(preferredPort + 1);
153
- await occupyPort(preferredPort + 2);
154
-
155
- await expect(
156
- findAvailablePort(preferredPort, maxAttempts),
157
- ).rejects.toThrow(
158
- `Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`,
159
- );
160
- });
161
-
162
- it('should respect custom maxAttempts parameter', async () => {
163
- const { port: preferredPort } = await occupyPort(0);
164
- const maxAttempts = 5;
165
-
166
- // Occupy consecutive ports (4 total including base)
167
- await occupyPort(preferredPort + 1);
168
- await occupyPort(preferredPort + 2);
169
- await occupyPort(preferredPort + 3);
170
-
171
- const foundPort = await findAvailablePort(preferredPort, maxAttempts);
172
- // Should find port at preferredPort + 4 (within 5 attempts)
173
- expect(foundPort).toBe(preferredPort + 4);
174
- });
175
- });
176
- });
177
-
178
- describePortTests('DevServer', () => {
179
- describe('port selection', () => {
180
- it('should use requested port when available', async () => {
181
- // Get a random port, close it, then use as requested
182
- const { server, port: requestedPort } = await occupyPort(0);
183
- server.close();
184
- await new Promise((resolve) => setTimeout(resolve, 50));
185
-
186
- const actualPort = await findAvailablePort(requestedPort);
187
- expect(actualPort).toBe(requestedPort);
188
- });
189
-
190
- it('should select alternative port when requested is in use', async () => {
191
- const { port: requestedPort } = await occupyPort(0);
192
-
193
- const actualPort = await findAvailablePort(requestedPort);
194
- expect(actualPort).not.toBe(requestedPort);
195
- expect(actualPort).toBeGreaterThan(requestedPort);
196
- expect(actualPort).toBeLessThanOrEqual(requestedPort + 10);
197
- });
198
- });
199
- });
200
-
201
- describePortTests('devCommand edge cases', () => {
202
- it('should handle port conflicts gracefully', async () => {
203
- const { port } = await occupyPort(0);
204
-
205
- // The dev command should find an alternative port
206
- const alternativePort = await findAvailablePort(port);
207
- expect(alternativePort).toBeGreaterThan(port);
208
- });
209
-
210
- it('should handle concurrent port checks', async () => {
211
- // Get three random available ports as base
212
- const { server: s1, port: p1 } = await occupyPort(0);
213
- const { server: s2, port: p2 } = await occupyPort(0);
214
- const { server: s3, port: p3 } = await occupyPort(0);
215
- s1.close();
216
- s2.close();
217
- s3.close();
218
- await new Promise((resolve) => setTimeout(resolve, 50));
219
-
220
- // Run multiple port checks concurrently
221
- const results = await Promise.all([
222
- findAvailablePort(p1),
223
- findAvailablePort(p2),
224
- findAvailablePort(p3),
225
- ]);
226
-
227
- // All should succeed and return valid ports
228
- expect(results).toHaveLength(3);
229
- expect(results[0]).toBe(p1);
230
- expect(results[1]).toBe(p2);
231
- expect(results[2]).toBe(p3);
232
- });
233
- });
234
-
235
- describe('normalizeTelescopeConfig', () => {
236
- it('should return undefined when config is false', () => {
237
- const result = normalizeTelescopeConfig(false);
238
- expect(result).toBeUndefined();
239
- });
240
-
241
- it('should return default config when config is true', () => {
242
- const result = normalizeTelescopeConfig(true);
243
- expect(result).toEqual({
244
- enabled: true,
245
- path: '/__telescope',
246
- ignore: [],
247
- recordBody: true,
248
- maxEntries: 1000,
249
- websocket: true,
250
- });
251
- });
252
-
253
- it('should return default config when config is undefined', () => {
254
- const result = normalizeTelescopeConfig(undefined);
255
- expect(result).toEqual({
256
- enabled: true,
257
- path: '/__telescope',
258
- ignore: [],
259
- recordBody: true,
260
- maxEntries: 1000,
261
- websocket: true,
262
- });
263
- });
264
-
265
- it('should return undefined when config.enabled is false', () => {
266
- const result = normalizeTelescopeConfig({ enabled: false });
267
- expect(result).toBeUndefined();
268
- });
269
-
270
- it('should merge custom config with defaults', () => {
271
- const result = normalizeTelescopeConfig({
272
- path: '/__debug',
273
- ignore: ['/health', '/metrics'],
274
- recordBody: false,
275
- maxEntries: 500,
276
- });
277
- expect(result).toEqual({
278
- enabled: true,
279
- path: '/__debug',
280
- ignore: ['/health', '/metrics'],
281
- recordBody: false,
282
- maxEntries: 500,
283
- websocket: true,
284
- });
285
- });
286
-
287
- it('should use defaults for missing config values', () => {
288
- const result = normalizeTelescopeConfig({
289
- path: '/__custom',
290
- });
291
- expect(result).toEqual({
292
- enabled: true,
293
- path: '/__custom',
294
- ignore: [],
295
- recordBody: true,
296
- maxEntries: 1000,
297
- websocket: true,
298
- });
299
- });
300
-
301
- it('should handle empty object config', () => {
302
- const result = normalizeTelescopeConfig({});
303
- expect(result).toEqual({
304
- enabled: true,
305
- path: '/__telescope',
306
- ignore: [],
307
- recordBody: true,
308
- maxEntries: 1000,
309
- websocket: true,
310
- });
311
- });
312
-
313
- it('should allow disabling websocket', () => {
314
- const result = normalizeTelescopeConfig({
315
- websocket: false,
316
- });
317
- expect(result).toEqual({
318
- enabled: true,
319
- path: '/__telescope',
320
- ignore: [],
321
- recordBody: true,
322
- maxEntries: 1000,
323
- websocket: false,
324
- });
325
- });
326
- });
327
-
328
- describe('normalizeStudioConfig', () => {
329
- it('should return undefined when config is false', () => {
330
- const result = normalizeStudioConfig(false);
331
- expect(result).toBeUndefined();
332
- });
333
-
334
- it('should return default config when config is true', () => {
335
- const result = normalizeStudioConfig(true);
336
- expect(result).toEqual({
337
- enabled: true,
338
- path: '/__studio',
339
- schema: 'public',
340
- });
341
- });
342
-
343
- it('should return default config when config is undefined', () => {
344
- const result = normalizeStudioConfig(undefined);
345
- expect(result).toEqual({
346
- enabled: true,
347
- path: '/__studio',
348
- schema: 'public',
349
- });
350
- });
351
-
352
- it('should return undefined when config.enabled is false', () => {
353
- const result = normalizeStudioConfig({ enabled: false });
354
- expect(result).toBeUndefined();
355
- });
356
-
357
- it('should merge custom config with defaults', () => {
358
- const result = normalizeStudioConfig({
359
- path: '/__db',
360
- schema: 'custom_schema',
361
- });
362
- expect(result).toEqual({
363
- enabled: true,
364
- path: '/__db',
365
- schema: 'custom_schema',
366
- });
367
- });
368
- });
369
-
370
- describe('normalizeHooksConfig', () => {
371
- it('should return undefined when no hooks configured', () => {
372
- const result = normalizeHooksConfig(undefined);
373
- expect(result).toBeUndefined();
374
- });
375
-
376
- it('should return undefined when server hook not configured', () => {
377
- const result = normalizeHooksConfig({});
378
- expect(result).toBeUndefined();
379
- });
380
-
381
- it('should return path when server hook is configured', () => {
382
- const result = normalizeHooksConfig({ server: './src/hooks' });
383
- expect(result).toBeDefined();
384
- expect(result?.serverHooksPath).toContain('src/hooks.ts');
385
- });
386
-
387
- it('should handle .ts extension in path', () => {
388
- const result = normalizeHooksConfig({ server: './src/hooks.ts' });
389
- expect(result).toBeDefined();
390
- expect(result?.serverHooksPath).toContain('src/hooks.ts');
391
- });
392
- });
393
-
394
- describe('normalizeProductionConfig', () => {
395
- it('should return undefined when cliProduction is false', () => {
396
- const result = normalizeProductionConfig(false);
397
- expect(result).toBeUndefined();
398
- });
399
-
400
- it('should return undefined when cliProduction is false even with config', () => {
401
- const result = normalizeProductionConfig(false, { bundle: true });
402
- expect(result).toBeUndefined();
403
- });
404
-
405
- it('should return default config when cliProduction is true', () => {
406
- const result = normalizeProductionConfig(true);
407
- expect(result).toBeDefined();
408
- expect(result?.enabled).toBe(true);
409
- expect(result?.bundle).toBe(true);
410
- });
411
-
412
- it('should merge custom config with defaults', () => {
413
- const result = normalizeProductionConfig(true, { bundle: false });
414
- expect(result).toBeDefined();
415
- expect(result?.enabled).toBe(true);
416
- expect(result?.bundle).toBe(false);
417
- });
418
- });
419
-
420
- describe('Workspace Dev Server', () => {
421
- /**
422
- * Helper to create a test workspace configuration.
423
- * Automatically adds resolvedDeployTarget to each app.
424
- */
425
- function createTestWorkspace(
426
- apps: Record<string, Omit<NormalizedAppConfig, 'resolvedDeployTarget'>>,
427
- overrides: Partial<NormalizedWorkspace> = {},
428
- ): NormalizedWorkspace {
429
- const appsWithDeployTarget: NormalizedWorkspace['apps'] = {};
430
- for (const [name, app] of Object.entries(apps)) {
431
- appsWithDeployTarget[name] = {
432
- ...app,
433
- resolvedDeployTarget: 'dokploy',
434
- };
435
- }
436
- return {
437
- name: 'test-workspace',
438
- root: '/test/workspace',
439
- apps: appsWithDeployTarget,
440
- services: {},
441
- deploy: { default: 'dokploy' },
442
- shared: { packages: ['packages/*'] },
443
- secrets: {},
444
- ...overrides,
445
- };
446
- }
447
-
448
- describe('checkPortConflicts', () => {
449
- it('should return empty array when no conflicts', () => {
450
- const workspace = createTestWorkspace({
451
- api: {
452
- type: 'backend',
453
- path: 'apps/api',
454
- port: 3000,
455
- dependencies: [],
456
- },
457
- web: {
458
- type: 'web',
459
- path: 'apps/web',
460
- port: 3001,
461
- dependencies: [],
462
- },
463
- admin: {
464
- type: 'web',
465
- path: 'apps/admin',
466
- port: 3002,
467
- dependencies: [],
468
- },
469
- });
470
-
471
- const conflicts = checkPortConflicts(workspace);
472
- expect(conflicts).toEqual([]);
473
- });
474
-
475
- it('should detect port conflicts between two apps', () => {
476
- const workspace = createTestWorkspace({
477
- api: {
478
- type: 'backend',
479
- path: 'apps/api',
480
- port: 3000,
481
- dependencies: [],
482
- },
483
- web: {
484
- type: 'web',
485
- path: 'apps/web',
486
- port: 3000, // Same port as api!
487
- dependencies: [],
488
- },
489
- });
490
-
491
- const conflicts = checkPortConflicts(workspace);
492
- expect(conflicts).toHaveLength(1);
493
- expect(conflicts[0]).toEqual({
494
- app1: 'api',
495
- app2: 'web',
496
- port: 3000,
497
- });
498
- });
499
-
500
- it('should detect multiple port conflicts', () => {
501
- const workspace = createTestWorkspace({
502
- api: {
503
- type: 'backend',
504
- path: 'apps/api',
505
- port: 3000,
506
- dependencies: [],
507
- },
508
- auth: {
509
- type: 'backend',
510
- path: 'apps/auth',
511
- port: 3000, // Conflicts with api
512
- dependencies: [],
513
- },
514
- web: {
515
- type: 'web',
516
- path: 'apps/web',
517
- port: 3001,
518
- dependencies: [],
519
- },
520
- admin: {
521
- type: 'web',
522
- path: 'apps/admin',
523
- port: 3001, // Conflicts with web
524
- dependencies: [],
525
- },
526
- });
527
-
528
- const conflicts = checkPortConflicts(workspace);
529
- expect(conflicts).toHaveLength(2);
530
- });
531
-
532
- it('should handle single app workspace', () => {
533
- const workspace = createTestWorkspace({
534
- api: {
535
- type: 'backend',
536
- path: 'apps/api',
537
- port: 3000,
538
- dependencies: [],
539
- },
540
- });
541
-
542
- const conflicts = checkPortConflicts(workspace);
543
- expect(conflicts).toEqual([]);
544
- });
545
- });
546
-
547
- describe('generateAllDependencyEnvVars', () => {
548
- it('should generate empty object for apps with no dependencies', () => {
549
- const workspace = createTestWorkspace({
550
- api: {
551
- type: 'backend',
552
- path: 'apps/api',
553
- port: 3000,
554
- dependencies: [],
555
- },
556
- web: {
557
- type: 'web',
558
- path: 'apps/web',
559
- port: 3001,
560
- dependencies: [],
561
- },
562
- });
563
-
564
- const env = generateAllDependencyEnvVars(workspace);
565
- expect(env).toEqual({});
566
- });
567
-
568
- it('should generate URL env vars for dependencies', () => {
569
- const workspace = createTestWorkspace({
570
- api: {
571
- type: 'backend',
572
- path: 'apps/api',
573
- port: 3000,
574
- dependencies: [],
575
- },
576
- auth: {
577
- type: 'backend',
578
- path: 'apps/auth',
579
- port: 3001,
580
- dependencies: [],
581
- },
582
- web: {
583
- type: 'web',
584
- path: 'apps/web',
585
- port: 3002,
586
- dependencies: ['api', 'auth'],
587
- },
588
- });
589
-
590
- const env = generateAllDependencyEnvVars(workspace);
591
- expect(env).toEqual({
592
- API_URL: 'http://localhost:3000',
593
- NEXT_PUBLIC_API_URL: 'http://localhost:3000',
594
- AUTH_URL: 'http://localhost:3001',
595
- NEXT_PUBLIC_AUTH_URL: 'http://localhost:3001',
596
- });
597
- });
598
-
599
- it('should handle cross-dependencies between backend apps', () => {
600
- const workspace = createTestWorkspace({
601
- 'api-gateway': {
602
- type: 'backend',
603
- path: 'apps/api-gateway',
604
- port: 3000,
605
- dependencies: [],
606
- },
607
- 'user-service': {
608
- type: 'backend',
609
- path: 'apps/user-service',
610
- port: 3001,
611
- dependencies: [],
612
- },
613
- 'order-service': {
614
- type: 'backend',
615
- path: 'apps/order-service',
616
- port: 3002,
617
- dependencies: ['user-service'],
618
- },
619
- });
620
-
621
- const env = generateAllDependencyEnvVars(workspace);
622
- expect(env).toEqual({
623
- 'USER-SERVICE_URL': 'http://localhost:3001',
624
- 'NEXT_PUBLIC_USER-SERVICE_URL': 'http://localhost:3001',
625
- });
626
- });
627
-
628
- it('should use custom URL prefix', () => {
629
- const workspace = createTestWorkspace({
630
- api: {
631
- type: 'backend',
632
- path: 'apps/api',
633
- port: 3000,
634
- dependencies: [],
635
- },
636
- web: {
637
- type: 'web',
638
- path: 'apps/web',
639
- port: 3001,
640
- dependencies: ['api'],
641
- },
642
- });
643
-
644
- const env = generateAllDependencyEnvVars(workspace, 'http://127.0.0.1');
645
- expect(env).toEqual({
646
- API_URL: 'http://127.0.0.1:3000',
647
- NEXT_PUBLIC_API_URL: 'http://127.0.0.1:3000',
648
- });
649
- });
650
-
651
- it('should handle complex dependency graph', () => {
652
- const workspace = createTestWorkspace({
653
- api: {
654
- type: 'backend',
655
- path: 'apps/api',
656
- port: 3000,
657
- dependencies: [],
658
- },
659
- auth: {
660
- type: 'backend',
661
- path: 'apps/auth',
662
- port: 3001,
663
- dependencies: [],
664
- },
665
- payments: {
666
- type: 'backend',
667
- path: 'apps/payments',
668
- port: 3002,
669
- dependencies: ['auth'], // Payments depends on auth
670
- },
671
- web: {
672
- type: 'web',
673
- path: 'apps/web',
674
- port: 3003,
675
- dependencies: ['api', 'auth'], // Web depends on api and auth
676
- },
677
- admin: {
678
- type: 'web',
679
- path: 'apps/admin',
680
- port: 3004,
681
- dependencies: ['api', 'payments'], // Admin depends on api and payments
682
- },
683
- });
684
-
685
- const env = generateAllDependencyEnvVars(workspace);
686
- expect(env).toEqual({
687
- AUTH_URL: 'http://localhost:3001',
688
- NEXT_PUBLIC_AUTH_URL: 'http://localhost:3001',
689
- API_URL: 'http://localhost:3000',
690
- NEXT_PUBLIC_API_URL: 'http://localhost:3000',
691
- PAYMENTS_URL: 'http://localhost:3002',
692
- NEXT_PUBLIC_PAYMENTS_URL: 'http://localhost:3002',
693
- });
694
- });
695
- });
696
-
697
- describe('validateFrontendApp', () => {
698
- let testDir: string;
699
-
700
- beforeEach(() => {
701
- // Create a unique temp directory for each test
702
- testDir = join(
703
- tmpdir(),
704
- `gkm-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
705
- );
706
- mkdirSync(testDir, { recursive: true });
707
- });
708
-
709
- afterEach(() => {
710
- // Clean up temp directory
711
- if (existsSync(testDir)) {
712
- rmSync(testDir, { recursive: true, force: true });
713
- }
714
- });
715
-
716
- it('should validate a properly configured Next.js app', async () => {
717
- // Create a valid Next.js app structure
718
- const appDir = join(testDir, 'apps/web');
719
- mkdirSync(appDir, { recursive: true });
720
-
721
- // Create next.config.js
722
- writeFileSync(join(appDir, 'next.config.js'), 'module.exports = {}');
723
-
724
- // Create package.json with next dependency
725
- writeFileSync(
726
- join(appDir, 'package.json'),
727
- JSON.stringify({
728
- name: 'web',
729
- dependencies: { next: '^14.0.0', react: '^18.0.0' },
730
- scripts: { dev: 'next dev' },
731
- }),
732
- );
733
-
734
- const result = await validateFrontendApp('web', 'apps/web', testDir);
735
-
736
- expect(result.valid).toBe(true);
737
- expect(result.errors).toHaveLength(0);
738
- expect(result.warnings).toHaveLength(0);
739
- });
740
-
741
- it('should detect missing Next.js config file', async () => {
742
- const appDir = join(testDir, 'apps/web');
743
- mkdirSync(appDir, { recursive: true });
744
-
745
- // Create package.json without next.config.js
746
- writeFileSync(
747
- join(appDir, 'package.json'),
748
- JSON.stringify({
749
- name: 'web',
750
- dependencies: { next: '^14.0.0' },
751
- scripts: { dev: 'next dev' },
752
- }),
753
- );
754
-
755
- const result = await validateFrontendApp('web', 'apps/web', testDir);
756
-
757
- expect(result.valid).toBe(false);
758
- expect(result.errors).toContainEqual(
759
- expect.stringContaining('Next.js config file not found'),
760
- );
761
- });
762
-
763
- it('should detect missing Next.js dependency', async () => {
764
- const appDir = join(testDir, 'apps/web');
765
- mkdirSync(appDir, { recursive: true });
766
-
767
- // Create next.config.js
768
- writeFileSync(join(appDir, 'next.config.js'), 'module.exports = {}');
769
-
770
- // Create package.json WITHOUT next dependency
771
- writeFileSync(
772
- join(appDir, 'package.json'),
773
- JSON.stringify({
774
- name: 'web',
775
- dependencies: { react: '^18.0.0' },
776
- scripts: { dev: 'echo no next' },
777
- }),
778
- );
779
-
780
- const result = await validateFrontendApp('web', 'apps/web', testDir);
781
-
782
- expect(result.valid).toBe(false);
783
- expect(result.errors).toContainEqual(
784
- expect.stringContaining('Next.js not found in dependencies'),
785
- );
786
- });
787
-
788
- it('should detect missing package.json', async () => {
789
- const appDir = join(testDir, 'apps/web');
790
- mkdirSync(appDir, { recursive: true });
791
-
792
- // Create next.config.js but no package.json
793
- writeFileSync(join(appDir, 'next.config.js'), 'module.exports = {}');
794
-
795
- const result = await validateFrontendApp('web', 'apps/web', testDir);
796
-
797
- expect(result.valid).toBe(false);
798
- expect(result.errors).toContainEqual(
799
- expect.stringContaining('package.json not found'),
800
- );
801
- });
802
-
803
- it('should warn about missing dev script', async () => {
804
- const appDir = join(testDir, 'apps/web');
805
- mkdirSync(appDir, { recursive: true });
806
-
807
- writeFileSync(join(appDir, 'next.config.js'), 'module.exports = {}');
808
-
809
- // Create package.json WITHOUT dev script
810
- writeFileSync(
811
- join(appDir, 'package.json'),
812
- JSON.stringify({
813
- name: 'web',
814
- dependencies: { next: '^14.0.0' },
815
- // No scripts
816
- }),
817
- );
818
-
819
- const result = await validateFrontendApp('web', 'apps/web', testDir);
820
-
821
- expect(result.valid).toBe(true); // Still valid, just a warning
822
- expect(result.warnings).toContainEqual(
823
- expect.stringContaining('No "dev" script found'),
824
- );
825
- });
826
-
827
- it('should accept next.config.ts', async () => {
828
- const appDir = join(testDir, 'apps/web');
829
- mkdirSync(appDir, { recursive: true });
830
-
831
- // Create next.config.ts (TypeScript config)
832
- writeFileSync(
833
- join(appDir, 'next.config.ts'),
834
- 'export default { reactStrictMode: true }',
835
- );
836
-
837
- writeFileSync(
838
- join(appDir, 'package.json'),
839
- JSON.stringify({
840
- name: 'web',
841
- dependencies: { next: '^14.0.0' },
842
- scripts: { dev: 'next dev' },
843
- }),
844
- );
845
-
846
- const result = await validateFrontendApp('web', 'apps/web', testDir);
847
-
848
- expect(result.valid).toBe(true);
849
- });
850
-
851
- it('should accept next.config.mjs', async () => {
852
- const appDir = join(testDir, 'apps/web');
853
- mkdirSync(appDir, { recursive: true });
854
-
855
- // Create next.config.mjs (ESM config)
856
- writeFileSync(
857
- join(appDir, 'next.config.mjs'),
858
- 'export default { reactStrictMode: true }',
859
- );
860
-
861
- writeFileSync(
862
- join(appDir, 'package.json'),
863
- JSON.stringify({
864
- name: 'web',
865
- dependencies: { next: '^14.0.0' },
866
- scripts: { dev: 'next dev' },
867
- }),
868
- );
869
-
870
- const result = await validateFrontendApp('web', 'apps/web', testDir);
871
-
872
- expect(result.valid).toBe(true);
873
- });
874
-
875
- it('should detect next in devDependencies', async () => {
876
- const appDir = join(testDir, 'apps/web');
877
- mkdirSync(appDir, { recursive: true });
878
-
879
- writeFileSync(join(appDir, 'next.config.js'), 'module.exports = {}');
880
-
881
- // Next in devDependencies (valid)
882
- writeFileSync(
883
- join(appDir, 'package.json'),
884
- JSON.stringify({
885
- name: 'web',
886
- devDependencies: { next: '^14.0.0' },
887
- scripts: { dev: 'next dev' },
888
- }),
889
- );
890
-
891
- const result = await validateFrontendApp('web', 'apps/web', testDir);
892
-
893
- expect(result.valid).toBe(true);
894
- });
895
-
896
- it('should validate a properly configured Vite app', async () => {
897
- const appDir = join(testDir, 'apps/web');
898
- mkdirSync(appDir, { recursive: true });
899
-
900
- writeFileSync(join(appDir, 'vite.config.ts'), 'export default {}');
901
- writeFileSync(
902
- join(appDir, 'package.json'),
903
- JSON.stringify({
904
- name: 'web',
905
- devDependencies: { vite: '^5.0.0' },
906
- scripts: { dev: 'vite' },
907
- }),
908
- );
909
-
910
- const result = await validateFrontendApp('web', 'apps/web', testDir);
911
-
912
- expect(result.valid).toBe(true);
913
- expect(result.errors).toHaveLength(0);
914
- });
915
-
916
- it('should validate a properly configured Remix app', async () => {
917
- const appDir = join(testDir, 'apps/web');
918
- mkdirSync(appDir, { recursive: true });
919
-
920
- writeFileSync(join(appDir, 'vite.config.ts'), 'export default {}');
921
- writeFileSync(
922
- join(appDir, 'package.json'),
923
- JSON.stringify({
924
- name: 'web',
925
- devDependencies: {
926
- '@remix-run/dev': '^2.0.0',
927
- vite: '^5.0.0',
928
- },
929
- scripts: { dev: 'remix vite:dev' },
930
- }),
931
- );
932
-
933
- const result = await validateFrontendApp('web', 'apps/web', testDir);
934
-
935
- expect(result.valid).toBe(true);
936
- expect(result.errors).toHaveLength(0);
937
- });
938
-
939
- it('should warn but stay valid for a custom frontend with a dev script', async () => {
940
- const appDir = join(testDir, 'apps/web');
941
- mkdirSync(appDir, { recursive: true });
942
-
943
- // No recognized config, no recognized dep — but has a dev script
944
- writeFileSync(
945
- join(appDir, 'package.json'),
946
- JSON.stringify({
947
- name: 'web',
948
- dependencies: { react: '^18.0.0' },
949
- scripts: { dev: 'node serve.mjs' },
950
- }),
951
- );
952
-
953
- const result = await validateFrontendApp('web', 'apps/web', testDir);
954
-
955
- expect(result.valid).toBe(true);
956
- expect(result.errors).toHaveLength(0);
957
- expect(result.warnings).toContainEqual(
958
- expect.stringContaining('No recognized frontend framework detected'),
959
- );
960
- });
961
-
962
- it('should error for a custom frontend without a dev script', async () => {
963
- const appDir = join(testDir, 'apps/web');
964
- mkdirSync(appDir, { recursive: true });
965
-
966
- writeFileSync(
967
- join(appDir, 'package.json'),
968
- JSON.stringify({
969
- name: 'web',
970
- dependencies: { react: '^18.0.0' },
971
- }),
972
- );
973
-
974
- const result = await validateFrontendApp('web', 'apps/web', testDir);
975
-
976
- expect(result.valid).toBe(false);
977
- expect(result.errors).toContainEqual(
978
- expect.stringContaining('No "dev" script found'),
979
- );
980
- });
981
-
982
- it('should validate strictly against an explicit framework', async () => {
983
- const appDir = join(testDir, 'apps/web');
984
- mkdirSync(appDir, { recursive: true });
985
-
986
- // Has next.config.js + next dep — auto-detect would say nextjs
987
- writeFileSync(join(appDir, 'next.config.js'), 'module.exports = {}');
988
- writeFileSync(
989
- join(appDir, 'package.json'),
990
- JSON.stringify({
991
- name: 'web',
992
- dependencies: { next: '^14.0.0' },
993
- scripts: { dev: 'next dev' },
994
- }),
995
- );
996
-
997
- // Caller explicitly declares Vite — should fail because vite
998
- // config and vite dep are missing.
999
- const result = await validateFrontendApp(
1000
- 'web',
1001
- 'apps/web',
1002
- testDir,
1003
- 'vite',
1004
- );
1005
-
1006
- expect(result.valid).toBe(false);
1007
- expect(result.errors).toContainEqual(
1008
- expect.stringContaining('Vite config file not found'),
1009
- );
1010
- expect(result.errors).toContainEqual(
1011
- expect.stringContaining('Vite not found in dependencies'),
1012
- );
1013
- });
1014
- });
1015
-
1016
- describe('validateFrontendApps', () => {
1017
- let testDir: string;
1018
-
1019
- beforeEach(() => {
1020
- testDir = join(
1021
- tmpdir(),
1022
- `gkm-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
1023
- );
1024
- mkdirSync(testDir, { recursive: true });
1025
- });
1026
-
1027
- afterEach(() => {
1028
- if (existsSync(testDir)) {
1029
- rmSync(testDir, { recursive: true, force: true });
1030
- }
1031
- });
1032
-
1033
- it('should validate only frontend apps', async () => {
1034
- // Create valid frontend app
1035
- const webDir = join(testDir, 'apps/web');
1036
- mkdirSync(webDir, { recursive: true });
1037
- writeFileSync(join(webDir, 'next.config.js'), 'module.exports = {}');
1038
- writeFileSync(
1039
- join(webDir, 'package.json'),
1040
- JSON.stringify({
1041
- name: 'web',
1042
- dependencies: { next: '^14.0.0' },
1043
- scripts: { dev: 'next dev' },
1044
- }),
1045
- );
1046
-
1047
- const workspace: NormalizedWorkspace = {
1048
- name: 'test-workspace',
1049
- root: testDir,
1050
- apps: {
1051
- api: {
1052
- type: 'backend',
1053
- path: 'apps/api',
1054
- port: 3000,
1055
- dependencies: [],
1056
- resolvedDeployTarget: 'dokploy',
1057
- },
1058
- web: {
1059
- type: 'web',
1060
- path: 'apps/web',
1061
- port: 3001,
1062
- dependencies: ['api'],
1063
- resolvedDeployTarget: 'dokploy',
1064
- },
1065
- },
1066
- services: {},
1067
- deploy: { default: 'dokploy' },
1068
- shared: { packages: [] },
1069
- secrets: {},
1070
- };
1071
-
1072
- const results = await validateFrontendApps(workspace);
1073
-
1074
- // Should only validate the frontend app
1075
- expect(results).toHaveLength(1);
1076
- expect(results[0]?.appName).toBe('web');
1077
- expect(results[0]?.valid).toBe(true);
1078
- });
1079
-
1080
- it('should return empty array for backend-only workspace', async () => {
1081
- const workspace: NormalizedWorkspace = {
1082
- name: 'test-workspace',
1083
- root: testDir,
1084
- apps: {
1085
- api: {
1086
- type: 'backend',
1087
- path: 'apps/api',
1088
- port: 3000,
1089
- dependencies: [],
1090
- resolvedDeployTarget: 'dokploy',
1091
- },
1092
- },
1093
- services: {},
1094
- deploy: { default: 'dokploy' },
1095
- shared: { packages: [] },
1096
- secrets: {},
1097
- };
1098
-
1099
- const results = await validateFrontendApps(workspace);
1100
-
1101
- expect(results).toHaveLength(0);
1102
- });
1103
- });
1104
- });
1105
-
1106
- describe('loadSecretsForApp', () => {
1107
- let testDir: string;
1108
-
1109
- beforeEach(() => {
1110
- testDir = join(
1111
- tmpdir(),
1112
- `gkm-secrets-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
1113
- );
1114
- mkdirSync(testDir, { recursive: true });
1115
- });
1116
-
1117
- afterEach(() => {
1118
- if (existsSync(testDir)) {
1119
- rmSync(testDir, { recursive: true, force: true });
1120
- }
1121
- });
1122
-
1123
- /**
1124
- * Helper to create a secrets file in legacy (unencrypted) format.
1125
- * This matches the StageSecrets structure.
1126
- */
1127
- function createSecretsFile(
1128
- stage: string,
1129
- secrets: Record<string, string>,
1130
- root = testDir,
1131
- ) {
1132
- const secretsDir = join(root, '.gkm', 'secrets');
1133
- mkdirSync(secretsDir, { recursive: true });
1134
- const stageSecrets = {
1135
- stage,
1136
- createdAt: new Date().toISOString(),
1137
- updatedAt: new Date().toISOString(),
1138
- services: {},
1139
- urls: {},
1140
- custom: secrets,
1141
- };
1142
- writeFileSync(
1143
- join(secretsDir, `${stage}.json`),
1144
- JSON.stringify(stageSecrets, null, 2),
1145
- );
1146
- }
1147
-
1148
- describe('single app mode (no appName)', () => {
1149
- it('should return secrets as-is without mapping', async () => {
1150
- createSecretsFile('development', {
1151
- DATABASE_URL: 'postgresql://localhost/mydb',
1152
- JWT_SECRET: 'super-secret',
1153
- NODE_ENV: 'development',
1154
- });
1155
-
1156
- const secrets = await loadSecretsForApp(testDir);
1157
-
1158
- expect(secrets).toEqual({
1159
- DATABASE_URL: 'postgresql://localhost/mydb',
1160
- JWT_SECRET: 'super-secret',
1161
- NODE_ENV: 'development',
1162
- });
1163
- });
1164
-
1165
- it('should try dev stage first, then development', async () => {
1166
- createSecretsFile('dev', {
1167
- DATABASE_URL: 'postgresql://localhost/devdb',
1168
- });
1169
- createSecretsFile('development', {
1170
- DATABASE_URL: 'postgresql://localhost/developmentdb',
1171
- });
1172
-
1173
- const secrets = await loadSecretsForApp(testDir);
1174
-
1175
- // Should use 'dev' stage since it's checked first
1176
- expect(secrets.DATABASE_URL).toBe('postgresql://localhost/devdb');
1177
- });
1178
-
1179
- it('should fallback to development if dev does not exist', async () => {
1180
- createSecretsFile('development', {
1181
- DATABASE_URL: 'postgresql://localhost/developmentdb',
1182
- });
1183
-
1184
- const secrets = await loadSecretsForApp(testDir);
1185
-
1186
- expect(secrets.DATABASE_URL).toBe('postgresql://localhost/developmentdb');
1187
- });
1188
-
1189
- it('should return empty object if no secrets exist', async () => {
1190
- const secrets = await loadSecretsForApp(testDir);
1191
-
1192
- expect(secrets).toEqual({});
1193
- });
1194
- });
1195
-
1196
- describe('workspace app mode (with appName)', () => {
1197
- it('should map {APP}_DATABASE_URL to DATABASE_URL', async () => {
1198
- createSecretsFile('development', {
1199
- AUTH_DATABASE_URL: 'postgresql://auth_user:pass@localhost/authdb',
1200
- API_DATABASE_URL: 'postgresql://api_user:pass@localhost/apidb',
1201
- JWT_SECRET: 'shared-secret',
1202
- });
1203
-
1204
- const authSecrets = await loadSecretsForApp(testDir, 'auth');
1205
-
1206
- expect(authSecrets.DATABASE_URL).toBe(
1207
- 'postgresql://auth_user:pass@localhost/authdb',
1208
- );
1209
- // Original prefixed secrets are also available
1210
- expect(authSecrets.AUTH_DATABASE_URL).toBe(
1211
- 'postgresql://auth_user:pass@localhost/authdb',
1212
- );
1213
- expect(authSecrets.JWT_SECRET).toBe('shared-secret');
1214
- });
1215
-
1216
- it('should map API secrets correctly', async () => {
1217
- createSecretsFile('development', {
1218
- AUTH_DATABASE_URL: 'postgresql://auth_user:pass@localhost/authdb',
1219
- API_DATABASE_URL: 'postgresql://api_user:pass@localhost/apidb',
1220
- });
1221
-
1222
- const apiSecrets = await loadSecretsForApp(testDir, 'api');
1223
-
1224
- expect(apiSecrets.DATABASE_URL).toBe(
1225
- 'postgresql://api_user:pass@localhost/apidb',
1226
- );
1227
- });
1228
-
1229
- it('should not override DATABASE_URL if no prefixed version exists', async () => {
1230
- createSecretsFile('development', {
1231
- DATABASE_URL: 'postgresql://localhost/maindb',
1232
- JWT_SECRET: 'secret',
1233
- });
1234
-
1235
- // Asking for 'auth' app but AUTH_DATABASE_URL doesn't exist
1236
- const authSecrets = await loadSecretsForApp(testDir, 'auth');
1237
-
1238
- // Should keep the original DATABASE_URL since there's no AUTH_DATABASE_URL
1239
- expect(authSecrets.DATABASE_URL).toBe('postgresql://localhost/maindb');
1240
- });
1241
-
1242
- it('should handle uppercase app names in secrets', async () => {
1243
- createSecretsFile('development', {
1244
- MYSERVICE_DATABASE_URL: 'postgresql://localhost/myservicedb',
1245
- });
1246
-
1247
- // App name is lowercase but secrets are uppercase prefixed
1248
- const secrets = await loadSecretsForApp(testDir, 'myservice');
1249
-
1250
- expect(secrets.DATABASE_URL).toBe('postgresql://localhost/myservicedb');
1251
- });
1252
-
1253
- it('should return empty object if no secrets exist for app', async () => {
1254
- const secrets = await loadSecretsForApp(testDir, 'api');
1255
-
1256
- expect(secrets).toEqual({});
1257
- });
1258
- });
1259
-
1260
- describe('service credentials mapping', () => {
1261
- it('should include postgres service credentials', async () => {
1262
- const secretsDir = join(testDir, '.gkm', 'secrets');
1263
- mkdirSync(secretsDir, { recursive: true });
1264
- const stageSecrets = {
1265
- stage: 'development',
1266
- createdAt: new Date().toISOString(),
1267
- updatedAt: new Date().toISOString(),
1268
- services: {
1269
- postgres: {
1270
- username: 'postgres',
1271
- password: 'postgres123',
1272
- database: 'myapp',
1273
- host: 'localhost',
1274
- port: 5432,
1275
- },
1276
- },
1277
- urls: {},
1278
- custom: { NODE_ENV: 'development' },
1279
- };
1280
- writeFileSync(
1281
- join(secretsDir, 'development.json'),
1282
- JSON.stringify(stageSecrets, null, 2),
1283
- );
1284
-
1285
- const secrets = await loadSecretsForApp(testDir);
1286
-
1287
- expect(secrets.POSTGRES_USER).toBe('postgres');
1288
- expect(secrets.POSTGRES_PASSWORD).toBe('postgres123');
1289
- expect(secrets.POSTGRES_DB).toBe('myapp');
1290
- expect(secrets.POSTGRES_HOST).toBe('localhost');
1291
- expect(secrets.POSTGRES_PORT).toBe('5432');
1292
- expect(secrets.NODE_ENV).toBe('development');
1293
- });
1294
-
1295
- it('should include redis service credentials', async () => {
1296
- const secretsDir = join(testDir, '.gkm', 'secrets');
1297
- mkdirSync(secretsDir, { recursive: true });
1298
- const stageSecrets = {
1299
- stage: 'development',
1300
- createdAt: new Date().toISOString(),
1301
- updatedAt: new Date().toISOString(),
1302
- services: {
1303
- redis: {
1304
- password: 'redis123',
1305
- host: 'localhost',
1306
- port: 6379,
1307
- },
1308
- },
1309
- urls: {},
1310
- custom: {},
1311
- };
1312
- writeFileSync(
1313
- join(secretsDir, 'development.json'),
1314
- JSON.stringify(stageSecrets, null, 2),
1315
- );
1316
-
1317
- const secrets = await loadSecretsForApp(testDir);
1318
-
1319
- expect(secrets.REDIS_PASSWORD).toBe('redis123');
1320
- expect(secrets.REDIS_HOST).toBe('localhost');
1321
- expect(secrets.REDIS_PORT).toBe('6379');
1322
- });
1323
- });
1324
- });
1325
-
1326
- describe('replacePortInUrl', () => {
1327
- it('should replace port in a postgresql URL', () => {
1328
- const url = 'postgresql://app:pass@localhost:5432/mydb';
1329
- expect(replacePortInUrl(url, 5432, 5433)).toBe(
1330
- 'postgresql://app:pass@localhost:5433/mydb',
1331
- );
1332
- });
1333
-
1334
- it('should replace port in a redis URL', () => {
1335
- const url = 'redis://:pass@localhost:6379';
1336
- expect(replacePortInUrl(url, 6379, 6380)).toBe(
1337
- 'redis://:pass@localhost:6380',
1338
- );
1339
- });
1340
-
1341
- it('should replace port with Docker hostname', () => {
1342
- const url = 'postgresql://app:pass@postgres:5432/mydb';
1343
- expect(replacePortInUrl(url, 5432, 5433)).toBe(
1344
- 'postgresql://app:pass@postgres:5433/mydb',
1345
- );
1346
- });
1347
-
1348
- it('should return url unchanged when ports are equal', () => {
1349
- const url = 'postgresql://app:pass@localhost:5432/mydb';
1350
- expect(replacePortInUrl(url, 5432, 5432)).toBe(url);
1351
- });
1352
-
1353
- it('should not replace port that appears in path or password', () => {
1354
- const url = 'postgresql://app:pass5432@localhost:5432/db5432';
1355
- const result = replacePortInUrl(url, 5432, 5433);
1356
- // Only the :5432 before / should be replaced
1357
- expect(result).toBe('postgresql://app:pass5432@localhost:5433/db5432');
1358
- });
1359
-
1360
- it('should replace port followed by query string', () => {
1361
- const url = 'pgboss://pgboss:pass@localhost:5432/mydb?schema=pgboss';
1362
- expect(replacePortInUrl(url, 5432, 5433)).toBe(
1363
- 'pgboss://pgboss:pass@localhost:5433/mydb?schema=pgboss',
1364
- );
1365
- });
1366
-
1367
- it('should replace port in SNS URL with query params and no path', () => {
1368
- const url = 'sns://key:secret@localhost:4566?region=us-east-1';
1369
- expect(replacePortInUrl(url, 4566, 4567)).toBe(
1370
- 'sns://key:secret@localhost:4567?region=us-east-1',
1371
- );
1372
- });
1373
-
1374
- it('should replace URL-encoded port in query params', () => {
1375
- const url =
1376
- 'sns://key:secret@localhost:4566?region=us-east-1&endpoint=http%3A%2F%2Flocalhost%3A4566';
1377
- expect(replacePortInUrl(url, 4566, 4567)).toBe(
1378
- 'sns://key:secret@localhost:4567?region=us-east-1&endpoint=http%3A%2F%2Flocalhost%3A4567',
1379
- );
1380
- });
1381
- });
1382
-
1383
- describe('rewriteUrlsWithPorts', () => {
1384
- const pgMapping = {
1385
- service: 'postgres',
1386
- envVar: 'POSTGRES_HOST_PORT',
1387
- defaultPort: 5432,
1388
- containerPort: 5432,
1389
- };
1390
- const redisMapping = {
1391
- service: 'redis',
1392
- envVar: 'REDIS_HOST_PORT',
1393
- defaultPort: 6379,
1394
- containerPort: 6379,
1395
- };
1396
- const rmqMapping = {
1397
- service: 'rabbitmq',
1398
- envVar: 'RABBITMQ_HOST_PORT',
1399
- defaultPort: 5672,
1400
- containerPort: 5672,
1401
- };
1402
-
1403
- it('should rewrite DATABASE_URL with resolved postgres port and hostname', () => {
1404
- const secrets = {
1405
- DATABASE_URL: 'postgresql://app:pass@postgres:5432/mydb',
1406
- POSTGRES_PORT: '5432',
1407
- POSTGRES_HOST: 'postgres',
1408
- SOME_OTHER: 'value',
1409
- };
1410
- const result = rewriteUrlsWithPorts(secrets, {
1411
- dockerEnv: { POSTGRES_HOST_PORT: '5433' },
1412
- ports: { POSTGRES_HOST_PORT: 5433 },
1413
- mappings: [pgMapping],
1414
- });
1415
- expect(result.DATABASE_URL).toBe(
1416
- 'postgresql://app:pass@localhost:5433/mydb',
1417
- );
1418
- expect(result.POSTGRES_PORT).toBe('5433');
1419
- expect(result.POSTGRES_HOST).toBe('localhost');
1420
- expect(result.SOME_OTHER).toBe('value');
1421
- });
1422
-
1423
- it('should rewrite fullstack APP_DATABASE_URL', () => {
1424
- const secrets = {
1425
- API_DATABASE_URL: 'postgresql://api:pass@localhost:5432/mydb',
1426
- AUTH_DATABASE_URL: 'postgresql://auth:pass@localhost:5432/mydb',
1427
- };
1428
- const result = rewriteUrlsWithPorts(secrets, {
1429
- dockerEnv: { POSTGRES_HOST_PORT: '5433' },
1430
- ports: { POSTGRES_HOST_PORT: 5433 },
1431
- mappings: [pgMapping],
1432
- });
1433
- expect(result.API_DATABASE_URL).toBe(
1434
- 'postgresql://api:pass@localhost:5433/mydb',
1435
- );
1436
- expect(result.AUTH_DATABASE_URL).toBe(
1437
- 'postgresql://auth:pass@localhost:5433/mydb',
1438
- );
1439
- });
1440
-
1441
- it('should rewrite REDIS_URL, REDIS_PORT, and REDIS_HOST', () => {
1442
- const secrets = {
1443
- REDIS_URL: 'redis://:pass@redis:6379',
1444
- REDIS_PORT: '6379',
1445
- REDIS_HOST: 'redis',
1446
- };
1447
- const result = rewriteUrlsWithPorts(secrets, {
1448
- dockerEnv: { REDIS_HOST_PORT: '6380' },
1449
- ports: { REDIS_HOST_PORT: 6380 },
1450
- mappings: [redisMapping],
1451
- });
1452
- expect(result.REDIS_URL).toBe('redis://:pass@localhost:6380');
1453
- expect(result.REDIS_PORT).toBe('6380');
1454
- expect(result.REDIS_HOST).toBe('localhost');
1455
- });
1456
-
1457
- it('should rewrite RABBITMQ_URL and RABBITMQ_PORT', () => {
1458
- const secrets = {
1459
- RABBITMQ_URL: 'amqp://app:pass@rabbitmq:5672/%2F',
1460
- RABBITMQ_PORT: '5672',
1461
- };
1462
- const result = rewriteUrlsWithPorts(secrets, {
1463
- dockerEnv: { RABBITMQ_HOST_PORT: '5673' },
1464
- ports: { RABBITMQ_HOST_PORT: 5673 },
1465
- mappings: [rmqMapping],
1466
- });
1467
- expect(result.RABBITMQ_URL).toBe('amqp://app:pass@localhost:5673/%2F');
1468
- expect(result.RABBITMQ_PORT).toBe('5673');
1469
- });
1470
-
1471
- it('should handle multiple services at once', () => {
1472
- const secrets = {
1473
- DATABASE_URL: 'postgresql://app:pass@postgres:5432/mydb',
1474
- POSTGRES_PORT: '5432',
1475
- REDIS_URL: 'redis://:pass@redis:6379',
1476
- REDIS_PORT: '6379',
1477
- };
1478
- const result = rewriteUrlsWithPorts(secrets, {
1479
- dockerEnv: {
1480
- POSTGRES_HOST_PORT: '5433',
1481
- REDIS_HOST_PORT: '6380',
1482
- },
1483
- ports: { POSTGRES_HOST_PORT: 5433, REDIS_HOST_PORT: 6380 },
1484
- mappings: [pgMapping, redisMapping],
1485
- });
1486
- expect(result.DATABASE_URL).toBe(
1487
- 'postgresql://app:pass@localhost:5433/mydb',
1488
- );
1489
- expect(result.POSTGRES_PORT).toBe('5433');
1490
- expect(result.REDIS_URL).toBe('redis://:pass@localhost:6380');
1491
- expect(result.REDIS_PORT).toBe('6380');
1492
- });
1493
-
1494
- it('should rewrite hostnames even when ports are defaults', () => {
1495
- const secrets = {
1496
- DATABASE_URL: 'postgresql://app:pass@postgres:5432/mydb',
1497
- POSTGRES_PORT: '5432',
1498
- POSTGRES_HOST: 'postgres',
1499
- };
1500
- const result = rewriteUrlsWithPorts(secrets, {
1501
- dockerEnv: { POSTGRES_HOST_PORT: '5432' },
1502
- ports: { POSTGRES_HOST_PORT: 5432 },
1503
- mappings: [pgMapping],
1504
- });
1505
- expect(result.DATABASE_URL).toBe(
1506
- 'postgresql://app:pass@localhost:5432/mydb',
1507
- );
1508
- expect(result.POSTGRES_HOST).toBe('localhost');
1509
- expect(result.POSTGRES_PORT).toBe('5432');
1510
- });
1511
-
1512
- it('should not rewrite _HOST vars that are already localhost', () => {
1513
- const secrets = {
1514
- DATABASE_URL: 'postgresql://app:pass@localhost:5432/mydb',
1515
- POSTGRES_HOST: 'localhost',
1516
- };
1517
- const result = rewriteUrlsWithPorts(secrets, {
1518
- dockerEnv: { POSTGRES_HOST_PORT: '5432' },
1519
- ports: { POSTGRES_HOST_PORT: 5432 },
1520
- mappings: [pgMapping],
1521
- });
1522
- expect(result.DATABASE_URL).toBe(
1523
- 'postgresql://app:pass@localhost:5432/mydb',
1524
- );
1525
- expect(result.POSTGRES_HOST).toBe('localhost');
1526
- });
1527
-
1528
- it('should rewrite EVENT_PUBLISHER_CONNECTION_STRING and EVENT_SUBSCRIBER_CONNECTION_STRING', () => {
1529
- const secrets = {
1530
- EVENT_PUBLISHER_CONNECTION_STRING:
1531
- 'pgboss://pgboss:pass@localhost:5432/mydb?schema=pgboss',
1532
- EVENT_SUBSCRIBER_CONNECTION_STRING:
1533
- 'pgboss://pgboss:pass@localhost:5432/mydb?schema=pgboss',
1534
- };
1535
- const result = rewriteUrlsWithPorts(secrets, {
1536
- dockerEnv: { POSTGRES_HOST_PORT: '5433' },
1537
- ports: { POSTGRES_HOST_PORT: 5433 },
1538
- mappings: [pgMapping],
1539
- });
1540
- expect(result.EVENT_PUBLISHER_CONNECTION_STRING).toBe(
1541
- 'pgboss://pgboss:pass@localhost:5433/mydb?schema=pgboss',
1542
- );
1543
- expect(result.EVENT_SUBSCRIBER_CONNECTION_STRING).toBe(
1544
- 'pgboss://pgboss:pass@localhost:5433/mydb?schema=pgboss',
1545
- );
1546
- });
1547
-
1548
- it('should rewrite SNS connection strings with encoded endpoint port', () => {
1549
- const localstackMapping = {
1550
- service: 'localstack',
1551
- envVar: 'LOCALSTACK_PORT',
1552
- defaultPort: 4566,
1553
- containerPort: 4566,
1554
- };
1555
- const secrets = {
1556
- EVENT_PUBLISHER_CONNECTION_STRING:
1557
- 'sns://LSIAkey:secret@localhost:4566?region=us-east-1&endpoint=http%3A%2F%2Flocalhost%3A4566',
1558
- EVENT_SUBSCRIBER_CONNECTION_STRING:
1559
- 'sqs://LSIAkey:secret@localhost:4566?region=us-east-1&endpoint=http%3A%2F%2Flocalhost%3A4566',
1560
- };
1561
- const result = rewriteUrlsWithPorts(secrets, {
1562
- dockerEnv: { LOCALSTACK_PORT: '4567' },
1563
- ports: { LOCALSTACK_PORT: 4567 },
1564
- mappings: [localstackMapping],
1565
- });
1566
- expect(result.EVENT_PUBLISHER_CONNECTION_STRING).toBe(
1567
- 'sns://LSIAkey:secret@localhost:4567?region=us-east-1&endpoint=http%3A%2F%2Flocalhost%3A4567',
1568
- );
1569
- expect(result.EVENT_SUBSCRIBER_CONNECTION_STRING).toBe(
1570
- 'sqs://LSIAkey:secret@localhost:4567?region=us-east-1&endpoint=http%3A%2F%2Flocalhost%3A4567',
1571
- );
1572
- });
1573
-
1574
- it('should return empty for no mappings', () => {
1575
- const result = rewriteUrlsWithPorts(
1576
- {},
1577
- { dockerEnv: {}, ports: {}, mappings: [] },
1578
- );
1579
- expect(result).toEqual({});
1580
- });
1581
- });
1582
-
1583
- describe('port state persistence', () => {
1584
- let testDir: string;
1585
-
1586
- beforeEach(() => {
1587
- testDir = join(tmpdir(), `gkm-port-state-test-${Date.now()}`);
1588
- mkdirSync(testDir, { recursive: true });
1589
- });
1590
-
1591
- afterEach(() => {
1592
- rmSync(testDir, { recursive: true, force: true });
1593
- });
1594
-
1595
- it('should return empty object when no state file exists', async () => {
1596
- const state = await loadPortState(testDir);
1597
- expect(state).toEqual({});
1598
- });
1599
-
1600
- it('should save and load port state', async () => {
1601
- const ports = { POSTGRES_HOST_PORT: 5433, REDIS_HOST_PORT: 6380 };
1602
- await savePortState(testDir, ports);
1603
- const loaded = await loadPortState(testDir);
1604
- expect(loaded).toEqual(ports);
1605
- });
1606
-
1607
- it('should create .gkm directory if missing', async () => {
1608
- const ports = { POSTGRES_HOST_PORT: 5433 };
1609
- await savePortState(testDir, ports);
1610
- expect(existsSync(join(testDir, '.gkm'))).toBe(true);
1611
- expect(existsSync(join(testDir, '.gkm', 'ports.json'))).toBe(true);
1612
- });
1613
-
1614
- it('should overwrite existing state', async () => {
1615
- await savePortState(testDir, { POSTGRES_HOST_PORT: 5433 });
1616
- await savePortState(testDir, {
1617
- POSTGRES_HOST_PORT: 5434,
1618
- REDIS_HOST_PORT: 6380,
1619
- });
1620
- const loaded = await loadPortState(testDir);
1621
- expect(loaded).toEqual({
1622
- POSTGRES_HOST_PORT: 5434,
1623
- REDIS_HOST_PORT: 6380,
1624
- });
1625
- });
1626
- });
1627
-
1628
- describe('parseComposePortMappings', () => {
1629
- let testDir: string;
1630
-
1631
- beforeEach(() => {
1632
- testDir = join(tmpdir(), `gkm-compose-parse-test-${Date.now()}`);
1633
- mkdirSync(testDir, { recursive: true });
1634
- });
1635
-
1636
- afterEach(() => {
1637
- rmSync(testDir, { recursive: true, force: true });
1638
- });
1639
-
1640
- it('should parse standard api template ports', () => {
1641
- const composePath = join(testDir, 'docker-compose.yml');
1642
- writeFileSync(
1643
- composePath,
1644
- `
1645
- services:
1646
- postgres:
1647
- image: postgres:17
1648
- ports:
1649
- - '\${POSTGRES_HOST_PORT:-5432}:5432'
1650
- redis:
1651
- image: redis:7
1652
- ports:
1653
- - '\${REDIS_HOST_PORT:-6379}:6379'
1654
- mailpit:
1655
- image: axllent/mailpit
1656
- ports:
1657
- - '\${SMTP_PORT:-1025}:1025'
1658
- - '\${MAILPIT_PORT:-8025}:8025'
1659
- `,
1660
- );
1661
-
1662
- const mappings = parseComposePortMappings(composePath);
1663
- expect(mappings).toEqual([
1664
- {
1665
- service: 'postgres',
1666
- envVar: 'POSTGRES_HOST_PORT',
1667
- defaultPort: 5432,
1668
- containerPort: 5432,
1669
- },
1670
- {
1671
- service: 'redis',
1672
- envVar: 'REDIS_HOST_PORT',
1673
- defaultPort: 6379,
1674
- containerPort: 6379,
1675
- },
1676
- {
1677
- service: 'mailpit',
1678
- envVar: 'SMTP_PORT',
1679
- defaultPort: 1025,
1680
- containerPort: 1025,
1681
- },
1682
- {
1683
- service: 'mailpit',
1684
- envVar: 'MAILPIT_PORT',
1685
- defaultPort: 8025,
1686
- containerPort: 8025,
1687
- },
1688
- ]);
1689
- });
1690
-
1691
- it('should parse worker template with rabbitmq', () => {
1692
- const composePath = join(testDir, 'docker-compose.yml');
1693
- writeFileSync(
1694
- composePath,
1695
- `
1696
- services:
1697
- postgres:
1698
- image: postgres:17
1699
- ports:
1700
- - '\${POSTGRES_HOST_PORT:-5432}:5432'
1701
- rabbitmq:
1702
- image: rabbitmq:3-management
1703
- ports:
1704
- - '\${RABBITMQ_HOST_PORT:-5672}:5672'
1705
- - '\${RABBITMQ_MGMT_PORT:-15672}:15672'
1706
- `,
1707
- );
1708
-
1709
- const mappings = parseComposePortMappings(composePath);
1710
- expect(mappings).toEqual([
1711
- {
1712
- service: 'postgres',
1713
- envVar: 'POSTGRES_HOST_PORT',
1714
- defaultPort: 5432,
1715
- containerPort: 5432,
1716
- },
1717
- {
1718
- service: 'rabbitmq',
1719
- envVar: 'RABBITMQ_HOST_PORT',
1720
- defaultPort: 5672,
1721
- containerPort: 5672,
1722
- },
1723
- {
1724
- service: 'rabbitmq',
1725
- envVar: 'RABBITMQ_MGMT_PORT',
1726
- defaultPort: 15672,
1727
- containerPort: 15672,
1728
- },
1729
- ]);
1730
- });
1731
-
1732
- it('should parse serverless template with redis variants', () => {
1733
- const composePath = join(testDir, 'docker-compose.yml');
1734
- writeFileSync(
1735
- composePath,
1736
- `
1737
- services:
1738
- postgres:
1739
- image: postgres:17
1740
- ports:
1741
- - '\${POSTGRES_HOST_PORT:-5432}:5432'
1742
- redis:
1743
- image: redis:7
1744
- ports:
1745
- - '\${REDIS_HOST_PORT:-6379}:6379'
1746
- serverless-redis-http:
1747
- image: hiett/serverless-redis-http:latest
1748
- ports:
1749
- - '\${SRH_PORT:-8079}:80'
1750
- `,
1751
- );
1752
-
1753
- const mappings = parseComposePortMappings(composePath);
1754
- expect(mappings).toEqual([
1755
- {
1756
- service: 'postgres',
1757
- envVar: 'POSTGRES_HOST_PORT',
1758
- defaultPort: 5432,
1759
- containerPort: 5432,
1760
- },
1761
- {
1762
- service: 'redis',
1763
- envVar: 'REDIS_HOST_PORT',
1764
- defaultPort: 6379,
1765
- containerPort: 6379,
1766
- },
1767
- {
1768
- service: 'serverless-redis-http',
1769
- envVar: 'SRH_PORT',
1770
- defaultPort: 8079,
1771
- containerPort: 80,
1772
- },
1773
- ]);
1774
- });
1775
-
1776
- it('should handle custom user-added services', () => {
1777
- const composePath = join(testDir, 'docker-compose.yml');
1778
- writeFileSync(
1779
- composePath,
1780
- `
1781
- services:
1782
- postgres:
1783
- image: postgres:17
1784
- ports:
1785
- - '\${POSTGRES_HOST_PORT:-5432}:5432'
1786
- pgadmin:
1787
- image: dpage/pgadmin4
1788
- ports:
1789
- - '\${PGADMIN_HOST_PORT:-5050}:80'
1790
- minio:
1791
- image: minio/minio
1792
- ports:
1793
- - '\${MINIO_API_PORT:-9000}:9000'
1794
- - '\${MINIO_CONSOLE_PORT:-9001}:9001'
1795
- `,
1796
- );
1797
-
1798
- const mappings = parseComposePortMappings(composePath);
1799
- expect(mappings).toEqual([
1800
- {
1801
- service: 'postgres',
1802
- envVar: 'POSTGRES_HOST_PORT',
1803
- defaultPort: 5432,
1804
- containerPort: 5432,
1805
- },
1806
- {
1807
- service: 'pgadmin',
1808
- envVar: 'PGADMIN_HOST_PORT',
1809
- defaultPort: 5050,
1810
- containerPort: 80,
1811
- },
1812
- {
1813
- service: 'minio',
1814
- envVar: 'MINIO_API_PORT',
1815
- defaultPort: 9000,
1816
- containerPort: 9000,
1817
- },
1818
- {
1819
- service: 'minio',
1820
- envVar: 'MINIO_CONSOLE_PORT',
1821
- defaultPort: 9001,
1822
- containerPort: 9001,
1823
- },
1824
- ]);
1825
- });
1826
-
1827
- it('should skip fixed port mappings without env vars', () => {
1828
- const composePath = join(testDir, 'docker-compose.yml');
1829
- writeFileSync(
1830
- composePath,
1831
- `
1832
- services:
1833
- postgres:
1834
- image: postgres:17
1835
- ports:
1836
- - '\${POSTGRES_HOST_PORT:-5432}:5432'
1837
- nginx:
1838
- image: nginx
1839
- ports:
1840
- - '8080:80'
1841
- - '443:443'
1842
- `,
1843
- );
1844
-
1845
- const mappings = parseComposePortMappings(composePath);
1846
- expect(mappings).toEqual([
1847
- {
1848
- service: 'postgres',
1849
- envVar: 'POSTGRES_HOST_PORT',
1850
- defaultPort: 5432,
1851
- containerPort: 5432,
1852
- },
1853
- ]);
1854
- });
1855
-
1856
- it('should skip services without ports', () => {
1857
- const composePath = join(testDir, 'docker-compose.yml');
1858
- writeFileSync(
1859
- composePath,
1860
- `
1861
- services:
1862
- postgres:
1863
- image: postgres:17
1864
- ports:
1865
- - '\${POSTGRES_HOST_PORT:-5432}:5432'
1866
- worker:
1867
- image: node:22
1868
- command: npm start
1869
- `,
1870
- );
1871
-
1872
- const mappings = parseComposePortMappings(composePath);
1873
- expect(mappings).toEqual([
1874
- {
1875
- service: 'postgres',
1876
- envVar: 'POSTGRES_HOST_PORT',
1877
- defaultPort: 5432,
1878
- containerPort: 5432,
1879
- },
1880
- ]);
1881
- });
1882
-
1883
- it('should return empty array when file does not exist', () => {
1884
- const composePath = join(testDir, 'nonexistent.yml');
1885
- const mappings = parseComposePortMappings(composePath);
1886
- expect(mappings).toEqual([]);
1887
- });
1888
-
1889
- it('should return empty array when no services defined', () => {
1890
- const composePath = join(testDir, 'docker-compose.yml');
1891
- writeFileSync(composePath, 'version: "3.8"\n');
1892
-
1893
- const mappings = parseComposePortMappings(composePath);
1894
- expect(mappings).toEqual([]);
1895
- });
1896
- });
1897
-
1898
- describe('parseComposeServiceNames', () => {
1899
- let testDir: string;
1900
-
1901
- beforeEach(() => {
1902
- testDir = join(tmpdir(), `gkm-test-service-names-${Date.now()}`);
1903
- mkdirSync(testDir, { recursive: true });
1904
- });
1905
-
1906
- afterEach(() => {
1907
- rmSync(testDir, { recursive: true, force: true });
1908
- });
1909
-
1910
- it('should return all service names from docker-compose.yml', () => {
1911
- const composePath = join(testDir, 'docker-compose.yml');
1912
- writeFileSync(
1913
- composePath,
1914
- `services:
1915
- postgres:
1916
- image: postgres:18-alpine
1917
- redis:
1918
- image: redis:7-alpine
1919
- minio:
1920
- image: minio/minio:latest
1921
- `,
1922
- );
1923
-
1924
- const names = parseComposeServiceNames(composePath);
1925
- expect(names).toEqual(['postgres', 'redis', 'minio']);
1926
- });
1927
-
1928
- it('should return empty array when file does not exist', () => {
1929
- const names = parseComposeServiceNames(join(testDir, 'missing.yml'));
1930
- expect(names).toEqual([]);
1931
- });
1932
-
1933
- it('should return empty array when no services defined', () => {
1934
- const composePath = join(testDir, 'docker-compose.yml');
1935
- writeFileSync(composePath, 'version: "3.8"\n');
1936
-
1937
- const names = parseComposeServiceNames(composePath);
1938
- expect(names).toEqual([]);
1939
- });
1940
-
1941
- it('should include app and infrastructure services', () => {
1942
- const composePath = join(testDir, 'docker-compose.yml');
1943
- writeFileSync(
1944
- composePath,
1945
- `services:
1946
- api:
1947
- build: .
1948
- web:
1949
- build: .
1950
- postgres:
1951
- image: postgres:18-alpine
1952
- redis:
1953
- image: redis:7-alpine
1954
- custom-service:
1955
- image: custom:latest
1956
- `,
1957
- );
1958
-
1959
- const names = parseComposeServiceNames(composePath);
1960
- expect(names).toEqual([
1961
- 'api',
1962
- 'web',
1963
- 'postgres',
1964
- 'redis',
1965
- 'custom-service',
1966
- ]);
1967
- });
1968
- });
1969
-
1970
- describe('generateServerEntryContent', () => {
1971
- it('should use dynamic import for createApp when secrets are provided', () => {
1972
- const content = generateServerEntryContent({
1973
- secretsJsonPath: '/tmp/dev-secrets.json',
1974
- });
1975
-
1976
- // createApp must be a dynamic import so Credentials are populated first
1977
- expect(content).toContain("await import('./app.js')");
1978
- // Must NOT have a static import of createApp
1979
- expect(content).not.toMatch(/^import.*createApp/m);
1980
- });
1981
-
1982
- it('should use dynamic import for createApp without secrets', () => {
1983
- const content = generateServerEntryContent({});
1984
-
1985
- expect(content).toContain("await import('./app.js')");
1986
- expect(content).not.toMatch(/^import.*createApp/m);
1987
- });
1988
-
1989
- it('should inject credentials via globalThis before dynamic import', () => {
1990
- const content = generateServerEntryContent({
1991
- secretsJsonPath: '/tmp/dev-secrets.json',
1992
- });
1993
-
1994
- const credentialsIdx = content.indexOf('__gkm_credentials__');
1995
- const dynamicImportIdx = content.indexOf("await import('./app.js')");
1996
-
1997
- expect(credentialsIdx).toBeGreaterThan(-1);
1998
- expect(dynamicImportIdx).toBeGreaterThan(-1);
1999
- expect(credentialsIdx).toBeLessThan(dynamicImportIdx);
2000
- });
2001
-
2002
- it('should not include credentials injection when no secrets path', () => {
2003
- const content = generateServerEntryContent({});
2004
-
2005
- expect(content).not.toContain('__gkm_credentials__');
2006
- expect(content).not.toContain('existsSync');
2007
- });
2008
-
2009
- it('should use custom app import path when provided', () => {
2010
- const content = generateServerEntryContent({
2011
- appImportPath: './custom-app.js',
2012
- });
2013
-
2014
- expect(content).toContain("await import('./custom-app.js')");
2015
- });
2016
- });
2017
-
2018
- describe('buildDockerComposeEnv', () => {
2019
- it('should include secrets in the env passed to docker compose', () => {
2020
- const secretsEnv = {
2021
- POSTGRES_USER: 'app',
2022
- POSTGRES_PASSWORD: 'supersecret',
2023
- POSTGRES_DB: 'myproject_dev',
2024
- };
2025
- const portEnv = { POSTGRES_HOST_PORT: '5434' };
2026
-
2027
- const env = buildDockerComposeEnv(secretsEnv, portEnv);
2028
-
2029
- expect(env.POSTGRES_USER).toBe('app');
2030
- expect(env.POSTGRES_PASSWORD).toBe('supersecret');
2031
- expect(env.POSTGRES_DB).toBe('myproject_dev');
2032
- expect(env.POSTGRES_HOST_PORT).toBe('5434');
2033
- });
2034
-
2035
- it('should include multiple service secrets', () => {
2036
- const secretsEnv = {
2037
- POSTGRES_USER: 'app',
2038
- POSTGRES_PASSWORD: 'dbpass',
2039
- REDIS_PASSWORD: 'redispass',
2040
- };
2041
-
2042
- const env = buildDockerComposeEnv(secretsEnv, {});
2043
-
2044
- expect(env.POSTGRES_USER).toBe('app');
2045
- expect(env.POSTGRES_PASSWORD).toBe('dbpass');
2046
- expect(env.REDIS_PASSWORD).toBe('redispass');
2047
- });
2048
-
2049
- it('should let port env override secrets env for same key', () => {
2050
- const secretsEnv = { POSTGRES_HOST_PORT: '5432' };
2051
- const portEnv = { POSTGRES_HOST_PORT: '5434' };
2052
-
2053
- const env = buildDockerComposeEnv(secretsEnv, portEnv);
2054
-
2055
- expect(env.POSTGRES_HOST_PORT).toBe('5434');
2056
- });
2057
-
2058
- it('should work without secrets env', () => {
2059
- const env = buildDockerComposeEnv(undefined, {
2060
- POSTGRES_HOST_PORT: '5434',
2061
- });
2062
-
2063
- expect(env.POSTGRES_HOST_PORT).toBe('5434');
2064
- });
2065
-
2066
- it('should work without any arguments', () => {
2067
- const env = buildDockerComposeEnv();
2068
-
2069
- // Should at least have process.env
2070
- expect(env.PATH).toBeDefined();
2071
- });
2072
-
2073
- it('should include process.env as base', () => {
2074
- const env = buildDockerComposeEnv(
2075
- { POSTGRES_USER: 'app' },
2076
- { POSTGRES_HOST_PORT: '5434' },
2077
- );
2078
-
2079
- // process.env values should be present
2080
- expect(env.PATH).toBeDefined();
2081
- // Custom values should override
2082
- expect(env.POSTGRES_USER).toBe('app');
2083
- });
2084
- });