@better-openclaw/core 1.0.23 → 1.0.24

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 (254) hide show
  1. package/dist/bare-metal-partition.test.cjs +3 -4
  2. package/dist/bare-metal-partition.test.cjs.map +1 -1
  3. package/dist/bare-metal-partition.test.mjs +3 -4
  4. package/dist/bare-metal-partition.test.mjs.map +1 -1
  5. package/dist/composer.cjs +13 -1
  6. package/dist/composer.cjs.map +1 -1
  7. package/dist/composer.d.cts.map +1 -1
  8. package/dist/composer.d.mts.map +1 -1
  9. package/dist/composer.mjs +13 -1
  10. package/dist/composer.mjs.map +1 -1
  11. package/dist/composer.snapshot.test.cjs +1 -1
  12. package/dist/composer.snapshot.test.mjs +1 -1
  13. package/dist/composer.test.cjs +3 -2
  14. package/dist/composer.test.cjs.map +1 -1
  15. package/dist/composer.test.mjs +3 -2
  16. package/dist/composer.test.mjs.map +1 -1
  17. package/dist/deployers/strip-host-ports.test.cjs +1 -1
  18. package/dist/deployers/strip-host-ports.test.mjs +1 -1
  19. package/dist/generate.cjs +6 -2
  20. package/dist/generate.cjs.map +1 -1
  21. package/dist/generate.d.cts.map +1 -1
  22. package/dist/generate.d.mts.map +1 -1
  23. package/dist/generate.mjs +6 -2
  24. package/dist/generate.mjs.map +1 -1
  25. package/dist/generate.test.cjs +2 -2
  26. package/dist/generate.test.cjs.map +1 -1
  27. package/dist/generate.test.mjs +2 -2
  28. package/dist/generate.test.mjs.map +1 -1
  29. package/dist/generators/bare-metal-install.test.cjs +1 -1
  30. package/dist/generators/bare-metal-install.test.mjs +1 -1
  31. package/dist/generators/caddy.test.cjs +1 -1
  32. package/dist/generators/caddy.test.mjs +1 -1
  33. package/dist/generators/clone-repos.cjs +140 -0
  34. package/dist/generators/clone-repos.cjs.map +1 -0
  35. package/dist/generators/clone-repos.d.cts +11 -0
  36. package/dist/generators/clone-repos.d.cts.map +1 -0
  37. package/dist/generators/clone-repos.d.mts +11 -0
  38. package/dist/generators/clone-repos.d.mts.map +1 -0
  39. package/dist/generators/clone-repos.mjs +139 -0
  40. package/dist/generators/clone-repos.mjs.map +1 -0
  41. package/dist/generators/clone-repos.test.cjs +140 -0
  42. package/dist/generators/clone-repos.test.cjs.map +1 -0
  43. package/dist/generators/clone-repos.test.d.cts +1 -0
  44. package/dist/generators/clone-repos.test.d.mts +1 -0
  45. package/dist/generators/clone-repos.test.mjs +141 -0
  46. package/dist/generators/clone-repos.test.mjs.map +1 -0
  47. package/dist/generators/env.test.cjs +1 -1
  48. package/dist/generators/env.test.mjs +1 -1
  49. package/dist/generators/health-check.test.cjs +1 -1
  50. package/dist/generators/health-check.test.mjs +1 -1
  51. package/dist/generators/postgres-init.cjs +20 -0
  52. package/dist/generators/postgres-init.cjs.map +1 -1
  53. package/dist/generators/postgres-init.d.cts.map +1 -1
  54. package/dist/generators/postgres-init.d.mts.map +1 -1
  55. package/dist/generators/postgres-init.mjs +20 -0
  56. package/dist/generators/postgres-init.mjs.map +1 -1
  57. package/dist/generators/scripts.cjs +332 -3
  58. package/dist/generators/scripts.cjs.map +1 -1
  59. package/dist/generators/scripts.d.cts +3 -1
  60. package/dist/generators/scripts.d.cts.map +1 -1
  61. package/dist/generators/scripts.d.mts +3 -1
  62. package/dist/generators/scripts.d.mts.map +1 -1
  63. package/dist/generators/scripts.mjs +332 -3
  64. package/dist/generators/scripts.mjs.map +1 -1
  65. package/dist/generators/scripts.test.cjs +39 -5
  66. package/dist/generators/scripts.test.cjs.map +1 -1
  67. package/dist/generators/scripts.test.mjs +39 -5
  68. package/dist/generators/scripts.test.mjs.map +1 -1
  69. package/dist/generators/stack-manifest.cjs +1 -0
  70. package/dist/generators/stack-manifest.cjs.map +1 -1
  71. package/dist/generators/stack-manifest.d.cts +3 -2
  72. package/dist/generators/stack-manifest.d.cts.map +1 -1
  73. package/dist/generators/stack-manifest.d.mts +3 -2
  74. package/dist/generators/stack-manifest.d.mts.map +1 -1
  75. package/dist/generators/stack-manifest.mjs +1 -0
  76. package/dist/generators/stack-manifest.mjs.map +1 -1
  77. package/dist/generators/traefik.test.cjs +1 -1
  78. package/dist/generators/traefik.test.mjs +1 -1
  79. package/dist/index.cjs +8 -1
  80. package/dist/index.d.cts +5 -3
  81. package/dist/index.d.mts +5 -3
  82. package/dist/index.mjs +5 -3
  83. package/dist/migrations.test.cjs +1 -1
  84. package/dist/migrations.test.mjs +1 -1
  85. package/dist/presets/registry.cjs.map +1 -1
  86. package/dist/presets/registry.d.cts.map +1 -1
  87. package/dist/presets/registry.d.mts.map +1 -1
  88. package/dist/presets/registry.mjs.map +1 -1
  89. package/dist/presets/registry.test.cjs +1 -1
  90. package/dist/presets/registry.test.mjs +1 -1
  91. package/dist/resolver.cjs +8 -0
  92. package/dist/resolver.cjs.map +1 -1
  93. package/dist/resolver.mjs +9 -1
  94. package/dist/resolver.mjs.map +1 -1
  95. package/dist/resolver.test.cjs +47 -12
  96. package/dist/resolver.test.cjs.map +1 -1
  97. package/dist/resolver.test.mjs +47 -12
  98. package/dist/resolver.test.mjs.map +1 -1
  99. package/dist/{schema-B4c64P8N.d.cts → schema-eX44HhRp.d.mts} +62 -8
  100. package/dist/schema-eX44HhRp.d.mts.map +1 -0
  101. package/dist/{schema-CXNhYci1.d.mts → schema-tn5RK8CM.d.cts} +62 -8
  102. package/dist/schema-tn5RK8CM.d.cts.map +1 -0
  103. package/dist/schema.cjs +22 -4
  104. package/dist/schema.cjs.map +1 -1
  105. package/dist/schema.d.cts +2 -2
  106. package/dist/schema.d.mts +2 -2
  107. package/dist/schema.mjs +21 -5
  108. package/dist/schema.mjs.map +1 -1
  109. package/dist/schema.test.cjs +1 -1
  110. package/dist/schema.test.mjs +1 -1
  111. package/dist/services/definitions/apptension-saas.cjs +87 -0
  112. package/dist/services/definitions/apptension-saas.cjs.map +1 -0
  113. package/dist/services/definitions/apptension-saas.d.cts +7 -0
  114. package/dist/services/definitions/apptension-saas.d.cts.map +1 -0
  115. package/dist/services/definitions/apptension-saas.d.mts +7 -0
  116. package/dist/services/definitions/apptension-saas.d.mts.map +1 -0
  117. package/dist/services/definitions/apptension-saas.mjs +86 -0
  118. package/dist/services/definitions/apptension-saas.mjs.map +1 -0
  119. package/dist/services/definitions/boxyhq-saas.cjs +88 -0
  120. package/dist/services/definitions/boxyhq-saas.cjs.map +1 -0
  121. package/dist/services/definitions/boxyhq-saas.d.cts +7 -0
  122. package/dist/services/definitions/boxyhq-saas.d.cts.map +1 -0
  123. package/dist/services/definitions/boxyhq-saas.d.mts +7 -0
  124. package/dist/services/definitions/boxyhq-saas.d.mts.map +1 -0
  125. package/dist/services/definitions/boxyhq-saas.mjs +87 -0
  126. package/dist/services/definitions/boxyhq-saas.mjs.map +1 -0
  127. package/dist/services/definitions/cmsaas-starter.cjs +86 -0
  128. package/dist/services/definitions/cmsaas-starter.cjs.map +1 -0
  129. package/dist/services/definitions/cmsaas-starter.d.cts +7 -0
  130. package/dist/services/definitions/cmsaas-starter.d.cts.map +1 -0
  131. package/dist/services/definitions/cmsaas-starter.d.mts +7 -0
  132. package/dist/services/definitions/cmsaas-starter.d.mts.map +1 -0
  133. package/dist/services/definitions/cmsaas-starter.mjs +85 -0
  134. package/dist/services/definitions/cmsaas-starter.mjs.map +1 -0
  135. package/dist/services/definitions/index.cjs +51 -36
  136. package/dist/services/definitions/index.cjs.map +1 -1
  137. package/dist/services/definitions/index.d.cts +30 -25
  138. package/dist/services/definitions/index.d.cts.map +1 -1
  139. package/dist/services/definitions/index.d.mts +30 -25
  140. package/dist/services/definitions/index.d.mts.map +1 -1
  141. package/dist/services/definitions/index.mjs +47 -37
  142. package/dist/services/definitions/index.mjs.map +1 -1
  143. package/dist/services/definitions/ixartz-saas.cjs +88 -0
  144. package/dist/services/definitions/ixartz-saas.cjs.map +1 -0
  145. package/dist/services/definitions/ixartz-saas.d.cts +7 -0
  146. package/dist/services/definitions/ixartz-saas.d.cts.map +1 -0
  147. package/dist/services/definitions/ixartz-saas.d.mts +7 -0
  148. package/dist/services/definitions/ixartz-saas.d.mts.map +1 -0
  149. package/dist/services/definitions/ixartz-saas.mjs +87 -0
  150. package/dist/services/definitions/ixartz-saas.mjs.map +1 -0
  151. package/dist/services/definitions/mission-control.cjs +16 -2
  152. package/dist/services/definitions/mission-control.cjs.map +1 -1
  153. package/dist/services/definitions/mission-control.mjs +16 -2
  154. package/dist/services/definitions/mission-control.mjs.map +1 -1
  155. package/dist/services/definitions/open-saas.cjs +81 -0
  156. package/dist/services/definitions/open-saas.cjs.map +1 -0
  157. package/dist/services/definitions/open-saas.d.cts +7 -0
  158. package/dist/services/definitions/open-saas.d.cts.map +1 -0
  159. package/dist/services/definitions/open-saas.d.mts +7 -0
  160. package/dist/services/definitions/open-saas.d.mts.map +1 -0
  161. package/dist/services/definitions/open-saas.mjs +80 -0
  162. package/dist/services/definitions/open-saas.mjs.map +1 -0
  163. package/dist/services/registry.cjs +3 -0
  164. package/dist/services/registry.cjs.map +1 -1
  165. package/dist/services/registry.d.cts.map +1 -1
  166. package/dist/services/registry.d.mts.map +1 -1
  167. package/dist/services/registry.mjs +3 -0
  168. package/dist/services/registry.mjs.map +1 -1
  169. package/dist/services/registry.test.cjs +8 -1
  170. package/dist/services/registry.test.cjs.map +1 -1
  171. package/dist/services/registry.test.mjs +8 -1
  172. package/dist/services/registry.test.mjs.map +1 -1
  173. package/dist/{skill-manifest-BVUXU0__.mjs → skill-manifest-6XhrhWsG.mjs} +49 -1
  174. package/dist/{skill-manifest--IgY9REK.cjs.map → skill-manifest-6XhrhWsG.mjs.map} +1 -1
  175. package/dist/{skill-manifest--IgY9REK.cjs → skill-manifest-B8znSsym.cjs} +49 -1
  176. package/dist/{skill-manifest-BVUXU0__.mjs.map → skill-manifest-B8znSsym.cjs.map} +1 -1
  177. package/dist/skills/registry.cjs +3 -3
  178. package/dist/skills/registry.cjs.map +1 -1
  179. package/dist/skills/registry.mjs +3 -3
  180. package/dist/skills/registry.mjs.map +1 -1
  181. package/dist/skills/skill-manifest.cjs +1 -1
  182. package/dist/skills/skill-manifest.mjs +1 -1
  183. package/dist/track-analytics.cjs +50 -0
  184. package/dist/track-analytics.cjs.map +1 -0
  185. package/dist/track-analytics.d.cts +34 -0
  186. package/dist/track-analytics.d.cts.map +1 -0
  187. package/dist/track-analytics.d.mts +34 -0
  188. package/dist/track-analytics.d.mts.map +1 -0
  189. package/dist/track-analytics.mjs +48 -0
  190. package/dist/track-analytics.mjs.map +1 -0
  191. package/dist/track-analytics.test.cjs +91 -0
  192. package/dist/track-analytics.test.cjs.map +1 -0
  193. package/dist/track-analytics.test.d.cts +1 -0
  194. package/dist/track-analytics.test.d.mts +1 -0
  195. package/dist/track-analytics.test.mjs +92 -0
  196. package/dist/track-analytics.test.mjs.map +1 -0
  197. package/dist/types.cjs +7 -0
  198. package/dist/types.cjs.map +1 -1
  199. package/dist/types.d.cts +4 -2
  200. package/dist/types.d.cts.map +1 -1
  201. package/dist/types.d.mts +4 -2
  202. package/dist/types.d.mts.map +1 -1
  203. package/dist/types.mjs +7 -0
  204. package/dist/types.mjs.map +1 -1
  205. package/dist/validator.test.cjs +1 -1
  206. package/dist/validator.test.mjs +1 -1
  207. package/dist/version-manager.cjs +1 -1
  208. package/dist/version-manager.cjs.map +1 -1
  209. package/dist/version-manager.mjs +1 -1
  210. package/dist/version-manager.mjs.map +1 -1
  211. package/dist/version-manager.test.cjs +7 -5
  212. package/dist/version-manager.test.cjs.map +1 -1
  213. package/dist/version-manager.test.mjs +7 -5
  214. package/dist/version-manager.test.mjs.map +1 -1
  215. package/dist/{vi.2VT5v0um-DvC3SVNc.mjs → vi.2VT5v0um-C_jmO7m2.mjs} +5 -5
  216. package/dist/{vi.2VT5v0um-DvC3SVNc.mjs.map → vi.2VT5v0um-C_jmO7m2.mjs.map} +1 -1
  217. package/dist/{vi.2VT5v0um-CRqXre87.cjs → vi.2VT5v0um-iVBt6Fyq.cjs} +5 -5
  218. package/dist/{vi.2VT5v0um-CRqXre87.cjs.map → vi.2VT5v0um-iVBt6Fyq.cjs.map} +1 -1
  219. package/package.json +1 -1
  220. package/src/__snapshots__/composer.snapshot.test.ts.snap +155 -0
  221. package/src/bare-metal-partition.test.ts +4 -3
  222. package/src/composer.test.ts +4 -2
  223. package/src/composer.ts +20 -1
  224. package/src/generate.test.ts +2 -1
  225. package/src/generate.ts +10 -1
  226. package/src/generators/clone-repos.test.ts +154 -0
  227. package/src/generators/clone-repos.ts +159 -0
  228. package/src/generators/postgres-init.ts +17 -0
  229. package/src/generators/scripts.test.ts +52 -4
  230. package/src/generators/scripts.ts +351 -3
  231. package/src/generators/stack-manifest.ts +4 -2
  232. package/src/index.ts +8 -0
  233. package/src/presets/registry.ts +241 -329
  234. package/src/resolver.test.ts +53 -15
  235. package/src/resolver.ts +13 -1
  236. package/src/schema.ts +33 -4
  237. package/src/services/definitions/apptension-saas.ts +84 -0
  238. package/src/services/definitions/boxyhq-saas.ts +84 -0
  239. package/src/services/definitions/cmsaas-starter.ts +84 -0
  240. package/src/services/definitions/index.ts +90 -70
  241. package/src/services/definitions/ixartz-saas.ts +84 -0
  242. package/src/services/definitions/mission-control.ts +19 -2
  243. package/src/services/definitions/open-saas.ts +79 -0
  244. package/src/services/registry.test.ts +8 -0
  245. package/src/services/registry.ts +7 -0
  246. package/src/skills/manifest.json +64 -0
  247. package/src/skills/registry.ts +3 -3
  248. package/src/track-analytics.test.ts +82 -0
  249. package/src/track-analytics.ts +76 -0
  250. package/src/types.ts +11 -0
  251. package/src/version-manager.test.ts +10 -5
  252. package/src/version-manager.ts +1 -1
  253. package/dist/schema-B4c64P8N.d.cts.map +0 -1
  254. package/dist/schema-CXNhYci1.d.mts.map +0 -1
@@ -1,4 +1,4 @@
1
- const require_vi_2VT5v0um = require("./vi.2VT5v0um-CRqXre87.cjs");
1
+ const require_vi_2VT5v0um = require("./vi.2VT5v0um-iVBt6Fyq.cjs");
2
2
  const require_bare_metal_partition = require("./bare-metal-partition.cjs");
3
3
  const require_resolver = require("./resolver.cjs");
4
4
  //#region src/bare-metal-partition.test.ts
@@ -19,9 +19,8 @@ require_vi_2VT5v0um.describe("bare-metal partition", () => {
19
19
  monitoring: false
20
20
  }), "linux/amd64");
21
21
  require_vi_2VT5v0um.globalExpect(result.nativeIds.has("redis")).toBe(true);
22
- require_vi_2VT5v0um.globalExpect(result.nativeServices.length).toBe(1);
23
- require_vi_2VT5v0um.globalExpect(result.nativeServices[0].definition.id).toBe("redis");
24
- require_vi_2VT5v0um.globalExpect(result.dockerOnlyServices.length).toBe(0);
22
+ require_vi_2VT5v0um.globalExpect(result.nativeServices.find((s) => s.definition.id === "redis")).toBeDefined();
23
+ require_vi_2VT5v0um.globalExpect(result.dockerOnlyServices.length).toBeGreaterThanOrEqual(0);
25
24
  });
