@cicore/cli 1.0.0

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 (337) hide show
  1. package/bin/ci.js +13 -0
  2. package/dist/commands/addon/api-actions.d.ts +45 -0
  3. package/dist/commands/addon/api-actions.d.ts.map +1 -0
  4. package/dist/commands/addon/api-actions.js +281 -0
  5. package/dist/commands/addon/api-actions.js.map +1 -0
  6. package/dist/commands/addon/build.d.ts +11 -0
  7. package/dist/commands/addon/build.d.ts.map +1 -0
  8. package/dist/commands/addon/build.js +182 -0
  9. package/dist/commands/addon/build.js.map +1 -0
  10. package/dist/commands/addon/create.d.ts +11 -0
  11. package/dist/commands/addon/create.d.ts.map +1 -0
  12. package/dist/commands/addon/create.js +1186 -0
  13. package/dist/commands/addon/create.js.map +1 -0
  14. package/dist/commands/addon/delete.d.ts +13 -0
  15. package/dist/commands/addon/delete.d.ts.map +1 -0
  16. package/dist/commands/addon/delete.js +83 -0
  17. package/dist/commands/addon/delete.js.map +1 -0
  18. package/dist/commands/addon/deploy.d.ts +27 -0
  19. package/dist/commands/addon/deploy.d.ts.map +1 -0
  20. package/dist/commands/addon/deploy.js +459 -0
  21. package/dist/commands/addon/deploy.js.map +1 -0
  22. package/dist/commands/addon/dev-deploy.d.ts +31 -0
  23. package/dist/commands/addon/dev-deploy.d.ts.map +1 -0
  24. package/dist/commands/addon/dev-deploy.js +128 -0
  25. package/dist/commands/addon/dev-deploy.js.map +1 -0
  26. package/dist/commands/addon/dev.d.ts +36 -0
  27. package/dist/commands/addon/dev.d.ts.map +1 -0
  28. package/dist/commands/addon/dev.js +323 -0
  29. package/dist/commands/addon/dev.js.map +1 -0
  30. package/dist/commands/addon/extract-classes.d.ts +23 -0
  31. package/dist/commands/addon/extract-classes.d.ts.map +1 -0
  32. package/dist/commands/addon/extract-classes.js +281 -0
  33. package/dist/commands/addon/extract-classes.js.map +1 -0
  34. package/dist/commands/addon/generate-safelist.d.ts +24 -0
  35. package/dist/commands/addon/generate-safelist.d.ts.map +1 -0
  36. package/dist/commands/addon/generate-safelist.js +276 -0
  37. package/dist/commands/addon/generate-safelist.js.map +1 -0
  38. package/dist/commands/addon/index.d.ts +19 -0
  39. package/dist/commands/addon/index.d.ts.map +1 -0
  40. package/dist/commands/addon/index.js +296 -0
  41. package/dist/commands/addon/index.js.map +1 -0
  42. package/dist/commands/addon/init-repo.d.ts +25 -0
  43. package/dist/commands/addon/init-repo.d.ts.map +1 -0
  44. package/dist/commands/addon/init-repo.js +171 -0
  45. package/dist/commands/addon/init-repo.js.map +1 -0
  46. package/dist/commands/addon/install.d.ts +23 -0
  47. package/dist/commands/addon/install.d.ts.map +1 -0
  48. package/dist/commands/addon/install.js +84 -0
  49. package/dist/commands/addon/install.js.map +1 -0
  50. package/dist/commands/addon/list.d.ts +10 -0
  51. package/dist/commands/addon/list.d.ts.map +1 -0
  52. package/dist/commands/addon/list.js +102 -0
  53. package/dist/commands/addon/list.js.map +1 -0
  54. package/dist/commands/addon/manifest-refresh.d.ts +17 -0
  55. package/dist/commands/addon/manifest-refresh.d.ts.map +1 -0
  56. package/dist/commands/addon/manifest-refresh.js +48 -0
  57. package/dist/commands/addon/manifest-refresh.js.map +1 -0
  58. package/dist/commands/addon/migrate.d.ts +40 -0
  59. package/dist/commands/addon/migrate.d.ts.map +1 -0
  60. package/dist/commands/addon/migrate.js +236 -0
  61. package/dist/commands/addon/migrate.js.map +1 -0
  62. package/dist/commands/addon/publish.d.ts +33 -0
  63. package/dist/commands/addon/publish.d.ts.map +1 -0
  64. package/dist/commands/addon/publish.js +236 -0
  65. package/dist/commands/addon/publish.js.map +1 -0
  66. package/dist/commands/addon/scaffold-quality.d.ts +21 -0
  67. package/dist/commands/addon/scaffold-quality.d.ts.map +1 -0
  68. package/dist/commands/addon/scaffold-quality.js +90 -0
  69. package/dist/commands/addon/scaffold-quality.js.map +1 -0
  70. package/dist/commands/addon/sign.d.ts +9 -0
  71. package/dist/commands/addon/sign.d.ts.map +1 -0
  72. package/dist/commands/addon/sign.js +83 -0
  73. package/dist/commands/addon/sign.js.map +1 -0
  74. package/dist/commands/addon/toggle.d.ts +6 -0
  75. package/dist/commands/addon/toggle.d.ts.map +1 -0
  76. package/dist/commands/addon/toggle.js +46 -0
  77. package/dist/commands/addon/toggle.js.map +1 -0
  78. package/dist/commands/agent/index.d.ts +34 -0
  79. package/dist/commands/agent/index.d.ts.map +1 -0
  80. package/dist/commands/agent/index.js +564 -0
  81. package/dist/commands/agent/index.js.map +1 -0
  82. package/dist/commands/brand/index.d.ts +54 -0
  83. package/dist/commands/brand/index.d.ts.map +1 -0
  84. package/dist/commands/brand/index.js +367 -0
  85. package/dist/commands/brand/index.js.map +1 -0
  86. package/dist/commands/build/index.d.ts +53 -0
  87. package/dist/commands/build/index.d.ts.map +1 -0
  88. package/dist/commands/build/index.js +726 -0
  89. package/dist/commands/build/index.js.map +1 -0
  90. package/dist/commands/cache/flush-local.d.ts +31 -0
  91. package/dist/commands/cache/flush-local.d.ts.map +1 -0
  92. package/dist/commands/cache/flush-local.js +161 -0
  93. package/dist/commands/cache/flush-local.js.map +1 -0
  94. package/dist/commands/cache/index.d.ts +14 -0
  95. package/dist/commands/cache/index.d.ts.map +1 -0
  96. package/dist/commands/cache/index.js +453 -0
  97. package/dist/commands/cache/index.js.map +1 -0
  98. package/dist/commands/check/index.d.ts +8 -0
  99. package/dist/commands/check/index.d.ts.map +1 -0
  100. package/dist/commands/check/index.js +1316 -0
  101. package/dist/commands/check/index.js.map +1 -0
  102. package/dist/commands/cloudflare/index.d.ts +8 -0
  103. package/dist/commands/cloudflare/index.d.ts.map +1 -0
  104. package/dist/commands/cloudflare/index.js +453 -0
  105. package/dist/commands/cloudflare/index.js.map +1 -0
  106. package/dist/commands/core/create.d.ts +12 -0
  107. package/dist/commands/core/create.d.ts.map +1 -0
  108. package/dist/commands/core/create.js +206 -0
  109. package/dist/commands/core/create.js.map +1 -0
  110. package/dist/commands/core/delete.d.ts +11 -0
  111. package/dist/commands/core/delete.d.ts.map +1 -0
  112. package/dist/commands/core/delete.js +64 -0
  113. package/dist/commands/core/delete.js.map +1 -0
  114. package/dist/commands/core/env.d.ts +12 -0
  115. package/dist/commands/core/env.d.ts.map +1 -0
  116. package/dist/commands/core/env.js +95 -0
  117. package/dist/commands/core/env.js.map +1 -0
  118. package/dist/commands/core/health.d.ts +6 -0
  119. package/dist/commands/core/health.d.ts.map +1 -0
  120. package/dist/commands/core/health.js +215 -0
  121. package/dist/commands/core/health.js.map +1 -0
  122. package/dist/commands/core/index.d.ts +15 -0
  123. package/dist/commands/core/index.d.ts.map +1 -0
  124. package/dist/commands/core/index.js +86 -0
  125. package/dist/commands/core/index.js.map +1 -0
  126. package/dist/commands/core/list.d.ts +11 -0
  127. package/dist/commands/core/list.d.ts.map +1 -0
  128. package/dist/commands/core/list.js +58 -0
  129. package/dist/commands/core/list.js.map +1 -0
  130. package/dist/commands/core/rebuild.d.ts +13 -0
  131. package/dist/commands/core/rebuild.d.ts.map +1 -0
  132. package/dist/commands/core/rebuild.js +119 -0
  133. package/dist/commands/core/rebuild.js.map +1 -0
  134. package/dist/commands/db/index.d.ts +23 -0
  135. package/dist/commands/db/index.d.ts.map +1 -0
  136. package/dist/commands/db/index.js +355 -0
  137. package/dist/commands/db/index.js.map +1 -0
  138. package/dist/commands/db/promote-silo.d.ts +320 -0
  139. package/dist/commands/db/promote-silo.d.ts.map +1 -0
  140. package/dist/commands/db/promote-silo.js +930 -0
  141. package/dist/commands/db/promote-silo.js.map +1 -0
  142. package/dist/commands/db/relocate.d.ts +41 -0
  143. package/dist/commands/db/relocate.d.ts.map +1 -0
  144. package/dist/commands/db/relocate.js +482 -0
  145. package/dist/commands/db/relocate.js.map +1 -0
  146. package/dist/commands/db/rollback-silo.d.ts +44 -0
  147. package/dist/commands/db/rollback-silo.d.ts.map +1 -0
  148. package/dist/commands/db/rollback-silo.js +402 -0
  149. package/dist/commands/db/rollback-silo.js.map +1 -0
  150. package/dist/commands/deploy/index.d.ts +26 -0
  151. package/dist/commands/deploy/index.d.ts.map +1 -0
  152. package/dist/commands/deploy/index.js +107 -0
  153. package/dist/commands/deploy/index.js.map +1 -0
  154. package/dist/commands/devops/index.d.ts +6 -0
  155. package/dist/commands/devops/index.d.ts.map +1 -0
  156. package/dist/commands/devops/index.js +220 -0
  157. package/dist/commands/devops/index.js.map +1 -0
  158. package/dist/commands/domain/index.d.ts +8 -0
  159. package/dist/commands/domain/index.d.ts.map +1 -0
  160. package/dist/commands/domain/index.js +386 -0
  161. package/dist/commands/domain/index.js.map +1 -0
  162. package/dist/commands/image/index.d.ts +8 -0
  163. package/dist/commands/image/index.d.ts.map +1 -0
  164. package/dist/commands/image/index.js +308 -0
  165. package/dist/commands/image/index.js.map +1 -0
  166. package/dist/commands/install/factory-reset.d.ts +21 -0
  167. package/dist/commands/install/factory-reset.d.ts.map +1 -0
  168. package/dist/commands/install/factory-reset.js +83 -0
  169. package/dist/commands/install/factory-reset.js.map +1 -0
  170. package/dist/commands/install/index.d.ts +17 -0
  171. package/dist/commands/install/index.d.ts.map +1 -0
  172. package/dist/commands/install/index.js +44 -0
  173. package/dist/commands/install/index.js.map +1 -0
  174. package/dist/commands/install/install.d.ts +35 -0
  175. package/dist/commands/install/install.d.ts.map +1 -0
  176. package/dist/commands/install/install.js +171 -0
  177. package/dist/commands/install/install.js.map +1 -0
  178. package/dist/commands/login/index.d.ts +15 -0
  179. package/dist/commands/login/index.d.ts.map +1 -0
  180. package/dist/commands/login/index.js +58 -0
  181. package/dist/commands/login/index.js.map +1 -0
  182. package/dist/commands/nginx/index.d.ts +11 -0
  183. package/dist/commands/nginx/index.d.ts.map +1 -0
  184. package/dist/commands/nginx/index.js +580 -0
  185. package/dist/commands/nginx/index.js.map +1 -0
  186. package/dist/commands/server/bootstrap.d.ts +25 -0
  187. package/dist/commands/server/bootstrap.d.ts.map +1 -0
  188. package/dist/commands/server/bootstrap.js +260 -0
  189. package/dist/commands/server/bootstrap.js.map +1 -0
  190. package/dist/commands/server/index.d.ts +8 -0
  191. package/dist/commands/server/index.d.ts.map +1 -0
  192. package/dist/commands/server/index.js +2524 -0
  193. package/dist/commands/server/index.js.map +1 -0
  194. package/dist/commands/setup/index.d.ts +34 -0
  195. package/dist/commands/setup/index.d.ts.map +1 -0
  196. package/dist/commands/setup/index.js +423 -0
  197. package/dist/commands/setup/index.js.map +1 -0
  198. package/dist/commands/ssl/index.d.ts +8 -0
  199. package/dist/commands/ssl/index.d.ts.map +1 -0
  200. package/dist/commands/ssl/index.js +275 -0
  201. package/dist/commands/ssl/index.js.map +1 -0
  202. package/dist/commands/superadmin/index.d.ts +16 -0
  203. package/dist/commands/superadmin/index.d.ts.map +1 -0
  204. package/dist/commands/superadmin/index.js +81 -0
  205. package/dist/commands/superadmin/index.js.map +1 -0
  206. package/dist/commands/tenant/index.d.ts +6 -0
  207. package/dist/commands/tenant/index.d.ts.map +1 -0
  208. package/dist/commands/tenant/index.js +192 -0
  209. package/dist/commands/tenant/index.js.map +1 -0
  210. package/dist/index.d.ts +11 -0
  211. package/dist/index.d.ts.map +1 -0
  212. package/dist/index.js +107 -0
  213. package/dist/index.js.map +1 -0
  214. package/dist/lib/addon-sign.d.ts +23 -0
  215. package/dist/lib/addon-sign.d.ts.map +1 -0
  216. package/dist/lib/addon-sign.js +39 -0
  217. package/dist/lib/addon-sign.js.map +1 -0
  218. package/dist/lib/addon-sign.test.d.ts +2 -0
  219. package/dist/lib/addon-sign.test.d.ts.map +1 -0
  220. package/dist/lib/addon-sign.test.js +27 -0
  221. package/dist/lib/addon-sign.test.js.map +1 -0
  222. package/dist/lib/cdn.d.ts +25 -0
  223. package/dist/lib/cdn.d.ts.map +1 -0
  224. package/dist/lib/cdn.js +131 -0
  225. package/dist/lib/cdn.js.map +1 -0
  226. package/dist/lib/cloudflare.d.ts +133 -0
  227. package/dist/lib/cloudflare.d.ts.map +1 -0
  228. package/dist/lib/cloudflare.js +435 -0
  229. package/dist/lib/cloudflare.js.map +1 -0
  230. package/dist/lib/config.d.ts +96 -0
  231. package/dist/lib/config.d.ts.map +1 -0
  232. package/dist/lib/config.js +132 -0
  233. package/dist/lib/config.js.map +1 -0
  234. package/dist/lib/env.d.ts +8 -0
  235. package/dist/lib/env.d.ts.map +1 -0
  236. package/dist/lib/env.js +64 -0
  237. package/dist/lib/env.js.map +1 -0
  238. package/dist/lib/hosts.d.ts +194 -0
  239. package/dist/lib/hosts.d.ts.map +1 -0
  240. package/dist/lib/hosts.js +183 -0
  241. package/dist/lib/hosts.js.map +1 -0
  242. package/dist/lib/logger.d.ts +68 -0
  243. package/dist/lib/logger.d.ts.map +1 -0
  244. package/dist/lib/logger.js +130 -0
  245. package/dist/lib/logger.js.map +1 -0
  246. package/dist/lib/nginx-config.d.ts +78 -0
  247. package/dist/lib/nginx-config.d.ts.map +1 -0
  248. package/dist/lib/nginx-config.js +736 -0
  249. package/dist/lib/nginx-config.js.map +1 -0
  250. package/dist/lib/ops/addon-dev.d.ts +93 -0
  251. package/dist/lib/ops/addon-dev.d.ts.map +1 -0
  252. package/dist/lib/ops/addon-dev.js +237 -0
  253. package/dist/lib/ops/addon-dev.js.map +1 -0
  254. package/dist/lib/ops/addon-quality.d.ts +38 -0
  255. package/dist/lib/ops/addon-quality.d.ts.map +1 -0
  256. package/dist/lib/ops/addon-quality.js +338 -0
  257. package/dist/lib/ops/addon-quality.js.map +1 -0
  258. package/dist/lib/ops/addon-routes.d.ts +49 -0
  259. package/dist/lib/ops/addon-routes.d.ts.map +1 -0
  260. package/dist/lib/ops/addon-routes.js +189 -0
  261. package/dist/lib/ops/addon-routes.js.map +1 -0
  262. package/dist/lib/ops/addon.d.ts +120 -0
  263. package/dist/lib/ops/addon.d.ts.map +1 -0
  264. package/dist/lib/ops/addon.js +260 -0
  265. package/dist/lib/ops/addon.js.map +1 -0
  266. package/dist/lib/ops/cdn.d.ts +87 -0
  267. package/dist/lib/ops/cdn.d.ts.map +1 -0
  268. package/dist/lib/ops/cdn.js +170 -0
  269. package/dist/lib/ops/cdn.js.map +1 -0
  270. package/dist/lib/ops/cf.d.ts +36 -0
  271. package/dist/lib/ops/cf.d.ts.map +1 -0
  272. package/dist/lib/ops/cf.js +114 -0
  273. package/dist/lib/ops/cf.js.map +1 -0
  274. package/dist/lib/ops/compose.d.ts +95 -0
  275. package/dist/lib/ops/compose.d.ts.map +1 -0
  276. package/dist/lib/ops/compose.js +165 -0
  277. package/dist/lib/ops/compose.js.map +1 -0
  278. package/dist/lib/ops/core.d.ts +117 -0
  279. package/dist/lib/ops/core.d.ts.map +1 -0
  280. package/dist/lib/ops/core.js +322 -0
  281. package/dist/lib/ops/core.js.map +1 -0
  282. package/dist/lib/ops/db.d.ts +116 -0
  283. package/dist/lib/ops/db.d.ts.map +1 -0
  284. package/dist/lib/ops/db.js +351 -0
  285. package/dist/lib/ops/db.js.map +1 -0
  286. package/dist/lib/ops/dns.d.ts +111 -0
  287. package/dist/lib/ops/dns.d.ts.map +1 -0
  288. package/dist/lib/ops/dns.js +306 -0
  289. package/dist/lib/ops/dns.js.map +1 -0
  290. package/dist/lib/ops/image.d.ts +94 -0
  291. package/dist/lib/ops/image.d.ts.map +1 -0
  292. package/dist/lib/ops/image.js +159 -0
  293. package/dist/lib/ops/image.js.map +1 -0
  294. package/dist/lib/ops/nginx.d.ts +114 -0
  295. package/dist/lib/ops/nginx.d.ts.map +1 -0
  296. package/dist/lib/ops/nginx.js +388 -0
  297. package/dist/lib/ops/nginx.js.map +1 -0
  298. package/dist/lib/ops/redis.d.ts +7 -0
  299. package/dist/lib/ops/redis.d.ts.map +1 -0
  300. package/dist/lib/ops/redis.js +35 -0
  301. package/dist/lib/ops/redis.js.map +1 -0
  302. package/dist/lib/ops/ssh.d.ts +127 -0
  303. package/dist/lib/ops/ssh.d.ts.map +1 -0
  304. package/dist/lib/ops/ssh.js +269 -0
  305. package/dist/lib/ops/ssh.js.map +1 -0
  306. package/dist/lib/prompts.d.ts +46 -0
  307. package/dist/lib/prompts.d.ts.map +1 -0
  308. package/dist/lib/prompts.js +113 -0
  309. package/dist/lib/prompts.js.map +1 -0
  310. package/dist/lib/sast.d.ts +43 -0
  311. package/dist/lib/sast.d.ts.map +1 -0
  312. package/dist/lib/sast.js +79 -0
  313. package/dist/lib/sast.js.map +1 -0
  314. package/dist/lib/sast.test.d.ts +2 -0
  315. package/dist/lib/sast.test.d.ts.map +1 -0
  316. package/dist/lib/sast.test.js +33 -0
  317. package/dist/lib/sast.test.js.map +1 -0
  318. package/dist/lib/shell.d.ts +61 -0
  319. package/dist/lib/shell.d.ts.map +1 -0
  320. package/dist/lib/shell.js +183 -0
  321. package/dist/lib/shell.js.map +1 -0
  322. package/dist/lib/ssh-config.d.ts +37 -0
  323. package/dist/lib/ssh-config.d.ts.map +1 -0
  324. package/dist/lib/ssh-config.js +122 -0
  325. package/dist/lib/ssh-config.js.map +1 -0
  326. package/dist/lib/tenant-scope.d.ts +38 -0
  327. package/dist/lib/tenant-scope.d.ts.map +1 -0
  328. package/dist/lib/tenant-scope.js +129 -0
  329. package/dist/lib/tenant-scope.js.map +1 -0
  330. package/dist/lib/tenant-scope.test.d.ts +2 -0
  331. package/dist/lib/tenant-scope.test.d.ts.map +1 -0
  332. package/dist/lib/tenant-scope.test.js +223 -0
  333. package/dist/lib/tenant-scope.test.js.map +1 -0
  334. package/package.json +58 -0
  335. package/templates/bootstrap/.env.template +54 -0
  336. package/templates/bootstrap/docker-compose.yml +145 -0
  337. package/templates/vhost.conf.tmpl +446 -0
