@geekmidas/cli 10.0.0-alpha.72 → 10.0.0-alpha.74

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 (197) hide show
  1. package/dist/{OpenApiTsGenerator-BlP53kf2.cjs → EndpointGenerator-CP6sBvQV.cjs} +4 -510
  2. package/dist/EndpointGenerator-CP6sBvQV.cjs.map +1 -0
  3. package/dist/{OpenApiTsGenerator-Dv0gT2DL.mjs → EndpointGenerator-DftalLYg.mjs} +3 -509
  4. package/dist/EndpointGenerator-DftalLYg.mjs.map +1 -0
  5. package/dist/{LegacyStateStore-NSHEinQi.mjs → LegacyStateStore-DTo7z924.mjs} +2 -2
  6. package/dist/{LegacyStateStore-NSHEinQi.mjs.map → LegacyStateStore-DTo7z924.mjs.map} +1 -1
  7. package/dist/{LegacyStateStore-Dd5IwiyC.cjs → LegacyStateStore-D_78eaHM.cjs} +2 -2
  8. package/dist/{LegacyStateStore-Dd5IwiyC.cjs.map → LegacyStateStore-D_78eaHM.cjs.map} +1 -1
  9. package/dist/{LocalStateStore-DTylG4T4.cjs → LocalStateStore-BhrlQIXD.cjs} +2 -2
  10. package/dist/{LocalStateStore-DTylG4T4.cjs.map → LocalStateStore-BhrlQIXD.cjs.map} +1 -1
  11. package/dist/{LocalStateStore-DUoG2wA_.mjs → LocalStateStore-CNjgkRk4.mjs} +2 -2
  12. package/dist/{LocalStateStore-DUoG2wA_.mjs.map → LocalStateStore-CNjgkRk4.mjs.map} +1 -1
  13. package/dist/OpenApiTsGenerator-CKBCckKz.cjs +514 -0
  14. package/dist/OpenApiTsGenerator-CKBCckKz.cjs.map +1 -0
  15. package/dist/OpenApiTsGenerator-nu1r1NgB.mjs +509 -0
  16. package/dist/OpenApiTsGenerator-nu1r1NgB.mjs.map +1 -0
  17. package/dist/{S3StateStore-Dxob3PNA.mjs → S3StateStore-g2P7T_Mo.mjs} +2 -2
  18. package/dist/{S3StateStore-Dxob3PNA.mjs.map → S3StateStore-g2P7T_Mo.mjs.map} +1 -1
  19. package/dist/{S3StateStore-D7hrU-sy.cjs → S3StateStore-oCxRs6HM.cjs} +2 -2
  20. package/dist/{S3StateStore-D7hrU-sy.cjs.map → S3StateStore-oCxRs6HM.cjs.map} +1 -1
  21. package/dist/{SSMStateStore-Dvl4A8Up.mjs → SSMStateStore-CYenunmK.mjs} +2 -2
  22. package/dist/{SSMStateStore-Dvl4A8Up.mjs.map → SSMStateStore-CYenunmK.mjs.map} +1 -1
  23. package/dist/{SSMStateStore-DGIKJ4NN.cjs → SSMStateStore-DXp0m7XI.cjs} +2 -2
  24. package/dist/{SSMStateStore-DGIKJ4NN.cjs.map → SSMStateStore-DXp0m7XI.cjs.map} +1 -1
  25. package/dist/SubscriberGenerator-5looQ3gB.mjs +448 -0
  26. package/dist/SubscriberGenerator-5looQ3gB.mjs.map +1 -0
  27. package/dist/SubscriberGenerator-CajwOa5W.cjs +489 -0
  28. package/dist/SubscriberGenerator-CajwOa5W.cjs.map +1 -0
  29. package/dist/{add-XTLU19_E.mjs → add-BTH7WaH3.mjs} +4 -4
  30. package/dist/{add-XTLU19_E.mjs.map → add-BTH7WaH3.mjs.map} +1 -1
  31. package/dist/{add-DeC2Kyyv.cjs → add-Djrr12Xc.cjs} +4 -4
  32. package/dist/{add-DeC2Kyyv.cjs.map → add-Djrr12Xc.cjs.map} +1 -1
  33. package/dist/{bundler-CNTVLCCA.mjs → bundler-BUojOkof.mjs} +6 -6
  34. package/dist/bundler-BUojOkof.mjs.map +1 -0
  35. package/dist/{bundler-CEaR6Dpb.cjs → bundler-utHuESvL.cjs} +6 -6
  36. package/dist/bundler-utHuESvL.cjs.map +1 -0
  37. package/dist/{cli-MMgBrHx7.cjs → cli-Bx0sUzxH.cjs} +4 -4
  38. package/dist/{cli-MMgBrHx7.cjs.map → cli-Bx0sUzxH.cjs.map} +1 -1
  39. package/dist/{cli-D1arS0p1.mjs → cli-CyYceKTS.mjs} +4 -4
  40. package/dist/{cli-D1arS0p1.mjs.map → cli-CyYceKTS.mjs.map} +1 -1
  41. package/dist/{compose-T3hnnjKZ.mjs → compose-DZp8DJ0A.mjs} +419 -70
  42. package/dist/compose-DZp8DJ0A.mjs.map +1 -0
  43. package/dist/{compose-B-MME-4C.cjs → compose-iuw8wfHk.cjs} +420 -71
  44. package/dist/compose-iuw8wfHk.cjs.map +1 -0
  45. package/dist/{config-j9zBKaEG.d.cts → config-B38YWicj.d.cts} +3 -3
  46. package/dist/{config-BN6iXvyR.d.mts.map → config-B38YWicj.d.cts.map} +1 -1
  47. package/dist/{config-BN6iXvyR.d.mts → config-DA0kRTQe.d.mts} +3 -3
  48. package/dist/{config-j9zBKaEG.d.cts.map → config-DA0kRTQe.d.mts.map} +1 -1
  49. package/dist/config.cjs +4 -1
  50. package/dist/config.cjs.map +1 -1
  51. package/dist/config.d.cts +3 -3
  52. package/dist/config.d.mts +3 -3
  53. package/dist/config.mjs +4 -1
  54. package/dist/config.mjs.map +1 -1
  55. package/dist/{credentialSchemas-BmYxnEz_.mjs → credentialSchemas-BJqRJNK4.mjs} +2 -2
  56. package/dist/{credentialSchemas-BmYxnEz_.mjs.map → credentialSchemas-BJqRJNK4.mjs.map} +1 -1
  57. package/dist/{credentialSchemas-BcNL1N5R.cjs → credentialSchemas-DCR4Wd1W.cjs} +2 -2
  58. package/dist/{credentialSchemas-BcNL1N5R.cjs.map → credentialSchemas-DCR4Wd1W.cjs.map} +1 -1
  59. package/dist/deploy/public.cjs +7 -7
  60. package/dist/deploy/public.d.cts +4 -4
  61. package/dist/deploy/public.d.cts.map +1 -1
  62. package/dist/deploy/public.d.mts +4 -4
  63. package/dist/deploy/public.d.mts.map +1 -1
  64. package/dist/deploy/public.mjs +7 -7
  65. package/dist/deploy/sniffer-routes-worker.cjs +2 -2
  66. package/dist/deploy/sniffer-routes-worker.mjs +2 -2
  67. package/dist/{deploy-ClC53rc6.mjs → deploy-C17K7Imy.mjs} +12 -6
  68. package/dist/deploy-C17K7Imy.mjs.map +1 -0
  69. package/dist/{deploy-CZE0m6jd.cjs → deploy-eWorhvQp.cjs} +12 -6
  70. package/dist/deploy-eWorhvQp.cjs.map +1 -0
  71. package/dist/{discover-Buwf1z4P.mjs → discover-BPelzeGW.mjs} +21 -2
  72. package/dist/discover-BPelzeGW.mjs.map +1 -0
  73. package/dist/{discover-B_IkPukx.d.cts → discover-CU0paUyH.d.cts} +10 -1
  74. package/dist/discover-CU0paUyH.d.cts.map +1 -0
  75. package/dist/{discover-B4VjaFJk.cjs → discover-D1It5r_h.cjs} +21 -2
  76. package/dist/discover-D1It5r_h.cjs.map +1 -0
  77. package/dist/{discover-B6yPpvRV.d.mts → discover-DFiJxwfv.d.mts} +10 -1
  78. package/dist/discover-DFiJxwfv.d.mts.map +1 -0
  79. package/dist/{dokploy-D1UwQKm3.mjs → dokploy-DKa49Nqm.mjs} +2 -2
  80. package/dist/{dokploy-D1UwQKm3.mjs.map → dokploy-DKa49Nqm.mjs.map} +1 -1
  81. package/dist/{dokploy-lwmH05zr.cjs → dokploy-DKzcI-RL.cjs} +2 -2
  82. package/dist/{dokploy-lwmH05zr.cjs.map → dokploy-DKzcI-RL.cjs.map} +1 -1
  83. package/dist/drivers-BdvtCKc7.cjs +200 -0
  84. package/dist/drivers-BdvtCKc7.cjs.map +1 -0
  85. package/dist/drivers-Ch1rJitn.mjs +177 -0
  86. package/dist/drivers-Ch1rJitn.mjs.map +1 -0
  87. package/dist/{encryption-D05sNwED.cjs → encryption-DSkJdoyk.cjs} +2 -2
  88. package/dist/{encryption-D05sNwED.cjs.map → encryption-DSkJdoyk.cjs.map} +1 -1
  89. package/dist/{encryption-DcrxWl1s.mjs → encryption-sTMz3aWi.mjs} +2 -2
  90. package/dist/{encryption-DcrxWl1s.mjs.map → encryption-sTMz3aWi.mjs.map} +1 -1
  91. package/dist/{engine-DmQS3XHX.mjs → engine-B1b4I34Z.mjs} +117 -17
  92. package/dist/engine-B1b4I34Z.mjs.map +1 -0
  93. package/dist/{engine-Cry1vwMz.cjs → engine-Bh76sAHy.cjs} +117 -17
  94. package/dist/engine-Bh76sAHy.cjs.map +1 -0
  95. package/dist/{github-BchWoiVx.cjs → github-BvN3CfxM.cjs} +2 -2
  96. package/dist/{github-BchWoiVx.cjs.map → github-BvN3CfxM.cjs.map} +1 -1
  97. package/dist/{github-CqYOP8cI.mjs → github-C9ODoyZU.mjs} +2 -2
  98. package/dist/{github-CqYOP8cI.mjs.map → github-C9ODoyZU.mjs.map} +1 -1
  99. package/dist/{index-DvoxuTqR.d.mts → index-B-1lLGzy.d.mts} +2 -2
  100. package/dist/{index-Bk3NY6DN.d.cts.map → index-B-1lLGzy.d.mts.map} +1 -1
  101. package/dist/{index-Bk3NY6DN.d.cts → index-vMidfqhU.d.cts} +2 -2
  102. package/dist/{index-DvoxuTqR.d.mts.map → index-vMidfqhU.d.cts.map} +1 -1
  103. package/dist/index.cjs +130 -473
  104. package/dist/index.cjs.map +1 -1
  105. package/dist/index.mjs +122 -465
  106. package/dist/index.mjs.map +1 -1
  107. package/dist/{migrate-qKeGYq21.cjs → migrate-B0FCZNkp.cjs} +3 -3
  108. package/dist/{migrate-qKeGYq21.cjs.map → migrate-B0FCZNkp.cjs.map} +1 -1
  109. package/dist/{migrate-guck0NJK.mjs → migrate-CTtJhGV9.mjs} +3 -3
  110. package/dist/{migrate-guck0NJK.mjs.map → migrate-CTtJhGV9.mjs.map} +1 -1
  111. package/dist/openapi.cjs +3 -2
  112. package/dist/openapi.cjs.map +1 -1
  113. package/dist/openapi.d.cts +1 -1
  114. package/dist/openapi.d.mts +1 -1
  115. package/dist/openapi.mjs +2 -1
  116. package/dist/openapi.mjs.map +1 -1
  117. package/dist/ready-BB9xS4S9.cjs +402 -0
  118. package/dist/ready-BB9xS4S9.cjs.map +1 -0
  119. package/dist/ready-ZZoNskrr.mjs +373 -0
  120. package/dist/ready-ZZoNskrr.mjs.map +1 -0
  121. package/dist/reconcile/public.cjs +1 -1
  122. package/dist/reconcile/public.d.cts +2 -2
  123. package/dist/reconcile/public.d.mts +2 -2
  124. package/dist/reconcile/public.mjs +1 -1
  125. package/dist/{resolve-BsXEXAUz.mjs → resolve-DSAXj_47.mjs} +3 -3
  126. package/dist/{resolve-BsXEXAUz.mjs.map → resolve-DSAXj_47.mjs.map} +1 -1
  127. package/dist/{resolve-wEjEvbJ5.cjs → resolve-D_BU-wKA.cjs} +3 -3
  128. package/dist/{resolve-wEjEvbJ5.cjs.map → resolve-D_BU-wKA.cjs.map} +1 -1
  129. package/dist/{rollback-Bk91csFg.mjs → rollback-CWYTUhfW.mjs} +4 -4
  130. package/dist/{rollback-Bk91csFg.mjs.map → rollback-CWYTUhfW.mjs.map} +1 -1
  131. package/dist/{rollback-DmcU-efZ.cjs → rollback-DJy3t696.cjs} +4 -4
  132. package/dist/{rollback-DmcU-efZ.cjs.map → rollback-DJy3t696.cjs.map} +1 -1
  133. package/dist/{runtime-DHJhe4QI.d.mts → runtime-BzZD5HHi.d.cts} +2 -2
  134. package/dist/{runtime-CYAO4iZG.d.cts.map → runtime-BzZD5HHi.d.cts.map} +1 -1
  135. package/dist/{runtime-CYAO4iZG.d.cts → runtime-Cm6J6L7n.d.mts} +2 -2
  136. package/dist/{runtime-DHJhe4QI.d.mts.map → runtime-Cm6J6L7n.d.mts.map} +1 -1
  137. package/dist/sandbox/credentials-worker.cjs +2 -2
  138. package/dist/sandbox/credentials-worker.mjs +2 -2
  139. package/dist/sandbox/discover-worker.cjs +6 -3
  140. package/dist/sandbox/discover-worker.cjs.map +1 -1
  141. package/dist/sandbox/discover-worker.mjs +6 -3
  142. package/dist/sandbox/discover-worker.mjs.map +1 -1
  143. package/dist/sandbox/migrate-worker.cjs +1 -1
  144. package/dist/sandbox/migrate-worker.mjs +1 -1
  145. package/dist/{sniffer-vAXsU_4G.mjs → sniffer-BFmdZpZ2.mjs} +6 -6
  146. package/dist/{sniffer-vAXsU_4G.mjs.map → sniffer-BFmdZpZ2.mjs.map} +1 -1
  147. package/dist/{sniffer-DpHpFZBD.cjs → sniffer-C-T7dSMN.cjs} +6 -6
  148. package/dist/{sniffer-DpHpFZBD.cjs.map → sniffer-C-T7dSMN.cjs.map} +1 -1
  149. package/dist/{state-CCuTJNRH.cjs → state-DCwRWyh-.cjs} +954 -29
  150. package/dist/state-DCwRWyh-.cjs.map +1 -0
  151. package/dist/{state-BJFherj1.mjs → state-DpkxSwzI.mjs} +889 -24
  152. package/dist/state-DpkxSwzI.mjs.map +1 -0
  153. package/dist/target/public.cjs +1 -1
  154. package/dist/target/public.d.cts +2 -2
  155. package/dist/target/public.d.mts +2 -2
  156. package/dist/target/public.mjs +1 -1
  157. package/dist/{terminal-CFO3xWoF.cjs → terminal-BL5E0xbR.cjs} +2 -2
  158. package/dist/{terminal-CFO3xWoF.cjs.map → terminal-BL5E0xbR.cjs.map} +1 -1
  159. package/dist/{terminal-C6TBo_SA.mjs → terminal-BdvrmkaC.mjs} +2 -2
  160. package/dist/{terminal-C6TBo_SA.mjs.map → terminal-BdvrmkaC.mjs.map} +1 -1
  161. package/dist/{types-DkG5WW8l.d.mts → types-Dp9lgx_S.d.mts} +22 -2
  162. package/dist/types-Dp9lgx_S.d.mts.map +1 -0
  163. package/dist/{types-BD1jgA85.d.cts → types-TzCChNrA.d.cts} +22 -2
  164. package/dist/types-TzCChNrA.d.cts.map +1 -0
  165. package/dist/vitest.cjs +1 -1
  166. package/dist/vitest.mjs +1 -1
  167. package/dist/workspace/index.d.cts +2 -2
  168. package/dist/workspace/index.d.mts +2 -2
  169. package/dist/workspace-C7NqCsQF.mjs.map +1 -1
  170. package/dist/workspace-vdrcRI-m.cjs.map +1 -1
  171. package/package.json +15 -15
  172. package/dist/OpenApiTsGenerator-BlP53kf2.cjs.map +0 -1
  173. package/dist/OpenApiTsGenerator-Dv0gT2DL.mjs.map +0 -1
  174. package/dist/bundler-CEaR6Dpb.cjs.map +0 -1
  175. package/dist/bundler-CNTVLCCA.mjs.map +0 -1
  176. package/dist/compose-B-MME-4C.cjs.map +0 -1
  177. package/dist/compose-T3hnnjKZ.mjs.map +0 -1
  178. package/dist/deploy-CZE0m6jd.cjs.map +0 -1
  179. package/dist/deploy-ClC53rc6.mjs.map +0 -1
  180. package/dist/discover-B4VjaFJk.cjs.map +0 -1
  181. package/dist/discover-B6yPpvRV.d.mts.map +0 -1
  182. package/dist/discover-B_IkPukx.d.cts.map +0 -1
  183. package/dist/discover-Buwf1z4P.mjs.map +0 -1
  184. package/dist/engine-Cry1vwMz.cjs.map +0 -1
  185. package/dist/engine-DmQS3XHX.mjs.map +0 -1
  186. package/dist/fakes-C9RiT6XG.mjs +0 -125
  187. package/dist/fakes-C9RiT6XG.mjs.map +0 -1
  188. package/dist/fakes-zW-jNAbQ.cjs +0 -148
  189. package/dist/fakes-zW-jNAbQ.cjs.map +0 -1
  190. package/dist/ready-B-7LayMF.cjs +0 -872
  191. package/dist/ready-B-7LayMF.cjs.map +0 -1
  192. package/dist/ready-ULc5BTOb.mjs +0 -819
  193. package/dist/ready-ULc5BTOb.mjs.map +0 -1
  194. package/dist/state-BJFherj1.mjs.map +0 -1
  195. package/dist/state-CCuTJNRH.cjs.map +0 -1
  196. package/dist/types-BD1jgA85.d.cts.map +0 -1
  197. package/dist/types-DkG5WW8l.d.mts.map +0 -1
