@geekmidas/cli 10.0.0-alpha.69 → 10.0.0-alpha.71

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 (148) hide show
  1. package/dist/{LegacyStateStore-LS_uTO8D.cjs → LegacyStateStore-Dd5IwiyC.cjs} +2 -2
  2. package/dist/{LegacyStateStore-LS_uTO8D.cjs.map → LegacyStateStore-Dd5IwiyC.cjs.map} +1 -1
  3. package/dist/{LegacyStateStore-BE1Et8RS.mjs → LegacyStateStore-NSHEinQi.mjs} +2 -2
  4. package/dist/{LegacyStateStore-BE1Et8RS.mjs.map → LegacyStateStore-NSHEinQi.mjs.map} +1 -1
  5. package/dist/{LocalStateStore-CV6phNMZ.cjs → LocalStateStore-DTylG4T4.cjs} +2 -2
  6. package/dist/{LocalStateStore-CV6phNMZ.cjs.map → LocalStateStore-DTylG4T4.cjs.map} +1 -1
  7. package/dist/{LocalStateStore-DAnFc708.mjs → LocalStateStore-DUoG2wA_.mjs} +2 -2
  8. package/dist/{LocalStateStore-DAnFc708.mjs.map → LocalStateStore-DUoG2wA_.mjs.map} +1 -1
  9. package/dist/{OpenApiTsGenerator-CSaXs-dB.mjs → OpenApiTsGenerator-Bhr_XWWN.mjs} +57 -638
  10. package/dist/OpenApiTsGenerator-Bhr_XWWN.mjs.map +1 -0
  11. package/dist/{OpenApiTsGenerator-CxB8mqXz.cjs → OpenApiTsGenerator-DZZwHdDC.cjs} +57 -638
  12. package/dist/OpenApiTsGenerator-DZZwHdDC.cjs.map +1 -0
  13. package/dist/{S3StateStore-dA34LAYm.cjs → S3StateStore-D7hrU-sy.cjs} +2 -2
  14. package/dist/{S3StateStore-dA34LAYm.cjs.map → S3StateStore-D7hrU-sy.cjs.map} +1 -1
  15. package/dist/{S3StateStore-8kJEGnTh.mjs → S3StateStore-Dxob3PNA.mjs} +2 -2
  16. package/dist/{S3StateStore-8kJEGnTh.mjs.map → S3StateStore-Dxob3PNA.mjs.map} +1 -1
  17. package/dist/{SSMStateStore-DBzlfc-S.cjs → SSMStateStore-DGIKJ4NN.cjs} +2 -2
  18. package/dist/{SSMStateStore-DBzlfc-S.cjs.map → SSMStateStore-DGIKJ4NN.cjs.map} +1 -1
  19. package/dist/{SSMStateStore-Dkdw-fPL.mjs → SSMStateStore-Dvl4A8Up.mjs} +2 -2
  20. package/dist/{SSMStateStore-Dkdw-fPL.mjs.map → SSMStateStore-Dvl4A8Up.mjs.map} +1 -1
  21. package/dist/{add-CoIr2wgv.cjs → add-DeC2Kyyv.cjs} +3 -3
  22. package/dist/{add-CoIr2wgv.cjs.map → add-DeC2Kyyv.cjs.map} +1 -1
  23. package/dist/{add-Dzk23476.mjs → add-XTLU19_E.mjs} +3 -3
  24. package/dist/{add-Dzk23476.mjs.map → add-XTLU19_E.mjs.map} +1 -1
  25. package/dist/{bundler-CSR2nM4m.cjs → bundler-CEaR6Dpb.cjs} +6 -6
  26. package/dist/bundler-CEaR6Dpb.cjs.map +1 -0
  27. package/dist/{bundler-Cn0JF5G0.mjs → bundler-CNTVLCCA.mjs} +6 -6
  28. package/dist/bundler-CNTVLCCA.mjs.map +1 -0
  29. package/dist/{cli-ij8Lwlww.mjs → cli-D1arS0p1.mjs} +3 -3
  30. package/dist/{cli-ij8Lwlww.mjs.map → cli-D1arS0p1.mjs.map} +1 -1
  31. package/dist/{cli-IoM2-x48.cjs → cli-MMgBrHx7.cjs} +3 -3
  32. package/dist/{cli-IoM2-x48.cjs.map → cli-MMgBrHx7.cjs.map} +1 -1
  33. package/dist/{compose-DfIioFX0.cjs → compose-B-MME-4C.cjs} +358 -16
  34. package/dist/compose-B-MME-4C.cjs.map +1 -0
  35. package/dist/{compose-CZtinmtw.mjs → compose-T3hnnjKZ.mjs} +353 -17
  36. package/dist/compose-T3hnnjKZ.mjs.map +1 -0
  37. package/dist/{config-CxEp_g7j.d.mts → config-BN6iXvyR.d.mts} +3 -3
  38. package/dist/{config-BnA0kPTY.d.cts.map → config-BN6iXvyR.d.mts.map} +1 -1
  39. package/dist/{config-BnA0kPTY.d.cts → config-j9zBKaEG.d.cts} +3 -3
  40. package/dist/{config-CxEp_g7j.d.mts.map → config-j9zBKaEG.d.cts.map} +1 -1
  41. package/dist/config.cjs +1 -1
  42. package/dist/config.d.cts +3 -3
  43. package/dist/config.d.mts +3 -3
  44. package/dist/config.mjs +1 -1
  45. package/dist/deploy/public.cjs +6 -6
  46. package/dist/deploy/public.d.cts +3 -3
  47. package/dist/deploy/public.d.cts.map +1 -1
  48. package/dist/deploy/public.d.mts +3 -3
  49. package/dist/deploy/public.d.mts.map +1 -1
  50. package/dist/deploy/public.mjs +6 -6
  51. package/dist/{deploy-Dpz9raPM.cjs → deploy-CZE0m6jd.cjs} +5 -5
  52. package/dist/{deploy-Dpz9raPM.cjs.map → deploy-CZE0m6jd.cjs.map} +1 -1
  53. package/dist/{deploy-BzqbkJ3o.mjs → deploy-ClC53rc6.mjs} +5 -5
  54. package/dist/{deploy-BzqbkJ3o.mjs.map → deploy-ClC53rc6.mjs.map} +1 -1
  55. package/dist/{dokploy-DkwcORbv.mjs → dokploy-D1UwQKm3.mjs} +2 -2
  56. package/dist/{dokploy-DkwcORbv.mjs.map → dokploy-D1UwQKm3.mjs.map} +1 -1
  57. package/dist/{dokploy-B6O-J1fb.cjs → dokploy-lwmH05zr.cjs} +2 -2
  58. package/dist/{dokploy-B6O-J1fb.cjs.map → dokploy-lwmH05zr.cjs.map} +1 -1
  59. package/dist/{encryption-C8Ic5t0C.cjs → encryption-D05sNwED.cjs} +3 -3
  60. package/dist/{encryption-C8Ic5t0C.cjs.map → encryption-D05sNwED.cjs.map} +1 -1
  61. package/dist/{encryption-7m1mKcRy.mjs → encryption-DcrxWl1s.mjs} +3 -3
  62. package/dist/{encryption-7m1mKcRy.mjs.map → encryption-DcrxWl1s.mjs.map} +1 -1
  63. package/dist/{engine-BR9qlS7A.cjs → engine-Cry1vwMz.cjs} +6 -5
  64. package/dist/engine-Cry1vwMz.cjs.map +1 -0
  65. package/dist/{engine-62Kl70Pj.mjs → engine-DmQS3XHX.mjs} +6 -5
  66. package/dist/{engine-62Kl70Pj.mjs.map → engine-DmQS3XHX.mjs.map} +1 -1
  67. package/dist/{github-CmGIZjhu.cjs → github-BchWoiVx.cjs} +3 -3
  68. package/dist/{github-CmGIZjhu.cjs.map → github-BchWoiVx.cjs.map} +1 -1
  69. package/dist/{github-DtymyOcH.mjs → github-CqYOP8cI.mjs} +3 -3
  70. package/dist/{github-DtymyOcH.mjs.map → github-CqYOP8cI.mjs.map} +1 -1
  71. package/dist/{index-CkX89m6S.d.mts → index-Bk3NY6DN.d.cts} +2 -2
  72. package/dist/{index-CkX89m6S.d.mts.map → index-Bk3NY6DN.d.cts.map} +1 -1
  73. package/dist/{index-EvZBkycu.d.cts → index-DvoxuTqR.d.mts} +2 -2
  74. package/dist/{index-EvZBkycu.d.cts.map → index-DvoxuTqR.d.mts.map} +1 -1
  75. package/dist/index.cjs +53 -52
  76. package/dist/index.cjs.map +1 -1
  77. package/dist/index.mjs +53 -52
  78. package/dist/index.mjs.map +1 -1
  79. package/dist/{migrate-CEzq6DjL.mjs → migrate-guck0NJK.mjs} +3 -3
  80. package/dist/{migrate-CEzq6DjL.mjs.map → migrate-guck0NJK.mjs.map} +1 -1
  81. package/dist/{migrate-CYC-hBOU.cjs → migrate-qKeGYq21.cjs} +3 -3
  82. package/dist/{migrate-CYC-hBOU.cjs.map → migrate-qKeGYq21.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/{ready-QGgjmcuN.cjs → ready-Dp6arkI4.cjs} +5 -5
  88. package/dist/ready-Dp6arkI4.cjs.map +1 -0
  89. package/dist/{ready-DeYaSoNN.mjs → ready-xpoX11uu.mjs} +5 -5
  90. package/dist/ready-xpoX11uu.mjs.map +1 -0
  91. package/dist/reconcile/public.d.cts +1 -1
  92. package/dist/reconcile/public.d.mts +1 -1
  93. package/dist/{resolve-Dn5wGf1r.mjs → resolve-BsXEXAUz.mjs} +3 -3
  94. package/dist/{resolve-Dn5wGf1r.mjs.map → resolve-BsXEXAUz.mjs.map} +1 -1
  95. package/dist/{resolve-BDsMffvz.cjs → resolve-wEjEvbJ5.cjs} +3 -3
  96. package/dist/{resolve-BDsMffvz.cjs.map → resolve-wEjEvbJ5.cjs.map} +1 -1
  97. package/dist/{rollback-DDfaqv7P.mjs → rollback-Bk91csFg.mjs} +5 -5
  98. package/dist/{rollback-DDfaqv7P.mjs.map → rollback-Bk91csFg.mjs.map} +1 -1
  99. package/dist/{rollback-5XHaBDxW.cjs → rollback-DmcU-efZ.cjs} +5 -5
  100. package/dist/{rollback-5XHaBDxW.cjs.map → rollback-DmcU-efZ.cjs.map} +1 -1
  101. package/dist/{runtime-B3jNgeP-.d.mts → runtime-CYAO4iZG.d.cts} +2 -2
  102. package/dist/{runtime-B3jNgeP-.d.mts.map → runtime-CYAO4iZG.d.cts.map} +1 -1
  103. package/dist/{runtime-DWThEP-r.d.cts → runtime-DHJhe4QI.d.mts} +2 -2
  104. package/dist/{runtime-DWThEP-r.d.cts.map → runtime-DHJhe4QI.d.mts.map} +1 -1
  105. package/dist/{sniffer-Bc0nNBDF.cjs → sniffer-DpHpFZBD.cjs} +6 -6
  106. package/dist/sniffer-DpHpFZBD.cjs.map +1 -0
  107. package/dist/{sniffer-DMqi2b1q.mjs → sniffer-vAXsU_4G.mjs} +6 -6
  108. package/dist/sniffer-vAXsU_4G.mjs.map +1 -0
  109. package/dist/{state-DClnPZt5.mjs → state-BJFherj1.mjs} +60 -5
  110. package/dist/state-BJFherj1.mjs.map +1 -0
  111. package/dist/{state-8RgXeX5Y.cjs → state-CCuTJNRH.cjs} +71 -4
  112. package/dist/state-CCuTJNRH.cjs.map +1 -0
  113. package/dist/target/public.cjs +1 -1
  114. package/dist/target/public.d.cts +2 -2
  115. package/dist/target/public.d.mts +2 -2
  116. package/dist/target/public.mjs +1 -1
  117. package/dist/{types-CZdCxUCK.d.cts → types-BD1jgA85.d.cts} +70 -2
  118. package/dist/types-BD1jgA85.d.cts.map +1 -0
  119. package/dist/{types-Da0E3Qv7.d.mts → types-DkG5WW8l.d.mts} +70 -2
  120. package/dist/types-DkG5WW8l.d.mts.map +1 -0
  121. package/dist/vitest.cjs +1 -1
  122. package/dist/vitest.mjs +1 -1
  123. package/dist/workspace/index.cjs +1 -1
  124. package/dist/workspace/index.d.cts +3 -3
  125. package/dist/workspace/index.d.mts +3 -3
  126. package/dist/workspace/index.mjs +1 -1
  127. package/dist/{workspace-B2UK_rVP.mjs → workspace-C7NqCsQF.mjs} +104 -2
  128. package/dist/workspace-C7NqCsQF.mjs.map +1 -0
  129. package/dist/{workspace-8NuAjaXJ.cjs → workspace-vdrcRI-m.cjs} +115 -1
  130. package/dist/workspace-vdrcRI-m.cjs.map +1 -0
  131. package/package.json +15 -15
  132. package/dist/OpenApiTsGenerator-CSaXs-dB.mjs.map +0 -1
  133. package/dist/OpenApiTsGenerator-CxB8mqXz.cjs.map +0 -1
  134. package/dist/bundler-CSR2nM4m.cjs.map +0 -1
  135. package/dist/bundler-Cn0JF5G0.mjs.map +0 -1
  136. package/dist/compose-CZtinmtw.mjs.map +0 -1
  137. package/dist/compose-DfIioFX0.cjs.map +0 -1
  138. package/dist/engine-BR9qlS7A.cjs.map +0 -1
  139. package/dist/ready-DeYaSoNN.mjs.map +0 -1
  140. package/dist/ready-QGgjmcuN.cjs.map +0 -1
  141. package/dist/sniffer-Bc0nNBDF.cjs.map +0 -1
  142. package/dist/sniffer-DMqi2b1q.mjs.map +0 -1
  143. package/dist/state-8RgXeX5Y.cjs.map +0 -1
  144. package/dist/state-DClnPZt5.mjs.map +0 -1
  145. package/dist/types-CZdCxUCK.d.cts.map +0 -1
  146. package/dist/types-Da0E3Qv7.d.mts.map +0 -1
  147. package/dist/workspace-8NuAjaXJ.cjs.map +0 -1
  148. package/dist/workspace-B2UK_rVP.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"cli-IoM2-x48.cjs","names":["targetForProvider","formatError","parseDevServices","deploy","storedCredentials","terminalCredentials"],"sources":["../src/deploy/cli.ts"],"sourcesContent":["/**\n * `gkm deploy`: the terminal around `deploy()`. CLI layer only.\n *\n * Everything that belongs to a person at a terminal lives here and nowhere\n * below it: prompting for a missing login (and storing it), printing progress,\n * and turning the outcome into an exit code. The deploy itself is the same\n * `deploy()` a host calls.\n */\n\nimport { formatError } from '../debug';\nimport { targetForProvider } from '../target/provider';\nimport { storedCredentials } from './credentials';\nimport { deploy } from './deploy';\nimport { type DevService, parseDevServices } from './devServices';\nimport { terminalCredentials } from './terminal';\n\nexport interface DeployCliOptions {\n\t/** The project directory: `--cwd`, else where the command was run. */\n\tcwd: string;\n\t/** `--target`: where to deploy. Defaults to `deploy.default`. */\n\ttarget?: string;\n\t/** `--provider`, deprecated: `dokploy` means `--target dokploy`. */\n\tprovider?: string;\n\tstage: string;\n\ttag?: string;\n\t/**\n\t * Print each event as one JSON object per line on stdout, instead of the\n\t * progress lines. Nothing prompts: a missing credential fails the run.\n\t */\n\tjson?: boolean;\n\t/** Plan only: create, change, build and push nothing. */\n\tdryRun?: boolean;\n\t/** On a failed release, roll back every app, not only the failed ones. */\n\tatomic?: boolean;\n\t/** `--allow-dev-services minio,mailpit`, as typed. */\n\tallowDevServices?: string;\n}\n\n/** Where the command writes. The process's own streams, outside tests. */\nexport interface DeployCliStreams {\n\tstdout: Pick<NodeJS.WriteStream, 'write'>;\n}\n\n/**\n * Run `gkm deploy` and resolve with its exit code: 0 when the run finished,\n * 1 when anything stopped it.\n */\nexport async function deployCli(\n\toptions: DeployCliOptions,\n\tstreams: DeployCliStreams = { stdout: process.stdout },\n): Promise<number> {\n\tlet target = options.target;\n\tif (options.provider !== undefined && target === undefined) {\n\t\ttry {\n\t\t\t// On stderr, so a `--json` run's stdout still carries only events.\n\t\t\ttarget = targetForProvider(options.provider, (message) =>\n\t\t\t\tconsole.warn(message),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.error(formatError(error));\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tlet allowDevServices: DevService[];\n\ttry {\n\t\tallowDevServices = parseDevServices(options.allowDevServices);\n\t} catch (error) {\n\t\tconsole.error(formatError(error));\n\t\treturn 1;\n\t}\n\n\tconst input = {\n\t\tcwd: options.cwd,\n\t\tstage: options.stage,\n\t\t...(target ? { target } : {}),\n\t\t...(options.tag ? { tag: options.tag } : {}),\n\t\t...(options.dryRun ? { dryRun: true } : {}),\n\t\t...(options.atomic ? { atomic: true } : {}),\n\t\t...(allowDevServices.length > 0 ? { allowDevServices } : {}),\n\t};\n\n\tif (options.json) {\n\t\t// stdout carries the events and nothing else: docker's own output goes\n\t\t// to stderr, and nobody is asked anything — a prompt would be written\n\t\t// into the stream a program is parsing.\n\t\tconst run = deploy({\n\t\t\t...input,\n\t\t\tcredentials: storedCredentials(),\n\t\t\tchildOutput: 'stderr',\n\t\t});\n\t\tfor await (const event of run) {\n\t\t\tstreams.stdout.write(`${JSON.stringify(event)}\\n`);\n\t\t}\n\t\treturn run.result.then(\n\t\t\t() => 0,\n\t\t\t(error) => {\n\t\t\t\tconsole.error(formatError(error));\n\t\t\t\treturn 1;\n\t\t\t},\n\t\t);\n\t}\n\n\t// The lines arrive as they are written, rather than as events read back a\n\t// tick later, so they interleave with a prompt exactly as they always did.\n\tconst run = deploy({\n\t\t...input,\n\t\tcredentials: terminalCredentials(storedCredentials()),\n\t\tlogger: {\n\t\t\tinfo: (message) => console.log(message),\n\t\t\twarn: (message) => console.warn(message),\n\t\t\terror: (message) => console.error(message),\n\t\t},\n\t});\n\n\ttry {\n\t\tawait run.result;\n\t\treturn 0;\n\t} catch (error) {\n\t\tconsole.error(formatError(error));\n\t\treturn 1;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA+CA,eAAsB,UACrB,SACA,UAA4B,EAAE,QAAQ,QAAQ,OAAO,GACnC;CAClB,IAAI,SAAS,QAAQ;CACrB,IAAI,QAAQ,aAAa,KAAA,KAAa,WAAW,KAAA,GAChD,IAAI;EAEH,SAASA,iBAAAA,kBAAkB,QAAQ,WAAW,YAC7C,QAAQ,KAAK,OAAO,CACrB;CACD,SAAS,OAAO;EACf,QAAQ,MAAMC,cAAAA,YAAY,KAAK,CAAC;EAChC,OAAO;CACR;CAGD,IAAI;CACJ,IAAI;EACH,mBAAmBC,cAAAA,iBAAiB,QAAQ,gBAAgB;CAC7D,SAAS,OAAO;EACf,QAAQ,MAAMD,cAAAA,YAAY,KAAK,CAAC;EAChC,OAAO;CACR;CAEA,MAAM,QAAQ;EACb,KAAK,QAAQ;EACb,OAAO,QAAQ;EACf,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC3B,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;EAC1C,GAAI,QAAQ,SAAS,EAAE,QAAQ,KAAK,IAAI,CAAC;EACzC,GAAI,QAAQ,SAAS,EAAE,QAAQ,KAAK,IAAI,CAAC;EACzC,GAAI,iBAAiB,SAAS,IAAI,EAAE,iBAAiB,IAAI,CAAC;CAC3D;CAEA,IAAI,QAAQ,MAAM;EAIjB,MAAM,MAAME,eAAAA,OAAO;GAClB,GAAG;GACH,aAAaC,oBAAAA,kBAAkB;GAC/B,aAAa;EACd,CAAC;EACD,WAAW,MAAM,SAAS,KACzB,QAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,KAAK,EAAE,GAAG;EAElD,OAAO,IAAI,OAAO,WACX,IACL,UAAU;GACV,QAAQ,MAAMH,cAAAA,YAAY,KAAK,CAAC;GAChC,OAAO;EACR,CACD;CACD;CAIA,MAAM,MAAME,eAAAA,OAAO;EAClB,GAAG;EACH,aAAaE,iBAAAA,oBAAoBD,oBAAAA,kBAAkB,CAAC;EACpD,QAAQ;GACP,OAAO,YAAY,QAAQ,IAAI,OAAO;GACtC,OAAO,YAAY,QAAQ,KAAK,OAAO;GACvC,QAAQ,YAAY,QAAQ,MAAM,OAAO;EAC1C;CACD,CAAC;CAED,IAAI;EACH,MAAM,IAAI;EACV,OAAO;CACR,SAAS,OAAO;EACf,QAAQ,MAAMH,cAAAA,YAAY,KAAK,CAAC;EAChC,OAAO;CACR;AACD"}
1
+ {"version":3,"file":"cli-MMgBrHx7.cjs","names":["targetForProvider","formatError","parseDevServices","deploy","storedCredentials","terminalCredentials"],"sources":["../src/deploy/cli.ts"],"sourcesContent":["/**\n * `gkm deploy`: the terminal around `deploy()`. CLI layer only.\n *\n * Everything that belongs to a person at a terminal lives here and nowhere\n * below it: prompting for a missing login (and storing it), printing progress,\n * and turning the outcome into an exit code. The deploy itself is the same\n * `deploy()` a host calls.\n */\n\nimport { formatError } from '../debug';\nimport { targetForProvider } from '../target/provider';\nimport { storedCredentials } from './credentials';\nimport { deploy } from './deploy';\nimport { type DevService, parseDevServices } from './devServices';\nimport { terminalCredentials } from './terminal';\n\nexport interface DeployCliOptions {\n\t/** The project directory: `--cwd`, else where the command was run. */\n\tcwd: string;\n\t/** `--target`: where to deploy. Defaults to `deploy.default`. */\n\ttarget?: string;\n\t/** `--provider`, deprecated: `dokploy` means `--target dokploy`. */\n\tprovider?: string;\n\tstage: string;\n\ttag?: string;\n\t/**\n\t * Print each event as one JSON object per line on stdout, instead of the\n\t * progress lines. Nothing prompts: a missing credential fails the run.\n\t */\n\tjson?: boolean;\n\t/** Plan only: create, change, build and push nothing. */\n\tdryRun?: boolean;\n\t/** On a failed release, roll back every app, not only the failed ones. */\n\tatomic?: boolean;\n\t/** `--allow-dev-services minio,mailpit`, as typed. */\n\tallowDevServices?: string;\n}\n\n/** Where the command writes. The process's own streams, outside tests. */\nexport interface DeployCliStreams {\n\tstdout: Pick<NodeJS.WriteStream, 'write'>;\n}\n\n/**\n * Run `gkm deploy` and resolve with its exit code: 0 when the run finished,\n * 1 when anything stopped it.\n */\nexport async function deployCli(\n\toptions: DeployCliOptions,\n\tstreams: DeployCliStreams = { stdout: process.stdout },\n): Promise<number> {\n\tlet target = options.target;\n\tif (options.provider !== undefined && target === undefined) {\n\t\ttry {\n\t\t\t// On stderr, so a `--json` run's stdout still carries only events.\n\t\t\ttarget = targetForProvider(options.provider, (message) =>\n\t\t\t\tconsole.warn(message),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.error(formatError(error));\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\tlet allowDevServices: DevService[];\n\ttry {\n\t\tallowDevServices = parseDevServices(options.allowDevServices);\n\t} catch (error) {\n\t\tconsole.error(formatError(error));\n\t\treturn 1;\n\t}\n\n\tconst input = {\n\t\tcwd: options.cwd,\n\t\tstage: options.stage,\n\t\t...(target ? { target } : {}),\n\t\t...(options.tag ? { tag: options.tag } : {}),\n\t\t...(options.dryRun ? { dryRun: true } : {}),\n\t\t...(options.atomic ? { atomic: true } : {}),\n\t\t...(allowDevServices.length > 0 ? { allowDevServices } : {}),\n\t};\n\n\tif (options.json) {\n\t\t// stdout carries the events and nothing else: docker's own output goes\n\t\t// to stderr, and nobody is asked anything — a prompt would be written\n\t\t// into the stream a program is parsing.\n\t\tconst run = deploy({\n\t\t\t...input,\n\t\t\tcredentials: storedCredentials(),\n\t\t\tchildOutput: 'stderr',\n\t\t});\n\t\tfor await (const event of run) {\n\t\t\tstreams.stdout.write(`${JSON.stringify(event)}\\n`);\n\t\t}\n\t\treturn run.result.then(\n\t\t\t() => 0,\n\t\t\t(error) => {\n\t\t\t\tconsole.error(formatError(error));\n\t\t\t\treturn 1;\n\t\t\t},\n\t\t);\n\t}\n\n\t// The lines arrive as they are written, rather than as events read back a\n\t// tick later, so they interleave with a prompt exactly as they always did.\n\tconst run = deploy({\n\t\t...input,\n\t\tcredentials: terminalCredentials(storedCredentials()),\n\t\tlogger: {\n\t\t\tinfo: (message) => console.log(message),\n\t\t\twarn: (message) => console.warn(message),\n\t\t\terror: (message) => console.error(message),\n\t\t},\n\t});\n\n\ttry {\n\t\tawait run.result;\n\t\treturn 0;\n\t} catch (error) {\n\t\tconsole.error(formatError(error));\n\t\treturn 1;\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA+CA,eAAsB,UACrB,SACA,UAA4B,EAAE,QAAQ,QAAQ,OAAO,GACnC;CAClB,IAAI,SAAS,QAAQ;CACrB,IAAI,QAAQ,aAAa,KAAA,KAAa,WAAW,KAAA,GAChD,IAAI;EAEH,SAASA,iBAAAA,kBAAkB,QAAQ,WAAW,YAC7C,QAAQ,KAAK,OAAO,CACrB;CACD,SAAS,OAAO;EACf,QAAQ,MAAMC,cAAAA,YAAY,KAAK,CAAC;EAChC,OAAO;CACR;CAGD,IAAI;CACJ,IAAI;EACH,mBAAmBC,cAAAA,iBAAiB,QAAQ,gBAAgB;CAC7D,SAAS,OAAO;EACf,QAAQ,MAAMD,cAAAA,YAAY,KAAK,CAAC;EAChC,OAAO;CACR;CAEA,MAAM,QAAQ;EACb,KAAK,QAAQ;EACb,OAAO,QAAQ;EACf,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC3B,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;EAC1C,GAAI,QAAQ,SAAS,EAAE,QAAQ,KAAK,IAAI,CAAC;EACzC,GAAI,QAAQ,SAAS,EAAE,QAAQ,KAAK,IAAI,CAAC;EACzC,GAAI,iBAAiB,SAAS,IAAI,EAAE,iBAAiB,IAAI,CAAC;CAC3D;CAEA,IAAI,QAAQ,MAAM;EAIjB,MAAM,MAAME,eAAAA,OAAO;GAClB,GAAG;GACH,aAAaC,oBAAAA,kBAAkB;GAC/B,aAAa;EACd,CAAC;EACD,WAAW,MAAM,SAAS,KACzB,QAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,KAAK,EAAE,GAAG;EAElD,OAAO,IAAI,OAAO,WACX,IACL,UAAU;GACV,QAAQ,MAAMH,cAAAA,YAAY,KAAK,CAAC;GAChC,OAAO;EACR,CACD;CACD;CAIA,MAAM,MAAME,eAAAA,OAAO;EAClB,GAAG;EACH,aAAaE,iBAAAA,oBAAoBD,oBAAAA,kBAAkB,CAAC;EACpD,QAAQ;GACP,OAAO,YAAY,QAAQ,IAAI,OAAO;GACtC,OAAO,YAAY,QAAQ,KAAK,OAAO;GACvC,QAAQ,YAAY,QAAQ,MAAM,OAAO;EAC1C;CACD,CAAC;CAED,IAAI;EACH,MAAM,IAAI;EACV,OAAO;CACR,SAAS,OAAO;EACf,QAAQ,MAAMH,cAAAA,YAAY,KAAK,CAAC;EAChC,OAAO;CACR;AACD"}
@@ -2,15 +2,19 @@ const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
2
2
  const require_run = require("./run-fVZ-rLnc.cjs");
3
3
  const require_discover = require("./discover-B4VjaFJk.cjs");
4
4
  const require_derive = require("./derive-DAbo49sm.cjs");
5
+ const require_workspace = require("./workspace-vdrcRI-m.cjs");
5
6
  const require_define = require("./define-DzRz8FgH.cjs");
6
- const require_state = require("./state-8RgXeX5Y.cjs");
7
+ const require_state = require("./state-CCuTJNRH.cjs");
7
8
  const require_fakes = require("./fakes-zW-jNAbQ.cjs");
8
9
  const require_databases = require("./databases-C2p5LGAp.cjs");
9
10
  const require_credentialSchemas = require("./credentialSchemas-BcNL1N5R.cjs");
10
11
  let node_path = require("node:path");
12
+ let node_fs = require("node:fs");
11
13
  let node_fs_promises = require("node:fs/promises");
14
+ let node_os = require("node:os");
12
15
  let _geekmidas_manifest = require("@geekmidas/manifest");
13
16
  let node_child_process = require("node:child_process");
17
+ let node_crypto = require("node:crypto");
14
18
  let yaml = require("yaml");
15
19
  let node_https = require("node:https");
16
20
  //#region src/compose/docker.ts
@@ -66,6 +70,7 @@ function compose(stack, args) {
66
70
  stack.project,
67
71
  "-f",
68
72
  stack.file,
73
+ ...(stack.overrides ?? []).flatMap((file) => ["-f", file]),
69
74
  ...args
70
75
  ];
71
76
  }
@@ -172,6 +177,16 @@ const dockerCompose = {
172
177
  if (!Number.isInteger(port) || port <= 0) throw new ServicePortUnknown(service, inside, output);
173
178
  return port;
174
179
  },
180
+ async health(stack, service) {
181
+ const { code, stdout } = await capture("docker", [...compose(stack, [
182
+ "ps",
183
+ "--format",
184
+ "{{.Health}}",
185
+ service
186
+ ])]);
187
+ if (code !== 0) return void 0;
188
+ return stdout.trim().split("\n")[0] || void 0;
189
+ },
175
190
  async copyOut(stack, service, from, to) {
176
191
  await (0, node_fs_promises.mkdir)((0, node_path.dirname)(to), { recursive: true });
177
192
  await require_run.run("docker", compose(stack, [
@@ -251,6 +266,184 @@ async function assertImagesExist(docker, tag, images) {
251
266
  if (missing.length > 0) throw new ImageTagNotFound(tag, missing);
252
267
  }
253
268
  //#endregion
269
+ //#region src/compose/logs.ts
270
+ /**
271
+ * The stack's log UI: OpenObserve, run beside the apps when
272
+ * `deploy.compose.logs` asks for it, and every backend's OpenTelemetry
273
+ * pointed at it.
274
+ *
275
+ * The production server already exports its traces and pino logs over
276
+ * OTLP/HTTP when `OTEL_EXPORTER_OTLP_ENDPOINT` is set. OpenObserve takes OTLP
277
+ * at `/api/<org>/v1/{logs,traces}` with HTTP basic auth, so wiring an app is
278
+ * two variables: the endpoint, `http://openobserve:5080/api/default`, and
279
+ * the header that signs in as the root user.
280
+ *
281
+ * It is reached, by default, through an SSH tunnel: published on 127.0.0.1
282
+ * only, because a port Docker publishes on every interface is opened in the
283
+ * firewall by Docker itself, past ufw's rules. Served publicly, it is a host
284
+ * on the stack's Caddy that refuses every address not allowed.
285
+ *
286
+ * Pure: the stack decides it, and the phases print and check it.
287
+ */
288
+ /**
289
+ * The image, pinned. A log store's on-disk format is the thing that must not
290
+ * move under a running stack, so a new version is a change to this line.
291
+ */
292
+ const OPENOBSERVE_IMAGE = "public.ecr.aws/zinclabs/openobserve:v1.0.4";
293
+ /** Its compose service name — the host the apps send to. */
294
+ const LOGS_SERVICE = "openobserve";
295
+ /** The organisation every app's telemetry lands in. */
296
+ const LOGS_ORG = "default";
297
+ /** The stage secrets that sign in as OpenObserve's root user. */
298
+ const LOGS_EMAIL_KEY = "ZO_ROOT_USER_EMAIL";
299
+ const LOGS_PASSWORD_KEY = "ZO_ROOT_USER_PASSWORD";
300
+ /** The stage sends its telemetry somewhere already, and logs are on too. */
301
+ var LogsEndpointConflict = class extends Error {
302
+ stage;
303
+ keys;
304
+ constructor(stage, keys) {
305
+ super(`The stage '${stage}' sets ${keys.join(", ")} in its secrets, and deploy.compose.logs also runs OpenObserve for it — each backend can send its telemetry to one place. Either keep your own OTLP backend: remove logs from deploy.compose in gkm.config.ts; or use the bundled OpenObserve: remove ${keys.join(", ")} from the stage's secrets, and every backend is pointed at it.`);
306
+ this.stage = stage;
307
+ this.keys = keys;
308
+ this.name = "LogsEndpointConflict";
309
+ }
310
+ };
311
+ /** A root password OpenObserve would refuse to start with. */
312
+ var LogsPasswordWeak = class extends Error {
313
+ stage;
314
+ constructor(stage) {
315
+ super(`The stage '${stage}' sets ${LOGS_PASSWORD_KEY}, and OpenObserve refuses it: a root password must be 8 to 128 characters with a lowercase letter, an uppercase letter, a digit and a symbol. Set another with gkm secrets:set ${LOGS_PASSWORD_KEY} '…' --stage ${stage}, or unset it to have one generated.`);
316
+ this.stage = stage;
317
+ this.name = "LogsPasswordWeak";
318
+ }
319
+ };
320
+ /** A deployed stage with logs on and no root password generated yet. */
321
+ var LogsPasswordMissing = class extends Error {
322
+ stage;
323
+ constructor(stage) {
324
+ super(`The stage '${stage}' runs OpenObserve and has no ${LOGS_PASSWORD_KEY} in its secrets. Run gkm compose --stage ${stage} without --dry-run once to generate it, or set one: gkm secrets:set ${LOGS_PASSWORD_KEY} '…' --stage ${stage}`);
325
+ this.stage = stage;
326
+ this.name = "LogsPasswordMissing";
327
+ }
328
+ };
329
+ /** Whether OpenObserve accepts `password` for its root user. */
330
+ function isStrongLogsPassword(password) {
331
+ return password.length >= 8 && password.length <= 128 && /[a-z]/.test(password) && /[A-Z]/.test(password) && /\d/.test(password) && /[^A-Za-z0-9]/.test(password);
332
+ }
333
+ /**
334
+ * A root password: 192 random bits, with a fixed tail so every class
335
+ * OpenObserve requires is present whatever the random part drew.
336
+ */
337
+ function generateLogsPassword() {
338
+ return `${(0, node_crypto.randomBytes)(24).toString("base64url")}-Zo1`;
339
+ }
340
+ /**
341
+ * The stage's secrets with a generated root password, where it has none.
342
+ * Pure, like `withGeneratedSecrets`: the caller keeps the result once the
343
+ * run goes ahead.
344
+ */
345
+ function withLogsPassword(secrets) {
346
+ if (secrets.custom?.["ZO_ROOT_USER_PASSWORD"]) return {
347
+ secrets,
348
+ generated: []
349
+ };
350
+ return {
351
+ secrets: {
352
+ ...secrets,
353
+ custom: {
354
+ ...secrets.custom,
355
+ [LOGS_PASSWORD_KEY]: generateLogsPassword()
356
+ },
357
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
358
+ },
359
+ generated: [LOGS_PASSWORD_KEY]
360
+ };
361
+ }
362
+ /**
363
+ * `OTEL_EXPORTER_OTLP_HEADERS` signing in as `email`.
364
+ *
365
+ * The variable's values are percent-decoded by the SDK (they follow the W3C
366
+ * baggage format), so the space after `Basic` is written `%20`. Base64's own
367
+ * `+`, `/` and `=` pass through decoding unchanged.
368
+ */
369
+ function otlpHeaders(email, password) {
370
+ return `Authorization=Basic%20${Buffer.from(`${email}:${password}`).toString("base64")}`;
371
+ }
372
+ /** The stack's OpenObserve for one stage. */
373
+ function stackLogs(options) {
374
+ const { config, stage, local, custom } = options;
375
+ const conflicting = Object.keys(custom).filter(require_state.isOtlpDestination).sort();
376
+ if (conflicting.length > 0) throw new LogsEndpointConflict(stage, conflicting);
377
+ if (!local && !options.domain) throw new require_state.NoDomainForStage(stage);
378
+ const email = custom["ZO_ROOT_USER_EMAIL"] ?? (local ? "admin@gkm.localhost" : `admin@${options.domain}`);
379
+ const set = custom[LOGS_PASSWORD_KEY];
380
+ if (!set && !local) throw new LogsPasswordMissing(stage);
381
+ const password = set ?? "Geekmidas-1";
382
+ if (!isStrongLogsPassword(password)) throw new LogsPasswordWeak(stage);
383
+ const host = config.public ? local ? `logs.${options.project}.localhost` : `logs.${options.domain}` : void 0;
384
+ const url = host ? `https://${host}${local && options.https !== 443 ? `:${options.https}` : ""}` : `http://localhost:${config.port}`;
385
+ return {
386
+ ...config,
387
+ email,
388
+ password,
389
+ passwordFromSecrets: Boolean(set),
390
+ url,
391
+ ...host ? { host } : {},
392
+ env: {
393
+ [LOGS_EMAIL_KEY]: email,
394
+ [LOGS_PASSWORD_KEY]: password,
395
+ ZO_DATA_DIR: "/data",
396
+ ZO_COMPACT_DATA_RETENTION_DAYS: String(config.retentionDays),
397
+ ZO_TELEMETRY: "false"
398
+ },
399
+ appEnv: {
400
+ OTEL_EXPORTER_OTLP_ENDPOINT: `http://${LOGS_SERVICE}:${require_workspace.LOGS_PORT}/api/${LOGS_ORG}`,
401
+ OTEL_EXPORTER_OTLP_HEADERS: otlpHeaders(email, password)
402
+ }
403
+ };
404
+ }
405
+ /**
406
+ * OpenObserve's service. The image has no shell, `wget` or `curl`, so its
407
+ * health check is its own binary asking the running server for its status —
408
+ * which exits non-zero when nothing answers on its port.
409
+ */
410
+ function logsService(logs) {
411
+ return {
412
+ image: OPENOBSERVE_IMAGE,
413
+ restart: "unless-stopped",
414
+ env_file: [{
415
+ path: `./${LOGS_SERVICE}.env`,
416
+ format: "raw"
417
+ }],
418
+ volumes: ["openobserve-data:/data"],
419
+ ...logs.public ? {} : { ports: [`127.0.0.1:${logs.port}:${require_workspace.LOGS_PORT}`] },
420
+ healthcheck: {
421
+ test: [
422
+ "CMD",
423
+ "/openobserve",
424
+ "node",
425
+ "status"
426
+ ],
427
+ interval: "10s",
428
+ timeout: "5s",
429
+ retries: 12,
430
+ start_period: "20s"
431
+ }
432
+ };
433
+ }
434
+ /** What `gkm compose` prints once the stack is up: how to open the logs. */
435
+ function logsAccess(logs, ctx) {
436
+ const login = `login: ${logs.email}, password: ${ctx.local && !logs.passwordFromSecrets ? logs.password : `gkm secrets:show --stage ${ctx.stage} --reveal → ${LOGS_PASSWORD_KEY}`}`;
437
+ if (logs.public) return [`📜 Logs (OpenObserve): ${logs.url} (${login})`, ` answered only for ${logs.public.allow.join(", ")}; every other address gets 403`];
438
+ return [
439
+ `📜 Logs (OpenObserve) on 127.0.0.1:${logs.port} — from your computer:`,
440
+ ...ctx.local ? [` on this machine, open http://localhost:${logs.port} directly; from another:`] : [],
441
+ ` ssh -N -L ${logs.port}:localhost:${logs.port} ${ctx.user}@${ctx.hostname} (user and host are guesses: this machine's)`,
442
+ ` then open http://localhost:${logs.port} (${login})`,
443
+ `⚠️ Docker-published ports bypass ufw, so OpenObserve is bound to 127.0.0.1 only — reach it through the tunnel, not by opening the port.`
444
+ ];
445
+ }
446
+ //#endregion
254
447
  //#region src/compose/caddyfile.ts
255
448
  /** The Caddyfile for a stack's edge. */
256
449
  function edgeCaddyfile(sites, options) {
@@ -262,8 +455,13 @@ function edgeCaddyfile(sites, options) {
262
455
  }` : options.email ? `{
263
456
  email ${options.email}
264
457
  }` : "";
265
- const blocks = sites.map(({ host, upstream, rewrite }) => `https://${host} {${options.local ? `
458
+ const blocks = sites.map(({ host, upstream, rewrite, allow }) => `https://${host} {${options.local ? `
266
459
  tls internal
460
+ ` : ""}${allow ? `
461
+ # Only these addresses — the peer Caddy sees, never a header a client
462
+ # could set. Everything else is refused before it reaches the upstream.
463
+ @denied not remote_ip ${allow.join(" ")}
464
+ respond @denied 403
267
465
  ` : ""}${rewrite ? `
268
466
  # A bucket, served at a host of its own: the bucket is a prefix on MinIO,
269
467
  # which routes and signs on the Host header — so it is sent the upstream's.
@@ -283,8 +481,9 @@ function edgeCaddyfile(sites, options) {
283
481
  }
284
482
  const HEADER = `# Generated by gkm compose from the construct manifest — do not edit.
285
483
  #
286
- # One host per API and site, each in front of its own container, and one per
287
- # file server in front of the stack's MinIO.
484
+ # One host per API and site, each in front of its own container, one per
485
+ # file server in front of the stack's MinIO, and — when its logs are served
486
+ # publicly — one for OpenObserve, to the addresses it allows.
288
487
  `;
289
488
  //#endregion
290
489
  //#region src/compose/stack.ts
@@ -351,6 +550,20 @@ const STORAGE_KEYS = [
351
550
  "AWS_SECRET_ACCESS_KEY",
352
551
  "AWS_REGION"
353
552
  ];
553
+ /**
554
+ * Every service's Docker logs, rotated. The json-file driver keeps a
555
+ * container's output in one file that grows until the disk is full — on a
556
+ * small server, in weeks. Three files of 10 MB each per container is enough
557
+ * to see what happened and never fills anything. A project's own override
558
+ * file that sets `logging` wins over it.
559
+ */
560
+ const LOG_ROTATION = {
561
+ driver: "json-file",
562
+ options: {
563
+ "max-size": "10m",
564
+ "max-file": "3"
565
+ }
566
+ };
354
567
  /** A value only the stage's secrets can hold, and they do not. */
355
568
  var StageSecretMissing = class extends Error {
356
569
  app;
@@ -460,6 +673,16 @@ function composeStack(input) {
460
673
  reads.set(app.name, site?.kind === "site" ? Object.values((0, _geekmidas_manifest.publicEnvFor)(site, manifest)) : [...require_state.appEnvKeys(manifest, app.name, input.runnables) ?? []]);
461
674
  }
462
675
  const domain = workspace.deploy?.domains?.[stage];
676
+ const logsConfig = require_workspace.resolveLogs(workspace.deploy?.compose?.logs);
677
+ const logs = logsConfig ? stackLogs({
678
+ config: logsConfig,
679
+ stage,
680
+ local,
681
+ project: workspace.name,
682
+ ...domain ? { domain } : {},
683
+ custom,
684
+ https
685
+ }) : void 0;
463
686
  const services = local ? {
464
687
  missing: [],
465
688
  minio: plan.resources.filter((r) => r.kind === "objects").map((r) => r.id),
@@ -471,7 +694,7 @@ function composeStack(input) {
471
694
  allow: input.allowDevServices ?? [],
472
695
  ...domain ? { domain } : {}
473
696
  });
474
- const infra = plan.containers.filter((c) => !NOT_RUN[c]).filter((c) => c !== "minio" || services.minio.length > 0).filter((c) => c !== "mailpit" || services.mailpit.length > 0).sort();
697
+ const infra = [...plan.containers.filter((c) => !NOT_RUN[c]).filter((c) => c !== "minio" || services.minio.length > 0).filter((c) => c !== "mailpit" || services.mailpit.length > 0), ...logs ? [LOGS_SERVICE] : []].sort();
475
698
  const storage = infra.includes("minio") ? (() => {
476
699
  const buckets = services.minio.map((id) => byId.get(id)?.name).filter((name) => Boolean(name)).sort();
477
700
  return {
@@ -572,6 +795,10 @@ function composeStack(input) {
572
795
  }
573
796
  app.env = {
574
797
  ...values,
798
+ ...require_state.otelEnv({
799
+ ...custom,
800
+ ...logs?.appEnv
801
+ }, app.name),
575
802
  NODE_ENV: "production",
576
803
  PORT: String(app.port),
577
804
  STAGE: stage
@@ -586,16 +813,25 @@ function composeStack(input) {
586
813
  apps,
587
814
  master: credential.master,
588
815
  ...storage ? { storage } : {},
816
+ ...logs ? { logs } : {},
589
817
  https,
590
818
  http,
591
819
  composeDir: (0, node_path.join)(workspace.root, stackDir(stage)),
592
820
  buildRoot: layout.buildRoot,
593
821
  workspaceRoot: workspace.root
594
822
  });
595
- const caddyfile = edgeCaddyfile([...apps.map((app) => ({
596
- host: app.host,
597
- upstream: `${app.name}:${app.port}`
598
- })), ...[...fileServers.values()].map(({ site }) => site)], { local });
823
+ const caddyfile = edgeCaddyfile([
824
+ ...apps.map((app) => ({
825
+ host: app.host,
826
+ upstream: `${app.name}:${app.port}`
827
+ })),
828
+ ...[...fileServers.values()].map(({ site }) => site),
829
+ ...logs?.host && logs.public ? [{
830
+ host: logs.host,
831
+ upstream: `${LOGS_SERVICE}:${require_workspace.LOGS_PORT}`,
832
+ allow: logs.public.allow
833
+ }] : []
834
+ ], { local });
599
835
  const dockerfiles = {};
600
836
  for (const app of apps) {
601
837
  if (!app.build) continue;
@@ -617,7 +853,8 @@ function composeStack(input) {
617
853
  plan,
618
854
  credential,
619
855
  ...storage ? { storage } : {},
620
- devServices: local ? [] : require_state.devServicesUsed(services)
856
+ devServices: local ? [] : require_state.devServicesUsed(services),
857
+ ...logs ? { logs } : {}
621
858
  };
622
859
  }
623
860
  /**
@@ -704,7 +941,8 @@ function hostOf(name, app, workspace, resource, options) {
704
941
  }
705
942
  /** The compose document: the infrastructure, the apps, and the edge. */
706
943
  function stackFile(options) {
707
- const { project, plan, infra, apps } = options;
944
+ const { project, plan, apps } = options;
945
+ const infra = options.infra.filter((name) => name !== LOGS_SERVICE);
708
946
  const derived = require_state.composeFor({
709
947
  ...plan,
710
948
  containers: [...infra]
@@ -716,7 +954,8 @@ function stackFile(options) {
716
954
  const volumes = {
717
955
  ...derived.volumes,
718
956
  "caddy-data": {},
719
- "caddy-config": {}
957
+ "caddy-config": {},
958
+ ...options.logs ? { "openobserve-data": {} } : {}
720
959
  };
721
960
  for (const name of infra) {
722
961
  const { ports: _ports, depends_on, profiles: _profiles, ...service } = derived.services[name];
@@ -742,6 +981,7 @@ function stackFile(options) {
742
981
  };
743
982
  minio.ports = ["127.0.0.1::9000"];
744
983
  }
984
+ if (options.logs) services[LOGS_SERVICE] = logsService(options.logs);
745
985
  for (const app of apps) services[app.name] = {
746
986
  image: app.ref,
747
987
  ...app.build ? { build: {
@@ -784,6 +1024,10 @@ function stackFile(options) {
784
1024
  retries: 5
785
1025
  }
786
1026
  };
1027
+ for (const service of Object.values(services)) service.logging = {
1028
+ driver: LOG_ROTATION.driver,
1029
+ options: { ...LOG_ROTATION.options }
1030
+ };
787
1031
  return {
788
1032
  name: project,
789
1033
  services,
@@ -979,6 +1223,15 @@ var NoGitRevision = class extends Error {
979
1223
  this.name = "NoGitRevision";
980
1224
  }
981
1225
  };
1226
+ /**
1227
+ * The project's own file merged over a stage's stack: `docker-compose.<stage>.yml`
1228
+ * at the workspace root. What the generated file cannot know — a port bound
1229
+ * to a tailnet address, another log driver — goes there, and is never
1230
+ * overwritten.
1231
+ */
1232
+ function stackOverrideFile(root, stage) {
1233
+ return (0, node_path.join)(root, `docker-compose.${stage}.yml`);
1234
+ }
982
1235
  /** The commit HEAD is at, short — and `-dirty` when the tree has changes. */
983
1236
  async function gitRevision(root) {
984
1237
  let sha;
@@ -1084,7 +1337,14 @@ async function validateCompose(ctx, deps, forced) {
1084
1337
  }
1085
1338
  const stack = withBuildCredentials(composed, masterKeys);
1086
1339
  if (mode === "pull" && !ctx.dryRun) await assertImagesExist(deps.docker, tag, stack.apps);
1340
+ if (stack.logs && (!stack.local || stack.logs.passwordFromSecrets)) {
1341
+ ctx.secrets.mask(stack.logs.password);
1342
+ ctx.secrets.mask(stack.logs.appEnv.OTEL_EXPORTER_OTLP_HEADERS);
1343
+ }
1087
1344
  const dir = (0, node_path.join)(root, stackDir(stage));
1345
+ const override = stackOverrideFile(root, stage);
1346
+ const overrides = (0, node_fs.existsSync)(override) ? [override] : [];
1347
+ if (overrides.length > 0) ctx.logger.info(`🧩 Merging docker-compose.${stage}.yml over the generated stack`);
1088
1348
  const previous = Object.fromEntries(Object.entries((await ctx.state.read(stage))?.state.releases ?? {}).map(([name, releases]) => {
1089
1349
  const { ref, tag, digest } = releases.current;
1090
1350
  return [name, {
@@ -1100,6 +1360,7 @@ async function validateCompose(ctx, deps, forced) {
1100
1360
  ref: {
1101
1361
  project: composeProject(identity),
1102
1362
  file: (0, node_path.join)(dir, "docker-compose.yml"),
1363
+ ...overrides.length > 0 ? { overrides } : {},
1103
1364
  cwd: root,
1104
1365
  output: ctx.childOutput,
1105
1366
  signal: ctx.signal
@@ -1125,7 +1386,13 @@ async function stageSecrets(ctx, manifest) {
1125
1386
  secrets: stored,
1126
1387
  generated: []
1127
1388
  };
1128
- const { secrets, generated } = require_state.withGeneratedSecrets(stored ?? require_state.initStageSecrets(ctx.stage), manifest);
1389
+ const withSeed = require_state.withGeneratedSecrets(stored ?? require_state.initStageSecrets(ctx.stage), manifest);
1390
+ const withLogs = require_workspace.resolveLogs(ctx.workspace.deploy?.compose?.logs) ? withLogsPassword(withSeed.secrets) : {
1391
+ secrets: withSeed.secrets,
1392
+ generated: []
1393
+ };
1394
+ const secrets = withLogs.secrets;
1395
+ const generated = [...withSeed.generated, ...withLogs.generated];
1129
1396
  if (secrets.seed) ctx.secrets.mask(secrets.seed);
1130
1397
  for (const value of Object.values(secrets.custom ?? {})) ctx.secrets.mask(value);
1131
1398
  return {
@@ -1337,15 +1604,83 @@ async function verifyCompose(ctx, run, deps) {
1337
1604
  const { stack } = run;
1338
1605
  const ca = stack.local ? await (0, node_fs_promises.readFile)(caFile(run), "utf-8").catch(() => void 0) : void 0;
1339
1606
  ctx.logger.info("\n🩺 Checking each app through Caddy…");
1340
- const down = (await Promise.all(stack.apps.map((app) => checkApp(ctx, app, deps, {
1607
+ const results = await Promise.all(stack.apps.map((app) => checkApp(ctx, app, deps, {
1341
1608
  ca,
1342
1609
  local: stack.local
1343
- })))).filter((result) => !result.healthy).map(({ app, url, last }) => ({
1610
+ })));
1611
+ if (stack.logs) results.push(await checkLogs(ctx, run, deps));
1612
+ const down = results.filter((result) => !result.healthy).map(({ app, url, last }) => ({
1344
1613
  app,
1345
1614
  url,
1346
1615
  last
1347
1616
  }));
1348
1617
  if (down.length > 0) throw new ComposeAppsUnhealthy(stack.project, down);
1618
+ if (stack.logs) reportLogs(ctx, stack.logs, stack.local);
1619
+ }
1620
+ /**
1621
+ * OpenObserve's own health check, as Docker reports it. It is not asked
1622
+ * through the edge: by default it has no host there, and served publicly it
1623
+ * answers only the addresses it allows — which need not include this one.
1624
+ */
1625
+ async function checkLogs(ctx, run, deps) {
1626
+ const url = `docker:${LOGS_SERVICE}`;
1627
+ let last = "not asked";
1628
+ for (let attempt = 1; attempt <= deps.healthAttempts; attempt++) {
1629
+ ctx.signal.throwIfAborted();
1630
+ const health = await deps.docker.health(run.ref, LOGS_SERVICE).catch((error) => error instanceof Error ? error.message : String(error));
1631
+ last = health ?? "not running";
1632
+ const healthy = health === "healthy";
1633
+ ctx.emit({
1634
+ type: "health.checked",
1635
+ app: LOGS_SERVICE,
1636
+ url,
1637
+ healthy,
1638
+ attempt
1639
+ });
1640
+ if (healthy) {
1641
+ ctx.logger.info(` ✓ ${LOGS_SERVICE.padEnd(12)} ${url} (${last})`);
1642
+ return {
1643
+ app: LOGS_SERVICE,
1644
+ url,
1645
+ last,
1646
+ healthy
1647
+ };
1648
+ }
1649
+ if (attempt < deps.healthAttempts) await new Promise((resolve) => setTimeout(resolve, deps.healthIntervalMs));
1650
+ }
1651
+ ctx.logger.warn(` ✗ ${LOGS_SERVICE.padEnd(12)} ${url} (${last})`);
1652
+ return {
1653
+ app: LOGS_SERVICE,
1654
+ url,
1655
+ last,
1656
+ healthy: false
1657
+ };
1658
+ }
1659
+ /** How to open the logs, printed — and as an event, for a headless caller. */
1660
+ function reportLogs(ctx, logs, local) {
1661
+ const lines = logsAccess(logs, {
1662
+ stage: ctx.stage,
1663
+ local,
1664
+ user: currentUser(),
1665
+ hostname: (0, node_os.hostname)()
1666
+ });
1667
+ ctx.logger.info(`\n${lines.join("\n")}`);
1668
+ ctx.emit({
1669
+ type: "logs.ready",
1670
+ service: LOGS_SERVICE,
1671
+ access: logs.public ? "public" : "tunnel",
1672
+ url: logs.url,
1673
+ ...logs.public ? { allow: [...logs.public.allow] } : { port: logs.port },
1674
+ email: logs.email
1675
+ });
1676
+ }
1677
+ /** This machine's user — a guess at who would SSH in. */
1678
+ function currentUser() {
1679
+ try {
1680
+ return (0, node_os.userInfo)().username;
1681
+ } catch {
1682
+ return process.env.USER ?? "user";
1683
+ }
1349
1684
  }
1350
1685
  async function checkApp(ctx, app, deps, options) {
1351
1686
  const url = `${app.url}${app.kind === "site" ? "/" : "/health"}`;
@@ -1450,6 +1785,7 @@ async function writeStack(root, dir, stack, credentials = {}) {
1450
1785
  await write((0, node_path.join)(dir, "docker-compose.yml"), composeYaml(stack));
1451
1786
  await write((0, node_path.join)(dir, "Caddyfile"), stack.caddyfile);
1452
1787
  for (const app of stack.apps) if (app.env) await write((0, node_path.join)(dir, `${app.name}.env`), envFile(app.env), 384);
1788
+ if (stack.logs) await write((0, node_path.join)(dir, `${LOGS_SERVICE}.env`), envFile(stack.logs.env), 384);
1453
1789
  for (const [name, payload] of Object.entries(credentials)) await write((0, node_path.join)(dir, credentialsFile(name)), require_state.credentialsFileContent(payload), 384);
1454
1790
  for (const [path, content] of Object.entries(stack.dockerfiles)) await write((0, node_path.join)(root, path), content);
1455
1791
  await require_state.ensureDockerignore(require_state.findBuildRoot(root));
@@ -1563,5 +1899,11 @@ Object.defineProperty(exports, "stackDir", {
1563
1899
  return stackDir;
1564
1900
  }
1565
1901
  });
1902
+ Object.defineProperty(exports, "stackOverrideFile", {
1903
+ enumerable: true,
1904
+ get: function() {
1905
+ return stackOverrideFile;
1906
+ }
1907
+ });
1566
1908
 
1567
- //# sourceMappingURL=compose-DfIioFX0.cjs.map
1909
+ //# sourceMappingURL=compose-B-MME-4C.cjs.map