26
25
  require_vi_2VT5v0um.it("partitions n8n as Docker-only (no native recipe)", () => {
27
26
  const result = require_bare_metal_partition.partitionBareMetal(require_resolver.resolve({
@@ -1 +1 @@
1
- {"version":3,"file":"bare-metal-partition.test.cjs","names":["describe","platformToNativePlatform","partitionBareMetal","resolve","resolvedWithOnlyServices"],"sources":["../src/bare-metal-partition.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport {\n\tpartitionBareMetal,\n\tplatformToNativePlatform,\n\tresolvedWithOnlyServices,\n} from \"./bare-metal-partition.js\";\nimport { resolve } from \"./resolver.js\";\n\ndescribe(\"bare-metal partition\", () => {\n\tit(\"platformToNativePlatform maps platform to native\", () => {\n\t\texpect(platformToNativePlatform(\"linux/amd64\")).toBe(\"linux\");\n\t\texpect(platformToNativePlatform(\"linux/arm64\")).toBe(\"linux\");\n\t\texpect(platformToNativePlatform(\"windows/amd64\")).toBe(\"windows\");\n\t\texpect(platformToNativePlatform(\"macos/arm64\")).toBe(\"macos\");\n\t});\n\n\tit(\"partitions redis as native on Linux when redis has native recipe\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tmonitoring: false,\n\t\t});\n\t\tconst result = partitionBareMetal(resolved, \"linux/amd64\");\n\t\texpect(result.nativeIds.has(\"redis\")).toBe(true);\n\t\texpect(result.nativeServices.length).toBe(1);\n\t\texpect(result.nativeServices[0].definition.id).toBe(\"redis\");\n\t\texpect(result.dockerOnlyServices.length).toBe(0);\n\t});\n\n\tit(\"partitions n8n as Docker-only (no native recipe)\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"n8n\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tmonitoring: false,\n\t\t});\n\t\tconst result = partitionBareMetal(resolved, \"linux/amd64\");\n\t\texpect(result.nativeIds.has(\"n8n\")).toBe(false);\n\t\texpect(result.nativeServices.length).toBe(0);\n\t\texpect(result.dockerOnlyServices.length).toBeGreaterThanOrEqual(1);\n\t\texpect(result.dockerOnlyServices.some((s) => s.definition.id === \"n8n\")).toBe(true);\n\t});\n\n\tit(\"resolvedWithOnlyServices filters to given service list\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"redis\", \"n8n\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tmonitoring: false,\n\t\t});\n\t\tconst partition = partitionBareMetal(resolved, \"linux/amd64\");\n\t\tconst filtered = resolvedWithOnlyServices(resolved, partition.dockerOnlyServices);\n\t\texpect(filtered.services.every((s) => s.definition.id !== \"redis\")).toBe(true);\n\t\texpect(filtered.services.some((s) => s.definition.id === \"n8n\")).toBe(true);\n\t});\n});\n"],"mappings":";;;;AAQAA,oBAAAA,SAAS,8BAA8B;AACtC,qBAAA,GAAG,0DAA0D;AAC5D,sBAAA,aAAOC,6BAAAA,yBAAyB,cAAc,CAAC,CAAC,KAAK,QAAQ;AAC7D,sBAAA,aAAOA,6BAAAA,yBAAyB,cAAc,CAAC,CAAC,KAAK,QAAQ;AAC7D,sBAAA,aAAOA,6BAAAA,yBAAyB,gBAAgB,CAAC,CAAC,KAAK,UAAU;AACjE,sBAAA,aAAOA,6BAAAA,yBAAyB,cAAc,CAAC,CAAC,KAAK,QAAQ;GAC5D;AAEF,qBAAA,GAAG,0EAA0E;EAS5E,MAAM,SAASC,6BAAAA,mBAREC,iBAAAA,QAAQ;GACxB,UAAU,CAAC,QAAQ;GACnB,YAAY,EAAE;GACd,OAAO;GACP,KAAK;GACL,UAAU;GACV,YAAY;GACZ,CAAC,EAC0C,cAAc;AAC1D,sBAAA,aAAO,OAAO,UAAU,IAAI,QAAQ,CAAC,CAAC,KAAK,KAAK;AAChD,sBAAA,aAAO,OAAO,eAAe,OAAO,CAAC,KAAK,EAAE;AAC5C,sBAAA,aAAO,OAAO,eAAe,GAAG,WAAW,GAAG,CAAC,KAAK,QAAQ;AAC5D,sBAAA,aAAO,OAAO,mBAAmB,OAAO,CAAC,KAAK,EAAE;GAC/C;AAEF,qBAAA,GAAG,0DAA0D;EAS5D,MAAM,SAASD,6BAAAA,mBAREC,iBAAAA,QAAQ;GACxB,UAAU,CAAC,MAAM;GACjB,YAAY,EAAE;GACd,OAAO;GACP,KAAK;GACL,UAAU;GACV,YAAY;GACZ,CAAC,EAC0C,cAAc;AAC1D,sBAAA,aAAO,OAAO,UAAU,IAAI,MAAM,CAAC,CAAC,KAAK,MAAM;AAC/C,sBAAA,aAAO,OAAO,eAAe,OAAO,CAAC,KAAK,EAAE;AAC5C,sBAAA,aAAO,OAAO,mBAAmB,OAAO,CAAC,uBAAuB,EAAE;AAClE,sBAAA,aAAO,OAAO,mBAAmB,MAAM,MAAM,EAAE,WAAW,OAAO,MAAM,CAAC,CAAC,KAAK,KAAK;GAClF;AAEF,qBAAA,GAAG,gEAAgE;EAClE,MAAM,WAAWA,iBAAAA,QAAQ;GACxB,UAAU,CAAC,SAAS,MAAM;GAC1B,YAAY,EAAE;GACd,OAAO;GACP,KAAK;GACL,UAAU;GACV,YAAY;GACZ,CAAC;EAEF,MAAM,WAAWC,6BAAAA,yBAAyB,UADxBF,6BAAAA,mBAAmB,UAAU,cAAc,CACC,mBAAmB;AACjF,sBAAA,aAAO,SAAS,SAAS,OAAO,MAAM,EAAE,WAAW,OAAO,QAAQ,CAAC,CAAC,KAAK,KAAK;AAC9E,sBAAA,aAAO,SAAS,SAAS,MAAM,MAAM,EAAE,WAAW,OAAO,MAAM,CAAC,CAAC,KAAK,KAAK;GAC1E;EACD"}
1
+ {"version":3,"file":"bare-metal-partition.test.cjs","names":["describe","platformToNativePlatform","partitionBareMetal","resolve","resolvedWithOnlyServices"],"sources":["../src/bare-metal-partition.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport {\n\tpartitionBareMetal,\n\tplatformToNativePlatform,\n\tresolvedWithOnlyServices,\n} from \"./bare-metal-partition.js\";\nimport { resolve } from \"./resolver.js\";\n\ndescribe(\"bare-metal partition\", () => {\n\tit(\"platformToNativePlatform maps platform to native\", () => {\n\t\texpect(platformToNativePlatform(\"linux/amd64\")).toBe(\"linux\");\n\t\texpect(platformToNativePlatform(\"linux/arm64\")).toBe(\"linux\");\n\t\texpect(platformToNativePlatform(\"windows/amd64\")).toBe(\"windows\");\n\t\texpect(platformToNativePlatform(\"macos/arm64\")).toBe(\"macos\");\n\t});\n\n\tit(\"partitions redis as native on Linux when redis has native recipe\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tmonitoring: false,\n\t\t});\n\t\tconst result = partitionBareMetal(resolved, \"linux/amd64\");\n\t\texpect(result.nativeIds.has(\"redis\")).toBe(true);\n\t\tconst redisNative = result.nativeServices.find((s) => s.definition.id === \"redis\");\n\t\texpect(redisNative).toBeDefined();\n\t\t// Mandatory services (convex, mission-control, tailscale) are Docker-only\n\t\texpect(result.dockerOnlyServices.length).toBeGreaterThanOrEqual(0);\n\t});\n\n\tit(\"partitions n8n as Docker-only (no native recipe)\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"n8n\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tmonitoring: false,\n\t\t});\n\t\tconst result = partitionBareMetal(resolved, \"linux/amd64\");\n\t\texpect(result.nativeIds.has(\"n8n\")).toBe(false);\n\t\texpect(result.nativeServices.length).toBe(0);\n\t\texpect(result.dockerOnlyServices.length).toBeGreaterThanOrEqual(1);\n\t\texpect(result.dockerOnlyServices.some((s) => s.definition.id === \"n8n\")).toBe(true);\n\t});\n\n\tit(\"resolvedWithOnlyServices filters to given service list\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"redis\", \"n8n\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tmonitoring: false,\n\t\t});\n\t\tconst partition = partitionBareMetal(resolved, \"linux/amd64\");\n\t\tconst filtered = resolvedWithOnlyServices(resolved, partition.dockerOnlyServices);\n\t\texpect(filtered.services.every((s) => s.definition.id !== \"redis\")).toBe(true);\n\t\texpect(filtered.services.some((s) => s.definition.id === \"n8n\")).toBe(true);\n\t});\n});\n"],"mappings":";;;;AAQAA,oBAAAA,SAAS,8BAA8B;AACtC,qBAAA,GAAG,0DAA0D;AAC5D,sBAAA,aAAOC,6BAAAA,yBAAyB,cAAc,CAAC,CAAC,KAAK,QAAQ;AAC7D,sBAAA,aAAOA,6BAAAA,yBAAyB,cAAc,CAAC,CAAC,KAAK,QAAQ;AAC7D,sBAAA,aAAOA,6BAAAA,yBAAyB,gBAAgB,CAAC,CAAC,KAAK,UAAU;AACjE,sBAAA,aAAOA,6BAAAA,yBAAyB,cAAc,CAAC,CAAC,KAAK,QAAQ;GAC5D;AAEF,qBAAA,GAAG,0EAA0E;EAS5E,MAAM,SAASC,6BAAAA,mBAREC,iBAAAA,QAAQ;GACxB,UAAU,CAAC,QAAQ;GACnB,YAAY,EAAE;GACd,OAAO;GACP,KAAK;GACL,UAAU;GACV,YAAY;GACZ,CAAC,EAC0C,cAAc;AAC1D,sBAAA,aAAO,OAAO,UAAU,IAAI,QAAQ,CAAC,CAAC,KAAK,KAAK;AAEhD,sBAAA,aADoB,OAAO,eAAe,MAAM,MAAM,EAAE,WAAW,OAAO,QAAQ,CAC/D,CAAC,aAAa;AAEjC,sBAAA,aAAO,OAAO,mBAAmB,OAAO,CAAC,uBAAuB,EAAE;GACjE;AAEF,qBAAA,GAAG,0DAA0D;EAS5D,MAAM,SAASD,6BAAAA,mBAREC,iBAAAA,QAAQ;GACxB,UAAU,CAAC,MAAM;GACjB,YAAY,EAAE;GACd,OAAO;GACP,KAAK;GACL,UAAU;GACV,YAAY;GACZ,CAAC,EAC0C,cAAc;AAC1D,sBAAA,aAAO,OAAO,UAAU,IAAI,MAAM,CAAC,CAAC,KAAK,MAAM;AAC/C,sBAAA,aAAO,OAAO,eAAe,OAAO,CAAC,KAAK,EAAE;AAC5C,sBAAA,aAAO,OAAO,mBAAmB,OAAO,CAAC,uBAAuB,EAAE;AAClE,sBAAA,aAAO,OAAO,mBAAmB,MAAM,MAAM,EAAE,WAAW,OAAO,MAAM,CAAC,CAAC,KAAK,KAAK;GAClF;AAEF,qBAAA,GAAG,gEAAgE;EAClE,MAAM,WAAWA,iBAAAA,QAAQ;GACxB,UAAU,CAAC,SAAS,MAAM;GAC1B,YAAY,EAAE;GACd,OAAO;GACP,KAAK;GACL,UAAU;GACV,YAAY;GACZ,CAAC;EAEF,MAAM,WAAWC,6BAAAA,yBAAyB,UADxBF,6BAAAA,mBAAmB,UAAU,cAAc,CACC,mBAAmB;AACjF,sBAAA,aAAO,SAAS,SAAS,OAAO,MAAM,EAAE,WAAW,OAAO,QAAQ,CAAC,CAAC,KAAK,KAAK;AAC9E,sBAAA,aAAO,SAAS,SAAS,MAAM,MAAM,EAAE,WAAW,OAAO,MAAM,CAAC,CAAC,KAAK,KAAK;GAC1E;EACD"}
@@ -1,4 +1,4 @@
1
- import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-DvC3SVNc.mjs";
1
+ import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-C_jmO7m2.mjs";
2
2
  import { partitionBareMetal, platformToNativePlatform, resolvedWithOnlyServices } from "./bare-metal-partition.mjs";
3
3
  import { resolve } from "./resolver.mjs";
4
4
  //#region src/bare-metal-partition.test.ts
@@ -19,9 +19,8 @@ describe("bare-metal partition", () => {
19
19
  monitoring: false
20
20
  }), "linux/amd64");
21
21
  globalExpect(result.nativeIds.has("redis")).toBe(true);
22
- globalExpect(result.nativeServices.length).toBe(1);
23
- globalExpect(result.nativeServices[0].definition.id).toBe("redis");
24
- globalExpect(result.dockerOnlyServices.length).toBe(0);
22
+ globalExpect(result.nativeServices.find((s) => s.definition.id === "redis")).toBeDefined();
23
+ globalExpect(result.dockerOnlyServices.length).toBeGreaterThanOrEqual(0);
25
24
  });