@@ -1,16 +1,19 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
2
2
  import { a as runOutput, i as run$1, s as gkmHome } from "./run-BJLIerMM.mjs";
3
- import { d as output, r as discover } from "./discover-Buwf1z4P.mjs";
3
+ import { d as output, r as discover, u as ConstructGenerator } from "./discover-BPelzeGW.mjs";
4
4
  import { t as appKey } from "./derive-B4vDpzgP.mjs";
5
5
  import { O as TEST_STAGE, h as getPublicEnvPrefix, n as allConstructGlobs } from "./workspace-C7NqCsQF.mjs";
6
6
  import { ConfigNotFound, NotInAnApp, loadWorkspaceAppInfo, loadWorkspaceConfig } from "./config.mjs";
7
- import { c as runtimeOf } from "./resolve-BsXEXAUz.mjs";
8
- import { i as DEFAULT_STORAGE, n as DEFAULT_CACHE, r as DEFAULT_EVENTS$1, t as readFakes } from "./fakes-C9RiT6XG.mjs";
7
+ import { n as runtimeFor, r as generateTelemetryModule } from "./EndpointGenerator-DftalLYg.mjs";
8
+ import { c as runtimeOf } from "./resolve-DSAXj_47.mjs";
9
+ import { a as DEFAULT_STORAGE, i as DEFAULT_EVENTS$1, n as readFakes, o as holdsEveryCache, r as DEFAULT_CACHE, t as SubscriberGenerator } from "./SubscriberGenerator-5looQ3gB.mjs";
9
10
  import { basename, dirname, isAbsolute, join, parse, relative, resolve, sep } from "node:path";
