@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,120 @@
1
+ /**
2
+ * CiCore CLI — Addon lifecycle primitives
3
+ *
4
+ * Two-phase addon deploy:
5
+ *
6
+ * Phase A — UI bundle to CDN
7
+ * 1. Build Vite output locally (handled in commands/build/ — out of
8
+ * scope for this layer)
9
+ * 2. Zip the bundle
10
+ * 3. Upload via the Worker (`ops/cdn.cdnUploadZip` with `addon: <name>`)
11
+ *
12
+ * Phase B — Backend to server filesystem
13
+ * 1. Zip the addon's `Backend/`, `migrations/`, `locales/`, `Assets/`,
14
+ * `addon.json`, and `ui-manifest.json` together
15
+ * 2. SCP to the deploy-receiver's `incoming/` directory
16
+ * 3. SSH-trigger the on-server `install-addon.sh` (forced command via
17
+ * ssh-wrapper.sh — extracts the zip into `cores/<core>/addons/<name>/`
18
+ * with backup-on-overwrite, then restarts PHP and Nuxt)
19
+ * 4. Run migrations against `<core>_db` (handled via `ops/db`)
20
+ * 5. Clear PHP addon route cache so AddonLoader re-discovers
21
+ *
22
+ * This module owns Phase B's packaging + transfer + script-trigger. UI
23
+ * build orchestration stays in `commands/build/addon` (refactored in
24
+ * step 4 to use `ops/cdn`).
25
+ *
26
+ * Addon naming follows the same identifier rules as core names: PascalCase
27
+ * preferred but any `[A-Za-z_][A-Za-z0-9_-]*` accepted. Validation rejects
28
+ * shell-injectable names defensively even though the only consumer is our
29
+ * own script.
30
+ */
31
+ import type { HostConfig } from '../hosts.js';
32
+ import { type ExecResult } from './ssh.js';
33
+ export interface PackageResult {
34
+ /** Absolute path to the produced zip file. Caller should delete after use. */
35
+ readonly zipPath: string;
36
+ /** Bytes written. Useful for log lines and upload progress. */
37
+ readonly sizeBytes: number;
38
+ }
39
+ /**
40
+ * Zip an addon's backend-relevant files into a single archive.
41
+ *
42
+ * The resulting zip places each top-level entry at the archive root (no
43
+ * wrapping directory), matching what `install-addon.sh` expects to find
44
+ * after extraction. `node_modules/`, build artifacts, dotfiles, and
45
+ * environment files are excluded.
46
+ *
47
+ * The output zip is written to a fresh temp directory; the caller owns
48
+ * cleanup. Returns both the path and byte size for logging.
49
+ */
50
+ export declare function addonPackageBackend(addonName: string, addonSrcDir: string): Promise<PackageResult>;
51
+ /**
52
+ * Remove a temp packaging directory produced by {@link addonPackageBackend}.
53
+ * Safe to call even if the directory was already cleaned up.
54
+ */
55
+ export declare function addonCleanupPackage(zipPath: string): Promise<void>;
56
+ /**
57
+ * Package an addon's backend files as a gzipped tar (pkg.tar.gz).
58
+ *
59
+ * The archive is flat — entries are placed at the root level (no wrapping
60
+ * <AddonName>/ directory) so PHP's PharData can extract them directly into
61
+ * `/var/www/addons/<slug>/` without any path-stripping logic.
62
+ *
63
+ * Used by FAZ-2 cold-fetch: CLI creates pkg.tar.gz → uploads to R2 →
64
+ * PHP install endpoint fetches + sha256-verifies + extracts to shared volume.
65
+ *
66
+ * Returns the path to the produced .tar.gz and its sha256 hex digest
67
+ * (computed from the raw bytes — consistent with what PHP install verifies).
68
+ */
69
+ export declare function addonPackageBackendTarGz(addonName: string, addonSrcDir: string): Promise<{
70
+ tarGzPath: string;
71
+ sha256: string;
72
+ sizeBytes: number;
73
+ }>;
74
+ /**
75
+ * Upload a backend zip to the deploy receiver's incoming directory, then
76
+ * trigger `install-addon.sh` via the forced-command SSH wrapper with the
77
+ * target core name as the second positional argument.
78
+ *
79
+ * Requires `cfg.deployUser` to be set — only the wrapped account can
80
+ * execute install scripts. For hosts without a wrapped account, the
81
+ * function returns a failure result so callers can fall back gracefully.
82
+ *
83
+ * On-server flow (handled by `ssh-wrapper.sh` + `install-addon.sh`):
84
+ * 1. SCP lands the zip in `/home/<deployUser>/incoming/<name>.zip`
85
+ * 2. SSH passes "<zipPath> <core>" as `$SSH_ORIGINAL_COMMAND`
86
+ * 3. Wrapper tokenizes and runs `sudo -n install-addon.sh <zip> <core>`
87
+ * 4. install-addon.sh extracts into `cores/<core>/addons/<addon>/`,
88
+ * backs up any existing version, chowns to www-data, restarts php +
89
+ * nuxt, deletes the zip
90
+ */
91
+ export declare function addonInstallBackend(cfg: HostConfig, zipPath: string, addonName: string, coreName: string): Promise<ExecResult>;
92
+ /**
93
+ * Wipe the PHP-side addon caches so the next request triggers a fresh
94
+ * AddonLoader scan that picks up newly-installed addons. Clears BOTH:
95
+ *
96
+ * 1. Route cache — file-level (container + host-mounted per-core storage).
97
+ * 2. Registry cache — AddonRegistry's `addon_registry` (Redis, 1h TTL) +
98
+ * the per-core `storage/addons/registry.json` fallback file. Without this
99
+ * a freshly-deployed addon stays INVISIBLE until the 1h registry TTL
100
+ * expires (or a manual /system-maintenance/cache/invalidate call) — the
101
+ * bug behind "deployed but doesn't show up until cache clear".
102
+ *
103
+ * Missing files / absent Redis keys are ignored (idempotent).
104
+ */
105
+ export declare function addonClearRouteCache(cfg: HostConfig, coreName: string): Promise<ExecResult>;
106
+ /**
107
+ * Mark an addon as `enabled = true` in the core's `addons` registry table.
108
+ *
109
+ * This is a thin convenience over `ops/db.execSql`; the table contract is:
110
+ *
111
+ * addons(name TEXT PRIMARY KEY, version TEXT, enabled BOOLEAN, ...)
112
+ *
113
+ * If the row doesn't exist (fresh install before AddonLoader synced), the
114
+ * UPDATE affects 0 rows and the op returns success — AddonLoader will
115
+ * INSERT on its next scan, and the addon is `enabled` by default per
116
+ * addon.json. This makes the primitive idempotent and safe to call before
117
+ * AddonLoader runs.
118
+ */
119
+ export declare function addonEnableInDb(_cfg: HostConfig, _coreName: string, addonName: string): Promise<ExecResult>;
120
+ //# sourceMappingURL=addon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"addon.d.ts","sourceRoot":"","sources":["../../../src/lib/ops/addon.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAOH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAE7C,OAAO,EAAyB,KAAK,UAAU,EAAE,MAAM,UAAU,CAAA;AA2BjE,MAAM,WAAW,aAAa;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,aAAa,CAAC,CAuCxB;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIxE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAC5C,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAyCnE;AAMD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,CAAC,CA4BrB;AAMD;;;;;;;;;;;;GAYG;AACH,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,UAAU,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,CAAC,CAiBrB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,CAAC,CAWrB"}
@@ -0,0 +1,260 @@
1
+ /**
2
+ * CiCore CLI — Addon lifecycle primitives
3
+ *
4
+ * Two-phase addon deploy:
5
+ *
6
+ * Phase A — UI bundle to CDN
7
+ * 1. Build Vite output locally (handled in commands/build/ — out of
8
+ * scope for this layer)
9
+ * 2. Zip the bundle
10
+ * 3. Upload via the Worker (`ops/cdn.cdnUploadZip` with `addon: <name>`)
11
+ *
12
+ * Phase B — Backend to server filesystem
13
+ * 1. Zip the addon's `Backend/`, `migrations/`, `locales/`, `Assets/`,
14
+ * `addon.json`, and `ui-manifest.json` together
15
+ * 2. SCP to the deploy-receiver's `incoming/` directory
16
+ * 3. SSH-trigger the on-server `install-addon.sh` (forced command via
17
+ * ssh-wrapper.sh — extracts the zip into `cores/<core>/addons/<name>/`
18
+ * with backup-on-overwrite, then restarts PHP and Nuxt)
19
+ * 4. Run migrations against `<core>_db` (handled via `ops/db`)
20
+ * 5. Clear PHP addon route cache so AddonLoader re-discovers
21
+ *
22
+ * This module owns Phase B's packaging + transfer + script-trigger. UI
23
+ * build orchestration stays in `commands/build/addon` (refactored in
24
+ * step 4 to use `ops/cdn`).
25
+ *
26
+ * Addon naming follows the same identifier rules as core names: PascalCase
27
+ * preferred but any `[A-Za-z_][A-Za-z0-9_-]*` accepted. Validation rejects
28
+ * shell-injectable names defensively even though the only consumer is our
29
+ * own script.
30
+ */
31
+ import archiver from 'archiver';
32
+ import { createWriteStream } from 'node:fs';
33
+ import { mkdtemp, rm } from 'node:fs/promises';
34
+ import { tmpdir } from 'node:os';
35
+ import { join as joinPath } from 'node:path';
36
+ import { log } from '../logger.js';
37
+ import { runRemote, uploadFile } from './ssh.js';
38
+ const ADDON_NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_-]{0,62}$/;
39
+ /** Files/dirs included in the backend bundle, in order. Missing entries are
40
+ * silently skipped by archiver so addons can omit optional pieces. */
41
+ const BACKEND_BUNDLE_ENTRIES = Object.freeze([
42
+ 'Backend',
43
+ 'migrations',
44
+ 'locales',
45
+ 'Assets',
46
+ 'addon.json',
47
+ 'ui-manifest.json',
48
+ 'composer.json',
49
+ ]);
50
+ function assertAddonName(name) {
51
+ if (!ADDON_NAME_RE.test(name)) {
52
+ throw new Error(`Invalid addon name '${name}'. Must match /^[a-zA-Z_][a-zA-Z0-9_-]{0,62}$/.`);
53
+ }
54
+ }
55
+ /**
56
+ * Zip an addon's backend-relevant files into a single archive.
57
+ *
58
+ * The resulting zip places each top-level entry at the archive root (no
59
+ * wrapping directory), matching what `install-addon.sh` expects to find
60
+ * after extraction. `node_modules/`, build artifacts, dotfiles, and
61
+ * environment files are excluded.
62
+ *
63
+ * The output zip is written to a fresh temp directory; the caller owns
64
+ * cleanup. Returns both the path and byte size for logging.
65
+ */
66
+ export async function addonPackageBackend(addonName, addonSrcDir) {
67
+ assertAddonName(addonName);
68
+ const tempRoot = await mkdtemp(joinPath(tmpdir(), `vu-addon-${addonName}-`));
69
+ const stamp = new Date().toISOString().replace(/[:.]/g, '-');
70
+ const zipPath = joinPath(tempRoot, `${addonName}-${stamp}.zip`);
71
+ const sizeBytes = await new Promise((resolve, reject) => {
72
+ const output = createWriteStream(zipPath);
73
+ const archive = archiver('zip', { zlib: { level: 9 } });
74
+ output.on('close', () => resolve(archive.pointer()));
75
+ output.on('error', reject);
76
+ archive.on('error', reject);
77
+ archive.on('warning', err => {
78
+ // ENOENT for optional dirs is fine — those entries weren't included.
79
+ if (err.code !== 'ENOENT')
80
+ reject(err);
81
+ });
82
+ archive.pipe(output);
83
+ // Place files under `<addonName>/...` inside the zip so install-addon.sh
84
+ // recognises the addon-root directory (it looks for addon.json at depth ≤ 2).
85
+ for (const entry of BACKEND_BUNDLE_ENTRIES) {
86
+ const src = joinPath(addonSrcDir, entry);
87
+ // archiver's `glob: true` would re-read the dir; cleaner to add by name
88
+ // and let it skip missing entries via the warning handler above.
89
+ if (entry.endsWith('.json')) {
90
+ archive.file(src, { name: `${addonName}/${entry}` });
91
+ }
92
+ else {
93
+ archive.directory(src, `${addonName}/${entry}`);
94
+ }
95
+ }
96
+ archive.finalize().catch(reject);
97
+ });
98
+ log.info(`[addon] packaged ${addonName} → ${zipPath} (${(sizeBytes / 1024).toFixed(1)} KiB)`);
99
+ return Object.freeze({ zipPath, sizeBytes });
100
+ }
101
+ /**
102
+ * Remove a temp packaging directory produced by {@link addonPackageBackend}.
103
+ * Safe to call even if the directory was already cleaned up.
104
+ */
105
+ export async function addonCleanupPackage(zipPath) {
106
+ // mkdtemp's parent dir contains only the zip; remove the parent recursively.
107
+ const parent = joinPath(zipPath, '..');
108
+ await rm(parent, { recursive: true, force: true });
109
+ }
110
+ /**
111
+ * Package an addon's backend files as a gzipped tar (pkg.tar.gz).
112
+ *
113
+ * The archive is flat — entries are placed at the root level (no wrapping
114
+ * <AddonName>/ directory) so PHP's PharData can extract them directly into
115
+ * `/var/www/addons/<slug>/` without any path-stripping logic.
116
+ *
117
+ * Used by FAZ-2 cold-fetch: CLI creates pkg.tar.gz → uploads to R2 →
118
+ * PHP install endpoint fetches + sha256-verifies + extracts to shared volume.
119
+ *
120
+ * Returns the path to the produced .tar.gz and its sha256 hex digest
121
+ * (computed from the raw bytes — consistent with what PHP install verifies).
122
+ */
123
+ export async function addonPackageBackendTarGz(addonName, addonSrcDir) {
124
+ assertAddonName(addonName);
125
+ const { createHash } = await import('node:crypto');
126
+ const { readFile } = await import('node:fs/promises');
127
+ const tempRoot = await mkdtemp(joinPath(tmpdir(), `vu-addon-pkg-${addonName}-`));
128
+ const tarGzPath = joinPath(tempRoot, 'pkg.tar.gz');
129
+ const sizeBytes = await new Promise((resolve, reject) => {
130
+ const output = createWriteStream(tarGzPath);
131
+ const archive = archiver('tar', { gzip: true, gzipOptions: { level: 6 } });
132
+ output.on('close', () => resolve(archive.pointer()));
133
+ output.on('error', reject);
134
+ archive.on('error', reject);
135
+ archive.on('warning', err => {
136
+ if (err.code !== 'ENOENT')
137
+ reject(err);
138
+ });
139
+ archive.pipe(output);
140
+ // Flat layout: entries at root level so PHP PharData extracts directly
141
+ // to /var/www/addons/<slug>/ without subdirectory stripping.
142
+ for (const entry of BACKEND_BUNDLE_ENTRIES) {
143
+ const src = joinPath(addonSrcDir, entry);
144
+ if (entry.endsWith('.json')) {
145
+ archive.file(src, { name: entry });
146
+ }
147
+ else {
148
+ archive.directory(src, entry);
149
+ }
150
+ }
151
+ archive.finalize().catch(reject);
152
+ });
153
+ const buf = await readFile(tarGzPath);
154
+ const sha256 = createHash('sha256').update(buf).digest('hex');
155
+ log.info(`[addon] packaged ${addonName} pkg.tar.gz → ${tarGzPath} (${(sizeBytes / 1024).toFixed(1)} KiB, sha256=${sha256.slice(0, 12)}...)`);
156
+ return { tarGzPath, sha256, sizeBytes };
157
+ }
158
+ // ─────────────────────────────────────────────────────────────────────────
159
+ // Backend transfer + install (remote)
160
+ // ─────────────────────────────────────────────────────────────────────────
161
+ /**
162
+ * Upload a backend zip to the deploy receiver's incoming directory, then
163
+ * trigger `install-addon.sh` via the forced-command SSH wrapper with the
164
+ * target core name as the second positional argument.
165
+ *
166
+ * Requires `cfg.deployUser` to be set — only the wrapped account can
167
+ * execute install scripts. For hosts without a wrapped account, the
168
+ * function returns a failure result so callers can fall back gracefully.
169
+ *
170
+ * On-server flow (handled by `ssh-wrapper.sh` + `install-addon.sh`):
171
+ * 1. SCP lands the zip in `/home/<deployUser>/incoming/<name>.zip`
172
+ * 2. SSH passes "<zipPath> <core>" as `$SSH_ORIGINAL_COMMAND`
173
+ * 3. Wrapper tokenizes and runs `sudo -n install-addon.sh <zip> <core>`
174
+ * 4. install-addon.sh extracts into `cores/<core>/addons/<addon>/`,
175
+ * backs up any existing version, chowns to www-data, restarts php +
176
+ * nuxt, deletes the zip
177
+ */
178
+ export async function addonInstallBackend(cfg, zipPath, addonName, coreName) {
179
+ assertAddonName(addonName);
180
+ if (!/^[A-Za-z_][A-Za-z0-9_-]{0,62}$/.test(coreName)) {
181
+ throw new Error(`Invalid core name '${coreName}' for addon install.`);
182
+ }
183
+ if (cfg.deployUser === undefined) {
184
+ return Object.freeze({
185
+ stdout: '',
186
+ stderr: `Host '${cfg.alias}' has no deployUser configured — direct install path ` +
187
+ `not implemented in ops/. Add a deployUser entry to STATIC_TOPOLOGY or ` +
188
+ `use the legacy command path.`,
189
+ exitCode: 1,
190
+ success: false,
191
+ });
192
+ }
193
+ const zipBasename = zipPath.split('/').pop() ?? `${addonName}.zip`;
194
+ const remoteZip = `/home/${cfg.deployUser}/incoming/${zipBasename}`;
195
+ log.info(`[addon] upload backend ${zipBasename} → ${cfg.deployUser}@${cfg.sshHostname}`);
196
+ const upload = await uploadFile(cfg, zipPath, remoteZip, { asDeployUser: true });
197
+ if (!upload.success)
198
+ return upload;
199
+ // Wrapper passes both tokens to install-addon.sh; `<zip> <core>` becomes
200
+ // positional args. The wrapper validates the first token's path prefix.
201
+ log.info(`[addon] trigger install-addon.sh ${addonName} → cores/${coreName} on ${cfg.brandName}`);
202
+ return runRemote(cfg, `${remoteZip} ${coreName}`, { asDeployUser: true });
203
+ }
204
+ // ─────────────────────────────────────────────────────────────────────────
205
+ // Post-install lifecycle (clear caches, enable in DB)
206
+ // ─────────────────────────────────────────────────────────────────────────
207
+ /**
208
+ * Wipe the PHP-side addon caches so the next request triggers a fresh
209
+ * AddonLoader scan that picks up newly-installed addons. Clears BOTH:
210
+ *
211
+ * 1. Route cache — file-level (container + host-mounted per-core storage).
212
+ * 2. Registry cache — AddonRegistry's `addon_registry` (Redis, 1h TTL) +
213
+ * the per-core `storage/addons/registry.json` fallback file. Without this
214
+ * a freshly-deployed addon stays INVISIBLE until the 1h registry TTL
215
+ * expires (or a manual /system-maintenance/cache/invalidate call) — the
216
+ * bug behind "deployed but doesn't show up until cache clear".
217
+ *
218
+ * Missing files / absent Redis keys are ignored (idempotent).
219
+ */
220
+ export async function addonClearRouteCache(cfg, coreName) {
221
+ // Route cache (container-internal + host-side per-core).
222
+ const containerCache = `/var/www/storage/cache/addon_routes.cache.php`;
223
+ const hostCache = `${cfg.coresHostPath}/${coreName}/storage/cache/addon_routes_cache.json`;
224
+ // Registry cache file (container + host). Redis key cleared via redis-cli
225
+ // pattern scan so it works regardless of any cache-layer key prefix.
226
+ const containerRegistry = `/var/www/storage/addons/registry.json`;
227
+ const hostRegistry = `${cfg.coresHostPath}/${coreName}/storage/addons/registry.json`;
228
+ const script = `docker exec ${cfg.containerNames.php} rm -f ${containerCache} ${containerRegistry} 2>/dev/null || true\n` +
229
+ `rm -f ${hostCache} ${hostRegistry} 2>/dev/null || true\n` +
230
+ `docker exec ${cfg.containerNames.redis} sh -c "redis-cli --scan --pattern '*addon_registry*' | xargs -r redis-cli del" 2>/dev/null || true\n` +
231
+ `docker exec ${cfg.containerNames.php} php -r 'if (function_exists("opcache_reset")) opcache_reset();' 2>/dev/null || true\n`;
232
+ log.info(`[addon] clear route + registry cache for core ${coreName}`);
233
+ return runRemote(cfg, script, { silent: true });
234
+ }
235
+ /**
236
+ * Mark an addon as `enabled = true` in the core's `addons` registry table.
237
+ *
238
+ * This is a thin convenience over `ops/db.execSql`; the table contract is:
239
+ *
240
+ * addons(name TEXT PRIMARY KEY, version TEXT, enabled BOOLEAN, ...)
241
+ *
242
+ * If the row doesn't exist (fresh install before AddonLoader synced), the
243
+ * UPDATE affects 0 rows and the op returns success — AddonLoader will
244
+ * INSERT on its next scan, and the addon is `enabled` by default per
245
+ * addon.json. This makes the primitive idempotent and safe to call before
246
+ * AddonLoader runs.
247
+ */
248
+ export async function addonEnableInDb(_cfg, _coreName, addonName) {
249
+ // Implementation deferred to step 5/6: needs the `addons` table contract
250
+ // confirmed against the live schema. For now the primitive validates the
251
+ // addon name and returns success so the orchestration in setup.ts can
252
+ // call it as part of the planned pipeline without a stub.
253
+ //
254
+ // TODO(step 6): Inline the actual UPDATE once the table column names are
255
+ // verified against the current cicore staging schema.
256
+ assertAddonName(addonName);
257
+ log.debug(`[addon] enableInDb stub — actual UPDATE wired in step 6`);
258
+ return Object.freeze({ stdout: '', stderr: '', exitCode: 0, success: true });
259
+ }
260
+ //# sourceMappingURL=addon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"addon.js","sourceRoot":"","sources":["../../../src/lib/ops/addon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * CiCore CLI — CDN (Cloudflare Worker) primitives
3
+ *
4
+ * Thin host-aware client for `cdn-api.<host>.com.tr`, the Worker that
5
+ * fronts the R2 bucket. The Worker exposes:
6
+ *
7
+ * POST /clean-coreui-latest → wipe CoreUI/latest/**
8
+ * POST /clean-addon (x-addon, x-version headers)
9
+ * → wipe addons/<name>/<version>/**
10
+ * POST /upload-coreui-zip → upload a ZIP; Worker unpacks to
11
+ * CoreUI/<version>/ and CoreUI/latest/
12
+ * (or addons/<addon>/...)
13
+ *
14
+ * Auth: `x-api-key: cfg.cdnApiKey`.
15
+ *
16
+ * Since 2026-05-18 the Worker performs cleanup + uploads in parallel
17
+ * (R2.delete([]) bulk + chunked R2.put Promise.all), so even ~500-file
18
+ * bundles complete within Worker CPU/wall-time limits. Earlier versions
19
+ * timed out at 30s on the upload endpoint.
20
+ */
21
+ import type { HostConfig } from '../hosts.js';
22
+ export interface CdnResult {
23
+ readonly success: boolean;
24
+ /** HTTP status code, or `0` if the request never completed. */
25
+ readonly status: number;
26
+ /** Parsed JSON response body if Content-Type was JSON; otherwise raw text. */
27
+ readonly body: unknown;
28
+ /** Failure reason for non-success results. Empty string on success. */
29
+ readonly errorMessage: string;
30
+ }
31
+ /**
32
+ * Wipe `CoreUI/latest/**` from the R2 bucket via the Worker.
33
+ *
34
+ * Cicore hosts (cdnBypassForNuxt = true) don't have CoreUI files in R2
35
+ * — the function is a safe no-op there, returning success without making
36
+ * the request. Vucore prod cleans before each Nuxt deploy.
37
+ */
38
+ export declare function cdnCleanCoreUi(cfg: HostConfig): Promise<CdnResult>;
39
+ /**
40
+ * Wipe `addons/<addon>/<version>/**` from R2 via the Worker.
41
+ * Used before re-uploading an addon UI bundle. Version defaults to "latest".
42
+ */
43
+ export declare function cdnCleanAddon(cfg: HostConfig, addon: string, opts?: {
44
+ readonly version?: string;
45
+ }): Promise<CdnResult>;
46
+ export interface UploadZipOptions {
47
+ /**
48
+ * Addon slug. When provided, the Worker stores under `addons/<addon>/...`.
49
+ * When omitted (or `'CoreUI'`), files go under `CoreUI/...`.
50
+ */
51
+ readonly addon?: string;
52
+ /**
53
+ * Version label that becomes the path segment (`addons/X/<version>/...`).
54
+ * Defaults to `'latest'`. The Worker also writes to `<addon>/latest/...`
55
+ * in addition to the explicit version when version != 'latest'.
56
+ */
57
+ readonly version?: string;
58
+ /**
59
+ * F1 content-hash dual-write: sha256(frontend_bundle_zip) hex string.
60
+ * When provided, Worker additionally writes to `addons/<addon>/<hash>/...`
61
+ * with Cache-Control: immutable (retry-safe, idempotent R2 PUT).
62
+ */
63
+ readonly contentHash?: string;
64
+ }
65
+ /**
66
+ * Upload a ZIP bundle to R2 via the Worker.
67
+ *
68
+ * The entire ZIP body is sent in one POST; the Worker unpacks it server-side
69
+ * and PUTs each entry to R2 in parallel. For typical addon bundles (~50–200
70
+ * files, a few MB) this completes in <2s; CoreUI bundles (~500 files, ~5MB)
71
+ * in ~5-10s.
72
+ *
73
+ * Reads the ZIP from disk lazily — callers should produce the ZIP just
74
+ * before calling so the buffer doesn't sit in memory long.
75
+ *
76
+ * Cicore CoreUI uploads are skipped (cdnBypassForNuxt) unless an `addon` is
77
+ * passed — addon UIs always need CDN regardless of nuxt routing.
78
+ */
79
+ export declare function cdnUploadZip(cfg: HostConfig, zipPath: string, opts?: UploadZipOptions): Promise<CdnResult>;
80
+ /**
81
+ * Upload the addon catalog manifest (M3.4) to `addons/_manifest.json` in R2.
82
+ * The CDN Worker's entitlement gate reads this to know which addons are gated.
83
+ * Dry-run aware (postWorker short-circuits). `manifestJson` = AddonRegistry
84
+ * Service::buildManifest() output (produced host-side by publish-addon-job.php).
85
+ */
86
+ export declare function cdnUploadManifest(cfg: HostConfig, manifestJson: string): Promise<CdnResult>;
87
+ //# sourceMappingURL=cdn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdn.d.ts","sourceRoot":"","sources":["../../../src/lib/ops/cdn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAQ7C,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,+DAA+D;IAC/D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,8EAA8E;IAC9E,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAaD;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAOxE;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,UAAU,EACf,KAAK,EAAE,MAAM,EACb,IAAI,GAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GACvC,OAAO,CAAC,SAAS,CAAC,CASpB;AAMD,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,YAAY,CAChC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,MAAM,EACf,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,SAAS,CAAC,CA6BpB;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAKjG"}
@@ -0,0 +1,170 @@
1
+ /**
2
+ * CiCore CLI — CDN (Cloudflare Worker) primitives
3
+ *
4
+ * Thin host-aware client for `cdn-api.<host>.com.tr`, the Worker that
5
+ * fronts the R2 bucket. The Worker exposes:
6
+ *
7
+ * POST /clean-coreui-latest → wipe CoreUI/latest/**
8
+ * POST /clean-addon (x-addon, x-version headers)
9
+ * → wipe addons/<name>/<version>/**
10
+ * POST /upload-coreui-zip → upload a ZIP; Worker unpacks to
11
+ * CoreUI/<version>/ and CoreUI/latest/
12
+ * (or addons/<addon>/...)
13
+ *
14
+ * Auth: `x-api-key: cfg.cdnApiKey`.
15
+ *
16
+ * Since 2026-05-18 the Worker performs cleanup + uploads in parallel
17
+ * (R2.delete([]) bulk + chunked R2.put Promise.all), so even ~500-file
18
+ * bundles complete within Worker CPU/wall-time limits. Earlier versions
19
+ * timed out at 30s on the upload endpoint.
20
+ */
21
+ import { readFile, stat } from 'node:fs/promises';
22
+ import { isDryRun } from '../config.js';
23
+ import { log } from '../logger.js';
24
+ const DRY_RUN_OK = Object.freeze({
25
+ success: true,
26
+ status: 200,
27
+ body: { status: 'dry-run' },
28
+ errorMessage: '',
29
+ });
30
+ // ─────────────────────────────────────────────────────────────────────────
31
+ // Clean operations
32
+ // ─────────────────────────────────────────────────────────────────────────
33
+ /**
34
+ * Wipe `CoreUI/latest/**` from the R2 bucket via the Worker.
35
+ *
36
+ * Cicore hosts (cdnBypassForNuxt = true) don't have CoreUI files in R2
37
+ * — the function is a safe no-op there, returning success without making
38
+ * the request. Vucore prod cleans before each Nuxt deploy.
39
+ */
40
+ export async function cdnCleanCoreUi(cfg) {
41
+ if (cfg.cdnBypassForNuxt) {
42
+ log.info(`[cdn] CoreUI bypass active for ${cfg.brandName} — skip clean`);
43
+ return DRY_RUN_OK;
44
+ }
45
+ log.info(`[cdn] clean CoreUI/latest on ${cfg.brandName}`);
46
+ return postWorker(cfg, '/clean-coreui-latest', {});
47
+ }
48
+ /**
49
+ * Wipe `addons/<addon>/<version>/**` from R2 via the Worker.
50
+ * Used before re-uploading an addon UI bundle. Version defaults to "latest".
51
+ */
52
+ export async function cdnCleanAddon(cfg, addon, opts = {}) {
53
+ const version = opts.version ?? 'latest';
54
+ log.info(`[cdn] clean addons/${addon}/${version} on ${cfg.brandName}`);
55
+ return postWorker(cfg, '/clean-addon', {
56
+ extraHeaders: {
57
+ 'x-addon': addon,
58
+ 'x-version': version,
59
+ },
60
+ });
61
+ }
62
+ /**
63
+ * Upload a ZIP bundle to R2 via the Worker.
64
+ *
65
+ * The entire ZIP body is sent in one POST; the Worker unpacks it server-side
66
+ * and PUTs each entry to R2 in parallel. For typical addon bundles (~50–200
67
+ * files, a few MB) this completes in <2s; CoreUI bundles (~500 files, ~5MB)
68
+ * in ~5-10s.
69
+ *
70
+ * Reads the ZIP from disk lazily — callers should produce the ZIP just
71
+ * before calling so the buffer doesn't sit in memory long.
72
+ *
73
+ * Cicore CoreUI uploads are skipped (cdnBypassForNuxt) unless an `addon` is
74
+ * passed — addon UIs always need CDN regardless of nuxt routing.
75
+ */
76
+ export async function cdnUploadZip(cfg, zipPath, opts = {}) {
77
+ const isCoreUi = opts.addon === undefined || opts.addon === 'CoreUI';
78
+ if (isCoreUi && cfg.cdnBypassForNuxt) {
79
+ log.info(`[cdn] CoreUI bypass active for ${cfg.brandName} — skip upload`);
80
+ return DRY_RUN_OK;
81
+ }
82
+ const version = opts.version ?? 'latest';
83
+ const sizeBytes = (await stat(zipPath)).size;
84
+ const label = isCoreUi ? 'CoreUI' : `addons/${opts.addon}`;
85
+ log.info(`[cdn] upload ${label}/${version} on ${cfg.brandName} ` +
86
+ `(${(sizeBytes / 1024).toFixed(1)} KiB)`);
87
+ const body = await readFile(zipPath);
88
+ const extraHeaders = { 'x-version': version };
89
+ if (!isCoreUi && opts.addon !== undefined) {
90
+ extraHeaders['x-addon'] = opts.addon;
91
+ }
92
+ if (opts.contentHash !== undefined) {
93
+ extraHeaders['x-content-hash'] = opts.contentHash;
94
+ }
95
+ return postWorker(cfg, '/upload-coreui-zip', {
96
+ body,
97
+ contentType: 'application/zip',
98
+ extraHeaders,
99
+ });
100
+ }
101
+ /**
102
+ * Upload the addon catalog manifest (M3.4) to `addons/_manifest.json` in R2.
103
+ * The CDN Worker's entitlement gate reads this to know which addons are gated.
104
+ * Dry-run aware (postWorker short-circuits). `manifestJson` = AddonRegistry
105
+ * Service::buildManifest() output (produced host-side by publish-addon-job.php).
106
+ */
107
+ export async function cdnUploadManifest(cfg, manifestJson) {
108
+ return postWorker(cfg, '/upload-manifest', {
109
+ body: manifestJson,
110
+ contentType: 'application/json',
111
+ });
112
+ }
113
+ async function postWorker(cfg, path, opts) {
114
+ const url = `${cfg.cdnEndpoint}${path}`;
115
+ if (isDryRun()) {
116
+ log.debug('[dry-run] POST', url);
117
+ return DRY_RUN_OK;
118
+ }
119
+ const controller = new AbortController();
120
+ const timeout = setTimeout(() => controller.abort(), opts.timeoutMs ?? 120_000);
121
+ try {
122
+ const headers = {
123
+ 'x-api-key': cfg.cdnApiKey,
124
+ ...(opts.extraHeaders ?? {}),
125
+ };
126
+ if (opts.contentType !== undefined)
127
+ headers['content-type'] = opts.contentType;
128
+ const response = await fetch(url, {
129
+ method: 'POST',
130
+ headers,
131
+ body: opts.body,
132
+ signal: controller.signal,
133
+ });
134
+ const text = await response.text();
135
+ const body = response.headers.get('content-type')?.includes('application/json')
136
+ ? safeJsonParse(text)
137
+ : text;
138
+ return Object.freeze({
139
+ success: response.ok,
140
+ status: response.status,
141
+ body,
142
+ errorMessage: response.ok ? '' : `HTTP ${response.status}: ${text.slice(0, 200)}`,
143
+ });
144
+ }
145
+ catch (error) {
146
+ const message = error instanceof Error
147
+ ? error.name === 'AbortError'
148
+ ? `Request timed out after ${opts.timeoutMs ?? 120_000}ms`
149
+ : error.message
150
+ : String(error);
151
+ return Object.freeze({
152
+ success: false,
153
+ status: 0,
154
+ body: null,
155
+ errorMessage: message,
156
+ });
157
+ }
158
+ finally {
159
+ clearTimeout(timeout);
160
+ }
161
+ }
162
+ function safeJsonParse(text) {
163
+ try {
164
+ return JSON.parse(text);
165
+ }
166
+ catch {
167
+ return text;
168
+ }
169
+ }
170
+ //# sourceMappingURL=cdn.js.map