@geekmidas/cli 9.0.2 → 10.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -120
- package/dist/{CachedStateProvider-BdX7klRc.cjs → CachedStateProvider-BLo7zv7S.cjs} +1 -1
- package/dist/CachedStateProvider-BxoAm-B2.mjs +3 -0
- package/dist/{CachedStateProvider-klLWZXWF.cjs → CachedStateProvider-CqPdW7CB.cjs} +1 -1
- package/dist/{CachedStateProvider-klLWZXWF.cjs.map → CachedStateProvider-CqPdW7CB.cjs.map} +1 -1
- package/dist/{CachedStateProvider-CzyFct42.mjs → CachedStateProvider-DTniehMP.mjs} +1 -1
- package/dist/{CachedStateProvider-CzyFct42.mjs.map → CachedStateProvider-DTniehMP.mjs.map} +1 -1
- package/dist/{HostingerProvider-B2ZYz7Yc.mjs → HostingerProvider-Cft7lEYr.mjs} +4 -3
- package/dist/HostingerProvider-Cft7lEYr.mjs.map +1 -0
- package/dist/{HostingerProvider-CrHfsgz9.cjs → HostingerProvider-h3EbEp-Z.cjs} +4 -3
- package/dist/HostingerProvider-h3EbEp-Z.cjs.map +1 -0
- package/dist/{LocalStateProvider-CP49rjzt.mjs → LocalStateProvider-Bn6tS-oL.mjs} +1 -1
- package/dist/{LocalStateProvider-CP49rjzt.mjs.map → LocalStateProvider-Bn6tS-oL.mjs.map} +1 -1
- package/dist/{LocalStateProvider-CalzNfNA.cjs → LocalStateProvider-Db9SEjyH.cjs} +1 -1
- package/dist/{LocalStateProvider-CalzNfNA.cjs.map → LocalStateProvider-Db9SEjyH.cjs.map} +1 -1
- package/dist/{Route53Provider-tlgBzrRQ.mjs → Route53Provider-RdpUDJQk.mjs} +1 -1
- package/dist/{Route53Provider-tlgBzrRQ.mjs.map → Route53Provider-RdpUDJQk.mjs.map} +1 -1
- package/dist/{Route53Provider-DwgXiLxt.cjs → Route53Provider-di2fMM5N.cjs} +1 -1
- package/dist/{Route53Provider-DwgXiLxt.cjs.map → Route53Provider-di2fMM5N.cjs.map} +1 -1
- package/dist/{SSMStateProvider-BlrTiV5m.mjs → SSMStateProvider-BRt2py-A.mjs} +1 -1
- package/dist/{SSMStateProvider-BlrTiV5m.mjs.map → SSMStateProvider-BRt2py-A.mjs.map} +1 -1
- package/dist/{SSMStateProvider-Bt6r8CTL.cjs → SSMStateProvider-DyX8s9HI.cjs} +1 -1
- package/dist/{SSMStateProvider-Bt6r8CTL.cjs.map → SSMStateProvider-DyX8s9HI.cjs.map} +1 -1
- package/dist/{backup-provisioner-Xunq-p_8.mjs → backup-provisioner-Bgj5Vi_L.mjs} +2 -2
- package/dist/{backup-provisioner-Xunq-p_8.mjs.map → backup-provisioner-Bgj5Vi_L.mjs.map} +1 -1
- package/dist/{backup-provisioner-B3HBN-2k.cjs → backup-provisioner-BrsHwYG0.cjs} +2 -2
- package/dist/{backup-provisioner-B3HBN-2k.cjs.map → backup-provisioner-BrsHwYG0.cjs.map} +1 -1
- package/dist/{bundler-Dufq3uvI.cjs → bundler-BY1OmV1e.cjs} +44 -14
- package/dist/bundler-BY1OmV1e.cjs.map +1 -0
- package/dist/{bundler-DkA4qzUD.mjs → bundler-Be8Zg4Wd.mjs} +45 -15
- package/dist/bundler-Be8Zg4Wd.mjs.map +1 -0
- package/dist/config-D8o6I_7Z.mjs +469 -0
- package/dist/config-D8o6I_7Z.mjs.map +1 -0
- package/dist/config-DbPE1KXF.cjs +535 -0
- package/dist/config-DbPE1KXF.cjs.map +1 -0
- package/dist/config.cjs +3 -2
- package/dist/config.d.cts +2 -2
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +2 -2
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -2
- package/dist/{credentials-s1kLcIzK.mjs → credentials-B5zRHdAl.mjs} +21 -6
- package/dist/credentials-B5zRHdAl.mjs.map +1 -0
- package/dist/{credentials-C8DWtnMY.cjs → credentials-BapzjyV5.cjs} +26 -5
- package/dist/credentials-BapzjyV5.cjs.map +1 -0
- package/dist/credentials-Dh0Q6HzY.mjs +2548 -0
- package/dist/credentials-Dh0Q6HzY.mjs.map +1 -0
- package/dist/credentials-K961ThG3.cjs +2680 -0
- package/dist/credentials-K961ThG3.cjs.map +1 -0
- package/dist/deploy/sniffer-routes-worker.cjs +6 -2
- package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.mjs +6 -2
- package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
- package/dist/discover-CAlFgYec.mjs +229 -0
- package/dist/discover-CAlFgYec.mjs.map +1 -0
- package/dist/discover-DTmFeloL.cjs +271 -0
- package/dist/discover-DTmFeloL.cjs.map +1 -0
- package/dist/dokploy-api-BS2xnJLZ.mjs +3 -0
- package/dist/{dokploy-api-CqOIKHme.cjs → dokploy-api-BsVH0y_s.cjs} +141 -32
- package/dist/dokploy-api-BsVH0y_s.cjs.map +1 -0
- package/dist/dokploy-api-BuxDRo3l.cjs +3 -0
- package/dist/{dokploy-api-sfZFlWuE.mjs → dokploy-api-aTFPz7_4.mjs} +141 -32
- package/dist/dokploy-api-aTFPz7_4.mjs.map +1 -0
- package/dist/emit-CbcRolsa.cjs +236 -0
- package/dist/emit-CbcRolsa.cjs.map +1 -0
- package/dist/emit-CiZS_5Ub.mjs +206 -0
- package/dist/emit-CiZS_5Ub.mjs.map +1 -0
- package/dist/{encryption-MfXAiFTm.cjs → encryption-C6jaU0hE.cjs} +1 -1
- package/dist/{encryption-MfXAiFTm.cjs.map → encryption-C6jaU0hE.cjs.map} +1 -1
- package/dist/{encryption-BVS7Vmzs.cjs → encryption-CHzzeR8a.cjs} +1 -1
- package/dist/encryption-CQUncu7v.mjs +3 -0
- package/dist/{encryption-CO5Q4Bhf.mjs → encryption-D-abyhZx.mjs} +1 -1
- package/dist/{encryption-CO5Q4Bhf.mjs.map → encryption-D-abyhZx.mjs.map} +1 -1
- package/dist/fullstack-secrets-C-GGohke.cjs +111 -0
- package/dist/fullstack-secrets-C-GGohke.cjs.map +1 -0
- package/dist/fullstack-secrets-D1o9-G5l.mjs +87 -0
- package/dist/fullstack-secrets-D1o9-G5l.mjs.map +1 -0
- package/dist/{fullstack-secrets-BYzi6Hwl.cjs → generator-CNzBw7g5.cjs} +1 -114
- package/dist/generator-CNzBw7g5.cjs.map +1 -0
- package/dist/{fullstack-secrets-l8s3II_H.mjs → generator-VE2JrFlY.mjs} +2 -85
- package/dist/generator-VE2JrFlY.mjs.map +1 -0
- package/dist/index-BThUzEIp.d.mts +175 -0
- package/dist/index-BThUzEIp.d.mts.map +1 -0
- package/dist/index-CHB4tSzX.d.cts +175 -0
- package/dist/index-CHB4tSzX.d.cts.map +1 -0
- package/dist/index.cjs +2329 -1584
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2306 -1561
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-Cfwi0fqF.mjs → openapi-CgpMWMmM.mjs} +174 -95
- package/dist/openapi-CgpMWMmM.mjs.map +1 -0
- package/dist/{openapi-BzNOC7_i.cjs → openapi-CljONd_M.cjs} +175 -108
- package/dist/openapi-CljONd_M.cjs.map +1 -0
- package/dist/{openapi-react-query-C4iIvpPf.mjs → openapi-react-query-Byltvm_7.mjs} +2 -2
- package/dist/openapi-react-query-Byltvm_7.mjs.map +1 -0
- package/dist/{openapi-react-query-CYWik72n.cjs → openapi-react-query-CmJ1xDbn.cjs} +2 -2
- package/dist/openapi-react-query-CmJ1xDbn.cjs.map +1 -0
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +4 -3
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +4 -3
- package/dist/reconcile/public.cjs +9 -0
- package/dist/reconcile/public.d.cts +125 -0
- package/dist/reconcile/public.d.cts.map +1 -0
- package/dist/reconcile/public.d.mts +125 -0
- package/dist/reconcile/public.d.mts.map +1 -0
- package/dist/reconcile/public.mjs +4 -0
- package/dist/{reconcile-B2FoJE0a.mjs → reconcile-CFs8C_1a.mjs} +5 -4
- package/dist/reconcile-CFs8C_1a.mjs.map +1 -0
- package/dist/{reconcile-BzqKJ2Rt.cjs → reconcile-D0pimNiy.cjs} +5 -4
- package/dist/reconcile-D0pimNiy.cjs.map +1 -0
- package/dist/setup-CJiQM2pX.cjs +473 -0
- package/dist/setup-CJiQM2pX.cjs.map +1 -0
- package/dist/setup-DMtU3p41.cjs +11 -0
- package/dist/setup-DOsYueeQ.mjs +443 -0
- package/dist/setup-DOsYueeQ.mjs.map +1 -0
- package/dist/setup-DSutDAlt.mjs +11 -0
- package/dist/{storage-B1wvztiJ.cjs → storage-BJmzYxt8.cjs} +1 -1
- package/dist/{storage-B1wvztiJ.cjs.map → storage-BJmzYxt8.cjs.map} +1 -1
- package/dist/{storage-DOEtT2Hr.cjs → storage-BM9hGpgv.cjs} +1 -1
- package/dist/{storage-dbb9RyBl.mjs → storage-Bi24WfoW.mjs} +1 -1
- package/dist/{storage-dbb9RyBl.mjs.map → storage-Bi24WfoW.mjs.map} +1 -1
- package/dist/{storage-Cs4WBsc4.mjs → storage-CYlGwWTj.mjs} +1 -1
- package/dist/{sync-CSooqRQ8.mjs → sync-CmEuNZsB.mjs} +2 -2
- package/dist/{sync-CSooqRQ8.mjs.map → sync-CmEuNZsB.mjs.map} +1 -1
- package/dist/sync-D306yOWu.cjs +4 -0
- package/dist/{sync-o_CtEbvR.cjs → sync-SizaChmC.cjs} +2 -2
- package/dist/{sync-o_CtEbvR.cjs.map → sync-SizaChmC.cjs.map} +1 -1
- package/dist/{sync-B5UHXwRM.mjs → sync-hOqAWHR6.mjs} +1 -1
- package/dist/{index-Bw548Bta.d.mts → types-C-arnQ1r.d.mts} +590 -143
- package/dist/types-C-arnQ1r.d.mts.map +1 -0
- package/dist/{index-DTpkeFb8.d.cts → types-abJQuk7m.d.cts} +590 -143
- package/dist/types-abJQuk7m.d.cts.map +1 -0
- package/dist/workspace/index.cjs +5 -1
- package/dist/workspace/index.d.cts +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +2 -2
- package/dist/{workspace-D-37Ylqo.mjs → workspace-7FqprxuJ.mjs} +117 -30
- package/dist/workspace-7FqprxuJ.mjs.map +1 -0
- package/dist/workspace-8acupeaI.cjs +9 -0
- package/dist/{workspace-CQTT7HJQ.cjs → workspace-XvqO9jIv.cjs} +139 -28
- package/dist/workspace-XvqO9jIv.cjs.map +1 -0
- package/dist/workspace-yP5AHnrn.mjs +8 -0
- package/package.json +26 -9
- package/CHANGELOG.md +0 -650
- package/FUNCTION_CRON_SUPPORT.md +0 -266
- package/dist/CachedStateProvider-GcGziXh1.mjs +0 -3
- package/dist/HostingerProvider-B2ZYz7Yc.mjs.map +0 -1
- package/dist/HostingerProvider-CrHfsgz9.cjs.map +0 -1
- package/dist/bundler-DkA4qzUD.mjs.map +0 -1
- package/dist/bundler-Dufq3uvI.cjs.map +0 -1
- package/dist/config-Bbu1JKvf.mjs +0 -211
- package/dist/config-Bbu1JKvf.mjs.map +0 -1
- package/dist/config-DujV8DbR.cjs +0 -253
- package/dist/config-DujV8DbR.cjs.map +0 -1
- package/dist/credentials-C8DWtnMY.cjs.map +0 -1
- package/dist/credentials-s1kLcIzK.mjs.map +0 -1
- package/dist/dokploy-api-BPYkG-ZZ.cjs +0 -3
- package/dist/dokploy-api-BiSzWM4v.mjs +0 -3
- package/dist/dokploy-api-CqOIKHme.cjs.map +0 -1
- package/dist/dokploy-api-sfZFlWuE.mjs.map +0 -1
- package/dist/encryption-Bd_m4q3g.mjs +0 -3
- package/dist/fullstack-secrets-BYzi6Hwl.cjs.map +0 -1
- package/dist/fullstack-secrets-l8s3II_H.mjs.map +0 -1
- package/dist/index-Bw548Bta.d.mts.map +0 -1
- package/dist/index-DTpkeFb8.d.cts.map +0 -1
- package/dist/openapi-BzNOC7_i.cjs.map +0 -1
- package/dist/openapi-Cfwi0fqF.mjs.map +0 -1
- package/dist/openapi-react-query-C4iIvpPf.mjs.map +0 -1
- package/dist/openapi-react-query-CYWik72n.cjs.map +0 -1
- package/dist/reconcile-B2FoJE0a.mjs.map +0 -1
- package/dist/reconcile-BzqKJ2Rt.cjs.map +0 -1
- package/dist/setup-DDXUXzzl.mjs +0 -765
- package/dist/setup-DDXUXzzl.mjs.map +0 -1
- package/dist/setup-DLwhfyt-.cjs +0 -8
- package/dist/setup-Dh-B7YTX.mjs +0 -8
- package/dist/setup-WZ-xqAQO.cjs +0 -849
- package/dist/setup-WZ-xqAQO.cjs.map +0 -1
- package/dist/sync-BLA94rgC.cjs +0 -4
- package/dist/types-B3GQUg3k.d.mts +0 -280
- package/dist/types-B3GQUg3k.d.mts.map +0 -1
- package/dist/types-BWQDiooe.d.cts +0 -280
- package/dist/types-BWQDiooe.d.cts.map +0 -1
- package/dist/workspace-CQTT7HJQ.cjs.map +0 -1
- package/dist/workspace-D-37Ylqo.mjs.map +0 -1
- package/docs/OPENAPI_TYPESCRIPT_DESIGN.md +0 -408
- package/docs/dev-server-resilience-design.md +0 -189
- package/docs/manifest-refactor-design.md +0 -287
- package/examples/cron-example.ts +0 -45
- package/examples/env.ts +0 -33
- package/examples/function-example.ts +0 -40
- package/examples/gkm.config.json +0 -22
- package/examples/gkm.config.ts +0 -14
- package/examples/gkm.minimal.config.json +0 -7
- package/examples/gkm.production.config.json +0 -27
- package/examples/logger.ts +0 -10
- package/scripts/sync-versions.ts +0 -108
- package/src/__tests__/EndpointGenerator.hooks.spec.ts +0 -204
- package/src/__tests__/config.spec.ts +0 -422
- package/src/__tests__/loadEnvFiles.spec.ts +0 -131
- package/src/__tests__/normalizeHooksConfig.spec.ts +0 -63
- package/src/__tests__/openapi-react-query.spec.ts +0 -506
- package/src/__tests__/openapi.spec.ts +0 -625
- package/src/__tests__/test-helpers.ts +0 -181
- package/src/auth/__tests__/credentials.spec.ts +0 -331
- package/src/auth/__tests__/index.spec.ts +0 -237
- package/src/auth/credentials.ts +0 -286
- package/src/auth/index.ts +0 -233
- package/src/build/__tests__/bundler.spec.ts +0 -446
- package/src/build/__tests__/endpoint-analyzer.spec.ts +0 -623
- package/src/build/__tests__/handler-templates.spec.ts +0 -1287
- package/src/build/__tests__/index-new.spec.ts +0 -550
- package/src/build/__tests__/manifests.spec.ts +0 -583
- package/src/build/__tests__/partitions.spec.ts +0 -110
- package/src/build/__tests__/workspace-build.spec.ts +0 -215
- package/src/build/bundler.ts +0 -256
- package/src/build/endpoint-analyzer.ts +0 -236
- package/src/build/handler-templates.ts +0 -1253
- package/src/build/index.ts +0 -616
- package/src/build/manifests.ts +0 -285
- package/src/build/partitions.ts +0 -58
- package/src/build/providerResolver.ts +0 -184
- package/src/build/types.ts +0 -104
- package/src/config.ts +0 -330
- package/src/credentials/__tests__/fullDockerPorts.spec.ts +0 -144
- package/src/credentials/__tests__/helpers.ts +0 -112
- package/src/credentials/__tests__/prepareEntryCredentials.spec.ts +0 -125
- package/src/credentials/__tests__/readonlyDockerPorts.spec.ts +0 -190
- package/src/credentials/__tests__/workspaceCredentials.spec.ts +0 -209
- package/src/credentials/index.ts +0 -843
- package/src/debug.ts +0 -68
- package/src/deploy/CachedStateProvider.ts +0 -86
- package/src/deploy/LocalStateProvider.ts +0 -57
- package/src/deploy/SSMStateProvider.ts +0 -117
- package/src/deploy/StateProvider.ts +0 -175
- package/src/deploy/__tests__/CachedStateProvider.spec.ts +0 -235
- package/src/deploy/__tests__/HostingerProvider.spec.ts +0 -347
- package/src/deploy/__tests__/LocalStateProvider.spec.ts +0 -130
- package/src/deploy/__tests__/Route53Provider.spec.ts +0 -425
- package/src/deploy/__tests__/SSMStateProvider.spec.ts +0 -201
- package/src/deploy/__tests__/__fixtures__/entry-apps/async-entry.ts +0 -24
- package/src/deploy/__tests__/__fixtures__/entry-apps/nested-config-entry.ts +0 -24
- package/src/deploy/__tests__/__fixtures__/entry-apps/no-env-entry.ts +0 -12
- package/src/deploy/__tests__/__fixtures__/entry-apps/simple-entry.ts +0 -14
- package/src/deploy/__tests__/__fixtures__/entry-apps/throwing-entry.ts +0 -16
- package/src/deploy/__tests__/__fixtures__/env-parsers/non-function-export.ts +0 -10
- package/src/deploy/__tests__/__fixtures__/env-parsers/optional-env-parser.ts +0 -21
- package/src/deploy/__tests__/__fixtures__/env-parsers/parseable-env-parser.ts +0 -18
- package/src/deploy/__tests__/__fixtures__/env-parsers/throwing-env-parser.ts +0 -16
- package/src/deploy/__tests__/__fixtures__/env-parsers/valid-env-parser.ts +0 -16
- package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/auth.ts +0 -16
- package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/health.ts +0 -13
- package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/users.ts +0 -15
- package/src/deploy/__tests__/__fixtures__/route-apps/services.ts +0 -55
- package/src/deploy/__tests__/backup-provisioner.spec.ts +0 -428
- package/src/deploy/__tests__/createDnsProvider.spec.ts +0 -195
- package/src/deploy/__tests__/createStateProvider.spec.ts +0 -126
- package/src/deploy/__tests__/dns-orchestration.spec.ts +0 -192
- package/src/deploy/__tests__/dns-verification.spec.ts +0 -229
- package/src/deploy/__tests__/docker.spec.ts +0 -149
- package/src/deploy/__tests__/dokploy-api.spec.ts +0 -697
- package/src/deploy/__tests__/dokploy.spec.ts +0 -435
- package/src/deploy/__tests__/domain.spec.ts +0 -235
- package/src/deploy/__tests__/env-resolver.spec.ts +0 -903
- package/src/deploy/__tests__/index.spec.ts +0 -1091
- package/src/deploy/__tests__/init.spec.ts +0 -793
- package/src/deploy/__tests__/secrets.spec.ts +0 -303
- package/src/deploy/__tests__/sniffer.spec.ts +0 -838
- package/src/deploy/__tests__/state-e2e.spec.ts +0 -387
- package/src/deploy/__tests__/state.spec.ts +0 -876
- package/src/deploy/__tests__/undeploy.spec.ts +0 -758
- package/src/deploy/backup-provisioner.ts +0 -316
- package/src/deploy/dns/DnsProvider.ts +0 -201
- package/src/deploy/dns/HostingerProvider.ts +0 -174
- package/src/deploy/dns/Route53Provider.ts +0 -340
- package/src/deploy/dns/hostinger-api.ts +0 -261
- package/src/deploy/dns/index.ts +0 -627
- package/src/deploy/docker.ts +0 -260
- package/src/deploy/dokploy-api.ts +0 -1101
- package/src/deploy/dokploy.ts +0 -177
- package/src/deploy/domain.ts +0 -134
- package/src/deploy/env-resolver.ts +0 -305
- package/src/deploy/index.ts +0 -2094
- package/src/deploy/init.ts +0 -427
- package/src/deploy/secrets.ts +0 -187
- package/src/deploy/sniffer-envkit-patch.ts +0 -61
- package/src/deploy/sniffer-hooks.ts +0 -57
- package/src/deploy/sniffer-loader.ts +0 -28
- package/src/deploy/sniffer-routes-worker.ts +0 -104
- package/src/deploy/sniffer-worker.ts +0 -75
- package/src/deploy/sniffer.ts +0 -578
- package/src/deploy/state-commands.ts +0 -274
- package/src/deploy/state.ts +0 -482
- package/src/deploy/types.ts +0 -102
- package/src/deploy/undeploy.ts +0 -407
- package/src/dev/__tests__/entry-integration.spec.ts +0 -347
- package/src/dev/__tests__/entry.spec.ts +0 -144
- package/src/dev/__tests__/index.spec.ts +0 -2084
- package/src/dev/index.ts +0 -1649
- package/src/docker/__tests__/compose.spec.ts +0 -1142
- package/src/docker/__tests__/templates.spec.ts +0 -694
- package/src/docker/compose.ts +0 -808
- package/src/docker/index.ts +0 -523
- package/src/docker/templates.ts +0 -1067
- package/src/exec/index.ts +0 -118
- package/src/generators/CronGenerator.ts +0 -105
- package/src/generators/EndpointGenerator.ts +0 -954
- package/src/generators/FunctionGenerator.ts +0 -117
- package/src/generators/Generator.ts +0 -147
- package/src/generators/OpenApiTsGenerator.ts +0 -821
- package/src/generators/QueueGenerator.ts +0 -245
- package/src/generators/SubscriberGenerator.ts +0 -282
- package/src/generators/TopicGenerator.ts +0 -45
- package/src/generators/__tests__/CronGenerator.spec.ts +0 -445
- package/src/generators/__tests__/EndpointGenerator.spec.ts +0 -544
- package/src/generators/__tests__/FunctionGenerator.spec.ts +0 -256
- package/src/generators/__tests__/Generator.bustCache.spec.ts +0 -72
- package/src/generators/__tests__/OpenApiTsGenerator.registry.spec.ts +0 -98
- package/src/generators/__tests__/OpenApiTsGenerator.responseType.spec.ts +0 -56
- package/src/generators/__tests__/QueueGenerator.spec.ts +0 -141
- package/src/generators/__tests__/SubscriberGenerator.spec.ts +0 -410
- package/src/generators/index.ts +0 -11
- package/src/index.ts +0 -972
- package/src/init/__tests__/generators.spec.ts +0 -1070
- package/src/init/__tests__/init.spec.ts +0 -577
- package/src/init/__tests__/utils.spec.ts +0 -104
- package/src/init/generators/auth.ts +0 -225
- package/src/init/generators/config.ts +0 -316
- package/src/init/generators/docker.ts +0 -388
- package/src/init/generators/env.ts +0 -62
- package/src/init/generators/index.ts +0 -6
- package/src/init/generators/mobile-expo.ts +0 -576
- package/src/init/generators/models.ts +0 -153
- package/src/init/generators/monorepo.ts +0 -475
- package/src/init/generators/package.ts +0 -95
- package/src/init/generators/source.ts +0 -15
- package/src/init/generators/test.ts +0 -178
- package/src/init/generators/ui.ts +0 -1473
- package/src/init/generators/web-tanstack.ts +0 -348
- package/src/init/generators/web.ts +0 -363
- package/src/init/index.ts +0 -542
- package/src/init/templates/api.ts +0 -404
- package/src/init/templates/index.ts +0 -342
- package/src/init/templates/minimal.ts +0 -207
- package/src/init/templates/serverless.ts +0 -148
- package/src/init/templates/worker.ts +0 -216
- package/src/init/utils.ts +0 -197
- package/src/init/versions.ts +0 -52
- package/src/openapi-react-query.ts +0 -288
- package/src/openapi.ts +0 -259
- package/src/secrets/__tests__/encryption.spec.ts +0 -226
- package/src/secrets/__tests__/generator.spec.ts +0 -575
- package/src/secrets/__tests__/index.spec.ts +0 -91
- package/src/secrets/__tests__/reconcile.spec.ts +0 -123
- package/src/secrets/__tests__/storage.spec.ts +0 -645
- package/src/secrets/encryption.ts +0 -91
- package/src/secrets/generator.ts +0 -329
- package/src/secrets/index.ts +0 -417
- package/src/secrets/keystore.ts +0 -144
- package/src/secrets/reconcile.ts +0 -53
- package/src/secrets/storage.ts +0 -337
- package/src/secrets/sync.ts +0 -136
- package/src/secrets/types.ts +0 -70
- package/src/setup/__tests__/auto-setup.spec.ts +0 -147
- package/src/setup/__tests__/reconcile-secrets.spec.ts +0 -439
- package/src/setup/fullstack-secrets.ts +0 -140
- package/src/setup/index.ts +0 -411
- package/src/test/__tests__/__fixtures__/workspace.ts +0 -104
- package/src/test/__tests__/api.spec.ts +0 -199
- package/src/test/__tests__/auth.spec.ts +0 -162
- package/src/test/__tests__/index.spec.ts +0 -502
- package/src/test/__tests__/web.spec.ts +0 -210
- package/src/test/index.ts +0 -198
- package/src/types.ts +0 -363
- package/src/upgrade/__tests__/index.spec.ts +0 -354
- package/src/upgrade/index.ts +0 -253
- package/src/workspace/__tests__/client-generator.spec.ts +0 -326
- package/src/workspace/__tests__/index.spec.ts +0 -766
- package/src/workspace/__tests__/publicEnv.spec.ts +0 -64
- package/src/workspace/__tests__/schema.spec.ts +0 -737
- package/src/workspace/__tests__/type-inference.spec.ts +0 -251
- package/src/workspace/client-generator.ts +0 -109
- package/src/workspace/index.ts +0 -433
- package/src/workspace/publicEnv.ts +0 -57
- package/src/workspace/schema.ts +0 -861
- package/src/workspace/types.ts +0 -943
- package/tsconfig.json +0 -9
- 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
|
-
});
|