@better-openclaw/core 1.0.23 → 1.0.25

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 (335) hide show
  1. package/dist/addon-stack.cjs +673 -0
  2. package/dist/addon-stack.cjs.map +1 -0
  3. package/dist/addon-stack.d.cts +23 -0
  4. package/dist/addon-stack.d.cts.map +1 -0
  5. package/dist/addon-stack.d.mts +23 -0
  6. package/dist/addon-stack.d.mts.map +1 -0
  7. package/dist/addon-stack.mjs +671 -0
  8. package/dist/addon-stack.mjs.map +1 -0
  9. package/dist/addon-stack.test.cjs +349 -0
  10. package/dist/addon-stack.test.cjs.map +1 -0
  11. package/dist/addon-stack.test.d.cts +1 -0
  12. package/dist/addon-stack.test.d.mts +1 -0
  13. package/dist/addon-stack.test.mjs +349 -0
  14. package/dist/addon-stack.test.mjs.map +1 -0
  15. package/dist/bare-metal-partition.test.cjs +20 -21
  16. package/dist/bare-metal-partition.test.cjs.map +1 -1
  17. package/dist/bare-metal-partition.test.mjs +4 -5
  18. package/dist/bare-metal-partition.test.mjs.map +1 -1
  19. package/dist/composer.cjs +17 -1
  20. package/dist/composer.cjs.map +1 -1
  21. package/dist/composer.d.cts +24 -1
  22. package/dist/composer.d.cts.map +1 -1
  23. package/dist/composer.d.mts +24 -1
  24. package/dist/composer.d.mts.map +1 -1
  25. package/dist/composer.mjs +14 -2
  26. package/dist/composer.mjs.map +1 -1
  27. package/dist/composer.snapshot.test.cjs +20 -20
  28. package/dist/composer.snapshot.test.cjs.map +1 -1
  29. package/dist/composer.snapshot.test.mjs +2 -2
  30. package/dist/composer.test.cjs +53 -52
  31. package/dist/composer.test.cjs.map +1 -1
  32. package/dist/composer.test.mjs +4 -3
  33. package/dist/composer.test.mjs.map +1 -1
  34. package/dist/deployers/strip-host-ports.test.cjs +26 -26
  35. package/dist/deployers/strip-host-ports.test.cjs.map +1 -1
  36. package/dist/deployers/strip-host-ports.test.mjs +1 -1
  37. package/dist/generate.cjs +8 -4
  38. package/dist/generate.cjs.map +1 -1
  39. package/dist/generate.d.cts.map +1 -1
  40. package/dist/generate.d.mts.map +1 -1
  41. package/dist/generate.mjs +9 -5
  42. package/dist/generate.mjs.map +1 -1
  43. package/dist/generate.test.cjs +55 -55
  44. package/dist/generate.test.cjs.map +1 -1
  45. package/dist/generate.test.mjs +2 -2
  46. package/dist/generate.test.mjs.map +1 -1
  47. package/dist/generators/bare-metal-install.test.cjs +18 -18
  48. package/dist/generators/bare-metal-install.test.cjs.map +1 -1
  49. package/dist/generators/bare-metal-install.test.mjs +1 -1
  50. package/dist/generators/caddy.test.cjs +13 -13
  51. package/dist/generators/caddy.test.cjs.map +1 -1
  52. package/dist/generators/caddy.test.mjs +1 -1
  53. package/dist/generators/clone-repos.cjs +140 -0
  54. package/dist/generators/clone-repos.cjs.map +1 -0
  55. package/dist/generators/clone-repos.d.cts +11 -0
  56. package/dist/generators/clone-repos.d.cts.map +1 -0
  57. package/dist/generators/clone-repos.d.mts +11 -0
  58. package/dist/generators/clone-repos.d.mts.map +1 -0
  59. package/dist/generators/clone-repos.mjs +139 -0
  60. package/dist/generators/clone-repos.mjs.map +1 -0
  61. package/dist/generators/clone-repos.test.cjs +140 -0
  62. package/dist/generators/clone-repos.test.cjs.map +1 -0
  63. package/dist/generators/clone-repos.test.d.cts +1 -0
  64. package/dist/generators/clone-repos.test.d.mts +1 -0
  65. package/dist/generators/clone-repos.test.mjs +141 -0
  66. package/dist/generators/clone-repos.test.mjs.map +1 -0
  67. package/dist/generators/env.test.cjs +17 -17
  68. package/dist/generators/env.test.cjs.map +1 -1
  69. package/dist/generators/env.test.mjs +1 -1
  70. package/dist/generators/health-check.test.cjs +39 -39
  71. package/dist/generators/health-check.test.cjs.map +1 -1
  72. package/dist/generators/health-check.test.mjs +1 -1
  73. package/dist/generators/postgres-init.cjs +20 -0
  74. package/dist/generators/postgres-init.cjs.map +1 -1
  75. package/dist/generators/postgres-init.d.cts.map +1 -1
  76. package/dist/generators/postgres-init.d.mts.map +1 -1
  77. package/dist/generators/postgres-init.mjs +20 -0
  78. package/dist/generators/postgres-init.mjs.map +1 -1
  79. package/dist/generators/scripts.cjs +332 -3
  80. package/dist/generators/scripts.cjs.map +1 -1
  81. package/dist/generators/scripts.d.cts +3 -1
  82. package/dist/generators/scripts.d.cts.map +1 -1
  83. package/dist/generators/scripts.d.mts +3 -1
  84. package/dist/generators/scripts.d.mts.map +1 -1
  85. package/dist/generators/scripts.mjs +332 -3
  86. package/dist/generators/scripts.mjs.map +1 -1
  87. package/dist/generators/scripts.test.cjs +57 -23
  88. package/dist/generators/scripts.test.cjs.map +1 -1
  89. package/dist/generators/scripts.test.mjs +39 -5
  90. package/dist/generators/scripts.test.mjs.map +1 -1
  91. package/dist/generators/stack-manifest.cjs +1 -0
  92. package/dist/generators/stack-manifest.cjs.map +1 -1
  93. package/dist/generators/stack-manifest.d.cts +3 -2
  94. package/dist/generators/stack-manifest.d.cts.map +1 -1
  95. package/dist/generators/stack-manifest.d.mts +3 -2
  96. package/dist/generators/stack-manifest.d.mts.map +1 -1
  97. package/dist/generators/stack-manifest.mjs +1 -0
  98. package/dist/generators/stack-manifest.mjs.map +1 -1
  99. package/dist/generators/traefik.test.cjs +32 -32
  100. package/dist/generators/traefik.test.cjs.map +1 -1
  101. package/dist/generators/traefik.test.mjs +1 -1
  102. package/dist/index.cjs +28 -5
  103. package/dist/index.d.cts +7 -4
  104. package/dist/index.d.mts +7 -4
  105. package/dist/index.mjs +10 -7
  106. package/dist/migrations.test.cjs +16 -16
  107. package/dist/migrations.test.cjs.map +1 -1
  108. package/dist/migrations.test.mjs +1 -1
  109. package/dist/presets/registry.cjs.map +1 -1
  110. package/dist/presets/registry.d.cts.map +1 -1
  111. package/dist/presets/registry.d.mts.map +1 -1
  112. package/dist/presets/registry.mjs.map +1 -1
  113. package/dist/presets/registry.test.cjs +14 -14
  114. package/dist/presets/registry.test.cjs.map +1 -1
  115. package/dist/presets/registry.test.mjs +1 -1
  116. package/dist/resolver.cjs +8 -0
  117. package/dist/resolver.cjs.map +1 -1
  118. package/dist/resolver.mjs +9 -1
  119. package/dist/resolver.mjs.map +1 -1
  120. package/dist/resolver.test.cjs +125 -90
  121. package/dist/resolver.test.cjs.map +1 -1
  122. package/dist/resolver.test.mjs +47 -12
  123. package/dist/resolver.test.mjs.map +1 -1
  124. package/dist/{schema-B4c64P8N.d.cts → schema-CKBRu-Rt.d.cts} +355 -8
  125. package/dist/schema-CKBRu-Rt.d.cts.map +1 -0
  126. package/dist/{schema-CXNhYci1.d.mts → schema-Dn-_Jpb6.d.mts} +355 -8
  127. package/dist/schema-Dn-_Jpb6.d.mts.map +1 -0
  128. package/dist/schema.cjs +160 -5
  129. package/dist/schema.cjs.map +1 -1
  130. package/dist/schema.d.cts +2 -2
  131. package/dist/schema.d.mts +2 -2
  132. package/dist/schema.mjs +150 -6
  133. package/dist/schema.mjs.map +1 -1
  134. package/dist/schema.test.cjs +86 -86
  135. package/dist/schema.test.cjs.map +1 -1
  136. package/dist/schema.test.mjs +1 -1
  137. package/dist/services/definitions/apptension-saas.cjs +87 -0
  138. package/dist/services/definitions/apptension-saas.cjs.map +1 -0
  139. package/dist/services/definitions/apptension-saas.d.cts +7 -0
  140. package/dist/services/definitions/apptension-saas.d.cts.map +1 -0
  141. package/dist/services/definitions/apptension-saas.d.mts +7 -0
  142. package/dist/services/definitions/apptension-saas.d.mts.map +1 -0
  143. package/dist/services/definitions/apptension-saas.mjs +86 -0
  144. package/dist/services/definitions/apptension-saas.mjs.map +1 -0
  145. package/dist/services/definitions/boxyhq-saas.cjs +88 -0
  146. package/dist/services/definitions/boxyhq-saas.cjs.map +1 -0
  147. package/dist/services/definitions/boxyhq-saas.d.cts +7 -0
  148. package/dist/services/definitions/boxyhq-saas.d.cts.map +1 -0
  149. package/dist/services/definitions/boxyhq-saas.d.mts +7 -0
  150. package/dist/services/definitions/boxyhq-saas.d.mts.map +1 -0
  151. package/dist/services/definitions/boxyhq-saas.mjs +87 -0
  152. package/dist/services/definitions/boxyhq-saas.mjs.map +1 -0
  153. package/dist/services/definitions/browserless.cjs +4 -1
  154. package/dist/services/definitions/browserless.cjs.map +1 -1
  155. package/dist/services/definitions/browserless.mjs +4 -1
  156. package/dist/services/definitions/browserless.mjs.map +1 -1
  157. package/dist/services/definitions/cmsaas-starter.cjs +86 -0
  158. package/dist/services/definitions/cmsaas-starter.cjs.map +1 -0
  159. package/dist/services/definitions/cmsaas-starter.d.cts +7 -0
  160. package/dist/services/definitions/cmsaas-starter.d.cts.map +1 -0
  161. package/dist/services/definitions/cmsaas-starter.d.mts +7 -0
  162. package/dist/services/definitions/cmsaas-starter.d.mts.map +1 -0
  163. package/dist/services/definitions/cmsaas-starter.mjs +85 -0
  164. package/dist/services/definitions/cmsaas-starter.mjs.map +1 -0
  165. package/dist/services/definitions/convex.cjs +43 -1
  166. package/dist/services/definitions/convex.cjs.map +1 -1
  167. package/dist/services/definitions/convex.mjs +43 -1
  168. package/dist/services/definitions/convex.mjs.map +1 -1
  169. package/dist/services/definitions/grafana.cjs +11 -1
  170. package/dist/services/definitions/grafana.cjs.map +1 -1
  171. package/dist/services/definitions/grafana.mjs +11 -1
  172. package/dist/services/definitions/grafana.mjs.map +1 -1
  173. package/dist/services/definitions/index.cjs +51 -36
  174. package/dist/services/definitions/index.cjs.map +1 -1
  175. package/dist/services/definitions/index.d.cts +30 -25
  176. package/dist/services/definitions/index.d.cts.map +1 -1
  177. package/dist/services/definitions/index.d.mts +30 -25
  178. package/dist/services/definitions/index.d.mts.map +1 -1
  179. package/dist/services/definitions/index.mjs +47 -37
  180. package/dist/services/definitions/index.mjs.map +1 -1
  181. package/dist/services/definitions/ixartz-saas.cjs +88 -0
  182. package/dist/services/definitions/ixartz-saas.cjs.map +1 -0
  183. package/dist/services/definitions/ixartz-saas.d.cts +7 -0
  184. package/dist/services/definitions/ixartz-saas.d.cts.map +1 -0
  185. package/dist/services/definitions/ixartz-saas.d.mts +7 -0
  186. package/dist/services/definitions/ixartz-saas.d.mts.map +1 -0
  187. package/dist/services/definitions/ixartz-saas.mjs +87 -0
  188. package/dist/services/definitions/ixartz-saas.mjs.map +1 -0
  189. package/dist/services/definitions/meilisearch.cjs +11 -1
  190. package/dist/services/definitions/meilisearch.cjs.map +1 -1
  191. package/dist/services/definitions/meilisearch.mjs +11 -1
  192. package/dist/services/definitions/meilisearch.mjs.map +1 -1
  193. package/dist/services/definitions/minio.cjs +3 -1
  194. package/dist/services/definitions/minio.cjs.map +1 -1
  195. package/dist/services/definitions/minio.mjs +3 -1
  196. package/dist/services/definitions/minio.mjs.map +1 -1
  197. package/dist/services/definitions/mission-control.cjs +16 -2
  198. package/dist/services/definitions/mission-control.cjs.map +1 -1
  199. package/dist/services/definitions/mission-control.mjs +16 -2
  200. package/dist/services/definitions/mission-control.mjs.map +1 -1
  201. package/dist/services/definitions/n8n.cjs +11 -1
  202. package/dist/services/definitions/n8n.cjs.map +1 -1
  203. package/dist/services/definitions/n8n.mjs +11 -1
  204. package/dist/services/definitions/n8n.mjs.map +1 -1
  205. package/dist/services/definitions/ollama.cjs +3 -1
  206. package/dist/services/definitions/ollama.cjs.map +1 -1
  207. package/dist/services/definitions/ollama.mjs +3 -1
  208. package/dist/services/definitions/ollama.mjs.map +1 -1
  209. package/dist/services/definitions/open-saas.cjs +81 -0
  210. package/dist/services/definitions/open-saas.cjs.map +1 -0
  211. package/dist/services/definitions/open-saas.d.cts +7 -0
  212. package/dist/services/definitions/open-saas.d.cts.map +1 -0
  213. package/dist/services/definitions/open-saas.d.mts +7 -0
  214. package/dist/services/definitions/open-saas.d.mts.map +1 -0
  215. package/dist/services/definitions/open-saas.mjs +80 -0
  216. package/dist/services/definitions/open-saas.mjs.map +1 -0
  217. package/dist/services/definitions/qdrant.cjs +3 -1
  218. package/dist/services/definitions/qdrant.cjs.map +1 -1
  219. package/dist/services/definitions/qdrant.mjs +3 -1
  220. package/dist/services/definitions/qdrant.mjs.map +1 -1
  221. package/dist/services/definitions/searxng.cjs +8 -1
  222. package/dist/services/definitions/searxng.cjs.map +1 -1
  223. package/dist/services/definitions/searxng.mjs +8 -1
  224. package/dist/services/definitions/searxng.mjs.map +1 -1
  225. package/dist/services/definitions/uptime-kuma.cjs +8 -1
  226. package/dist/services/definitions/uptime-kuma.cjs.map +1 -1
  227. package/dist/services/definitions/uptime-kuma.mjs +8 -1
  228. package/dist/services/definitions/uptime-kuma.mjs.map +1 -1
  229. package/dist/services/registry.cjs +3 -0
  230. package/dist/services/registry.cjs.map +1 -1
  231. package/dist/services/registry.d.cts.map +1 -1
  232. package/dist/services/registry.d.mts.map +1 -1
  233. package/dist/services/registry.mjs +3 -0
  234. package/dist/services/registry.mjs.map +1 -1
  235. package/dist/services/registry.test.cjs +40 -33
  236. package/dist/services/registry.test.cjs.map +1 -1
  237. package/dist/services/registry.test.mjs +8 -1
  238. package/dist/services/registry.test.mjs.map +1 -1
  239. package/dist/{skill-manifest-BVUXU0__.mjs → skill-manifest-6XhrhWsG.mjs} +49 -1
  240. package/dist/{skill-manifest--IgY9REK.cjs.map → skill-manifest-6XhrhWsG.mjs.map} +1 -1
  241. package/dist/{skill-manifest--IgY9REK.cjs → skill-manifest-B8znSsym.cjs} +49 -1
  242. package/dist/{skill-manifest-BVUXU0__.mjs.map → skill-manifest-B8znSsym.cjs.map} +1 -1
  243. package/dist/skills/registry.cjs +3 -3
  244. package/dist/skills/registry.cjs.map +1 -1
  245. package/dist/skills/registry.mjs +3 -3
  246. package/dist/skills/registry.mjs.map +1 -1
  247. package/dist/skills/skill-manifest.cjs +1 -1
  248. package/dist/skills/skill-manifest.mjs +1 -1
  249. package/dist/{vi.2VT5v0um-DvC3SVNc.mjs → test.CTcmp4Su-ClCHJ3FA.mjs} +6793 -6403
  250. package/dist/test.CTcmp4Su-ClCHJ3FA.mjs.map +1 -0
  251. package/dist/{vi.2VT5v0um-CRqXre87.cjs → test.CTcmp4Su-DlzTarwH.cjs} +6793 -6403
  252. package/dist/test.CTcmp4Su-DlzTarwH.cjs.map +1 -0
  253. package/dist/track-analytics.cjs +50 -0
  254. package/dist/track-analytics.cjs.map +1 -0
  255. package/dist/track-analytics.d.cts +34 -0
  256. package/dist/track-analytics.d.cts.map +1 -0
  257. package/dist/track-analytics.d.mts +34 -0
  258. package/dist/track-analytics.d.mts.map +1 -0
  259. package/dist/track-analytics.mjs +48 -0
  260. package/dist/track-analytics.mjs.map +1 -0
  261. package/dist/track-analytics.test.cjs +91 -0
  262. package/dist/track-analytics.test.cjs.map +1 -0
  263. package/dist/track-analytics.test.d.cts +1 -0
  264. package/dist/track-analytics.test.d.mts +1 -0
  265. package/dist/track-analytics.test.mjs +92 -0
  266. package/dist/track-analytics.test.mjs.map +1 -0
  267. package/dist/types.cjs +7 -0
  268. package/dist/types.cjs.map +1 -1
  269. package/dist/types.d.cts +12 -2
  270. package/dist/types.d.cts.map +1 -1
  271. package/dist/types.d.mts +12 -2
  272. package/dist/types.d.mts.map +1 -1
  273. package/dist/types.mjs +7 -0
  274. package/dist/types.mjs.map +1 -1
  275. package/dist/validator.test.cjs +15 -15
  276. package/dist/validator.test.cjs.map +1 -1
  277. package/dist/validator.test.mjs +2 -2
  278. package/dist/version-manager.cjs +1 -1
  279. package/dist/version-manager.cjs.map +1 -1
  280. package/dist/version-manager.mjs +1 -1
  281. package/dist/version-manager.mjs.map +1 -1
  282. package/dist/version-manager.test.cjs +40 -38
  283. package/dist/version-manager.test.cjs.map +1 -1
  284. package/dist/version-manager.test.mjs +7 -5
  285. package/dist/version-manager.test.mjs.map +1 -1
  286. package/package.json +4 -4
  287. package/src/__snapshots__/composer.snapshot.test.ts.snap +160 -0
  288. package/src/addon-stack.test.ts +490 -0
  289. package/src/addon-stack.ts +998 -0
  290. package/src/bare-metal-partition.test.ts +4 -3
  291. package/src/composer.test.ts +4 -2
  292. package/src/composer.ts +24 -5
  293. package/src/generate.test.ts +2 -1
  294. package/src/generate.ts +10 -1
  295. package/src/generators/clone-repos.test.ts +154 -0
  296. package/src/generators/clone-repos.ts +159 -0
  297. package/src/generators/postgres-init.ts +17 -0
  298. package/src/generators/scripts.test.ts +52 -4
  299. package/src/generators/scripts.ts +351 -3
  300. package/src/generators/stack-manifest.ts +4 -2
  301. package/src/index.ts +28 -2
  302. package/src/presets/registry.ts +241 -329
  303. package/src/resolver.test.ts +53 -15
  304. package/src/resolver.ts +13 -1
  305. package/src/schema.ts +216 -4
  306. package/src/services/definitions/apptension-saas.ts +84 -0
  307. package/src/services/definitions/boxyhq-saas.ts +84 -0
  308. package/src/services/definitions/browserless.ts +3 -0
  309. package/src/services/definitions/cmsaas-starter.ts +84 -0
  310. package/src/services/definitions/convex.ts +31 -0
  311. package/src/services/definitions/grafana.ts +9 -0
  312. package/src/services/definitions/index.ts +90 -70
  313. package/src/services/definitions/ixartz-saas.ts +84 -0
  314. package/src/services/definitions/meilisearch.ts +9 -0
  315. package/src/services/definitions/minio.ts +2 -0
  316. package/src/services/definitions/mission-control.ts +19 -2
  317. package/src/services/definitions/n8n.ts +9 -0
  318. package/src/services/definitions/ollama.ts +2 -0
  319. package/src/services/definitions/open-saas.ts +79 -0
  320. package/src/services/definitions/qdrant.ts +2 -0
  321. package/src/services/definitions/searxng.ts +3 -0
  322. package/src/services/definitions/uptime-kuma.ts +3 -0
  323. package/src/services/registry.test.ts +8 -0
  324. package/src/services/registry.ts +7 -0
  325. package/src/skills/manifest.json +64 -0
  326. package/src/skills/registry.ts +3 -3
  327. package/src/track-analytics.test.ts +82 -0
  328. package/src/track-analytics.ts +76 -0
  329. package/src/types.ts +29 -0
  330. package/src/version-manager.test.ts +10 -5
  331. package/src/version-manager.ts +1 -1
  332. package/dist/schema-B4c64P8N.d.cts.map +0 -1
  333. package/dist/schema-CXNhYci1.d.mts.map +0 -1
  334. package/dist/vi.2VT5v0um-CRqXre87.cjs.map +0 -1
  335. package/dist/vi.2VT5v0um-DvC3SVNc.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"composer.snapshot.test.cjs","names":["composeMultiFile","resolve","describe"],"sources":["../src/composer.snapshot.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { composeMultiFile } from \"./composer.js\";\nimport { resolve } from \"./resolver.js\";\n\nfunction generateForPreset(services: string[], skillPacks: string[] = []) {\n\tconst resolved = resolve({ services, skillPacks });\n\treturn composeMultiFile(resolved, {\n\t\tprojectName: \"test-stack\",\n\t\tproxy: \"none\",\n\t\tgpu: false,\n\t\tplatform: \"linux/amd64\",\n\t\tdeployment: \"local\",\n\t\topenclawVersion: \"latest\",\n\t});\n}\n\ndescribe(\"compose snapshot tests\", () => {\n\tit(\"minimal preset (redis only)\", () => {\n\t\tconst result = generateForPreset([\"redis\"]);\n\t\texpect(result.files[\"docker-compose.yml\"]).toMatchSnapshot();\n\t});\n\n\tit(\"creator preset (ffmpeg + remotion + minio + redis)\", () => {\n\t\tconst result = generateForPreset([\"ffmpeg\", \"remotion\", \"minio\", \"redis\"], [\"video-creator\"]);\n\t\texpect(result.files[\"docker-compose.yml\"]).toMatchSnapshot();\n\t\tif (result.files[\"docker-compose.media.yml\"]) {\n\t\t\texpect(result.files[\"docker-compose.media.yml\"]).toMatchSnapshot();\n\t\t}\n\t});\n\n\tit(\"researcher preset (qdrant + searxng + browserless + redis)\", () => {\n\t\tconst result = generateForPreset(\n\t\t\t[\"qdrant\", \"searxng\", \"browserless\", \"redis\"],\n\t\t\t[\"research-agent\"],\n\t\t);\n\t\texpect(result.files[\"docker-compose.yml\"]).toMatchSnapshot();\n\t});\n\n\tit(\"devops preset (n8n + postgresql + redis + monitoring)\", () => {\n\t\tconst result = generateForPreset(\n\t\t\t[\"n8n\", \"postgresql\", \"redis\", \"uptime-kuma\", \"grafana\", \"prometheus\"],\n\t\t\t[\"dev-ops\"],\n\t\t);\n\t\texpect(result.files[\"docker-compose.yml\"]).toMatchSnapshot();\n\t\tif (result.files[\"docker-compose.monitoring.yml\"]) {\n\t\t\texpect(result.files[\"docker-compose.monitoring.yml\"]).toMatchSnapshot();\n\t\t}\n\t});\n\n\tit(\"full preset (many services)\", () => {\n\t\tconst result = generateForPreset([\n\t\t\t\"redis\",\n\t\t\t\"postgresql\",\n\t\t\t\"qdrant\",\n\t\t\t\"n8n\",\n\t\t\t\"ffmpeg\",\n\t\t\t\"remotion\",\n\t\t\t\"minio\",\n\t\t\t\"caddy\",\n\t\t\t\"browserless\",\n\t\t\t\"searxng\",\n\t\t\t\"meilisearch\",\n\t\t\t\"uptime-kuma\",\n\t\t\t\"grafana\",\n\t\t\t\"prometheus\",\n\t\t\t\"ollama\",\n\t\t\t\"whisper\",\n\t\t\t\"gotify\",\n\t\t]);\n\t\texpect(result.files[\"docker-compose.yml\"]).toMatchSnapshot();\n\t\texpect(result.profiles.length).toBeGreaterThan(0);\n\t\t// Verify multiple compose files were created\n\t\texpect(Object.keys(result.files).length).toBeGreaterThan(1);\n\t});\n\n\tit(\"multi-file output has correct profile assignments\", () => {\n\t\tconst result = generateForPreset([\"redis\", \"ollama\", \"open-webui\", \"grafana\", \"prometheus\"]);\n\t\t// AI services should be in the ai profile file\n\t\tif (result.files[\"docker-compose.ai.yml\"]) {\n\t\t\texpect(result.files[\"docker-compose.ai.yml\"]).toContain(\"profiles:\");\n\t\t}\n\t\t// Monitoring should be in the monitoring profile file\n\t\tif (result.files[\"docker-compose.monitoring.yml\"]) {\n\t\t\texpect(result.files[\"docker-compose.monitoring.yml\"]).toContain(\"profiles:\");\n\t\t}\n\t});\n});\n"],"mappings":";;;;AAIA,SAAS,kBAAkB,UAAoB,aAAuB,EAAE,EAAE;AAEzE,QAAOA,iBAAAA,iBADUC,iBAAAA,QAAQ;EAAE;EAAU;EAAY,CAAC,EAChB;EACjC,aAAa;EACb,OAAO;EACP,KAAK;EACL,UAAU;EACV,YAAY;EACZ,iBAAiB;EACjB,CAAC;;AAGHC,oBAAAA,SAAS,gCAAgC;AACxC,qBAAA,GAAG,qCAAqC;AAEvC,sBAAA,aADe,kBAAkB,CAAC,QAAQ,CAAC,CAC7B,MAAM,sBAAsB,CAAC,iBAAiB;GAC3D;AAEF,qBAAA,GAAG,4DAA4D;EAC9D,MAAM,SAAS,kBAAkB;GAAC;GAAU;GAAY;GAAS;GAAQ,EAAE,CAAC,gBAAgB,CAAC;AAC7F,sBAAA,aAAO,OAAO,MAAM,sBAAsB,CAAC,iBAAiB;AAC5D,MAAI,OAAO,MAAM,4BAChB,qBAAA,aAAO,OAAO,MAAM,4BAA4B,CAAC,iBAAiB;GAElE;AAEF,qBAAA,GAAG,oEAAoE;AAKtE,sBAAA,aAJe,kBACd;GAAC;GAAU;GAAW;GAAe;GAAQ,EAC7C,CAAC,iBAAiB,CAClB,CACa,MAAM,sBAAsB,CAAC,iBAAiB;GAC3D;AAEF,qBAAA,GAAG,+DAA+D;EACjE,MAAM,SAAS,kBACd;GAAC;GAAO;GAAc;GAAS;GAAe;GAAW;GAAa,EACtE,CAAC,UAAU,CACX;AACD,sBAAA,aAAO,OAAO,MAAM,sBAAsB,CAAC,iBAAiB;AAC5D,MAAI,OAAO,MAAM,iCAChB,qBAAA,aAAO,OAAO,MAAM,iCAAiC,CAAC,iBAAiB;GAEvE;AAEF,qBAAA,GAAG,qCAAqC;EACvC,MAAM,SAAS,kBAAkB;GAChC;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,CAAC;AACF,sBAAA,aAAO,OAAO,MAAM,sBAAsB,CAAC,iBAAiB;AAC5D,sBAAA,aAAO,OAAO,SAAS,OAAO,CAAC,gBAAgB,EAAE;AAEjD,sBAAA,aAAO,OAAO,KAAK,OAAO,MAAM,CAAC,OAAO,CAAC,gBAAgB,EAAE;GAC1D;AAEF,qBAAA,GAAG,2DAA2D;EAC7D,MAAM,SAAS,kBAAkB;GAAC;GAAS;GAAU;GAAc;GAAW;GAAa,CAAC;AAE5F,MAAI,OAAO,MAAM,yBAChB,qBAAA,aAAO,OAAO,MAAM,yBAAyB,CAAC,UAAU,YAAY;AAGrE,MAAI,OAAO,MAAM,iCAChB,qBAAA,aAAO,OAAO,MAAM,iCAAiC,CAAC,UAAU,YAAY;GAE5E;EACD"}
