@geekmidas/cli 10.0.0-alpha.95 → 10.0.0-alpha.97

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/dist/{GoDaddyProvider-BwW51hPm.mjs → GoDaddyProvider-AqNu60WQ.mjs} +2 -2
  2. package/dist/{GoDaddyProvider-BwW51hPm.mjs.map → GoDaddyProvider-AqNu60WQ.mjs.map} +1 -1
  3. package/dist/{GoDaddyProvider-C_DgIkMv.cjs → GoDaddyProvider-B4R_QFl8.cjs} +2 -2
  4. package/dist/{GoDaddyProvider-C_DgIkMv.cjs.map → GoDaddyProvider-B4R_QFl8.cjs.map} +1 -1
  5. package/dist/{LegacyStateStore-D5DarNA2.mjs → LegacyStateStore-8F05-BOo.mjs} +3 -3
  6. package/dist/{LegacyStateStore-D5DarNA2.mjs.map → LegacyStateStore-8F05-BOo.mjs.map} +1 -1
  7. package/dist/{LegacyStateStore-BawjE_5n.cjs → LegacyStateStore-B-aRqRdD.cjs} +3 -3
  8. package/dist/{LegacyStateStore-BawjE_5n.cjs.map → LegacyStateStore-B-aRqRdD.cjs.map} +1 -1
  9. package/dist/{LocalStateStore-D8fu5WiF.cjs → LocalStateStore-B_bRI8M7.cjs} +2 -2
  10. package/dist/{LocalStateStore-D8fu5WiF.cjs.map → LocalStateStore-B_bRI8M7.cjs.map} +1 -1
  11. package/dist/{LocalStateStore-YjIqlgAz.mjs → LocalStateStore-B_mfgcO8.mjs} +2 -2
  12. package/dist/{LocalStateStore-YjIqlgAz.mjs.map → LocalStateStore-B_mfgcO8.mjs.map} +1 -1
  13. package/dist/{S3StateStore-CjTAVsWh.mjs → S3StateStore-CMN0Uzdp.mjs} +69 -8
  14. package/dist/S3StateStore-CMN0Uzdp.mjs.map +1 -0
  15. package/dist/{S3StateStore-Cm-v2IPI.cjs → S3StateStore-zK9zcd9f.cjs} +69 -8
  16. package/dist/S3StateStore-zK9zcd9f.cjs.map +1 -0
  17. package/dist/{SSMStateStore-BS4BiZCN.cjs → SSMStateStore-C489DnR6.cjs} +58 -7
  18. package/dist/SSMStateStore-C489DnR6.cjs.map +1 -0
  19. package/dist/{SSMStateStore-BhtM6jOh.mjs → SSMStateStore-Cv7VcMak.mjs} +58 -7
  20. package/dist/SSMStateStore-Cv7VcMak.mjs.map +1 -0
  21. package/dist/{add-ylx8dejw.mjs → add-BA5d4pA0.mjs} +4 -4
  22. package/dist/{add-ylx8dejw.mjs.map → add-BA5d4pA0.mjs.map} +1 -1
  23. package/dist/{add-D-3IKT7W.cjs → add-HXzTCy_I.cjs} +4 -4
  24. package/dist/{add-D-3IKT7W.cjs.map → add-HXzTCy_I.cjs.map} +1 -1
  25. package/dist/{bundler-ywR3C1B_.mjs → bundler-CBomFsnd.mjs} +5 -5
  26. package/dist/{bundler-ywR3C1B_.mjs.map → bundler-CBomFsnd.mjs.map} +1 -1
  27. package/dist/{bundler-CI3elfZS.cjs → bundler-SX3Uy8qT.cjs} +5 -5
  28. package/dist/{bundler-CI3elfZS.cjs.map → bundler-SX3Uy8qT.cjs.map} +1 -1
  29. package/dist/{cli-Dewt2T21.cjs → cli-1l0L9LCA.cjs} +2 -2
  30. package/dist/{cli-Dewt2T21.cjs.map → cli-1l0L9LCA.cjs.map} +1 -1
  31. package/dist/{cli-BCSuCfQk.mjs → cli-D7z1lQtO.mjs} +2 -2
  32. package/dist/{cli-BCSuCfQk.mjs.map → cli-D7z1lQtO.mjs.map} +1 -1
  33. package/dist/{compose-Dd0XphvF.cjs → compose-C39XupJu.cjs} +7 -7
  34. package/dist/{compose-Dd0XphvF.cjs.map → compose-C39XupJu.cjs.map} +1 -1
  35. package/dist/{compose-C4js1JDv.mjs → compose-DgWrNd2k.mjs} +7 -7
  36. package/dist/{compose-C4js1JDv.mjs.map → compose-DgWrNd2k.mjs.map} +1 -1
  37. package/dist/{config-4yirvxS8.d.cts → config-D-k0gaAr.d.mts} +4 -4
  38. package/dist/{config-4yirvxS8.d.cts.map → config-D-k0gaAr.d.mts.map} +1 -1
  39. package/dist/{config-C8NPCn1l.d.mts → config-xIWIaDhW.d.cts} +4 -4
  40. package/dist/{config-C8NPCn1l.d.mts.map → config-xIWIaDhW.d.cts.map} +1 -1
  41. package/dist/config.cjs +1 -1
  42. package/dist/config.d.cts +3 -3
  43. package/dist/config.d.mts +3 -3
  44. package/dist/config.mjs +1 -1
  45. package/dist/deploy/public.cjs +7 -7
  46. package/dist/deploy/public.d.cts +5 -5
  47. package/dist/deploy/public.d.cts.map +1 -1
  48. package/dist/deploy/public.d.mts +5 -5
  49. package/dist/deploy/public.d.mts.map +1 -1
  50. package/dist/deploy/public.mjs +7 -7
  51. package/dist/{deploy-Bw1FjX_x.cjs → deploy-Cu_HyWoJ.cjs} +212 -47
  52. package/dist/deploy-Cu_HyWoJ.cjs.map +1 -0
  53. package/dist/{deploy-B0drCD6s.mjs → deploy-LY-20Yeb.mjs} +212 -47
  54. package/dist/deploy-LY-20Yeb.mjs.map +1 -0
  55. package/dist/{discover-BtFofyQB.d.cts → discover-CbJCQq4J.d.mts} +2 -2
  56. package/dist/{discover-BtFofyQB.d.cts.map → discover-CbJCQq4J.d.mts.map} +1 -1
  57. package/dist/{discover-D-MdXZgm.d.mts → discover-RCW5mBXb.d.cts} +2 -2
  58. package/dist/{discover-D-MdXZgm.d.mts.map → discover-RCW5mBXb.d.cts.map} +1 -1
  59. package/dist/{dns-Bmk7P9c2.cjs → dns-BFnoawgh.cjs} +3 -3
  60. package/dist/{dns-Bmk7P9c2.cjs.map → dns-BFnoawgh.cjs.map} +1 -1
  61. package/dist/{dns-idXfyCay.mjs → dns-BbTOzmVp.mjs} +3 -3
  62. package/dist/{dns-idXfyCay.mjs.map → dns-BbTOzmVp.mjs.map} +1 -1
  63. package/dist/{dns-X3oqEV4C.mjs → dns-Bmbhmz41.mjs} +5 -5
  64. package/dist/{dns-X3oqEV4C.mjs.map → dns-Bmbhmz41.mjs.map} +1 -1
  65. package/dist/{dns-CyMIq6to.cjs → dns-Dro_vbk9.cjs} +5 -5
  66. package/dist/{dns-CyMIq6to.cjs.map → dns-Dro_vbk9.cjs.map} +1 -1
  67. package/dist/{dokploy-DaT0mgLU.cjs → dokploy-CKgSww82.cjs} +2 -2
  68. package/dist/{dokploy-DaT0mgLU.cjs.map → dokploy-CKgSww82.cjs.map} +1 -1
  69. package/dist/{dokploy-CZYxXeMa.mjs → dokploy-LR94sBVI.mjs} +2 -2
  70. package/dist/{dokploy-CZYxXeMa.mjs.map → dokploy-LR94sBVI.mjs.map} +1 -1
  71. package/dist/{encryption-D0ucY4O7.mjs → encryption-C0plOf4a.mjs} +3 -3
  72. package/dist/{encryption-D0ucY4O7.mjs.map → encryption-C0plOf4a.mjs.map} +1 -1
  73. package/dist/{encryption-Dp0IvLVX.cjs → encryption-CUyw-8sc.cjs} +3 -3
  74. package/dist/{encryption-Dp0IvLVX.cjs.map → encryption-CUyw-8sc.cjs.map} +1 -1
  75. package/dist/{engine-WY6j_kvA.cjs → engine-D8TeraDK.cjs} +4 -4
  76. package/dist/{engine-WY6j_kvA.cjs.map → engine-D8TeraDK.cjs.map} +1 -1
  77. package/dist/{engine-BcuhnyQO.mjs → engine-DqbqQhck.mjs} +4 -4
  78. package/dist/{engine-BcuhnyQO.mjs.map → engine-DqbqQhck.mjs.map} +1 -1
  79. package/dist/{github-DgmW83OP.cjs → github-DC4SIqV1.cjs} +35 -20
  80. package/dist/github-DC4SIqV1.cjs.map +1 -0
  81. package/dist/{github-DUGA57du.mjs → github-zgaBOQvh.mjs} +35 -20
  82. package/dist/github-zgaBOQvh.mjs.map +1 -0
  83. package/dist/{index-5eL8rM6H.d.mts → index-ClqOsbBl.d.mts} +2 -2
  84. package/dist/{index-5eL8rM6H.d.mts.map → index-ClqOsbBl.d.mts.map} +1 -1
  85. package/dist/{index-Dz-gyjm_.d.cts → index-DgOBAEl5.d.cts} +2 -2
  86. package/dist/{index-Dz-gyjm_.d.cts.map → index-DgOBAEl5.d.cts.map} +1 -1
  87. package/dist/index.cjs +56 -52
  88. package/dist/index.cjs.map +1 -1
  89. package/dist/index.mjs +56 -52
  90. package/dist/index.mjs.map +1 -1
  91. package/dist/{migrate-CZ-v5WV0.mjs → migrate-BJ8aLOWk.mjs} +3 -3
  92. package/dist/{migrate-CZ-v5WV0.mjs.map → migrate-BJ8aLOWk.mjs.map} +1 -1
  93. package/dist/{migrate-D6PlNv2o.mjs → migrate-C230QeXU.mjs} +3 -3
  94. package/dist/{migrate-D6PlNv2o.mjs.map → migrate-C230QeXU.mjs.map} +1 -1
  95. package/dist/{migrate-afgRL3Hg.cjs → migrate-CWq8-s21.cjs} +3 -3
  96. package/dist/{migrate-afgRL3Hg.cjs.map → migrate-CWq8-s21.cjs.map} +1 -1
  97. package/dist/{migrate-BBcG1dhS.cjs → migrate-CsMrI7Dk.cjs} +3 -3
  98. package/dist/{migrate-BBcG1dhS.cjs.map → migrate-CsMrI7Dk.cjs.map} +1 -1
  99. package/dist/openapi.cjs +1 -1
  100. package/dist/openapi.d.cts +1 -1
  101. package/dist/openapi.d.mts +1 -1
  102. package/dist/openapi.mjs +1 -1
  103. package/dist/projectBucket-BFp-SFee.mjs +177 -0
  104. package/dist/projectBucket-BFp-SFee.mjs.map +1 -0
  105. package/dist/projectBucket-Cho9DpSh.cjs +206 -0
  106. package/dist/projectBucket-Cho9DpSh.cjs.map +1 -0
  107. package/dist/reconcile/public.d.cts +2 -2
  108. package/dist/reconcile/public.d.mts +2 -2
  109. package/dist/{resolve-DakDhr6Z.mjs → resolve-6EYNqYdr.mjs} +3 -3
  110. package/dist/{resolve-DakDhr6Z.mjs.map → resolve-6EYNqYdr.mjs.map} +1 -1
  111. package/dist/{resolve-CFb4Navi.cjs → resolve-C8VkR4oW.cjs} +3 -3
  112. package/dist/{resolve-CFb4Navi.cjs.map → resolve-C8VkR4oW.cjs.map} +1 -1
  113. package/dist/{rollback-B1TIpjdE.mjs → rollback-BsThTeSN.mjs} +7 -6
  114. package/dist/{rollback-B1TIpjdE.mjs.map → rollback-BsThTeSN.mjs.map} +1 -1
  115. package/dist/{rollback-B-1dPP0m.cjs → rollback-CvGfm4DX.cjs} +7 -6
  116. package/dist/{rollback-B-1dPP0m.cjs.map → rollback-CvGfm4DX.cjs.map} +1 -1
  117. package/dist/{runtime-DUQdM-mW.d.cts → runtime-CaW4QqF2.d.cts} +2 -2
  118. package/dist/{runtime-DUQdM-mW.d.cts.map → runtime-CaW4QqF2.d.cts.map} +1 -1
  119. package/dist/{runtime-Vr6mleo2.d.mts → runtime-K5uxp6HE.d.mts} +2 -2
  120. package/dist/{runtime-Vr6mleo2.d.mts.map → runtime-K5uxp6HE.d.mts.map} +1 -1
  121. package/dist/{sandbox-DGOsMPMf.d.mts → sandbox-B_BkfmTP.d.mts} +2 -2
  122. package/dist/{sandbox-BbJmh2rI.d.cts.map → sandbox-B_BkfmTP.d.mts.map} +1 -1
  123. package/dist/{sandbox-BbJmh2rI.d.cts → sandbox-D8cfZUAA.d.cts} +2 -2
  124. package/dist/{sandbox-DGOsMPMf.d.mts.map → sandbox-D8cfZUAA.d.cts.map} +1 -1
  125. package/dist/{secrets-CPM0Qffe.cjs → secrets-BTY6JhTp.cjs} +3 -3
  126. package/dist/{secrets-CPM0Qffe.cjs.map → secrets-BTY6JhTp.cjs.map} +1 -1
  127. package/dist/{secrets-oI9ClAPR.mjs → secrets-HzKNTWg9.mjs} +3 -3
  128. package/dist/{secrets-oI9ClAPR.mjs.map → secrets-HzKNTWg9.mjs.map} +1 -1
  129. package/dist/{seeds-CnXkgKUv.cjs → seeds-BQlj9Fsg.cjs} +4 -4
  130. package/dist/{seeds-CnXkgKUv.cjs.map → seeds-BQlj9Fsg.cjs.map} +1 -1
  131. package/dist/{seeds-CoKkrSny.mjs → seeds-BsJRUL1K.mjs} +4 -4
  132. package/dist/{seeds-CoKkrSny.mjs.map → seeds-BsJRUL1K.mjs.map} +1 -1
  133. package/dist/{sniffer-vbiQ2Isn.cjs → sniffer-CeUKUoHn.cjs} +100 -10
  134. package/dist/sniffer-CeUKUoHn.cjs.map +1 -0
  135. package/dist/{sniffer-CFRzIrby.mjs → sniffer-DVsvP8s-.mjs} +77 -11
  136. package/dist/sniffer-DVsvP8s-.mjs.map +1 -0
  137. package/dist/{stack-B7O975TR.mjs → stack-BQOyqaVG.mjs} +3 -3
  138. package/dist/{stack-B7O975TR.mjs.map → stack-BQOyqaVG.mjs.map} +1 -1
  139. package/dist/{stack-1g57OfOw.cjs → stack-Clzi40EE.cjs} +3 -3
  140. package/dist/{stack-1g57OfOw.cjs.map → stack-Clzi40EE.cjs.map} +1 -1
  141. package/dist/{stages-BujUB4we.cjs → stages-BpDXFPGg.cjs} +4 -4
  142. package/dist/{stages-BujUB4we.cjs.map → stages-BpDXFPGg.cjs.map} +1 -1
  143. package/dist/{stages-Bt4OggMm.mjs → stages-DEmn7qI8.mjs} +4 -4
  144. package/dist/{stages-Bt4OggMm.mjs.map → stages-DEmn7qI8.mjs.map} +1 -1
  145. package/dist/target/public.cjs +1 -1
  146. package/dist/target/public.d.cts +2 -2
  147. package/dist/target/public.d.mts +2 -2
  148. package/dist/target/public.mjs +1 -1
  149. package/dist/{types-dipJRmKV.d.cts → types-4vPS_YFX.d.cts} +21 -4
  150. package/dist/types-4vPS_YFX.d.cts.map +1 -0
  151. package/dist/{types-bkqVscdN.d.mts → types-CFsEyFb1.d.mts} +21 -4
  152. package/dist/types-CFsEyFb1.d.mts.map +1 -0
  153. package/dist/workspace/index.cjs +1 -1
  154. package/dist/workspace/index.d.cts +2 -2
  155. package/dist/workspace/index.d.mts +2 -2
  156. package/dist/workspace/index.mjs +1 -1
  157. package/dist/{workspace-DWK1PTXP.mjs → workspace-Bv4xROeu.mjs} +61 -5
  158. package/dist/workspace-Bv4xROeu.mjs.map +1 -0
  159. package/dist/{workspace-cSyi5nb1.cjs → workspace-C1M-S4al.cjs} +78 -4
  160. package/dist/workspace-C1M-S4al.cjs.map +1 -0
  161. package/package.json +16 -15
  162. package/dist/S3StateStore-CjTAVsWh.mjs.map +0 -1
  163. package/dist/S3StateStore-Cm-v2IPI.cjs.map +0 -1
  164. package/dist/SSMStateStore-BS4BiZCN.cjs.map +0 -1
  165. package/dist/SSMStateStore-BhtM6jOh.mjs.map +0 -1
  166. package/dist/deploy-B0drCD6s.mjs.map +0 -1
  167. package/dist/deploy-Bw1FjX_x.cjs.map +0 -1
  168. package/dist/github-DUGA57du.mjs.map +0 -1
  169. package/dist/github-DgmW83OP.cjs.map +0 -1
  170. package/dist/sniffer-CFRzIrby.mjs.map +0 -1
  171. package/dist/sniffer-vbiQ2Isn.cjs.map +0 -1
  172. package/dist/types-bkqVscdN.d.mts.map +0 -1
  173. package/dist/types-dipJRmKV.d.cts.map +0 -1
  174. package/dist/workspace-DWK1PTXP.mjs.map +0 -1
  175. package/dist/workspace-cSyi5nb1.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"stack-1g57OfOw.cjs","names":["createHash","CLIENT_IP_HEADER","yaml","stringify","GkmError","CLIENT_IP_HEADER","GkmError","localRolePassword","postgresSuperuser","LOCAL_RABBITMQ_USER","EMULATOR_ACCESS_KEY_ID","proxyFor","certificateFor","planFor","provisionOrder","DEFAULT_EVENTS","STACK_CACHE","workerUnits","provideKey","resolveStageTelemetry","usesTelemetry","stackLogs","externalServices","stageServiceDeclarations","stageProviderNotes","stackRedis","LOGS_SERVICE","bucketPolicies","hostFor","NoDomainForStage","appKey","networkEnv","isReservedStageKey","suppliedOnly","telemetryEnv","otlpTelemetryEnv","isTelemetryKey","publicEnvFor","appEnvKeys","scopeTelemetryEnv","telemetryOf","workerEnvKeys","join","LOGS_PORT","appDockerfile","workerDockerfile","devServicesUsed","validateImageRef","imageRef","WORKER_PORT","resolveHost","isMainFrontendApp","composeFor","portKeys","REDIS_SERVICE","redisService","logsService","composeBuildPaths","DEFAULT_IMAGES","TURBO_VERSION"],"sources":["../src/compose/traefik.ts","../src/compose/images.ts","../src/compose/caddyfile.ts","../src/compose/stack.ts"],"sourcesContent":["/**\n * The server's shared edge, rendered for Traefik: what the edge itself runs\n * with, and what one stack registers with it.\n *\n * One Traefik per server owns 80 and 443, the ACME state and the redirect to\n * HTTPS. It is configured through its file provider alone: each stack writes\n * one file — its routers, services and middlewares, every name prefixed with\n * its compose project — into a directory the edge watches, and removes it\n * when it stops. There is no Docker socket: the edge never learns what runs\n * on the server except from those files.\n *\n * The edge and the stacks meet on one external Docker network. Only a\n * stack's public services join it, each under an alias prefixed with its\n * project (`<project>-api`), because the network is shared: a bare service\n * name there would be answered by every stack's `api`.\n *\n * Pure: the documents are data, and the edge's side effects are `edge.ts`'s.\n */\n\nimport { createHash } from 'node:crypto';\nimport { CLIENT_IP_HEADER } from '@geekmidas/manifest';\nimport { stringify } from 'yaml';\nimport type { EdgeRoute, EdgeTls, Upstream } from './routes.js';\n\n/**\n * The image, pinned: the edge is every stack's way in, so a new version is a\n * change to this line, not whatever `latest` is on the day a server starts.\n */\nexport const TRAEFIK_IMAGE = 'traefik:v3.7.13';\n\n/** The edge's compose project, one per server. */\nexport const EDGE_PROJECT = 'gkm-edge';\n\n/** The external network the edge and every stack's public services share. */\nexport const EDGE_NETWORK = 'gkm-edge';\n\n/** The edge's compose service. */\nexport const EDGE_SERVICE = 'traefik';\n\n/** Inside the edge's container: what it watches, and where certificates are. */\nexport const EDGE_DYNAMIC_DIR = '/etc/gkm-edge/dynamic';\nexport const EDGE_CERTS_DIR = '/etc/gkm-edge/certs';\n\n/** The certificate resolver every deployed route without its own certificate uses. */\nexport const ACME_RESOLVER = 'letsencrypt';\n\n/** The internal entrypoint the edge's health check asks; never published. */\nconst PING_PORT = 8082;\n\n/** A stack's service, as the edge — and the stack itself — reach it. */\nexport function edgeAlias(project: string, service: string): string {\n\treturn `${project}-${service}`;\n}\n\n/** A stack's certificate, as the edge's container reads it. */\nexport function edgeCertificatePaths(project: string): {\n\tcertFile: string;\n\tkeyFile: string;\n} {\n\treturn {\n\t\tcertFile: `${EDGE_CERTS_DIR}/${project}.crt`,\n\t\tkeyFile: `${EDGE_CERTS_DIR}/${project}.key`,\n\t};\n}\n\n/** The edge's ports on the host. */\nexport interface EdgePorts {\n\thttps: number;\n\thttp: number;\n}\n\n/**\n * The edge's static configuration: the entrypoints, the redirect, ACME, and\n * the directory it watches. No API and no dashboard — nothing about the edge\n * is reachable but the routes stacks register.\n */\nexport function traefikStatic(ports: EdgePorts): Record<string, unknown> {\n\treturn {\n\t\tglobal: { checkNewVersion: false, sendAnonymousUsage: false },\n\t\tentryPoints: {\n\t\t\tweb: {\n\t\t\t\taddress: ':80',\n\t\t\t\thttp: {\n\t\t\t\t\t// Every plain-HTTP request is sent to HTTPS — but an ACME\n\t\t\t\t\t// HTTP-01 challenge, which Traefik answers before the redirect.\n\t\t\t\t\tredirections: {\n\t\t\t\t\t\tentryPoint: {\n\t\t\t\t\t\t\t// The published port when it is not 443, so the redirect\n\t\t\t\t\t\t\t// lands where the edge actually listens.\n\t\t\t\t\t\t\tto: ports.https === 443 ? 'websecure' : `:${ports.https}`,\n\t\t\t\t\t\t\tscheme: 'https',\n\t\t\t\t\t\t\tpermanent: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twebsecure: { address: ':443' },\n\t\t\tping: { address: `:${PING_PORT}` },\n\t\t},\n\t\tping: { entryPoint: 'ping' },\n\t\tproviders: {\n\t\t\tfile: { directory: EDGE_DYNAMIC_DIR, watch: true },\n\t\t},\n\t\tcertificatesResolvers: {\n\t\t\t[ACME_RESOLVER]: {\n\t\t\t\tacme: {\n\t\t\t\t\tstorage: '/acme/acme.json',\n\t\t\t\t\thttpChallenge: { entryPoint: 'web' },\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tlog: { level: 'INFO' },\n\t};\n}\n\n/** The edge's compose document. */\nexport function edgeCompose(options: {\n\t/** The edge's directory on the host. */\n\tdir: string;\n\tports: EdgePorts;\n\t/** The static configuration, as written — its hash recreates the edge. */\n\tstaticConfig: string;\n\tlogging: { driver: string; options: Record<string, string> };\n}): Record<string, unknown> {\n\tconst { dir, ports } = options;\n\treturn {\n\t\tname: EDGE_PROJECT,\n\t\tservices: {\n\t\t\t[EDGE_SERVICE]: {\n\t\t\t\timage: TRAEFIK_IMAGE,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: [`${ports.https}:443`, `${ports.http}:80`],\n\t\t\t\tvolumes: [\n\t\t\t\t\t`${dir}/traefik.yml:/etc/traefik/traefik.yml:ro`,\n\t\t\t\t\t// Directories, not files: a stack's file is replaced by a\n\t\t\t\t\t// rename, which a file mounted on its own would never see.\n\t\t\t\t\t`${dir}/dynamic:${EDGE_DYNAMIC_DIR}:ro`,\n\t\t\t\t\t`${dir}/certs:${EDGE_CERTS_DIR}:ro`,\n\t\t\t\t\t'acme:/acme',\n\t\t\t\t],\n\t\t\t\t// Static configuration is read at start: a change to it is a\n\t\t\t\t// change to the service, so `up` recreates the edge.\n\t\t\t\tlabels: {\n\t\t\t\t\t'dev.geekmidas.edge.config': createHash('sha256')\n\t\t\t\t\t\t.update(options.staticConfig)\n\t\t\t\t\t\t.digest('hex')\n\t\t\t\t\t\t.slice(0, 16),\n\t\t\t\t},\n\t\t\t\tnetworks: ['edge'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD', 'traefik', 'healthcheck', '--ping'],\n\t\t\t\t\tinterval: '10s',\n\t\t\t\t\ttimeout: '5s',\n\t\t\t\t\tretries: 5,\n\t\t\t\t},\n\t\t\t\tlogging: options.logging,\n\t\t\t},\n\t\t},\n\t\tvolumes: { acme: {} },\n\t\tnetworks: { edge: { name: EDGE_NETWORK, external: true } },\n\t};\n}\n\n/** What a stack registers with the edge — its routes, and its certificate. */\nexport interface TraefikStackOptions {\n\tproject: string;\n\t/** ACME, or the stage's own certificate. Never the local CA: that is Caddy's. */\n\ttls: Exclude<EdgeTls, { kind: 'internal' }>;\n}\n\n/** A stack's dynamic configuration, as an object. */\nexport function traefikDynamic(\n\troutes: readonly EdgeRoute[],\n\toptions: TraefikStackOptions,\n): Record<string, unknown> {\n\tconst { project, tls } = options;\n\tconst name = (route: EdgeRoute, suffix?: string) =>\n\t\t`${project}-${route.name}${suffix ? `-${suffix}` : ''}`;\n\tconst url = ({ service, port }: Upstream) =>\n\t\t`http://${edgeAlias(project, service)}:${port}`;\n\n\tconst routers: Record<string, unknown> = {};\n\tconst services: Record<string, unknown> = {};\n\tconst middlewares: Record<string, unknown> = {};\n\n\t// gkm's own header, which only a service of this stack may send — the\n\t// auth server rate-limits by the client address it carries — removed from\n\t// every request that arrives from outside. An empty value is Traefik's\n\t// way of removing a request header.\n\tconst strip = `${project}-strip-gkm-headers`;\n\tif (routes.length > 0) {\n\t\tmiddlewares[strip] = {\n\t\t\theaders: { customRequestHeaders: { [CLIENT_IP_HEADER]: '' } },\n\t\t};\n\t}\n\n\tfor (const route of routes) {\n\t\tconst chain: string[] = [strip];\n\t\tif (route.allow) {\n\t\t\t// The peer Traefik sees — never X-Forwarded-For, which a client\n\t\t\t// could set. Everything else gets 403.\n\t\t\tmiddlewares[name(route, 'allow')] = {\n\t\t\t\tipAllowList: { sourceRange: [...route.allow] },\n\t\t\t};\n\t\t\tchain.push(name(route, 'allow'));\n\t\t}\n\t\tif (route.prefix) {\n\t\t\tmiddlewares[name(route, 'prefix')] = {\n\t\t\t\taddPrefix: { prefix: route.prefix },\n\t\t\t};\n\t\t\tchain.push(name(route, 'prefix'));\n\t\t}\n\n\t\trouters[name(route)] = {\n\t\t\trule: `Host(\\`${route.host}\\`)`,\n\t\t\tentryPoints: ['websecure'],\n\t\t\tservice: name(route),\n\t\t\tmiddlewares: chain,\n\t\t\ttls: tls.kind === 'acme' ? { certResolver: ACME_RESOLVER } : {},\n\t\t};\n\n\t\tservices[name(route)] = {\n\t\t\tloadBalancer: {\n\t\t\t\tservers: route.upstreams.map((upstream) => ({ url: url(upstream) })),\n\t\t\t\t// A bucket is routed and signed on the Host header, so MinIO is\n\t\t\t\t// sent its own; an app is sent the one the caller used, which is\n\t\t\t\t// what it builds redirects, cookies and links on.\n\t\t\t\t...(route.prefix ? { passHostHeader: false } : {}),\n\t\t\t\t...(route.streaming\n\t\t\t\t\t? {\n\t\t\t\t\t\t\t// Never buffer: each chunk of a streamed response is\n\t\t\t\t\t\t\t// flushed to the client as it is written.\n\t\t\t\t\t\t\tresponseForwarding: { flushInterval: '-1ms' },\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t\t...(route.health\n\t\t\t\t\t? {\n\t\t\t\t\t\t\thealthCheck: {\n\t\t\t\t\t\t\t\tpath: route.health,\n\t\t\t\t\t\t\t\tinterval: '5s',\n\t\t\t\t\t\t\t\ttimeout: '3s',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\thttp: {\n\t\t\trouters,\n\t\t\tservices,\n\t\t\t...(Object.keys(middlewares).length > 0 ? { middlewares } : {}),\n\t\t},\n\t\t...(tls.kind === 'files'\n\t\t\t? {\n\t\t\t\t\ttls: {\n\t\t\t\t\t\tcertificates: [{ certFile: tls.certFile, keyFile: tls.keyFile }],\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t};\n}\n\n/** A stack's dynamic configuration file, as written to the edge's directory. */\nexport function traefikDynamicFile(\n\troutes: readonly EdgeRoute[],\n\toptions: TraefikStackOptions,\n): string {\n\treturn `# Generated by gkm compose for ${options.project} — do not edit.\n# The shared edge (${EDGE_PROJECT}) reads every file in this directory; this\n# one is removed by gkm compose --down.\n${yaml(traefikDynamic(routes, options))}`;\n}\n\n/** The edge's static configuration file. */\nexport function traefikStaticFile(ports: EdgePorts): string {\n\treturn `# Generated by gkm compose — the server's shared edge. Do not edit.\n${yaml(traefikStatic(ports))}`;\n}\n\n/** The edge's compose file. */\nexport function edgeComposeFile(\n\toptions: Parameters<typeof edgeCompose>[0],\n): string {\n\treturn `# Generated by gkm compose — the server's shared edge. Do not edit.\n#\n# Started by the first stack that runs with proxy: 'traefik', and left\n# running by gkm compose --down. Stop it, once nothing uses it, with:\n#\n# docker compose -p ${EDGE_PROJECT} -f ${options.dir}/docker-compose.yml down\n${yaml(edgeCompose(options))}`;\n}\n\nfunction yaml(value: unknown): string {\n\treturn stringify(value, { lineWidth: 0, aliasDuplicateObjects: false });\n}\n","/**\n * Which image each app runs — and, for a tag, whether every one exists.\n *\n * A tag is a release's identity: `--tag=v1.4.0` means the images CI pushed as\n * v1.4.0, and nothing else. So a stack is never half rolled out: every app's\n * image is asked of the registry before anything is pulled or the running\n * stack is touched, and one missing image stops the whole run naming every\n * one that is missing. Nor is a missing image quietly built instead — a build\n * of whatever is checked out here is not the release the tag names.\n */\n\nimport { GkmError } from '../errors';\nimport type { ComposeDocker } from './docker';\n\n/** One app's image, as the stack runs it. */\nexport interface AppImage {\n\tapp: string;\n\tref: string;\n\ttag: string;\n}\n\n/**\n * The tag a site's image carries for a release.\n *\n * A site's public URLs are inlined when it is built, so one bundle serves one\n * stage: `v1.4.0-production` and `v1.4.0-staging` are two images of the same\n * commit. A backend reads its URLs at runtime, so one image serves every stage\n * and its tag is the release's alone.\n */\nexport function siteTag(tag: string, stage: string): string {\n\treturn `${tag}-${stage}`;\n}\n\n/** Images a tag names that the registry does not have. */\nexport class ImageTagNotFound extends GkmError {\n\tconstructor(\n\t\treadonly tag: string,\n\t\treadonly refs: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`The registry has no image for ${refs.length === 1 ? 'this app' : `${refs.length} apps`} at '${tag}':\\n` +\n\t\t\t\trefs.map((ref) => ` - ${ref}`).join('\\n') +\n\t\t\t\t`\\nNothing was pulled or started. Push '${tag}' for every app from CI, ` +\n\t\t\t\t`pick a tag that exists, or run without --tag to build from this checkout.`,\n\t\t);\n\t\tthis.name = 'ImageTagNotFound';\n\t}\n}\n\n/** The registry did not answer, or did not let this machine in. */\nexport class RegistryUnreachable extends GkmError {\n\tconstructor(\n\t\treadonly ref: string,\n\t\treadonly detail: string,\n\t) {\n\t\tsuper(\n\t\t\t`Could not ask the registry about ${ref}: ${detail || 'no answer'}\\n` +\n\t\t\t\t`Nothing was pulled or started. Check the registry is reachable and ` +\n\t\t\t\t`that this machine is logged in to it (\\`docker login <registry>\\`).`,\n\t\t);\n\t\tthis.name = 'RegistryUnreachable';\n\t}\n}\n\n/**\n * Assert every image exists, before anything else happens.\n *\n * Asked all at once and reported all at once: a release missing two images is\n * one problem with two names, and finding the second only after fixing the\n * first is a slower way to learn the same thing.\n *\n * @throws {RegistryUnreachable} when the registry cannot say\n * @throws {ImageTagNotFound} listing every missing image\n */\nexport async function assertImagesExist(\n\tdocker: Pick<ComposeDocker, 'lookup'>,\n\ttag: string,\n\timages: readonly AppImage[],\n): Promise<void> {\n\tconst lookups = await Promise.all(\n\t\timages.map((image) => docker.lookup(image.ref)),\n\t);\n\n\tconst unreachable = lookups.find((lookup) => lookup.status === 'unreachable');\n\tif (unreachable?.status === 'unreachable') {\n\t\tthrow new RegistryUnreachable(unreachable.ref, unreachable.detail);\n\t}\n\n\tconst missing = lookups\n\t\t.filter((lookup) => lookup.status === 'missing')\n\t\t.map((lookup) => lookup.ref);\n\tif (missing.length > 0) throw new ImageTagNotFound(tag, missing);\n}\n\n/**\n * A push or a pull with no `deploy.registry` to push to or pull from.\n *\n * Without one an image is named `<namespace>/<project>-<app>:<tag>`, which\n * Docker resolves to Docker Hub — so a release would be pushed to, or pulled\n * from, somebody else's account. It is never a fallback.\n */\nexport class RegistryRequired extends GkmError {\n\tconstructor(\n\t\t/** What needed the registry: `--push`, or a pull (`--tag`/`--pull`). */\n\t\treadonly operation: 'push' | 'pull',\n\t\t/** The ref an image would have had: a Docker Hub name. */\n\t\treadonly ref: string,\n\t) {\n\t\tsuper(\n\t\t\t`${operation === 'push' ? 'Pushing' : 'Pulling'} images needs a registry, and gkm.config.ts sets no deploy.registry: ` +\n\t\t\t\t`'${ref}' would be ${operation === 'push' ? 'pushed to' : 'pulled from'} Docker Hub. ` +\n\t\t\t\t`Set deploy.registry (e.g. registry: 'ghcr.io/acme') in gkm.config.ts.` +\n\t\t\t\t(operation === 'pull'\n\t\t\t\t\t? ' To run images built here instead, pass --build.'\n\t\t\t\t\t: ''),\n\t\t);\n\t\tthis.name = 'RegistryRequired';\n\t}\n}\n\n/**\n * What a push reports, and what a pull can pin to: each app's image as\n * `<ref>@sha256:…` — the tag it was pushed as, and the exact image, whatever\n * that tag is later moved to.\n */\nexport type ImageDigests = Record<string, string>;\n\n/** An image pinned to its digest: `<ref>@sha256:…`. */\nexport function pinnedRef(ref: string, digest: string): string {\n\treturn `${ref.split('@')[0]}@${digest}`;\n}\n\n/** A digests file with no entry for an app the stack runs. */\nexport class ImageDigestMissing extends GkmError {\n\tconstructor(\n\t\treadonly file: string,\n\t\treadonly apps: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${file} pins no digest for ${apps.join(', ')}. Use the file the ` +\n\t\t\t\t`same release's \\`gkm compose --build --push --digests-file\\` wrote, ` +\n\t\t\t\t`or run without --digests-file to pull by tag.`,\n\t\t);\n\t\tthis.name = 'ImageDigestMissing';\n\t}\n}\n\n/** A digests file entry that is not this app's image. */\nexport class ImageDigestMismatch extends GkmError {\n\tconstructor(\n\t\treadonly app: string,\n\t\treadonly expected: string,\n\t\treadonly found: string,\n\t) {\n\t\tsuper(\n\t\t\t`The digests file pins '${app}' to ${found}, but this release runs ` +\n\t\t\t\t`${expected}. It was written for another registry, project, stage ` +\n\t\t\t\t`or tag; use the file this stage's push of this tag wrote.`,\n\t\t);\n\t\tthis.name = 'ImageDigestMismatch';\n\t}\n}\n\n/** A digests file that is not a JSON object of app to pinned ref. */\nexport class ImageDigestsInvalid extends GkmError {\n\tconstructor(\n\t\treadonly file: string,\n\t\treadonly detail: string,\n\t) {\n\t\tsuper(\n\t\t\t`${file} is not a digests file (${detail}). It should be the JSON ` +\n\t\t\t\t`object \\`gkm compose --build --push --digests-file\\` writes: ` +\n\t\t\t\t`{ \"api\": \"<registry>/<image>:<tag>@sha256:…\" }.`,\n\t\t);\n\t\tthis.name = 'ImageDigestsInvalid';\n\t}\n}\n\n/** Parse a digests file's content, checking its shape. */\nexport function parseDigests(file: string, content: string): ImageDigests {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(content);\n\t} catch (error) {\n\t\tthrow new ImageDigestsInvalid(\n\t\t\tfile,\n\t\t\terror instanceof Error ? error.message : String(error),\n\t\t);\n\t}\n\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\tthrow new ImageDigestsInvalid(file, 'not a JSON object');\n\t}\n\tfor (const [app, value] of Object.entries(parsed)) {\n\t\tif (typeof value !== 'string' || !/@sha256:[0-9a-f]{64}$/.test(value)) {\n\t\t\tthrow new ImageDigestsInvalid(\n\t\t\t\tfile,\n\t\t\t\t`'${app}' is not an <image>:<tag>@sha256:… ref`,\n\t\t\t);\n\t\t}\n\t}\n\treturn parsed as ImageDigests;\n}\n\n/**\n * Each image pinned to the digest the file names for it.\n *\n * @throws {ImageDigestMissing} naming every app the file has no entry for\n * @throws {ImageDigestMismatch} when an entry is another image or tag\n */\nexport function pinImages<T extends AppImage>(\n\tfile: string,\n\tdigests: ImageDigests,\n\timages: readonly T[],\n): T[] {\n\tconst missing = images\n\t\t.filter((image) => !digests[image.app])\n\t\t.map((image) => image.app);\n\tif (missing.length > 0) throw new ImageDigestMissing(file, missing);\n\n\treturn images.map((image) => {\n\t\tconst pinned = digests[image.app]!;\n\t\tif (pinned.split('@')[0] !== image.ref) {\n\t\t\tthrow new ImageDigestMismatch(image.app, image.ref, pinned);\n\t\t}\n\t\treturn { ...image, ref: pinned };\n\t});\n}\n","/**\n * The stack's own edge, rendered for Caddy: one site block per route.\n *\n * Deployed, each host is the stage's real domain and Caddy gets it a\n * certificate from Let's Encrypt on first request — the same automatic HTTPS\n * it does anywhere, with nothing to configure but the name — or serves the\n * stage's own certificate (`deploy.compose.tls`). Locally the hosts are\n * `*.localhost` and the certificates come from Caddy's own CA, as the `gkm\n * dev` edge does, so a local stack is `https://` like a deployed one.\n *\n * Pure: the routes are data (`routes.ts`), and rendering them is one string.\n */\n\nimport { CLIENT_IP_HEADER } from '@geekmidas/manifest';\nimport type { EdgeRoute, EdgeTls, Upstream } from './routes.js';\n\n/** Where Caddy reads a stage's own certificate, inside its container. */\nexport const CADDY_TLS_DIR = '/etc/caddy/tls';\n\nexport interface CaddyfileOptions {\n\ttls: EdgeTls;\n\t/** Where ACME sends expiry notices, when someone wants them. */\n\temail?: string;\n}\n\n/** An upstream on the stack's own network: its service name. */\nfunction address({ service, port }: Upstream): string {\n\treturn `${service}:${port}`;\n}\n\n/** The Caddyfile for a stack's edge. */\nexport function edgeCaddyfile(\n\troutes: readonly EdgeRoute[],\n\toptions: CaddyfileOptions,\n): string {\n\tconst { tls } = options;\n\tconst global =\n\t\ttls.kind === 'internal'\n\t\t\t? `{\n\t# No ACME and no public DNS: the CA is Caddy's own and the names resolve\n\t# to this machine.\n\tlocal_certs\n\tauto_https disable_redirects\n}`\n\t\t\t: options.email\n\t\t\t\t? `{\n\temail ${options.email}\n}`\n\t\t\t\t: '';\n\n\tconst certificate =\n\t\ttls.kind === 'internal'\n\t\t\t? `\n\ttls internal\n`\n\t\t\t: tls.kind === 'files'\n\t\t\t\t? `\n\ttls ${tls.certFile} ${tls.keyFile}\n`\n\t\t\t\t: '';\n\n\tconst blocks = routes.map(\n\t\t({\n\t\t\thost,\n\t\t\tupstreams,\n\t\t\tprefix,\n\t\t\tallow,\n\t\t\tstreaming,\n\t\t}) => `https://${host} {${certificate}\n\t# gkm's own header, which only a service of this stack may send: the auth\n\t# server rate-limits by the client address it carries.\n\trequest_header -${CLIENT_IP_HEADER}\n${\n\tallow\n\t\t? `\n\t# Only these addresses — the peer Caddy sees, never a header a client\n\t# could set. Everything else is refused before it reaches the upstream.\n\t@denied not remote_ip ${allow.join(' ')}\n\trespond @denied 403\n`\n\t\t: ''\n}${\n\tprefix\n\t\t? `\n\t# A bucket, served at a host of its own: the bucket is a prefix on MinIO,\n\t# which routes and signs on the Host header — so it is sent the upstream's.\n\treverse_proxy ${upstreams.map(address).join(' ')} {\n\t\trewrite ${prefix}{uri}\n\t\theader_up Host {upstream_hostport}\n\t}`\n\t\t: `\n\t# Host and X-Forwarded-* are passed through as Caddy does by default, so\n\t# an app builds redirects, cookies and links on the address a caller used.\n\treverse_proxy ${upstreams.map(address).join(' ')}${\n\t\tstreaming\n\t\t\t? ` {\n\t\t# Never buffer: a streamed response — server-sent events, a Next.js\n\t\t# RSC payload — reaches the client as each chunk is written.\n\t\tflush_interval -1\n\t}`\n\t\t\t: ''\n\t}`\n}\n}`,\n\t);\n\n\t// No blank line under the header: `caddy fmt` wants the first block there.\n\treturn `${HEADER}${global ? `${global}\\n\\n` : ''}${blocks.join('\\n\\n')}\\n`;\n}\n\nconst HEADER = `# Generated by gkm compose from the construct manifest — do not edit.\n#\n# One host per API and site, each in front of its own container, one per\n# file server in front of the stack's MinIO, and — when its logs are served\n# publicly — one for OpenObserve, to the addresses it allows.\n`;\n","/**\n * The stack a workspace runs as for one stage — pure.\n *\n * Everything `gkm compose` writes is decided here, as data: the compose file,\n * the Caddyfile, one env file per backend, and the image each app runs. The\n * command around it reads the workspace, asks the registry, writes the files\n * and starts Docker; none of those decide anything, so all of it can be\n * asserted without a daemon.\n *\n * The values come from the same derivations every other target uses. The\n * plan is reconcile's `planFor`, the keys an app reads are `appEnvKeys`, and\n * every URL is `envFor`'s — asked twice, once with each app at its public\n * address and once on the compose network, because a browser and a sibling\n * service reach the same API by different names.\n */\n\nimport { join } from 'node:path';\nimport {\n\ttype ConstructManifest,\n\tprovideKey,\n\tprovisionOrder,\n\tpublicEnvFor,\n} from '@geekmidas/manifest';\nimport { type WorkerUnit, workerUnits } from '../build/workers.js';\nimport {\n\ttype DevServiceUse,\n\tdevServicesUsed,\n\ttype ExternalServices,\n\texternalServices,\n\ttype ServiceDeclaration,\n\tstageServiceDeclarations,\n\tsuppliedOnly,\n} from '../deploy/devServices.js';\nimport { isMainFrontendApp, resolveHost } from '../deploy/domain.js';\nimport { type DeployIdentity, imageRef } from '../deploy/identity.js';\nimport { validateImageRef } from '../docker/imageRef.js';\nimport { appDockerfile, workerDockerfile } from '../docker/index.js';\nimport { composeBuildPaths, type ImageLayout } from '../docker/layout.js';\nimport { TURBO_VERSION, WORKER_PORT } from '../docker/templates.js';\nimport { GkmError } from '../errors';\nimport { stageProviderNotes } from '../providers/notes.js';\nimport { appEnvKeys, networkEnv, workerEnvKeys } from '../reconcile/apps.js';\nimport { hostFor } from '../reconcile/caddyfile.js';\nimport { type ComposeService, composeFor } from '../reconcile/compose.js';\nimport { DEFAULT_IMAGES, portKeys } from '../reconcile/containers.js';\nimport { EMULATOR_ACCESS_KEY_ID } from '../reconcile/emulator.js';\nimport { localRolePassword } from '../reconcile/env.js';\nimport {\n\ttype ContainerCredentials,\n\tLOCAL_RABBITMQ_USER,\n\ttype LocalCredentials,\n\tpostgresSuperuser,\n} from '../reconcile/localCredentials.js';\nimport { type Plan, type PlannedResource, planFor } from '../reconcile/plan.js';\nimport { bucketPolicies } from '../reconcile/provision.js';\nimport type { StageSecrets } from '../secrets/types.js';\nimport { NoDomainForStage } from '../target/dokploy/domain.js';\nimport {\n\totlpTelemetryEnv,\n\ttype ResolvedTelemetry,\n\tresolveStageTelemetry,\n\ttelemetryEnv,\n} from '../telemetry/config.js';\nimport {\n\tisTelemetryKey,\n\tscopeTelemetryEnv,\n\ttelemetryOf,\n\tusesTelemetry,\n} from '../telemetry/edges.js';\nimport { DEFAULT_EVENTS } from '../types.js';\nimport { appKey } from '../workspace/derive.js';\nimport type {\n\tComposeTlsConfig,\n\tNormalizedAppConfig,\n\tNormalizedWorkspace,\n} from '../workspace/types.js';\nimport { CADDY_TLS_DIR, edgeCaddyfile } from './caddyfile.js';\nimport { isReservedStageKey } from './dnsConfig.js';\nimport { type AppImage, siteTag } from './images.js';\nimport {\n\tLOGS_SERVICE,\n\tlogsService,\n\ttype StackLogs,\n\tstackLogs,\n} from './logs.js';\nimport { LOGS_PORT } from './logsConfig.js';\nimport { certificateFor, proxyFor } from './proxy.js';\nimport {\n\tREDIS_SERVICE,\n\tredisService,\n\tSTACK_CACHE,\n\ttype StackRedis,\n\tstackRedis,\n} from './redis.js';\nimport type { ComposeProxy, EdgeRoute, EdgeTls } from './routes.js';\nimport {\n\tEDGE_NETWORK,\n\tedgeAlias,\n\tedgeCertificatePaths,\n\ttraefikDynamicFile,\n} from './traefik.js';\n\n/** Where a stack's files are written, relative to the workspace root. */\nexport function stackDir(stage: string): string {\n\treturn `.gkm/compose/${stage}`;\n}\n\n/**\n * The compose project a stage's stack runs as.\n *\n * The deploy identity's scope, so two workspaces with one name in different\n * namespaces never share containers or volumes — and the stage, so a\n * machine can run two stages side by side.\n */\nexport function composeProject(identity: DeployIdentity): string {\n\treturn `${identity.scope}-${identity.stage}`;\n}\n\n/** The ports the edge is published on, and the variables that move them. */\nexport const EDGE_PORT_ENV = {\n\thttps: 'GKM_COMPOSE_HTTPS_PORT',\n\thttp: 'GKM_COMPOSE_HTTP_PORT',\n} as const;\n\n/**\n * The variable that moves the local stage's OpenObserve off its loopback\n * port, 5080 — which the local stage takes from nowhere else, since it\n * ignores `deploy.telemetry`. Another stack, or another tool, may hold 5080.\n */\nexport const LOGS_PORT_ENV = 'GKM_COMPOSE_LOGS_PORT';\n\n/**\n * Containers this target does not run.\n *\n * The AWS emulator never applies: a stack is a server target, whose events\n * are pg-boss. Reconcile's Caddy fronts `gkm dev`'s host processes; the stack\n * brings its own edge.\n *\n * MinIO and Mailpit run on the local stage always, and on a deployed stage\n * only where `--allow-dev-services` allowed them and its secrets configure no\n * real bucket or mail server — see `devServices.ts`.\n */\nconst NOT_RUN: Readonly<Record<string, true>> = {\n\tlocalstack: true,\n\tcaddy: true,\n};\n\n/** The kinds a deployed stage takes from its secrets, or a dev service. */\nconst SERVICE_KINDS: Readonly<Record<string, ServiceDeclaration['kind']>> = {\n\temail: 'email',\n\tobjects: 'objects',\n\t'file-server': 'file-server',\n};\n\n/** The region MinIO is addressed with. It has no regions; the SDK wants one. */\nconst MINIO_REGION = 'us-east-1';\n\n/** The S3 client's credentials, read beside a bucket's URL when it has them. */\nconst STORAGE_KEYS = [\n\t'AWS_ACCESS_KEY_ID',\n\t'AWS_SECRET_ACCESS_KEY',\n\t'AWS_REGION',\n] as const;\n\n/**\n * Every service's Docker logs, rotated. The json-file driver keeps a\n * container's output in one file that grows until the disk is full — on a\n * small server, in weeks. Three files of 10 MB each per container is enough\n * to see what happened and never fills anything. A project's own override\n * file that sets `logging` wins over it.\n */\nexport const LOG_ROTATION = {\n\tdriver: 'json-file',\n\toptions: { 'max-size': '10m', 'max-file': '3' },\n} as const;\n\n/** Each image's build, where the stack builds rather than pulls. */\nexport interface AppBuild {\n\t/** Where the Dockerfile is written, relative to the workspace root. */\n\tdockerfile: string;\n\t/** A site's public URLs, inlined by its bundler. */\n\targs?: Record<string, string>;\n}\n\n/** One app in the stack. */\nexport interface StackApp extends AppImage {\n\t/** Its compose service name — the app key, `api`. */\n\tname: string;\n\t/** The construct it serves. */\n\tid: string;\n\tkind: 'rest-api' | 'site';\n\t/** Its directory, relative to the workspace root. */\n\tpath: string;\n\tport: number;\n\t/** The host the edge answers it on. */\n\thost: string;\n\t/** `https://<host>` — with the edge's port where it is not 443. */\n\turl: string;\n\tbuild?: AppBuild;\n\t/** A backend's environment, exactly the keys it reads. Absent for a site. */\n\tenv?: Record<string, string>;\n}\n\n/**\n * A Worker in the stack: its crons, queue consumers and subscribers in a\n * container of their own. Nothing routes to it and nothing is published —\n * its one route is the health check Docker asks.\n */\nexport interface StackWorker extends AppImage {\n\t/** Its compose service name — `jobs`. */\n\tname: string;\n\t/** The Worker construct. */\n\tid: string;\n\t/** The app whose build writes its entry, which its image is built from. */\n\thost: string;\n\t/** Where its health check answers, inside the container. */\n\tport: number;\n\tbuild?: AppBuild;\n\t/** Exactly the keys the worker's constructs read. */\n\tenv?: Record<string, string>;\n}\n\n/** A service as the stack's compose file defines it. */\nexport interface StackService {\n\timage: string;\n\tbuild?: {\n\t\tcontext: string;\n\t\tdockerfile: string;\n\t\targs?: Record<string, string>;\n\t};\n\trestart?: string;\n\tcommand?: string | string[];\n\tenv_file?: { path: string; format: 'raw' }[];\n\tenvironment?: Record<string, string>;\n\tports?: string[];\n\tvolumes?: string[];\n\tdepends_on?: Record<string, { condition: 'service_healthy' }>;\n\thealthcheck?: ComposeService['healthcheck'];\n\t/**\n\t * Behind the shared edge, a public service is on the stack's own network\n\t * and the edge's, by an alias unique on both.\n\t */\n\tnetworks?: Record<string, { aliases: string[] }>;\n\tlogging?: {\n\t\tdriver: string;\n\t\toptions: Record<string, string>;\n\t};\n}\n\nexport interface StackFile {\n\tname: string;\n\tservices: Record<string, StackService>;\n\tvolumes: Record<string, Record<string, never>>;\n\t/** The shared edge's network, which the stack joins but never creates. */\n\tnetworks?: Record<string, { name: string; external: true }>;\n}\n\nexport interface ComposeStack {\n\tproject: string;\n\tstage: string;\n\t/** The project's local stage, served with Caddy's internal CA. */\n\tlocal: boolean;\n\tapps: StackApp[];\n\t/** Each Worker with background work, in a container of its own. */\n\tworkers: StackWorker[];\n\t/** The infrastructure containers, by compose service name. */\n\tinfra: string[];\n\tcompose: StackFile;\n\t/**\n\t * What serves the stack: its own Caddy, or the server's shared Traefik\n\t * edge (`deploy.compose.proxy`). The local stage is always Caddy.\n\t */\n\tproxy: ComposeProxy;\n\t/** Every public host and what answers it — what either proxy renders. */\n\troutes: EdgeRoute[];\n\t/** The stack's own Caddy's configuration — with `proxy: 'caddy'`. */\n\tcaddyfile?: string;\n\t/**\n\t * What the stack registers with the shared edge — with `proxy:\n\t * 'traefik'`: the dynamic configuration file written to its directory.\n\t */\n\ttraefik?: string;\n\t/**\n\t * The stage's own certificate and key, on this machine\n\t * (`deploy.compose.tls`) — copied to where the proxy reads it.\n\t */\n\ttls?: ComposeTlsConfig;\n\t/** Generated Dockerfiles, by path relative to the workspace root. */\n\tdockerfiles: Record<string, string>;\n\t/** The plan the stack was derived from — what provisioning creates. */\n\tplan: Plan;\n\t/** The containers' logins, and the seed role passwords take. */\n\tcredential: StackCredential;\n\t/**\n\t * The stack's MinIO, where it runs one: its root credential — what the\n\t * backends sign with — and the buckets and open paths provisioning\n\t * creates in it.\n\t */\n\tstorage?: StackStorage;\n\t/** The dev services a deployed stage runs (`--allow-dev-services`). */\n\tdevServices: DevServiceUse[];\n\t/**\n\t * The stack's OpenObserve, when the stage's telemetry is self-hosted —\n\t * every process with a `Telemetry` edge sends its logs and traces to it.\n\t */\n\tlogs?: StackLogs;\n\t/** Where the stage's telemetry goes, when any process uses it. */\n\ttelemetry?: ResolvedTelemetry;\n\t/**\n\t * The stack's Redis, where a declared cache lives in it — every cache\n\t * whose URL the stage's secrets do not set.\n\t */\n\tredis?: StackRedis;\n}\n\n/** What a stack's containers run with, and what its role passwords take. */\nexport interface StackCredential {\n\tcontainers: ContainerCredentials;\n\tseed: string;\n}\n\n/** The local stage's stack, without the logins `gkm dev` generates. */\nexport class LocalCredentialsMissing extends GkmError {\n\tconstructor(readonly stage: string) {\n\t\tsuper(\n\t\t\t`The local stage '${stage}' runs with this machine's generated logins, and none were passed. Load them with loadLocalCredentials(workspace) and pass them as localCredentials.`,\n\t\t);\n\t\tthis.name = 'LocalCredentialsMissing';\n\t}\n}\n\n/**\n * A deployed stage's container logins, derived from its seed like its role\n * passwords — so every deploy of the stage computes the same ones, and no one\n * reading the repo can.\n */\nexport function derivedCredentials(\n\tproject: string,\n\tplan: Plan,\n\tseed: string,\n): ContainerCredentials {\n\tconst derive = (purpose: string) =>\n\t\tlocalRolePassword(project, plan, purpose, seed);\n\treturn {\n\t\tpostgres: { user: postgresSuperuser(project), password: derive('master') },\n\t\tminio: { user: `${project}-minio`, password: derive('minio') },\n\t\trabbitmq: { user: LOCAL_RABBITMQ_USER, password: derive('rabbitmq') },\n\t\tredis: { password: derive('redis') },\n\t\tcacheToken: derive('cache-token'),\n\t\temulator: {\n\t\t\taccessKeyId: EMULATOR_ACCESS_KEY_ID,\n\t\t\tsecretAccessKey: derive('emulator'),\n\t\t},\n\t\t// Never read: a deployed stage's OpenObserve signs in with the root\n\t\t// login its secrets hold (`stackLogs`), and the stack hands each process\n\t\t// its telemetry keys from that, not from the derivation.\n\t\tlogs: { email: `admin@${project}`, password: derive('logs') },\n\t};\n}\n\n/** The stack's MinIO, and what is created in it. */\nexport interface StackStorage {\n\tuser: string;\n\tpassword: string;\n\t/** Each bucket's stage-scoped name. */\n\tbuckets: string[];\n\t/** The open paths of each served bucket. */\n\tpolicies: { bucket: string; open: string[] }[];\n}\n\nexport interface StackInput {\n\tworkspace: NormalizedWorkspace;\n\tmanifest: ConstructManifest;\n\t/** Each owner's runnables' edges, from discovery. */\n\trunnables?: Readonly<Record<string, readonly string[]>>;\n\t/**\n\t * Each Worker's crons', queues' and subscribers' files, from discovery —\n\t * which workers run, and which app each is built from.\n\t */\n\tbackground?: Readonly<Record<string, readonly string[]>>;\n\tstage: string;\n\tidentity: DeployIdentity;\n\timages: {\n\t\tmode: 'build' | 'pull';\n\t\t/** A release tag, or — building — the commit. */\n\t\ttag: string;\n\t\tregistry?: string;\n\t};\n\t/** The stage's secrets — set by hand, and what a deployed stage generated. */\n\tsecrets?: StageSecrets | null;\n\t/**\n\t * This machine's generated local logins — required for the local stage,\n\t * which runs with the same ones `gkm dev` does. See `localCredentials.ts`.\n\t */\n\tlocalCredentials?: LocalCredentials;\n\t/**\n\t * `--allow-dev-services`: a deployed stage runs the dev service for every\n\t * bucket and mail it does not account for. The local stage runs both\n\t * regardless.\n\t */\n\tallowDevServices?: boolean;\n\t/** The edge's published ports. 443 and 80 by default. */\n\tports?: {\n\t\thttps?: number;\n\t\thttp?: number;\n\t\t/** The local stage's OpenObserve port — see {@link LOGS_PORT_ENV}. */\n\t\tlogs?: number;\n\t};\n\t/**\n\t * The stack only builds its images, to push them (`--build --push`): no\n\t * backend's or worker's runtime environment is resolved and no service\n\t * reads an env file, so building needs none of the stage's backend\n\t * secrets. A site's build args — its public URLs — are still resolved.\n\t */\n\tbuildOnly?: boolean;\n\t/**\n\t * For builds: where the images are built from and with what — the build\n\t * root, its package manager and turbo. The workspace's root, with pnpm,\n\t * when not given.\n\t */\n\tlayout?: ImageLayout;\n}\n\n/** A value only the stage's secrets can hold, and they do not. */\nexport class StageSecretMissing extends GkmError {\n\tconstructor(\n\t\treadonly app: string,\n\t\treadonly key: string,\n\t\treadonly stage: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${app}' reads ${key}, and the stage '${stage}' has none. Nothing in ` +\n\t\t\t\t`the stack can derive it; set it in the stage's secrets: ` +\n\t\t\t\t`gkm secrets:set ${key} '…' --stage ${stage}`,\n\t\t);\n\t\tthis.name = 'StageSecretMissing';\n\t}\n}\n\n/** A deployed stage's secrets with no seed to derive its passwords from. */\nexport class StageSeedMissing extends GkmError {\n\tconstructor(readonly stage: string) {\n\t\tsuper(\n\t\t\t`The stage '${stage}' has no seed in its secrets, and a deployed stage's ` +\n\t\t\t\t`database passwords are derived from it. Run gkm compose --stage ${stage} ` +\n\t\t\t\t`without --dry-run once, or gkm deploy, to generate it.`,\n\t\t);\n\t\tthis.name = 'StageSeedMissing';\n\t}\n}\n\n/** A workspace with no API and no site to run. */\nexport class NothingToCompose extends GkmError {\n\tconstructor(readonly root: string) {\n\t\tsuper(\n\t\t\t`${root} declares no RestApi, no site and no Worker with work to do, ` +\n\t\t\t\t`so a stack would run nothing. Mobile apps ship through their own ` +\n\t\t\t\t`toolchain.`,\n\t\t);\n\t\tthis.name = 'NothingToCompose';\n\t}\n}\n\n/** A value that cannot be written to an env file a line at a time. */\nexport class EnvValueMultiline extends GkmError {\n\tconstructor(\n\t\treadonly app: string,\n\t\treadonly key: string,\n\t) {\n\t\tsuper(\n\t\t\t`${key} for '${app}' spans more than one line, and an env file holds ` +\n\t\t\t\t`one value per line. Store it on one line — base64, or JSON — and ` +\n\t\t\t\t`decode it where it is read.`,\n\t\t);\n\t\tthis.name = 'EnvValueMultiline';\n\t}\n}\n\n/** The services a stack's apps wait on and the edge waits on in turn. */\nconst HEALTHY = { condition: 'service_healthy' } as const;\n\n/**\n * An app's health check: its own server, asked on 127.0.0.1 — not\n * `localhost`, which resolves to `::1` first in an Alpine container while a\n * server may listen on IPv4 alone.\n */\nfunction probe(port: number, path: string): StackService['healthcheck'] {\n\treturn {\n\t\ttest: [\n\t\t\t'CMD',\n\t\t\t'wget',\n\t\t\t'-q',\n\t\t\t'-O',\n\t\t\t'/dev/null',\n\t\t\t`http://127.0.0.1:${port}${path}`,\n\t\t],\n\t\tinterval: '5s',\n\t\ttimeout: '3s',\n\t\tretries: 20,\n\t\tstart_period: '10s',\n\t};\n}\n\n/** The stack for one stage. */\nexport function composeStack(input: StackInput): ComposeStack {\n\tconst { workspace, manifest, stage, identity } = input;\n\tconst local = stage === workspace.stages.local;\n\tconst project = composeProject(identity);\n\tconst custom = input.secrets?.custom ?? {};\n\tconst https = input.ports?.https ?? 443;\n\tconst http = input.ports?.http ?? 80;\n\tconst proxy = proxyFor(workspace.deploy?.compose, stage, local);\n\tconst certificate = local\n\t\t? undefined\n\t\t: certificateFor(workspace.deploy?.compose, stage, workspace.root);\n\n\t// A server target's backends, whatever `deploy.default` says: the stack is\n\t// one machine running containers, so events are pg-boss beside the\n\t// declared database — and every cache, one declared from that database\n\t// included, is in the stack's own Redis.\n\tconst plan = planFor(manifest, stage, provisionOrder(manifest), {\n\t\tlocalStage: workspace.stages.local,\n\t\tevents: DEFAULT_EVENTS.server,\n\t\tcache: STACK_CACHE,\n\t\t// The stack brings its own edge; reconcile's would front the host.\n\t\tedge: false,\n\t});\n\tconst byId = new Map(plan.resources.map((r) => [r.id, r]));\n\n\t// The local stage's logins are the ones `gkm dev` generated for this\n\t// machine, so the two run their containers with the same; a deployed\n\t// stage's are derived from its seed.\n\tif (local && !input.localCredentials) {\n\t\tthrow new LocalCredentialsMissing(stage);\n\t}\n\tconst seed = local ? input.localCredentials!.seed : input.secrets?.seed;\n\tif (!seed) throw new StageSeedMissing(stage);\n\tconst containers: ContainerCredentials = local\n\t\t? input.localCredentials!\n\t\t: derivedCredentials(workspace.name, plan, seed);\n\tconst credential: StackCredential = {\n\t\tcontainers: {\n\t\t\t...containers,\n\t\t\t// The stage's own key pair, where it set one, is what MinIO runs as.\n\t\t\tminio: {\n\t\t\t\tuser: custom.AWS_ACCESS_KEY_ID ?? containers.minio.user,\n\t\t\t\tpassword: custom.AWS_SECRET_ACCESS_KEY ?? containers.minio.password,\n\t\t\t},\n\t\t},\n\t\tseed,\n\t};\n\n\tconst apps = stackApps(workspace, manifest, plan, {\n\t\tlocal,\n\t\tstage,\n\t\thttps,\n\t\tidentity,\n\t\timages: input.images,\n\t});\n\tconst workers = stackWorkers(\n\t\tworkerUnits(workspace, manifest, input.background ?? {}),\n\t\t{ stage, identity, images: input.images },\n\t);\n\tif (apps.length === 0 && workers.length === 0) {\n\t\tthrow new NothingToCompose(workspace.root);\n\t}\n\n\t// Which construct each key belongs to, so a key is resolved by what it is\n\t// rather than by its spelling.\n\tconst owners = new Map<string, PlannedResource>();\n\tfor (const resource of plan.resources) {\n\t\towners.set(resource.envKey, resource);\n\t\tif (resource.kind === 'email') {\n\t\t\towners.set(provideKey(resource.id, 'from'), resource);\n\t\t\towners.set(provideKey(resource.id, 'inboxUrl'), resource);\n\t\t}\n\t\tif (resource.kind === 'external-api') {\n\t\t\towners.set(provideKey(resource.id, 'credentials'), resource);\n\t\t}\n\t}\n\n\tconst domain = workspace.domains?.[stage];\n\n\t// Where the stage's telemetry goes, when a process uses a `Telemetry`\n\t// node: the local stage always to OpenObserve, a deployed one where\n\t// `deploy.telemetry` says — and OpenObserve when it says nothing.\n\tconst telemetry = resolveStageTelemetry({\n\t\t...(workspace.deploy?.telemetry\n\t\t\t? { telemetry: workspace.deploy.telemetry }\n\t\t\t: {}),\n\t\tstage,\n\t\tlocal,\n\t\ttarget: 'compose',\n\t\truntime: 'server',\n\t\tselfHosted: true,\n\t\tused: usesTelemetry(manifest),\n\t});\n\n\t// The log UI, when the stage's telemetry is self-hosted: its root login,\n\t// how it is reached, and what each process with the edge is handed to send\n\t// to it.\n\tconst logsBase =\n\t\ttelemetry?.provider === 'self-hosted'\n\t\t\t? stackLogs({\n\t\t\t\t\tconfig:\n\t\t\t\t\t\tlocal && input.ports?.logs\n\t\t\t\t\t\t\t? { ...telemetry.selfHosted, port: input.ports.logs }\n\t\t\t\t\t\t\t: telemetry.selfHosted,\n\t\t\t\t\tstage,\n\t\t\t\t\tlocal,\n\t\t\t\t\tproject: workspace.name,\n\t\t\t\t\t...(domain ? { domain } : {}),\n\t\t\t\t\tcustom,\n\t\t\t\t\thttps,\n\t\t\t\t\t...(local ? { localLogin: input.localCredentials!.logs } : {}),\n\t\t\t\t})\n\t\t\t: undefined;\n\n\t// Mail and storage: the local stage runs Mailpit and MinIO for all of it.\n\t// A deployed stage takes each from its secrets — or, where allowed, from a\n\t// dev service. Only decided here: whether the stage lacks a key is the\n\t// deploy's readiness check's to say, once, and a build asks no such thing.\n\tconst services: ExternalServices = local\n\t\t? {\n\t\t\t\tmissing: [],\n\t\t\t\tminio: plan.resources\n\t\t\t\t\t.filter((r) => r.kind === 'objects')\n\t\t\t\t\t.map((r) => r.id),\n\t\t\t\tmailpit: plan.resources\n\t\t\t\t\t.filter((r) => r.kind === 'email')\n\t\t\t\t\t.map((r) => r.id),\n\t\t\t}\n\t\t: externalServices({\n\t\t\t\tstage,\n\t\t\t\tdeclarations: stageServiceDeclarations({\n\t\t\t\t\tmanifest,\n\t\t\t\t\t...(input.runnables ? { runnables: input.runnables } : {}),\n\t\t\t\t}),\n\t\t\t\tsupplied: custom,\n\t\t\t\tallow: input.allowDevServices === true,\n\t\t\t\t...(domain ? { domain } : {}),\n\t\t\t\tproviders: stageProviderNotes(workspace, stage),\n\t\t\t});\n\n\t// The caches: in the stack's Redis, unless the stage set a cache's URL —\n\t// a managed Redis — and with every one set, there is no Redis to run.\n\tconst redis = stackRedis({\n\t\tplan,\n\t\tstage,\n\t\tlocal,\n\t\tcustom,\n\t\t...(local ? { localPassword: input.localCredentials!.redis.password } : {}),\n\t});\n\n\tconst infra = [\n\t\t...plan.containers\n\t\t\t.filter((c) => !NOT_RUN[c])\n\t\t\t// Run as the stack's own, below, with the stage's login.\n\t\t\t.filter((c) => c !== LOGS_SERVICE)\n\t\t\t.filter((c) => c !== 'minio' || services.minio.length > 0)\n\t\t\t.filter((c) => c !== 'mailpit' || services.mailpit.length > 0)\n\t\t\t.filter((c) => c !== REDIS_SERVICE || redis !== undefined),\n\t\t...(logsBase ? [LOGS_SERVICE] : []),\n\t].sort();\n\n\t// The stack's MinIO signs with the stage's own key pair where it set one,\n\t// and otherwise with the stage's login: the local one `gkm dev` generated,\n\t// or — deployed — one derived from the stage's seed.\n\tconst storage: StackStorage | undefined = infra.includes('minio')\n\t\t? (() => {\n\t\t\t\tconst buckets = services.minio\n\t\t\t\t\t.map((id) => byId.get(id)?.name)\n\t\t\t\t\t.filter((name): name is string => Boolean(name))\n\t\t\t\t\t.sort();\n\t\t\t\treturn {\n\t\t\t\t\t...credential.containers.minio,\n\t\t\t\t\tbuckets,\n\t\t\t\t\tpolicies: bucketPolicies(plan).filter((p) =>\n\t\t\t\t\t\tbuckets.includes(p.bucket),\n\t\t\t\t\t),\n\t\t\t\t};\n\t\t\t})()\n\t\t: undefined;\n\n\t// Each file server over the stack's MinIO, at a host of its own on the\n\t// edge — the shape it has deployed: a domain serving a bucket.\n\tconst fileServers = new Map<string, { url: string; route: EdgeRoute }>();\n\tif (storage) {\n\t\tfor (const resource of plan.resources) {\n\t\t\tif (resource.kind !== 'file-server' || !resource.of) continue;\n\t\t\tif (!services.minio.includes(resource.of)) continue;\n\t\t\tif (custom[resource.envKey] !== undefined) continue;\n\t\t\tconst bucket = byId.get(resource.of);\n\t\t\tif (!bucket) continue;\n\n\t\t\tlet host: string;\n\t\t\tif (local) {\n\t\t\t\thost = hostFor(resource, workspace.name);\n\t\t\t} else {\n\t\t\t\tif (!domain) throw new NoDomainForStage(stage);\n\t\t\t\thost = `${resource.subdomain ?? appKey(resource.id)}.${domain}`;\n\t\t\t}\n\t\t\tfileServers.set(resource.id, {\n\t\t\t\turl: `https://${host}${local && https !== 443 ? `:${https}` : ''}`,\n\t\t\t\troute: {\n\t\t\t\t\tname: `files-${appKey(resource.id)}`,\n\t\t\t\t\thost,\n\t\t\t\t\tupstreams: [{ service: 'minio', port: 9000 }],\n\t\t\t\t\tstreaming: false,\n\t\t\t\t\tprefix: `/${bucket.name}`,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\n\t// Behind the shared edge, the services it routes to are on a network\n\t// every stack's public services share, where a bare name — `api` — would\n\t// be answered by each stack's. They are reached, from the edge and from\n\t// inside the stack alike, by an alias prefixed with the project.\n\tconst edgeServices = new Set(\n\t\tproxy === 'traefik'\n\t\t\t? [\n\t\t\t\t\t...apps.map((app) => app.name),\n\t\t\t\t\t...(fileServers.size > 0 ? ['minio'] : []),\n\t\t\t\t\t...(logsBase?.host && logsBase.public ? [LOGS_SERVICE] : []),\n\t\t\t\t]\n\t\t\t: [],\n\t);\n\tconst hostOf = (service: string) =>\n\t\tedgeServices.has(service) ? edgeAlias(project, service) : service;\n\tconst logs: StackLogs | undefined = logsBase && {\n\t\t...logsBase,\n\t\tappEnv: {\n\t\t\t...logsBase.appEnv,\n\t\t\tOTEL_EXPORTER_OTLP_ENDPOINT:\n\t\t\t\tlogsBase.appEnv.OTEL_EXPORTER_OTLP_ENDPOINT.replace(\n\t\t\t\t\t`//${LOGS_SERVICE}:`,\n\t\t\t\t\t`//${hostOf(LOGS_SERVICE)}:`,\n\t\t\t\t),\n\t\t},\n\t};\n\n\t// Every key the stage resolves, twice: with each app at its public address\n\t// — what a browser is handed, and what an app says it is — and with each on\n\t// the compose network, which is how one service reaches another.\n\tconst derivation = {\n\t\tproject: workspace.name,\n\t\tcredentials: credential.containers,\n\t\tseed: credential.seed,\n\t\t// Mailpit on a deployed stage sends as the stage's domain.\n\t\t...(!local && domain ? { mailFrom: `noreply@${domain}` } : {}),\n\t};\n\tconst outside = networkEnv(\n\t\tplan,\n\t\t{\n\t\t\t...derivation,\n\t\t\taddresses: Object.fromEntries(apps.map((app) => [app.id, app.url])),\n\t\t},\n\t\thostOf,\n\t);\n\tconst inside = networkEnv(\n\t\tplan,\n\t\t{\n\t\t\t...derivation,\n\t\t\taddresses: Object.fromEntries(\n\t\t\t\tapps.map((app) => [app.id, `http://${hostOf(app.name)}:${app.port}`]),\n\t\t\t),\n\t\t},\n\t\thostOf,\n\t);\n\n\tconst surfaceUrls = new Map(\n\t\tplan.resources\n\t\t\t.filter((r) => r.kind === 'rest-api' || r.kind === 'site')\n\t\t\t.map((r) => [r.envKey, r.id]),\n\t);\n\tconst appById = new Map(apps.map((app) => [app.id, app]));\n\n\t/**\n\t * What a key resolves to for one app, from the stage's secrets where only\n\t * they can hold it, and the derivations otherwise.\n\t */\n\tconst valueFor = (\n\t\tapp: Pick<StackApp, 'name' | 'id' | 'kind'>,\n\t\tkey: string,\n\t\towner: PlannedResource | undefined,\n\t): string | undefined => {\n\t\t// The server's address is gkm's own: never in an app's environment.\n\t\tif (isReservedStageKey(key)) return undefined;\n\t\t// Mail and storage: what the stage set — a real server, a real bucket —\n\t\t// and otherwise the dev service it runs, which `services` has already\n\t\t// checked it may.\n\t\tif (owner && SERVICE_KINDS[owner.kind]) {\n\t\t\tif (custom[key] !== undefined) return custom[key];\n\t\t\tif (owner.kind === 'file-server') return fileServers.get(owner.id)?.url;\n\t\t\treturn outside[key];\n\t\t}\n\n\t\t// Supplied by somebody: a third party's credentials, and — deployed —\n\t\t// the secrets the stage generated.\n\t\tif (owner && suppliedOnly(owner.kind, owner.id, key, local)) {\n\t\t\tconst value = custom[key];\n\t\t\tif (value === undefined) {\n\t\t\t\tthrow new StageSecretMissing(app.name, key, stage);\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\n\t\t// Set by hand wins over derived: it is the stage saying what it is.\n\t\tif (custom[key] !== undefined) return custom[key];\n\n\t\t// A cache, in the stack's Redis, by its name on the network.\n\t\tif (owner?.kind === 'cache') return redis?.urls[key];\n\n\t\t// A backend reaches a sibling surface on the compose network; its own\n\t\t// address is the public one, which is what it builds links on. A site\n\t\t// runs in a browser, which has only the public addresses.\n\t\tconst surface = surfaceUrls.get(key);\n\t\tif (app.kind === 'rest-api' && surface && surface !== app.id) {\n\t\t\treturn inside[key];\n\t\t}\n\n\t\treturn outside[key];\n\t};\n\n\t// What the stage resolves for its `Telemetry` node, before a process is\n\t// named: the stack's OpenObserve, or the provider `deploy.telemetry` names.\n\tconst telemetryValues: Record<string, string> | undefined = logs\n\t\t? telemetryEnv(\n\t\t\t\t{\n\t\t\t\t\tendpoint: logs.appEnv.OTEL_EXPORTER_OTLP_ENDPOINT,\n\t\t\t\t\theaders: logs.appEnv.OTEL_EXPORTER_OTLP_HEADERS,\n\t\t\t\t},\n\t\t\t\ttelemetry?.sampleRate ?? 1,\n\t\t\t)\n\t\t: telemetry?.provider === 'otlp'\n\t\t\t? otlpTelemetryEnv(telemetry)\n\t\t\t: undefined;\n\n\t/** Each key one backend process reads, resolved for this stage. */\n\tconst valuesFor = (\n\t\tapp: Pick<StackApp, 'name' | 'id' | 'kind'>,\n\t\tkeys: ReadonlySet<string>,\n\t): Record<string, string> => {\n\t\tconst values: Record<string, string> = {};\n\t\tfor (const key of [...keys].sort()) {\n\t\t\t// The node's keys are the stage's telemetry, added per process.\n\t\t\tif (isTelemetryKey(key)) continue;\n\t\t\tconst owner = owners.get(key) ?? storageOwner(key, plan);\n\t\t\t// A deployed stage has no inbox: Mailpit runs only locally.\n\t\t\tif (!local && owner?.kind === 'email' && key.endsWith('_INBOX_URL'))\n\t\t\t\tcontinue;\n\t\t\tif ((STORAGE_KEYS as readonly string[]).includes(key)) {\n\t\t\t\t// The stage's own key pair where it set one — an external bucket\n\t\t\t\t// signs with it — and otherwise the stack's MinIO's.\n\t\t\t\tconst value = custom[key] ?? storageValue(key, storage);\n\t\t\t\tif (value !== undefined) values[key] = value;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst value = valueFor(app, key, owner);\n\t\t\tif (value !== undefined) values[key] = value;\n\t\t}\n\t\treturn values;\n\t};\n\n\tfor (const app of apps) {\n\t\tif (app.kind === 'site') {\n\t\t\tconst site = manifest[app.id];\n\t\t\tif (site?.kind !== 'site') continue;\n\n\t\t\tconst args: Record<string, string> = {};\n\t\t\tfor (const [key, source] of Object.entries(\n\t\t\t\tpublicEnvFor(site, manifest),\n\t\t\t)) {\n\t\t\t\tconst value = valueFor(app, source, owners.get(source));\n\t\t\t\tif (value !== undefined) args[key] = value;\n\t\t\t}\n\t\t\tif (app.build && Object.keys(args).length > 0) app.build.args = args;\n\t\t\tcontinue;\n\t\t}\n\t\t// Built to be pushed, a backend's image is every stage's: what it reads\n\t\t// at runtime is the deploy's business, not the build's.\n\t\tif (input.buildOnly) continue;\n\n\t\tconst keys = appEnvKeys(manifest, app.name, input.runnables) ?? new Set();\n\t\tconst values = valuesFor(app, keys);\n\n\t\t// The origins Better Auth trusts are the browser's and, now, each\n\t\t// service that calls this surface across the compose network: its CSRF\n\t\t// check applies to every caller, and a sibling's request carries its\n\t\t// own origin, not a browser's.\n\t\tconst trusted = provideKey(app.id, 'trustedOrigins');\n\t\tif (keys.has(trusted)) {\n\t\t\tconst internal = (byId.get(app.id)?.callers ?? [])\n\t\t\t\t.map((caller) => appById.get(caller))\n\t\t\t\t.filter((caller): caller is StackApp => caller?.kind === 'rest-api')\n\t\t\t\t.map((caller) => `http://${hostOf(caller.name)}:${caller.port}`);\n\t\t\tconst origins = [\n\t\t\t\t...(values[trusted] ?? '').split(',').filter(Boolean),\n\t\t\t\t...internal,\n\t\t\t];\n\t\t\tvalues[trusted] = [...new Set(origins)].join(',');\n\t\t}\n\n\t\t// Telemetry: the stage's, to a process with an edge to the node.\n\t\tapp.env = {\n\t\t\t...scopeTelemetryEnv(values, {\n\t\t\t\tuses: telemetryOf(manifest, app.id) !== undefined,\n\t\t\t\tserviceName: app.name,\n\t\t\t\t...(telemetryValues ? { telemetry: telemetryValues } : {}),\n\t\t\t}),\n\t\t\tNODE_ENV: 'production',\n\t\t\tPORT: String(app.port),\n\t\t\tSTAGE: stage,\n\t\t};\n\n\t\tfor (const [key, value] of Object.entries(app.env)) {\n\t\t\tif (/[\\r\\n]/.test(value)) throw new EnvValueMultiline(app.name, key);\n\t\t}\n\t}\n\n\t// A worker reaches every surface across the compose network: it is never\n\t// one, so no address is its own.\n\tfor (const worker of input.buildOnly ? [] : workers) {\n\t\tconst keys =\n\t\t\tworkerEnvKeys(manifest, worker.id, input.runnables) ?? new Set();\n\t\tworker.env = {\n\t\t\t...scopeTelemetryEnv(\n\t\t\t\tvaluesFor({ name: worker.name, id: worker.id, kind: 'rest-api' }, keys),\n\t\t\t\t{\n\t\t\t\t\tuses: telemetryOf(manifest, worker.id) !== undefined,\n\t\t\t\t\tserviceName: worker.name,\n\t\t\t\t\t...(telemetryValues ? { telemetry: telemetryValues } : {}),\n\t\t\t\t},\n\t\t\t),\n\t\t\tNODE_ENV: 'production',\n\t\t\tPORT: String(worker.port),\n\t\t\tSTAGE: stage,\n\t\t};\n\t\tfor (const [key, value] of Object.entries(worker.env)) {\n\t\t\tif (/[\\r\\n]/.test(value)) throw new EnvValueMultiline(worker.name, key);\n\t\t}\n\t}\n\n\t// A deployed stage's certificates: its own, where it set one, as the\n\t// proxy's container reads them — otherwise Let's Encrypt's.\n\tconst deployedTls: Exclude<EdgeTls, { kind: 'internal' }> = certificate\n\t\t? {\n\t\t\t\tkind: 'files',\n\t\t\t\t...(proxy === 'caddy'\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tcertFile: `${CADDY_TLS_DIR}/cert.pem`,\n\t\t\t\t\t\t\tkeyFile: `${CADDY_TLS_DIR}/key.pem`,\n\t\t\t\t\t\t}\n\t\t\t\t\t: edgeCertificatePaths(project)),\n\t\t\t}\n\t\t: { kind: 'acme' };\n\tconst tls: EdgeTls = local ? { kind: 'internal' } : deployedTls;\n\n\tconst layout = input.layout ?? defaultLayout(workspace.root);\n\tconst compose = stackFile({\n\t\tproject,\n\t\tplan,\n\t\tinfra,\n\t\tapps,\n\t\tworkers,\n\t\tcredentials: credential.containers,\n\t\t...(storage ? { storage } : {}),\n\t\t...(logs ? { logs } : {}),\n\t\t...(redis ? { redis } : {}),\n\t\thttps,\n\t\thttp,\n\t\tcomposeDir: join(workspace.root, stackDir(stage)),\n\t\tbuildRoot: layout.buildRoot,\n\t\tworkspaceRoot: workspace.root,\n\t\tenvFiles: !input.buildOnly,\n\t\tproxy,\n\t\tedge: new Map(\n\t\t\t[...edgeServices].map((service) => [service, hostOf(service)]),\n\t\t),\n\t\ttls: certificate !== undefined,\n\t});\n\n\t// One route per public host — the data both proxies render.\n\tconst routes: EdgeRoute[] = [\n\t\t...apps.map(\n\t\t\t(app): EdgeRoute => ({\n\t\t\t\tname: app.name,\n\t\t\t\thost: app.host,\n\t\t\t\tupstreams: [{ service: app.name, port: app.port }],\n\t\t\t\tstreaming: true,\n\t\t\t\thealth: app.kind === 'site' ? '/' : '/health',\n\t\t\t}),\n\t\t),\n\t\t...[...fileServers.values()].map(({ route }) => route),\n\t\t...(logs?.host && logs.public\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: LOGS_SERVICE,\n\t\t\t\t\t\thost: logs.host,\n\t\t\t\t\t\tupstreams: [{ service: LOGS_SERVICE, port: LOGS_PORT }],\n\t\t\t\t\t\tstreaming: true,\n\t\t\t\t\t\tallow: logs.public.allow,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t];\n\tconst dockerfiles: Record<string, string> = {};\n\tfor (const app of apps) {\n\t\tif (!app.build) continue;\n\t\t// The same Dockerfile `gkm docker` writes: the image is built inside\n\t\t// Docker from a pruned slice of the build root, and nothing is built\n\t\t// here first.\n\t\tdockerfiles[app.build.dockerfile] = appDockerfile(\n\t\t\tapp.name,\n\t\t\tworkspace.apps[app.name]!,\n\t\t\t{\n\t\t\t\tlayout,\n\t\t\t\tworkspaceRoot: workspace.root,\n\t\t\t\tapps: workspace.apps,\n\t\t\t\tmanifest,\n\t\t\t\tcache: STACK_CACHE,\n\t\t\t\t// A site's clients trace at this stage's sample rate.\n\t\t\t\tworkspace,\n\t\t\t\tstage,\n\t\t\t},\n\t\t);\n\t}\n\tfor (const worker of workers) {\n\t\tif (!worker.build) continue;\n\t\tdockerfiles[worker.build.dockerfile] = workerDockerfile(\n\t\t\t{ id: worker.id, name: worker.name, app: worker.host },\n\t\t\tworkspace.apps[worker.host]!,\n\t\t\t{ layout, workspaceRoot: workspace.root, cache: STACK_CACHE },\n\t\t);\n\t}\n\n\treturn {\n\t\tproject,\n\t\tstage,\n\t\tlocal,\n\t\tapps,\n\t\tworkers,\n\t\tinfra,\n\t\tcompose,\n\t\tproxy,\n\t\troutes,\n\t\t...(proxy === 'caddy'\n\t\t\t? { caddyfile: edgeCaddyfile(routes, { tls }) }\n\t\t\t: {\n\t\t\t\t\ttraefik: traefikDynamicFile(routes, {\n\t\t\t\t\t\tproject,\n\t\t\t\t\t\ttls: deployedTls,\n\t\t\t\t\t}),\n\t\t\t\t}),\n\t\t...(certificate ? { tls: certificate } : {}),\n\t\tdockerfiles,\n\t\tplan,\n\t\tcredential,\n\t\t...(storage ? { storage } : {}),\n\t\tdevServices: local ? [] : devServicesUsed(services),\n\t\t...(logs ? { logs } : {}),\n\t\t...(telemetry ? { telemetry } : {}),\n\t\t...(redis ? { redis } : {}),\n\t};\n}\n\n/** The stack's MinIO's value for one of the S3 client's keys. */\nfunction storageValue(\n\tkey: string,\n\tstorage: StackStorage | undefined,\n): string | undefined {\n\tif (!storage) return undefined;\n\tif (key === 'AWS_ACCESS_KEY_ID') return storage.user;\n\tif (key === 'AWS_SECRET_ACCESS_KEY') return storage.password;\n\tif (key === 'AWS_REGION') return MINIO_REGION;\n\treturn undefined;\n}\n\n/** Which bucket an S3 credential key belongs to — the first one declared. */\nfunction storageOwner(key: string, plan: Plan): PlannedResource | undefined {\n\tif (!(STORAGE_KEYS as readonly string[]).includes(key)) return undefined;\n\treturn plan.resources.find((r) => r.kind === 'objects');\n}\n\n/** The apps a stack runs: every API and every site, at its stage address. */\nfunction stackApps(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n\tplan: Plan,\n\toptions: {\n\t\tlocal: boolean;\n\t\tstage: string;\n\t\thttps: number;\n\t\tidentity: DeployIdentity;\n\t\timages: StackInput['images'];\n\t},\n): StackApp[] {\n\tconst apps: StackApp[] = [];\n\tconst resources = new Map(plan.resources.map((r) => [r.id, r]));\n\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (declaration.kind !== 'rest-api' && declaration.kind !== 'site')\n\t\t\tcontinue;\n\n\t\tconst name = appKey(id);\n\t\tconst app = workspace.apps[name];\n\t\tconst resource = resources.get(id);\n\t\tif (!app || !resource || !app.port) continue;\n\n\t\tconst host = hostOf(name, app, workspace, resource, options);\n\t\tconst site = declaration.kind === 'site';\n\t\tconst tag = site\n\t\t\t? siteTag(options.images.tag, options.stage)\n\t\t\t: options.images.tag;\n\t\tconst ref = validateImageRef(\n\t\t\timageRef(options.identity, name, options.images.registry, tag),\n\t\t);\n\n\t\tapps.push({\n\t\t\tapp: name,\n\t\t\tname,\n\t\t\tid,\n\t\t\tkind: declaration.kind,\n\t\t\tpath: app.path,\n\t\t\tport: app.port,\n\t\t\thost,\n\t\t\turl: `https://${host}${options.local && options.https !== 443 ? `:${options.https}` : ''}`,\n\t\t\tref,\n\t\t\ttag,\n\t\t\t...(options.images.mode === 'build'\n\t\t\t\t? {\n\t\t\t\t\t\tbuild: {\n\t\t\t\t\t\t\tdockerfile: `${stackDir(options.stage)}/Dockerfile.${name}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t});\n\t}\n\n\treturn apps.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/** Each worker's image, and where it is built from. */\nfunction stackWorkers(\n\tunits: readonly WorkerUnit[],\n\toptions: {\n\t\tstage: string;\n\t\tidentity: DeployIdentity;\n\t\timages: StackInput['images'];\n\t},\n): StackWorker[] {\n\treturn units.map((unit) => ({\n\t\tapp: unit.name,\n\t\tname: unit.name,\n\t\tid: unit.id,\n\t\thost: unit.app,\n\t\tport: WORKER_PORT,\n\t\tref: validateImageRef(\n\t\t\timageRef(\n\t\t\t\toptions.identity,\n\t\t\t\tunit.name,\n\t\t\t\toptions.images.registry,\n\t\t\t\toptions.images.tag,\n\t\t\t),\n\t\t),\n\t\ttag: options.images.tag,\n\t\t...(options.images.mode === 'build'\n\t\t\t? {\n\t\t\t\t\tbuild: {\n\t\t\t\t\t\tdockerfile: `${stackDir(options.stage)}/Dockerfile.${unit.name}`,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t}));\n}\n\n/**\n * The host an app answers on for a stage: the `gkm dev` edge's\n * `*.localhost` names for the local stage, and the stage's domain — by the\n * same rule a deploy follows — for any other.\n */\nfunction hostOf(\n\tname: string,\n\tapp: NormalizedAppConfig,\n\tworkspace: NormalizedWorkspace,\n\tresource: PlannedResource,\n\toptions: { local: boolean; stage: string },\n): string {\n\tif (options.local) return hostFor(resource, workspace.name);\n\n\treturn resolveHost(\n\t\tname,\n\t\tapp,\n\t\toptions.stage,\n\t\tworkspace.domains,\n\t\tisMainFrontendApp(name, app, workspace.apps),\n\t);\n}\n\n/** The compose document: the infrastructure, the apps, and the edge. */\nfunction stackFile(options: {\n\tproject: string;\n\tplan: Plan;\n\tinfra: readonly string[];\n\tapps: readonly StackApp[];\n\tworkers: readonly StackWorker[];\n\tcredentials: ContainerCredentials;\n\tstorage?: StackStorage;\n\tlogs?: StackLogs;\n\tredis?: StackRedis;\n\thttps: number;\n\thttp: number;\n\tcomposeDir: string;\n\tbuildRoot: string;\n\tworkspaceRoot: string;\n\t/** Whether services read env files — not in a stack that only builds. */\n\tenvFiles: boolean;\n\tproxy: ComposeProxy;\n\t/** Behind the shared edge: each service it routes to, and its alias. */\n\tedge: ReadonlyMap<string, string>;\n\t/** Whether the stage has its own certificate, which Caddy mounts. */\n\ttls: boolean;\n}): StackFile {\n\tconst { project, plan, apps } = options;\n\t// What reconcile defines; OpenObserve is the stack's own.\n\tconst infra = options.infra.filter((name) => name !== LOGS_SERVICE);\n\n\t// Reconcile's definitions — images, health checks, volumes — with nothing\n\t// published to the host. Ports are allocation's business there; here the\n\t// edge is the only way in.\n\tconst derived = composeFor(\n\t\t{ ...plan, containers: [...infra] },\n\t\t{\n\t\t\tproject,\n\t\t\tports: Object.fromEntries(\n\t\t\t\tportKeys(infra, plan.fakes).map((key) => [key, 0]),\n\t\t\t),\n\t\t\tcredentials: options.credentials,\n\t\t},\n\t);\n\n\tconst services: Record<string, StackService> = {};\n\tconst volumes: Record<string, Record<string, never>> = {\n\t\t...derived.volumes,\n\t\t...(options.proxy === 'caddy'\n\t\t\t? { 'caddy-data': {}, 'caddy-config': {} }\n\t\t\t: {}),\n\t\t...(options.logs ? { 'openobserve-data': {} } : {}),\n\t};\n\n\tfor (const name of infra) {\n\t\tconst {\n\t\t\tports: _ports,\n\t\t\tdepends_on,\n\t\t\tprofiles: _profiles,\n\t\t\t...service\n\t\t} = derived.services[name]!;\n\t\tservices[name] = {\n\t\t\t...service,\n\t\t\t...(depends_on?.length\n\t\t\t\t? {\n\t\t\t\t\t\tdepends_on: Object.fromEntries(\n\t\t\t\t\t\t\tdepends_on.map((dep) => [dep, HEALTHY]),\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\tconst postgres = services.postgres;\n\tif (postgres) {\n\t\t// Loopback only, on a port Docker picks: gkm creates the databases and\n\t\t// roles and runs the migrations from this machine, and nothing else\n\t\t// should reach the database from outside the stack.\n\t\tpostgres.ports = ['127.0.0.1::5432'];\n\t}\n\n\tconst minio = services.minio;\n\tif (minio && options.storage) {\n\t\tminio.environment = {\n\t\t\t...minio.environment,\n\t\t\tMINIO_ROOT_USER: options.storage.user,\n\t\t\tMINIO_ROOT_PASSWORD: options.storage.password,\n\t\t};\n\t\t// Loopback only, like Postgres: gkm creates the buckets and their\n\t\t// policies from this machine. The apps reach it on the network, and a\n\t\t// browser through a file server's host on the edge.\n\t\tminio.ports = ['127.0.0.1::9000'];\n\t}\n\n\t// Its own definition rather than reconcile's: `gkm dev`'s Redis is an\n\t// open, unbounded scratch store; this one is a production cache.\n\tif (services[REDIS_SERVICE] && options.redis) {\n\t\tservices[REDIS_SERVICE] = redisService(options.redis);\n\t}\n\n\t// Started with the infrastructure, but nothing waits on it: an app whose\n\t// telemetry cannot be delivered still serves.\n\tif (options.logs) services[LOGS_SERVICE] = logsService(options.logs);\n\n\tfor (const app of apps) {\n\t\tservices[app.name] = {\n\t\t\timage: app.ref,\n\t\t\t...(app.build\n\t\t\t\t? {\n\t\t\t\t\t\tbuild: {\n\t\t\t\t\t\t\t...composeBuildPaths({\n\t\t\t\t\t\t\t\tcomposeDir: options.composeDir,\n\t\t\t\t\t\t\t\tbuildRoot: options.buildRoot,\n\t\t\t\t\t\t\t\tworkspaceRoot: options.workspaceRoot,\n\t\t\t\t\t\t\t\tdockerfile: app.build.dockerfile,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t...(app.build.args ? { args: app.build.args } : {}),\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\trestart: 'unless-stopped',\n\t\t\t// A site is a bundle and reads nothing at runtime; a backend reads\n\t\t\t// exactly the keys in its own file.\n\t\t\t...(app.kind === 'rest-api'\n\t\t\t\t? {\n\t\t\t\t\t\tenv_file: [{ path: `./${app.name}.env`, format: 'raw' as const }],\n\t\t\t\t\t\t...(infra.length\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tdepends_on: Object.fromEntries(\n\t\t\t\t\t\t\t\t\t\tinfra.map((name) => [name, HEALTHY]),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\thealthcheck: probe(app.port, app.kind === 'site' ? '/' : '/health'),\n\t\t};\n\t}\n\n\t// A worker: no route, no published port — its health check is Docker's.\n\tfor (const worker of options.workers) {\n\t\tservices[worker.name] = {\n\t\t\timage: worker.ref,\n\t\t\t...(worker.build\n\t\t\t\t? {\n\t\t\t\t\t\tbuild: composeBuildPaths({\n\t\t\t\t\t\t\tcomposeDir: options.composeDir,\n\t\t\t\t\t\t\tbuildRoot: options.buildRoot,\n\t\t\t\t\t\t\tworkspaceRoot: options.workspaceRoot,\n\t\t\t\t\t\t\tdockerfile: worker.build.dockerfile,\n\t\t\t\t\t\t}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\trestart: 'unless-stopped',\n\t\t\tenv_file: [{ path: `./${worker.name}.env`, format: 'raw' as const }],\n\t\t\t...(infra.length\n\t\t\t\t? {\n\t\t\t\t\t\tdepends_on: Object.fromEntries(\n\t\t\t\t\t\t\tinfra.map((name) => [name, HEALTHY]),\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\thealthcheck: probe(worker.port, '/health'),\n\t\t};\n\t}\n\n\tif (options.proxy === 'caddy') {\n\t\tservices.caddy = {\n\t\t\timage: DEFAULT_IMAGES.caddy!,\n\t\t\trestart: 'unless-stopped',\n\t\t\tports: [`${options.https}:443`, `${options.http}:80`],\n\t\t\tvolumes: [\n\t\t\t\t'./Caddyfile:/etc/caddy/Caddyfile:ro',\n\t\t\t\t'caddy-data:/data',\n\t\t\t\t'caddy-config:/config',\n\t\t\t\t// The stage's own certificate, copied beside the Caddyfile.\n\t\t\t\t...(options.tls ? [`./tls:${CADDY_TLS_DIR}:ro`] : []),\n\t\t\t],\n\t\t\tdepends_on: Object.fromEntries(apps.map((app) => [app.name, HEALTHY])),\n\t\t\thealthcheck: {\n\t\t\t\ttest: ['CMD', 'caddy', 'version'],\n\t\t\t\tinterval: '10s',\n\t\t\t\ttimeout: '5s',\n\t\t\t\tretries: 5,\n\t\t\t},\n\t\t};\n\t}\n\n\t// Behind the shared edge: each service it routes to joins its network,\n\t// under the alias the edge reaches it by — and keeps the stack's own,\n\t// with the same alias, which is how the stack's services reach it too.\n\t// Everything else — the databases, Redis, the workers — stays off it.\n\tfor (const [service, alias] of options.edge) {\n\t\tconst definition = services[service];\n\t\tif (!definition) continue;\n\t\tdefinition.networks = {\n\t\t\tdefault: { aliases: [alias] },\n\t\t\tedge: { aliases: [alias] },\n\t\t};\n\t}\n\n\tfor (const service of Object.values(services)) {\n\t\tif (!options.envFiles) delete service.env_file;\n\t\tservice.logging = {\n\t\t\tdriver: LOG_ROTATION.driver,\n\t\t\toptions: { ...LOG_ROTATION.options },\n\t\t};\n\t}\n\n\treturn {\n\t\tname: project,\n\t\tservices,\n\t\tvolumes,\n\t\t...(options.edge.size > 0\n\t\t\t? { networks: { edge: { name: EDGE_NETWORK, external: true as const } } }\n\t\t\t: {}),\n\t};\n}\n\n/**\n * A build without a layout: the workspace is the build root, with pnpm.\n * What a caller that never looked at the filesystem gets.\n */\nfunction defaultLayout(root: string): ImageLayout {\n\treturn {\n\t\tbuildRoot: root,\n\t\tgkmRoot: '.',\n\t\ttools: {\n\t\t\tpackageManager: 'pnpm',\n\t\t\tturboVersion: TURBO_VERSION,\n\t\t\tmonorepo: true,\n\t\t},\n\t\tgkmPaths: ['gkm.config.*'],\n\t};\n}\n\n/** Render an app's env file: one `KEY=value` per line, read raw by compose. */\nexport function envFile(env: Readonly<Record<string, string>>): string {\n\treturn `${Object.entries(env)\n\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t.map(([key, value]) => `${key}=${value}`)\n\t\t.join('\\n')}\\n`;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"stack-Clzi40EE.cjs","names":["createHash","CLIENT_IP_HEADER","yaml","stringify","GkmError","CLIENT_IP_HEADER","GkmError","localRolePassword","postgresSuperuser","LOCAL_RABBITMQ_USER","EMULATOR_ACCESS_KEY_ID","proxyFor","certificateFor","planFor","provisionOrder","DEFAULT_EVENTS","STACK_CACHE","workerUnits","provideKey","resolveStageTelemetry","usesTelemetry","stackLogs","externalServices","stageServiceDeclarations","stageProviderNotes","stackRedis","LOGS_SERVICE","bucketPolicies","hostFor","NoDomainForStage","appKey","networkEnv","isReservedStageKey","suppliedOnly","telemetryEnv","otlpTelemetryEnv","isTelemetryKey","publicEnvFor","appEnvKeys","scopeTelemetryEnv","telemetryOf","workerEnvKeys","join","LOGS_PORT","appDockerfile","workerDockerfile","devServicesUsed","validateImageRef","imageRef","WORKER_PORT","resolveHost","isMainFrontendApp","composeFor","portKeys","REDIS_SERVICE","redisService","logsService","composeBuildPaths","DEFAULT_IMAGES","TURBO_VERSION"],"sources":["../src/compose/traefik.ts","../src/compose/images.ts","../src/compose/caddyfile.ts","../src/compose/stack.ts"],"sourcesContent":["/**\n * The server's shared edge, rendered for Traefik: what the edge itself runs\n * with, and what one stack registers with it.\n *\n * One Traefik per server owns 80 and 443, the ACME state and the redirect to\n * HTTPS. It is configured through its file provider alone: each stack writes\n * one file — its routers, services and middlewares, every name prefixed with\n * its compose project — into a directory the edge watches, and removes it\n * when it stops. There is no Docker socket: the edge never learns what runs\n * on the server except from those files.\n *\n * The edge and the stacks meet on one external Docker network. Only a\n * stack's public services join it, each under an alias prefixed with its\n * project (`<project>-api`), because the network is shared: a bare service\n * name there would be answered by every stack's `api`.\n *\n * Pure: the documents are data, and the edge's side effects are `edge.ts`'s.\n */\n\nimport { createHash } from 'node:crypto';\nimport { CLIENT_IP_HEADER } from '@geekmidas/manifest';\nimport { stringify } from 'yaml';\nimport type { EdgeRoute, EdgeTls, Upstream } from './routes.js';\n\n/**\n * The image, pinned: the edge is every stack's way in, so a new version is a\n * change to this line, not whatever `latest` is on the day a server starts.\n */\nexport const TRAEFIK_IMAGE = 'traefik:v3.7.13';\n\n/** The edge's compose project, one per server. */\nexport const EDGE_PROJECT = 'gkm-edge';\n\n/** The external network the edge and every stack's public services share. */\nexport const EDGE_NETWORK = 'gkm-edge';\n\n/** The edge's compose service. */\nexport const EDGE_SERVICE = 'traefik';\n\n/** Inside the edge's container: what it watches, and where certificates are. */\nexport const EDGE_DYNAMIC_DIR = '/etc/gkm-edge/dynamic';\nexport const EDGE_CERTS_DIR = '/etc/gkm-edge/certs';\n\n/** The certificate resolver every deployed route without its own certificate uses. */\nexport const ACME_RESOLVER = 'letsencrypt';\n\n/** The internal entrypoint the edge's health check asks; never published. */\nconst PING_PORT = 8082;\n\n/** A stack's service, as the edge — and the stack itself — reach it. */\nexport function edgeAlias(project: string, service: string): string {\n\treturn `${project}-${service}`;\n}\n\n/** A stack's certificate, as the edge's container reads it. */\nexport function edgeCertificatePaths(project: string): {\n\tcertFile: string;\n\tkeyFile: string;\n} {\n\treturn {\n\t\tcertFile: `${EDGE_CERTS_DIR}/${project}.crt`,\n\t\tkeyFile: `${EDGE_CERTS_DIR}/${project}.key`,\n\t};\n}\n\n/** The edge's ports on the host. */\nexport interface EdgePorts {\n\thttps: number;\n\thttp: number;\n}\n\n/**\n * The edge's static configuration: the entrypoints, the redirect, ACME, and\n * the directory it watches. No API and no dashboard — nothing about the edge\n * is reachable but the routes stacks register.\n */\nexport function traefikStatic(ports: EdgePorts): Record<string, unknown> {\n\treturn {\n\t\tglobal: { checkNewVersion: false, sendAnonymousUsage: false },\n\t\tentryPoints: {\n\t\t\tweb: {\n\t\t\t\taddress: ':80',\n\t\t\t\thttp: {\n\t\t\t\t\t// Every plain-HTTP request is sent to HTTPS — but an ACME\n\t\t\t\t\t// HTTP-01 challenge, which Traefik answers before the redirect.\n\t\t\t\t\tredirections: {\n\t\t\t\t\t\tentryPoint: {\n\t\t\t\t\t\t\t// The published port when it is not 443, so the redirect\n\t\t\t\t\t\t\t// lands where the edge actually listens.\n\t\t\t\t\t\t\tto: ports.https === 443 ? 'websecure' : `:${ports.https}`,\n\t\t\t\t\t\t\tscheme: 'https',\n\t\t\t\t\t\t\tpermanent: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t\twebsecure: { address: ':443' },\n\t\t\tping: { address: `:${PING_PORT}` },\n\t\t},\n\t\tping: { entryPoint: 'ping' },\n\t\tproviders: {\n\t\t\tfile: { directory: EDGE_DYNAMIC_DIR, watch: true },\n\t\t},\n\t\tcertificatesResolvers: {\n\t\t\t[ACME_RESOLVER]: {\n\t\t\t\tacme: {\n\t\t\t\t\tstorage: '/acme/acme.json',\n\t\t\t\t\thttpChallenge: { entryPoint: 'web' },\n\t\t\t\t},\n\t\t\t},\n\t\t},\n\t\tlog: { level: 'INFO' },\n\t};\n}\n\n/** The edge's compose document. */\nexport function edgeCompose(options: {\n\t/** The edge's directory on the host. */\n\tdir: string;\n\tports: EdgePorts;\n\t/** The static configuration, as written — its hash recreates the edge. */\n\tstaticConfig: string;\n\tlogging: { driver: string; options: Record<string, string> };\n}): Record<string, unknown> {\n\tconst { dir, ports } = options;\n\treturn {\n\t\tname: EDGE_PROJECT,\n\t\tservices: {\n\t\t\t[EDGE_SERVICE]: {\n\t\t\t\timage: TRAEFIK_IMAGE,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: [`${ports.https}:443`, `${ports.http}:80`],\n\t\t\t\tvolumes: [\n\t\t\t\t\t`${dir}/traefik.yml:/etc/traefik/traefik.yml:ro`,\n\t\t\t\t\t// Directories, not files: a stack's file is replaced by a\n\t\t\t\t\t// rename, which a file mounted on its own would never see.\n\t\t\t\t\t`${dir}/dynamic:${EDGE_DYNAMIC_DIR}:ro`,\n\t\t\t\t\t`${dir}/certs:${EDGE_CERTS_DIR}:ro`,\n\t\t\t\t\t'acme:/acme',\n\t\t\t\t],\n\t\t\t\t// Static configuration is read at start: a change to it is a\n\t\t\t\t// change to the service, so `up` recreates the edge.\n\t\t\t\tlabels: {\n\t\t\t\t\t'dev.geekmidas.edge.config': createHash('sha256')\n\t\t\t\t\t\t.update(options.staticConfig)\n\t\t\t\t\t\t.digest('hex')\n\t\t\t\t\t\t.slice(0, 16),\n\t\t\t\t},\n\t\t\t\tnetworks: ['edge'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD', 'traefik', 'healthcheck', '--ping'],\n\t\t\t\t\tinterval: '10s',\n\t\t\t\t\ttimeout: '5s',\n\t\t\t\t\tretries: 5,\n\t\t\t\t},\n\t\t\t\tlogging: options.logging,\n\t\t\t},\n\t\t},\n\t\tvolumes: { acme: {} },\n\t\tnetworks: { edge: { name: EDGE_NETWORK, external: true } },\n\t};\n}\n\n/** What a stack registers with the edge — its routes, and its certificate. */\nexport interface TraefikStackOptions {\n\tproject: string;\n\t/** ACME, or the stage's own certificate. Never the local CA: that is Caddy's. */\n\ttls: Exclude<EdgeTls, { kind: 'internal' }>;\n}\n\n/** A stack's dynamic configuration, as an object. */\nexport function traefikDynamic(\n\troutes: readonly EdgeRoute[],\n\toptions: TraefikStackOptions,\n): Record<string, unknown> {\n\tconst { project, tls } = options;\n\tconst name = (route: EdgeRoute, suffix?: string) =>\n\t\t`${project}-${route.name}${suffix ? `-${suffix}` : ''}`;\n\tconst url = ({ service, port }: Upstream) =>\n\t\t`http://${edgeAlias(project, service)}:${port}`;\n\n\tconst routers: Record<string, unknown> = {};\n\tconst services: Record<string, unknown> = {};\n\tconst middlewares: Record<string, unknown> = {};\n\n\t// gkm's own header, which only a service of this stack may send — the\n\t// auth server rate-limits by the client address it carries — removed from\n\t// every request that arrives from outside. An empty value is Traefik's\n\t// way of removing a request header.\n\tconst strip = `${project}-strip-gkm-headers`;\n\tif (routes.length > 0) {\n\t\tmiddlewares[strip] = {\n\t\t\theaders: { customRequestHeaders: { [CLIENT_IP_HEADER]: '' } },\n\t\t};\n\t}\n\n\tfor (const route of routes) {\n\t\tconst chain: string[] = [strip];\n\t\tif (route.allow) {\n\t\t\t// The peer Traefik sees — never X-Forwarded-For, which a client\n\t\t\t// could set. Everything else gets 403.\n\t\t\tmiddlewares[name(route, 'allow')] = {\n\t\t\t\tipAllowList: { sourceRange: [...route.allow] },\n\t\t\t};\n\t\t\tchain.push(name(route, 'allow'));\n\t\t}\n\t\tif (route.prefix) {\n\t\t\tmiddlewares[name(route, 'prefix')] = {\n\t\t\t\taddPrefix: { prefix: route.prefix },\n\t\t\t};\n\t\t\tchain.push(name(route, 'prefix'));\n\t\t}\n\n\t\trouters[name(route)] = {\n\t\t\trule: `Host(\\`${route.host}\\`)`,\n\t\t\tentryPoints: ['websecure'],\n\t\t\tservice: name(route),\n\t\t\tmiddlewares: chain,\n\t\t\ttls: tls.kind === 'acme' ? { certResolver: ACME_RESOLVER } : {},\n\t\t};\n\n\t\tservices[name(route)] = {\n\t\t\tloadBalancer: {\n\t\t\t\tservers: route.upstreams.map((upstream) => ({ url: url(upstream) })),\n\t\t\t\t// A bucket is routed and signed on the Host header, so MinIO is\n\t\t\t\t// sent its own; an app is sent the one the caller used, which is\n\t\t\t\t// what it builds redirects, cookies and links on.\n\t\t\t\t...(route.prefix ? { passHostHeader: false } : {}),\n\t\t\t\t...(route.streaming\n\t\t\t\t\t? {\n\t\t\t\t\t\t\t// Never buffer: each chunk of a streamed response is\n\t\t\t\t\t\t\t// flushed to the client as it is written.\n\t\t\t\t\t\t\tresponseForwarding: { flushInterval: '-1ms' },\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t\t...(route.health\n\t\t\t\t\t? {\n\t\t\t\t\t\t\thealthCheck: {\n\t\t\t\t\t\t\t\tpath: route.health,\n\t\t\t\t\t\t\t\tinterval: '5s',\n\t\t\t\t\t\t\t\ttimeout: '3s',\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\thttp: {\n\t\t\trouters,\n\t\t\tservices,\n\t\t\t...(Object.keys(middlewares).length > 0 ? { middlewares } : {}),\n\t\t},\n\t\t...(tls.kind === 'files'\n\t\t\t? {\n\t\t\t\t\ttls: {\n\t\t\t\t\t\tcertificates: [{ certFile: tls.certFile, keyFile: tls.keyFile }],\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t};\n}\n\n/** A stack's dynamic configuration file, as written to the edge's directory. */\nexport function traefikDynamicFile(\n\troutes: readonly EdgeRoute[],\n\toptions: TraefikStackOptions,\n): string {\n\treturn `# Generated by gkm compose for ${options.project} — do not edit.\n# The shared edge (${EDGE_PROJECT}) reads every file in this directory; this\n# one is removed by gkm compose --down.\n${yaml(traefikDynamic(routes, options))}`;\n}\n\n/** The edge's static configuration file. */\nexport function traefikStaticFile(ports: EdgePorts): string {\n\treturn `# Generated by gkm compose — the server's shared edge. Do not edit.\n${yaml(traefikStatic(ports))}`;\n}\n\n/** The edge's compose file. */\nexport function edgeComposeFile(\n\toptions: Parameters<typeof edgeCompose>[0],\n): string {\n\treturn `# Generated by gkm compose — the server's shared edge. Do not edit.\n#\n# Started by the first stack that runs with proxy: 'traefik', and left\n# running by gkm compose --down. Stop it, once nothing uses it, with:\n#\n# docker compose -p ${EDGE_PROJECT} -f ${options.dir}/docker-compose.yml down\n${yaml(edgeCompose(options))}`;\n}\n\nfunction yaml(value: unknown): string {\n\treturn stringify(value, { lineWidth: 0, aliasDuplicateObjects: false });\n}\n","/**\n * Which image each app runs — and, for a tag, whether every one exists.\n *\n * A tag is a release's identity: `--tag=v1.4.0` means the images CI pushed as\n * v1.4.0, and nothing else. So a stack is never half rolled out: every app's\n * image is asked of the registry before anything is pulled or the running\n * stack is touched, and one missing image stops the whole run naming every\n * one that is missing. Nor is a missing image quietly built instead — a build\n * of whatever is checked out here is not the release the tag names.\n */\n\nimport { GkmError } from '../errors';\nimport type { ComposeDocker } from './docker';\n\n/** One app's image, as the stack runs it. */\nexport interface AppImage {\n\tapp: string;\n\tref: string;\n\ttag: string;\n}\n\n/**\n * The tag a site's image carries for a release.\n *\n * A site's public URLs are inlined when it is built, so one bundle serves one\n * stage: `v1.4.0-production` and `v1.4.0-staging` are two images of the same\n * commit. A backend reads its URLs at runtime, so one image serves every stage\n * and its tag is the release's alone.\n */\nexport function siteTag(tag: string, stage: string): string {\n\treturn `${tag}-${stage}`;\n}\n\n/** Images a tag names that the registry does not have. */\nexport class ImageTagNotFound extends GkmError {\n\tconstructor(\n\t\treadonly tag: string,\n\t\treadonly refs: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`The registry has no image for ${refs.length === 1 ? 'this app' : `${refs.length} apps`} at '${tag}':\\n` +\n\t\t\t\trefs.map((ref) => ` - ${ref}`).join('\\n') +\n\t\t\t\t`\\nNothing was pulled or started. Push '${tag}' for every app from CI, ` +\n\t\t\t\t`pick a tag that exists, or run without --tag to build from this checkout.`,\n\t\t);\n\t\tthis.name = 'ImageTagNotFound';\n\t}\n}\n\n/** The registry did not answer, or did not let this machine in. */\nexport class RegistryUnreachable extends GkmError {\n\tconstructor(\n\t\treadonly ref: string,\n\t\treadonly detail: string,\n\t) {\n\t\tsuper(\n\t\t\t`Could not ask the registry about ${ref}: ${detail || 'no answer'}\\n` +\n\t\t\t\t`Nothing was pulled or started. Check the registry is reachable and ` +\n\t\t\t\t`that this machine is logged in to it (\\`docker login <registry>\\`).`,\n\t\t);\n\t\tthis.name = 'RegistryUnreachable';\n\t}\n}\n\n/**\n * Assert every image exists, before anything else happens.\n *\n * Asked all at once and reported all at once: a release missing two images is\n * one problem with two names, and finding the second only after fixing the\n * first is a slower way to learn the same thing.\n *\n * @throws {RegistryUnreachable} when the registry cannot say\n * @throws {ImageTagNotFound} listing every missing image\n */\nexport async function assertImagesExist(\n\tdocker: Pick<ComposeDocker, 'lookup'>,\n\ttag: string,\n\timages: readonly AppImage[],\n): Promise<void> {\n\tconst lookups = await Promise.all(\n\t\timages.map((image) => docker.lookup(image.ref)),\n\t);\n\n\tconst unreachable = lookups.find((lookup) => lookup.status === 'unreachable');\n\tif (unreachable?.status === 'unreachable') {\n\t\tthrow new RegistryUnreachable(unreachable.ref, unreachable.detail);\n\t}\n\n\tconst missing = lookups\n\t\t.filter((lookup) => lookup.status === 'missing')\n\t\t.map((lookup) => lookup.ref);\n\tif (missing.length > 0) throw new ImageTagNotFound(tag, missing);\n}\n\n/**\n * A push or a pull with no `deploy.registry` to push to or pull from.\n *\n * Without one an image is named `<namespace>/<project>-<app>:<tag>`, which\n * Docker resolves to Docker Hub — so a release would be pushed to, or pulled\n * from, somebody else's account. It is never a fallback.\n */\nexport class RegistryRequired extends GkmError {\n\tconstructor(\n\t\t/** What needed the registry: `--push`, or a pull (`--tag`/`--pull`). */\n\t\treadonly operation: 'push' | 'pull',\n\t\t/** The ref an image would have had: a Docker Hub name. */\n\t\treadonly ref: string,\n\t) {\n\t\tsuper(\n\t\t\t`${operation === 'push' ? 'Pushing' : 'Pulling'} images needs a registry, and gkm.config.ts sets no deploy.registry: ` +\n\t\t\t\t`'${ref}' would be ${operation === 'push' ? 'pushed to' : 'pulled from'} Docker Hub. ` +\n\t\t\t\t`Set deploy.registry (e.g. registry: 'ghcr.io/acme') in gkm.config.ts.` +\n\t\t\t\t(operation === 'pull'\n\t\t\t\t\t? ' To run images built here instead, pass --build.'\n\t\t\t\t\t: ''),\n\t\t);\n\t\tthis.name = 'RegistryRequired';\n\t}\n}\n\n/**\n * What a push reports, and what a pull can pin to: each app's image as\n * `<ref>@sha256:…` — the tag it was pushed as, and the exact image, whatever\n * that tag is later moved to.\n */\nexport type ImageDigests = Record<string, string>;\n\n/** An image pinned to its digest: `<ref>@sha256:…`. */\nexport function pinnedRef(ref: string, digest: string): string {\n\treturn `${ref.split('@')[0]}@${digest}`;\n}\n\n/** A digests file with no entry for an app the stack runs. */\nexport class ImageDigestMissing extends GkmError {\n\tconstructor(\n\t\treadonly file: string,\n\t\treadonly apps: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${file} pins no digest for ${apps.join(', ')}. Use the file the ` +\n\t\t\t\t`same release's \\`gkm compose --build --push --digests-file\\` wrote, ` +\n\t\t\t\t`or run without --digests-file to pull by tag.`,\n\t\t);\n\t\tthis.name = 'ImageDigestMissing';\n\t}\n}\n\n/** A digests file entry that is not this app's image. */\nexport class ImageDigestMismatch extends GkmError {\n\tconstructor(\n\t\treadonly app: string,\n\t\treadonly expected: string,\n\t\treadonly found: string,\n\t) {\n\t\tsuper(\n\t\t\t`The digests file pins '${app}' to ${found}, but this release runs ` +\n\t\t\t\t`${expected}. It was written for another registry, project, stage ` +\n\t\t\t\t`or tag; use the file this stage's push of this tag wrote.`,\n\t\t);\n\t\tthis.name = 'ImageDigestMismatch';\n\t}\n}\n\n/** A digests file that is not a JSON object of app to pinned ref. */\nexport class ImageDigestsInvalid extends GkmError {\n\tconstructor(\n\t\treadonly file: string,\n\t\treadonly detail: string,\n\t) {\n\t\tsuper(\n\t\t\t`${file} is not a digests file (${detail}). It should be the JSON ` +\n\t\t\t\t`object \\`gkm compose --build --push --digests-file\\` writes: ` +\n\t\t\t\t`{ \"api\": \"<registry>/<image>:<tag>@sha256:…\" }.`,\n\t\t);\n\t\tthis.name = 'ImageDigestsInvalid';\n\t}\n}\n\n/** Parse a digests file's content, checking its shape. */\nexport function parseDigests(file: string, content: string): ImageDigests {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(content);\n\t} catch (error) {\n\t\tthrow new ImageDigestsInvalid(\n\t\t\tfile,\n\t\t\terror instanceof Error ? error.message : String(error),\n\t\t);\n\t}\n\tif (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\tthrow new ImageDigestsInvalid(file, 'not a JSON object');\n\t}\n\tfor (const [app, value] of Object.entries(parsed)) {\n\t\tif (typeof value !== 'string' || !/@sha256:[0-9a-f]{64}$/.test(value)) {\n\t\t\tthrow new ImageDigestsInvalid(\n\t\t\t\tfile,\n\t\t\t\t`'${app}' is not an <image>:<tag>@sha256:… ref`,\n\t\t\t);\n\t\t}\n\t}\n\treturn parsed as ImageDigests;\n}\n\n/**\n * Each image pinned to the digest the file names for it.\n *\n * @throws {ImageDigestMissing} naming every app the file has no entry for\n * @throws {ImageDigestMismatch} when an entry is another image or tag\n */\nexport function pinImages<T extends AppImage>(\n\tfile: string,\n\tdigests: ImageDigests,\n\timages: readonly T[],\n): T[] {\n\tconst missing = images\n\t\t.filter((image) => !digests[image.app])\n\t\t.map((image) => image.app);\n\tif (missing.length > 0) throw new ImageDigestMissing(file, missing);\n\n\treturn images.map((image) => {\n\t\tconst pinned = digests[image.app]!;\n\t\tif (pinned.split('@')[0] !== image.ref) {\n\t\t\tthrow new ImageDigestMismatch(image.app, image.ref, pinned);\n\t\t}\n\t\treturn { ...image, ref: pinned };\n\t});\n}\n","/**\n * The stack's own edge, rendered for Caddy: one site block per route.\n *\n * Deployed, each host is the stage's real domain and Caddy gets it a\n * certificate from Let's Encrypt on first request — the same automatic HTTPS\n * it does anywhere, with nothing to configure but the name — or serves the\n * stage's own certificate (`deploy.compose.tls`). Locally the hosts are\n * `*.localhost` and the certificates come from Caddy's own CA, as the `gkm\n * dev` edge does, so a local stack is `https://` like a deployed one.\n *\n * Pure: the routes are data (`routes.ts`), and rendering them is one string.\n */\n\nimport { CLIENT_IP_HEADER } from '@geekmidas/manifest';\nimport type { EdgeRoute, EdgeTls, Upstream } from './routes.js';\n\n/** Where Caddy reads a stage's own certificate, inside its container. */\nexport const CADDY_TLS_DIR = '/etc/caddy/tls';\n\nexport interface CaddyfileOptions {\n\ttls: EdgeTls;\n\t/** Where ACME sends expiry notices, when someone wants them. */\n\temail?: string;\n}\n\n/** An upstream on the stack's own network: its service name. */\nfunction address({ service, port }: Upstream): string {\n\treturn `${service}:${port}`;\n}\n\n/** The Caddyfile for a stack's edge. */\nexport function edgeCaddyfile(\n\troutes: readonly EdgeRoute[],\n\toptions: CaddyfileOptions,\n): string {\n\tconst { tls } = options;\n\tconst global =\n\t\ttls.kind === 'internal'\n\t\t\t? `{\n\t# No ACME and no public DNS: the CA is Caddy's own and the names resolve\n\t# to this machine.\n\tlocal_certs\n\tauto_https disable_redirects\n}`\n\t\t\t: options.email\n\t\t\t\t? `{\n\temail ${options.email}\n}`\n\t\t\t\t: '';\n\n\tconst certificate =\n\t\ttls.kind === 'internal'\n\t\t\t? `\n\ttls internal\n`\n\t\t\t: tls.kind === 'files'\n\t\t\t\t? `\n\ttls ${tls.certFile} ${tls.keyFile}\n`\n\t\t\t\t: '';\n\n\tconst blocks = routes.map(\n\t\t({\n\t\t\thost,\n\t\t\tupstreams,\n\t\t\tprefix,\n\t\t\tallow,\n\t\t\tstreaming,\n\t\t}) => `https://${host} {${certificate}\n\t# gkm's own header, which only a service of this stack may send: the auth\n\t# server rate-limits by the client address it carries.\n\trequest_header -${CLIENT_IP_HEADER}\n${\n\tallow\n\t\t? `\n\t# Only these addresses — the peer Caddy sees, never a header a client\n\t# could set. Everything else is refused before it reaches the upstream.\n\t@denied not remote_ip ${allow.join(' ')}\n\trespond @denied 403\n`\n\t\t: ''\n}${\n\tprefix\n\t\t? `\n\t# A bucket, served at a host of its own: the bucket is a prefix on MinIO,\n\t# which routes and signs on the Host header — so it is sent the upstream's.\n\treverse_proxy ${upstreams.map(address).join(' ')} {\n\t\trewrite ${prefix}{uri}\n\t\theader_up Host {upstream_hostport}\n\t}`\n\t\t: `\n\t# Host and X-Forwarded-* are passed through as Caddy does by default, so\n\t# an app builds redirects, cookies and links on the address a caller used.\n\treverse_proxy ${upstreams.map(address).join(' ')}${\n\t\tstreaming\n\t\t\t? ` {\n\t\t# Never buffer: a streamed response — server-sent events, a Next.js\n\t\t# RSC payload — reaches the client as each chunk is written.\n\t\tflush_interval -1\n\t}`\n\t\t\t: ''\n\t}`\n}\n}`,\n\t);\n\n\t// No blank line under the header: `caddy fmt` wants the first block there.\n\treturn `${HEADER}${global ? `${global}\\n\\n` : ''}${blocks.join('\\n\\n')}\\n`;\n}\n\nconst HEADER = `# Generated by gkm compose from the construct manifest — do not edit.\n#\n# One host per API and site, each in front of its own container, one per\n# file server in front of the stack's MinIO, and — when its logs are served\n# publicly — one for OpenObserve, to the addresses it allows.\n`;\n","/**\n * The stack a workspace runs as for one stage — pure.\n *\n * Everything `gkm compose` writes is decided here, as data: the compose file,\n * the Caddyfile, one env file per backend, and the image each app runs. The\n * command around it reads the workspace, asks the registry, writes the files\n * and starts Docker; none of those decide anything, so all of it can be\n * asserted without a daemon.\n *\n * The values come from the same derivations every other target uses. The\n * plan is reconcile's `planFor`, the keys an app reads are `appEnvKeys`, and\n * every URL is `envFor`'s — asked twice, once with each app at its public\n * address and once on the compose network, because a browser and a sibling\n * service reach the same API by different names.\n */\n\nimport { join } from 'node:path';\nimport {\n\ttype ConstructManifest,\n\tprovideKey,\n\tprovisionOrder,\n\tpublicEnvFor,\n} from '@geekmidas/manifest';\nimport { type WorkerUnit, workerUnits } from '../build/workers.js';\nimport {\n\ttype DevServiceUse,\n\tdevServicesUsed,\n\ttype ExternalServices,\n\texternalServices,\n\ttype ServiceDeclaration,\n\tstageServiceDeclarations,\n\tsuppliedOnly,\n} from '../deploy/devServices.js';\nimport { isMainFrontendApp, resolveHost } from '../deploy/domain.js';\nimport { type DeployIdentity, imageRef } from '../deploy/identity.js';\nimport { validateImageRef } from '../docker/imageRef.js';\nimport { appDockerfile, workerDockerfile } from '../docker/index.js';\nimport { composeBuildPaths, type ImageLayout } from '../docker/layout.js';\nimport { TURBO_VERSION, WORKER_PORT } from '../docker/templates.js';\nimport { GkmError } from '../errors';\nimport { stageProviderNotes } from '../providers/notes.js';\nimport { appEnvKeys, networkEnv, workerEnvKeys } from '../reconcile/apps.js';\nimport { hostFor } from '../reconcile/caddyfile.js';\nimport { type ComposeService, composeFor } from '../reconcile/compose.js';\nimport { DEFAULT_IMAGES, portKeys } from '../reconcile/containers.js';\nimport { EMULATOR_ACCESS_KEY_ID } from '../reconcile/emulator.js';\nimport { localRolePassword } from '../reconcile/env.js';\nimport {\n\ttype ContainerCredentials,\n\tLOCAL_RABBITMQ_USER,\n\ttype LocalCredentials,\n\tpostgresSuperuser,\n} from '../reconcile/localCredentials.js';\nimport { type Plan, type PlannedResource, planFor } from '../reconcile/plan.js';\nimport { bucketPolicies } from '../reconcile/provision.js';\nimport type { StageSecrets } from '../secrets/types.js';\nimport { NoDomainForStage } from '../target/dokploy/domain.js';\nimport {\n\totlpTelemetryEnv,\n\ttype ResolvedTelemetry,\n\tresolveStageTelemetry,\n\ttelemetryEnv,\n} from '../telemetry/config.js';\nimport {\n\tisTelemetryKey,\n\tscopeTelemetryEnv,\n\ttelemetryOf,\n\tusesTelemetry,\n} from '../telemetry/edges.js';\nimport { DEFAULT_EVENTS } from '../types.js';\nimport { appKey } from '../workspace/derive.js';\nimport type {\n\tComposeTlsConfig,\n\tNormalizedAppConfig,\n\tNormalizedWorkspace,\n} from '../workspace/types.js';\nimport { CADDY_TLS_DIR, edgeCaddyfile } from './caddyfile.js';\nimport { isReservedStageKey } from './dnsConfig.js';\nimport { type AppImage, siteTag } from './images.js';\nimport {\n\tLOGS_SERVICE,\n\tlogsService,\n\ttype StackLogs,\n\tstackLogs,\n} from './logs.js';\nimport { LOGS_PORT } from './logsConfig.js';\nimport { certificateFor, proxyFor } from './proxy.js';\nimport {\n\tREDIS_SERVICE,\n\tredisService,\n\tSTACK_CACHE,\n\ttype StackRedis,\n\tstackRedis,\n} from './redis.js';\nimport type { ComposeProxy, EdgeRoute, EdgeTls } from './routes.js';\nimport {\n\tEDGE_NETWORK,\n\tedgeAlias,\n\tedgeCertificatePaths,\n\ttraefikDynamicFile,\n} from './traefik.js';\n\n/** Where a stack's files are written, relative to the workspace root. */\nexport function stackDir(stage: string): string {\n\treturn `.gkm/compose/${stage}`;\n}\n\n/**\n * The compose project a stage's stack runs as.\n *\n * The deploy identity's scope, so two workspaces with one name in different\n * namespaces never share containers or volumes — and the stage, so a\n * machine can run two stages side by side.\n */\nexport function composeProject(identity: DeployIdentity): string {\n\treturn `${identity.scope}-${identity.stage}`;\n}\n\n/** The ports the edge is published on, and the variables that move them. */\nexport const EDGE_PORT_ENV = {\n\thttps: 'GKM_COMPOSE_HTTPS_PORT',\n\thttp: 'GKM_COMPOSE_HTTP_PORT',\n} as const;\n\n/**\n * The variable that moves the local stage's OpenObserve off its loopback\n * port, 5080 — which the local stage takes from nowhere else, since it\n * ignores `deploy.telemetry`. Another stack, or another tool, may hold 5080.\n */\nexport const LOGS_PORT_ENV = 'GKM_COMPOSE_LOGS_PORT';\n\n/**\n * Containers this target does not run.\n *\n * The AWS emulator never applies: a stack is a server target, whose events\n * are pg-boss. Reconcile's Caddy fronts `gkm dev`'s host processes; the stack\n * brings its own edge.\n *\n * MinIO and Mailpit run on the local stage always, and on a deployed stage\n * only where `--allow-dev-services` allowed them and its secrets configure no\n * real bucket or mail server — see `devServices.ts`.\n */\nconst NOT_RUN: Readonly<Record<string, true>> = {\n\tlocalstack: true,\n\tcaddy: true,\n};\n\n/** The kinds a deployed stage takes from its secrets, or a dev service. */\nconst SERVICE_KINDS: Readonly<Record<string, ServiceDeclaration['kind']>> = {\n\temail: 'email',\n\tobjects: 'objects',\n\t'file-server': 'file-server',\n};\n\n/** The region MinIO is addressed with. It has no regions; the SDK wants one. */\nconst MINIO_REGION = 'us-east-1';\n\n/** The S3 client's credentials, read beside a bucket's URL when it has them. */\nconst STORAGE_KEYS = [\n\t'AWS_ACCESS_KEY_ID',\n\t'AWS_SECRET_ACCESS_KEY',\n\t'AWS_REGION',\n] as const;\n\n/**\n * Every service's Docker logs, rotated. The json-file driver keeps a\n * container's output in one file that grows until the disk is full — on a\n * small server, in weeks. Three files of 10 MB each per container is enough\n * to see what happened and never fills anything. A project's own override\n * file that sets `logging` wins over it.\n */\nexport const LOG_ROTATION = {\n\tdriver: 'json-file',\n\toptions: { 'max-size': '10m', 'max-file': '3' },\n} as const;\n\n/** Each image's build, where the stack builds rather than pulls. */\nexport interface AppBuild {\n\t/** Where the Dockerfile is written, relative to the workspace root. */\n\tdockerfile: string;\n\t/** A site's public URLs, inlined by its bundler. */\n\targs?: Record<string, string>;\n}\n\n/** One app in the stack. */\nexport interface StackApp extends AppImage {\n\t/** Its compose service name — the app key, `api`. */\n\tname: string;\n\t/** The construct it serves. */\n\tid: string;\n\tkind: 'rest-api' | 'site';\n\t/** Its directory, relative to the workspace root. */\n\tpath: string;\n\tport: number;\n\t/** The host the edge answers it on. */\n\thost: string;\n\t/** `https://<host>` — with the edge's port where it is not 443. */\n\turl: string;\n\tbuild?: AppBuild;\n\t/** A backend's environment, exactly the keys it reads. Absent for a site. */\n\tenv?: Record<string, string>;\n}\n\n/**\n * A Worker in the stack: its crons, queue consumers and subscribers in a\n * container of their own. Nothing routes to it and nothing is published —\n * its one route is the health check Docker asks.\n */\nexport interface StackWorker extends AppImage {\n\t/** Its compose service name — `jobs`. */\n\tname: string;\n\t/** The Worker construct. */\n\tid: string;\n\t/** The app whose build writes its entry, which its image is built from. */\n\thost: string;\n\t/** Where its health check answers, inside the container. */\n\tport: number;\n\tbuild?: AppBuild;\n\t/** Exactly the keys the worker's constructs read. */\n\tenv?: Record<string, string>;\n}\n\n/** A service as the stack's compose file defines it. */\nexport interface StackService {\n\timage: string;\n\tbuild?: {\n\t\tcontext: string;\n\t\tdockerfile: string;\n\t\targs?: Record<string, string>;\n\t};\n\trestart?: string;\n\tcommand?: string | string[];\n\tenv_file?: { path: string; format: 'raw' }[];\n\tenvironment?: Record<string, string>;\n\tports?: string[];\n\tvolumes?: string[];\n\tdepends_on?: Record<string, { condition: 'service_healthy' }>;\n\thealthcheck?: ComposeService['healthcheck'];\n\t/**\n\t * Behind the shared edge, a public service is on the stack's own network\n\t * and the edge's, by an alias unique on both.\n\t */\n\tnetworks?: Record<string, { aliases: string[] }>;\n\tlogging?: {\n\t\tdriver: string;\n\t\toptions: Record<string, string>;\n\t};\n}\n\nexport interface StackFile {\n\tname: string;\n\tservices: Record<string, StackService>;\n\tvolumes: Record<string, Record<string, never>>;\n\t/** The shared edge's network, which the stack joins but never creates. */\n\tnetworks?: Record<string, { name: string; external: true }>;\n}\n\nexport interface ComposeStack {\n\tproject: string;\n\tstage: string;\n\t/** The project's local stage, served with Caddy's internal CA. */\n\tlocal: boolean;\n\tapps: StackApp[];\n\t/** Each Worker with background work, in a container of its own. */\n\tworkers: StackWorker[];\n\t/** The infrastructure containers, by compose service name. */\n\tinfra: string[];\n\tcompose: StackFile;\n\t/**\n\t * What serves the stack: its own Caddy, or the server's shared Traefik\n\t * edge (`deploy.compose.proxy`). The local stage is always Caddy.\n\t */\n\tproxy: ComposeProxy;\n\t/** Every public host and what answers it — what either proxy renders. */\n\troutes: EdgeRoute[];\n\t/** The stack's own Caddy's configuration — with `proxy: 'caddy'`. */\n\tcaddyfile?: string;\n\t/**\n\t * What the stack registers with the shared edge — with `proxy:\n\t * 'traefik'`: the dynamic configuration file written to its directory.\n\t */\n\ttraefik?: string;\n\t/**\n\t * The stage's own certificate and key, on this machine\n\t * (`deploy.compose.tls`) — copied to where the proxy reads it.\n\t */\n\ttls?: ComposeTlsConfig;\n\t/** Generated Dockerfiles, by path relative to the workspace root. */\n\tdockerfiles: Record<string, string>;\n\t/** The plan the stack was derived from — what provisioning creates. */\n\tplan: Plan;\n\t/** The containers' logins, and the seed role passwords take. */\n\tcredential: StackCredential;\n\t/**\n\t * The stack's MinIO, where it runs one: its root credential — what the\n\t * backends sign with — and the buckets and open paths provisioning\n\t * creates in it.\n\t */\n\tstorage?: StackStorage;\n\t/** The dev services a deployed stage runs (`--allow-dev-services`). */\n\tdevServices: DevServiceUse[];\n\t/**\n\t * The stack's OpenObserve, when the stage's telemetry is self-hosted —\n\t * every process with a `Telemetry` edge sends its logs and traces to it.\n\t */\n\tlogs?: StackLogs;\n\t/** Where the stage's telemetry goes, when any process uses it. */\n\ttelemetry?: ResolvedTelemetry;\n\t/**\n\t * The stack's Redis, where a declared cache lives in it — every cache\n\t * whose URL the stage's secrets do not set.\n\t */\n\tredis?: StackRedis;\n}\n\n/** What a stack's containers run with, and what its role passwords take. */\nexport interface StackCredential {\n\tcontainers: ContainerCredentials;\n\tseed: string;\n}\n\n/** The local stage's stack, without the logins `gkm dev` generates. */\nexport class LocalCredentialsMissing extends GkmError {\n\tconstructor(readonly stage: string) {\n\t\tsuper(\n\t\t\t`The local stage '${stage}' runs with this machine's generated logins, and none were passed. Load them with loadLocalCredentials(workspace) and pass them as localCredentials.`,\n\t\t);\n\t\tthis.name = 'LocalCredentialsMissing';\n\t}\n}\n\n/**\n * A deployed stage's container logins, derived from its seed like its role\n * passwords — so every deploy of the stage computes the same ones, and no one\n * reading the repo can.\n */\nexport function derivedCredentials(\n\tproject: string,\n\tplan: Plan,\n\tseed: string,\n): ContainerCredentials {\n\tconst derive = (purpose: string) =>\n\t\tlocalRolePassword(project, plan, purpose, seed);\n\treturn {\n\t\tpostgres: { user: postgresSuperuser(project), password: derive('master') },\n\t\tminio: { user: `${project}-minio`, password: derive('minio') },\n\t\trabbitmq: { user: LOCAL_RABBITMQ_USER, password: derive('rabbitmq') },\n\t\tredis: { password: derive('redis') },\n\t\tcacheToken: derive('cache-token'),\n\t\temulator: {\n\t\t\taccessKeyId: EMULATOR_ACCESS_KEY_ID,\n\t\t\tsecretAccessKey: derive('emulator'),\n\t\t},\n\t\t// Never read: a deployed stage's OpenObserve signs in with the root\n\t\t// login its secrets hold (`stackLogs`), and the stack hands each process\n\t\t// its telemetry keys from that, not from the derivation.\n\t\tlogs: { email: `admin@${project}`, password: derive('logs') },\n\t};\n}\n\n/** The stack's MinIO, and what is created in it. */\nexport interface StackStorage {\n\tuser: string;\n\tpassword: string;\n\t/** Each bucket's stage-scoped name. */\n\tbuckets: string[];\n\t/** The open paths of each served bucket. */\n\tpolicies: { bucket: string; open: string[] }[];\n}\n\nexport interface StackInput {\n\tworkspace: NormalizedWorkspace;\n\tmanifest: ConstructManifest;\n\t/** Each owner's runnables' edges, from discovery. */\n\trunnables?: Readonly<Record<string, readonly string[]>>;\n\t/**\n\t * Each Worker's crons', queues' and subscribers' files, from discovery —\n\t * which workers run, and which app each is built from.\n\t */\n\tbackground?: Readonly<Record<string, readonly string[]>>;\n\tstage: string;\n\tidentity: DeployIdentity;\n\timages: {\n\t\tmode: 'build' | 'pull';\n\t\t/** A release tag, or — building — the commit. */\n\t\ttag: string;\n\t\tregistry?: string;\n\t};\n\t/** The stage's secrets — set by hand, and what a deployed stage generated. */\n\tsecrets?: StageSecrets | null;\n\t/**\n\t * This machine's generated local logins — required for the local stage,\n\t * which runs with the same ones `gkm dev` does. See `localCredentials.ts`.\n\t */\n\tlocalCredentials?: LocalCredentials;\n\t/**\n\t * `--allow-dev-services`: a deployed stage runs the dev service for every\n\t * bucket and mail it does not account for. The local stage runs both\n\t * regardless.\n\t */\n\tallowDevServices?: boolean;\n\t/** The edge's published ports. 443 and 80 by default. */\n\tports?: {\n\t\thttps?: number;\n\t\thttp?: number;\n\t\t/** The local stage's OpenObserve port — see {@link LOGS_PORT_ENV}. */\n\t\tlogs?: number;\n\t};\n\t/**\n\t * The stack only builds its images, to push them (`--build --push`): no\n\t * backend's or worker's runtime environment is resolved and no service\n\t * reads an env file, so building needs none of the stage's backend\n\t * secrets. A site's build args — its public URLs — are still resolved.\n\t */\n\tbuildOnly?: boolean;\n\t/**\n\t * For builds: where the images are built from and with what — the build\n\t * root, its package manager and turbo. The workspace's root, with pnpm,\n\t * when not given.\n\t */\n\tlayout?: ImageLayout;\n}\n\n/** A value only the stage's secrets can hold, and they do not. */\nexport class StageSecretMissing extends GkmError {\n\tconstructor(\n\t\treadonly app: string,\n\t\treadonly key: string,\n\t\treadonly stage: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${app}' reads ${key}, and the stage '${stage}' has none. Nothing in ` +\n\t\t\t\t`the stack can derive it; set it in the stage's secrets: ` +\n\t\t\t\t`gkm secrets:set ${key} '…' --stage ${stage}`,\n\t\t);\n\t\tthis.name = 'StageSecretMissing';\n\t}\n}\n\n/** A deployed stage's secrets with no seed to derive its passwords from. */\nexport class StageSeedMissing extends GkmError {\n\tconstructor(readonly stage: string) {\n\t\tsuper(\n\t\t\t`The stage '${stage}' has no seed in its secrets, and a deployed stage's ` +\n\t\t\t\t`database passwords are derived from it. Run gkm compose --stage ${stage} ` +\n\t\t\t\t`without --dry-run once, or gkm deploy, to generate it.`,\n\t\t);\n\t\tthis.name = 'StageSeedMissing';\n\t}\n}\n\n/** A workspace with no API and no site to run. */\nexport class NothingToCompose extends GkmError {\n\tconstructor(readonly root: string) {\n\t\tsuper(\n\t\t\t`${root} declares no RestApi, no site and no Worker with work to do, ` +\n\t\t\t\t`so a stack would run nothing. Mobile apps ship through their own ` +\n\t\t\t\t`toolchain.`,\n\t\t);\n\t\tthis.name = 'NothingToCompose';\n\t}\n}\n\n/** A value that cannot be written to an env file a line at a time. */\nexport class EnvValueMultiline extends GkmError {\n\tconstructor(\n\t\treadonly app: string,\n\t\treadonly key: string,\n\t) {\n\t\tsuper(\n\t\t\t`${key} for '${app}' spans more than one line, and an env file holds ` +\n\t\t\t\t`one value per line. Store it on one line — base64, or JSON — and ` +\n\t\t\t\t`decode it where it is read.`,\n\t\t);\n\t\tthis.name = 'EnvValueMultiline';\n\t}\n}\n\n/** The services a stack's apps wait on and the edge waits on in turn. */\nconst HEALTHY = { condition: 'service_healthy' } as const;\n\n/**\n * An app's health check: its own server, asked on 127.0.0.1 — not\n * `localhost`, which resolves to `::1` first in an Alpine container while a\n * server may listen on IPv4 alone.\n */\nfunction probe(port: number, path: string): StackService['healthcheck'] {\n\treturn {\n\t\ttest: [\n\t\t\t'CMD',\n\t\t\t'wget',\n\t\t\t'-q',\n\t\t\t'-O',\n\t\t\t'/dev/null',\n\t\t\t`http://127.0.0.1:${port}${path}`,\n\t\t],\n\t\tinterval: '5s',\n\t\ttimeout: '3s',\n\t\tretries: 20,\n\t\tstart_period: '10s',\n\t};\n}\n\n/** The stack for one stage. */\nexport function composeStack(input: StackInput): ComposeStack {\n\tconst { workspace, manifest, stage, identity } = input;\n\tconst local = stage === workspace.stages.local;\n\tconst project = composeProject(identity);\n\tconst custom = input.secrets?.custom ?? {};\n\tconst https = input.ports?.https ?? 443;\n\tconst http = input.ports?.http ?? 80;\n\tconst proxy = proxyFor(workspace.deploy?.compose, stage, local);\n\tconst certificate = local\n\t\t? undefined\n\t\t: certificateFor(workspace.deploy?.compose, stage, workspace.root);\n\n\t// A server target's backends, whatever `deploy.default` says: the stack is\n\t// one machine running containers, so events are pg-boss beside the\n\t// declared database — and every cache, one declared from that database\n\t// included, is in the stack's own Redis.\n\tconst plan = planFor(manifest, stage, provisionOrder(manifest), {\n\t\tlocalStage: workspace.stages.local,\n\t\tevents: DEFAULT_EVENTS.server,\n\t\tcache: STACK_CACHE,\n\t\t// The stack brings its own edge; reconcile's would front the host.\n\t\tedge: false,\n\t});\n\tconst byId = new Map(plan.resources.map((r) => [r.id, r]));\n\n\t// The local stage's logins are the ones `gkm dev` generated for this\n\t// machine, so the two run their containers with the same; a deployed\n\t// stage's are derived from its seed.\n\tif (local && !input.localCredentials) {\n\t\tthrow new LocalCredentialsMissing(stage);\n\t}\n\tconst seed = local ? input.localCredentials!.seed : input.secrets?.seed;\n\tif (!seed) throw new StageSeedMissing(stage);\n\tconst containers: ContainerCredentials = local\n\t\t? input.localCredentials!\n\t\t: derivedCredentials(workspace.name, plan, seed);\n\tconst credential: StackCredential = {\n\t\tcontainers: {\n\t\t\t...containers,\n\t\t\t// The stage's own key pair, where it set one, is what MinIO runs as.\n\t\t\tminio: {\n\t\t\t\tuser: custom.AWS_ACCESS_KEY_ID ?? containers.minio.user,\n\t\t\t\tpassword: custom.AWS_SECRET_ACCESS_KEY ?? containers.minio.password,\n\t\t\t},\n\t\t},\n\t\tseed,\n\t};\n\n\tconst apps = stackApps(workspace, manifest, plan, {\n\t\tlocal,\n\t\tstage,\n\t\thttps,\n\t\tidentity,\n\t\timages: input.images,\n\t});\n\tconst workers = stackWorkers(\n\t\tworkerUnits(workspace, manifest, input.background ?? {}),\n\t\t{ stage, identity, images: input.images },\n\t);\n\tif (apps.length === 0 && workers.length === 0) {\n\t\tthrow new NothingToCompose(workspace.root);\n\t}\n\n\t// Which construct each key belongs to, so a key is resolved by what it is\n\t// rather than by its spelling.\n\tconst owners = new Map<string, PlannedResource>();\n\tfor (const resource of plan.resources) {\n\t\towners.set(resource.envKey, resource);\n\t\tif (resource.kind === 'email') {\n\t\t\towners.set(provideKey(resource.id, 'from'), resource);\n\t\t\towners.set(provideKey(resource.id, 'inboxUrl'), resource);\n\t\t}\n\t\tif (resource.kind === 'external-api') {\n\t\t\towners.set(provideKey(resource.id, 'credentials'), resource);\n\t\t}\n\t}\n\n\tconst domain = workspace.domains?.[stage];\n\n\t// Where the stage's telemetry goes, when a process uses a `Telemetry`\n\t// node: the local stage always to OpenObserve, a deployed one where\n\t// `deploy.telemetry` says — and OpenObserve when it says nothing.\n\tconst telemetry = resolveStageTelemetry({\n\t\t...(workspace.deploy?.telemetry\n\t\t\t? { telemetry: workspace.deploy.telemetry }\n\t\t\t: {}),\n\t\tstage,\n\t\tlocal,\n\t\ttarget: 'compose',\n\t\truntime: 'server',\n\t\tselfHosted: true,\n\t\tused: usesTelemetry(manifest),\n\t});\n\n\t// The log UI, when the stage's telemetry is self-hosted: its root login,\n\t// how it is reached, and what each process with the edge is handed to send\n\t// to it.\n\tconst logsBase =\n\t\ttelemetry?.provider === 'self-hosted'\n\t\t\t? stackLogs({\n\t\t\t\t\tconfig:\n\t\t\t\t\t\tlocal && input.ports?.logs\n\t\t\t\t\t\t\t? { ...telemetry.selfHosted, port: input.ports.logs }\n\t\t\t\t\t\t\t: telemetry.selfHosted,\n\t\t\t\t\tstage,\n\t\t\t\t\tlocal,\n\t\t\t\t\tproject: workspace.name,\n\t\t\t\t\t...(domain ? { domain } : {}),\n\t\t\t\t\tcustom,\n\t\t\t\t\thttps,\n\t\t\t\t\t...(local ? { localLogin: input.localCredentials!.logs } : {}),\n\t\t\t\t})\n\t\t\t: undefined;\n\n\t// Mail and storage: the local stage runs Mailpit and MinIO for all of it.\n\t// A deployed stage takes each from its secrets — or, where allowed, from a\n\t// dev service. Only decided here: whether the stage lacks a key is the\n\t// deploy's readiness check's to say, once, and a build asks no such thing.\n\tconst services: ExternalServices = local\n\t\t? {\n\t\t\t\tmissing: [],\n\t\t\t\tminio: plan.resources\n\t\t\t\t\t.filter((r) => r.kind === 'objects')\n\t\t\t\t\t.map((r) => r.id),\n\t\t\t\tmailpit: plan.resources\n\t\t\t\t\t.filter((r) => r.kind === 'email')\n\t\t\t\t\t.map((r) => r.id),\n\t\t\t}\n\t\t: externalServices({\n\t\t\t\tstage,\n\t\t\t\tdeclarations: stageServiceDeclarations({\n\t\t\t\t\tmanifest,\n\t\t\t\t\t...(input.runnables ? { runnables: input.runnables } : {}),\n\t\t\t\t}),\n\t\t\t\tsupplied: custom,\n\t\t\t\tallow: input.allowDevServices === true,\n\t\t\t\t...(domain ? { domain } : {}),\n\t\t\t\tproviders: stageProviderNotes(workspace, stage),\n\t\t\t});\n\n\t// The caches: in the stack's Redis, unless the stage set a cache's URL —\n\t// a managed Redis — and with every one set, there is no Redis to run.\n\tconst redis = stackRedis({\n\t\tplan,\n\t\tstage,\n\t\tlocal,\n\t\tcustom,\n\t\t...(local ? { localPassword: input.localCredentials!.redis.password } : {}),\n\t});\n\n\tconst infra = [\n\t\t...plan.containers\n\t\t\t.filter((c) => !NOT_RUN[c])\n\t\t\t// Run as the stack's own, below, with the stage's login.\n\t\t\t.filter((c) => c !== LOGS_SERVICE)\n\t\t\t.filter((c) => c !== 'minio' || services.minio.length > 0)\n\t\t\t.filter((c) => c !== 'mailpit' || services.mailpit.length > 0)\n\t\t\t.filter((c) => c !== REDIS_SERVICE || redis !== undefined),\n\t\t...(logsBase ? [LOGS_SERVICE] : []),\n\t].sort();\n\n\t// The stack's MinIO signs with the stage's own key pair where it set one,\n\t// and otherwise with the stage's login: the local one `gkm dev` generated,\n\t// or — deployed — one derived from the stage's seed.\n\tconst storage: StackStorage | undefined = infra.includes('minio')\n\t\t? (() => {\n\t\t\t\tconst buckets = services.minio\n\t\t\t\t\t.map((id) => byId.get(id)?.name)\n\t\t\t\t\t.filter((name): name is string => Boolean(name))\n\t\t\t\t\t.sort();\n\t\t\t\treturn {\n\t\t\t\t\t...credential.containers.minio,\n\t\t\t\t\tbuckets,\n\t\t\t\t\tpolicies: bucketPolicies(plan).filter((p) =>\n\t\t\t\t\t\tbuckets.includes(p.bucket),\n\t\t\t\t\t),\n\t\t\t\t};\n\t\t\t})()\n\t\t: undefined;\n\n\t// Each file server over the stack's MinIO, at a host of its own on the\n\t// edge — the shape it has deployed: a domain serving a bucket.\n\tconst fileServers = new Map<string, { url: string; route: EdgeRoute }>();\n\tif (storage) {\n\t\tfor (const resource of plan.resources) {\n\t\t\tif (resource.kind !== 'file-server' || !resource.of) continue;\n\t\t\tif (!services.minio.includes(resource.of)) continue;\n\t\t\tif (custom[resource.envKey] !== undefined) continue;\n\t\t\tconst bucket = byId.get(resource.of);\n\t\t\tif (!bucket) continue;\n\n\t\t\tlet host: string;\n\t\t\tif (local) {\n\t\t\t\thost = hostFor(resource, workspace.name);\n\t\t\t} else {\n\t\t\t\tif (!domain) throw new NoDomainForStage(stage);\n\t\t\t\thost = `${resource.subdomain ?? appKey(resource.id)}.${domain}`;\n\t\t\t}\n\t\t\tfileServers.set(resource.id, {\n\t\t\t\turl: `https://${host}${local && https !== 443 ? `:${https}` : ''}`,\n\t\t\t\troute: {\n\t\t\t\t\tname: `files-${appKey(resource.id)}`,\n\t\t\t\t\thost,\n\t\t\t\t\tupstreams: [{ service: 'minio', port: 9000 }],\n\t\t\t\t\tstreaming: false,\n\t\t\t\t\tprefix: `/${bucket.name}`,\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\t}\n\n\t// Behind the shared edge, the services it routes to are on a network\n\t// every stack's public services share, where a bare name — `api` — would\n\t// be answered by each stack's. They are reached, from the edge and from\n\t// inside the stack alike, by an alias prefixed with the project.\n\tconst edgeServices = new Set(\n\t\tproxy === 'traefik'\n\t\t\t? [\n\t\t\t\t\t...apps.map((app) => app.name),\n\t\t\t\t\t...(fileServers.size > 0 ? ['minio'] : []),\n\t\t\t\t\t...(logsBase?.host && logsBase.public ? [LOGS_SERVICE] : []),\n\t\t\t\t]\n\t\t\t: [],\n\t);\n\tconst hostOf = (service: string) =>\n\t\tedgeServices.has(service) ? edgeAlias(project, service) : service;\n\tconst logs: StackLogs | undefined = logsBase && {\n\t\t...logsBase,\n\t\tappEnv: {\n\t\t\t...logsBase.appEnv,\n\t\t\tOTEL_EXPORTER_OTLP_ENDPOINT:\n\t\t\t\tlogsBase.appEnv.OTEL_EXPORTER_OTLP_ENDPOINT.replace(\n\t\t\t\t\t`//${LOGS_SERVICE}:`,\n\t\t\t\t\t`//${hostOf(LOGS_SERVICE)}:`,\n\t\t\t\t),\n\t\t},\n\t};\n\n\t// Every key the stage resolves, twice: with each app at its public address\n\t// — what a browser is handed, and what an app says it is — and with each on\n\t// the compose network, which is how one service reaches another.\n\tconst derivation = {\n\t\tproject: workspace.name,\n\t\tcredentials: credential.containers,\n\t\tseed: credential.seed,\n\t\t// Mailpit on a deployed stage sends as the stage's domain.\n\t\t...(!local && domain ? { mailFrom: `noreply@${domain}` } : {}),\n\t};\n\tconst outside = networkEnv(\n\t\tplan,\n\t\t{\n\t\t\t...derivation,\n\t\t\taddresses: Object.fromEntries(apps.map((app) => [app.id, app.url])),\n\t\t},\n\t\thostOf,\n\t);\n\tconst inside = networkEnv(\n\t\tplan,\n\t\t{\n\t\t\t...derivation,\n\t\t\taddresses: Object.fromEntries(\n\t\t\t\tapps.map((app) => [app.id, `http://${hostOf(app.name)}:${app.port}`]),\n\t\t\t),\n\t\t},\n\t\thostOf,\n\t);\n\n\tconst surfaceUrls = new Map(\n\t\tplan.resources\n\t\t\t.filter((r) => r.kind === 'rest-api' || r.kind === 'site')\n\t\t\t.map((r) => [r.envKey, r.id]),\n\t);\n\tconst appById = new Map(apps.map((app) => [app.id, app]));\n\n\t/**\n\t * What a key resolves to for one app, from the stage's secrets where only\n\t * they can hold it, and the derivations otherwise.\n\t */\n\tconst valueFor = (\n\t\tapp: Pick<StackApp, 'name' | 'id' | 'kind'>,\n\t\tkey: string,\n\t\towner: PlannedResource | undefined,\n\t): string | undefined => {\n\t\t// The server's address is gkm's own: never in an app's environment.\n\t\tif (isReservedStageKey(key)) return undefined;\n\t\t// Mail and storage: what the stage set — a real server, a real bucket —\n\t\t// and otherwise the dev service it runs, which `services` has already\n\t\t// checked it may.\n\t\tif (owner && SERVICE_KINDS[owner.kind]) {\n\t\t\tif (custom[key] !== undefined) return custom[key];\n\t\t\tif (owner.kind === 'file-server') return fileServers.get(owner.id)?.url;\n\t\t\treturn outside[key];\n\t\t}\n\n\t\t// Supplied by somebody: a third party's credentials, and — deployed —\n\t\t// the secrets the stage generated.\n\t\tif (owner && suppliedOnly(owner.kind, owner.id, key, local)) {\n\t\t\tconst value = custom[key];\n\t\t\tif (value === undefined) {\n\t\t\t\tthrow new StageSecretMissing(app.name, key, stage);\n\t\t\t}\n\t\t\treturn value;\n\t\t}\n\n\t\t// Set by hand wins over derived: it is the stage saying what it is.\n\t\tif (custom[key] !== undefined) return custom[key];\n\n\t\t// A cache, in the stack's Redis, by its name on the network.\n\t\tif (owner?.kind === 'cache') return redis?.urls[key];\n\n\t\t// A backend reaches a sibling surface on the compose network; its own\n\t\t// address is the public one, which is what it builds links on. A site\n\t\t// runs in a browser, which has only the public addresses.\n\t\tconst surface = surfaceUrls.get(key);\n\t\tif (app.kind === 'rest-api' && surface && surface !== app.id) {\n\t\t\treturn inside[key];\n\t\t}\n\n\t\treturn outside[key];\n\t};\n\n\t// What the stage resolves for its `Telemetry` node, before a process is\n\t// named: the stack's OpenObserve, or the provider `deploy.telemetry` names.\n\tconst telemetryValues: Record<string, string> | undefined = logs\n\t\t? telemetryEnv(\n\t\t\t\t{\n\t\t\t\t\tendpoint: logs.appEnv.OTEL_EXPORTER_OTLP_ENDPOINT,\n\t\t\t\t\theaders: logs.appEnv.OTEL_EXPORTER_OTLP_HEADERS,\n\t\t\t\t},\n\t\t\t\ttelemetry?.sampleRate ?? 1,\n\t\t\t)\n\t\t: telemetry?.provider === 'otlp'\n\t\t\t? otlpTelemetryEnv(telemetry)\n\t\t\t: undefined;\n\n\t/** Each key one backend process reads, resolved for this stage. */\n\tconst valuesFor = (\n\t\tapp: Pick<StackApp, 'name' | 'id' | 'kind'>,\n\t\tkeys: ReadonlySet<string>,\n\t): Record<string, string> => {\n\t\tconst values: Record<string, string> = {};\n\t\tfor (const key of [...keys].sort()) {\n\t\t\t// The node's keys are the stage's telemetry, added per process.\n\t\t\tif (isTelemetryKey(key)) continue;\n\t\t\tconst owner = owners.get(key) ?? storageOwner(key, plan);\n\t\t\t// A deployed stage has no inbox: Mailpit runs only locally.\n\t\t\tif (!local && owner?.kind === 'email' && key.endsWith('_INBOX_URL'))\n\t\t\t\tcontinue;\n\t\t\tif ((STORAGE_KEYS as readonly string[]).includes(key)) {\n\t\t\t\t// The stage's own key pair where it set one — an external bucket\n\t\t\t\t// signs with it — and otherwise the stack's MinIO's.\n\t\t\t\tconst value = custom[key] ?? storageValue(key, storage);\n\t\t\t\tif (value !== undefined) values[key] = value;\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst value = valueFor(app, key, owner);\n\t\t\tif (value !== undefined) values[key] = value;\n\t\t}\n\t\treturn values;\n\t};\n\n\tfor (const app of apps) {\n\t\tif (app.kind === 'site') {\n\t\t\tconst site = manifest[app.id];\n\t\t\tif (site?.kind !== 'site') continue;\n\n\t\t\tconst args: Record<string, string> = {};\n\t\t\tfor (const [key, source] of Object.entries(\n\t\t\t\tpublicEnvFor(site, manifest),\n\t\t\t)) {\n\t\t\t\tconst value = valueFor(app, source, owners.get(source));\n\t\t\t\tif (value !== undefined) args[key] = value;\n\t\t\t}\n\t\t\tif (app.build && Object.keys(args).length > 0) app.build.args = args;\n\t\t\tcontinue;\n\t\t}\n\t\t// Built to be pushed, a backend's image is every stage's: what it reads\n\t\t// at runtime is the deploy's business, not the build's.\n\t\tif (input.buildOnly) continue;\n\n\t\tconst keys = appEnvKeys(manifest, app.name, input.runnables) ?? new Set();\n\t\tconst values = valuesFor(app, keys);\n\n\t\t// The origins Better Auth trusts are the browser's and, now, each\n\t\t// service that calls this surface across the compose network: its CSRF\n\t\t// check applies to every caller, and a sibling's request carries its\n\t\t// own origin, not a browser's.\n\t\tconst trusted = provideKey(app.id, 'trustedOrigins');\n\t\tif (keys.has(trusted)) {\n\t\t\tconst internal = (byId.get(app.id)?.callers ?? [])\n\t\t\t\t.map((caller) => appById.get(caller))\n\t\t\t\t.filter((caller): caller is StackApp => caller?.kind === 'rest-api')\n\t\t\t\t.map((caller) => `http://${hostOf(caller.name)}:${caller.port}`);\n\t\t\tconst origins = [\n\t\t\t\t...(values[trusted] ?? '').split(',').filter(Boolean),\n\t\t\t\t...internal,\n\t\t\t];\n\t\t\tvalues[trusted] = [...new Set(origins)].join(',');\n\t\t}\n\n\t\t// Telemetry: the stage's, to a process with an edge to the node.\n\t\tapp.env = {\n\t\t\t...scopeTelemetryEnv(values, {\n\t\t\t\tuses: telemetryOf(manifest, app.id) !== undefined,\n\t\t\t\tserviceName: app.name,\n\t\t\t\t...(telemetryValues ? { telemetry: telemetryValues } : {}),\n\t\t\t}),\n\t\t\tNODE_ENV: 'production',\n\t\t\tPORT: String(app.port),\n\t\t\tSTAGE: stage,\n\t\t};\n\n\t\tfor (const [key, value] of Object.entries(app.env)) {\n\t\t\tif (/[\\r\\n]/.test(value)) throw new EnvValueMultiline(app.name, key);\n\t\t}\n\t}\n\n\t// A worker reaches every surface across the compose network: it is never\n\t// one, so no address is its own.\n\tfor (const worker of input.buildOnly ? [] : workers) {\n\t\tconst keys =\n\t\t\tworkerEnvKeys(manifest, worker.id, input.runnables) ?? new Set();\n\t\tworker.env = {\n\t\t\t...scopeTelemetryEnv(\n\t\t\t\tvaluesFor({ name: worker.name, id: worker.id, kind: 'rest-api' }, keys),\n\t\t\t\t{\n\t\t\t\t\tuses: telemetryOf(manifest, worker.id) !== undefined,\n\t\t\t\t\tserviceName: worker.name,\n\t\t\t\t\t...(telemetryValues ? { telemetry: telemetryValues } : {}),\n\t\t\t\t},\n\t\t\t),\n\t\t\tNODE_ENV: 'production',\n\t\t\tPORT: String(worker.port),\n\t\t\tSTAGE: stage,\n\t\t};\n\t\tfor (const [key, value] of Object.entries(worker.env)) {\n\t\t\tif (/[\\r\\n]/.test(value)) throw new EnvValueMultiline(worker.name, key);\n\t\t}\n\t}\n\n\t// A deployed stage's certificates: its own, where it set one, as the\n\t// proxy's container reads them — otherwise Let's Encrypt's.\n\tconst deployedTls: Exclude<EdgeTls, { kind: 'internal' }> = certificate\n\t\t? {\n\t\t\t\tkind: 'files',\n\t\t\t\t...(proxy === 'caddy'\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tcertFile: `${CADDY_TLS_DIR}/cert.pem`,\n\t\t\t\t\t\t\tkeyFile: `${CADDY_TLS_DIR}/key.pem`,\n\t\t\t\t\t\t}\n\t\t\t\t\t: edgeCertificatePaths(project)),\n\t\t\t}\n\t\t: { kind: 'acme' };\n\tconst tls: EdgeTls = local ? { kind: 'internal' } : deployedTls;\n\n\tconst layout = input.layout ?? defaultLayout(workspace.root);\n\tconst compose = stackFile({\n\t\tproject,\n\t\tplan,\n\t\tinfra,\n\t\tapps,\n\t\tworkers,\n\t\tcredentials: credential.containers,\n\t\t...(storage ? { storage } : {}),\n\t\t...(logs ? { logs } : {}),\n\t\t...(redis ? { redis } : {}),\n\t\thttps,\n\t\thttp,\n\t\tcomposeDir: join(workspace.root, stackDir(stage)),\n\t\tbuildRoot: layout.buildRoot,\n\t\tworkspaceRoot: workspace.root,\n\t\tenvFiles: !input.buildOnly,\n\t\tproxy,\n\t\tedge: new Map(\n\t\t\t[...edgeServices].map((service) => [service, hostOf(service)]),\n\t\t),\n\t\ttls: certificate !== undefined,\n\t});\n\n\t// One route per public host — the data both proxies render.\n\tconst routes: EdgeRoute[] = [\n\t\t...apps.map(\n\t\t\t(app): EdgeRoute => ({\n\t\t\t\tname: app.name,\n\t\t\t\thost: app.host,\n\t\t\t\tupstreams: [{ service: app.name, port: app.port }],\n\t\t\t\tstreaming: true,\n\t\t\t\thealth: app.kind === 'site' ? '/' : '/health',\n\t\t\t}),\n\t\t),\n\t\t...[...fileServers.values()].map(({ route }) => route),\n\t\t...(logs?.host && logs.public\n\t\t\t? [\n\t\t\t\t\t{\n\t\t\t\t\t\tname: LOGS_SERVICE,\n\t\t\t\t\t\thost: logs.host,\n\t\t\t\t\t\tupstreams: [{ service: LOGS_SERVICE, port: LOGS_PORT }],\n\t\t\t\t\t\tstreaming: true,\n\t\t\t\t\t\tallow: logs.public.allow,\n\t\t\t\t\t},\n\t\t\t\t]\n\t\t\t: []),\n\t];\n\tconst dockerfiles: Record<string, string> = {};\n\tfor (const app of apps) {\n\t\tif (!app.build) continue;\n\t\t// The same Dockerfile `gkm docker` writes: the image is built inside\n\t\t// Docker from a pruned slice of the build root, and nothing is built\n\t\t// here first.\n\t\tdockerfiles[app.build.dockerfile] = appDockerfile(\n\t\t\tapp.name,\n\t\t\tworkspace.apps[app.name]!,\n\t\t\t{\n\t\t\t\tlayout,\n\t\t\t\tworkspaceRoot: workspace.root,\n\t\t\t\tapps: workspace.apps,\n\t\t\t\tmanifest,\n\t\t\t\tcache: STACK_CACHE,\n\t\t\t\t// A site's clients trace at this stage's sample rate.\n\t\t\t\tworkspace,\n\t\t\t\tstage,\n\t\t\t},\n\t\t);\n\t}\n\tfor (const worker of workers) {\n\t\tif (!worker.build) continue;\n\t\tdockerfiles[worker.build.dockerfile] = workerDockerfile(\n\t\t\t{ id: worker.id, name: worker.name, app: worker.host },\n\t\t\tworkspace.apps[worker.host]!,\n\t\t\t{ layout, workspaceRoot: workspace.root, cache: STACK_CACHE },\n\t\t);\n\t}\n\n\treturn {\n\t\tproject,\n\t\tstage,\n\t\tlocal,\n\t\tapps,\n\t\tworkers,\n\t\tinfra,\n\t\tcompose,\n\t\tproxy,\n\t\troutes,\n\t\t...(proxy === 'caddy'\n\t\t\t? { caddyfile: edgeCaddyfile(routes, { tls }) }\n\t\t\t: {\n\t\t\t\t\ttraefik: traefikDynamicFile(routes, {\n\t\t\t\t\t\tproject,\n\t\t\t\t\t\ttls: deployedTls,\n\t\t\t\t\t}),\n\t\t\t\t}),\n\t\t...(certificate ? { tls: certificate } : {}),\n\t\tdockerfiles,\n\t\tplan,\n\t\tcredential,\n\t\t...(storage ? { storage } : {}),\n\t\tdevServices: local ? [] : devServicesUsed(services),\n\t\t...(logs ? { logs } : {}),\n\t\t...(telemetry ? { telemetry } : {}),\n\t\t...(redis ? { redis } : {}),\n\t};\n}\n\n/** The stack's MinIO's value for one of the S3 client's keys. */\nfunction storageValue(\n\tkey: string,\n\tstorage: StackStorage | undefined,\n): string | undefined {\n\tif (!storage) return undefined;\n\tif (key === 'AWS_ACCESS_KEY_ID') return storage.user;\n\tif (key === 'AWS_SECRET_ACCESS_KEY') return storage.password;\n\tif (key === 'AWS_REGION') return MINIO_REGION;\n\treturn undefined;\n}\n\n/** Which bucket an S3 credential key belongs to — the first one declared. */\nfunction storageOwner(key: string, plan: Plan): PlannedResource | undefined {\n\tif (!(STORAGE_KEYS as readonly string[]).includes(key)) return undefined;\n\treturn plan.resources.find((r) => r.kind === 'objects');\n}\n\n/** The apps a stack runs: every API and every site, at its stage address. */\nfunction stackApps(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n\tplan: Plan,\n\toptions: {\n\t\tlocal: boolean;\n\t\tstage: string;\n\t\thttps: number;\n\t\tidentity: DeployIdentity;\n\t\timages: StackInput['images'];\n\t},\n): StackApp[] {\n\tconst apps: StackApp[] = [];\n\tconst resources = new Map(plan.resources.map((r) => [r.id, r]));\n\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (declaration.kind !== 'rest-api' && declaration.kind !== 'site')\n\t\t\tcontinue;\n\n\t\tconst name = appKey(id);\n\t\tconst app = workspace.apps[name];\n\t\tconst resource = resources.get(id);\n\t\tif (!app || !resource || !app.port) continue;\n\n\t\tconst host = hostOf(name, app, workspace, resource, options);\n\t\tconst site = declaration.kind === 'site';\n\t\tconst tag = site\n\t\t\t? siteTag(options.images.tag, options.stage)\n\t\t\t: options.images.tag;\n\t\tconst ref = validateImageRef(\n\t\t\timageRef(options.identity, name, options.images.registry, tag),\n\t\t);\n\n\t\tapps.push({\n\t\t\tapp: name,\n\t\t\tname,\n\t\t\tid,\n\t\t\tkind: declaration.kind,\n\t\t\tpath: app.path,\n\t\t\tport: app.port,\n\t\t\thost,\n\t\t\turl: `https://${host}${options.local && options.https !== 443 ? `:${options.https}` : ''}`,\n\t\t\tref,\n\t\t\ttag,\n\t\t\t...(options.images.mode === 'build'\n\t\t\t\t? {\n\t\t\t\t\t\tbuild: {\n\t\t\t\t\t\t\tdockerfile: `${stackDir(options.stage)}/Dockerfile.${name}`,\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t});\n\t}\n\n\treturn apps.sort((a, b) => a.name.localeCompare(b.name));\n}\n\n/** Each worker's image, and where it is built from. */\nfunction stackWorkers(\n\tunits: readonly WorkerUnit[],\n\toptions: {\n\t\tstage: string;\n\t\tidentity: DeployIdentity;\n\t\timages: StackInput['images'];\n\t},\n): StackWorker[] {\n\treturn units.map((unit) => ({\n\t\tapp: unit.name,\n\t\tname: unit.name,\n\t\tid: unit.id,\n\t\thost: unit.app,\n\t\tport: WORKER_PORT,\n\t\tref: validateImageRef(\n\t\t\timageRef(\n\t\t\t\toptions.identity,\n\t\t\t\tunit.name,\n\t\t\t\toptions.images.registry,\n\t\t\t\toptions.images.tag,\n\t\t\t),\n\t\t),\n\t\ttag: options.images.tag,\n\t\t...(options.images.mode === 'build'\n\t\t\t? {\n\t\t\t\t\tbuild: {\n\t\t\t\t\t\tdockerfile: `${stackDir(options.stage)}/Dockerfile.${unit.name}`,\n\t\t\t\t\t},\n\t\t\t\t}\n\t\t\t: {}),\n\t}));\n}\n\n/**\n * The host an app answers on for a stage: the `gkm dev` edge's\n * `*.localhost` names for the local stage, and the stage's domain — by the\n * same rule a deploy follows — for any other.\n */\nfunction hostOf(\n\tname: string,\n\tapp: NormalizedAppConfig,\n\tworkspace: NormalizedWorkspace,\n\tresource: PlannedResource,\n\toptions: { local: boolean; stage: string },\n): string {\n\tif (options.local) return hostFor(resource, workspace.name);\n\n\treturn resolveHost(\n\t\tname,\n\t\tapp,\n\t\toptions.stage,\n\t\tworkspace.domains,\n\t\tisMainFrontendApp(name, app, workspace.apps),\n\t);\n}\n\n/** The compose document: the infrastructure, the apps, and the edge. */\nfunction stackFile(options: {\n\tproject: string;\n\tplan: Plan;\n\tinfra: readonly string[];\n\tapps: readonly StackApp[];\n\tworkers: readonly StackWorker[];\n\tcredentials: ContainerCredentials;\n\tstorage?: StackStorage;\n\tlogs?: StackLogs;\n\tredis?: StackRedis;\n\thttps: number;\n\thttp: number;\n\tcomposeDir: string;\n\tbuildRoot: string;\n\tworkspaceRoot: string;\n\t/** Whether services read env files — not in a stack that only builds. */\n\tenvFiles: boolean;\n\tproxy: ComposeProxy;\n\t/** Behind the shared edge: each service it routes to, and its alias. */\n\tedge: ReadonlyMap<string, string>;\n\t/** Whether the stage has its own certificate, which Caddy mounts. */\n\ttls: boolean;\n}): StackFile {\n\tconst { project, plan, apps } = options;\n\t// What reconcile defines; OpenObserve is the stack's own.\n\tconst infra = options.infra.filter((name) => name !== LOGS_SERVICE);\n\n\t// Reconcile's definitions — images, health checks, volumes — with nothing\n\t// published to the host. Ports are allocation's business there; here the\n\t// edge is the only way in.\n\tconst derived = composeFor(\n\t\t{ ...plan, containers: [...infra] },\n\t\t{\n\t\t\tproject,\n\t\t\tports: Object.fromEntries(\n\t\t\t\tportKeys(infra, plan.fakes).map((key) => [key, 0]),\n\t\t\t),\n\t\t\tcredentials: options.credentials,\n\t\t},\n\t);\n\n\tconst services: Record<string, StackService> = {};\n\tconst volumes: Record<string, Record<string, never>> = {\n\t\t...derived.volumes,\n\t\t...(options.proxy === 'caddy'\n\t\t\t? { 'caddy-data': {}, 'caddy-config': {} }\n\t\t\t: {}),\n\t\t...(options.logs ? { 'openobserve-data': {} } : {}),\n\t};\n\n\tfor (const name of infra) {\n\t\tconst {\n\t\t\tports: _ports,\n\t\t\tdepends_on,\n\t\t\tprofiles: _profiles,\n\t\t\t...service\n\t\t} = derived.services[name]!;\n\t\tservices[name] = {\n\t\t\t...service,\n\t\t\t...(depends_on?.length\n\t\t\t\t? {\n\t\t\t\t\t\tdepends_on: Object.fromEntries(\n\t\t\t\t\t\t\tdepends_on.map((dep) => [dep, HEALTHY]),\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\tconst postgres = services.postgres;\n\tif (postgres) {\n\t\t// Loopback only, on a port Docker picks: gkm creates the databases and\n\t\t// roles and runs the migrations from this machine, and nothing else\n\t\t// should reach the database from outside the stack.\n\t\tpostgres.ports = ['127.0.0.1::5432'];\n\t}\n\n\tconst minio = services.minio;\n\tif (minio && options.storage) {\n\t\tminio.environment = {\n\t\t\t...minio.environment,\n\t\t\tMINIO_ROOT_USER: options.storage.user,\n\t\t\tMINIO_ROOT_PASSWORD: options.storage.password,\n\t\t};\n\t\t// Loopback only, like Postgres: gkm creates the buckets and their\n\t\t// policies from this machine. The apps reach it on the network, and a\n\t\t// browser through a file server's host on the edge.\n\t\tminio.ports = ['127.0.0.1::9000'];\n\t}\n\n\t// Its own definition rather than reconcile's: `gkm dev`'s Redis is an\n\t// open, unbounded scratch store; this one is a production cache.\n\tif (services[REDIS_SERVICE] && options.redis) {\n\t\tservices[REDIS_SERVICE] = redisService(options.redis);\n\t}\n\n\t// Started with the infrastructure, but nothing waits on it: an app whose\n\t// telemetry cannot be delivered still serves.\n\tif (options.logs) services[LOGS_SERVICE] = logsService(options.logs);\n\n\tfor (const app of apps) {\n\t\tservices[app.name] = {\n\t\t\timage: app.ref,\n\t\t\t...(app.build\n\t\t\t\t? {\n\t\t\t\t\t\tbuild: {\n\t\t\t\t\t\t\t...composeBuildPaths({\n\t\t\t\t\t\t\t\tcomposeDir: options.composeDir,\n\t\t\t\t\t\t\t\tbuildRoot: options.buildRoot,\n\t\t\t\t\t\t\t\tworkspaceRoot: options.workspaceRoot,\n\t\t\t\t\t\t\t\tdockerfile: app.build.dockerfile,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t...(app.build.args ? { args: app.build.args } : {}),\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\trestart: 'unless-stopped',\n\t\t\t// A site is a bundle and reads nothing at runtime; a backend reads\n\t\t\t// exactly the keys in its own file.\n\t\t\t...(app.kind === 'rest-api'\n\t\t\t\t? {\n\t\t\t\t\t\tenv_file: [{ path: `./${app.name}.env`, format: 'raw' as const }],\n\t\t\t\t\t\t...(infra.length\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tdepends_on: Object.fromEntries(\n\t\t\t\t\t\t\t\t\t\tinfra.map((name) => [name, HEALTHY]),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\thealthcheck: probe(app.port, app.kind === 'site' ? '/' : '/health'),\n\t\t};\n\t}\n\n\t// A worker: no route, no published port — its health check is Docker's.\n\tfor (const worker of options.workers) {\n\t\tservices[worker.name] = {\n\t\t\timage: worker.ref,\n\t\t\t...(worker.build\n\t\t\t\t? {\n\t\t\t\t\t\tbuild: composeBuildPaths({\n\t\t\t\t\t\t\tcomposeDir: options.composeDir,\n\t\t\t\t\t\t\tbuildRoot: options.buildRoot,\n\t\t\t\t\t\t\tworkspaceRoot: options.workspaceRoot,\n\t\t\t\t\t\t\tdockerfile: worker.build.dockerfile,\n\t\t\t\t\t\t}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\trestart: 'unless-stopped',\n\t\t\tenv_file: [{ path: `./${worker.name}.env`, format: 'raw' as const }],\n\t\t\t...(infra.length\n\t\t\t\t? {\n\t\t\t\t\t\tdepends_on: Object.fromEntries(\n\t\t\t\t\t\t\tinfra.map((name) => [name, HEALTHY]),\n\t\t\t\t\t\t),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\thealthcheck: probe(worker.port, '/health'),\n\t\t};\n\t}\n\n\tif (options.proxy === 'caddy') {\n\t\tservices.caddy = {\n\t\t\timage: DEFAULT_IMAGES.caddy!,\n\t\t\trestart: 'unless-stopped',\n\t\t\tports: [`${options.https}:443`, `${options.http}:80`],\n\t\t\tvolumes: [\n\t\t\t\t'./Caddyfile:/etc/caddy/Caddyfile:ro',\n\t\t\t\t'caddy-data:/data',\n\t\t\t\t'caddy-config:/config',\n\t\t\t\t// The stage's own certificate, copied beside the Caddyfile.\n\t\t\t\t...(options.tls ? [`./tls:${CADDY_TLS_DIR}:ro`] : []),\n\t\t\t],\n\t\t\tdepends_on: Object.fromEntries(apps.map((app) => [app.name, HEALTHY])),\n\t\t\thealthcheck: {\n\t\t\t\ttest: ['CMD', 'caddy', 'version'],\n\t\t\t\tinterval: '10s',\n\t\t\t\ttimeout: '5s',\n\t\t\t\tretries: 5,\n\t\t\t},\n\t\t};\n\t}\n\n\t// Behind the shared edge: each service it routes to joins its network,\n\t// under the alias the edge reaches it by — and keeps the stack's own,\n\t// with the same alias, which is how the stack's services reach it too.\n\t// Everything else — the databases, Redis, the workers — stays off it.\n\tfor (const [service, alias] of options.edge) {\n\t\tconst definition = services[service];\n\t\tif (!definition) continue;\n\t\tdefinition.networks = {\n\t\t\tdefault: { aliases: [alias] },\n\t\t\tedge: { aliases: [alias] },\n\t\t};\n\t}\n\n\tfor (const service of Object.values(services)) {\n\t\tif (!options.envFiles) delete service.env_file;\n\t\tservice.logging = {\n\t\t\tdriver: LOG_ROTATION.driver,\n\t\t\toptions: { ...LOG_ROTATION.options },\n\t\t};\n\t}\n\n\treturn {\n\t\tname: project,\n\t\tservices,\n\t\tvolumes,\n\t\t...(options.edge.size > 0\n\t\t\t? { networks: { edge: { name: EDGE_NETWORK, external: true as const } } }\n\t\t\t: {}),\n\t};\n}\n\n/**\n * A build without a layout: the workspace is the build root, with pnpm.\n * What a caller that never looked at the filesystem gets.\n */\nfunction defaultLayout(root: string): ImageLayout {\n\treturn {\n\t\tbuildRoot: root,\n\t\tgkmRoot: '.',\n\t\ttools: {\n\t\t\tpackageManager: 'pnpm',\n\t\t\tturboVersion: TURBO_VERSION,\n\t\t\tmonorepo: true,\n\t\t},\n\t\tgkmPaths: ['gkm.config.*'],\n\t};\n}\n\n/** Render an app's env file: one `KEY=value` per line, read raw by compose. */\nexport function envFile(env: Readonly<Record<string, string>>): string {\n\treturn `${Object.entries(env)\n\t\t.sort(([a], [b]) => a.localeCompare(b))\n\t\t.map(([key, value]) => `${key}=${value}`)\n\t\t.join('\\n')}\\n`;\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  const require_errors = require("./errors-DMC4Umsb.cjs");
2
- const require_workspace = require("./workspace-cSyi5nb1.cjs");
2
+ const require_workspace = require("./workspace-C1M-S4al.cjs");
3
3
  const require_config = require("./config.cjs");
4
- const require_seeds = require("./seeds-CnXkgKUv.cjs");
5
- const require_dns = require("./dns-Bmk7P9c2.cjs");
4
+ const require_seeds = require("./seeds-BQlj9Fsg.cjs");
5
+ const require_dns = require("./dns-BFnoawgh.cjs");
6
6
  let node_fs_promises = require("node:fs/promises");
7
7
  //#region src/stages/plan.ts
8
8
  /**
@@ -186,4 +186,4 @@ var GithubEventNotSet = class extends require_errors.GkmError {
186
186
  //#endregion
187
187
  exports.stagesCommand = stagesCommand;
188
188
 
189
- //# sourceMappingURL=stages-BujUB4we.cjs.map
189
+ //# sourceMappingURL=stages-BpDXFPGg.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"stages-BujUB4we.cjs","names":["UndeclaredStage","GkmError","PROVIDER_KINDS","stageProvider","stageDnsDomains","loadWorkspaceSettings","appendFile","GkmError"],"sources":["../src/stages/plan.ts","../src/stages/index.ts"],"sourcesContent":["/**\n * Which stages a workflow run builds and deploys, from the event that started\n * it and the project's `stages`.\n *\n * The rules live here, in tested code, rather than in a workflow's YAML or in\n * the action that runs `gkm stages --github-output`: a project's deploy\n * workflow names no stage, and what it does for each event is decided by the\n * CLI the project installed.\n *\n * | event | build | deploy |\n * | ------------------- | ------------------ | ------------------------------- |\n * | `push` | every deployed | deployed, less the protected |\n * | `release` | none | the protected |\n * | `workflow_dispatch` | none | the one stage named |\n * | anything else | none | none |\n *\n * A push builds every deployed stage, protected ones included, so a release\n * deploys images built from the very commit its tag names; a release and a\n * manual run deploy what a push already built.\n */\n\nimport { GkmError } from '../errors';\nimport { UndeclaredStage } from '../workspace/stages.js';\nimport type { StagesConfig } from '../workspace/types.js';\n\nexport interface StagePlan {\n\t/** The stages whose images this run builds and pushes. */\n\tbuild: string[];\n\t/** The stages this run deploys. */\n\tdeploy: string[];\n}\n\nexport interface PlanInput {\n\t/** The GitHub event name: `push`, `release`, `workflow_dispatch`, … */\n\tevent: string;\n\t/** The stage a `workflow_dispatch` names. */\n\tstage?: string;\n}\n\nexport function planStages(stages: StagesConfig, input: PlanInput): StagePlan {\n\tconst kept = stages.protected ?? [];\n\n\tswitch (input.event) {\n\t\tcase 'push':\n\t\t\treturn {\n\t\t\t\tbuild: [...stages.deployed],\n\t\t\t\tdeploy: stages.deployed.filter((stage) => !kept.includes(stage)),\n\t\t\t};\n\t\tcase 'release':\n\t\t\treturn { build: [], deploy: [...kept] };\n\t\tcase 'workflow_dispatch': {\n\t\t\tconst stage = input.stage?.trim();\n\t\t\tif (!stage) throw new DispatchNamesNoStage(stages.deployed);\n\t\t\tif (!stages.deployed.includes(stage)) {\n\t\t\t\tthrow new UndeclaredStage(stage, stages.deployed);\n\t\t\t}\n\t\t\treturn { build: [], deploy: [stage] };\n\t\t}\n\t\tdefault:\n\t\t\treturn { build: [], deploy: [] };\n\t}\n}\n\n/**\n * A manual run with no stage. Refused rather than read as \"nothing to do\", so\n * a run started with the field left empty fails where it can be seen.\n */\nexport class DispatchNamesNoStage extends GkmError {\n\tconstructor(readonly deployed: readonly string[]) {\n\t\tsuper(\n\t\t\t`A manual run deploys one stage, and none was given. Run the workflow again with stage set to one of: ${deployed.join(', ') || 'nothing yet — gkm.config.ts deploys to no stage'}.`,\n\t\t);\n\t\tthis.name = 'DispatchNamesNoStage';\n\t}\n}\n","/**\n * `gkm stages` — the workspace's stages, as gkm.config.ts declares them.\n *\n * ```bash\n * gkm stages # a table\n * gkm stages --json # {\"local\":\"dev\",\"deployed\":[\"prod\"],\"protected\":[\"prod\"]}\n * gkm stages --github-output # a workflow run's build and deploy matrices\n * ```\n *\n * The config is read by the normal loader (and sandbox), so a workflow never\n * parses TypeScript to learn which stages exist. `--github-output` is what the\n * `geekmidas/toolbox/actions/stages` action runs: it writes the stages, and\n * which of them this run builds and deploys, to `$GITHUB_OUTPUT`.\n */\n\nimport { appendFile } from 'node:fs/promises';\nimport { stageDnsDomains } from '../compose/dns.js';\nimport { loadWorkspaceSettings } from '../config.js';\nimport { GkmError } from '../errors';\nimport { stageProvider } from '../providers/config.js';\nimport { PROVIDER_KINDS } from '../providers/types.js';\nimport type { NormalizedWorkspace, StagesConfig } from '../workspace/types.js';\nimport { planStages } from './plan.js';\n\nexport { DispatchNamesNoStage, type PlanInput, planStages } from './plan.js';\n\nexport interface StagesOptions {\n\t/** Print the stages as JSON. */\n\tjson?: boolean;\n\t/** Write the outputs a deploy workflow reads to `$GITHUB_OUTPUT`. */\n\tgithubOutput?: boolean;\n\t/** The event to plan for; defaults to `GITHUB_EVENT_NAME`. */\n\tevent?: string;\n\t/** The stage a `workflow_dispatch` names. */\n\tstage?: string;\n}\n\nexport interface StagesDeps {\n\tcwd?: string;\n\tenv?: NodeJS.ProcessEnv;\n\t/** Where lines go; stdout by default. */\n\tlog?: (line: string) => void;\n}\n\n/** The stages, as `gkm stages --json` prints them. */\nexport interface StagesJson {\n\tlocal: string;\n\tdeployed: string[];\n\tprotected: string[];\n}\n\nexport function stagesJson(stages: StagesConfig): StagesJson {\n\treturn {\n\t\tlocal: stages.local,\n\t\tdeployed: [...stages.deployed],\n\t\tprotected: [...(stages.protected ?? [])],\n\t};\n}\n\n/**\n * The region of the workspace's AWS secrets store — `ssm` or\n * `secrets-manager` — or `''` when its deployed stages keep their secrets\n * anywhere else. A job assumes the stage's AWS role only when this is set.\n */\nexport function awsSecretsRegion(workspace: NormalizedWorkspace): string {\n\tconst store = workspace.secrets?.store;\n\tif (typeof store !== 'object' || store === null) return '';\n\tif (store.provider === 'ssm' || store.provider === 'secrets-manager') {\n\t\treturn store.region;\n\t}\n\treturn '';\n}\n\n/**\n * The deployed stages with resources the deploy creates — a provider under\n * `deploy.<kind>.<stage>`, or a domain under a `dns` entry whose provider\n * writes records — and secrets a CI runner can reach (an AWS store): a\n * compose deploy workflow creates their resources on the runner, with the\n * stage's role and DNS token, rather than on the server.\n */\nexport function resourceStages(workspace: NormalizedWorkspace): string[] {\n\tif (!awsSecretsRegion(workspace)) return [];\n\treturn workspace.stages.deployed.filter(\n\t\t(stage) =>\n\t\t\tPROVIDER_KINDS.some(\n\t\t\t\t(kind) => stageProvider(workspace, kind, stage).mode === 'provider',\n\t\t\t) ||\n\t\t\tstageDnsDomains(stage, workspace.domains, workspace.dns).some(\n\t\t\t\t({ config }) => config.provider !== 'manual',\n\t\t\t),\n\t);\n}\n\n/**\n * Every output the stages action sets, each a string `fromJSON()` reads, for\n * one workflow run.\n */\nexport function githubOutputs(\n\tworkspace: NormalizedWorkspace,\n\tinput: { event: string; stage?: string },\n): Record<string, string> {\n\tconst stages = stagesJson(workspace.stages);\n\tconst plan = planStages(workspace.stages, input);\n\n\treturn {\n\t\tlocal: JSON.stringify(stages.local),\n\t\tdeployed: JSON.stringify(stages.deployed),\n\t\tprotected: JSON.stringify(stages.protected),\n\t\tbuild: JSON.stringify(plan.build),\n\t\tdeploy: JSON.stringify(plan.deploy),\n\t\t// A matrix over an empty list is an error on GitHub, so a job over one\n\t\t// is skipped on these instead.\n\t\t'has-build': String(plan.build.length > 0),\n\t\t'has-deploy': String(plan.deploy.length > 0),\n\t\t'aws-region': awsSecretsRegion(workspace),\n\t\tresources: JSON.stringify(resourceStages(workspace)),\n\t};\n}\n\n/** `name=value` lines, as `$GITHUB_OUTPUT` takes single-line values. */\nexport function formatGithubOutputs(outputs: Record<string, string>): string {\n\treturn Object.entries(outputs)\n\t\t.map(([name, value]) => `${name}=${value}\\n`)\n\t\t.join('');\n}\n\n/** A message as a workflow command's data: GitHub reads `%`, CR and LF escaped. */\nfunction workflowCommandData(message: string): string {\n\treturn message\n\t\t.replaceAll('%', '%25')\n\t\t.replaceAll('\\r', '%0D')\n\t\t.replaceAll('\\n', '%0A');\n}\n\nfunction table(stages: StagesJson): string {\n\tconst rows: [string, string][] = [\n\t\t['STAGE', 'KIND'],\n\t\t[stages.local, 'local'],\n\t\t...stages.deployed.map((name): [string, string] => [\n\t\t\tname,\n\t\t\tstages.protected.includes(name) ? 'deployed, protected' : 'deployed',\n\t\t]),\n\t];\n\tconst width = Math.max(...rows.map(([name]) => name.length));\n\treturn rows\n\t\t.map(([name, kind]) => `${name.padEnd(width)} ${kind}`)\n\t\t.join('\\n');\n}\n\nexport async function stagesCommand(\n\toptions: StagesOptions,\n\tdeps: StagesDeps = {},\n): Promise<void> {\n\tconst env = deps.env ?? process.env;\n\tconst log = deps.log ?? ((line: string) => console.log(line));\n\n\tif (!options.githubOutput) {\n\t\tconst workspace = await loadWorkspaceSettings(deps.cwd);\n\t\tconst stages = stagesJson(workspace.stages);\n\t\tlog(options.json ? JSON.stringify(stages) : table(stages));\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst file = env.GITHUB_OUTPUT;\n\t\tif (!file) throw new GithubOutputNotSet();\n\n\t\tconst workspace = await loadWorkspaceSettings(deps.cwd);\n\t\tconst event = options.event || env.GITHUB_EVENT_NAME;\n\t\tif (!event) throw new GithubEventNotSet();\n\n\t\tconst outputs = githubOutputs(workspace, {\n\t\t\tevent,\n\t\t\t...(options.stage ? { stage: options.stage } : {}),\n\t\t});\n\t\tawait appendFile(file, formatGithubOutputs(outputs));\n\n\t\tlog(`${event}: build ${outputs.build}, deploy ${outputs.deploy}`);\n\t} catch (error) {\n\t\t// Annotated on the run, where whoever started it looks first.\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tlog(`::error title=gkm stages::${workflowCommandData(message)}`);\n\t\tthrow error;\n\t}\n}\n\n/** `--github-output` outside a GitHub Actions step, which sets the file. */\nexport class GithubOutputNotSet extends GkmError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'gkm stages --github-output writes to the file GITHUB_OUTPUT names, and it is not set. Run it in a GitHub Actions step, or set GITHUB_OUTPUT to a file to write to.',\n\t\t);\n\t\tthis.name = 'GithubOutputNotSet';\n\t}\n}\n\n/** No `--event`, and no `GITHUB_EVENT_NAME` to take it from. */\nexport class GithubEventNotSet extends GkmError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'gkm stages --github-output plans for an event, and none was given. Pass --event (push, release, workflow_dispatch), or run it in a GitHub Actions step, which sets GITHUB_EVENT_NAME.',\n\t\t);\n\t\tthis.name = 'GithubEventNotSet';\n\t}\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"stages-BpDXFPGg.cjs","names":["UndeclaredStage","GkmError","PROVIDER_KINDS","stageProvider","stageDnsDomains","loadWorkspaceSettings","appendFile","GkmError"],"sources":["../src/stages/plan.ts","../src/stages/index.ts"],"sourcesContent":["/**\n * Which stages a workflow run builds and deploys, from the event that started\n * it and the project's `stages`.\n *\n * The rules live here, in tested code, rather than in a workflow's YAML or in\n * the action that runs `gkm stages --github-output`: a project's deploy\n * workflow names no stage, and what it does for each event is decided by the\n * CLI the project installed.\n *\n * | event | build | deploy |\n * | ------------------- | ------------------ | ------------------------------- |\n * | `push` | every deployed | deployed, less the protected |\n * | `release` | none | the protected |\n * | `workflow_dispatch` | none | the one stage named |\n * | anything else | none | none |\n *\n * A push builds every deployed stage, protected ones included, so a release\n * deploys images built from the very commit its tag names; a release and a\n * manual run deploy what a push already built.\n */\n\nimport { GkmError } from '../errors';\nimport { UndeclaredStage } from '../workspace/stages.js';\nimport type { StagesConfig } from '../workspace/types.js';\n\nexport interface StagePlan {\n\t/** The stages whose images this run builds and pushes. */\n\tbuild: string[];\n\t/** The stages this run deploys. */\n\tdeploy: string[];\n}\n\nexport interface PlanInput {\n\t/** The GitHub event name: `push`, `release`, `workflow_dispatch`, … */\n\tevent: string;\n\t/** The stage a `workflow_dispatch` names. */\n\tstage?: string;\n}\n\nexport function planStages(stages: StagesConfig, input: PlanInput): StagePlan {\n\tconst kept = stages.protected ?? [];\n\n\tswitch (input.event) {\n\t\tcase 'push':\n\t\t\treturn {\n\t\t\t\tbuild: [...stages.deployed],\n\t\t\t\tdeploy: stages.deployed.filter((stage) => !kept.includes(stage)),\n\t\t\t};\n\t\tcase 'release':\n\t\t\treturn { build: [], deploy: [...kept] };\n\t\tcase 'workflow_dispatch': {\n\t\t\tconst stage = input.stage?.trim();\n\t\t\tif (!stage) throw new DispatchNamesNoStage(stages.deployed);\n\t\t\tif (!stages.deployed.includes(stage)) {\n\t\t\t\tthrow new UndeclaredStage(stage, stages.deployed);\n\t\t\t}\n\t\t\treturn { build: [], deploy: [stage] };\n\t\t}\n\t\tdefault:\n\t\t\treturn { build: [], deploy: [] };\n\t}\n}\n\n/**\n * A manual run with no stage. Refused rather than read as \"nothing to do\", so\n * a run started with the field left empty fails where it can be seen.\n */\nexport class DispatchNamesNoStage extends GkmError {\n\tconstructor(readonly deployed: readonly string[]) {\n\t\tsuper(\n\t\t\t`A manual run deploys one stage, and none was given. Run the workflow again with stage set to one of: ${deployed.join(', ') || 'nothing yet — gkm.config.ts deploys to no stage'}.`,\n\t\t);\n\t\tthis.name = 'DispatchNamesNoStage';\n\t}\n}\n","/**\n * `gkm stages` — the workspace's stages, as gkm.config.ts declares them.\n *\n * ```bash\n * gkm stages # a table\n * gkm stages --json # {\"local\":\"dev\",\"deployed\":[\"prod\"],\"protected\":[\"prod\"]}\n * gkm stages --github-output # a workflow run's build and deploy matrices\n * ```\n *\n * The config is read by the normal loader (and sandbox), so a workflow never\n * parses TypeScript to learn which stages exist. `--github-output` is what the\n * `geekmidas/toolbox/actions/stages` action runs: it writes the stages, and\n * which of them this run builds and deploys, to `$GITHUB_OUTPUT`.\n */\n\nimport { appendFile } from 'node:fs/promises';\nimport { stageDnsDomains } from '../compose/dns.js';\nimport { loadWorkspaceSettings } from '../config.js';\nimport { GkmError } from '../errors';\nimport { stageProvider } from '../providers/config.js';\nimport { PROVIDER_KINDS } from '../providers/types.js';\nimport type { NormalizedWorkspace, StagesConfig } from '../workspace/types.js';\nimport { planStages } from './plan.js';\n\nexport { DispatchNamesNoStage, type PlanInput, planStages } from './plan.js';\n\nexport interface StagesOptions {\n\t/** Print the stages as JSON. */\n\tjson?: boolean;\n\t/** Write the outputs a deploy workflow reads to `$GITHUB_OUTPUT`. */\n\tgithubOutput?: boolean;\n\t/** The event to plan for; defaults to `GITHUB_EVENT_NAME`. */\n\tevent?: string;\n\t/** The stage a `workflow_dispatch` names. */\n\tstage?: string;\n}\n\nexport interface StagesDeps {\n\tcwd?: string;\n\tenv?: NodeJS.ProcessEnv;\n\t/** Where lines go; stdout by default. */\n\tlog?: (line: string) => void;\n}\n\n/** The stages, as `gkm stages --json` prints them. */\nexport interface StagesJson {\n\tlocal: string;\n\tdeployed: string[];\n\tprotected: string[];\n}\n\nexport function stagesJson(stages: StagesConfig): StagesJson {\n\treturn {\n\t\tlocal: stages.local,\n\t\tdeployed: [...stages.deployed],\n\t\tprotected: [...(stages.protected ?? [])],\n\t};\n}\n\n/**\n * The region of the workspace's AWS secrets store — `ssm` or\n * `secrets-manager` — or `''` when its deployed stages keep their secrets\n * anywhere else. A job assumes the stage's AWS role only when this is set.\n */\nexport function awsSecretsRegion(workspace: NormalizedWorkspace): string {\n\tconst store = workspace.secrets?.store;\n\tif (typeof store !== 'object' || store === null) return '';\n\tif (store.provider === 'ssm' || store.provider === 'secrets-manager') {\n\t\treturn store.region;\n\t}\n\treturn '';\n}\n\n/**\n * The deployed stages with resources the deploy creates — a provider under\n * `deploy.<kind>.<stage>`, or a domain under a `dns` entry whose provider\n * writes records — and secrets a CI runner can reach (an AWS store): a\n * compose deploy workflow creates their resources on the runner, with the\n * stage's role and DNS token, rather than on the server.\n */\nexport function resourceStages(workspace: NormalizedWorkspace): string[] {\n\tif (!awsSecretsRegion(workspace)) return [];\n\treturn workspace.stages.deployed.filter(\n\t\t(stage) =>\n\t\t\tPROVIDER_KINDS.some(\n\t\t\t\t(kind) => stageProvider(workspace, kind, stage).mode === 'provider',\n\t\t\t) ||\n\t\t\tstageDnsDomains(stage, workspace.domains, workspace.dns).some(\n\t\t\t\t({ config }) => config.provider !== 'manual',\n\t\t\t),\n\t);\n}\n\n/**\n * Every output the stages action sets, each a string `fromJSON()` reads, for\n * one workflow run.\n */\nexport function githubOutputs(\n\tworkspace: NormalizedWorkspace,\n\tinput: { event: string; stage?: string },\n): Record<string, string> {\n\tconst stages = stagesJson(workspace.stages);\n\tconst plan = planStages(workspace.stages, input);\n\n\treturn {\n\t\tlocal: JSON.stringify(stages.local),\n\t\tdeployed: JSON.stringify(stages.deployed),\n\t\tprotected: JSON.stringify(stages.protected),\n\t\tbuild: JSON.stringify(plan.build),\n\t\tdeploy: JSON.stringify(plan.deploy),\n\t\t// A matrix over an empty list is an error on GitHub, so a job over one\n\t\t// is skipped on these instead.\n\t\t'has-build': String(plan.build.length > 0),\n\t\t'has-deploy': String(plan.deploy.length > 0),\n\t\t'aws-region': awsSecretsRegion(workspace),\n\t\tresources: JSON.stringify(resourceStages(workspace)),\n\t};\n}\n\n/** `name=value` lines, as `$GITHUB_OUTPUT` takes single-line values. */\nexport function formatGithubOutputs(outputs: Record<string, string>): string {\n\treturn Object.entries(outputs)\n\t\t.map(([name, value]) => `${name}=${value}\\n`)\n\t\t.join('');\n}\n\n/** A message as a workflow command's data: GitHub reads `%`, CR and LF escaped. */\nfunction workflowCommandData(message: string): string {\n\treturn message\n\t\t.replaceAll('%', '%25')\n\t\t.replaceAll('\\r', '%0D')\n\t\t.replaceAll('\\n', '%0A');\n}\n\nfunction table(stages: StagesJson): string {\n\tconst rows: [string, string][] = [\n\t\t['STAGE', 'KIND'],\n\t\t[stages.local, 'local'],\n\t\t...stages.deployed.map((name): [string, string] => [\n\t\t\tname,\n\t\t\tstages.protected.includes(name) ? 'deployed, protected' : 'deployed',\n\t\t]),\n\t];\n\tconst width = Math.max(...rows.map(([name]) => name.length));\n\treturn rows\n\t\t.map(([name, kind]) => `${name.padEnd(width)} ${kind}`)\n\t\t.join('\\n');\n}\n\nexport async function stagesCommand(\n\toptions: StagesOptions,\n\tdeps: StagesDeps = {},\n): Promise<void> {\n\tconst env = deps.env ?? process.env;\n\tconst log = deps.log ?? ((line: string) => console.log(line));\n\n\tif (!options.githubOutput) {\n\t\tconst workspace = await loadWorkspaceSettings(deps.cwd);\n\t\tconst stages = stagesJson(workspace.stages);\n\t\tlog(options.json ? JSON.stringify(stages) : table(stages));\n\t\treturn;\n\t}\n\n\ttry {\n\t\tconst file = env.GITHUB_OUTPUT;\n\t\tif (!file) throw new GithubOutputNotSet();\n\n\t\tconst workspace = await loadWorkspaceSettings(deps.cwd);\n\t\tconst event = options.event || env.GITHUB_EVENT_NAME;\n\t\tif (!event) throw new GithubEventNotSet();\n\n\t\tconst outputs = githubOutputs(workspace, {\n\t\t\tevent,\n\t\t\t...(options.stage ? { stage: options.stage } : {}),\n\t\t});\n\t\tawait appendFile(file, formatGithubOutputs(outputs));\n\n\t\tlog(`${event}: build ${outputs.build}, deploy ${outputs.deploy}`);\n\t} catch (error) {\n\t\t// Annotated on the run, where whoever started it looks first.\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\tlog(`::error title=gkm stages::${workflowCommandData(message)}`);\n\t\tthrow error;\n\t}\n}\n\n/** `--github-output` outside a GitHub Actions step, which sets the file. */\nexport class GithubOutputNotSet extends GkmError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'gkm stages --github-output writes to the file GITHUB_OUTPUT names, and it is not set. Run it in a GitHub Actions step, or set GITHUB_OUTPUT to a file to write to.',\n\t\t);\n\t\tthis.name = 'GithubOutputNotSet';\n\t}\n}\n\n/** No `--event`, and no `GITHUB_EVENT_NAME` to take it from. */\nexport class GithubEventNotSet extends GkmError {\n\tconstructor() {\n\t\tsuper(\n\t\t\t'gkm stages --github-output plans for an event, and none was given. Pass --event (push, release, workflow_dispatch), or run it in a GitHub Actions step, which sets GITHUB_EVENT_NAME.',\n\t\t);\n\t\tthis.name = 'GithubEventNotSet';\n\t}\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { t as GkmError } from "./errors-D8Gwp5kU.mjs";
2
- import { M as UndeclaredStage, U as stageProvider } from "./workspace-DWK1PTXP.mjs";
2
+ import { M as UndeclaredStage, U as stageProvider } from "./workspace-Bv4xROeu.mjs";
3
3
  import { loadWorkspaceSettings } from "./config.mjs";
4
- import { P as PROVIDER_KINDS } from "./seeds-CoKkrSny.mjs";
5
- import { c as stageDnsDomains } from "./dns-idXfyCay.mjs";
4
+ import { P as PROVIDER_KINDS } from "./seeds-BsJRUL1K.mjs";
5
+ import { c as stageDnsDomains } from "./dns-BbTOzmVp.mjs";
6
6
  import { appendFile } from "node:fs/promises";
7
7
  //#region src/stages/plan.ts
8
8
  /**
@@ -186,4 +186,4 @@ var GithubEventNotSet = class extends GkmError {
186
186
  //#endregion
187
187
  export { stagesCommand };
188
188
 
189
- //# sourceMappingURL=stages-Bt4OggMm.mjs.map
189
+ //# sourceMappingURL=stages-DEmn7qI8.mjs.map