alepha 0.21.2 → 0.22.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 (463) hide show
  1. package/README.md +0 -1
  2. package/dist/api/audits/index.browser.js.map +1 -1
  3. package/dist/api/audits/index.d.ts +393 -403
  4. package/dist/api/audits/index.d.ts.map +1 -1
  5. package/dist/api/audits/index.js +25 -56
  6. package/dist/api/audits/index.js.map +1 -1
  7. package/dist/api/files/index.browser.js +31 -1
  8. package/dist/api/files/index.browser.js.map +1 -1
  9. package/dist/api/files/index.d.ts +313 -208
  10. package/dist/api/files/index.d.ts.map +1 -1
  11. package/dist/api/files/index.js +152 -42
  12. package/dist/api/files/index.js.map +1 -1
  13. package/dist/api/jobs/index.browser.js +2 -2
  14. package/dist/api/jobs/index.browser.js.map +1 -1
  15. package/dist/api/jobs/index.d.ts +289 -292
  16. package/dist/api/jobs/index.d.ts.map +1 -1
  17. package/dist/api/jobs/index.js +39 -33
  18. package/dist/api/jobs/index.js.map +1 -1
  19. package/dist/api/keys/index.d.ts +211 -216
  20. package/dist/api/keys/index.d.ts.map +1 -1
  21. package/dist/api/keys/index.js.map +1 -1
  22. package/dist/api/notifications/index.browser.js.map +1 -1
  23. package/dist/api/notifications/index.d.ts +188 -195
  24. package/dist/api/notifications/index.d.ts.map +1 -1
  25. package/dist/api/notifications/index.js.map +1 -1
  26. package/dist/api/oauth/index.d.ts +71 -76
  27. package/dist/api/oauth/index.d.ts.map +1 -1
  28. package/dist/api/oauth/index.js.map +1 -1
  29. package/dist/api/organizations/index.browser.js.map +1 -1
  30. package/dist/api/organizations/index.d.ts +104 -109
  31. package/dist/api/organizations/index.d.ts.map +1 -1
  32. package/dist/api/organizations/index.js.map +1 -1
  33. package/dist/api/parameters/index.browser.js +43 -16
  34. package/dist/api/parameters/index.browser.js.map +1 -1
  35. package/dist/api/parameters/index.d.ts +488 -344
  36. package/dist/api/parameters/index.d.ts.map +1 -1
  37. package/dist/api/parameters/index.js +175 -35
  38. package/dist/api/parameters/index.js.map +1 -1
  39. package/dist/api/payments/index.d.ts +396 -402
  40. package/dist/api/payments/index.d.ts.map +1 -1
  41. package/dist/api/payments/index.js.map +1 -1
  42. package/dist/api/subscriptions/index.d.ts +644 -652
  43. package/dist/api/subscriptions/index.d.ts.map +1 -1
  44. package/dist/api/subscriptions/index.js +1 -1
  45. package/dist/api/subscriptions/index.js.map +1 -1
  46. package/dist/api/users/index.browser.js +7 -0
  47. package/dist/api/users/index.browser.js.map +1 -1
  48. package/dist/api/users/index.d.ts +1073 -1006
  49. package/dist/api/users/index.d.ts.map +1 -1
  50. package/dist/api/users/index.js +283 -61
  51. package/dist/api/users/index.js.map +1 -1
  52. package/dist/api/verifications/index.browser.js.map +1 -1
  53. package/dist/api/verifications/index.d.ts +134 -140
  54. package/dist/api/verifications/index.d.ts.map +1 -1
  55. package/dist/api/verifications/index.js.map +1 -1
  56. package/dist/background/index.d.ts +95 -0
  57. package/dist/background/index.d.ts.map +1 -0
  58. package/dist/background/index.js +121 -0
  59. package/dist/background/index.js.map +1 -0
  60. package/dist/background/index.workerd.js +110 -0
  61. package/dist/background/index.workerd.js.map +1 -0
  62. package/dist/batch/index.d.ts +5 -7
  63. package/dist/batch/index.d.ts.map +1 -1
  64. package/dist/batch/index.js.map +1 -1
  65. package/dist/bin/index.js.map +1 -1
  66. package/dist/bucket/index.d.ts +76 -54
  67. package/dist/bucket/index.d.ts.map +1 -1
  68. package/dist/bucket/index.js +58 -11
  69. package/dist/bucket/index.js.map +1 -1
  70. package/dist/bucket/index.workerd.js +200 -5
  71. package/dist/bucket/index.workerd.js.map +1 -1
  72. package/dist/cache/core/index.d.ts +7 -10
  73. package/dist/cache/core/index.d.ts.map +1 -1
  74. package/dist/cache/core/index.js.map +1 -1
  75. package/dist/cache/core/index.workerd.js.map +1 -1
  76. package/dist/cache/database/index.d.ts +22 -26
  77. package/dist/cache/database/index.d.ts.map +1 -1
  78. package/dist/cache/database/index.js.map +1 -1
  79. package/dist/cache/redis/index.d.ts +4 -7
  80. package/dist/cache/redis/index.d.ts.map +1 -1
  81. package/dist/cache/redis/index.js.map +1 -1
  82. package/dist/captcha/index.d.ts +3 -6
  83. package/dist/captcha/index.d.ts.map +1 -1
  84. package/dist/captcha/index.js.map +1 -1
  85. package/dist/cli/config/index.d.ts.map +1 -1
  86. package/dist/cli/config/index.js.map +1 -1
  87. package/dist/cli/core/index.d.ts +417 -214
  88. package/dist/cli/core/index.d.ts.map +1 -1
  89. package/dist/cli/core/index.js +325 -563
  90. package/dist/cli/core/index.js.map +1 -1
  91. package/dist/cli/devtools/index.d.ts +3 -5
  92. package/dist/cli/devtools/index.d.ts.map +1 -1
  93. package/dist/cli/devtools/index.js.map +1 -1
  94. package/dist/cli/i18n/index.d.ts +8 -12
  95. package/dist/cli/i18n/index.d.ts.map +1 -1
  96. package/dist/cli/i18n/index.js.map +1 -1
  97. package/dist/cli/platform/index.d.ts +126 -1342
  98. package/dist/cli/platform/index.d.ts.map +1 -1
  99. package/dist/cli/platform/index.js +136 -2374
  100. package/dist/cli/platform/index.js.map +1 -1
  101. package/dist/cli/platform-lib/index.d.ts +1446 -0
  102. package/dist/cli/platform-lib/index.d.ts.map +1 -0
  103. package/dist/cli/platform-lib/index.js +2597 -0
  104. package/dist/cli/platform-lib/index.js.map +1 -0
  105. package/dist/cli/vendor/index.d.ts +17 -21
  106. package/dist/cli/vendor/index.d.ts.map +1 -1
  107. package/dist/cli/vendor/index.js.map +1 -1
  108. package/dist/command/index.d.ts +21 -20
  109. package/dist/command/index.d.ts.map +1 -1
  110. package/dist/command/index.js +39 -10
  111. package/dist/command/index.js.map +1 -1
  112. package/dist/{containers → container}/core/index.d.ts +13 -15
  113. package/dist/container/core/index.d.ts.map +1 -0
  114. package/dist/{containers → container}/core/index.js +23 -14
  115. package/dist/container/core/index.js.map +1 -0
  116. package/dist/{containers → container}/core/index.workerd.js +37 -22
  117. package/dist/container/core/index.workerd.js.map +1 -0
  118. package/dist/core/index.browser.js +27 -1
  119. package/dist/core/index.browser.js.map +1 -1
  120. package/dist/core/index.d.ts +48 -24
  121. package/dist/core/index.d.ts.map +1 -1
  122. package/dist/core/index.js +27 -1
  123. package/dist/core/index.js.map +1 -1
  124. package/dist/core/index.native.js +27 -1
  125. package/dist/core/index.native.js.map +1 -1
  126. package/dist/core/index.workerd.js +27 -1
  127. package/dist/core/index.workerd.js.map +1 -1
  128. package/dist/crypto/index.browser.js.map +1 -1
  129. package/dist/crypto/index.d.ts +5 -8
  130. package/dist/crypto/index.d.ts.map +1 -1
  131. package/dist/crypto/index.js.map +1 -1
  132. package/dist/datetime/index.d.ts +3 -4
  133. package/dist/datetime/index.d.ts.map +1 -1
  134. package/dist/datetime/index.js.map +1 -1
  135. package/dist/email/brevo/index.d.ts +2 -4
  136. package/dist/email/brevo/index.d.ts.map +1 -1
  137. package/dist/email/brevo/index.js.map +1 -1
  138. package/dist/email/cloudflare/index.d.ts +20 -7
  139. package/dist/email/cloudflare/index.d.ts.map +1 -1
  140. package/dist/email/cloudflare/index.js +46 -9
  141. package/dist/email/cloudflare/index.js.map +1 -1
  142. package/dist/email/core/index.d.ts +6 -9
  143. package/dist/email/core/index.d.ts.map +1 -1
  144. package/dist/email/core/index.js.map +1 -1
  145. package/dist/email/core/index.workerd.js.map +1 -1
  146. package/dist/email/smtp/index.d.ts +10 -13
  147. package/dist/email/smtp/index.d.ts.map +1 -1
  148. package/dist/email/smtp/index.js +107 -32
  149. package/dist/email/smtp/index.js.map +1 -1
  150. package/dist/fake/index.d.ts +1 -2
  151. package/dist/fake/index.d.ts.map +1 -1
  152. package/dist/fake/index.js.map +1 -1
  153. package/dist/lock/core/index.d.ts +9 -14
  154. package/dist/lock/core/index.d.ts.map +1 -1
  155. package/dist/lock/core/index.js.map +1 -1
  156. package/dist/lock/redis/index.d.ts +2 -4
  157. package/dist/lock/redis/index.d.ts.map +1 -1
  158. package/dist/lock/redis/index.js.map +1 -1
  159. package/dist/logger/index.d.ts +105 -76
  160. package/dist/logger/index.d.ts.map +1 -1
  161. package/dist/logger/index.js +196 -174
  162. package/dist/logger/index.js.map +1 -1
  163. package/dist/mcp/index.d.ts +16 -20
  164. package/dist/mcp/index.d.ts.map +1 -1
  165. package/dist/mcp/index.js.map +1 -1
  166. package/dist/orm/core/index.browser.js.map +1 -1
  167. package/dist/orm/core/index.bun.js +19 -1
  168. package/dist/orm/core/index.bun.js.map +1 -1
  169. package/dist/orm/core/index.d.ts +76 -62
  170. package/dist/orm/core/index.d.ts.map +1 -1
  171. package/dist/orm/core/index.js +20 -2
  172. package/dist/orm/core/index.js.map +1 -1
  173. package/dist/orm/postgres/index.bun.js.map +1 -1
  174. package/dist/orm/postgres/index.d.ts +28 -20
  175. package/dist/orm/postgres/index.d.ts.map +1 -1
  176. package/dist/orm/postgres/index.js.map +1 -1
  177. package/dist/queue/core/index.d.ts +12 -15
  178. package/dist/queue/core/index.d.ts.map +1 -1
  179. package/dist/queue/core/index.js.map +1 -1
  180. package/dist/queue/core/index.workerd.js.map +1 -1
  181. package/dist/queue/redis/index.d.ts +3 -5
  182. package/dist/queue/redis/index.d.ts.map +1 -1
  183. package/dist/queue/redis/index.js.map +1 -1
  184. package/dist/react/auth/index.browser.js +9 -2
  185. package/dist/react/auth/index.browser.js.map +1 -1
  186. package/dist/react/auth/index.d.ts +14 -9
  187. package/dist/react/auth/index.d.ts.map +1 -1
  188. package/dist/react/auth/index.js +9 -2
  189. package/dist/react/auth/index.js.map +1 -1
  190. package/dist/react/core/index.d.ts +7 -8
  191. package/dist/react/core/index.d.ts.map +1 -1
  192. package/dist/react/core/index.js +6 -3
  193. package/dist/react/core/index.js.map +1 -1
  194. package/dist/react/form/index.d.ts +2 -4
  195. package/dist/react/form/index.d.ts.map +1 -1
  196. package/dist/react/form/index.js.map +1 -1
  197. package/dist/react/head/index.browser.js.map +1 -1
  198. package/dist/react/head/index.d.ts +2 -4
  199. package/dist/react/head/index.d.ts.map +1 -1
  200. package/dist/react/head/index.js.map +1 -1
  201. package/dist/react/i18n/index.d.ts +47 -11
  202. package/dist/react/i18n/index.d.ts.map +1 -1
  203. package/dist/react/i18n/index.js +33 -1
  204. package/dist/react/i18n/index.js.map +1 -1
  205. package/dist/react/intro/index.d.ts +1 -2
  206. package/dist/react/intro/index.d.ts.map +1 -1
  207. package/dist/react/intro/index.js +2 -2
  208. package/dist/react/intro/index.js.map +1 -1
  209. package/dist/react/router/index.browser.js +65 -19
  210. package/dist/react/router/index.browser.js.map +1 -1
  211. package/dist/react/router/index.d.ts +327 -222
  212. package/dist/react/router/index.d.ts.map +1 -1
  213. package/dist/react/router/index.js +65 -29
  214. package/dist/react/router/index.js.map +1 -1
  215. package/dist/react/testing/index.d.ts +1 -2
  216. package/dist/react/testing/index.d.ts.map +1 -1
  217. package/dist/react/testing/index.js +16 -17
  218. package/dist/react/testing/index.js.map +1 -1
  219. package/dist/react/ui/index.d.ts +20 -25
  220. package/dist/react/ui/index.d.ts.map +1 -1
  221. package/dist/react/ui/index.js.map +1 -1
  222. package/dist/redis/index.bun.js.map +1 -1
  223. package/dist/redis/index.d.ts +17 -19
  224. package/dist/redis/index.d.ts.map +1 -1
  225. package/dist/redis/index.js.map +1 -1
  226. package/dist/retry/index.d.ts +2 -4
  227. package/dist/retry/index.d.ts.map +1 -1
  228. package/dist/retry/index.js.map +1 -1
  229. package/dist/router/index.d.ts.map +1 -1
  230. package/dist/router/index.js.map +1 -1
  231. package/dist/scheduler/index.d.ts +10 -13
  232. package/dist/scheduler/index.d.ts.map +1 -1
  233. package/dist/scheduler/index.js.map +1 -1
  234. package/dist/scheduler/index.workerd.js.map +1 -1
  235. package/dist/security/index.browser.js.map +1 -1
  236. package/dist/security/index.d.ts +45 -48
  237. package/dist/security/index.d.ts.map +1 -1
  238. package/dist/security/index.js.map +1 -1
  239. package/dist/server/auth/index.browser.js.map +1 -1
  240. package/dist/server/auth/index.d.ts +167 -172
  241. package/dist/server/auth/index.d.ts.map +1 -1
  242. package/dist/server/auth/index.js +4 -8
  243. package/dist/server/auth/index.js.map +1 -1
  244. package/dist/server/cookies/index.browser.js.map +1 -1
  245. package/dist/server/cookies/index.d.ts +5 -7
  246. package/dist/server/cookies/index.d.ts.map +1 -1
  247. package/dist/server/cookies/index.js.map +1 -1
  248. package/dist/server/core/index.browser.js.map +1 -1
  249. package/dist/server/core/index.d.ts +88 -73
  250. package/dist/server/core/index.d.ts.map +1 -1
  251. package/dist/server/core/index.js +19 -0
  252. package/dist/server/core/index.js.map +1 -1
  253. package/dist/server/cors/index.d.ts +11 -14
  254. package/dist/server/cors/index.d.ts.map +1 -1
  255. package/dist/server/cors/index.js.map +1 -1
  256. package/dist/server/etag/index.d.ts +6 -9
  257. package/dist/server/etag/index.d.ts.map +1 -1
  258. package/dist/server/etag/index.js.map +1 -1
  259. package/dist/server/health/index.d.ts +18 -21
  260. package/dist/server/health/index.d.ts.map +1 -1
  261. package/dist/server/health/index.js.map +1 -1
  262. package/dist/server/links/index.browser.js +2 -0
  263. package/dist/server/links/index.browser.js.map +1 -1
  264. package/dist/server/links/index.d.ts +63 -67
  265. package/dist/server/links/index.d.ts.map +1 -1
  266. package/dist/server/links/index.js +2 -0
  267. package/dist/server/links/index.js.map +1 -1
  268. package/dist/server/metrics/index.d.ts +5 -7
  269. package/dist/server/metrics/index.d.ts.map +1 -1
  270. package/dist/server/metrics/index.js.map +1 -1
  271. package/dist/server/proxy/index.d.ts +3 -5
  272. package/dist/server/proxy/index.d.ts.map +1 -1
  273. package/dist/server/proxy/index.js.map +1 -1
  274. package/dist/server/rate-limit/index.d.ts +10 -13
  275. package/dist/server/rate-limit/index.d.ts.map +1 -1
  276. package/dist/server/rate-limit/index.js.map +1 -1
  277. package/dist/server/static/index.d.ts +3 -5
  278. package/dist/server/static/index.d.ts.map +1 -1
  279. package/dist/server/static/index.js.map +1 -1
  280. package/dist/server/swagger/index.d.ts +5 -8
  281. package/dist/server/swagger/index.d.ts.map +1 -1
  282. package/dist/server/swagger/index.js.map +1 -1
  283. package/dist/sms/index.d.ts +3 -5
  284. package/dist/sms/index.d.ts.map +1 -1
  285. package/dist/sms/index.js.map +1 -1
  286. package/dist/system/index.browser.js.map +1 -1
  287. package/dist/system/index.d.ts +2 -4
  288. package/dist/system/index.d.ts.map +1 -1
  289. package/dist/system/index.js.map +1 -1
  290. package/dist/system/index.workerd.js.map +1 -1
  291. package/dist/topic/core/index.d.ts +4 -6
  292. package/dist/topic/core/index.d.ts.map +1 -1
  293. package/dist/topic/core/index.js.map +1 -1
  294. package/dist/topic/redis/index.d.ts +5 -8
  295. package/dist/topic/redis/index.d.ts.map +1 -1
  296. package/dist/topic/redis/index.js.map +1 -1
  297. package/package.json +45 -22
  298. package/src/api/audits/__tests__/AuditService.spec.ts +18 -110
  299. package/src/api/audits/controllers/AdminAuditController.ts +14 -0
  300. package/src/api/audits/services/AuditService.ts +21 -88
  301. package/src/api/files/__tests__/FileService.spec.ts +207 -2
  302. package/src/api/files/index.ts +3 -0
  303. package/src/api/files/schemas/fileCreatorSummarySchema.ts +22 -0
  304. package/src/api/files/schemas/fileResourceSchema.ts +10 -1
  305. package/src/api/files/services/FileService.ts +170 -72
  306. package/src/api/jobs/__tests__/$job.spec.ts +24 -1
  307. package/src/api/jobs/index.ts +4 -3
  308. package/src/api/jobs/primitives/$job.ts +7 -3
  309. package/src/api/jobs/providers/DirectJobDispatcher.ts +17 -36
  310. package/src/api/jobs/providers/JobProvider.ts +53 -24
  311. package/src/api/jobs/schemas/jobConfigAtom.ts +1 -1
  312. package/src/api/jobs/schemas/jobExecutionResourceSchema.ts +4 -1
  313. package/src/api/keys/schemas/adminApiKeyResourceSchema.ts +3 -1
  314. package/src/api/parameters/__tests__/$parameter.spec.ts +19 -2
  315. package/src/api/parameters/audits/ParameterAudits.ts +17 -0
  316. package/src/api/parameters/controllers/AdminParameterController.ts +95 -19
  317. package/src/api/parameters/index.ts +3 -0
  318. package/src/api/parameters/schemas/activateParameterBodySchema.ts +3 -3
  319. package/src/api/parameters/schemas/createParameterVersionBodySchema.ts +3 -2
  320. package/src/api/parameters/schemas/parameterCreatorSummarySchema.ts +25 -0
  321. package/src/api/parameters/schemas/parameterResponseSchema.ts +5 -0
  322. package/src/api/parameters/schemas/rollbackParameterBodySchema.ts +4 -2
  323. package/src/api/parameters/services/ParameterProvider.ts +69 -6
  324. package/src/api/subscriptions/jobs/SubscriptionJobs.ts +1 -1
  325. package/src/api/users/__tests__/AdminSessionController.spec.ts +37 -0
  326. package/src/api/users/audits/SessionAudits.ts +33 -0
  327. package/src/api/users/audits/UserAudits.ts +19 -43
  328. package/src/api/users/controllers/AdminUserController.ts +66 -1
  329. package/src/api/users/entities/sessions.ts +6 -0
  330. package/src/api/users/entities/users.ts +2 -0
  331. package/src/api/users/index.ts +9 -1
  332. package/src/api/users/primitives/$realm.ts +3 -0
  333. package/src/api/users/schemas/sessionResourceSchema.ts +16 -0
  334. package/src/api/users/schemas/updateUserSchema.ts +1 -8
  335. package/src/api/users/schemas/userQuerySchema.ts +7 -0
  336. package/src/api/users/services/CredentialService.ts +15 -6
  337. package/src/api/users/services/IdentityService.ts +2 -1
  338. package/src/api/users/services/RegistrationService.ts +2 -1
  339. package/src/api/users/services/SessionCrudService.ts +19 -2
  340. package/src/api/users/services/SessionService.ts +39 -19
  341. package/src/api/users/services/UserService.ts +106 -8
  342. package/src/background/__tests__/BackgroundTaskProvider.spec.ts +96 -0
  343. package/src/background/index.ts +37 -0
  344. package/src/background/index.workerd.ts +28 -0
  345. package/src/background/providers/BackgroundTaskProvider.ts +70 -0
  346. package/src/background/providers/WorkerdBackgroundTaskProvider.ts +43 -0
  347. package/src/bucket/__tests__/$bucket.spec.ts +18 -0
  348. package/src/bucket/__tests__/LocalFileStorageProvider.spec.ts +5 -0
  349. package/src/bucket/__tests__/MemoryFileStorageProvider.spec.ts +5 -0
  350. package/src/bucket/__tests__/NodeS3BucketProvider.spec.ts +23 -4
  351. package/src/bucket/__tests__/shared.ts +30 -0
  352. package/src/bucket/index.ts +5 -5
  353. package/src/bucket/index.workerd.ts +11 -4
  354. package/src/bucket/primitives/$bucket.ts +27 -0
  355. package/src/bucket/providers/FileStorageProvider.ts +13 -0
  356. package/src/bucket/providers/LocalFileStorageProvider.ts +17 -1
  357. package/src/bucket/providers/MemoryFileStorageProvider.ts +7 -0
  358. package/src/bucket/providers/{CloudflareR2Provider.ts → R2FileStorageProvider.ts} +10 -1
  359. package/src/bucket/providers/{NodeS3BucketProvider.ts → S3FileStorageProvider.ts} +27 -5
  360. package/src/cli/core/__tests__/BuildDockerTask.spec.ts +25 -1
  361. package/src/cli/core/__tests__/init.spec.ts +0 -219
  362. package/src/cli/core/commands/__tests__/BuildCommand.spec.ts +43 -0
  363. package/src/cli/core/commands/build.ts +108 -30
  364. package/src/cli/core/commands/init.ts +0 -12
  365. package/src/cli/core/commands/pack.ts +133 -0
  366. package/src/cli/core/index.ts +3 -0
  367. package/src/cli/core/providers/ViteDevServerProvider.ts +40 -16
  368. package/src/cli/core/services/PackageManagerUtils.ts +0 -16
  369. package/src/cli/core/services/ProjectScaffolder.ts +29 -291
  370. package/src/cli/core/tasks/BuildCloudflareTask.ts +353 -47
  371. package/src/cli/core/tasks/BuildDockerTask.ts +33 -3
  372. package/src/cli/core/tasks/BuildTask.ts +34 -0
  373. package/src/cli/core/templates/apiIndexTs.ts +1 -22
  374. package/src/cli/core/templates/mainCss.ts +0 -1
  375. package/src/cli/core/templates/webAppRouterTs.ts +0 -99
  376. package/src/cli/core/templates/webIndexTs.ts +1 -22
  377. package/src/cli/platform/__tests__/SecretsCommand.spec.ts +5 -3
  378. package/src/cli/platform/commands/SecretsCommand.ts +8 -6
  379. package/src/cli/platform/commands/platform.ts +192 -46
  380. package/src/cli/platform/index.ts +12 -52
  381. package/src/cli/{platform → platform-lib}/__tests__/CloudflareAdapter.spec.ts +426 -169
  382. package/src/cli/{platform → platform-lib}/__tests__/NamingService.spec.ts +91 -4
  383. package/src/cli/{platform → platform-lib}/__tests__/VercelAdapter.spec.ts +56 -85
  384. package/src/cli/{platform → platform-lib}/adapters/CloudflareAdapter.ts +402 -165
  385. package/src/cli/{platform → platform-lib}/adapters/PlatformAdapter.ts +62 -35
  386. package/src/cli/{platform → platform-lib}/adapters/VercelAdapter.ts +6 -10
  387. package/src/cli/{platform → platform-lib}/atoms/platformOptions.ts +34 -1
  388. package/src/cli/platform-lib/index.ts +67 -0
  389. package/src/cli/platform-lib/services/NamingService.ts +136 -0
  390. package/src/cli/{platform → platform-lib}/services/PlatformInspector.ts +60 -13
  391. package/src/cli/{platform → platform-lib}/services/PlatformOrchestrator.ts +54 -43
  392. package/src/cli/{platform → platform-lib}/services/WranglerApi.ts +4 -2
  393. package/src/command/__tests__/Runner.spec.ts +20 -0
  394. package/src/command/helpers/EnvUtils.ts +19 -3
  395. package/src/command/helpers/Runner.ts +12 -2
  396. package/src/command/providers/CliProvider.ts +34 -1
  397. package/src/{containers → container}/core/__tests__/$container.spec.ts +5 -5
  398. package/src/{containers → container}/core/index.ts +4 -4
  399. package/src/{containers → container}/core/index.workerd.ts +19 -3
  400. package/src/{containers → container}/core/primitives/$container.ts +1 -1
  401. package/src/{containers → container}/core/providers/CloudflareContainerProvider.ts +17 -19
  402. package/src/{containers → container}/core/providers/ContainerProvider.ts +16 -2
  403. package/src/{containers → container}/core/providers/MockContainerProvider.ts +1 -1
  404. package/src/core/Alepha.ts +49 -1
  405. package/src/core/__tests__/$env.spec.ts +42 -0
  406. package/src/core/__tests__/dump.spec.ts +47 -0
  407. package/src/email/cloudflare/__tests__/CloudflareEmailProvider.spec.ts +42 -10
  408. package/src/email/cloudflare/index.ts +14 -5
  409. package/src/email/cloudflare/providers/CloudflareEmailProvider.ts +54 -9
  410. package/src/logger/__tests__/Logger.spec.ts +55 -0
  411. package/src/logger/index.ts +13 -0
  412. package/src/logger/services/Logger.ts +31 -1
  413. package/src/orm/__tests__/orm-showcase-tests.ts +27 -0
  414. package/src/orm/__tests__/orm-showcase.spec.ts +12 -0
  415. package/src/orm/core/interfaces/PgQuery.ts +4 -1
  416. package/src/orm/core/services/Repository.ts +27 -11
  417. package/src/react/auth/hooks/useAuth.ts +10 -5
  418. package/src/react/core/__tests__/useQuery.browser.spec.tsx +25 -0
  419. package/src/react/core/hooks/useAction.ts +14 -3
  420. package/src/react/core/hooks/useQuery.ts +24 -4
  421. package/src/react/i18n/components/Translate.tsx +47 -0
  422. package/src/react/i18n/index.ts +2 -0
  423. package/src/react/intro/components/GettingStartedAdminSlide.tsx +2 -2
  424. package/src/react/router/__tests__/$page.spec.tsx +3 -2
  425. package/src/react/router/__tests__/page-can.spec.ts +18 -13
  426. package/src/react/router/hooks/useQueryParams.ts +114 -14
  427. package/src/react/router/primitives/$page.ts +85 -4
  428. package/src/react/router/providers/ReactBrowserRouterProvider.ts +3 -7
  429. package/src/react/router/providers/ReactServerProvider.ts +4 -13
  430. package/src/react/ui/services/SchemaControl.ts +3 -4
  431. package/src/server/core/providers/ServerMultipartProvider.ts +19 -0
  432. package/src/server/links/providers/LinkProvider.ts +10 -0
  433. package/dist/containers/core/index.d.ts.map +0 -1
  434. package/dist/containers/core/index.js.map +0 -1
  435. package/dist/containers/core/index.workerd.js.map +0 -1
  436. package/src/cli/core/templates/componentsJsonTs.ts +0 -39
  437. package/src/cli/core/templates/saasAdminLayoutTsx.ts +0 -77
  438. package/src/cli/core/templates/saasAdminPagesTsx.ts +0 -26
  439. package/src/cli/core/templates/saasAuthLayoutTsx.ts +0 -22
  440. package/src/cli/core/templates/saasAuthPagesTsx.ts +0 -62
  441. package/src/cli/core/templates/saasRealmProviderTs.ts +0 -52
  442. package/src/cli/platform/services/NamingService.ts +0 -54
  443. /package/dist/orm/core/{chunk-o8xxKEmq.js → chunk-B4FMCO8f.js} +0 -0
  444. /package/dist/react/testing/{chunk-6Ep1yQYe.js → chunk-BpyX8vjI.js} +0 -0
  445. /package/src/cli/{platform → platform-lib}/__tests__/GitHubSecretStore.spec.ts +0 -0
  446. /package/src/cli/{platform → platform-lib}/__tests__/PlatformCacheProvider.spec.ts +0 -0
  447. /package/src/cli/{platform → platform-lib}/__tests__/PlatformInspector.spec.ts +0 -0
  448. /package/src/cli/{platform → platform-lib}/__tests__/PlatformOrchestrator.spec.ts +0 -0
  449. /package/src/cli/{platform → platform-lib}/__tests__/SecretFilterService.spec.ts +0 -0
  450. /package/src/cli/{platform → platform-lib}/__tests__/detectResources.spec.ts +0 -0
  451. /package/src/cli/{platform → platform-lib}/providers/GitHubSecretStore.ts +0 -0
  452. /package/src/cli/{platform → platform-lib}/providers/MemorySecretStore.ts +0 -0
  453. /package/src/cli/{platform → platform-lib}/providers/PlatformCacheProvider.ts +0 -0
  454. /package/src/cli/{platform → platform-lib}/providers/SecretStoreProvider.ts +0 -0
  455. /package/src/cli/{platform → platform-lib}/schemas/cloudflare.ts +0 -0
  456. /package/src/cli/{platform → platform-lib}/schemas/platform.ts +0 -0
  457. /package/src/cli/{platform → platform-lib}/schemas/vercel.ts +0 -0
  458. /package/src/cli/{platform → platform-lib}/services/CloudflareApi.ts +0 -0
  459. /package/src/cli/{platform → platform-lib}/services/SecretFilterService.ts +0 -0
  460. /package/src/cli/{platform → platform-lib}/services/VercelApi.ts +0 -0
  461. /package/src/cli/{platform → platform-lib}/services/VercelCli.ts +0 -0
  462. /package/src/{containers → container}/core/interfaces/ContainerOptions.ts +0 -0
  463. /package/src/{containers → container}/core/providers/NodeContainerProvider.ts +0 -0