1
+ {"version":3,"file":"composer.snapshot.test.cjs","names":["composeMultiFile","resolve","describe"],"sources":["../src/composer.snapshot.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { composeMultiFile } from \"./composer.js\";\nimport { resolve } from \"./resolver.js\";\n\nfunction generateForPreset(services: string[], skillPacks: string[] = []) {\n\tconst resolved = resolve({ services, skillPacks });\n\treturn composeMultiFile(resolved, {\n\t\tprojectName: \"test-stack\",\n\t\tproxy: \"none\",\n\t\tgpu: false,\n\t\tplatform: \"linux/amd64\",\n\t\tdeployment: \"local\",\n\t\topenclawVersion: \"latest\",\n\t});\n}\n\ndescribe(\"compose snapshot tests\", () => {\n\tit(\"minimal preset (redis only)\", () => {\n\t\tconst result = generateForPreset([\"redis\"]);\n\t\texpect(result.files[\"docker-compose.yml\"]).toMatchSnapshot();\n\t});\n\n\tit(\"creator preset (ffmpeg + remotion + minio + redis)\", () => {\n\t\tconst result = generateForPreset([\"ffmpeg\", \"remotion\", \"minio\", \"redis\"], [\"video-creator\"]);\n\t\texpect(result.files[\"docker-compose.yml\"]).toMatchSnapshot();\n\t\tif (result.files[\"docker-compose.media.yml\"]) {\n\t\t\texpect(result.files[\"docker-compose.media.yml\"]).toMatchSnapshot();\n\t\t}\n\t});\n\n\tit(\"researcher preset (qdrant + searxng + browserless + redis)\", () => {\n\t\tconst result = generateForPreset(\n\t\t\t[\"qdrant\", \"searxng\", \"browserless\", \"redis\"],\n\t\t\t[\"research-agent\"],\n\t\t);\n\t\texpect(result.files[\"docker-compose.yml\"]).toMatchSnapshot();\n\t});\n\n\tit(\"devops preset (n8n + postgresql + redis + monitoring)\", () => {\n\t\tconst result = generateForPreset(\n\t\t\t[\"n8n\", \"postgresql\", \"redis\", \"uptime-kuma\", \"grafana\", \"prometheus\"],\n\t\t\t[\"dev-ops\"],\n\t\t);\n\t\texpect(result.files[\"docker-compose.yml\"]).toMatchSnapshot();\n\t\tif (result.files[\"docker-compose.monitoring.yml\"]) {\n\t\t\texpect(result.files[\"docker-compose.monitoring.yml\"]).toMatchSnapshot();\n\t\t}\n\t});\n\n\tit(\"full preset (many services)\", () => {\n\t\tconst result = generateForPreset([\n\t\t\t\"redis\",\n\t\t\t\"postgresql\",\n\t\t\t\"qdrant\",\n\t\t\t\"n8n\",\n\t\t\t\"ffmpeg\",\n\t\t\t\"remotion\",\n\t\t\t\"minio\",\n\t\t\t\"caddy\",\n\t\t\t\"browserless\",\n\t\t\t\"searxng\",\n\t\t\t\"meilisearch\",\n\t\t\t\"uptime-kuma\",\n\t\t\t\"grafana\",\n\t\t\t\"prometheus\",\n\t\t\t\"ollama\",\n\t\t\t\"whisper\",\n\t\t\t\"gotify\",\n\t\t]);\n\t\texpect(result.files[\"docker-compose.yml\"]).toMatchSnapshot();\n\t\texpect(result.profiles.length).toBeGreaterThan(0);\n\t\t// Verify multiple compose files were created\n\t\texpect(Object.keys(result.files).length).toBeGreaterThan(1);\n\t});\n\n\tit(\"multi-file output has correct profile assignments\", () => {\n\t\tconst result = generateForPreset([\"redis\", \"ollama\", \"open-webui\", \"grafana\", \"prometheus\"]);\n\t\t// AI services should be in the ai profile file\n\t\tif (result.files[\"docker-compose.ai.yml\"]) {\n\t\t\texpect(result.files[\"docker-compose.ai.yml\"]).toContain(\"profiles:\");\n\t\t}\n\t\t// Monitoring should be in the monitoring profile file\n\t\tif (result.files[\"docker-compose.monitoring.yml\"]) {\n\t\t\texpect(result.files[\"docker-compose.monitoring.yml\"]).toContain(\"profiles:\");\n\t\t}\n\t});\n});\n"],"mappings":";;;;AAIA,SAAS,kBAAkB,UAAoB,aAAuB,EAAE,EAAE;AAEzE,QAAOA,iBAAAA,iBADUC,iBAAAA,QAAQ;EAAE;EAAU;EAAY,CAAC,EAChB;EACjC,aAAa;EACb,OAAO;EACP,KAAK;EACL,UAAU;EACV,YAAY;EACZ,iBAAiB;EACjB,CAAC;;AAGHC,sBAAAA,SAAS,gCAAgC;AACxC,uBAAA,GAAG,qCAAqC;AAEvC,wBAAA,aADe,kBAAkB,CAAC,QAAQ,CAAC,CAC7B,MAAM,sBAAsB,CAAC,iBAAiB;GAC3D;AAEF,uBAAA,GAAG,4DAA4D;EAC9D,MAAM,SAAS,kBAAkB;GAAC;GAAU;GAAY;GAAS;GAAQ,EAAE,CAAC,gBAAgB,CAAC;AAC7F,wBAAA,aAAO,OAAO,MAAM,sBAAsB,CAAC,iBAAiB;AAC5D,MAAI,OAAO,MAAM,4BAChB,uBAAA,aAAO,OAAO,MAAM,4BAA4B,CAAC,iBAAiB;GAElE;AAEF,uBAAA,GAAG,oEAAoE;AAKtE,wBAAA,aAJe,kBACd;GAAC;GAAU;GAAW;GAAe;GAAQ,EAC7C,CAAC,iBAAiB,CAClB,CACa,MAAM,sBAAsB,CAAC,iBAAiB;GAC3D;AAEF,uBAAA,GAAG,+DAA+D;EACjE,MAAM,SAAS,kBACd;GAAC;GAAO;GAAc;GAAS;GAAe;GAAW;GAAa,EACtE,CAAC,UAAU,CACX;AACD,wBAAA,aAAO,OAAO,MAAM,sBAAsB,CAAC,iBAAiB;AAC5D,MAAI,OAAO,MAAM,iCAChB,uBAAA,aAAO,OAAO,MAAM,iCAAiC,CAAC,iBAAiB;GAEvE;AAEF,uBAAA,GAAG,qCAAqC;EACvC,MAAM,SAAS,kBAAkB;GAChC;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,CAAC;AACF,wBAAA,aAAO,OAAO,MAAM,sBAAsB,CAAC,iBAAiB;AAC5D,wBAAA,aAAO,OAAO,SAAS,OAAO,CAAC,gBAAgB,EAAE;AAEjD,wBAAA,aAAO,OAAO,KAAK,OAAO,MAAM,CAAC,OAAO,CAAC,gBAAgB,EAAE;GAC1D;AAEF,uBAAA,GAAG,2DAA2D;EAC7D,MAAM,SAAS,kBAAkB;GAAC;GAAS;GAAU;GAAc;GAAW;GAAa,CAAC;AAE5F,MAAI,OAAO,MAAM,yBAChB,uBAAA,aAAO,OAAO,MAAM,yBAAyB,CAAC,UAAU,YAAY;AAGrE,MAAI,OAAO,MAAM,iCAChB,uBAAA,aAAO,OAAO,MAAM,iCAAiC,CAAC,UAAU,YAAY;GAE5E;EACD"}
@@ -1,6 +1,6 @@
1
- import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-DvC3SVNc.mjs";
2
- import { resolve } from "./resolver.mjs";
1
+ import { n as describe, r as it, t as globalExpect } from "./test.CTcmp4Su-ClCHJ3FA.mjs";
3
2
  import { composeMultiFile } from "./composer.mjs";