26
25
  it("partitions n8n as Docker-only (no native recipe)", () => {
27
26
  const result = partitionBareMetal(resolve({
@@ -1 +1 @@
1
- {"version":3,"file":"bare-metal-partition.test.mjs","names":[],"sources":["../src/bare-metal-partition.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport {\n\tpartitionBareMetal,\n\tplatformToNativePlatform,\n\tresolvedWithOnlyServices,\n} from \"./bare-metal-partition.js\";\nimport { resolve } from \"./resolver.js\";\n\ndescribe(\"bare-metal partition\", () => {\n\tit(\"platformToNativePlatform maps platform to native\", () => {\n\t\texpect(platformToNativePlatform(\"linux/amd64\")).toBe(\"linux\");\n\t\texpect(platformToNativePlatform(\"linux/arm64\")).toBe(\"linux\");\n\t\texpect(platformToNativePlatform(\"windows/amd64\")).toBe(\"windows\");\n\t\texpect(platformToNativePlatform(\"macos/arm64\")).toBe(\"macos\");\n\t});\n\n\tit(\"partitions redis as native on Linux when redis has native recipe\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tmonitoring: false,\n\t\t});\n\t\tconst result = partitionBareMetal(resolved, \"linux/amd64\");\n\t\texpect(result.nativeIds.has(\"redis\")).toBe(true);\n\t\texpect(result.nativeServices.length).toBe(1);\n\t\texpect(result.nativeServices[0].definition.id).toBe(\"redis\");\n\t\texpect(result.dockerOnlyServices.length).toBe(0);\n\t});\n\n\tit(\"partitions n8n as Docker-only (no native recipe)\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"n8n\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tmonitoring: false,\n\t\t});\n\t\tconst result = partitionBareMetal(resolved, \"linux/amd64\");\n\t\texpect(result.nativeIds.has(\"n8n\")).toBe(false);\n\t\texpect(result.nativeServices.length).toBe(0);\n\t\texpect(result.dockerOnlyServices.length).toBeGreaterThanOrEqual(1);\n\t\texpect(result.dockerOnlyServices.some((s) => s.definition.id === \"n8n\")).toBe(true);\n\t});\n\n\tit(\"resolvedWithOnlyServices filters to given service list\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"redis\", \"n8n\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tmonitoring: false,\n\t\t});\n\t\tconst partition = partitionBareMetal(resolved, \"linux/amd64\");\n\t\tconst filtered = resolvedWithOnlyServices(resolved, partition.dockerOnlyServices);\n\t\texpect(filtered.services.every((s) => s.definition.id !== \"redis\")).toBe(true);\n\t\texpect(filtered.services.some((s) => s.definition.id === \"n8n\")).toBe(true);\n\t});\n});\n"],"mappings":";;;;AAQA,SAAS,8BAA8B;AACtC,IAAG,0DAA0D;AAC5D,eAAO,yBAAyB,cAAc,CAAC,CAAC,KAAK,QAAQ;AAC7D,eAAO,yBAAyB,cAAc,CAAC,CAAC,KAAK,QAAQ;AAC7D,eAAO,yBAAyB,gBAAgB,CAAC,CAAC,KAAK,UAAU;AACjE,eAAO,yBAAyB,cAAc,CAAC,CAAC,KAAK,QAAQ;GAC5D;AAEF,IAAG,0EAA0E;EAS5E,MAAM,SAAS,mBARE,QAAQ;GACxB,UAAU,CAAC,QAAQ;GACnB,YAAY,EAAE;GACd,OAAO;GACP,KAAK;GACL,UAAU;GACV,YAAY;GACZ,CAAC,EAC0C,cAAc;AAC1D,eAAO,OAAO,UAAU,IAAI,QAAQ,CAAC,CAAC,KAAK,KAAK;AAChD,eAAO,OAAO,eAAe,OAAO,CAAC,KAAK,EAAE;AAC5C,eAAO,OAAO,eAAe,GAAG,WAAW,GAAG,CAAC,KAAK,QAAQ;AAC5D,eAAO,OAAO,mBAAmB,OAAO,CAAC,KAAK,EAAE;GAC/C;AAEF,IAAG,0DAA0D;EAS5D,MAAM,SAAS,mBARE,QAAQ;GACxB,UAAU,CAAC,MAAM;GACjB,YAAY,EAAE;GACd,OAAO;GACP,KAAK;GACL,UAAU;GACV,YAAY;GACZ,CAAC,EAC0C,cAAc;AAC1D,eAAO,OAAO,UAAU,IAAI,MAAM,CAAC,CAAC,KAAK,MAAM;AAC/C,eAAO,OAAO,eAAe,OAAO,CAAC,KAAK,EAAE;AAC5C,eAAO,OAAO,mBAAmB,OAAO,CAAC,uBAAuB,EAAE;AAClE,eAAO,OAAO,mBAAmB,MAAM,MAAM,EAAE,WAAW,OAAO,MAAM,CAAC,CAAC,KAAK,KAAK;GAClF;AAEF,IAAG,gEAAgE;EAClE,MAAM,WAAW,QAAQ;GACxB,UAAU,CAAC,SAAS,MAAM;GAC1B,YAAY,EAAE;GACd,OAAO;GACP,KAAK;GACL,UAAU;GACV,YAAY;GACZ,CAAC;EAEF,MAAM,WAAW,yBAAyB,UADxB,mBAAmB,UAAU,cAAc,CACC,mBAAmB;AACjF,eAAO,SAAS,SAAS,OAAO,MAAM,EAAE,WAAW,OAAO,QAAQ,CAAC,CAAC,KAAK,KAAK;AAC9E,eAAO,SAAS,SAAS,MAAM,MAAM,EAAE,WAAW,OAAO,MAAM,CAAC,CAAC,KAAK,KAAK;GAC1E;EACD"}
1
+ {"version":3,"file":"bare-metal-partition.test.mjs","names":[],"sources":["../src/bare-metal-partition.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport {\n\tpartitionBareMetal,\n\tplatformToNativePlatform,\n\tresolvedWithOnlyServices,\n} from \"./bare-metal-partition.js\";\nimport { resolve } from \"./resolver.js\";\n\ndescribe(\"bare-metal partition\", () => {\n\tit(\"platformToNativePlatform maps platform to native\", () => {\n\t\texpect(platformToNativePlatform(\"linux/amd64\")).toBe(\"linux\");\n\t\texpect(platformToNativePlatform(\"linux/arm64\")).toBe(\"linux\");\n\t\texpect(platformToNativePlatform(\"windows/amd64\")).toBe(\"windows\");\n\t\texpect(platformToNativePlatform(\"macos/arm64\")).toBe(\"macos\");\n\t});\n\n\tit(\"partitions redis as native on Linux when redis has native recipe\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"redis\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tmonitoring: false,\n\t\t});\n\t\tconst result = partitionBareMetal(resolved, \"linux/amd64\");\n\t\texpect(result.nativeIds.has(\"redis\")).toBe(true);\n\t\tconst redisNative = result.nativeServices.find((s) => s.definition.id === \"redis\");\n\t\texpect(redisNative).toBeDefined();\n\t\t// Mandatory services (convex, mission-control, tailscale) are Docker-only\n\t\texpect(result.dockerOnlyServices.length).toBeGreaterThanOrEqual(0);\n\t});\n\n\tit(\"partitions n8n as Docker-only (no native recipe)\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"n8n\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tmonitoring: false,\n\t\t});\n\t\tconst result = partitionBareMetal(resolved, \"linux/amd64\");\n\t\texpect(result.nativeIds.has(\"n8n\")).toBe(false);\n\t\texpect(result.nativeServices.length).toBe(0);\n\t\texpect(result.dockerOnlyServices.length).toBeGreaterThanOrEqual(1);\n\t\texpect(result.dockerOnlyServices.some((s) => s.definition.id === \"n8n\")).toBe(true);\n\t});\n\n\tit(\"resolvedWithOnlyServices filters to given service list\", () => {\n\t\tconst resolved = resolve({\n\t\t\tservices: [\"redis\", \"n8n\"],\n\t\t\tskillPacks: [],\n\t\t\tproxy: \"none\",\n\t\t\tgpu: false,\n\t\t\tplatform: \"linux/amd64\",\n\t\t\tmonitoring: false,\n\t\t});\n\t\tconst partition = partitionBareMetal(resolved, \"linux/amd64\");\n\t\tconst filtered = resolvedWithOnlyServices(resolved, partition.dockerOnlyServices);\n\t\texpect(filtered.services.every((s) => s.definition.id !== \"redis\")).toBe(true);\n\t\texpect(filtered.services.some((s) => s.definition.id === \"n8n\")).toBe(true);\n\t});\n});\n"],"mappings":";;;;AAQA,SAAS,8BAA8B;AACtC,IAAG,0DAA0D;AAC5D,eAAO,yBAAyB,cAAc,CAAC,CAAC,KAAK,QAAQ;AAC7D,eAAO,yBAAyB,cAAc,CAAC,CAAC,KAAK,QAAQ;AAC7D,eAAO,yBAAyB,gBAAgB,CAAC,CAAC,KAAK,UAAU;AACjE,eAAO,yBAAyB,cAAc,CAAC,CAAC,KAAK,QAAQ;GAC5D;AAEF,IAAG,0EAA0E;EAS5E,MAAM,SAAS,mBARE,QAAQ;GACxB,UAAU,CAAC,QAAQ;GACnB,YAAY,EAAE;GACd,OAAO;GACP,KAAK;GACL,UAAU;GACV,YAAY;GACZ,CAAC,EAC0C,cAAc;AAC1D,eAAO,OAAO,UAAU,IAAI,QAAQ,CAAC,CAAC,KAAK,KAAK;AAEhD,eADoB,OAAO,eAAe,MAAM,MAAM,EAAE,WAAW,OAAO,QAAQ,CAC/D,CAAC,aAAa;AAEjC,eAAO,OAAO,mBAAmB,OAAO,CAAC,uBAAuB,EAAE;GACjE;AAEF,IAAG,0DAA0D;EAS5D,MAAM,SAAS,mBARE,QAAQ;GACxB,UAAU,CAAC,MAAM;GACjB,YAAY,EAAE;GACd,OAAO;GACP,KAAK;GACL,UAAU;GACV,YAAY;GACZ,CAAC,EAC0C,cAAc;AAC1D,eAAO,OAAO,UAAU,IAAI,MAAM,CAAC,CAAC,KAAK,MAAM;AAC/C,eAAO,OAAO,eAAe,OAAO,CAAC,KAAK,EAAE;AAC5C,eAAO,OAAO,mBAAmB,OAAO,CAAC,uBAAuB,EAAE;AAClE,eAAO,OAAO,mBAAmB,MAAM,MAAM,EAAE,WAAW,OAAO,MAAM,CAAC,CAAC,KAAK,KAAK;GAClF;AAEF,IAAG,gEAAgE;EAClE,MAAM,WAAW,QAAQ;GACxB,UAAU,CAAC,SAAS,MAAM;GAC1B,YAAY,EAAE;GACd,OAAO;GACP,KAAK;GACL,UAAU;GACV,YAAY;GACZ,CAAC;EAEF,MAAM,WAAW,yBAAyB,UADxB,mBAAmB,UAAU,cAAc,CACC,mBAAmB;AACjF,eAAO,SAAS,SAAS,OAAO,MAAM,EAAE,WAAW,OAAO,QAAQ,CAAC,CAAC,KAAK,KAAK;AAC9E,eAAO,SAAS,SAAS,MAAM,MAAM,EAAE,WAAW,OAAO,MAAM,CAAC,CAAC,KAAK,KAAK;GAC1E;EACD"}
package/dist/composer.cjs CHANGED
@@ -59,6 +59,10 @@ const CATEGORY_PROFILE_MAP = {
59
59
  communication: {
60
60
  file: "docker-compose.communication.yml",
61
61
  profile: "communication"
62
+ },
63
+ "saas-boilerplate": {
64
+ file: "docker-compose.saas.yml",
65
+ profile: "saas"
62
66
  }
63
67
  };
64
68
  const YAML_OPTIONS = {
@@ -174,7 +178,15 @@ function buildGatewayServices(resolved, options, dependsOn) {
174
178
  function buildCompanionService(def, resolved, options, allVolumes) {
175
179
  const svc = {};
176
180
  const volumeNames = [];
177
- svc.image = `${def.image}:${def.imageTag}`;
181
+ if (def.gitSource && def.buildContext) {
182
+ const subdir = def.gitSource.subdirectory || ".";
183
+ const ctxPath = def.buildContext.context || ".";
184
+ const buildBlock = { context: subdir === "." ? `./repos/${def.id}/${ctxPath}` : `./repos/${def.id}/${subdir}/${ctxPath}` };
185
+ if (def.buildContext.dockerfile) buildBlock.dockerfile = def.buildContext.dockerfile;
186
+ if (def.buildContext.args && Object.keys(def.buildContext.args).length > 0) buildBlock.args = def.buildContext.args;
187
+ if (def.buildContext.target) buildBlock.target = def.buildContext.target;
188
+ svc.build = buildBlock;
189
+ } else svc.image = `${def.image}:${def.imageTag}`;
178
190
  if (def.environment.length > 0) {
179
191
  const env = {};
180
192
  for (const e of def.environment) env[e.key] = e.secret ? `\${${e.key}}` : e.defaultValue;
@@ -1 +1 @@
1
- {"version":3,"file":"composer.cjs","names":["Scalar","getDbRequirements"],"sources":["../src/composer.ts"],"sourcesContent":["import { Scalar, stringify } from \"yaml\";\nimport { getDbRequirements } from \"./generators/postgres-init.js\";\nimport type {\n\tComposeOptions,\n\tOpenclawImageVariant,\n\tResolverOutput,\n\tServiceCategory,\n} from \"./types.js\";\n\n/** Maps image variant to the Docker image string. */\nconst IMAGE_VARIANTS: Record<OpenclawImageVariant, string> = {\n\tofficial: \"ghcr.io/openclaw/openclaw\",\n\tcoolify: \"coollabsio/openclaw\",\n\talpine: \"alpine/openclaw\",\n};\n\n/** Returns the OpenClaw image string with version tag for a given variant. */\nfunction getOpenclawImage(variant: OpenclawImageVariant, version: string): string {\n\tconst base = IMAGE_VARIANTS[variant];\n\t// Coolify and alpine images use :latest by default\n\tconst tag = variant === \"official\" ? version : \"latest\";\n\treturn `${base}:${tag}`;\n}\n\n/** Creates a YAML scalar that is always quoted — avoids YAML 1.1 bare `no` → false. */\nfunction quotedStr(value: string): Scalar {\n\tconst s = new Scalar(value);\n\ts.type = Scalar.QUOTE_DOUBLE;\n\treturn s;\n}\n\n// ── Public Types ────────────────────────────────────────────────────────────\n\nexport interface ComposeResult {\n\tfiles: Record<string, string>; // filename -> YAML content\n\tmainFile: string; // \"docker-compose.yml\"\n\tprofiles: string[]; // list of profiles used\n}\n\n// ── Category → Profile Mapping ──────────────────────────────────────────────\n\nconst CATEGORY_PROFILE_MAP: Partial<Record<ServiceCategory, { file: string; profile: string }>> = {\n\tai: { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\t\"ai-platform\": { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\tmedia: { file: \"docker-compose.media.yml\", profile: \"media\" },\n\tmonitoring: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\tanalytics: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\t\"dev-tools\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"coding-agent\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"social-media\": { file: \"docker-compose.social.yml\", profile: \"social\" },\n\tknowledge: { file: \"docker-compose.knowledge.yml\", profile: \"knowledge\" },\n\tcommunication: { file: \"docker-compose.communication.yml\", profile: \"communication\" },\n};\n\nconst YAML_OPTIONS = { lineWidth: 120, nullStr: \"\" };\n\n// ── Shared Gateway Builder ──────────────────────────────────────────────────\n\ninterface GatewayBuildResult {\n\tgatewayService: Record<string, unknown>;\n\tcliService: Record<string, unknown>;\n\tallVolumes: Set<string>;\n}\n\n/**\n * Builds the OpenClaw gateway and CLI service entries.\n * Matches the real OpenClaw docker-compose.yml structure:\n * - Bridge port 18790 for ACP/WebSocket\n * - Bind-mount volumes (not named volumes)\n * - Claude web-provider session env vars\n * - Gateway bind mode (--bind lan)\n * - CLI companion service with stdin/tty\n */\nfunction buildGatewayServices(\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tdependsOn?: Record<string, { condition: string }>,\n): GatewayBuildResult {\n\tconst allVolumes = new Set<string>();\n\n\t// Gateway environment\n\tconst gatewayEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t// Claude web-provider session vars (optional, user fills in .env)\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add AI provider API keys to gateway environment\n\tconst providerKeys = [\n\t\t\"OPENAI_API_KEY\",\n\t\t\"ANTHROPIC_API_KEY\",\n\t\t\"GOOGLE_API_KEY\",\n\t\t\"XAI_API_KEY\",\n\t\t\"DEEPSEEK_API_KEY\",\n\t\t\"GROQ_API_KEY\",\n\t\t\"OPENROUTER_API_KEY\",\n\t\t\"MISTRAL_API_KEY\",\n\t\t\"TOGETHER_API_KEY\",\n\t\t\"OLLAMA_API_KEY\",\n\t];\n\tfor (const key of providerKeys) {\n\t\tgatewayEnv[key] = `\\${${key}}`;\n\t}\n\n\t// Gateway volumes (bind-mount style matching real docker-setup.sh)\n\tconst gatewayVolumes: string[] = [\n\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t];\n\n\t// Collect env vars and volume mounts from companion services\n\tfor (const { definition: def } of resolved.services) {\n\t\tfor (const env of def.openclawEnvVars) {\n\t\t\tgatewayEnv[env.key] = env.secret ? `\\${${env.key}}` : env.defaultValue;\n\t\t}\n\t\tif (def.openclawVolumeMounts) {\n\t\t\tfor (const vol of def.openclawVolumeMounts) {\n\t\t\t\tconst isBindMount =\n\t\t\t\t\tvol.name.startsWith(\"./\") || vol.name.startsWith(\"/\") || vol.name.startsWith(\"~\");\n\t\t\t\tif (!isBindMount) {\n\t\t\t\t\tallVolumes.add(vol.name);\n\t\t\t\t}\n\t\t\t\tgatewayVolumes.push(`${vol.name}:${vol.containerPath}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Gateway service\n\tconst defaultImage = getOpenclawImage(\n\t\toptions.openclawImage ?? \"official\",\n\t\toptions.openclawVersion,\n\t);\n\tconst gateway: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tenvironment: gatewayEnv,\n\t\tvolumes: gatewayVolumes,\n\t\tports: [\"${OPENCLAW_GATEWAY_PORT:-18789}:18789\", \"${OPENCLAW_BRIDGE_PORT:-18790}:18790\"],\n\t\tnetworks: [\"openclaw-network\"],\n\t\tinit: true,\n\t\trestart: \"unless-stopped\",\n\t\tcommand: [\n\t\t\t\"node\",\n\t\t\t\"dist/index.js\",\n\t\t\t\"gateway\",\n\t\t\t\"--bind\",\n\t\t\t\"${OPENCLAW_GATEWAY_BIND:-lan}\",\n\t\t\t\"--port\",\n\t\t\t\"18789\",\n\t\t\t\"--allow-unconfigured\",\n\t\t],\n\t\thealthcheck: {\n\t\t\ttest: [\n\t\t\t\t\"CMD\",\n\t\t\t\t\"node\",\n\t\t\t\t\"-e\",\n\t\t\t\t\"fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\",\n\t\t\t],\n\t\t\tinterval: \"30s\",\n\t\t\ttimeout: \"5s\",\n\t\t\tretries: 5,\n\t\t\tstart_period: \"20s\",\n\t\t},\n\t};\n\n\t// Traefik labels for the gateway\n\tconst gwTraefikLabels = options.traefikLabels?.get(\"openclaw-gateway\");\n\tif (gwTraefikLabels) {\n\t\tgateway.labels = gwTraefikLabels;\n\t}\n\n\tif (options.bareMetalNativeHost) {\n\t\tgateway.extra_hosts = [\"host.docker.internal:host-gateway\"];\n\t}\n\n\tif (dependsOn && Object.keys(dependsOn).length > 0) {\n\t\tgateway.depends_on = dependsOn;\n\t}\n\n\t// CLI companion service (matching real OpenClaw docker-compose.yml)\n\t// Build CLI environment with same keys as gateway for consistency\n\tconst cliEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\tBROWSER: \"echo\",\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add same AI provider API keys to CLI for direct AI interactions\n\tfor (const key of providerKeys) {\n\t\tcliEnv[key] = `\\${${key}}`;\n\t}\n\n\tconst cliService: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tnetwork_mode: \"service:openclaw-gateway\",\n\t\tcap_drop: [\"NET_RAW\", \"NET_ADMIN\"],\n\t\tsecurity_opt: [\"no-new-privileges:true\"],\n\t\tenvironment: cliEnv,\n\t\tvolumes: [\n\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t],\n\t\tstdin_open: true,\n\t\ttty: true,\n\t\tinit: true,\n\t\tentrypoint: [\"node\", \"dist/index.js\"],\n\t\tdepends_on: [\"openclaw-gateway\"],\n\t};\n\n\treturn { gatewayService: gateway, cliService: cliService, allVolumes };\n}\n\n// ── Shared Companion Service Builder ────────────────────────────────────────\n\nfunction buildCompanionService(\n\tdef: ResolverOutput[\"services\"][number][\"definition\"],\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tallVolumes: Set<string>,\n): { entry: Record<string, unknown>; volumeNames: string[] } {\n\tconst svc: Record<string, unknown> = {};\n\tconst volumeNames: string[] = [];\n\n\tsvc.image = `${def.image}:${def.imageTag}`;\n\n\tif (def.environment.length > 0) {\n\t\tconst env: Record<string, string> = {};\n\t\tfor (const e of def.environment) {\n\t\t\tenv[e.key] = e.secret ? `\\${${e.key}}` : e.defaultValue;\n\t\t}\n\t\tsvc.environment = env;\n\t}\n\n\tconst exposedPorts = def.ports.filter((p) => p.exposed);\n\tif (exposedPorts.length > 0) {\n\t\tconst prefix = def.id.toUpperCase().replace(/-/g, \"_\");\n\t\tsvc.ports = exposedPorts.map((p, i) => {\n\t\t\tconst suffix = exposedPorts.length > 1 ? `_${i}` : \"\";\n\t\t\tlet defaultPort = p.host;\n\n\t\t\t// Override proxy ports if custom ports are specified\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpPort !== undefined &&\n\t\t\t\tp.container === 80\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpPort;\n\t\t\t}\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpsPort !== undefined &&\n\t\t\t\tp.container === 443\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpsPort;\n\t\t\t}\n\n\t\t\t// Apply global port overrides if specified\n\t\t\tif (options.portOverrides?.[def.id]?.[p.host] !== undefined) {\n\t\t\t\tdefaultPort = options.portOverrides?.[def.id]?.[p.host] || p.host;\n\t\t\t}\n\n\t\t\t// Use _EXTERNAL_PORT to avoid colliding with openclawEnvVars _PORT keys.\n\t\t\t// openclawEnvVars define the container port (e.g. GRAFANA_PORT=3000 for\n\t\t\t// internal Docker networking), while these are host port mappings (e.g. 3150).\n\t\t\t// Without this, GRAFANA_PORT=3000 from .env would override the default\n\t\t\t// 3150 in ${GRAFANA_PORT:-3150}:3000, mapping host port 3000 instead.\n\t\t\treturn `\\${${prefix}_EXTERNAL_PORT${suffix}:-${defaultPort}}:${p.container}`;\n\t\t});\n\t}\n\n\tif (def.volumes.length > 0) {\n\t\tsvc.volumes = def.volumes.map((v) => {\n\t\t\tconst isBindMount =\n\t\t\t\tv.name.startsWith(\"./\") || v.name.startsWith(\"/\") || v.name.startsWith(\"~\");\n\n\t\t\tif (!isBindMount) {\n\t\t\t\tallVolumes.add(v.name);\n\t\t\t\tvolumeNames.push(v.name);\n\t\t\t}\n\t\t\treturn `${v.name}:${v.containerPath}`;\n\t\t});\n\t}\n\n\t// PostgreSQL: mount the init script and pass per-service DB passwords\n\tif (def.id === \"postgresql\") {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./postgres/init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh:ro\",\n\t\t);\n\n\t\t// Pass per-service database password env vars so the init script can use them\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tif (dbReqs.length > 0) {\n\t\t\tconst env = (svc.environment ?? {}) as Record<string, string>;\n\t\t\tfor (const req of dbReqs) {\n\t\t\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t\t\t}\n\t\t\tsvc.environment = env;\n\t\t}\n\t}\n\n\tif (def.healthcheck) {\n\t\tconst hc: Record<string, unknown> = {\n\t\t\ttest: [\"CMD-SHELL\", def.healthcheck.test],\n\t\t\tinterval: def.healthcheck.interval,\n\t\t\ttimeout: def.healthcheck.timeout,\n\t\t\tretries: def.healthcheck.retries,\n\t\t};\n\t\tif (def.healthcheck.startPeriod) {\n\t\t\thc.start_period = def.healthcheck.startPeriod;\n\t\t}\n\t\tsvc.healthcheck = hc;\n\t}\n\n\tsvc.restart = def.restartPolicy;\n\tsvc.networks = def.networks;\n\n\tif (def.command) svc.command = def.command;\n\tif (def.entrypoint) svc.entrypoint = def.entrypoint;\n\n\t// Labels: merge static definition labels with dynamic Traefik labels\n\tconst mergedLabels: Record<string, string> = {};\n\tif (def.labels) Object.assign(mergedLabels, def.labels);\n\tconst traefikLabels = options.traefikLabels?.get(def.id);\n\tif (traefikLabels) Object.assign(mergedLabels, traefikLabels);\n\tif (Object.keys(mergedLabels).length > 0) svc.labels = mergedLabels;\n\n\t// Traefik: bind-mount static config and Docker socket\n\tif (def.id === \"traefik\" && options.traefikLabels) {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./traefik/traefik.yml:/etc/traefik/traefik.yml:ro\",\n\t\t\t\"/var/run/docker.sock:/var/run/docker.sock:ro\",\n\t\t);\n\t}\n\n\tlet deploy: Record<string, unknown> | undefined;\n\tif (def.deploy) {\n\t\tdeploy = JSON.parse(JSON.stringify(def.deploy)) as Record<string, unknown>;\n\t}\n\tif (options.gpu && def.gpuRequired) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\treservations: {\n\t\t\t\t\t...((resources.reservations as Record<string, unknown>) ?? {}),\n\t\t\t\t\tdevices: [{ driver: \"nvidia\", count: \"all\", capabilities: [\"gpu\"] }],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\t// Memory limits from estimatedMemoryMB\n\tif (def.minMemoryMB && options.hardened) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tconst limits = (resources.limits as Record<string, unknown>) ?? {};\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\tlimits: {\n\t\t\t\t\t...limits,\n\t\t\t\t\tmemory: `${def.minMemoryMB * 2}m`, // 2x min as safe limit\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\tif (deploy) svc.deploy = deploy;\n\n\t// Security hardening (when enabled)\n\tif (options.hardened) {\n\t\tsvc.cap_drop = [\"ALL\"];\n\t\tsvc.security_opt = [\"no-new-privileges:true\"];\n\n\t\t// Services that need specific capabilities\n\t\tconst capAddMap: Record<string, string[]> = {\n\t\t\tcaddy: [\"NET_BIND_SERVICE\"],\n\t\t\ttraefik: [\"NET_BIND_SERVICE\"],\n\t\t\tcrowdsec: [\"NET_BIND_SERVICE\", \"DAC_READ_SEARCH\"],\n\t\t};\n\t\tif (capAddMap[def.id]) {\n\t\t\tsvc.cap_add = capAddMap[def.id];\n\t\t}\n\t}\n\n\t// Merge both dependsOn and requires to ensure proper Docker startup ordering\n\tconst depIds = [...new Set([...def.dependsOn, ...def.requires])].filter((id) =>\n\t\tresolved.services.some((s) => s.definition.id === id),\n\t);\n\tif (depIds.length > 0) {\n\t\tconst dependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const depId of depIds) {\n\t\t\tconst dep = resolved.services.find((s) => s.definition.id === depId);\n\t\t\tdependsOn[depId] = {\n\t\t\t\tcondition: dep?.definition.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t};\n\t\t}\n\t\tsvc.depends_on = dependsOn;\n\t}\n\n\treturn { entry: svc, volumeNames };\n}\n\n// ── PostgreSQL Setup Init Container ─────────────────────────────────────────\n\n/**\n * Builds a one-shot init container that creates databases and users for\n * services that need their own PostgreSQL database. Runs AFTER PostgreSQL\n * is healthy, on every `docker compose up`, and is idempotent.\n *\n * Uses standard PG* environment variables (PGHOST, PGUSER, PGDATABASE,\n * PGPASSWORD) so psql/createuser/createdb automatically connect without\n * needing explicit -h/-U/-d flags — simpler and avoids YAML escaping issues.\n *\n * Returns null when no setup is needed (no PostgreSQL or no DB requirements).\n */\nfunction buildPostgresSetup(resolved: ResolverOutput): Record<string, unknown> | null {\n\tconst hasPostgres = resolved.services.some((s) => s.definition.id === \"postgresql\");\n\tif (!hasPostgres) return null;\n\n\tconst dbReqs = getDbRequirements(resolved);\n\tif (dbReqs.length === 0) return null;\n\n\t// Build a shell script with one command per line.\n\t// Uses $$ to escape $ from Docker Compose's variable substitution —\n\t// Docker Compose converts $$ → $ before passing to the container.\n\t// NO set -e: we handle errors explicitly so one failed service doesn't block others.\n\tconst scriptLines = [\"echo '=== PostgreSQL database setup ==='\", \"FAILED=0\"];\n\n\tfor (const req of dbReqs) {\n\t\t// Each service's setup is wrapped so a failure doesn't block the others.\n\t\t// Uses psql -v ON_ERROR_STOP=0 so SQL errors don't abort psql.\n\t\tscriptLines.push(\n\t\t\t`echo \"Setting up database '${req.dbName}' with user '${req.dbUser}'...\"`,\n\t\t\t// Create user if not exists (pure SQL, no createuser binary quirks)\n\t\t\t`psql -c \"SELECT 1 FROM pg_roles WHERE rolname='${req.dbUser}'\" | grep -q 1 || psql -c \"CREATE ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Always sync the password to match current env\n\t\t\t`psql -c \"ALTER ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Create database if not exists\n\t\t\t`psql -tc \"SELECT 1 FROM pg_database WHERE datname='${req.dbName}'\" | grep -q 1 || psql -c \"CREATE DATABASE ${req.dbName} OWNER ${req.dbUser}\"`,\n\t\t\t// Grant privileges (idempotent)\n\t\t\t`psql -c \"GRANT ALL PRIVILEGES ON DATABASE ${req.dbName} TO ${req.dbUser}\" || FAILED=1`,\n\t\t\t`echo \" Done: ${req.dbName}\"`,\n\t\t);\n\t}\n\n\tscriptLines.push(\"echo '=== All databases ready ==='\", \"exit $$FAILED\");\n\n\t// Standard PG* env vars: psql/createuser/createdb use these automatically\n\tconst env: Record<string, string> = {\n\t\tPGHOST: \"postgresql\",\n\t\tPGUSER: \"${POSTGRES_USER:-openclaw}\",\n\t\tPGDATABASE: \"${POSTGRES_DB:-openclaw}\",\n\t\tPGPASSWORD: \"${POSTGRES_PASSWORD}\",\n\t};\n\tfor (const req of dbReqs) {\n\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t}\n\n\treturn {\n\t\timage: \"postgres:17-alpine\",\n\t\tdepends_on: {\n\t\t\tpostgresql: { condition: \"service_healthy\" },\n\t\t},\n\t\tenvironment: env,\n\t\t// command MUST be a single-element array so the entire script is passed\n\t\t// as ONE argument to `sh -c`. A plain string gets shlex-split by Docker\n\t\t// Compose into multiple args, breaking multi-line scripts.\n\t\tentrypoint: [\"/bin/sh\", \"-c\"],\n\t\tcommand: [scriptLines.join(\"\\n\")],\n\t\trestart: quotedStr(\"no\"),\n\t\tnetworks: [\"openclaw-network\"],\n\t};\n}\n\n// ── Single-File Compose ─────────────────────────────────────────────────────\n\n/**\n * Generates a single Docker Compose YAML string with ALL services.\n * Backward-compatible signature.\n */\nexport function compose(resolved: ResolverOutput, options: ComposeOptions): string {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\n\t// Build depends_on for ALL companion services\n\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\tfor (const { definition: def } of resolved.services) {\n\t\tgatewayDependsOn[def.id] = {\n\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t};\n\t}\n\n\tconst services: Record<string, Record<string, unknown>> = {};\n\tlet allVolumes = new Set<string>();\n\n\tif (!isDirectInstall) {\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\t\tallVolumes = gwVolumes;\n\t\tservices[\"openclaw-gateway\"] = gatewayService;\n\t\t// CLI service added after companions\n\t\t// Determine which services need DB setup so we can redirect their depends_on\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tservices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers, just companion services\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst volumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tvolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\treturn stringify({ services, volumes, networks }, YAML_OPTIONS);\n}\n\n// ── Multi-File Compose ──────────────────────────────────────────────────────\n\ninterface ServiceInfo {\n\tid: string;\n\tcategory: ServiceCategory;\n\tentry: Record<string, unknown>;\n\tvolumeNames: string[];\n}\n\n/**\n * Generates multiple Docker Compose files, splitting services into profile-based\n * override files by category.\n */\nexport function composeMultiFile(resolved: ResolverOutput, options: ComposeOptions): ComposeResult {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\tconst allVolumes = new Set<string>();\n\n\t// Build all companion service entries & classify by category\n\tconst serviceInfos: ServiceInfo[] = [];\n\tconst dbReqs = getDbRequirements(resolved);\n\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\tfor (const { definition: def } of resolved.services) {\n\t\tconst { entry, volumeNames } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t// Redirect DB-dependent services to depend on postgres-setup\n\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\tif (deps.postgresql) {\n\t\t\t\tdelete deps.postgresql;\n\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t}\n\t\t}\n\t\tserviceInfos.push({ id: def.id, category: def.category, entry, volumeNames });\n\t}\n\n\t// Partition services into base vs. profile files\n\tconst baseServiceIds = new Set<string>();\n\tconst profileFileMap: Record<string, { profile: string; services: ServiceInfo[] }> = {};\n\n\tfor (const info of serviceInfos) {\n\t\tconst mapping = CATEGORY_PROFILE_MAP[info.category];\n\t\tif (mapping) {\n\t\t\tif (!profileFileMap[mapping.file]) {\n\t\t\t\tprofileFileMap[mapping.file] = { profile: mapping.profile, services: [] };\n\t\t\t}\n\t\t\tprofileFileMap[mapping.file]!.services.push(info);\n\t\t} else {\n\t\t\tbaseServiceIds.add(info.id);\n\t\t}\n\t}\n\n\tconst baseServices: Record<string, Record<string, unknown>> = {};\n\n\tif (!isDirectInstall) {\n\t\t// Gateway depends_on (only base services)\n\t\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tif (baseServiceIds.has(def.id)) {\n\t\t\t\tgatewayDependsOn[def.id] = {\n\t\t\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\n\t\t// Merge gateway volumes into allVolumes\n\t\tfor (const v of gwVolumes) allVolumes.add(v);\n\n\t\tbaseServices[\"openclaw-gateway\"] = gatewayService;\n\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\t// Add postgres-setup init container if needed\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tbaseServices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst sortedAllVolumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tsortedAllVolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\tconst files: Record<string, string> = {};\n\tfiles[\"docker-compose.yml\"] = stringify(\n\t\t{ services: baseServices, volumes: sortedAllVolumes, networks },\n\t\tYAML_OPTIONS,\n\t);\n\n\t// Profile override files\n\tconst usedProfiles = new Set<string>();\n\n\tfor (const [fileName, { profile, services }] of Object.entries(profileFileMap)) {\n\t\tusedProfiles.add(profile);\n\n\t\tconst profileServices: Record<string, Record<string, unknown>> = {};\n\t\tconst profileVolumes = new Set<string>();\n\n\t\tfor (const info of services) {\n\t\t\tprofileServices[info.id] = { ...info.entry, profiles: [profile] };\n\t\t\tfor (const vName of info.volumeNames) {\n\t\t\t\tprofileVolumes.add(vName);\n\t\t\t}\n\t\t}\n\n\t\tconst fileContent: Record<string, unknown> = { services: profileServices };\n\n\t\tif (profileVolumes.size > 0) {\n\t\t\tconst sortedProfileVolumes: Record<string, null> = {};\n\t\t\tfor (const v of [...profileVolumes].sort()) {\n\t\t\t\tsortedProfileVolumes[v] = null;\n\t\t\t}\n\t\t\tfileContent.volumes = sortedProfileVolumes;\n\t\t}\n\n\t\tfiles[fileName] = stringify(fileContent, YAML_OPTIONS);\n\t}\n\n\treturn {\n\t\tfiles,\n\t\tmainFile: \"docker-compose.yml\",\n\t\tprofiles: [...usedProfiles].sort(),\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"composer.cjs","names":["Scalar","getDbRequirements"],"sources":["../src/composer.ts"],"sourcesContent":["import { Scalar, stringify } from \"yaml\";\nimport { getDbRequirements } from \"./generators/postgres-init.js\";\nimport type {\n\tComposeOptions,\n\tOpenclawImageVariant,\n\tResolverOutput,\n\tServiceCategory,\n} from \"./types.js\";\n\n/** Maps image variant to the Docker image string. */\nconst IMAGE_VARIANTS: Record<OpenclawImageVariant, string> = {\n\tofficial: \"ghcr.io/openclaw/openclaw\",\n\tcoolify: \"coollabsio/openclaw\",\n\talpine: \"alpine/openclaw\",\n};\n\n/** Returns the OpenClaw image string with version tag for a given variant. */\nfunction getOpenclawImage(variant: OpenclawImageVariant, version: string): string {\n\tconst base = IMAGE_VARIANTS[variant];\n\t// Coolify and alpine images use :latest by default\n\tconst tag = variant === \"official\" ? version : \"latest\";\n\treturn `${base}:${tag}`;\n}\n\n/** Creates a YAML scalar that is always quoted — avoids YAML 1.1 bare `no` → false. */\nfunction quotedStr(value: string): Scalar {\n\tconst s = new Scalar(value);\n\ts.type = Scalar.QUOTE_DOUBLE;\n\treturn s;\n}\n\n// ── Public Types ────────────────────────────────────────────────────────────\n\nexport interface ComposeResult {\n\tfiles: Record<string, string>; // filename -> YAML content\n\tmainFile: string; // \"docker-compose.yml\"\n\tprofiles: string[]; // list of profiles used\n}\n\n// ── Category → Profile Mapping ──────────────────────────────────────────────\n\nconst CATEGORY_PROFILE_MAP: Partial<Record<ServiceCategory, { file: string; profile: string }>> = {\n\tai: { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\t\"ai-platform\": { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\tmedia: { file: \"docker-compose.media.yml\", profile: \"media\" },\n\tmonitoring: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\tanalytics: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\t\"dev-tools\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"coding-agent\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"social-media\": { file: \"docker-compose.social.yml\", profile: \"social\" },\n\tknowledge: { file: \"docker-compose.knowledge.yml\", profile: \"knowledge\" },\n\tcommunication: { file: \"docker-compose.communication.yml\", profile: \"communication\" },\n\t\"saas-boilerplate\": { file: \"docker-compose.saas.yml\", profile: \"saas\" },\n};\n\nconst YAML_OPTIONS = { lineWidth: 120, nullStr: \"\" };\n\n// ── Shared Gateway Builder ──────────────────────────────────────────────────\n\ninterface GatewayBuildResult {\n\tgatewayService: Record<string, unknown>;\n\tcliService: Record<string, unknown>;\n\tallVolumes: Set<string>;\n}\n\n/**\n * Builds the OpenClaw gateway and CLI service entries.\n * Matches the real OpenClaw docker-compose.yml structure:\n * - Bridge port 18790 for ACP/WebSocket\n * - Bind-mount volumes (not named volumes)\n * - Claude web-provider session env vars\n * - Gateway bind mode (--bind lan)\n * - CLI companion service with stdin/tty\n */\nfunction buildGatewayServices(\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tdependsOn?: Record<string, { condition: string }>,\n): GatewayBuildResult {\n\tconst allVolumes = new Set<string>();\n\n\t// Gateway environment\n\tconst gatewayEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t// Claude web-provider session vars (optional, user fills in .env)\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add AI provider API keys to gateway environment\n\tconst providerKeys = [\n\t\t\"OPENAI_API_KEY\",\n\t\t\"ANTHROPIC_API_KEY\",\n\t\t\"GOOGLE_API_KEY\",\n\t\t\"XAI_API_KEY\",\n\t\t\"DEEPSEEK_API_KEY\",\n\t\t\"GROQ_API_KEY\",\n\t\t\"OPENROUTER_API_KEY\",\n\t\t\"MISTRAL_API_KEY\",\n\t\t\"TOGETHER_API_KEY\",\n\t\t\"OLLAMA_API_KEY\",\n\t];\n\tfor (const key of providerKeys) {\n\t\tgatewayEnv[key] = `\\${${key}}`;\n\t}\n\n\t// Gateway volumes (bind-mount style matching real docker-setup.sh)\n\tconst gatewayVolumes: string[] = [\n\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t];\n\n\t// Collect env vars and volume mounts from companion services\n\tfor (const { definition: def } of resolved.services) {\n\t\tfor (const env of def.openclawEnvVars) {\n\t\t\tgatewayEnv[env.key] = env.secret ? `\\${${env.key}}` : env.defaultValue;\n\t\t}\n\t\tif (def.openclawVolumeMounts) {\n\t\t\tfor (const vol of def.openclawVolumeMounts) {\n\t\t\t\tconst isBindMount =\n\t\t\t\t\tvol.name.startsWith(\"./\") || vol.name.startsWith(\"/\") || vol.name.startsWith(\"~\");\n\t\t\t\tif (!isBindMount) {\n\t\t\t\t\tallVolumes.add(vol.name);\n\t\t\t\t}\n\t\t\t\tgatewayVolumes.push(`${vol.name}:${vol.containerPath}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Gateway service\n\tconst defaultImage = getOpenclawImage(\n\t\toptions.openclawImage ?? \"official\",\n\t\toptions.openclawVersion,\n\t);\n\tconst gateway: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tenvironment: gatewayEnv,\n\t\tvolumes: gatewayVolumes,\n\t\tports: [\"${OPENCLAW_GATEWAY_PORT:-18789}:18789\", \"${OPENCLAW_BRIDGE_PORT:-18790}:18790\"],\n\t\tnetworks: [\"openclaw-network\"],\n\t\tinit: true,\n\t\trestart: \"unless-stopped\",\n\t\tcommand: [\n\t\t\t\"node\",\n\t\t\t\"dist/index.js\",\n\t\t\t\"gateway\",\n\t\t\t\"--bind\",\n\t\t\t\"${OPENCLAW_GATEWAY_BIND:-lan}\",\n\t\t\t\"--port\",\n\t\t\t\"18789\",\n\t\t\t\"--allow-unconfigured\",\n\t\t],\n\t\thealthcheck: {\n\t\t\ttest: [\n\t\t\t\t\"CMD\",\n\t\t\t\t\"node\",\n\t\t\t\t\"-e\",\n\t\t\t\t\"fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\",\n\t\t\t],\n\t\t\tinterval: \"30s\",\n\t\t\ttimeout: \"5s\",\n\t\t\tretries: 5,\n\t\t\tstart_period: \"20s\",\n\t\t},\n\t};\n\n\t// Traefik labels for the gateway\n\tconst gwTraefikLabels = options.traefikLabels?.get(\"openclaw-gateway\");\n\tif (gwTraefikLabels) {\n\t\tgateway.labels = gwTraefikLabels;\n\t}\n\n\tif (options.bareMetalNativeHost) {\n\t\tgateway.extra_hosts = [\"host.docker.internal:host-gateway\"];\n\t}\n\n\tif (dependsOn && Object.keys(dependsOn).length > 0) {\n\t\tgateway.depends_on = dependsOn;\n\t}\n\n\t// CLI companion service (matching real OpenClaw docker-compose.yml)\n\t// Build CLI environment with same keys as gateway for consistency\n\tconst cliEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\tBROWSER: \"echo\",\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add same AI provider API keys to CLI for direct AI interactions\n\tfor (const key of providerKeys) {\n\t\tcliEnv[key] = `\\${${key}}`;\n\t}\n\n\tconst cliService: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tnetwork_mode: \"service:openclaw-gateway\",\n\t\tcap_drop: [\"NET_RAW\", \"NET_ADMIN\"],\n\t\tsecurity_opt: [\"no-new-privileges:true\"],\n\t\tenvironment: cliEnv,\n\t\tvolumes: [\n\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t],\n\t\tstdin_open: true,\n\t\ttty: true,\n\t\tinit: true,\n\t\tentrypoint: [\"node\", \"dist/index.js\"],\n\t\tdepends_on: [\"openclaw-gateway\"],\n\t};\n\n\treturn { gatewayService: gateway, cliService: cliService, allVolumes };\n}\n\n// ── Shared Companion Service Builder ────────────────────────────────────────\n\nfunction buildCompanionService(\n\tdef: ResolverOutput[\"services\"][number][\"definition\"],\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tallVolumes: Set<string>,\n): { entry: Record<string, unknown>; volumeNames: string[] } {\n\tconst svc: Record<string, unknown> = {};\n\tconst volumeNames: string[] = [];\n\n\t// Git-based services use build: context; image-based services use image:\n\tif (def.gitSource && def.buildContext) {\n\t\tconst subdir = def.gitSource.subdirectory || \".\";\n\t\tconst ctxPath = def.buildContext.context || \".\";\n\t\tconst contextFull = subdir === \".\" ? `./repos/${def.id}/${ctxPath}` : `./repos/${def.id}/${subdir}/${ctxPath}`;\n\t\tconst buildBlock: Record<string, unknown> = { context: contextFull };\n\t\tif (def.buildContext.dockerfile) {\n\t\t\tbuildBlock.dockerfile = def.buildContext.dockerfile;\n\t\t}\n\t\tif (def.buildContext.args && Object.keys(def.buildContext.args).length > 0) {\n\t\t\tbuildBlock.args = def.buildContext.args;\n\t\t}\n\t\tif (def.buildContext.target) {\n\t\t\tbuildBlock.target = def.buildContext.target;\n\t\t}\n\t\tsvc.build = buildBlock;\n\t} else {\n\t\tsvc.image = `${def.image}:${def.imageTag}`;\n\t}\n\n\tif (def.environment.length > 0) {\n\t\tconst env: Record<string, string> = {};\n\t\tfor (const e of def.environment) {\n\t\t\tenv[e.key] = e.secret ? `\\${${e.key}}` : e.defaultValue;\n\t\t}\n\t\tsvc.environment = env;\n\t}\n\n\tconst exposedPorts = def.ports.filter((p) => p.exposed);\n\tif (exposedPorts.length > 0) {\n\t\tconst prefix = def.id.toUpperCase().replace(/-/g, \"_\");\n\t\tsvc.ports = exposedPorts.map((p, i) => {\n\t\t\tconst suffix = exposedPorts.length > 1 ? `_${i}` : \"\";\n\t\t\tlet defaultPort = p.host;\n\n\t\t\t// Override proxy ports if custom ports are specified\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpPort !== undefined &&\n\t\t\t\tp.container === 80\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpPort;\n\t\t\t}\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpsPort !== undefined &&\n\t\t\t\tp.container === 443\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpsPort;\n\t\t\t}\n\n\t\t\t// Apply global port overrides if specified\n\t\t\tif (options.portOverrides?.[def.id]?.[p.host] !== undefined) {\n\t\t\t\tdefaultPort = options.portOverrides?.[def.id]?.[p.host] || p.host;\n\t\t\t}\n\n\t\t\t// Use _EXTERNAL_PORT to avoid colliding with openclawEnvVars _PORT keys.\n\t\t\t// openclawEnvVars define the container port (e.g. GRAFANA_PORT=3000 for\n\t\t\t// internal Docker networking), while these are host port mappings (e.g. 3150).\n\t\t\t// Without this, GRAFANA_PORT=3000 from .env would override the default\n\t\t\t// 3150 in ${GRAFANA_PORT:-3150}:3000, mapping host port 3000 instead.\n\t\t\treturn `\\${${prefix}_EXTERNAL_PORT${suffix}:-${defaultPort}}:${p.container}`;\n\t\t});\n\t}\n\n\tif (def.volumes.length > 0) {\n\t\tsvc.volumes = def.volumes.map((v) => {\n\t\t\tconst isBindMount =\n\t\t\t\tv.name.startsWith(\"./\") || v.name.startsWith(\"/\") || v.name.startsWith(\"~\");\n\n\t\t\tif (!isBindMount) {\n\t\t\t\tallVolumes.add(v.name);\n\t\t\t\tvolumeNames.push(v.name);\n\t\t\t}\n\t\t\treturn `${v.name}:${v.containerPath}`;\n\t\t});\n\t}\n\n\t// PostgreSQL: mount the init script and pass per-service DB passwords\n\tif (def.id === \"postgresql\") {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./postgres/init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh:ro\",\n\t\t);\n\n\t\t// Pass per-service database password env vars so the init script can use them\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tif (dbReqs.length > 0) {\n\t\t\tconst env = (svc.environment ?? {}) as Record<string, string>;\n\t\t\tfor (const req of dbReqs) {\n\t\t\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t\t\t}\n\t\t\tsvc.environment = env;\n\t\t}\n\t}\n\n\tif (def.healthcheck) {\n\t\tconst hc: Record<string, unknown> = {\n\t\t\ttest: [\"CMD-SHELL\", def.healthcheck.test],\n\t\t\tinterval: def.healthcheck.interval,\n\t\t\ttimeout: def.healthcheck.timeout,\n\t\t\tretries: def.healthcheck.retries,\n\t\t};\n\t\tif (def.healthcheck.startPeriod) {\n\t\t\thc.start_period = def.healthcheck.startPeriod;\n\t\t}\n\t\tsvc.healthcheck = hc;\n\t}\n\n\tsvc.restart = def.restartPolicy;\n\tsvc.networks = def.networks;\n\n\tif (def.command) svc.command = def.command;\n\tif (def.entrypoint) svc.entrypoint = def.entrypoint;\n\n\t// Labels: merge static definition labels with dynamic Traefik labels\n\tconst mergedLabels: Record<string, string> = {};\n\tif (def.labels) Object.assign(mergedLabels, def.labels);\n\tconst traefikLabels = options.traefikLabels?.get(def.id);\n\tif (traefikLabels) Object.assign(mergedLabels, traefikLabels);\n\tif (Object.keys(mergedLabels).length > 0) svc.labels = mergedLabels;\n\n\t// Traefik: bind-mount static config and Docker socket\n\tif (def.id === \"traefik\" && options.traefikLabels) {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./traefik/traefik.yml:/etc/traefik/traefik.yml:ro\",\n\t\t\t\"/var/run/docker.sock:/var/run/docker.sock:ro\",\n\t\t);\n\t}\n\n\tlet deploy: Record<string, unknown> | undefined;\n\tif (def.deploy) {\n\t\tdeploy = JSON.parse(JSON.stringify(def.deploy)) as Record<string, unknown>;\n\t}\n\tif (options.gpu && def.gpuRequired) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\treservations: {\n\t\t\t\t\t...((resources.reservations as Record<string, unknown>) ?? {}),\n\t\t\t\t\tdevices: [{ driver: \"nvidia\", count: \"all\", capabilities: [\"gpu\"] }],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\t// Memory limits from estimatedMemoryMB\n\tif (def.minMemoryMB && options.hardened) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tconst limits = (resources.limits as Record<string, unknown>) ?? {};\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\tlimits: {\n\t\t\t\t\t...limits,\n\t\t\t\t\tmemory: `${def.minMemoryMB * 2}m`, // 2x min as safe limit\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\tif (deploy) svc.deploy = deploy;\n\n\t// Security hardening (when enabled)\n\tif (options.hardened) {\n\t\tsvc.cap_drop = [\"ALL\"];\n\t\tsvc.security_opt = [\"no-new-privileges:true\"];\n\n\t\t// Services that need specific capabilities\n\t\tconst capAddMap: Record<string, string[]> = {\n\t\t\tcaddy: [\"NET_BIND_SERVICE\"],\n\t\t\ttraefik: [\"NET_BIND_SERVICE\"],\n\t\t\tcrowdsec: [\"NET_BIND_SERVICE\", \"DAC_READ_SEARCH\"],\n\t\t};\n\t\tif (capAddMap[def.id]) {\n\t\t\tsvc.cap_add = capAddMap[def.id];\n\t\t}\n\t}\n\n\t// Merge both dependsOn and requires to ensure proper Docker startup ordering\n\tconst depIds = [...new Set([...def.dependsOn, ...def.requires])].filter((id) =>\n\t\tresolved.services.some((s) => s.definition.id === id),\n\t);\n\tif (depIds.length > 0) {\n\t\tconst dependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const depId of depIds) {\n\t\t\tconst dep = resolved.services.find((s) => s.definition.id === depId);\n\t\t\tdependsOn[depId] = {\n\t\t\t\tcondition: dep?.definition.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t};\n\t\t}\n\t\tsvc.depends_on = dependsOn;\n\t}\n\n\treturn { entry: svc, volumeNames };\n}\n\n// ── PostgreSQL Setup Init Container ─────────────────────────────────────────\n\n/**\n * Builds a one-shot init container that creates databases and users for\n * services that need their own PostgreSQL database. Runs AFTER PostgreSQL\n * is healthy, on every `docker compose up`, and is idempotent.\n *\n * Uses standard PG* environment variables (PGHOST, PGUSER, PGDATABASE,\n * PGPASSWORD) so psql/createuser/createdb automatically connect without\n * needing explicit -h/-U/-d flags — simpler and avoids YAML escaping issues.\n *\n * Returns null when no setup is needed (no PostgreSQL or no DB requirements).\n */\nfunction buildPostgresSetup(resolved: ResolverOutput): Record<string, unknown> | null {\n\tconst hasPostgres = resolved.services.some((s) => s.definition.id === \"postgresql\");\n\tif (!hasPostgres) return null;\n\n\tconst dbReqs = getDbRequirements(resolved);\n\tif (dbReqs.length === 0) return null;\n\n\t// Build a shell script with one command per line.\n\t// Uses $$ to escape $ from Docker Compose's variable substitution —\n\t// Docker Compose converts $$ → $ before passing to the container.\n\t// NO set -e: we handle errors explicitly so one failed service doesn't block others.\n\tconst scriptLines = [\"echo '=== PostgreSQL database setup ==='\", \"FAILED=0\"];\n\n\tfor (const req of dbReqs) {\n\t\t// Each service's setup is wrapped so a failure doesn't block the others.\n\t\t// Uses psql -v ON_ERROR_STOP=0 so SQL errors don't abort psql.\n\t\tscriptLines.push(\n\t\t\t`echo \"Setting up database '${req.dbName}' with user '${req.dbUser}'...\"`,\n\t\t\t// Create user if not exists (pure SQL, no createuser binary quirks)\n\t\t\t`psql -c \"SELECT 1 FROM pg_roles WHERE rolname='${req.dbUser}'\" | grep -q 1 || psql -c \"CREATE ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Always sync the password to match current env\n\t\t\t`psql -c \"ALTER ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Create database if not exists\n\t\t\t`psql -tc \"SELECT 1 FROM pg_database WHERE datname='${req.dbName}'\" | grep -q 1 || psql -c \"CREATE DATABASE ${req.dbName} OWNER ${req.dbUser}\"`,\n\t\t\t// Grant privileges (idempotent)\n\t\t\t`psql -c \"GRANT ALL PRIVILEGES ON DATABASE ${req.dbName} TO ${req.dbUser}\" || FAILED=1`,\n\t\t\t`echo \" Done: ${req.dbName}\"`,\n\t\t);\n\t}\n\n\tscriptLines.push(\"echo '=== All databases ready ==='\", \"exit $$FAILED\");\n\n\t// Standard PG* env vars: psql/createuser/createdb use these automatically\n\tconst env: Record<string, string> = {\n\t\tPGHOST: \"postgresql\",\n\t\tPGUSER: \"${POSTGRES_USER:-openclaw}\",\n\t\tPGDATABASE: \"${POSTGRES_DB:-openclaw}\",\n\t\tPGPASSWORD: \"${POSTGRES_PASSWORD}\",\n\t};\n\tfor (const req of dbReqs) {\n\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t}\n\n\treturn {\n\t\timage: \"postgres:17-alpine\",\n\t\tdepends_on: {\n\t\t\tpostgresql: { condition: \"service_healthy\" },\n\t\t},\n\t\tenvironment: env,\n\t\t// command MUST be a single-element array so the entire script is passed\n\t\t// as ONE argument to `sh -c`. A plain string gets shlex-split by Docker\n\t\t// Compose into multiple args, breaking multi-line scripts.\n\t\tentrypoint: [\"/bin/sh\", \"-c\"],\n\t\tcommand: [scriptLines.join(\"\\n\")],\n\t\trestart: quotedStr(\"no\"),\n\t\tnetworks: [\"openclaw-network\"],\n\t};\n}\n\n// ── Single-File Compose ─────────────────────────────────────────────────────\n\n/**\n * Generates a single Docker Compose YAML string with ALL services.\n * Backward-compatible signature.\n */\nexport function compose(resolved: ResolverOutput, options: ComposeOptions): string {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\n\t// Build depends_on for ALL companion services\n\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\tfor (const { definition: def } of resolved.services) {\n\t\tgatewayDependsOn[def.id] = {\n\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t};\n\t}\n\n\tconst services: Record<string, Record<string, unknown>> = {};\n\tlet allVolumes = new Set<string>();\n\n\tif (!isDirectInstall) {\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\t\tallVolumes = gwVolumes;\n\t\tservices[\"openclaw-gateway\"] = gatewayService;\n\t\t// CLI service added after companions\n\t\t// Determine which services need DB setup so we can redirect their depends_on\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tservices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers, just companion services\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst volumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tvolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\treturn stringify({ services, volumes, networks }, YAML_OPTIONS);\n}\n\n// ── Multi-File Compose ──────────────────────────────────────────────────────\n\ninterface ServiceInfo {\n\tid: string;\n\tcategory: ServiceCategory;\n\tentry: Record<string, unknown>;\n\tvolumeNames: string[];\n}\n\n/**\n * Generates multiple Docker Compose files, splitting services into profile-based\n * override files by category.\n */\nexport function composeMultiFile(resolved: ResolverOutput, options: ComposeOptions): ComposeResult {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\tconst allVolumes = new Set<string>();\n\n\t// Build all companion service entries & classify by category\n\tconst serviceInfos: ServiceInfo[] = [];\n\tconst dbReqs = getDbRequirements(resolved);\n\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\tfor (const { definition: def } of resolved.services) {\n\t\tconst { entry, volumeNames } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t// Redirect DB-dependent services to depend on postgres-setup\n\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\tif (deps.postgresql) {\n\t\t\t\tdelete deps.postgresql;\n\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t}\n\t\t}\n\t\tserviceInfos.push({ id: def.id, category: def.category, entry, volumeNames });\n\t}\n\n\t// Partition services into base vs. profile files\n\tconst baseServiceIds = new Set<string>();\n\tconst profileFileMap: Record<string, { profile: string; services: ServiceInfo[] }> = {};\n\n\tfor (const info of serviceInfos) {\n\t\tconst mapping = CATEGORY_PROFILE_MAP[info.category];\n\t\tif (mapping) {\n\t\t\tif (!profileFileMap[mapping.file]) {\n\t\t\t\tprofileFileMap[mapping.file] = { profile: mapping.profile, services: [] };\n\t\t\t}\n\t\t\tprofileFileMap[mapping.file]!.services.push(info);\n\t\t} else {\n\t\t\tbaseServiceIds.add(info.id);\n\t\t}\n\t}\n\n\tconst baseServices: Record<string, Record<string, unknown>> = {};\n\n\tif (!isDirectInstall) {\n\t\t// Gateway depends_on (only base services)\n\t\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tif (baseServiceIds.has(def.id)) {\n\t\t\t\tgatewayDependsOn[def.id] = {\n\t\t\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\n\t\t// Merge gateway volumes into allVolumes\n\t\tfor (const v of gwVolumes) allVolumes.add(v);\n\n\t\tbaseServices[\"openclaw-gateway\"] = gatewayService;\n\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\t// Add postgres-setup init container if needed\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tbaseServices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst sortedAllVolumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tsortedAllVolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\tconst files: Record<string, string> = {};\n\tfiles[\"docker-compose.yml\"] = stringify(\n\t\t{ services: baseServices, volumes: sortedAllVolumes, networks },\n\t\tYAML_OPTIONS,\n\t);\n\n\t// Profile override files\n\tconst usedProfiles = new Set<string>();\n\n\tfor (const [fileName, { profile, services }] of Object.entries(profileFileMap)) {\n\t\tusedProfiles.add(profile);\n\n\t\tconst profileServices: Record<string, Record<string, unknown>> = {};\n\t\tconst profileVolumes = new Set<string>();\n\n\t\tfor (const info of services) {\n\t\t\tprofileServices[info.id] = { ...info.entry, profiles: [profile] };\n\t\t\tfor (const vName of info.volumeNames) {\n\t\t\t\tprofileVolumes.add(vName);\n\t\t\t}\n\t\t}\n\n\t\tconst fileContent: Record<string, unknown> = { services: profileServices };\n\n\t\tif (profileVolumes.size > 0) {\n\t\t\tconst sortedProfileVolumes: Record<string, null> = {};\n\t\t\tfor (const v of [...profileVolumes].sort()) {\n\t\t\t\tsortedProfileVolumes[v] = null;\n\t\t\t}\n\t\t\tfileContent.volumes = sortedProfileVolumes;\n\t\t}\n\n\t\tfiles[fileName] = stringify(fileContent, YAML_OPTIONS);\n\t}\n\n\treturn {\n\t\tfiles,\n\t\tmainFile: \"docker-compose.yml\",\n\t\tprofiles: [...usedProfiles].sort(),\n\t};\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"composer.d.cts","names":[],"sources":["../src/composer.ts"],"mappings":";;;UAiCiB,aAAA;EAChB,KAAA,EAAO,MAAA;EACP,QAAA;EACA,QAAA;AAAA;;;;;iBAyce,OAAA,CAAQ,QAAA,EAAU,cAAA,EAAgB,OAAA,EAAS,cAAA;;;AAA3D;;iBA2FgB,gBAAA,CAAiB,QAAA,EAAU,cAAA,EAAgB,OAAA,EAAS,cAAA,GAAiB,aAAA"}
1
+ {"version":3,"file":"composer.d.cts","names":[],"sources":["../src/composer.ts"],"mappings":";;;UAiCiB,aAAA;EAChB,KAAA,EAAO,MAAA;EACP,QAAA;EACA,QAAA;AAAA;;;;;iBA4de,OAAA,CAAQ,QAAA,EAAU,cAAA,EAAgB,OAAA,EAAS,cAAA;;;AAA3D;;iBA2FgB,gBAAA,CAAiB,QAAA,EAAU,cAAA,EAAgB,OAAA,EAAS,cAAA,GAAiB,aAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"composer.d.mts","names":[],"sources":["../src/composer.ts"],"mappings":";;;UAiCiB,aAAA;EAChB,KAAA,EAAO,MAAA;EACP,QAAA;EACA,QAAA;AAAA;;;;;iBAyce,OAAA,CAAQ,QAAA,EAAU,cAAA,EAAgB,OAAA,EAAS,cAAA;;;;AAA3D;iBA2FgB,gBAAA,CAAiB,QAAA,EAAU,cAAA,EAAgB,OAAA,EAAS,cAAA,GAAiB,aAAA"}
1
+ {"version":3,"file":"composer.d.mts","names":[],"sources":["../src/composer.ts"],"mappings":";;;UAiCiB,aAAA;EAChB,KAAA,EAAO,MAAA;EACP,QAAA;EACA,QAAA;AAAA;;;;;iBA4de,OAAA,CAAQ,QAAA,EAAU,cAAA,EAAgB,OAAA,EAAS,cAAA;;;;AAA3D;iBA2FgB,gBAAA,CAAiB,QAAA,EAAU,cAAA,EAAgB,OAAA,EAAS,cAAA,GAAiB,aAAA"}
package/dist/composer.mjs CHANGED
@@ -57,6 +57,10 @@ const CATEGORY_PROFILE_MAP = {
57
57
  communication: {
58
58
  file: "docker-compose.communication.yml",
59
59
  profile: "communication"
60
+ },
61
+ "saas-boilerplate": {
62
+ file: "docker-compose.saas.yml",
63
+ profile: "saas"
60
64
  }
61
65
  };
62
66
  const YAML_OPTIONS = {
@@ -172,7 +176,15 @@ function buildGatewayServices(resolved, options, dependsOn) {
172
176
  function buildCompanionService(def, resolved, options, allVolumes) {
173
177
  const svc = {};
174
178
  const volumeNames = [];
175
- svc.image = `${def.image}:${def.imageTag}`;
179
+ if (def.gitSource && def.buildContext) {
180
+ const subdir = def.gitSource.subdirectory || ".";
181
+ const ctxPath = def.buildContext.context || ".";
182
+ const buildBlock = { context: subdir === "." ? `./repos/${def.id}/${ctxPath}` : `./repos/${def.id}/${subdir}/${ctxPath}` };
183
+ if (def.buildContext.dockerfile) buildBlock.dockerfile = def.buildContext.dockerfile;
184
+ if (def.buildContext.args && Object.keys(def.buildContext.args).length > 0) buildBlock.args = def.buildContext.args;
185
+ if (def.buildContext.target) buildBlock.target = def.buildContext.target;
186
+ svc.build = buildBlock;
187
+ } else svc.image = `${def.image}:${def.imageTag}`;
176
188
  if (def.environment.length > 0) {
177
189
  const env = {};
178
190
  for (const e of def.environment) env[e.key] = e.secret ? `\${${e.key}}` : e.defaultValue;
@@ -1 +1 @@
1
- {"version":3,"file":"composer.mjs","names":[],"sources":["../src/composer.ts"],"sourcesContent":["import { Scalar, stringify } from \"yaml\";\nimport { getDbRequirements } from \"./generators/postgres-init.js\";\nimport type {\n\tComposeOptions,\n\tOpenclawImageVariant,\n\tResolverOutput,\n\tServiceCategory,\n} from \"./types.js\";\n\n/** Maps image variant to the Docker image string. */\nconst IMAGE_VARIANTS: Record<OpenclawImageVariant, string> = {\n\tofficial: \"ghcr.io/openclaw/openclaw\",\n\tcoolify: \"coollabsio/openclaw\",\n\talpine: \"alpine/openclaw\",\n};\n\n/** Returns the OpenClaw image string with version tag for a given variant. */\nfunction getOpenclawImage(variant: OpenclawImageVariant, version: string): string {\n\tconst base = IMAGE_VARIANTS[variant];\n\t// Coolify and alpine images use :latest by default\n\tconst tag = variant === \"official\" ? version : \"latest\";\n\treturn `${base}:${tag}`;\n}\n\n/** Creates a YAML scalar that is always quoted — avoids YAML 1.1 bare `no` → false. */\nfunction quotedStr(value: string): Scalar {\n\tconst s = new Scalar(value);\n\ts.type = Scalar.QUOTE_DOUBLE;\n\treturn s;\n}\n\n// ── Public Types ────────────────────────────────────────────────────────────\n\nexport interface ComposeResult {\n\tfiles: Record<string, string>; // filename -> YAML content\n\tmainFile: string; // \"docker-compose.yml\"\n\tprofiles: string[]; // list of profiles used\n}\n\n// ── Category → Profile Mapping ──────────────────────────────────────────────\n\nconst CATEGORY_PROFILE_MAP: Partial<Record<ServiceCategory, { file: string; profile: string }>> = {\n\tai: { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\t\"ai-platform\": { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\tmedia: { file: \"docker-compose.media.yml\", profile: \"media\" },\n\tmonitoring: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\tanalytics: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\t\"dev-tools\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"coding-agent\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"social-media\": { file: \"docker-compose.social.yml\", profile: \"social\" },\n\tknowledge: { file: \"docker-compose.knowledge.yml\", profile: \"knowledge\" },\n\tcommunication: { file: \"docker-compose.communication.yml\", profile: \"communication\" },\n};\n\nconst YAML_OPTIONS = { lineWidth: 120, nullStr: \"\" };\n\n// ── Shared Gateway Builder ──────────────────────────────────────────────────\n\ninterface GatewayBuildResult {\n\tgatewayService: Record<string, unknown>;\n\tcliService: Record<string, unknown>;\n\tallVolumes: Set<string>;\n}\n\n/**\n * Builds the OpenClaw gateway and CLI service entries.\n * Matches the real OpenClaw docker-compose.yml structure:\n * - Bridge port 18790 for ACP/WebSocket\n * - Bind-mount volumes (not named volumes)\n * - Claude web-provider session env vars\n * - Gateway bind mode (--bind lan)\n * - CLI companion service with stdin/tty\n */\nfunction buildGatewayServices(\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tdependsOn?: Record<string, { condition: string }>,\n): GatewayBuildResult {\n\tconst allVolumes = new Set<string>();\n\n\t// Gateway environment\n\tconst gatewayEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t// Claude web-provider session vars (optional, user fills in .env)\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add AI provider API keys to gateway environment\n\tconst providerKeys = [\n\t\t\"OPENAI_API_KEY\",\n\t\t\"ANTHROPIC_API_KEY\",\n\t\t\"GOOGLE_API_KEY\",\n\t\t\"XAI_API_KEY\",\n\t\t\"DEEPSEEK_API_KEY\",\n\t\t\"GROQ_API_KEY\",\n\t\t\"OPENROUTER_API_KEY\",\n\t\t\"MISTRAL_API_KEY\",\n\t\t\"TOGETHER_API_KEY\",\n\t\t\"OLLAMA_API_KEY\",\n\t];\n\tfor (const key of providerKeys) {\n\t\tgatewayEnv[key] = `\\${${key}}`;\n\t}\n\n\t// Gateway volumes (bind-mount style matching real docker-setup.sh)\n\tconst gatewayVolumes: string[] = [\n\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t];\n\n\t// Collect env vars and volume mounts from companion services\n\tfor (const { definition: def } of resolved.services) {\n\t\tfor (const env of def.openclawEnvVars) {\n\t\t\tgatewayEnv[env.key] = env.secret ? `\\${${env.key}}` : env.defaultValue;\n\t\t}\n\t\tif (def.openclawVolumeMounts) {\n\t\t\tfor (const vol of def.openclawVolumeMounts) {\n\t\t\t\tconst isBindMount =\n\t\t\t\t\tvol.name.startsWith(\"./\") || vol.name.startsWith(\"/\") || vol.name.startsWith(\"~\");\n\t\t\t\tif (!isBindMount) {\n\t\t\t\t\tallVolumes.add(vol.name);\n\t\t\t\t}\n\t\t\t\tgatewayVolumes.push(`${vol.name}:${vol.containerPath}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Gateway service\n\tconst defaultImage = getOpenclawImage(\n\t\toptions.openclawImage ?? \"official\",\n\t\toptions.openclawVersion,\n\t);\n\tconst gateway: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tenvironment: gatewayEnv,\n\t\tvolumes: gatewayVolumes,\n\t\tports: [\"${OPENCLAW_GATEWAY_PORT:-18789}:18789\", \"${OPENCLAW_BRIDGE_PORT:-18790}:18790\"],\n\t\tnetworks: [\"openclaw-network\"],\n\t\tinit: true,\n\t\trestart: \"unless-stopped\",\n\t\tcommand: [\n\t\t\t\"node\",\n\t\t\t\"dist/index.js\",\n\t\t\t\"gateway\",\n\t\t\t\"--bind\",\n\t\t\t\"${OPENCLAW_GATEWAY_BIND:-lan}\",\n\t\t\t\"--port\",\n\t\t\t\"18789\",\n\t\t\t\"--allow-unconfigured\",\n\t\t],\n\t\thealthcheck: {\n\t\t\ttest: [\n\t\t\t\t\"CMD\",\n\t\t\t\t\"node\",\n\t\t\t\t\"-e\",\n\t\t\t\t\"fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\",\n\t\t\t],\n\t\t\tinterval: \"30s\",\n\t\t\ttimeout: \"5s\",\n\t\t\tretries: 5,\n\t\t\tstart_period: \"20s\",\n\t\t},\n\t};\n\n\t// Traefik labels for the gateway\n\tconst gwTraefikLabels = options.traefikLabels?.get(\"openclaw-gateway\");\n\tif (gwTraefikLabels) {\n\t\tgateway.labels = gwTraefikLabels;\n\t}\n\n\tif (options.bareMetalNativeHost) {\n\t\tgateway.extra_hosts = [\"host.docker.internal:host-gateway\"];\n\t}\n\n\tif (dependsOn && Object.keys(dependsOn).length > 0) {\n\t\tgateway.depends_on = dependsOn;\n\t}\n\n\t// CLI companion service (matching real OpenClaw docker-compose.yml)\n\t// Build CLI environment with same keys as gateway for consistency\n\tconst cliEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\tBROWSER: \"echo\",\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add same AI provider API keys to CLI for direct AI interactions\n\tfor (const key of providerKeys) {\n\t\tcliEnv[key] = `\\${${key}}`;\n\t}\n\n\tconst cliService: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tnetwork_mode: \"service:openclaw-gateway\",\n\t\tcap_drop: [\"NET_RAW\", \"NET_ADMIN\"],\n\t\tsecurity_opt: [\"no-new-privileges:true\"],\n\t\tenvironment: cliEnv,\n\t\tvolumes: [\n\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t],\n\t\tstdin_open: true,\n\t\ttty: true,\n\t\tinit: true,\n\t\tentrypoint: [\"node\", \"dist/index.js\"],\n\t\tdepends_on: [\"openclaw-gateway\"],\n\t};\n\n\treturn { gatewayService: gateway, cliService: cliService, allVolumes };\n}\n\n// ── Shared Companion Service Builder ────────────────────────────────────────\n\nfunction buildCompanionService(\n\tdef: ResolverOutput[\"services\"][number][\"definition\"],\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tallVolumes: Set<string>,\n): { entry: Record<string, unknown>; volumeNames: string[] } {\n\tconst svc: Record<string, unknown> = {};\n\tconst volumeNames: string[] = [];\n\n\tsvc.image = `${def.image}:${def.imageTag}`;\n\n\tif (def.environment.length > 0) {\n\t\tconst env: Record<string, string> = {};\n\t\tfor (const e of def.environment) {\n\t\t\tenv[e.key] = e.secret ? `\\${${e.key}}` : e.defaultValue;\n\t\t}\n\t\tsvc.environment = env;\n\t}\n\n\tconst exposedPorts = def.ports.filter((p) => p.exposed);\n\tif (exposedPorts.length > 0) {\n\t\tconst prefix = def.id.toUpperCase().replace(/-/g, \"_\");\n\t\tsvc.ports = exposedPorts.map((p, i) => {\n\t\t\tconst suffix = exposedPorts.length > 1 ? `_${i}` : \"\";\n\t\t\tlet defaultPort = p.host;\n\n\t\t\t// Override proxy ports if custom ports are specified\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpPort !== undefined &&\n\t\t\t\tp.container === 80\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpPort;\n\t\t\t}\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpsPort !== undefined &&\n\t\t\t\tp.container === 443\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpsPort;\n\t\t\t}\n\n\t\t\t// Apply global port overrides if specified\n\t\t\tif (options.portOverrides?.[def.id]?.[p.host] !== undefined) {\n\t\t\t\tdefaultPort = options.portOverrides?.[def.id]?.[p.host] || p.host;\n\t\t\t}\n\n\t\t\t// Use _EXTERNAL_PORT to avoid colliding with openclawEnvVars _PORT keys.\n\t\t\t// openclawEnvVars define the container port (e.g. GRAFANA_PORT=3000 for\n\t\t\t// internal Docker networking), while these are host port mappings (e.g. 3150).\n\t\t\t// Without this, GRAFANA_PORT=3000 from .env would override the default\n\t\t\t// 3150 in ${GRAFANA_PORT:-3150}:3000, mapping host port 3000 instead.\n\t\t\treturn `\\${${prefix}_EXTERNAL_PORT${suffix}:-${defaultPort}}:${p.container}`;\n\t\t});\n\t}\n\n\tif (def.volumes.length > 0) {\n\t\tsvc.volumes = def.volumes.map((v) => {\n\t\t\tconst isBindMount =\n\t\t\t\tv.name.startsWith(\"./\") || v.name.startsWith(\"/\") || v.name.startsWith(\"~\");\n\n\t\t\tif (!isBindMount) {\n\t\t\t\tallVolumes.add(v.name);\n\t\t\t\tvolumeNames.push(v.name);\n\t\t\t}\n\t\t\treturn `${v.name}:${v.containerPath}`;\n\t\t});\n\t}\n\n\t// PostgreSQL: mount the init script and pass per-service DB passwords\n\tif (def.id === \"postgresql\") {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./postgres/init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh:ro\",\n\t\t);\n\n\t\t// Pass per-service database password env vars so the init script can use them\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tif (dbReqs.length > 0) {\n\t\t\tconst env = (svc.environment ?? {}) as Record<string, string>;\n\t\t\tfor (const req of dbReqs) {\n\t\t\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t\t\t}\n\t\t\tsvc.environment = env;\n\t\t}\n\t}\n\n\tif (def.healthcheck) {\n\t\tconst hc: Record<string, unknown> = {\n\t\t\ttest: [\"CMD-SHELL\", def.healthcheck.test],\n\t\t\tinterval: def.healthcheck.interval,\n\t\t\ttimeout: def.healthcheck.timeout,\n\t\t\tretries: def.healthcheck.retries,\n\t\t};\n\t\tif (def.healthcheck.startPeriod) {\n\t\t\thc.start_period = def.healthcheck.startPeriod;\n\t\t}\n\t\tsvc.healthcheck = hc;\n\t}\n\n\tsvc.restart = def.restartPolicy;\n\tsvc.networks = def.networks;\n\n\tif (def.command) svc.command = def.command;\n\tif (def.entrypoint) svc.entrypoint = def.entrypoint;\n\n\t// Labels: merge static definition labels with dynamic Traefik labels\n\tconst mergedLabels: Record<string, string> = {};\n\tif (def.labels) Object.assign(mergedLabels, def.labels);\n\tconst traefikLabels = options.traefikLabels?.get(def.id);\n\tif (traefikLabels) Object.assign(mergedLabels, traefikLabels);\n\tif (Object.keys(mergedLabels).length > 0) svc.labels = mergedLabels;\n\n\t// Traefik: bind-mount static config and Docker socket\n\tif (def.id === \"traefik\" && options.traefikLabels) {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./traefik/traefik.yml:/etc/traefik/traefik.yml:ro\",\n\t\t\t\"/var/run/docker.sock:/var/run/docker.sock:ro\",\n\t\t);\n\t}\n\n\tlet deploy: Record<string, unknown> | undefined;\n\tif (def.deploy) {\n\t\tdeploy = JSON.parse(JSON.stringify(def.deploy)) as Record<string, unknown>;\n\t}\n\tif (options.gpu && def.gpuRequired) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\treservations: {\n\t\t\t\t\t...((resources.reservations as Record<string, unknown>) ?? {}),\n\t\t\t\t\tdevices: [{ driver: \"nvidia\", count: \"all\", capabilities: [\"gpu\"] }],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\t// Memory limits from estimatedMemoryMB\n\tif (def.minMemoryMB && options.hardened) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tconst limits = (resources.limits as Record<string, unknown>) ?? {};\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\tlimits: {\n\t\t\t\t\t...limits,\n\t\t\t\t\tmemory: `${def.minMemoryMB * 2}m`, // 2x min as safe limit\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\tif (deploy) svc.deploy = deploy;\n\n\t// Security hardening (when enabled)\n\tif (options.hardened) {\n\t\tsvc.cap_drop = [\"ALL\"];\n\t\tsvc.security_opt = [\"no-new-privileges:true\"];\n\n\t\t// Services that need specific capabilities\n\t\tconst capAddMap: Record<string, string[]> = {\n\t\t\tcaddy: [\"NET_BIND_SERVICE\"],\n\t\t\ttraefik: [\"NET_BIND_SERVICE\"],\n\t\t\tcrowdsec: [\"NET_BIND_SERVICE\", \"DAC_READ_SEARCH\"],\n\t\t};\n\t\tif (capAddMap[def.id]) {\n\t\t\tsvc.cap_add = capAddMap[def.id];\n\t\t}\n\t}\n\n\t// Merge both dependsOn and requires to ensure proper Docker startup ordering\n\tconst depIds = [...new Set([...def.dependsOn, ...def.requires])].filter((id) =>\n\t\tresolved.services.some((s) => s.definition.id === id),\n\t);\n\tif (depIds.length > 0) {\n\t\tconst dependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const depId of depIds) {\n\t\t\tconst dep = resolved.services.find((s) => s.definition.id === depId);\n\t\t\tdependsOn[depId] = {\n\t\t\t\tcondition: dep?.definition.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t};\n\t\t}\n\t\tsvc.depends_on = dependsOn;\n\t}\n\n\treturn { entry: svc, volumeNames };\n}\n\n// ── PostgreSQL Setup Init Container ─────────────────────────────────────────\n\n/**\n * Builds a one-shot init container that creates databases and users for\n * services that need their own PostgreSQL database. Runs AFTER PostgreSQL\n * is healthy, on every `docker compose up`, and is idempotent.\n *\n * Uses standard PG* environment variables (PGHOST, PGUSER, PGDATABASE,\n * PGPASSWORD) so psql/createuser/createdb automatically connect without\n * needing explicit -h/-U/-d flags — simpler and avoids YAML escaping issues.\n *\n * Returns null when no setup is needed (no PostgreSQL or no DB requirements).\n */\nfunction buildPostgresSetup(resolved: ResolverOutput): Record<string, unknown> | null {\n\tconst hasPostgres = resolved.services.some((s) => s.definition.id === \"postgresql\");\n\tif (!hasPostgres) return null;\n\n\tconst dbReqs = getDbRequirements(resolved);\n\tif (dbReqs.length === 0) return null;\n\n\t// Build a shell script with one command per line.\n\t// Uses $$ to escape $ from Docker Compose's variable substitution —\n\t// Docker Compose converts $$ → $ before passing to the container.\n\t// NO set -e: we handle errors explicitly so one failed service doesn't block others.\n\tconst scriptLines = [\"echo '=== PostgreSQL database setup ==='\", \"FAILED=0\"];\n\n\tfor (const req of dbReqs) {\n\t\t// Each service's setup is wrapped so a failure doesn't block the others.\n\t\t// Uses psql -v ON_ERROR_STOP=0 so SQL errors don't abort psql.\n\t\tscriptLines.push(\n\t\t\t`echo \"Setting up database '${req.dbName}' with user '${req.dbUser}'...\"`,\n\t\t\t// Create user if not exists (pure SQL, no createuser binary quirks)\n\t\t\t`psql -c \"SELECT 1 FROM pg_roles WHERE rolname='${req.dbUser}'\" | grep -q 1 || psql -c \"CREATE ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Always sync the password to match current env\n\t\t\t`psql -c \"ALTER ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Create database if not exists\n\t\t\t`psql -tc \"SELECT 1 FROM pg_database WHERE datname='${req.dbName}'\" | grep -q 1 || psql -c \"CREATE DATABASE ${req.dbName} OWNER ${req.dbUser}\"`,\n\t\t\t// Grant privileges (idempotent)\n\t\t\t`psql -c \"GRANT ALL PRIVILEGES ON DATABASE ${req.dbName} TO ${req.dbUser}\" || FAILED=1`,\n\t\t\t`echo \" Done: ${req.dbName}\"`,\n\t\t);\n\t}\n\n\tscriptLines.push(\"echo '=== All databases ready ==='\", \"exit $$FAILED\");\n\n\t// Standard PG* env vars: psql/createuser/createdb use these automatically\n\tconst env: Record<string, string> = {\n\t\tPGHOST: \"postgresql\",\n\t\tPGUSER: \"${POSTGRES_USER:-openclaw}\",\n\t\tPGDATABASE: \"${POSTGRES_DB:-openclaw}\",\n\t\tPGPASSWORD: \"${POSTGRES_PASSWORD}\",\n\t};\n\tfor (const req of dbReqs) {\n\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t}\n\n\treturn {\n\t\timage: \"postgres:17-alpine\",\n\t\tdepends_on: {\n\t\t\tpostgresql: { condition: \"service_healthy\" },\n\t\t},\n\t\tenvironment: env,\n\t\t// command MUST be a single-element array so the entire script is passed\n\t\t// as ONE argument to `sh -c`. A plain string gets shlex-split by Docker\n\t\t// Compose into multiple args, breaking multi-line scripts.\n\t\tentrypoint: [\"/bin/sh\", \"-c\"],\n\t\tcommand: [scriptLines.join(\"\\n\")],\n\t\trestart: quotedStr(\"no\"),\n\t\tnetworks: [\"openclaw-network\"],\n\t};\n}\n\n// ── Single-File Compose ─────────────────────────────────────────────────────\n\n/**\n * Generates a single Docker Compose YAML string with ALL services.\n * Backward-compatible signature.\n */\nexport function compose(resolved: ResolverOutput, options: ComposeOptions): string {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\n\t// Build depends_on for ALL companion services\n\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\tfor (const { definition: def } of resolved.services) {\n\t\tgatewayDependsOn[def.id] = {\n\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t};\n\t}\n\n\tconst services: Record<string, Record<string, unknown>> = {};\n\tlet allVolumes = new Set<string>();\n\n\tif (!isDirectInstall) {\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\t\tallVolumes = gwVolumes;\n\t\tservices[\"openclaw-gateway\"] = gatewayService;\n\t\t// CLI service added after companions\n\t\t// Determine which services need DB setup so we can redirect their depends_on\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tservices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers, just companion services\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst volumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tvolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\treturn stringify({ services, volumes, networks }, YAML_OPTIONS);\n}\n\n// ── Multi-File Compose ──────────────────────────────────────────────────────\n\ninterface ServiceInfo {\n\tid: string;\n\tcategory: ServiceCategory;\n\tentry: Record<string, unknown>;\n\tvolumeNames: string[];\n}\n\n/**\n * Generates multiple Docker Compose files, splitting services into profile-based\n * override files by category.\n */\nexport function composeMultiFile(resolved: ResolverOutput, options: ComposeOptions): ComposeResult {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\tconst allVolumes = new Set<string>();\n\n\t// Build all companion service entries & classify by category\n\tconst serviceInfos: ServiceInfo[] = [];\n\tconst dbReqs = getDbRequirements(resolved);\n\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\tfor (const { definition: def } of resolved.services) {\n\t\tconst { entry, volumeNames } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t// Redirect DB-dependent services to depend on postgres-setup\n\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\tif (deps.postgresql) {\n\t\t\t\tdelete deps.postgresql;\n\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t}\n\t\t}\n\t\tserviceInfos.push({ id: def.id, category: def.category, entry, volumeNames });\n\t}\n\n\t// Partition services into base vs. profile files\n\tconst baseServiceIds = new Set<string>();\n\tconst profileFileMap: Record<string, { profile: string; services: ServiceInfo[] }> = {};\n\n\tfor (const info of serviceInfos) {\n\t\tconst mapping = CATEGORY_PROFILE_MAP[info.category];\n\t\tif (mapping) {\n\t\t\tif (!profileFileMap[mapping.file]) {\n\t\t\t\tprofileFileMap[mapping.file] = { profile: mapping.profile, services: [] };\n\t\t\t}\n\t\t\tprofileFileMap[mapping.file]!.services.push(info);\n\t\t} else {\n\t\t\tbaseServiceIds.add(info.id);\n\t\t}\n\t}\n\n\tconst baseServices: Record<string, Record<string, unknown>> = {};\n\n\tif (!isDirectInstall) {\n\t\t// Gateway depends_on (only base services)\n\t\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tif (baseServiceIds.has(def.id)) {\n\t\t\t\tgatewayDependsOn[def.id] = {\n\t\t\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\n\t\t// Merge gateway volumes into allVolumes\n\t\tfor (const v of gwVolumes) allVolumes.add(v);\n\n\t\tbaseServices[\"openclaw-gateway\"] = gatewayService;\n\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\t// Add postgres-setup init container if needed\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tbaseServices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst sortedAllVolumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tsortedAllVolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\tconst files: Record<string, string> = {};\n\tfiles[\"docker-compose.yml\"] = stringify(\n\t\t{ services: baseServices, volumes: sortedAllVolumes, networks },\n\t\tYAML_OPTIONS,\n\t);\n\n\t// Profile override files\n\tconst usedProfiles = new Set<string>();\n\n\tfor (const [fileName, { profile, services }] of Object.entries(profileFileMap)) {\n\t\tusedProfiles.add(profile);\n\n\t\tconst profileServices: Record<string, Record<string, unknown>> = {};\n\t\tconst profileVolumes = new Set<string>();\n\n\t\tfor (const info of services) {\n\t\t\tprofileServices[info.id] = { ...info.entry, profiles: [profile] };\n\t\t\tfor (const vName of info.volumeNames) {\n\t\t\t\tprofileVolumes.add(vName);\n\t\t\t}\n\t\t}\n\n\t\tconst fileContent: Record<string, unknown> = { services: profileServices };\n\n\t\tif (profileVolumes.size > 0) {\n\t\t\tconst sortedProfileVolumes: Record<string, null> = {};\n\t\t\tfor (const v of [...profileVolumes].sort()) {\n\t\t\t\tsortedProfileVolumes[v] = null;\n\t\t\t}\n\t\t\tfileContent.volumes = sortedProfileVolumes;\n\t\t}\n\n\t\tfiles[fileName] = stringify(fileContent, YAML_OPTIONS);\n\t}\n\n\treturn {\n\t\tfiles,\n\t\tmainFile: \"docker-compose.yml\",\n\t\tprofiles: [...usedProfiles].sort(),\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"composer.mjs","names":[],"sources":["../src/composer.ts"],"sourcesContent":["import { Scalar, stringify } from \"yaml\";\nimport { getDbRequirements } from \"./generators/postgres-init.js\";\nimport type {\n\tComposeOptions,\n\tOpenclawImageVariant,\n\tResolverOutput,\n\tServiceCategory,\n} from \"./types.js\";\n\n/** Maps image variant to the Docker image string. */\nconst IMAGE_VARIANTS: Record<OpenclawImageVariant, string> = {\n\tofficial: \"ghcr.io/openclaw/openclaw\",\n\tcoolify: \"coollabsio/openclaw\",\n\talpine: \"alpine/openclaw\",\n};\n\n/** Returns the OpenClaw image string with version tag for a given variant. */\nfunction getOpenclawImage(variant: OpenclawImageVariant, version: string): string {\n\tconst base = IMAGE_VARIANTS[variant];\n\t// Coolify and alpine images use :latest by default\n\tconst tag = variant === \"official\" ? version : \"latest\";\n\treturn `${base}:${tag}`;\n}\n\n/** Creates a YAML scalar that is always quoted — avoids YAML 1.1 bare `no` → false. */\nfunction quotedStr(value: string): Scalar {\n\tconst s = new Scalar(value);\n\ts.type = Scalar.QUOTE_DOUBLE;\n\treturn s;\n}\n\n// ── Public Types ────────────────────────────────────────────────────────────\n\nexport interface ComposeResult {\n\tfiles: Record<string, string>; // filename -> YAML content\n\tmainFile: string; // \"docker-compose.yml\"\n\tprofiles: string[]; // list of profiles used\n}\n\n// ── Category → Profile Mapping ──────────────────────────────────────────────\n\nconst CATEGORY_PROFILE_MAP: Partial<Record<ServiceCategory, { file: string; profile: string }>> = {\n\tai: { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\t\"ai-platform\": { file: \"docker-compose.ai.yml\", profile: \"ai\" },\n\tmedia: { file: \"docker-compose.media.yml\", profile: \"media\" },\n\tmonitoring: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\tanalytics: { file: \"docker-compose.monitoring.yml\", profile: \"monitoring\" },\n\t\"dev-tools\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"coding-agent\": { file: \"docker-compose.tools.yml\", profile: \"tools\" },\n\t\"social-media\": { file: \"docker-compose.social.yml\", profile: \"social\" },\n\tknowledge: { file: \"docker-compose.knowledge.yml\", profile: \"knowledge\" },\n\tcommunication: { file: \"docker-compose.communication.yml\", profile: \"communication\" },\n\t\"saas-boilerplate\": { file: \"docker-compose.saas.yml\", profile: \"saas\" },\n};\n\nconst YAML_OPTIONS = { lineWidth: 120, nullStr: \"\" };\n\n// ── Shared Gateway Builder ──────────────────────────────────────────────────\n\ninterface GatewayBuildResult {\n\tgatewayService: Record<string, unknown>;\n\tcliService: Record<string, unknown>;\n\tallVolumes: Set<string>;\n}\n\n/**\n * Builds the OpenClaw gateway and CLI service entries.\n * Matches the real OpenClaw docker-compose.yml structure:\n * - Bridge port 18790 for ACP/WebSocket\n * - Bind-mount volumes (not named volumes)\n * - Claude web-provider session env vars\n * - Gateway bind mode (--bind lan)\n * - CLI companion service with stdin/tty\n */\nfunction buildGatewayServices(\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tdependsOn?: Record<string, { condition: string }>,\n): GatewayBuildResult {\n\tconst allVolumes = new Set<string>();\n\n\t// Gateway environment\n\tconst gatewayEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\t// Claude web-provider session vars (optional, user fills in .env)\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add AI provider API keys to gateway environment\n\tconst providerKeys = [\n\t\t\"OPENAI_API_KEY\",\n\t\t\"ANTHROPIC_API_KEY\",\n\t\t\"GOOGLE_API_KEY\",\n\t\t\"XAI_API_KEY\",\n\t\t\"DEEPSEEK_API_KEY\",\n\t\t\"GROQ_API_KEY\",\n\t\t\"OPENROUTER_API_KEY\",\n\t\t\"MISTRAL_API_KEY\",\n\t\t\"TOGETHER_API_KEY\",\n\t\t\"OLLAMA_API_KEY\",\n\t];\n\tfor (const key of providerKeys) {\n\t\tgatewayEnv[key] = `\\${${key}}`;\n\t}\n\n\t// Gateway volumes (bind-mount style matching real docker-setup.sh)\n\tconst gatewayVolumes: string[] = [\n\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t];\n\n\t// Collect env vars and volume mounts from companion services\n\tfor (const { definition: def } of resolved.services) {\n\t\tfor (const env of def.openclawEnvVars) {\n\t\t\tgatewayEnv[env.key] = env.secret ? `\\${${env.key}}` : env.defaultValue;\n\t\t}\n\t\tif (def.openclawVolumeMounts) {\n\t\t\tfor (const vol of def.openclawVolumeMounts) {\n\t\t\t\tconst isBindMount =\n\t\t\t\t\tvol.name.startsWith(\"./\") || vol.name.startsWith(\"/\") || vol.name.startsWith(\"~\");\n\t\t\t\tif (!isBindMount) {\n\t\t\t\t\tallVolumes.add(vol.name);\n\t\t\t\t}\n\t\t\t\tgatewayVolumes.push(`${vol.name}:${vol.containerPath}`);\n\t\t\t}\n\t\t}\n\t}\n\n\t// Gateway service\n\tconst defaultImage = getOpenclawImage(\n\t\toptions.openclawImage ?? \"official\",\n\t\toptions.openclawVersion,\n\t);\n\tconst gateway: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tenvironment: gatewayEnv,\n\t\tvolumes: gatewayVolumes,\n\t\tports: [\"${OPENCLAW_GATEWAY_PORT:-18789}:18789\", \"${OPENCLAW_BRIDGE_PORT:-18790}:18790\"],\n\t\tnetworks: [\"openclaw-network\"],\n\t\tinit: true,\n\t\trestart: \"unless-stopped\",\n\t\tcommand: [\n\t\t\t\"node\",\n\t\t\t\"dist/index.js\",\n\t\t\t\"gateway\",\n\t\t\t\"--bind\",\n\t\t\t\"${OPENCLAW_GATEWAY_BIND:-lan}\",\n\t\t\t\"--port\",\n\t\t\t\"18789\",\n\t\t\t\"--allow-unconfigured\",\n\t\t],\n\t\thealthcheck: {\n\t\t\ttest: [\n\t\t\t\t\"CMD\",\n\t\t\t\t\"node\",\n\t\t\t\t\"-e\",\n\t\t\t\t\"fetch('http://127.0.0.1:18789/healthz').then((r)=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))\",\n\t\t\t],\n\t\t\tinterval: \"30s\",\n\t\t\ttimeout: \"5s\",\n\t\t\tretries: 5,\n\t\t\tstart_period: \"20s\",\n\t\t},\n\t};\n\n\t// Traefik labels for the gateway\n\tconst gwTraefikLabels = options.traefikLabels?.get(\"openclaw-gateway\");\n\tif (gwTraefikLabels) {\n\t\tgateway.labels = gwTraefikLabels;\n\t}\n\n\tif (options.bareMetalNativeHost) {\n\t\tgateway.extra_hosts = [\"host.docker.internal:host-gateway\"];\n\t}\n\n\tif (dependsOn && Object.keys(dependsOn).length > 0) {\n\t\tgateway.depends_on = dependsOn;\n\t}\n\n\t// CLI companion service (matching real OpenClaw docker-compose.yml)\n\t// Build CLI environment with same keys as gateway for consistency\n\tconst cliEnv: Record<string, string> = {\n\t\tHOME: \"/home/node\",\n\t\tTERM: \"xterm-256color\",\n\t\tOPENCLAW_GATEWAY_TOKEN: \"${OPENCLAW_GATEWAY_TOKEN}\",\n\t\tOPENCLAW_ALLOW_INSECURE_PRIVATE_WS: \"${OPENCLAW_ALLOW_INSECURE_PRIVATE_WS:-}\",\n\t\tBROWSER: \"echo\",\n\t\tCLAUDE_AI_SESSION_KEY: \"${CLAUDE_AI_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_SESSION_KEY: \"${CLAUDE_WEB_SESSION_KEY:-}\",\n\t\tCLAUDE_WEB_COOKIE: \"${CLAUDE_WEB_COOKIE:-}\",\n\t};\n\n\t// Add same AI provider API keys to CLI for direct AI interactions\n\tfor (const key of providerKeys) {\n\t\tcliEnv[key] = `\\${${key}}`;\n\t}\n\n\tconst cliService: Record<string, unknown> = {\n\t\timage: `\\${OPENCLAW_IMAGE:-${defaultImage}}`,\n\t\tnetwork_mode: \"service:openclaw-gateway\",\n\t\tcap_drop: [\"NET_RAW\", \"NET_ADMIN\"],\n\t\tsecurity_opt: [\"no-new-privileges:true\"],\n\t\tenvironment: cliEnv,\n\t\tvolumes: [\n\t\t\t\"${OPENCLAW_CONFIG_DIR:-./openclaw/config}:/home/node/.openclaw\",\n\t\t\t\"${OPENCLAW_WORKSPACE_DIR:-./openclaw/workspace}:/home/node/.openclaw/workspace\",\n\t\t],\n\t\tstdin_open: true,\n\t\ttty: true,\n\t\tinit: true,\n\t\tentrypoint: [\"node\", \"dist/index.js\"],\n\t\tdepends_on: [\"openclaw-gateway\"],\n\t};\n\n\treturn { gatewayService: gateway, cliService: cliService, allVolumes };\n}\n\n// ── Shared Companion Service Builder ────────────────────────────────────────\n\nfunction buildCompanionService(\n\tdef: ResolverOutput[\"services\"][number][\"definition\"],\n\tresolved: ResolverOutput,\n\toptions: ComposeOptions,\n\tallVolumes: Set<string>,\n): { entry: Record<string, unknown>; volumeNames: string[] } {\n\tconst svc: Record<string, unknown> = {};\n\tconst volumeNames: string[] = [];\n\n\t// Git-based services use build: context; image-based services use image:\n\tif (def.gitSource && def.buildContext) {\n\t\tconst subdir = def.gitSource.subdirectory || \".\";\n\t\tconst ctxPath = def.buildContext.context || \".\";\n\t\tconst contextFull = subdir === \".\" ? `./repos/${def.id}/${ctxPath}` : `./repos/${def.id}/${subdir}/${ctxPath}`;\n\t\tconst buildBlock: Record<string, unknown> = { context: contextFull };\n\t\tif (def.buildContext.dockerfile) {\n\t\t\tbuildBlock.dockerfile = def.buildContext.dockerfile;\n\t\t}\n\t\tif (def.buildContext.args && Object.keys(def.buildContext.args).length > 0) {\n\t\t\tbuildBlock.args = def.buildContext.args;\n\t\t}\n\t\tif (def.buildContext.target) {\n\t\t\tbuildBlock.target = def.buildContext.target;\n\t\t}\n\t\tsvc.build = buildBlock;\n\t} else {\n\t\tsvc.image = `${def.image}:${def.imageTag}`;\n\t}\n\n\tif (def.environment.length > 0) {\n\t\tconst env: Record<string, string> = {};\n\t\tfor (const e of def.environment) {\n\t\t\tenv[e.key] = e.secret ? `\\${${e.key}}` : e.defaultValue;\n\t\t}\n\t\tsvc.environment = env;\n\t}\n\n\tconst exposedPorts = def.ports.filter((p) => p.exposed);\n\tif (exposedPorts.length > 0) {\n\t\tconst prefix = def.id.toUpperCase().replace(/-/g, \"_\");\n\t\tsvc.ports = exposedPorts.map((p, i) => {\n\t\t\tconst suffix = exposedPorts.length > 1 ? `_${i}` : \"\";\n\t\t\tlet defaultPort = p.host;\n\n\t\t\t// Override proxy ports if custom ports are specified\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpPort !== undefined &&\n\t\t\t\tp.container === 80\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpPort;\n\t\t\t}\n\t\t\tif (\n\t\t\t\t(def.id === \"caddy\" || def.id === \"traefik\") &&\n\t\t\t\toptions.proxyHttpsPort !== undefined &&\n\t\t\t\tp.container === 443\n\t\t\t) {\n\t\t\t\tdefaultPort = options.proxyHttpsPort;\n\t\t\t}\n\n\t\t\t// Apply global port overrides if specified\n\t\t\tif (options.portOverrides?.[def.id]?.[p.host] !== undefined) {\n\t\t\t\tdefaultPort = options.portOverrides?.[def.id]?.[p.host] || p.host;\n\t\t\t}\n\n\t\t\t// Use _EXTERNAL_PORT to avoid colliding with openclawEnvVars _PORT keys.\n\t\t\t// openclawEnvVars define the container port (e.g. GRAFANA_PORT=3000 for\n\t\t\t// internal Docker networking), while these are host port mappings (e.g. 3150).\n\t\t\t// Without this, GRAFANA_PORT=3000 from .env would override the default\n\t\t\t// 3150 in ${GRAFANA_PORT:-3150}:3000, mapping host port 3000 instead.\n\t\t\treturn `\\${${prefix}_EXTERNAL_PORT${suffix}:-${defaultPort}}:${p.container}`;\n\t\t});\n\t}\n\n\tif (def.volumes.length > 0) {\n\t\tsvc.volumes = def.volumes.map((v) => {\n\t\t\tconst isBindMount =\n\t\t\t\tv.name.startsWith(\"./\") || v.name.startsWith(\"/\") || v.name.startsWith(\"~\");\n\n\t\t\tif (!isBindMount) {\n\t\t\t\tallVolumes.add(v.name);\n\t\t\t\tvolumeNames.push(v.name);\n\t\t\t}\n\t\t\treturn `${v.name}:${v.containerPath}`;\n\t\t});\n\t}\n\n\t// PostgreSQL: mount the init script and pass per-service DB passwords\n\tif (def.id === \"postgresql\") {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./postgres/init-databases.sh:/docker-entrypoint-initdb.d/init-databases.sh:ro\",\n\t\t);\n\n\t\t// Pass per-service database password env vars so the init script can use them\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tif (dbReqs.length > 0) {\n\t\t\tconst env = (svc.environment ?? {}) as Record<string, string>;\n\t\t\tfor (const req of dbReqs) {\n\t\t\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t\t\t}\n\t\t\tsvc.environment = env;\n\t\t}\n\t}\n\n\tif (def.healthcheck) {\n\t\tconst hc: Record<string, unknown> = {\n\t\t\ttest: [\"CMD-SHELL\", def.healthcheck.test],\n\t\t\tinterval: def.healthcheck.interval,\n\t\t\ttimeout: def.healthcheck.timeout,\n\t\t\tretries: def.healthcheck.retries,\n\t\t};\n\t\tif (def.healthcheck.startPeriod) {\n\t\t\thc.start_period = def.healthcheck.startPeriod;\n\t\t}\n\t\tsvc.healthcheck = hc;\n\t}\n\n\tsvc.restart = def.restartPolicy;\n\tsvc.networks = def.networks;\n\n\tif (def.command) svc.command = def.command;\n\tif (def.entrypoint) svc.entrypoint = def.entrypoint;\n\n\t// Labels: merge static definition labels with dynamic Traefik labels\n\tconst mergedLabels: Record<string, string> = {};\n\tif (def.labels) Object.assign(mergedLabels, def.labels);\n\tconst traefikLabels = options.traefikLabels?.get(def.id);\n\tif (traefikLabels) Object.assign(mergedLabels, traefikLabels);\n\tif (Object.keys(mergedLabels).length > 0) svc.labels = mergedLabels;\n\n\t// Traefik: bind-mount static config and Docker socket\n\tif (def.id === \"traefik\" && options.traefikLabels) {\n\t\tif (!svc.volumes) svc.volumes = [];\n\t\t(svc.volumes as string[]).push(\n\t\t\t\"./traefik/traefik.yml:/etc/traefik/traefik.yml:ro\",\n\t\t\t\"/var/run/docker.sock:/var/run/docker.sock:ro\",\n\t\t);\n\t}\n\n\tlet deploy: Record<string, unknown> | undefined;\n\tif (def.deploy) {\n\t\tdeploy = JSON.parse(JSON.stringify(def.deploy)) as Record<string, unknown>;\n\t}\n\tif (options.gpu && def.gpuRequired) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\treservations: {\n\t\t\t\t\t...((resources.reservations as Record<string, unknown>) ?? {}),\n\t\t\t\t\tdevices: [{ driver: \"nvidia\", count: \"all\", capabilities: [\"gpu\"] }],\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\t// Memory limits from estimatedMemoryMB\n\tif (def.minMemoryMB && options.hardened) {\n\t\tconst base = deploy ?? {};\n\t\tconst resources = (base.resources ?? {}) as Record<string, unknown>;\n\t\tconst limits = (resources.limits as Record<string, unknown>) ?? {};\n\t\tdeploy = {\n\t\t\t...base,\n\t\t\tresources: {\n\t\t\t\t...resources,\n\t\t\t\tlimits: {\n\t\t\t\t\t...limits,\n\t\t\t\t\tmemory: `${def.minMemoryMB * 2}m`, // 2x min as safe limit\n\t\t\t\t},\n\t\t\t},\n\t\t};\n\t}\n\tif (deploy) svc.deploy = deploy;\n\n\t// Security hardening (when enabled)\n\tif (options.hardened) {\n\t\tsvc.cap_drop = [\"ALL\"];\n\t\tsvc.security_opt = [\"no-new-privileges:true\"];\n\n\t\t// Services that need specific capabilities\n\t\tconst capAddMap: Record<string, string[]> = {\n\t\t\tcaddy: [\"NET_BIND_SERVICE\"],\n\t\t\ttraefik: [\"NET_BIND_SERVICE\"],\n\t\t\tcrowdsec: [\"NET_BIND_SERVICE\", \"DAC_READ_SEARCH\"],\n\t\t};\n\t\tif (capAddMap[def.id]) {\n\t\t\tsvc.cap_add = capAddMap[def.id];\n\t\t}\n\t}\n\n\t// Merge both dependsOn and requires to ensure proper Docker startup ordering\n\tconst depIds = [...new Set([...def.dependsOn, ...def.requires])].filter((id) =>\n\t\tresolved.services.some((s) => s.definition.id === id),\n\t);\n\tif (depIds.length > 0) {\n\t\tconst dependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const depId of depIds) {\n\t\t\tconst dep = resolved.services.find((s) => s.definition.id === depId);\n\t\t\tdependsOn[depId] = {\n\t\t\t\tcondition: dep?.definition.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t};\n\t\t}\n\t\tsvc.depends_on = dependsOn;\n\t}\n\n\treturn { entry: svc, volumeNames };\n}\n\n// ── PostgreSQL Setup Init Container ─────────────────────────────────────────\n\n/**\n * Builds a one-shot init container that creates databases and users for\n * services that need their own PostgreSQL database. Runs AFTER PostgreSQL\n * is healthy, on every `docker compose up`, and is idempotent.\n *\n * Uses standard PG* environment variables (PGHOST, PGUSER, PGDATABASE,\n * PGPASSWORD) so psql/createuser/createdb automatically connect without\n * needing explicit -h/-U/-d flags — simpler and avoids YAML escaping issues.\n *\n * Returns null when no setup is needed (no PostgreSQL or no DB requirements).\n */\nfunction buildPostgresSetup(resolved: ResolverOutput): Record<string, unknown> | null {\n\tconst hasPostgres = resolved.services.some((s) => s.definition.id === \"postgresql\");\n\tif (!hasPostgres) return null;\n\n\tconst dbReqs = getDbRequirements(resolved);\n\tif (dbReqs.length === 0) return null;\n\n\t// Build a shell script with one command per line.\n\t// Uses $$ to escape $ from Docker Compose's variable substitution —\n\t// Docker Compose converts $$ → $ before passing to the container.\n\t// NO set -e: we handle errors explicitly so one failed service doesn't block others.\n\tconst scriptLines = [\"echo '=== PostgreSQL database setup ==='\", \"FAILED=0\"];\n\n\tfor (const req of dbReqs) {\n\t\t// Each service's setup is wrapped so a failure doesn't block the others.\n\t\t// Uses psql -v ON_ERROR_STOP=0 so SQL errors don't abort psql.\n\t\tscriptLines.push(\n\t\t\t`echo \"Setting up database '${req.dbName}' with user '${req.dbUser}'...\"`,\n\t\t\t// Create user if not exists (pure SQL, no createuser binary quirks)\n\t\t\t`psql -c \"SELECT 1 FROM pg_roles WHERE rolname='${req.dbUser}'\" | grep -q 1 || psql -c \"CREATE ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Always sync the password to match current env\n\t\t\t`psql -c \"ALTER ROLE ${req.dbUser} WITH LOGIN PASSWORD '$$${req.passwordEnvVar}'\"`,\n\t\t\t// Create database if not exists\n\t\t\t`psql -tc \"SELECT 1 FROM pg_database WHERE datname='${req.dbName}'\" | grep -q 1 || psql -c \"CREATE DATABASE ${req.dbName} OWNER ${req.dbUser}\"`,\n\t\t\t// Grant privileges (idempotent)\n\t\t\t`psql -c \"GRANT ALL PRIVILEGES ON DATABASE ${req.dbName} TO ${req.dbUser}\" || FAILED=1`,\n\t\t\t`echo \" Done: ${req.dbName}\"`,\n\t\t);\n\t}\n\n\tscriptLines.push(\"echo '=== All databases ready ==='\", \"exit $$FAILED\");\n\n\t// Standard PG* env vars: psql/createuser/createdb use these automatically\n\tconst env: Record<string, string> = {\n\t\tPGHOST: \"postgresql\",\n\t\tPGUSER: \"${POSTGRES_USER:-openclaw}\",\n\t\tPGDATABASE: \"${POSTGRES_DB:-openclaw}\",\n\t\tPGPASSWORD: \"${POSTGRES_PASSWORD}\",\n\t};\n\tfor (const req of dbReqs) {\n\t\tenv[req.passwordEnvVar] = `\\${${req.passwordEnvVar}}`;\n\t}\n\n\treturn {\n\t\timage: \"postgres:17-alpine\",\n\t\tdepends_on: {\n\t\t\tpostgresql: { condition: \"service_healthy\" },\n\t\t},\n\t\tenvironment: env,\n\t\t// command MUST be a single-element array so the entire script is passed\n\t\t// as ONE argument to `sh -c`. A plain string gets shlex-split by Docker\n\t\t// Compose into multiple args, breaking multi-line scripts.\n\t\tentrypoint: [\"/bin/sh\", \"-c\"],\n\t\tcommand: [scriptLines.join(\"\\n\")],\n\t\trestart: quotedStr(\"no\"),\n\t\tnetworks: [\"openclaw-network\"],\n\t};\n}\n\n// ── Single-File Compose ─────────────────────────────────────────────────────\n\n/**\n * Generates a single Docker Compose YAML string with ALL services.\n * Backward-compatible signature.\n */\nexport function compose(resolved: ResolverOutput, options: ComposeOptions): string {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\n\t// Build depends_on for ALL companion services\n\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\tfor (const { definition: def } of resolved.services) {\n\t\tgatewayDependsOn[def.id] = {\n\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t};\n\t}\n\n\tconst services: Record<string, Record<string, unknown>> = {};\n\tlet allVolumes = new Set<string>();\n\n\tif (!isDirectInstall) {\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\t\tallVolumes = gwVolumes;\n\t\tservices[\"openclaw-gateway\"] = gatewayService;\n\t\t// CLI service added after companions\n\t\t// Determine which services need DB setup so we can redirect their depends_on\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tservices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers, just companion services\n\t\tconst dbReqs = getDbRequirements(resolved);\n\t\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tconst { entry } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\t\tif (deps.postgresql) {\n\t\t\t\t\tdelete deps.postgresql;\n\t\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t\t}\n\t\t\t}\n\t\t\tservices[def.id] = entry;\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tservices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst volumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tvolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\treturn stringify({ services, volumes, networks }, YAML_OPTIONS);\n}\n\n// ── Multi-File Compose ──────────────────────────────────────────────────────\n\ninterface ServiceInfo {\n\tid: string;\n\tcategory: ServiceCategory;\n\tentry: Record<string, unknown>;\n\tvolumeNames: string[];\n}\n\n/**\n * Generates multiple Docker Compose files, splitting services into profile-based\n * override files by category.\n */\nexport function composeMultiFile(resolved: ResolverOutput, options: ComposeOptions): ComposeResult {\n\tconst isDirectInstall = options.openclawInstallMethod === \"direct\";\n\tconst allVolumes = new Set<string>();\n\n\t// Build all companion service entries & classify by category\n\tconst serviceInfos: ServiceInfo[] = [];\n\tconst dbReqs = getDbRequirements(resolved);\n\tconst dbServiceIds = new Set(dbReqs.map((r) => r.serviceId));\n\n\tfor (const { definition: def } of resolved.services) {\n\t\tconst { entry, volumeNames } = buildCompanionService(def, resolved, options, allVolumes);\n\t\t// Redirect DB-dependent services to depend on postgres-setup\n\t\tif (dbServiceIds.has(def.id) && entry.depends_on) {\n\t\t\tconst deps = entry.depends_on as Record<string, { condition: string }>;\n\t\t\tif (deps.postgresql) {\n\t\t\t\tdelete deps.postgresql;\n\t\t\t\tdeps[\"postgres-setup\"] = { condition: \"service_completed_successfully\" };\n\t\t\t}\n\t\t}\n\t\tserviceInfos.push({ id: def.id, category: def.category, entry, volumeNames });\n\t}\n\n\t// Partition services into base vs. profile files\n\tconst baseServiceIds = new Set<string>();\n\tconst profileFileMap: Record<string, { profile: string; services: ServiceInfo[] }> = {};\n\n\tfor (const info of serviceInfos) {\n\t\tconst mapping = CATEGORY_PROFILE_MAP[info.category];\n\t\tif (mapping) {\n\t\t\tif (!profileFileMap[mapping.file]) {\n\t\t\t\tprofileFileMap[mapping.file] = { profile: mapping.profile, services: [] };\n\t\t\t}\n\t\t\tprofileFileMap[mapping.file]!.services.push(info);\n\t\t} else {\n\t\t\tbaseServiceIds.add(info.id);\n\t\t}\n\t}\n\n\tconst baseServices: Record<string, Record<string, unknown>> = {};\n\n\tif (!isDirectInstall) {\n\t\t// Gateway depends_on (only base services)\n\t\tconst gatewayDependsOn: Record<string, { condition: string }> = {};\n\t\tfor (const { definition: def } of resolved.services) {\n\t\t\tif (baseServiceIds.has(def.id)) {\n\t\t\t\tgatewayDependsOn[def.id] = {\n\t\t\t\t\tcondition: def.healthcheck ? \"service_healthy\" : \"service_started\",\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\tconst {\n\t\t\tgatewayService,\n\t\t\tcliService,\n\t\t\tallVolumes: gwVolumes,\n\t\t} = buildGatewayServices(resolved, options, gatewayDependsOn);\n\n\t\t// Merge gateway volumes into allVolumes\n\t\tfor (const v of gwVolumes) allVolumes.add(v);\n\n\t\tbaseServices[\"openclaw-gateway\"] = gatewayService;\n\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\t// Add postgres-setup init container if needed\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\n\t\tbaseServices[\"openclaw-cli\"] = cliService;\n\t} else {\n\t\t// Direct install: no gateway/CLI containers\n\t\tfor (const info of serviceInfos) {\n\t\t\tif (baseServiceIds.has(info.id)) {\n\t\t\t\tbaseServices[info.id] = info.entry;\n\t\t\t}\n\t\t}\n\n\t\tconst pgSetup = buildPostgresSetup(resolved);\n\t\tif (pgSetup) {\n\t\t\tbaseServices[\"postgres-setup\"] = pgSetup;\n\t\t}\n\t}\n\n\tconst sortedAllVolumes: Record<string, null> = {};\n\tfor (const v of [...allVolumes].sort()) {\n\t\tsortedAllVolumes[v] = null;\n\t}\n\n\tconst networks = { \"openclaw-network\": { driver: \"bridge\" } };\n\n\tconst files: Record<string, string> = {};\n\tfiles[\"docker-compose.yml\"] = stringify(\n\t\t{ services: baseServices, volumes: sortedAllVolumes, networks },\n\t\tYAML_OPTIONS,\n\t);\n\n\t// Profile override files\n\tconst usedProfiles = new Set<string>();\n\n\tfor (const [fileName, { profile, services }] of Object.entries(profileFileMap)) {\n\t\tusedProfiles.add(profile);\n\n\t\tconst profileServices: Record<string, Record<string, unknown>> = {};\n\t\tconst profileVolumes = new Set<string>();\n\n\t\tfor (const info of services) {\n\t\t\tprofileServices[info.id] = { ...info.entry, profiles: [profile] };\n\t\t\tfor (const vName of info.volumeNames) {\n\t\t\t\tprofileVolumes.add(vName);\n\t\t\t}\n\t\t}\n\n\t\tconst fileContent: Record<string, unknown> = { services: profileServices };\n\n\t\tif (profileVolumes.size > 0) {\n\t\t\tconst sortedProfileVolumes: Record<string, null> = {};\n\t\t\tfor (const v of [...profileVolumes].sort()) {\n\t\t\t\tsortedProfileVolumes[v] = null;\n\t\t\t}\n\t\t\tfileContent.volumes = sortedProfileVolumes;\n\t\t}\n\n\t\tfiles[fileName] = stringify(fileContent, YAML_OPTIONS);\n\t}\n\n\treturn {\n\t\tfiles,\n\t\tmainFile: \"docker-compose.yml\",\n\t\tprofiles: [...usedProfiles].sort(),\n\t};\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- const require_vi_2VT5v0um = require("./vi.2VT5v0um-CRqXre87.cjs");
1
+ const require_vi_2VT5v0um = require("./vi.2VT5v0um-iVBt6Fyq.cjs");
2
2
  const require_resolver = require("./resolver.cjs");
3
3
  const require_composer = require("./composer.cjs");
4
4
  //#region src/composer.snapshot.test.ts
@@ -1,4 +1,4 @@
1
- import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-DvC3SVNc.mjs";
1
+ import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-C_jmO7m2.mjs";
2
2
  import { resolve } from "./resolver.mjs";
3
3
  import { composeMultiFile } from "./composer.mjs";
4
4
  //#region src/composer.snapshot.test.ts
@@ -1,5 +1,5 @@
1
1
  require("./skills-BlzpHmpH.cjs");
2
- const require_vi_2VT5v0um = require("./vi.2VT5v0um-CRqXre87.cjs");
2
+ const require_vi_2VT5v0um = require("./vi.2VT5v0um-iVBt6Fyq.cjs");
3
3
  const require_resolver = require("./resolver.cjs");
4
4
  const require_composer = require("./composer.cjs");
5
5
  let yaml = require("yaml");
@@ -27,7 +27,8 @@ require_vi_2VT5v0um.describe("compose", () => {
27
27
  require_vi_2VT5v0um.globalExpect(parsed.networks).toHaveProperty("openclaw-network");
28
28
  require_vi_2VT5v0um.globalExpect(parsed.networks["openclaw-network"].driver).toBe("bridge");
29
29
  require_vi_2VT5v0um.globalExpect(parsed.services).toHaveProperty("openclaw-cli");
30
- require_vi_2VT5v0um.globalExpect(parsed.services["openclaw-gateway"]).not.toHaveProperty("depends_on");
30
+ require_vi_2VT5v0um.globalExpect(parsed.services).toHaveProperty("convex");
31
+ require_vi_2VT5v0um.globalExpect(parsed.services).toHaveProperty("mission-control");
31
32
  require_vi_2VT5v0um.globalExpect(parsed.services["openclaw-gateway"].restart).toBe("unless-stopped");
32
33
  });
33
34
  require_vi_2VT5v0um.it("generates Redis companion with proper gateway depends_on", () => {