@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,351 @@
1
+ /**
2
+ * CiCore CLI — PostgreSQL primitives
3
+ *
4
+ * Host-aware database operations that run psql inside the postgres
5
+ * container on the remote host (`docker exec -i <dbContainerName> psql`).
6
+ * The container exposes psql to localhost only; routing through `docker
7
+ * exec` keeps us from needing inbound 5432 on the server.
8
+ *
9
+ * Authentication uses `cfg.dbAdminUser` + `cfg.dbAdminPassword`. The
10
+ * password is passed via PGPASSWORD env to avoid `-W` interactive prompts
11
+ * and to keep it out of `ps` output (PGPASSWORD is read by libpq and
12
+ * doesn't appear in argv).
13
+ *
14
+ * Idempotency:
15
+ * - `dbCreateIfMissing`: checks pg_database before CREATE
16
+ * - `dbRunMigrations`: uses a `schema_migrations` ledger table inside
17
+ * each database, only applying versions not already recorded
18
+ *
19
+ * Migration files: `*.sql` in a local directory, sorted by filename.
20
+ * Convention `NNN_description.sql` (e.g. `001_create_greetings.sql`).
21
+ * The version key = filename without `.sql`.
22
+ */
23
+ import { readdir, readFile } from 'node:fs/promises';
24
+ import { join as joinPath } from 'node:path';
25
+ import { log } from '../logger.js';
26
+ import { runRemoteScript } from './ssh.js';
27
+ // ─────────────────────────────────────────────────────────────────────────
28
+ // Identifier validation — defense in depth against SQL injection through
29
+ // caller-provided database/user names. CREATE DATABASE doesn't accept
30
+ // parameterized identifiers; only literal interpolation works, so the
31
+ // name MUST be validated before substitution.
32
+ // ─────────────────────────────────────────────────────────────────────────
33
+ /** Postgres identifier rules: alpha/underscore, then alpha/digit/underscore, ≤63 chars. */
34
+ const IDENT_RE = /^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/;
35
+ function assertIdent(value, kind) {
36
+ if (!IDENT_RE.test(value)) {
37
+ throw new Error(`Invalid ${kind} identifier '${value}'. ` +
38
+ `Must match /^[a-zA-Z_][a-zA-Z0-9_]{0,62}$/ (Postgres unquoted identifier rules).`);
39
+ }
40
+ }
41
+ // ─────────────────────────────────────────────────────────────────────────
42
+ // Existence checks
43
+ // ─────────────────────────────────────────────────────────────────────────
44
+ /**
45
+ * Returns true if a database with the given name exists.
46
+ * Uses `pg_database` catalog query — works regardless of search_path.
47
+ */
48
+ export async function dbExists(cfg, dbName) {
49
+ assertIdent(dbName, 'database');
50
+ const result = await execSql(cfg, 'postgres', `SELECT 1 FROM pg_database WHERE datname='${dbName}';`);
51
+ if (!result.success)
52
+ return false;
53
+ return result.stdout.includes('(1 row)');
54
+ }
55
+ // ─────────────────────────────────────────────────────────────────────────
56
+ // Role & Database lifecycle
57
+ // ─────────────────────────────────────────────────────────────────────────
58
+ /**
59
+ * Check if a postgres ROLE (login user) already exists.
60
+ * Used by `dbCreateRoleAndDb` for idempotency.
61
+ */
62
+ export async function roleExists(cfg, role) {
63
+ assertIdent(role, 'role');
64
+ const result = await execSql(cfg, 'postgres', `SELECT 1 FROM pg_roles WHERE rolname='${role}';`);
65
+ if (!result.success)
66
+ return false;
67
+ return result.stdout.includes('(1 row)');
68
+ }
69
+ /**
70
+ * Create the database if missing (idempotent). On creation, grants the
71
+ * admin user full privileges — postgres makes the creating role the owner
72
+ * automatically, but the GRANT is belt-and-suspenders against permission
73
+ * drift from later role changes.
74
+ */
75
+ export async function dbCreateIfMissing(cfg, dbName) {
76
+ assertIdent(dbName, 'database');
77
+ if (await dbExists(cfg, dbName)) {
78
+ log.info(`[db] '${dbName}' already exists on ${cfg.brandName} — skip create`);
79
+ return Object.freeze({ stdout: '', stderr: '', exitCode: 0, success: true });
80
+ }
81
+ log.info(`[db] CREATE DATABASE ${dbName} on ${cfg.brandName}`);
82
+ // CREATE DATABASE can't run inside a transaction block; psql autocommits
83
+ // single statements when no BEGIN is present, so this works as-is.
84
+ const sql = `CREATE DATABASE ${dbName} OWNER ${cfg.dbAdminUser};\n` +
85
+ `GRANT ALL PRIVILEGES ON DATABASE ${dbName} TO ${cfg.dbAdminUser};`;
86
+ return execSql(cfg, 'postgres', sql);
87
+ }
88
+ /**
89
+ * Per-core role + database provisioning (replaces the shared admin
90
+ * pattern). Each core gets its own postgres LOGIN role with a random
91
+ * password, owning its own database — defense in depth: if one core's
92
+ * .env leaks, the blast radius is one database, not the whole instance.
93
+ *
94
+ * Idempotency:
95
+ * - If the role already exists, its password is updated (ALTER ROLE)
96
+ * so a fresh .env re-write stays in sync with the server.
97
+ * - If the database already exists, ownership is reassigned to the
98
+ * role (REASSIGN OWNED is overkill for first-run; a simpler
99
+ * ALTER DATABASE ... OWNER TO covers it).
100
+ *
101
+ * Both statements run under the admin connection (`cfg.dbAdminUser`)
102
+ * because CREATE ROLE / CREATE DATABASE require it. The single quotes
103
+ * in the password are escaped before substitution; identifiers go
104
+ * through `assertIdent` to forbid SQL-meta characters entirely.
105
+ *
106
+ * Caller responsibility: pass a strong password (≥24 random base64url
107
+ * chars) and a globally-unique role name. The setup orchestrator
108
+ * generates both via Node `crypto.randomBytes`.
109
+ */
110
+ export async function dbCreateRoleAndDb(cfg, role, password, dbName) {
111
+ assertIdent(role, 'role');
112
+ assertIdent(dbName, 'database');
113
+ if (!password || password.length < 16) {
114
+ throw new Error(`Password too short (got ${password?.length ?? 0} chars, need ≥16)`);
115
+ }
116
+ // Postgres string literal escape: double up single quotes. The role
117
+ // name itself is already validated as a plain identifier, so it's
118
+ // interpolated raw.
119
+ const pwEscaped = password.replace(/'/g, "''");
120
+ log.info(`[db] ensure role+db '${role}'/'${dbName}' on ${cfg.brandName}`);
121
+ // Step 1: ROLE — create with password, or update password if exists.
122
+ // CREATE ROLE inside a DO block lets us branch on existence atomically.
123
+ // CREATEDB: the per-core role must be able to create its own database —
124
+ // the app's installer (POST /setup/install → SetupService::createDatabase)
125
+ // connects AS this role and issues CREATE DATABASE. Without it the install
126
+ // fails "permission denied to create database". Scoped to DB creation only
127
+ // (not superuser), so per-core isolation holds. (Bug surfaced 2026-05-24.)
128
+ const roleSql = `DO $$\n` +
129
+ `BEGIN\n` +
130
+ ` IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '${role}') THEN\n` +
131
+ ` ALTER ROLE ${role} WITH LOGIN CREATEDB PASSWORD '${pwEscaped}';\n` +
132
+ ` ELSE\n` +
133
+ ` CREATE ROLE ${role} WITH LOGIN CREATEDB PASSWORD '${pwEscaped}';\n` +
134
+ ` END IF;\n` +
135
+ `END $$;`;
136
+ const roleResult = await execSql(cfg, 'postgres', roleSql);
137
+ if (!roleResult.success) {
138
+ return Object.freeze({
139
+ stdout: roleResult.stdout,
140
+ stderr: `role create/update failed: ${roleResult.stderr}`,
141
+ exitCode: roleResult.exitCode,
142
+ success: false,
143
+ });
144
+ }
145
+ // Step 2: DATABASE — create owned by role, or reassign ownership if
146
+ // it already exists. CREATE DATABASE can't run in a DO block (not
147
+ // allowed in transactions), so check + branch in two statements.
148
+ if (await dbExists(cfg, dbName)) {
149
+ log.info(`[db] '${dbName}' exists — reassigning owner to '${role}'`);
150
+ const alterSql = `ALTER DATABASE ${dbName} OWNER TO ${role};\n` +
151
+ `GRANT ALL PRIVILEGES ON DATABASE ${dbName} TO ${role};`;
152
+ return execSql(cfg, 'postgres', alterSql);
153
+ }
154
+ log.info(`[db] CREATE DATABASE ${dbName} OWNER ${role}`);
155
+ const createSql = `CREATE DATABASE ${dbName} OWNER ${role};\n` +
156
+ `GRANT ALL PRIVILEGES ON DATABASE ${dbName} TO ${role};`;
157
+ return execSql(cfg, 'postgres', createSql);
158
+ }
159
+ /**
160
+ * Apply every `*.sql` file under `migrationsDir` that hasn't already run
161
+ * against `dbName`. Files are applied in filename-sorted order so that the
162
+ * `NNN_description.sql` convention controls ordering.
163
+ *
164
+ * Each migration runs inside an explicit transaction:
165
+ *
166
+ * BEGIN;
167
+ * <file contents>
168
+ * INSERT INTO schema_migrations(version) VALUES ('<version>');
169
+ * COMMIT;
170
+ *
171
+ * On failure the transaction rolls back atomically — partial migrations
172
+ * never leave the schema in a half-applied state. Subsequent migrations
173
+ * are skipped, and {@link MigrationRunResult.success} is false.
174
+ *
175
+ * The ledger table is created on first call (idempotent). Existing rows
176
+ * dictate which versions are skipped on subsequent runs.
177
+ */
178
+ export async function dbRunMigrations(cfg, dbName, migrationsDir) {
179
+ assertIdent(dbName, 'database');
180
+ const files = await listMigrationFiles(migrationsDir);
181
+ if (files.length === 0) {
182
+ log.debug(`[db] no migration files in ${migrationsDir}`);
183
+ return Object.freeze({ applied: [], skipped: [], success: true });
184
+ }
185
+ const ledgerSetup = await ensureMigrationsLedger(cfg, dbName);
186
+ if (!ledgerSetup.success) {
187
+ return Object.freeze({
188
+ applied: [],
189
+ skipped: [],
190
+ success: false,
191
+ });
192
+ }
193
+ const alreadyApplied = await listAppliedMigrations(cfg, dbName);
194
+ const appliedSet = new Set(alreadyApplied);
195
+ const applied = [];
196
+ const skipped = [];
197
+ for (const file of files) {
198
+ const version = file.version;
199
+ if (appliedSet.has(version)) {
200
+ skipped.push(version);
201
+ log.debug(`[db] migration ${version} already applied — skip`);
202
+ continue;
203
+ }
204
+ log.info(`[db] apply migration ${version} → ${dbName}`);
205
+ const body = await readFile(file.path, 'utf8');
206
+ // Compose a single transactional script: file body + ledger insert.
207
+ // We use dollar-quoted string for the version to avoid escaping issues
208
+ // when the version itself contains single quotes (it won't, but defense).
209
+ const tx = `BEGIN;\n${body}\nINSERT INTO schema_migrations(version) VALUES ($mv$${version}$mv$);\nCOMMIT;\n`;
210
+ const result = await execSql(cfg, dbName, tx);
211
+ applied.push(Object.freeze({
212
+ version,
213
+ success: result.success,
214
+ stderr: result.stderr,
215
+ }));
216
+ if (!result.success) {
217
+ // Stop the chain — earlier migrations may depend on this one.
218
+ return Object.freeze({
219
+ applied: Object.freeze(applied),
220
+ skipped: Object.freeze(skipped),
221
+ success: false,
222
+ });
223
+ }
224
+ }
225
+ return Object.freeze({
226
+ applied: Object.freeze(applied),
227
+ skipped: Object.freeze(skipped),
228
+ success: true,
229
+ });
230
+ }
231
+ /** List `<dir>/*.sql` sorted by filename, paired with the version key. */
232
+ async function listMigrationFiles(dir) {
233
+ const entries = await readdir(dir).catch(() => []);
234
+ const sqlFiles = entries
235
+ .filter(name => name.endsWith('.sql') && !name.startsWith('.'))
236
+ .sort();
237
+ return Object.freeze(sqlFiles.map(name => Object.freeze({
238
+ version: name.slice(0, -'.sql'.length),
239
+ path: joinPath(dir, name),
240
+ })));
241
+ }
242
+ /** Ensure `schema_migrations` ledger table exists (CREATE IF NOT EXISTS). */
243
+ async function ensureMigrationsLedger(cfg, dbName) {
244
+ const sql = `CREATE TABLE IF NOT EXISTS schema_migrations (\n` +
245
+ ` version TEXT PRIMARY KEY,\n` +
246
+ ` applied_at TIMESTAMPTZ NOT NULL DEFAULT now()\n` +
247
+ `);`;
248
+ return execSql(cfg, dbName, sql);
249
+ }
250
+ /** Return the list of already-applied migration versions, sorted. */
251
+ async function listAppliedMigrations(cfg, dbName) {
252
+ // -A unaligned, -t tuples-only — strips headers and separators for clean parsing.
253
+ const result = await execSql(cfg, dbName, 'SELECT version FROM schema_migrations ORDER BY version;', {
254
+ psqlFlags: ['-A', '-t'],
255
+ });
256
+ if (!result.success)
257
+ return [];
258
+ return result.stdout
259
+ .split('\n')
260
+ .map(line => line.trim())
261
+ .filter(line => line.length > 0);
262
+ }
263
+ // ─────────────────────────────────────────────────────────────────────────
264
+ // Read-only query (control-plane reconcile, status reads)
265
+ // ─────────────────────────────────────────────────────────────────────────
266
+ /**
267
+ * Run a read-only SELECT against `dbName` and return rows as parsed objects.
268
+ *
269
+ * The caller's inner query is wrapped in `json_agg` so every row round-trips
270
+ * as one JSON document — jsonb columns (themes), embedded commas, quotes and
271
+ * escapes all survive losslessly, with none of the fragile CSV/line parsing
272
+ * the migration ledger reader can get away with for a single text column.
273
+ *
274
+ * Returns `[]` on an empty result set or a failed/unparseable query (the
275
+ * failure is logged). `innerSelect` must be a bare SELECT with no trailing
276
+ * semicolon (it's interpolated as a subquery).
277
+ */
278
+ export async function dbSelectJson(cfg, dbName, innerSelect) {
279
+ const sql = `SELECT COALESCE(json_agg(t), '[]'::json) FROM (${innerSelect}) t;`;
280
+ // Read-only: dry-run planının gerçek veriyi okuyabilmesi için global dry-run'da
281
+ // bile koştur (Part A). Aksi halde validate/inventory boş döner → "not found".
282
+ const result = await execSql(cfg, dbName, sql, { psqlFlags: ['-A', '-t'], silent: true, allowInDryRun: true });
283
+ if (!result.success) {
284
+ log.error(`[db] query on ${dbName} failed: ${result.stderr.trim() || result.stdout.trim()}`);
285
+ return [];
286
+ }
287
+ const text = result.stdout.trim();
288
+ if (text === '')
289
+ return [];
290
+ try {
291
+ return JSON.parse(text);
292
+ }
293
+ catch (err) {
294
+ log.error(`[db] could not parse query result: ${err instanceof Error ? err.message : String(err)}`);
295
+ return [];
296
+ }
297
+ }
298
+ /**
299
+ * Run a write statement (UPDATE/INSERT) against `dbName`. Thin wrapper over
300
+ * the internal executor for callers outside this module (e.g. reconcile
301
+ * writing achieved DNS state back to the control-plane). The caller is
302
+ * responsible for validating any interpolated values — there is no parameter
303
+ * binding here; pass only validated identifiers / enum literals.
304
+ */
305
+ export async function dbExec(cfg, dbName, sql) {
306
+ return execSql(cfg, dbName, sql, { silent: true });
307
+ }
308
+ /**
309
+ * Execute a SQL script inside the postgres container via `docker exec -i`.
310
+ *
311
+ * The script is piped through stdin (not -c) so multi-statement migrations
312
+ * including BEGIN/COMMIT work as expected. PGPASSWORD is exported in the
313
+ * remote shell before invoking docker exec so libpq picks it up — never
314
+ * appears in argv.
315
+ */
316
+ async function execSql(cfg, dbName, sql, opts = {}) {
317
+ assertIdent(dbName, 'database');
318
+ // Build psql argv. ON_ERROR_STOP=1 makes psql exit non-zero on the first
319
+ // SQL error inside a script — without it, psql continues and reports
320
+ // success even though individual statements failed.
321
+ const psqlArgs = [
322
+ '-v',
323
+ 'ON_ERROR_STOP=1',
324
+ '-U',
325
+ cfg.dbAdminUser,
326
+ '-d',
327
+ dbName,
328
+ ...(opts.psqlFlags ?? []),
329
+ ];
330
+ const psqlArgline = psqlArgs.map(quoteShellArg).join(' ');
331
+ // The remote shell sees: docker exec -i -e PGPASSWORD=… <container> psql <args>
332
+ // Heredoc carries the SQL. -i keeps stdin attached. -e injects the password
333
+ // into the container env (not just the outer shell), so libpq can read it.
334
+ const script = `docker exec -i -e PGPASSWORD="$PGPASSWORD" ${quoteShellArg(cfg.dbContainerName)} ` +
335
+ `psql ${psqlArgline} <<'__VU_SQL_EOF__'\n` +
336
+ sql +
337
+ `\n__VU_SQL_EOF__\n`;
338
+ return runRemoteScript(cfg, script, {
339
+ env: { PGPASSWORD: cfg.dbAdminPassword },
340
+ silent: opts.silent,
341
+ allowInDryRun: opts.allowInDryRun,
342
+ });
343
+ }
344
+ // ─────────────────────────────────────────────────────────────────────────
345
+ // Helpers
346
+ // ─────────────────────────────────────────────────────────────────────────
347
+ /** Single-quote wrap for safe shell interpolation. */
348
+ function quoteShellArg(s) {
349
+ return `'${s.replace(/'/g, `'\\''`)}'`;
350
+ }
351
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../../src/lib/ops/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,111 @@
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 type { HostConfig } from '../hosts.js';
32
+ /** How a hostname is routed + secured. Mirrors `cp_domains.dns_mode`. */
33
+ export type DnsMode = 'owned_zone' | 'custom_hostname';
34
+ /** Routing status, aligned with `cp_domains.routing_status` / `ssl_status`. */
35
+ export type DnsStatus = 'active' | 'pending' | 'error';
36
+ export interface DnsHostRequest {
37
+ /** Fully-qualified hostname to provision (e.g. `lab.multinodex.com`). */
38
+ readonly hostname: string;
39
+ /** Routing mode. Defaults to auto-detection via {@link DnsProvider.detectMode}. */
40
+ readonly mode: DnsMode;
41
+ /**
42
+ * Where traffic should land:
43
+ * - owned_zone: CNAME content (e.g. the host's proxied A-record name or
44
+ * a tunnel CNAME target) the proxied record points at.
45
+ * - custom_hostname: the fallback-origin hostname customers CNAME to;
46
+ * used to surface the CNAME target back to the caller. The custom
47
+ * hostname itself is registered against `cfg.cfZoneId`.
48
+ */
49
+ readonly originTarget: string;
50
+ }
51
+ /** Resolved state of a provisioned hostname — enough to persist in `cp_domains`. */
52
+ export interface DnsHostState {
53
+ readonly success: boolean;
54
+ readonly hostname: string;
55
+ readonly mode: DnsMode;
56
+ /** Routing reachability. */
57
+ readonly routingStatus: DnsStatus;
58
+ /** TLS issuance/validation status. */
59
+ readonly sslStatus: DnsStatus;
60
+ /** custom_hostname only: CF object id → persist as `cf_custom_hostname_id`. */
61
+ readonly customHostnameId?: string;
62
+ /** Zone the record/hostname lives in → persist as `cf_zone_id`. */
63
+ readonly cfZoneId?: string;
64
+ /** owned_zone: CNAME content. custom_hostname: target the customer must CNAME to. */
65
+ readonly cnameTarget?: string;
66
+ /**
67
+ * custom_hostname only: ownership + DV validation records the customer (or
68
+ * we, for owned validation) must publish → persist as `verification_record`.
69
+ */
70
+ readonly verification?: unknown;
71
+ readonly errorMessage: string;
72
+ }
73
+ /** Minimal mutation result for teardown / bootstrap ops. */
74
+ export interface DnsResult {
75
+ readonly success: boolean;
76
+ readonly errorMessage: string;
77
+ }
78
+ /**
79
+ * Vendor-neutral DNS/TLS provisioning surface. A `CloudflareDnsProvider`
80
+ * implements it today; the brand provisioning flow + `ci setup` depend only
81
+ * on this interface.
82
+ */
83
+ export interface DnsProvider {
84
+ /**
85
+ * Decide whether `hostname` falls under a zone we manage (`owned_zone`) or
86
+ * belongs to a customer (`custom_hostname`). Lets callers omit an explicit
87
+ * mode and "do the right thing" for both our subdomains and BYO domains.
88
+ */
89
+ detectMode(hostname: string): Promise<DnsMode>;
90
+ /**
91
+ * Make `hostname` reachable + TLS-valid, routing to our origin. Idempotent.
92
+ * For `custom_hostname`, TLS is issued asynchronously by Cloudflare — the
93
+ * returned `sslStatus` may be `pending`; poll {@link getHostState} until it
94
+ * flips to `active` before marking the domain live.
95
+ */
96
+ ensureHost(req: DnsHostRequest): Promise<DnsHostState>;
97
+ /** Current routing + TLS state. Use to poll a `pending` custom hostname. */
98
+ getHostState(hostname: string, mode: DnsMode): Promise<DnsHostState>;
99
+ /** Remove routing + cert for `hostname` (rollback / brand teardown). Idempotent. */
100
+ removeHost(hostname: string, mode: DnsMode): Promise<DnsResult>;
101
+ /**
102
+ * One-time per-zone bootstrap (custom_hostname mode): designate
103
+ * `originHostname` — a *proxied* record that already exists in the SaaS zone
104
+ * — as the Cloudflare for SaaS fallback origin. Required before any custom
105
+ * hostname can be served. Idempotent.
106
+ */
107
+ ensureFallbackOrigin(originHostname: string): Promise<DnsResult>;
108
+ }
109
+ /** Construct the configured provider for a host. Cloudflare-only today. */
110
+ export declare function createDnsProvider(cfg: HostConfig): DnsProvider;
111
+ //# sourceMappingURL=dns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dns.d.ts","sourceRoot":"","sources":["../../../src/lib/ops/dns.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAQ7C,yEAAyE;AACzE,MAAM,MAAM,OAAO,GAAG,YAAY,GAAG,iBAAiB,CAAA;AAEtD,+EAA+E;AAC/E,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAA;AAEtD,MAAM,WAAW,cAAc;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,mFAAmF;IACnF,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAED,oFAAoF;AACpF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,4BAA4B;IAC5B,QAAQ,CAAC,aAAa,EAAE,SAAS,CAAA;IACjC,sCAAsC;IACtC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;IAC7B,+EAA+E;IAC/E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;IAClC,mEAAmE;IACnE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,qFAAqF;IACrF,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAA;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAED,4DAA4D;AAC5D,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAID;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B;;;;OAIG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IAE9C;;;;;OAKG;IACH,UAAU,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAEtD,4EAA4E;IAC5E,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;IAEpE,oFAAoF;IACpF,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;IAE/D;;;;;OAKG;IACH,oBAAoB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAA;CACjE;AAED,2EAA2E;AAC3E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,GAAG,WAAW,CAE9D"}