@geekmidas/cli 10.0.0-alpha.76 → 10.0.0-alpha.78

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 (133) hide show
  1. package/dist/{LegacyStateStore-dyq07_Ir.mjs → LegacyStateStore-Ceq7A_75.mjs} +2 -2
  2. package/dist/{LegacyStateStore-dyq07_Ir.mjs.map → LegacyStateStore-Ceq7A_75.mjs.map} +1 -1
  3. package/dist/{LegacyStateStore-DwngfJxr.cjs → LegacyStateStore-Z-EKUGSX.cjs} +2 -2
  4. package/dist/{LegacyStateStore-DwngfJxr.cjs.map → LegacyStateStore-Z-EKUGSX.cjs.map} +1 -1
  5. package/dist/{LocalStateStore-B0NCde8Z.cjs → LocalStateStore-Bocb8dmZ.cjs} +2 -2
  6. package/dist/{LocalStateStore-B0NCde8Z.cjs.map → LocalStateStore-Bocb8dmZ.cjs.map} +1 -1
  7. package/dist/{LocalStateStore-DLxCCatI.mjs → LocalStateStore-C72bwpG2.mjs} +2 -2
  8. package/dist/{LocalStateStore-DLxCCatI.mjs.map → LocalStateStore-C72bwpG2.mjs.map} +1 -1
  9. package/dist/{S3StateStore-BOenb-3o.cjs → S3StateStore-35YfcdL_.cjs} +2 -2
  10. package/dist/{S3StateStore-BOenb-3o.cjs.map → S3StateStore-35YfcdL_.cjs.map} +1 -1
  11. package/dist/{S3StateStore-CGK_4hlo.mjs → S3StateStore-C9upSW5o.mjs} +2 -2
  12. package/dist/{S3StateStore-CGK_4hlo.mjs.map → S3StateStore-C9upSW5o.mjs.map} +1 -1
  13. package/dist/{SSMStateStore-DYeD__7p.cjs → SSMStateStore-W-Ps22AQ.cjs} +2 -2
  14. package/dist/{SSMStateStore-DYeD__7p.cjs.map → SSMStateStore-W-Ps22AQ.cjs.map} +1 -1
  15. package/dist/{SSMStateStore-B_5_RqY-.mjs → SSMStateStore-dOmFO6g1.mjs} +2 -2
  16. package/dist/{SSMStateStore-B_5_RqY-.mjs.map → SSMStateStore-dOmFO6g1.mjs.map} +1 -1
  17. package/dist/{add-j2yI3cbS.cjs → add-BvOICikp.cjs} +8 -8
  18. package/dist/{add-j2yI3cbS.cjs.map → add-BvOICikp.cjs.map} +1 -1
  19. package/dist/{add-B7IMXzou.mjs → add-D2XKszYP.mjs} +3 -3
  20. package/dist/{add-B7IMXzou.mjs.map → add-D2XKszYP.mjs.map} +1 -1
  21. package/dist/{bundler-Cf6OgaOe.cjs → bundler-C6T-1OLT.cjs} +5 -5
  22. package/dist/{bundler-Cf6OgaOe.cjs.map → bundler-C6T-1OLT.cjs.map} +1 -1
  23. package/dist/{bundler-DO6bHhnb.mjs → bundler-CtHzKqnZ.mjs} +5 -5
  24. package/dist/{bundler-DO6bHhnb.mjs.map → bundler-CtHzKqnZ.mjs.map} +1 -1
  25. package/dist/{cli-BCPOHmg9.mjs → cli-BgPyhx1i.mjs} +3 -3
  26. package/dist/{cli-BCPOHmg9.mjs.map → cli-BgPyhx1i.mjs.map} +1 -1
  27. package/dist/{cli-CNanUB7a.cjs → cli-K6Vj_pPW.cjs} +4 -4
  28. package/dist/{cli-CNanUB7a.cjs.map → cli-K6Vj_pPW.cjs.map} +1 -1
  29. package/dist/{compose-h_6oNnHC.cjs → compose-B7MC19eC.cjs} +668 -110
  30. package/dist/compose-B7MC19eC.cjs.map +1 -0
  31. package/dist/{compose-Dq4VCtu5.mjs → compose-DByK353T.mjs} +606 -66
  32. package/dist/compose-DByK353T.mjs.map +1 -0
  33. package/dist/{config-CqAvPP-f.d.mts → config-Bql0AmJt.d.mts} +3 -3
  34. package/dist/{config-CWyhLSY1.d.cts.map → config-Bql0AmJt.d.mts.map} +1 -1
  35. package/dist/{config-CWyhLSY1.d.cts → config-DlNpHK8p.d.cts} +3 -3
  36. package/dist/{config-CqAvPP-f.d.mts.map → config-DlNpHK8p.d.cts.map} +1 -1
  37. package/dist/config.cjs +1 -1
  38. package/dist/config.d.cts +3 -3
  39. package/dist/config.d.mts +3 -3
  40. package/dist/config.mjs +1 -1
  41. package/dist/deploy/public.cjs +12 -12
  42. package/dist/deploy/public.d.cts +3 -3
  43. package/dist/deploy/public.d.cts.map +1 -1
  44. package/dist/deploy/public.d.mts +3 -3
  45. package/dist/deploy/public.d.mts.map +1 -1
  46. package/dist/deploy/public.mjs +6 -6
  47. package/dist/{deploy-CVXEx7Nm.mjs → deploy-BerP7ppB.mjs} +5 -5
  48. package/dist/{deploy-CVXEx7Nm.mjs.map → deploy-BerP7ppB.mjs.map} +1 -1
  49. package/dist/{deploy-CT3R9de9.cjs → deploy-Dt42xiQA.cjs} +11 -11
  50. package/dist/{deploy-CT3R9de9.cjs.map → deploy-Dt42xiQA.cjs.map} +1 -1
  51. package/dist/{dokploy-eSGmlhhj.cjs → dokploy-NG9UrVrP.cjs} +2 -2
  52. package/dist/{dokploy-eSGmlhhj.cjs.map → dokploy-NG9UrVrP.cjs.map} +1 -1
  53. package/dist/{dokploy-CYm2ozFs.mjs → dokploy-ssZQRK2C.mjs} +2 -2
  54. package/dist/{dokploy-CYm2ozFs.mjs.map → dokploy-ssZQRK2C.mjs.map} +1 -1
  55. package/dist/{encryption-vS3Qzw73.cjs → encryption-CbMI24fI.cjs} +6 -6
  56. package/dist/{encryption-vS3Qzw73.cjs.map → encryption-CbMI24fI.cjs.map} +1 -1
  57. package/dist/{encryption-DC0rsR1W.mjs → encryption-De6MN0O5.mjs} +3 -3
  58. package/dist/{encryption-DC0rsR1W.mjs.map → encryption-De6MN0O5.mjs.map} +1 -1
  59. package/dist/{engine-C0N2d1i5.cjs → engine-CoCsNoW_.cjs} +73 -68
  60. package/dist/{engine-C0N2d1i5.cjs.map → engine-CoCsNoW_.cjs.map} +1 -1
  61. package/dist/{engine-BOO5TWZd.mjs → engine-DOjGIgl4.mjs} +9 -4
  62. package/dist/{engine-BOO5TWZd.mjs.map → engine-DOjGIgl4.mjs.map} +1 -1
  63. package/dist/{github-CEaFDL6t.mjs → github-CwX9z89E.mjs} +3 -3
  64. package/dist/{github-CEaFDL6t.mjs.map → github-CwX9z89E.mjs.map} +1 -1
  65. package/dist/{github-ClkdCOL9.cjs → github-D9vGqdLk.cjs} +7 -7
  66. package/dist/{github-ClkdCOL9.cjs.map → github-D9vGqdLk.cjs.map} +1 -1
  67. package/dist/{index-DOxmDKqv.d.mts → index-BXO0vlme.d.mts} +2 -2
  68. package/dist/{index-Ccu0f_2m.d.cts.map → index-BXO0vlme.d.mts.map} +1 -1
  69. package/dist/{index-Ccu0f_2m.d.cts → index-Rs0Fua-q.d.cts} +2 -2
  70. package/dist/{index-DOxmDKqv.d.mts.map → index-Rs0Fua-q.d.cts.map} +1 -1
  71. package/dist/index.cjs +180 -219
  72. package/dist/index.cjs.map +1 -1
  73. package/dist/index.mjs +108 -147
  74. package/dist/index.mjs.map +1 -1
  75. package/dist/{migrate-BRbYtFXG.cjs → migrate-B4GBvCNY.cjs} +5 -5
  76. package/dist/{migrate-BRbYtFXG.cjs.map → migrate-B4GBvCNY.cjs.map} +1 -1
  77. package/dist/{migrate-D3NAT7Ns.mjs → migrate-BQ4wVKq_.mjs} +3 -3
  78. package/dist/{migrate-D3NAT7Ns.mjs.map → migrate-BQ4wVKq_.mjs.map} +1 -1
  79. package/dist/{migrate-DrO2p9Rl.mjs → migrate-CNKaQl3K.mjs} +3 -3
  80. package/dist/{migrate-DrO2p9Rl.mjs.map → migrate-CNKaQl3K.mjs.map} +1 -1
  81. package/dist/{migrate-D5_-ylI1.cjs → migrate-Cgoy58Sh.cjs} +8 -8
  82. package/dist/{migrate-D5_-ylI1.cjs.map → migrate-Cgoy58Sh.cjs.map} +1 -1
  83. package/dist/openapi.cjs +1 -1
  84. package/dist/openapi.d.cts +1 -1
  85. package/dist/openapi.d.mts +1 -1
  86. package/dist/openapi.mjs +1 -1
  87. package/dist/reconcile/public.d.cts +1 -1
  88. package/dist/reconcile/public.d.mts +1 -1
  89. package/dist/{resolve-Fa1Ii0BK.cjs → resolve-CEaqRBzY.cjs} +3 -3
  90. package/dist/{resolve-Fa1Ii0BK.cjs.map → resolve-CEaqRBzY.cjs.map} +1 -1
  91. package/dist/{resolve-C1muFos5.mjs → resolve-mvaystiE.mjs} +3 -3
  92. package/dist/{resolve-C1muFos5.mjs.map → resolve-mvaystiE.mjs.map} +1 -1
  93. package/dist/{rollback-C9N5lKVf.cjs → rollback-F3P-rA-r.cjs} +7 -7
  94. package/dist/{rollback-C9N5lKVf.cjs.map → rollback-F3P-rA-r.cjs.map} +1 -1
  95. package/dist/{rollback-DeQzbRqM.mjs → rollback-S2C-Y9pB.mjs} +5 -5
  96. package/dist/{rollback-DeQzbRqM.mjs.map → rollback-S2C-Y9pB.mjs.map} +1 -1
  97. package/dist/{runtime-Cm7QA53H.d.cts → runtime-D9AxGzrw.d.cts} +2 -2
  98. package/dist/{runtime-BSvzPqXW.d.mts.map → runtime-D9AxGzrw.d.cts.map} +1 -1
  99. package/dist/{runtime-BSvzPqXW.d.mts → runtime-Dflki3Vs.d.mts} +2 -2
  100. package/dist/{runtime-Cm7QA53H.d.cts.map → runtime-Dflki3Vs.d.mts.map} +1 -1
  101. package/dist/{sniffer-Y6GQOCXB.cjs → sniffer-BjG-1mlD.cjs} +6 -6
  102. package/dist/{sniffer-Y6GQOCXB.cjs.map → sniffer-BjG-1mlD.cjs.map} +1 -1
  103. package/dist/{sniffer-DR8AlRCk.mjs → sniffer-ByWtH28i.mjs} +6 -6
  104. package/dist/{sniffer-DR8AlRCk.mjs.map → sniffer-ByWtH28i.mjs.map} +1 -1
  105. package/dist/{state-C2qi0iQw.cjs → stale-BsiPJI7Y.cjs} +111 -25
  106. package/dist/stale-BsiPJI7Y.cjs.map +1 -0
  107. package/dist/{state-CUecOCNJ.mjs → stale-C5FqIVc8.mjs} +106 -26
  108. package/dist/stale-C5FqIVc8.mjs.map +1 -0
  109. package/dist/target/public.cjs +1 -1
  110. package/dist/target/public.d.cts +2 -2
  111. package/dist/target/public.d.mts +2 -2
  112. package/dist/target/public.mjs +1 -1
  113. package/dist/{types-BdptGBFS.d.cts → types-ByY0zi3n.d.cts} +42 -3
  114. package/dist/types-ByY0zi3n.d.cts.map +1 -0
  115. package/dist/{types-OxE0OAn2.d.mts → types-DKxkXsKC.d.mts} +42 -3
  116. package/dist/types-DKxkXsKC.d.mts.map +1 -0
  117. package/dist/workspace/index.cjs +1 -1
  118. package/dist/workspace/index.d.cts +2 -2
  119. package/dist/workspace/index.d.mts +2 -2
  120. package/dist/workspace/index.mjs +1 -1
  121. package/dist/{workspace-QUTqYmVy.mjs → workspace-BBdqmkch.mjs} +114 -17
  122. package/dist/workspace-BBdqmkch.mjs.map +1 -0
  123. package/dist/{workspace-DOm81oUo.cjs → workspace-Bp8FEkF-.cjs} +130 -15
  124. package/dist/workspace-Bp8FEkF-.cjs.map +1 -0
  125. package/package.json +15 -15
  126. package/dist/compose-Dq4VCtu5.mjs.map +0 -1
  127. package/dist/compose-h_6oNnHC.cjs.map +0 -1
  128. package/dist/state-C2qi0iQw.cjs.map +0 -1
  129. package/dist/state-CUecOCNJ.mjs.map +0 -1
  130. package/dist/types-BdptGBFS.d.cts.map +0 -1
  131. package/dist/types-OxE0OAn2.d.mts.map +0 -1
  132. package/dist/workspace-DOm81oUo.cjs.map +0 -1
  133. package/dist/workspace-QUTqYmVy.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"state-CUecOCNJ.mjs","names":["packageName","logger","DEFAULT_EVENTS","LOCAL_USER","LOCAL_TOKEN","LOCAL_USER","run"],"sources":["../src/secrets/storage.ts","../src/deploy/identity.ts","../src/secrets/keystore.ts","../src/secrets/file.ts","../src/secrets/store.ts","../src/credentials/index.ts","../src/reconcile/containers.ts","../src/workspace/backends.ts","../src/exec/imageBuild.ts","../src/docker/templates.ts","../src/docker/layout.ts","../src/reconcile/caddyfile.ts","../src/reconcile/emulator.ts","../src/workspace/rootSite.ts","../src/reconcile/plan.ts","../src/reconcile/env.ts","../src/reconcile/apps.ts","../src/reconcile/clients.ts","../src/reconcile/compose.ts","../src/reconcile/docker.ts","../src/reconcile/lan.ts","../src/reconcile/ports.ts","../src/reconcile/provision.ts","../src/reconcile/state.ts","../src/reconcile/index.ts","../src/reconcile/workspace.ts","../src/generators/CronGenerator.ts","../src/generators/QueueGenerator.ts","../src/generators/WorkerGenerator.ts","../src/build/workers.ts","../src/deploy/devServices.ts","../src/deploy/otel.ts","../src/target/dokploy/domain.ts","../src/docker/imageRef.ts","../src/docker/index.ts","../src/deploy/generated.ts","../src/deploy/journal.ts","../src/deploy/state.ts"],"sourcesContent":["/**\n * What a stage's secrets are, independent of where they are kept: building\n * them, changing them, and handing them to a process. Where they are kept is a\n * {@link SecretsStore}'s business — see `secretsStoreFor`.\n */\n\nimport type { SecretsStore } from './store.js';\nimport type { EmbeddableSecrets, StageSecrets } from './types';\n\n/**\n * Initialize an empty StageSecrets object for a stage.\n */\nexport function initStageSecrets(stage: string): StageSecrets {\n\tconst now = new Date().toISOString();\n\treturn {\n\t\tstage,\n\t\tcreatedAt: now,\n\t\tupdatedAt: now,\n\t\tservices: {},\n\t\turls: {},\n\t\tcustom: {},\n\t};\n}\n\n/**\n * Convert StageSecrets to embeddable format (flat key-value pairs).\n * This is what gets encrypted and embedded in the bundle.\n */\nexport function toEmbeddableSecrets(secrets: StageSecrets): EmbeddableSecrets {\n\treturn {\n\t\t...secrets.urls,\n\t\t...secrets.custom,\n\t\t// Also include individual service credentials if needed\n\t\t...(secrets.services.postgres && {\n\t\t\tPOSTGRES_USER: secrets.services.postgres.username,\n\t\t\tPOSTGRES_PASSWORD: secrets.services.postgres.password,\n\t\t\tPOSTGRES_DB: secrets.services.postgres.database ?? 'app',\n\t\t\tPOSTGRES_HOST: secrets.services.postgres.host,\n\t\t\tPOSTGRES_PORT: String(secrets.services.postgres.port),\n\t\t}),\n\t\t...(secrets.services.redis && {\n\t\t\tREDIS_PASSWORD: secrets.services.redis.password,\n\t\t\tREDIS_HOST: secrets.services.redis.host,\n\t\t\tREDIS_PORT: String(secrets.services.redis.port),\n\t\t}),\n\t\t...(secrets.services.minio && {\n\t\t\tSTORAGE_ACCESS_KEY_ID: secrets.services.minio.username,\n\t\t\tSTORAGE_SECRET_ACCESS_KEY: secrets.services.minio.password,\n\t\t\tSTORAGE_BUCKET: secrets.services.minio.bucket ?? 'app',\n\t\t\tSTORAGE_REGION: 'eu-west-1',\n\t\t\tSTORAGE_FORCE_PATH_STYLE: 'true',\n\t\t}),\n\t\t...(secrets.services.mailpit && {\n\t\t\tSMTP_HOST: secrets.services.mailpit.host,\n\t\t\tSMTP_PORT: String(secrets.services.mailpit.port),\n\t\t\tSMTP_USER: secrets.services.mailpit.username,\n\t\t\tSMTP_PASS: secrets.services.mailpit.password,\n\t\t\tSMTP_SECURE: 'false',\n\t\t\tMAIL_FROM: 'noreply@localhost',\n\t\t}),\n\t\t...(secrets.services.localstack && {\n\t\t\tAWS_ACCESS_KEY_ID:\n\t\t\t\tsecrets.services.localstack.accessKeyId ??\n\t\t\t\tsecrets.services.localstack.username,\n\t\t\tAWS_SECRET_ACCESS_KEY: secrets.services.localstack.password,\n\t\t\tAWS_REGION: secrets.services.localstack.region ?? 'us-east-1',\n\t\t\tAWS_ENDPOINT_URL: `http://${secrets.services.localstack.host}:${secrets.services.localstack.port}`,\n\t\t}),\n\t\t...(secrets.services.pgboss && {\n\t\t\tPGBOSS_DB_USER: secrets.services.pgboss.username,\n\t\t\tPGBOSS_DB_PASSWORD: secrets.services.pgboss.password,\n\t\t}),\n\t};\n}\n\n/** The same secrets with one custom value set, and `updatedAt` moved on. */\nexport function withCustomSecret(\n\tsecrets: StageSecrets,\n\tkey: string,\n\tvalue: string,\n): StageSecrets {\n\treturn {\n\t\t...secrets,\n\t\tupdatedAt: new Date().toISOString(),\n\t\tcustom: { ...secrets.custom, [key]: value },\n\t};\n}\n\n/**\n * Mask a password for display (show first 4 and last 2 chars).\n */\nexport function maskPassword(password: string): string {\n\tif (password.length <= 8) {\n\t\treturn '********';\n\t}\n\treturn `${password.slice(0, 4)}${'*'.repeat(password.length - 6)}${password.slice(-2)}`;\n}\n\n/**\n * Result of environment variable validation.\n */\nexport interface EnvValidationResult {\n\t/** Whether all required environment variables are present */\n\tvalid: boolean;\n\t/** List of missing environment variable names */\n\tmissing: string[];\n\t/** List of environment variables that are provided */\n\tprovided: string[];\n\t/** List of environment variables that were required */\n\trequired: string[];\n}\n\n/**\n * Validate that all required environment variables are present in secrets.\n *\n * @param requiredVars - Array of environment variable names required by the application\n * @param secrets - Stage secrets to validate against\n * @returns Validation result with missing and provided variables\n *\n * @example\n * ```typescript\n * const required = ['DATABASE_URL', 'API_KEY', 'JWT_SECRET'];\n * const secrets = await secretsStoreFor(workspace, 'production').then((s) => s.read('production'));\n * const result = validateEnvironmentVariables(required, secrets);\n *\n * if (!result.valid) {\n * console.error(`Missing environment variables: ${result.missing.join(', ')}`);\n * }\n * ```\n */\nexport function validateEnvironmentVariables(\n\trequiredVars: string[],\n\tsecrets: StageSecrets,\n): EnvValidationResult {\n\tconst embeddable = toEmbeddableSecrets(secrets);\n\tconst availableVars = new Set(Object.keys(embeddable));\n\n\tconst missing: string[] = [];\n\tconst provided: string[] = [];\n\n\tfor (const varName of requiredVars) {\n\t\tif (availableVars.has(varName)) {\n\t\t\tprovided.push(varName);\n\t\t} else {\n\t\t\tmissing.push(varName);\n\t\t}\n\t}\n\n\treturn {\n\t\tvalid: missing.length === 0,\n\t\tmissing: missing.sort(),\n\t\tprovided: provided.sort(),\n\t\trequired: [...requiredVars].sort(),\n\t};\n}\n","/**\n * Who a deploy is, on a target it shares with other workspaces.\n *\n * A Dokploy server is one flat namespace of projects, images and Docker\n * service names. Deploys used to identify everything they made by the\n * workspace name alone, so two workspaces called `shop` — or `Shop` and\n * `shop` — deploying to one server landed in the same project, pushed over\n * each other's images and redeployed each other's applications. A\n * `DeployIdentity` is what a deploy names and claims its resources by instead.\n */\n\nimport { kebabCase, scopedName } from '@geekmidas/manifest';\n\nexport interface DeployIdentity {\n\t/**\n\t * Whose deploy this is — an organisation, a team, a repository. Two\n\t * workspaces with the same name are told apart by it. Defaults to the\n\t * kebab-cased workspace name; set `deploy.namespace` to choose one.\n\t */\n\treadonly namespace: string;\n\t/** The workspace name as the target spells it: lowercase, `[a-z0-9-]`. */\n\treadonly project: string;\n\treadonly stage: string;\n\t/**\n\t * `<namespace>/<project>`: what a Dokploy project is claimed by.\n\t *\n\t * The stage is not part of it because one Dokploy project holds every stage\n\t * of a workspace, each as an environment — the key has to be the same from\n\t * every stage that deploys into it.\n\t */\n\treadonly key: string;\n\t/**\n\t * The prefix every name on the target carries: the project alone in the\n\t * default namespace, `<namespace>-<project>` in any other.\n\t *\n\t * The default adds nothing so that a workspace that never set a namespace\n\t * keeps the names it was already deployed under — a Postgres is found by\n\t * its name, and a new name would be a new, empty database.\n\t */\n\treadonly scope: string;\n}\n\n/** A namespace that cannot be part of a Dokploy name or an image path. */\nexport class DeployNamespaceInvalid extends Error {\n\tconstructor(readonly namespace: string) {\n\t\tsuper(\n\t\t\t`deploy.namespace '${namespace}' is not usable as a name: use lowercase letters, digits and single '-' between them (e.g. 'acme' or 'acme-platform') in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'DeployNamespaceInvalid';\n\t}\n}\n\n/** A workspace name with nothing a target can name a resource by. */\nexport class DeployProjectNameInvalid extends Error {\n\tconstructor(readonly workspaceName: string) {\n\t\tsuper(\n\t\t\t`The workspace name '${workspaceName}' has no letters or digits to name deployed resources by. Set \"name\" in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'DeployProjectNameInvalid';\n\t}\n}\n\nconst NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * Lowercase `[a-z0-9-]`, runs of anything else collapsed to one `-`: valid as\n * a Docker image path component, a Docker service name and a Dokploy name.\n */\nfunction slug(value: string): string {\n\treturn value\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t.replace(/^-+|-+$/g, '');\n}\n\n/** The namespace a workspace is in when it does not name one. */\nexport function defaultNamespace(workspaceName: string): string {\n\treturn slug(kebabCase(workspaceName));\n}\n\n/** What an identity is derived from: a workspace's name and namespace. */\nexport interface IdentitySource {\n\tname: string;\n\tdeploy?: { namespace?: string };\n}\n\nexport function deployIdentity(\n\tworkspace: IdentitySource,\n\tstage: string,\n): DeployIdentity {\n\t// Lowercased rather than kebab-cased: the names a workspace was already\n\t// deployed under were `${stage}-${name}`.toLowerCase(), and this has to\n\t// reproduce them for the databases it finds by name.\n\tconst project = slug(workspace.name);\n\tif (!project) throw new DeployProjectNameInvalid(workspace.name);\n\n\tconst fallback = defaultNamespace(workspace.name);\n\tconst namespace = workspace.deploy?.namespace ?? fallback;\n\tif (!NAME.test(namespace)) throw new DeployNamespaceInvalid(namespace);\n\n\treturn {\n\t\tnamespace,\n\t\tproject,\n\t\tstage,\n\t\tkey: `${namespace}/${project}`,\n\t\tscope: namespace === fallback ? project : `${namespace}-${project}`,\n\t};\n}\n\n/**\n * The token a Dokploy project's description carries to say which identity\n * created it. A project with the right name and no marker belongs to someone\n * else, and a deploy never adopts it.\n */\nexport function ownershipMarker(identity: DeployIdentity): string {\n\treturn `gkm:${identity.key}`;\n}\n\n/** Whether a project description carries `identity`'s marker. */\nexport function isOwnedBy(\n\tdescription: string | null | undefined,\n\tidentity: DeployIdentity,\n): boolean {\n\tconst marker = ownershipMarker(identity);\n\treturn (description ?? '').split(/\\s+/).includes(marker);\n}\n\n/** Any `gkm:` marker a description carries — whoever's it is. */\nexport function markerOf(\n\tdescription: string | null | undefined,\n): string | undefined {\n\treturn (description ?? '').split(/\\s+/).find((t) => t.startsWith('gkm:'));\n}\n\n/** What the Dokploy project is called. */\nexport function projectName(identity: DeployIdentity): string {\n\treturn identity.scope;\n}\n\n/**\n * The repository an app's image is pushed to, below the registry:\n * `<namespace>/<project>-<app>`.\n *\n * The namespace is a path segment of its own so that a registry that maps\n * paths to owners (an organisation's packages, a Harbor project) can grant it\n * as one; the app is joined to the project so the two read as one name.\n */\nexport function imageName(identity: DeployIdentity, app: string): string {\n\treturn `${identity.namespace}/${identity.project}-${slug(kebabCase(app))}`;\n}\n\n/** The full image ref an app is built, pushed and deployed as. */\nexport function imageRef(\n\tidentity: DeployIdentity,\n\tapp: string,\n\tregistry: string | undefined,\n\ttag: string,\n): string {\n\tconst name = imageName(identity, app);\n\treturn registry\n\t\t? `${registry.replace(/\\/+$/, '')}/${name}:${tag}`\n\t\t: `${name}:${tag}`;\n}\n\n/**\n * What one application is called on the target.\n *\n * The same rule the constructs use, through the same `scopedName`, scoped by\n * the stage and the identity: the application beside\n * `production-kitchen-sink-database` is `production-kitchen-sink-api`. A\n * Dokploy application's name is also its Docker service name, which is unique\n * on the whole server, so the namespace has to be in it too — `acme`'s and\n * `globex`'s `shop` would otherwise both run `production-shop-api`.\n *\n * The app id is dropped when it repeats the project, so a project named for its\n * one application is `production-kitchen-sink` rather than\n * `production-kitchen-sink-kitchen-sink`.\n */\nexport function applicationName(identity: DeployIdentity, app: string): string {\n\treturn slug(app) === identity.project\n\t\t? `${identity.stage}-${identity.scope}`\n\t\t: scopedName([identity.stage, identity.scope], app);\n}\n","/**\n * The keys that open each stage's encrypted secrets file, kept outside the\n * project in the CLI's home.\n *\n * Keys were kept at `~/.gkm/<folder>/<stage>.key`, named after the project\n * directory — so two checkouts in folders with the same name (`~/work/api`\n * and `~/oss/api`) shared one key, and the second `gkm secrets:init`\n * overwrote the first project's. They are now kept under the project's\n * identity, `<home>/keys/<namespace>/<project>/<stage>.key`, the same\n * `<namespace>/<project>` a deploy claims its Dokploy project by. A key still\n * at the old place is copied to the new one the first time it is read; the\n * old file stays, so an older CLI on the same machine keeps working.\n */\n\nimport { randomBytes } from 'node:crypto';\nimport { constants, existsSync } from 'node:fs';\nimport {\n\tchmod,\n\tcopyFile,\n\tmkdir,\n\treadFile,\n\trm,\n\twriteFile,\n} from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { deployIdentity, type IdentitySource } from '../deploy/identity';\nimport { gkmHome } from '../home';\n\n/** Key length for AES-256 encryption */\nconst KEY_LENGTH = 32; // 256 bits\n\n/** Whose keys, and where the CLI's home is. */\nexport interface KeystoreProject {\n\t/** `<namespace>/<project>`, as `DeployIdentity.key` spells it. */\n\tkey: string;\n\t/**\n\t * The names the project's keys were kept under, `~/.gkm/<name>/`, before\n\t * they were keyed by identity: its directory's basename, which the secrets\n\t * file used, and its workspace name, which `gkm init` and the generated CI\n\t * workflow wrote to. Looked in, and copied from, when the project has no\n\t * key of its own yet.\n\t */\n\tlegacy?: readonly string[];\n\t/**\n\t * Other project keys this project's keys may be copied from when it has\n\t * none: the default namespace's, for a workspace that has just set\n\t * `deploy.namespace` — the secrets file in the checkout is the same one.\n\t */\n\taliases?: string[];\n\t/** The CLI's home. Defaults to `GKM_HOME`, else `~/.gkm`. */\n\thome?: string;\n}\n\n/** A keystore project key that is not `<namespace>/<project>`. */\nexport class KeystoreProjectInvalid extends Error {\n\tconstructor(readonly key: string) {\n\t\tsuper(\n\t\t\t`'${key}' is not a keystore project: expected '<namespace>/<project>' in lowercase letters, digits and '-', as deployIdentity() produces. Pass the workspace's identity key.`,\n\t\t);\n\t\tthis.name = 'KeystoreProjectInvalid';\n\t}\n}\n\n/** A stage has no key, here or at the place keys used to be kept. */\nexport class KeyNotFound extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly project: string,\n\t\treadonly path: string,\n\t) {\n\t\tsuper(\n\t\t\t`Encryption key not found for stage \"${stage}\" in project \"${project}\". ` +\n\t\t\t\t`Expected key at: ${path}. Run \\`gkm secrets:init --stage ${stage}\\` to create one, or copy the stage's key there.`,\n\t\t);\n\t\tthis.name = 'KeyNotFound';\n\t}\n}\n\nconst PROJECT_KEY = /^[a-z0-9]+(?:-[a-z0-9]+)*\\/[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * The keystore key of a workspace: its deploy identity without the stage, so\n * a project's keys and its Dokploy project are named by the same thing.\n */\nexport function projectKey(workspace: IdentitySource): string {\n\treturn deployIdentity(workspace, '').key;\n}\n\n/** The keystore project of a loaded workspace. */\nexport function keystoreProject(\n\tworkspace: IdentitySource & { root: string },\n\thome?: string,\n): KeystoreProject {\n\tconst key = projectKey(workspace);\n\tconst unscoped = projectKey({ name: workspace.name });\n\tconst legacy = [\n\t\t...new Set([folderName(workspace.root), workspace.name]),\n\t].filter((name): name is string => !!name);\n\treturn {\n\t\tkey,\n\t\tlegacy,\n\t\t...(unscoped !== key ? { aliases: [unscoped] } : {}),\n\t\t...(home ? { home } : {}),\n\t};\n}\n\n/**\n * Whether `name` stays inside the home it is joined to. A workspace name was\n * joined as is — `@acme/shop` is `~/.gkm/@acme/shop` — but never upwards.\n */\nfunction isLegacyName(name: string): boolean {\n\treturn name.split(/[\\\\/]/).every((part) => part !== '..' && part !== '');\n}\n\n/** The last segment of a path, as the old keystore named projects. */\nfunction folderName(path: string): string | undefined {\n\tconst name = path.split(/[\\\\/]/).filter(Boolean).at(-1);\n\treturn name && name !== '.' && name !== '..' ? name : undefined;\n}\n\n/**\n * Get the keystore directory for a project:\n * `<home>/keys/<namespace>/<project>`.\n */\nexport function getKeystoreDir(project: KeystoreProject): string {\n\tif (!PROJECT_KEY.test(project.key)) {\n\t\tthrow new KeystoreProjectInvalid(project.key);\n\t}\n\tconst [namespace, name] = project.key.split('/') as [string, string];\n\treturn join(project.home ?? gkmHome(), 'keys', namespace, name);\n}\n\n/** Get the path to a stage's encryption key. */\nexport function getKeyPath(stage: string, project: KeystoreProject): string {\n\treturn join(getKeystoreDir(project), `${stage}.key`);\n}\n\n/**\n * Where else a stage's key may be, most likely first: under an alias of the\n * project, then where keys were kept before they were keyed by identity —\n * under the configured home, then under `~/.gkm`, which is where every CLI\n * before `GKM_HOME` wrote.\n */\nfunction legacyKeyPaths(stage: string, project: KeystoreProject): string[] {\n\tconst aliases = (project.aliases ?? []).map((key) =>\n\t\tgetKeyPath(stage, { key, home: project.home }),\n\t);\n\tconst homes = [\n\t\t...new Set([project.home ?? gkmHome(), join(homedir(), '.gkm')]),\n\t];\n\tconst names = (project.legacy ?? []).filter(isLegacyName);\n\treturn [\n\t\t...aliases,\n\t\t...names.flatMap((name) =>\n\t\t\thomes.map((home) => join(home, name, `${stage}.key`)),\n\t\t),\n\t];\n}\n\n/** Check if a key exists for a stage, here or where it used to be kept. */\nexport function keyExists(stage: string, project: KeystoreProject): boolean {\n\treturn (\n\t\texistsSync(getKeyPath(stage, project)) ||\n\t\tlegacyKeyPaths(stage, project).some((path) => existsSync(path))\n\t);\n}\n\n/**\n * Generate a new encryption key for a stage, stored owner-only at\n * `<home>/keys/<namespace>/<project>/<stage>.key`.\n */\nexport async function generateKey(\n\tstage: string,\n\tproject: KeystoreProject,\n\t/** `wx` to refuse replacing a key that is already there. */\n\tflag: 'w' | 'wx' = 'w',\n): Promise<string> {\n\tconst keyDir = getKeystoreDir(project);\n\tconst keyPath = getKeyPath(stage, project);\n\n\t// Ensure keystore directory exists with restricted permissions\n\tawait mkdir(keyDir, { recursive: true, mode: 0o700 });\n\n\t// Generate random key\n\tconst key = randomBytes(KEY_LENGTH).toString('hex');\n\n\t// Write key with restricted permissions (owner read/write only)\n\tawait writeFile(keyPath, key, { mode: 0o600, encoding: 'utf-8', flag });\n\n\t// Ensure permissions are set correctly (in case file existed)\n\tawait chmod(keyPath, 0o600);\n\n\treturn key;\n}\n\n/**\n * Copy a key from where it used to be kept to where it is kept now.\n *\n * Copied, not moved: an older CLI on the same machine still reads the old\n * place. Exclusive, so two processes migrating at once cannot overwrite a key\n * one of them already put there.\n */\nasync function migrateLegacyKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<boolean> {\n\tconst legacy = legacyKeyPaths(stage, project).find((path) =>\n\t\texistsSync(path),\n\t);\n\tif (!legacy) return false;\n\n\tconst keyPath = getKeyPath(stage, project);\n\tawait mkdir(getKeystoreDir(project), { recursive: true, mode: 0o700 });\n\ttry {\n\t\tawait copyFile(legacy, keyPath, constants.COPYFILE_EXCL);\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n\t}\n\tawait chmod(keyPath, 0o600);\n\treturn true;\n}\n\n/**\n * Read an encryption key for a stage, or `null` when there is none — after\n * copying it over from where keys used to be kept, if it is still there.\n */\nexport async function readKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string | null> {\n\tconst keyPath = getKeyPath(stage, project);\n\n\tif (!existsSync(keyPath) && !(await migrateLegacyKey(stage, project))) {\n\t\treturn null;\n\t}\n\n\tconst key = await readFile(keyPath, 'utf-8');\n\treturn key.trim();\n}\n\n/**\n * Read an encryption key for a stage, throwing {@link KeyNotFound} if there\n * is none.\n */\nexport async function requireKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string> {\n\tconst key = await readKey(stage, project);\n\n\tif (!key) {\n\t\tthrow new KeyNotFound(stage, project.key, getKeyPath(stage, project));\n\t}\n\n\treturn key;\n}\n\n/**\n * Delete a key for a stage. Only the current one: a key left at the old place\n * belongs to whichever older CLI still reads it.\n */\nexport async function deleteKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<void> {\n\tawait rm(getKeyPath(stage, project), { force: true });\n}\n\n/**\n * Get or create a key for a stage.\n * If the key already exists, it is returned. Otherwise, a new key is generated.\n */\nexport async function getOrCreateKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string> {\n\tconst existingKey = await readKey(stage, project);\n\n\tif (existingKey) {\n\t\treturn existingKey;\n\t}\n\n\t// Exclusively: a key is now shared by every checkout of one project, and\n\t// two commands creating it at once must not each keep a different one.\n\ttry {\n\t\treturn await generateKey(stage, project, 'wx');\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n\t\treturn requireKey(stage, project);\n\t}\n}\n","import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport {\n\tgetKeyPath,\n\tgetOrCreateKey,\n\ttype KeystoreProject,\n\tprojectKey,\n\treadKey,\n} from './keystore';\nimport type { SecretsStore } from './store.js';\nimport type { StageSecrets } from './types';\n\n/** Where the files live, relative to the project root. */\nconst SECRETS_DIR = '.gkm/secrets';\n\n/** AES-256-GCM configuration */\nconst ALGORITHM = 'aes-256-gcm';\nconst IV_LENGTH = 12; // 96 bits for GCM\nconst AUTH_TAG_LENGTH = 16; // 128 bits\n\n/** Encrypted secrets file structure */\ninterface EncryptedSecretsFile {\n\t/** Version for future format changes */\n\tversion: 1;\n\t/** Base64 encoded encrypted data (ciphertext + auth tag) */\n\tencrypted: string;\n\t/** Hex encoded IV */\n\tiv: string;\n}\n\n/**\n * A stage's secrets as an encrypted file on this machine:\n * `.gkm/secrets/<stage>.json`, with its key in the CLI's home at\n * `keys/<namespace>/<project>/<stage>.key`.\n *\n * The local stage's store, always — its secrets belong to the machine running\n * `gkm dev` — and a deployed stage's when `secrets.store` is `'file'`.\n */\nexport class FileSecretsStore implements SecretsStore {\n\treadonly name = 'file';\n\tprivate readonly project: KeystoreProject;\n\n\t/**\n\t * @param project - Whose keys: a workspace's `keystoreProject()`. Outside a\n\t * workspace there is no config to name the project, so it is named the\n\t * way a workspace without `name` is — its package name, else its folder.\n\t */\n\tconstructor(\n\t\tprivate readonly root: string,\n\t\tproject?: KeystoreProject,\n\t) {\n\t\tthis.project = project ?? {\n\t\t\tkey: projectKey({ name: packageName(root) ?? basename(root) }),\n\t\t\tlegacy: [basename(root)],\n\t\t};\n\t}\n\n\t/** The file a stage's secrets are kept in. */\n\tpath(stage: string): string {\n\t\treturn join(this.root, SECRETS_DIR, `${stage}.json`);\n\t}\n\n\tasync read(stage: string): Promise<StageSecrets | null> {\n\t\tconst path = this.path(stage);\n\t\tif (!existsSync(path)) return null;\n\n\t\tconst data = JSON.parse(await readFile(path, 'utf-8'));\n\n\t\t// Legacy: unencrypted format (for backwards compatibility)\n\t\tif (!(data.version === 1 && data.encrypted && data.iv)) {\n\t\t\treturn data as StageSecrets;\n\t\t}\n\n\t\tconst key = await readKey(stage, this.project);\n\t\tif (!key) {\n\t\t\tthrow new MissingSecretsKey(\n\t\t\t\tstage,\n\t\t\t\tthis.project.key,\n\t\t\t\tgetKeyPath(stage, this.project),\n\t\t\t);\n\t\t}\n\n\t\treturn decrypt(data as EncryptedSecretsFile, key);\n\t}\n\n\tasync write(stage: string, secrets: StageSecrets): Promise<void> {\n\t\tawait mkdir(join(this.root, SECRETS_DIR), { recursive: true });\n\n\t\tconst key = await getOrCreateKey(stage, this.project);\n\t\tawait writeFile(\n\t\t\tthis.path(stage),\n\t\t\tJSON.stringify(encrypt(secrets, key), null, 2),\n\t\t\t'utf-8',\n\t\t);\n\t}\n}\n\n/** A stage's secrets file exists, and the key that opens it does not. */\nexport class MissingSecretsKey extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly project: string,\n\t\treadonly path: string,\n\t) {\n\t\tsuper(\n\t\t\t`Decryption key not found for stage \"${stage}\". ` +\n\t\t\t\t`Expected key at: ${path}. Copy the stage's key there from whoever created it, or set secrets.store to a store this machine can reach.`,\n\t\t);\n\t\tthis.name = 'MissingSecretsKey';\n\t}\n}\n\n/** A project's package name without its scope, as a workspace name reads it. */\nfunction packageName(root: string): string | undefined {\n\ttry {\n\t\tconst pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));\n\t\treturn typeof pkg.name === 'string'\n\t\t\t? pkg.name.replace(/^@[^/]+\\//, '')\n\t\t\t: undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction encrypt(secrets: StageSecrets, keyHex: string): EncryptedSecretsFile {\n\tconst key = Buffer.from(keyHex, 'hex');\n\tconst iv = randomBytes(IV_LENGTH);\n\n\tconst cipher = createCipheriv(ALGORITHM, key, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(JSON.stringify(secrets), 'utf-8'),\n\t\tcipher.final(),\n\t]);\n\n\t// Ciphertext and auth tag, together\n\tconst combined = Buffer.concat([ciphertext, cipher.getAuthTag()]);\n\n\treturn {\n\t\tversion: 1,\n\t\tencrypted: combined.toString('base64'),\n\t\tiv: iv.toString('hex'),\n\t};\n}\n\nfunction decrypt(data: EncryptedSecretsFile, keyHex: string): StageSecrets {\n\tconst key = Buffer.from(keyHex, 'hex');\n\tconst combined = Buffer.from(data.encrypted, 'base64');\n\n\tconst decipher = createDecipheriv(\n\t\tALGORITHM,\n\t\tkey,\n\t\tBuffer.from(data.iv, 'hex'),\n\t);\n\tdecipher.setAuthTag(combined.subarray(-AUTH_TAG_LENGTH));\n\n\tconst plaintext = Buffer.concat([\n\t\tdecipher.update(combined.subarray(0, -AUTH_TAG_LENGTH)),\n\t\tdecipher.final(),\n\t]);\n\n\treturn JSON.parse(plaintext.toString('utf-8')) as StageSecrets;\n}\n","/**\n * Where a stage's secrets are kept.\n *\n * Every command that needs a stage's secrets — `gkm dev`, `test`, `deploy`,\n * `build`, `setup`, `secrets:*` — asks {@link secretsStoreFor} for that\n * stage's store and reads or writes it. None of them knows whether that is a\n * file on this machine, SSM or Secrets Manager in the stage's account: the local stage is\n * always the file, and a deployed stage is whatever `secrets.store` names.\n *\n * The same shape deploy state has (`StateProvider`): one interface, a backend\n * per place, and a custom object for any place this package does not ship.\n */\n\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport { FileSecretsStore } from './file.js';\nimport { keystoreProject } from './keystore.js';\nimport { UnknownSecretsStoreProvider } from './providers.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface SecretsStore {\n\t/**\n\t * Which kind of store this is — `'file'`, `'ssm'`, `'secrets-manager'`, or\n\t * a custom one's.\n\t */\n\treadonly name: string;\n\t/** The stage's secrets, or null when it has none here. */\n\tread(stage: string): Promise<StageSecrets | null>;\n\t/** Replace the stage's secrets. */\n\twrite(stage: string, secrets: StageSecrets): Promise<void>;\n}\n\n/** Secrets in SSM Parameter Store, in the account of the active credentials. */\nexport interface SsmSecretsStoreConfig {\n\tprovider: 'ssm';\n\tregion: string;\n}\n\n/**\n * Secrets in AWS Secrets Manager, in the account of the active credentials:\n * one secret per stage, `gkm/<project>/<stage>/secrets`. Up to 64 KB where\n * SSM holds 8 KB, at a monthly price per secret.\n */\nexport interface SecretsManagerSecretsStoreConfig {\n\tprovider: 'secrets-manager';\n\tregion: string;\n\t/**\n\t * The KMS key (id, ARN or alias) a stage's secret is created with. Unset,\n\t * it is the account's AWS-managed `aws/secretsmanager` key.\n\t */\n\tkmsKeyId?: string;\n}\n\n/** Any other backend: an object implementing {@link SecretsStore}. */\nexport interface CustomSecretsStoreConfig {\n\tprovider: SecretsStore;\n}\n\n/**\n * `secrets.store` in gkm.config.ts.\n *\n * `'file'` — the default — keeps each stage's secrets in the encrypted\n * `.gkm/secrets/<stage>.json`, its key in the CLI's home (`GKM_HOME`, else\n * `~/.gkm`). It cannot serve a\n * deploy from CI while `.gkm/` is gitignored; `ssm`, `secrets-manager` or a\n * custom store can.\n */\nexport type SecretsStoreConfig =\n\t| 'file'\n\t| SsmSecretsStoreConfig\n\t| SecretsManagerSecretsStoreConfig\n\t| CustomSecretsStoreConfig;\n\nexport interface SecretsStoreOptions {\n\t/**\n\t * The AWS profile for the stage's account. Only that profile is used —\n\t * never AWS_* from the environment — so a store cannot quietly land in\n\t * whichever account the shell was exported for.\n\t */\n\tprofile?: string;\n\t/**\n\t * The CLI's home, where a file store's keys are kept. Defaults to\n\t * `GKM_HOME`, else `~/.gkm`.\n\t */\n\thome?: string;\n}\n\n/**\n * The store a stage's secrets are kept in — what every command reads and\n * writes, for the stage it acts on.\n *\n * The local stage is always the file: its secrets belong to the machine\n * running `gkm dev`. A deployed stage uses `secrets.store`.\n */\nexport async function secretsStoreFor(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n\toptions: SecretsStoreOptions = {},\n): Promise<SecretsStore> {\n\tconst configured = workspace.secrets.store ?? 'file';\n\n\tif (stage === workspace.stages.local || configured === 'file') {\n\t\treturn new FileSecretsStore(\n\t\t\tworkspace.root,\n\t\t\tkeystoreProject(workspace, options.home),\n\t\t);\n\t}\n\n\treturn storeFromConfig(workspace, configured, options);\n}\n\n/**\n * The store a `secrets.store` value describes, for any stage — what\n * {@link secretsStoreFor} answers for a deployed stage, and what\n * `gkm secrets:migrate` builds for the store it copies to.\n */\nexport async function storeFromConfig(\n\tworkspace: NormalizedWorkspace,\n\tconfig: SecretsStoreConfig,\n\toptions: SecretsStoreOptions = {},\n): Promise<SecretsStore> {\n\tif (config === 'file') {\n\t\treturn new FileSecretsStore(\n\t\t\tworkspace.root,\n\t\t\tkeystoreProject(workspace, options.home),\n\t\t);\n\t}\n\n\tconst profile = options.profile ? { profile: options.profile } : {};\n\n\tswitch (config.provider) {\n\t\tcase 'ssm': {\n\t\t\tconst { AwsSecretsStore } = await import('./aws.js');\n\t\t\treturn new AwsSecretsStore({\n\t\t\t\tproject: workspace.name,\n\t\t\t\tregion: config.region,\n\t\t\t\t...profile,\n\t\t\t});\n\t\t}\n\t\tcase 'secrets-manager': {\n\t\t\tconst { kmsKeyId } = config as SecretsManagerSecretsStoreConfig;\n\t\t\tconst { SecretsManagerSecretsStore } = await import(\n\t\t\t\t'./secretsManager.js'\n\t\t\t);\n\t\t\treturn new SecretsManagerSecretsStore({\n\t\t\t\tproject: workspace.name,\n\t\t\t\tregion: config.region,\n\t\t\t\t...(kmsKeyId ? { kmsKeyId } : {}),\n\t\t\t\t...profile,\n\t\t\t});\n\t\t}\n\t\tdefault: {\n\t\t\t// Typed as a SecretsStore, but a config assembled by hand may name\n\t\t\t// a provider that is not one.\n\t\t\tconst provider: unknown = config.provider;\n\t\t\tif (typeof provider === 'object' && provider !== null) {\n\t\t\t\treturn provider as SecretsStore;\n\t\t\t}\n\t\t\tthrow new UnknownSecretsStoreProvider(provider);\n\t\t}\n\t}\n}\n\n/** Whether a stage's secrets live somewhere other than this machine. */\nexport function isRemoteStore(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n): boolean {\n\tconst configured = workspace.secrets.store ?? 'file';\n\treturn stage !== workspace.stages.local && configured !== 'file';\n}\n\nexport { FileSecretsStore };\nexport {\n\tassertKnownSecretsStore,\n\tSECRETS_STORE_PROVIDERS,\n\ttype SecretsStoreProvider,\n\tUnknownSecretsStoreProvider,\n} from './providers.js';\n","import { existsSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { createServer } from 'node:net';\nimport { dirname, join, resolve } from 'node:path';\nimport { config as dotenvConfig } from 'dotenv';\nimport {\n\tConfigNotFound,\n\tloadWorkspaceAppInfo,\n\tloadWorkspaceConfig,\n\tNotInAnApp,\n\ttype WorkspaceAppInfo,\n} from '../config';\nimport type { ReconcileResult } from '../reconcile/index.js';\nimport { toEmbeddableSecrets } from '../secrets/storage.js';\nimport {\n\tFileSecretsStore,\n\ttype SecretsStore,\n\tsecretsStoreFor,\n} from '../secrets/store.js';\nimport type { NormalizedWorkspace } from '../workspace/index.js';\n\nconst logger = console;\n\n// ---------------------------------------------------------------------------\n// Environment files\n// ---------------------------------------------------------------------------\n\n/**\n * Load environment files\n * @internal Exported for testing\n */\nexport function loadEnvFiles(\n\tenvConfig: string | string[] | undefined,\n\tcwd: string = process.cwd(),\n): { loaded: string[]; missing: string[] } {\n\tconst loaded: string[] = [];\n\tconst missing: string[] = [];\n\n\t// Normalize to array\n\tconst envFiles = envConfig\n\t\t? Array.isArray(envConfig)\n\t\t\t? envConfig\n\t\t\t: [envConfig]\n\t\t: ['.env'];\n\n\t// Load each env file in order (later files override earlier)\n\tfor (const envFile of envFiles) {\n\t\tconst envPath = resolve(cwd, envFile);\n\t\tif (existsSync(envPath)) {\n\t\t\tdotenvConfig({ path: envPath, override: true, quiet: true });\n\t\t\tloaded.push(envFile);\n\t\t} else if (envConfig) {\n\t\t\t// Only report as missing if explicitly configured\n\t\t\tmissing.push(envFile);\n\t\t}\n\t}\n\n\treturn { loaded, missing };\n}\n\n// ---------------------------------------------------------------------------\n// Port utilities\n// ---------------------------------------------------------------------------\n\n/**\n * Check if a port is available\n * @internal Exported for testing\n */\nexport async function isPortAvailable(port: number): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst server = createServer();\n\n\t\tserver.once('error', (err: NodeJS.ErrnoException) => {\n\t\t\tif (err.code === 'EADDRINUSE') {\n\t\t\t\tresolve(false);\n\t\t\t} else {\n\t\t\t\tresolve(false);\n\t\t\t}\n\t\t});\n\n\t\tserver.once('listening', () => {\n\t\t\tserver.close();\n\t\t\tresolve(true);\n\t\t});\n\n\t\tserver.listen(port);\n\t});\n}\n\n/**\n * Find an available port starting from the preferred port\n * @internal Exported for testing\n */\nexport async function findAvailablePort(\n\tpreferredPort: number,\n\tmaxAttempts = 10,\n): Promise<number> {\n\tfor (let i = 0; i < maxAttempts; i++) {\n\t\tconst port = preferredPort + i;\n\t\tif (await isPortAvailable(port)) {\n\t\t\treturn port;\n\t\t}\n\t\tlogger.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);\n\t}\n\n\tthrow new Error(\n\t\t`Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`,\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// Docker Compose port mapping\n// ---------------------------------------------------------------------------\n\n/** Port state persisted to .gkm/ports.json, keyed by env var name. */\nexport type PortState = Record<string, number>;\n\nconst PORT_STATE_PATH = '.gkm/ports.json';\n\n/**\n * Load saved port state from .gkm/ports.json.\n * @internal Exported for testing\n */\nexport async function loadPortState(workspaceRoot: string): Promise<PortState> {\n\ttry {\n\t\tconst raw = await readFile(join(workspaceRoot, PORT_STATE_PATH), 'utf-8');\n\t\treturn JSON.parse(raw) as PortState;\n\t} catch {\n\t\treturn {};\n\t}\n}\n\n/**\n * Save port state to .gkm/ports.json.\n * @internal Exported for testing\n */\nexport async function savePortState(\n\tworkspaceRoot: string,\n\tports: PortState,\n): Promise<void> {\n\tconst dir = join(workspaceRoot, '.gkm');\n\tawait mkdir(dir, { recursive: true });\n\tawait writeFile(\n\t\tjoin(workspaceRoot, PORT_STATE_PATH),\n\t\t`${JSON.stringify(ports, null, 2)}\\n`,\n\t);\n}\n\n// ---------------------------------------------------------------------------\n// URL rewriting\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Docker Compose services\n// ---------------------------------------------------------------------------\n\n// ---------------------------------------------------------------------------\n// Secrets loading\n// ---------------------------------------------------------------------------\n\n/**\n * Load and flatten secrets for an app from encrypted storage.\n * For workspace app: maps {APP}_DATABASE_URL → DATABASE_URL.\n * @internal Exported for testing\n */\nexport async function loadSecretsForApp(\n\tstore: SecretsStore,\n\tstage: string,\n\tappName?: string,\n): Promise<Record<string, string>> {\n\tlet secrets: Record<string, string> = {};\n\n\tconst stageSecrets = await store.read(stage);\n\tif (stageSecrets) {\n\t\tlogger.log(`🔐 Loading secrets from stage: ${stage}`);\n\t\tsecrets = toEmbeddableSecrets(stageSecrets);\n\t}\n\n\tif (Object.keys(secrets).length === 0) {\n\t\treturn {};\n\t}\n\n\t// Single app mode - no mapping needed\n\tif (!appName) {\n\t\treturn secrets;\n\t}\n\n\t// Workspace app mode - map {APP}_* to generic names\n\tconst prefix = appName.toUpperCase();\n\tconst mapped = { ...secrets };\n\n\t// Map {APP}_DATABASE_URL → DATABASE_URL\n\tconst appDbUrl = secrets[`${prefix}_DATABASE_URL`];\n\tif (appDbUrl) {\n\t\tmapped.DATABASE_URL = appDbUrl;\n\t}\n\n\treturn mapped;\n}\n\n/**\n * Walk up the directory tree to find the root containing .gkm/secrets/.\n * @internal Exported for testing\n */\nexport function findSecretsRoot(startDir: string): string {\n\tlet dir = startDir;\n\twhile (dir !== '/') {\n\t\tif (existsSync(join(dir, '.gkm', 'secrets'))) {\n\t\t\treturn dir;\n\t\t}\n\t\tconst parent = dirname(dir);\n\t\tif (parent === dir) break;\n\t\tdir = parent;\n\t}\n\treturn startDir;\n}\n\n// ---------------------------------------------------------------------------\n// Credentials preload / injection\n// ---------------------------------------------------------------------------\n\n/**\n * Generate the credentials injection code snippet.\n * This is the common logic used by both entry wrapper and exec preload.\n * @internal\n */\nfunction generateCredentialsInjection(secretsJsonPath: string): string {\n\treturn `import { existsSync, readFileSync } from 'node:fs';\n\n// Inject dev secrets via globalThis and process.env\n// Using globalThis.__gkm_credentials__ avoids CJS/ESM interop issues where\n// Object.assign on the Credentials export only mutates one module copy.\nconst secretsPath = '${secretsJsonPath}';\nif (existsSync(secretsPath)) {\n const secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));\n globalThis.__gkm_credentials__ = secrets;\n Object.assign(process.env, secrets);\n}\n`;\n}\n\n/**\n * Create a preload script that injects secrets into Credentials.\n * Used by `gkm exec` to inject secrets before running any command.\n * @internal Exported for testing\n */\nexport async function createCredentialsPreload(\n\tpreloadPath: string,\n\tsecretsJsonPath: string,\n): Promise<void> {\n\tconst content = `/**\n * Credentials preload generated by 'gkm exec'\n * This file is loaded via NODE_OPTIONS=\"--import <path>\"\n */\n${generateCredentialsInjection(secretsJsonPath)}`;\n\n\tawait writeFile(preloadPath, content);\n}\n\n/**\n * Create a wrapper script that injects secrets before importing the entry file.\n * @internal Exported for testing\n */\nexport async function createEntryWrapper(\n\twrapperPath: string,\n\tentryPath: string,\n\tsecretsJsonPath?: string,\n): Promise<void> {\n\tconst credentialsInjection = secretsJsonPath\n\t\t? `${generateCredentialsInjection(secretsJsonPath)}\n`\n\t\t: '';\n\n\t// Use dynamic import() to ensure secrets are assigned before the entry file loads\n\t// Static imports are hoisted, so Object.assign would run after the entry file is loaded\n\tconst content = `#!/usr/bin/env node\n/**\n * Entry wrapper generated by 'gkm dev --entry'\n */\n${credentialsInjection}// Import and run the user's entry file (dynamic import ensures secrets load first)\nawait import('${entryPath}');\n`;\n\n\tawait writeFile(wrapperPath, content);\n}\n\n// ---------------------------------------------------------------------------\n// Prepare credentials (shared by dev, exec, test)\n// ---------------------------------------------------------------------------\n\n/**\n * Result of preparing credentials.\n */\nexport interface EntryCredentialsResult {\n\t/** Credentials to inject (secrets + PORT) */\n\tcredentials: Record<string, string>;\n\t/** Resolved port (from --port, workspace config, or default 3000) */\n\tresolvedPort: number;\n\t/** Path where credentials JSON was written */\n\tsecretsJsonPath: string;\n\t/** Resolved app name (if in workspace) */\n\tappName: string | undefined;\n\t/** Secrets root directory */\n\tsecretsRoot: string;\n\t/** Workspace app info (if in a workspace) */\n\tappInfo?: WorkspaceAppInfo;\n\t/**\n\t * The keys the manifest declared, as opposed to those a secret store held.\n\t *\n\t * Kept apart because they cannot be *sniffed*. A construct reads its own key\n\t * inside `@geekmidas/constructs`, not in application code, so a walk of the\n\t * app finds no `get('MAIL_URL')` to find — and a caller that filters the\n\t * environment down to what it sniffed would drop every URL the target just\n\t * resolved. `gkm test` did exactly that.\n\t */\n\tdeclaredKeys: string[];\n\t/** The reconcile just run, in a workspace: its plan and its ports. */\n\treconciled?: ReconcileResult;\n}\n\n/**\n * Prepare credentials for dev/exec/test modes.\n * Loads workspace config, secrets, resolves Docker ports, rewrites URLs,\n * injects PORT, dependency URLs, and writes credentials JSON.\n *\n * @param options.stage - The stage whose secrets to load. Default: the project's `stages.local`.\n * @param options.startDocker - Start Docker Compose services after port resolution. Default: false.\n * @param options.secretsFileName - Custom secrets JSON filename. Default: 'dev-secrets-{appName}.json' or 'dev-secrets.json'.\n * @internal Exported for testing\n */\nexport async function prepareEntryCredentials(options: {\n\texplicitPort?: number;\n\tcwd?: string;\n\t/** The stage whose secrets to load. Default: the project's `stages.local` */\n\tstage?: string;\n\t/** Start Docker Compose services after port resolution. Default: false */\n\tstartDocker?: boolean;\n\t/** Custom secrets JSON filename. Default: 'dev-secrets-{appName}.json' or 'dev-secrets.json' */\n\tsecretsFileName?: string;\n\t/**\n\t * The stage to reconcile the local target for.\n\t *\n\t * `gkm test` passes `test` and gets the same containers with suffixed\n\t * resources; left out, it is the project's local stage and they are\n\t * unsuffixed. Only read by workspaces that have adopted the constructs glob.\n\t */\n\treconcileStage?: string;\n}): Promise<EntryCredentialsResult> {\n\tconst cwd = options.cwd ?? process.cwd();\n\n\t// Try to get workspace app config for port and secrets\n\tlet workspaceAppPort: number | undefined;\n\tlet secretsRoot: string = cwd;\n\tlet appName: string | undefined;\n\tlet appInfo: WorkspaceAppInfo | undefined;\n\n\tlet workspace: NormalizedWorkspace | undefined;\n\n\ttry {\n\t\tappInfo = await loadWorkspaceAppInfo(cwd);\n\t\tworkspace = appInfo.workspace;\n\t\tworkspaceAppPort = appInfo.app.port;\n\t\tsecretsRoot = appInfo.workspaceRoot;\n\t\tappName = appInfo.appName;\n\t} catch (error) {\n\t\t// Anything but \"not in an app\" or \"not in a workspace\" is a real fault —\n\t\t// a config that does not load — and must not be mistaken for either.\n\t\tif (!(error instanceof NotInAnApp || error instanceof ConfigNotFound)) {\n\t\t\tthrow error;\n\t\t}\n\t\t// Not an app — but possibly the workspace root, which is where the\n\t\t// scaffold's own `pnpm test` runs `gkm test` from. Without the workspace\n\t\t// here that run skipped the reconcile below: no container started, and the\n\t\t// URLs were whatever the stored secrets said, on ports nothing listened on.\n\t\tworkspace = await loadWorkspaceConfig(cwd)\n\t\t\t.then((loaded) => loaded.workspace)\n\t\t\t.catch(() => undefined);\n\t\t// Otherwise not in a workspace at all (expected for non-gkm apps using\n\t\t// gkm exec) — use defaults.\n\t\t// No app here, so no per-app keys to map.\n\t\tsecretsRoot = workspace?.root ?? findSecretsRoot(cwd);\n\t}\n\n\t// The app's local port, by the rule `gkm dev` applies: moved off a port\n\t// another project holds, and kept. Never refused here — the command may be\n\t// a build or a script that binds nothing, so this app's own dev server\n\t// holding the port is no reason to stop it.\n\tif (appName && workspace && options.explicitPort === undefined) {\n\t\tconst { assignAppPorts, describeHolder, holderOf, withAppPorts } =\n\t\t\tawait import('../dev/appPorts.js');\n\t\tconst assigned = await assignAppPorts(workspace, [appName], {\n\t\t\tfree: isPortAvailable,\n\t\t\tholder: holderOf,\n\t\t});\n\t\tfor (const { app, from, to, holder } of assigned.moved) {\n\t\t\tlogger.log(\n\t\t\t\t`↪️ ${app}: ${from} is held by ${describeHolder(holder)}, which is not this workspace's — using ${to}`,\n\t\t\t);\n\t\t}\n\t\tworkspace = withAppPorts(workspace, assigned.ports);\n\t\tworkspaceAppPort = assigned.ports[appName] ?? workspaceAppPort;\n\t}\n\n\t// Tagged, so a later `gkm dev` can tell what this starts from another\n\t// project's process on the same port; the command inherits it.\n\tif (appName) {\n\t\tconst { APP_TAG_ENV, appTag } = await import('../dev/appPorts.js');\n\t\tprocess.env[APP_TAG_ENV] = appTag(secretsRoot, appName);\n\t}\n\n\t// Determine port: explicit --port > the app's local port > default 3000\n\tconst resolvedPort = options.explicitPort ?? workspaceAppPort ?? 3000;\n\n\t// Load secrets and inject PORT. Outside a workspace there are no declared\n\t// stages to read, so only a stage asked for by name is loaded.\n\tconst stage = options.stage ?? workspace?.stages.local;\n\t// The stage's own store; outside a workspace nothing names one but the file.\n\tconst store = stage\n\t\t? workspace\n\t\t\t? await secretsStoreFor(workspace, stage)\n\t\t\t: new FileSecretsStore(secretsRoot)\n\t\t: undefined;\n\tconst credentials =\n\t\tstore && stage ? await loadSecretsForApp(store, stage, appName) : {};\n\n\t// Always inject PORT into credentials so apps can read it\n\tcredentials.PORT = String(resolvedPort);\n\t// Expo reads its Metro port from here when `expo start` is given none, so\n\t// a mobile app runs where the workspace placed it — and where the `exp://`\n\t// origins its auth server trusts say it is.\n\tif (appInfo?.app.type === 'mobile') {\n\t\tcredentials.RCT_METRO_PORT = String(resolvedPort);\n\t}\n\n\tconst declaredKeys: string[] = [];\n\tlet reconciled: ReconcileResult | undefined;\n\n\t// Every address — containers, the edge, each app — comes from what the\n\t// workspace declares. Outside a workspace (a non-gkm app using `gkm exec`)\n\t// there is nothing to resolve, only the secrets above.\n\tif (workspace) {\n\t\tconst { reconcileWorkspace } = await import('../reconcile/workspace.js');\n\t\treconciled = await reconcileWorkspace(workspace, {\n\t\t\tstage: options.reconcileStage ?? workspace.stages.local,\n\t\t\tstart: options.startDocker ?? false,\n\t\t});\n\n\t\t// Declared URLs win over anything stored: the manifest is the statement\n\t\t// of what exists, and a stale secret naming an old port is exactly the\n\t\t// drift this replaces.\n\t\tObject.assign(credentials, reconciled.env);\n\t\tdeclaredKeys.push(...Object.keys(reconciled.env));\n\n\t\tif (Object.keys(reconciled.env).length > 0) {\n\t\t\tlogger.log(\n\t\t\t\t`🔌 Resolved ${Object.keys(reconciled.env).length} declared URL(s)`,\n\t\t\t);\n\t\t}\n\t}\n\n\t// Write secrets to temp JSON file (always write since we have PORT)\n\t// Use app-specific filename to avoid race conditions when running multiple apps via turbo\n\tconst secretsDir = join(secretsRoot, '.gkm');\n\tawait mkdir(secretsDir, { recursive: true });\n\tconst secretsFileName =\n\t\toptions.secretsFileName ??\n\t\t(appName ? `dev-secrets-${appName}.json` : 'dev-secrets.json');\n\tconst secretsJsonPath = join(secretsDir, secretsFileName);\n\tawait writeFile(secretsJsonPath, JSON.stringify(credentials, null, 2));\n\n\treturn {\n\t\tcredentials,\n\t\tresolvedPort,\n\t\tsecretsJsonPath,\n\t\tappName,\n\t\tsecretsRoot,\n\t\tappInfo,\n\t\tdeclaredKeys,\n\t\t...(reconciled ? { reconciled } : {}),\n\t};\n}\n","import {\n\tDEFAULT_POSTGRES_VERSION,\n\tkebabCase,\n\ttype PostgresVersion,\n} from '@geekmidas/manifest';\n/**\n * What gkm knows about each container it can derive.\n *\n * Everything provider-specific about the local target lives here: the image, the\n * ports the image listens on, and whether its data is worth keeping. The plan\n * says *which* containers exist and never says any of this, which is the split\n * the design asks for — neutral facts from the declaration, provider detail\n * from here and from config.\n *\n * Shared by allocation and by the compose writer so the two cannot disagree\n * about how many ports a container needs.\n */\n\n/**\n * The Postgres image for a major version.\n *\n * One place that knows the tag shape, so a declared version reaches the\n * container without anyone restating `-alpine` beside it.\n */\nexport function postgresImage(version: PostgresVersion): string {\n\treturn `postgres:${version}-alpine`;\n}\n\n/**\n * The default image for each container.\n *\n * Pinned to a major rather than `latest` where the image publishes one, so a\n * rebuild does not silently move a developer's Postgres a version. Overridable\n * per project: a project needing `postgis/postgis` says so in config.\n */\nexport const DEFAULT_IMAGES: Readonly<Record<string, string>> = {\n\tpostgres: postgresImage(DEFAULT_POSTGRES_VERSION),\n\tminio: 'pgsty/minio:RELEASE.2026-08-04T00-00-00Z',\n\tmailpit: 'axllent/mailpit:latest',\n\tredis: 'redis:8-alpine',\n\t'redis-http': 'hiett/serverless-redis-http:latest',\n\trabbitmq: 'rabbitmq:4-management-alpine',\n\t// Pinned: 2.2.0 answers a KMS decrypt under the wrong encryption context\n\t// with UnknownError instead of InvalidCiphertextException.\n\tlocalstack: 'floci/floci:2.1.0',\n\t// The local edge: TLS with its own CA, and host-based routing onto the\n\t// bucket behind it. Not a CDN — what is missing locally is the *mapping*\n\t// and the certificate, not caching, and a caching proxy would add an\n\t// invalidation story no local stack needs.\n\tcaddy: 'caddy:2-alpine',\n};\n\n/** One port an image listens on. */\nexport interface ContainerPort {\n\t/**\n\t * The name this port is allocated and persisted under.\n\t *\n\t * Per port rather than per container so that adding a console port later\n\t * assigns a new one instead of shifting the port the app already connects to.\n\t */\n\tkey: string;\n\t/** The port inside the container, fixed by the image. */\n\tinside: number;\n\t/** What it is for, in the one line `gkm dev` and `gkm setup` print. */\n\tlabel: string;\n\t/** A page a person opens, so it prints as a link rather than an address. */\n\tweb?: true;\n}\n\n/**\n * The ports each container listens on.\n *\n * The first is always the one an app connects to; the rest are consoles and\n * management UIs, published because a bucket or an inbox you can look at is most\n * of why these are real containers rather than stubs.\n */\nconst PORTS: Readonly<Record<string, readonly ContainerPort[]>> = {\n\tpostgres: [{ key: 'postgres', inside: 5432, label: 'postgres' }],\n\tminio: [\n\t\t{ key: 'minio', inside: 9000, label: 'minio api' },\n\t\t{ key: 'minio-console', inside: 9001, label: 'minio console', web: true },\n\t],\n\tmailpit: [\n\t\t{ key: 'mailpit', inside: 1025, label: 'smtp' },\n\t\t{ key: 'mailpit-web', inside: 8025, label: 'mailpit inbox', web: true },\n\t],\n\tredis: [{ key: 'redis', inside: 6379, label: 'redis' }],\n\t'redis-http': [{ key: 'redis-http', inside: 80, label: 'cache' }],\n\trabbitmq: [\n\t\t{ key: 'rabbitmq', inside: 5672, label: 'amqp' },\n\t\t{\n\t\t\tkey: 'rabbitmq-management',\n\t\t\tinside: 15672,\n\t\t\tlabel: 'rabbitmq console',\n\t\t\tweb: true,\n\t\t},\n\t],\n\tlocalstack: [{ key: 'localstack', inside: 4566, label: 'localstack' }],\n\t// One port, and an assigned one rather than 443. The whole point of\n\t// allocation is that two projects run at once, and an edge that insists on\n\t// the privileged port puts that back — at the cost of a port in the URL,\n\t// which no cookie or CORS rule looks at.\n\tcaddy: [{ key: 'caddy', inside: 443, label: 'https edge' }],\n};\n\n/**\n * An external API's fake, as the local target runs it.\n *\n * Keyed by its port key, `<id>-fake`, which is also its container's name when\n * it has one — so `primaryPortKey` finds its port with no table to consult.\n * A module fake has no image: gkm serves it, on the port this key is assigned.\n */\nexport interface PlannedFake {\n\t/** The external API it stands in for. */\n\tid: string;\n\t/** The image to run, for a fake the provider publishes. */\n\timage?: string;\n\t/** The port that image listens on inside the container. */\n\tport?: number;\n}\n\n/** The port key — and container name, for an image — of an API's fake. */\nexport function fakeKey(id: string): string {\n\treturn `${kebabCase(id)}-fake`;\n}\n\n/**\n * The ports a container needs published. Empty for one gkm does not know.\n *\n * `fakes` answers for the containers no table can: an external API's fake\n * listens wherever its declaration says.\n */\nexport function portsOf(\n\tcontainer: string,\n\tfakes: Readonly<Record<string, PlannedFake>> = {},\n): readonly ContainerPort[] {\n\tconst fake = fakes[container];\n\tif (fake?.port !== undefined) {\n\t\treturn [{ key: container, inside: fake.port, label: `${fake.id} fake` }];\n\t}\n\n\treturn PORTS[container] ?? [];\n}\n\n/** One published port, as a person reads it. */\nexport interface ServiceAddress {\n\tcontainer: string;\n\tlabel: string;\n\t/** `http://localhost:<port>` for a page, `localhost:<port>` otherwise. */\n\taddress: string;\n}\n\n/**\n * Every port the containers publish — not only the one an app connects to.\n *\n * The consoles and the inbox are most of why these are real containers, and an\n * inbox nobody can find is an inbox nobody opens: a sign-in link gets fished\n * out of `docker ps` instead.\n */\nexport function serviceAddresses(\n\tcontainers: readonly string[],\n\tports: Readonly<Record<string, number>>,\n\tfakes: Readonly<Record<string, PlannedFake>> = {},\n): ServiceAddress[] {\n\treturn containers.flatMap((container) =>\n\t\tportsOf(container, fakes).flatMap((port) => {\n\t\t\tconst assigned = ports[port.key];\n\t\t\tif (assigned === undefined) return [];\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tcontainer,\n\t\t\t\t\tlabel: port.label,\n\t\t\t\t\taddress: port.web\n\t\t\t\t\t\t? `http://localhost:${assigned}`\n\t\t\t\t\t\t: `localhost:${assigned}`,\n\t\t\t\t},\n\t\t\t];\n\t\t}),\n\t);\n}\n\n/** The lines `gkm dev` and `gkm setup` print under the services heading. */\nexport function describeServices(\n\tservices: readonly ServiceAddress[],\n): string[] {\n\tconst width = Math.max(0, ...services.map(({ label }) => label.length));\n\treturn services.map(\n\t\t({ label, address }) => ` ${label.padEnd(width)} ${address}`,\n\t);\n}\n\n/** The key a container's primary port is allocated under. */\nexport function primaryPortKey(container: string): string {\n\treturn portsOf(container)[0]?.key ?? container;\n}\n\n/**\n * Every port key a set of containers needs, in a stable order.\n *\n * What allocation is handed: it assigns ports to names and knows nothing about\n * what listens on them.\n */\nexport function portKeys(\n\tcontainers: readonly string[],\n\tfakes: Readonly<Record<string, PlannedFake>> = {},\n): string[] {\n\tconst keys = [...containers]\n\t\t.sort()\n\t\t.flatMap((container) => portsOf(container, fakes).map((port) => port.key));\n\n\t// A module fake runs in no container, and still needs a port to answer on.\n\treturn [...new Set([...keys, ...Object.keys(fakes).sort()])];\n}\n\n/**\n * The named volume a container's data lives in.\n *\n * Mailpit deliberately has none: local mail is disposable, and an inbox that\n * survives days of dev is noise rather than state.\n */\nexport function volumeOf(container: string): string | undefined {\n\tconst volumes: Readonly<Record<string, string>> = {\n\t\tpostgres: 'postgres-data',\n\t\tminio: 'minio-data',\n\t\tredis: 'redis-data',\n\t\trabbitmq: 'rabbitmq-data',\n\t\tlocalstack: 'localstack-data',\n\t\t// Holds the CA it generated. Losing it means a new root on every start,\n\t\t// and a trust store full of dead authorities.\n\t\tcaddy: 'caddy-data',\n\t};\n\n\treturn volumes[container];\n}\n","/**\n * Which backend each declared kind resolves to — decided by where the project\n * deploys, and by nothing else.\n *\n * These used to be read out of a `services:` block, where one key answered two\n * questions: *does this exist* and *what backs it*. The first is the manifest's\n * — a declared `Cache` is why there is a cache — and the second is the deploy\n * target's, because the right cache for a Lambda is not the right cache for a\n * box already running Postgres. With both answered elsewhere there was nothing\n * left for the block to say, so it is gone and these read the target.\n *\n * Mail has no answer here at all: every provider speaks SMTP, so the provider\n * is whichever one the stage's mail URL points at.\n */\n\nimport { runtimeOf, type TargetSource } from '../target/runtime.js';\nimport {\n\tDEFAULT_CACHE,\n\tDEFAULT_EVENTS,\n\tDEFAULT_STORAGE,\n\ttype EventsBackend,\n\ttype MainProvider,\n\ttype StorageBackend,\n\ttype TargetCacheBackend,\n} from '../types.js';\n\n/**\n * Which family of defaults a workspace's deploy target belongs to.\n *\n * One question, asked once: **does this target run containers the project\n * controls?** Dokploy does, so a bucket or a cache can live beside the app;\n * AWS, Vercel and Cloudflare do not, so the default has to be something\n * managed. That is the whole of the distinction, and it is why `MainProvider`\n * is the right axis rather than the deploy target's own name — targets share\n * answers.\n *\n * Each target declares its answer as its `runtime` — a target package in its\n * `package.json`, so this never loads one. A workspace that names no target\n * is on AWS: an SST project predates `deploy.default: 'sst'`.\n */\nexport function providerOf(workspace: TargetSource): MainProvider {\n\tconst target = workspace.deploy?.default;\n\treturn target ? runtimeOf(target, workspace) : 'aws';\n}\n\n/**\n * The cache backend a target implies.\n *\n * The same answer locally and deployed: a local `gkm dev` for an AWS app still\n * uses the Upstash protocol, because a backend that differed between the two\n * would be worse than a slower one.\n */\nexport function cacheBackendFor(on: MainProvider): TargetCacheBackend {\n\treturn DEFAULT_CACHE[on];\n}\n\n/** Where a declared bucket lives on a target. */\nexport function storageBackendFor(on: MainProvider): StorageBackend {\n\treturn DEFAULT_STORAGE[on];\n}\n\n/**\n * Which broker carries declared queues and topics on a target.\n *\n * Only consulted when something declares one; a project with no `Topic` or\n * `Queue` has no events at all, whatever the target.\n */\nexport function eventsBackendFor(on: MainProvider): EventsBackend {\n\treturn DEFAULT_EVENTS[on];\n}\n","import { PUBLIC_ENV_PREFIXES } from '../workspace/publicEnv.js';\n\n/**\n * Set by a site's Dockerfile in its builder stage: this process is building\n * an image.\n *\n * Inside an image there is no stage to load secrets for and no local\n * container to resolve an address from — the public URLs the bundle inlines\n * arrived as the Dockerfile's build args, already in the environment. So\n * `gkm exec -- next build` there injects exactly those, and never the\n * `localhost` addresses a workspace resolves on a developer's machine.\n */\nexport const IMAGE_BUILD_ENV = 'GKM_IMAGE_BUILD';\n\n/** Whether this process is building an image. */\nexport function isImageBuild(env: NodeJS.ProcessEnv = process.env): boolean {\n\treturn env[IMAGE_BUILD_ENV] === '1';\n}\n\n/**\n * What `gkm exec` injects inside an image build: the public values its build\n * args set — every `NEXT_PUBLIC_*`, `VITE_*` and `EXPO_PUBLIC_*` — and\n * nothing read from a secrets store, a file or a container.\n */\nexport function imageBuildCredentials(\n\tenv: NodeJS.ProcessEnv = process.env,\n): Record<string, string> {\n\tconst credentials: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(env)) {\n\t\tif (value === undefined) continue;\n\t\tif (PUBLIC_ENV_PREFIXES.some((prefix) => key.startsWith(prefix))) {\n\t\t\tcredentials[key] = value;\n\t\t}\n\t}\n\treturn credentials;\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join, parse } from 'node:path';\nimport { IMAGE_BUILD_ENV } from '../exec/imageBuild';\nimport type { CacheBackend, DockerConfig, GkmConfig } from '../types';\n\nexport type PackageManager = 'pnpm' | 'npm' | 'yarn' | 'bun';\n\n/**\n * The turbo every image prunes and builds with when the build root resolves\n * none of its own: a repository without turbo still needs `turbo prune` to\n * cut its slice of the workspace, and an unpinned `turbo` is whatever the\n * registry says on the day of the build.\n */\nexport const TURBO_VERSION = '2.5.4';\n\n/**\n * Caddy, which serves a static site's files. A 2.x tag, so a rebuild picks\n * up patch releases and never a major.\n */\nexport const STATIC_SITE_IMAGE = 'caddy:2.10-alpine';\n\n/**\n * How an image is built: every template prunes the build root to the app's\n * slice, installs it, builds it and copies the result into a runner.\n *\n * Every path is relative to the build root — the directory that holds the\n * lockfile, which is the gkm workspace's root in a project of its own and the\n * monorepo's root when the workspace is nested inside one.\n */\nexport interface ImageTemplateOptions {\n\t/** The image the Dockerfile builds; informational. */\n\timageName?: string;\n\tbaseImage: string;\n\tport: number;\n\t/** The app's directory, relative to the build root. */\n\tappPath: string;\n\t/** The app's package, which turbo prunes to. */\n\tturboPackage: string;\n\t/** Detected package manager */\n\tpackageManager: PackageManager;\n\t/**\n\t * The exact version the build root's `packageManager` field names. Absent,\n\t * the image installs the package manager's latest.\n\t */\n\tpackageManagerVersion?: string;\n\t/** The turbo the build root resolves; `TURBO_VERSION` otherwise. */\n\tturboVersion?: string;\n\t/**\n\t * Whether the build root is a workspace of packages, which turbo prunes. A\n\t * single package is copied whole. Defaults to true.\n\t */\n\tmonorepo?: boolean;\n\t/**\n\t * Other packages the slice keeps: a gkm workspace nested in a monorepo is a\n\t * package of its own, holding the config and constructs the app is built\n\t * from.\n\t */\n\tprunePackages?: string[];\n\t/** The gkm workspace's root, relative to the build root. Defaults to `.` */\n\tgkmRoot?: string;\n\t/**\n\t * What under the gkm root the build reads besides the app — its config and\n\t * the directories its construct globs start in. `turbo prune` keeps\n\t * packages; these are copied beside them.\n\t */\n\tgkmPaths?: string[];\n\t/**\n\t * A site's: the backends whose typed clients it imports, each generated\n\t * in the builder — `gkm openapi --app <app>` from the backend's directory\n\t * (relative to the build root) — before the site is built.\n\t */\n\tclients?: { app: string; path: string }[];\n}\n\nexport interface FrontendDockerfileOptions extends ImageTemplateOptions {\n\t/**\n\t * Public URL build args to include in the Dockerfile.\n\t * These will be declared as ARG and converted to ENV for the build.\n\t * Example: ['NEXT_PUBLIC_API_URL', 'NEXT_PUBLIC_AUTH_URL']\n\t */\n\tpublicUrlArgs?: string[];\n}\n\nconst LOCKFILES: [string, PackageManager][] = [\n\t['pnpm-lock.yaml', 'pnpm'],\n\t['bun.lockb', 'bun'],\n\t['yarn.lock', 'yarn'],\n\t['package-lock.json', 'npm'],\n];\n\n/**\n * Detect package manager from lockfiles\n * Walks up the directory tree to find lockfile (for monorepos)\n */\nexport function detectPackageManager(\n\tcwd: string = process.cwd(),\n): PackageManager {\n\tconst lockfile = findLockfilePath(cwd);\n\tif (!lockfile) return 'pnpm';\n\tconst name = lockfile.slice(dirname(lockfile).length + 1);\n\treturn LOCKFILES.find(([file]) => file === name)?.[1] ?? 'pnpm';\n}\n\n/**\n * Find the lockfile path by walking up the directory tree\n * Returns the full path to the lockfile, or null if not found\n */\nexport function findLockfilePath(cwd: string = process.cwd()): string | null {\n\tfor (const dir of upFrom(cwd)) {\n\t\tfor (const [lockfile] of LOCKFILES) {\n\t\t\tconst lockfilePath = join(dir, lockfile);\n\t\t\tif (existsSync(lockfilePath)) {\n\t\t\t\treturn lockfilePath;\n\t\t\t}\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * The directory every image is built from: the nearest at or above `cwd`\n * holding a lockfile or a `pnpm-workspace.yaml` — the package manager's root,\n * which is what an install resolves the workspace's dependencies against.\n *\n * In a project of its own that is the gkm workspace's root. A gkm workspace\n * nested inside a monorepo, whose dependencies are the monorepo's\n * `workspace:*` packages, is built from the monorepo's root. `cwd` itself\n * when neither is found.\n */\nexport function findBuildRoot(cwd: string = process.cwd()): string {\n\tfor (const dir of upFrom(cwd)) {\n\t\tif (existsSync(join(dir, 'pnpm-workspace.yaml'))) return dir;\n\t\tfor (const [lockfile] of LOCKFILES) {\n\t\t\tif (existsSync(join(dir, lockfile))) return dir;\n\t\t}\n\t}\n\treturn cwd;\n}\n\n/** `cwd` and every directory above it, the filesystem root last. */\nfunction upFrom(cwd: string): string[] {\n\tconst dirs: string[] = [];\n\tconst root = parse(cwd).root;\n\tlet dir = cwd;\n\twhile (dir !== root) {\n\t\tdirs.push(dir);\n\t\tdir = dirname(dir);\n\t}\n\tdirs.push(root);\n\treturn dirs;\n}\n\n/** What a build root builds its images with. */\nexport interface BuildTools {\n\tpackageManager: PackageManager;\n\t/** From `packageManager` in the build root's package.json, if set. */\n\tpackageManagerVersion?: string;\n\t/** The turbo the build root resolves, or `TURBO_VERSION`. */\n\tturboVersion: string;\n\t/** Whether the build root is a workspace of packages. */\n\tmonorepo: boolean;\n}\n\n/** The package manager, its version, turbo's and the shape of a build root. */\nexport function resolveBuildTools(buildRoot: string): BuildTools {\n\tconst pkg = readJson(join(buildRoot, 'package.json'));\n\tconst packageManager = detectPackageManager(buildRoot);\n\tconst field =\n\t\ttypeof pkg?.packageManager === 'string' ? pkg.packageManager : '';\n\t// `pnpm@10.30.1+sha512.…` — the hash is corepack's, the version is ours.\n\tconst match = /^([a-z]+)@(\\d[^+\\s]*)/.exec(field);\n\tconst packageManagerVersion =\n\t\tmatch && match[1] === packageManager ? match[2] : undefined;\n\n\treturn {\n\t\tpackageManager,\n\t\t...(packageManagerVersion ? { packageManagerVersion } : {}),\n\t\tturboVersion: resolveTurboVersion(buildRoot) ?? TURBO_VERSION,\n\t\tmonorepo:\n\t\t\texistsSync(join(buildRoot, 'pnpm-workspace.yaml')) ||\n\t\t\tpkg?.workspaces !== undefined,\n\t};\n}\n\n/**\n * The turbo version a build root resolves: what is installed, or what its\n * lockfile pins. Undefined when it has none.\n */\nexport function resolveTurboVersion(buildRoot: string): string | undefined {\n\tconst installed = readJson(\n\t\tjoin(buildRoot, 'node_modules', 'turbo', 'package.json'),\n\t);\n\tif (typeof installed?.version === 'string') return installed.version;\n\n\tconst read = (file: string) => {\n\t\ttry {\n\t\t\treturn readFileSync(join(buildRoot, file), 'utf-8');\n\t\t} catch {\n\t\t\treturn undefined;\n\t\t}\n\t};\n\n\tconst pnpm = read('pnpm-lock.yaml');\n\tconst fromPnpm = pnpm && /^ {2}'?turbo@(\\d[^:'(\\s]*)'?:/m.exec(pnpm);\n\tif (fromPnpm) return fromPnpm[1];\n\n\tconst npm = read('package-lock.json');\n\tif (npm) {\n\t\tconst lock = safeParse(npm) as {\n\t\t\tpackages?: Record<string, { version?: string }>;\n\t\t};\n\t\tconst version = lock?.packages?.['node_modules/turbo']?.version;\n\t\tif (version) return version;\n\t}\n\n\tconst yarn = read('yarn.lock');\n\tconst fromYarn =\n\t\tyarn && /^\"?turbo@[^\\n]*:\\n\\s+version:? \"?(\\d[^\"\\s]*)\"?/m.exec(yarn);\n\tif (fromYarn) return fromYarn[1];\n\n\treturn undefined;\n}\n\nfunction readJson(path: string): Record<string, unknown> | undefined {\n\ttry {\n\t\treturn safeParse(readFileSync(path, 'utf-8'));\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction safeParse(text: string): Record<string, unknown> | undefined {\n\ttry {\n\t\tconst value = JSON.parse(text);\n\t\treturn value && typeof value === 'object' ? value : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Get the lockfile name for a package manager\n */\nexport function getLockfileName(pm: PackageManager): string {\n\tconst lockfileMap: Record<PackageManager, string> = {\n\t\tpnpm: 'pnpm-lock.yaml',\n\t\tnpm: 'package-lock.json',\n\t\tyarn: 'yarn.lock',\n\t\tbun: 'bun.lockb',\n\t};\n\treturn lockfileMap[pm];\n}\n\n/**\n * Check if we're in a monorepo (lockfile is in a parent directory)\n */\nexport function isMonorepo(cwd: string = process.cwd()): boolean {\n\tconst lockfilePath = findLockfilePath(cwd);\n\tif (!lockfilePath) {\n\t\treturn false;\n\t}\n\n\t// Check if lockfile is in a parent directory (not in cwd)\n\tconst lockfileDir = dirname(lockfilePath);\n\treturn lockfileDir !== cwd;\n}\n\n/**\n * Check if turbo.json exists (walks up directory tree)\n */\nexport function hasTurboConfig(cwd: string = process.cwd()): boolean {\n\treturn upFrom(cwd).some((dir) => existsSync(join(dir, 'turbo.json')));\n}\n\n/**\n * Package manager specific commands. `version` pins what corepack (or npm,\n * for bun) installs; without it, the latest.\n */\nfunction getPmConfig(pm: PackageManager, version?: string) {\n\tconst configs = {\n\t\tpnpm: {\n\t\t\tinstall: `corepack enable && corepack prepare pnpm@${version ?? 'latest'} --activate`,\n\t\t\tlockfile: 'pnpm-lock.yaml',\n\t\t\t// turbo prune writes a lockfile for the slice, not the repository's,\n\t\t\t// so the install cannot be frozen to it.\n\t\t\tinstallCmd: 'pnpm install',\n\t\t\tcacheTarget: '/root/.local/share/pnpm/store',\n\t\t\tcacheId: 'pnpm',\n\t\t\trun: 'pnpm run',\n\t\t\tdlx: 'pnpm dlx',\n\t\t},\n\t\tnpm: {\n\t\t\tinstall: version ? `npm install -g npm@${version}` : '',\n\t\t\tlockfile: 'package-lock.json',\n\t\t\tinstallCmd: 'npm install',\n\t\t\tcacheTarget: '/root/.npm',\n\t\t\tcacheId: 'npm',\n\t\t\trun: 'npm run',\n\t\t\tdlx: 'npx --yes',\n\t\t},\n\t\tyarn: {\n\t\t\tinstall: `corepack enable && corepack prepare yarn@${version ?? 'stable'} --activate`,\n\t\t\tlockfile: 'yarn.lock',\n\t\t\tinstallCmd: 'yarn install',\n\t\t\tcacheTarget: '/root/.yarn/cache',\n\t\t\tcacheId: 'yarn',\n\t\t\trun: 'yarn run',\n\t\t\tdlx: 'yarn dlx',\n\t\t},\n\t\tbun: {\n\t\t\tinstall: `npm install -g bun@${version ?? 'latest'}`,\n\t\t\tlockfile: 'bun.lockb',\n\t\t\tinstallCmd: 'bun install',\n\t\t\tcacheTarget: '/root/.bun/install/cache',\n\t\t\tcacheId: 'bun',\n\t\t\trun: 'bun run',\n\t\t\tdlx: 'bunx',\n\t\t},\n\t};\n\treturn configs[pm];\n}\n\n/** What every template's first three stages need, resolved from options. */\nfunction layout(options: ImageTemplateOptions) {\n\tconst pm = getPmConfig(options.packageManager, options.packageManagerVersion);\n\tconst turbo = `${pm.dlx} turbo@${options.turboVersion ?? TURBO_VERSION}`;\n\tconst gkmRoot = options.gkmRoot ?? '.';\n\treturn {\n\t\tpm,\n\t\tturbo,\n\t\tinstallPm: pm.install ? `RUN ${pm.install}` : '',\n\t\tmonorepo: options.monorepo ?? true,\n\t\tgkmRoot,\n\t\t/** The app, absolute inside the image. */\n\t\tapp: `/app/${options.appPath}`.replace(/\\/\\.$/, ''),\n\t};\n}\n\n/**\n * The pruner stage: the build context cut to the app's slice, in turbo's\n * `out/` layout — `out/json` for the install, `out/full` for the build.\n *\n * `turbo prune` keeps the packages the app depends on and nothing else at\n * the root, so the root's own files (a `tsconfig.base.json` the packages\n * extend, the config their build reads) and what the gkm workspace keeps\n * outside any package are copied in beside them. A single package is copied\n * whole.\n */\nfunction prunerStage(options: ImageTemplateOptions): string {\n\tconst { pm, turbo, installPm, monorepo, gkmRoot } = layout(options);\n\tconst scopes = [options.turboPackage, ...(options.prunePackages ?? [])].join(\n\t\t' ',\n\t);\n\n\tif (!monorepo) {\n\t\treturn `# Stage 1: The build context, in the layout the stages below read\nFROM ${options.baseImage} AS pruner\nWORKDIR /app\nCOPY . .\nRUN mkdir -p /tmp/out/json /tmp/out/full && \\\\\n cp package.json /tmp/out/json/ && \\\\\n cp ${pm.lockfile} /tmp/out/ && \\\\\n cp -a . /tmp/out/full/ && \\\\\n mv /tmp/out ./out`;\n\t}\n\n\tconst gkmPaths = options.gkmPaths ?? [];\n\tconst nested = gkmRoot !== '.';\n\tconst dest = nested ? `/app/out/full/${gkmRoot}` : '/app/out/full';\n\tconst copyGkm = gkmPaths.length\n\t\t? `\n\n# What the gkm workspace keeps outside any package — its config, the\n# directories its constructs are declared in — beside the slice.\nRUN ${nested ? `cd ${gkmRoot} && ` : ''}for path in ${gkmPaths.join(' ')}; do \\\\\n if [ -e \"$path\" ]; then \\\\\n mkdir -p \"${dest}\" && cp -rn \"$path\" \"${dest}/\"; \\\\\n fi; \\\\\n done`\n\t\t: '';\n\n\treturn `# Stage 1: Prune the monorepo to the app's slice\nFROM ${options.baseImage} AS pruner\nWORKDIR /app\n${installPm}\nCOPY . .\nRUN ${turbo} prune ${scopes} --docker\n\n# The root's own files — the tsconfig the packages extend, the config their\n# build reads — which turbo prune leaves out. What the install itself reads\n# (registry config, local tarballs) goes beside the manifests too.\nRUN find . -maxdepth 1 -type f ! -name '${pm.lockfile}' ! -name package.json \\\\\n -exec cp -n {} out/full/ \\\\; && \\\\\n for file in .npmrc .yarnrc.yml .pnpmfile.cjs bunfig.toml *.tgz; do \\\\\n if [ -f \"$file\" ]; then cp \"$file\" out/json/; fi; \\\\\n done${copyGkm}`;\n}\n\n/** The deps stage: the slice's dependencies, installed from its lockfile. */\nfunction depsStage(options: ImageTemplateOptions): string {\n\tconst { pm, installPm } = layout(options);\n\treturn `# Stage 2: Install dependencies\nFROM ${options.baseImage} AS deps\nWORKDIR /app\n${installPm}\nCOPY --from=pruner /app/out/${pm.lockfile} ./\nCOPY --from=pruner /app/out/json/ ./\nRUN --mount=type=cache,id=${pm.cacheId},target=${pm.cacheTarget} \\\\\n ${pm.installCmd}`;\n}\n\n/**\n * The start of the builder stage: the slice's source over its dependencies,\n * and every workspace package the app depends on built from it.\n *\n * Nothing built on the host reaches the image — `.dockerignore` keeps every\n * `dist` out — so a workspace package (a CLI that is a sibling package) is\n * built here: turbo's build of the app's dependencies (`^build`) and nothing\n * else. The root's own `build` script is never run: the slice holds one app,\n * and a root `build` that is `gkm build` (what `gkm init` scaffolds) builds\n * every app the workspace declares, most of which are not in it. The app\n * itself is built after this, by `gkm build` or its framework.\n */\nfunction builderStage(\n\toptions: ImageTemplateOptions,\n\textra: { args?: string[] } = {},\n): string {\n\tconst { turbo, monorepo } = layout(options);\n\tconst args = extra.args ?? [];\n\tconst argLines = args.length\n\t\t? `\\n# Build-time args: public values the bundler inlines\\n${args.map((a) => `ARG ${a}=\"\"`).join('\\n')}\\n${args.map((a) => `ENV ${a}=$${a}`).join('\\n')}\\n`\n\t\t: '';\n\tconst scopes = [options.turboPackage, ...(options.prunePackages ?? [])];\n\tconst dependencies = monorepo\n\t\t? `\\n# The workspace packages the app depends on, built from source.\\nRUN ${turbo} run build ${scopes.map((s) => `--filter='${s}^...'`).join(' ')}`\n\t\t: '';\n\n\treturn `# Stage 3: Build\nFROM deps AS builder\nWORKDIR /app\n${argLines}\nCOPY --from=pruner /app/out/full/ ./${dependencies}`;\n}\n\n/**\n * The PATH a command in the app's directory runs with: the app's binaries,\n * then the gkm workspace's, then the root's — a nested workspace's apps\n * declare nothing, and its CLI is installed where its config is.\n */\nfunction binPath(options: ImageTemplateOptions): string {\n\tconst { app, gkmRoot } = layout(options);\n\tconst dirs = [\n\t\t`${app}/node_modules/.bin`,\n\t\tgkmRoot === '.' ? '' : `/app/${gkmRoot}/node_modules/.bin`,\n\t\t'/app/node_modules/.bin',\n\t].filter(Boolean);\n\treturn `PATH=\"${[...new Set(dirs)].join(':')}:$PATH\"`;\n}\n\n/**\n * Run the gkm CLI the app resolves, from the app's directory: node on its\n * `bin/gkm.mjs`, found the way Node resolves a package — up from the app.\n *\n * Not through `node_modules/.bin`: a CLI that is a workspace package has no\n * bin linked, since its files were not there when the slice's dependencies\n * were installed.\n */\nfunction gkmCommand(args: string): string {\n\treturn `GKM_BIN=\"$(node -e '${FIND_GKM_BIN}')\" && node \"$GKM_BIN\" ${args}`;\n}\n\n/** Prints the `bin/gkm.mjs` Node resolves from the cwd, or fails saying so. */\nconst FIND_GKM_BIN = `let d=process.cwd();const{existsSync:e}=require(\"fs\"),{dirname:u,join:j}=require(\"path\");for(;;){const p=j(d,\"node_modules/@geekmidas/cli/bin/gkm.mjs\");if(e(p)){console.log(p);break}if(u(d)===d){console.error(\"@geekmidas/cli is not installed where the app can resolve it\");process.exit(1)}d=u(d)}`;\n\n/**\n * What a site's builder does with the gkm workspace it carries, before its\n * framework builds it — all of it inside the image, nothing from the host:\n *\n * - `gkm` on the PATH: the CLI the workspace installs, so a build script that\n * is `gkm exec -- next build` runs. Resolved from the gkm root rather than\n * through `node_modules/.bin`, where a CLI that is a workspace package has\n * no bin linked.\n * - `GKM_IMAGE_BUILD`, which has `gkm exec` inject the build args above — the\n * stage's public URLs — and never load a secret or resolve a local address.\n * - each backend's typed client, generated from its endpoints at the\n * workspace root's `.gkm/client/`, where the site's tsconfig paths point.\n */\nfunction siteWorkspaceSteps(options: ImageTemplateOptions): string {\n\tconst { gkmRoot } = layout(options);\n\tconst root = `/app/${gkmRoot}`.replace(/\\/\\.$/, '');\n\tconst clients = options.clients ?? [];\n\tconst generate = clients.length\n\t\t? `\n\n# The typed client of each API this site calls, generated from its endpoints\n# into ${gkmRoot === '.' ? '' : `${gkmRoot}/`}.gkm/client/ — offline, with no secret and no container.\nRUN ${clients.map(({ app, path }) => `cd /app/${path} && gkm openapi --app ${app}`).join(' && \\\\\\n ')}`\n\t\t: '';\n\n\treturn `# The gkm CLI the workspace installs, as \\`gkm\\`\nRUN cd ${root} && GKM_BIN=\"$(node -e '${FIND_GKM_BIN}')\" && \\\\\n { echo '#!/bin/sh'; echo \"exec node \\\\\"$GKM_BIN\\\\\" \\\\\"\\\\$@\\\\\"\"; } > /usr/local/bin/gkm && \\\\\n chmod +x /usr/local/bin/gkm\n\n# \\`gkm exec\\` in this build injects the build args, never a stage's secrets\nENV ${IMAGE_BUILD_ENV}=1${generate}`;\n}\n\n/**\n * Build the site with turbo. Loose env mode, so the task sees what the\n * Dockerfile set — the build args and `GKM_IMAGE_BUILD` — whatever the\n * project's turbo.json declares: the image's environment holds nothing else.\n */\nfunction siteBuild(options: ImageTemplateOptions): string {\n\tconst { turbo } = layout(options);\n\treturn `RUN ${turbo} run build --filter='${options.turboPackage}' --env-mode=loose`;\n}\n\n/**\n * The encrypted credentials, from a build secret — never an ARG, which is\n * recorded in the image history — written where the build embeds them from.\n * Two lines, ciphertext then IV; absent when the app has no secrets.\n *\n * A BuildKit secret is not part of the cache key, so `GKM_CIPHERTEXT_HASH`\n * (a hash of the ciphertext, not a secret) is: credentials encrypted under a\n * new key rebuild the layer that embeds them.\n */\nfunction credentialsStep(appDir: string, then: string): string {\n\treturn `ARG GKM_CIPHERTEXT_HASH=\"\"\nRUN --mount=type=secret,id=gkm_credentials,required=false \\\\\n echo \"credentials: \\${GKM_CIPHERTEXT_HASH:-none}\" && \\\\\n if [ -s /run/secrets/gkm_credentials ]; then \\\\\n mkdir -p ${appDir}/.gkm && \\\\\n sed -n 1p /run/secrets/gkm_credentials > ${appDir}/.gkm/credentials.enc && \\\\\n sed -n 2p /run/secrets/gkm_credentials > ${appDir}/.gkm/credentials.iv; \\\\\n fi && \\\\\n ${then} && \\\\\n rm -f ${appDir}/.gkm/credentials.enc ${appDir}/.gkm/credentials.iv`;\n}\n\n/**\n * The externals a backend's bundle leaves out (esbuild `--external`), each\n * installed for Linux at the version the builder resolved, so the runner has\n * exactly those beside `server.mjs`. Nothing when there are none: the runner\n * is the one file.\n */\nfunction externalsStages(\n\toptions: ImageTemplateOptions & { external?: string[] },\n\tappDir: string,\n): { stage: string; copy: string } {\n\tconst external = options.external ?? [];\n\tif (external.length === 0) return { stage: '', copy: '' };\n\n\treturn {\n\t\tstage: `\n# Stage 4: The packages the bundle leaves external, installed for Linux at the\n# versions the build resolved — and nothing else.\nFROM builder AS externals-manifest\nWORKDIR ${appDir}\nRUN node <<'EOF'\nconst { existsSync, readFileSync, writeFileSync } = require('node:fs');\nconst { dirname, join } = require('node:path');\nconst names = ${JSON.stringify(external)};\nconst dependencies = {};\nfor (const name of names) {\n let dir = process.cwd();\n for (;;) {\n const manifest = join(dir, 'node_modules', name, 'package.json');\n if (existsSync(manifest)) {\n dependencies[name] = JSON.parse(readFileSync(manifest, 'utf-8')).version;\n break;\n }\n if (dirname(dir) === dir) {\n console.error(name + ' is external to the bundle, and is not installed: add it to the app\\\\'s dependencies.');\n process.exit(1);\n }\n dir = dirname(dir);\n }\n}\nwriteFileSync('/tmp/externals.json', JSON.stringify({ private: true, dependencies }));\nEOF\n\nFROM ${options.baseImage} AS externals\nWORKDIR /externals\nCOPY --from=externals-manifest /tmp/externals.json ./package.json\nRUN --mount=type=cache,id=npm,target=/root/.npm \\\\\n npm install --omit=dev --no-package-lock --no-audit --no-fund\n`,\n\t\tcopy: '\\nCOPY --from=externals --chown=hono:nodejs /externals/node_modules ./node_modules',\n\t};\n}\n\n/**\n * Generate .dockerignore file\n *\n * Written at the build root. Nothing built on the host may reach an image —\n * every image builds its packages from source — and nothing secret may reach\n * a build context.\n */\nexport function generateDockerignore(): string {\n\treturn `# Dependencies: installed in the image, for Linux\n**/node_modules\n.pnpm-store\n\n# Build output: every image builds from source, never from the host's\n**/dist\n**/.next\n**/.turbo\n**/.gkm\n\n# The key gkm build --stage encrypted secrets with: runtime env only, never\n# the build context\n**/master.key\n\n# gkm compose's stacks: each app's env file holds its stage's secrets\n**/.gkm/compose\n\n# IDE and editor\n.idea\n.vscode\n*.swp\n*.swo\n\n# Git\n.git\n\n# Logs\n*.log\nnpm-debug.log*\npnpm-debug.log*\n\n# Test files\ncoverage\n\n# Environment files (handle secrets separately)\n**/.env\n**/.env.*\n!**/.env.example\n\n# Docker files (don't copy recursively)\nDockerfile*\ndocker-compose*\n.dockerignore\n`;\n}\n\n/**\n * Generate docker-entrypoint.sh for custom startup logic\n */\nexport function generateDockerEntrypoint(): string {\n\treturn `#!/bin/sh\nset -e\n\n# Run any custom startup scripts here\n# Example: wait for database\n# until nc -z $DB_HOST $DB_PORT; do\n# echo \"Waiting for database...\"\n# sleep 1\n# done\n\n# Execute the main command\nexec \"$@\"\n`;\n}\n\n/**\n * Resolve Docker configuration from GkmConfig with defaults\n */\nexport function resolveDockerConfig(config: GkmConfig): Required<DockerConfig> {\n\tconst docker = config.docker ?? {};\n\n\treturn {\n\t\tregistry: docker.registry ?? '',\n\t\t// The project's name, from the config — what somebody types after\n\t\t// `docker pull` — never whatever the directory's package.json is called.\n\t\timageName: docker.imageName ?? config.name ?? 'api',\n\t\tbaseImage: docker.baseImage ?? 'node:22-alpine',\n\t\tport: docker.port ?? 3000,\n\t};\n}\n\n/** A site's root answers its health check. */\nfunction siteHealthcheck(port: number): string {\n\treturn `# A site answers at its root; Docker, and Dokploy, read the container's\n# health from it rather than from the process merely running. 127.0.0.1, not\n# localhost: the server listens on IPv4, and localhost can resolve to ::1.\nHEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\\\\n CMD wget -qO- http://127.0.0.1:${port}/ > /dev/null 2>&1 || exit 1`;\n}\n\n/**\n * Generate a Dockerfile for Next.js frontend apps using standalone output.\n *\n * Pruned at the build root, Next traces from there, so the standalone\n * server lands at `<appPath>/server.js` under `.next/standalone`.\n * @internal Exported for testing\n */\nexport function generateNextjsDockerfile(\n\toptions: FrontendDockerfileOptions,\n): string {\n\tconst {\n\t\tport,\n\t\tappPath,\n\t\tpublicUrlArgs = ['NEXT_PUBLIC_API_URL', 'NEXT_PUBLIC_AUTH_URL'],\n\t} = options;\n\n\treturn `# syntax=docker/dockerfile:1\n# Next.js standalone Dockerfile, built from a turbo-pruned slice\n\n${prunerStage(options)}\n\n${depsStage(options)}\n\n${builderStage(options, { args: publicUrlArgs })}\n\n${siteWorkspaceSteps(options)}\n\n# Ensure public directory exists (may be empty for scaffolded projects)\nRUN mkdir -p ${appPath}/public\n\nENV NEXT_TELEMETRY_DISABLED=1\n\n# Build the application\n${siteBuild(options)}\n\n# The runner needs the standalone server; without it there is nothing to run.\nRUN if [ ! -d ${appPath}/.next/standalone ]; then \\\\\n echo \"${appPath}/.next/standalone was not built: set output: 'standalone' in next.config so the image can run the app without node_modules.\" >&2; \\\\\n exit 1; \\\\\n fi\n\n# Stage 4: Production\nFROM ${options.baseImage} AS runner\n\nWORKDIR /app\n\n# Install tini for proper signal handling\nRUN apk add --no-cache tini\n\n# Create non-root user\nRUN addgroup --system --gid 1001 nodejs && \\\\\n adduser --system --uid 1001 nextjs\n\n# Set environment\nENV NODE_ENV=production\nENV NEXT_TELEMETRY_DISABLED=1\nENV PORT=${port}\nENV HOSTNAME=\"0.0.0.0\"\n\n# The standalone server, with its static files and public directory beside it\nCOPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/standalone ./\nCOPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/static ./${appPath}/.next/static\nCOPY --from=builder --chown=nextjs:nodejs /app/${appPath}/public ./${appPath}/public\n\nUSER nextjs\n\nEXPOSE ${port}\n\n${siteHealthcheck(port)}\n\nENTRYPOINT [\"/sbin/tini\", \"--\"]\nCMD [\"node\", \"${appPath}/server.js\"]\n`;\n}\n\n/**\n * Generate a Dockerfile for a gkm backend: pruned, installed, and bundled by\n * `gkm build --provider server --production` inside the image. The runner is\n * `server.mjs` alone — plus any package the bundle leaves external.\n * @internal Exported for testing\n */\nexport function generateBackendDockerfile(\n\toptions: ImageTemplateOptions & {\n\t\thealthCheckPath?: string;\n\t\t/** Packages the bundle leaves external (esbuild `--external`). */\n\t\texternal?: string[];\n\t\t/** The cache backend the entry registers, where not the target's. */\n\t\tcache?: CacheBackend;\n\t},\n): string {\n\treturn gkmBundleDockerfile(options, {\n\t\ttitle: 'Backend Dockerfile',\n\t\tbundle: 'server.mjs',\n\t\tas: 'server.mjs',\n\t\texpose: true,\n\t});\n}\n\n/** The port a worker answers its health check on, inside its container. */\nexport const WORKER_PORT = 3000;\n\n/**\n * A Worker's image: the same build as its host app's backend — a pruned\n * slice, `gkm build --provider server --production` in the image, with the\n * encrypted credentials from the `gkm_credentials` secret — and a runner that\n * is the worker's bundle alone. It publishes nothing: its one route is the\n * health check the HEALTHCHECK asks.\n * @internal Exported for testing\n */\nexport function generateWorkerDockerfile(\n\toptions: ImageTemplateOptions & {\n\t\t/** The worker's construct id, for the comment. */\n\t\tworker: string;\n\t\t/** Its bundle under the host app's `.gkm/server/dist`. */\n\t\tbundle: string;\n\t\thealthCheckPath?: string;\n\t\texternal?: string[];\n\t\t/** The cache backend the entry registers, where not the target's. */\n\t\tcache?: CacheBackend;\n\t},\n): string {\n\treturn gkmBundleDockerfile(options, {\n\t\ttitle: `Worker Dockerfile (${options.worker})`,\n\t\tbundle: options.bundle,\n\t\tas: 'worker.mjs',\n\t\texpose: false,\n\t});\n}\n\nfunction gkmBundleDockerfile(\n\toptions: ImageTemplateOptions & {\n\t\thealthCheckPath?: string;\n\t\texternal?: string[];\n\t\tcache?: CacheBackend;\n\t},\n\trunner: { title: string; bundle: string; as: string; expose: boolean },\n): string {\n\tconst { port, healthCheckPath = '/health' } = options;\n\tconst { app } = layout(options);\n\tconst externals = externalsStages(options, app);\n\n\treturn `# syntax=docker/dockerfile:1\n# ${runner.title}: a turbo-pruned slice, bundled by gkm build in the image\n\n${prunerStage(options)}\n\n${depsStage(options)}\n\n${builderStage(options)}\n\n# Bundle the production server, with the encrypted credentials embedded\n${credentialsStep(app, `cd ${app} && ${gkmCommand(`build --provider server --production${options.cache ? ` --cache ${options.cache}` : ''}`)}`)}\n${externals.stage}\n# Production\nFROM ${options.baseImage} AS runner\n\nWORKDIR /app\n\nRUN apk add --no-cache tini\n\nRUN addgroup --system --gid 1001 nodejs && \\\\\n adduser --system --uid 1001 hono\n\n# The bundled ${runner.as === 'server.mjs' ? 'server' : 'worker'}\nCOPY --from=builder --chown=hono:nodejs ${app}/.gkm/server/dist/${runner.bundle} ./${runner.as === runner.bundle ? '' : runner.as}${externals.copy}\n\nENV NODE_ENV=production\nENV PORT=${port}\n\nHEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\\\\n CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1\n\nUSER hono\n${runner.expose ? `\\nEXPOSE ${port}\\n` : ''}\nENTRYPOINT [\"/sbin/tini\", \"--\"]\nCMD [\"node\", \"${runner.as}\"]\n`;\n}\n\n/**\n * Options for entry-based Dockerfile generation.\n */\nexport interface EntryDockerfileOptions extends ImageTemplateOptions {\n\t/** Entry file path relative to app path (e.g., './src/index.ts') */\n\tentry: string;\n\t/** Health check path (default: '/health') */\n\thealthCheckPath?: string;\n}\n\n/**\n * Generate a Dockerfile for apps with a custom entry point.\n * Uses esbuild to bundle the entry point into dist/index.mjs with all dependencies.\n * This is used for apps that don't use gkm routes (e.g., Better Auth servers).\n * @internal Exported for testing\n */\nexport function generateEntryDockerfile(\n\toptions: EntryDockerfileOptions,\n): string {\n\tconst { port, entry, healthCheckPath = '/health' } = options;\n\tconst { app } = layout(options);\n\n\tconst esbuild = (defines: string) =>\n\t\t`esbuild ${entry} --bundle --platform=node --target=node22 --format=esm \\\\\n --outfile=dist/index.mjs --packages=bundle \\\\\n --banner:js='import { createRequire } from \"module\"; const require = createRequire(import.meta.url);'${defines}`;\n\n\treturn `# syntax=docker/dockerfile:1\n# Entry-based Dockerfile: a turbo-pruned slice, bundled by esbuild in the image\n\n${prunerStage(options)}\n\n${depsStage(options)}\n\n${builderStage(options)}\n\n# Bundle the entry point with every dependency (dist/index.mjs), embedding the\n# encrypted credentials when there are any.\nARG GKM_CIPHERTEXT_HASH=\"\"\nRUN --mount=type=secret,id=gkm_credentials,required=false \\\\\n echo \"credentials: \\${GKM_CIPHERTEXT_HASH:-none}\" && \\\\\n cd ${app} && export ${binPath(options)} && \\\\\n if [ -s /run/secrets/gkm_credentials ]; then \\\\\n CREDS=$(sed -n 1p /run/secrets/gkm_credentials) && \\\\\n IV=$(sed -n 2p /run/secrets/gkm_credentials) && \\\\\n ${esbuild(` \\\\\n --define:__GKM_ENCRYPTED_CREDENTIALS__=\"\\\\\"$CREDS\\\\\"\" \\\\\n --define:__GKM_CREDENTIALS_IV__=\"\\\\\"$IV\\\\\"\"`)}; \\\\\n else \\\\\n ${esbuild('')}; \\\\\n fi\n\n# Production\nFROM ${options.baseImage} AS runner\n\nWORKDIR /app\n\nRUN apk add --no-cache tini\n\nRUN addgroup --system --gid 1001 nodejs && \\\\\n adduser --system --uid 1001 app\n\n# Copy bundled output only (no node_modules needed - fully bundled)\nCOPY --from=builder --chown=app:nodejs ${app}/dist/index.mjs ./\n\nENV NODE_ENV=production\nENV PORT=${port}\n\nHEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\\\\n CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1\n\nUSER app\n\nEXPOSE ${port}\n\nENTRYPOINT [\"/sbin/tini\", \"--\"]\nCMD [\"node\", \"index.mjs\"]\n`;\n}\n\n/**\n * Generate a Dockerfile for Node-based SSR web frameworks (TanStack Start,\n * Remix). Builds with turbo, then runs the framework's production start\n * script.\n *\n * Public URL build args are declared so framework bundlers (Vite, Vinxi)\n * can inline them at build time. Per-framework prefix is the caller's job.\n * @internal Exported for testing\n */\nexport function generateNodeWebDockerfile(\n\toptions: FrontendDockerfileOptions,\n): string {\n\tconst { port, appPath } = options;\n\n\treturn `# syntax=docker/dockerfile:1\n# Node SSR web Dockerfile (TanStack Start / Remix), from a turbo-pruned slice\n\n${prunerStage(options)}\n\n${depsStage(options)}\n\n${builderStage(options, { args: options.publicUrlArgs ?? [] })}\n\n${siteWorkspaceSteps(options)}\n\n${siteBuild(options)}\n\nFROM ${options.baseImage} AS runner\nWORKDIR /app\nRUN apk add --no-cache tini\nRUN addgroup --system --gid 1001 nodejs && \\\\\n adduser --system --uid 1001 app\n\n# Copy the whole built workspace — frameworks differ on output paths\nCOPY --from=builder --chown=app:nodejs /app/ ./\n\nENV NODE_ENV=production\nENV PORT=${port}\nENV HOSTNAME=\"0.0.0.0\"\n\nUSER app\nEXPOSE ${port}\n\n${siteHealthcheck(port)}\n\nENTRYPOINT [\"/sbin/tini\", \"--\"]\n# npm ships with node, and runs the start script whichever package manager\n# installed it: the runner needs no other.\nCMD [\"sh\", \"-c\", \"cd ${appPath} && npm start\"]\n`;\n}\n\n/**\n * Generate a Dockerfile for Vite SPA apps. Builds the static bundle with\n * turbo, then serves it with Caddy: Vite's hashed `/assets/*` cached for a\n * year, everything else — `index.html`, and every deep link that falls back\n * to it — revalidated on each request.\n * @internal Exported for testing\n */\nexport function generateViteStaticDockerfile(\n\toptions: FrontendDockerfileOptions,\n): string {\n\tconst { port, appPath } = options;\n\n\treturn `# syntax=docker/dockerfile:1\n# Vite SPA Dockerfile — a turbo-pruned slice built to static files, served by Caddy\n\n${prunerStage(options)}\n\n${depsStage(options)}\n\n${builderStage(options, { args: options.publicUrlArgs ?? [] })}\n\n${siteWorkspaceSteps(options)}\n\n${siteBuild(options)}\n\nFROM ${STATIC_SITE_IMAGE} AS runner\n\nCOPY <<'EOF' /etc/caddy/Caddyfile\n{\n\tadmin off\n\tauto_https off\n\tpersist_config off\n}\n\n:${port} {\n\troot * /srv\n\tencode zstd gzip\n\n\t# Vite names every built asset by its content hash: a changed file is a\n\t# new URL, so the old one can be cached for good.\n\thandle /assets/* {\n\t\theader Cache-Control \"public, max-age=31536000, immutable\"\n\t\tfile_server\n\t}\n\n\t# index.html, and every client-side route that falls back to it, is\n\t# asked for again each time so a deploy is seen at once.\n\thandle {\n\t\theader Cache-Control \"no-cache\"\n\t\ttry_files {path} /index.html\n\t\tfile_server\n\t}\n}\nEOF\n\nCOPY --from=builder /app/${appPath}/dist /srv\n\nRUN addgroup -S -g 1001 site && adduser -S -u 1001 -G site site && \\\\\n chown -R site:site /data /config\n\nUSER site\n\nEXPOSE ${port}\n\n${siteHealthcheck(port)}\n\nCMD [\"caddy\", \"run\", \"--config\", \"/etc/caddy/Caddyfile\", \"--adapter\", \"caddyfile\"]\n`;\n}\n","/**\n * Where a workspace's images are built from, and what each is built with.\n *\n * Every builder of images — `gkm docker`, `gkm compose`, a Dokploy deploy —\n * builds from the same place with the same templates: the build root (the\n * package manager's root, at or above the gkm workspace), cut by `turbo\n * prune` to one app's slice. Nothing is built on the host.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { isAbsolute, join, relative, sep } from 'node:path';\nimport { allConstructGlobs } from '../workspace/index.js';\nimport type {\n\tNormalizedAppConfig,\n\tNormalizedWorkspace,\n} from '../workspace/types.js';\nimport {\n\ttype BuildTools,\n\tfindBuildRoot,\n\thasTurboConfig,\n\ttype ImageTemplateOptions,\n\tresolveBuildTools,\n} from './templates.js';\n\n/** A workspace of packages with no turbo.json, which every image prunes with. */\nexport class MonorepoNeedsTurbo extends Error {\n\tconstructor(readonly buildRoot: string) {\n\t\tsuper(\n\t\t\t`${buildRoot} is a workspace of packages, and its images are built from a turbo-pruned slice of it — but it has no turbo.json.\\n\\n` +\n\t\t\t\t'To fix this:\\n' +\n\t\t\t\t' 1. Install turbo: pnpm add -Dw turbo\\n' +\n\t\t\t\t` 2. Create turbo.json in ${buildRoot}\\n` +\n\t\t\t\t' 3. Run this command again\\n\\n' +\n\t\t\t\t'See: https://turbo.build/repo/docs/guides/tools/docker',\n\t\t);\n\t\tthis.name = 'MonorepoNeedsTurbo';\n\t}\n}\n\nexport interface ImageLayout {\n\t/** The build context of every image, absolute. */\n\tbuildRoot: string;\n\t/** The gkm workspace's root, relative to the build root (`.` when equal). */\n\tgkmRoot: string;\n\ttools: BuildTools;\n\t/**\n\t * The gkm workspace's own package, when it is nested in a monorepo as one:\n\t * kept in every backend's slice, since it holds the config, the constructs\n\t * and the dependencies they import.\n\t */\n\tworkspacePackage?: string;\n\t/** What under the gkm root a build reads besides the app's own directory. */\n\tgkmPaths: string[];\n}\n\n/**\n * Where a workspace's images are built from.\n *\n * Refuses a workspace of packages with no turbo.json — every image prunes\n * with turbo — so only a caller about to generate or build images asks.\n */\nexport function imageLayout(workspace: NormalizedWorkspace): ImageLayout {\n\tconst buildRoot = findBuildRoot(workspace.root);\n\tconst tools = resolveBuildTools(buildRoot);\n\tif (tools.monorepo && !hasTurboConfig(buildRoot)) {\n\t\tthrow new MonorepoNeedsTurbo(buildRoot);\n\t}\n\n\tconst gkmRoot = toPosix(relative(buildRoot, workspace.root)) || '.';\n\tconst workspacePackage =\n\t\tgkmRoot !== '.' ? packageName(workspace.root) : undefined;\n\n\treturn {\n\t\tbuildRoot,\n\t\tgkmRoot,\n\t\ttools,\n\t\t...(workspacePackage ? { workspacePackage } : {}),\n\t\tgkmPaths: ['gkm.config.*', ...constructRoots(workspace)],\n\t};\n}\n\n/**\n * How a compose file builds an app: the build root as the context, relative\n * to the directory the compose file is in, and the Dockerfile — written under\n * the gkm root — relative to that context, which is what compose resolves\n * `dockerfile` against.\n */\nexport function composeBuildPaths(options: {\n\t/** The directory the compose file is in, absolute. */\n\tcomposeDir: string;\n\tbuildRoot: string;\n\tworkspaceRoot: string;\n\t/** The Dockerfile, relative to the gkm workspace's root. */\n\tdockerfile: string;\n}): { context: string; dockerfile: string } {\n\tconst { composeDir, buildRoot, workspaceRoot, dockerfile } = options;\n\treturn {\n\t\tcontext: toPosix(relative(composeDir, buildRoot)) || '.',\n\t\tdockerfile: toPosix(join(relative(buildRoot, workspaceRoot), dockerfile)),\n\t};\n}\n\n/** A path under the gkm root, as the build root sees it. */\nexport function fromBuildRoot(layout: ImageLayout, path: string): string {\n\tif (layout.gkmRoot === '.') return toPosix(path);\n\tconst joined = toPosix(join(layout.gkmRoot, path));\n\treturn joined === '' ? '.' : joined;\n}\n\n/** Everything a template needs about one app, relative to the build root. */\nexport function appImageOptions(\n\tlayout: ImageLayout,\n\tappName: string,\n\tapp: NormalizedAppConfig,\n\tworkspaceRoot: string,\n\t/**\n\t * Every app in the workspace: a site's image generates the client of each\n\t * backend it depends on, so it has to know where they are.\n\t */\n\tapps: Readonly<Record<string, NormalizedAppConfig>> = {},\n): ImageTemplateOptions {\n\tconst turboPackage =\n\t\tpackageName(\n\t\t\tisAbsolute(app.path) ? app.path : join(workspaceRoot, app.path),\n\t\t) ?? appName;\n\treturn {\n\t\timageName: appName,\n\t\tbaseImage: 'node:22-alpine',\n\t\tport: app.port,\n\t\tappPath: fromBuildRoot(layout, app.path),\n\t\tturboPackage,\n\t\tpackageManager: layout.tools.packageManager,\n\t\t...(layout.tools.packageManagerVersion\n\t\t\t? { packageManagerVersion: layout.tools.packageManagerVersion }\n\t\t\t: {}),\n\t\tturboVersion: layout.tools.turboVersion,\n\t\tmonorepo: layout.tools.monorepo,\n\t\tgkmRoot: layout.gkmRoot,\n\t\t// A backend is built by gkm, from the workspace's config and constructs.\n\t\t// So is half of a site: the typed client it imports is generated from\n\t\t// the endpoints of the backends it depends on, in its image, before its\n\t\t// bundler runs — so it carries the same workspace, and those backends'\n\t\t// packages for what their endpoints import.\n\t\t...(app.type === 'backend'\n\t\t\t? {\n\t\t\t\t\tgkmPaths: layout.gkmPaths,\n\t\t\t\t\t...(layout.workspacePackage &&\n\t\t\t\t\tlayout.workspacePackage !== turboPackage\n\t\t\t\t\t\t? { prunePackages: [layout.workspacePackage] }\n\t\t\t\t\t\t: {}),\n\t\t\t\t}\n\t\t\t: app.type === 'web'\n\t\t\t\t? siteWorkspace(layout, app, apps, workspaceRoot, turboPackage)\n\t\t\t\t: {}),\n\t};\n}\n\n/**\n * What a site's image carries of the gkm workspace: its config and construct\n * directories, the workspace's own package where it is nested in a monorepo\n * (it holds the CLI), and each backend the site calls — whose endpoints its\n * client is generated from, and whose package their imports resolve in.\n */\nfunction siteWorkspace(\n\tlayout: ImageLayout,\n\tsite: NormalizedAppConfig,\n\tapps: Readonly<Record<string, NormalizedAppConfig>>,\n\tworkspaceRoot: string,\n\tturboPackage: string,\n): Pick<ImageTemplateOptions, 'gkmPaths' | 'prunePackages' | 'clients'> {\n\tconst clients = siteClients(site, apps);\n\tconst packages = new Set<string>();\n\tif (layout.workspacePackage) packages.add(layout.workspacePackage);\n\tfor (const { app } of clients) {\n\t\tconst backend = apps[app]!;\n\t\tconst name = packageName(\n\t\t\tisAbsolute(backend.path)\n\t\t\t\t? backend.path\n\t\t\t\t: join(workspaceRoot, backend.path),\n\t\t);\n\t\tif (name) packages.add(name);\n\t}\n\tpackages.delete(turboPackage);\n\n\treturn {\n\t\tgkmPaths: layout.gkmPaths,\n\t\t...(packages.size > 0 ? { prunePackages: [...packages].sort() } : {}),\n\t\t...(clients.length > 0\n\t\t\t? {\n\t\t\t\t\tclients: clients.map(({ app }) => ({\n\t\t\t\t\t\tapp,\n\t\t\t\t\t\tpath: fromBuildRoot(layout, apps[app]!.path),\n\t\t\t\t\t})),\n\t\t\t\t}\n\t\t\t: {}),\n\t};\n}\n\n/**\n * The backends whose clients a site imports: each it depends on that gkm\n * builds from endpoints — not one with an entry of its own, and not one that\n * turned OpenAPI off.\n */\nexport function siteClients(\n\tsite: NormalizedAppConfig,\n\tapps: Readonly<Record<string, NormalizedAppConfig>>,\n): { app: string }[] {\n\treturn site.dependencies\n\t\t.filter((name) => {\n\t\t\tconst app = apps[name];\n\t\t\tif (!app || app.type !== 'backend' || app.entry) return false;\n\t\t\tif (app.openapi === false) return false;\n\t\t\tif (typeof app.openapi === 'object' && app.openapi.enabled === false)\n\t\t\t\treturn false;\n\t\t\treturn true;\n\t\t})\n\t\t.sort()\n\t\t.map((app) => ({ app }));\n}\n\n/**\n * The directories the workspace's construct globs start in, relative to its\n * root: `./constructs/**\\/*.ts` reads from `constructs`.\n */\nexport function constructRoots(workspace: NormalizedWorkspace): string[] {\n\tconst roots = new Set<string>();\n\tfor (const glob of allConstructGlobs(workspace)) {\n\t\tconst rel = toPosix(relative(workspace.root, glob));\n\t\tif (!rel || rel.startsWith('..')) continue;\n\t\tconst first = rel.split('/')[0]!;\n\t\tif (/[*?{}[\\]!]/.test(first)) continue;\n\t\troots.add(first);\n\t}\n\treturn [...roots].sort();\n}\n\nfunction packageName(dir: string): string | undefined {\n\ttry {\n\t\tconst pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));\n\t\treturn typeof pkg.name === 'string' ? pkg.name : undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction toPosix(path: string): string {\n\treturn path.split(sep).join('/');\n}\n","/**\n * The Caddy configuration a plan implies.\n *\n * A file server is a *domain that serves a bucket*, and locally it had neither:\n * `UPLOADS_SERVER_URL` resolved to MinIO's path-style address, which works and\n * is not the deployed shape. MinIO's own virtual-host mode cannot fix that —\n * it reads the leading label *as the bucket name*, so it produces the right\n * shape only when the server's id and the bucket's happen to agree, and never\n * for a server fronting two buckets.\n *\n * So the mapping lives in front of MinIO instead: one host per file server,\n * rewriting the bucket in as a path prefix. That is the whole of it — no\n * caching, because caching is not what is missing locally, and a caching proxy\n * would add an invalidation story a local stack has no use for.\n *\n * What it buys beyond the shape is a certificate. Caddy issues one per host\n * from its own CA, so a local address is `https://` like the deployed one, and\n * everything downstream of that — `Secure` cookies, `SameSite`, a cookie domain\n * with more than one host under it — starts behaving the way it will in\n * production rather than the way `http://localhost` allows.\n *\n * Pure, like `composeFor`: what should exist is data, and rendering it is one\n * line at the edge.\n */\n\nimport type { Plan, PlannedResource } from './plan';\n\n/** One host on the edge, and what answers behind it. */\nexport interface CaddySite {\n\t/** e.g. `api.kitchen-sink.localhost` */\n\thost: string;\n\t/** Where the request is sent, e.g. `http://minio:9000`. */\n\tupstream: string;\n\t/**\n\t * A path to rewrite to, where the upstream expects a different one.\n\t *\n\t * The bucket prefix on an object store is the case. A surface and a site\n\t * want none: they are already serving the paths the client asked for.\n\t */\n\trewrite?: string;\n}\n\n/**\n * The kinds the edge serves.\n *\n * Everything that owns a *public address*. Deployed, each of these has a real\n * hostname and a certificate, so this is what makes the local shape the same\n * one rather than a near-enough one — and the application is indifferent\n * either way, because it reads whichever address was injected and never\n * composes one.\n *\n * Adding a kind here is the whole of adding it to the edge.\n */\nexport const EDGE_KINDS: Readonly<Record<string, true>> = {\n\t'file-server': true,\n\t'rest-api': true,\n\tsite: true,\n};\n\n/**\n * How the edge reaches the host, from inside its container.\n *\n * A surface and a site are served by processes `gkm dev` starts on the host,\n * not by containers — so the edge has to leave Docker's network to reach them.\n * Docker Desktop resolves this name already; on Linux the compose definition\n * maps it to the host gateway.\n */\nconst HOST_GATEWAY = 'host.docker.internal';\n\n/**\n * The suffix every local host shares.\n *\n * `.localhost` rather than a made-up TLD because it resolves to loopback\n * without touching `/etc/hosts` — macOS and systemd-resolved both answer for\n * subdomains of it, and Caddy issues internal certificates for it by default.\n */\nexport const LOCAL_TLD = 'localhost';\n\n/**\n * The sites a plan implies, one per file server.\n *\n * The leading label is the *server's* stage-scoped name rather than the\n * bucket's, which is the difference that matters: two servers over one bucket\n * are a legitimate arrangement — two cache behaviours, one origin — and naming\n * the host after the bucket would make them collide.\n */\nexport function sitesFor(\n\tplan: Plan,\n\tproject: string,\n\t/**\n\t * Where surfaces and sites answer on the host, keyed by construct id.\n\t *\n\t * Assigned by whatever starts them rather than published by a container,\n\t * which is why they arrive here instead of being read off a port.\n\t */\n\taddresses: Readonly<Record<string, string>> = {},\n): CaddySite[] {\n\tconst byId = new Map(plan.resources.map((r) => [r.id, r]));\n\tconst sites: CaddySite[] = [];\n\n\tfor (const resource of plan.resources) {\n\t\tif (!EDGE_KINDS[resource.kind]) continue;\n\n\t\tconst host = hostFor(resource, project);\n\n\t\tif (resource.kind === 'file-server') {\n\t\t\t// The bucket is a prefix on the origin, never part of the address: the\n\t\t\t// client asks for the key it stored.\n\t\t\tconst origin = resource.of ? byId.get(resource.of) : undefined;\n\t\t\tif (!origin) continue;\n\n\t\t\tsites.push({\n\t\t\t\thost,\n\t\t\t\tupstream: 'http://minio:9000',\n\t\t\t\trewrite: `/${origin.name}{uri}`,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\t// A surface or a site: a process on the host, at an address something\n\t\t// else assigned. Without one there is nothing to route to — which is the\n\t\t// ordinary state before `gkm dev` has decided where things listen.\n\t\tconst address = addresses[resource.id];\n\t\tif (!address) continue;\n\n\t\tconst port = portOf(address);\n\t\tif (!port) continue;\n\n\t\tsites.push({ host, upstream: `http://${HOST_GATEWAY}:${port}` });\n\t}\n\n\treturn sites;\n}\n\n/** The port a local address answers on, defaulting by scheme. */\nfunction portOf(address: string): string | undefined {\n\ttry {\n\t\tconst { port, protocol } = new URL(address);\n\n\t\treturn port || (protocol === 'https:' ? '443' : '80');\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * Where one file server answers, including the stage its name carries.\n *\n * The project is a label rather than a decoration: it is what keeps two\n * checkouts from answering on one address. Omitted when there is no project to\n * name, because an empty label makes `uploads..localhost` — a host nothing\n * resolves and no certificate can be issued for.\n */\nexport function hostFor(resource: PlannedResource, project: string): string {\n\t// The root site answers on the project's bare host, as it does on the base\n\t// domain deployed; everything else on a subdomain of it. A stage other than\n\t// the local one keeps a label — the stage — because one edge serves every\n\t// stage, and `gkm test` claiming the bare host would take it from `gkm dev`.\n\t// Only under a project: without one, the \"bare host\" is `localhost` itself,\n\t// which is nobody's domain to take.\n\tconst label =\n\t\tresource.root && project ? stageOf(resource) : labelOf(resource);\n\n\treturn [label, project, LOCAL_TLD].filter(Boolean).join('.');\n}\n\n/**\n * A resource's own label: its `subdomain` where it declared one — the label a\n * deploy uses too — with the stage it carries kept, so `gkm test` and `gkm dev`\n * still answer on different hosts.\n */\nfunction labelOf(resource: PlannedResource): string {\n\tif (!resource.subdomain) return resource.name;\n\tconst stage = stageOf(resource);\n\treturn stage ? `${resource.subdomain}-${stage}` : resource.subdomain;\n}\n\n/** The stage a resource's name carries, or nothing for the local stage's. */\nfunction stageOf(resource: PlannedResource): string | undefined {\n\tconst base = resource.id.toLowerCase();\n\treturn resource.name === base\n\t\t? undefined\n\t\t: resource.name.slice(base.length + 1);\n}\n\n/**\n * The static root config, which only ever imports.\n *\n * Written once and identical for every stage, because the *sites* are what\n * differ and they live in their own files. That split is not tidiness: one\n * container serves every stage — the same way one Postgres holds `orders` and\n * `orders_test` — so a single Caddyfile would mean `gkm test` deleting the\n * routes `gkm dev` is serving, and each command silently breaking the other.\n */\nexport function caddyfileRoot(): string {\n\treturn `${GENERATED}\n{\n\t# No ACME, no public DNS: the CA is local and so are the names.\n\tlocal_certs\n\tauto_https disable_redirects\n}\n\n# One file per stage, so two stages share this edge the way they already share\n# one Postgres and one MinIO.\nimport /etc/caddy/sites/*.caddy\n`;\n}\n\n/**\n * One stage's sites, as the file imported above.\n *\n * `rewrite` rather than a path prefix on the address: the bucket is the\n * origin's business, and the client asks for the key it stored.\n *\n * `header_up Host {upstream_hostport}` matters more than it looks. MinIO routes\n * and signs on the Host header, so forwarding the requested hostname would make\n * it look for a bucket named after the domain.\n */\nexport function toCaddyfile(sites: readonly CaddySite[]): string {\n\tif (sites.length === 0) {\n\t\treturn `${GENERATED}\n# No file server is declared in this stage, so it routes nothing.\n`;\n\t}\n\n\tconst blocks = sites.map(\n\t\t({ host, upstream, rewrite }) => `\nhttps://${host} {\n\ttls internal\n\n\treverse_proxy ${upstream} {${\n\t\trewrite\n\t\t\t? `\n\t\trewrite ${rewrite}\n\t\t# MinIO routes and signs on the Host header, so forwarding the requested\n\t\t# hostname would make it look for a bucket named after the domain.\n\t\theader_up Host {upstream_hostport}`\n\t\t\t: `\n\t\t# The application reads whatever address was injected, so it has to see\n\t\t# the one a caller used — that is what makes a redirect, a cookie domain\n\t\t# and a generated link point back here rather than at the upstream.\n\t\theader_up Host {host}\n\t\theader_up X-Forwarded-Proto {scheme}`\n\t}\n\t}\n}`,\n\t);\n\n\treturn `${GENERATED}${blocks.join('\\n')}\\n`;\n}\n\nconst GENERATED = `# Generated by gkm from the construct manifest — do not edit.\n#\n# One host per declared file server, in front of the object store that holds its\n# objects. This is the shape a file server has deployed — a domain serving a\n# bucket — which MinIO alone cannot produce: its virtual-host mode reads the\n# leading label as the bucket name.\n#\n# Certificates come from Caddy's own CA. \"gkm dev\" and \"gkm test\" export its\n# root and point Node at it, so nothing has to be installed to use these URLs\n# from code; a browser needs the root trusted once.\n`;\n","/**\n * The AWS emulator (floci, LocalStack-compatible) the `sns` backend runs on\n * locally — the facts the compose file, the env writer and the applier must\n * agree on.\n *\n * The emulator has one account and answers with deterministic ARNs and queue\n * URLs, which is what lets a connection string be composed before the topic\n * or queue it names has been created.\n */\n\n/** The emulator's single account. */\nexport const EMULATOR_ACCOUNT = '000000000000';\n\nexport const EMULATOR_REGION = 'us-east-1';\n\n/**\n * The emulator's credential. The `LSIA` prefix is what LocalStack required on\n * issued keys; floci does not care, and keeping it means a project that pins\n * the old image still works.\n */\nexport const EMULATOR_CREDENTIALS = {\n\taccessKeyId: 'LSIAQAAAAAAVNCBMPNSG',\n\tsecretAccessKey: 'geekmidas',\n} as const;\n\n/** The emulator's address from the host, on the port it was published on. */\nexport function emulatorEndpoint(port: number): string {\n\treturn `http://localhost:${port}`;\n}\n\nexport function emulatorTopicArn(name: string): string {\n\treturn `arn:aws:sns:${EMULATOR_REGION}:${EMULATOR_ACCOUNT}:${name}`;\n}\n\nexport function emulatorQueueUrl(name: string, port: number): string {\n\treturn `${emulatorEndpoint(port)}/${EMULATOR_ACCOUNT}/${name}`;\n}\n","/**\n * Which site the base domain points at — deployed and locally alike.\n *\n * Three rules, in order, and the last one is the point:\n *\n * 1. **One site** — it is the root. Unambiguous, so nothing has to be said.\n * 2. **Named `web`** — the convention wins, because people already rely on it.\n * 3. **Declared `root: true`** — the site says so itself.\n *\n * Anything else throws. It used to take the *first* site it iterated, and that\n * order came from the manifest, which is built in glob traversal order — so two\n * sites with neither named `web` meant renaming a file could move the root\n * domain, silently.\n *\n * One rule for both targets: deployed the root site gets the base domain, and\n * locally it gets the project's bare host behind the edge. Before this was\n * shared, the local edge put every site on a subdomain, so what a developer\n * browsed was not the shape production served.\n *\n * @returns the root site's name, or `undefined` when there are no sites\n * @throws {AmbiguousRootSite} when several sites could be the root and none says it is\n */\nexport function rootSite(\n\tsites: readonly { name: string; root?: boolean }[],\n): string | undefined {\n\tif (sites.length === 0) return undefined;\n\n\t// 1. The only site there is.\n\tif (sites.length === 1) return sites[0]!.name;\n\n\t// 2. The convention.\n\tif (sites.some(({ name }) => name === 'web')) return 'web';\n\n\t// 3. What a site declared about itself.\n\tconst declared = sites.filter(({ root }) => root === true);\n\tif (declared.length === 1) return declared[0]!.name;\n\n\tthrow new AmbiguousRootSite(\n\t\tsites.map(({ name }) => name),\n\t\tdeclared.length > 1,\n\t);\n}\n\n/** Several sites could hold the base domain, and none of them says it does. */\nexport class AmbiguousRootSite extends Error {\n\tconstructor(\n\t\treadonly sites: readonly string[],\n\t\treadonly tooMany = false,\n\t) {\n\t\tsuper(\n\t\t\ttooMany\n\t\t\t\t? `More than one site declares \\`root: true\\` — ${sites.join(', ')}. ` +\n\t\t\t\t\t\t`The base domain points at one of them.`\n\t\t\t\t: `${sites.length} sites and nothing says which holds the base ` +\n\t\t\t\t\t\t`domain: ${sites.join(', ')}. Name one of them \\`web\\`, or ` +\n\t\t\t\t\t\t`declare \\`root: true\\` on the one the base domain points at. ` +\n\t\t\t\t\t\t`It used to be whichever the glob reached first, which is not a ` +\n\t\t\t\t\t\t`thing to decide a hostname.`,\n\t\t);\n\t\tthis.name = 'AmbiguousRootSite';\n\t}\n}\n","/**\n * The reconcile plan — what a manifest wants to exist, for one stage.\n *\n * Pure. Turning the plan into running containers and provisioned resources is\n * the applier's job; deciding *what* should exist is this, so the decisions can\n * be asserted as data rather than by starting Docker.\n *\n * One plan serves every stage. `gkm dev` reconciles `development` and `gkm test`\n * reconciles `test`; they differ only in what the resources are called, never in\n * what infrastructure exists. Roles are cluster-scoped in Postgres while grants\n * are per-object, so one container and one role pair serve both.\n */\n\nimport {\n\ttype ConstructManifest,\n\ttype Declaration,\n\ttype DeclarationKind,\n\tdependentsOf,\n\ttype PostgresVersion,\n\tprovidedKeyFor,\n\tpublicEnvFor,\n} from '@geekmidas/manifest';\nimport {\n\ttype CacheBackend,\n\tDEFAULT_CACHE,\n\ttype EventsBackend,\n\tholdsEveryCache,\n} from '../types';\nimport { appKey } from '../workspace/derive.js';\nimport { rootSite } from '../workspace/rootSite.js';\nimport { EDGE_KINDS } from './caddyfile';\nimport { fakeKey, type PlannedFake } from './containers';\nimport type { LocalFake } from './fakes';\n\n/**\n * Which container serves a kind.\n *\n * Derived kinds map to the same container as their parent — a schema tenant\n * lives in its parent's Postgres — so the set is deduplicated rather than\n * counted.\n */\nconst CONTAINERS: Partial<Record<DeclarationKind, string>> = {\n\tdatabase: 'postgres',\n\t'database-reader': 'postgres',\n\t'database-schema': 'postgres',\n\tobjects: 'minio',\n\t// The server is not the bucket. Its own container is the local edge, which\n\t// is what lets it answer on a *host* rather than on a path under MinIO —\n\t// the shape it has deployed, and the one MinIO's virtual-host mode cannot\n\t// produce because that mode reads the leading label as the bucket name.\n\t//\n\t// MinIO still runs: it is implied by the bucket this server is declared\n\t// over, which is a parent it always has.\n\t'file-server': 'caddy',\n\t// Every mail backend speaks SMTP, so locally there is one answer whichever\n\t// one is selected — which is the same reason the declaration has no\n\t// `provider` field.\n\temail: 'mailpit',\n};\n\n/**\n * The container a cache backend needs locally.\n *\n * Each one matches the protocol its deployed form speaks, which is the property\n * worth having: a cache that behaves differently in the two places is worse than\n * a slower one.\n *\n * `db` is deliberately absent, the same way `pgboss` is absent from\n * `EVENT_CONTAINERS`: the cache is a table in a database the manifest already\n * declares, so mapping it here would start a container for a project that needs\n * none.\n */\nconst CACHE_CONTAINERS: Partial<Record<CacheBackend, string>> = {\n\t// The proxy, not the Redis behind it: the client speaks HTTP with a token\n\t// wherever it runs, which is what makes dev and prod the same client.\n\tupstash: 'redis-http',\n\t// The wire protocol, which is what ElastiCache offers — so dev speaks it too.\n\telasticache: 'redis',\n\t// The stack's own Redis: the same container, on the stack's network.\n\tredis: 'redis',\n};\n\n/**\n * Kinds that resolve a value without anything running.\n *\n * A secret is the case: it has no address, so there is nothing to start and\n * nothing to connect to — but it still appears in the plan, because the target\n * still has to resolve it.\n */\nconst CONTAINERLESS: Partial<Record<DeclarationKind, true>> = {\n\tsecret: true,\n\t// A credential has no address either — and unlike a secret it has no value\n\t// this target can derive, because it was issued by somebody else.\n\tcredential: true,\n\t// An encryption key is a value too: a keyring the target derives, the way\n\t// it derives a secret.\n\tencryption: true,\n\t// A surface answers on the app's own port. It is the first kind whose\n\t// address belongs to something gkm starts rather than something Docker does.\n\t'rest-api': true,\n\t// A site is the second: it is served by its own framework's dev server, and\n\t// what the target resolves for it is only where that server answers.\n\tsite: true,\n\t// A mobile app resolves its scheme, which nothing has to be running for.\n\t'mobile-app': true,\n\t// An external API is answered by its fake. A module fake runs in no\n\t// container — gkm serves it — and an image's container is added per API,\n\t// since no kind-wide table can name it.\n\t'external-api': true,\n};\n\n/**\n * Containers that cannot run alone.\n *\n * The one case is the cache: `serverless-redis-http` is a proxy and needs the\n * Redis it proxies. Kept here rather than in the compose definition so the plan\n * -- which is what decides what must be running -- still names everything.\n */\nconst REQUIRES: Readonly<Record<string, readonly string[]>> = {\n\t'redis-http': ['redis'],\n};\n\n/**\n * The role each kind's one key plays, where it is not a URL.\n *\n * The twin of `ProvidesByKind` in `@geekmidas/manifest`: that is what a\n * construct declares, this is what the local target resolves, and both derive\n * the key from the same `provideKey` so they cannot disagree.\n */\nconst ROLES: Partial<Record<DeclarationKind, string>> = {\n\tqueue: 'publisherConnectionString',\n\ttopic: 'publisherConnectionString',\n\tcredential: 'credentials',\n\t'mobile-app': 'scheme',\n};\n\n/** The kinds whose container is the events backend's rather than their own. */\nconst EVENT_KINDS: Partial<Record<DeclarationKind, true>> = {\n\tqueue: true,\n\ttopic: true,\n};\n\n/**\n * Which kinds provision something *inside* their container.\n *\n * A database creates a database, a bucket creates a bucket; mail creates\n * nothing — Mailpit accepts whatever is sent to it and has no per-stage object\n * to name. Both still appear in the plan, because both still resolve a URL; the\n * flag is the difference between \"must be running\" and \"must be created\".\n */\nconst PROVISIONS: Partial<Record<DeclarationKind, true>> = {\n\tdatabase: true,\n\t'database-reader': true,\n\t'database-schema': true,\n\tobjects: true,\n\t// Only a cache that lives in a database creates anything — its table. The\n\t// other backends have nothing to create inside the container they use.\n\tcache: true,\n\t// The open patterns become a bucket policy on the origin — real\n\t// enforcement locally, from the same declaration the CDN behaviour comes\n\t// from deployed.\n\t'file-server': true,\n};\n// Queues and topics are absent deliberately: pg-boss creates its own schema and\n// tables on first connect, and a RabbitMQ exchange is declared by the client\n// that binds to it. Both still appear in the plan, because both still resolve a\n// connection string.\n\n/**\n * The container each events backend needs, where it needs one of its own.\n *\n * `pgboss` is deliberately absent rather than mapped to `postgres`: it is a\n * schema tenant in a database the manifest already declares, so mapping it here\n * would start a Postgres for a project that declared no database at all.\n *\n * This one comes from config rather than from a declaration because there are\n * no `topic` or `queue` kinds yet. When they land the mapping moves to\n * {@link CONTAINERS} keyed by kind, and this constant goes with them.\n */\nconst EVENT_CONTAINERS: Partial<Record<EventsBackend, string>> = {\n\tsns: 'localstack',\n\trabbitmq: 'rabbitmq',\n};\n\n/** The backend a project gets when it declares queues or topics and says nothing. */\nexport const DEFAULT_EVENTS: EventsBackend = 'pgboss';\n\n/**\n * How a resource of each kind separates its stage suffix.\n *\n * Postgres identifiers are snake_case and a hyphen would need quoting\n * everywhere; buckets and queues are kebab and disallow underscores in some\n * providers. The suffix is the same word either way.\n */\nconst SEPARATORS: Partial<Record<DeclarationKind, string>> = {\n\tdatabase: '_',\n\t'database-reader': '_',\n\t'database-schema': '_',\n\tobjects: '-',\n};\n\n/** One thing that must exist, and what it is called in this stage. */\nexport interface PlannedResource {\n\t/** The construct's canonical id, e.g. `Orders`. */\n\tid: string;\n\tkind: DeclarationKind;\n\t/** The container that serves it, e.g. `postgres`. Absent for a secret. */\n\tcontainer?: string;\n\t/** The stage-scoped name, e.g. `orders_test`. */\n\tname: string;\n\t/** The env key it resolves onto anything depending on it. */\n\tenvKey: string;\n\t/** The parent it derives from, for kinds that provision nothing themselves. */\n\tof?: string;\n\t/**\n\t * Whether anything has to be created inside the container for this.\n\t *\n\t * False for mail: Mailpit takes whatever is sent to it. The applier skips\n\t * these; the env writer still resolves their URL.\n\t */\n\tprovisions: boolean;\n\t/**\n\t * The site the base domain points at, by the rule a deploy uses\n\t * (`rootSite`) — so locally it answers on the project's bare host, the\n\t * shape production serves, rather than a subdomain of it.\n\t */\n\troot?: true;\n\t/**\n\t * The label a surface or site answers on, its declaration's `subdomain` —\n\t * the same one a deploy puts under the stage's domain.\n\t */\n\tsubdomain?: string;\n\t/** The schema a tenant pins on its roles' `search_path`. */\n\tschema?: string;\n\t/** For a cache in a database: the table entries are kept in. */\n\ttable?: string;\n\t/**\n\t * Whether to provision the owner/runtime role split.\n\t *\n\t * `false` is the documented downgrade — both URLs fall back to the cluster's\n\t * master credential — and it is a choice someone made rather than a default\n\t * they got, which is why it is carried through rather than inferred.\n\t */\n\troles?: boolean;\n\t/**\n\t * For a database: the engine major version to run.\n\t *\n\t * Carried like `schema` and `roles` — read off the declaration so the\n\t * container tag and the deployed engine come from one statement. They used\n\t * to come from two, and had drifted a major apart with nothing recording it.\n\t */\n\tversion?: PostgresVersion;\n\t/**\n\t * For a file server: the paths it serves without a signature.\n\t *\n\t * Carried into the plan because the local target enforces them for real — a\n\t * bucket policy with prefix resources, from the same declaration the CDN\n\t * behaviour comes from deployed. An open path that is open locally and shut\n\t * in production is the bug this avoids.\n\t */\n\topen?: readonly string[];\n\t/**\n\t * For an external API: the port key its fake answers on, and the\n\t * credentials it accepts — what a local stage is handed in place of the\n\t * provider and the stage's own.\n\t */\n\tfake?: { key: string; credentials: string };\n\t/**\n\t * For an external API with no fake in this plan: the URL it is called at,\n\t * as declared — one string, or one per stage name.\n\t */\n\turl?: string | Readonly<Record<string, string>>;\n\t/**\n\t * For a site: the keys its bundle needs, mapped to the key each value comes\n\t * from — `{ VITE_API_URL: 'API_URL' }`.\n\t *\n\t * One neutral name from the construct and one serialisation per framework.\n\t * The prefix is the whole of what varies, which is why the mapping is a\n\t * rename rather than a second derivation: `API_URL` is resolved once, by the\n\t * same code that resolves it for the server, and a site simply reads it\n\t * under the name its bundler will inline.\n\t */\n\tpublicEnv?: Record<string, string>;\n\t/** For a mobile app: the scheme it gave, when not the project's name. */\n\tscheme?: string;\n\t/**\n\t * The ids that depend on this one — the graph read backwards.\n\t *\n\t * Carried on surfaces, where it is the whole answer to \"who may call this\":\n\t * CORS origins, trusted origins, and the cookie domain are three readings of\n\t * this one list. Resolved to addresses by the target, because the manifest\n\t * knows which constructs call which and only the target knows where any of\n\t * them answer.\n\t */\n\tcallers?: string[];\n}\n\nexport interface Plan {\n\tstage: string;\n\t/**\n\t * The events backend this plan was built for.\n\t *\n\t * Carried on the plan rather than passed alongside it, because everything\n\t * downstream that composes a connection string needs it and reading it off\n\t * the plan is what keeps the two from disagreeing.\n\t */\n\tevents: EventsBackend;\n\t/** Where the cache lives, for everything downstream that composes its URL. */\n\tcache: CacheBackend;\n\t/**\n\t * A worker is declared and the broker is pg-boss, so the broker is needed\n\t * whether or not a queue or topic is.\n\t *\n\t * On a server a worker's crons are scheduled through the events broker —\n\t * the app's one pg-boss. Crons are not in the manifest (a worker does not\n\t * list them), so the worker is the signal. pg-boss is a schema in the\n\t * declared database, so this starts nothing and provisions nothing; it only\n\t * means the broker's connection strings are resolved.\n\t */\n\tworkerBroker?: true;\n\t/** Containers to start, deduplicated. */\n\tcontainers: string[];\n\t/**\n\t * Each external API's fake, by its port key — `<id>-fake`. An image fake's\n\t * key is also in `containers`; a module fake's is a port gkm serves on.\n\t */\n\tfakes?: Record<string, PlannedFake>;\n\t/**\n\t * Everything the stage resolves a URL for, parents before children.\n\t *\n\t * Not all of it is provisioned — see `PlannedResource.provisions`.\n\t */\n\tresources: PlannedResource[];\n}\n\n/** What the plan needs that the manifest cannot tell it. */\nexport interface PlanOptions {\n\t/**\n\t * The project's local stage (`stages.local`), whose resources carry no\n\t * suffix. Absent, every stage is suffixed — which is right for a deploy,\n\t * since a deployed stage is never the local one.\n\t */\n\tlocalStage?: string;\n\t/**\n\t * Whether the local edge fronts the addresses this plan resolves.\n\t *\n\t * On by default, because deployed every surface, site and file server has a\n\t * real hostname and a certificate, and the point of the local target is to\n\t * be the same shape rather than a near-enough one. Off falls back to\n\t * `http://localhost:<port>`.\n\t *\n\t * Config rather than declaration for the same reason `cache` is: the\n\t * application reads whichever address was injected and composes none, so it\n\t * cannot tell the difference.\n\t */\n\tedge?: boolean;\n\t/**\n\t * Where a declared cache lives, which decides both the container it needs\n\t * locally and the protocol its URL speaks.\n\t *\n\t * Config rather than declaration, because the same application code caches\n\t * into any of them — the same reason `events` is config.\n\t */\n\tcache?: CacheBackend;\n\t/**\n\t * The events backend, which selects a container of its own for everything\n\t * except pg-boss.\n\t *\n\t * Config rather than declaration only until `topic` and `queue` are kinds —\n\t * see {@link EVENT_CONTAINERS}.\n\t */\n\tevents?: EventsBackend;\n\t/**\n\t * Containers no construct implies — the escape hatch, and the whole of what\n\t * the workspace `services:` block becomes.\n\t *\n\t * A list of exceptions rather than a list you maintain: everything a\n\t * construct implies is already in the plan before this is read.\n\t */\n\textraContainers?: readonly string[];\n\t/**\n\t * Each external API's fake, by id — read from `test/fakes` by `readFakes`,\n\t * since the declaration never names one. An external API without one\n\t * resolves no URL.\n\t */\n\tfakes?: Readonly<Record<string, LocalFake>>;\n}\n\n/**\n * The stage-scoped name for a resource.\n *\n * The local stage carries no suffix, so `gkm dev` keeps using `orders` and a\n * developer's psql history, saved connections, and muscle memory survive a\n * change of stage name. Every other stage is suffixed.\n */\nexport function resourceName(\n\tid: string,\n\tkind: DeclarationKind,\n\tstage: string,\n\t/** The project's local stage, whose names are unsuffixed. */\n\tlocalStage?: string,\n): string {\n\tconst base = id.toLowerCase();\n\tif (stage === localStage) return base;\n\n\treturn `${base}${SEPARATORS[kind] ?? '-'}${stage}`;\n}\n\n/**\n * The container a kind needs, or `undefined` if it needs none.\n *\n * Queues and topics have no container of their own — they live in whichever\n * broker the project selected, which is why the backend is an argument here\n * rather than a second pass that adds containers after the fact.\n */\nexport function containerFor(\n\tkind: DeclarationKind,\n\tevents: EventsBackend = DEFAULT_EVENTS,\n\t// The target-aware choice is made upstream, in `reconcile/workspace.ts`,\n\t// where the workspace knows where it deploys. This fires only for a caller\n\t// that passed nothing at all.\n\tcache: CacheBackend = DEFAULT_CACHE.aws,\n\t/** Whether the declaration named a parent — see the `cache` branch. */\n\tderived = false,\n\t/** Whether the local edge exists — see the `file-server` branch. */\n\tedge = true,\n): string | undefined {\n\tif (EVENT_KINDS[kind]) {\n\t\t// pg-boss is deliberately absent from EVENT_CONTAINERS: it is a schema\n\t\t// tenant in a database the manifest already declares, so its container is\n\t\t// that database's.\n\t\treturn EVENT_CONTAINERS[events] ?? CONTAINERS.database;\n\t}\n\n\t// A file server answers on the edge, which is what gives it a host of its\n\t// own. With the edge off it falls back to the object store that holds its\n\t// objects — a path under MinIO, which works and is not the deployed shape.\n\tif (kind === 'file-server') return edge ? 'caddy' : CONTAINERS.objects;\n\n\t// The same shape: a cache in the database lives in the database's container.\n\tif (kind === 'cache') {\n\t\t// A cache that *named* a database is in it whatever the backend config\n\t\t// says — the declaration is the stronger statement, and config choosing\n\t\t// otherwise would move a cache the app said lives here — except where\n\t\t// the backend holds every cache (the stack's own Redis).\n\t\tif (derived && !holdsEveryCache(cache)) return CONTAINERS.database;\n\n\t\treturn CACHE_CONTAINERS[cache] ?? CONTAINERS.database;\n\t}\n\n\treturn CONTAINERS[kind];\n}\n\n/**\n * Build the plan for a manifest and stage.\n *\n * `order` is the caller's provisioning order — parents before children — which\n * `provisionOrder` produces from the manifest. Passing it in keeps this\n * function free of the traversal and makes the ordering assertable on its own.\n */\nexport function planFor(\n\tmanifest: ConstructManifest,\n\tstage: string,\n\torder: readonly string[],\n\toptions: PlanOptions = {},\n): Plan {\n\tconst containers = new Set<string>();\n\tconst resources: PlannedResource[] = [];\n\tconst fakes: Record<string, PlannedFake> = {};\n\n\tconst events = options.events ?? DEFAULT_EVENTS;\n\tconst cache = options.cache ?? DEFAULT_CACHE.aws;\n\n\tfor (const id of order) {\n\t\tconst declaration: Declaration | undefined = manifest[id];\n\t\tif (!declaration) continue;\n\n\t\tconst local =\n\t\t\tdeclaration.kind === 'external-api' ? options.fakes?.[id] : undefined;\n\t\tconst fake = local && { key: fakeKey(id), ...local };\n\t\tif (fake) {\n\t\t\tfakes[fake.key] = {\n\t\t\t\tid,\n\t\t\t\t...(fake.image ? { image: fake.image, port: fake.port } : {}),\n\t\t\t};\n\t\t}\n\n\t\t// A cache whose backend holds every cache is detached from the database\n\t\t// it was declared from: it has no `of` and no table, so nothing\n\t\t// downstream composes a database URL for it or creates its table.\n\t\tconst detached = declaration.kind === 'cache' && holdsEveryCache(cache);\n\n\t\tconst container = fake?.image\n\t\t\t? fake.key\n\t\t\t: containerFor(\n\t\t\t\t\tdeclaration.kind,\n\t\t\t\t\tevents,\n\t\t\t\t\tcache,\n\t\t\t\t\t'of' in declaration && typeof declaration.of === 'string',\n\t\t\t\t\toptions.edge !== false,\n\t\t\t\t);\n\t\tif (!container && !CONTAINERLESS[declaration.kind]) continue;\n\n\t\tif (container) {\n\t\t\tcontainers.add(container);\n\t\t\tfor (const required of REQUIRES[container] ?? [])\n\t\t\t\tcontainers.add(required);\n\t\t}\n\n\t\tresources.push({\n\t\t\tid,\n\t\t\tkind: declaration.kind,\n\t\t\t...(container ? { container } : {}),\n\t\t\tname: resourceName(id, declaration.kind, stage, options.localStage),\n\t\t\t// Through the shared derivation: a secret's name *is* its key, and\n\t\t\t// this target and the deploy target must not answer that separately.\n\t\t\tenvKey: providedKeyFor(\n\t\t\t\tid,\n\t\t\t\tdeclaration.kind,\n\t\t\t\tROLES[declaration.kind] ?? 'url',\n\t\t\t),\n\t\t\tprovisions: PROVISIONS[declaration.kind] === true,\n\t\t\t// Only for surfaces. Every other kind is reached by a URL that says\n\t\t\t// nothing about who holds it, so a caller list would be a fact with\n\t\t\t// no reader.\n\t\t\t...(declaration.kind === 'rest-api'\n\t\t\t\t? { callers: dependentsOf(manifest, id) }\n\t\t\t\t: {}),\n\t\t\t...(declaration.kind === 'site'\n\t\t\t\t? { publicEnv: publicEnvFor(declaration, manifest) }\n\t\t\t\t: {}),\n\t\t\t...(declaration.kind === 'mobile-app'\n\t\t\t\t? {\n\t\t\t\t\t\tpublicEnv: publicEnvFor(declaration, manifest),\n\t\t\t\t\t\t...(declaration.scheme ? { scheme: declaration.scheme } : {}),\n\t\t\t\t\t}\n\t\t\t\t: {}),\n\t\t\t...(declaration.kind === 'file-server' && declaration.open?.length\n\t\t\t\t? { open: declaration.open }\n\t\t\t\t: {}),\n\t\t\t...((declaration.kind === 'rest-api' || declaration.kind === 'site') &&\n\t\t\tdeclaration.subdomain\n\t\t\t\t? { subdomain: declaration.subdomain }\n\t\t\t\t: {}),\n\t\t\t...(fake\n\t\t\t\t? { fake: { key: fake.key, credentials: fake.credentials } }\n\t\t\t\t: {}),\n\t\t\t...(declaration.kind === 'external-api' && !fake\n\t\t\t\t? { url: declaration.url }\n\t\t\t\t: {}),\n\t\t\t...('of' in declaration && !detached ? { of: declaration.of } : {}),\n\t\t\t...('schema' in declaration && declaration.schema\n\t\t\t\t? { schema: declaration.schema }\n\t\t\t\t: {}),\n\t\t\t...('version' in declaration && declaration.version\n\t\t\t\t? { version: declaration.version }\n\t\t\t\t: {}),\n\t\t\t...('roles' in declaration && declaration.roles === false\n\t\t\t\t? { roles: false }\n\t\t\t\t: {}),\n\t\t\t...('table' in declaration && declaration.table && !detached\n\t\t\t\t? { table: declaration.table }\n\t\t\t\t: {}),\n\t\t});\n\t}\n\n\t// The edge serves anything that owns a public address — a surface and a site\n\t// as much as a file server. Added here rather than in `containerFor` because\n\t// those two are containerless in every other sense: nothing runs them in\n\t// Docker, and what the edge does is put a hostname and a certificate in\n\t// front of a process on the host.\n\t//\n\t// `edge: false` opts out, and then every local address is\n\t// `http://localhost:<port>` as before. The application cannot tell: it reads\n\t// whichever address was injected and composes none.\n\tif (\n\t\toptions.edge !== false &&\n\t\tresources.some((r) => EDGE_KINDS[r.kind] === true)\n\t) {\n\t\tcontainers.add('caddy');\n\t}\n\n\tfor (const extra of options.extraContainers ?? []) containers.add(extra);\n\n\t// pg-boss lives in a declared database. Without one there is nothing for it\n\t// to live in, and starting a Postgres to hold only a queue would be the\n\t// container this design refuses to invent.\n\tif (\n\t\tevents === 'pgboss' &&\n\t\tresources.some((r) => EVENT_KINDS[r.kind]) &&\n\t\t!resources.some((r) => r.kind === 'database')\n\t) {\n\t\tthrow new PgBossNeedsDatabase(\n\t\t\tresources.filter((r) => EVENT_KINDS[r.kind]).map((r) => r.id),\n\t\t);\n\t}\n\n\t// A cache the deploy target placed in a database becomes an *edge*, here,\n\t// once.\n\t//\n\t// A server target's `'db'` cache names something that exists in this graph, which\n\t// makes it different in kind from `upstash` or `elasticache`: those name a\n\t// backend nothing declares, while this one names a database sitting right\n\t// there. So it is resolved to `of` — the same field `database.cache()` sets\n\t// — and every reader downstream follows an edge instead of re-deriving the\n\t// answer from a string.\n\t//\n\t// It also turns a silent guess into an error. \"The declared database\" is\n\t// unambiguous with one and arbitrary with two, and picking whichever came\n\t// first would put the cache in a database nobody chose.\n\t//\n\t// A cache that named its parent is untouched: the declaration is the\n\t// stronger statement, and its parent is checked by `assertDerivations`,\n\t// which names the database that is missing rather than reporting that some\n\t// database is.\n\tif (cache === 'db') {\n\t\tconst placeless = resources.filter((r) => r.kind === 'cache' && !r.of);\n\n\t\tif (placeless.length > 0) {\n\t\t\tconst databases = resources.filter((r) => r.kind === 'database');\n\t\t\tconst [only] = databases;\n\n\t\t\t// Starting a Postgres to hold only a cache would be the container this\n\t\t\t// design refuses to invent.\n\t\t\tif (!only) {\n\t\t\t\tthrow new CacheNeedsDatabase(\n\t\t\t\t\tresources.filter((r) => r.kind === 'cache').map((r) => r.id),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (databases.length > 1) {\n\t\t\t\tthrow new CacheIsAmbiguous(\n\t\t\t\t\tplaceless.map((r) => r.id),\n\t\t\t\t\tdatabases.map((r) => r.id),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tfor (const resource of placeless) resource.of = only.id;\n\t\t}\n\t}\n\n\t// The site the base domain points at: the same answer a deploy gives, so the\n\t// local edge serves it on the bare host.\n\tconst sites = resources.filter((r) => r.kind === 'site');\n\tconst root = rootSite(\n\t\tsites.map((r) => {\n\t\t\tconst declared = manifest[r.id];\n\t\t\treturn {\n\t\t\t\tname: appKey(r.id),\n\t\t\t\t...(declared?.kind === 'site' && declared.root ? { root: true } : {}),\n\t\t\t};\n\t\t}),\n\t);\n\tfor (const site of sites) {\n\t\tif (appKey(site.id) === root) site.root = true;\n\t}\n\n\tconst workerBroker =\n\t\tevents === 'pgboss' &&\n\t\tObject.values(manifest).some((d) => d?.kind === 'worker') &&\n\t\tresources.some((r) => r.kind === 'database');\n\n\treturn {\n\t\tstage,\n\t\tevents,\n\t\tcache,\n\t\tcontainers: [...containers],\n\t\tresources,\n\t\t...(Object.keys(fakes).length ? { fakes } : {}),\n\t\t...(workerBroker ? { workerBroker: true as const } : {}),\n\t};\n}\n\n/**\n * Queues or topics were declared with pg-boss and no database to hold them.\n *\n * Not a reconcile failure but a manifest one: the fix is a line of application\n * code either way — declare a database, or select a backend that brings its own\n * broker.\n */\nexport class CacheNeedsDatabase extends Error {\n\tconstructor(readonly ids: readonly string[]) {\n\t\tsuper(\n\t\t\t`A cache backed by the database needs a declared database, and none ` +\n\t\t\t\t`was declared, and on this deploy target that is where a cache lives. ` +\n\t\t\t\t`Declare one. Caches affected: ${ids.join(', ')}.`,\n\t\t);\n\t\tthis.name = 'CacheNeedsDatabase';\n\t}\n}\n\n/**\n * A `'db'` cache — a server target's default — in an app that declares more\n * than one database.\n *\n * The target says *a* database and the graph offers several, so there is no\n * answer to give — and the wrong kind of failure would be to pick one, since a\n * cache silently landing in the wrong database is a bug that surfaces as\n * missing rows much later. The fix is a stronger statement in application code:\n * `orders.cache('Sessions')` names the database, which is a fact about the\n * application rather than a deployment choice.\n */\nexport class CacheIsAmbiguous extends Error {\n\tconstructor(\n\t\treadonly ids: readonly string[],\n\t\treadonly databases: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`A cache backed by the database needs to know which one, and this app ` +\n\t\t\t\t`declares ${databases.length}: ${databases.join(', ')}. Declare the ` +\n\t\t\t\t`cache from its database — e.g. ${databases[0]}.cache('Sessions') — ` +\n\t\t\t\t`rather than as a bare new Cache(). Caches affected: ${ids.join(', ')}.`,\n\t\t);\n\t\tthis.name = 'CacheIsAmbiguous';\n\t}\n}\n\nexport class PgBossNeedsDatabase extends Error {\n\tconstructor(readonly ids: readonly string[]) {\n\t\tsuper(\n\t\t\t`pg-boss keeps its queues in a declared database, and none was declared. ` +\n\t\t\t\t`Declare one: on this deploy target, events are carried by pg-boss. ` +\n\t\t\t\t`Queues and topics affected: ${ids.join(', ')}.`,\n\t\t);\n\t\tthis.name = 'PgBossNeedsDatabase';\n\t}\n}\n","/**\n * The URLs a reconciled stage injects.\n *\n * One `<NAME>_URL` per construct, and the protocol picks the driver. This is the\n * seam between the target that writes a URL and the client that parses it, so\n * every component of it is read off the resource — never inherited from ambient\n * environment. A bucket can live in a different region than the function reading\n * it, and `AWS_REGION` in a Lambda is the *function's* region, so a URL that\n * omits `?region=` breaks cross-region silently at runtime.\n *\n * Locally that self-containment is what lets the port be whatever was free: the\n * app reads `ORDERS_URL` and never sees a host or a port.\n */\n\nimport { createHash } from 'node:crypto';\nimport { formatKeyring } from '@geekmidas/constructs/encryption';\nimport { ownerRole, readerRole } from '@geekmidas/db/pg/roles';\nimport * as snsUrl from '@geekmidas/events/sns/url';\nimport * as sqsUrl from '@geekmidas/events/sqs/url';\nimport {\n\tcacheTable,\n\tcookieDomain,\n\texternalApiUrl,\n\tmobileOrigins,\n\tprovideKey,\n\tschemeBase,\n} from '@geekmidas/manifest';\nimport { hostFor } from './caddyfile';\nimport { primaryPortKey } from './containers';\nimport {\n\tEMULATOR_CREDENTIALS,\n\tEMULATOR_REGION,\n\temulatorEndpoint,\n\temulatorQueueUrl,\n\temulatorTopicArn,\n} from './emulator';\nimport { PgBossNeedsDatabase, type Plan, type PlannedResource } from './plan';\nimport type { PortAssignments } from './ports';\n\n/**\n * The credential local containers are brought up with.\n *\n * The design's `roles: false` case — both URLs fall back to the cluster's master\n * credential. A deliberate downgrade, not a default: it holds until the role DDL\n * lands in `@geekmidas/db`, at which point the runtime role's URL replaces this\n * and the owner URL stops being reachable from any manifest edge.\n */\nconst LOCAL_USER = 'geekmidas';\n\n/** The host containers are published on. */\nconst LOCAL_HOST = 'localhost';\n\n/** The region local buckets claim, so the client has one to sign with. */\nconst LOCAL_REGION = 'us-east-1';\n\n/**\n * The credentials the S3 client resolves for MinIO.\n *\n * An `s3://` URL deliberately carries none: deployed, the SDK reads them from\n * the execution role, so a URL that embedded a key would be one more thing to\n * rotate and one more thing to leak into a log. Locally there is no role, and\n * the same chain reads these — which is why they are injected beside the URL\n * rather than written into it.\n */\nconst STORAGE_CREDENTIALS: Readonly<Record<string, string>> = {\n\tAWS_ACCESS_KEY_ID: LOCAL_USER,\n\tAWS_SECRET_ACCESS_KEY: LOCAL_USER,\n\tAWS_REGION: LOCAL_REGION,\n};\n\n/** The token the local cache proxy accepts. Matches the compose definition. */\nconst LOCAL_TOKEN = 'geekmidas';\n\n/** The port Mailpit's inbox — its web UI and HTTP API — is published under. */\nconst MAILPIT_INBOX_PORT = 'mailpit-web';\n\n/** The schema pg-boss keeps its tables in, inside the declared database. */\nconst PGBOSS_SCHEMA = 'pgboss';\n\n/** The exchange every local topic and queue shares on RabbitMQ. */\nconst RABBITMQ_EXCHANGE = 'geekmidas.events';\n\nexport interface EnvOptions {\n\t/** Assigned ports, keyed by port key. */\n\tports: PortAssignments;\n\t/**\n\t * The project, which seeds any secret this stage derives.\n\t *\n\t * Derived rather than random so it survives a restart -- a signing key that\n\t * changed on every `gkm dev` would invalidate every session you were in the\n\t * middle of -- and seeded by the project so two checkouts do not share one.\n\t */\n\tproject?: string;\n\t/**\n\t * Where each address-owning construct answers, keyed by id — surfaces and\n\t * sites, e.g. `{ Api: 'http://localhost:3000', Console: 'http://localhost:5173' }`.\n\t *\n\t * Not container addresses: `gkm dev` assigns these, which is why they arrive\n\t * here rather than being read off a published port. Keyed by id rather than\n\t * being one `surface` string because there is more than one address in a\n\t * workspace and the difference between them is exactly what CORS is about.\n\t */\n\taddresses?: Readonly<Record<string, string>>;\n\t/**\n\t * This machine's address on the local network — what a phone running the\n\t * app reaches it on. Only a local stage has one, and only a project with a\n\t * mobile app reads it: its `exp://` origins, and the address sign-in links\n\t * for the app are built on.\n\t */\n\tlanAddress?: string;\n\t/** Each mobile app's Metro port, by id — the exact port its `exp://` origins name. */\n\tmetroPorts?: Readonly<Record<string, number>>;\n\t/**\n\t * The domain mail is sent from locally.\n\t *\n\t * Stage config, exactly as it is deployed — the difference is only that here\n\t * nobody had to verify it.\n\t */\n\tmailFrom?: string;\n\t/**\n\t * The stage's random seed, salting every derived role password.\n\t *\n\t * Absent locally, where the cluster is on loopback and a password derived\n\t * from the project and stage alone is the convenience the local stage\n\t * wants. A stack that serves a deployed stage passes the seed its store\n\t * keeps, so its passwords are the ones a deploy derives — and are not\n\t * computable from the repo.\n\t */\n\tseed?: string;\n\t/**\n\t * The cluster master's password, where it is not the local one.\n\t *\n\t * pg-boss and a `roles: false` database connect as the master, so its\n\t * password is in their URLs; a deployed stage's is derived from its seed.\n\t */\n\tmaster?: string;\n}\n\n/**\n * The credentials a stage's URLs carry: the master's password and the seed\n * role passwords are salted with. Locally both are the fixed defaults.\n */\ninterface StageCredential {\n\tmaster: string;\n\tseed?: string;\n}\n\n/** The credential `options` describe, defaulting to the local one. */\nfunction credentialOf(options: { seed?: string; master?: string }) {\n\treturn {\n\t\tmaster: options.master ?? LOCAL_USER,\n\t\t...(options.seed ? { seed: options.seed } : {}),\n\t} satisfies StageCredential;\n}\n\n/**\n * Every env key a reconciled plan resolves, and its value.\n *\n * Keys come from the declaration rather than being re-derived, so what the build\n * publishes and what the app reads cannot drift.\n */\nexport function envFor(\n\tplan: Plan,\n\toptions: EnvOptions,\n): Record<string, string> {\n\tconst env: Record<string, string> = {};\n\tconst credential = credentialOf(options);\n\n\t// Resolved once, up front, because a surface's cookie domain and origin list\n\t// are derived from *other constructs'* addresses — and reading them as the\n\t// loop happened to reach them would make the answer depend on the order the\n\t// manifest was keyed in.\n\tconst resolved: Record<string, string> = {};\n\tfor (const resource of plan.resources) {\n\t\tconst url = urlFor(\n\t\t\tresource,\n\t\t\tplan,\n\t\t\toptions.ports,\n\t\t\toptions.project ?? '',\n\t\t\toptions.addresses,\n\t\t\tcredential,\n\t\t);\n\t\tif (url) resolved[resource.id] = url;\n\t}\n\n\tfor (const resource of plan.resources) {\n\t\tconst url = resolved[resource.id];\n\n\t\t// A surface carries the origins its callers may come from, and they are\n\t\t// its inbound edges — nothing more. Better Auth's CSRF check applies to\n\t\t// every caller and not only to browsers, so a sibling service calling it\n\t\t// is rejected unless its origin is listed; that this list is now the\n\t\t// graph rather than every app the workspace happens to run is what makes\n\t\t// it the same list deployed, where no workspace is watching.\n\t\tif (resource.kind === 'rest-api') {\n\t\t\tObject.assign(\n\t\t\t\tenv,\n\t\t\t\tsurfaceEnv(resource, url, resolved, {\n\t\t\t\t\tplan,\n\t\t\t\t\t...(options.addresses ? { addresses: options.addresses } : {}),\n\t\t\t\t\t...(options.lanAddress ? { lanAddress: options.lanAddress } : {}),\n\t\t\t\t\t...(options.metroPorts ? { metroPorts: options.metroPorts } : {}),\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tif (url) env[resource.envKey] = url;\n\n\t\t// A database owns a second key, and it is deliberately not in `provides`:\n\t\t// the owner URL is what a migrator connects with, so no edge in any\n\t\t// manifest can name it and nothing can be granted DDL rights by mistake.\n\t\t// `gkm exec` injects it; a handler never sees it.\n\t\tif (isDatabase(resource.kind) && resource.roles !== false) {\n\t\t\tconst owner = ownerUrl(\n\t\t\t\tresource,\n\t\t\t\tplan,\n\t\t\t\toptions.ports,\n\t\t\t\toptions.project ?? '',\n\t\t\t\tcredential,\n\t\t\t);\n\t\t\tif (owner) env[provideKey(resource.id, 'ownerUrl')] = owner;\n\t\t}\n\n\t\t// An external API owns a second key. Faked, it is the fake's; otherwise\n\t\t// it is the stage's own, from its secrets like any value it was given.\n\t\tif (resource.kind === 'external-api' && url && resource.fake) {\n\t\t\tenv[provideKey(resource.id, 'credentials')] = resource.fake.credentials;\n\t\t}\n\n\t\t// Mail owns a second key. It is the sending identity, which is the one\n\t\t// thing about mail that differs per stage — so it travels with the URL\n\t\t// rather than being written into the construct.\n\t\tif (resource.kind === 'email' && url) {\n\t\t\tenv[provideKey(resource.id, 'from')] =\n\t\t\t\toptions.mailFrom ?? `noreply@${LOCAL_HOST}`;\n\n\t\t\t// And, locally, a third: where the mail it sent can be read back.\n\t\t\t// Mailpit answers SMTP on one port and its inbox on another; a feature\n\t\t\t// test signs in by opening the email that was actually sent, and this\n\t\t\t// is how it finds it. Deployed mail has no inbox, so nothing deployed\n\t\t\t// publishes this key.\n\t\t\tconst inbox = options.ports[MAILPIT_INBOX_PORT];\n\t\t\tif (inbox !== undefined) {\n\t\t\t\tenv[provideKey(resource.id, 'inboxUrl')] =\n\t\t\t\t\t`http://${LOCAL_HOST}:${inbox}`;\n\t\t\t}\n\t\t}\n\t}\n\n\t// After the loop, and deliberately: a site's keys are renames of values the\n\t// constructs it depends on resolved, so every source has to exist before any\n\t// of them can be read. Doing it inline would make the result depend on the\n\t// order the manifest happened to be keyed in.\n\tObject.assign(env, publicEnv(plan, env, options.addresses));\n\n\tObject.assign(env, brokerEnv(plan, options.ports, credential));\n\n\t// Only once a bucket actually resolved: an unresolvable plan resolves\n\t// nothing, and credentials for a container that is not running are noise.\n\tif (plan.resources.some((r) => r.kind === 'objects' && env[r.envKey])) {\n\t\tObject.assign(env, STORAGE_CREDENTIALS);\n\t}\n\n\treturn env;\n}\n\n/**\n * The values a site's bundler inlines, under the names it inlines them by.\n *\n * A rename and nothing more. `API_URL` was resolved once, by the same code that\n * resolved it for the server; a site reads the same value under `VITE_API_URL`\n * because that prefix is how its bundler is told to ship it. Nothing is derived\n * twice, so a site and its API cannot come to disagree about where the API is.\n *\n * A source that resolved to nothing is skipped rather than written empty: an\n * inlined empty string is a frontend that builds and then fails at runtime\n * against `http:///`, where a missing variable fails at build with the name of\n * the thing that is missing.\n */\nfunction publicEnv(\n\tplan: Plan,\n\tresolved: Record<string, string>,\n\t/** Where each surface answers on its own port — what a mobile app is given. */\n\taddresses: Readonly<Record<string, string>> = {},\n): Record<string, string> {\n\tconst env: Record<string, string> = {};\n\t// Which construct each key belongs to, so a mobile app can be handed the\n\t// surface's own port rather than the edge's hostname.\n\tconst owners = new Map(plan.resources.map((r) => [r.envKey, r.id]));\n\n\tfor (const resource of plan.resources) {\n\t\tfor (const [key, source] of Object.entries(resource.publicEnv ?? {})) {\n\t\t\t// A phone cannot open `https://api-dev.shop.localhost` — the hostname\n\t\t\t// resolves only on this machine, and the edge routes by it, so no LAN\n\t\t\t// address can stand in for it. The surface's own port can: the app\n\t\t\t// swaps `localhost` for the host Metro was served from, and reaches it.\n\t\t\tconst direct =\n\t\t\t\tresource.kind === 'mobile-app'\n\t\t\t\t\t? addresses[owners.get(source) ?? '']\n\t\t\t\t\t: undefined;\n\t\t\tconst value = direct ?? resolved[source];\n\t\t\tif (value) env[key] = value;\n\t\t}\n\t}\n\n\treturn env;\n}\n\n/**\n * What a surface publishes beyond its own address: who may call it, and where\n * a cookie set by it is readable.\n *\n * Both are read off the same list — the constructs that declared an edge to\n * this surface — which is why neither appears in application code. A surface\n * that listed its own callers would be edited every time something new called\n * it, and the thing being edited is already recorded in the graph.\n *\n * An empty origin list is written, and a surface with no address writes nothing\n * at all. The two cases look alike and are not: \"nothing depends on this yet\" is\n * a real state a target should publish, while \"this surface has no address here\"\n * means it is not running in this stage, and keys belong with the address they\n * describe.\n */\nfunction surfaceEnv(\n\tresource: PlannedResource,\n\turl: string | undefined,\n\t/**\n\t * Every construct's *resolved* address, keyed by id — not the raw ones the\n\t * workspace assigned.\n\t *\n\t * The difference is the feature. Behind the edge a surface and its callers\n\t * are `api.shop.localhost` and `console.shop.localhost`, which share a\n\t * parent and therefore a cookie; the addresses they were assigned are\n\t * `localhost:3000` and `localhost:5173`, which are one host with two ports\n\t * and share nothing. Deriving from the wrong one produces a local cookie\n\t * model that is *different* from the deployed one rather than matching it.\n\t */\n\tresolved: Readonly<Record<string, string>> = {},\n\tlocal: {\n\t\tplan?: Plan;\n\t\taddresses?: Readonly<Record<string, string>>;\n\t\tlanAddress?: string;\n\t\tmetroPorts?: Readonly<Record<string, number>>;\n\t} = {},\n): Record<string, string> {\n\tif (!url) return {};\n\n\tconst origins = [\n\t\t...new Set(\n\t\t\t(resource.callers ?? [])\n\t\t\t\t.map((caller) => resolved[caller])\n\t\t\t\t.filter((address): address is string => Boolean(address))\n\t\t\t\t.map(originOf)\n\t\t\t\t.filter((origin): origin is string => Boolean(origin)),\n\t\t),\n\t].sort();\n\n\t// Its own address belongs in the cookie derivation but not in the origin\n\t// list: a surface does not need permission to call itself, and adding it\n\t// would make every surface trust every other one that shares a port.\n\tconst domain = cookieDomain([url, ...origins]);\n\n\t// A mobile caller is reached by its scheme, not an address, so it adds its\n\t// scheme — and, on this local stage, the `exp://` hosts Expo Go sends from —\n\t// rather than an origin read off a URL. After the cookie domain on purpose:\n\t// a scheme is not a host anything shares a cookie with.\n\tconst mobile = (resource.callers ?? [])\n\t\t.map((caller) =>\n\t\t\tlocal.plan?.resources.find(\n\t\t\t\t(r) => r.id === caller && r.kind === 'mobile-app',\n\t\t\t),\n\t\t)\n\t\t.filter((caller): caller is PlannedResource => Boolean(caller));\n\tconst hosts = [...(local.lanAddress ? [local.lanAddress] : []), LOCAL_HOST];\n\tconst schemes = mobile.flatMap((caller) => {\n\t\tconst scheme = resolved[caller.id];\n\t\tconst port = local.metroPorts?.[caller.id];\n\t\treturn scheme\n\t\t\t? mobileOrigins(scheme, { hosts, ...(port ? { port } : {}) })\n\t\t\t: [];\n\t});\n\n\t// Where a phone reaches this surface: its own port on the LAN address. What\n\t// the auth server builds a sign-in link on when the app asked for it — the\n\t// link is opened on the phone, which cannot resolve the edge's hostname.\n\tconst own = local.addresses?.[resource.id];\n\tconst device =\n\t\tmobile.length > 0 && local.lanAddress && own\n\t\t\t? own.replace(\n\t\t\t\t\t/\\/\\/(localhost|127\\.0\\.0\\.1)(?=[:/]|$)/,\n\t\t\t\t\t`//${local.lanAddress}`,\n\t\t\t\t)\n\t\t\t: undefined;\n\n\treturn {\n\t\t[provideKey(resource.id, 'trustedOrigins')]: [\n\t\t\t...origins,\n\t\t\t...new Set(schemes),\n\t\t].join(','),\n\t\t...(domain ? { [provideKey(resource.id, 'cookieDomain')]: domain } : {}),\n\t\t...(device ? { [provideKey(resource.id, 'deviceUrl')]: device } : {}),\n\t};\n}\n\n/** An address reduced to the origin a browser compares against. */\nfunction originOf(address: string): string | undefined {\n\ttry {\n\t\treturn new URL(address).origin;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\n/**\n * The runtime role of whatever a derived node hangs off.\n *\n * A reader's parent may itself be a schema tenant, so this follows `of` by one\n * hop rather than assuming the cluster — the reader of a tenant reads that\n * tenant's schema, not the database's.\n */\nfunction parentRole(resource: PlannedResource, plan: Plan): string {\n\tconst parent = plan.resources.find((r) => r.id === resource.of);\n\n\treturn parent ? localRole(parent) : resource.name;\n}\n\n/** Whether a kind connects to Postgres and therefore has an owner role. */\nfunction isDatabase(kind: PlannedResource['kind']): boolean {\n\treturn kind === 'database' || kind === 'database-schema';\n}\n\n/**\n * The URL a migrator connects with — the owner role, which can create, alter\n * and drop.\n *\n * Never in `provides`, which is the whole point. A handler that could reach this\n * could `DROP TABLE`, and the security property the role split exists for is\n * that it cannot: the value is injected by `gkm exec` for the migrate step and\n * by nothing else.\n */\nfunction ownerUrl(\n\tresource: PlannedResource,\n\tplan: Plan,\n\tports: PortAssignments,\n\tproject: string,\n\tcredential: StageCredential,\n): string | undefined {\n\tif (!resource.container) return undefined;\n\n\tconst port = ports[primaryPortKey(resource.container)];\n\tif (port === undefined) return undefined;\n\n\tconst owner = ownerRole(localRole(resource));\n\n\treturn postgres(port, rootDatabase(resource, plan), {\n\t\tuser: owner,\n\t\tpassword: localRolePassword(project, plan, owner, credential.seed),\n\t});\n}\n\n/**\n * The shared broker keys, when anything is published or consumed.\n *\n * A queue's own key is the producer's. These two are the *connection* itself,\n * which locally is one broker for every queue and topic in the project: the\n * generated pollers open a single connection and subscribe each worker by name\n * on it. Deployed there is no such thing — a Lambda is handed its own event\n * source — so this pair exists for the local target and says so.\n */\nfunction brokerEnv(\n\tplan: Plan,\n\tports: PortAssignments,\n\tcredential: StageCredential,\n): Record<string, string> {\n\tconst carrier = plan.resources.find(\n\t\t(r) => r.kind === 'queue' || r.kind === 'topic',\n\t);\n\n\tconst publisher = carrier\n\t\t? urlFor(carrier, plan, ports, '', {}, credential)\n\t\t: plan.workerBroker\n\t\t\t? workerBroker(plan, ports, credential)\n\t\t\t: undefined;\n\tif (!publisher) return {};\n\n\t// One broker address only where there is one broker: pg-boss and RabbitMQ.\n\t// On SNS every topic and queue is its own address, carried by its own\n\t// `<ID>_PUBLISHER_CONNECTION_STRING`, and no single string stands for them.\n\tif (plan.events === 'sns') return {};\n\n\treturn { EVENT_PUBLISHER_CONNECTION_STRING: publisher };\n}\n\n/**\n * The broker a worker schedules its crons through, when nothing else declared\n * one — pg-boss in the declared database, on that database's port.\n */\nfunction workerBroker(\n\tplan: Plan,\n\tports: PortAssignments,\n\tcredential: StageCredential,\n): string | undefined {\n\tconst database = plan.resources.find((r) => r.kind === 'database');\n\tif (!database?.container) return undefined;\n\n\tconst port = ports[primaryPortKey(database.container)];\n\treturn port === undefined ? undefined : broker(plan, port, credential);\n}\n\n/**\n * The URL for one planned resource.\n *\n * Every part of it comes from the resource and the port it was published on.\n */\nfunction urlFor(\n\tresource: PlannedResource,\n\tplan: Plan,\n\tports: PortAssignments,\n\tproject: string,\n\taddresses: Readonly<Record<string, string>> = {},\n\tcredential: StageCredential = { master: LOCAL_USER },\n): string | undefined {\n\t// A secret has no address, so there is no port to wait for.\n\tif (resource.kind === 'secret') return localSecret(project, plan, resource);\n\n\t// Nor has an encryption key: a keyring derived the way a secret is.\n\tif (resource.kind === 'encryption') {\n\t\treturn localKeyring(project, plan, resource);\n\t}\n\n\t// A mobile app resolves its scheme — the same on every stage.\n\tif (resource.kind === 'mobile-app') {\n\t\treturn schemeBase(project, resource.scheme);\n\t}\n\n\t// A credential resolves to nothing here, deliberately. A secret is derived\n\t// because the platform owns it; a credential was issued by a third party, so\n\t// inventing a value would produce a Stripe key that is not a Stripe key and\n\t// fail at the first call rather than at the first read. It comes from\n\t// `gkm secrets` or `.env` like any other supplied value, and the construct's\n\t// own schema is what reports it missing.\n\tif (resource.kind === 'credential') return undefined;\n\n\t// An external API is the provider, at its URL for this stage — unless the\n\t// plan was made with fakes (`gkm test`, `gkm dev --fake`), and then it is\n\t// its fake, on the port its key was assigned.\n\tif (resource.kind === 'external-api') {\n\t\tif (!resource.fake) {\n\t\t\treturn resource.url === undefined\n\t\t\t\t? undefined\n\t\t\t\t: externalApiUrl({ id: resource.id, url: resource.url }, plan.stage);\n\t\t}\n\t\tconst port = ports[resource.fake.key];\n\t\treturn port === undefined ? undefined : `http://${LOCAL_HOST}:${port}`;\n\t}\n\n\t// A surface answers on the app's own port, and a site on its dev server's —\n\t// both assigned by the workspace, neither published by a container.\n\t//\n\t// Behind the edge when there is one, which is what gives them a hostname and\n\t// a certificate rather than a port on localhost. The application cannot tell\n\t// the difference: it reads whichever address was injected and composes none,\n\t// so this is the target's decision alone.\n\tif (resource.kind === 'rest-api' || resource.kind === 'site') {\n\t\tconst address = addresses[resource.id];\n\t\tconst edge = plan.containers.includes('caddy')\n\t\t\t? ports[primaryPortKey('caddy')]\n\t\t\t: undefined;\n\n\t\treturn address && edge !== undefined\n\t\t\t? `https://${hostFor(resource, project)}:${edge}`\n\t\t\t: address;\n\t}\n\n\tif (!resource.container) return undefined;\n\n\tconst port = ports[primaryPortKey(resource.container)];\n\tif (port === undefined) return undefined;\n\n\tswitch (resource.kind) {\n\t\tcase 'database':\n\t\tcase 'database-schema':\n\t\tcase 'database-reader': {\n\t\t\t// A tenant and a reader both live in the *parent's* database; what\n\t\t\t// separates them is the role's grants and the `search_path` pinned on\n\t\t\t// that role, never a database of their own.\n\t\t\tconst database = rootDatabase(resource, plan);\n\n\t\t\t// The runtime role, which can read and write rows and create nothing.\n\t\t\t// `search_path` is pinned on the role by the DDL rather than carried\n\t\t\t// here, so a connection string cannot forget it — and forgetting it\n\t\t\t// looks like an empty database rather than an error.\n\t\t\t//\n\t\t\t// `roles: false` is the documented downgrade: both URLs fall back to\n\t\t\t// the cluster's master credential, and the schema goes back into the\n\t\t\t// URL because there is no role to pin it on.\n\t\t\tif (resource.roles === false) {\n\t\t\t\tconst schema = schemaOf(resource, plan);\n\n\t\t\t\tconst master = { user: LOCAL_USER, password: credential.master };\n\t\t\t\treturn schema\n\t\t\t\t\t? `${postgres(port, database, master)}?search_path=${schema}`\n\t\t\t\t\t: postgres(port, database, master);\n\t\t\t}\n\n\t\t\t// A reader connects as the read-only role on its parent, not as a role\n\t\t\t// of its own: read-only is enforced by the grants, which is what makes\n\t\t\t// falling back to the writer's endpoint safe where no replica exists.\n\t\t\tconst role =\n\t\t\t\tresource.kind === 'database-reader'\n\t\t\t\t\t? readerRole(parentRole(resource, plan))\n\t\t\t\t\t: localRole(resource);\n\n\t\t\treturn postgres(port, database, {\n\t\t\t\tuser: role,\n\t\t\t\tpassword: localRolePassword(project, plan, role, credential.seed),\n\t\t\t});\n\t\t}\n\n\t\tcase 'email':\n\t\t\t// The same scheme the deployed target writes; only host and credentials\n\t\t\t// differ, which is what lets the client never branch on provider.\n\t\t\treturn `smtp://${LOCAL_HOST}:${port}`;\n\n\t\tcase 'objects':\n\t\t\t// `?endpoint=` is what points the same S3 client at MinIO — the client\n\t\t\t// is identical, only the URL differs. Path-style addressing goes with\n\t\t\t// it: virtual-host style resolves `bucket.localhost`, which is not\n\t\t\t// MinIO and not anything.\n\t\t\treturn `s3://${resource.name}?region=${LOCAL_REGION}&endpoint=http://${LOCAL_HOST}:${port}&forcePathStyle=true`;\n\n\t\tcase 'file-server': {\n\t\t\t// A host of its own, over TLS — the shape it has deployed, and the one\n\t\t\t// MinIO alone cannot produce: its virtual-host mode reads the leading\n\t\t\t// label *as the bucket name*, so it matches only when the server's id\n\t\t\t// and the bucket's agree and never for a server fronting two buckets.\n\t\t\t// The local edge in front of it does the mapping instead, and issues\n\t\t\t// the certificate. An AWS emulator supplies neither: CloudFront\n\t\t\t// emulation is control plane only, and this is the data plane.\n\t\t\t//\n\t\t\t// The port is in the address because ports are assigned rather than\n\t\t\t// fixed, so two projects can run at once. Nothing that reads a\n\t\t\t// hostname — a cookie domain, a CORS origin — looks at it.\n\t\t\tconst origin = plan.resources.find((r) => r.id === resource.of);\n\t\t\tif (!origin) return undefined;\n\n\t\t\t// With the edge off, the honest local answer is the address that\n\t\t\t// works: a path under the object store. It is not the deployed shape,\n\t\t\t// and the application still cannot tell — it reads the key it was\n\t\t\t// given and never composes one.\n\t\t\treturn plan.containers.includes('caddy')\n\t\t\t\t? `https://${hostFor(resource, project)}:${port}`\n\t\t\t\t: `http://${LOCAL_HOST}:${port}/${origin.name}`;\n\t\t}\n\n\t\tcase 'cache':\n\t\t\t// The scheme is the backend, and the backend is the same one deployed —\n\t\t\t// which is what lets a driver registered at build time match the URL\n\t\t\t// resolved at run time.\n\t\t\t//\n\t\t\t// A cache in a database has no address of its own: no second host and\n\t\t\t// no second credential, just its parent's URL. Reached by following\n\t\t\t// `of` and nothing else — `planFor` has already resolved a\n\t\t\t// `cache: 'db'` backend into that edge, so there is one way a cache\n\t\t\t// lands in a database and one way to find out.\n\t\t\tif (resource.of) {\n\t\t\t\tconst parent = plan.resources.find((r) => r.id === resource.of);\n\t\t\t\tconst url = parent\n\t\t\t\t\t? urlFor(parent, plan, ports, project, addresses, credential)\n\t\t\t\t\t: undefined;\n\n\t\t\t\t// The parent's address plus the one thing that distinguishes this\n\t\t\t\t// cache from another in the same database: its table. Two caches on\n\t\t\t\t// one database resolve the same connection string, so without it a\n\t\t\t\t// client built from the URL cannot tell them apart.\n\t\t\t\treturn url\n\t\t\t\t\t? withTable(url, resource.table ?? cacheTable(resource.id))\n\t\t\t\t\t: undefined;\n\t\t\t}\n\n\t\t\tswitch (plan.cache) {\n\t\t\t\tcase 'elasticache':\n\t\t\t\tcase 'redis':\n\t\t\t\t\t// The wire protocol, unauthenticated locally. Deployed it is\n\t\t\t\t\t// `rediss://` inside a VPC; the client is the same either way.\n\t\t\t\t\treturn `redis://${LOCAL_HOST}:${port}`;\n\n\t\t\t\tdefault:\n\t\t\t\t\t// The token in the userinfo, because an address and the credential\n\t\t\t\t\t// that opens it are one fact. Deployed the scheme is https and the\n\t\t\t\t\t// host is the provider's; nothing else differs.\n\t\t\t\t\treturn `http://:${LOCAL_TOKEN}@${LOCAL_HOST}:${port}`;\n\t\t\t}\n\n\t\tcase 'queue':\n\t\tcase 'topic':\n\t\t\treturn broker(plan, port, credential, resource);\n\n\t\tdefault:\n\t\t\treturn undefined;\n\t}\n}\n\n/**\n * The producer's connection string, composed from the backend the plan chose.\n *\n * The protocol is what picks the transport, so a producer never branches: the\n * same `.publish()` reaches pg-boss here and SQS deployed because the string it\n * was handed said so.\n */\nfunction broker(\n\tplan: Plan,\n\tport: number,\n\tcredential: StageCredential,\n\t/** The topic or queue — needed where each is its own address (SNS/SQS). */\n\tresource?: PlannedResource,\n): string {\n\tswitch (plan.events) {\n\t\tcase 'pgboss': {\n\t\t\t// A schema tenant of the database the app already declared, which is\n\t\t\t// why nothing here invents a Postgres of its own.\n\t\t\tconst database = plan.resources.find((r) => r.kind === 'database');\n\t\t\tif (!database) throw new PgBossNeedsDatabase([]);\n\n\t\t\treturn `pgboss://${LOCAL_USER}:${encodeURIComponent(credential.master)}@${LOCAL_HOST}:${port}/${database.name}?schema=${PGBOSS_SCHEMA}`;\n\t\t}\n\n\t\tcase 'rabbitmq':\n\t\t\t// The exchange is declared by whichever client connects first, so\n\t\t\t// naming it here is the whole of the setup.\n\t\t\treturn `rabbitmq://${LOCAL_USER}:${LOCAL_USER}@${LOCAL_HOST}:${port}?exchange=${RABBITMQ_EXCHANGE}`;\n\n\t\tcase 'sns': {\n\t\t\t// The emulator's ARNs and queue URLs are deterministic, so the address\n\t\t\t// is composed here and the topic or queue is created beside it by the\n\t\t\t// applier (`applyCarriers`). Credentials ride in the string: the\n\t\t\t// emulator's are not the developer's AWS profile, and must not be\n\t\t\t// picked up from it.\n\t\t\tif (!resource) throw new UnprovisionedEventsBackend('sns');\n\t\t\tconst endpoint = emulatorEndpoint(port);\n\t\t\tconst address =\n\t\t\t\tresource.kind === 'topic'\n\t\t\t\t\t? snsUrl.build({\n\t\t\t\t\t\t\ttopicArn: emulatorTopicArn(resource.name),\n\t\t\t\t\t\t\tregion: EMULATOR_REGION,\n\t\t\t\t\t\t\tendpoint,\n\t\t\t\t\t\t})\n\t\t\t\t\t: sqsUrl.build({\n\t\t\t\t\t\t\tqueueUrl: emulatorQueueUrl(resource.name, port),\n\t\t\t\t\t\t\tregion: EMULATOR_REGION,\n\t\t\t\t\t\t\tendpoint,\n\t\t\t\t\t\t});\n\t\t\tconst url = new URL(address);\n\t\t\turl.searchParams.set('accessKeyId', EMULATOR_CREDENTIALS.accessKeyId);\n\t\t\turl.searchParams.set(\n\t\t\t\t'secretAccessKey',\n\t\t\t\tEMULATOR_CREDENTIALS.secretAccessKey,\n\t\t\t);\n\t\t\treturn url.toString();\n\t\t}\n\t}\n}\n\n/** A local Postgres URL, master credential and all. */\nfunction postgres(\n\tport: number,\n\tdatabase: string,\n\tcredential: { user: string; password: string } = {\n\t\tuser: LOCAL_USER,\n\t\tpassword: LOCAL_USER,\n\t},\n): string {\n\treturn `postgres://${credential.user}:${encodeURIComponent(credential.password)}@${LOCAL_HOST}:${port}/${database}`;\n}\n\n/**\n * The role a handler connects as, for one database or tenant.\n *\n * The construct's id, lowercased and stage-scoped exactly as its database or\n * schema is — so `Orders` in the `test` stage connects as `orders_test`, and two\n * stages sharing one cluster cannot share a credential. Roles are cluster-scoped\n * in Postgres, which is why the suffix is not optional here the way it is for a\n * schema.\n */\nexport function localRole(resource: PlannedResource): string {\n\treturn resource.name;\n}\n\n/**\n * The password for a derived local role.\n *\n * Derived rather than random, for the same reason the local secret is: a\n * password that changed on every `gkm dev` would lock a developer out of the\n * data they had a moment ago. Seeded by the project so two checkouts do not\n * share one, and by the stage so `test` and `development` do not.\n *\n * Without a seed it is a *local* credential by construction — the cluster is\n * on loopback and everything it is derived from is in the repo. `gkm compose`\n * serving a deployed stage passes that stage's random seed, which is what\n * makes the same derivation a secret.\n */\nexport function localRolePassword(\n\tproject: string,\n\tplan: Plan,\n\trole: string,\n\t/**\n\t * A deployed stage's random seed. Given, the derivation is the one a\n\t * deploy makes — the seed first — so a stack serving that stage connects\n\t * with the passwords its roles were created with.\n\t */\n\tseed?: string,\n): string {\n\treturn createHash('sha256')\n\t\t.update(`${seed ? `${seed}:` : ''}${project}:${plan.stage}:role:${role}`)\n\t\t.digest('base64url')\n\t\t.slice(0, 32);\n}\n\n/**\n * A cache's address: its database's URL, carrying the table it reads.\n *\n * Appended rather than kept beside the URL as a second key, for the reason the\n * design gives everywhere else — an address and what it opens are one fact, and\n * two keys are one more thing to keep in step.\n */\nfunction withTable(url: string, table: string): string {\n\tconst parsed = new URL(url);\n\tparsed.searchParams.set('table', table);\n\n\treturn parsed.toString();\n}\n\n/**\n * The physical database a derived resource lives in.\n *\n * Walks `of` to the top rather than reading the immediate parent, because a\n * reader's parent may itself be a schema tenant — two hops from the database\n * that actually exists.\n */\nexport function rootDatabase(resource: PlannedResource, plan: Plan): string {\n\tconst byId = new Map(plan.resources.map((r) => [r.id, r]));\n\tlet current = resource;\n\n\twhile (current.of) {\n\t\tconst parent = byId.get(current.of);\n\t\tif (!parent) break;\n\t\tcurrent = parent;\n\t}\n\n\treturn current.name;\n}\n\n/** The schema a derived resource puts on its search path, if it has one. */\nfunction schemaOf(resource: PlannedResource, plan: Plan): string | undefined {\n\tconst byId = new Map(plan.resources.map((r) => [r.id, r]));\n\tlet current: PlannedResource | undefined = resource;\n\n\t// A reader has no schema of its own; it reads whatever its parent addresses.\n\twhile (current) {\n\t\tif (current.kind === 'database-schema') return current.schema;\n\t\tcurrent = current.of ? byId.get(current.of) : undefined;\n\t}\n\n\treturn undefined;\n}\n\n/**\n * A broker address asked for without the topic or queue it would name.\n *\n * On SNS each topic and queue is its own address, so there is no project-wide\n * one to hand out — a cron's schedule store, say, cannot live there.\n */\nexport class UnprovisionedEventsBackend extends Error {\n\tconstructor(readonly backend: string) {\n\t\tsuper(\n\t\t\t`'${backend}' has no single broker address: each topic and queue is ` +\n\t\t\t\t`addressed by its own ARN. Something asked for one without naming ` +\n\t\t\t\t`the topic or queue.`,\n\t\t);\n\t\tthis.name = 'UnprovisionedEventsBackend';\n\t}\n}\n\n/**\n * The keyring an `Encryption` construct resolves to locally.\n *\n * Derived like a secret — stable across restarts, distinct per project, stage\n * and construct, never written to disk — so what `gkm dev` encrypted yesterday\n * still opens today. One key, because nothing local is worth rotating: a\n * server stage's keyring is generated and rotated in its secrets instead.\n */\nfunction localKeyring(\n\tproject: string,\n\tplan: Plan,\n\tresource: PlannedResource,\n): string {\n\tconst derive = (purpose: string) =>\n\t\tcreateHash('sha256')\n\t\t\t.update(`${project}:${plan.stage}:${resource.id}:${purpose}`)\n\t\t\t.digest();\n\n\treturn formatKeyring({\n\t\tkeys: [{ id: 'k1', key: derive('k1') }],\n\t\tindex: derive('index'),\n\t});\n}\n\n/**\n * The value a secret resolves to locally.\n *\n * A hash of what identifies it, so it is stable, distinct per project, stage\n * and construct, and never written to disk as a literal. Deployed this is the\n * one kind whose value does *not* come from here — a secret manager generates\n * it once and the target reads it back.\n */\nfunction localSecret(\n\tproject: string,\n\tplan: Plan,\n\tresource: PlannedResource,\n): string {\n\treturn createHash('sha256')\n\t\t.update(`${project}:${plan.stage}:${resource.id}`)\n\t\t.digest('base64url')\n\t\t.slice(0, 43);\n}\n","/**\n * The apps, as services beside the containers their constructs derive.\n *\n * `gkm docker` used to write these by hand: every backend got\n * `DATABASE_URL=postgresql://…@postgres:5432/app` and `REDIS_URL=redis://redis`,\n * which was right only for a project whose single database happened to be\n * called `Database` — an `Orders` database publishes `ORDERS_URL`, a schema\n * tenant has a URL of its own, and buckets and mail were not wired at all.\n *\n * Now an app's environment is exactly what reconcile hands `gkm dev`: every key\n * the declared constructs provide, from the same derivation. The only\n * difference is where the addresses point — at the containers by service name\n * on the compose network, rather than at `localhost` on the ports published for\n * the host.\n *\n * Always from the *local* stage's plan, whichever stage is being reconciled:\n * `gkm test` rewrites the same file, and running the tests must not quietly\n * point these apps at the test databases.\n */\n\nimport {\n\ttype ConstructManifest,\n\tdependenciesOf,\n\tprovideKey,\n\tprovisionOrder,\n\tpublicEnvFor,\n} from '@geekmidas/manifest';\nimport { composeBuildPaths } from '../docker/layout.js';\nimport { findBuildRoot } from '../docker/templates.js';\nimport { appKey } from '../workspace/derive.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { ComposeService } from './compose.js';\nimport { portKeys, portsOf } from './containers.js';\nimport { type EnvOptions, envFor } from './env.js';\nimport { type Plan, type PlanOptions, planFor } from './plan.js';\nimport { backendsOf, surfaceAddresses } from './workspace.js';\n\n/** The profile app services carry, so `gkm dev` starts only the containers. */\nexport const APPS_PROFILE = 'apps';\n\n/**\n * Where an app's image is built from.\n *\n * An app at the project root is a single-app project, whose `gkm docker`\n * writes `.gkm/docker/Dockerfile`; a workspace writes one per app.\n */\nexport function dockerfileOf(appName: string, path: string): string {\n\treturn path === '.'\n\t\t? '.gkm/docker/Dockerfile'\n\t\t: `.gkm/docker/Dockerfile.${appName}`;\n}\n\n/**\n * Every construct key, addressed across the compose network.\n *\n * `envFor` is asked with a placeholder port for every port key, so each\n * `localhost:<placeholder>` in its answer names exactly one container port —\n * two containers listening on the same port inside cannot be confused — and is\n * rewritten to `<container>:<inside>`.\n */\nexport function inNetworkEnv(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n\t/**\n\t * External APIs' fakes. An image fake is a container on the network like\n\t * any other; a module fake is served by `gkm dev` on the host, which an app\n\t * container does not reach, so its URL is left as the host sees it.\n\t */\n\tfakes: PlanOptions['fakes'] = {},\n): Record<string, string> {\n\treturn networkEnv(localPlan(workspace, manifest, fakes), {\n\t\tproject: workspace.name,\n\t\taddresses: surfaceAddresses(\n\t\t\tworkspace,\n\t\t\tmanifest,\n\t\t\t(app, port) => `http://${app}:${port}`,\n\t\t),\n\t});\n}\n\n/**\n * The local stage's plan, which the app services are always written from.\n *\n * Without the edge: these apps reach each other by service name and a browser\n * reaches them on the ports this file publishes. Behind the edge every\n * surface resolved to its `*.localhost` host on the edge's port, which the\n * rewrite to the compose network then turned into an address nothing answers.\n */\nfunction localPlan(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n\tfakes: PlanOptions['fakes'] = {},\n): Plan {\n\treturn planFor(manifest, workspace.stages.local, provisionOrder(manifest), {\n\t\tlocalStage: workspace.stages.local,\n\t\t...backendsOf(workspace),\n\t\tfakes,\n\t\tedge: false,\n\t});\n}\n\n/**\n * The values a site's bundler inlines, by the name it inlines them under —\n * `{ VITE_API_URL: 'http://localhost:3000' }`.\n *\n * Build arguments, not environment: a static bundle is finished when it is\n * built, so a value handed to its container at runtime reaches nothing. And\n * addresses a *browser* can open, since that is where the bundle runs — the\n * host's port for each app, never a name on the compose network.\n */\nexport function sitePublicArgs(\n\tmanifest: ConstructManifest,\n\tappName: string,\n\t/** Every key the stage resolves, with surfaces at their public addresses. */\n\tenv: Readonly<Record<string, string>>,\n): Record<string, string> {\n\tconst declaration = Object.entries(manifest).find(\n\t\t([id, d]) => d.kind === 'site' && appKey(id) === appName,\n\t)?.[1];\n\tif (declaration?.kind !== 'site') return {};\n\n\tconst args: Record<string, string> = {};\n\tfor (const key of Object.keys(publicEnvFor(declaration, manifest))) {\n\t\tconst value = env[key];\n\t\tif (value !== undefined) args[key] = value;\n\t}\n\treturn args;\n}\n\n/**\n * Every key a plan resolves, with each container addressed by its service\n * name on the compose network — `postgres:5432` rather than the port\n * published for the host.\n *\n * The addresses of surfaces and sites are the caller's: on the compose\n * network for a service another service calls, public for what a browser is\n * handed. `gkm compose` asks twice, once for each.\n */\nexport function networkEnv(\n\tplan: Plan,\n\toptions: Omit<EnvOptions, 'ports'>,\n): Record<string, string> {\n\tconst placeholders: Record<string, number> = {};\n\tconst targets = new Map<number, string>();\n\tlet next = 49_152;\n\tfor (const container of [...plan.containers].sort()) {\n\t\tfor (const port of portsOf(container, plan.fakes)) {\n\t\t\tplaceholders[port.key] = next;\n\t\t\ttargets.set(next, `${container}:${port.inside}`);\n\t\t\tnext += 1;\n\t\t}\n\t}\n\t// Every key allocation would have assigned, so envFor finds each one.\n\tfor (const key of portKeys(plan.containers, plan.fakes)) {\n\t\tplaceholders[key] ??= next++;\n\t}\n\n\tconst env = envFor(plan, { ...options, ports: placeholders });\n\n\tconst rewrite = (value: string) =>\n\t\tvalue.replace(/(?:localhost|127\\.0\\.0\\.1):(\\d+)/g, (match, port) => {\n\t\t\treturn targets.get(Number(port)) ?? match;\n\t\t});\n\n\treturn Object.fromEntries(\n\t\tObject.entries(env).map(([key, value]) => [key, rewrite(value)]),\n\t);\n}\n\n/**\n * The keys one app's environment may hold: what its own declaration provides\n * and requires, and what each construct it has an edge to provides.\n *\n * The edges, not the manifest. Every app used to be handed every key the\n * workspace resolved — a web app got the database's owner URL and the auth\n * server's signing secret, and the API got the auth server's database — which\n * is exactly what a declared edge exists to rule out. A deploy composes an\n * app's environment from its edges; so does this.\n *\n * One hop: a surface an app calls is reached over HTTP, so its URL is the\n * app's business and its database is not. A site also gets the public\n * variant of each key its bundle inlines (`NEXT_PUBLIC_API_URL`), and a surface\n * the edges of the workers whose crons and subscribers its server runs.\n *\n * `undefined` for an app no declaration describes, which gets nothing but its\n * port.\n */\nexport function appEnvKeys(\n\tmanifest: ConstructManifest,\n\tappName: string,\n\t/** Each owner's runnables' edges, from discovery — see `DiscoverOptions`. */\n\trunnables: Readonly<Record<string, readonly string[]>> = {},\n): Set<string> | undefined {\n\tconst entry = Object.entries(manifest).find(\n\t\t([id, d]) =>\n\t\t\t(d.kind === 'rest-api' || d.kind === 'site' || d.kind === 'mobile-app') &&\n\t\t\tappKey(id) === appName,\n\t);\n\tif (!entry) return undefined;\n\tconst [id, declaration] = entry;\n\n\tconst keys = new Set<string>([\n\t\t...(declaration.provides ?? []),\n\t\t...(declaration.requires ?? []),\n\t\t// A handler the surface declares itself — an auth server's wildcard —\n\t\t// states what it needs on the handler.\n\t\t...(declaration.kind === 'rest-api'\n\t\t\t? declaration.endpoints.flatMap((endpoint) => endpoint.requires ?? [])\n\t\t\t: []),\n\t]);\n\n\tconst edges = [\n\t\t...dependenciesOf(declaration).map((edge) => edge.target),\n\t\t// The endpoints the glob found, which the surface's node does not list.\n\t\t...(runnables[id] ?? []),\n\t];\n\t// The server the build generates — a surface whose endpoints the glob finds,\n\t// so it declares none — also runs the workers' crons and subscribers. One\n\t// that serves itself (an auth server's wildcard) runs only its own handler.\n\tif (declaration.kind === 'rest-api' && declaration.endpoints.length === 0) {\n\t\tconst runsWorkers = Object.values(manifest).some(\n\t\t\t(other) => other.kind === 'worker',\n\t\t);\n\t\tfor (const [otherId, other] of Object.entries(manifest)) {\n\t\t\tif (other.kind !== 'worker') continue;\n\t\t\tedges.push(\n\t\t\t\t...dependenciesOf(other).map((edge) => edge.target),\n\t\t\t\t...(runnables[otherId] ?? []),\n\t\t\t);\n\t\t\t// A server schedules its crons through the broker.\n\t\t\tkeys.add('EVENT_PUBLISHER_CONNECTION_STRING');\n\t\t}\n\t\t// Each consumer reaches the thing it consumes: a queue's consumer its\n\t\t// queue, a subscriber its topic. Which subscriber binds which topic is\n\t\t// only known once the build has found them, so a server that runs the\n\t\t// workers is handed every carrier's address.\n\t\tif (runsWorkers) {\n\t\t\tfor (const other of Object.values(manifest)) {\n\t\t\t\tif (other.kind !== 'queue' && other.kind !== 'topic') continue;\n\t\t\t\tfor (const key of other.provides ?? []) keys.add(key);\n\t\t\t}\n\t\t}\n\t}\n\n\taddEdgeKeys(manifest, edges, keys);\n\n\tif (declaration.kind === 'site' || declaration.kind === 'mobile-app') {\n\t\tfor (const key of Object.keys(publicEnvFor(declaration, manifest))) {\n\t\t\tkeys.add(key);\n\t\t}\n\t}\n\n\treturn keys;\n}\n\n/**\n * The keys a Worker's own process reads: what the worker and everything built\n * from it declared an edge to, the broker its crons schedule through, and the\n * address of every queue and topic — which of them its consumers and\n * subscribers are bound to is only known once a build has found them, so a\n * process that runs them is handed each carrier's.\n *\n * `undefined` when no worker has that id.\n */\nexport function workerEnvKeys(\n\tmanifest: ConstructManifest,\n\tworkerId: string,\n\t/** Each owner's runnables' edges, from discovery — see `DiscoverOptions`. */\n\trunnables: Readonly<Record<string, readonly string[]>> = {},\n): Set<string> | undefined {\n\tconst declaration = manifest[workerId];\n\tif (declaration?.kind !== 'worker') return undefined;\n\n\tconst keys = new Set<string>([\n\t\t...(declaration.provides ?? []),\n\t\t...(declaration.requires ?? []),\n\t\t// A server schedules its crons through the broker.\n\t\t'EVENT_PUBLISHER_CONNECTION_STRING',\n\t]);\n\tfor (const other of Object.values(manifest)) {\n\t\tif (other.kind !== 'queue' && other.kind !== 'topic') continue;\n\t\tfor (const key of other.provides ?? []) keys.add(key);\n\t}\n\taddEdgeKeys(\n\t\tmanifest,\n\t\t[\n\t\t\t...dependenciesOf(declaration).map((edge) => edge.target),\n\t\t\t...(runnables[workerId] ?? []),\n\t\t],\n\t\tkeys,\n\t);\n\treturn keys;\n}\n\n/** What each construct an edge reaches hands the process that reaches it. */\nfunction addEdgeKeys(\n\tmanifest: ConstructManifest,\n\tedges: readonly string[],\n\tkeys: Set<string>,\n): void {\n\tfor (const id of edges) {\n\t\tconst target = manifest[id];\n\t\tif (!target) continue;\n\t\t// A surface is reached over HTTP, so its caller is given its address\n\t\t// and nothing else it provides: who it trusts and the domain its\n\t\t// cookies are scoped to are the surface's own settings, read by its own\n\t\t// process. For an auth server that is the whole point — the API gets\n\t\t// AUTH_URL, and the auth app alone holds its secret, tenant and mailer.\n\t\tif (target.kind === 'rest-api') {\n\t\t\tkeys.add(provideKey(id, 'url'));\n\t\t\tcontinue;\n\t\t}\n\t\tfor (const key of target.provides ?? []) keys.add(key);\n\n\t\tif (target.kind === 'queue' || target.kind === 'topic') {\n\t\t\tkeys.add('EVENT_PUBLISHER_CONNECTION_STRING');\n\t\t}\n\t\t// The S3 client's shared key pair, for a bucket whose URL carries no key\n\t\t// of its own — handed over only where the stage has one. A file server\n\t\t// is reached by its URL alone.\n\t\tif (target.kind === 'objects') {\n\t\t\t// A FileServer is one construct declaring two nodes — the bucket,\n\t\t\t// which the edge points at, and the domain that serves it — and its\n\t\t\t// client reads both: a handler signs uploads to the bucket and hands\n\t\t\t// out URLs on the domain. The domain's address comes with the edge.\n\t\t\tfor (const other of Object.values(manifest)) {\n\t\t\t\tif (other.kind === 'file-server' && other.of === id) {\n\t\t\t\t\tfor (const key of other.provides ?? []) keys.add(key);\n\t\t\t\t}\n\t\t\t}\n\t\t\tkeys.add('AWS_ACCESS_KEY_ID');\n\t\t\tkeys.add('AWS_SECRET_ACCESS_KEY');\n\t\t\tkeys.add('AWS_REGION');\n\t\t}\n\t}\n}\n\n/** One compose service per app the workspace runs, behind the apps profile. */\nexport function appServices(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n\tcontainers: readonly string[],\n\trunnables: Readonly<Record<string, readonly string[]>> = {},\n\tfakes: PlanOptions['fakes'] = {},\n): Record<string, ComposeService> {\n\tconst plan = localPlan(workspace, manifest, fakes);\n\tconst env = networkEnv(plan, {\n\t\tproject: workspace.name,\n\t\taddresses: surfaceAddresses(\n\t\t\tworkspace,\n\t\t\tmanifest,\n\t\t\t(app, port) => `http://${app}:${port}`,\n\t\t),\n\t});\n\t// What a browser is handed: each app on the port this file publishes it on.\n\tconst browser = networkEnv(plan, {\n\t\tproject: workspace.name,\n\t\taddresses: surfaceAddresses(workspace, manifest),\n\t});\n\tconst services: Record<string, ComposeService> = {};\n\tconst buildRoot = findBuildRoot(workspace.root);\n\n\tfor (const [name, app] of Object.entries(workspace.apps)) {\n\t\t// A mobile app ships through its own toolchain; nothing here runs it.\n\t\tif (app.type === 'mobile' || !app.port) continue;\n\n\t\tconst key = appKey(name);\n\t\tconst health = app.type === 'web' ? '/' : '/health';\n\t\t// Built from the build root — the package manager's, which is above the\n\t\t// workspace when it is nested in a monorepo — by the Dockerfile `gkm\n\t\t// docker` writes under the workspace.\n\t\tconst build = composeBuildPaths({\n\t\t\tcomposeDir: workspace.root,\n\t\t\tbuildRoot,\n\t\t\tworkspaceRoot: workspace.root,\n\t\t\tdockerfile: dockerfileOf(name, app.path),\n\t\t});\n\t\tconst ports = [`${app.port}:${app.port}`];\n\n\t\t// A site is a bundle: its public URLs are inlined when it is built, so\n\t\t// they are build arguments, and it reads nothing at runtime — no server\n\t\t// env, and nothing to wait for but its own server.\n\t\tif (app.type === 'web') {\n\t\t\tconst args = sitePublicArgs(manifest, name, browser);\n\t\t\tservices[key] = {\n\t\t\t\timage: `${key}:\\${TAG:-latest}`,\n\t\t\t\tbuild: {\n\t\t\t\t\t...build,\n\t\t\t\t\t...(Object.keys(args).length ? { args } : {}),\n\t\t\t\t},\n\t\t\t\tprofiles: [APPS_PROFILE],\n\t\t\t\tports,\n\t\t\t\thealthcheck: healthcheck(app.port, health),\n\t\t\t};\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst allowed = appEnvKeys(manifest, name, runnables);\n\t\tconst own = Object.fromEntries(\n\t\t\tObject.entries(env).filter(([envKey]) => allowed?.has(envKey)),\n\t\t);\n\n\t\tservices[key] = {\n\t\t\timage: `${key}:\\${TAG:-latest}`,\n\t\t\tbuild,\n\t\t\tprofiles: [APPS_PROFILE],\n\t\t\tports,\n\t\t\tenvironment: {\n\t\t\t\tNODE_ENV: 'production',\n\t\t\t\tPORT: String(app.port),\n\t\t\t\t...own,\n\t\t\t},\n\t\t\t// The edge fronts the host's browser; apps talk to each other directly.\n\t\t\t...(containers.some((c) => c !== 'caddy')\n\t\t\t\t? { depends_on: containers.filter((c) => c !== 'caddy') }\n\t\t\t\t: {}),\n\t\t\thealthcheck: healthcheck(app.port, health),\n\t\t};\n\t}\n\n\treturn services;\n}\n\n/** An app's health check: its own server answering on `path`. */\nexport function healthcheck(\n\tport: number,\n\tpath: string,\n): NonNullable<ComposeService['healthcheck']> {\n\treturn {\n\t\ttest: ['CMD', 'wget', '-q', '--spider', `http://localhost:${port}${path}`],\n\t\tinterval: '10s',\n\t\ttimeout: '5s',\n\t\tretries: 5,\n\t};\n}\n","/**\n * The real Postgres and MinIO clients the applier runs against.\n *\n * Kept apart from `provision.ts` so the rules there — what gets created, in what\n * order, and never dropping anything — are asserted against fakes, while the\n * drivers live here where there is nothing to assert but wiring.\n *\n * Both are constructed from the same local addresses the env writer composes,\n * so the applier cannot end up talking to a different container than the app.\n */\n\nimport {\n\tCreateBucketCommand,\n\tGetBucketPolicyCommand,\n\tHeadBucketCommand,\n\tPutBucketPolicyCommand,\n\tS3Client,\n} from '@aws-sdk/client-s3';\nimport {\n\tCreateTopicCommand,\n\tGetTopicAttributesCommand,\n\tSNSClient,\n} from '@aws-sdk/client-sns';\nimport {\n\tCreateQueueCommand,\n\tGetQueueUrlCommand,\n\tSQSClient,\n} from '@aws-sdk/client-sqs';\nimport { Client } from 'pg';\nimport {\n\tEMULATOR_CREDENTIALS,\n\tEMULATOR_REGION,\n\temulatorEndpoint,\n\temulatorTopicArn,\n} from './emulator';\nimport type { BucketClient, CarrierClient, SqlClient } from './provision';\n\n/** The credential local containers are brought up with. */\nconst LOCAL_USER = 'geekmidas';\n\n/**\n * A Postgres client that opens one connection per database it is asked about.\n *\n * `CREATE DATABASE` cannot run from a connection to the database being created,\n * and a schema must be created from inside its own database — so the database is\n * part of each query rather than fixed when the client is built.\n */\nexport function pgClient(\n\tport: number,\n\t/**\n\t * The master's password, where the cluster is not a local one — a\n\t * `gkm compose` stack serving a deployed stage derives its own.\n\t */\n\tpassword: string = LOCAL_USER,\n): SqlClient {\n\treturn {\n\t\tasync query(database, sql, values) {\n\t\t\tconst client = new Client({\n\t\t\t\thost: 'localhost',\n\t\t\t\tport,\n\t\t\t\tuser: LOCAL_USER,\n\t\t\t\tpassword,\n\t\t\t\t// The cluster's own database, which the image creates. Connecting to\n\t\t\t\t// it is what makes `CREATE DATABASE` possible at all.\n\t\t\t\tdatabase: database ?? LOCAL_USER,\n\t\t\t});\n\n\t\t\tawait client.connect();\n\t\t\ttry {\n\t\t\t\tconst result = await client.query(sql, values as never[]);\n\t\t\t\treturn result.rows;\n\t\t\t} finally {\n\t\t\t\tawait client.end();\n\t\t\t}\n\t\t},\n\t};\n}\n\n/** A bucket client pointed at a MinIO on this machine. */\nexport function bucketClient(\n\tport: number,\n\t/**\n\t * Its root credential, where it is not the local one — a `gkm compose`\n\t * stack serving a deployed stage derives its own.\n\t */\n\tcredentials: { user: string; password: string } = {\n\t\tuser: LOCAL_USER,\n\t\tpassword: LOCAL_USER,\n\t},\n): BucketClient {\n\tconst s3 = new S3Client({\n\t\tregion: 'us-east-1',\n\t\tendpoint: `http://localhost:${port}`,\n\t\t// MinIO serves buckets as paths, not as subdomains of the endpoint.\n\t\tforcePathStyle: true,\n\t\tcredentials: {\n\t\t\taccessKeyId: credentials.user,\n\t\t\tsecretAccessKey: credentials.password,\n\t\t},\n\t});\n\n\treturn {\n\t\tasync exists(bucket) {\n\t\t\ttry {\n\t\t\t\tawait s3.send(new HeadBucketCommand({ Bucket: bucket }));\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\t// Missing, or not reachable. Creating is idempotent enough that\n\t\t\t\t// treating both as \"missing\" is safe.\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync create(bucket) {\n\t\t\tawait s3.send(new CreateBucketCommand({ Bucket: bucket }));\n\t\t},\n\n\t\tasync policy(bucket) {\n\t\t\ttry {\n\t\t\t\tconst result = await s3.send(\n\t\t\t\t\tnew GetBucketPolicyCommand({ Bucket: bucket }),\n\t\t\t\t);\n\t\t\t\treturn result.Policy;\n\t\t\t} catch {\n\t\t\t\t// No policy, or the bucket is not reachable. Both mean there is\n\t\t\t\t// nothing to compare against, and writing one is idempotent.\n\t\t\t\treturn undefined;\n\t\t\t}\n\t\t},\n\n\t\tasync setPolicy(bucket, policy) {\n\t\t\tawait s3.send(\n\t\t\t\tnew PutBucketPolicyCommand({ Bucket: bucket, Policy: policy }),\n\t\t\t);\n\t\t},\n\t};\n}\n\n/** A topic and queue client pointed at the local AWS emulator. */\nexport function carrierClient(port: number): CarrierClient {\n\tconst config = {\n\t\tregion: EMULATOR_REGION,\n\t\tendpoint: emulatorEndpoint(port),\n\t\tcredentials: EMULATOR_CREDENTIALS,\n\t};\n\tconst sns = new SNSClient(config);\n\tconst sqs = new SQSClient(config);\n\n\treturn {\n\t\tasync topicExists(name) {\n\t\t\ttry {\n\t\t\t\tawait sns.send(\n\t\t\t\t\tnew GetTopicAttributesCommand({ TopicArn: emulatorTopicArn(name) }),\n\t\t\t\t);\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\t// Missing, or not reachable; creating is idempotent either way.\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync createTopic(name) {\n\t\t\tawait sns.send(new CreateTopicCommand({ Name: name }));\n\t\t},\n\n\t\tasync queueExists(name) {\n\t\t\ttry {\n\t\t\t\tawait sqs.send(new GetQueueUrlCommand({ QueueName: name }));\n\t\t\t\treturn true;\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\n\t\tasync createQueue(name) {\n\t\t\tawait sqs.send(new CreateQueueCommand({ QueueName: name }));\n\t\t},\n\t};\n}\n","/**\n * The compose document a plan implies.\n *\n * The third declaration of the same containers disappears here: today a\n * `services: ['postgres','minio']` block is kept in step with the code by hand,\n * which is the triplication this design exists to remove, in the one place you\n * touch every day. A `database` implies Postgres; the list is derived.\n *\n * Returns an object rather than a string for the same reason `plan` is pure —\n * what should exist is asserted as data, and turning it into YAML is one line at\n * the edge.\n *\n * What stays config is unchanged: images and versions are pinned explicitly, and\n * a container no construct implies is still declared by hand. Neutral facts come\n * from the declaration, provider detail from config.\n */\n\nimport { stringify } from 'yaml';\nimport { DEFAULT_IMAGES, portsOf, postgresImage, volumeOf } from './containers';\nimport { EMULATOR_CREDENTIALS, EMULATOR_REGION } from './emulator';\nimport type { Plan } from './plan';\nimport type { PortAssignments } from './ports';\n\n/** A compose service definition — as much of the schema as is emitted here. */\nexport interface ComposeService {\n\timage: string;\n\t/** An app is built from its generated Dockerfile; a container is pulled. */\n\tbuild?: {\n\t\tcontext: string;\n\t\tdockerfile: string;\n\t\t/** A site's public URLs, which its bundler inlines at build time. */\n\t\targs?: Record<string, string>;\n\t};\n\t/** Behind a profile, so starting the containers does not start the apps. */\n\tprofiles?: string[];\n\trestart?: string;\n\tentrypoint?: string;\n\tcommand?: string;\n\tports?: string[];\n\tenvironment?: Record<string, string>;\n\tvolumes?: string[];\n\tdepends_on?: string[];\n\thealthcheck?: {\n\t\ttest: string[];\n\t\tinterval: string;\n\t\ttimeout: string;\n\t\tretries: number;\n\t\tstart_period?: string;\n\t};\n\textra_hosts?: string[];\n}\n\nexport interface ComposeFile {\n\tname: string;\n\tservices: Record<string, ComposeService>;\n\tvolumes?: Record<string, Record<string, never>>;\n}\n\n/** The credential every derived container is brought up with locally. */\nconst LOCAL_USER = 'geekmidas';\n\n/**\n * The token the local cache accepts.\n *\n * Fixed rather than generated: it is only ever reachable on a port this project\n * published, and the URL that carries it is derived, so nothing is made safer\n * by making it unguessable and a stable one keeps `redis-cli` sessions working.\n */\nconst LOCAL_TOKEN = 'geekmidas';\n\nexport interface ComposeOptions {\n\t/** Names the compose project, which is what isolates it from other stacks. */\n\tproject: string;\n\t/** Ports assigned by `allocate`, keyed by port key — see `portKeys`. */\n\tports: PortAssignments;\n\t/** App services to write beside the containers — see `reconcile/apps.ts`. */\n\tapps?: Readonly<Record<string, ComposeService>>;\n}\n\n/**\n * Build the compose document for a plan.\n *\n * Every container is published on its assigned port, never on the image's\n * default: the point of allocation is that two projects can run at once, and\n * one container publishing 5432 puts that back.\n */\nexport function composeFor(plan: Plan, options: ComposeOptions): ComposeFile {\n\tconst services: Record<string, ComposeService> = {};\n\tconst volumes: Record<string, Record<string, never>> = {};\n\n\tfor (const container of [...plan.containers].sort()) {\n\t\t// A declared version beats the default image, so the container a\n\t\t// developer runs is the major the declaration names — and the same one a\n\t\t// deploy provisions. An image pin is the project's own\n\t\t// `docker-compose.yml`, merged over this file, and wins over both.\n\t\tconst declared =\n\t\t\tcontainer === 'postgres'\n\t\t\t\t? plan.resources.find((r) => r.version)?.version\n\t\t\t\t: undefined;\n\n\t\tconst fake = plan.fakes?.[container];\n\t\tconst image = declared\n\t\t\t? postgresImage(declared)\n\t\t\t: (fake?.image ?? DEFAULT_IMAGES[container]);\n\t\tif (!image) throw new UnknownContainer(container);\n\n\t\tconst published = publish(container, options.ports, plan.fakes);\n\t\tservices[container] = fake\n\t\t\t? // An external API's fake: the provider's image, as it ships. It has\n\t\t\t\t// no volume — a fake keeps nothing worth keeping — and no\n\t\t\t\t// healthcheck, so running is what ready means.\n\t\t\t\t{ image, restart: 'unless-stopped', ports: published }\n\t\t\t: define(container, image, published);\n\n\t\tconst volume = volumeOf(container);\n\t\tif (volume) volumes[volume] = {};\n\t}\n\n\treturn {\n\t\t// Compose derives a project name from the directory otherwise, so two\n\t\t// checkouts of the same repo would fight over one set of containers.\n\t\tname: options.project,\n\t\tservices: { ...services, ...options.apps },\n\t\t...(Object.keys(volumes).length ? { volumes } : {}),\n\t};\n}\n\n/** Render a compose document as the file it becomes. */\nexport function toYaml(file: ComposeFile): string {\n\treturn `# Generated by gkm from the construct manifest — do not edit.\n# Containers are derived from what the app declares. Image pins and anything\n# no construct implies go in your own docker-compose.yml, which is merged over\n# this file and wins:\n#\n# docker compose -f docker-compose.constructs.yml -f docker-compose.yml …\n#\n# \"gkm dev\" and \"gkm test\" start the containers. The apps sit behind the\n# \"apps\" profile: run \"gkm setup\", then\n# docker compose -f docker-compose.constructs.yml --profile apps up --build\n${stringify(file, { lineWidth: 0 })}`;\n}\n\n/** A container with no image, from config or from the defaults. */\nexport class UnknownContainer extends Error {\n\t/** The container that could not be resolved. */\n\treadonly container: string;\n\t/** The containers that have a default, so the caller can see the set. */\n\treadonly known: readonly string[] = Object.keys(DEFAULT_IMAGES).sort();\n\n\tconstructor(container: string) {\n\t\tsuper('No image is known for this container; pin one in config');\n\t\tthis.name = 'UnknownContainer';\n\t\tthis.container = container;\n\t}\n}\n\n/** One container's definition, published on the port it was assigned. */\nfunction define(\n\tcontainer: string,\n\timage: string,\n\tpublished: string[],\n): ComposeService {\n\tswitch (container) {\n\t\tcase 'postgres':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\tenvironment: {\n\t\t\t\t\tPOSTGRES_USER: LOCAL_USER,\n\t\t\t\t\tPOSTGRES_PASSWORD: LOCAL_USER,\n\t\t\t\t\t// The cluster's own database. Every declared database is created\n\t\t\t\t\t// inside this cluster by the applier, not by the image.\n\t\t\t\t\tPOSTGRES_DB: LOCAL_USER,\n\t\t\t\t},\n\t\t\t\t// One mount at the parent, not at `data`: the 18 image keeps its\n\t\t\t\t// cluster in a version-named subdirectory and refuses to start when\n\t\t\t\t// something is mounted over the legacy path. Mounting the parent is\n\t\t\t\t// also what makes the next major bump a `pg_upgrade --link` rather\n\t\t\t\t// than a new volume.\n\t\t\t\tvolumes: ['postgres-data:/var/lib/postgresql'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD-SHELL', `pg_isready -U ${LOCAL_USER}`],\n\t\t\t\t\tinterval: '5s',\n\t\t\t\t\ttimeout: '3s',\n\t\t\t\t\tretries: 10,\n\t\t\t\t\tstart_period: '10s',\n\t\t\t\t},\n\t\t\t};\n\n\t\tcase 'minio':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\tenvironment: {\n\t\t\t\t\tMINIO_ROOT_USER: LOCAL_USER,\n\t\t\t\t\tMINIO_ROOT_PASSWORD: LOCAL_USER,\n\t\t\t\t},\n\t\t\t\t// No bucket is created here: buckets are planned resources, and\n\t\t\t\t// creating them in the entrypoint would put their names back in the\n\t\t\t\t// compose file — the declaration this change removes.\n\t\t\t\tcommand: 'server --console-address \":9001\" /data',\n\t\t\t\tvolumes: ['minio-data:/data'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD', 'mc', 'ready', 'local'],\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\tstart_period: '10s',\n\t\t\t\t},\n\t\t\t};\n\n\t\tcase 'caddy':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\t// Relative to this file, which is at the project root, so the\n\t\t\t\t// mount resolves into `.gkm/` wherever the project lives.\n\t\t\t\tvolumes: [\n\t\t\t\t\t'./.gkm/Caddyfile:/etc/caddy/Caddyfile:ro',\n\t\t\t\t\t// A directory, not a file: every stage writes its own routes into\n\t\t\t\t\t// it, so `gkm test` cannot delete what `gkm dev` is serving.\n\t\t\t\t\t'./.gkm/caddy-sites:/etc/caddy/sites:ro',\n\t\t\t\t\t'caddy-data:/data',\n\t\t\t\t],\n\t\t\t\t// A surface and a site are processes on the *host*, not containers,\n\t\t\t\t// so the edge has to leave Docker's network to reach them. Docker\n\t\t\t\t// Desktop resolves this name already; on Linux it needs saying.\n\t\t\t\textra_hosts: ['host.docker.internal:host-gateway'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD', 'caddy', 'version'],\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};\n\n\t\tcase 'mailpit':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\tenvironment: {\n\t\t\t\t\t// Accept mail from anything on this network. The URL carries no\n\t\t\t\t\t// credentials locally, which is what the parser's optional `auth`\n\t\t\t\t\t// exists for.\n\t\t\t\t\tMP_SMTP_AUTH_ACCEPT_ANY: '1',\n\t\t\t\t\tMP_SMTP_AUTH_ALLOW_INSECURE: '1',\n\t\t\t\t},\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD', 'wget', '-q', '--spider', 'http://localhost:8025'],\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};\n\n\t\tcase 'redis':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\tvolumes: ['redis-data:/data'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD', 'redis-cli', 'ping'],\n\t\t\t\t\tinterval: '5s',\n\t\t\t\t\ttimeout: '3s',\n\t\t\t\t\tretries: 5,\n\t\t\t\t},\n\t\t\t};\n\n\t\tcase 'redis-http':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\tenvironment: {\n\t\t\t\t\tSRH_MODE: 'env',\n\t\t\t\t\tSRH_TOKEN: LOCAL_TOKEN,\n\t\t\t\t\t// The Redis beside it, by compose service name — the one address\n\t\t\t\t\t// in this file that is not published, because nothing outside the\n\t\t\t\t\t// network speaks to it.\n\t\t\t\t\tSRH_CONNECTION_STRING: 'redis://redis:6379',\n\t\t\t\t},\n\t\t\t\tdepends_on: ['redis'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\t// 127.0.0.1, not localhost: the proxy binds IPv4 only, and\n\t\t\t\t\t// `localhost` resolves to ::1 first inside the container — which\n\t\t\t\t\t// is a health check that never passes and a container that never\n\t\t\t\t\t// comes up.\n\t\t\t\t\ttest: [\n\t\t\t\t\t\t'CMD',\n\t\t\t\t\t\t'wget',\n\t\t\t\t\t\t'-q',\n\t\t\t\t\t\t'-O',\n\t\t\t\t\t\t'/dev/null',\n\t\t\t\t\t\t'http://127.0.0.1:80/',\n\t\t\t\t\t],\n\t\t\t\t\tinterval: '5s',\n\t\t\t\t\ttimeout: '3s',\n\t\t\t\t\tretries: 10,\n\t\t\t\t},\n\t\t\t};\n\n\t\tcase 'rabbitmq':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\tenvironment: {\n\t\t\t\t\tRABBITMQ_DEFAULT_USER: LOCAL_USER,\n\t\t\t\t\tRABBITMQ_DEFAULT_PASS: LOCAL_USER,\n\t\t\t\t},\n\t\t\t\tvolumes: ['rabbitmq-data:/var/lib/rabbitmq'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\ttest: ['CMD', 'rabbitmq-diagnostics', '-q', '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};\n\n\t\tcase 'localstack':\n\t\t\treturn {\n\t\t\t\timage,\n\t\t\t\trestart: 'unless-stopped',\n\t\t\t\tports: published,\n\t\t\t\t// SNS pushes to subscribers on the host — the dev server — and only\n\t\t\t\t// Docker Desktop resolves host.docker.internal on its own. On Linux\n\t\t\t\t// the name has to be mapped, or every push is refused.\n\t\t\t\textra_hosts: ['host.docker.internal:host-gateway'],\n\t\t\t\tenvironment: {\n\t\t\t\t\tAWS_DEFAULT_REGION: EMULATOR_REGION,\n\t\t\t\t\tAWS_ACCESS_KEY_ID: EMULATOR_CREDENTIALS.accessKeyId,\n\t\t\t\t\tAWS_SECRET_ACCESS_KEY: EMULATOR_CREDENTIALS.secretAccessKey,\n\t\t\t\t},\n\t\t\t\tvolumes: ['localstack-data:/var/lib/localstack'],\n\t\t\t\thealthcheck: {\n\t\t\t\t\t// Bash, not curl: the floci image ships no curl, so a curl check\n\t\t\t\t\t// never passes and `--wait` fails every start. A TCP connect is\n\t\t\t\t\t// the fact a waiter needs — the gateway binds the port once its\n\t\t\t\t\t// HTTP server is up — and the repo's own compose file does the\n\t\t\t\t\t// same.\n\t\t\t\t\ttest: ['CMD', 'bash', '-c', 'exec 3<>/dev/tcp/localhost/4566'],\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\tstart_period: '10s',\n\t\t\t\t},\n\t\t\t};\n\n\t\tdefault:\n\t\t\tthrow new UnknownContainer(container);\n\t}\n}\n\n/**\n * The published port mappings for a container.\n *\n * Every port is looked up by its own key rather than being derived as an offset\n * from the container's first, so MinIO's console cannot land on whatever was\n * allocated to the next container.\n */\nfunction publish(\n\tcontainer: string,\n\tports: PortAssignments,\n\tfakes?: Plan['fakes'],\n): string[] {\n\treturn portsOf(container, fakes).map((port) => {\n\t\tconst assigned = ports[port.key];\n\t\tif (assigned === undefined) throw new UnassignedPort(port.key);\n\n\t\treturn `${assigned}:${port.inside}`;\n\t});\n}\n\n/** A planned port that allocation never assigned. */\nexport class UnassignedPort extends Error {\n\t/** The port key that went unassigned, e.g. `minio-console`. */\n\treadonly key: string;\n\n\tconstructor(key: string) {\n\t\tsuper('No port was assigned for this container port');\n\t\tthis.name = 'UnassignedPort';\n\t\tthis.key = key;\n\t}\n}\n","/**\n * The real Docker, behind the small interface reconcile needs.\n *\n * Shells out to `docker compose` rather than talking to the daemon socket: it is\n * what a developer runs by hand when something is wrong, so the two cannot\n * disagree, and it keeps Docker out of this package's dependencies.\n *\n * Every call is scoped to the generated compose file. Nothing here can touch a\n * container this project did not generate.\n */\n\nimport { execFile, spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport type { Docker } from './index';\n\nconst run = promisify(execFile);\n\n/**\n * The generated file, then the project's own if it has one.\n *\n * Merged in that order so the project's wins — an `image:` there pins a\n * container this file derived, and a service there is added beside them.\n */\nexport function composeFiles(composePath: string): string[] {\n\tconst project = join(dirname(composePath), 'docker-compose.yml');\n\treturn ['-f', composePath, ...(existsSync(project) ? ['-f', project] : [])];\n}\n\n/** How long to wait for containers to pass their health checks. */\nconst HEALTH_TIMEOUT_MS = 120_000;\n\n/** `docker compose up` exited unsuccessfully; its own output said why. */\nexport class ComposeUpFailed extends Error {\n\tconstructor(\n\t\treadonly services: readonly string[],\n\t\treadonly code: number | null,\n\t) {\n\t\tsuper(\n\t\t\t`docker compose could not start ${services.join(', ')} (exit ${code ?? 'signal'}). Its output above names the container; \\`docker compose logs <service>\\` shows why it stopped.`,\n\t\t);\n\t\tthis.name = 'ComposeUpFailed';\n\t}\n}\n\n/** Run compose with its output on this terminal rather than captured. */\nfunction shown(args: readonly string[], services: readonly string[]) {\n\treturn new Promise<void>((resolve, reject) => {\n\t\tconst child = spawn('docker', args, {\n\t\t\tstdio: ['ignore', 'inherit', 'inherit'],\n\t\t});\n\t\tchild.on('error', reject);\n\t\tchild.on('close', (code) =>\n\t\t\tcode === 0 ? resolve() : reject(new ComposeUpFailed(services, code)),\n\t\t);\n\t});\n}\n\nexport const dockerCli: Docker = {\n\tasync publishedPort(composePath, service, inside) {\n\t\ttry {\n\t\t\tconst { stdout } = await run('docker', [\n\t\t\t\t'compose',\n\t\t\t\t...composeFiles(composePath),\n\t\t\t\t'port',\n\t\t\t\tservice,\n\t\t\t\tString(inside),\n\t\t\t]);\n\n\t\t\t// `0.0.0.0:54321` — the part after the last colon is the host port.\n\t\t\tconst port = Number(stdout.trim().split(':').pop());\n\n\t\t\treturn Number.isFinite(port) && port > 0 ? port : undefined;\n\t\t} catch {\n\t\t\t// Not running, or no such service. Both mean \"nothing to reuse\".\n\t\t\treturn undefined;\n\t\t}\n\t},\n\n\tasync up(composePath, services, options = {}) {\n\t\tconst args = [\n\t\t\t'compose',\n\t\t\t...composeFiles(composePath),\n\t\t\t'up',\n\t\t\t'-d',\n\t\t\t// Recreate anything whose definition moved — a changed image pin is\n\t\t\t// exactly the case the hash detects, and leaving the old container\n\t\t\t// running would make the reconcile a lie.\n\t\t\t'--remove-orphans',\n\t\t\t'--wait',\n\t\t\t`--wait-timeout=${Math.floor(HEALTH_TIMEOUT_MS / 1000)}`,\n\t\t\t...services,\n\t\t];\n\n\t\tif (options.show) return shown(args, services);\n\t\tawait run('docker', args);\n\t},\n\n\tasync copyOut(composePath, service, from, to) {\n\t\tawait mkdir(dirname(to), { recursive: true });\n\t\tawait run('docker', [\n\t\t\t'compose',\n\t\t\t...composeFiles(composePath),\n\t\t\t'cp',\n\t\t\t`${service}:${from}`,\n\t\t\tto,\n\t\t]);\n\t},\n\n\tasync reload(composePath, service) {\n\t\t// Caddy's own reload is graceful and validates first, so a config it\n\t\t// refuses leaves the previous one serving rather than dropping the edge.\n\t\tawait run('docker', [\n\t\t\t'compose',\n\t\t\t...composeFiles(composePath),\n\t\t\t'exec',\n\t\t\t'-T',\n\t\t\tservice,\n\t\t\t'caddy',\n\t\t\t'reload',\n\t\t\t'--config',\n\t\t\t'/etc/caddy/Caddyfile',\n\t\t]);\n\t},\n\n\tasync healthy(composePath, services) {\n\t\tif (services.length === 0) return true;\n\n\t\ttry {\n\t\t\tconst { stdout } = await run('docker', [\n\t\t\t\t'compose',\n\t\t\t\t...composeFiles(composePath),\n\t\t\t\t'ps',\n\t\t\t\t'--format',\n\t\t\t\t'json',\n\t\t\t\t...services,\n\t\t\t]);\n\n\t\t\tconst running = new Set<string>();\n\t\t\tfor (const line of stdout.trim().split('\\n').filter(Boolean)) {\n\t\t\t\tconst entry = JSON.parse(line) as {\n\t\t\t\t\tService?: string;\n\t\t\t\t\tState?: string;\n\t\t\t\t\tHealth?: string;\n\t\t\t\t};\n\n\t\t\t\t// A container with a health check must be passing it; one without\n\t\t\t\t// only has to be running, since there is nothing better to ask.\n\t\t\t\tconst ok =\n\t\t\t\t\tentry.State === 'running' &&\n\t\t\t\t\t(entry.Health === undefined ||\n\t\t\t\t\t\tentry.Health === '' ||\n\t\t\t\t\t\tentry.Health === 'healthy');\n\n\t\t\t\tif (ok && entry.Service) running.add(entry.Service);\n\t\t\t}\n\n\t\t\treturn services.every((service) => running.has(service));\n\t\t} catch {\n\t\t\t// No compose file yet, or no daemon. Either way, not healthy.\n\t\t\treturn false;\n\t\t}\n\t},\n};\n\nexport { HEALTH_TIMEOUT_MS };\n","/**\n * This machine's address on the local network — what a phone running the app\n * reaches it on.\n */\n\nimport { type NetworkInterfaceInfo, networkInterfaces } from 'node:os';\n\n/**\n * The first private IPv4 address of an interface that is up and not loopback.\n *\n * Private ranges only (`10/8`, `172.16/12`, `192.168/16`): a public address\n * would mean listening for a phone on the open internet, and a link-local one\n * (`169.254/16`) is what an interface has when it found no network at all.\n * Undefined when there is none — a laptop on no network still runs, its mobile\n * app reaching only a simulator.\n */\nexport function lanAddress(\n\tinterfaces: Record<\n\t\tstring,\n\t\tNetworkInterfaceInfo[] | undefined\n\t> = networkInterfaces(),\n): string | undefined {\n\tfor (const entries of Object.values(interfaces)) {\n\t\tfor (const entry of entries ?? []) {\n\t\t\tif (entry.family !== 'IPv4' || entry.internal) continue;\n\t\t\tif (isPrivate(entry.address)) return entry.address;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction isPrivate(address: string): boolean {\n\treturn (\n\t\t/^10\\./.test(address) ||\n\t\t/^192\\.168\\./.test(address) ||\n\t\t/^172\\.(1[6-9]|2\\d|3[01])\\./.test(address)\n\t);\n}\n","/**\n * Port allocation — why two projects can run at once.\n *\n * Publishing `5432:5432` means the second project on your machine cannot start.\n * Nothing solves that except giving each project its own ports, and what makes\n * it *possible* is the single-`<NAME>_URL` rule: an app reads `ORDERS_URL` and\n * never sees a host or a port, so reconcile is free to bind whatever is\n * available and compose the URL from what it bound. An app reading `DB_HOST` and\n * `DB_PORT` separately could not be moved this way.\n *\n * Assignments are **persisted, not recomputed**. Recomputing means adding a\n * construct renumbers its neighbours, and every running container, saved\n * connection string, and `psql` history goes stale at once.\n *\n * Pure, with the liveness probe injected — a collision with another project's\n * stack is a fact about the machine, and keeping it a parameter is what lets the\n * rules be asserted without binding a socket.\n */\n\nimport { createHash } from 'node:crypto';\nimport { createServer } from 'node:net';\n\n/** What was assigned, by container name. Persisted between runs. */\nexport type PortAssignments = Readonly<Record<string, number>>;\n\n/**\n * The window ports are drawn from.\n *\n * Above the registered range so nothing here needs privileges, and below the\n * ephemeral range both Linux (32768+) and macOS (49152+) allocate from — a port\n * the kernel might hand to an outgoing connection is one that is free when\n * checked and taken by the time it is bound.\n */\nconst WINDOW = { min: 20000, max: 32000 } as const;\n\n/**\n * How far apart two projects start.\n *\n * Wide enough that a project can add containers without walking into its\n * neighbour's block, narrow enough that the window holds many projects.\n */\nconst BLOCK = 50;\n\n/**\n * Where a project's ports start.\n *\n * Derived from the name rather than assigned sequentially so that a fresh clone\n * of the same project on the same machine lands on the same block — the first\n * run has nothing persisted to read, and hashing is what keeps that first run\n * from colliding with whatever else is already allocated.\n *\n * @example startingPort('toolbox') // stable for a given name\n */\nexport function startingPort(project: string): number {\n\tconst digest = createHash('sha256').update(project).digest();\n\tconst blocks = Math.floor((WINDOW.max - WINDOW.min) / BLOCK);\n\n\treturn WINDOW.min + (digest.readUInt32BE(0) % blocks) * BLOCK;\n}\n\n/**\n * Whether a port can be bound right now.\n *\n * Async because the only way to answer it is to try binding, and\n * `server.listen` is asynchronous — there is no synchronous form to wrap. A\n * probe that pretended otherwise would have to lie.\n */\nexport type PortProbe = (port: number) => Promise<boolean>;\n\n/**\n * Assign a port to every container that has none, keeping the ones that do.\n *\n * Existing assignments are returned untouched even when the container is no\n * longer in the plan: removing a construct must not renumber what is left, and\n * keeping the entry means re-adding it later restores the same port rather than\n * inventing a new one.\n *\n * @param probe - reports what is actually listening. A port assigned to this\n * project is skipped without probing, because on a converged reconcile our own\n * container is the thing listening on it.\n * @throws {NoPortAvailable} when the window is exhausted.\n */\nexport async function allocate(\n\tproject: string,\n\tcontainers: readonly string[],\n\texisting: PortAssignments = {},\n\tprobe: PortProbe = isPortFree,\n): Promise<PortAssignments> {\n\tconst assignments: Record<string, number> = { ...existing };\n\tconst taken = new Set(Object.values(assignments));\n\n\t// Sorted so the order containers happen to appear in the plan cannot change\n\t// which port each one gets.\n\tfor (const container of [...containers].sort()) {\n\t\tif (assignments[container] !== undefined) continue;\n\n\t\tconst port = await nextFree(startingPort(project), taken, probe);\n\t\tassignments[container] = port;\n\t\ttaken.add(port);\n\t}\n\n\treturn assignments;\n}\n\n/**\n * The ports to keep: what running containers are observed on, then what was\n * saved — minus any saved entry whose port something else already holds.\n *\n * Spreading one over the other let an observed port override its own key and\n * leave a *different* key still saved on the same number: two services on one\n * port, and whichever answered first won. Saved keys for containers the plan no\n * longer has — a backend switched and back — were the usual source. Observed\n * wins because it is what is actually bound; a dropped entry is simply\n * allocated again.\n */\nexport function keptPorts(\n\tsaved: PortAssignments = {},\n\tobserved: PortAssignments = {},\n): PortAssignments {\n\tconst kept: Record<string, number> = { ...observed };\n\tconst taken = new Set(Object.values(observed));\n\n\t// Sorted, so which of two colliding saved entries survives is stable.\n\tfor (const [key, port] of Object.entries(saved).sort(([a], [b]) =>\n\t\ta < b ? -1 : 1,\n\t)) {\n\t\tif (key in kept || taken.has(port)) continue;\n\t\tkept[key] = port;\n\t\ttaken.add(port);\n\t}\n\n\treturn kept;\n}\n\n/** The window was exhausted. */\nexport class NoPortAvailable extends Error {\n\t/** Where the search started. */\n\treadonly from: number;\n\t/** The window searched, so the caller can report what was full. */\n\treadonly window: { min: number; max: number } = WINDOW;\n\n\tconstructor(from: number) {\n\t\tsuper('No free port is available in the local port range');\n\t\tthis.name = 'NoPortAvailable';\n\t\tthis.from = from;\n\t}\n}\n\n/**\n * The first port at or after `from` that is neither ours nor in use.\n *\n * Wraps at the top of the window rather than stopping, so a project whose block\n * sits near the end is not the one that fails.\n */\nasync function nextFree(\n\tfrom: number,\n\ttaken: ReadonlySet<number>,\n\tprobe: PortProbe,\n): Promise<number> {\n\tconst span = WINDOW.max - WINDOW.min;\n\n\tfor (let offset = 0; offset < span; offset++) {\n\t\tconst port = WINDOW.min + ((from - WINDOW.min + offset) % span);\n\t\tif (taken.has(port)) continue;\n\t\tif (await probe(port)) return port;\n\t}\n\n\tthrow new NoPortAvailable(from);\n}\n\n/**\n * Whether nothing is listening on a port.\n *\n * Binds rather than connects: a refused connection says nothing about whether\n * the port can be *published*, which is the question Docker will ask. Binds on\n * `0.0.0.0` for the same reason — a port free on loopback can still be taken on\n * the address Docker publishes to.\n *\n * Inherently racy, and that is acceptable: Docker failing to bind stays the real\n * check. This exists so the common case surfaces as a message at reconcile\n * rather than as a connection failure ten minutes later.\n */\nexport function isPortFree(port: number): Promise<boolean> {\n\treturn new Promise((resolve) => {\n\t\tconst server = createServer();\n\n\t\tserver.once('error', () => resolve(false));\n\t\tserver.once('listening', () => server.close(() => resolve(true)));\n\t\tserver.listen(port, '0.0.0.0');\n\t});\n}\n","/**\n * Creating what the URLs name.\n *\n * Convergent and idempotent: every statement asks whether the thing exists\n * first, so reconciling repeatedly is free and a half-applied run recovers by\n * being run again. That property is what lets `gkm dev` do this on every start.\n *\n * The line the design draws is at data, not at side effects — this creates\n * databases, schemas, and buckets, and it never seeds, resets, or drops. A\n * developer who wants their data gone asks for it explicitly.\n *\n * Statement generation is separated from application for the reason the design\n * gives: the same DDL has to run in-process locally and from a provisioner in a\n * VPC, and one implementation is what stops \"works locally, fails deployed\".\n * When the role DDL lands in `@geekmidas/db` this delegates to it rather than\n * growing its own.\n */\n\nimport { cacheTableStatements } from '@geekmidas/cache/postgres';\nimport { ownerRole, readerRole, roleStatements } from '@geekmidas/db/pg/roles';\nimport { cacheTable } from '@geekmidas/manifest';\nimport { localRole, localRolePassword, rootDatabase } from './env';\nimport type { Plan, PlannedResource } from './plan';\n\n/** One thing to create, and how to tell whether it already exists. */\nexport interface Statement {\n\t/** The construct this is for, so a failure can name it. */\n\tid: string;\n\t/** What it does, in the line reconcile prints. */\n\tdescribe: string;\n\t/** The database to connect to. Absent means the cluster's default. */\n\tdatabase?: string;\n\t/**\n\t * Answers \"does this already exist\", parameterised.\n\t *\n\t * Absent for a statement that is idempotent in itself — a `GRANT` re-granting\n\t * what is already granted is a no-op, and checking would cost a round trip to\n\t * learn nothing. Those run every time and report unchanged.\n\t */\n\texists?: { sql: string; values: unknown[] };\n\t/**\n\t * The DDL, run only when `exists` returns nothing.\n\t *\n\t * Not parameterised, because identifiers cannot be — which is why\n\t * {@link quoteIdentifier} exists and why names are canonicalised long before\n\t * they reach here.\n\t */\n\tcreate: string;\n}\n\n/**\n * The Postgres work a plan implies.\n *\n * Databases before the schemas inside them, which `provisionOrder` already\n * guarantees by putting parents first.\n */\nexport function postgresStatements(\n\tplan: Plan,\n\t/**\n\t * Seeds the derived role passwords. The same project seeds the same\n\t * passwords, so a restart does not lock a developer out of their own data —\n\t * and two checkouts do not share a credential.\n\t */\n\tproject = '',\n\t/**\n\t * A deployed stage's random seed, salting the role passwords the way a\n\t * deploy does — see `localRolePassword`. Absent for the local stages.\n\t */\n\tseed?: string,\n): Statement[] {\n\tconst statements: Statement[] = [];\n\n\tfor (const resource of plan.resources) {\n\t\tif (!resource.provisions || resource.container !== 'postgres') continue;\n\n\t\tif (resource.kind === 'database') {\n\t\t\tstatements.push({\n\t\t\t\tid: resource.id,\n\t\t\t\tdescribe: `database ${resource.name}`,\n\t\t\t\texists: {\n\t\t\t\t\tsql: 'SELECT 1 FROM pg_database WHERE datname = $1',\n\t\t\t\t\tvalues: [resource.name],\n\t\t\t\t},\n\t\t\t\t// `CREATE DATABASE` cannot run inside a transaction, which is why\n\t\t\t\t// each statement is applied on its own rather than batched.\n\t\t\t\tcreate: `CREATE DATABASE ${quoteIdentifier(resource.name)}`,\n\t\t\t});\n\n\t\t\t// After the database, never before: roles are cluster-scoped but their\n\t\t\t// grants are not, so every statement after the first two has to run\n\t\t\t// against a database that exists.\n\t\t\tif (resource.schema) {\n\t\t\t\tstatements.push(\n\t\t\t\t\t...rolesFor(resource, resource.schema, plan, project, seed),\n\t\t\t\t);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (resource.kind === 'database-schema' && resource.schema) {\n\t\t\t// The schema *and* the roles that reach it, from one generator shared\n\t\t\t// with the deploy target — the same DDL has to run in-process here and\n\t\t\t// from a provisioner in a VPC, and one implementation is what stops\n\t\t\t// \"works locally, fails deployed\".\n\t\t\tstatements.push(\n\t\t\t\t...rolesFor(resource, resource.schema, plan, project, seed),\n\t\t\t);\n\t\t}\n\n\t\t// A cache that lives in a database is a table in it, and a table is DDL —\n\t\t// so the owner creates it, not the handler's role, which may not create\n\t\t// anything. The same reason the driver does not do it lazily.\n\t\t//\n\t\t// `of` is the whole test: `planFor` resolves a `cache: 'db'` backend into\n\t\t// that edge before anything reads the plan, so a cache lands in a\n\t\t// database exactly one way and this does not have to know the config\n\t\t// existed.\n\t\tconst cacheHome =\n\t\t\tresource.kind === 'cache' && resource.of\n\t\t\t\t? plan.resources.find((r) => r.id === resource.of)\n\t\t\t\t: undefined;\n\n\t\tif (cacheHome) {\n\t\t\tstatements.push(...cacheTableDdl(resource, cacheHome, plan));\n\t\t}\n\n\t\t// A reader provisions nothing: it is a set of grants on an endpoint that\n\t\t// already exists, and falling back to the writer where no replica exists\n\t\t// stays safe because read-only is enforced by the role.\n\t}\n\n\treturn statements;\n}\n\n/**\n * A cache's table, in the schema the role that reads it resolves names in.\n *\n * Two things have to line up, and neither is the table's contents. The driver\n * names the table unqualified and lets the connection's `search_path` place it\n * — which the role carries — so creating it from the master connection, whose\n * path is `public`, leaves a table the application cannot see and a\n * `relation \"cache\" does not exist` on the first request. And the default\n * privileges that make a tenant's tables reachable are granted *for the owner\n * role*, so nothing covers a table the master created.\n *\n * Hence: qualified with the tenant's schema, handed to its owner, and granted\n * explicitly — `roleStatements` grants what exists at the time it runs, and\n * this table does not exist yet.\n *\n * A database that opted out of roles needs none of it: everything connects as\n * the master, whose `search_path` finds `public`, and the table it creates\n * there is the table it reads.\n */\nfunction cacheTableDdl(\n\tcache: PlannedResource,\n\thome: PlannedResource,\n\tplan: Plan,\n): Statement[] {\n\tconst database = rootDatabase(home, plan);\n\tconst schema = home.roles === false ? undefined : home.schema;\n\t// The same default the URL carries — `cacheTable` is read by both, so the\n\t// table a client reads and the table a target creates cannot drift.\n\tconst name = cache.table ?? cacheTable(cache.id);\n\tconst table = schema ? `${schema}.${name}` : name;\n\n\tconst created = cacheTableStatements({ table }).map((statement) => ({\n\t\tid: cache.id,\n\t\tdescribe: statement.describe,\n\t\tdatabase,\n\t\t...(statement.exists ? { exists: statement.exists } : {}),\n\t\tcreate: statement.sql,\n\t}));\n\n\tif (!schema) return created;\n\n\tconst runtime = localRole(home);\n\tconst owner = ownerRole(runtime);\n\tconst qualified = table\n\t\t.split('.')\n\t\t.map((part) => quoteIdentifier(part))\n\t\t.join('.');\n\n\treturn [\n\t\t...created,\n\t\t{\n\t\t\tid: cache.id,\n\t\t\tdescribe: `cache table ${table} is owned by ${owner}`,\n\t\t\tdatabase,\n\t\t\tcreate: `ALTER TABLE ${qualified} OWNER TO ${quoteIdentifier(owner)}`,\n\t\t},\n\t\t{\n\t\t\tid: cache.id,\n\t\t\tdescribe: `${runtime} may read and write ${table}`,\n\t\t\tdatabase,\n\t\t\tcreate:\n\t\t\t\t`GRANT SELECT, INSERT, UPDATE, DELETE ON ${qualified} ` +\n\t\t\t\t`TO ${quoteIdentifier(runtime)}`,\n\t\t},\n\t];\n}\n\n/**\n * The roles and schema for one database or tenant.\n *\n * Skipped entirely when the construct declared `roles: false` — the documented\n * downgrade, where both URLs fall back to the cluster's master credential. It\n * is a choice someone made rather than a default they got.\n */\nfunction rolesFor(\n\tresource: PlannedResource,\n\tschema: string,\n\tplan: Plan,\n\tproject: string,\n\tseed?: string,\n): Statement[] {\n\tif (resource.roles === false) return [];\n\n\tconst runtime = localRole(resource);\n\tconst owner = ownerRole(runtime);\n\n\t// A reader role only where something reads through one. Creating roles\n\t// nothing connects as is noise in `\\du` and one more thing to explain.\n\tconst reader = plan.resources.some(\n\t\t(r) => r.kind === 'database-reader' && r.of === resource.id,\n\t)\n\t\t? readerRole(runtime)\n\t\t: undefined;\n\n\treturn roleStatements({\n\t\truntime,\n\t\towner,\n\t\t...(reader ? { reader } : {}),\n\t\tschema,\n\t\t// A database's owner may create in it — trusted extensions; a tenant's\n\t\t// stays confined to its schema.\n\t\t...(resource.kind === 'database'\n\t\t\t? { database: rootDatabase(resource, plan) }\n\t\t\t: {}),\n\t\tpasswords: {\n\t\t\truntime: localRolePassword(project, plan, runtime, seed),\n\t\t\towner: localRolePassword(project, plan, owner, seed),\n\t\t\t...(reader\n\t\t\t\t? { reader: localRolePassword(project, plan, reader, seed) }\n\t\t\t\t: {}),\n\t\t},\n\t}).map((statement) => ({\n\t\tid: resource.id,\n\t\tdescribe: statement.describe,\n\t\t// Inside the parent's database — a tenant is a schema, never a database\n\t\t// of its own. Roles are cluster-scoped, but the grants are not, so all of\n\t\t// it has to run against the database the objects live in.\n\t\tdatabase: rootDatabase(resource, plan),\n\t\t...(statement.exists ? { exists: statement.exists } : {}),\n\t\tcreate: statement.sql,\n\t}));\n}\n\n/** The buckets a plan implies. */\nexport function bucketNames(plan: Plan): string[] {\n\treturn plan.resources\n\t\t.filter((r) => r.provisions && r.kind === 'objects')\n\t\t.map((r) => r.name);\n}\n\n/**\n * Quote an identifier for use in DDL.\n *\n * Names reaching here are already canonical and stage-suffixed, so this is\n * defence rather than sanitisation — but DDL cannot be parameterised, and an\n * unquoted identifier is the one place a name could ever be more than a name.\n */\nexport function quoteIdentifier(name: string): string {\n\treturn `\"${name.replace(/\"/g, '\"\"')}\"`;\n}\n\n/** What applying a statement did. */\nexport interface Applied {\n\tid: string;\n\tdescribe: string;\n\t/** False when it already existed — the converged case. */\n\tcreated: boolean;\n}\n\n/** The database operations the applier needs, so it can be run against a fake. */\nexport interface SqlClient {\n\t/** Run a query against one database, returning its rows. */\n\tquery(\n\t\tdatabase: string | undefined,\n\t\tsql: string,\n\t\tvalues?: unknown[],\n\t): Promise<unknown[]>;\n}\n\n/**\n * Apply the plan's Postgres statements.\n *\n * Each is checked before it is run, so this converges rather than erroring on a\n * second pass.\n */\nexport async function applyPostgres(\n\tclient: SqlClient,\n\tstatements: readonly Statement[],\n): Promise<Applied[]> {\n\tconst applied: Applied[] = [];\n\n\tfor (const statement of statements) {\n\t\t// No check means the statement is idempotent in itself — a GRANT\n\t\t// re-granting what is already granted is a no-op — so it runs and\n\t\t// reports unchanged, and a converged reconcile still says converged.\n\t\tif (!statement.exists) {\n\t\t\tawait client.query(statement.database, statement.create);\n\t\t\tapplied.push({\n\t\t\t\tid: statement.id,\n\t\t\t\tdescribe: statement.describe,\n\t\t\t\tcreated: false,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst rows = await client.query(\n\t\t\tstatement.database,\n\t\t\tstatement.exists.sql,\n\t\t\tstatement.exists.values,\n\t\t);\n\n\t\tif (rows.length > 0) {\n\t\t\tapplied.push({\n\t\t\t\tid: statement.id,\n\t\t\t\tdescribe: statement.describe,\n\t\t\t\tcreated: false,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tawait client.query(statement.database, statement.create);\n\t\tapplied.push({\n\t\t\tid: statement.id,\n\t\t\tdescribe: statement.describe,\n\t\t\tcreated: true,\n\t\t});\n\t}\n\n\treturn applied;\n}\n\n/**\n * What each bucket's open paths are, if anything serves it.\n *\n * Keyed by the *origin*, because that is where the policy lives: two servers\n * over one bucket contribute to one policy, so the patterns are unioned rather\n * than the last one winning.\n */\nexport function bucketPolicies(\n\tplan: Plan,\n): { bucket: string; open: string[] }[] {\n\tconst byBucket = new Map<string, Set<string>>();\n\n\tfor (const resource of plan.resources) {\n\t\tif (resource.kind !== 'file-server' || !resource.open?.length) continue;\n\n\t\tconst origin = plan.resources.find((r) => r.id === resource.of);\n\t\tif (!origin) continue;\n\n\t\tconst patterns = byBucket.get(origin.name) ?? new Set<string>();\n\t\tfor (const pattern of resource.open) patterns.add(pattern);\n\t\tbyBucket.set(origin.name, patterns);\n\t}\n\n\treturn [...byBucket]\n\t\t.map(([bucket, open]) => ({ bucket, open: [...open].sort() }))\n\t\t.sort((a, b) => a.bucket.localeCompare(b.bucket));\n}\n\n/**\n * One bucket's open paths as an S3 bucket policy.\n *\n * Anonymous `s3:GetObject` on the named prefixes and nothing else — which is\n * what \"open\" means everywhere: the server serves that path without a\n * signature, and the bucket stays private for everything else.\n *\n * One fidelity note, stated rather than discovered: an S3 policy resource's `*`\n * crosses `/`, so a single-star pattern is *wider* here than the construct's own\n * runtime check, which stops at a segment boundary. The construct is the\n * stricter of the two, so a key it refuses to serve unsigned is never one this\n * policy was relied on to refuse — but a key fetched directly, bypassing the\n * client, can be admitted by the policy where the client would have said no.\n * Prefer `**` where crossing segments is what you meant.\n */\nexport function bucketPolicy(bucket: string, open: readonly string[]): string {\n\treturn JSON.stringify({\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tSid: 'GkmOpenPaths',\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tPrincipal: { AWS: ['*'] },\n\t\t\t\tAction: ['s3:GetObject'],\n\t\t\t\tResource: open.map(\n\t\t\t\t\t(pattern) =>\n\t\t\t\t\t\t`arn:aws:s3:::${bucket}/${pattern.replace(/\\*\\*/g, '*')}`,\n\t\t\t\t),\n\t\t\t},\n\t\t],\n\t});\n}\n\n/**\n * The topics and queues to create on the AWS emulator.\n *\n * Only on the `sns` backend: pg-boss makes its own tables on first connect and\n * a RabbitMQ exchange is declared by the client, but an SNS topic and an SQS\n * queue must exist before anything can publish to them.\n */\nexport function carrierNames(plan: Plan): {\n\ttopics: string[];\n\tqueues: string[];\n} {\n\tif (plan.events !== 'sns') return { topics: [], queues: [] };\n\n\tconst named = (kind: 'topic' | 'queue') =>\n\t\tplan.resources.filter((r) => r.kind === kind).map((r) => r.name);\n\n\treturn { topics: named('topic'), queues: named('queue') };\n}\n\n/** The emulator operations the applier needs. Both creates are idempotent. */\nexport interface CarrierClient {\n\ttopicExists(name: string): Promise<boolean>;\n\tcreateTopic(name: string): Promise<void>;\n\tqueueExists(name: string): Promise<boolean>;\n\tcreateQueue(name: string): Promise<void>;\n}\n\n/** Create each topic and queue the plan names, skipping those that exist. */\nexport async function applyCarriers(\n\tclient: CarrierClient,\n\tcarriers: { topics: readonly string[]; queues: readonly string[] },\n): Promise<Applied[]> {\n\tconst applied: Applied[] = [];\n\n\tfor (const name of carriers.topics) {\n\t\tconst created = !(await client.topicExists(name));\n\t\tif (created) await client.createTopic(name);\n\t\tapplied.push({ id: name, describe: `topic ${name}`, created });\n\t}\n\tfor (const name of carriers.queues) {\n\t\tconst created = !(await client.queueExists(name));\n\t\tif (created) await client.createQueue(name);\n\t\tapplied.push({ id: name, describe: `queue ${name}`, created });\n\t}\n\n\treturn applied;\n}\n\n/** The Postgres databases the plan creates — each is a `CREATE DATABASE`. */\nexport function postgresDatabaseNames(plan: Plan): string[] {\n\treturn plan.resources\n\t\t.filter(\n\t\t\t(resource) =>\n\t\t\t\tresource.kind === 'database' &&\n\t\t\t\tresource.provisions &&\n\t\t\t\tresource.container === 'postgres',\n\t\t)\n\t\t.map((resource) => resource.name);\n}\n\n/**\n * Whether every one of these databases exists. A database dropped behind the\n * recorded state's back — by another checkout's test teardown, against the\n * same container — makes the state a lie, and only asking Postgres finds out.\n * Unreachable counts as missing: applying again is what finds out why.\n */\nexport async function databasesExist(\n\tclient: SqlClient,\n\tnames: readonly string[],\n): Promise<boolean> {\n\tif (names.length === 0) return true;\n\ttry {\n\t\tconst rows = (await client.query(\n\t\t\tundefined,\n\t\t\t'SELECT datname FROM pg_catalog.pg_database WHERE datname = ANY($1)',\n\t\t\t[names],\n\t\t)) as { datname: string }[];\n\t\treturn new Set(rows.map((row) => row.datname)).size === names.length;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** The object-storage operations the applier needs. */\nexport interface BucketClient {\n\texists(bucket: string): Promise<boolean>;\n\tcreate(bucket: string): Promise<void>;\n\t/** The bucket's current policy, or `undefined` where it has none. */\n\tpolicy(bucket: string): Promise<string | undefined>;\n\tsetPolicy(bucket: string, policy: string): Promise<void>;\n}\n\n/**\n * Apply each served bucket's open-path policy, skipping those already correct.\n *\n * Compared as parsed JSON rather than as text: the same policy re-serialised\n * with different key order is the same policy, and rewriting it every reconcile\n * would report a change on every start.\n */\nexport async function applyPolicies(\n\tclient: BucketClient,\n\tpolicies: readonly { bucket: string; open: string[] }[],\n): Promise<Applied[]> {\n\tconst applied: Applied[] = [];\n\n\tfor (const { bucket, open } of policies) {\n\t\tconst wanted = bucketPolicy(bucket, open);\n\t\tconst describe = `open paths on ${bucket}`;\n\n\t\tif (equivalent(await client.policy(bucket), wanted)) {\n\t\t\tapplied.push({ id: `${bucket}:policy`, describe, created: false });\n\t\t\tcontinue;\n\t\t}\n\n\t\tawait client.setPolicy(bucket, wanted);\n\t\tapplied.push({ id: `${bucket}:policy`, describe, created: true });\n\t}\n\n\treturn applied;\n}\n\n/** Whether two policy documents say the same thing. */\nfunction equivalent(current: string | undefined, wanted: string): boolean {\n\tif (!current) return false;\n\n\ttry {\n\t\treturn (\n\t\t\tJSON.stringify(JSON.parse(current)) === JSON.stringify(JSON.parse(wanted))\n\t\t);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/** Create every bucket the plan names, skipping those that exist. */\nexport async function applyBuckets(\n\tclient: BucketClient,\n\tbuckets: readonly string[],\n): Promise<Applied[]> {\n\tconst applied: Applied[] = [];\n\n\tfor (const bucket of buckets) {\n\t\tif (await client.exists(bucket)) {\n\t\t\tapplied.push({\n\t\t\t\tid: bucket,\n\t\t\t\tdescribe: `bucket ${bucket}`,\n\t\t\t\tcreated: false,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tawait client.create(bucket);\n\t\tapplied.push({ id: bucket, describe: `bucket ${bucket}`, created: true });\n\t}\n\n\treturn applied;\n}\n","/**\n * What reconcile records, and the hash that lets it do nothing.\n *\n * Reconciling on every `gkm dev` is only acceptable if the converged case is\n * free, so the desired plan is hashed: matching hash plus healthy containers\n * means there is nothing to do. Without that, the automatic reconcile the design\n * asks for would tax every start.\n *\n * Almost nothing needs recording — roles are queried, migrations have their own\n * table, containers are remembered by Docker. Only ports and this hash have no\n * other owner.\n */\n\nimport { createHash } from 'node:crypto';\nimport { mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport type { ComposeFile } from './compose';\nimport type { Plan } from './plan';\n\n/** What was true at the end of the last successful reconcile. */\nexport interface ReconcileState {\n\t/** The hash of the plan that was applied. */\n\thash: string;\n\t/** The stage it was applied for, so a mismatched file is ignored. */\n\tstage: string;\n}\n\nconst STATE_DIR = '.gkm/reconcile';\n\n/**\n * The hash of everything a reconcile would act on.\n *\n * Covers the compose document rather than the plan alone, because the plan is\n * unchanged when only an image pin moves — and a changed image is exactly a\n * container that must be recreated. `stringify` is stable here because\n * `composeFor` sorts its containers, which is what makes the hash meaningful.\n *\n * Deliberately a plain function over serialisable input: the deployed target\n * keys its provisioner invocation on the same idea, and the two must not be able\n * to disagree about what counts as a change.\n */\nexport function planHash(\n\tplan: Plan,\n\tcompose: ComposeFile,\n\t/**\n\t * Anything else generated from the plan that a container reads.\n\t *\n\t * The edge's Caddyfile is the case: a file server added or a bucket renamed\n\t * changes the routing without changing a single container, so a hash over\n\t * the compose document alone would report convergence and leave the old\n\t * routes serving.\n\t */\n\tgenerated: Readonly<Record<string, string>> = {},\n): string {\n\treturn createHash('sha256')\n\t\t.update(JSON.stringify({ stage: plan.stage, plan, compose, generated }))\n\t\t.digest('hex');\n}\n\n/** Read the recorded state, or nothing if this stage has never reconciled. */\nexport async function loadState(\n\troot: string,\n\tstage: string,\n): Promise<ReconcileState | undefined> {\n\ttry {\n\t\tconst raw = await readFile(statePath(root, stage), 'utf-8');\n\t\tconst state = JSON.parse(raw) as ReconcileState;\n\n\t\t// A file written for another stage is not this stage's state. Treating it\n\t\t// as one is how `gkm test` would skip work `gkm dev` did.\n\t\treturn state.stage === stage ? state : undefined;\n\t} catch {\n\t\t// Unreadable, absent, or corrupt all mean the same thing: reconcile. The\n\t\t// loop is convergent, so the cost of being wrong here is one slow start.\n\t\treturn undefined;\n\t}\n}\n\n/** Record what was applied, so the next run can skip it. */\nexport async function saveState(\n\troot: string,\n\tstate: ReconcileState,\n): Promise<void> {\n\tconst path = statePath(root, state.stage);\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, `${JSON.stringify(state, null, 2)}\\n`);\n}\n\n/**\n * Forget what was applied, so the next reconcile of this stage converges from\n * nothing — creating again whatever was dropped underneath the record.\n */\nexport async function forgetState(root: string, stage: string): Promise<void> {\n\tawait rm(statePath(root, stage), { force: true });\n}\n\nfunction statePath(root: string, stage: string): string {\n\treturn join(root, STATE_DIR, `${stage}.json`);\n}\n","/**\n * Reconcile — the one convergent function `gkm dev`, `gkm test`, and `gkm setup`\n * all call.\n *\n * Three phases, and only this one has side effects: declaring is pure (`gkm\n * build` and the dev watcher), running reads `<NAME>_URL` and provisions\n * nothing. Reconcile computes the desired state, compares, and applies the\n * difference — which is what makes \"when does it run\" answerable with\n * *whenever, repeatedly*.\n *\n * It is safe to run automatically because its blast radius is entirely local:\n * this project's containers, this project's `.gkm/`. Nothing it does can reach a\n * cloud. The line is drawn at data, not at side effects — it allocates ports,\n * writes compose, starts containers, and creates roles; it never seeds, resets,\n * or drops.\n *\n * Docker is injected rather than imported so the rules above can be asserted\n * without a daemon. The default implementation is the real one.\n */\n\nimport { existsSync } from 'node:fs';\nimport { mkdir, readdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { type ConstructManifest, provisionOrder } from '@geekmidas/manifest';\nimport type { CacheBackend, EventsBackend } from '../types';\nimport { TEST_STAGE } from '../workspace/stages';\nimport { caddyfileRoot, sitesFor, toCaddyfile } from './caddyfile';\nimport { bucketClient, carrierClient, pgClient } from './clients';\nimport {\n\ttype ComposeFile,\n\ttype ComposeService,\n\tcomposeFor,\n\ttoYaml,\n} from './compose';\nimport {\n\tportKeys,\n\tportsOf,\n\tprimaryPortKey,\n\ttype ServiceAddress,\n\tserviceAddresses,\n} from './containers';\nimport { dockerCli } from './docker';\nimport { envFor } from './env';\nimport type { LocalFake } from './fakes';\nimport { lanAddress } from './lan';\nimport { type Plan, type PlanOptions, planFor } from './plan';\nimport {\n\tallocate,\n\tisPortFree,\n\tkeptPorts,\n\ttype PortAssignments,\n\ttype PortProbe,\n} from './ports';\nimport {\n\ttype Applied,\n\tapplyBuckets,\n\tapplyCarriers,\n\tapplyPolicies,\n\tapplyPostgres,\n\ttype BucketClient,\n\tbucketNames,\n\tbucketPolicies,\n\ttype CarrierClient,\n\tcarrierNames,\n\tdatabasesExist,\n\tpostgresDatabaseNames,\n\tpostgresStatements,\n\ttype SqlClient,\n} from './provision';\nimport { loadState, planHash, saveState } from './state';\n\nexport type { ComposeFile } from './compose';\nexport type { Plan, PlannedResource } from './plan';\nexport type { PortAssignments } from './ports';\n\n/**\n * Where the generated compose file is written, relative to the project root.\n *\n * At the root, beside the project's own `docker-compose.yml`, because the two\n * are one stack: this file is everything the constructs imply and is never\n * edited, that one is anything they cannot express — an image pin, an extra\n * service — and is merged over it, so it wins.\n */\nexport const COMPOSE_PATH = 'docker-compose.constructs.yml';\n\n/** The project's own compose file, merged over the generated one if present. */\nexport const PROJECT_COMPOSE_PATH = 'docker-compose.yml';\n\n/**\n * The local edge's config, beside the compose file that mounts it.\n *\n * Relative in the compose volume, so it resolves against `.gkm/` wherever the\n * project lives.\n */\nexport const CADDYFILE_PATH = '.gkm/Caddyfile';\n\n/**\n * Where one stage's routes live, imported by the file above.\n *\n * Per stage because one edge serves every stage, the same way one Postgres\n * holds `orders` and `orders_test`. A single file would mean `gkm test`\n * deleting the routes `gkm dev` is serving.\n */\nexport const caddySitesPath = (stage: string) =>\n\t`.gkm/caddy-sites/${stage}.caddy`;\n\n/**\n * Where the local edge's root certificate is copied to.\n *\n * Exported so a process can be pointed at it without installing anything:\n * `NODE_EXTRA_CA_CERTS` is the whole of the trust story for Node, and it needs\n * no sudo. A browser is the case that still wants the root in the system store,\n * which is a one-time `caddy trust` rather than something a reconcile should do\n * on a developer's behalf.\n */\nexport const LOCAL_CA_PATH = '.gkm/caddy-root.crt';\n\n/** Where Caddy keeps the root of the CA it generated. */\nconst CADDY_ROOT_IN_CONTAINER = '/data/caddy/pki/authorities/local/root.crt';\n\n/**\n * The Docker operations reconcile needs.\n *\n * Small on purpose: everything else about containers Docker already remembers,\n * so there is no state here to keep in step with it.\n */\nexport interface Docker {\n\t/**\n\t * The host port a running container publishes for one of its internal ports,\n\t * or `undefined` if it is not running.\n\t *\n\t * Consulted before the saved assignments so that reconcile converges against\n\t * what is actually running rather than only against its own file — a\n\t * container someone started by hand is still the container serving the app.\n\t */\n\tpublishedPort(\n\t\tcomposePath: string,\n\t\tservice: string,\n\t\tinside: number,\n\t): Promise<number | undefined>;\n\t/**\n\t * Bring the named services up, detached, and wait for their health checks.\n\t *\n\t * `show` lets Compose draw its own progress — the pulls, the containers it\n\t * creates, each one turning healthy — rather than capturing it. A first\n\t * start pulls every image and can take minutes, and minutes of nothing\n\t * read as a hang.\n\t */\n\tup(\n\t\tcomposePath: string,\n\t\tservices: readonly string[],\n\t\toptions?: { show?: boolean },\n\t): Promise<void>;\n\t/** Whether every named service is running and passing its health check. */\n\thealthy(composePath: string, services: readonly string[]): Promise<boolean>;\n\t/**\n\t * Copy a file out of a running container.\n\t *\n\t * One case: the certificate authority the local edge generates. It lives in\n\t * the container's volume, and everything that has to *trust* it — Node, the\n\t * test suite, curl — lives outside.\n\t */\n\tcopyOut(\n\t\tcomposePath: string,\n\t\tservice: string,\n\t\tfrom: string,\n\t\tto: string,\n\t): Promise<void>;\n\t/**\n\t * Ask a running service to re-read its configuration.\n\t *\n\t * The local edge is the case: its routes are a mounted file, and a container\n\t * that is already up will not notice one changing.\n\t */\n\treload(composePath: string, service: string): Promise<void>;\n}\n\nexport interface ReconcileOptions {\n\t/** The project root — where `.gkm/` lives. */\n\troot: string;\n\t/**\n\t * Told each slow step as it starts — starting the containers, creating what\n\t * the constructs declare — and, given one, Docker shows its own progress.\n\t * Absent, reconcile is silent: `gkm test` and the converged case say\n\t * nothing.\n\t */\n\tprogress?: (message: string) => void;\n\t/**\n\t * The project name, which seeds port allocation and names the compose\n\t * project. Two checkouts of the same repo share it deliberately.\n\t */\n\tproject: string;\n\t/** The construct manifest — what the app declared. */\n\tmanifest: ConstructManifest;\n\t/** The stage being reconciled: the project's local stage, or `test`. */\n\tstage: string;\n\t/**\n\t * This machine's LAN address, for a mobile app's `exp://` origins and the\n\t * address its sign-in links are built on. Read from the network interfaces\n\t * when absent; `null` for none.\n\t */\n\tlanAddress?: string | null;\n\t/** Each mobile app's Metro port, by construct id. */\n\tmetroPorts?: Readonly<Record<string, number>>;\n\t/** The project's local stage, whose resources carry no suffix. */\n\tlocalStage?: string;\n\t/** The events backend, until `topic` and `queue` are kinds. */\n\tevents?: EventsBackend;\n\t/** Where a declared cache lives — see {@link CacheBackend}. */\n\tcache?: CacheBackend;\n\t/** Containers no construct implies — the config exceptions. */\n\textraContainers?: readonly string[];\n\t/** Each external API's fake, by id — see `readFakes`. */\n\tfakes?: PlanOptions['fakes'];\n\t/** Whether the local edge fronts surfaces, sites and file servers. */\n\tedge?: boolean;\n\t/**\n\t * The apps, as services beside the containers — see `reconcile/apps.ts`.\n\t * Handed the derived containers so an app can depend on them.\n\t */\n\tapps?: (containers: readonly string[]) => Record<string, ComposeService>;\n\t/** Ports assigned by previous runs, from `.gkm/ports.json`. */\n\tsaved?: PortAssignments;\n\t/** Start containers and wait for health. Off answers \"what would change\". */\n\tstart?: boolean;\n\t/**\n\t * Create the databases, schemas, and buckets the plan names.\n\t *\n\t * Requires the containers to be running, so it is skipped when `start` is\n\t * off. Never seeds, resets, or drops — the line is at data, not side effects.\n\t */\n\tprovision?: boolean;\n\t/** The address mail is sent from locally. */\n\tmailFrom?: string;\n\t/**\n\t * Where each address-owning construct answers, keyed by id.\n\t *\n\t * Surfaces and sites. Assigned by whatever starts them, which is why it\n\t * arrives here rather than being read off a published container port.\n\t */\n\taddresses?: Readonly<Record<string, string>>;\n\tdocker?: Docker;\n\tprobe?: PortProbe;\n\t/** Injected for tests; the defaults talk to the containers just started. */\n\tsql?: (port: number) => SqlClient;\n\tbuckets?: (port: number) => BucketClient;\n\tcarriers?: (port: number) => CarrierClient;\n}\n\nexport interface ReconcileResult {\n\tstage: string;\n\tplan: Plan;\n\tcompose: ComposeFile;\n\t/** Every assigned port, keyed by port key. Persist this. */\n\tports: PortAssignments;\n\t/** Every port the containers publish, labelled — consoles and inbox too. */\n\tservices: readonly ServiceAddress[];\n\t/** The `<NAME>_URL` values this stage resolves. */\n\tenv: Readonly<Record<string, string>>;\n\t/** What the applier created, or found already there. */\n\tprovisioned: Applied[];\n\t/**\n\t * Each external API's fake, by id. An image fake is already running with\n\t * the containers; an app fake is the caller's to serve — `gkm dev` does —\n\t * on `ports[fakeKey(id)]`.\n\t */\n\tfakes: Readonly<Record<string, LocalFake>>;\n\t/** The hash recorded for this state. */\n\thash: string;\n\t/**\n\t * Whether anything was applied. False is the fast path — the recorded hash\n\t * matched and the containers were healthy, so nothing was written or started.\n\t */\n\tchanged: boolean;\n}\n\n/**\n * Converge the local target on what the manifest declares.\n *\n * Cheap when there is nothing to do: the hash is compared before the compose\n * file is written or a container is touched, and a converged project pays one\n * hash and one health check.\n */\nexport async function reconcile(\n\toptions: ReconcileOptions,\n): Promise<ReconcileResult> {\n\tconst {\n\t\troot,\n\t\tproject,\n\t\tmanifest,\n\t\tstage,\n\t\tstart = true,\n\t\tprovision = true,\n\t\tdocker = dockerCli,\n\t\tprobe = isPortFree,\n\t\tsql = pgClient,\n\t\tbuckets = bucketClient,\n\t\tcarriers = carrierClient,\n\t} = options;\n\n\tconst plan = planFor(manifest, stage, provisionOrder(manifest), {\n\t\tlocalStage: options.localStage,\n\t\tevents: options.events,\n\t\tcache: options.cache,\n\t\textraContainers: options.extraContainers,\n\t\t...(options.fakes ? { fakes: options.fakes } : {}),\n\t\t...(options.edge === undefined ? {} : { edge: options.edge }),\n\t});\n\n\tconst composePath = join(root, COMPOSE_PATH);\n\n\t// What is already running wins over what was recorded: a container on a port\n\t// is the fact, and the file is only a memory of one.\n\tconst observed = await observedPorts(\n\t\tdocker,\n\t\tcomposePath,\n\t\tplan.containers,\n\t\tplan.fakes,\n\t);\n\tconst ports = await allocate(\n\t\tproject,\n\t\tportKeys(plan.containers, plan.fakes),\n\t\tkeptPorts(options.saved, observed),\n\t\tprobe,\n\t);\n\n\tconst compose = composeFor(plan, {\n\t\tproject,\n\t\tports,\n\t\t...(options.apps ? { apps: options.apps(plan.containers) } : {}),\n\t});\n\n\t// The edge's config is part of what \"converged\" means: a file server added or\n\t// a bucket renamed changes the routing without changing a container, and a\n\t// hash that ignored it would leave the old routes in place.\n\tconst caddyfile = toCaddyfile(sitesFor(plan, project, options.addresses));\n\t// And so is what provisioning runs. A toolbox upgrade that changes the role\n\t// DDL — a new grant — changes no container and no route, so a hash without\n\t// it reported convergence and the existing database never got the grant.\n\tconst postgres = postgresStatements(plan, project)\n\t\t.map((statement) => statement.create)\n\t\t.join(';\\n');\n\tconst hash = planHash(plan, compose, { caddyfile, postgres });\n\tconst services = serviceAddresses(plan.containers, ports, plan.fakes);\n\t// Only a project with a mobile app reads it, so only one looks for it.\n\tconst lan = plan.resources.some((r) => r.kind === 'mobile-app')\n\t\t? options.lanAddress === undefined\n\t\t\t? lanAddress()\n\t\t\t: (options.lanAddress ?? undefined)\n\t\t: undefined;\n\tconst env = envFor(plan, {\n\t\tports,\n\t\tproject,\n\t\t...(options.mailFrom ? { mailFrom: options.mailFrom } : {}),\n\t\t...(options.addresses ? { addresses: options.addresses } : {}),\n\t\t...(lan ? { lanAddress: lan } : {}),\n\t\t...(options.metroPorts ? { metroPorts: options.metroPorts } : {}),\n\t});\n\t// Pointed at whether or not it exists yet: the copy below fills it in, and\n\t// anything that reads the environment starts after this returns.\n\tif (plan.containers.includes('caddy')) {\n\t\tenv.NODE_EXTRA_CA_CERTS = join(root, LOCAL_CA_PATH);\n\t}\n\n\tconst result: ReconcileResult = {\n\t\tstage,\n\t\tplan,\n\t\tcompose,\n\t\tports,\n\t\tservices,\n\t\tenv,\n\t\tprovisioned: [],\n\t\tfakes: options.fakes ?? {},\n\t\thash,\n\t\tchanged: false,\n\t};\n\n\tconst recorded = await loadState(root, stage);\n\tconst postgresPort = ports[primaryPortKey('postgres')];\n\tconst converged =\n\t\trecorded?.hash === hash &&\n\t\t// The file is gitignored and derived; a fresh checkout with the state\n\t\t// still around must write it rather than trust a hash of nothing.\n\t\texistsSync(composePath) &&\n\t\t(!start || (await docker.healthy(composePath, plan.containers))) &&\n\t\t// Every checkout shares one Postgres, so another one's test teardown can\n\t\t// drop a database this checkout's state still records as created.\n\t\t(!start ||\n\t\t\t!provision ||\n\t\t\tpostgresPort === undefined ||\n\t\t\t(await databasesExist(sql(postgresPort), postgresDatabaseNames(plan))));\n\n\t// The fast path, and the reason reconciling on every start is acceptable.\n\tif (converged) return result;\n\n\tawait write(composePath, toYaml(compose));\n\n\t// Before the containers: Caddy reads these at startup, and mounting a file\n\t// that does not exist yet gets a directory instead.\n\tif (plan.containers.includes('caddy')) {\n\t\tawait write(join(root, CADDYFILE_PATH), caddyfileRoot());\n\t\tawait write(join(root, caddySitesPath(stage)), caddyfile);\n\t\tawait pruneCaddySites(root, [stage, options.localStage, TEST_STAGE]);\n\t}\n\n\tif (start && plan.containers.length > 0) {\n\t\toptions.progress?.(\n\t\t\t`🐳 Starting ${plan.containers.join(', ')} — the first start pulls each image, which can take a few minutes`,\n\t\t);\n\t\tawait docker.up(\n\t\t\tcomposePath,\n\t\t\tplan.containers,\n\t\t\toptions.progress ? { show: true } : {},\n\t\t);\n\t}\n\n\t// A running Caddy does not notice a changed import, so the routes this\n\t// reconcile just wrote are inert until it is told. Failing is not fatal: the\n\t// container may have only this second come up, in which case it already read\n\t// them.\n\tif (start && plan.containers.includes('caddy')) {\n\t\tawait docker.reload(composePath, 'caddy').catch(() => {});\n\t}\n\n\t// After `up`, because Caddy generates its CA on first start — and only then,\n\t// so copying earlier gets nothing.\n\tif (start && plan.containers.includes('caddy')) {\n\t\tawait docker\n\t\t\t.copyOut(\n\t\t\t\tcomposePath,\n\t\t\t\t'caddy',\n\t\t\t\tCADDY_ROOT_IN_CONTAINER,\n\t\t\t\tjoin(root, LOCAL_CA_PATH),\n\t\t\t)\n\t\t\t.catch(() => {\n\t\t\t\t// Not fatal. Without it, `https://` local addresses fail to verify\n\t\t\t\t// and say so clearly; taking the whole reconcile down would be a\n\t\t\t\t// worse trade for a developer who is not using them yet.\n\t\t\t});\n\t}\n\n\t// Only once the containers are up: there is nothing to create inside a\n\t// container that is not running.\n\tif (start && provision) {\n\t\toptions.progress?.(\n\t\t\t'🗄️ Creating what the constructs declare — databases, roles, buckets, topics',\n\t\t);\n\t}\n\tconst provisioned =\n\t\tstart && provision\n\t\t\t? await create(plan, ports, sql, buckets, carriers, project)\n\t\t\t: [];\n\n\tawait saveState(root, { hash, stage });\n\n\treturn { ...result, provisioned, changed: true };\n}\n\n/**\n * Create the databases, schemas, buckets, topics and queues the plan names.\n *\n * Idempotent throughout, so this runs on every non-converged reconcile rather\n * than being something to remember.\n */\nasync function create(\n\tplan: Plan,\n\tports: PortAssignments,\n\tsql: (port: number) => SqlClient,\n\tbuckets: (port: number) => BucketClient,\n\temulatorCarriers: (port: number) => CarrierClient,\n\t/** Seeds the derived role passwords — see `localRolePassword`. */\n\tproject: string,\n): Promise<Applied[]> {\n\tconst applied: Applied[] = [];\n\n\tconst postgresPort = ports[primaryPortKey('postgres')];\n\tconst statements = postgresStatements(plan, project);\n\tif (postgresPort !== undefined && statements.length > 0) {\n\t\tapplied.push(...(await applyPostgres(sql(postgresPort), statements)));\n\t}\n\n\tconst minioPort = ports[primaryPortKey('minio')];\n\tconst names = bucketNames(plan);\n\tconst policies = bucketPolicies(plan);\n\tif (minioPort !== undefined && (names.length > 0 || policies.length > 0)) {\n\t\tconst client = buckets(minioPort);\n\n\t\t// Buckets first: a policy names a bucket, and applying one to a bucket\n\t\t// that does not exist yet fails on the first reconcile of a new project.\n\t\tif (names.length > 0) applied.push(...(await applyBuckets(client, names)));\n\t\tif (policies.length > 0)\n\t\t\tapplied.push(...(await applyPolicies(client, policies)));\n\t}\n\n\tconst emulatorPort = ports[primaryPortKey('localstack')];\n\tconst carriers = carrierNames(plan);\n\tif (\n\t\temulatorPort !== undefined &&\n\t\tcarriers.topics.length + carriers.queues.length > 0\n\t) {\n\t\tapplied.push(\n\t\t\t...(await applyCarriers(emulatorCarriers(emulatorPort), carriers)),\n\t\t);\n\t}\n\n\treturn applied;\n}\n\n/**\n * The host ports of containers already running.\n *\n * Keyed the same way allocation keys them, so the two merge without either\n * knowing about the other.\n */\nasync function observedPorts(\n\tdocker: Docker,\n\tcomposePath: string,\n\tcontainers: readonly string[],\n\tfakes?: Plan['fakes'],\n): Promise<PortAssignments> {\n\tconst observed: Record<string, number> = {};\n\n\tfor (const container of containers) {\n\t\tfor (const port of portsOf(container, fakes)) {\n\t\t\tconst running = await docker.publishedPort(\n\t\t\t\tcomposePath,\n\t\t\t\tcontainer,\n\t\t\t\tport.inside,\n\t\t\t);\n\t\t\tif (running !== undefined) observed[port.key] = running;\n\t\t}\n\t}\n\n\treturn observed;\n}\n\n/** Write a file only when its content would change, so mtimes stay meaningful. */\n/**\n * Remove the routes of stages this project no longer reconciles.\n *\n * Only the local stage and `test` are ever written, so any other file is left\n * over from a stage that was renamed — `development` becoming `dev`. Caddy\n * imports every file, and the leftover declares the same hosts as its\n * replacement: it refuses the whole config as ambiguous, and every URL behind\n * the edge goes with it.\n */\nasync function pruneCaddySites(\n\troot: string,\n\tstages: ReadonlyArray<string | undefined>,\n): Promise<void> {\n\tconst dir = dirname(join(root, caddySitesPath(TEST_STAGE)));\n\tconst keep = new Set(stages.map((stage) => stage && `${stage}.caddy`));\n\n\tfor (const file of await readdir(dir)) {\n\t\tif (file.endsWith('.caddy') && !keep.has(file)) {\n\t\t\tawait rm(join(dir, file));\n\t\t}\n\t}\n}\n\nasync function write(path: string, content: string): Promise<void> {\n\ttry {\n\t\tif ((await readFile(path, 'utf-8')) === content) return;\n\t} catch {\n\t\t// Absent or unreadable — write it.\n\t}\n\n\tawait mkdir(dirname(path), { recursive: true });\n\tawait writeFile(path, content);\n}\n","/**\n * Workspace config → reconcile.\n *\n * Nothing here is read from a `services:` block — there is none. Which\n * containers exist comes from the manifest; which backend a cache or a broker\n * resolves to comes from the deploy target; and an image pin is the project's\n * own `docker-compose.yml`, merged over the generated file.\n *\n * Shared by `gkm setup`, `gkm dev`, and `gkm test` so all three converge on the\n * same state, differing only in the stage they pass.\n */\n\nimport { type ConstructManifest, provisionOrder } from '@geekmidas/manifest';\nimport { loadPortState, savePortState } from '../credentials/index.js';\nimport {\n\tcacheBackendFor,\n\teventsBackendFor,\n\tproviderOf,\n} from '../workspace/backends.js';\nimport { appKey } from '../workspace/derive.js';\nimport { allConstructGlobs } from '../workspace/index.js';\nimport { TEST_STAGE } from '../workspace/stages.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport { appServices } from './apps.js';\nimport { discover } from './discover.js';\nimport { readFakes } from './fakes.js';\nimport { type ReconcileResult, reconcile } from './index.js';\nimport { planFor } from './plan.js';\n\n/**\n * Every constructs glob in the workspace, resolved against its app.\n *\n * Absolute because discovery globs from a single cwd while apps live in\n * different directories.\n */\nexport function constructGlobs(workspace: NormalizedWorkspace): string[] {\n\treturn allConstructGlobs(workspace);\n}\n\n/** The backends a workspace's target implies, as the plan takes them. */\nexport function backendsOf(workspace: NormalizedWorkspace) {\n\tconst on = providerOf(workspace);\n\treturn { events: eventsBackendFor(on), cache: cacheBackendFor(on) };\n}\n\n/**\n * The containers a workspace's declarations imply, without reconciling.\n *\n * The same derivation `reconcileWorkspace` makes, available to callers that\n * need to know *what would run* without starting anything or writing ports —\n * `gkm setup` deciding which credentials to generate, `gkm docker` writing a\n * compose file. Those used to each read a boolean per service out of config,\n * which is how a container could exist because config asked for one rather than\n * because something declared it.\n */\nexport async function derivedContainers(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n\tmanifest?: ConstructManifest,\n): Promise<readonly string[]> {\n\tconst found =\n\t\tmanifest ??\n\t\t(await discover({\n\t\t\tpatterns: constructGlobs(workspace),\n\t\t\tcwd: workspace.root,\n\t\t}));\n\n\t// No fakes: they run only for `gkm test` and `gkm dev --fake`, and the\n\t// commands asking this — `setup`, `secrets:*`, `docker` — are neither.\n\treturn planFor(found, stage, provisionOrder(found), {\n\t\tlocalStage: workspace.stages.local,\n\t\t...backendsOf(workspace),\n\t}).containers;\n}\n\nexport interface WorkspaceReconcileOptions {\n\tstage: string;\n\t/** Start containers and wait for health. */\n\tstart?: boolean;\n\t/** A manifest already in hand — the dev watcher has one; setup does not. */\n\tmanifest?: ConstructManifest;\n\t/**\n\t * Point every external API at its fake (`test/fakes/<id>.ts`) instead of\n\t * the provider — `gkm dev --fake`. Always so for the test stage; otherwise\n\t * an external API is the real one, at its URL for this stage.\n\t */\n\tfake?: boolean;\n\t/** Told each slow step as it starts — see `ReconcileOptions.progress`. */\n\tprogress?: (message: string) => void;\n}\n\n/**\n * Set by `gkm dev --fake` for the apps it starts. Each app's own `gkm dev`\n * reconciles too, and must answer the way the workspace did.\n */\nexport const FAKE_ENV = 'GKM_FAKE';\n\n/**\n * Whether external APIs are faked: always for `gkm test`, and for `gkm dev`\n * only when asked — by `--fake`, or by the workspace that started this app\n * with it. Never for anything acting on a deployed stage.\n */\nexport function fakesApply(\n\tstage: string,\n\tfake?: boolean,\n\tenv: NodeJS.ProcessEnv = process.env,\n): boolean {\n\treturn fake === true || stage === TEST_STAGE || env[FAKE_ENV] === '1';\n}\n\n/**\n * Reconcile a workspace for one stage.\n *\n * Ports are loaded and saved around the call rather than inside it, so the loop\n * stays a pure-ish function of its inputs and the store keeps its single owner.\n */\nexport async function reconcileWorkspace(\n\tworkspace: NormalizedWorkspace,\n\toptions: WorkspaceReconcileOptions,\n): Promise<ReconcileResult & { manifest: ConstructManifest }> {\n\t// Discovered here even when a manifest was handed in: the apps' services\n\t// are given the keys their runnables reach, and only discovery sees those.\n\tconst runnables: Record<string, string[]> = {};\n\tconst discovered = await discover({\n\t\tpatterns: constructGlobs(workspace),\n\t\tcwd: workspace.root,\n\t\trunnables,\n\t});\n\tconst manifest = options.manifest ?? discovered;\n\tconst fakes = fakesApply(options.stage, options.fake)\n\t\t? await readFakes(workspace.root, manifest)\n\t\t: {};\n\n\tconst result = await reconcile({\n\t\troot: workspace.root,\n\t\tproject: workspace.name,\n\t\tmanifest,\n\t\tstage: options.stage,\n\t\tlocalStage: workspace.stages.local,\n\t\t...backendsOf(workspace),\n\t\tsaved: await loadPortState(workspace.root),\n\t\taddresses: surfaceAddresses(workspace, manifest),\n\t\tmetroPorts: metroPorts(workspace, manifest),\n\t\tfakes,\n\t\tapps: (containers) =>\n\t\t\tappServices(workspace, manifest, containers, runnables, fakes),\n\t\t...(options.start === undefined ? {} : { start: options.start }),\n\t\t...(options.progress ? { progress: options.progress } : {}),\n\t});\n\n\tawait savePortState(workspace.root, { ...result.ports });\n\n\t// With the manifest, so `gkm dev` can say what the workspace declares\n\t// without discovering it a second time.\n\treturn { ...result, manifest };\n}\n\n/**\n * Where each mobile app's Metro runs: its app's port, which `gkm exec` hands\n * Expo as `RCT_METRO_PORT`. Kept apart from the surfaces' addresses — a phone\n * reaches Metro by `exp://`, not through the edge.\n */\nexport function metroPorts(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n): Record<string, number> {\n\tconst ports: Record<string, number> = {};\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (declaration.kind !== 'mobile-app') continue;\n\t\tconst port = workspace.apps[appKey(id)]?.port;\n\t\tif (port) ports[id] = port;\n\t}\n\treturn ports;\n}\n\n/**\n * Where each declared surface and site answers locally.\n *\n * The addresses come from the ports the workspace already assigns; which\n * construct sits at which one comes from the manifest. That split is the point:\n * this used to answer \"who may call whom\" by listing every app the workspace\n * runs, which is a different question that happened to give the same answer in\n * a single-repo workspace and no answer at all deployed. Now it answers only\n * \"where does this construct answer\", and the graph answers the rest.\n *\n * A site is matched to its app by `path`, because that is the one thing a site\n * declaration and a workspace app both name. Surfaces are matched to the\n * backend app: every `rest-api` in a process answers on that process's port, so\n * an app serving both its own API and an auth server publishes one address\n * twice — which is exactly what it does at runtime.\n */\nexport function surfaceAddresses(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n\t/**\n\t * How an app is addressed: on the host by default, or — for the compose\n\t * apps — by service name on the compose network.\n\t */\n\tat: (app: string, port: number) => string = (_app, port) =>\n\t\tlocalAddress(port),\n): Record<string, string> {\n\tconst addresses: Record<string, string> = {};\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\t// Every site and every surface is its own app, keyed by its id the way\n\t\t// the derivation keyed it.\n\t\tconst host = appKey(id);\n\t\tconst port = workspace.apps[host]?.port;\n\n\t\tif (port) addresses[id] = at(host, port);\n\t}\n\n\treturn addresses;\n}\n\n/** Where a local process answers, given the port the workspace gave it. */\nfunction localAddress(port: number): string {\n\treturn `http://localhost:${port}`;\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\nimport { Cron } from '@geekmidas/constructs/crons';\nimport type { BuildContext } from '../build/types';\nimport type { CronInfo } from '../types';\nimport { runtimeFor } from './EndpointGenerator.js';\nimport {\n\tConstructGenerator,\n\ttype GeneratedConstruct,\n\ttype GeneratorOptions,\n} from './Generator';\n\nexport class CronGenerator extends ConstructGenerator<\n\tCron<any, any, any, any, any, any>,\n\tCronInfo[]\n> {\n\tasync build(\n\t\tcontext: BuildContext,\n\t\tconstructs: GeneratedConstruct<Cron<any, any, any, any, any, any>>[],\n\t\toutputDir: string,\n\t\toptions?: GeneratorOptions,\n\t): Promise<CronInfo[]> {\n\t\tconst target = options?.target ?? 'aws';\n\t\tconst logger = console;\n\t\tconst cronInfos: CronInfo[] = [];\n\n\t\tif (target === 'server') {\n\t\t\t// A server runs its crons itself, the way it already runs its\n\t\t\t// subscribers: one file, wired into the generated entry. Written even\n\t\t\t// when there are none, so the entry can import it unconditionally.\n\t\t\tawait this.generateServerCronsFile(outputDir, constructs);\n\n\t\t\tlogger.log(`Generated server crons file with ${constructs.length} crons`);\n\n\t\t\t// A server cron has no individual handler to point infra at — the\n\t\t\t// process schedules it.\n\t\t\treturn cronInfos;\n\t\t}\n\n\t\tif (constructs.length === 0) {\n\t\t\treturn cronInfos;\n\t\t}\n\n\t\t// Create crons subdirectory\n\t\tconst cronsDir = join(outputDir, 'crons');\n\t\tawait mkdir(cronsDir, { recursive: true });\n\n\t\t// Generate cron handlers\n\t\tfor (const { key, construct, path } of constructs) {\n\t\t\tconst handlerFile = await this.generateCronHandler(\n\t\t\t\tcronsDir,\n\t\t\t\tpath.relative,\n\t\t\t\tkey,\n\t\t\t\tcontext,\n\t\t\t\tconstruct.owner,\n\t\t\t);\n\n\t\t\tcronInfos.push({\n\t\t\t\tname: key,\n\t\t\t\thandler: relative(options?.root ?? process.cwd(), handlerFile).replace(\n\t\t\t\t\t/\\.ts$/,\n\t\t\t\t\t'.handler',\n\t\t\t\t),\n\t\t\t\tschedule: construct.schedule || 'rate(1 hour)',\n\t\t\t\ttimeout: construct.timeout,\n\t\t\t\tmemorySize: construct.memorySize,\n\t\t\t\tenvironment: await construct.getEnvironment({\n\t\t\t\t\tmarkOptional: context.markOptional,\n\t\t\t\t}),\n\t\t\t\tdependencies: construct.constructs,\n\t\t\t});\n\n\t\t\tlogger.log(`Generated cron handler: ${key}`);\n\t\t}\n\n\t\treturn cronInfos;\n\t}\n\n\tisConstruct(value: any): value is Cron<any, any, any, any, any, any> {\n\t\treturn Cron.isCron(value);\n\t}\n\n\t/**\n\t * The crons file a server entry imports.\n\t *\n\t * Shaped after `generateServerSubscribersFile`: one module, every cron, a\n\t * `setupCrons` the generated entry calls beside `setupSubscribers`.\n\t *\n\t * It picks its scheduler from what the process can reach. With a Postgres it\n\t * uses pg-boss, whose schedules live in the database — so a deployment\n\t * running four replicas fires each job once, which is the thing a timer in\n\t * every process gets wrong and does not report. With no database it falls\n\t * back to timers and says plainly that it is doing so.\n\t */\n\tprivate async generateServerCronsFile(\n\t\toutputDir: string,\n\t\tcrons: GeneratedConstruct<Cron<any, any, any, any, any, any>>[],\n\t): Promise<string> {\n\t\tawait mkdir(outputDir, { recursive: true });\n\n\t\tconst cronsPath = join(outputDir, 'crons.ts');\n\n\t\t// Nothing to schedule, so nothing to import — the entry imports this file\n\t\t// whatever the app declares, and a project with no events must not have\n\t\t// to resolve `@geekmidas/events`.\n\t\tif (crons.length === 0) {\n\t\t\tawait writeFile(\n\t\t\t\tcronsPath,\n\t\t\t\t`/**\n * Generated cron setup — this app declares none.\n */\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\n\nexport async function setupCrons(\n _envParser: EnvironmentParser<any>,\n _logger: Logger,\n): Promise<void> {}\n\nexport function cronsStatus(): { failed: string[]; connections: unknown[] } {\n return { failed: [], connections: [] };\n}\n\nexport async function stopCrons(): Promise<void> {}\n`,\n\t\t\t);\n\t\t\treturn cronsPath;\n\t\t}\n\n\t\tconst importsByFile = new Map<string, string[]>();\n\t\tfor (const { path, key } of crons) {\n\t\t\tconst relativePath = relative(dirname(cronsPath), path.relative);\n\t\t\tconst importPath = relativePath.replace(/\\.ts$/, '.js');\n\n\t\t\tif (!importsByFile.has(importPath)) importsByFile.set(importPath, []);\n\t\t\timportsByFile.get(importPath)?.push(key);\n\t\t}\n\n\t\tconst imports = Array.from(importsByFile.entries())\n\t\t\t.map(\n\t\t\t\t([importPath, exports]) =>\n\t\t\t\t\t`import { ${exports.join(', ')} } from '${importPath}';`,\n\t\t\t)\n\t\t\t.join('\\n');\n\n\t\tconst entries = crons\n\t\t\t.map(({ key }) => ` { name: '${key}', cron: ${key} }`)\n\t\t\t.join(',\\n');\n\n\t\tconst content = `/**\n * Generated cron setup.\n *\n * Schedules every cron this app declares, in the process that serves it.\n *\n * The schedule lives in Postgres, through pg-boss, in the database the worker\n * named with \\`.database(db)\\`. That is what makes running several replicas\n * safe: each job fires once, where a timer in every process fires it once per\n * replica and reports nothing.\n *\n * No connection string appears here. The construct that owns the database is\n * the only thing that knows its key, and it is resolved through service\n * discovery like any other dependency.\n */\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { runCron, scheduleInProcess, toCronExpression } from '@geekmidas/constructs/crons';\nimport { ServiceDiscovery } from '@geekmidas/services';\n${imports}\n\nconst crons = [\n${entries}\n];\n\ntype Stoppable = { stop: () => Promise<void> | void };\nconst running: Stoppable[] = [];\n// What a worker's health check reads: the crons that cannot be scheduled, and\n// the broker connection the schedule is kept through.\nconst failed: string[] = [];\nconst connections: import('@geekmidas/events').EventConnection[] = [];\n\nexport async function setupCrons(\n envParser: EnvironmentParser<any>,\n logger: Logger,\n): Promise<void> {\n if (crons.length === 0) {\n return;\n }\n\n const serviceDiscovery = ServiceDiscovery.getInstance(envParser);\n\n // Resolved once per cron rather than per firing: registering services on\n // every tick would reconnect a database every minute.\n const prepared = [];\n for (const { name, cron } of crons) {\n try {\n const schedule = toCronExpression(cron.schedule);\n const services = cron.services.length > 0\n ? await serviceDiscovery.register(cron.services)\n : {};\n\n prepared.push({ name, cron, schedule, services });\n } catch (error) {\n // An unrepresentable schedule is a build-time mistake, and failing the\n // process for it would take the HTTP server down with it.\n logger.error({ err: error, cron: name }, 'Cron has no runnable schedule, skipping');\n failed.push(name);\n }\n }\n\n const run = async (entry: (typeof prepared)[number]) => {\n try {\n // The cron's own function, the way its Lambda would run it — services,\n // the worker's database as \\`db\\`, its events. It used to call a\n // \\`handler\\` a Cron does not have, so every firing logged a failure.\n await runCron(entry.cron, serviceDiscovery);\n } catch (error) {\n logger.error({ err: error, cron: entry.name }, 'Cron failed');\n }\n };\n\n // The app's one pg-boss: the events broker's, as the broker's role, in the\n // broker's schema. A server fires its own crons, and a process scheduling in\n // memory fires each job once per replica — so the schedule lives in\n // Postgres. It used to be a second pg-boss on the worker's database, as the\n // runtime role, which can neither create a schema nor use the broker's.\n const { url, devPid } = envParser\n .create((get) => ({\n url: get('EVENT_PUBLISHER_CONNECTION_STRING').string().optional(),\n // Set by \\`gkm dev\\` on the server it starts, and by nothing deployed.\n devPid: get('GKM_DEV_PID').string().optional(),\n }))\n .parse();\n\n if (!url?.startsWith('pgboss://')) {\n // \\`gkm dev\\` on a target whose crons are infrastructure once deployed —\n // EventBridge rules on AWS — has no broker to keep a schedule in. It is one\n // process, so a timer fires each job once, which is all pg-boss is for\n // here. Only there: a deployed server firing by timer fires once per\n // replica, so outside dev this stays the error below.\n if (devPid) {\n for (const entry of prepared) {\n running.push(scheduleInProcess(entry.schedule, () => run(entry)));\n logger.info({ cron: entry.name, schedule: entry.schedule }, 'Cron scheduled in this process');\n }\n return;\n }\n\n logger.error(\n { crons: prepared.map(({ name }) => name) },\n 'These crons have nowhere to keep their schedule. On a server they are ' +\n 'scheduled through the pg-boss events broker ' +\n '(EVENT_PUBLISHER_CONNECTION_STRING), and ' +\n (url ? 'the broker configured is not pg-boss.' : 'none is configured.'),\n );\n failed.push(...prepared.map(({ name }) => name));\n return;\n }\n\n // Through the registered pg-boss driver, which the entry registers for a\n // server whose broker is pg-boss. Naming \\`@geekmidas/events/pgboss\\` here\n // made every bundle with a cron resolve pg-boss, whatever its broker.\n const { EventConnectionFactory } = await import('@geekmidas/events');\n const connection = (await EventConnectionFactory.fromConnectionString(\n url,\n )) as import('@geekmidas/events').EventConnection & { instance?: any };\n const boss = connection.instance!;\n\n // What this app declares now. Anything else under the prefix belonged to a\n // cron since renamed or deleted, and would keep firing at a handler that has\n // gone.\n //\n // The prefix is \\`cron.\\` rather than \\`cron:\\`: pg-boss accepts\n // alphanumerics, underscores, hyphens, periods and slashes in a queue name,\n // and rejects a colon outright.\n const declared = new Set(prepared.map(({ name }) => \\`cron.\\${name}\\`));\n for (const existing of await boss.getSchedules()) {\n if (existing.name.startsWith('cron.') && !declared.has(existing.name)) {\n await boss.unschedule(existing.name);\n logger.info({ cron: existing.name }, 'Removed a schedule nothing declares');\n }\n }\n\n for (const entry of prepared) {\n const queue = \\`cron.\\${entry.name}\\`;\n await boss.createQueue(queue);\n // UTC, so this agrees with the schedule a deploy target would have made.\n await boss.schedule(queue, entry.schedule, undefined, { tz: 'UTC' });\n await boss.work(queue, () => run(entry));\n\n logger.info({ cron: entry.name, schedule: entry.schedule }, 'Cron scheduled');\n }\n\n running.push({ stop: () => connection.close() });\n connections.push(connection);\n logger.info({ count: prepared.length }, 'Crons scheduled');\n}\n\n/** The crons that cannot be scheduled, and the connection that schedules the rest. */\nexport function cronsStatus(): {\n failed: string[];\n connections: import('@geekmidas/events').EventConnection[];\n} {\n return { failed: [...failed], connections: [...connections] };\n}\n\n/**\n * Stop scheduling, let a firing in flight finish, and close the broker\n * connection — pg-boss's graceful stop does all three. The caller bounds it.\n */\nexport async function stopCrons(): Promise<void> {\n const open = [...running];\n running.length = 0;\n connections.length = 0;\n await Promise.all(open.map((r) => r.stop()));\n}\n`;\n\n\t\tawait writeFile(cronsPath, content);\n\n\t\treturn cronsPath;\n\t}\n\n\tprivate async generateCronHandler(\n\t\toutputDir: string,\n\t\tsourceFile: string,\n\t\texportName: string,\n\t\tcontext: BuildContext,\n\t\t/** The construct that owns this — its worker, or its surface. */\n\t\towner: string | undefined,\n\t): Promise<string> {\n\t\tconst handlerFileName = `${exportName}.ts`;\n\t\tconst handlerPath = join(outputDir, handlerFileName);\n\n\t\tconst relativePath = relative(dirname(handlerPath), sourceFile);\n\t\tconst importPath = relativePath.replace(/\\.ts$/, '.js');\n\n\t\t// Imports the construct that owns this, and binds `envParser` and\n\t\t// `logger` off it — the objects it was declared with, rather than a\n\t\t// module path printed in from config.\n\t\tconst runtime = runtimeFor(context, dirname(handlerPath), owner);\n\n\t\tconst content = `import { AWSScheduledFunction } from '@geekmidas/constructs/aws';\nimport { ${exportName} } from '${importPath}';\n${runtime.imports}\n${context.storageDrivers?.imports ?? ''}\n${runtime.bindings}\n${context.storageDrivers?.setup ? `\\n// The handler registers the drivers its target needs.\\n${context.storageDrivers.setup}\\n` : ''}\nconst adapter = new AWSScheduledFunction(envParser, ${exportName});\n\nexport const handler = adapter.handler;\n`;\n\n\t\tawait writeFile(handlerPath, content);\n\t\treturn handlerPath;\n\t}\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\nimport { Queue } from '@geekmidas/constructs/queue';\nimport { provideKey } from '@geekmidas/manifest';\nimport type { BuildContext } from '../build/types';\nimport type { QueueInfo } from '../types';\nimport { runtimeFor } from './EndpointGenerator.js';\nimport {\n\tConstructGenerator,\n\ttype GeneratedConstruct,\n\ttype GeneratorOptions,\n} from './Generator';\n\n/**\n * Generates the runtime for queue consumers (`worker.queue(…)`).\n *\n * - **server** (`gkm dev`): a single `queues.ts` exposing `setupQueues()` that\n * polls each queue in-process, alongside the Hono server, through the queue's\n * own connection string — pg-boss by default, SQS on the AWS emulator.\n * - **aws-lambda**: one handler file per queue wrapping `AWSLambdaQueue`, backed\n * by an SQS event-source mapping.\n */\nexport class QueueGenerator extends ConstructGenerator<\n\tQueue<any, any, any, any, any, any>,\n\tQueueInfo[]\n> {\n\tisConstruct(value: any): value is Queue<any, any, any, any, any, any> {\n\t\treturn Queue.isQueue(value);\n\t}\n\n\tasync build(\n\t\tcontext: BuildContext,\n\t\tconstructs: GeneratedConstruct<Queue<any, any, any, any, any, any>>[],\n\t\toutputDir: string,\n\t\toptions?: GeneratorOptions,\n\t): Promise<QueueInfo[]> {\n\t\tconst target = options?.target ?? 'aws';\n\t\tconst logger = console;\n\t\tconst queueInfos: QueueInfo[] = [];\n\n\t\tif (target === 'server') {\n\t\t\t// Generate queues.ts for in-process polling (even if empty, so the\n\t\t\t// server entry can always import setupQueues).\n\t\t\tawait this.generateServerQueuesFile(outputDir, constructs);\n\t\t\tlogger.log(\n\t\t\t\t`Generated server queues file with ${constructs.length} queues (polling mode)`,\n\t\t\t);\n\t\t\treturn queueInfos;\n\t\t}\n\n\t\tif (constructs.length === 0) {\n\t\t\treturn queueInfos;\n\t\t}\n\n\t\tconst queuesDir = join(outputDir, 'queues');\n\t\tawait mkdir(queuesDir, { recursive: true });\n\n\t\tfor (const { key, construct, path } of constructs) {\n\t\t\tconst handlerFile = await this.generateQueueHandler(\n\t\t\t\tqueuesDir,\n\t\t\t\tpath.relative,\n\t\t\t\tkey,\n\t\t\t\tcontext,\n\t\t\t\tconstruct.owner,\n\t\t\t);\n\n\t\t\tqueueInfos.push({\n\t\t\t\tname: construct.name,\n\t\t\t\thandler: relative(options?.root ?? process.cwd(), handlerFile).replace(\n\t\t\t\t\t/\\.ts$/,\n\t\t\t\t\t'.handler',\n\t\t\t\t),\n\t\t\t\tbatchSize: construct.batchSize,\n\t\t\t\tfifo: construct.fifo,\n\t\t\t\ttimeout: construct.timeout,\n\t\t\t\tenvironment: await construct.getEnvironment({\n\t\t\t\t\tmarkOptional: context.markOptional,\n\t\t\t\t}),\n\t\t\t\tdependencies: construct.constructs,\n\t\t\t});\n\n\t\t\tlogger.log(`Generated queue handler: ${key}`);\n\t\t}\n\n\t\treturn queueInfos;\n\t}\n\n\tprivate async generateQueueHandler(\n\t\toutputDir: string,\n\t\tsourceFile: string,\n\t\texportName: string,\n\t\tcontext: BuildContext,\n\t\t/** The construct that owns this — its worker, or its surface. */\n\t\towner: string | undefined,\n\t): Promise<string> {\n\t\tconst handlerPath = join(outputDir, `${exportName}.ts`);\n\t\tconst importPath = relative(dirname(handlerPath), sourceFile).replace(\n\t\t\t/\\.ts$/,\n\t\t\t'.js',\n\t\t);\n\t\t// Imports the construct that owns this, and binds `envParser` and\n\t\t// `logger` off it — the objects it was declared with, rather than a\n\t\t// module path printed in from config.\n\t\tconst runtime = runtimeFor(context, dirname(handlerPath), owner);\n\n\t\tconst content = `import { AWSLambdaQueue } from '@geekmidas/constructs/aws';\nimport { ${exportName} } from '${importPath}';\n${runtime.imports}\n${context.storageDrivers?.imports ?? ''}\n${runtime.bindings}\n${context.storageDrivers?.setup ? `\\n// The handler registers the drivers its target needs.\\n${context.storageDrivers.setup}\\n` : ''}\nconst adapter = new AWSLambdaQueue(envParser, ${exportName});\n\nexport const handler = adapter.handler;\n`;\n\n\t\tawait writeFile(handlerPath, content);\n\t\treturn handlerPath;\n\t}\n\n\tprivate async generateServerQueuesFile(\n\t\toutputDir: string,\n\t\tqueues: GeneratedConstruct<Queue<any, any, any, any, any, any>>[],\n\t): Promise<string> {\n\t\tawait mkdir(outputDir, { recursive: true });\n\n\t\tconst queuesPath = join(outputDir, 'queues.ts');\n\n\t\t// Nothing to poll, so nothing to import. Every app gets this file, and\n\t\t// `@geekmidas/events` is only installed by one that declared a Topic or\n\t\t// a Queue — importing it here crashed every dev server that had not.\n\t\tif (queues.length === 0) {\n\t\t\tawait writeFile(\n\t\t\t\tqueuesPath,\n\t\t\t\t`/**\n * Generated queues setup — this app declares none.\n */\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\n\nexport async function setupQueues(\n _envParser: EnvironmentParser<any>,\n _logger: Logger,\n): Promise<void> {}\n\nexport function queuesStatus(): { failed: string[]; connections: unknown[] } {\n return { failed: [], connections: [] };\n}\n\nexport async function stopQueues(): Promise<void> {}\n`,\n\t\t\t);\n\t\t\treturn queuesPath;\n\t\t}\n\n\t\t// Group imports by file\n\t\tconst importsByFile = new Map<string, string[]>();\n\t\tfor (const { path, key } of queues) {\n\t\t\tconst importPath = relative(dirname(queuesPath), path.relative).replace(\n\t\t\t\t/\\.ts$/,\n\t\t\t\t'.js',\n\t\t\t);\n\t\t\tif (!importsByFile.has(importPath)) {\n\t\t\t\timportsByFile.set(importPath, []);\n\t\t\t}\n\t\t\timportsByFile.get(importPath)?.push(key);\n\t\t}\n\n\t\tconst imports = Array.from(importsByFile.entries())\n\t\t\t.map(\n\t\t\t\t([importPath, exports]) =>\n\t\t\t\t\t`import { ${exports.join(', ')} } from '${importPath}';`,\n\t\t\t)\n\t\t\t.join('\\n');\n\n\t\t// Each queue's consumer reaches its own queue, by the queue's own key.\n\t\tconst entries = queues\n\t\t\t.map(\n\t\t\t\t({ key, construct }) =>\n\t\t\t\t\t` { queue: ${key}, connectionKey: '${provideKey(\n\t\t\t\t\t\tconstruct.id,\n\t\t\t\t\t\t'publisherConnectionString',\n\t\t\t\t\t)}' },`,\n\t\t\t)\n\t\t\t.join('\\n');\n\n\t\tconst content = `/**\n * Generated queue consumers setup.\n *\n * Each queue's one consumer polls its own queue, through the queue's own\n * connection string — the one its producers publish on. SQS cannot push, so\n * this is a poller on every transport: pg-boss locally by default, the SQS\n * queue on the AWS emulator.\n *\n * Deployed on Lambda, a queue's consumer is an SQS event source instead.\n */\nimport type { EnvironmentParser } from '@geekmidas/envkit';\nimport type { Logger } from '@geekmidas/logger';\nimport { EventConnectionFactory, Subscriber } from '@geekmidas/events';\nimport type { EventConnection } from '@geekmidas/events';\nimport { ServiceDiscovery } from '@geekmidas/services';\n${imports}\n\nconst queues = [\n${entries}\n];\n\n// What was opened, and what could not be: a worker's health check reads both,\n// and its shutdown closes what was opened.\nconst connections = new Map<string, EventConnection>();\nconst failed: string[] = [];\n\nexport async function setupQueues(\n envParser: EnvironmentParser<any>,\n logger: Logger,\n): Promise<void> {\n const serviceDiscovery = ServiceDiscovery.getInstance(envParser);\n\n for (const { queue, connectionKey } of queues) {\n const connectionString = process.env[connectionKey];\n if (!connectionString) {\n logger.error({ queue: queue.name, connectionKey }, 'No connection string for this queue');\n failed.push(queue.name);\n continue;\n }\n\n try {\n let connection = connections.get(connectionString);\n if (!connection) {\n connection = await EventConnectionFactory.fromConnectionString(connectionString);\n connections.set(connectionString, connection);\n }\n const eventSubscriber = await Subscriber.fromConnection(connection);\n const services = queue.services.length > 0\n ? await serviceDiscovery.register(queue.services)\n : {};\n // The queue's database — the worker's, or its own — handed over as \\`db\\`.\n const db = queue.databaseService\n ? (await serviceDiscovery.register([queue.databaseService]))[\n queue.databaseService.serviceName\n ]\n : undefined;\n\n // A queue's messages carry one type — its own name.\n await eventSubscriber.subscribe([queue.name], async (message) => {\n try {\n const validation = await queue.messageSchema['~standard'].validate(message.payload);\n if (validation.issues) {\n logger.error({ issues: validation.issues, queue: queue.name }, 'Queue message failed validation');\n return;\n }\n\n await queue.handler({\n messages: [validation.value],\n services: services,\n logger: queue.logger,\n db,\n } as any);\n } catch (error) {\n logger.error({ err: error, queue: queue.name }, 'Failed to process queue message');\n // Rethrown so the transport keeps the message for a retry.\n throw error;\n }\n });\n logger.info({ queue: queue.name }, 'Queue consumer started polling');\n } catch (error) {\n logger.error({ err: error, queue: queue.name }, 'Failed to set up queue consumer');\n failed.push(queue.name);\n }\n }\n}\n\n/** The consumers that could not start, and the connections that did. */\nexport function queuesStatus(): { failed: string[]; connections: EventConnection[] } {\n return { failed: [...failed], connections: [...connections.values()] };\n}\n\n/**\n * Stop polling, let the messages in flight finish, and close each broker\n * connection — pg-boss's graceful stop does all three. The caller bounds it.\n */\nexport async function stopQueues(): Promise<void> {\n const open = [...connections.values()];\n connections.clear();\n await Promise.all(open.map((connection) => connection.close()));\n}\n`;\n\n\t\tawait writeFile(queuesPath, content);\n\t\treturn queuesPath;\n\t}\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { Cron } from '@geekmidas/constructs/crons';\nimport type { Queue } from '@geekmidas/constructs/queue';\nimport type { Subscriber } from '@geekmidas/constructs/subscribers';\nimport type { BuildContext } from '../build/types';\nimport { appKey } from '../workspace/derive.js';\nimport { CronGenerator } from './CronGenerator';\nimport { runtimeFor } from './EndpointGenerator.js';\nimport type { GeneratedConstruct } from './Generator';\nimport { QueueGenerator } from './QueueGenerator';\nimport { SubscriberGenerator } from './SubscriberGenerator';\nimport { generateTelemetryModule } from './telemetry';\n\n/**\n * A worker whose topic subscribers would have to be pushed to over HTTP.\n *\n * On SNS a subscriber is an HTTP endpoint SNS delivers to, and a worker's\n * process serves nothing but its health check. A server target's broker is\n * pg-boss, where each subscriber polls a queue of its own, so this is only\n * reached by building server images for a project that deploys to AWS — where\n * a subscriber is a Lambda instead.\n */\nexport class WorkerSubscribersNeedPush extends Error {\n\tconstructor(\n\t\treadonly worker: string,\n\t\treadonly subscribers: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`The worker '${worker}' runs topic subscribers (${subscribers.join(', ')}), ` +\n\t\t\t\t`and this build's broker is SNS, which pushes each event to its ` +\n\t\t\t\t`subscriber over HTTP. A worker image serves only its health check. ` +\n\t\t\t\t`Deploy to a server target, whose broker is pg-boss and whose ` +\n\t\t\t\t`subscribers poll, or to AWS, where each subscriber is a Lambda.`,\n\t\t);\n\t\tthis.name = 'WorkerSubscribersNeedPush';\n\t}\n}\n\n/** Where a worker's generated entry lives, under an app's `.gkm/server`. */\nexport function workerEntryDir(serverDir: string, workerId: string): string {\n\treturn join(serverDir, 'workers', appKey(workerId));\n}\n\n/** The file a worker's bundle is written to, beside the server's. */\nexport function workerBundleName(workerId: string): string {\n\treturn `worker-${appKey(workerId)}.mjs`;\n}\n\nexport interface WorkerEntryInput {\n\tcontext: BuildContext;\n\t/** The worker's construct id, `Jobs`. */\n\tworkerId: string;\n\t/** The worker's own directory — see `workerEntryDir`. */\n\toutputDir: string;\n\t/** Everything built from this worker, and nothing built from another. */\n\tcrons: GeneratedConstruct<Cron<any, any, any, any, any, any>>[];\n\tqueues: GeneratedConstruct<Queue<any, any, any, any, any, any>>[];\n\tsubscribers: GeneratedConstruct<\n\t\tSubscriber<any, any, any, any, any, any, any>\n\t>[];\n}\n\n/**\n * A Worker's production entry: the process that runs its crons, its queue\n * consumers and its topic subscribers, and answers nothing but a health check.\n *\n * It is the server's runtime without the server. The `crons.ts`, `queues.ts`\n * and `subscribers.ts` it starts are the files `gkm dev` starts — written by\n * the same generators, given only this worker's constructs — so a consumer\n * runs the same code in development and in the image. What it adds is the\n * process around them: drivers, a health check that says when a consumer or\n * the broker is down, and a shutdown that drains.\n */\nexport class WorkerGenerator {\n\tconstructor(\n\t\tprivate readonly generators = {\n\t\t\tcron: new CronGenerator(),\n\t\t\tqueue: new QueueGenerator(),\n\t\t\tsubscriber: new SubscriberGenerator(),\n\t\t},\n\t) {}\n\n\t/** Write the worker's files; returns its entry, `worker.ts`. */\n\tasync build(input: WorkerEntryInput): Promise<string> {\n\t\tconst { context, workerId, outputDir } = input;\n\n\t\tif (\n\t\t\tinput.subscribers.length > 0 &&\n\t\t\tcontext.eventsBackends?.includes('sns')\n\t\t) {\n\t\t\tthrow new WorkerSubscribersNeedPush(\n\t\t\t\tworkerId,\n\t\t\t\tinput.subscribers.map(({ key }) => key),\n\t\t\t);\n\t\t}\n\n\t\tawait mkdir(outputDir, { recursive: true });\n\t\tconst options = { target: 'server' as const };\n\t\tawait Promise.all([\n\t\t\tthis.generators.cron.build(context, input.crons, outputDir, options),\n\t\t\tthis.generators.queue.build(context, input.queues, outputDir, options),\n\t\t\tthis.generators.subscriber.build(\n\t\t\t\tcontext,\n\t\t\t\tinput.subscribers,\n\t\t\t\toutputDir,\n\t\t\t\toptions,\n\t\t\t),\n\t\t]);\n\n\t\tawait writeFile(\n\t\t\tjoin(outputDir, 'app.ts'),\n\t\t\tworkerApp(context, workerId, outputDir),\n\t\t);\n\t\tawait writeFile(\n\t\t\tjoin(outputDir, 'telemetry.ts'),\n\t\t\tgenerateTelemetryModule(\n\t\t\t\tcontext.telemetry\n\t\t\t\t\t? { ...context.telemetry, serviceName: workerId }\n\t\t\t\t\t: undefined,\n\t\t\t),\n\t\t);\n\n\t\tconst entry = join(outputDir, 'worker.ts');\n\t\tawait writeFile(\n\t\t\tentry,\n\t\t\tworkerEntry(context.production?.healthCheck ?? '/health'),\n\t\t);\n\t\treturn entry;\n\t}\n}\n\n/**\n * `app.ts`: the worker's runtime — its logger and environment parser off the\n * Worker construct, the drivers its target needs, and a `startWorker` that\n * starts every cron, consumer and subscriber it owns.\n */\nfunction workerApp(\n\tcontext: BuildContext,\n\tworkerId: string,\n\toutputDir: string,\n): string {\n\tconst runtime = runtimeFor(context, outputDir, workerId);\n\n\treturn `/**\n * Generated worker runtime for ${workerId}.\n * Generated by 'gkm build --production'.\n */\nimport { setupCrons, cronsStatus, stopCrons } from './crons.js';\nimport { setupQueues, queuesStatus, stopQueues } from './queues.js';\nimport { setupSubscribers, subscribersStatus, stopSubscribers } from './subscribers.js';\n${runtime.imports}\n${context.storageDrivers?.imports ?? ''}\n\n${runtime.bindings}\n\n${\n\tcontext.storageDrivers?.setup\n\t\t? `// The entry point registers the drivers its target needs.\n${context.storageDrivers.setup}\n`\n\t\t: ''\n}\n// Who this worker is to Postgres: application_name on every connection.\nprocess.env.GKM_APP_NAME ??= ${JSON.stringify(workerId)};\n\nexport { logger };\n\n/** What the health check answers, and why. */\nexport interface WorkerHealth {\n healthy: boolean;\n /** The crons, queues and subscribers that could not start. */\n failed: string[];\n /** How many broker connections did not answer. */\n down: number;\n}\n\n/** Whether one broker connection answers: open, and — on pg-boss — a query. */\nasync function answers(connection: unknown): Promise<boolean> {\n const c = connection as {\n isConnected?: () => boolean;\n instance?: { getDb?: () => { executeSql: (sql: string) => Promise<unknown> } };\n };\n if (c.isConnected && !c.isConnected()) return false;\n const db = c.instance?.getDb?.();\n if (!db) return true;\n try {\n await Promise.race([\n db.executeSql('select 1'),\n new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 2000).unref()),\n ]);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Start everything this worker runs. */\nexport async function startWorker(): Promise<{\n health: () => Promise<WorkerHealth>;\n stop: () => Promise<void>;\n}> {\n // An app object with no routes: on a server target nothing is pushed to a\n // subscriber, so there is nothing to mount.\n const noRoutes = { post: () => undefined };\n\n await setupSubscribers(noRoutes, envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to start subscribers');\n });\n await setupQueues(envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to start queue consumers');\n });\n await setupCrons(envParser, logger).catch((error) => {\n logger.error({ err: error }, 'Failed to schedule crons');\n });\n\n const statuses = () => [subscribersStatus(), queuesStatus(), cronsStatus()];\n\n return {\n async health() {\n const all = statuses();\n const failed = all.flatMap((s) => s.failed);\n const alive = await Promise.all(\n all.flatMap((s) => s.connections as unknown[]).map(answers),\n );\n const down = alive.filter((ok) => !ok).length;\n return { healthy: failed.length === 0 && down === 0, failed, down };\n },\n // Each stops pulling, waits for what it has in flight, and closes its\n // broker connection. Together, so one slow handler does not hold the\n // others' connections open.\n async stop() {\n await Promise.all([stopSubscribers(), stopQueues(), stopCrons()]);\n },\n };\n}\n`;\n}\n\n/**\n * `worker.ts`: the process. Health first, so the container answers while the\n * consumers connect; then the worker; then a shutdown that drains.\n */\nfunction workerEntry(healthCheck: string): string {\n\treturn `#!/usr/bin/env node\n/**\n * Production worker entry point.\n * Generated by 'gkm build --production'.\n *\n * Runs this worker's crons, queue consumers and topic subscribers, and serves\n * one route: ${healthCheck}, on PORT — 200 when every consumer started and\n * every broker connection answers, 503 otherwise.\n *\n * On SIGTERM it stops pulling messages and scheduling crons, lets the handlers\n * in flight finish, closes its broker connections and database pools, and\n * exits 0 — or 1, at GKM_SHUTDOWN_TIMEOUT_MS (8s by default, under Docker's\n * 10s stop timeout).\n */\nimport { createServer } from 'node:http';\nimport { runShutdownHooks } from '@geekmidas/constructs';\nimport { startTelemetry } from './telemetry.js';\n\n// Before the worker is imported, so the libraries it loads are instrumented.\nawait startTelemetry({ ignorePaths: [${JSON.stringify(healthCheck)}] });\n\nconst { startWorker, logger } = await import('./app.js');\n\nconst port = Number(process.env.PORT) || 3000;\n\nlet worker: Awaited<ReturnType<typeof startWorker>> | undefined;\nlet stopping = false;\n\nconst server = createServer((req, res) => {\n const path = (req.url ?? '/').split('?')[0];\n if (path !== ${JSON.stringify(healthCheck)} || (req.method !== 'GET' && req.method !== 'HEAD')) {\n res.writeHead(404).end();\n return;\n }\n const answer = (status: number, body: unknown) => {\n res.writeHead(status, { 'content-type': 'application/json' });\n res.end(JSON.stringify(body));\n };\n if (stopping) return answer(503, { status: 'stopping' });\n if (!worker) return answer(503, { status: 'starting' });\n worker.health().then(\n (health) => answer(health.healthy ? 200 : 503, { status: health.healthy ? 'ok' : 'unhealthy', ...health }),\n (error) => answer(503, { status: 'unhealthy', error: String(error) }),\n );\n});\nserver.listen(port);\n\nconst shutdown = async () => {\n if (stopping) return;\n stopping = true;\n const deadline = Number(process.env.GKM_SHUTDOWN_TIMEOUT_MS) || 8000;\n logger.info({ deadline }, 'Graceful shutdown initiated');\n setTimeout(() => {\n logger.warn({ deadline }, 'Shutdown deadline reached, exiting');\n process.exit(1);\n }, deadline).unref();\n\n // Nothing new is pulled or scheduled; what is in flight finishes.\n await worker?.stop().catch((error) => logger.error({ err: error }, 'Failed to stop the worker'));\n // Then what the handlers used: database pools.\n await runShutdownHooks((error) => logger.error({ err: error }, 'Shutdown hook failed'));\n server.close();\n logger.info('Worker stopped');\n process.exit(0);\n};\n\nprocess.on('SIGTERM', shutdown);\nprocess.on('SIGINT', shutdown);\n\nworker = await startWorker();\nlogger.info({ port }, 'Worker started');\n`;\n}\n","import { isAbsolute, join, relative, sep } from 'node:path';\nimport type { ConstructManifest } from '@geekmidas/manifest';\nimport { appKey } from '../workspace/derive.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\n/**\n * A Worker as a deploy unit: one process, one image, one service.\n *\n * A worker declares no app — it names a process, not a directory — so the\n * image is built from the app whose code holds its work: `gkm build` there\n * writes the worker's entry beside the server's, and the worker's image\n * copies that bundle out instead.\n */\nexport interface WorkerUnit {\n\t/** The construct, `Jobs`. */\n\tid: string;\n\t/** Its service and image name, `jobs`. */\n\tname: string;\n\t/** The backend app whose build writes its entry, `api`. */\n\tapp: string;\n}\n\n/** A worker with background work and no backend app to build it from. */\nexport class WorkerHasNoApp extends Error {\n\tconstructor(readonly worker: string) {\n\t\tsuper(\n\t\t\t`The worker '${worker}' has crons, queues or subscribers, and the ` +\n\t\t\t\t`workspace has no backend app that builds a server to carry its ` +\n\t\t\t\t`entry. Declare a RestApi whose app holds the worker's code.`,\n\t\t);\n\t\tthis.name = 'WorkerHasNoApp';\n\t}\n}\n\n/** A worker whose service name an app already has. */\nexport class WorkerNameTaken extends Error {\n\tconstructor(\n\t\treadonly worker: string,\n\t\treadonly service: string,\n\t) {\n\t\tsuper(\n\t\t\t`The worker '${worker}' runs as the service '${service}', and an app is ` +\n\t\t\t\t`already called that. Rename the worker — new Worker('${worker}Worker') ` +\n\t\t\t\t`— so each container has a name of its own.`,\n\t\t);\n\t\tthis.name = 'WorkerNameTaken';\n\t}\n}\n\n/**\n * The workers a workspace deploys — each one that has background work — and\n * the app each is built from.\n *\n * The host is the backend app whose directory holds the most of the worker's\n * files; one whose work sits outside every app (a shared `jobs/` directory)\n * is built by the first backend, by name, that generates a server. An app\n * with its own entry is never one — gkm's generators do not build it — and a\n * surface that serves itself, an auth server, only when it holds the work.\n *\n * @param background Each worker's files, from discovery's `background`.\n */\nexport function workerUnits(\n\tworkspace: NormalizedWorkspace,\n\tmanifest: ConstructManifest,\n\tbackground: Readonly<Record<string, readonly string[]>>,\n): WorkerUnit[] {\n\tconst hosts = Object.entries(workspace.apps)\n\t\t.filter(([, app]) => app.type === 'backend' && !app.entry)\n\t\t.map(([name, app]) => {\n\t\t\tconst surface = Object.entries(manifest).find(\n\t\t\t\t([id, d]) => d.kind === 'rest-api' && appKey(id) === name,\n\t\t\t)?.[1];\n\t\t\treturn {\n\t\t\t\tname,\n\t\t\t\troot: isAbsolute(app.path) ? app.path : join(workspace.root, app.path),\n\t\t\t\t// Endpoints declared on the surface itself are one that serves\n\t\t\t\t// itself — an auth server — whose build generates no server.\n\t\t\t\tgenerated: !(\n\t\t\t\t\tsurface?.kind === 'rest-api' && surface.endpoints.length > 0\n\t\t\t\t),\n\t\t\t};\n\t\t})\n\t\t.sort((a, b) => a.name.localeCompare(b.name));\n\n\tconst units: WorkerUnit[] = [];\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tif (declaration.kind !== 'worker') continue;\n\t\tconst files = background[id] ?? [];\n\t\tif (files.length === 0) continue;\n\n\t\tconst name = appKey(id);\n\t\tif (workspace.apps[name]) throw new WorkerNameTaken(id, name);\n\n\t\t// The app holding the most of its work; then one that generates a\n\t\t// server; then by name.\n\t\tconst ranked = hosts\n\t\t\t.map((host) => ({\n\t\t\t\thost,\n\t\t\t\tholds: files.filter((file) => isUnder(file, host.root)).length,\n\t\t\t}))\n\t\t\t.sort(\n\t\t\t\t(a, b) =>\n\t\t\t\t\tb.holds - a.holds ||\n\t\t\t\t\tNumber(b.host.generated) - Number(a.host.generated),\n\t\t\t);\n\t\tconst host = ranked[0]?.host;\n\t\tif (!host || (ranked[0]!.holds === 0 && !host.generated)) {\n\t\t\tthrow new WorkerHasNoApp(id);\n\t\t}\n\n\t\tunits.push({ id, name, app: host.name });\n\t}\n\n\treturn units.sort((a, b) => a.name.localeCompare(b.name));\n}\n\nfunction isUnder(file: string, dir: string): boolean {\n\tconst rel = relative(dir, file);\n\treturn rel !== '' && !rel.startsWith('..') && !isAbsolute(rel)\n\t\t? !rel.startsWith(`..${sep}`)\n\t\t: false;\n}\n","/**\n * Mail and object storage on a deployed stage: real ones, or — only when\n * someone asked — the dev services the local stage runs.\n *\n * Locally a declared `Email` is Mailpit and a declared bucket is MinIO, and\n * nobody has to configure either. Deployed, the same two containers would be\n * a stage that silently delivers no mail and keeps its files on one\n * container's disk. So a deployed stage on a server target takes both from\n * its secrets — the mail server's URL and sending address, and each bucket's\n * URL — and a stage missing any of them fails before anything is built,\n * provisioned or written, naming every key at once.\n *\n * A bucket's credentials are not checked: they are optional, and come from\n * either of two places. The bucket's URL may carry a key of its own\n * (`s3://KEY:SECRET@uploads`), which wins; otherwise the S3 client's default\n * chain applies — the stage's shared `AWS_ACCESS_KEY_ID`/\n * `AWS_SECRET_ACCESS_KEY` when it set them, or a role.\n *\n * `--allow-dev-services minio,mailpit` is the way out for a stage that is\n * not production — a preview, a demo: the target runs the dev service and\n * derives the keys from it. Keys the stage did set still win. Every run that\n * uses one says so, loudly.\n *\n * Pure: what each target runs is decided here from declarations and the\n * stage's values, and the targets do the running.\n */\n\nimport {\n\ttype ConstructManifest,\n\tdependentsOf,\n\tprovideKey,\n} from '@geekmidas/manifest';\nimport { appKey } from '../workspace/derive.js';\n\n/** The dev services a deployed stage may be allowed to run. */\nexport const DEV_SERVICES = ['minio', 'mailpit'] as const;\n\nexport type DevService = (typeof DEV_SERVICES)[number];\n\n/**\n * The S3 client's shared key pair. Optional: a bucket whose URL carries its own\n * key (`s3://KEY:SECRET@bucket`) signs with that, and one without signs with\n * this pair when the stage set it, or with whatever else the SDK's default\n * chain finds — a role.\n */\nexport const STORAGE_KEY_PAIR = [\n\t'AWS_ACCESS_KEY_ID',\n\t'AWS_SECRET_ACCESS_KEY',\n] as const;\n\n/** A `--allow-dev-services` value that is not a dev service. */\nexport class UnknownDevService extends Error {\n\tconstructor(readonly value: string) {\n\t\tsuper(\n\t\t\t`'${value}' is not a dev service. --allow-dev-services takes a ` +\n\t\t\t\t`comma-separated list of ${DEV_SERVICES.join(', ')}: ` +\n\t\t\t\t`--allow-dev-services ${DEV_SERVICES.join(',')}`,\n\t\t);\n\t\tthis.name = 'UnknownDevService';\n\t}\n}\n\n/** Dev services asked of a target that runs no containers. */\nexport class DevServicesNeedServerTarget extends Error {\n\tconstructor(\n\t\treadonly target: string,\n\t\treadonly services: readonly DevService[],\n\t) {\n\t\tsuper(\n\t\t\t`--allow-dev-services applies to server targets only, and '${target}' ` +\n\t\t\t\t`deploys to AWS, where a bucket is S3 and mail is the stage's own. ` +\n\t\t\t\t`Drop --allow-dev-services ${services.join(',')}.`,\n\t\t);\n\t\tthis.name = 'DevServicesNeedServerTarget';\n\t}\n}\n\n/** One key a deployed stage needs and its secrets do not hold. */\nexport interface MissingServiceKey {\n\tkey: string;\n\t/** The construct that reads it. */\n\tid: string;\n\t/** What it is, for the line that names it. */\n\twhat: string;\n\t/** A line after `what`, for what else there is to know about it. */\n\tnote?: string;\n\t/** A placeholder value to show in the `gkm secrets:set` line. */\n\texample: string;\n\t/** The dev service that would stand in for it. */\n\tservice: DevService;\n\t/** The apps that read it, where known. */\n\tapps?: readonly string[];\n}\n\n/**\n * A deployed stage with mail or object storage that its secrets do not\n * configure — every missing key, across every app, at once.\n */\nexport class ExternalServicesNotConfigured extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly missing: readonly MissingServiceKey[],\n\t) {\n\t\tconst services = [...new Set(missing.map((m) => m.service))].sort();\n\t\tconst lines = missing.map((m) => {\n\t\t\tconst by = m.apps?.length ? `, read by ${m.apps.join(', ')}` : '';\n\t\t\treturn (\n\t\t\t\t` gkm secrets:set ${m.key} '${m.example}' --stage ${stage}\\n` +\n\t\t\t\t` ${m.what}${by}` +\n\t\t\t\t(m.note ? `\\n ${m.note}` : '')\n\t\t\t);\n\t\t});\n\t\tsuper(\n\t\t\t`The stage '${stage}' is deployed, and a deployed stage's mail and ` +\n\t\t\t\t`object storage are real services: gkm runs no Mailpit or MinIO for ` +\n\t\t\t\t`it. Set ${missing.length === 1 ? 'this key' : `these ${missing.length} keys`} in the stage's secrets:\\n\\n` +\n\t\t\t\t`${lines.join('\\n')}\\n\\n` +\n\t\t\t\t`For a stage that is not production — a preview, a demo — the dev ` +\n\t\t\t\t`services can run instead, with --allow-dev-services ` +\n\t\t\t\t`${services.join(',')}. Mailpit delivers no mail, and MinIO keeps ` +\n\t\t\t\t`every object on one container's disk.\\n\\n` +\n\t\t\t\t`Or run: gkm secrets:add --stage ${stage}`,\n\t\t);\n\t\tthis.name = 'ExternalServicesNotConfigured';\n\t}\n}\n\n/**\n * The dev services a value names: `'minio,mailpit'`, or a list of them.\n *\n * @throws {UnknownDevService} for anything that is not one\n */\nexport function parseDevServices(\n\tvalue: string | readonly string[] | undefined,\n): DevService[] {\n\tif (value === undefined) return [];\n\tconst items = (typeof value === 'string' ? [value] : value)\n\t\t.flatMap((item) => item.split(','))\n\t\t.map((item) => item.trim())\n\t\t.filter(Boolean);\n\n\tconst services = new Set<DevService>();\n\tfor (const item of items) {\n\t\tif (!(DEV_SERVICES as readonly string[]).includes(item)) {\n\t\t\tthrow new UnknownDevService(item);\n\t\t}\n\t\tservices.add(item as DevService);\n\t}\n\treturn [...services].sort();\n}\n\n/** A declaration that is mail or object storage. */\nexport interface ServiceDeclaration {\n\tid: string;\n\tkind: 'email' | 'objects' | 'file-server';\n\t/** A file server's bucket. */\n\tof?: string;\n\t/** The apps that read it, where known. */\n\tapps?: readonly string[];\n}\n\n/**\n * Every mail and storage construct a manifest declares, with the apps that\n * depend on each — what a target that provisions the whole manifest checks.\n */\nexport function manifestServiceDeclarations(\n\tmanifest: ConstructManifest,\n): ServiceDeclaration[] {\n\treturn Object.entries(manifest).flatMap(\n\t\t([id, declaration]): ServiceDeclaration[] => {\n\t\t\tconst { kind } = declaration;\n\t\t\tif (kind !== 'email' && kind !== 'objects' && kind !== 'file-server') {\n\t\t\t\treturn [];\n\t\t\t}\n\t\t\tconst apps = dependentsOf(manifest, id)\n\t\t\t\t.filter((caller) => {\n\t\t\t\t\tconst callerKind = manifest[caller]?.kind;\n\t\t\t\t\treturn callerKind === 'rest-api' || callerKind === 'site';\n\t\t\t\t})\n\t\t\t\t.map(appKey);\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tid,\n\t\t\t\t\tkind,\n\t\t\t\t\t...(declaration.kind === 'file-server' ? { of: declaration.of } : {}),\n\t\t\t\t\t...(apps.length ? { apps } : {}),\n\t\t\t\t},\n\t\t\t];\n\t\t},\n\t);\n}\n\nexport interface ExternalServicesInput {\n\tstage: string;\n\t/** Every mail and storage construct something on the stage reads. */\n\tdeclarations: readonly ServiceDeclaration[];\n\t/** The stage's values by key — what `gkm secrets:set` stored. */\n\tsupplied: Readonly<Record<string, string>>;\n\t/** What `--allow-dev-services` allowed. */\n\tallow: readonly DevService[];\n\t/** The stage's base domain, for the examples. */\n\tdomain?: string;\n}\n\n/** Where a deployed stage's mail and storage come from. */\nexport interface ExternalServices {\n\t/** Keys the stage needs and has not set. Empty when it can deploy. */\n\tmissing: MissingServiceKey[];\n\t/** The buckets the target runs MinIO for. */\n\tminio: string[];\n\t/** The mail constructs the target runs Mailpit for. */\n\tmailpit: string[];\n}\n\n/** One dev service a run uses, and for which constructs. */\nexport interface DevServiceUse {\n\tservice: DevService;\n\tids: string[];\n}\n\n/** A third party's credentials a construct reads: an external API's, or a `Credential`. */\nexport interface CredentialDeclaration {\n\tid: string;\n\tkind: 'external-api' | 'credential';\n\t/** The apps that read it, where known. */\n\tapps?: readonly string[];\n}\n\n/** What a stage-supplied key belongs to. */\nexport type StageKeyKind =\n\t| 'bucket'\n\t| 'email'\n\t| 'file-server'\n\t| 'external-api'\n\t| 'credential';\n\n/**\n * One key only the stage's secrets can hold — nothing derives it — whether\n * or not it is set.\n */\nexport interface StageKey {\n\tkey: string;\n\t/** The construct that reads it. */\n\tid: string;\n\tkind: StageKeyKind;\n\t/** What it is, for the line that names it. */\n\twhat: string;\n\t/** A line after `what`, for what else there is to know about it. */\n\tnote?: string;\n\t/** A placeholder value to show in the `gkm secrets:set` line. */\n\texample: string;\n\t/** The dev service that would stand in for it, where one can. */\n\tservice?: DevService;\n\t/** A file server's bucket. */\n\tof?: string;\n\t/** The apps that read it, where known. */\n\tapps?: readonly string[];\n}\n\nexport interface StageKeysInput {\n\t/** Whether the stage is the local one, which derives its mail and storage. */\n\tlocal: boolean;\n\t/** Every mail and storage construct something on the stage reads. */\n\tservices?: readonly ServiceDeclaration[];\n\t/** Every third party's credentials something on the stage reads. */\n\tcredentials?: readonly CredentialDeclaration[];\n\t/** The stage's base domain, for the examples. */\n\tdomain?: string;\n}\n\n/**\n * Every key a stage must be given rather than derive — the one list both a\n * deploy's checks and `gkm secrets:add` read, so what a deploy refuses and\n * what the builder offers cannot drift.\n *\n * - Deployed, mail is its URL and its sending address, a bucket its URL, and\n * a file server its public URL. Locally all of it is Mailpit and MinIO.\n * - On every stage, a third party's credentials: nobody but the third party\n * can issue them.\n *\n * Never a derived value: a database URL, a generated secret, the seed.\n */\nexport function requiredStageKeys(input: StageKeysInput): StageKey[] {\n\tconst domain = input.domain ?? 'example.com';\n\tconst keys: StageKey[] = [];\n\tconst add = (entry: StageKey) => {\n\t\tif (!keys.some((k) => k.key === entry.key)) keys.push(entry);\n\t};\n\tconst byId = <T extends { id: string }>(list: readonly T[] | undefined) =>\n\t\t[...(list ?? [])].sort((a, b) => a.id.localeCompare(b.id));\n\tconst apps = (d: { apps?: readonly string[] }) =>\n\t\td.apps?.length ? { apps: [...d.apps].sort() } : {};\n\n\tif (!input.local) {\n\t\tconst services = byId(input.services);\n\t\tfor (const d of services.filter((d) => d.kind === 'email')) {\n\t\t\tadd({\n\t\t\t\tkey: provideKey(d.id, 'url'),\n\t\t\t\tid: d.id,\n\t\t\t\tkind: 'email',\n\t\t\t\twhat: `where '${d.id}' sends mail — any SMTP server`,\n\t\t\t\texample: `smtp://user:password@smtp.${domain}:587`,\n\t\t\t\tservice: 'mailpit',\n\t\t\t\t...apps(d),\n\t\t\t});\n\t\t\tadd({\n\t\t\t\tkey: provideKey(d.id, 'from'),\n\t\t\t\tid: d.id,\n\t\t\t\tkind: 'email',\n\t\t\t\twhat: `the address '${d.id}' sends from, on a domain the mail server has verified`,\n\t\t\t\texample: `noreply@${domain}`,\n\t\t\t\tservice: 'mailpit',\n\t\t\t\t...apps(d),\n\t\t\t});\n\t\t}\n\t\tfor (const d of services.filter((d) => d.kind === 'objects')) {\n\t\t\tadd({\n\t\t\t\tkey: provideKey(d.id, 'url'),\n\t\t\t\tid: d.id,\n\t\t\t\tkind: 'bucket',\n\t\t\t\twhat: `the bucket '${d.id}' — S3, R2, or any S3-compatible store (add &endpoint=… for one that is not S3)`,\n\t\t\t\tnote: `credentials are optional: a key for this bucket alone in the URL (s3://KEY:SECRET@${appKey(d.id)}?…) wins; without one, the shared AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY or a role signs`,\n\t\t\t\texample: `s3://${appKey(d.id)}?region=eu-west-1`,\n\t\t\t\tservice: 'minio',\n\t\t\t\t...apps(d),\n\t\t\t});\n\t\t}\n\t\tfor (const d of services.filter((d) => d.kind === 'file-server')) {\n\t\t\tadd({\n\t\t\t\tkey: provideKey(d.id, 'url'),\n\t\t\t\tid: d.id,\n\t\t\t\tkind: 'file-server',\n\t\t\t\twhat: `the public address '${d.id}' serves its bucket on — a CDN or the bucket's own domain`,\n\t\t\t\texample: `https://${appKey(d.id)}.${domain}`,\n\t\t\t\tservice: 'minio',\n\t\t\t\t...(d.of ? { of: d.of } : {}),\n\t\t\t\t...apps(d),\n\t\t\t});\n\t\t}\n\t}\n\n\tfor (const d of byId(input.credentials)) {\n\t\tadd({\n\t\t\tkey: provideKey(d.id, 'credentials'),\n\t\t\tid: d.id,\n\t\t\tkind: d.kind,\n\t\t\twhat:\n\t\t\t\td.kind === 'external-api'\n\t\t\t\t\t? `the credentials '${d.id}' was issued for this stage, as its schema describes them`\n\t\t\t\t\t: `the credential '${d.id}', as its schema describes it`,\n\t\t\texample: '{\"apiKey\":\"…\"}',\n\t\t\t...apps(d),\n\t\t});\n\t}\n\n\treturn keys;\n}\n\n/**\n * Whether only the stage's secrets can supply `key` to a construct of `kind`\n * — what nothing on the stage derives.\n *\n * A third party's credentials, always. Deployed, also each generated secret\n * and keyring: the stage generated each one once, and every run reads the\n * same value back.\n */\nexport function suppliedOnly(\n\tkind: string,\n\tid: string,\n\tkey: string,\n\tlocal: boolean,\n): boolean {\n\tif (kind === 'credential') return true;\n\tif (kind === 'external-api') return key === provideKey(id, 'credentials');\n\tif (local) return false;\n\treturn kind === 'secret' || kind === 'encryption';\n}\n\n/**\n * Where a deployed stage's mail and storage come from, and what it lacks.\n *\n * - Mail is external once its URL is set, and then needs its sending\n * address too. Unset, it is Mailpit where allowed — the address derived\n * unless set — and missing otherwise.\n * - A bucket is external once its URL is set; its credentials, in the URL or\n * the shared pair, are optional. Unset, it is MinIO where allowed, and\n * missing otherwise.\n * - A file server's URL is derived where its bucket is MinIO, unless set;\n * over an external bucket the stage must set it.\n *\n * The keys themselves are {@link requiredStageKeys}'s.\n */\nexport function externalServices(\n\tinput: ExternalServicesInput,\n): ExternalServices {\n\tconst { supplied, allow } = input;\n\tconst has = (key: string) => supplied[key] !== undefined;\n\n\tconst required = requiredStageKeys({\n\t\tlocal: false,\n\t\tservices: input.declarations,\n\t\t...(input.domain ? { domain: input.domain } : {}),\n\t});\n\n\t// A dev service stands in for a construct the stage configured nothing for.\n\tconst mailpit = required\n\t\t.filter((k) => k.kind === 'email' && k.key === provideKey(k.id, 'url'))\n\t\t.filter((k) => !has(k.key) && allow.includes('mailpit'))\n\t\t.map((k) => k.id);\n\tconst minio = required\n\t\t.filter((k) => k.kind === 'bucket')\n\t\t.filter((k) => !has(k.key) && allow.includes('minio'))\n\t\t.map((k) => k.id);\n\n\tconst covered = (k: StageKey) =>\n\t\t(k.kind === 'email' && mailpit.includes(k.id)) ||\n\t\t(k.kind === 'bucket' && minio.includes(k.id)) ||\n\t\t(k.kind === 'file-server' && k.of !== undefined && minio.includes(k.of));\n\n\tconst missing = required\n\t\t.filter((k) => !has(k.key) && !covered(k))\n\t\t.map(\n\t\t\t({ key, id, what, note, example, service, apps }): MissingServiceKey => ({\n\t\t\t\tkey,\n\t\t\t\tid,\n\t\t\t\twhat,\n\t\t\t\t...(note ? { note } : {}),\n\t\t\t\texample,\n\t\t\t\tservice: service!,\n\t\t\t\t...(apps ? { apps } : {}),\n\t\t\t}),\n\t\t);\n\n\treturn { missing, minio, mailpit };\n}\n\n/**\n * `externalServices`, refusing a stage that lacks anything.\n *\n * @throws {ExternalServicesNotConfigured} naming every missing key\n */\nexport function assertExternalServices(\n\tinput: ExternalServicesInput,\n): ExternalServices {\n\tconst services = externalServices(input);\n\tif (services.missing.length > 0) {\n\t\tthrow new ExternalServicesNotConfigured(input.stage, services.missing);\n\t}\n\treturn services;\n}\n\n/** The dev services a run uses, one entry per service it runs. */\nexport function devServicesUsed(services: ExternalServices): DevServiceUse[] {\n\tconst used: DevServiceUse[] = [];\n\tif (services.mailpit.length > 0) {\n\t\tused.push({ service: 'mailpit', ids: services.mailpit });\n\t}\n\tif (services.minio.length > 0) {\n\t\tused.push({ service: 'minio', ids: services.minio });\n\t}\n\treturn used;\n}\n\n/** The line a run prints for each dev service a deployed stage uses. */\nexport function devServiceWarning(stage: string, use: DevServiceUse): string {\n\tconst what =\n\t\tuse.service === 'mailpit'\n\t\t\t? `Mailpit is catching mail for ${use.ids.join(', ')}. It delivers NO mail: nobody receives a sign-in link, a receipt or a reset`\n\t\t\t: `MinIO is storing ${use.ids.join(', ')}. Every object is on one container's disk, with no replication and no backup`;\n\treturn (\n\t\t`⚠️ DEV SERVICE ON A DEPLOYED STAGE (${stage}): ${what}. It is not ` +\n\t\t`production-grade — set the stage's own keys in its secrets for that.`\n\t);\n}\n\n/**\n * Report each dev service a run uses: a loud line, and a `dev-service.used`\n * event a host can act on.\n */\nexport function reportDevServices(\n\tctx: {\n\t\tstage: string;\n\t\tlogger: { warn(message: string): void };\n\t\temit(event: {\n\t\t\ttype: 'dev-service.used';\n\t\t\tservice: DevService;\n\t\t\tstage: string;\n\t\t\tconstructs: string[];\n\t\t}): void;\n\t},\n\tuses: readonly DevServiceUse[],\n): void {\n\tfor (const use of uses) {\n\t\tctx.logger.warn(`\\n${devServiceWarning(ctx.stage, use)}`);\n\t\tctx.emit({\n\t\t\ttype: 'dev-service.used',\n\t\t\tservice: use.service,\n\t\t\tstage: ctx.stage,\n\t\t\tconstructs: use.ids,\n\t\t});\n\t}\n}\n","/**\n * The OpenTelemetry variables a stage's secrets pass to every backend.\n *\n * A production server starts OpenTelemetry when `OTEL_EXPORTER_OTLP_ENDPOINT`\n * is set (`generators/telemetry.ts`), and the SDK reads the rest of its\n * configuration from the standard `OTEL_*` variables. No construct declares\n * them — they say where the telemetry goes, not what the app is — so a target\n * that writes each backend only the keys it reads would never hand them over.\n * Every target that runs backends asks this module instead.\n *\n * Which keys pass is a pattern over the `OTEL_` prefix rather than the prefix\n * alone or a list:\n *\n * - The bare prefix would forward anything a stage happened to name `OTEL_*`\n * — `OTEL_LOG_LEVEL=debug`, `OTEL_SDK_DISABLED` — to every backend, without\n * anyone choosing to.\n * - A list would have to spell out the exporter's five settings once for all\n * signals and once per signal — twenty keys — and would miss the next one.\n *\n * The pattern names the families the server uses: the OTLP exporter's\n * endpoint, headers, protocol, timeout and compression, for every signal or\n * for one (`OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`); sampling; and the resource's\n * identity.\n *\n * Sites get none of it: a site's environment is inlined into a bundle that\n * every browser downloads, and an exporter's headers are a credential.\n */\n\n/** The `OTEL_*` variables a backend is handed from the stage's secrets. */\nexport const OTEL_PASSTHROUGH =\n\t/^OTEL_(?:EXPORTER_OTLP_(?:(?:TRACES|LOGS|METRICS)_)?(?:ENDPOINT|HEADERS|PROTOCOL|TIMEOUT|COMPRESSION)|TRACES_SAMPLER(?:_ARG)?|RESOURCE_ATTRIBUTES|SERVICE_NAME)$/;\n\n/** Whether a stage secret is one of the passed-through `OTEL_*` variables. */\nexport function isOtelVariable(key: string): boolean {\n\treturn OTEL_PASSTHROUGH.test(key);\n}\n\n/** Whether a variable says where an OTLP exporter sends, or how it signs. */\nexport function isOtlpDestination(key: string): boolean {\n\treturn /^OTEL_EXPORTER_OTLP_(?:(?:TRACES|LOGS|METRICS)_)?(?:ENDPOINT|HEADERS)$/.test(\n\t\tkey,\n\t);\n}\n\n/**\n * The `OTEL_*` variables one backend runs with: every one the stage's secrets\n * hold, and `OTEL_SERVICE_NAME` — the app's name — where the stage set\n * telemetry up without naming the service. Empty when the stage set none.\n */\nexport function otelEnv(\n\tcustom: Readonly<Record<string, string>>,\n\tapp: string,\n): Record<string, string> {\n\tconst env: Record<string, string> = {};\n\tfor (const [key, value] of Object.entries(custom)) {\n\t\tif (isOtelVariable(key) && value !== '') env[key] = value;\n\t}\n\tif (Object.keys(env).length > 0 && !env.OTEL_SERVICE_NAME) {\n\t\tenv.OTEL_SERVICE_NAME = app;\n\t}\n\treturn env;\n}\n","import { getPublicEnvPrefix } from '../../workspace/index.js';\nimport { rootSite } from '../../workspace/rootSite.js';\nimport type {\n\tDomainsConfig,\n\tNormalizedAppConfig,\n} from '../../workspace/types.js';\n\n/** A stage was deployed that `deploy.domains` gives no domain. */\nexport class NoDomainForStage extends Error {\n\tconstructor(readonly stage: string) {\n\t\tsuper(\n\t\t\t`No domain for stage \"${stage}\". Add it to gkm.config.ts: deploy: { domains: { ${stage}: 'example.com' } }.`,\n\t\t);\n\t\tthis.name = 'NoDomainForStage';\n\t}\n}\n\n/**\n * The hostname an app answers on for a stage.\n *\n * 1. An explicit `app.domain` — a string, or one per stage — wins.\n * 2. Otherwise from the stage's base domain in `deploy.domains`: the root site\n * answers on the domain itself, and every other app on\n * `{subdomain}.{domain}` — its declaration's `subdomain`, or its own name.\n *\n * @throws {NoDomainForStage} when `deploy.domains` names no domain for it\n */\nexport function resolveHost(\n\tappName: string,\n\tapp: NormalizedAppConfig,\n\tstage: string,\n\tdomains: DomainsConfig | undefined,\n\tisMainFrontend: boolean,\n): string {\n\tif (app.domain) {\n\t\tif (typeof app.domain === 'string') {\n\t\t\treturn app.domain;\n\t\t}\n\t\tif (app.domain[stage]) {\n\t\t\treturn app.domain[stage]!;\n\t\t}\n\t}\n\n\tconst baseDomain = domains?.[stage];\n\tif (!baseDomain) throw new NoDomainForStage(stage);\n\n\tif (isMainFrontend) return baseDomain;\n\n\treturn `${app.subdomain ?? appName}.${baseDomain}`;\n}\n\n/**\n * Whether this app is the site the base domain points at — see `rootSite`,\n * which the local edge asks too.\n *\n * @throws {AmbiguousRootSite} when several sites could be the root and none says it is\n */\nexport function isMainFrontendApp(\n\tappName: string,\n\tapp: NormalizedAppConfig,\n\tallApps: Record<string, NormalizedAppConfig>,\n): boolean {\n\tif (app.type !== 'web') return false;\n\n\tconst sites = Object.entries(allApps)\n\t\t.filter(([, a]) => a.type === 'web')\n\t\t.map(([name, a]) => ({ name, root: a.root }));\n\n\treturn rootSite(sites) === appName;\n}\n\nexport { AmbiguousRootSite } from '../../workspace/rootSite.js';\n\n/**\n * Generate public URL build args for a web/mobile app based on its dependencies.\n *\n * The prefix is chosen by the app's framework (NEXT_PUBLIC_, VITE_,\n * EXPO_PUBLIC_, or none for Remix). Apps without a public prefix get no\n * build args — they should fetch URLs at runtime instead.\n *\n * @param app - The web/mobile app configuration\n * @param deployedUrls - Map of app name to deployed public URL\n * @returns Array of build args like 'NEXT_PUBLIC_API_URL=https://api.example.com'\n */\nexport function generatePublicUrlBuildArgs(\n\tapp: NormalizedAppConfig,\n\tdeployedUrls: Record<string, string>,\n): string[] {\n\tconst prefix = getPublicEnvPrefix(app.framework);\n\tif (!prefix) return [];\n\n\tconst buildArgs: string[] = [];\n\tfor (const dep of app.dependencies) {\n\t\tconst publicUrl = deployedUrls[dep];\n\t\tif (publicUrl) {\n\t\t\tbuildArgs.push(`${prefix}${dep.toUpperCase()}_URL=${publicUrl}`);\n\t\t}\n\t}\n\n\treturn buildArgs;\n}\n\n/**\n * Get public URL arg names from app dependencies.\n *\n * @param app - The web/mobile app configuration\n * @returns Array of arg names like 'NEXT_PUBLIC_API_URL', or [] for frameworks\n * without a public prefix (e.g. Remix).\n */\nexport function getPublicUrlArgNames(app: NormalizedAppConfig): string[] {\n\tconst prefix = getPublicEnvPrefix(app.framework);\n\tif (!prefix) return [];\n\treturn app.dependencies.map((dep) => `${prefix}${dep.toUpperCase()}_URL`);\n}\n","/**\n * An image reference, checked before it reaches `docker`.\n *\n * The parts come from the workspace — a registry in `gkm.config.ts`, an image\n * name from the project, a tag from `--tag` — and an argument array already\n * keeps them out of a shell. What it cannot do is stop `docker` reading a value\n * that starts with `-` as one of its own flags, so a ref is held to Docker's\n * own grammar (distribution/reference) and anything else is refused by name\n * before a build starts.\n */\n\nconst DOMAIN_COMPONENT = '(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])';\nconst DOMAIN = `${DOMAIN_COMPONENT}(?:\\\\.${DOMAIN_COMPONENT})*(?::[0-9]+)?`;\nconst PATH_COMPONENT = '[a-z0-9]+(?:(?:[._]|__|-+)[a-z0-9]+)*';\nconst TAG = '[\\\\w][\\\\w.-]{0,127}';\nconst DIGEST =\n\t'[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*:[0-9a-fA-F]{32,}';\n\nconst REFERENCE = new RegExp(\n\t`^(?:${DOMAIN}/)?${PATH_COMPONENT}(?:/${PATH_COMPONENT})*(?::${TAG})?(?:@${DIGEST})?$`,\n);\n\n/** Docker refuses a repository name longer than this. */\nconst MAX_NAME_LENGTH = 255;\n\n/** A value that is not an image reference was about to be built or pushed. */\nexport class ImageRefInvalid extends Error {\n\tconstructor(readonly ref: string) {\n\t\tsuper(\n\t\t\t`'${ref}' is not a valid image reference. An image is \\`[registry/]name[:tag]\\`: the name lowercase letters, digits and . _ - separators, the tag up to 128 of letters, digits, _ . and -, not starting with . or -. Check docker.registry in gkm.config.ts and the --tag passed.`,\n\t\t);\n\t\tthis.name = 'ImageRefInvalid';\n\t}\n}\n\n/** Throw {@link ImageRefInvalid} unless `ref` is a Docker image reference. */\nexport function validateImageRef(ref: string): string {\n\tconst name = ref.split('@')[0]!.replace(/:[\\w][\\w.-]*$/, '');\n\tif (!REFERENCE.test(ref) || name.length > MAX_NAME_LENGTH) {\n\t\tthrow new ImageRefInvalid(ref);\n\t}\n\treturn ref;\n}\n","import { existsSync } from 'node:fs';\nimport { appendFile, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { dirname, join, relative } from 'node:path';\nimport { type ConstructManifest, publicEnvFor } from '@geekmidas/manifest';\nimport { type WorkerUnit, workerUnits } from '../build/workers.js';\nimport { loadWorkspaceConfig } from '../config';\nimport { workerBundleName } from '../generators/WorkerGenerator.js';\nimport { output } from '../output';\nimport { dockerfileOf } from '../reconcile/apps.js';\nimport { COMPOSE_PATH } from '../reconcile/index.js';\nimport { reconcileWorkspace } from '../reconcile/workspace.js';\nimport { run } from '../run';\nimport { getPublicUrlArgNames } from '../target/dokploy/domain.js';\nimport type { CacheBackend, GkmConfig } from '../types';\nimport { appKey } from '../workspace/derive.js';\nimport type {\n\tNormalizedAppConfig,\n\tNormalizedWorkspace,\n} from '../workspace/types.js';\nimport { validateImageRef } from './imageRef';\nimport { appImageOptions, type ImageLayout, imageLayout } from './layout';\nimport {\n\tgenerateBackendDockerfile,\n\tgenerateDockerignore,\n\tgenerateEntryDockerfile,\n\tgenerateNextjsDockerfile,\n\tgenerateNodeWebDockerfile,\n\tgenerateViteStaticDockerfile,\n\tgenerateWorkerDockerfile,\n\tresolveDockerConfig,\n\tWORKER_PORT,\n} from './templates';\n\nexport { ImageRefInvalid, validateImageRef } from './imageRef';\nexport {\n\tappImageOptions,\n\tcomposeBuildPaths,\n\tfromBuildRoot,\n\ttype ImageLayout,\n\timageLayout,\n\tMonorepoNeedsTurbo,\n} from './layout';\nexport {\n\tdetectPackageManager,\n\tfindBuildRoot,\n\tfindLockfilePath,\n\thasTurboConfig,\n\tisMonorepo,\n} from './templates';\n\n// Through `output`, so a deploy run that generates a Dockerfile hears it.\nconst logger = output;\n\n/** `docker build` failed; what docker printed above says why. */\nexport class DockerBuildFailed extends Error {\n\tconstructor(\n\t\treadonly imageRef: string,\n\t\tcause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Failed to build Docker image: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t{ cause },\n\t\t);\n\t\tthis.name = 'DockerBuildFailed';\n\t}\n}\n\n/** `docker push` failed — usually the registry login or its permissions. */\nexport class DockerPushFailed extends Error {\n\tconstructor(\n\t\treadonly imageRef: string,\n\t\tcause: unknown,\n\t) {\n\t\tsuper(\n\t\t\t`Failed to push Docker image: ${cause instanceof Error ? cause.message : String(cause)}`,\n\t\t\t{ cause },\n\t\t);\n\t\tthis.name = 'DockerPushFailed';\n\t}\n}\n\n/** `gkm docker --push` was asked for with nowhere to push to. */\nexport class PushNeedsRegistry extends Error {\n\tconstructor(readonly imageName: string) {\n\t\tsuper(\n\t\t\t`Registry is required to push Docker image '${imageName}'. Use --registry or configure docker.registry in gkm.config.ts`,\n\t\t);\n\t\tthis.name = 'PushNeedsRegistry';\n\t}\n}\n\nexport interface DockerOptions {\n\t/** Build each app's image after generating files */\n\tbuild?: boolean;\n\t/** Push the images to the registry after building */\n\tpush?: boolean;\n\t/** Image tag (default: 'latest') */\n\ttag?: string;\n\t/** Container registry URL */\n\tregistry?: string;\n\t/**\n\t * The directory to generate for — an app's own, when a deploy builds it.\n\t * Defaults to the process's working directory.\n\t */\n\tcwd?: string;\n}\n\n/**\n * Docker command implementation: one Dockerfile per deployable app, each\n * building its image inside Docker from a turbo-pruned slice of the build\n * root — nothing is built on the host — plus the build root's\n * `.dockerignore` and `docker-compose.constructs.yml`.\n */\nexport async function dockerCommand(\n\toptions: DockerOptions,\n): Promise<WorkspaceDockerResult> {\n\tconst cwd = options.cwd ?? process.cwd();\n\tconst loaded = await loadWorkspaceConfig(cwd);\n\n\t// A single-app config is a workspace of one app at its root; the image is\n\t// named the way its `docker` block says.\n\tconst names: Record<string, string> =\n\t\tloaded.type === 'single'\n\t\t\t? Object.fromEntries(\n\t\t\t\t\tObject.keys(loaded.workspace.apps).map((name) => [\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tresolveDockerConfig(loaded.raw as GkmConfig).imageName,\n\t\t\t\t\t]),\n\t\t\t\t)\n\t\t\t: {};\n\n\tconst result = await workspaceDockerCommand(\n\t\tloaded.workspace,\n\t\tloaded.manifest,\n\t\tnames,\n\t\tloaded.background,\n\t);\n\n\tif (options.build) {\n\t\tfor (const app of result.apps) {\n\t\t\tawait buildDockerImage(app, result.buildRoot, options);\n\t\t}\n\t}\n\tif (options.push) {\n\t\tfor (const app of result.apps) {\n\t\t\tawait pushDockerImage(app.imageName, options, result.buildRoot);\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Build one app's image, from the build root.\n * Uses BuildKit for cache mount support\n */\nasync function buildDockerImage(\n\tapp: AppDockerResult,\n\tbuildRoot: string,\n\toptions: DockerOptions,\n): Promise<void> {\n\tconst tag = options.tag ?? 'latest';\n\tconst registry = options.registry;\n\n\t// Before anything runs: a ref docker would misread is refused by name.\n\tconst fullImageName = validateImageRef(\n\t\tregistry\n\t\t\t? `${registry}/${app.imageName}:${tag}`\n\t\t\t: `${app.imageName}:${tag}`,\n\t);\n\n\tlogger.log(`\\n🐳 Building Docker image: ${fullImageName}`);\n\n\ttry {\n\t\t// An argument array, so nothing in the ref is read by a shell, and\n\t\t// `--tag=` so it cannot be read as a flag.\n\t\tawait run(\n\t\t\t'docker',\n\t\t\t[\n\t\t\t\t'build',\n\t\t\t\t`--file=${relative(buildRoot, app.dockerfile)}`,\n\t\t\t\t`--tag=${fullImageName}`,\n\t\t\t\t'.',\n\t\t\t],\n\t\t\t{\n\t\t\t\tcwd: buildRoot,\n\t\t\t\t// BuildKit, for the templates' `RUN --mount=type=cache`.\n\t\t\t\tenv: { ...process.env, DOCKER_BUILDKIT: '1' },\n\t\t\t},\n\t\t);\n\t\tlogger.log(`✅ Docker image built: ${fullImageName}`);\n\t} catch (error) {\n\t\tthrow new DockerBuildFailed(fullImageName, error);\n\t}\n}\n\n/**\n * Push Docker image to registry\n */\nasync function pushDockerImage(\n\timageName: string,\n\toptions: DockerOptions,\n\tcwd: string,\n): Promise<void> {\n\tconst tag = options.tag ?? 'latest';\n\tconst registry = options.registry;\n\n\tif (!registry) {\n\t\tthrow new PushNeedsRegistry(imageName);\n\t}\n\n\tconst fullImageName = validateImageRef(`${registry}/${imageName}:${tag}`);\n\n\tlogger.log(`\\n🚀 Pushing Docker image: ${fullImageName}`);\n\n\ttry {\n\t\tawait run('docker', ['push', fullImageName], { cwd });\n\t\tlogger.log(`✅ Docker image pushed: ${fullImageName}`);\n\t} catch (error) {\n\t\tthrow new DockerPushFailed(fullImageName, error);\n\t}\n}\n\n/**\n * Result of generating Docker files for a single app in a workspace.\n */\nexport interface AppDockerResult {\n\tappName: string;\n\ttype: 'backend' | 'web' | 'mobile';\n\t/** The Dockerfile, absolute. */\n\tdockerfile: string;\n\timageName: string;\n}\n\n/**\n * Result of workspace docker command.\n */\nexport interface WorkspaceDockerResult {\n\tapps: AppDockerResult[];\n\tdockerCompose: string;\n\tdockerignore: string;\n\t/** Every image's build context, absolute. */\n\tbuildRoot: string;\n}\n\n/**\n * Generate Dockerfiles for all apps in a workspace.\n * @internal Exported for testing\n */\nexport async function workspaceDockerCommand(\n\tworkspace: NormalizedWorkspace,\n\t/** What the workspace declares — where a site's public keys come from. */\n\tmanifest?: ConstructManifest,\n\t/** Image names other than the app's own. */\n\timageNames: Readonly<Record<string, string>> = {},\n\t/** Each Worker's files, from discovery — which app each is built from. */\n\tbackground: Readonly<Record<string, readonly string[]>> = {},\n): Promise<WorkspaceDockerResult> {\n\tconst results: AppDockerResult[] = [];\n\tconst layout = imageLayout(workspace);\n\n\tlogger.log(`\\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);\n\tlogger.log(` Build root: ${layout.buildRoot}`);\n\tlogger.log(\n\t\t` Package manager: ${layout.tools.packageManager}${layout.tools.packageManagerVersion ? `@${layout.tools.packageManagerVersion}` : ''}, turbo@${layout.tools.turboVersion}`,\n\t);\n\n\tfor (const [appName, app] of Object.entries(workspace.apps)) {\n\t\t// Mobile apps deploy via their own toolchain (e.g. EAS Build for Expo)\n\t\t// — no Docker image is produced.\n\t\tif (app.type === 'mobile') {\n\t\t\tlogger.log(\n\t\t\t\t`\\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`,\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst buildType = app.entry ? 'entry' : app.type;\n\t\tlogger.log(`\\n 📄 Generating Dockerfile for ${appName} (${buildType})`);\n\n\t\tconst dockerfile = appDockerfile(appName, app, {\n\t\t\tlayout,\n\t\t\tworkspaceRoot: workspace.root,\n\t\t\tapps: workspace.apps,\n\t\t\t...(manifest ? { manifest } : {}),\n\t\t});\n\n\t\tconst path = dockerfileOf(appName, app.path);\n\t\tconst dockerfilePath = join(workspace.root, path);\n\t\tawait mkdir(dirname(dockerfilePath), { recursive: true });\n\t\tawait writeFile(dockerfilePath, dockerfile);\n\t\tlogger.log(` Generated: ${path}`);\n\n\t\tresults.push({\n\t\t\tappName,\n\t\t\ttype: app.type,\n\t\t\tdockerfile: dockerfilePath,\n\t\t\timageName: imageNames[appName] ?? appName,\n\t\t});\n\t}\n\n\t// Each Worker with background work: an image of its own, built from the\n\t// app whose build writes its entry.\n\tfor (const worker of manifest\n\t\t? workerUnits(workspace, manifest, background)\n\t\t: []) {\n\t\tlogger.log(\n\t\t\t`\\n 📄 Generating Dockerfile for ${worker.name} (worker ${worker.id}, built from ${worker.app})`,\n\t\t);\n\t\tconst dockerfile = workerDockerfile(worker, workspace.apps[worker.app]!, {\n\t\t\tlayout,\n\t\t\tworkspaceRoot: workspace.root,\n\t\t});\n\t\t// Always named for the worker: an app at the root has the bare\n\t\t// `Dockerfile`, which this must not overwrite.\n\t\tconst path = workerDockerfileOf(worker.name);\n\t\tconst dockerfilePath = join(workspace.root, path);\n\t\tawait mkdir(dirname(dockerfilePath), { recursive: true });\n\t\tawait writeFile(dockerfilePath, dockerfile);\n\t\tlogger.log(` Generated: ${path}`);\n\n\t\tresults.push({\n\t\t\tappName: worker.name,\n\t\t\ttype: 'backend',\n\t\t\tdockerfile: dockerfilePath,\n\t\t\timageName: imageNames[worker.name] ?? worker.name,\n\t\t});\n\t}\n\n\tconst dockerignorePath = await ensureDockerignore(layout.buildRoot);\n\tlogger.log(`\\n Ensured: ${dockerignorePath}`);\n\n\tconst composePath = await writeConstructsCompose(workspace);\n\n\t// Summary\n\tlogger.log(\n\t\t`\\n✅ Generated ${results.length} Dockerfile(s) + ${COMPOSE_PATH}`,\n\t);\n\tlogger.log(`\\n📋 Build commands (from ${layout.buildRoot}):`);\n\tfor (const result of results) {\n\t\tconst icon =\n\t\t\tresult.type === 'backend' ? '⚙️' : result.type === 'mobile' ? '📱' : '🌐';\n\t\tlogger.log(\n\t\t\t` ${icon} docker build -f ${relative(layout.buildRoot, result.dockerfile)} -t ${result.imageName} .`,\n\t\t);\n\t}\n\tprintRunInstructions(workspace);\n\n\treturn {\n\t\tapps: results,\n\t\tdockerCompose: composePath,\n\t\tdockerignore: dockerignorePath,\n\t\tbuildRoot: layout.buildRoot,\n\t};\n}\n\n/**\n * One app's Dockerfile, by what it is: a gkm backend bundled by `gkm build`,\n * a backend with its own entry bundled by esbuild, or a site by its\n * framework. The one choice every builder of images makes.\n */\nexport function appDockerfile(\n\tappName: string,\n\tapp: NormalizedAppConfig,\n\toptions: {\n\t\tlayout: ImageLayout;\n\t\tworkspaceRoot: string;\n\t\t/**\n\t\t * Every app in the workspace, so a site's image knows the backends whose\n\t\t * clients it generates.\n\t\t */\n\t\tapps?: Readonly<Record<string, NormalizedAppConfig>>;\n\t\tmanifest?: ConstructManifest;\n\t\t/** A site's public keys, when the caller resolved them itself. */\n\t\tpublicUrlArgs?: string[];\n\t\t/**\n\t\t * The cache backend a gkm backend's entry registers, where it is not the\n\t\t * deploy target's — the compose stack's own Redis.\n\t\t */\n\t\tcache?: CacheBackend;\n\t},\n): string {\n\tconst image = appImageOptions(\n\t\toptions.layout,\n\t\tappName,\n\t\tapp,\n\t\toptions.workspaceRoot,\n\t\toptions.apps,\n\t);\n\n\tif (app.type === 'web') {\n\t\treturn siteDockerfile(appName, app, {\n\t\t\timage,\n\t\t\t...(options.manifest ? { manifest: options.manifest } : {}),\n\t\t\t...(options.publicUrlArgs\n\t\t\t\t? { publicUrlArgs: options.publicUrlArgs }\n\t\t\t\t: {}),\n\t\t});\n\t}\n\tif (app.entry) {\n\t\treturn generateEntryDockerfile({\n\t\t\t...image,\n\t\t\tentry: app.entry,\n\t\t\thealthCheckPath: '/health',\n\t\t});\n\t}\n\treturn generateBackendDockerfile({\n\t\t...image,\n\t\thealthCheckPath: '/health',\n\t\t...(options.cache ? { cache: options.cache } : {}),\n\t});\n}\n\n/** Where `gkm docker` writes a worker's Dockerfile. */\nexport function workerDockerfileOf(worker: string): string {\n\treturn `.gkm/docker/Dockerfile.${worker}`;\n}\n\n/**\n * A Worker's Dockerfile: its host app's build, and the worker's bundle as the\n * runner — see `generateWorkerDockerfile`.\n */\nexport function workerDockerfile(\n\tworker: WorkerUnit,\n\thost: NormalizedAppConfig,\n\toptions: {\n\t\tlayout: ImageLayout;\n\t\tworkspaceRoot: string;\n\t\t/** As {@link appDockerfile}'s: the cache backend its entry registers. */\n\t\tcache?: CacheBackend;\n\t},\n): string {\n\tconst image = appImageOptions(\n\t\toptions.layout,\n\t\tworker.app,\n\t\thost,\n\t\toptions.workspaceRoot,\n\t);\n\treturn generateWorkerDockerfile({\n\t\t...image,\n\t\timageName: worker.name,\n\t\tport: WORKER_PORT,\n\t\tworker: worker.id,\n\t\tbundle: workerBundleName(worker.id),\n\t\thealthCheckPath: '/health',\n\t\t...(options.cache ? { cache: options.cache } : {}),\n\t});\n}\n\n/**\n * A site's Dockerfile, with an `ARG` for every key its bundler inlines.\n *\n * The keys come from the site's declaration where there is one — every\n * public key its edges give it, `VITE_UPLOADS_SERVER_URL` included — and an\n * `ARG` the Dockerfile does not declare is a build argument the build drops,\n * leaving the bundle with an empty URL.\n */\nexport function siteDockerfile(\n\tappName: string,\n\tapp: NormalizedAppConfig,\n\toptions: {\n\t\timage: ReturnType<typeof appImageOptions>;\n\t\tmanifest?: ConstructManifest;\n\t\tpublicUrlArgs?: string[];\n\t},\n): string {\n\tconst declared = options.manifest\n\t\t? Object.entries(options.manifest).find(\n\t\t\t\t([id, d]) => d.kind === 'site' && appKey(id) === appName,\n\t\t\t)?.[1]\n\t\t: undefined;\n\tconst publicUrlArgs =\n\t\toptions.publicUrlArgs ??\n\t\t(declared?.kind === 'site' && options.manifest\n\t\t\t? Object.keys(publicEnvFor(declared, options.manifest))\n\t\t\t: getPublicUrlArgNames(app));\n\n\tconst webOpts = { ...options.image, publicUrlArgs };\n\n\tswitch (app.framework) {\n\t\tcase 'vite':\n\t\t\treturn generateViteStaticDockerfile(webOpts);\n\t\tcase 'tanstack-start':\n\t\tcase 'remix':\n\t\t\treturn generateNodeWebDockerfile(webOpts);\n\t\tdefault:\n\t\t\t// nextjs (and any unspecified web framework — schema requires a valid\n\t\t\t// framework, so this is just a default).\n\t\t\treturn generateNextjsDockerfile(webOpts);\n\t}\n}\n\n/**\n * What a build root's `.dockerignore` must leave out of every image's\n * context: dependencies installed for another platform, anything built on the\n * host (every image builds from source), git, and gkm's own output —\n * `.gkm/compose`'s env files hold a stage's secrets.\n */\nconst REQUIRED_IGNORES = [\n\t'**/node_modules',\n\t'.git',\n\t'**/dist',\n\t'**/.next',\n\t'**/.turbo',\n\t'**/.gkm',\n\t'**/.gkm/compose',\n];\n\n/**\n * Make sure the build root's `.dockerignore` leaves out what no image may\n * hold: written whole where there is none, and each missing line appended to\n * one the project keeps.\n */\nexport async function ensureDockerignore(buildRoot: string): Promise<string> {\n\tconst path = join(buildRoot, '.dockerignore');\n\tif (!existsSync(path)) {\n\t\tawait writeFile(path, generateDockerignore());\n\t\treturn path;\n\t}\n\n\tconst current = await readFile(path, 'utf-8');\n\tconst lines = new Set(current.split(/\\r?\\n/).map((line) => line.trim()));\n\tconst missing = REQUIRED_IGNORES.filter((line) => !lines.has(line));\n\tif (missing.length === 0) return path;\n\n\tawait appendFile(\n\t\tpath,\n\t\t`${current.endsWith('\\n') || current === '' ? '' : '\\n'}\\n# gkm: images build from source, and a stack's env files hold its secrets\\n${missing.join('\\n')}\\n`,\n\t);\n\treturn path;\n}\n\n/**\n * Write `docker-compose.constructs.yml` — the same file `gkm dev` writes, with\n * the apps beside the containers their constructs derive.\n *\n * Through reconcile rather than a second generator, so what `gkm docker`\n * describes and what `gkm dev` runs cannot disagree. Nothing is started.\n */\nasync function writeConstructsCompose(\n\tworkspace: NormalizedWorkspace,\n): Promise<string> {\n\tawait reconcileWorkspace(workspace, {\n\t\tstage: workspace.stages.local,\n\t\tstart: false,\n\t});\n\tlogger.log(` Generated: ${COMPOSE_PATH} (project root)`);\n\treturn join(workspace.root, COMPOSE_PATH);\n}\n\nfunction printRunInstructions(workspace: NormalizedWorkspace): void {\n\tconst project = existsSync(join(workspace.root, 'docker-compose.yml'))\n\t\t? ' -f docker-compose.yml'\n\t\t: '';\n\tlogger.log('\\n📋 Run everything (the apps sit behind the \"apps\" profile):');\n\tlogger.log(\n\t\t' gkm setup # starts the containers and creates what they hold',\n\t);\n\tlogger.log(\n\t\t` docker compose -f ${COMPOSE_PATH}${project} --profile apps up --build`,\n\t);\n}\n","/**\n * The values a deployed stage generates once and keeps: what nothing can\n * provision and nobody else issues.\n *\n * - **A seed**, which salts every password a deploy derives — a database's\n * master and roles, a bucket's root user. Derived from repo facts alone\n * (project, stage, construct id), those passwords were computable by anyone\n * who could read the repo.\n * - **Each `secret` construct's value** — an auth server's signing secret —\n * random, rather than derived.\n * - **Each `encryption` construct's keyring** — one random key and an index\n * key. Later keys are added by `gkm encryption:rotate`, never by a deploy.\n *\n * Both are generated on the first deploy that needs them and written to the\n * stage's store, so every later deploy reads the same values: sessions survive\n * a redeploy, and a role's password does not move. A value set by hand with\n * `gkm secrets:set` is kept, never replaced.\n */\n\nimport { randomBytes } from 'node:crypto';\nimport { generateKeyring } from '@geekmidas/constructs/encryption';\nimport type { ConstructManifest } from '@geekmidas/manifest';\nimport type { StageSecrets } from '../secrets/types.js';\n\nexport interface GeneratedSecrets {\n\t/** The stage's secrets with everything it generates present. */\n\tsecrets: StageSecrets;\n\t/** What was generated this time, by key — empty when nothing was missing. */\n\tgenerated: string[];\n}\n\n/**\n * The stage's secrets with its seed and every declared `secret`'s value\n * present, generating what is missing. Pure: the caller writes the result\n * back to the stage's store when `generated` is not empty.\n */\nexport function withGeneratedSecrets(\n\tsecrets: StageSecrets,\n\tmanifest: ConstructManifest,\n): GeneratedSecrets {\n\tconst generated: string[] = [];\n\tconst custom = { ...secrets.custom };\n\n\tfor (const declaration of Object.values(manifest)) {\n\t\tif (declaration?.kind !== 'secret' && declaration?.kind !== 'encryption') {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst key = declaration.provides?.[0];\n\t\tif (!key || custom[key]) continue;\n\n\t\tcustom[key] =\n\t\t\tdeclaration.kind === 'encryption' ? generateKeyring() : random();\n\t\tgenerated.push(key);\n\t}\n\n\tconst seed = secrets.seed ?? random();\n\tif (!secrets.seed) generated.push('seed');\n\n\treturn {\n\t\tsecrets: generated.length\n\t\t\t? { ...secrets, seed, custom, updatedAt: new Date().toISOString() }\n\t\t\t: secrets,\n\t\tgenerated,\n\t};\n}\n\n/** 256 bits, URL-safe. */\nfunction random(): string {\n\treturn randomBytes(32).toString('base64url');\n}\n","/**\n * A deploy's record of what it is creating, written as it goes.\n *\n * Deploy used to write state once, after every app had deployed, so a run that\n * failed half way lost the ids of everything it had created and the next run\n * fell back to finding them by name — or made them again. The journal writes\n * through the stage's `StateStore` after every resource instead:\n *\n * 1. `pending`, before the call that creates it — \"this may now exist\";\n * 2. `ready` with the target's id, once the call has returned.\n *\n * A run that meets a `pending` record looks the resource up before it creates\n * anything, so a run killed between the create and the record adopts what the\n * dead run made instead of making a second one. Every write is conditional on\n * the version the journal last wrote, so a writer that skipped the stage's\n * lock raises `StateVersionConflict` here rather than being overwritten.\n */\n\nimport type { ResourceRecord, StateStore, StateVersion } from './StateStore';\nimport type { DokployStageState } from './state';\n\n/** One resource, as the journal keys and describes it. */\nexport interface JournalEntry {\n\t/** `<type>:<name>`, unique within the stage — `application:api`. */\n\tkey: string;\n\ttype: string;\n\t/** What a later run needs to find it again — its name, usually. */\n\tdata?: Record<string, unknown>;\n}\n\n/** How to get at one kind of resource on the target. */\nexport interface ResourceSteps<T> {\n\t/** By the id a `ready` record holds; `null` when the target lost it. */\n\tget?: (id: string) => Promise<T | null | undefined>;\n\t/** By what identifies it otherwise — its name, in its environment. */\n\tfind: () => Promise<T | null | undefined>;\n\tcreate: () => Promise<T>;\n\tid: (resource: T) => string;\n}\n\nexport interface Ensured<T> {\n\tresource: T;\n\t/**\n\t * - `recorded`: the id in a `ready` record still resolves\n\t * - `resumed`: a `pending` record's resource was found — a run died\n\t * between creating it and recording it\n\t * - `found`: it existed, unrecorded\n\t * - `created`: it did not exist\n\t */\n\tvia: 'recorded' | 'resumed' | 'found' | 'created';\n\t/** The recorded id that no longer resolved, if there was one. */\n\tstaleId?: string;\n}\n\nexport class DeployJournal {\n\tprivate constructor(\n\t\tprivate readonly store: StateStore,\n\t\treadonly stage: string,\n\t\t/** The stage's state, mutated by the deploy and written by `save`. */\n\t\treadonly state: DokployStageState,\n\t\tprivate resources: Record<string, ResourceRecord>,\n\t\tprivate version: StateVersion,\n\t\t/** Whether the stage had state before this run. */\n\t\treadonly existed: boolean,\n\t) {}\n\n\t/**\n\t * The stage's journal — its stored state, or `initial` written as the\n\t * stage's first state, so there is somewhere to record the very first\n\t * resource before it is created.\n\t */\n\tstatic async open(\n\t\tstore: StateStore,\n\t\tstage: string,\n\t\tinitial: () => DokployStageState,\n\t): Promise<DeployJournal> {\n\t\tconst stored = await store.read(stage);\n\t\tif (stored) {\n\t\t\treturn new DeployJournal(\n\t\t\t\tstore,\n\t\t\t\tstage,\n\t\t\t\tstored.state,\n\t\t\t\tstored.resources,\n\t\t\t\tstored.version,\n\t\t\t\ttrue,\n\t\t\t);\n\t\t}\n\n\t\tconst state = initial();\n\t\tconst version = await store.write(stage, state, { expectedVersion: null });\n\t\treturn new DeployJournal(store, stage, state, {}, version, false);\n\t}\n\n\trecord(key: string): ResourceRecord | undefined {\n\t\treturn this.resources[key];\n\t}\n\n\t/** The keys a previous run marked `pending` and never saw created. */\n\tunfinished(): string[] {\n\t\treturn Object.values(this.resources)\n\t\t\t.filter((record) => record.status === 'pending')\n\t\t\t.map((record) => record.key);\n\t}\n\n\t/** Records that `entry` is about to be created. */\n\tasync pending(entry: JournalEntry): Promise<void> {\n\t\tawait this.put({ ...entry, status: 'pending' });\n\t}\n\n\t/** Records `entry` as existing with `id`; a no-op if it already is. */\n\tasync ready(entry: JournalEntry, id: string): Promise<void> {\n\t\tconst current = this.resources[entry.key];\n\t\tif (current?.status === 'ready' && current.id === id) return;\n\t\tawait this.put({ ...entry, status: 'ready', id });\n\t}\n\n\t/**\n\t * The resource `entry` names: by its recorded id, else by looking it up,\n\t * else created — with `pending` written before the create and `ready`\n\t * after, so a run that dies in between leaves a record the next run\n\t * resolves by looking the resource up rather than creating another.\n\t */\n\tasync ensure<T>(\n\t\tentry: JournalEntry,\n\t\tsteps: ResourceSteps<T>,\n\t): Promise<Ensured<T>> {\n\t\tconst record = this.resources[entry.key];\n\t\tlet staleId: string | undefined;\n\n\t\tif (record?.status === 'ready' && record.id && steps.get) {\n\t\t\tconst known = await steps.get(record.id);\n\t\t\tif (known) {\n\t\t\t\tawait this.ready(entry, steps.id(known));\n\t\t\t\treturn { resource: known, via: 'recorded' };\n\t\t\t}\n\t\t\tstaleId = record.id;\n\t\t}\n\n\t\tconst existing = await steps.find();\n\t\tif (existing) {\n\t\t\tawait this.ready(entry, steps.id(existing));\n\t\t\treturn {\n\t\t\t\tresource: existing,\n\t\t\t\tvia: record?.status === 'pending' ? 'resumed' : 'found',\n\t\t\t\t...(staleId ? { staleId } : {}),\n\t\t\t};\n\t\t}\n\n\t\tawait this.pending(entry);\n\t\tconst created = await steps.create();\n\t\tawait this.ready(entry, steps.id(created));\n\t\treturn {\n\t\t\tresource: created,\n\t\t\tvia: 'created',\n\t\t\t...(staleId ? { staleId } : {}),\n\t\t};\n\t}\n\n\t/** Writes the stage's state, keeping its resource records. */\n\tasync save(): Promise<void> {\n\t\tthis.state.lastDeployedAt = new Date().toISOString();\n\t\tthis.version = await this.store.write(this.stage, this.state, {\n\t\t\texpectedVersion: this.version,\n\t\t});\n\t}\n\n\tprivate async put(record: Omit<ResourceRecord, 'updatedAt'>): Promise<void> {\n\t\tthis.version = await this.store.putResource(this.stage, record, {\n\t\t\texpectedVersion: this.version,\n\t\t});\n\t\tthis.resources = {\n\t\t\t...this.resources,\n\t\t\t[record.key]: { ...record, updatedAt: new Date().toISOString() },\n\t\t};\n\t}\n}\n","/**\n * Deploy state management for Dokploy deployments\n *\n * Stores resource IDs (applications, services) per stage to avoid\n * re-creating resources on subsequent deploys.\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { output } from '../output';\n\n/**\n * Per-app database credentials\n */\nexport interface AppDbCredentials {\n\tdbUser: string;\n\tdbPassword: string;\n}\n\n/**\n * DNS verification record for a hostname\n */\nexport interface DnsVerificationRecord {\n\tserverIp: string;\n\tverifiedAt: string;\n}\n\n/**\n * A DNS record that was created during deploy\n */\nexport interface CreatedDnsRecord {\n\t/** The domain this record belongs to (e.g., 'example.com') */\n\tdomain: string;\n\t/** Record name/subdomain (e.g., 'api' or '@' for root) */\n\tname: string;\n\t/** Record type (A, CNAME, etc.) */\n\ttype: string;\n\t/** Record value (IP address, hostname, etc.) */\n\tvalue: string;\n\t/** TTL in seconds */\n\tttl: number;\n\t/** When this record was created */\n\tcreatedAt: string;\n}\n\n/**\n * Backup destination state\n */\nexport interface BackupState {\n\t/** S3 bucket name for backups */\n\tbucketName: string;\n\t/** S3 bucket ARN */\n\tbucketArn: string;\n\t/** IAM user name created for backup access */\n\tiamUserName: string;\n\t/** IAM access key ID */\n\tiamAccessKeyId: string;\n\t/** IAM secret access key */\n\tiamSecretAccessKey: string;\n\t/** Dokploy destination ID */\n\tdestinationId: string;\n\t/** Dokploy backup schedule ID for postgres (if configured) */\n\tpostgresBackupId?: string;\n\t/** AWS region where bucket was created */\n\tregion: string;\n\t/** Timestamp when backup was configured */\n\tcreatedAt: string;\n}\n\n/**\n * State for a single stage deployment\n */\nexport interface DokployStageState {\n\tprovider: 'dokploy';\n\tstage: string;\n\t/** Dokploy project ID - created on first deploy */\n\tprojectId: string;\n\tenvironmentId: string;\n\tapplications: Record<string, string>; // appName -> applicationId\n\tservices: {\n\t\tpostgresId?: string;\n\t\tredisId?: string;\n\t};\n\t/** Per-app database credentials for reuse on subsequent deploys */\n\tappCredentials?: Record<string, AppDbCredentials>;\n\t/** Auto-generated secrets per app (e.g., BETTER_AUTH_SECRET) */\n\tgeneratedSecrets?: Record<string, Record<string, string>>;\n\t/** DNS verification state per hostname */\n\tdnsVerified?: Record<string, DnsVerificationRecord>;\n\t/** DNS records created during deploy (keyed by \"name:type\", e.g., \"api:A\") */\n\tdnsRecords?: Record<string, CreatedDnsRecord>;\n\t/** Backup destination state */\n\tbackups?: BackupState;\n\t/**\n\t * The deploy identity's key (`<namespace>/<project>`) that last deployed\n\t * this stage — what the project's ownership marker says.\n\t */\n\tidentity?: string;\n\t/**\n\t * The Dokploy registry the stage's images are pulled through. Kept per\n\t * stage rather than once per machine, so a stage keeps the registry it was\n\t * deployed with whoever deploys it next.\n\t */\n\tregistryId?: string;\n\t/**\n\t * Each app's releases, keyed by app name: what runs now, what ran before\n\t * it — which `gkm deploy:rollback` restores — and the history behind both.\n\t */\n\treleases?: Record<string, AppReleases>;\n\tlastDeployedAt: string;\n}\n\n/** An image a stage runs: the ref it was pushed as, and what it resolved to. */\nexport interface DeployedImage {\n\tref: string;\n\t/**\n\t * The tag it was released under — a site's carries its stage\n\t * (`v1.4.0-production`). Recorded by targets that pull a release by tag.\n\t */\n\ttag?: string;\n\t/** The registry's digest, `sha256:…` — absent if it could not be read. */\n\tdigest?: string;\n}\n\n/**\n * Get the state file path for a stage\n */\nfunction getStateFilePath(workspaceRoot: string, stage: string): string {\n\treturn join(workspaceRoot, '.gkm', `deploy-${stage}.json`);\n}\n\n/**\n * Read the deploy state for a stage\n * Returns null if state file doesn't exist\n */\nexport async function readStageState(\n\tworkspaceRoot: string,\n\tstage: string,\n): Promise<DokployStageState | null> {\n\tconst filePath = getStateFilePath(workspaceRoot, stage);\n\n\ttry {\n\t\tconst content = await readFile(filePath, 'utf-8');\n\t\treturn JSON.parse(content) as DokployStageState;\n\t} catch (error) {\n\t\t// File doesn't exist or is invalid - return null\n\t\tif ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\treturn null;\n\t\t}\n\t\t// Log other errors but don't fail\n\t\toutput.warn(`Warning: Could not read deploy state: ${error}`);\n\t\treturn null;\n\t}\n}\n\n/**\n * Write the deploy state for a stage\n */\nexport async function writeStageState(\n\tworkspaceRoot: string,\n\tstage: string,\n\tstate: DokployStageState,\n): Promise<void> {\n\tconst filePath = getStateFilePath(workspaceRoot, stage);\n\tconst dir = join(workspaceRoot, '.gkm');\n\n\t// Ensure .gkm directory exists\n\tawait mkdir(dir, { recursive: true });\n\n\t// Update last deployed timestamp\n\tstate.lastDeployedAt = new Date().toISOString();\n\n\tawait writeFile(filePath, JSON.stringify(state, null, 2));\n}\n\n/**\n * Create a new empty state for a stage\n */\nexport function createEmptyState(\n\tstage: string,\n\tprojectId: string,\n\tenvironmentId: string,\n): DokployStageState {\n\treturn {\n\t\tprovider: 'dokploy',\n\t\tstage,\n\t\tprojectId,\n\t\tenvironmentId,\n\t\tapplications: {},\n\t\tservices: {},\n\t\tlastDeployedAt: new Date().toISOString(),\n\t};\n}\n\n/**\n * Get application ID from state\n */\nexport function getApplicationId(\n\tstate: DokployStageState | null,\n\tappName: string,\n): string | undefined {\n\treturn state?.applications[appName];\n}\n\n/**\n * Set application ID in state (mutates state)\n */\nexport function setApplicationId(\n\tstate: DokployStageState,\n\tappName: string,\n\tapplicationId: string,\n): void {\n\tstate.applications[appName] = applicationId;\n}\n\n/** An image that went live, and when. */\nexport interface ReleasedImage extends DeployedImage {\n\t/** The image tag the deploy released under. */\n\ttag?: string;\n\treleasedAt: string;\n\t/** Set when a rollback replaced it: it is never rolled back *to*. */\n\trolledBack?: true;\n}\n\n/** An app's releases: what runs, what ran before it, and what ran before that. */\nexport interface AppReleases {\n\tcurrent: ReleasedImage;\n\t/** What a rollback restores; absent until a second release. */\n\tprevious?: ReleasedImage;\n\t/** Every release, newest first, at most {@link RELEASE_HISTORY} of them. */\n\thistory: ReleasedImage[];\n}\n\n/** How many releases an app's history keeps. */\nexport const RELEASE_HISTORY = 10;\n\n/** Two records naming the same image: by digest when both have one. */\nfunction sameImage(a: DeployedImage, b: DeployedImage): boolean {\n\treturn a.digest && b.digest ? a.digest === b.digest : a.ref === b.ref;\n}\n\n/** The newest release in `history` after `index` that a rollback may restore. */\nfunction restorable(\n\thistory: readonly ReleasedImage[],\n\tindex: number,\n\tcurrent: DeployedImage,\n): ReleasedImage | undefined {\n\treturn history\n\t\t.slice(index + 1)\n\t\t.find((entry) => !entry.rolledBack && !sameImage(entry, current));\n}\n\n/**\n * Record that `image` went live for `appName` (mutates state): it becomes\n * `current`, and what was current becomes `previous`. Releasing what already\n * runs — a redeploy of the same digest — changes nothing.\n */\nexport function recordRelease(\n\tstate: DokployStageState,\n\tappName: string,\n\timage: Omit<ReleasedImage, 'releasedAt' | 'rolledBack'>,\n\treleasedAt: Date = new Date(),\n): void {\n\tconst releases = state.releases?.[appName];\n\tif (releases && sameImage(releases.current, image)) return;\n\n\tconst entry: ReleasedImage = {\n\t\t...image,\n\t\treleasedAt: releasedAt.toISOString(),\n\t};\n\tconst history = [entry, ...(releases?.history ?? [])].slice(\n\t\t0,\n\t\tRELEASE_HISTORY,\n\t);\n\tconst previous = releases?.current;\n\tstate.releases = {\n\t\t...state.releases,\n\t\t[appName]: { current: entry, ...(previous ? { previous } : {}), history },\n\t};\n}\n\n/**\n * Record that `appName` was rolled back to `to` (mutates state): every\n * release newer than it is marked rolled back, so neither this rollback nor\n * a later one restores it, and `previous` moves to the release before `to`.\n */\nexport function recordRollback(\n\tstate: DokployStageState,\n\tappName: string,\n\tto: DeployedImage,\n): void {\n\tconst releases = state.releases?.[appName];\n\tif (!releases) return;\n\n\tconst index = releases.history.findIndex(\n\t\t(entry) => !entry.rolledBack && sameImage(entry, to),\n\t);\n\tif (index === -1) return;\n\n\tconst history = releases.history.map((entry, i) =>\n\t\ti < index ? { ...entry, rolledBack: true as const } : entry,\n\t);\n\tconst current = history[index]!;\n\tconst previous = restorable(history, index, current);\n\tstate.releases = {\n\t\t...state.releases,\n\t\t[appName]: { current, ...(previous ? { previous } : {}), history },\n\t};\n}\n\n/**\n * Get postgres ID from state\n */\nexport function getPostgresId(\n\tstate: DokployStageState | null,\n): string | undefined {\n\treturn state?.services.postgresId;\n}\n\n/**\n * Set postgres ID in state (mutates state)\n */\nexport function setPostgresId(\n\tstate: DokployStageState,\n\tpostgresId: string,\n): void {\n\tstate.services.postgresId = postgresId;\n}\n\n/**\n * Get redis ID from state\n */\nexport function getRedisId(\n\tstate: DokployStageState | null,\n): string | undefined {\n\treturn state?.services.redisId;\n}\n\n/**\n * Set redis ID in state (mutates state)\n */\nexport function setRedisId(state: DokployStageState, redisId: string): void {\n\tstate.services.redisId = redisId;\n}\n\n/**\n * Get app credentials from state\n */\nexport function getAppCredentials(\n\tstate: DokployStageState | null,\n\tappName: string,\n): AppDbCredentials | undefined {\n\treturn state?.appCredentials?.[appName];\n}\n\n/**\n * Set app credentials in state (mutates state)\n */\nexport function setAppCredentials(\n\tstate: DokployStageState,\n\tappName: string,\n\tcredentials: AppDbCredentials,\n): void {\n\tif (!state.appCredentials) {\n\t\tstate.appCredentials = {};\n\t}\n\tstate.appCredentials[appName] = credentials;\n}\n\n/**\n * Get all app credentials from state\n */\nexport function getAllAppCredentials(\n\tstate: DokployStageState | null,\n): Record<string, AppDbCredentials> {\n\treturn state?.appCredentials ?? {};\n}\n\n// ============================================================================\n// Generated Secrets\n// ============================================================================\n\n/**\n * Get a generated secret for an app\n */\nexport function getGeneratedSecret(\n\tstate: DokployStageState | null,\n\tappName: string,\n\tsecretName: string,\n): string | undefined {\n\treturn state?.generatedSecrets?.[appName]?.[secretName];\n}\n\n/**\n * Set a generated secret for an app (mutates state)\n */\nexport function setGeneratedSecret(\n\tstate: DokployStageState,\n\tappName: string,\n\tsecretName: string,\n\tvalue: string,\n): void {\n\tif (!state.generatedSecrets) {\n\t\tstate.generatedSecrets = {};\n\t}\n\tif (!state.generatedSecrets[appName]) {\n\t\tstate.generatedSecrets[appName] = {};\n\t}\n\tstate.generatedSecrets[appName][secretName] = value;\n}\n\n/**\n * Get all generated secrets for an app\n */\nexport function getAppGeneratedSecrets(\n\tstate: DokployStageState | null,\n\tappName: string,\n): Record<string, string> {\n\treturn state?.generatedSecrets?.[appName] ?? {};\n}\n\n/**\n * Get all generated secrets from state\n */\nexport function getAllGeneratedSecrets(\n\tstate: DokployStageState | null,\n): Record<string, Record<string, string>> {\n\treturn state?.generatedSecrets ?? {};\n}\n\n// ============================================================================\n// DNS Verification\n// ============================================================================\n\n/**\n * Get DNS verification record for a hostname\n */\nexport function getDnsVerification(\n\tstate: DokployStageState | null,\n\thostname: string,\n): DnsVerificationRecord | undefined {\n\treturn state?.dnsVerified?.[hostname];\n}\n\n/**\n * Set DNS verification record for a hostname (mutates state)\n */\nexport function setDnsVerification(\n\tstate: DokployStageState,\n\thostname: string,\n\tserverIp: string,\n): void {\n\tif (!state.dnsVerified) {\n\t\tstate.dnsVerified = {};\n\t}\n\tstate.dnsVerified[hostname] = {\n\t\tserverIp,\n\t\tverifiedAt: new Date().toISOString(),\n\t};\n}\n\n/**\n * Check if a hostname is already verified with the given IP\n */\nexport function isDnsVerified(\n\tstate: DokployStageState | null,\n\thostname: string,\n\tserverIp: string,\n): boolean {\n\tconst record = state?.dnsVerified?.[hostname];\n\treturn record?.serverIp === serverIp;\n}\n\n/**\n * Get all DNS verification records from state\n */\nexport function getAllDnsVerifications(\n\tstate: DokployStageState | null,\n): Record<string, DnsVerificationRecord> {\n\treturn state?.dnsVerified ?? {};\n}\n\n// ============================================================================\n// DNS Records\n// ============================================================================\n\n/**\n * Get the key for a DNS record in state\n */\nfunction getDnsRecordKey(name: string, type: string): string {\n\treturn `${name}:${type}`;\n}\n\n/**\n * Get a created DNS record from state\n */\nexport function getDnsRecord(\n\tstate: DokployStageState | null,\n\tname: string,\n\ttype: string,\n): CreatedDnsRecord | undefined {\n\treturn state?.dnsRecords?.[getDnsRecordKey(name, type)];\n}\n\n/**\n * Set a created DNS record in state (mutates state)\n */\nexport function setDnsRecord(\n\tstate: DokployStageState,\n\trecord: Omit<CreatedDnsRecord, 'createdAt'>,\n): void {\n\tif (!state.dnsRecords) {\n\t\tstate.dnsRecords = {};\n\t}\n\tconst key = getDnsRecordKey(record.name, record.type);\n\tstate.dnsRecords[key] = {\n\t\t...record,\n\t\tcreatedAt: new Date().toISOString(),\n\t};\n}\n\n/**\n * Remove a DNS record from state (mutates state)\n */\nexport function removeDnsRecord(\n\tstate: DokployStageState,\n\tname: string,\n\ttype: string,\n): void {\n\tif (state.dnsRecords) {\n\t\tdelete state.dnsRecords[getDnsRecordKey(name, type)];\n\t}\n}\n\n/**\n * Get all created DNS records from state\n */\nexport function getAllDnsRecords(\n\tstate: DokployStageState | null,\n): CreatedDnsRecord[] {\n\tif (!state?.dnsRecords) {\n\t\treturn [];\n\t}\n\treturn Object.values(state.dnsRecords);\n}\n\n/**\n * Clear all DNS records from state (mutates state)\n */\nexport function clearDnsRecords(state: DokployStageState): void {\n\tstate.dnsRecords = {};\n\tstate.dnsVerified = {};\n}\n\n// ============================================================================\n// Backup State\n// ============================================================================\n\n/**\n * Get backup state from state\n */\nexport function getBackupState(\n\tstate: DokployStageState | null,\n): BackupState | undefined {\n\treturn state?.backups;\n}\n\n/**\n * Set backup state (mutates state)\n */\nexport function setBackupState(\n\tstate: DokployStageState,\n\tbackupState: BackupState,\n): void {\n\tstate.backups = backupState;\n}\n\n/**\n * Get backup destination ID from state\n */\nexport function getBackupDestinationId(\n\tstate: DokployStageState | null,\n): string | undefined {\n\treturn state?.backups?.destinationId;\n}\n\n/**\n * Get postgres backup ID from state\n */\nexport function getPostgresBackupId(\n\tstate: DokployStageState | null,\n): string | undefined {\n\treturn state?.backups?.postgresBackupId;\n}\n\n/**\n * Set postgres backup ID in state (mutates state)\n */\nexport function setPostgresBackupId(\n\tstate: DokployStageState,\n\tbackupId: string,\n): void {\n\tif (state.backups) {\n\t\tstate.backups.postgresBackupId = backupId;\n\t}\n}\n"],"mappings":"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"}