@@ -0,0 +1,306 @@
1
+ /**
2
+ * CiCore CLI — DNS / TLS provider abstraction
3
+ *
4
+ * Vendor-neutral {@link DnsProvider} interface + a Cloudflare implementation.
5
+ * The interface exists so a second vendor (Route53, …) can slot in later
6
+ * without touching callers (see brand-layer plan §K2 / §8). For now the only
7
+ * implementation is {@link CloudflareDnsProvider}.
8
+ *
9
+ * Two routing modes — both first-class, both modelled by the control plane
10
+ * (`cp_domains.dns_mode`):
11
+ *
12
+ * - `owned_zone` — the hostname lives in a zone *we* manage (e.g.
13
+ * `lab.multinodex.com`). We upsert a proxied CNAME → our origin target;
14
+ * TLS is covered by the zone's Universal / Advanced certificate. The
15
+ * zone id is resolved from the hostname at call time (a single host can
16
+ * serve several owned zones — mkt fronts both multinodex.com and
17
+ * randevu360.net — so we can't rely on `cfg.cfZoneId` alone).
18
+ *
19
+ * - `custom_hostname` (PRIMARY) — a domain the *customer* owns (BYO domain,
20
+ * Shopify/Webflow model). We register it as a Cloudflare for SaaS custom
21
+ * hostname in our SaaS zone (`cfg.cfZoneId`); DV TLS is issued + renewed
22
+ * automatically, and the customer points a CNAME at our fallback origin.
23
+ * This retires the per-host tunnel-ingress + `api-lab` SSL-depth hack:
24
+ * deep subdomains (`api.lab.x`) that Universal SSL's single-level wildcard
25
+ * can't cover get their own per-hostname cert for free.
26
+ *
27
+ * All calls use the host's scoped token `cfg.cfApiToken` (Bearer) — NOT the
28
+ * stored-config token that `lib/cloudflare.ts` reads. Keep new DNS work here,
29
+ * on the host-aware path. Every mutation honours `--dry-run`.
30
+ */
31
+ import { isDryRun } from '../config.js';
32
+ import { log } from '../logger.js';
33
+ const OK_RESULT = Object.freeze({ success: true, errorMessage: '' });
34
+ /** Construct the configured provider for a host. Cloudflare-only today. */
35
+ export function createDnsProvider(cfg) {
36
+ return new CloudflareDnsProvider(cfg);
37
+ }
38
+ // ─────────────────────────────────────────────────────────────────────────
39
+ // Cloudflare implementation
40
+ // ─────────────────────────────────────────────────────────────────────────
41
+ const CF_API_BASE = 'https://api.cloudflare.com/client/v4';
42
+ const REQUEST_TIMEOUT_MS = 30_000;
43
+ class CloudflareDnsProvider {
44
+ cfg;
45
+ constructor(cfg) {
46
+ this.cfg = cfg;
47
+ }
48
+ async detectMode(hostname) {
49
+ const zoneId = await this.resolveZoneId(hostname);
50
+ return zoneId ? 'owned_zone' : 'custom_hostname';
51
+ }
52
+ async ensureHost(req) {
53
+ return req.mode === 'owned_zone'
54
+ ? this.ensureOwnedZone(req)
55
+ : this.ensureCustomHostname(req);
56
+ }
57
+ async getHostState(hostname, mode) {
58
+ if (mode === 'owned_zone') {
59
+ const zoneId = await this.resolveZoneId(hostname);
60
+ if (!zoneId)
61
+ return this.failState(hostname, mode, `no managed zone found for ${hostname}`);
62
+ const record = await this.findRecord(zoneId, hostname);
63
+ if (!record)
64
+ return this.failState(hostname, mode, `no DNS record for ${hostname}`);
65
+ return Object.freeze({
66
+ success: true,
67
+ hostname,
68
+ mode,
69
+ routingStatus: 'active',
70
+ sslStatus: 'active', // covered by zone Universal/ACM cert
71
+ cfZoneId: zoneId,
72
+ cnameTarget: record.content,
73
+ errorMessage: '',
74
+ });
75
+ }
76
+ const existing = await this.findCustomHostname(hostname);
77
+ if (!existing)
78
+ return this.failState(hostname, mode, `no custom hostname for ${hostname}`);
79
+ return this.customHostnameToState(hostname, existing);
80
+ }
81
+ async removeHost(hostname, mode) {
82
+ if (mode === 'owned_zone') {
83
+ const zoneId = await this.resolveZoneId(hostname);
84
+ if (!zoneId)
85
+ return OK_RESULT; // nothing to remove
86
+ const record = await this.findRecord(zoneId, hostname);
87
+ if (!record)
88
+ return OK_RESULT;
89
+ if (isDryRun()) {
90
+ log.debug(`[dry-run] DELETE dns_record ${hostname} (${record.id}) in zone ${zoneId}`);
91
+ return OK_RESULT;
92
+ }
93
+ const res = await this.cfFetch(`/zones/${zoneId}/dns_records/${record.id}`, { method: 'DELETE' });
94
+ return res.success ? OK_RESULT : Object.freeze({ success: false, errorMessage: res.errorMessage });
95
+ }
96
+ const existing = await this.findCustomHostname(hostname);
97
+ if (!existing)
98
+ return OK_RESULT;
99
+ if (isDryRun()) {
100
+ log.debug(`[dry-run] DELETE custom_hostname ${hostname} (${existing.id})`);
101
+ return OK_RESULT;
102
+ }
103
+ const res = await this.cfFetch(`/zones/${this.cfg.cfZoneId}/custom_hostnames/${existing.id}`, { method: 'DELETE' });
104
+ return res.success ? OK_RESULT : Object.freeze({ success: false, errorMessage: res.errorMessage });
105
+ }
106
+ async ensureFallbackOrigin(originHostname) {
107
+ log.info(`[dns] ensure SaaS fallback origin → ${originHostname} (zone ${this.cfg.cfZoneId})`);
108
+ if (isDryRun()) {
109
+ log.debug(`[dry-run] PUT custom_hostnames/fallback_origin { origin: ${originHostname} }`);
110
+ return OK_RESULT;
111
+ }
112
+ const res = await this.cfFetch(`/zones/${this.cfg.cfZoneId}/custom_hostnames/fallback_origin`, { method: 'PUT', body: { origin: originHostname } });
113
+ return res.success ? OK_RESULT : Object.freeze({ success: false, errorMessage: res.errorMessage });
114
+ }
115
+ // ── owned_zone ───────────────────────────────────────────────────────
116
+ async ensureOwnedZone(req) {
117
+ const { hostname, originTarget } = req;
118
+ const zoneId = await this.resolveZoneId(hostname);
119
+ if (!zoneId) {
120
+ return this.failState(hostname, 'owned_zone', `no Cloudflare zone manages '${hostname}'. Use custom_hostname mode for BYO domains, ` +
121
+ `or add the zone to the account.`);
122
+ }
123
+ log.info(`[dns] owned_zone: ${hostname} → CNAME ${originTarget} (proxied, zone ${zoneId})`);
124
+ if (isDryRun()) {
125
+ log.debug(`[dry-run] upsert proxied CNAME ${hostname} → ${originTarget}`);
126
+ return this.okOwnedState(hostname, zoneId, originTarget);
127
+ }
128
+ const existing = await this.findRecord(zoneId, hostname);
129
+ const body = { type: 'CNAME', name: hostname, content: originTarget, proxied: true, ttl: 1 };
130
+ const res = existing
131
+ ? await this.cfFetch(`/zones/${zoneId}/dns_records/${existing.id}`, { method: 'PUT', body })
132
+ : await this.cfFetch(`/zones/${zoneId}/dns_records`, { method: 'POST', body });
133
+ if (!res.success)
134
+ return this.failState(hostname, 'owned_zone', res.errorMessage);
135
+ return this.okOwnedState(hostname, zoneId, originTarget);
136
+ }
137
+ okOwnedState(hostname, zoneId, cnameTarget) {
138
+ // Universal SSL covers the apex + one wildcard level. A deeper subdomain
139
+ // (api.lab.x) is NOT covered by `*.x` — warn rather than silently claim
140
+ // active, so the operator knows to use custom_hostname or ACM there.
141
+ const sslStatus = isCoveredByUniversalSsl(hostname) ? 'active' : 'pending';
142
+ if (sslStatus === 'pending') {
143
+ log.warn(`[dns] '${hostname}' is a deep subdomain — Universal SSL ('*.<zone>') does not cover it. ` +
144
+ `Use custom_hostname mode or Advanced Certificate Manager for a valid cert.`);
145
+ }
146
+ return Object.freeze({
147
+ success: true,
148
+ hostname,
149
+ mode: 'owned_zone',
150
+ routingStatus: 'active',
151
+ sslStatus,
152
+ cfZoneId: zoneId,
153
+ cnameTarget,
154
+ errorMessage: '',
155
+ });
156
+ }
157
+ // ── custom_hostname (Cloudflare for SaaS) ────────────────────────────
158
+ async ensureCustomHostname(req) {
159
+ const { hostname, originTarget } = req;
160
+ log.info(`[dns] custom_hostname: register ${hostname} (CF for SaaS, DV auto-SSL)`);
161
+ if (isDryRun()) {
162
+ log.debug(`[dry-run] POST custom_hostnames { hostname: ${hostname}, ssl.method: http, type: dv }`);
163
+ return Object.freeze({
164
+ success: true,
165
+ hostname,
166
+ mode: 'custom_hostname',
167
+ routingStatus: 'pending',
168
+ sslStatus: 'pending',
169
+ cfZoneId: this.cfg.cfZoneId,
170
+ cnameTarget: originTarget,
171
+ errorMessage: '',
172
+ });
173
+ }
174
+ // Idempotent: reuse an existing custom hostname rather than 409-ing.
175
+ const existing = await this.findCustomHostname(hostname);
176
+ if (existing) {
177
+ log.debug(`[dns] custom hostname ${hostname} already registered (${existing.id})`);
178
+ return this.customHostnameToState(hostname, existing, originTarget);
179
+ }
180
+ const body = {
181
+ hostname,
182
+ ssl: {
183
+ method: 'http',
184
+ type: 'dv',
185
+ settings: { min_tls_version: '1.2' },
186
+ },
187
+ };
188
+ const res = await this.cfFetch(`/zones/${this.cfg.cfZoneId}/custom_hostnames`, { method: 'POST', body });
189
+ if (!res.success || !res.result) {
190
+ return this.failState(hostname, 'custom_hostname', res.errorMessage);
191
+ }
192
+ return this.customHostnameToState(hostname, res.result, originTarget);
193
+ }
194
+ customHostnameToState(hostname, ch, cnameTarget) {
195
+ const sslStatus = ch.ssl?.status === 'active' ? 'active' : 'pending';
196
+ const routingStatus = ch.status === 'active' ? 'active' : 'pending';
197
+ return Object.freeze({
198
+ success: true,
199
+ hostname,
200
+ mode: 'custom_hostname',
201
+ routingStatus,
202
+ sslStatus,
203
+ customHostnameId: ch.id,
204
+ cfZoneId: this.cfg.cfZoneId,
205
+ cnameTarget,
206
+ verification: {
207
+ ownership: ch.ownership_verification ?? null,
208
+ ssl: ch.ssl ?? null,
209
+ },
210
+ errorMessage: '',
211
+ });
212
+ }
213
+ // ── zone / record helpers ────────────────────────────────────────────
214
+ /**
215
+ * Resolve the Cloudflare zone id that manages `hostname`, or `undefined`
216
+ * if no zone in the account does (→ treat as a customer BYO domain).
217
+ *
218
+ * Walks the hostname's parent labels (e.g. `api.lab.multinodex.com` →
219
+ * `lab.multinodex.com` → `multinodex.com` → `com`) and returns the first
220
+ * label set that exactly matches a zone name. Handles multi-part TLDs
221
+ * (`.com.tr`) naturally because we test every suffix, not just the last two.
222
+ */
223
+ async resolveZoneId(hostname) {
224
+ const labels = hostname.split('.');
225
+ for (let i = 0; i < labels.length - 1; i++) {
226
+ const candidate = labels.slice(i).join('.');
227
+ const res = await this.cfFetch(`/zones?name=${encodeURIComponent(candidate)}`);
228
+ if (res.success && res.result && res.result.length > 0) {
229
+ return res.result[0].id;
230
+ }
231
+ }
232
+ return undefined;
233
+ }
234
+ async findRecord(zoneId, name) {
235
+ const res = await this.cfFetch(`/zones/${zoneId}/dns_records?name=${encodeURIComponent(name)}`);
236
+ return res.success && res.result && res.result.length > 0 ? res.result[0] : undefined;
237
+ }
238
+ async findCustomHostname(hostname) {
239
+ const res = await this.cfFetch(`/zones/${this.cfg.cfZoneId}/custom_hostnames?hostname=${encodeURIComponent(hostname)}`);
240
+ return res.success && res.result && res.result.length > 0 ? res.result[0] : undefined;
241
+ }
242
+ // ── HTTP ─────────────────────────────────────────────────────────────
243
+ failState(hostname, mode, errorMessage) {
244
+ return Object.freeze({
245
+ success: false,
246
+ hostname,
247
+ mode,
248
+ routingStatus: 'error',
249
+ sslStatus: 'error',
250
+ errorMessage,
251
+ });
252
+ }
253
+ /**
254
+ * Single Cloudflare v4 fetch. Bearer-auths with the host-scoped token,
255
+ * times out at {@link REQUEST_TIMEOUT_MS}, and normalises CF's
256
+ * `{ success, result, errors }` envelope into {@link CfApiResult}.
257
+ */
258
+ async cfFetch(endpoint, options = {}) {
259
+ const controller = new AbortController();
260
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
261
+ try {
262
+ const response = await fetch(`${CF_API_BASE}${endpoint}`, {
263
+ method: options.method ?? 'GET',
264
+ headers: {
265
+ Authorization: `Bearer ${this.cfg.cfApiToken}`,
266
+ 'Content-Type': 'application/json',
267
+ },
268
+ body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
269
+ signal: controller.signal,
270
+ });
271
+ const data = (await response.json());
272
+ if (data.success) {
273
+ return { success: true, result: data.result, errorMessage: '' };
274
+ }
275
+ const msg = data.errors?.map((e) => `${e.code ?? ''} ${e.message}`.trim()).join('; ');
276
+ return { success: false, errorMessage: msg || `HTTP ${response.status}` };
277
+ }
278
+ catch (error) {
279
+ const message = error instanceof Error
280
+ ? error.name === 'AbortError'
281
+ ? `Cloudflare request timed out after ${REQUEST_TIMEOUT_MS / 1000}s`
282
+ : error.message
283
+ : String(error);
284
+ return { success: false, errorMessage: message };
285
+ }
286
+ finally {
287
+ clearTimeout(timeout);
288
+ }
289
+ }
290
+ }
291
+ // ─────────────────────────────────────────────────────────────────────────
292
+ // Internal helpers
293
+ // ─────────────────────────────────────────────────────────────────────────
294
+ /**
295
+ * Cloudflare Universal SSL issues a cert for the apex plus a single wildcard
296
+ * level (`example.com` + `*.example.com`). A hostname with two or more labels
297
+ * below the registrable domain (e.g. `api.lab.example.com`) is therefore NOT
298
+ * covered. We approximate "registrable domain = last two labels" which is
299
+ * correct for plain TLDs and conservative (false-pending, never false-active)
300
+ * for multi-part TLDs like `.com.tr` — there it flags one level early, which
301
+ * only nudges the operator toward a proper per-hostname cert.
302
+ */
303
+ function isCoveredByUniversalSsl(hostname) {
304
+ return hostname.split('.').length <= 3;
305
+ }
306
+ //# sourceMappingURL=dns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dns.js","sourceRoot":"","sources":["../../../src/lib/ops/dns.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * CiCore CLI — Docker image primitives
3
+ *
4
+ * Build / push / pull / retag operations for the three core service images
5
+ * (`nuxt`, `php`, `nginx`). Image names are derived from {@link HostConfig}:
6
+ *
7
+ * push target = `<registry>/<imagePrefix>-<service>:<tag>`
8
+ * e.g. `cisofttr/cicore-nuxt:baea044`
9
+ *
10
+ * compose name = `<composeImageName>-<service>:latest`
11
+ * e.g. `baynis/vucore-nuxt:latest`
12
+ *
13
+ * For vucore (registry=baynis, imagePrefix=vucore, composeImageName=baynis/vucore)
14
+ * the two converge; the retag is just `TAG → latest`. For cicore (registry=
15
+ * cisofttr, imagePrefix=cicore, composeImageName=baynis/vucore) the retag
16
+ * also crosses namespaces so the unmodified docker-compose.yml on the server
17
+ * keeps working without source changes.
18
+ */
19
+ import type { AppServiceKey, HostConfig } from '../hosts.js';
20
+ import { type ExecResult } from './ssh.js';
21
+ /** Push-target reference for a service+tag combo: `<registry>/<prefix>-<svc>:<tag>`. */
22
+ export declare function pushImageRef(cfg: HostConfig, service: AppServiceKey, tag: string): string;
23
+ /** Compose-target reference (what docker-compose.yml expects): `<composeImageName>-<svc>:latest`. */
24
+ export declare function composeImageRef(cfg: HostConfig, service: AppServiceKey): string;
25
+ export interface BuildOptions {
26
+ /** Working directory for the docker build (where Dockerfile lives). Default: cwd. */
27
+ readonly cwd?: string;
28
+ /**
29
+ * Dockerfile path relative to `cwd`. Default: `Dockerfile.<service>.production`
30
+ * matching the existing repo convention.
31
+ */
32
+ readonly dockerfile?: string;
33
+ /**
34
+ * Buildx builder name. Auto-created on first use. Default `vu-cli-builder`.
35
+ * Separate builder = isolated cache from default `desktop-linux`.
36
+ */
37
+ readonly builderName?: string;
38
+ /** Suppress per-step log lines. Errors still surface in the returned result. */
39
+ readonly silent?: boolean;
40
+ }
41
+ /**
42
+ * Build a service image with buildx for `linux/amd64` (Mac M-series cross-build).
43
+ *
44
+ * Two tags are applied in one build pass: `:<tag>` and `:latest`. Buildx
45
+ * `--load` materializes the image in the local Docker daemon so the next
46
+ * push step finds it. The amd64 platform pin is mandatory — without it,
47
+ * arm64 hosts produce images that crash via emulation on the amd64 server.
48
+ *
49
+ * Builder is created lazily on first call; subsequent calls reuse it.
50
+ */
51
+ export declare function buildImage(cfg: HostConfig, service: AppServiceKey, tag: string, opts?: BuildOptions): Promise<ExecResult>;
52
+ /**
53
+ * Push a previously-built image to Docker Hub (or whichever registry the
54
+ * local docker daemon is logged into for `cfg.registry`).
55
+ *
56
+ * Pushes the specific `tag` and `:latest` in sequence — Docker dedupes shared
57
+ * layers, so the second push is mostly metadata.
58
+ */
59
+ export declare function pushImage(cfg: HostConfig, service: AppServiceKey, tag: string, opts?: {
60
+ readonly silent?: boolean;
61
+ }): Promise<ExecResult>;
62
+ /**
63
+ * Retag a locally-built image without rebuilding — useful when the same
64
+ * binary needs to ship under multiple names (e.g. `vu deploy core` reusing
65
+ * an already-pushed image for a hotfix tag).
66
+ */
67
+ export declare function tagImage(fromRef: string, toRef: string, opts?: {
68
+ readonly silent?: boolean;
69
+ }): Promise<ExecResult>;
70
+ /**
71
+ * `docker pull` the push-target image on the remote host. Assumes the host
72
+ * is already `docker login`-ed for `cfg.registry` (setup flow handles this).
73
+ */
74
+ export declare function pullImage(cfg: HostConfig, service: AppServiceKey, tag: string, opts?: {
75
+ readonly silent?: boolean;
76
+ }): Promise<ExecResult>;
77
+ /**
78
+ * Retag a pulled image to the name the compose file references.
79
+ *
80
+ * For cicore this crosses namespaces (`cisofttr/cicore-* → baynis/vucore-*`)
81
+ * so the unmodified docker-compose.yml keeps working. For vucore it's a
82
+ * within-namespace tag rename (`baynis/vucore-*:TAG → :latest`).
83
+ *
84
+ * Idempotent — retagging the same image multiple times has no effect.
85
+ */
86
+ export declare function retagForCompose(cfg: HostConfig, service: AppServiceKey, srcTag: string, opts?: {
87
+ readonly silent?: boolean;
88
+ }): Promise<ExecResult>;
89
+ /**
90
+ * Check whether an image with the given reference exists in the local
91
+ * docker daemon. Returns `false` for "doesn't exist" or any daemon error.
92
+ */
93
+ export declare function imageExistsLocally(ref: string): Promise<boolean>;
94
+ //# sourceMappingURL=image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../../src/lib/ops/image.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE5D,OAAO,EAAuB,KAAK,UAAU,EAAE,MAAM,UAAU,CAAA;AAM/D,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEzF;AAED,qGAAqG;AACrG,wBAAgB,eAAe,CAAC,GAAG,EAAE,UAAU,EAAE,OAAO,EAAE,aAAa,GAAG,MAAM,CAE/E;AAMD,MAAM,WAAW,YAAY;IAC3B,qFAAqF;IACrF,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,gFAAgF;IAChF,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,UAAU,CAAC,CA+BrB;AAiBD;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACvC,OAAO,CAAC,UAAU,CAAC,CAUrB;AAED;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACvC,OAAO,CAAC,UAAU,CAAC,CAGrB;AAMD;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,aAAa,EACtB,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACvC,OAAO,CAAC,UAAU,CAAC,CAIrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,MAAM,EACd,IAAI,GAAE;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GACvC,OAAO,CAAC,UAAU,CAAC,CAOrB;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGtE"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * CiCore CLI — Docker image primitives
3
+ *
4
+ * Build / push / pull / retag operations for the three core service images
5
+ * (`nuxt`, `php`, `nginx`). Image names are derived from {@link HostConfig}:
6
+ *
7
+ * push target = `<registry>/<imagePrefix>-<service>:<tag>`
8
+ * e.g. `cisofttr/cicore-nuxt:baea044`
9
+ *
10
+ * compose name = `<composeImageName>-<service>:latest`
11
+ * e.g. `baynis/vucore-nuxt:latest`
12
+ *
13
+ * For vucore (registry=baynis, imagePrefix=vucore, composeImageName=baynis/vucore)
14
+ * the two converge; the retag is just `TAG → latest`. For cicore (registry=
15
+ * cisofttr, imagePrefix=cicore, composeImageName=baynis/vucore) the retag
16
+ * also crosses namespaces so the unmodified docker-compose.yml on the server
17
+ * keeps working without source changes.
18
+ */
19
+ import { log } from '../logger.js';
20
+ import { runLocal, runRemote } from './ssh.js';
21
+ // ─────────────────────────────────────────────────────────────────────────
22
+ // Image name builders
23
+ // ─────────────────────────────────────────────────────────────────────────
24
+ /** Push-target reference for a service+tag combo: `<registry>/<prefix>-<svc>:<tag>`. */
25
+ export function pushImageRef(cfg, service, tag) {
26
+ return `${cfg.registry}/${cfg.imagePrefix}-${service}:${tag}`;
27
+ }
28
+ /** Compose-target reference (what docker-compose.yml expects): `<composeImageName>-<svc>:latest`. */
29
+ export function composeImageRef(cfg, service) {
30
+ return `${cfg.composeImageName}-${service}:latest`;
31
+ }
32
+ /**
33
+ * Build a service image with buildx for `linux/amd64` (Mac M-series cross-build).
34
+ *
35
+ * Two tags are applied in one build pass: `:<tag>` and `:latest`. Buildx
36
+ * `--load` materializes the image in the local Docker daemon so the next
37
+ * push step finds it. The amd64 platform pin is mandatory — without it,
38
+ * arm64 hosts produce images that crash via emulation on the amd64 server.
39
+ *
40
+ * Builder is created lazily on first call; subsequent calls reuse it.
41
+ */
42
+ export async function buildImage(cfg, service, tag, opts = {}) {
43
+ const builder = opts.builderName ?? 'vu-cli-builder';
44
+ await ensureBuilder(builder, opts.silent === true);
45
+ const dockerfile = opts.dockerfile ?? `Dockerfile.${service}.production`;
46
+ const tagged = pushImageRef(cfg, service, tag);
47
+ const latest = pushImageRef(cfg, service, 'latest');
48
+ if (!opts.silent) {
49
+ log.info(`Building ${tagged} (+latest) from ${dockerfile} ...`);
50
+ }
51
+ return runLocal('docker', [
52
+ 'buildx',
53
+ 'build',
54
+ '--builder',
55
+ builder,
56
+ '--platform',
57
+ 'linux/amd64',
58
+ '--load',
59
+ '-f',
60
+ dockerfile,
61
+ '-t',
62
+ tagged,
63
+ '-t',
64
+ latest,
65
+ '.',
66
+ ], { cwd: opts.cwd, stream: true });
67
+ }
68
+ async function ensureBuilder(name, silent) {
69
+ const inspect = await runLocal('docker', ['buildx', 'inspect', name], { silent: true });
70
+ if (inspect.success)
71
+ return;
72
+ if (!silent)
73
+ log.info(`Creating buildx builder '${name}' ...`);
74
+ await runLocal('docker', ['buildx', 'create', '--name', name, '--use', '--driver-opt', 'network=host'], { silent });
75
+ }
76
+ // ─────────────────────────────────────────────────────────────────────────
77
+ // Push (local → Docker Hub)
78
+ // ─────────────────────────────────────────────────────────────────────────
79
+ /**
80
+ * Push a previously-built image to Docker Hub (or whichever registry the
81
+ * local docker daemon is logged into for `cfg.registry`).
82
+ *
83
+ * Pushes the specific `tag` and `:latest` in sequence — Docker dedupes shared
84
+ * layers, so the second push is mostly metadata.
85
+ */
86
+ export async function pushImage(cfg, service, tag, opts = {}) {
87
+ const tagged = pushImageRef(cfg, service, tag);
88
+ const latest = pushImageRef(cfg, service, 'latest');
89
+ if (!opts.silent)
90
+ log.info(`Pushing ${tagged} ...`);
91
+ const taggedResult = await runLocal('docker', ['push', tagged], { stream: true });
92
+ if (!taggedResult.success)
93
+ return taggedResult;
94
+ if (!opts.silent)
95
+ log.info(`Pushing ${latest} ...`);
96
+ return runLocal('docker', ['push', latest], { stream: true });
97
+ }
98
+ /**
99
+ * Retag a locally-built image without rebuilding — useful when the same
100
+ * binary needs to ship under multiple names (e.g. `vu deploy core` reusing
101
+ * an already-pushed image for a hotfix tag).
102
+ */
103
+ export async function tagImage(fromRef, toRef, opts = {}) {
104
+ if (!opts.silent)
105
+ log.info(`Tagging ${fromRef} → ${toRef}`);
106
+ return runLocal('docker', ['tag', fromRef, toRef], { silent: opts.silent });
107
+ }
108
+ // ─────────────────────────────────────────────────────────────────────────
109
+ // Remote pull + retag (server side)
110
+ // ─────────────────────────────────────────────────────────────────────────
111
+ /**
112
+ * `docker pull` the push-target image on the remote host. Assumes the host
113
+ * is already `docker login`-ed for `cfg.registry` (setup flow handles this).
114
+ */
115
+ export async function pullImage(cfg, service, tag, opts = {}) {
116
+ const ref = pushImageRef(cfg, service, tag);
117
+ if (!opts.silent)
118
+ log.info(`Pulling ${ref} on ${cfg.brandName} ...`);
119
+ return runRemote(cfg, `docker pull ${shellQuote(ref)}`, { silent: opts.silent });
120
+ }
121
+ /**
122
+ * Retag a pulled image to the name the compose file references.
123
+ *
124
+ * For cicore this crosses namespaces (`cisofttr/cicore-* → baynis/vucore-*`)
125
+ * so the unmodified docker-compose.yml keeps working. For vucore it's a
126
+ * within-namespace tag rename (`baynis/vucore-*:TAG → :latest`).
127
+ *
128
+ * Idempotent — retagging the same image multiple times has no effect.
129
+ */
130
+ export async function retagForCompose(cfg, service, srcTag, opts = {}) {
131
+ const src = pushImageRef(cfg, service, srcTag);
132
+ const dst = composeImageRef(cfg, service);
133
+ if (!opts.silent)
134
+ log.info(`Retag ${src} → ${dst}`);
135
+ return runRemote(cfg, `docker tag ${shellQuote(src)} ${shellQuote(dst)}`, {
136
+ silent: opts.silent,
137
+ });
138
+ }
139
+ /**
140
+ * Check whether an image with the given reference exists in the local
141
+ * docker daemon. Returns `false` for "doesn't exist" or any daemon error.
142
+ */
143
+ export async function imageExistsLocally(ref) {
144
+ const result = await runLocal('docker', ['image', 'inspect', ref], { silent: true });
145
+ return result.success;
146
+ }
147
+ // ─────────────────────────────────────────────────────────────────────────
148
+ // Helpers
149
+ // ─────────────────────────────────────────────────────────────────────────
150
+ /**
151
+ * Single-quote-wrap a string for safe shell interpolation, escaping any
152
+ * embedded `'`. Used for image refs that may contain `/` (always) and `:`
153
+ * (always) — both shell-safe but quoting defends against future names with
154
+ * other characters.
155
+ */
156
+ function shellQuote(s) {
157
+ return `'${s.replace(/'/g, `'\\''`)}'`;
158
+ }
159
+ //# sourceMappingURL=image.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image.js","sourceRoot":"","sources":["../../../src/lib/ops/image.ts"],"names":[],"mappings":"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"}