@@ -0,0 +1,2597 @@
1
+ import { $atom, $inject, $module, $state, Alepha, AlephaError, t } from "alepha";
2
+ import { createHash } from "node:crypto";
3
+ import { readFile } from "node:fs/promises";
4
+ import { join } from "node:path";
5
+ import { AlephaCliUtils, BuildCloudflareTask, PackageManagerUtils } from "alepha/cli";
6
+ import { Asker, EnvUtils, Runner } from "alepha/command";
7
+ import { $logger, ConsoleColorProvider } from "alepha/logger";
8
+ import { FileSystemProvider, ShellProvider } from "alepha/system";
9
+ import { S3mini } from "s3mini";
10
+ import { DateTimeProvider } from "alepha/datetime";
11
+ import { homedir, platform } from "node:os";
12
+ //#region ../../src/cli/platform-lib/atoms/platformOptions.ts
13
+ /**
14
+ * Platform deployment configuration atom.
15
+ *
16
+ * Filled from the `platform` section of `alepha.config.ts`.
17
+ * Read by `PlatformCommand` to resolve environments and adapters.
18
+ */
19
+ const platformOptions = $atom({
20
+ name: "alepha.cli.platform.options",
21
+ description: "Platform deployment configuration",
22
+ schema: t.optional(t.object({
23
+ /**
24
+ * Project name override. Defaults to root package.json "name".
25
+ */
26
+ name: t.optional(t.text()),
27
+ /**
28
+ * Default environment when --env is omitted.
29
+ *
30
+ * @default "production"
31
+ */
32
+ default: t.optional(t.text()),
33
+ /**
34
+ * Multi-tenancy mode — controls whether `--tenant <slug>` is
35
+ * accepted/required and how it shapes resource names + the domain.
36
+ *
37
+ * - `none` (default): single instance. `--tenant` is rejected.
38
+ * - `required`: every deploy needs `--tenant`; resources are named
39
+ * `<tenant>-<project>-<env>` and the host becomes
40
+ * `<tenant>.<domain>`. Omitting it errors.
41
+ * - `optional`: a base instance (no `--tenant`) plus per-tenant
42
+ * instances coexist.
43
+ *
44
+ * @default "none"
45
+ */
46
+ tenancy: t.optional(t.enum([
47
+ "none",
48
+ "optional",
49
+ "required"
50
+ ])),
51
+ /**
52
+ * Secret store configuration for syncing .env secrets
53
+ * to external providers (e.g. GitHub Actions environments).
54
+ */
55
+ secrets: t.optional(t.object({
56
+ /**
57
+ * Explicit override of the worker secret-key allowlist.
58
+ *
59
+ * By default the deploy `secrets` step uses the build manifest's
60
+ * `env` list (every key the app declares via `$env`, captured at
61
+ * build time) as the allowlist, resolving each value from
62
+ * `.env.<env>[.local]` first, then `process.env`. This lets CI
63
+ * deliver secrets via the job environment (no `.env` file on the
64
+ * runner) while only ever pushing declared keys — ambient runner
65
+ * vars (PATH, GITHUB_*, …) can never leak.
66
+ *
67
+ * Set `keys` to override that auto-detected list (e.g. to narrow it,
68
+ * or to add a key read via `process.env` rather than `$env`). When
69
+ * neither this nor a manifest is present, the `.env.<env>` file is
70
+ * itself the allowlist (legacy fallback).
71
+ */
72
+ keys: t.optional(t.array(t.text())),
73
+ /**
74
+ * Secret store backend.
75
+ */
76
+ store: t.optional(t.enum(["github"])),
77
+ /**
78
+ * Pattern for resolving environment names in the store.
79
+ * Placeholders: {project}, {env}.
80
+ *
81
+ * @default "{project}-{env}"
82
+ */
83
+ environmentPattern: t.optional(t.text())
84
+ })),
85
+ /**
86
+ * Named environments with their adapter and configuration.
87
+ */
88
+ environments: t.record(t.text({ description: "Environment name (e.g. 'production', 'staging', 'preview'). Used in resource naming and selected via --env." }), t.object({
89
+ adapter: t.enum(["cloudflare", "vercel"]),
90
+ /**
91
+ * Custom domain for the deployed worker (e.g. "api.example.com").
92
+ *
93
+ * On Cloudflare this is attached as a custom-domain route.
94
+ * Omit to use the adapter's default `*.workers.dev` / preview URL.
95
+ *
96
+ * Wildcards are supported for multi-tenant SaaS apps:
97
+ * `"*.club.alepha.dev"` routes every subdomain to the worker.
98
+ * Wildcard patterns require `zone` to be set, and the wildcard DNS
99
+ * record must already exist (proxied) in the Cloudflare zone.
100
+ */
101
+ domain: t.optional(t.text()),
102
+ /**
103
+ * Cloudflare zone name (e.g. "alepha.dev") that owns `domain`.
104
+ *
105
+ * Required when `domain` contains a wildcard (`*`). Ignored for
106
+ * plain custom domains, which Cloudflare resolves automatically.
107
+ */
108
+ zone: t.optional(t.text()),
109
+ /**
110
+ * Cloudflare data jurisdiction for R2 buckets and D1 databases.
111
+ * - "eu": data stays within the EU
112
+ * - "fedramp": FedRAMP-authorized regions
113
+ *
114
+ * Omit for the default (global) jurisdiction.
115
+ */
116
+ jurisdiction: t.optional(t.enum(["eu", "fedramp"])),
117
+ /**
118
+ * Cloudflare account ID to deploy into.
119
+ *
120
+ * Falls back to `CLOUDFLARE_ACCOUNT_ID` env var, then to the
121
+ * token's account when the token is scoped to exactly one.
122
+ * Required when the token has access to multiple accounts.
123
+ */
124
+ accountId: t.optional(t.text())
125
+ }))
126
+ }))
127
+ });
128
+ //#endregion
129
+ //#region ../../src/cli/platform-lib/providers/PlatformCacheProvider.ts
130
+ /**
131
+ * Caches cloud provider login state to avoid slow auth checks.
132
+ *
133
+ * Stored in node_modules/.alepha/platform.json (gitignored, project-scoped).
134
+ * TTL: 4 hours.
135
+ */
136
+ var PlatformCacheProvider = class PlatformCacheProvider {
137
+ static TTL_MS = 14400 * 1e3;
138
+ fs = $inject(FileSystemProvider);
139
+ dateTime = $inject(DateTimeProvider);
140
+ cachePath(root) {
141
+ return this.fs.join(root, "node_modules", ".alepha", "platform.json");
142
+ }
143
+ async isLoginFresh(root, adapter) {
144
+ const entry = (await this.readCache(root))[adapter];
145
+ if (!entry) return false;
146
+ return this.dateTime.nowMillis() - entry.lastLoginCheck < PlatformCacheProvider.TTL_MS;
147
+ }
148
+ async getAccountId(root, adapter) {
149
+ return (await this.readCache(root))[adapter]?.accountId;
150
+ }
151
+ async recordLogin(root, adapter, accountId) {
152
+ const cache = await this.readCache(root);
153
+ cache[adapter] = {
154
+ lastLoginCheck: this.dateTime.nowMillis(),
155
+ accountId
156
+ };
157
+ await this.writeCache(root, cache);
158
+ }
159
+ async readCache(root) {
160
+ const path = this.cachePath(root);
161
+ try {
162
+ return await this.fs.readJsonFile(path);
163
+ } catch {
164
+ return {};
165
+ }
166
+ }
167
+ async writeCache(root, cache) {
168
+ const path = this.cachePath(root);
169
+ const lastSlash = path.lastIndexOf("/");
170
+ const dir = lastSlash > 0 ? path.slice(0, lastSlash) : path;
171
+ await this.fs.mkdir(dir, { recursive: true }).catch(() => null);
172
+ await this.fs.writeFile(path, JSON.stringify(cache, null, 2));
173
+ }
174
+ };
175
+ //#endregion
176
+ //#region ../../src/cli/platform-lib/schemas/cloudflare.ts
177
+ const cloudflareAccountSchema = t.object({
178
+ id: t.string(),
179
+ name: t.string()
180
+ });
181
+ const cloudflareD1Schema = t.object({
182
+ uuid: t.string(),
183
+ name: t.string()
184
+ });
185
+ const cloudflareKVSchema = t.object({
186
+ id: t.string(),
187
+ title: t.string()
188
+ });
189
+ const cloudflareR2Schema = t.object({
190
+ name: t.string(),
191
+ creation_date: t.optional(t.string())
192
+ });
193
+ const cloudflareR2ListSchema = t.object({ buckets: t.array(cloudflareR2Schema) });
194
+ const cloudflareQueueSchema = t.object({
195
+ queue_id: t.string(),
196
+ queue_name: t.string()
197
+ });
198
+ const cloudflareQueueConsumerSchema = t.object({
199
+ consumer_id: t.string(),
200
+ service: t.string(),
201
+ environment: t.optional(t.string())
202
+ });
203
+ const cloudflareHyperdriveOriginSchema = t.object({ host: t.string() });
204
+ const cloudflareHyperdriveSchema = t.object({
205
+ id: t.string(),
206
+ name: t.string(),
207
+ origin: cloudflareHyperdriveOriginSchema
208
+ });
209
+ const cloudflareWorkerSchema = t.object({
210
+ id: t.string(),
211
+ created_on: t.string(),
212
+ modified_on: t.string()
213
+ });
214
+ const cloudflareDeploymentVersionSchema = t.object({
215
+ version_id: t.string(),
216
+ percentage: t.number()
217
+ });
218
+ const cloudflareDeploymentSchema = t.object({
219
+ id: t.string(),
220
+ versions: t.array(cloudflareDeploymentVersionSchema),
221
+ created_on: t.string()
222
+ });
223
+ const cloudflareDeploymentListSchema = t.object({ deployments: t.array(cloudflareDeploymentSchema) });
224
+ const cloudflareVersionSchema = t.object({
225
+ id: t.string(),
226
+ metadata: t.object({ created_on: t.string() }),
227
+ annotations: t.optional(t.record(t.string(), t.string()))
228
+ });
229
+ const cloudflareVersionListSchema = t.object({ items: t.array(cloudflareVersionSchema) });
230
+ const cloudflareSecretSchema = t.object({
231
+ name: t.string(),
232
+ type: t.string()
233
+ });
234
+ const createD1BodySchema = t.object({
235
+ name: t.string(),
236
+ primary_location_hint: t.optional(t.string()),
237
+ jurisdiction: t.optional(t.string())
238
+ });
239
+ const createKVBodySchema = t.object({ title: t.string() });
240
+ const createR2BodySchema = t.object({ name: t.string() });
241
+ const cloudflareR2TokenSchema = t.object({
242
+ id: t.string(),
243
+ accessKeyId: t.string(),
244
+ secretAccessKey: t.string()
245
+ });
246
+ const createR2TokenBodySchema = t.object({
247
+ name: t.string(),
248
+ policies: t.array(t.object({
249
+ effect: t.string(),
250
+ permissions: t.array(t.string()),
251
+ buckets: t.optional(t.array(t.string()))
252
+ }))
253
+ });
254
+ const createQueueBodySchema = t.object({ queue_name: t.string() });
255
+ const createHyperdriveOriginSchema = t.object({
256
+ scheme: t.string(),
257
+ host: t.string(),
258
+ port: t.number(),
259
+ database: t.string(),
260
+ user: t.string(),
261
+ password: t.string()
262
+ });
263
+ const createHyperdriveBodySchema = t.object({
264
+ name: t.string(),
265
+ origin: createHyperdriveOriginSchema
266
+ });
267
+ const putSecretBodySchema = t.object({
268
+ name: t.string(),
269
+ text: t.string(),
270
+ type: t.string()
271
+ });
272
+ const cloudflareApiErrorSchema = t.object({
273
+ code: t.number(),
274
+ message: t.string()
275
+ });
276
+ //#endregion
277
+ //#region ../../src/cli/platform-lib/services/WranglerApi.ts
278
+ /**
279
+ * Wraps wrangler CLI commands that are kept as shell-outs.
280
+ *
281
+ * Only used for operations where wrangler provides value
282
+ * beyond a raw API call: OAuth login, worker deploy (bundling/upload),
283
+ * D1 migrations, and secret bulk push.
284
+ */
285
+ var WranglerApi = class {
286
+ log = $logger();
287
+ shell = $inject(ShellProvider);
288
+ utils = $inject(AlephaCliUtils);
289
+ pm = $inject(PackageManagerUtils);
290
+ runner = $inject(Runner);
291
+ async runShell(command, options = {}) {
292
+ const capture = options.capture;
293
+ const output = await this.shell.run(command, {
294
+ ...options,
295
+ capture: capture ?? this.runner.useDynamicLogger
296
+ });
297
+ if (capture && !this.runner.useDynamicLogger) this.log.info(output);
298
+ return output;
299
+ }
300
+ /**
301
+ * Ensure wrangler is installed in the project.
302
+ */
303
+ async ensureInstalled(root, run) {
304
+ await this.pm.ensureDependency(root, "wrangler", {
305
+ dev: true,
306
+ exec: async (cmd, opts) => {
307
+ run.pause();
308
+ try {
309
+ await this.utils.exec(cmd, opts);
310
+ } finally {
311
+ run.resume();
312
+ }
313
+ }
314
+ });
315
+ }
316
+ /**
317
+ * Check if the user is authenticated. Returns the whoami output.
318
+ */
319
+ async whoami() {
320
+ return await this.runShell("wrangler whoami", {
321
+ resolve: true,
322
+ capture: true
323
+ });
324
+ }
325
+ /**
326
+ * Open the browser-based OAuth login flow.
327
+ */
328
+ async login() {
329
+ await this.runShell("wrangler login", { resolve: true });
330
+ }
331
+ /**
332
+ * Get the current auth token from wrangler (auto-refreshes if expired).
333
+ */
334
+ async getAuthToken() {
335
+ const output = await this.shell.run("wrangler auth token --json", {
336
+ resolve: true,
337
+ capture: true
338
+ });
339
+ return JSON.parse(output).token;
340
+ }
341
+ /**
342
+ * Deploy a worker via wrangler (handles bundling and upload).
343
+ *
344
+ * Returns the workers.dev URL if found in the output.
345
+ */
346
+ async deploy(workerName, configPath, root) {
347
+ return (await this.runShell(`wrangler deploy --name=${workerName} --no-bundle --config=${configPath}`, {
348
+ resolve: true,
349
+ capture: true,
350
+ root
351
+ })).match(/https:\/\/[^\s]*\.workers\.dev/)?.[0];
352
+ }
353
+ /**
354
+ * Apply D1 migrations remotely.
355
+ */
356
+ async d1MigrationsApply(dbName, configPath, root) {
357
+ await this.runShell(`wrangler d1 migrations apply ${dbName} --remote --config=${configPath}`, {
358
+ resolve: true,
359
+ env: { CI: "1" },
360
+ root
361
+ });
362
+ }
363
+ };
364
+ //#endregion
365
+ //#region ../../src/cli/platform-lib/services/CloudflareApi.ts
366
+ /**
367
+ * Thin wrapper over the Cloudflare REST API.
368
+ *
369
+ * Uses `wrangler auth token` to obtain credentials,
370
+ * then calls `fetch()` directly for all CRUD operations.
371
+ */
372
+ var CloudflareApi = class CloudflareApi {
373
+ static BASE = "https://api.cloudflare.com/client/v4";
374
+ log = $logger();
375
+ alepha = $inject(Alepha);
376
+ wrangler = $inject(WranglerApi);
377
+ token;
378
+ accountId;
379
+ jurisdiction;
380
+ /**
381
+ * Set the Cloudflare data jurisdiction for R2 and D1 resources.
382
+ *
383
+ * R2 buckets and D1 databases created under a jurisdiction live in a
384
+ * separate namespace — every R2 API call (list/create/delete) must include
385
+ * the `cf-r2-jurisdiction` header, and D1 create must include the field
386
+ * in the request body. Omit / pass `undefined` for the default (global).
387
+ */
388
+ setJurisdiction(jurisdiction) {
389
+ this.jurisdiction = jurisdiction;
390
+ }
391
+ /**
392
+ * Override the Cloudflare account ID (from platform config).
393
+ *
394
+ * When unset, `resolveAccountId` falls back to `CLOUDFLARE_ACCOUNT_ID` env
395
+ * var or the token's single account.
396
+ */
397
+ setAccountId(accountId) {
398
+ this.accountId = accountId;
399
+ }
400
+ /**
401
+ * Obtain the current auth token from wrangler.
402
+ */
403
+ async resolveToken() {
404
+ if (this.token) return this.token;
405
+ this.token = await this.wrangler.getAuthToken();
406
+ return this.token;
407
+ }
408
+ /**
409
+ * Resolve the Cloudflare account ID.
410
+ *
411
+ * Calls /accounts and picks the first one. Cached after first call.
412
+ */
413
+ async resolveAccountId() {
414
+ if (this.accountId) return this.accountId;
415
+ const fromEnv = process.env.CLOUDFLARE_ACCOUNT_ID;
416
+ if (fromEnv) {
417
+ this.accountId = fromEnv;
418
+ return this.accountId;
419
+ }
420
+ const res = await this.fetch("/accounts", { schema: t.array(cloudflareAccountSchema) });
421
+ if (res.length === 0) throw new AlephaError("No Cloudflare accounts found for this token.");
422
+ if (res.length > 1) {
423
+ const list = res.map((a) => ` - ${a.id} ${a.name}`).join("\n");
424
+ throw new AlephaError(`Cloudflare token has access to ${res.length} accounts; set \`CLOUDFLARE_ACCOUNT_ID\` or the \`accountId\` field in your platform config to pick one:\n${list}`);
425
+ }
426
+ this.accountId = res[0].id;
427
+ return this.accountId;
428
+ }
429
+ async listD1() {
430
+ const accountId = await this.resolveAccountId();
431
+ return await this.paginate(`/accounts/${accountId}/d1/database`, cloudflareD1Schema);
432
+ }
433
+ async createD1(name, location = "weur") {
434
+ const accountId = await this.resolveAccountId();
435
+ const body = { name };
436
+ if (this.jurisdiction) body.jurisdiction = this.jurisdiction;
437
+ else body.primary_location_hint = location;
438
+ return await this.fetch(`/accounts/${accountId}/d1/database`, {
439
+ method: "POST",
440
+ body,
441
+ bodySchema: createD1BodySchema,
442
+ schema: cloudflareD1Schema
443
+ });
444
+ }
445
+ async deleteD1(databaseId) {
446
+ const accountId = await this.resolveAccountId();
447
+ await this.fetch(`/accounts/${accountId}/d1/database/${databaseId}`, { method: "DELETE" });
448
+ }
449
+ async listKV() {
450
+ const accountId = await this.resolveAccountId();
451
+ return await this.paginate(`/accounts/${accountId}/storage/kv/namespaces`, cloudflareKVSchema, 100);
452
+ }
453
+ async createKV(title) {
454
+ const accountId = await this.resolveAccountId();
455
+ return await this.fetch(`/accounts/${accountId}/storage/kv/namespaces`, {
456
+ method: "POST",
457
+ body: { title },
458
+ bodySchema: createKVBodySchema,
459
+ schema: cloudflareKVSchema
460
+ });
461
+ }
462
+ async deleteKV(namespaceId) {
463
+ const accountId = await this.resolveAccountId();
464
+ await this.fetch(`/accounts/${accountId}/storage/kv/namespaces/${namespaceId}`, { method: "DELETE" });
465
+ }
466
+ async listR2() {
467
+ const accountId = await this.resolveAccountId();
468
+ return await this.paginateCursor(`/accounts/${accountId}/r2/buckets`, "buckets", cloudflareR2Schema);
469
+ }
470
+ async createR2(name) {
471
+ const accountId = await this.resolveAccountId();
472
+ await this.fetch(`/accounts/${accountId}/r2/buckets`, {
473
+ method: "POST",
474
+ body: { name },
475
+ bodySchema: createR2BodySchema
476
+ });
477
+ }
478
+ async deleteR2(name) {
479
+ const accountId = await this.resolveAccountId();
480
+ await this.fetch(`/accounts/${accountId}/r2/buckets/${name}`, { method: "DELETE" });
481
+ }
482
+ /**
483
+ * Mint a bucket-scoped R2 API token (S3 access key + secret) using the
484
+ * current bearer token. Used by teardown to wipe a bucket over the S3
485
+ * protocol without requiring users to pre-create R2 access keys.
486
+ *
487
+ * The returned token should be revoked with `deleteR2Token` as soon as the
488
+ * wipe is done.
489
+ */
490
+ async createR2Token(name, bucket) {
491
+ const accountId = await this.resolveAccountId();
492
+ return await this.fetch(`/accounts/${accountId}/r2/api-tokens`, {
493
+ method: "POST",
494
+ body: {
495
+ name,
496
+ policies: [{
497
+ effect: "allow",
498
+ permissions: ["admin-read-write"],
499
+ buckets: [bucket]
500
+ }]
501
+ },
502
+ bodySchema: createR2TokenBodySchema,
503
+ schema: cloudflareR2TokenSchema
504
+ });
505
+ }
506
+ async deleteR2Token(tokenId) {
507
+ const accountId = await this.resolveAccountId();
508
+ await this.fetch(`/accounts/${accountId}/r2/api-tokens/${tokenId}`, { method: "DELETE" });
509
+ }
510
+ async listQueues() {
511
+ const accountId = await this.resolveAccountId();
512
+ return await this.paginate(`/accounts/${accountId}/queues`, cloudflareQueueSchema);
513
+ }
514
+ async createQueue(name) {
515
+ const accountId = await this.resolveAccountId();
516
+ return await this.fetch(`/accounts/${accountId}/queues`, {
517
+ method: "POST",
518
+ body: { queue_name: name },
519
+ bodySchema: createQueueBodySchema,
520
+ schema: cloudflareQueueSchema
521
+ });
522
+ }
523
+ async deleteQueue(queueId) {
524
+ const accountId = await this.resolveAccountId();
525
+ await this.fetch(`/accounts/${accountId}/queues/${queueId}`, { method: "DELETE" });
526
+ }
527
+ async listQueueConsumers(queueId) {
528
+ const accountId = await this.resolveAccountId();
529
+ return await this.paginate(`/accounts/${accountId}/queues/${queueId}/consumers`, cloudflareQueueConsumerSchema);
530
+ }
531
+ async deleteQueueConsumer(queueId, consumerService) {
532
+ const accountId = await this.resolveAccountId();
533
+ const consumer = (await this.listQueueConsumers(queueId)).find((c) => c.service === consumerService);
534
+ if (!consumer) return;
535
+ await this.fetch(`/accounts/${accountId}/queues/${queueId}/consumers/${consumer.consumer_id}`, { method: "DELETE" });
536
+ }
537
+ async listHyperdrive() {
538
+ const accountId = await this.resolveAccountId();
539
+ return await this.paginate(`/accounts/${accountId}/hyperdrive/configs`, cloudflareHyperdriveSchema);
540
+ }
541
+ async createHyperdrive(name, connectionString) {
542
+ const accountId = await this.resolveAccountId();
543
+ return await this.fetch(`/accounts/${accountId}/hyperdrive/configs`, {
544
+ method: "POST",
545
+ body: {
546
+ name,
547
+ origin: this.parseConnectionString(connectionString)
548
+ },
549
+ bodySchema: createHyperdriveBodySchema,
550
+ schema: cloudflareHyperdriveSchema
551
+ });
552
+ }
553
+ async deleteHyperdrive(configId) {
554
+ const accountId = await this.resolveAccountId();
555
+ await this.fetch(`/accounts/${accountId}/hyperdrive/configs/${configId}`, { method: "DELETE" });
556
+ }
557
+ async getWorker(scriptName) {
558
+ const accountId = await this.resolveAccountId();
559
+ try {
560
+ return await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}`, { schema: cloudflareWorkerSchema });
561
+ } catch {
562
+ return;
563
+ }
564
+ }
565
+ async deleteWorker(scriptName) {
566
+ const accountId = await this.resolveAccountId();
567
+ await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}`, {
568
+ method: "DELETE",
569
+ query: { force: "true" }
570
+ });
571
+ }
572
+ async listDeployments(scriptName) {
573
+ const accountId = await this.resolveAccountId();
574
+ return (await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}/deployments`, {
575
+ schema: cloudflareDeploymentListSchema,
576
+ query: { per_page: "100" }
577
+ })).deployments;
578
+ }
579
+ async listVersions(scriptName) {
580
+ const accountId = await this.resolveAccountId();
581
+ return await this.paginateCursor(`/accounts/${accountId}/workers/scripts/${scriptName}/versions`, "items", cloudflareVersionSchema);
582
+ }
583
+ async listSecrets(scriptName) {
584
+ const accountId = await this.resolveAccountId();
585
+ return await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}/secrets`, { schema: t.array(cloudflareSecretSchema) });
586
+ }
587
+ async putSecret(scriptName, name, value) {
588
+ const accountId = await this.resolveAccountId();
589
+ await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}/secrets`, {
590
+ method: "PUT",
591
+ body: {
592
+ name,
593
+ text: value,
594
+ type: "secret_text"
595
+ },
596
+ bodySchema: putSecretBodySchema
597
+ });
598
+ }
599
+ /**
600
+ * Fetch the current worker bindings via the script-settings endpoint.
601
+ * Used to merge new secrets into the existing binding set in one PATCH
602
+ * (avoids the per-secret `putSecret` calls, each of which creates a
603
+ * Cloudflare deployment — pushing 7 secrets meant 7 deployment rows).
604
+ *
605
+ * Secret bindings come back with `name` + `type` but no `text` (they're
606
+ * write-only on Cloudflare's side); to preserve them across a PATCH we
607
+ * forward each one as `{ type, name }` and Cloudflare keeps the stored
608
+ * value.
609
+ */
610
+ async getWorkerSettings(scriptName) {
611
+ const accountId = await this.resolveAccountId();
612
+ return await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}/settings`);
613
+ }
614
+ /**
615
+ * Replace the worker's binding set in one call (= one Cloudflare
616
+ * deployment, regardless of how many secrets are being updated).
617
+ *
618
+ * The endpoint expects multipart FormData with a `settings` field whose
619
+ * value is a JSON-encoded `{ bindings: [...] }` — the `fetch` helper
620
+ * above is JSON-only, so this one bypasses it and calls `globalThis.fetch`
621
+ * directly. Mirrors what `wrangler secret bulk` does internally.
622
+ */
623
+ async patchWorkerBindings(scriptName, bindings) {
624
+ const accountId = await this.resolveAccountId();
625
+ const token = await this.resolveToken();
626
+ const url = `${CloudflareApi.BASE}/accounts/${accountId}/workers/scripts/${scriptName}/settings`;
627
+ const form = new FormData();
628
+ form.set("settings", JSON.stringify({ bindings }));
629
+ const response = await globalThis.fetch(url, {
630
+ method: "PATCH",
631
+ headers: { Authorization: `Bearer ${token}` },
632
+ body: form
633
+ });
634
+ const json = await response.json().catch(() => null);
635
+ if (!response.ok || !json?.success) {
636
+ const messages = json?.errors?.map((e) => e.message).join(", ");
637
+ throw new AlephaError(`Cloudflare API error (PATCH /workers/scripts/${scriptName}/settings): ${messages ?? response.statusText}`);
638
+ }
639
+ }
640
+ async fetch(path, options = {}) {
641
+ const token = await this.resolveToken();
642
+ const { method = "GET", body, query } = options;
643
+ let url = `${CloudflareApi.BASE}${path}`;
644
+ if (query) {
645
+ const params = new URLSearchParams(query);
646
+ url += `?${params.toString()}`;
647
+ }
648
+ const headers = { Authorization: `Bearer ${token}` };
649
+ if (this.jurisdiction && /\/r2\//.test(path)) headers["cf-r2-jurisdiction"] = this.jurisdiction;
650
+ const init = {
651
+ method,
652
+ headers
653
+ };
654
+ if (body) {
655
+ headers["Content-Type"] = "application/json";
656
+ const validated = options.bodySchema ? this.alepha.codec.validate(options.bodySchema, body) : body;
657
+ init.body = JSON.stringify(validated);
658
+ }
659
+ const json = await (await globalThis.fetch(url, init)).json();
660
+ if (!json.success) throw new AlephaError(`Cloudflare API error (${method} ${path}): ${json.errors.map((e) => e.message).join(", ")}`);
661
+ if (options.schema) return this.alepha.codec.validate(options.schema, json.result);
662
+ return json.result;
663
+ }
664
+ /**
665
+ * Paginate a page-based list endpoint (`result_info.total_pages`).
666
+ *
667
+ * Cloudflare defaults to `per_page=20`; we push it to 1000 (max on most
668
+ * list endpoints) and loop if more pages exist. Each page is validated
669
+ * against the item schema.
670
+ */
671
+ async paginate(path, itemSchema, perPage = 1e3) {
672
+ const results = [];
673
+ let page = 1;
674
+ while (true) {
675
+ const token = await this.resolveToken();
676
+ const url = `${CloudflareApi.BASE}${path}?per_page=${perPage}&page=${page}`;
677
+ const headers = { Authorization: `Bearer ${token}` };
678
+ if (this.jurisdiction && /\/r2\//.test(path)) headers["cf-r2-jurisdiction"] = this.jurisdiction;
679
+ const json = await (await globalThis.fetch(url, {
680
+ method: "GET",
681
+ headers
682
+ })).json();
683
+ if (!json.success) throw new AlephaError(`Cloudflare API error (GET ${path}): ${json.errors.map((e) => e.message).join(", ")}`);
684
+ const validated = this.alepha.codec.validate(t.array(itemSchema), json.result);
685
+ results.push(...validated);
686
+ const totalPages = json.result_info?.total_pages;
687
+ if (!totalPages || page >= totalPages || validated.length === 0) break;
688
+ page++;
689
+ }
690
+ return results;
691
+ }
692
+ /**
693
+ * Paginate a cursor-based list endpoint where `result` is an object
694
+ * containing both the items array and a `cursor` field (R2 buckets,
695
+ * Workers versions). Returns the flattened item array.
696
+ */
697
+ async paginateCursor(path, itemsKey, itemSchema, perPage = 1e3) {
698
+ const results = [];
699
+ let cursor;
700
+ while (true) {
701
+ const query = { per_page: String(perPage) };
702
+ if (cursor) query.cursor = cursor;
703
+ const res = await this.fetch(path, { query });
704
+ const items = res[itemsKey] ?? [];
705
+ const validated = this.alepha.codec.validate(t.array(itemSchema), items);
706
+ results.push(...validated);
707
+ const nextCursor = res.cursor;
708
+ if (!nextCursor || validated.length === 0) break;
709
+ cursor = nextCursor;
710
+ }
711
+ return results;
712
+ }
713
+ /**
714
+ * Parse a postgres:// connection string into Hyperdrive origin fields.
715
+ */
716
+ parseConnectionString(connectionString) {
717
+ const url = new URL(connectionString);
718
+ return {
719
+ scheme: "postgres",
720
+ host: url.hostname,
721
+ port: Number(url.port) || 5432,
722
+ database: url.pathname.slice(1),
723
+ user: decodeURIComponent(url.username),
724
+ password: decodeURIComponent(url.password)
725
+ };
726
+ }
727
+ };
728
+ //#endregion
729
+ //#region ../../src/cli/platform-lib/services/NamingService.ts
730
+ /**
731
+ * Validate a `--tenant` value against the app's declared `tenancy` and
732
+ * return the effective tenant (or `undefined` for a base/non-tenanted
733
+ * deploy). Throws on any matrix violation so a misconfigured deploy fails
734
+ * fast instead of landing on the wrong resource names / host.
735
+ */
736
+ function resolveTenant(tenancy, tenant) {
737
+ const mode = tenancy ?? "none";
738
+ if (tenant !== void 0 && !/^[a-z0-9][a-z0-9-]*$/.test(tenant)) throw new AlephaError(`Invalid --tenant "${tenant}": must be a slug (lowercase alphanumeric + dashes, e.g. "b14").`);
739
+ if (mode === "none") {
740
+ if (tenant !== void 0) throw new AlephaError(`This app is not tenanted (tenancy: "none") but --tenant "${tenant}" was given. Set tenancy: "optional" | "required" in platform() to deploy per-tenant.`);
741
+ return;
742
+ }
743
+ if (mode === "required" && tenant === void 0) throw new AlephaError(`This app requires a tenant (tenancy: "required"). Pass --tenant <slug>.`);
744
+ return tenant;
745
+ }
746
+ /**
747
+ * Resolve the host a deploy is served on.
748
+ *
749
+ * - `override` (V2 custom domains, e.g. `reservation.club-b14.fr`) wins
750
+ * outright when supplied — not wired to a flag today, but the single
751
+ * seam a future `--domain` / Rocket `config.hostname` plugs into.
752
+ * - else a tenant becomes the leftmost DNS label: `b14` + `alepha.club`
753
+ * → `b14.alepha.club`.
754
+ * - else the base domain is used as-is.
755
+ */
756
+ function tenantDomain(base, tenant, override) {
757
+ if (override) return override;
758
+ if (!base) return base;
759
+ return tenant ? `${tenant}.${base}` : base;
760
+ }
761
+ /**
762
+ * Generates deterministic resource names for cloud deployments.
763
+ *
764
+ * Pattern: `<tenant>-<project>-<env>` (tenant segment omitted when the
765
+ * deploy isn't tenanted).
766
+ *
767
+ * All segments are slugified (lowercase, alphanumeric + dashes, max 63
768
+ * chars). One app per workspace — see `alepha platform`.
769
+ */
770
+ var NamingService = class {
771
+ forContext(project, env, tenant) {
772
+ return new NamingContext([
773
+ tenant,
774
+ project,
775
+ env
776
+ ].filter((segment) => !!segment).map((segment) => this.slugify(segment)).join("-"));
777
+ }
778
+ slugify(name) {
779
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
780
+ }
781
+ };
782
+ var NamingContext = class {
783
+ prefix;
784
+ constructor(prefix) {
785
+ this.prefix = prefix;
786
+ }
787
+ worker() {
788
+ return this.prefix;
789
+ }
790
+ d1() {
791
+ return this.prefix;
792
+ }
793
+ hyperdrive() {
794
+ return this.prefix;
795
+ }
796
+ r2() {
797
+ return this.prefix;
798
+ }
799
+ kv() {
800
+ return this.prefix;
801
+ }
802
+ queue() {
803
+ return this.prefix;
804
+ }
805
+ };
806
+ //#endregion
807
+ //#region ../../src/cli/platform-lib/adapters/PlatformAdapter.ts
808
+ /**
809
+ * Abstract platform adapter.
810
+ *
811
+ * Each cloud provider (Cloudflare, AKS, docker-compose) implements this.
812
+ * The PlatformOrchestrator calls these methods in the correct order.
813
+ */
814
+ var PlatformAdapter = class {
815
+ /**
816
+ * Create/ensure cloud resources exist (DB, buckets, queues).
817
+ * Not all adapters provision -- AKS defers to Helm.
818
+ */
819
+ async provision(_ctx, _run) {}
820
+ /**
821
+ * Run database migrations.
822
+ */
823
+ async migrate(_ctx, _run) {}
824
+ /**
825
+ * Export the deployed database to a local file — the remote → local dev
826
+ * snapshot workflow. Adapter/dialect specific; the default refuses.
827
+ */
828
+ async exportDb(_ctx, _run, _options = {}) {
829
+ throw new AlephaError(`Database export is not supported by the '${this.constructor.name}' adapter.`);
830
+ }
831
+ /**
832
+ * Push runtime secrets to the deployed worker(s).
833
+ *
834
+ * Reads secrets from `.env.{env}` files (parsed, not from process.env),
835
+ * filters out vars already handled by bindings (DATABASE_URL, R2, etc.),
836
+ * and pushes the rest via the platform's secret management.
837
+ */
838
+ async secrets(_ctx, _run) {}
839
+ };
840
+ //#endregion
841
+ //#region ../../src/cli/platform-lib/adapters/CloudflareAdapter.ts
842
+ /**
843
+ * Cloudflare Workers adapter.
844
+ *
845
+ * Uses the Cloudflare REST API (via CloudflareApi) for resource provisioning
846
+ * and teardown, and wrangler CLI (via WranglerApi) for login, deploy,
847
+ * D1 migrations, and secret bulk push.
848
+ */
849
+ var CloudflareAdapter = class CloudflareAdapter extends PlatformAdapter {
850
+ log = $logger();
851
+ fs = $inject(FileSystemProvider);
852
+ shell = $inject(ShellProvider);
853
+ cache = $inject(PlatformCacheProvider);
854
+ alepha = $inject(Alepha);
855
+ envUtils = $inject(EnvUtils);
856
+ api = $inject(CloudflareApi);
857
+ wrangler = $inject(WranglerApi);
858
+ runner = $inject(Runner);
859
+ buildTask = $inject(BuildCloudflareTask);
860
+ options = $state(platformOptions);
861
+ provisionedD1Id;
862
+ provisionedHyperdriveId;
863
+ provisionedKVIds = /* @__PURE__ */ new Map();
864
+ /**
865
+ * Check if the user's DATABASE_URL points to an external Postgres database.
866
+ * If so, we use Hyperdrive instead of D1.
867
+ *
868
+ * Reads from `.env.{env}` first, falls back to `process.env`.
869
+ */
870
+ async isPostgres(ctx) {
871
+ return !!((await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`])).DATABASE_URL ?? process.env.DATABASE_URL)?.startsWith("postgres:");
872
+ }
873
+ /**
874
+ * Propagate the environment's data-jurisdiction setting to the API client.
875
+ *
876
+ * Must be invoked at the top of every entry point (authenticate, build,
877
+ * deploy, secrets, provision, migrate, inspect, teardown) because
878
+ * CloudflareApi is a singleton reused across env invocations.
879
+ */
880
+ configureApi(ctx) {
881
+ this.api.setJurisdiction(ctx.envConfig.jurisdiction);
882
+ this.api.setAccountId(ctx.envConfig.accountId);
883
+ }
884
+ async runShell(command, options = {}) {
885
+ const capture = options.capture;
886
+ const output = await this.shell.run(command, {
887
+ ...options,
888
+ capture: capture ?? this.runner.useDynamicLogger
889
+ });
890
+ if (capture && !this.runner.useDynamicLogger) this.log.info(output);
891
+ return output;
892
+ }
893
+ async authenticate(ctx, run) {
894
+ this.configureApi(ctx);
895
+ await run({
896
+ name: "authenticate",
897
+ handler: async () => {
898
+ await this.wrangler.ensureInstalled(ctx.root, run);
899
+ let needsLogin = false;
900
+ try {
901
+ await this.wrangler.getAuthToken();
902
+ } catch {
903
+ needsLogin = true;
904
+ }
905
+ if (needsLogin) {
906
+ run.pause();
907
+ await this.wrangler.login();
908
+ run.resume();
909
+ }
910
+ if (await this.cache.isLoginFresh(ctx.root, "cloudflare")) return;
911
+ try {
912
+ const accountId = await this.api.resolveAccountId();
913
+ await this.cache.recordLogin(ctx.root, "cloudflare", accountId);
914
+ } catch {
915
+ await this.cache.recordLogin(ctx.root, "cloudflare");
916
+ }
917
+ }
918
+ });
919
+ }
920
+ async build(ctx, run) {
921
+ this.configureApi(ctx);
922
+ const appDir = ctx.root;
923
+ const env = {};
924
+ if (ctx.resources.hasDatabase) {
925
+ if (this.provisionedHyperdriveId) {
926
+ env.HYPERDRIVE_ID = this.provisionedHyperdriveId;
927
+ const pgSchema = (await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`])).POSTGRES_SCHEMA ?? process.env.POSTGRES_SCHEMA;
928
+ if (pgSchema) env.POSTGRES_SCHEMA = pgSchema;
929
+ } else if (this.provisionedD1Id) env.DATABASE_URL = `d1://${ctx.naming.d1()}:${this.provisionedD1Id}`;
930
+ }
931
+ if (ctx.resources.hasBucket) env.R2_BUCKET_NAME = ctx.naming.r2();
932
+ if (ctx.resources.hasKV) {
933
+ const kvName = ctx.naming.kv();
934
+ env.CLOUDFLARE_KV_NAME = kvName;
935
+ const kvId = this.provisionedKVIds.get(kvName);
936
+ if (kvId) env.CLOUDFLARE_KV_ID = kvId;
937
+ }
938
+ if (ctx.resources.hasQueue) env.CLOUDFLARE_QUEUE_NAME = ctx.naming.queue();
939
+ const host = tenantDomain(ctx.envConfig.domain, ctx.tenant);
940
+ if (host) {
941
+ if (host.includes("*") && !ctx.envConfig.zone) throw new AlephaError(`Wildcard domain "${host}" requires "zone" to be set in the environment config (the Cloudflare zone name, e.g. "alepha.dev").`);
942
+ env.CLOUDFLARE_DOMAIN = host;
943
+ if (ctx.envConfig.zone) env.CLOUDFLARE_ZONE = ctx.envConfig.zone;
944
+ }
945
+ if (ctx.envConfig.jurisdiction) env.CLOUDFLARE_JURISDICTION = ctx.envConfig.jurisdiction;
946
+ if (ctx.prebuilt) {
947
+ await run({
948
+ name: "alepha build -t cloudflare --prebuilt (in-process)",
949
+ handler: async () => {
950
+ await this.runBuildInProcess(appDir, env);
951
+ }
952
+ });
953
+ return;
954
+ }
955
+ const cmd = "alepha build -t cloudflare";
956
+ await run({
957
+ name: cmd,
958
+ handler: async () => {
959
+ await this.runShell(cmd, {
960
+ root: appDir,
961
+ env
962
+ });
963
+ }
964
+ });
965
+ }
966
+ /**
967
+ * Library-embed of `alepha build -t cloudflare --prebuilt`. Loads the
968
+ * pre-built `dist/manifest.json`, sets the per-tenant env vars on
969
+ * `process.env` for the duration of the call (the task's enhance*
970
+ * methods read them directly), then runs `BuildCloudflareTask`
971
+ * against a synthetic context.
972
+ *
973
+ * `ctx.alepha` is intentionally null — in manifest mode the task
974
+ * reads resources/crons/containers from `ctx.manifest` and never
975
+ * dereferences `ctx.alepha`. Same for `entry` and `hasClient`:
976
+ * prebuilt mode skips the bundle tasks; only the wrangler.jsonc /
977
+ * worker-entrypoint emission runs.
978
+ */
979
+ async runBuildInProcess(root, env) {
980
+ const manifestPath = join(root, "dist", "manifest.json");
981
+ let manifest;
982
+ try {
983
+ manifest = JSON.parse(await readFile(manifestPath, "utf-8"));
984
+ } catch (err) {
985
+ throw new AlephaError(`Cannot read ${manifestPath}: ${err.message}. Prebuilt deploys require dist/manifest.json (emitted by \`alepha build -t cloudflare\`).`);
986
+ }
987
+ const ctx = {
988
+ alepha: null,
989
+ options: {
990
+ target: "cloudflare",
991
+ output: {
992
+ dist: "dist",
993
+ public: "public"
994
+ }
995
+ },
996
+ run: this.runner.run,
997
+ root,
998
+ entry: {
999
+ root,
1000
+ server: ""
1001
+ },
1002
+ hasClient: false,
1003
+ manifest,
1004
+ platformOptions: null,
1005
+ flags: { prebuilt: true }
1006
+ };
1007
+ const previous = {};
1008
+ for (const [k, v] of Object.entries(env)) {
1009
+ previous[k] = process.env[k];
1010
+ process.env[k] = v;
1011
+ }
1012
+ try {
1013
+ await this.buildTask.run(ctx);
1014
+ } finally {
1015
+ for (const [k, prev] of Object.entries(previous)) if (prev === void 0) delete process.env[k];
1016
+ else process.env[k] = prev;
1017
+ }
1018
+ }
1019
+ async deploy(ctx, run) {
1020
+ this.configureApi(ctx);
1021
+ const workerName = ctx.naming.worker();
1022
+ const distDir = this.fs.join(ctx.root, "dist");
1023
+ let url;
1024
+ await run({
1025
+ name: `deploy worker ${ctx.project}`,
1026
+ handler: async () => {
1027
+ url = await this.wrangler.deploy(workerName, `${distDir}/wrangler.jsonc`, ctx.root);
1028
+ }
1029
+ });
1030
+ return url;
1031
+ }
1032
+ /**
1033
+ * Vars that are handled by wrangler bindings or build config.
1034
+ * These should not be pushed as secrets.
1035
+ */
1036
+ static EXCLUDED_SECRET_KEYS = new Set([
1037
+ "DATABASE_URL",
1038
+ "R2_BUCKET_NAME",
1039
+ "CLOUDFLARE_DOMAIN",
1040
+ "CLOUDFLARE_ZONE",
1041
+ "CLOUDFLARE_JURISDICTION",
1042
+ "HYPERDRIVE_ID",
1043
+ "POSTGRES_SCHEMA",
1044
+ "NODE_ENV",
1045
+ "LOG_LEVEL",
1046
+ "LOG_FORMAT",
1047
+ "SERVER_HOST",
1048
+ "SERVER_PORT",
1049
+ "TRUST_PROXY",
1050
+ "REACT_SSR_ENABLED",
1051
+ "DATABASE_SYNC",
1052
+ "DEBUG"
1053
+ ]);
1054
+ /**
1055
+ * Read the build manifest's `env` list (every key the app declares via
1056
+ * `$env`) from `dist/manifest.json`. Used as the default worker-secret
1057
+ * allowlist. Returns `undefined` when the manifest is absent or predates
1058
+ * the `env` field, so the caller falls back to the `.env` file keys.
1059
+ */
1060
+ async readManifestEnvKeys(root) {
1061
+ try {
1062
+ const manifest = await this.fs.readJsonFile(this.fs.join(root, "dist", "manifest.json"));
1063
+ return Array.isArray(manifest.env) ? manifest.env : void 0;
1064
+ } catch {
1065
+ return;
1066
+ }
1067
+ }
1068
+ async secrets(ctx, run) {
1069
+ this.configureApi(ctx);
1070
+ const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
1071
+ const declaredKeys = this.options?.secrets?.keys;
1072
+ const manifestKeys = await this.readManifestEnvKeys(ctx.root);
1073
+ const localKeys = Object.keys(await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}.local`]));
1074
+ const keys = declaredKeys ?? Array.from(new Set([...manifestKeys ?? Object.keys(envVars), ...localKeys]));
1075
+ const secrets = {};
1076
+ for (const key of keys) {
1077
+ if (CloudflareAdapter.EXCLUDED_SECRET_KEYS.has(key)) continue;
1078
+ if (key.startsWith("VITE_")) continue;
1079
+ const value = envVars[key] ?? process.env[key];
1080
+ if (!value) continue;
1081
+ secrets[key] = value;
1082
+ }
1083
+ if (!secrets.PUBLIC_URL) {
1084
+ const url = this.publicUrl(ctx);
1085
+ if (url) secrets.PUBLIC_URL = url;
1086
+ }
1087
+ if (Object.keys(secrets).length === 0) return;
1088
+ const hash = computeSecretsHash(secrets);
1089
+ {
1090
+ const workerName = ctx.naming.worker();
1091
+ await run({
1092
+ name: `push secrets to ${workerName} (bulk)`,
1093
+ handler: async () => {
1094
+ const existingBindings = (await this.api.getWorkerSettings(workerName)).bindings ?? [];
1095
+ if (existingBindings.find((b) => b.type === "plain_text" && b.name === CloudflareAdapter.SECRETS_HASH_BINDING)?.text === hash) {
1096
+ this.log.info(`Secrets for ${workerName} unchanged (hash ${hash.slice(0, 8)}…), skipping push.`);
1097
+ return;
1098
+ }
1099
+ const overwriting = new Set(Object.keys(secrets));
1100
+ const inherit = existingBindings.filter((b) => !(b.type === "plain_text" && b.name === CloudflareAdapter.SECRETS_HASH_BINDING) && (b.type !== "secret_text" || !overwriting.has(b.name))).map((b) => ({
1101
+ type: b.type,
1102
+ name: b.name
1103
+ }));
1104
+ const upsert = Object.entries(secrets).map(([name, text]) => ({
1105
+ type: "secret_text",
1106
+ name,
1107
+ text
1108
+ }));
1109
+ await this.api.patchWorkerBindings(workerName, [
1110
+ ...inherit,
1111
+ ...upsert,
1112
+ {
1113
+ type: "plain_text",
1114
+ name: CloudflareAdapter.SECRETS_HASH_BINDING,
1115
+ text: hash
1116
+ }
1117
+ ]);
1118
+ }
1119
+ });
1120
+ }
1121
+ }
1122
+ /**
1123
+ * Public base URL for this deploy, derived from the configured domain
1124
+ * (honoring tenant subdomains). Returns undefined when no domain is set or
1125
+ * the host is a wildcard — there's no single resolvable origin to point at.
1126
+ */
1127
+ publicUrl(ctx) {
1128
+ const host = tenantDomain(ctx.envConfig.domain, ctx.tenant);
1129
+ if (!host || host.includes("*")) return;
1130
+ return `https://${host}`;
1131
+ }
1132
+ /**
1133
+ * Plain-text binding used to fingerprint the deployed secret set so the
1134
+ * next `up` can skip the PATCH when nothing has changed.
1135
+ */
1136
+ static SECRETS_HASH_BINDING = "ALEPHA_SECRETS_HASH";
1137
+ async provision(ctx, run) {
1138
+ this.configureApi(ctx);
1139
+ const needsDB = ctx.resources.hasDatabase;
1140
+ const needsBucket = ctx.resources.hasBucket;
1141
+ const postgres = needsDB && await this.isPostgres(ctx);
1142
+ const tasks = [];
1143
+ if (needsDB) if (postgres) {
1144
+ const hdName = ctx.naming.hyperdrive();
1145
+ const dbUrl = (await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`])).DATABASE_URL ?? process.env.DATABASE_URL;
1146
+ tasks.push({
1147
+ name: `provision hyperdrive (${hdName})`,
1148
+ handler: async () => {
1149
+ this.provisionedHyperdriveId = await this.ensureHyperdrive(hdName, dbUrl);
1150
+ }
1151
+ });
1152
+ } else {
1153
+ const dbName = ctx.naming.d1();
1154
+ tasks.push({
1155
+ name: `provision d1 (${dbName})`,
1156
+ handler: async () => {
1157
+ this.provisionedD1Id = await this.ensureD1(dbName);
1158
+ }
1159
+ });
1160
+ }
1161
+ if (needsBucket) {
1162
+ const bucketName = ctx.naming.r2();
1163
+ tasks.push({
1164
+ name: `provision r2 (${bucketName})`,
1165
+ handler: async () => {
1166
+ await this.ensureR2(bucketName);
1167
+ }
1168
+ });
1169
+ }
1170
+ if (ctx.resources.hasKV) {
1171
+ const kvName = ctx.naming.kv();
1172
+ tasks.push({
1173
+ name: `provision kv (${kvName})`,
1174
+ handler: async () => {
1175
+ this.provisionedKVIds.set(kvName, await this.ensureKV(kvName));
1176
+ }
1177
+ });
1178
+ }
1179
+ if (ctx.resources.hasQueue) {
1180
+ const queueName = ctx.naming.queue();
1181
+ tasks.push({
1182
+ name: `provision queue (${queueName})`,
1183
+ handler: async () => {
1184
+ await this.ensureQueue(queueName);
1185
+ }
1186
+ });
1187
+ }
1188
+ await run(tasks);
1189
+ }
1190
+ async migrate(ctx, run) {
1191
+ this.configureApi(ctx);
1192
+ if (!ctx.resources.hasDatabase) return;
1193
+ if (await this.isPostgres(ctx)) await this.migratePostgres(ctx, run);
1194
+ else await this.migrateD1(ctx, run);
1195
+ }
1196
+ async exportDb(ctx, run, options = {}) {
1197
+ this.configureApi(ctx);
1198
+ if (!ctx.resources.hasDatabase) throw new AlephaError("No database detected for this app — nothing to export.");
1199
+ if (await this.isPostgres(ctx)) throw new AlephaError("Database export currently supports Cloudflare D1 only — Postgres/Hyperdrive export (pg_dump) is not implemented yet.");
1200
+ const dbName = ctx.naming.d1();
1201
+ const tmpDir = this.fs.join(ctx.root, "node_modules", ".alepha");
1202
+ const sqlPath = this.fs.join(tmpDir, `${dbName}.sql`);
1203
+ const dbPath = options.output ?? this.fs.join(tmpDir, "sqlite.db");
1204
+ await this.fs.mkdir(tmpDir, { recursive: true });
1205
+ await run(`wrangler d1 export ${dbName} --remote --output=${sqlPath}`, { alias: `export D1 ${dbName} → ${sqlPath}` });
1206
+ await this.fs.rm(dbPath, { force: true });
1207
+ await run(`sh -c "sqlite3 '${dbPath}' < '${sqlPath}'"`, { alias: `import dump → ${dbPath}` });
1208
+ if (!options.keepSql) await this.fs.rm(sqlPath, { force: true });
1209
+ }
1210
+ async migrateD1(ctx, run) {
1211
+ const dbName = ctx.naming.d1();
1212
+ await run({
1213
+ name: "migrate d1",
1214
+ handler: async () => {
1215
+ const migrationsDir = this.fs.join(ctx.root, "migrations", "sqlite");
1216
+ const env = { DATABASE_URL: this.provisionedD1Id ? `d1://${dbName}:${this.provisionedD1Id}` : `d1://${dbName}` };
1217
+ if (!ctx.prebuilt) if (await this.fs.exists(migrationsDir)) await this.runShell(`alepha db migrations check --mode ${ctx.env}`, {
1218
+ resolve: true,
1219
+ env
1220
+ });
1221
+ else await this.runShell(`alepha db migrations create --mode ${ctx.env}`, {
1222
+ resolve: true,
1223
+ env
1224
+ });
1225
+ const distMigrations = this.fs.join(ctx.root, "dist", "migrations");
1226
+ await this.fs.cp(migrationsDir, distMigrations);
1227
+ await this.wrangler.d1MigrationsApply(dbName, "dist/wrangler.jsonc", ctx.root);
1228
+ await this.fs.rm(distMigrations, { recursive: true });
1229
+ }
1230
+ });
1231
+ }
1232
+ async migratePostgres(ctx, run) {
1233
+ if (ctx.prebuilt) throw new AlephaError("Postgres migrations are not yet supported in prebuilt mode. Use the `alepha platform up` CLI for now.");
1234
+ await run({
1235
+ name: "migrate postgres",
1236
+ handler: async () => {
1237
+ const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
1238
+ const env = { DATABASE_URL: envVars.DATABASE_URL ?? process.env.DATABASE_URL };
1239
+ if (envVars.POSTGRES_SCHEMA ?? process.env.POSTGRES_SCHEMA) env.POSTGRES_SCHEMA = envVars.POSTGRES_SCHEMA ?? process.env.POSTGRES_SCHEMA;
1240
+ await this.runShell(`alepha db migrations apply --mode ${ctx.env}`, {
1241
+ resolve: true,
1242
+ env
1243
+ });
1244
+ }
1245
+ });
1246
+ }
1247
+ async inspect(ctx, run) {
1248
+ this.configureApi(ctx);
1249
+ const state = {
1250
+ workers: [],
1251
+ databases: [],
1252
+ buckets: [],
1253
+ kvNamespaces: [],
1254
+ queues: [],
1255
+ secrets: []
1256
+ };
1257
+ const tasks = [];
1258
+ {
1259
+ const name = ctx.naming.worker();
1260
+ tasks.push({
1261
+ name: `inspect worker (${name})`,
1262
+ handler: async () => {
1263
+ try {
1264
+ const deployment = await this.getActiveDeployment(name);
1265
+ if (deployment) state.workers.push({
1266
+ name,
1267
+ exists: true,
1268
+ version: deployment.versionId,
1269
+ tag: deployment.tag,
1270
+ createdAt: deployment.createdAt
1271
+ });
1272
+ else state.workers.push({
1273
+ name,
1274
+ exists: false
1275
+ });
1276
+ } catch {
1277
+ state.workers.push({
1278
+ name,
1279
+ exists: false
1280
+ });
1281
+ }
1282
+ }
1283
+ });
1284
+ }
1285
+ if (ctx.resources.hasDatabase) if (await this.isPostgres(ctx)) {
1286
+ const hdName = ctx.naming.hyperdrive();
1287
+ tasks.push({
1288
+ name: `inspect hyperdrive (${hdName})`,
1289
+ handler: async () => {
1290
+ const existing = (await this.api.listHyperdrive()).find((c) => c.name === hdName);
1291
+ state.databases.push({
1292
+ name: hdName,
1293
+ exists: !!existing,
1294
+ id: existing?.id,
1295
+ detail: existing?.origin.host
1296
+ });
1297
+ }
1298
+ });
1299
+ } else {
1300
+ const dbName = ctx.naming.d1();
1301
+ tasks.push({
1302
+ name: `inspect d1 (${dbName})`,
1303
+ handler: async () => {
1304
+ const existing = (await this.api.listD1()).find((db) => db.name === dbName);
1305
+ state.databases.push({
1306
+ name: dbName,
1307
+ exists: !!existing,
1308
+ id: existing?.uuid
1309
+ });
1310
+ }
1311
+ });
1312
+ }
1313
+ if (ctx.resources.hasBucket) {
1314
+ const bucketName = ctx.naming.r2();
1315
+ tasks.push({
1316
+ name: `inspect r2 (${bucketName})`,
1317
+ handler: async () => {
1318
+ const existing = (await this.api.listR2()).find((b) => b.name === bucketName);
1319
+ state.buckets.push({
1320
+ name: bucketName,
1321
+ exists: !!existing,
1322
+ id: existing?.creation_date
1323
+ });
1324
+ }
1325
+ });
1326
+ }
1327
+ if (ctx.resources.hasKV) {
1328
+ const kvName = ctx.naming.kv();
1329
+ tasks.push({
1330
+ name: `inspect kv (${kvName})`,
1331
+ handler: async () => {
1332
+ const existing = (await this.api.listKV()).find((ns) => ns.title === kvName);
1333
+ state.kvNamespaces.push({
1334
+ name: kvName,
1335
+ exists: !!existing,
1336
+ id: existing?.id
1337
+ });
1338
+ }
1339
+ });
1340
+ }
1341
+ if (ctx.resources.hasQueue) {
1342
+ const queueName = ctx.naming.queue();
1343
+ tasks.push({
1344
+ name: `inspect queue (${queueName})`,
1345
+ handler: async () => {
1346
+ const existing = (await this.api.listQueues()).find((q) => q.queue_name === queueName);
1347
+ state.queues.push({
1348
+ name: queueName,
1349
+ exists: !!existing,
1350
+ id: existing?.queue_id
1351
+ });
1352
+ }
1353
+ });
1354
+ }
1355
+ const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
1356
+ const expectedSecrets = Object.keys(envVars).filter((key) => envVars[key] && !CloudflareAdapter.EXCLUDED_SECRET_KEYS.has(key) && !key.startsWith("VITE_"));
1357
+ if (expectedSecrets.length > 0) {
1358
+ const workerName = ctx.naming.worker();
1359
+ tasks.push({
1360
+ name: "inspect secrets",
1361
+ handler: async () => {
1362
+ try {
1363
+ const deployed = await this.api.listSecrets(workerName);
1364
+ const deployedNames = new Set(deployed.map((s) => s.name));
1365
+ for (const key of expectedSecrets) state.secrets.push({
1366
+ name: key,
1367
+ deployed: deployedNames.has(key)
1368
+ });
1369
+ } catch {
1370
+ for (const key of expectedSecrets) state.secrets.push({
1371
+ name: key,
1372
+ deployed: false
1373
+ });
1374
+ }
1375
+ }
1376
+ });
1377
+ }
1378
+ await run(tasks);
1379
+ return state;
1380
+ }
1381
+ async teardown(ctx, run) {
1382
+ this.configureApi(ctx);
1383
+ if (ctx.resources.hasQueue) {
1384
+ const workerName = ctx.naming.worker();
1385
+ const queueName = ctx.naming.queue();
1386
+ await run({
1387
+ name: `unbind queue consumer ${queueName}`,
1388
+ handler: async () => {
1389
+ try {
1390
+ const queue = (await this.api.listQueues()).find((q) => q.queue_name === queueName);
1391
+ if (queue) await this.api.deleteQueueConsumer(queue.queue_id, workerName);
1392
+ } catch (error) {
1393
+ this.log.warn(`Failed to unbind queue consumer: ${String(error.message || "")}`);
1394
+ }
1395
+ }
1396
+ });
1397
+ }
1398
+ {
1399
+ const name = ctx.naming.worker();
1400
+ await run({
1401
+ name: `delete worker ${name}`,
1402
+ handler: async () => {
1403
+ try {
1404
+ await this.api.deleteWorker(name);
1405
+ } catch (error) {
1406
+ this.log.warn(`Failed to delete worker ${name}: ${String(error.message || "")}`);
1407
+ }
1408
+ }
1409
+ });
1410
+ }
1411
+ if (ctx.resources.hasQueue) {
1412
+ const name = ctx.naming.queue();
1413
+ await run({
1414
+ name: `delete queue ${name}`,
1415
+ handler: async () => {
1416
+ try {
1417
+ const queue = (await this.api.listQueues()).find((q) => q.queue_name === name);
1418
+ if (!queue) {
1419
+ this.log.debug(`Queue ${name} not found — skipping.`);
1420
+ return;
1421
+ }
1422
+ await this.api.deleteQueue(queue.queue_id);
1423
+ } catch (error) {
1424
+ this.log.warn(`Failed to delete queue ${name}: ${String(error.message || "")}`);
1425
+ }
1426
+ }
1427
+ });
1428
+ }
1429
+ if (ctx.resources.hasKV) {
1430
+ const name = ctx.naming.kv();
1431
+ await run({
1432
+ name: `delete kv ${name}`,
1433
+ handler: async () => {
1434
+ try {
1435
+ const existing = (await this.api.listKV()).find((ns) => ns.title === name);
1436
+ if (!existing) {
1437
+ this.log.debug(`KV namespace ${name} not found — skipping.`);
1438
+ return;
1439
+ }
1440
+ await this.api.deleteKV(existing.id);
1441
+ } catch (error) {
1442
+ this.log.warn(`Failed to delete kv ${name}: ${String(error.message || "")}`);
1443
+ }
1444
+ }
1445
+ });
1446
+ }
1447
+ if (ctx.resources.hasBucket) {
1448
+ const name = ctx.naming.r2();
1449
+ await run({
1450
+ name: `delete r2 ${name}`,
1451
+ handler: async () => {
1452
+ try {
1453
+ await this.wipeR2Bucket(name, ctx);
1454
+ await this.api.deleteR2(name);
1455
+ } catch (error) {
1456
+ const msg = String(error.message || "");
1457
+ if (msg.includes("does not exist") || msg.includes("NoSuchBucket")) this.log.debug(`Bucket ${name} not found — skipping.`);
1458
+ else this.log.warn(`Failed to delete r2 ${name}: ${msg}`);
1459
+ }
1460
+ }
1461
+ });
1462
+ }
1463
+ if (ctx.resources.hasDatabase) if (await this.isPostgres(ctx)) {
1464
+ const name = ctx.naming.hyperdrive();
1465
+ await run({
1466
+ name: `delete hyperdrive ${name}`,
1467
+ handler: async () => {
1468
+ try {
1469
+ const existing = (await this.api.listHyperdrive()).find((c) => c.name === name);
1470
+ if (!existing) {
1471
+ this.log.debug(`Hyperdrive ${name} not found — skipping.`);
1472
+ return;
1473
+ }
1474
+ await this.api.deleteHyperdrive(existing.id);
1475
+ } catch (error) {
1476
+ this.log.warn(`Failed to delete hyperdrive ${name}: ${String(error.message || "")}`);
1477
+ }
1478
+ }
1479
+ });
1480
+ } else {
1481
+ const name = ctx.naming.d1();
1482
+ await run({
1483
+ name: `delete d1 ${name}`,
1484
+ handler: async () => {
1485
+ try {
1486
+ const existing = (await this.api.listD1()).find((db) => db.name === name);
1487
+ if (!existing) {
1488
+ this.log.debug(`D1 database ${name} not found — skipping.`);
1489
+ return;
1490
+ }
1491
+ await this.api.deleteD1(existing.uuid);
1492
+ } catch (error) {
1493
+ this.log.warn(`Failed to delete d1 ${name}: ${String(error.message || "")}`);
1494
+ }
1495
+ }
1496
+ });
1497
+ }
1498
+ }
1499
+ async ensureD1(name) {
1500
+ const existing = (await this.api.listD1()).find((db) => db.name === name);
1501
+ if (existing) return existing.uuid;
1502
+ return (await this.api.createD1(name)).uuid;
1503
+ }
1504
+ async ensureHyperdrive(name, connectionString) {
1505
+ const existing = (await this.api.listHyperdrive()).find((c) => c.name === name);
1506
+ if (existing) return existing.id;
1507
+ return (await this.api.createHyperdrive(name, connectionString)).id;
1508
+ }
1509
+ async ensureR2(name) {
1510
+ if ((await this.api.listR2()).find((b) => b.name === name)) return;
1511
+ await this.api.createR2(name);
1512
+ }
1513
+ /**
1514
+ * Empty an R2 bucket via the S3-compatible API.
1515
+ *
1516
+ * Cloudflare's REST `DELETE /r2/buckets/:name` rejects non-empty buckets
1517
+ * with `BucketNotEmpty`, and the REST API has no object-level endpoints —
1518
+ * objects must be listed and deleted over the S3 protocol. To avoid
1519
+ * making users pre-create R2 access keys, we mint a short-lived
1520
+ * bucket-scoped API token using the wrangler bearer token, wipe the
1521
+ * bucket with `s3mini`, then revoke the token.
1522
+ *
1523
+ * Also aborts any pending multipart uploads — those count as bucket
1524
+ * contents from R2's perspective and would otherwise block the delete.
1525
+ */
1526
+ async wipeR2Bucket(bucketName, ctx) {
1527
+ if (!process.env.CLOUDFLARE_API_TOKEN) {
1528
+ this.log.warn(`Skipping R2 wipe for ${bucketName}: CLOUDFLARE_API_TOKEN not set. Delete the bucket manually in the Cloudflare dashboard.`);
1529
+ return;
1530
+ }
1531
+ const tokenName = `alepha-teardown-${bucketName}-${Date.now()}`;
1532
+ const token = await this.api.createR2Token(tokenName, bucketName);
1533
+ try {
1534
+ const accountId = await this.api.resolveAccountId();
1535
+ const jur = ctx.envConfig.jurisdiction;
1536
+ const host = jur ? `${accountId}.${jur}.r2.cloudflarestorage.com` : `${accountId}.r2.cloudflarestorage.com`;
1537
+ const client = new S3mini({
1538
+ accessKeyId: token.accessKeyId,
1539
+ secretAccessKey: token.secretAccessKey,
1540
+ region: "auto",
1541
+ endpoint: `https://${host}/${bucketName}`
1542
+ });
1543
+ try {
1544
+ const mp = await client.listMultipartUploads();
1545
+ if ("listMultipartUploadsResult" in mp) {
1546
+ const uploads = mp.listMultipartUploadsResult.uploads ?? [];
1547
+ for (const upload of uploads) {
1548
+ const u = upload;
1549
+ const key = u.Key ?? u.key;
1550
+ const uploadId = u.UploadId ?? u.uploadId;
1551
+ if (key && uploadId) await client.abortMultipartUpload(key, uploadId);
1552
+ }
1553
+ }
1554
+ } catch (error) {
1555
+ this.log.debug(`listMultipartUploads on ${bucketName} failed: ${String(error.message || "")}`);
1556
+ }
1557
+ let cursor;
1558
+ let total = 0;
1559
+ while (true) {
1560
+ const page = await client.listObjectsPaged(void 0, void 0, 1e3, cursor);
1561
+ const objects = page?.objects ?? [];
1562
+ if (objects.length === 0) break;
1563
+ await client.deleteObjects(objects.map((o) => o.Key));
1564
+ total += objects.length;
1565
+ cursor = page?.nextContinuationToken;
1566
+ if (!cursor) break;
1567
+ }
1568
+ if (total > 0) this.log.info(`Emptied ${total} object(s) from bucket ${bucketName}.`);
1569
+ } finally {
1570
+ try {
1571
+ await this.api.deleteR2Token(token.id);
1572
+ } catch (error) {
1573
+ this.log.warn(`Failed to revoke ephemeral R2 token ${token.id}: ${String(error.message || "")}`);
1574
+ }
1575
+ }
1576
+ }
1577
+ async ensureKV(name) {
1578
+ const existing = (await this.api.listKV()).find((ns) => ns.title === name);
1579
+ if (existing) return existing.id;
1580
+ return (await this.api.createKV(name)).id;
1581
+ }
1582
+ async ensureQueue(name) {
1583
+ if ((await this.api.listQueues()).find((q) => q.queue_name === name)) return;
1584
+ await this.api.createQueue(name);
1585
+ }
1586
+ /**
1587
+ * Get the currently active deployment for a worker.
1588
+ */
1589
+ async getActiveDeployment(workerName) {
1590
+ const latest = [...await this.api.listDeployments(workerName)].sort((a, b) => b.created_on.localeCompare(a.created_on))[0];
1591
+ if (!latest?.versions?.[0]) return;
1592
+ const activeVersionId = latest.versions[0].version_id;
1593
+ const version = (await this.api.listVersions(workerName)).find((v) => v.id === activeVersionId);
1594
+ return {
1595
+ versionId: activeVersionId,
1596
+ tag: version?.annotations?.["workers/tag"],
1597
+ createdAt: version?.metadata.created_on
1598
+ };
1599
+ }
1600
+ };
1601
+ /**
1602
+ * Stable SHA-256 of the secret set. Keys are sorted so reordering `.env`
1603
+ * lines does not invalidate the cache. Used as a fingerprint by
1604
+ * `CloudflareAdapter.secrets` — see the comment block there.
1605
+ */
1606
+ function computeSecretsHash(secrets) {
1607
+ const sorted = Object.keys(secrets).sort().map((k) => `${k}=${secrets[k]}`).join("\n");
1608
+ return createHash("sha256").update(sorted).digest("hex");
1609
+ }
1610
+ //#endregion
1611
+ //#region ../../src/cli/platform-lib/schemas/vercel.ts
1612
+ const vercelProjectSchema = t.object({
1613
+ id: t.string(),
1614
+ name: t.string(),
1615
+ accountId: t.string()
1616
+ });
1617
+ const createProjectBodySchema = t.object({
1618
+ name: t.string(),
1619
+ framework: t.optional(t.null())
1620
+ });
1621
+ const vercelDeploymentSchema = t.object({
1622
+ uid: t.string(),
1623
+ name: t.string(),
1624
+ url: t.string(),
1625
+ state: t.optional(t.string()),
1626
+ readyState: t.optional(t.string()),
1627
+ created: t.optional(t.number()),
1628
+ target: t.optional(t.string()),
1629
+ alias: t.optional(t.array(t.string()))
1630
+ });
1631
+ const vercelEnvVarSchema = t.object({
1632
+ id: t.string(),
1633
+ key: t.string(),
1634
+ value: t.optional(t.string()),
1635
+ type: t.string(),
1636
+ target: t.array(t.string())
1637
+ });
1638
+ const createEnvVarBodySchema = t.object({
1639
+ key: t.string(),
1640
+ value: t.string(),
1641
+ type: t.string(),
1642
+ target: t.array(t.string())
1643
+ });
1644
+ //#endregion
1645
+ //#region ../../src/cli/platform-lib/services/VercelCli.ts
1646
+ /**
1647
+ * Wraps Vercel CLI commands and token management.
1648
+ *
1649
+ * Used for operations where the Vercel CLI provides value:
1650
+ * OAuth login, prebuilt deploy, and auth token extraction.
1651
+ */
1652
+ var VercelCli = class {
1653
+ log = $logger();
1654
+ shell = $inject(ShellProvider);
1655
+ fs = $inject(FileSystemProvider);
1656
+ utils = $inject(AlephaCliUtils);
1657
+ pm = $inject(PackageManagerUtils);
1658
+ runner = $inject(Runner);
1659
+ async runShell(command, options = {}) {
1660
+ const capture = options.capture;
1661
+ const output = await this.shell.run(command, {
1662
+ ...options,
1663
+ capture: capture ?? this.runner.useDynamicLogger
1664
+ });
1665
+ if (capture && !this.runner.useDynamicLogger) this.log.info(output);
1666
+ return output;
1667
+ }
1668
+ /**
1669
+ * Ensure vercel CLI is installed in the project.
1670
+ */
1671
+ async ensureInstalled(root, run) {
1672
+ await this.pm.ensureDependency(root, "vercel", {
1673
+ dev: true,
1674
+ exec: async (cmd, opts) => {
1675
+ run.pause();
1676
+ try {
1677
+ await this.utils.exec(cmd, opts);
1678
+ } finally {
1679
+ run.resume();
1680
+ }
1681
+ }
1682
+ });
1683
+ }
1684
+ /**
1685
+ * Get the Vercel auth token.
1686
+ *
1687
+ * Priority:
1688
+ * 1. VERCEL_TOKEN environment variable (CI/CD)
1689
+ * 2. Vercel CLI auth.json file (local dev)
1690
+ */
1691
+ async getAuthToken() {
1692
+ const envToken = process.env.VERCEL_TOKEN;
1693
+ if (envToken) return envToken;
1694
+ const authPath = this.getAuthFilePath();
1695
+ if (!await this.fs.exists(authPath)) throw new AlephaError("Vercel auth token not found. Run `vercel login` or set VERCEL_TOKEN.");
1696
+ const content = await this.fs.readFile(authPath);
1697
+ const parsed = JSON.parse(content.toString());
1698
+ if (!parsed.token) throw new AlephaError("Vercel auth.json exists but contains no token. Run `vercel login`.");
1699
+ return parsed.token;
1700
+ }
1701
+ /**
1702
+ * Validate the current auth token.
1703
+ */
1704
+ async whoami() {
1705
+ return await this.runShell("vercel whoami", {
1706
+ resolve: true,
1707
+ capture: true
1708
+ });
1709
+ }
1710
+ /**
1711
+ * Open the browser-based login flow.
1712
+ */
1713
+ async login() {
1714
+ await this.runShell("vercel login", { resolve: true });
1715
+ }
1716
+ /**
1717
+ * Deploy a prebuilt .vercel/output/ directory.
1718
+ *
1719
+ * Returns the deployment URL.
1720
+ */
1721
+ async deploy(distDir, options) {
1722
+ const args = [
1723
+ "vercel",
1724
+ "deploy",
1725
+ "--prebuilt"
1726
+ ];
1727
+ if (options.prod) args.push("--prod");
1728
+ if (options.token) args.push(`--token=${options.token}`);
1729
+ return (await this.runShell(args.join(" "), {
1730
+ resolve: true,
1731
+ capture: true,
1732
+ root: distDir
1733
+ })).trim().split("\n").reverse().find((line) => line.trim().startsWith("https://"))?.trim();
1734
+ }
1735
+ /**
1736
+ * Resolve the path to Vercel CLI auth.json.
1737
+ */
1738
+ getAuthFilePath() {
1739
+ const os = platform();
1740
+ if (os === "darwin") return join(homedir(), "Library", "Application Support", "com.vercel.cli", "auth.json");
1741
+ if (os === "win32") return join(homedir(), "AppData", "Roaming", "xdg.data", "com.vercel.cli", "auth.json");
1742
+ return join(homedir(), ".local", "share", "com.vercel.cli", "auth.json");
1743
+ }
1744
+ };
1745
+ //#endregion
1746
+ //#region ../../src/cli/platform-lib/services/VercelApi.ts
1747
+ /**
1748
+ * Thin wrapper over the Vercel REST API.
1749
+ *
1750
+ * Uses the auth token from VercelCli for all requests.
1751
+ */
1752
+ var VercelApi = class VercelApi {
1753
+ static BASE = "https://api.vercel.com";
1754
+ log = $logger();
1755
+ alepha = $inject(Alepha);
1756
+ vercelCli = $inject(VercelCli);
1757
+ token;
1758
+ /**
1759
+ * Obtain the current auth token from the Vercel CLI.
1760
+ */
1761
+ async resolveToken() {
1762
+ if (this.token) return this.token;
1763
+ this.token = await this.vercelCli.getAuthToken();
1764
+ return this.token;
1765
+ }
1766
+ async listProjects() {
1767
+ return (await this.fetch("/v10/projects", { schema: t.object({ projects: t.array(vercelProjectSchema) }) })).projects;
1768
+ }
1769
+ async getProject(nameOrId) {
1770
+ try {
1771
+ return await this.fetch(`/v9/projects/${encodeURIComponent(nameOrId)}`, { schema: vercelProjectSchema });
1772
+ } catch {
1773
+ return;
1774
+ }
1775
+ }
1776
+ async createProject(name) {
1777
+ return await this.fetch("/v11/projects", {
1778
+ method: "POST",
1779
+ body: {
1780
+ name,
1781
+ framework: null
1782
+ },
1783
+ bodySchema: createProjectBodySchema,
1784
+ schema: vercelProjectSchema
1785
+ });
1786
+ }
1787
+ async updateProject(nameOrId, settings) {
1788
+ await this.fetch(`/v9/projects/${encodeURIComponent(nameOrId)}`, {
1789
+ method: "PATCH",
1790
+ body: settings
1791
+ });
1792
+ }
1793
+ async deleteProject(nameOrId) {
1794
+ await this.fetch(`/v9/projects/${encodeURIComponent(nameOrId)}`, { method: "DELETE" });
1795
+ }
1796
+ async listDeployments(projectId, options) {
1797
+ const query = { projectId };
1798
+ if (options?.limit) query.limit = String(options.limit);
1799
+ if (options?.target) query.target = options.target;
1800
+ return (await this.fetch("/v6/deployments", {
1801
+ query,
1802
+ schema: t.object({ deployments: t.array(vercelDeploymentSchema) })
1803
+ })).deployments;
1804
+ }
1805
+ async listEnvVars(projectId) {
1806
+ return (await this.fetch(`/v10/projects/${encodeURIComponent(projectId)}/env`, {
1807
+ query: { decrypt: "true" },
1808
+ schema: t.object({ envs: t.array(vercelEnvVarSchema) })
1809
+ })).envs;
1810
+ }
1811
+ async upsertEnvVars(projectId, vars) {
1812
+ for (const v of vars) await this.fetch(`/v10/projects/${encodeURIComponent(projectId)}/env`, {
1813
+ method: "POST",
1814
+ query: { upsert: "true" },
1815
+ body: {
1816
+ key: v.key,
1817
+ value: v.value,
1818
+ type: "encrypted",
1819
+ target: v.target
1820
+ },
1821
+ bodySchema: createEnvVarBodySchema
1822
+ });
1823
+ }
1824
+ async deleteEnvVar(projectId, envVarId) {
1825
+ await this.fetch(`/v9/projects/${encodeURIComponent(projectId)}/env/${envVarId}`, { method: "DELETE" });
1826
+ }
1827
+ async fetch(path, options = {}) {
1828
+ const token = await this.resolveToken();
1829
+ const { method = "GET", body, query } = options;
1830
+ let url = `${VercelApi.BASE}${path}`;
1831
+ if (query) {
1832
+ const params = new URLSearchParams(query);
1833
+ url += `?${params.toString()}`;
1834
+ }
1835
+ const headers = { Authorization: `Bearer ${token}` };
1836
+ const init = {
1837
+ method,
1838
+ headers
1839
+ };
1840
+ if (body) {
1841
+ headers["Content-Type"] = "application/json";
1842
+ const validated = options.bodySchema ? this.alepha.codec.validate(options.bodySchema, body) : body;
1843
+ init.body = JSON.stringify(validated);
1844
+ }
1845
+ const response = await globalThis.fetch(url, init);
1846
+ if (response.status === 204) return;
1847
+ const json = await response.json();
1848
+ if (json.error) throw new AlephaError(`Vercel API error (${method} ${path}): ${json.error.message ?? JSON.stringify(json.error)}`);
1849
+ if (!response.ok) throw new AlephaError(`Vercel API error (${method} ${path}): HTTP ${response.status}`);
1850
+ if (options.schema) return this.alepha.codec.validate(options.schema, json);
1851
+ return json;
1852
+ }
1853
+ };
1854
+ //#endregion
1855
+ //#region ../../src/cli/platform-lib/adapters/VercelAdapter.ts
1856
+ /**
1857
+ * Vercel platform adapter.
1858
+ *
1859
+ * Uses the Vercel CLI for login and deploy (--prebuilt),
1860
+ * and the Vercel REST API for project management, env vars, and inspection.
1861
+ *
1862
+ * v1 scope: deploy pipeline only. No DB/storage/KV provisioning.
1863
+ */
1864
+ var VercelAdapter = class VercelAdapter extends PlatformAdapter {
1865
+ log = $logger();
1866
+ fs = $inject(FileSystemProvider);
1867
+ shell = $inject(ShellProvider);
1868
+ utils = $inject(AlephaCliUtils);
1869
+ cache = $inject(PlatformCacheProvider);
1870
+ alepha = $inject(Alepha);
1871
+ envUtils = $inject(EnvUtils);
1872
+ api = $inject(VercelApi);
1873
+ vercelCli = $inject(VercelCli);
1874
+ runner = $inject(Runner);
1875
+ /**
1876
+ * Vars that should not be pushed as env vars.
1877
+ * These are either handled by the build or are internal.
1878
+ */
1879
+ static EXCLUDED_SECRET_KEYS = new Set(["NODE_ENV"]);
1880
+ async runShell(command, options = {}) {
1881
+ const capture = options.capture;
1882
+ const output = await this.shell.run(command, {
1883
+ ...options,
1884
+ capture: capture ?? this.runner.useDynamicLogger
1885
+ });
1886
+ if (capture && !this.runner.useDynamicLogger) this.log.info(output);
1887
+ return output;
1888
+ }
1889
+ async authenticate(ctx, run) {
1890
+ await run({
1891
+ name: "authenticate",
1892
+ handler: async () => {
1893
+ await this.vercelCli.ensureInstalled(ctx.root, run);
1894
+ let needsLogin = false;
1895
+ try {
1896
+ await this.vercelCli.getAuthToken();
1897
+ await this.vercelCli.whoami();
1898
+ } catch {
1899
+ needsLogin = true;
1900
+ }
1901
+ if (needsLogin) {
1902
+ run.pause();
1903
+ await this.vercelCli.login();
1904
+ run.resume();
1905
+ }
1906
+ if (await this.cache.isLoginFresh(ctx.root, "vercel")) return;
1907
+ await this.cache.recordLogin(ctx.root, "vercel");
1908
+ }
1909
+ });
1910
+ }
1911
+ async build(ctx, run) {
1912
+ const appDir = ctx.root;
1913
+ await run({
1914
+ name: "alepha build -t vercel",
1915
+ handler: async () => {
1916
+ await this.runShell("alepha build -t vercel", { root: appDir });
1917
+ }
1918
+ });
1919
+ }
1920
+ async deploy(ctx, run) {
1921
+ const distDir = this.fs.join(ctx.root, "dist");
1922
+ const projectName = ctx.naming.worker();
1923
+ let url;
1924
+ await run({
1925
+ name: `deploy ${ctx.project}`,
1926
+ handler: async () => {
1927
+ let project = await this.api.getProject(projectName);
1928
+ if (!project) project = await this.api.createProject(projectName);
1929
+ await this.api.updateProject(projectName, { framework: null });
1930
+ const vercelDir = this.fs.join(distDir, ".vercel");
1931
+ await this.fs.mkdir(vercelDir);
1932
+ await this.fs.writeFile(this.fs.join(vercelDir, "project.json"), JSON.stringify({
1933
+ projectId: project.id,
1934
+ orgId: project.accountId
1935
+ }, null, 2));
1936
+ const token = process.env.VERCEL_TOKEN;
1937
+ await this.vercelCli.deploy(distDir, {
1938
+ prod: true,
1939
+ token
1940
+ });
1941
+ const latest = (await this.api.listDeployments(project.id, {
1942
+ limit: 1,
1943
+ target: "production"
1944
+ }))[0];
1945
+ url = latest?.alias?.[0] ? `https://${latest.alias[0]}` : `https://${projectName}.vercel.app`;
1946
+ }
1947
+ });
1948
+ return url;
1949
+ }
1950
+ async secrets(ctx, run) {
1951
+ const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
1952
+ const vars = [];
1953
+ for (const [key, value] of Object.entries(envVars)) {
1954
+ if (!value) continue;
1955
+ if (VercelAdapter.EXCLUDED_SECRET_KEYS.has(key)) continue;
1956
+ if (key.startsWith("VITE_")) continue;
1957
+ vars.push({
1958
+ key,
1959
+ value,
1960
+ target: ["production", "preview"]
1961
+ });
1962
+ }
1963
+ if (vars.length === 0) return;
1964
+ {
1965
+ const projectName = ctx.naming.worker();
1966
+ await run({
1967
+ name: `push env vars to ${projectName}`,
1968
+ handler: async () => {
1969
+ await this.api.upsertEnvVars(projectName, vars);
1970
+ }
1971
+ });
1972
+ }
1973
+ }
1974
+ async inspect(ctx, run) {
1975
+ const state = {
1976
+ workers: [],
1977
+ databases: [],
1978
+ buckets: [],
1979
+ kvNamespaces: [],
1980
+ queues: [],
1981
+ secrets: []
1982
+ };
1983
+ const tasks = [];
1984
+ {
1985
+ const projectName = ctx.naming.worker();
1986
+ tasks.push({
1987
+ name: `inspect project (${projectName})`,
1988
+ handler: async () => {
1989
+ const project = await this.api.getProject(projectName);
1990
+ if (!project) {
1991
+ state.workers.push({
1992
+ name: projectName,
1993
+ exists: false
1994
+ });
1995
+ return;
1996
+ }
1997
+ const latest = (await this.api.listDeployments(project.id, { limit: 1 }))[0];
1998
+ state.workers.push({
1999
+ name: projectName,
2000
+ exists: true,
2001
+ version: latest?.uid,
2002
+ createdAt: latest?.created ? new Date(latest.created).toISOString() : void 0
2003
+ });
2004
+ }
2005
+ });
2006
+ }
2007
+ const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
2008
+ const expectedVars = Object.keys(envVars).filter((key) => envVars[key] && !VercelAdapter.EXCLUDED_SECRET_KEYS.has(key) && !key.startsWith("VITE_"));
2009
+ if (expectedVars.length > 0) {
2010
+ const projectName = ctx.naming.worker();
2011
+ tasks.push({
2012
+ name: "inspect env vars",
2013
+ handler: async () => {
2014
+ try {
2015
+ const deployed = await this.api.listEnvVars(projectName);
2016
+ const deployedKeys = new Set(deployed.map((v) => v.key));
2017
+ for (const key of expectedVars) state.secrets.push({
2018
+ name: key,
2019
+ deployed: deployedKeys.has(key)
2020
+ });
2021
+ } catch {
2022
+ for (const key of expectedVars) state.secrets.push({
2023
+ name: key,
2024
+ deployed: false
2025
+ });
2026
+ }
2027
+ }
2028
+ });
2029
+ }
2030
+ await run(tasks);
2031
+ return state;
2032
+ }
2033
+ async teardown(ctx, run) {
2034
+ {
2035
+ const projectName = ctx.naming.worker();
2036
+ await run({
2037
+ name: `delete project ${projectName}`,
2038
+ handler: async () => {
2039
+ try {
2040
+ await this.api.deleteProject(projectName);
2041
+ } catch (error) {
2042
+ this.log.warn(`Failed to delete project ${projectName}: ${String(error.message || "")}`);
2043
+ }
2044
+ }
2045
+ });
2046
+ }
2047
+ }
2048
+ };
2049
+ //#endregion
2050
+ //#region ../../src/cli/platform-lib/providers/GitHubSecretStore.ts
2051
+ /**
2052
+ * GitHub Actions secret store backed by the `gh` CLI.
2053
+ *
2054
+ * Requires the GitHub CLI (`gh`) to be installed and authenticated.
2055
+ * Pushes secrets into GitHub Actions environments.
2056
+ */
2057
+ var GitHubSecretStore = class {
2058
+ log = $logger();
2059
+ shell = $inject(ShellProvider);
2060
+ fs = $inject(FileSystemProvider);
2061
+ /**
2062
+ * Verify that `gh` is installed and authenticated.
2063
+ */
2064
+ async ensureAvailable() {
2065
+ if (!await this.shell.isInstalled("gh")) throw new AlephaError("GitHub CLI (gh) is not installed. Install it from https://cli.github.com");
2066
+ try {
2067
+ await this.shell.run("gh auth status", { capture: true });
2068
+ } catch {
2069
+ throw new AlephaError("GitHub CLI is not authenticated. Run `gh auth login` first.");
2070
+ }
2071
+ }
2072
+ /**
2073
+ * Create the GitHub Actions environment if it doesn't exist.
2074
+ */
2075
+ async ensureEnvironment(environment) {
2076
+ await this.shell.run(`gh api --method PUT /repos/{owner}/{repo}/environments/${environment} --silent`, { capture: true });
2077
+ this.log.debug(`Ensured environment "${environment}" exists`);
2078
+ }
2079
+ /**
2080
+ * List all secrets in a GitHub Actions environment.
2081
+ */
2082
+ async list(environment) {
2083
+ try {
2084
+ const output = await this.shell.run(`gh secret list --env ${environment} --json name,updatedAt`, { capture: true });
2085
+ return JSON.parse(output || "[]").map((s) => ({
2086
+ name: s.name,
2087
+ updatedAt: s.updatedAt
2088
+ }));
2089
+ } catch (error) {
2090
+ this.log.debug("Failed to list secrets", {
2091
+ environment,
2092
+ error
2093
+ });
2094
+ return [];
2095
+ }
2096
+ }
2097
+ /**
2098
+ * Set a secret in a GitHub Actions environment.
2099
+ *
2100
+ * Writes a dotenv-formatted file and uses `gh secret set --env-file` to
2101
+ * avoid shell pipe issues with NodeShellProvider escaping the `|` character.
2102
+ */
2103
+ async set(environment, key, value) {
2104
+ const tmpFile = `/tmp/alepha-secret-${key}-${Date.now()}`;
2105
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
2106
+ await this.fs.writeFile(tmpFile, `${key}="${escaped}"\n`);
2107
+ try {
2108
+ const output = await this.shell.run(`gh secret set -f ${tmpFile} --env ${environment}`, { capture: true });
2109
+ this.log.debug(`Secret set: ${key}`, { output });
2110
+ } finally {
2111
+ await this.fs.rm(tmpFile);
2112
+ }
2113
+ }
2114
+ /**
2115
+ * Delete a secret from a GitHub Actions environment.
2116
+ */
2117
+ async delete(environment, key) {
2118
+ await this.shell.run(`gh secret delete ${key} --env ${environment}`, { capture: true });
2119
+ }
2120
+ };
2121
+ //#endregion
2122
+ //#region ../../src/cli/platform-lib/providers/MemorySecretStore.ts
2123
+ /**
2124
+ * In-memory implementation of SecretStoreProvider for testing.
2125
+ * Records all operations and stores secrets in a nested Map.
2126
+ */
2127
+ var MemorySecretStore = class {
2128
+ /**
2129
+ * Secrets keyed by environment, then by key.
2130
+ */
2131
+ secrets = /* @__PURE__ */ new Map();
2132
+ /**
2133
+ * All recorded operations.
2134
+ */
2135
+ calls = [];
2136
+ /**
2137
+ * When set, ensureAvailable() will throw with this message.
2138
+ */
2139
+ availableError = null;
2140
+ async ensureAvailable() {
2141
+ this.calls.push({ method: "ensureAvailable" });
2142
+ if (this.availableError) throw new AlephaError(this.availableError);
2143
+ }
2144
+ async ensureEnvironment(environment) {
2145
+ this.calls.push({
2146
+ method: "ensureEnvironment",
2147
+ environment
2148
+ });
2149
+ if (!this.secrets.has(environment)) this.secrets.set(environment, /* @__PURE__ */ new Map());
2150
+ }
2151
+ async list(environment) {
2152
+ this.calls.push({
2153
+ method: "list",
2154
+ environment
2155
+ });
2156
+ const envSecrets = this.secrets.get(environment);
2157
+ if (!envSecrets) return [];
2158
+ return Array.from(envSecrets.keys()).map((name) => ({ name }));
2159
+ }
2160
+ async set(environment, key, value) {
2161
+ this.calls.push({
2162
+ method: "set",
2163
+ environment,
2164
+ key,
2165
+ value
2166
+ });
2167
+ let envSecrets = this.secrets.get(environment);
2168
+ if (!envSecrets) {
2169
+ envSecrets = /* @__PURE__ */ new Map();
2170
+ this.secrets.set(environment, envSecrets);
2171
+ }
2172
+ envSecrets.set(key, value);
2173
+ }
2174
+ async delete(environment, key) {
2175
+ this.calls.push({
2176
+ method: "delete",
2177
+ environment,
2178
+ key
2179
+ });
2180
+ this.secrets.get(environment)?.delete(key);
2181
+ }
2182
+ /**
2183
+ * Check if set() was called for a given environment and key.
2184
+ */
2185
+ wasSet(environment, key) {
2186
+ return this.calls.some((c) => c.method === "set" && c.environment === environment && c.key === key);
2187
+ }
2188
+ /**
2189
+ * Check if delete() was called for a given environment and key.
2190
+ */
2191
+ wasDeleted(environment, key) {
2192
+ return this.calls.some((c) => c.method === "delete" && c.environment === environment && c.key === key);
2193
+ }
2194
+ /**
2195
+ * Get all set() calls for a given environment.
2196
+ */
2197
+ getSetCalls(environment) {
2198
+ return this.calls.filter((c) => c.method === "set" && c.environment === environment).map((c) => ({
2199
+ key: c.key,
2200
+ value: c.value
2201
+ }));
2202
+ }
2203
+ /**
2204
+ * Reset all state.
2205
+ */
2206
+ reset() {
2207
+ this.secrets.clear();
2208
+ this.calls = [];
2209
+ this.availableError = null;
2210
+ }
2211
+ };
2212
+ //#endregion
2213
+ //#region ../../src/cli/platform-lib/services/PlatformInspector.ts
2214
+ /**
2215
+ * Reads platform config and resolves project topology.
2216
+ *
2217
+ * Validates project name and environment configuration. Does NOT
2218
+ * introspect app code for resources — that happens at deploy time via
2219
+ * ViteBuildProvider.
2220
+ *
2221
+ * Each app self-declares its platform topology via its own
2222
+ * `alepha.config.ts`. Run `alepha platform <op>` from the app's
2223
+ * directory; no monorepo-root orchestration here.
2224
+ */
2225
+ var PlatformInspector = class {
2226
+ log = $logger();
2227
+ alepha = $inject(Alepha);
2228
+ fs = $inject(FileSystemProvider);
2229
+ asker = $inject(Asker);
2230
+ options = $state(platformOptions);
2231
+ naming = $inject(NamingService);
2232
+ /**
2233
+ * Resolve and validate the full platform configuration.
2234
+ *
2235
+ * Source priority:
2236
+ * 1. `platformOptions` atom (set by `alepha.config.ts` during the
2237
+ * configure hook) — local dev / from-source deploys.
2238
+ * 2. `dist/manifest.json` (written by `alepha build`) — pre-built
2239
+ * deploys via Alepha Rocket or any `--prebuilt` consumer that
2240
+ * ships only the build artifact without `alepha.config.ts`.
2241
+ */
2242
+ async resolveConfig(root) {
2243
+ if (this.options) {
2244
+ const opts = this.options;
2245
+ const project = await this.resolveProjectName(root, opts.name);
2246
+ return {
2247
+ project: this.naming.slugify(project),
2248
+ defaultEnv: opts.default ?? "production",
2249
+ tenancy: opts.tenancy,
2250
+ environments: opts.environments
2251
+ };
2252
+ }
2253
+ const manifest = await this.readManifest(root);
2254
+ if (manifest) return {
2255
+ project: this.naming.slugify(manifest.project),
2256
+ defaultEnv: manifest.defaultEnv ?? "production",
2257
+ tenancy: manifest.tenancy,
2258
+ environments: manifest.environments
2259
+ };
2260
+ this.log.warn(` alepha.config.ts not found or missing platform config.
2261
+
2262
+ Please add a "platform" section to alepha.config.ts:
2263
+
2264
+ export default defineConfig({
2265
+ platform: {
2266
+ environments: {
2267
+ production: { adapter: "cloudflare" },
2268
+ },
2269
+ },
2270
+ });
2271
+ `);
2272
+ throw new AlephaError("Missing platform configuration.");
2273
+ }
2274
+ /**
2275
+ * Read `dist/manifest.json` if present. Returns null on any error so
2276
+ * callers fall back to the strict alepha.config.ts path.
2277
+ */
2278
+ async readManifest(root) {
2279
+ try {
2280
+ const fs = await import("node:fs/promises");
2281
+ const path = await import("node:path");
2282
+ const raw = await fs.readFile(path.join(root, "dist", "manifest.json"), "utf-8");
2283
+ return JSON.parse(raw);
2284
+ } catch {
2285
+ return null;
2286
+ }
2287
+ }
2288
+ /**
2289
+ * Resolve a specific environment, validating it exists.
2290
+ */
2291
+ async resolveEnvironment(root, envName) {
2292
+ const config = await this.resolveConfig(root);
2293
+ const envConfig = config.environments[envName];
2294
+ if (!envConfig) throw new AlephaError(`Unknown environment "${envName}". Available: ${Object.keys(config.environments).join(", ")}`);
2295
+ return envConfig;
2296
+ }
2297
+ async resolveProjectName(root, configName) {
2298
+ if (configName) return configName;
2299
+ try {
2300
+ const pkgPath = this.fs.join(root, "package.json");
2301
+ const pkg = await this.fs.readJsonFile(pkgPath);
2302
+ if (pkg.name) return pkg.name;
2303
+ } catch {}
2304
+ throw new AlephaError("Missing project name. Set \"name\" in alepha.config.ts or add a \"name\" field to package.json.");
2305
+ }
2306
+ };
2307
+ //#endregion
2308
+ //#region ../../src/cli/platform-lib/services/PlatformOrchestrator.ts
2309
+ /**
2310
+ * Orchestrates platform lifecycle operations.
2311
+ *
2312
+ * Coordinates adapter calls in the correct order for
2313
+ * up (build -> migrate -> deploy), down, plan, and status.
2314
+ */
2315
+ var PlatformOrchestrator = class {
2316
+ log = $logger();
2317
+ color = $inject(ConsoleColorProvider);
2318
+ inspector = $inject(PlatformInspector);
2319
+ naming = $inject(NamingService);
2320
+ cloudflareAdapter = $inject(CloudflareAdapter);
2321
+ vercelAdapter = $inject(VercelAdapter);
2322
+ alepha = $inject(Alepha);
2323
+ resolveAdapter(adapterName) {
2324
+ switch (adapterName) {
2325
+ case "cloudflare": return this.cloudflareAdapter;
2326
+ case "vercel": return this.vercelAdapter;
2327
+ default: throw new AlephaError(`Unknown adapter: "${adapterName}"`);
2328
+ }
2329
+ }
2330
+ async up(options) {
2331
+ const { root, env, entry, resources, run, prebuilt } = options;
2332
+ const envConfig = await this.inspector.resolveEnvironment(root, env);
2333
+ const config = await this.inspector.resolveConfig(root);
2334
+ const adapter = this.resolveAdapter(envConfig.adapter);
2335
+ const tenant = resolveTenant(config.tenancy, options.tenant);
2336
+ const namingCtx = this.naming.forContext(config.project, env, tenant);
2337
+ const ctx = {
2338
+ project: config.project,
2339
+ env,
2340
+ envConfig,
2341
+ root,
2342
+ entry,
2343
+ resources,
2344
+ naming: namingCtx,
2345
+ tenant,
2346
+ prebuilt
2347
+ };
2348
+ await adapter.authenticate(ctx, run);
2349
+ await adapter.provision(ctx, run);
2350
+ await adapter.build(ctx, run);
2351
+ await adapter.migrate(ctx, run);
2352
+ const url = await adapter.deploy(ctx, run);
2353
+ await adapter.secrets(ctx, run);
2354
+ run.end();
2355
+ return {
2356
+ urls: url ? [url] : [],
2357
+ domain: tenantDomain(envConfig.domain, tenant)
2358
+ };
2359
+ }
2360
+ /**
2361
+ * Pretty-print the `up()` result to stdout. Matches the formatting the
2362
+ * orchestrator used to emit inline; split out so callers that want
2363
+ * JSON output can skip this branch.
2364
+ */
2365
+ printUpSummary(result) {
2366
+ const c = this.color;
2367
+ if (result.domain) {
2368
+ this.log.info("");
2369
+ const display = result.domain.includes("*") ? `https://${result.domain} (wildcard route)` : `https://${result.domain}`;
2370
+ this.log.info(` ${c.set("GREEN", "→")} ${c.set("CYAN", display)}`);
2371
+ this.log.info("");
2372
+ } else for (const url of result.urls) {
2373
+ this.log.info("");
2374
+ this.log.info(` ${c.set("GREEN", "→")} ${c.set("CYAN", url)}`);
2375
+ this.log.info("");
2376
+ }
2377
+ }
2378
+ async down(options) {
2379
+ const { root, env, entry, resources, run, confirm } = options;
2380
+ const envConfig = await this.inspector.resolveEnvironment(root, env);
2381
+ const config = await this.inspector.resolveConfig(root);
2382
+ const adapter = this.resolveAdapter(envConfig.adapter);
2383
+ const tenant = resolveTenant(config.tenancy, options.tenant);
2384
+ const namingCtx = this.naming.forContext(config.project, env, tenant);
2385
+ const ctx = {
2386
+ project: config.project,
2387
+ env,
2388
+ envConfig,
2389
+ root,
2390
+ entry,
2391
+ resources,
2392
+ naming: namingCtx,
2393
+ tenant
2394
+ };
2395
+ if (!this.isTmpEnv(env)) {
2396
+ if (await confirm(`Type "${env}" to confirm teardown:`) !== env) {
2397
+ this.log.info("Aborted.");
2398
+ return false;
2399
+ }
2400
+ }
2401
+ await adapter.authenticate(ctx, run);
2402
+ await adapter.teardown(ctx, run);
2403
+ run.end();
2404
+ return true;
2405
+ }
2406
+ async plan(options) {
2407
+ const { root, env, resources } = options;
2408
+ const config = await this.inspector.resolveConfig(root);
2409
+ const tenant = resolveTenant(config.tenancy, options.tenant);
2410
+ return {
2411
+ config,
2412
+ naming: this.naming.forContext(config.project, env, tenant),
2413
+ resources
2414
+ };
2415
+ }
2416
+ async status(options) {
2417
+ const { root, env, entry, resources, run } = options;
2418
+ const envConfig = await this.inspector.resolveEnvironment(root, env);
2419
+ const config = await this.inspector.resolveConfig(root);
2420
+ const adapter = this.resolveAdapter(envConfig.adapter);
2421
+ const tenant = resolveTenant(config.tenancy, options.tenant);
2422
+ const namingCtx = this.naming.forContext(config.project, env, tenant);
2423
+ const ctx = {
2424
+ project: config.project,
2425
+ env,
2426
+ envConfig,
2427
+ root,
2428
+ entry,
2429
+ resources,
2430
+ naming: namingCtx,
2431
+ tenant
2432
+ };
2433
+ await adapter.authenticate(ctx, run);
2434
+ return {
2435
+ config,
2436
+ state: await adapter.inspect(ctx, run)
2437
+ };
2438
+ }
2439
+ isTmpEnv(env) {
2440
+ return env.startsWith("tmp");
2441
+ }
2442
+ };
2443
+ //#endregion
2444
+ //#region ../../src/cli/platform-lib/services/SecretFilterService.ts
2445
+ /**
2446
+ * Filters environment variables for secret store syncing.
2447
+ *
2448
+ * Excludes platform-managed vars (NODE_ENV), build-time vars (VITE_*),
2449
+ * and empty values. Keeps everything else — including DATABASE_URL
2450
+ * and POSTGRES_SCHEMA which GitHub Actions needs.
2451
+ *
2452
+ * Also handles renaming GITHUB_* keys since GitHub Actions rejects
2453
+ * secret names starting with GITHUB_.
2454
+ */
2455
+ var SecretFilterService = class SecretFilterService {
2456
+ static EXCLUDED_KEYS = new Set(["NODE_ENV"]);
2457
+ static GITHUB_PREFIX = "GITHUB_";
2458
+ static REMOTE_PREFIX = "APP_GITHUB_";
2459
+ /**
2460
+ * Return only the entries that should be pushed to a secret store.
2461
+ */
2462
+ filter(envVars) {
2463
+ const result = {};
2464
+ for (const [key, value] of Object.entries(envVars)) {
2465
+ if (!value) continue;
2466
+ if (SecretFilterService.EXCLUDED_KEYS.has(key)) continue;
2467
+ if (key.startsWith("VITE_")) continue;
2468
+ result[key] = value;
2469
+ }
2470
+ return result;
2471
+ }
2472
+ /**
2473
+ * Convert a local env key to a remote secret name.
2474
+ *
2475
+ * GITHUB_* keys are prefixed with APP_ since GitHub Actions rejects
2476
+ * secret names starting with GITHUB_.
2477
+ */
2478
+ toRemoteName(key) {
2479
+ if (key.startsWith(SecretFilterService.GITHUB_PREFIX)) return `${SecretFilterService.REMOTE_PREFIX}${key.slice(SecretFilterService.GITHUB_PREFIX.length)}`;
2480
+ return key;
2481
+ }
2482
+ /**
2483
+ * Convert a remote secret name back to the local env key.
2484
+ */
2485
+ toLocalName(remoteName) {
2486
+ if (remoteName.startsWith(SecretFilterService.REMOTE_PREFIX)) return `${SecretFilterService.GITHUB_PREFIX}${remoteName.slice(SecretFilterService.REMOTE_PREFIX.length)}`;
2487
+ return remoteName;
2488
+ }
2489
+ };
2490
+ //#endregion
2491
+ //#region ../../src/cli/platform-lib/providers/SecretStoreProvider.ts
2492
+ /**
2493
+ * Abstract provider for managing secrets in an external store.
2494
+ *
2495
+ * Implementations: GitHubSecretStore, MemorySecretStore
2496
+ */
2497
+ var SecretStoreProvider = class {};
2498
+ //#endregion
2499
+ //#region ../../src/cli/platform-lib/schemas/platform.ts
2500
+ const platformStatusWorkerSchema = t.object({
2501
+ name: t.string(),
2502
+ exists: t.boolean(),
2503
+ id: t.optional(t.string()),
2504
+ detail: t.optional(t.string()),
2505
+ version: t.optional(t.string()),
2506
+ tag: t.optional(t.string()),
2507
+ createdAt: t.optional(t.string())
2508
+ });
2509
+ const platformStatusResourceSchema = t.object({
2510
+ name: t.string(),
2511
+ exists: t.boolean(),
2512
+ id: t.optional(t.string()),
2513
+ detail: t.optional(t.string())
2514
+ });
2515
+ const platformStatusSecretSchema = t.object({
2516
+ name: t.string(),
2517
+ deployed: t.boolean()
2518
+ });
2519
+ const platformStatusSchema = t.object({
2520
+ project: t.string(),
2521
+ env: t.string(),
2522
+ adapter: t.string(),
2523
+ workers: t.array(platformStatusWorkerSchema),
2524
+ databases: t.array(platformStatusResourceSchema),
2525
+ buckets: t.array(platformStatusResourceSchema),
2526
+ kvNamespaces: t.array(platformStatusResourceSchema),
2527
+ queues: t.array(platformStatusResourceSchema),
2528
+ secrets: t.array(platformStatusSecretSchema)
2529
+ });
2530
+ const platformPlanAppResourcesSchema = t.object({
2531
+ hasDatabase: t.boolean(),
2532
+ hasBucket: t.boolean(),
2533
+ hasKV: t.boolean(),
2534
+ hasQueue: t.boolean(),
2535
+ hasCron: t.boolean()
2536
+ });
2537
+ const platformPlanAppSchema = t.object({
2538
+ name: t.string(),
2539
+ path: t.string(),
2540
+ resources: platformPlanAppResourcesSchema
2541
+ });
2542
+ const platformPlanEnvironmentSchema = t.object({
2543
+ adapter: t.string(),
2544
+ domain: t.optional(t.string()),
2545
+ zone: t.optional(t.string())
2546
+ });
2547
+ const platformPlanResourceSchema = t.object({
2548
+ label: t.string(),
2549
+ value: t.string()
2550
+ });
2551
+ const platformPlanSchema = t.object({
2552
+ project: t.string(),
2553
+ env: t.string(),
2554
+ mode: t.enum(["monorepo", "standalone"]),
2555
+ apps: t.array(platformPlanAppSchema),
2556
+ environments: t.record(t.string(), platformPlanEnvironmentSchema),
2557
+ resources: t.array(platformPlanResourceSchema),
2558
+ secretCount: t.number()
2559
+ });
2560
+ //#endregion
2561
+ //#region ../../src/cli/platform-lib/index.ts
2562
+ /**
2563
+ * Framework-agnostic platform deploy services.
2564
+ *
2565
+ * Exports `PlatformOrchestrator` + adapters + secret stores + the
2566
+ * `platformOptions` atom — everything needed to drive a deploy
2567
+ * programmatically. **No `$command` instances** and **no
2568
+ * `AppEntryProvider` / `ViteBuildProvider` dependency** — so consumers
2569
+ * importing this subpath don't pull in the CLI argv-parser or Vite.
2570
+ *
2571
+ * Used by Alepha Rocket (and other non-CLI deploy orchestrators) to
2572
+ * call `orchestrator.up({ ... })` directly. For CLI usage
2573
+ * (`alepha platform up`), import `AlephaCliPlatformPlugin` from
2574
+ * `alepha/cli/platform` — that one adds the command layer on top.
2575
+ */
2576
+ const AlephaPlatformLibPlugin = $module({
2577
+ name: "alepha.cli.platform-lib",
2578
+ services: [
2579
+ CloudflareAdapter,
2580
+ CloudflareApi,
2581
+ VercelAdapter,
2582
+ VercelApi,
2583
+ VercelCli,
2584
+ WranglerApi,
2585
+ PlatformCacheProvider,
2586
+ GitHubSecretStore,
2587
+ MemorySecretStore,
2588
+ NamingService,
2589
+ SecretFilterService,
2590
+ PlatformInspector,
2591
+ PlatformOrchestrator
2592
+ ]
2593
+ });
2594
+ //#endregion
2595
+ export { AlephaPlatformLibPlugin, CloudflareAdapter, CloudflareApi, GitHubSecretStore, MemorySecretStore, NamingContext, NamingService, PlatformAdapter, PlatformCacheProvider, PlatformInspector, PlatformOrchestrator, SecretFilterService, SecretStoreProvider, VercelAdapter, VercelApi, VercelCli, WranglerApi, cloudflareAccountSchema, cloudflareApiErrorSchema, cloudflareD1Schema, cloudflareDeploymentListSchema, cloudflareDeploymentSchema, cloudflareDeploymentVersionSchema, cloudflareHyperdriveOriginSchema, cloudflareHyperdriveSchema, cloudflareKVSchema, cloudflareQueueConsumerSchema, cloudflareQueueSchema, cloudflareR2ListSchema, cloudflareR2Schema, cloudflareR2TokenSchema, cloudflareSecretSchema, cloudflareVersionListSchema, cloudflareVersionSchema, cloudflareWorkerSchema, createD1BodySchema, createEnvVarBodySchema, createHyperdriveBodySchema, createHyperdriveOriginSchema, createKVBodySchema, createProjectBodySchema, createQueueBodySchema, createR2BodySchema, createR2TokenBodySchema, platformOptions, platformPlanAppResourcesSchema, platformPlanAppSchema, platformPlanEnvironmentSchema, platformPlanResourceSchema, platformPlanSchema, platformStatusResourceSchema, platformStatusSchema, platformStatusSecretSchema, platformStatusWorkerSchema, putSecretBodySchema, resolveTenant, tenantDomain, vercelDeploymentSchema, vercelEnvVarSchema, vercelProjectSchema };
2596
+
2597
+ //# sourceMappingURL=index.js.map