3
+ import { resolve } from "./resolver.mjs";
4
4
  //#region src/composer.snapshot.test.ts
5
5
  function generateForPreset(services, skillPacks = []) {
6
6
  return composeMultiFile(resolve({
@@ -1,7 +1,7 @@
1
1
  require("./skills-BlzpHmpH.cjs");
2
- const require_vi_2VT5v0um = require("./vi.2VT5v0um-CRqXre87.cjs");
3
- const require_resolver = require("./resolver.cjs");
2
+ const require_test_CTcmp4Su = require("./test.CTcmp4Su-DlzTarwH.cjs");
4
3
  const require_composer = require("./composer.cjs");
4
+ const require_resolver = require("./resolver.cjs");
5
5
  let yaml = require("yaml");
6
6
  //#region src/composer.test.ts
7
7
  const defaultOptions = {
@@ -12,57 +12,58 @@ const defaultOptions = {
12
12
  deployment: "local",
13
13
  openclawVersion: "latest"
14
14
  };
15
- require_vi_2VT5v0um.describe("compose", () => {
16
- require_vi_2VT5v0um.it("generates minimal stack with just OpenClaw gateway when no companions", () => {
15
+ require_test_CTcmp4Su.describe("compose", () => {
16
+ require_test_CTcmp4Su.it("generates minimal stack with just OpenClaw gateway when no companions", () => {
17
17
  const parsed = (0, yaml.parse)(require_composer.compose(require_resolver.resolve({
18
18
  services: [],
19
19
  skillPacks: []
20
20
  }), defaultOptions));
21
- require_vi_2VT5v0um.globalExpect(parsed.services).toHaveProperty("openclaw-gateway");
22
- require_vi_2VT5v0um.globalExpect(parsed.services["openclaw-gateway"].image).toContain("ghcr.io/openclaw/openclaw");
23
- require_vi_2VT5v0um.globalExpect(parsed.services["openclaw-gateway"].environment.HOME).toBe("/home/node");
24
- require_vi_2VT5v0um.globalExpect(parsed.services["openclaw-gateway"].environment.TERM).toBe("xterm-256color");
21
+ require_test_CTcmp4Su.globalExpect(parsed.services).toHaveProperty("openclaw-gateway");
22
+ require_test_CTcmp4Su.globalExpect(parsed.services["openclaw-gateway"].image).toContain("ghcr.io/openclaw/openclaw");
23
+ require_test_CTcmp4Su.globalExpect(parsed.services["openclaw-gateway"].environment.HOME).toBe("/home/node");
24
+ require_test_CTcmp4Su.globalExpect(parsed.services["openclaw-gateway"].environment.TERM).toBe("xterm-256color");
25
25
  const gwVolumes = parsed.services["openclaw-gateway"].volumes;
26
- require_vi_2VT5v0um.globalExpect(gwVolumes.some((v) => v.includes(".openclaw"))).toBe(true);
27
- require_vi_2VT5v0um.globalExpect(parsed.networks).toHaveProperty("openclaw-network");
28
- require_vi_2VT5v0um.globalExpect(parsed.networks["openclaw-network"].driver).toBe("bridge");
29
- require_vi_2VT5v0um.globalExpect(parsed.services).toHaveProperty("openclaw-cli");
30
- require_vi_2VT5v0um.globalExpect(parsed.services["openclaw-gateway"]).not.toHaveProperty("depends_on");
31
- require_vi_2VT5v0um.globalExpect(parsed.services["openclaw-gateway"].restart).toBe("unless-stopped");
26
+ require_test_CTcmp4Su.globalExpect(gwVolumes.some((v) => v.includes(".openclaw"))).toBe(true);
27
+ require_test_CTcmp4Su.globalExpect(parsed.networks).toHaveProperty("openclaw-network");
28
+ require_test_CTcmp4Su.globalExpect(parsed.networks["openclaw-network"].driver).toBe("bridge");
29
+ require_test_CTcmp4Su.globalExpect(parsed.services).toHaveProperty("openclaw-cli");
30
+ require_test_CTcmp4Su.globalExpect(parsed.services).toHaveProperty("convex");
31
+ require_test_CTcmp4Su.globalExpect(parsed.services).toHaveProperty("mission-control");
32
+ require_test_CTcmp4Su.globalExpect(parsed.services["openclaw-gateway"].restart).toBe("unless-stopped");
32
33
  });
33
- require_vi_2VT5v0um.it("generates Redis companion with proper gateway depends_on", () => {
34
+ require_test_CTcmp4Su.it("generates Redis companion with proper gateway depends_on", () => {
34
35
  const parsed = (0, yaml.parse)(require_composer.compose(require_resolver.resolve({
35
36
  services: ["redis"],
36
37
  skillPacks: []
37
38
  }), defaultOptions));
38
- require_vi_2VT5v0um.globalExpect(parsed.services).toHaveProperty("openclaw-gateway");
39
- require_vi_2VT5v0um.globalExpect(parsed.services).toHaveProperty("redis");
40
- require_vi_2VT5v0um.globalExpect(parsed.services.redis.image).toBe("redis:8-alpine");
41
- require_vi_2VT5v0um.globalExpect(parsed.services["openclaw-gateway"].depends_on).toHaveProperty("redis");
42
- require_vi_2VT5v0um.globalExpect(parsed.services["openclaw-gateway"].depends_on.redis.condition).toBe("service_healthy");
43
- require_vi_2VT5v0um.globalExpect(parsed.services.redis.healthcheck).toBeDefined();
44
- require_vi_2VT5v0um.globalExpect(parsed.services.redis.healthcheck.test).toContain("redis-cli ping");
45
- require_vi_2VT5v0um.globalExpect(parsed.services.redis.restart).toBe("unless-stopped");
46
- require_vi_2VT5v0um.globalExpect(parsed.services.redis.networks).toContain("openclaw-network");
47
- require_vi_2VT5v0um.globalExpect(parsed.services["openclaw-gateway"].environment).toHaveProperty("REDIS_HOST");
48
- require_vi_2VT5v0um.globalExpect(parsed.services["openclaw-gateway"].environment.REDIS_HOST).toBe("redis");
39
+ require_test_CTcmp4Su.globalExpect(parsed.services).toHaveProperty("openclaw-gateway");
40
+ require_test_CTcmp4Su.globalExpect(parsed.services).toHaveProperty("redis");
41
+ require_test_CTcmp4Su.globalExpect(parsed.services.redis.image).toBe("redis:8-alpine");
42
+ require_test_CTcmp4Su.globalExpect(parsed.services["openclaw-gateway"].depends_on).toHaveProperty("redis");
43
+ require_test_CTcmp4Su.globalExpect(parsed.services["openclaw-gateway"].depends_on.redis.condition).toBe("service_healthy");
44
+ require_test_CTcmp4Su.globalExpect(parsed.services.redis.healthcheck).toBeDefined();
45
+ require_test_CTcmp4Su.globalExpect(parsed.services.redis.healthcheck.test).toContain("redis-cli ping");
46
+ require_test_CTcmp4Su.globalExpect(parsed.services.redis.restart).toBe("unless-stopped");
47
+ require_test_CTcmp4Su.globalExpect(parsed.services.redis.networks).toContain("openclaw-network");
48
+ require_test_CTcmp4Su.globalExpect(parsed.services["openclaw-gateway"].environment).toHaveProperty("REDIS_HOST");
49
+ require_test_CTcmp4Su.globalExpect(parsed.services["openclaw-gateway"].environment.REDIS_HOST).toBe("redis");
49
50
  });
50
- require_vi_2VT5v0um.it("generates parseable YAML for a multi-service stack", () => {
51
+ require_test_CTcmp4Su.it("generates parseable YAML for a multi-service stack", () => {
51
52
  const yaml$1 = require_composer.compose(require_resolver.resolve({
52
53
  services: ["redis", "n8n"],
53
54
  skillPacks: []
54
55
  }), defaultOptions);
55
- require_vi_2VT5v0um.globalExpect(() => (0, yaml.parse)(yaml$1)).not.toThrow();
56
+ require_test_CTcmp4Su.globalExpect(() => (0, yaml.parse)(yaml$1)).not.toThrow();
56
57
  const parsed = (0, yaml.parse)(yaml$1);
57
- require_vi_2VT5v0um.globalExpect(parsed).toHaveProperty("services");
58
- require_vi_2VT5v0um.globalExpect(parsed).toHaveProperty("volumes");
59
- require_vi_2VT5v0um.globalExpect(parsed).toHaveProperty("networks");
60
- require_vi_2VT5v0um.globalExpect(parsed.services).toHaveProperty("openclaw-gateway");
61
- require_vi_2VT5v0um.globalExpect(parsed.services).toHaveProperty("redis");
62
- require_vi_2VT5v0um.globalExpect(parsed.services).toHaveProperty("n8n");
63
- require_vi_2VT5v0um.globalExpect(parsed.services).toHaveProperty("postgresql");
58
+ require_test_CTcmp4Su.globalExpect(parsed).toHaveProperty("services");
59
+ require_test_CTcmp4Su.globalExpect(parsed).toHaveProperty("volumes");
60
+ require_test_CTcmp4Su.globalExpect(parsed).toHaveProperty("networks");
61
+ require_test_CTcmp4Su.globalExpect(parsed.services).toHaveProperty("openclaw-gateway");
62
+ require_test_CTcmp4Su.globalExpect(parsed.services).toHaveProperty("redis");
63
+ require_test_CTcmp4Su.globalExpect(parsed.services).toHaveProperty("n8n");
64
+ require_test_CTcmp4Su.globalExpect(parsed.services).toHaveProperty("postgresql");
64
65
  });
65
- require_vi_2VT5v0um.it("includes all service volumes in top-level volumes section", () => {
66
+ require_test_CTcmp4Su.it("includes all service volumes in top-level volumes section", () => {
66
67
  const resolved = require_resolver.resolve({
67
68
  services: ["redis", "n8n"],
68
69
  skillPacks: []
@@ -70,11 +71,11 @@ require_vi_2VT5v0um.describe("compose", () => {
70
71
  const parsed = (0, yaml.parse)(require_composer.compose(resolved, defaultOptions));
71
72
  const serviceVolumeNames = /* @__PURE__ */ new Set();
72
73
  for (const rs of resolved.services) for (const vol of rs.definition.volumes) serviceVolumeNames.add(vol.name);
73
- for (const volName of serviceVolumeNames) require_vi_2VT5v0um.globalExpect(parsed.volumes).toHaveProperty(volName);
74
+ for (const volName of serviceVolumeNames) require_test_CTcmp4Su.globalExpect(parsed.volumes).toHaveProperty(volName);
74
75
  const gwVols = parsed.services["openclaw-gateway"].volumes;
75
- require_vi_2VT5v0um.globalExpect(gwVols.some((v) => v.includes(".openclaw"))).toBe(true);
76
+ require_test_CTcmp4Su.globalExpect(gwVols.some((v) => v.includes(".openclaw"))).toBe(true);
76
77
  });
77
- require_vi_2VT5v0um.it("includes GPU passthrough when gpu=true and service requires it", () => {
78
+ require_test_CTcmp4Su.it("includes GPU passthrough when gpu=true and service requires it", () => {
78
79
  const resolved = require_resolver.resolve({
79
80
  services: ["ollama"],
80
81
  skillPacks: []
@@ -92,14 +93,14 @@ require_vi_2VT5v0um.describe("compose", () => {
92
93
  ...defaultOptions,
93
94
  gpu: true
94
95
  })).services.ollama;
95
- require_vi_2VT5v0um.globalExpect(ollamaService.deploy).toBeDefined();
96
- require_vi_2VT5v0um.globalExpect(ollamaService.deploy.resources.reservations.devices).toEqual([{
96
+ require_test_CTcmp4Su.globalExpect(ollamaService.deploy).toBeDefined();
97
+ require_test_CTcmp4Su.globalExpect(ollamaService.deploy.resources.reservations.devices).toEqual([{
97
98
  driver: "nvidia",
98
99
  count: "all",
99
100
  capabilities: ["gpu"]
100
101
  }]);
101
102
  });
102
- require_vi_2VT5v0um.it("does not include GPU passthrough when gpu=false", () => {
103
+ require_test_CTcmp4Su.it("does not include GPU passthrough when gpu=false", () => {
103
104
  const resolved = require_resolver.resolve({
104
105
  services: ["ollama"],
105
106
  skillPacks: []
@@ -117,9 +118,9 @@ require_vi_2VT5v0um.describe("compose", () => {
117
118
  ...defaultOptions,
118
119
  gpu: false
119
120
  })).services.ollama;
120
- require_vi_2VT5v0um.globalExpect(ollamaService.deploy?.resources?.reservations?.devices).toBeUndefined();
121
+ require_test_CTcmp4Su.globalExpect(ollamaService.deploy?.resources?.reservations?.devices).toBeUndefined();
121
122
  });
122
- require_vi_2VT5v0um.it("does not map internal-only ports to host", () => {
123
+ require_test_CTcmp4Su.it("does not map internal-only ports to host", () => {
123
124
  const resolved = require_resolver.resolve({
124
125
  services: ["redis"],
125
126
  skillPacks: []
@@ -139,25 +140,25 @@ require_vi_2VT5v0um.describe("compose", () => {
139
140
  }
140
141
  }))
141
142
  }, defaultOptions)).services.redis.ports;
142
- require_vi_2VT5v0um.globalExpect(redisPorts).toHaveLength(1);
143
- require_vi_2VT5v0um.globalExpect(redisPorts[0]).toContain("6379");
144
- require_vi_2VT5v0um.globalExpect(redisPorts[0]).not.toContain("16379");
143
+ require_test_CTcmp4Su.globalExpect(redisPorts).toHaveLength(1);
144
+ require_test_CTcmp4Su.globalExpect(redisPorts[0]).toContain("6379");
145
+ require_test_CTcmp4Su.globalExpect(redisPorts[0]).not.toContain("16379");
145
146
  });
146
- require_vi_2VT5v0um.it("uses service_started condition for services without healthcheck", () => {
147
+ require_test_CTcmp4Su.it("uses service_started condition for services without healthcheck", () => {
147
148
  const resolved = require_resolver.resolve({
148
149
  services: ["minio"],
149
150
  skillPacks: []
150
151
  });
151
152
  const gatewayDeps = (0, yaml.parse)(require_composer.compose(resolved, defaultOptions)).services["openclaw-gateway"].depends_on;
152
- for (const [id, dep] of Object.entries(gatewayDeps)) if (resolved.services.find((s) => s.definition.id === id)?.definition.healthcheck) require_vi_2VT5v0um.globalExpect(dep.condition).toBe("service_healthy");
153
- else require_vi_2VT5v0um.globalExpect(dep.condition).toBe("service_started");
153
+ for (const [id, dep] of Object.entries(gatewayDeps)) if (resolved.services.find((s) => s.definition.id === id)?.definition.healthcheck) require_test_CTcmp4Su.globalExpect(dep.condition).toBe("service_healthy");
154
+ else require_test_CTcmp4Su.globalExpect(dep.condition).toBe("service_started");
154
155
  });
155
- require_vi_2VT5v0um.it("gateway is always the first service in the output", () => {
156
+ require_test_CTcmp4Su.it("gateway is always the first service in the output", () => {
156
157
  const parsed = (0, yaml.parse)(require_composer.compose(require_resolver.resolve({
157
158
  services: ["redis", "ollama"],
158
159
  skillPacks: []
159
160
  }), defaultOptions));
160
- require_vi_2VT5v0um.globalExpect(Object.keys(parsed.services)[0]).toBe("openclaw-gateway");
161
+ require_test_CTcmp4Su.globalExpect(Object.keys(parsed.services)[0]).toBe("openclaw-gateway");
161
162
  });
162
163
  });
163
164
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"composer.test.cjs","names":["describe","compose","resolve","yaml"],"sources":["../src/composer.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { parse } from \"yaml\";\nimport { compose } from \"./composer.js\";\nimport { resolve } from \"./resolver.js\";\nimport type { ComposeOptions, ResolverOutput } from \"./types.js\";\n\nconst defaultOptions: ComposeOptions = {\n\tprojectName: \"test-project\",\n\tproxy: \"none\",\n\tgpu: false,\n\tplatform: \"linux/amd64\",\n\tdeployment: \"local\",\n\topenclawVersion: \"latest\",\n};\n\ndescribe(\"compose\", () => {\n\tit(\"generates minimal stack with just OpenClaw gateway when no companions\", () => {\n\t\tconst resolved = resolve({ services: [], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should have the gateway service\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-gateway\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].image).toContain(\"ghcr.io/openclaw/openclaw\");\n\n\t\t// Gateway should have core environment\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment.HOME).toBe(\"/home/node\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment.TERM).toBe(\"xterm-256color\");\n\n\t\t// Gateway uses bind-mount volumes (not named volumes in top-level volumes section)\n\t\tconst gwVolumes = parsed.services[\"openclaw-gateway\"].volumes as string[];\n\t\texpect(gwVolumes.some((v: string) => v.includes(\".openclaw\"))).toBe(true);\n\n\t\t// Should have network\n\t\texpect(parsed.networks).toHaveProperty(\"openclaw-network\");\n\t\texpect(parsed.networks[\"openclaw-network\"].driver).toBe(\"bridge\");\n\n\t\t// Should have CLI companion service\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-cli\");\n\n\t\t// Gateway should have no depends_on (no companions)\n\t\texpect(parsed.services[\"openclaw-gateway\"]).not.toHaveProperty(\"depends_on\");\n\n\t\t// Gateway should have restart policy\n\t\texpect(parsed.services[\"openclaw-gateway\"].restart).toBe(\"unless-stopped\");\n\t});\n\n\tit(\"generates Redis companion with proper gateway depends_on\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should have gateway and redis services\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-gateway\");\n\t\texpect(parsed.services).toHaveProperty(\"redis\");\n\n\t\t// Redis service should have correct image\n\t\texpect(parsed.services.redis.image).toBe(\"redis:8-alpine\");\n\n\t\t// Gateway depends_on should include redis with service_healthy (redis has healthcheck)\n\t\texpect(parsed.services[\"openclaw-gateway\"].depends_on).toHaveProperty(\"redis\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].depends_on.redis.condition).toBe(\"service_healthy\");\n\n\t\t// Redis should have a healthcheck\n\t\texpect(parsed.services.redis.healthcheck).toBeDefined();\n\t\texpect(parsed.services.redis.healthcheck.test).toContain(\"redis-cli ping\");\n\n\t\t// Redis should have restart policy\n\t\texpect(parsed.services.redis.restart).toBe(\"unless-stopped\");\n\n\t\t// Redis should be on openclaw-network\n\t\texpect(parsed.services.redis.networks).toContain(\"openclaw-network\");\n\n\t\t// Gateway environment should include redis-related openclawEnvVars\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment).toHaveProperty(\"REDIS_HOST\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment.REDIS_HOST).toBe(\"redis\");\n\t});\n\n\tit(\"generates parseable YAML for a multi-service stack\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\", \"n8n\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\n\t\t// Should not throw when parsing\n\t\texpect(() => parse(yaml)).not.toThrow();\n\n\t\tconst parsed = parse(yaml);\n\t\texpect(parsed).toHaveProperty(\"services\");\n\t\texpect(parsed).toHaveProperty(\"volumes\");\n\t\texpect(parsed).toHaveProperty(\"networks\");\n\n\t\t// Should have gateway + redis + n8n + postgresql (n8n requires postgresql)\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-gateway\");\n\t\texpect(parsed.services).toHaveProperty(\"redis\");\n\t\texpect(parsed.services).toHaveProperty(\"n8n\");\n\t\texpect(parsed.services).toHaveProperty(\"postgresql\");\n\t});\n\n\tit(\"includes all service volumes in top-level volumes section\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\", \"n8n\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Collect all volume names from all resolved services\n\t\tconst serviceVolumeNames = new Set<string>();\n\t\tfor (const rs of resolved.services) {\n\t\t\tfor (const vol of rs.definition.volumes) {\n\t\t\t\tserviceVolumeNames.add(vol.name);\n\t\t\t}\n\t\t}\n\n\t\t// All service volumes must appear in top-level volumes\n\t\tfor (const volName of serviceVolumeNames) {\n\t\t\texpect(parsed.volumes).toHaveProperty(volName);\n\t\t}\n\n\t\t// Gateway uses bind-mount volumes (not in top-level volumes section)\n\t\tconst gwVols = parsed.services[\"openclaw-gateway\"].volumes as string[];\n\t\texpect(gwVols.some((v: string) => v.includes(\".openclaw\"))).toBe(true);\n\t});\n\n\tit(\"includes GPU passthrough when gpu=true and service requires it\", () => {\n\t\tconst resolved = resolve({ services: [\"ollama\"], skillPacks: [] });\n\n\t\t// Modify resolved output to simulate a GPU-required service\n\t\tconst gpuResolved: ResolverOutput = {\n\t\t\t...resolved,\n\t\t\tservices: resolved.services.map((s) => ({\n\t\t\t\t...s,\n\t\t\t\tdefinition: { ...s.definition, gpuRequired: true },\n\t\t\t})),\n\t\t};\n\n\t\tconst yaml = compose(gpuResolved, { ...defaultOptions, gpu: true });\n\t\tconst parsed = parse(yaml);\n\n\t\t// Ollama service should have GPU device reservation\n\t\tconst ollamaService = parsed.services.ollama;\n\t\texpect(ollamaService.deploy).toBeDefined();\n\t\texpect(ollamaService.deploy.resources.reservations.devices).toEqual([\n\t\t\t{\n\t\t\t\tdriver: \"nvidia\",\n\t\t\t\tcount: \"all\",\n\t\t\t\tcapabilities: [\"gpu\"],\n\t\t\t},\n\t\t]);\n\t});\n\n\tit(\"does not include GPU passthrough when gpu=false\", () => {\n\t\tconst resolved = resolve({ services: [\"ollama\"], skillPacks: [] });\n\n\t\t// Even if service requires GPU, gpu option is false\n\t\tconst gpuResolved: ResolverOutput = {\n\t\t\t...resolved,\n\t\t\tservices: resolved.services.map((s) => ({\n\t\t\t\t...s,\n\t\t\t\tdefinition: { ...s.definition, gpuRequired: true },\n\t\t\t})),\n\t\t};\n\n\t\tconst yaml = compose(gpuResolved, { ...defaultOptions, gpu: false });\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should NOT have deploy with GPU devices\n\t\tconst ollamaService = parsed.services.ollama;\n\t\texpect(ollamaService.deploy?.resources?.reservations?.devices).toBeUndefined();\n\t});\n\n\tit(\"does not map internal-only ports to host\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\"], skillPacks: [] });\n\n\t\t// Modify redis to have an internal-only port\n\t\tconst modifiedResolved: ResolverOutput = {\n\t\t\t...resolved,\n\t\t\tservices: resolved.services.map((s) => ({\n\t\t\t\t...s,\n\t\t\t\tdefinition: {\n\t\t\t\t\t...s.definition,\n\t\t\t\t\tports: [\n\t\t\t\t\t\t...s.definition.ports,\n\t\t\t\t\t\t{ host: 16379, container: 16379, description: \"Redis cluster bus\", exposed: false },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t})),\n\t\t};\n\n\t\tconst yaml = compose(modifiedResolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should only have the exposed port, not the internal one\n\t\tconst redisPorts = parsed.services.redis.ports;\n\t\texpect(redisPorts).toHaveLength(1);\n\t\texpect(redisPorts[0]).toContain(\"6379\");\n\t\texpect(redisPorts[0]).not.toContain(\"16379\");\n\t});\n\n\tit(\"uses service_started condition for services without healthcheck\", () => {\n\t\tconst resolved = resolve({ services: [\"minio\"], skillPacks: [] });\n\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Minio may or may not have a healthcheck – the gateway depends_on\n\t\t// condition should reflect the definition accurately\n\t\tconst gatewayDeps = parsed.services[\"openclaw-gateway\"].depends_on;\n\t\tfor (const [id, dep] of Object.entries(gatewayDeps)) {\n\t\t\tconst svc = resolved.services.find((s) => s.definition.id === id);\n\t\t\tif (svc?.definition.healthcheck) {\n\t\t\t\texpect((dep as { condition: string }).condition).toBe(\"service_healthy\");\n\t\t\t} else {\n\t\t\t\texpect((dep as { condition: string }).condition).toBe(\"service_started\");\n\t\t\t}\n\t\t}\n\t});\n\n\tit(\"gateway is always the first service in the output\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\", \"ollama\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// First key in services should be openclaw-gateway\n\t\tconst serviceKeys = Object.keys(parsed.services);\n\t\texpect(serviceKeys[0]).toBe(\"openclaw-gateway\");\n\t});\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"composer.test.cjs","names":["describe","compose","resolve","yaml"],"sources":["../src/composer.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { parse } from \"yaml\";\nimport { compose } from \"./composer.js\";\nimport { resolve } from \"./resolver.js\";\nimport type { ComposeOptions, ResolverOutput } from \"./types.js\";\n\nconst defaultOptions: ComposeOptions = {\n\tprojectName: \"test-project\",\n\tproxy: \"none\",\n\tgpu: false,\n\tplatform: \"linux/amd64\",\n\tdeployment: \"local\",\n\topenclawVersion: \"latest\",\n};\n\ndescribe(\"compose\", () => {\n\tit(\"generates minimal stack with just OpenClaw gateway when no companions\", () => {\n\t\tconst resolved = resolve({ services: [], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should have the gateway service\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-gateway\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].image).toContain(\"ghcr.io/openclaw/openclaw\");\n\n\t\t// Gateway should have core environment\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment.HOME).toBe(\"/home/node\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment.TERM).toBe(\"xterm-256color\");\n\n\t\t// Gateway uses bind-mount volumes (not named volumes in top-level volumes section)\n\t\tconst gwVolumes = parsed.services[\"openclaw-gateway\"].volumes as string[];\n\t\texpect(gwVolumes.some((v: string) => v.includes(\".openclaw\"))).toBe(true);\n\n\t\t// Should have network\n\t\texpect(parsed.networks).toHaveProperty(\"openclaw-network\");\n\t\texpect(parsed.networks[\"openclaw-network\"].driver).toBe(\"bridge\");\n\n\t\t// Should have CLI companion service\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-cli\");\n\n\t\t// Gateway should have depends_on for mandatory services (convex, mission-control, tailscale)\n\t\t// Even with no user-selected services, mandatory services are always present\n\t\texpect(parsed.services).toHaveProperty(\"convex\");\n\t\texpect(parsed.services).toHaveProperty(\"mission-control\");\n\n\t\t// Gateway should have restart policy\n\t\texpect(parsed.services[\"openclaw-gateway\"].restart).toBe(\"unless-stopped\");\n\t});\n\n\tit(\"generates Redis companion with proper gateway depends_on\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should have gateway and redis services\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-gateway\");\n\t\texpect(parsed.services).toHaveProperty(\"redis\");\n\n\t\t// Redis service should have correct image\n\t\texpect(parsed.services.redis.image).toBe(\"redis:8-alpine\");\n\n\t\t// Gateway depends_on should include redis with service_healthy (redis has healthcheck)\n\t\texpect(parsed.services[\"openclaw-gateway\"].depends_on).toHaveProperty(\"redis\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].depends_on.redis.condition).toBe(\"service_healthy\");\n\n\t\t// Redis should have a healthcheck\n\t\texpect(parsed.services.redis.healthcheck).toBeDefined();\n\t\texpect(parsed.services.redis.healthcheck.test).toContain(\"redis-cli ping\");\n\n\t\t// Redis should have restart policy\n\t\texpect(parsed.services.redis.restart).toBe(\"unless-stopped\");\n\n\t\t// Redis should be on openclaw-network\n\t\texpect(parsed.services.redis.networks).toContain(\"openclaw-network\");\n\n\t\t// Gateway environment should include redis-related openclawEnvVars\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment).toHaveProperty(\"REDIS_HOST\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment.REDIS_HOST).toBe(\"redis\");\n\t});\n\n\tit(\"generates parseable YAML for a multi-service stack\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\", \"n8n\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\n\t\t// Should not throw when parsing\n\t\texpect(() => parse(yaml)).not.toThrow();\n\n\t\tconst parsed = parse(yaml);\n\t\texpect(parsed).toHaveProperty(\"services\");\n\t\texpect(parsed).toHaveProperty(\"volumes\");\n\t\texpect(parsed).toHaveProperty(\"networks\");\n\n\t\t// Should have gateway + redis + n8n + postgresql (n8n requires postgresql)\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-gateway\");\n\t\texpect(parsed.services).toHaveProperty(\"redis\");\n\t\texpect(parsed.services).toHaveProperty(\"n8n\");\n\t\texpect(parsed.services).toHaveProperty(\"postgresql\");\n\t});\n\n\tit(\"includes all service volumes in top-level volumes section\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\", \"n8n\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Collect all volume names from all resolved services\n\t\tconst serviceVolumeNames = new Set<string>();\n\t\tfor (const rs of resolved.services) {\n\t\t\tfor (const vol of rs.definition.volumes) {\n\t\t\t\tserviceVolumeNames.add(vol.name);\n\t\t\t}\n\t\t}\n\n\t\t// All service volumes must appear in top-level volumes\n\t\tfor (const volName of serviceVolumeNames) {\n\t\t\texpect(parsed.volumes).toHaveProperty(volName);\n\t\t}\n\n\t\t// Gateway uses bind-mount volumes (not in top-level volumes section)\n\t\tconst gwVols = parsed.services[\"openclaw-gateway\"].volumes as string[];\n\t\texpect(gwVols.some((v: string) => v.includes(\".openclaw\"))).toBe(true);\n\t});\n\n\tit(\"includes GPU passthrough when gpu=true and service requires it\", () => {\n\t\tconst resolved = resolve({ services: [\"ollama\"], skillPacks: [] });\n\n\t\t// Modify resolved output to simulate a GPU-required service\n\t\tconst gpuResolved: ResolverOutput = {\n\t\t\t...resolved,\n\t\t\tservices: resolved.services.map((s) => ({\n\t\t\t\t...s,\n\t\t\t\tdefinition: { ...s.definition, gpuRequired: true },\n\t\t\t})),\n\t\t};\n\n\t\tconst yaml = compose(gpuResolved, { ...defaultOptions, gpu: true });\n\t\tconst parsed = parse(yaml);\n\n\t\t// Ollama service should have GPU device reservation\n\t\tconst ollamaService = parsed.services.ollama;\n\t\texpect(ollamaService.deploy).toBeDefined();\n\t\texpect(ollamaService.deploy.resources.reservations.devices).toEqual([\n\t\t\t{\n\t\t\t\tdriver: \"nvidia\",\n\t\t\t\tcount: \"all\",\n\t\t\t\tcapabilities: [\"gpu\"],\n\t\t\t},\n\t\t]);\n\t});\n\n\tit(\"does not include GPU passthrough when gpu=false\", () => {\n\t\tconst resolved = resolve({ services: [\"ollama\"], skillPacks: [] });\n\n\t\t// Even if service requires GPU, gpu option is false\n\t\tconst gpuResolved: ResolverOutput = {\n\t\t\t...resolved,\n\t\t\tservices: resolved.services.map((s) => ({\n\t\t\t\t...s,\n\t\t\t\tdefinition: { ...s.definition, gpuRequired: true },\n\t\t\t})),\n\t\t};\n\n\t\tconst yaml = compose(gpuResolved, { ...defaultOptions, gpu: false });\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should NOT have deploy with GPU devices\n\t\tconst ollamaService = parsed.services.ollama;\n\t\texpect(ollamaService.deploy?.resources?.reservations?.devices).toBeUndefined();\n\t});\n\n\tit(\"does not map internal-only ports to host\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\"], skillPacks: [] });\n\n\t\t// Modify redis to have an internal-only port\n\t\tconst modifiedResolved: ResolverOutput = {\n\t\t\t...resolved,\n\t\t\tservices: resolved.services.map((s) => ({\n\t\t\t\t...s,\n\t\t\t\tdefinition: {\n\t\t\t\t\t...s.definition,\n\t\t\t\t\tports: [\n\t\t\t\t\t\t...s.definition.ports,\n\t\t\t\t\t\t{ host: 16379, container: 16379, description: \"Redis cluster bus\", exposed: false },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t})),\n\t\t};\n\n\t\tconst yaml = compose(modifiedResolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should only have the exposed port, not the internal one\n\t\tconst redisPorts = parsed.services.redis.ports;\n\t\texpect(redisPorts).toHaveLength(1);\n\t\texpect(redisPorts[0]).toContain(\"6379\");\n\t\texpect(redisPorts[0]).not.toContain(\"16379\");\n\t});\n\n\tit(\"uses service_started condition for services without healthcheck\", () => {\n\t\tconst resolved = resolve({ services: [\"minio\"], skillPacks: [] });\n\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Minio may or may not have a healthcheck – the gateway depends_on\n\t\t// condition should reflect the definition accurately\n\t\tconst gatewayDeps = parsed.services[\"openclaw-gateway\"].depends_on;\n\t\tfor (const [id, dep] of Object.entries(gatewayDeps)) {\n\t\t\tconst svc = resolved.services.find((s) => s.definition.id === id);\n\t\t\tif (svc?.definition.healthcheck) {\n\t\t\t\texpect((dep as { condition: string }).condition).toBe(\"service_healthy\");\n\t\t\t} else {\n\t\t\t\texpect((dep as { condition: string }).condition).toBe(\"service_started\");\n\t\t\t}\n\t\t}\n\t});\n\n\tit(\"gateway is always the first service in the output\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\", \"ollama\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// First key in services should be openclaw-gateway\n\t\tconst serviceKeys = Object.keys(parsed.services);\n\t\texpect(serviceKeys[0]).toBe(\"openclaw-gateway\");\n\t});\n});\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { n as describe, r as it, t as globalExpect } from "./vi.2VT5v0um-DvC3SVNc.mjs";
2
- import { resolve } from "./resolver.mjs";
1
+ import { n as describe, r as it, t as globalExpect } from "./test.CTcmp4Su-ClCHJ3FA.mjs";
3
2
  import { compose } from "./composer.mjs";
3
+ import { resolve } from "./resolver.mjs";
4
4
  import { parse } from "yaml";
5
5
  //#region src/composer.test.ts
6
6
  const defaultOptions = {
@@ -26,7 +26,8 @@ describe("compose", () => {
26
26
  globalExpect(parsed.networks).toHaveProperty("openclaw-network");
27
27
  globalExpect(parsed.networks["openclaw-network"].driver).toBe("bridge");
28
28
  globalExpect(parsed.services).toHaveProperty("openclaw-cli");
29
- globalExpect(parsed.services["openclaw-gateway"]).not.toHaveProperty("depends_on");
29
+ globalExpect(parsed.services).toHaveProperty("convex");
30
+ globalExpect(parsed.services).toHaveProperty("mission-control");
30
31
  globalExpect(parsed.services["openclaw-gateway"].restart).toBe("unless-stopped");
31
32
  });
32
33
  it("generates Redis companion with proper gateway depends_on", () => {
@@ -1 +1 @@
1
- {"version":3,"file":"composer.test.mjs","names":[],"sources":["../src/composer.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { parse } from \"yaml\";\nimport { compose } from \"./composer.js\";\nimport { resolve } from \"./resolver.js\";\nimport type { ComposeOptions, ResolverOutput } from \"./types.js\";\n\nconst defaultOptions: ComposeOptions = {\n\tprojectName: \"test-project\",\n\tproxy: \"none\",\n\tgpu: false,\n\tplatform: \"linux/amd64\",\n\tdeployment: \"local\",\n\topenclawVersion: \"latest\",\n};\n\ndescribe(\"compose\", () => {\n\tit(\"generates minimal stack with just OpenClaw gateway when no companions\", () => {\n\t\tconst resolved = resolve({ services: [], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should have the gateway service\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-gateway\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].image).toContain(\"ghcr.io/openclaw/openclaw\");\n\n\t\t// Gateway should have core environment\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment.HOME).toBe(\"/home/node\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment.TERM).toBe(\"xterm-256color\");\n\n\t\t// Gateway uses bind-mount volumes (not named volumes in top-level volumes section)\n\t\tconst gwVolumes = parsed.services[\"openclaw-gateway\"].volumes as string[];\n\t\texpect(gwVolumes.some((v: string) => v.includes(\".openclaw\"))).toBe(true);\n\n\t\t// Should have network\n\t\texpect(parsed.networks).toHaveProperty(\"openclaw-network\");\n\t\texpect(parsed.networks[\"openclaw-network\"].driver).toBe(\"bridge\");\n\n\t\t// Should have CLI companion service\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-cli\");\n\n\t\t// Gateway should have no depends_on (no companions)\n\t\texpect(parsed.services[\"openclaw-gateway\"]).not.toHaveProperty(\"depends_on\");\n\n\t\t// Gateway should have restart policy\n\t\texpect(parsed.services[\"openclaw-gateway\"].restart).toBe(\"unless-stopped\");\n\t});\n\n\tit(\"generates Redis companion with proper gateway depends_on\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should have gateway and redis services\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-gateway\");\n\t\texpect(parsed.services).toHaveProperty(\"redis\");\n\n\t\t// Redis service should have correct image\n\t\texpect(parsed.services.redis.image).toBe(\"redis:8-alpine\");\n\n\t\t// Gateway depends_on should include redis with service_healthy (redis has healthcheck)\n\t\texpect(parsed.services[\"openclaw-gateway\"].depends_on).toHaveProperty(\"redis\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].depends_on.redis.condition).toBe(\"service_healthy\");\n\n\t\t// Redis should have a healthcheck\n\t\texpect(parsed.services.redis.healthcheck).toBeDefined();\n\t\texpect(parsed.services.redis.healthcheck.test).toContain(\"redis-cli ping\");\n\n\t\t// Redis should have restart policy\n\t\texpect(parsed.services.redis.restart).toBe(\"unless-stopped\");\n\n\t\t// Redis should be on openclaw-network\n\t\texpect(parsed.services.redis.networks).toContain(\"openclaw-network\");\n\n\t\t// Gateway environment should include redis-related openclawEnvVars\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment).toHaveProperty(\"REDIS_HOST\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment.REDIS_HOST).toBe(\"redis\");\n\t});\n\n\tit(\"generates parseable YAML for a multi-service stack\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\", \"n8n\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\n\t\t// Should not throw when parsing\n\t\texpect(() => parse(yaml)).not.toThrow();\n\n\t\tconst parsed = parse(yaml);\n\t\texpect(parsed).toHaveProperty(\"services\");\n\t\texpect(parsed).toHaveProperty(\"volumes\");\n\t\texpect(parsed).toHaveProperty(\"networks\");\n\n\t\t// Should have gateway + redis + n8n + postgresql (n8n requires postgresql)\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-gateway\");\n\t\texpect(parsed.services).toHaveProperty(\"redis\");\n\t\texpect(parsed.services).toHaveProperty(\"n8n\");\n\t\texpect(parsed.services).toHaveProperty(\"postgresql\");\n\t});\n\n\tit(\"includes all service volumes in top-level volumes section\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\", \"n8n\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Collect all volume names from all resolved services\n\t\tconst serviceVolumeNames = new Set<string>();\n\t\tfor (const rs of resolved.services) {\n\t\t\tfor (const vol of rs.definition.volumes) {\n\t\t\t\tserviceVolumeNames.add(vol.name);\n\t\t\t}\n\t\t}\n\n\t\t// All service volumes must appear in top-level volumes\n\t\tfor (const volName of serviceVolumeNames) {\n\t\t\texpect(parsed.volumes).toHaveProperty(volName);\n\t\t}\n\n\t\t// Gateway uses bind-mount volumes (not in top-level volumes section)\n\t\tconst gwVols = parsed.services[\"openclaw-gateway\"].volumes as string[];\n\t\texpect(gwVols.some((v: string) => v.includes(\".openclaw\"))).toBe(true);\n\t});\n\n\tit(\"includes GPU passthrough when gpu=true and service requires it\", () => {\n\t\tconst resolved = resolve({ services: [\"ollama\"], skillPacks: [] });\n\n\t\t// Modify resolved output to simulate a GPU-required service\n\t\tconst gpuResolved: ResolverOutput = {\n\t\t\t...resolved,\n\t\t\tservices: resolved.services.map((s) => ({\n\t\t\t\t...s,\n\t\t\t\tdefinition: { ...s.definition, gpuRequired: true },\n\t\t\t})),\n\t\t};\n\n\t\tconst yaml = compose(gpuResolved, { ...defaultOptions, gpu: true });\n\t\tconst parsed = parse(yaml);\n\n\t\t// Ollama service should have GPU device reservation\n\t\tconst ollamaService = parsed.services.ollama;\n\t\texpect(ollamaService.deploy).toBeDefined();\n\t\texpect(ollamaService.deploy.resources.reservations.devices).toEqual([\n\t\t\t{\n\t\t\t\tdriver: \"nvidia\",\n\t\t\t\tcount: \"all\",\n\t\t\t\tcapabilities: [\"gpu\"],\n\t\t\t},\n\t\t]);\n\t});\n\n\tit(\"does not include GPU passthrough when gpu=false\", () => {\n\t\tconst resolved = resolve({ services: [\"ollama\"], skillPacks: [] });\n\n\t\t// Even if service requires GPU, gpu option is false\n\t\tconst gpuResolved: ResolverOutput = {\n\t\t\t...resolved,\n\t\t\tservices: resolved.services.map((s) => ({\n\t\t\t\t...s,\n\t\t\t\tdefinition: { ...s.definition, gpuRequired: true },\n\t\t\t})),\n\t\t};\n\n\t\tconst yaml = compose(gpuResolved, { ...defaultOptions, gpu: false });\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should NOT have deploy with GPU devices\n\t\tconst ollamaService = parsed.services.ollama;\n\t\texpect(ollamaService.deploy?.resources?.reservations?.devices).toBeUndefined();\n\t});\n\n\tit(\"does not map internal-only ports to host\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\"], skillPacks: [] });\n\n\t\t// Modify redis to have an internal-only port\n\t\tconst modifiedResolved: ResolverOutput = {\n\t\t\t...resolved,\n\t\t\tservices: resolved.services.map((s) => ({\n\t\t\t\t...s,\n\t\t\t\tdefinition: {\n\t\t\t\t\t...s.definition,\n\t\t\t\t\tports: [\n\t\t\t\t\t\t...s.definition.ports,\n\t\t\t\t\t\t{ host: 16379, container: 16379, description: \"Redis cluster bus\", exposed: false },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t})),\n\t\t};\n\n\t\tconst yaml = compose(modifiedResolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should only have the exposed port, not the internal one\n\t\tconst redisPorts = parsed.services.redis.ports;\n\t\texpect(redisPorts).toHaveLength(1);\n\t\texpect(redisPorts[0]).toContain(\"6379\");\n\t\texpect(redisPorts[0]).not.toContain(\"16379\");\n\t});\n\n\tit(\"uses service_started condition for services without healthcheck\", () => {\n\t\tconst resolved = resolve({ services: [\"minio\"], skillPacks: [] });\n\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Minio may or may not have a healthcheck – the gateway depends_on\n\t\t// condition should reflect the definition accurately\n\t\tconst gatewayDeps = parsed.services[\"openclaw-gateway\"].depends_on;\n\t\tfor (const [id, dep] of Object.entries(gatewayDeps)) {\n\t\t\tconst svc = resolved.services.find((s) => s.definition.id === id);\n\t\t\tif (svc?.definition.healthcheck) {\n\t\t\t\texpect((dep as { condition: string }).condition).toBe(\"service_healthy\");\n\t\t\t} else {\n\t\t\t\texpect((dep as { condition: string }).condition).toBe(\"service_started\");\n\t\t\t}\n\t\t}\n\t});\n\n\tit(\"gateway is always the first service in the output\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\", \"ollama\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// First key in services should be openclaw-gateway\n\t\tconst serviceKeys = Object.keys(parsed.services);\n\t\texpect(serviceKeys[0]).toBe(\"openclaw-gateway\");\n\t});\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"composer.test.mjs","names":[],"sources":["../src/composer.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { parse } from \"yaml\";\nimport { compose } from \"./composer.js\";\nimport { resolve } from \"./resolver.js\";\nimport type { ComposeOptions, ResolverOutput } from \"./types.js\";\n\nconst defaultOptions: ComposeOptions = {\n\tprojectName: \"test-project\",\n\tproxy: \"none\",\n\tgpu: false,\n\tplatform: \"linux/amd64\",\n\tdeployment: \"local\",\n\topenclawVersion: \"latest\",\n};\n\ndescribe(\"compose\", () => {\n\tit(\"generates minimal stack with just OpenClaw gateway when no companions\", () => {\n\t\tconst resolved = resolve({ services: [], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should have the gateway service\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-gateway\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].image).toContain(\"ghcr.io/openclaw/openclaw\");\n\n\t\t// Gateway should have core environment\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment.HOME).toBe(\"/home/node\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment.TERM).toBe(\"xterm-256color\");\n\n\t\t// Gateway uses bind-mount volumes (not named volumes in top-level volumes section)\n\t\tconst gwVolumes = parsed.services[\"openclaw-gateway\"].volumes as string[];\n\t\texpect(gwVolumes.some((v: string) => v.includes(\".openclaw\"))).toBe(true);\n\n\t\t// Should have network\n\t\texpect(parsed.networks).toHaveProperty(\"openclaw-network\");\n\t\texpect(parsed.networks[\"openclaw-network\"].driver).toBe(\"bridge\");\n\n\t\t// Should have CLI companion service\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-cli\");\n\n\t\t// Gateway should have depends_on for mandatory services (convex, mission-control, tailscale)\n\t\t// Even with no user-selected services, mandatory services are always present\n\t\texpect(parsed.services).toHaveProperty(\"convex\");\n\t\texpect(parsed.services).toHaveProperty(\"mission-control\");\n\n\t\t// Gateway should have restart policy\n\t\texpect(parsed.services[\"openclaw-gateway\"].restart).toBe(\"unless-stopped\");\n\t});\n\n\tit(\"generates Redis companion with proper gateway depends_on\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should have gateway and redis services\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-gateway\");\n\t\texpect(parsed.services).toHaveProperty(\"redis\");\n\n\t\t// Redis service should have correct image\n\t\texpect(parsed.services.redis.image).toBe(\"redis:8-alpine\");\n\n\t\t// Gateway depends_on should include redis with service_healthy (redis has healthcheck)\n\t\texpect(parsed.services[\"openclaw-gateway\"].depends_on).toHaveProperty(\"redis\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].depends_on.redis.condition).toBe(\"service_healthy\");\n\n\t\t// Redis should have a healthcheck\n\t\texpect(parsed.services.redis.healthcheck).toBeDefined();\n\t\texpect(parsed.services.redis.healthcheck.test).toContain(\"redis-cli ping\");\n\n\t\t// Redis should have restart policy\n\t\texpect(parsed.services.redis.restart).toBe(\"unless-stopped\");\n\n\t\t// Redis should be on openclaw-network\n\t\texpect(parsed.services.redis.networks).toContain(\"openclaw-network\");\n\n\t\t// Gateway environment should include redis-related openclawEnvVars\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment).toHaveProperty(\"REDIS_HOST\");\n\t\texpect(parsed.services[\"openclaw-gateway\"].environment.REDIS_HOST).toBe(\"redis\");\n\t});\n\n\tit(\"generates parseable YAML for a multi-service stack\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\", \"n8n\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\n\t\t// Should not throw when parsing\n\t\texpect(() => parse(yaml)).not.toThrow();\n\n\t\tconst parsed = parse(yaml);\n\t\texpect(parsed).toHaveProperty(\"services\");\n\t\texpect(parsed).toHaveProperty(\"volumes\");\n\t\texpect(parsed).toHaveProperty(\"networks\");\n\n\t\t// Should have gateway + redis + n8n + postgresql (n8n requires postgresql)\n\t\texpect(parsed.services).toHaveProperty(\"openclaw-gateway\");\n\t\texpect(parsed.services).toHaveProperty(\"redis\");\n\t\texpect(parsed.services).toHaveProperty(\"n8n\");\n\t\texpect(parsed.services).toHaveProperty(\"postgresql\");\n\t});\n\n\tit(\"includes all service volumes in top-level volumes section\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\", \"n8n\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Collect all volume names from all resolved services\n\t\tconst serviceVolumeNames = new Set<string>();\n\t\tfor (const rs of resolved.services) {\n\t\t\tfor (const vol of rs.definition.volumes) {\n\t\t\t\tserviceVolumeNames.add(vol.name);\n\t\t\t}\n\t\t}\n\n\t\t// All service volumes must appear in top-level volumes\n\t\tfor (const volName of serviceVolumeNames) {\n\t\t\texpect(parsed.volumes).toHaveProperty(volName);\n\t\t}\n\n\t\t// Gateway uses bind-mount volumes (not in top-level volumes section)\n\t\tconst gwVols = parsed.services[\"openclaw-gateway\"].volumes as string[];\n\t\texpect(gwVols.some((v: string) => v.includes(\".openclaw\"))).toBe(true);\n\t});\n\n\tit(\"includes GPU passthrough when gpu=true and service requires it\", () => {\n\t\tconst resolved = resolve({ services: [\"ollama\"], skillPacks: [] });\n\n\t\t// Modify resolved output to simulate a GPU-required service\n\t\tconst gpuResolved: ResolverOutput = {\n\t\t\t...resolved,\n\t\t\tservices: resolved.services.map((s) => ({\n\t\t\t\t...s,\n\t\t\t\tdefinition: { ...s.definition, gpuRequired: true },\n\t\t\t})),\n\t\t};\n\n\t\tconst yaml = compose(gpuResolved, { ...defaultOptions, gpu: true });\n\t\tconst parsed = parse(yaml);\n\n\t\t// Ollama service should have GPU device reservation\n\t\tconst ollamaService = parsed.services.ollama;\n\t\texpect(ollamaService.deploy).toBeDefined();\n\t\texpect(ollamaService.deploy.resources.reservations.devices).toEqual([\n\t\t\t{\n\t\t\t\tdriver: \"nvidia\",\n\t\t\t\tcount: \"all\",\n\t\t\t\tcapabilities: [\"gpu\"],\n\t\t\t},\n\t\t]);\n\t});\n\n\tit(\"does not include GPU passthrough when gpu=false\", () => {\n\t\tconst resolved = resolve({ services: [\"ollama\"], skillPacks: [] });\n\n\t\t// Even if service requires GPU, gpu option is false\n\t\tconst gpuResolved: ResolverOutput = {\n\t\t\t...resolved,\n\t\t\tservices: resolved.services.map((s) => ({\n\t\t\t\t...s,\n\t\t\t\tdefinition: { ...s.definition, gpuRequired: true },\n\t\t\t})),\n\t\t};\n\n\t\tconst yaml = compose(gpuResolved, { ...defaultOptions, gpu: false });\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should NOT have deploy with GPU devices\n\t\tconst ollamaService = parsed.services.ollama;\n\t\texpect(ollamaService.deploy?.resources?.reservations?.devices).toBeUndefined();\n\t});\n\n\tit(\"does not map internal-only ports to host\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\"], skillPacks: [] });\n\n\t\t// Modify redis to have an internal-only port\n\t\tconst modifiedResolved: ResolverOutput = {\n\t\t\t...resolved,\n\t\t\tservices: resolved.services.map((s) => ({\n\t\t\t\t...s,\n\t\t\t\tdefinition: {\n\t\t\t\t\t...s.definition,\n\t\t\t\t\tports: [\n\t\t\t\t\t\t...s.definition.ports,\n\t\t\t\t\t\t{ host: 16379, container: 16379, description: \"Redis cluster bus\", exposed: false },\n\t\t\t\t\t],\n\t\t\t\t},\n\t\t\t})),\n\t\t};\n\n\t\tconst yaml = compose(modifiedResolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Should only have the exposed port, not the internal one\n\t\tconst redisPorts = parsed.services.redis.ports;\n\t\texpect(redisPorts).toHaveLength(1);\n\t\texpect(redisPorts[0]).toContain(\"6379\");\n\t\texpect(redisPorts[0]).not.toContain(\"16379\");\n\t});\n\n\tit(\"uses service_started condition for services without healthcheck\", () => {\n\t\tconst resolved = resolve({ services: [\"minio\"], skillPacks: [] });\n\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// Minio may or may not have a healthcheck – the gateway depends_on\n\t\t// condition should reflect the definition accurately\n\t\tconst gatewayDeps = parsed.services[\"openclaw-gateway\"].depends_on;\n\t\tfor (const [id, dep] of Object.entries(gatewayDeps)) {\n\t\t\tconst svc = resolved.services.find((s) => s.definition.id === id);\n\t\t\tif (svc?.definition.healthcheck) {\n\t\t\t\texpect((dep as { condition: string }).condition).toBe(\"service_healthy\");\n\t\t\t} else {\n\t\t\t\texpect((dep as { condition: string }).condition).toBe(\"service_started\");\n\t\t\t}\n\t\t}\n\t});\n\n\tit(\"gateway is always the first service in the output\", () => {\n\t\tconst resolved = resolve({ services: [\"redis\", \"ollama\"], skillPacks: [] });\n\t\tconst yaml = compose(resolved, defaultOptions);\n\t\tconst parsed = parse(yaml);\n\n\t\t// First key in services should be openclaw-gateway\n\t\tconst serviceKeys = Object.keys(parsed.services);\n\t\texpect(serviceKeys[0]).toBe(\"openclaw-gateway\");\n\t});\n});\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
- const require_vi_2VT5v0um = require("../vi.2VT5v0um-CRqXre87.cjs");
1
+ const require_test_CTcmp4Su = require("../test.CTcmp4Su-DlzTarwH.cjs");
2
2
  const require_deployers_strip_host_ports = require("./strip-host-ports.cjs");
3
3
  //#region src/deployers/strip-host-ports.test.ts
4
- require_vi_2VT5v0um.describe("stripHostPorts", () => {
5
- require_vi_2VT5v0um.it("strips host port from short syntax (host:container)", () => {
4
+ require_test_CTcmp4Su.describe("stripHostPorts", () => {
5
+ require_test_CTcmp4Su.it("strips host port from short syntax (host:container)", () => {
6
6
  const result = require_deployers_strip_host_ports.stripHostPorts(`
7
7
  services:
8
8
  web:
@@ -11,11 +11,11 @@ services:
11
11
  - "8080:80"
12
12
  - "443:443"
13
13
  `);
14
- require_vi_2VT5v0um.globalExpect(result).toContain("\"80\"");
15
- require_vi_2VT5v0um.globalExpect(result).toContain("\"443\"");
16
- require_vi_2VT5v0um.globalExpect(result).not.toContain("8080");
14
+ require_test_CTcmp4Su.globalExpect(result).toContain("\"80\"");
15
+ require_test_CTcmp4Su.globalExpect(result).toContain("\"443\"");
16
+ require_test_CTcmp4Su.globalExpect(result).not.toContain("8080");
17
17
  });
18
- require_vi_2VT5v0um.it("strips host IP and port from extended short syntax", () => {
18
+ require_test_CTcmp4Su.it("strips host IP and port from extended short syntax", () => {
19
19
  const result = require_deployers_strip_host_ports.stripHostPorts(`
20
20
  services:
21
21
  web:
@@ -23,11 +23,11 @@ services:
23
23
  ports:
24
24
  - "0.0.0.0:8080:80"
25
25
  `);
26
- require_vi_2VT5v0um.globalExpect(result).toContain("\"80\"");
27
- require_vi_2VT5v0um.globalExpect(result).not.toContain("8080");
28
- require_vi_2VT5v0um.globalExpect(result).not.toContain("0.0.0.0");
26
+ require_test_CTcmp4Su.globalExpect(result).toContain("\"80\"");
27
+ require_test_CTcmp4Su.globalExpect(result).not.toContain("8080");
28
+ require_test_CTcmp4Su.globalExpect(result).not.toContain("0.0.0.0");
29
29
  });
30
- require_vi_2VT5v0um.it("preserves protocol suffix", () => {
30
+ require_test_CTcmp4Su.it("preserves protocol suffix", () => {
31
31
  const result = require_deployers_strip_host_ports.stripHostPorts(`
32
32
  services:
33
33
  web:
@@ -36,13 +36,13 @@ services:
36
36
  - "8080:80/tcp"
37
37
  - "5353:53/udp"
38
38
  `);
39
- require_vi_2VT5v0um.globalExpect(result).toContain("80/tcp");
40
- require_vi_2VT5v0um.globalExpect(result).toContain("53/udp");
41
- require_vi_2VT5v0um.globalExpect(result).not.toContain("8080");
42
- require_vi_2VT5v0um.globalExpect(result).not.toContain("5353");
39
+ require_test_CTcmp4Su.globalExpect(result).toContain("80/tcp");
40
+ require_test_CTcmp4Su.globalExpect(result).toContain("53/udp");
41
+ require_test_CTcmp4Su.globalExpect(result).not.toContain("8080");
42
+ require_test_CTcmp4Su.globalExpect(result).not.toContain("5353");
43
43
  });
44
- require_vi_2VT5v0um.it("keeps container-only ports unchanged", () => {
45
- require_vi_2VT5v0um.globalExpect(require_deployers_strip_host_ports.stripHostPorts(`
44
+ require_test_CTcmp4Su.it("keeps container-only ports unchanged", () => {
45
+ require_test_CTcmp4Su.globalExpect(require_deployers_strip_host_ports.stripHostPorts(`
46
46
  services:
47
47
  web:
48
48
  image: nginx
@@ -50,7 +50,7 @@ services:
50
50
  - "80"
51
51
  `)).toContain("\"80\"");
52
52
  });
53
- require_vi_2VT5v0um.it("handles multiple services", () => {
53
+ require_test_CTcmp4Su.it("handles multiple services", () => {
54
54
  const result = require_deployers_strip_host_ports.stripHostPorts(`
55
55
  services:
56
56
  web:
@@ -66,17 +66,17 @@ services:
66
66
  ports:
67
67
  - "8888:8080"
68
68
  `);
69
- require_vi_2VT5v0um.globalExpect(result).toContain("\"80\"");
70
- require_vi_2VT5v0um.globalExpect(result).toContain("\"6379\"");
71
- require_vi_2VT5v0um.globalExpect(result).toContain("\"8080\"");
72
- require_vi_2VT5v0um.globalExpect(result).not.toContain("8888");
69
+ require_test_CTcmp4Su.globalExpect(result).toContain("\"80\"");
70
+ require_test_CTcmp4Su.globalExpect(result).toContain("\"6379\"");
71
+ require_test_CTcmp4Su.globalExpect(result).toContain("\"8080\"");
72
+ require_test_CTcmp4Su.globalExpect(result).not.toContain("8888");
73
73
  });
74
- require_vi_2VT5v0um.it("returns original YAML if no services section", () => {
74
+ require_test_CTcmp4Su.it("returns original YAML if no services section", () => {
75
75
  const yaml = `version: "3"`;
76
- require_vi_2VT5v0um.globalExpect(require_deployers_strip_host_ports.stripHostPorts(yaml)).toBe(yaml);
76
+ require_test_CTcmp4Su.globalExpect(require_deployers_strip_host_ports.stripHostPorts(yaml)).toBe(yaml);
77
77
  });
78
- require_vi_2VT5v0um.it("handles services with no ports", () => {
79
- require_vi_2VT5v0um.globalExpect(require_deployers_strip_host_ports.stripHostPorts(`
78
+ require_test_CTcmp4Su.it("handles services with no ports", () => {
79
+ require_test_CTcmp4Su.globalExpect(require_deployers_strip_host_ports.stripHostPorts(`
80
80
  services:
81
81
  web:
82
82
  image: nginx
@@ -1 +1 @@
1
- {"version":3,"file":"strip-host-ports.test.cjs","names":["describe","stripHostPorts"],"sources":["../../src/deployers/strip-host-ports.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { stripHostPorts } from \"./strip-host-ports.js\";\n\ndescribe(\"stripHostPorts\", () => {\n\tit(\"strips host port from short syntax (host:container)\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"8080:80\"\n - \"443:443\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t\texpect(result).toContain('\"443\"');\n\t\texpect(result).not.toContain(\"8080\");\n\t});\n\n\tit(\"strips host IP and port from extended short syntax\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"0.0.0.0:8080:80\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t\texpect(result).not.toContain(\"8080\");\n\t\texpect(result).not.toContain(\"0.0.0.0\");\n\t});\n\n\tit(\"preserves protocol suffix\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"8080:80/tcp\"\n - \"5353:53/udp\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain(\"80/tcp\");\n\t\texpect(result).toContain(\"53/udp\");\n\t\texpect(result).not.toContain(\"8080\");\n\t\texpect(result).not.toContain(\"5353\");\n\t});\n\n\tit(\"keeps container-only ports unchanged\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"80\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t});\n\n\tit(\"handles multiple services\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"8080:80\"\n redis:\n image: redis\n ports:\n - \"6379:6379\"\n searxng:\n image: searxng/searxng\n ports:\n - \"8888:8080\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t\texpect(result).toContain('\"6379\"');\n\t\texpect(result).toContain('\"8080\"');\n\t\texpect(result).not.toContain(\"8888\");\n\t});\n\n\tit(\"returns original YAML if no services section\", () => {\n\t\tconst yaml = `version: \"3\"`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toBe(yaml);\n\t});\n\n\tit(\"handles services with no ports\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain(\"nginx\");\n\t});\n});\n"],"mappings":";;;AAGAA,oBAAAA,SAAS,wBAAwB;AAChC,qBAAA,GAAG,6DAA6D;EAS/D,MAAM,SAASC,mCAAAA,eARF;;;;;;;EAQsB;AACnC,sBAAA,aAAO,OAAO,CAAC,UAAU,SAAO;AAChC,sBAAA,aAAO,OAAO,CAAC,UAAU,UAAQ;AACjC,sBAAA,aAAO,OAAO,CAAC,IAAI,UAAU,OAAO;GACnC;AAEF,qBAAA,GAAG,4DAA4D;EAQ9D,MAAM,SAASA,mCAAAA,eAPF;;;;;;EAOsB;AACnC,sBAAA,aAAO,OAAO,CAAC,UAAU,SAAO;AAChC,sBAAA,aAAO,OAAO,CAAC,IAAI,UAAU,OAAO;AACpC,sBAAA,aAAO,OAAO,CAAC,IAAI,UAAU,UAAU;GACtC;AAEF,qBAAA,GAAG,mCAAmC;EASrC,MAAM,SAASA,mCAAAA,eARF;;;;;;;EAQsB;AACnC,sBAAA,aAAO,OAAO,CAAC,UAAU,SAAS;AAClC,sBAAA,aAAO,OAAO,CAAC,UAAU,SAAS;AAClC,sBAAA,aAAO,OAAO,CAAC,IAAI,UAAU,OAAO;AACpC,sBAAA,aAAO,OAAO,CAAC,IAAI,UAAU,OAAO;GACnC;AAEF,qBAAA,GAAG,8CAA8C;AAShD,sBAAA,aADeA,mCAAAA,eAPF;;;;;;EAOsB,CACrB,CAAC,UAAU,SAAO;GAC/B;AAEF,qBAAA,GAAG,mCAAmC;EAgBrC,MAAM,SAASA,mCAAAA,eAfF;;;;;;;;;;;;;;EAesB;AACnC,sBAAA,aAAO,OAAO,CAAC,UAAU,SAAO;AAChC,sBAAA,aAAO,OAAO,CAAC,UAAU,WAAS;AAClC,sBAAA,aAAO,OAAO,CAAC,UAAU,WAAS;AAClC,sBAAA,aAAO,OAAO,CAAC,IAAI,UAAU,OAAO;GACnC;AAEF,qBAAA,GAAG,sDAAsD;EACxD,MAAM,OAAO;AAEb,sBAAA,aADeA,mCAAAA,eAAe,KAAK,CACrB,CAAC,KAAK,KAAK;GACxB;AAEF,qBAAA,GAAG,wCAAwC;AAO1C,sBAAA,aADeA,mCAAAA,eALF;;;;EAKsB,CACrB,CAAC,UAAU,QAAQ;GAChC;EACD"}
1
+ {"version":3,"file":"strip-host-ports.test.cjs","names":["describe","stripHostPorts"],"sources":["../../src/deployers/strip-host-ports.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\";\nimport { stripHostPorts } from \"./strip-host-ports.js\";\n\ndescribe(\"stripHostPorts\", () => {\n\tit(\"strips host port from short syntax (host:container)\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"8080:80\"\n - \"443:443\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t\texpect(result).toContain('\"443\"');\n\t\texpect(result).not.toContain(\"8080\");\n\t});\n\n\tit(\"strips host IP and port from extended short syntax\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"0.0.0.0:8080:80\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t\texpect(result).not.toContain(\"8080\");\n\t\texpect(result).not.toContain(\"0.0.0.0\");\n\t});\n\n\tit(\"preserves protocol suffix\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"8080:80/tcp\"\n - \"5353:53/udp\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain(\"80/tcp\");\n\t\texpect(result).toContain(\"53/udp\");\n\t\texpect(result).not.toContain(\"8080\");\n\t\texpect(result).not.toContain(\"5353\");\n\t});\n\n\tit(\"keeps container-only ports unchanged\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"80\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t});\n\n\tit(\"handles multiple services\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n ports:\n - \"8080:80\"\n redis:\n image: redis\n ports:\n - \"6379:6379\"\n searxng:\n image: searxng/searxng\n ports:\n - \"8888:8080\"\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain('\"80\"');\n\t\texpect(result).toContain('\"6379\"');\n\t\texpect(result).toContain('\"8080\"');\n\t\texpect(result).not.toContain(\"8888\");\n\t});\n\n\tit(\"returns original YAML if no services section\", () => {\n\t\tconst yaml = `version: \"3\"`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toBe(yaml);\n\t});\n\n\tit(\"handles services with no ports\", () => {\n\t\tconst yaml = `\nservices:\n web:\n image: nginx\n`;\n\t\tconst result = stripHostPorts(yaml);\n\t\texpect(result).toContain(\"nginx\");\n\t});\n});\n"],"mappings":";;;AAGAA,sBAAAA,SAAS,wBAAwB;AAChC,uBAAA,GAAG,6DAA6D;EAS/D,MAAM,SAASC,mCAAAA,eARF;;;;;;;EAQsB;AACnC,wBAAA,aAAO,OAAO,CAAC,UAAU,SAAO;AAChC,wBAAA,aAAO,OAAO,CAAC,UAAU,UAAQ;AACjC,wBAAA,aAAO,OAAO,CAAC,IAAI,UAAU,OAAO;GACnC;AAEF,uBAAA,GAAG,4DAA4D;EAQ9D,MAAM,SAASA,mCAAAA,eAPF;;;;;;EAOsB;AACnC,wBAAA,aAAO,OAAO,CAAC,UAAU,SAAO;AAChC,wBAAA,aAAO,OAAO,CAAC,IAAI,UAAU,OAAO;AACpC,wBAAA,aAAO,OAAO,CAAC,IAAI,UAAU,UAAU;GACtC;AAEF,uBAAA,GAAG,mCAAmC;EASrC,MAAM,SAASA,mCAAAA,eARF;;;;;;;EAQsB;AACnC,wBAAA,aAAO,OAAO,CAAC,UAAU,SAAS;AAClC,wBAAA,aAAO,OAAO,CAAC,UAAU,SAAS;AAClC,wBAAA,aAAO,OAAO,CAAC,IAAI,UAAU,OAAO;AACpC,wBAAA,aAAO,OAAO,CAAC,IAAI,UAAU,OAAO;GACnC;AAEF,uBAAA,GAAG,8CAA8C;AAShD,wBAAA,aADeA,mCAAAA,eAPF;;;;;;EAOsB,CACrB,CAAC,UAAU,SAAO;GAC/B;AAEF,uBAAA,GAAG,mCAAmC;EAgBrC,MAAM,SAASA,mCAAAA,eAfF;;;;;;;;;;;;;;EAesB;AACnC,wBAAA,aAAO,OAAO,CAAC,UAAU,SAAO;AAChC,wBAAA,aAAO,OAAO,CAAC,UAAU,WAAS;AAClC,wBAAA,aAAO,OAAO,CAAC,UAAU,WAAS;AAClC,wBAAA,aAAO,OAAO,CAAC,IAAI,UAAU,OAAO;GACnC;AAEF,uBAAA,GAAG,sDAAsD;EACxD,MAAM,OAAO;AAEb,wBAAA,aADeA,mCAAAA,eAAe,KAAK,CACrB,CAAC,KAAK,KAAK;GACxB;AAEF,uBAAA,GAAG,wCAAwC;AAO1C,wBAAA,aADeA,mCAAAA,eALF;;;;EAKsB,CACrB,CAAC,UAAU,QAAQ;GAChC;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 "../test.CTcmp4Su-ClCHJ3FA.mjs";
2
2
  import { stripHostPorts } from "./strip-host-ports.mjs";
3
3
  //#region src/deployers/strip-host-ports.test.ts
4
4
  describe("stripHostPorts", () => {
package/dist/generate.cjs CHANGED
@@ -1,12 +1,13 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_skills = require("./skills-BlzpHmpH.cjs");
3
- const require_bare_metal_partition = require("./bare-metal-partition.cjs");
4
- const require_resolver = require("./resolver.cjs");
5
3
  const require_generators_postgres_init = require("./generators/postgres-init.cjs");
6
4
  const require_composer = require("./composer.cjs");
5
+ const require_resolver = require("./resolver.cjs");
6
+ const require_bare_metal_partition = require("./bare-metal-partition.cjs");
7
7
  const require_errors = require("./errors.cjs");
8
8
  const require_generators_bare_metal_install = require("./generators/bare-metal-install.cjs");
9
9
  const require_generators_caddy = require("./generators/caddy.cjs");
10
+ const require_generators_clone_repos = require("./generators/clone-repos.cjs");
10
11
  const require_generators_cloud_init = require("./generators/cloud-init.cjs");
11
12
  const require_generators_env = require("./generators/env.cjs");
12
13
  const require_generators_get_shit_done = require("./generators/get-shit-done.cjs");
@@ -101,7 +102,8 @@ function generate(rawInput) {
101
102
  ".env.local",
102
103
  ".env.*.local",
103
104
  "*.log",
104
- "docker-compose.override.yml"
105
+ "docker-compose.override.yml",
106
+ "repos/"
105
107
  ].join("\n");
106
108
  const manifestFiles = require_generators_stack_manifest.generateStackManifest(resolved, input);
107
109
  for (const [path, content] of Object.entries(manifestFiles)) files[path] = content;
@@ -120,8 +122,10 @@ function generate(rawInput) {
120
122
  hasNativeServices: isBareMetal && nativeServices.length > 0,
121
123
  openclawInstallMethod: input.openclawInstallMethod
122
124
  });
123
- const scripts = require_generators_scripts.generateScripts();
125
+ const scripts = require_generators_scripts.generateScripts({ hasGitServices: resolvedForCompose.services.some((s) => s.definition.gitSource) });
124
126
  for (const [path, content] of Object.entries(scripts)) files[path] = content;
127
+ const cloneScripts = require_generators_clone_repos.generateCloneScripts(resolvedForCompose);
128
+ for (const [path, content] of Object.entries(cloneScripts)) files[path] = content;
125
129
  const healthCheckFiles = require_generators_health_check.generateHealthCheck(resolved, {
126
130
  projectName: input.projectName,
127
131
  deploymentType: input.deploymentType