10
11
  import { constants, existsSync, readFileSync } from "node:fs";
11
12
  import { appendFile, chmod, copyFile, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from "node:fs/promises";
12
13
  import { homedir, networkInterfaces, tmpdir } from "node:os";
13
14
  import { promisify } from "node:util";
15
+ import { Cron } from "@geekmidas/constructs/crons";
16
+ import { Queue } from "@geekmidas/constructs/queue";
14
17
  import { DEFAULT_POSTGRES_VERSION, cacheTable, cookieDomain, dependenciesOf, dependentsOf, externalApiUrl, kebabCase, mobileOrigins, provideKey, providedKeyFor, provisionOrder, publicEnvFor, schemeBase, scopedName } from "@geekmidas/manifest";
15
18
  import { createServer } from "node:net";
16
19
  import { execFile, spawn } from "node:child_process";
@@ -1536,11 +1539,37 @@ CMD ["node", "${appPath}/server.js"]
1536
1539
  * @internal Exported for testing
1537
1540
  */
1538
1541
  function generateBackendDockerfile(options) {
1542
+ return gkmBundleDockerfile(options, {
1543
+ title: "Backend Dockerfile",
1544
+ bundle: "server.mjs",
1545
+ as: "server.mjs",
1546
+ expose: true
1547
+ });
1548
+ }
1549
+ /** The port a worker answers its health check on, inside its container. */
1550
+ const WORKER_PORT = 3e3;
1551
+ /**
1552
+ * A Worker's image: the same build as its host app's backend — a pruned
1553
+ * slice, `gkm build --provider server --production` in the image, with the
1554
+ * encrypted credentials from the `gkm_credentials` secret — and a runner that
1555
+ * is the worker's bundle alone. It publishes nothing: its one route is the
1556
+ * health check the HEALTHCHECK asks.
1557
+ * @internal Exported for testing
1558
+ */
1559
+ function generateWorkerDockerfile(options) {
1560
+ return gkmBundleDockerfile(options, {
1561
+ title: `Worker Dockerfile (${options.worker})`,
1562
+ bundle: options.bundle,
1563
+ as: "worker.mjs",
1564
+ expose: false
1565
+ });
1566
+ }
1567
+ function gkmBundleDockerfile(options, runner) {
1539
1568
  const { port, healthCheckPath = "/health" } = options;
1540
1569
  const { app } = layout(options);
1541
1570
  const externals = externalsStages(options, app);
1542
1571
  return `# syntax=docker/dockerfile:1
1543
- # Backend Dockerfile: a turbo-pruned slice, bundled by gkm build in the image
1572
+ # ${runner.title}: a turbo-pruned slice, bundled by gkm build in the image
1544
1573
 
1545
1574
  ${prunerStage(options)}
1546
1575
 
@@ -1549,7 +1578,7 @@ ${depsStage(options)}
1549
1578
  ${builderStage(options)}
1550
1579
 
1551
1580
  # Bundle the production server, with the encrypted credentials embedded
1552
- ${credentialsStep(app, `cd ${app} && ${gkmCommand("build --provider server --production")}`)}
1581
+ ${credentialsStep(app, `cd ${app} && ${gkmCommand(`build --provider server --production${options.cache ? ` --cache ${options.cache}` : ""}`)}`)}
1553
1582
  ${externals.stage}
1554
1583
  # Production
1555
1584
  FROM ${options.baseImage} AS runner
@@ -1561,8 +1590,8 @@ RUN apk add --no-cache tini
1561
1590
  RUN addgroup --system --gid 1001 nodejs && \\
1562
1591
  adduser --system --uid 1001 hono
1563
1592
 
1564
- # The bundled server
1565
- COPY --from=builder --chown=hono:nodejs ${app}/.gkm/server/dist/server.mjs ./${externals.copy}
1593
+ # The bundled ${runner.as === "server.mjs" ? "server" : "worker"}
1594
+ COPY --from=builder --chown=hono:nodejs ${app}/.gkm/server/dist/${runner.bundle} ./${runner.as === runner.bundle ? "" : runner.as}${externals.copy}
1566
1595
 
1567
1596
  ENV NODE_ENV=production
1568
1597
  ENV PORT=${port}
@@ -1571,11 +1600,9 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
1571
1600
  CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
1572
1601
 
1573
1602
  USER hono
1574
-
1575
- EXPOSE ${port}
1576
-
1603
+ ${runner.expose ? `\nEXPOSE ${port}\n` : ""}
1577
1604
  ENTRYPOINT ["/sbin/tini", "--"]
1578
- CMD ["node", "server.mjs"]
1605
+ CMD ["node", "${runner.as}"]
1579
1606
  `;
1580
1607
  }
1581
1608
  /**
@@ -2153,7 +2180,8 @@ const CONTAINERS = {
2153
2180
  */
2154
2181
  const CACHE_CONTAINERS = {
2155
2182
  upstash: "redis-http",
2156
- elasticache: "redis"
2183
+ elasticache: "redis",
2184
+ redis: "redis"
2157
2185
  };
2158
2186
  /**
2159
2187
  * Kinds that resolve a value without anything running.
@@ -2266,7 +2294,7 @@ function containerFor(kind, events = DEFAULT_EVENTS, cache = DEFAULT_CACHE.aws,
2266
2294
  if (EVENT_KINDS[kind]) return EVENT_CONTAINERS[events] ?? CONTAINERS.database;
2267
2295
  if (kind === "file-server") return edge ? "caddy" : CONTAINERS.objects;
2268
2296
  if (kind === "cache") {
2269
- if (derived) return CONTAINERS.database;
2297
+ if (derived && !holdsEveryCache(cache)) return CONTAINERS.database;
2270
2298
  return CACHE_CONTAINERS[cache] ?? CONTAINERS.database;
2271
2299
  }
2272
2300
  return CONTAINERS[kind];
@@ -2299,6 +2327,7 @@ function planFor(manifest, stage, order, options = {}) {
2299
2327
  port: fake.port
2300
2328
  } : {}
2301
2329
  };
2330
+ const detached = declaration.kind === "cache" && holdsEveryCache(cache);
2302
2331
  const container = fake?.image ? fake.key : containerFor(declaration.kind, events, cache, "of" in declaration && typeof declaration.of === "string", options.edge !== false);
2303
2332
  if (!container && !CONTAINERLESS[declaration.kind]) continue;
2304
2333
  if (container) {
@@ -2325,11 +2354,11 @@ function planFor(manifest, stage, order, options = {}) {
2325
2354
  credentials: fake.credentials
2326
2355
  } } : {},
2327
2356
  ...declaration.kind === "external-api" && !fake ? { url: declaration.url } : {},
2328
- ..."of" in declaration ? { of: declaration.of } : {},
2357
+ ..."of" in declaration && !detached ? { of: declaration.of } : {},
2329
2358
  ..."schema" in declaration && declaration.schema ? { schema: declaration.schema } : {},
2330
2359
  ..."version" in declaration && declaration.version ? { version: declaration.version } : {},
2331
2360
  ..."roles" in declaration && declaration.roles === false ? { roles: false } : {},
2332
- ..."table" in declaration && declaration.table ? { table: declaration.table } : {}
2361
+ ..."table" in declaration && declaration.table && !detached ? { table: declaration.table } : {}
2333
2362
  });
2334
2363
  }
2335
2364
  if (options.edge !== false && resources.some((r) => EDGE_KINDS[r.kind] === true)) containers.add("caddy");
@@ -2691,7 +2720,8 @@ function urlFor(resource, plan, ports, project, addresses = {}, credential = { m
2691
2720
  return url ? withTable(url, resource.table ?? cacheTable(resource.id)) : void 0;
2692
2721
  }
2693
2722
  switch (plan.cache) {
2694
- case "elasticache": return `redis://${LOCAL_HOST}:${port}`;
2723
+ case "elasticache":
2724
+ case "redis": return `redis://${LOCAL_HOST}:${port}`;
2695
2725
  default: return `http://:${LOCAL_TOKEN$1}@${LOCAL_HOST}:${port}`;
2696
2726
  }
2697
2727
  case "queue":
@@ -2984,6 +3014,36 @@ function appEnvKeys(manifest, appName, runnables = {}) {
2984
3014
  for (const key of other.provides ?? []) keys.add(key);
2985
3015
  }
2986
3016
  }
3017
+ addEdgeKeys(manifest, edges, keys);
3018
+ if (declaration.kind === "site" || declaration.kind === "mobile-app") for (const key of Object.keys(publicEnvFor(declaration, manifest))) keys.add(key);
3019
+ return keys;
3020
+ }
3021
+ /**
3022
+ * The keys a Worker's own process reads: what the worker and everything built
3023
+ * from it declared an edge to, the broker its crons schedule through, and the
3024
+ * address of every queue and topic — which of them its consumers and
3025
+ * subscribers are bound to is only known once a build has found them, so a
3026
+ * process that runs them is handed each carrier's.
3027
+ *
3028
+ * `undefined` when no worker has that id.
3029
+ */
3030
+ function workerEnvKeys(manifest, workerId, runnables = {}) {
3031
+ const declaration = manifest[workerId];
3032
+ if (declaration?.kind !== "worker") return void 0;
3033
+ const keys = /* @__PURE__ */ new Set([
3034
+ ...declaration.provides ?? [],
3035
+ ...declaration.requires ?? [],
3036
+ "EVENT_PUBLISHER_CONNECTION_STRING"
3037
+ ]);
3038
+ for (const other of Object.values(manifest)) {
3039
+ if (other.kind !== "queue" && other.kind !== "topic") continue;
3040
+ for (const key of other.provides ?? []) keys.add(key);
3041
+ }
3042
+ addEdgeKeys(manifest, [...dependenciesOf(declaration).map((edge) => edge.target), ...runnables[workerId] ?? []], keys);
3043
+ return keys;
3044
+ }
3045
+ /** What each construct an edge reaches hands the process that reaches it. */
3046
+ function addEdgeKeys(manifest, edges, keys) {
2987
3047
  for (const id of edges) {
2988
3048
  const target = manifest[id];
2989
3049
  if (!target) continue;
@@ -3000,8 +3060,6 @@ function appEnvKeys(manifest, appName, runnables = {}) {
3000
3060
  keys.add("AWS_REGION");
3001
3061
  }
3002
3062
  }
3003
- if (declaration.kind === "site" || declaration.kind === "mobile-app") for (const key of Object.keys(publicEnvFor(declaration, manifest))) keys.add(key);
3004
- return keys;
3005
3063
  }
3006
3064
  /** One compose service per app the workspace runs, behind the apps profile. */
3007
3065
  function appServices(workspace, manifest, containers, runnables = {}, fakes = {}) {
@@ -4532,6 +4590,707 @@ function localAddress(port) {
4532
4590
  return `http://localhost:${port}`;
4533
4591
  }
4534
4592
  //#endregion
4593
+ //#region src/generators/CronGenerator.ts
4594
+ var CronGenerator = class extends ConstructGenerator {
4595
+ async build(context, constructs, outputDir, options) {
4596
+ const target = options?.target ?? "aws";
4597
+ const logger = console;
4598
+ const cronInfos = [];
4599
+ if (target === "server") {
4600
+ await this.generateServerCronsFile(outputDir, constructs);
4601
+ logger.log(`Generated server crons file with ${constructs.length} crons`);
4602
+ return cronInfos;
4603
+ }
4604
+ if (constructs.length === 0) return cronInfos;
4605
+ const cronsDir = join(outputDir, "crons");
4606
+ await mkdir(cronsDir, { recursive: true });
4607
+ for (const { key, construct, path } of constructs) {
4608
+ const handlerFile = await this.generateCronHandler(cronsDir, path.relative, key, context, construct.owner);
4609
+ cronInfos.push({
4610
+ name: key,
4611
+ handler: relative(options?.root ?? process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
4612
+ schedule: construct.schedule || "rate(1 hour)",
4613
+ timeout: construct.timeout,
4614
+ memorySize: construct.memorySize,
4615
+ environment: await construct.getEnvironment({ markOptional: context.markOptional }),
4616
+ dependencies: construct.constructs
4617
+ });
4618
+ logger.log(`Generated cron handler: ${key}`);
4619
+ }
4620
+ return cronInfos;
4621
+ }
4622
+ isConstruct(value) {
4623
+ return Cron.isCron(value);
4624
+ }
4625
+ /**
4626
+ * The crons file a server entry imports.
4627
+ *
4628
+ * Shaped after `generateServerSubscribersFile`: one module, every cron, a
4629
+ * `setupCrons` the generated entry calls beside `setupSubscribers`.
4630
+ *
4631
+ * It picks its scheduler from what the process can reach. With a Postgres it
4632
+ * uses pg-boss, whose schedules live in the database — so a deployment
4633
+ * running four replicas fires each job once, which is the thing a timer in
4634
+ * every process gets wrong and does not report. With no database it falls
4635
+ * back to timers and says plainly that it is doing so.
4636
+ */
4637
+ async generateServerCronsFile(outputDir, crons) {
4638
+ await mkdir(outputDir, { recursive: true });
4639
+ const cronsPath = join(outputDir, "crons.ts");
4640
+ if (crons.length === 0) {
4641
+ await writeFile(cronsPath, `/**
4642
+ * Generated cron setup — this app declares none.
4643
+ */
4644
+ import type { EnvironmentParser } from '@geekmidas/envkit';
4645
+ import type { Logger } from '@geekmidas/logger';
4646
+
4647
+ export async function setupCrons(
4648
+ _envParser: EnvironmentParser<any>,
4649
+ _logger: Logger,
4650
+ ): Promise<void> {}
4651
+
4652
+ export function cronsStatus(): { failed: string[]; connections: unknown[] } {
4653
+ return { failed: [], connections: [] };
4654
+ }
4655
+
4656
+ export async function stopCrons(): Promise<void> {}
4657
+ `);
4658
+ return cronsPath;
4659
+ }
4660
+ const importsByFile = /* @__PURE__ */ new Map();
4661
+ for (const { path, key } of crons) {
4662
+ const importPath = relative(dirname(cronsPath), path.relative).replace(/\.ts$/, ".js");
4663
+ if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
4664
+ importsByFile.get(importPath)?.push(key);
4665
+ }
4666
+ const content = `/**
4667
+ * Generated cron setup.
4668
+ *
4669
+ * Schedules every cron this app declares, in the process that serves it.
4670
+ *
4671
+ * The schedule lives in Postgres, through pg-boss, in the database the worker
4672
+ * named with \`.database(db)\`. That is what makes running several replicas
4673
+ * safe: each job fires once, where a timer in every process fires it once per
4674
+ * replica and reports nothing.
4675
+ *
4676
+ * No connection string appears here. The construct that owns the database is
4677
+ * the only thing that knows its key, and it is resolved through service
4678
+ * discovery like any other dependency.
4679
+ */
4680
+ import type { EnvironmentParser } from '@geekmidas/envkit';
4681
+ import type { Logger } from '@geekmidas/logger';
4682
+ import { runCron, scheduleInProcess, toCronExpression } from '@geekmidas/constructs/crons';
4683
+ import { ServiceDiscovery } from '@geekmidas/services';
4684
+ ${Array.from(importsByFile.entries()).map(([importPath, exports]) => `import { ${exports.join(", ")} } from '${importPath}';`).join("\n")}
4685
+
4686
+ const crons = [
4687
+ ${crons.map(({ key }) => ` { name: '${key}', cron: ${key} }`).join(",\n")}
4688
+ ];
4689
+
4690
+ type Stoppable = { stop: () => Promise<void> | void };
4691
+ const running: Stoppable[] = [];
4692
+ // What a worker's health check reads: the crons that cannot be scheduled, and
4693
+ // the broker connection the schedule is kept through.
4694
+ const failed: string[] = [];
4695
+ const connections: import('@geekmidas/events').EventConnection[] = [];
4696
+
4697
+ export async function setupCrons(
4698
+ envParser: EnvironmentParser<any>,
4699
+ logger: Logger,
4700
+ ): Promise<void> {
4701
+ if (crons.length === 0) {
4702
+ return;
4703
+ }
4704
+
4705
+ const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
4706
+
4707
+ // Resolved once per cron rather than per firing: registering services on
4708
+ // every tick would reconnect a database every minute.
4709
+ const prepared = [];
4710
+ for (const { name, cron } of crons) {
4711
+ try {
4712
+ const schedule = toCronExpression(cron.schedule);
4713
+ const services = cron.services.length > 0
4714
+ ? await serviceDiscovery.register(cron.services)
4715
+ : {};
4716
+
4717
+ prepared.push({ name, cron, schedule, services });
4718
+ } catch (error) {
4719
+ // An unrepresentable schedule is a build-time mistake, and failing the
4720
+ // process for it would take the HTTP server down with it.
4721
+ logger.error({ err: error, cron: name }, 'Cron has no runnable schedule, skipping');
4722
+ failed.push(name);
4723
+ }
4724
+ }
4725
+
4726
+ const run = async (entry: (typeof prepared)[number]) => {
4727
+ try {
4728
+ // The cron's own function, the way its Lambda would run it — services,
4729
+ // the worker's database as \`db\`, its events. It used to call a
4730
+ // \`handler\` a Cron does not have, so every firing logged a failure.
4731
+ await runCron(entry.cron, serviceDiscovery);
4732
+ } catch (error) {
4733
+ logger.error({ err: error, cron: entry.name }, 'Cron failed');
4734
+ }
4735
+ };
4736
+
4737
+ // The app's one pg-boss: the events broker's, as the broker's role, in the
4738
+ // broker's schema. A server fires its own crons, and a process scheduling in
4739
+ // memory fires each job once per replica — so the schedule lives in
4740
+ // Postgres. It used to be a second pg-boss on the worker's database, as the
4741
+ // runtime role, which can neither create a schema nor use the broker's.
4742
+ const { url, devPid } = envParser
4743
+ .create((get) => ({
4744
+ url: get('EVENT_PUBLISHER_CONNECTION_STRING').string().optional(),
4745
+ // Set by \`gkm dev\` on the server it starts, and by nothing deployed.
4746
+ devPid: get('GKM_DEV_PID').string().optional(),
4747
+ }))
4748
+ .parse();
4749
+
4750
+ if (!url?.startsWith('pgboss://')) {
4751
+ // \`gkm dev\` on a target whose crons are infrastructure once deployed —
4752
+ // EventBridge rules on AWS — has no broker to keep a schedule in. It is one
4753
+ // process, so a timer fires each job once, which is all pg-boss is for
4754
+ // here. Only there: a deployed server firing by timer fires once per
4755
+ // replica, so outside dev this stays the error below.
4756
+ if (devPid) {
4757
+ for (const entry of prepared) {
4758
+ running.push(scheduleInProcess(entry.schedule, () => run(entry)));
4759
+ logger.info({ cron: entry.name, schedule: entry.schedule }, 'Cron scheduled in this process');
4760
+ }
4761
+ return;
4762
+ }
4763
+
4764
+ logger.error(
4765
+ { crons: prepared.map(({ name }) => name) },
4766
+ 'These crons have nowhere to keep their schedule. On a server they are ' +
4767
+ 'scheduled through the pg-boss events broker ' +
4768
+ '(EVENT_PUBLISHER_CONNECTION_STRING), and ' +
4769
+ (url ? 'the broker configured is not pg-boss.' : 'none is configured.'),
4770
+ );
4771
+ failed.push(...prepared.map(({ name }) => name));
4772
+ return;
4773
+ }
4774
+
4775
+ // Through the registered pg-boss driver, which the entry registers for a
4776
+ // server whose broker is pg-boss. Naming \`@geekmidas/events/pgboss\` here
4777
+ // made every bundle with a cron resolve pg-boss, whatever its broker.
4778
+ const { EventConnectionFactory } = await import('@geekmidas/events');
4779
+ const connection = (await EventConnectionFactory.fromConnectionString(
4780
+ url,
4781
+ )) as import('@geekmidas/events').EventConnection & { instance?: any };
4782
+ const boss = connection.instance!;
4783
+
4784
+ // What this app declares now. Anything else under the prefix belonged to a
4785
+ // cron since renamed or deleted, and would keep firing at a handler that has
4786
+ // gone.
4787
+ //
4788
+ // The prefix is \`cron.\` rather than \`cron:\`: pg-boss accepts
4789
+ // alphanumerics, underscores, hyphens, periods and slashes in a queue name,
4790
+ // and rejects a colon outright.
4791
+ const declared = new Set(prepared.map(({ name }) => \`cron.\${name}\`));
4792
+ for (const existing of await boss.getSchedules()) {
4793
+ if (existing.name.startsWith('cron.') && !declared.has(existing.name)) {
4794
+ await boss.unschedule(existing.name);
4795
+ logger.info({ cron: existing.name }, 'Removed a schedule nothing declares');
4796
+ }
4797
+ }
4798
+
4799
+ for (const entry of prepared) {
4800
+ const queue = \`cron.\${entry.name}\`;
4801
+ await boss.createQueue(queue);
4802
+ // UTC, so this agrees with the schedule a deploy target would have made.
4803
+ await boss.schedule(queue, entry.schedule, undefined, { tz: 'UTC' });
4804
+ await boss.work(queue, () => run(entry));
4805
+
4806
+ logger.info({ cron: entry.name, schedule: entry.schedule }, 'Cron scheduled');
4807
+ }
4808
+
4809
+ running.push({ stop: () => connection.close() });
4810
+ connections.push(connection);
4811
+ logger.info({ count: prepared.length }, 'Crons scheduled');
4812
+ }
4813
+
4814
+ /** The crons that cannot be scheduled, and the connection that schedules the rest. */
4815
+ export function cronsStatus(): {
4816
+ failed: string[];
4817
+ connections: import('@geekmidas/events').EventConnection[];
4818
+ } {
4819
+ return { failed: [...failed], connections: [...connections] };
4820
+ }
4821
+
4822
+ /**
4823
+ * Stop scheduling, let a firing in flight finish, and close the broker
4824
+ * connection — pg-boss's graceful stop does all three. The caller bounds it.
4825
+ */
4826
+ export async function stopCrons(): Promise<void> {
4827
+ const open = [...running];
4828
+ running.length = 0;
4829
+ connections.length = 0;
4830
+ await Promise.all(open.map((r) => r.stop()));
4831
+ }
4832
+ `;
4833
+ await writeFile(cronsPath, content);
4834
+ return cronsPath;
4835
+ }
4836
+ async generateCronHandler(outputDir, sourceFile, exportName, context, owner) {
4837
+ const handlerFileName = `${exportName}.ts`;
4838
+ const handlerPath = join(outputDir, handlerFileName);
4839
+ const importPath = relative(dirname(handlerPath), sourceFile).replace(/\.ts$/, ".js");
4840
+ const runtime = runtimeFor(context, dirname(handlerPath), owner);
4841
+ const content = `import { AWSScheduledFunction } from '@geekmidas/constructs/aws';
4842
+ import { ${exportName} } from '${importPath}';
4843
+ ${runtime.imports}
4844
+ ${context.storageDrivers?.imports ?? ""}
4845
+ ${runtime.bindings}
4846
+ ${context.storageDrivers?.setup ? `\n// The handler registers the drivers its target needs.\n${context.storageDrivers.setup}\n` : ""}
4847
+ const adapter = new AWSScheduledFunction(envParser, ${exportName});
4848
+
4849
+ export const handler = adapter.handler;
4850
+ `;
4851
+ await writeFile(handlerPath, content);
4852
+ return handlerPath;
4853
+ }
4854
+ };
4855
+ //#endregion
4856
+ //#region src/generators/QueueGenerator.ts
4857
+ /**
4858
+ * Generates the runtime for queue consumers (`worker.queue(…)`).
4859
+ *
4860
+ * - **server** (`gkm dev`): a single `queues.ts` exposing `setupQueues()` that
4861
+ * polls each queue in-process, alongside the Hono server, through the queue's
4862
+ * own connection string — pg-boss by default, SQS on the AWS emulator.
4863
+ * - **aws-lambda**: one handler file per queue wrapping `AWSLambdaQueue`, backed
4864
+ * by an SQS event-source mapping.
4865
+ */
4866
+ var QueueGenerator = class extends ConstructGenerator {
4867
+ isConstruct(value) {
4868
+ return Queue.isQueue(value);
4869
+ }
4870
+ async build(context, constructs, outputDir, options) {
4871
+ const target = options?.target ?? "aws";
4872
+ const logger = console;
4873
+ const queueInfos = [];
4874
+ if (target === "server") {
4875
+ await this.generateServerQueuesFile(outputDir, constructs);
4876
+ logger.log(`Generated server queues file with ${constructs.length} queues (polling mode)`);
4877
+ return queueInfos;
4878
+ }
4879
+ if (constructs.length === 0) return queueInfos;
4880
+ const queuesDir = join(outputDir, "queues");
4881
+ await mkdir(queuesDir, { recursive: true });
4882
+ for (const { key, construct, path } of constructs) {
4883
+ const handlerFile = await this.generateQueueHandler(queuesDir, path.relative, key, context, construct.owner);
4884
+ queueInfos.push({
4885
+ name: construct.name,
4886
+ handler: relative(options?.root ?? process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
4887
+ batchSize: construct.batchSize,
4888
+ fifo: construct.fifo,
4889
+ timeout: construct.timeout,
4890
+ environment: await construct.getEnvironment({ markOptional: context.markOptional }),
4891
+ dependencies: construct.constructs
4892
+ });
4893
+ logger.log(`Generated queue handler: ${key}`);
4894
+ }
4895
+ return queueInfos;
4896
+ }
4897
+ async generateQueueHandler(outputDir, sourceFile, exportName, context, owner) {
4898
+ const handlerPath = join(outputDir, `${exportName}.ts`);
4899
+ const importPath = relative(dirname(handlerPath), sourceFile).replace(/\.ts$/, ".js");
4900
+ const runtime = runtimeFor(context, dirname(handlerPath), owner);
4901
+ const content = `import { AWSLambdaQueue } from '@geekmidas/constructs/aws';
4902
+ import { ${exportName} } from '${importPath}';
4903
+ ${runtime.imports}
4904
+ ${context.storageDrivers?.imports ?? ""}
4905
+ ${runtime.bindings}
4906
+ ${context.storageDrivers?.setup ? `\n// The handler registers the drivers its target needs.\n${context.storageDrivers.setup}\n` : ""}
4907
+ const adapter = new AWSLambdaQueue(envParser, ${exportName});
4908
+
4909
+ export const handler = adapter.handler;
4910
+ `;
4911
+ await writeFile(handlerPath, content);
4912
+ return handlerPath;
4913
+ }
4914
+ async generateServerQueuesFile(outputDir, queues) {
4915
+ await mkdir(outputDir, { recursive: true });
4916
+ const queuesPath = join(outputDir, "queues.ts");
4917
+ if (queues.length === 0) {
4918
+ await writeFile(queuesPath, `/**
4919
+ * Generated queues setup — this app declares none.
4920
+ */
4921
+ import type { EnvironmentParser } from '@geekmidas/envkit';
4922
+ import type { Logger } from '@geekmidas/logger';
4923
+
4924
+ export async function setupQueues(
4925
+ _envParser: EnvironmentParser<any>,
4926
+ _logger: Logger,
4927
+ ): Promise<void> {}
4928
+
4929
+ export function queuesStatus(): { failed: string[]; connections: unknown[] } {
4930
+ return { failed: [], connections: [] };
4931
+ }
4932
+
4933
+ export async function stopQueues(): Promise<void> {}
4934
+ `);
4935
+ return queuesPath;
4936
+ }
4937
+ const importsByFile = /* @__PURE__ */ new Map();
4938
+ for (const { path, key } of queues) {
4939
+ const importPath = relative(dirname(queuesPath), path.relative).replace(/\.ts$/, ".js");
4940
+ if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
4941
+ importsByFile.get(importPath)?.push(key);
4942
+ }
4943
+ const content = `/**
4944
+ * Generated queue consumers setup.
4945
+ *
4946
+ * Each queue's one consumer polls its own queue, through the queue's own
4947
+ * connection string — the one its producers publish on. SQS cannot push, so
4948
+ * this is a poller on every transport: pg-boss locally by default, the SQS
4949
+ * queue on the AWS emulator.
4950
+ *
4951
+ * Deployed on Lambda, a queue's consumer is an SQS event source instead.
4952
+ */
4953
+ import type { EnvironmentParser } from '@geekmidas/envkit';
4954
+ import type { Logger } from '@geekmidas/logger';
4955
+ import { EventConnectionFactory, Subscriber } from '@geekmidas/events';
4956
+ import type { EventConnection } from '@geekmidas/events';
4957
+ import { ServiceDiscovery } from '@geekmidas/services';
4958
+ ${Array.from(importsByFile.entries()).map(([importPath, exports]) => `import { ${exports.join(", ")} } from '${importPath}';`).join("\n")}
4959
+
4960
+ const queues = [
4961
+ ${queues.map(({ key, construct }) => ` { queue: ${key}, connectionKey: '${provideKey(construct.id, "publisherConnectionString")}' },`).join("\n")}
4962
+ ];
4963
+
4964
+ // What was opened, and what could not be: a worker's health check reads both,
4965
+ // and its shutdown closes what was opened.
4966
+ const connections = new Map<string, EventConnection>();
4967
+ const failed: string[] = [];
4968
+
4969
+ export async function setupQueues(
4970
+ envParser: EnvironmentParser<any>,
4971
+ logger: Logger,
4972
+ ): Promise<void> {
4973
+ const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
4974
+
4975
+ for (const { queue, connectionKey } of queues) {
4976
+ const connectionString = process.env[connectionKey];
4977
+ if (!connectionString) {
4978
+ logger.error({ queue: queue.name, connectionKey }, 'No connection string for this queue');
4979
+ failed.push(queue.name);
4980
+ continue;
4981
+ }
4982
+
4983
+ try {
4984
+ let connection = connections.get(connectionString);
4985
+ if (!connection) {
4986
+ connection = await EventConnectionFactory.fromConnectionString(connectionString);
4987
+ connections.set(connectionString, connection);
4988
+ }
4989
+ const eventSubscriber = await Subscriber.fromConnection(connection);
4990
+ const services = queue.services.length > 0
4991
+ ? await serviceDiscovery.register(queue.services)
4992
+ : {};
4993
+ // The queue's database — the worker's, or its own — handed over as \`db\`.
4994
+ const db = queue.databaseService
4995
+ ? (await serviceDiscovery.register([queue.databaseService]))[
4996
+ queue.databaseService.serviceName
4997
+ ]
4998
+ : undefined;
4999
+
5000
+ // A queue's messages carry one type — its own name.
5001
+ await eventSubscriber.subscribe([queue.name], async (message) => {
5002
+ try {
5003
+ const validation = await queue.messageSchema['~standard'].validate(message.payload);
5004
+ if (validation.issues) {
5005
+ logger.error({ issues: validation.issues, queue: queue.name }, 'Queue message failed validation');
5006
+ return;
5007
+ }
5008
+
5009
+ await queue.handler({
5010
+ messages: [validation.value],
5011
+ services: services,
5012
+ logger: queue.logger,
5013
+ db,
5014
+ } as any);
5015
+ } catch (error) {
5016
+ logger.error({ err: error, queue: queue.name }, 'Failed to process queue message');
5017
+ // Rethrown so the transport keeps the message for a retry.
5018
+ throw error;
5019
+ }
5020
+ });
5021
+ logger.info({ queue: queue.name }, 'Queue consumer started polling');
5022
+ } catch (error) {
5023
+ logger.error({ err: error, queue: queue.name }, 'Failed to set up queue consumer');
5024
+ failed.push(queue.name);
5025
+ }
5026
+ }
5027
+ }
5028
+
5029
+ /** The consumers that could not start, and the connections that did. */
5030
+ export function queuesStatus(): { failed: string[]; connections: EventConnection[] } {
5031
+ return { failed: [...failed], connections: [...connections.values()] };
5032
+ }
5033
+
5034
+ /**
5035
+ * Stop polling, let the messages in flight finish, and close each broker
5036
+ * connection — pg-boss's graceful stop does all three. The caller bounds it.
5037
+ */
5038
+ export async function stopQueues(): Promise<void> {
5039
+ const open = [...connections.values()];
5040
+ connections.clear();
5041
+ await Promise.all(open.map((connection) => connection.close()));
5042
+ }
5043
+ `;
5044
+ await writeFile(queuesPath, content);
5045
+ return queuesPath;
5046
+ }
5047
+ };
5048
+ //#endregion
5049
+ //#region src/generators/WorkerGenerator.ts
5050
+ /**
5051
+ * A worker whose topic subscribers would have to be pushed to over HTTP.
5052
+ *
5053
+ * On SNS a subscriber is an HTTP endpoint SNS delivers to, and a worker's
5054
+ * process serves nothing but its health check. A server target's broker is
5055
+ * pg-boss, where each subscriber polls a queue of its own, so this is only
5056
+ * reached by building server images for a project that deploys to AWS — where
5057
+ * a subscriber is a Lambda instead.
5058
+ */
5059
+ var WorkerSubscribersNeedPush = class extends Error {
5060
+ worker;
5061
+ subscribers;
5062
+ constructor(worker, subscribers) {
5063
+ super(`The worker '${worker}' runs topic subscribers (${subscribers.join(", ")}), and this build's broker is SNS, which pushes each event to its subscriber over HTTP. A worker image serves only its health check. Deploy to a server target, whose broker is pg-boss and whose subscribers poll, or to AWS, where each subscriber is a Lambda.`);
5064
+ this.worker = worker;
5065
+ this.subscribers = subscribers;
5066
+ this.name = "WorkerSubscribersNeedPush";
5067
+ }
5068
+ };
5069
+ /** Where a worker's generated entry lives, under an app's `.gkm/server`. */
5070
+ function workerEntryDir(serverDir, workerId) {
5071
+ return join(serverDir, "workers", appKey(workerId));
5072
+ }
5073
+ /** The file a worker's bundle is written to, beside the server's. */
5074
+ function workerBundleName(workerId) {
5075
+ return `worker-${appKey(workerId)}.mjs`;
5076
+ }
5077
+ /**
5078
+ * A Worker's production entry: the process that runs its crons, its queue
5079
+ * consumers and its topic subscribers, and answers nothing but a health check.
5080
+ *
5081
+ * It is the server's runtime without the server. The `crons.ts`, `queues.ts`
5082
+ * and `subscribers.ts` it starts are the files `gkm dev` starts — written by
5083
+ * the same generators, given only this worker's constructs — so a consumer
5084
+ * runs the same code in development and in the image. What it adds is the
5085
+ * process around them: drivers, a health check that says when a consumer or
5086
+ * the broker is down, and a shutdown that drains.
5087
+ */
5088
+ var WorkerGenerator = class {
5089
+ generators;
5090
+ constructor(generators = {
5091
+ cron: new CronGenerator(),
5092
+ queue: new QueueGenerator(),
5093
+ subscriber: new SubscriberGenerator()
5094
+ }) {
5095
+ this.generators = generators;
5096
+ }
5097
+ /** Write the worker's files; returns its entry, `worker.ts`. */
5098
+ async build(input) {
5099
+ const { context, workerId, outputDir } = input;
5100
+ if (input.subscribers.length > 0 && context.eventsBackends?.includes("sns")) throw new WorkerSubscribersNeedPush(workerId, input.subscribers.map(({ key }) => key));
5101
+ await mkdir(outputDir, { recursive: true });
5102
+ const options = { target: "server" };
5103
+ await Promise.all([
5104
+ this.generators.cron.build(context, input.crons, outputDir, options),
5105
+ this.generators.queue.build(context, input.queues, outputDir, options),
5106
+ this.generators.subscriber.build(context, input.subscribers, outputDir, options)
5107
+ ]);
5108
+ await writeFile(join(outputDir, "app.ts"), workerApp(context, workerId, outputDir));
5109
+ await writeFile(join(outputDir, "telemetry.ts"), generateTelemetryModule(context.telemetry ? {
5110
+ ...context.telemetry,
5111
+ serviceName: workerId
5112
+ } : void 0));
5113
+ const entry = join(outputDir, "worker.ts");
5114
+ await writeFile(entry, workerEntry(context.production?.healthCheck ?? "/health"));
5115
+ return entry;
5116
+ }
5117
+ };
5118
+ /**
5119
+ * `app.ts`: the worker's runtime — its logger and environment parser off the
5120
+ * Worker construct, the drivers its target needs, and a `startWorker` that
5121
+ * starts every cron, consumer and subscriber it owns.
5122
+ */
5123
+ function workerApp(context, workerId, outputDir) {
5124
+ const runtime = runtimeFor(context, outputDir, workerId);
5125
+ return `/**
5126
+ * Generated worker runtime for ${workerId}.
5127
+ * Generated by 'gkm build --production'.
5128
+ */
5129
+ import { setupCrons, cronsStatus, stopCrons } from './crons.js';
5130
+ import { setupQueues, queuesStatus, stopQueues } from './queues.js';
5131
+ import { setupSubscribers, subscribersStatus, stopSubscribers } from './subscribers.js';
5132
+ ${runtime.imports}
5133
+ ${context.storageDrivers?.imports ?? ""}
5134
+
5135
+ ${runtime.bindings}
5136
+
5137
+ ${context.storageDrivers?.setup ? `// The entry point registers the drivers its target needs.
5138
+ ${context.storageDrivers.setup}
5139
+ ` : ""}
5140
+ // Who this worker is to Postgres: application_name on every connection.
5141
+ process.env.GKM_APP_NAME ??= ${JSON.stringify(workerId)};
5142
+
5143
+ export { logger };
5144
+
5145
+ /** What the health check answers, and why. */
5146
+ export interface WorkerHealth {
5147
+ healthy: boolean;
5148
+ /** The crons, queues and subscribers that could not start. */
5149
+ failed: string[];
5150
+ /** How many broker connections did not answer. */
5151
+ down: number;
5152
+ }
5153
+
5154
+ /** Whether one broker connection answers: open, and — on pg-boss — a query. */
5155
+ async function answers(connection: unknown): Promise<boolean> {
5156
+ const c = connection as {
5157
+ isConnected?: () => boolean;
5158
+ instance?: { getDb?: () => { executeSql: (sql: string) => Promise<unknown> } };
5159
+ };
5160
+ if (c.isConnected && !c.isConnected()) return false;
5161
+ const db = c.instance?.getDb?.();
5162
+ if (!db) return true;
5163
+ try {
5164
+ await Promise.race([
5165
+ db.executeSql('select 1'),
5166
+ new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 2000).unref()),
5167
+ ]);
5168
+ return true;
5169
+ } catch {
5170
+ return false;
5171
+ }
5172
+ }
5173
+
5174
+ /** Start everything this worker runs. */
5175
+ export async function startWorker(): Promise<{
5176
+ health: () => Promise<WorkerHealth>;
5177
+ stop: () => Promise<void>;
5178
+ }> {
5179
+ // An app object with no routes: on a server target nothing is pushed to a
5180
+ // subscriber, so there is nothing to mount.
5181
+ const noRoutes = { post: () => undefined };
5182
+
5183
+ await setupSubscribers(noRoutes, envParser, logger).catch((error) => {
5184
+ logger.error({ err: error }, 'Failed to start subscribers');
5185
+ });
5186
+ await setupQueues(envParser, logger).catch((error) => {
5187
+ logger.error({ err: error }, 'Failed to start queue consumers');
5188
+ });
5189
+ await setupCrons(envParser, logger).catch((error) => {
5190
+ logger.error({ err: error }, 'Failed to schedule crons');
5191
+ });
5192
+
5193
+ const statuses = () => [subscribersStatus(), queuesStatus(), cronsStatus()];
5194
+
5195
+ return {
5196
+ async health() {
5197
+ const all = statuses();
5198
+ const failed = all.flatMap((s) => s.failed);
5199
+ const alive = await Promise.all(
5200
+ all.flatMap((s) => s.connections as unknown[]).map(answers),
5201
+ );
5202
+ const down = alive.filter((ok) => !ok).length;
5203
+ return { healthy: failed.length === 0 && down === 0, failed, down };
5204
+ },
5205
+ // Each stops pulling, waits for what it has in flight, and closes its
5206
+ // broker connection. Together, so one slow handler does not hold the
5207
+ // others' connections open.
5208
+ async stop() {
5209
+ await Promise.all([stopSubscribers(), stopQueues(), stopCrons()]);
5210
+ },
5211
+ };
5212
+ }
5213
+ `;
5214
+ }
5215
+ /**
5216
+ * `worker.ts`: the process. Health first, so the container answers while the
5217
+ * consumers connect; then the worker; then a shutdown that drains.
5218
+ */
5219
+ function workerEntry(healthCheck) {
5220
+ return `#!/usr/bin/env node
5221
+ /**
5222
+ * Production worker entry point.
5223
+ * Generated by 'gkm build --production'.
5224
+ *
5225
+ * Runs this worker's crons, queue consumers and topic subscribers, and serves
5226
+ * one route: ${healthCheck}, on PORT — 200 when every consumer started and
5227
+ * every broker connection answers, 503 otherwise.
5228
+ *
5229
+ * On SIGTERM it stops pulling messages and scheduling crons, lets the handlers
5230
+ * in flight finish, closes its broker connections and database pools, and
5231
+ * exits 0 — or 1, at GKM_SHUTDOWN_TIMEOUT_MS (8s by default, under Docker's
5232
+ * 10s stop timeout).
5233
+ */
5234
+ import { createServer } from 'node:http';
5235
+ import { runShutdownHooks } from '@geekmidas/constructs';
5236
+ import { startTelemetry } from './telemetry.js';
5237
+
5238
+ // Before the worker is imported, so the libraries it loads are instrumented.
5239
+ await startTelemetry({ ignorePaths: [${JSON.stringify(healthCheck)}] });
5240
+
5241
+ const { startWorker, logger } = await import('./app.js');
5242
+
5243
+ const port = Number(process.env.PORT) || 3000;
5244
+
5245
+ let worker: Awaited<ReturnType<typeof startWorker>> | undefined;
5246
+ let stopping = false;
5247
+
5248
+ const server = createServer((req, res) => {
5249
+ const path = (req.url ?? '/').split('?')[0];
5250
+ if (path !== ${JSON.stringify(healthCheck)} || (req.method !== 'GET' && req.method !== 'HEAD')) {
5251
+ res.writeHead(404).end();
5252
+ return;
5253
+ }
5254
+ const answer = (status: number, body: unknown) => {
5255
+ res.writeHead(status, { 'content-type': 'application/json' });
5256
+ res.end(JSON.stringify(body));
5257
+ };
5258
+ if (stopping) return answer(503, { status: 'stopping' });
5259
+ if (!worker) return answer(503, { status: 'starting' });
5260
+ worker.health().then(
5261
+ (health) => answer(health.healthy ? 200 : 503, { status: health.healthy ? 'ok' : 'unhealthy', ...health }),
5262
+ (error) => answer(503, { status: 'unhealthy', error: String(error) }),
5263
+ );
5264
+ });
5265
+ server.listen(port);
5266
+
5267
+ const shutdown = async () => {
5268
+ if (stopping) return;
5269
+ stopping = true;
5270
+ const deadline = Number(process.env.GKM_SHUTDOWN_TIMEOUT_MS) || 8000;
5271
+ logger.info({ deadline }, 'Graceful shutdown initiated');
5272
+ setTimeout(() => {
5273
+ logger.warn({ deadline }, 'Shutdown deadline reached, exiting');
5274
+ process.exit(1);
5275
+ }, deadline).unref();
5276
+
5277
+ // Nothing new is pulled or scheduled; what is in flight finishes.
5278
+ await worker?.stop().catch((error) => logger.error({ err: error }, 'Failed to stop the worker'));
5279
+ // Then what the handlers used: database pools.
5280
+ await runShutdownHooks((error) => logger.error({ err: error }, 'Shutdown hook failed'));
5281
+ server.close();
5282
+ logger.info('Worker stopped');
5283
+ process.exit(0);
5284
+ };
5285
+
5286
+ process.on('SIGTERM', shutdown);
5287
+ process.on('SIGINT', shutdown);
5288
+
5289
+ worker = await startWorker();
5290
+ logger.info({ port }, 'Worker started');
5291
+ `;
5292
+ }
5293
+ //#endregion
4535
5294
  //#region src/secrets/encryption.ts
4536
5295
  var encryption_exports = /* @__PURE__ */ __exportAll({
4537
5296
  encryptSecrets: () => encryptSecrets,
@@ -4584,6 +5343,74 @@ function keyFingerprint(masterKey) {
4584
5343
  return createHash("sha256").update(masterKey).digest("hex").slice(0, 8);
4585
5344
  }
4586
5345
  //#endregion
5346
+ //#region src/build/workers.ts
5347
+ /** A worker with background work and no backend app to build it from. */
5348
+ var WorkerHasNoApp = class extends Error {
5349
+ worker;
5350
+ constructor(worker) {
5351
+ super(`The worker '${worker}' has crons, queues or subscribers, and the workspace has no backend app that builds a server to carry its entry. Declare a RestApi whose app holds the worker's code.`);
5352
+ this.worker = worker;
5353
+ this.name = "WorkerHasNoApp";
5354
+ }
5355
+ };
5356
+ /** A worker whose service name an app already has. */
5357
+ var WorkerNameTaken = class extends Error {
5358
+ worker;
5359
+ service;
5360
+ constructor(worker, service) {
5361
+ super(`The worker '${worker}' runs as the service '${service}', and an app is already called that. Rename the worker — new Worker('${worker}Worker') — so each container has a name of its own.`);
5362
+ this.worker = worker;
5363
+ this.service = service;
5364
+ this.name = "WorkerNameTaken";
5365
+ }
5366
+ };
5367
+ /**
5368
+ * The workers a workspace deploys — each one that has background work — and
5369
+ * the app each is built from.
5370
+ *
5371
+ * The host is the backend app whose directory holds the most of the worker's
5372
+ * files; one whose work sits outside every app (a shared `jobs/` directory)
5373
+ * is built by the first backend, by name, that generates a server. An app
5374
+ * with its own entry is never one — gkm's generators do not build it — and a
5375
+ * surface that serves itself, an auth server, only when it holds the work.
5376
+ *
5377
+ * @param background Each worker's files, from discovery's `background`.
5378
+ */
5379
+ function workerUnits(workspace, manifest, background) {
5380
+ const hosts = Object.entries(workspace.apps).filter(([, app]) => app.type === "backend" && !app.entry).map(([name, app]) => {
5381
+ const surface = Object.entries(manifest).find(([id, d]) => d.kind === "rest-api" && appKey(id) === name)?.[1];
5382
+ return {
5383
+ name,
5384
+ root: isAbsolute(app.path) ? app.path : join(workspace.root, app.path),
5385
+ generated: !(surface?.kind === "rest-api" && surface.endpoints.length > 0)
5386
+ };
5387
+ }).sort((a, b) => a.name.localeCompare(b.name));
5388
+ const units = [];
5389
+ for (const [id, declaration] of Object.entries(manifest)) {
5390
+ if (declaration.kind !== "worker") continue;
5391
+ const files = background[id] ?? [];
5392
+ if (files.length === 0) continue;
5393
+ const name = appKey(id);
5394
+ if (workspace.apps[name]) throw new WorkerNameTaken(id, name);
5395
+ const ranked = hosts.map((host) => ({
5396
+ host,
5397
+ holds: files.filter((file) => isUnder(file, host.root)).length
5398
+ })).sort((a, b) => b.holds - a.holds || Number(b.host.generated) - Number(a.host.generated));
5399
+ const host = ranked[0]?.host;
5400
+ if (!host || ranked[0].holds === 0 && !host.generated) throw new WorkerHasNoApp(id);
5401
+ units.push({
5402
+ id,
5403
+ name,
5404
+ app: host.name
5405
+ });
5406
+ }
5407
+ return units.sort((a, b) => a.name.localeCompare(b.name));
5408
+ }
5409
+ function isUnder(file, dir) {
5410
+ const rel = relative(dir, file);
5411
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute(rel) ? !rel.startsWith(`..${sep}`) : false;
5412
+ }
5413
+ //#endregion
4587
5414
  //#region src/deploy/devServices.ts
4588
5415
  /**
4589
5416
  * Mail and object storage on a deployed stage: real ones, or — only when
@@ -5041,7 +5868,7 @@ async function dockerCommand(options) {
5041
5868
  const cwd = options.cwd ?? process.cwd();
5042
5869
  const loaded = await loadWorkspaceConfig(cwd);
5043
5870
  const names = loaded.type === "single" ? Object.fromEntries(Object.keys(loaded.workspace.apps).map((name) => [name, resolveDockerConfig(loaded.raw).imageName])) : {};
5044
- const result = await workspaceDockerCommand(loaded.workspace, loaded.manifest, names);
5871
+ const result = await workspaceDockerCommand(loaded.workspace, loaded.manifest, names, loaded.background);
5045
5872
  if (options.build) for (const app of result.apps) await buildDockerImage(app, result.buildRoot, options);
5046
5873
  if (options.push) for (const app of result.apps) await pushDockerImage(app.imageName, options, result.buildRoot);
5047
5874
  return result;
@@ -5093,7 +5920,7 @@ async function pushDockerImage(imageName, options, cwd) {
5093
5920
  * Generate Dockerfiles for all apps in a workspace.
5094
5921
  * @internal Exported for testing
5095
5922
  */
5096
- async function workspaceDockerCommand(workspace, manifest, imageNames = {}) {
5923
+ async function workspaceDockerCommand(workspace, manifest, imageNames = {}, background = {}) {
5097
5924
  const results = [];
5098
5925
  const layout = imageLayout(workspace);
5099
5926
  logger$1.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
@@ -5123,6 +5950,24 @@ async function workspaceDockerCommand(workspace, manifest, imageNames = {}) {
5123
5950
  imageName: imageNames[appName] ?? appName
5124
5951
  });
5125
5952
  }
5953
+ for (const worker of manifest ? workerUnits(workspace, manifest, background) : []) {
5954
+ logger$1.log(`\n 📄 Generating Dockerfile for ${worker.name} (worker ${worker.id}, built from ${worker.app})`);
5955
+ const dockerfile = workerDockerfile(worker, workspace.apps[worker.app], {
5956
+ layout,
5957
+ workspaceRoot: workspace.root
5958
+ });
5959
+ const path = workerDockerfileOf(worker.name);
5960
+ const dockerfilePath = join(workspace.root, path);
5961
+ await mkdir(dirname(dockerfilePath), { recursive: true });
5962
+ await writeFile(dockerfilePath, dockerfile);
5963
+ logger$1.log(` Generated: ${path}`);
5964
+ results.push({
5965
+ appName: worker.name,
5966
+ type: "backend",
5967
+ dockerfile: dockerfilePath,
5968
+ imageName: imageNames[worker.name] ?? worker.name
5969
+ });
5970
+ }
5126
5971
  const dockerignorePath = await ensureDockerignore(layout.buildRoot);
5127
5972
  logger$1.log(`\n Ensured: ${dockerignorePath}`);
5128
5973
  const composePath = await writeConstructsCompose(workspace);
@@ -5159,7 +6004,27 @@ function appDockerfile(appName, app, options) {
5159
6004
  });
5160
6005
  return generateBackendDockerfile({
5161
6006
  ...image,
5162
- healthCheckPath: "/health"
6007
+ healthCheckPath: "/health",
6008
+ ...options.cache ? { cache: options.cache } : {}
6009
+ });
6010
+ }
6011
+ /** Where `gkm docker` writes a worker's Dockerfile. */
6012
+ function workerDockerfileOf(worker) {
6013
+ return `.gkm/docker/Dockerfile.${worker}`;
6014
+ }
6015
+ /**
6016
+ * A Worker's Dockerfile: its host app's build, and the worker's bundle as the
6017
+ * runner — see `generateWorkerDockerfile`.
6018
+ */
6019
+ function workerDockerfile(worker, host, options) {
6020
+ return generateWorkerDockerfile({
6021
+ ...appImageOptions(options.layout, worker.app, host, options.workspaceRoot),
6022
+ imageName: worker.name,
6023
+ port: WORKER_PORT,
6024
+ worker: worker.id,
6025
+ bundle: workerBundleName(worker.id),
6026
+ healthCheckPath: "/health",
6027
+ ...options.cache ? { cache: options.cache } : {}
5163
6028
  });
5164
6029
  }
5165
6030
  /**
@@ -5399,7 +6264,7 @@ async function deployDocker(options) {
5399
6264
  const cwd = options.cwd ?? process.cwd();
5400
6265
  const docker = {
5401
6266
  cwd,
5402
- dockerfile: join(cwd, dockerfileOf(config.appName ?? imageName, options.appPath ?? ".")),
6267
+ dockerfile: join(cwd, options.dockerfile ?? dockerfileOf(config.appName ?? imageName, options.appPath ?? ".")),
5403
6268
  ...options.signal ? { signal: options.signal } : {},
5404
6269
  ...options.stdio ? { stdio: options.stdio } : {}
5405
6270
  };
@@ -5725,6 +6590,6 @@ function setPostgresBackupId(state, backupId) {
5725
6590
  if (state.backups) state.backups.postgresBackupId = backupId;
5726
6591
  }
5727
6592
  //#endregion
5728
- export { applyPostgres as $, storage_exports as $t, STORAGE_KEY_PAIR as A, findAvailablePort as At, encryptSecrets as B, keystoreProject as Bt, isMainFrontendApp as C, DEFAULT_IMAGES as Ct, DEV_SERVICES as D, primaryPortKey as Dt, otelEnv as E, portKeys as Et, manifestServiceDeclarations as F, isRemoteStore as Ft, constructGlobs as G, imageName as Gt, keyFingerprint as H, readKey as Ht, parseDevServices as I, secretsStoreFor as It, workspace_exports as J, markerOf as Jt, derivedContainers as K, imageRef as Kt, reportDevServices as L, store_exports as Lt, assertExternalServices as M, loadEnvFiles as Mt, devServicesUsed as N, loadSecretsForApp as Nt, DevServicesNeedServerTarget as O, createCredentialsPreload as Ot, externalServices as P, prepareEntryCredentials as Pt, applyPolicies as Q, maskPassword as Qt, requiredStageKeys as R, FileSecretsStore as Rt, NoDomainForStage as S, storageBackendFor as St, isOtlpDestination as T, fakeKey as Tt, FAKE_ENV as U, applicationName as Ut, encryption_exports as V, projectKey as Vt, backendsOf as W, deployIdentity as Wt, forgetState as X, projectName as Xt, LOCAL_CA_PATH as Y, ownershipMarker as Yt, applyBuckets as Z, initStageSecrets as Zt, deployDocker as _, detectPackageManager as _t, recordRelease as a, bucketClient as at, ensureDockerignore as b, eventsBackendFor as bt, setBackupState as c, networkEnv as ct, setGeneratedSecret as d, planFor as dt, toEmbeddableSecrets as en, bucketPolicies as et, setPostgresBackupId as f, resourceName as ft, credentialsFileContent as g, TURBO_VERSION as gt, credentialsBuildArg as h, imageLayout as ht, isDnsVerified as i, composeFor as it, UnknownDevService as j, isPortAvailable as jt, ExternalServicesNotConfigured as k, createEntryWrapper as kt, setDnsRecord as l, envFor as lt, withGeneratedSecrets as m, composeBuildPaths as mt, getBackupState as n, postgresStatements as nt, recordRollback as o, pgClient as ot, DeployJournal as p, hostFor as pt, reconcileWorkspace as q, isOwnedBy as qt, getGeneratedSecret as r, quoteIdentifier as rt, setApplicationId as s, appEnvKeys as st, createEmptyState as t, withCustomSecret as tn, postgresDatabaseNames as tt, setDnsVerification as u, localRolePassword as ut, appDockerfile as v, findBuildRoot as vt, resolveHost as w, describeServices as wt, validateImageRef as x, providerOf as xt, dockerCommand as y, cacheBackendFor as yt, suppliedOnly as z, getKeyPath as zt };
6593
+ export { constructGlobs as $, applicationName as $t, DevServicesNeedServerTarget as A, eventsBackendFor as At, requiredStageKeys as B, findAvailablePort as Bt, validateImageRef as C, composeBuildPaths as Ct, isOtlpDestination as D, detectPackageManager as Dt, resolveHost as E, WORKER_PORT as Et, devServicesUsed as F, fakeKey as Ft, keyFingerprint as G, isRemoteStore as Gt, workerUnits as H, loadEnvFiles as Ht, externalServices as I, portKeys as It, workerEntryDir as J, FileSecretsStore as Jt, WorkerGenerator as K, secretsStoreFor as Kt, manifestServiceDeclarations as L, primaryPortKey as Lt, STORAGE_KEY_PAIR as M, storageBackendFor as Mt, UnknownDevService as N, DEFAULT_IMAGES as Nt, otelEnv as O, findBuildRoot as Ot, assertExternalServices as P, describeServices as Pt, backendsOf as Q, readKey as Qt, parseDevServices as R, createCredentialsPreload as Rt, workerDockerfileOf as S, hostFor as St, isMainFrontendApp as T, TURBO_VERSION as Tt, encryptSecrets as U, loadSecretsForApp as Ut, suppliedOnly as V, isPortAvailable as Vt, encryption_exports as W, prepareEntryCredentials as Wt, CronGenerator as X, keystoreProject as Xt, QueueGenerator as Y, getKeyPath as Yt, FAKE_ENV as Z, projectKey as Zt, deployDocker as _, workerEnvKeys as _t, recordRelease as a, ownershipMarker as an, applyBuckets as at, ensureDockerignore as b, planFor as bt, setBackupState as c, maskPassword as cn, bucketPolicies as ct, setGeneratedSecret as d, withCustomSecret as dn, quoteIdentifier as dt, deployIdentity as en, derivedContainers as et, setPostgresBackupId as f, composeFor as ft, credentialsFileContent as g, networkEnv as gt, credentialsBuildArg as h, appEnvKeys as ht, isDnsVerified as i, markerOf as in, forgetState as it, ExternalServicesNotConfigured as j, providerOf as jt, DEV_SERVICES as k, cacheBackendFor as kt, setDnsRecord as l, storage_exports as ln, postgresDatabaseNames as lt, withGeneratedSecrets as m, pgClient as mt, getBackupState as n, imageRef as nn, workspace_exports as nt, recordRollback as o, projectName as on, applyPolicies as ot, DeployJournal as p, bucketClient as pt, workerBundleName as q, store_exports as qt, getGeneratedSecret as r, isOwnedBy as rn, LOCAL_CA_PATH as rt, setApplicationId as s, initStageSecrets as sn, applyPostgres as st, createEmptyState as t, imageName as tn, reconcileWorkspace as tt, setDnsVerification as u, toEmbeddableSecrets as un, postgresStatements as ut, appDockerfile as v, envFor as vt, NoDomainForStage as w, imageLayout as wt, workerDockerfile as x, resourceName as xt, dockerCommand as y, localRolePassword as yt, reportDevServices as z, createEntryWrapper as zt };
5729
6594
 
5730
- //# sourceMappingURL=state-BJFherj1.mjs.map
6595
+ //# sourceMappingURL=state-DpkxSwzI.mjs.map