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
@@ -1,2169 +1,8 @@
1
- import { $atom, $context, $inject, $module, $state, Alepha, AlephaError, t } from "alepha";
2
- import { AlephaCli, AlephaCliUtils, AppEntryProvider, PackageManagerUtils, ViteBuildProvider } from "alepha/cli";
3
- import { createHash } from "node:crypto";
4
- import { $command, Asker, EnvUtils, Runner } from "alepha/command";
1
+ import { $context, $inject, $module, $state, AlephaError, t } from "alepha";
2
+ import { AlephaCli, AppEntryProvider, ViteBuildProvider } from "alepha/cli";
3
+ import { AlephaPlatformLibPlugin, CloudflareAdapter, GitHubSecretStore, NamingService, PlatformInspector, PlatformOrchestrator, SecretFilterService, VercelAdapter, platformOptions, resolveTenant } from "alepha/cli/platform-lib";
4
+ import { $command, EnvUtils } from "alepha/command";
5
5
  import { $logger, ConsoleColorProvider } from "alepha/logger";
6
- import { FileSystemProvider, ShellProvider } from "alepha/system";
7
- import { S3mini } from "s3mini";
8
- import { DateTimeProvider } from "alepha/datetime";
9
- import { homedir, platform as platform$1 } from "node:os";
10
- import { join } from "node:path";
11
- //#region ../../src/cli/platform/providers/PlatformCacheProvider.ts
12
- /**
13
- * Caches cloud provider login state to avoid slow auth checks.
14
- *
15
- * Stored in node_modules/.alepha/platform.json (gitignored, project-scoped).
16
- * TTL: 4 hours.
17
- */
18
- var PlatformCacheProvider = class PlatformCacheProvider {
19
- static TTL_MS = 14400 * 1e3;
20
- fs = $inject(FileSystemProvider);
21
- dateTime = $inject(DateTimeProvider);
22
- cachePath(root) {
23
- return this.fs.join(root, "node_modules", ".alepha", "platform.json");
24
- }
25
- async isLoginFresh(root, adapter) {
26
- const entry = (await this.readCache(root))[adapter];
27
- if (!entry) return false;
28
- return this.dateTime.nowMillis() - entry.lastLoginCheck < PlatformCacheProvider.TTL_MS;
29
- }
30
- async getAccountId(root, adapter) {
31
- return (await this.readCache(root))[adapter]?.accountId;
32
- }
33
- async recordLogin(root, adapter, accountId) {
34
- const cache = await this.readCache(root);
35
- cache[adapter] = {
36
- lastLoginCheck: this.dateTime.nowMillis(),
37
- accountId
38
- };
39
- await this.writeCache(root, cache);
40
- }
41
- async readCache(root) {
42
- const path = this.cachePath(root);
43
- try {
44
- return await this.fs.readJsonFile(path);
45
- } catch {
46
- return {};
47
- }
48
- }
49
- async writeCache(root, cache) {
50
- const path = this.cachePath(root);
51
- const lastSlash = path.lastIndexOf("/");
52
- const dir = lastSlash > 0 ? path.slice(0, lastSlash) : path;
53
- await this.fs.mkdir(dir, { recursive: true }).catch(() => null);
54
- await this.fs.writeFile(path, JSON.stringify(cache, null, 2));
55
- }
56
- };
57
- //#endregion
58
- //#region ../../src/cli/platform/schemas/cloudflare.ts
59
- const cloudflareAccountSchema = t.object({
60
- id: t.string(),
61
- name: t.string()
62
- });
63
- const cloudflareD1Schema = t.object({
64
- uuid: t.string(),
65
- name: t.string()
66
- });
67
- const cloudflareKVSchema = t.object({
68
- id: t.string(),
69
- title: t.string()
70
- });
71
- const cloudflareR2Schema = t.object({
72
- name: t.string(),
73
- creation_date: t.optional(t.string())
74
- });
75
- const cloudflareR2ListSchema = t.object({ buckets: t.array(cloudflareR2Schema) });
76
- const cloudflareQueueSchema = t.object({
77
- queue_id: t.string(),
78
- queue_name: t.string()
79
- });
80
- const cloudflareQueueConsumerSchema = t.object({
81
- consumer_id: t.string(),
82
- service: t.string(),
83
- environment: t.optional(t.string())
84
- });
85
- const cloudflareHyperdriveOriginSchema = t.object({ host: t.string() });
86
- const cloudflareHyperdriveSchema = t.object({
87
- id: t.string(),
88
- name: t.string(),
89
- origin: cloudflareHyperdriveOriginSchema
90
- });
91
- const cloudflareWorkerSchema = t.object({
92
- id: t.string(),
93
- created_on: t.string(),
94
- modified_on: t.string()
95
- });
96
- const cloudflareDeploymentVersionSchema = t.object({
97
- version_id: t.string(),
98
- percentage: t.number()
99
- });
100
- const cloudflareDeploymentSchema = t.object({
101
- id: t.string(),
102
- versions: t.array(cloudflareDeploymentVersionSchema),
103
- created_on: t.string()
104
- });
105
- const cloudflareDeploymentListSchema = t.object({ deployments: t.array(cloudflareDeploymentSchema) });
106
- const cloudflareVersionSchema = t.object({
107
- id: t.string(),
108
- metadata: t.object({ created_on: t.string() }),
109
- annotations: t.optional(t.record(t.string(), t.string()))
110
- });
111
- const cloudflareVersionListSchema = t.object({ items: t.array(cloudflareVersionSchema) });
112
- const cloudflareSecretSchema = t.object({
113
- name: t.string(),
114
- type: t.string()
115
- });
116
- const createD1BodySchema = t.object({
117
- name: t.string(),
118
- primary_location_hint: t.optional(t.string()),
119
- jurisdiction: t.optional(t.string())
120
- });
121
- const createKVBodySchema = t.object({ title: t.string() });
122
- const createR2BodySchema = t.object({ name: t.string() });
123
- const cloudflareR2TokenSchema = t.object({
124
- id: t.string(),
125
- accessKeyId: t.string(),
126
- secretAccessKey: t.string()
127
- });
128
- const createR2TokenBodySchema = t.object({
129
- name: t.string(),
130
- policies: t.array(t.object({
131
- effect: t.string(),
132
- permissions: t.array(t.string()),
133
- buckets: t.optional(t.array(t.string()))
134
- }))
135
- });
136
- const createQueueBodySchema = t.object({ queue_name: t.string() });
137
- const createHyperdriveOriginSchema = t.object({
138
- scheme: t.string(),
139
- host: t.string(),
140
- port: t.number(),
141
- database: t.string(),
142
- user: t.string(),
143
- password: t.string()
144
- });
145
- const createHyperdriveBodySchema = t.object({
146
- name: t.string(),
147
- origin: createHyperdriveOriginSchema
148
- });
149
- const putSecretBodySchema = t.object({
150
- name: t.string(),
151
- text: t.string(),
152
- type: t.string()
153
- });
154
- const cloudflareApiErrorSchema = t.object({
155
- code: t.number(),
156
- message: t.string()
157
- });
158
- //#endregion
159
- //#region ../../src/cli/platform/services/WranglerApi.ts
160
- /**
161
- * Wraps wrangler CLI commands that are kept as shell-outs.
162
- *
163
- * Only used for operations where wrangler provides value
164
- * beyond a raw API call: OAuth login, worker deploy (bundling/upload),
165
- * D1 migrations, and secret bulk push.
166
- */
167
- var WranglerApi = class {
168
- log = $logger();
169
- shell = $inject(ShellProvider);
170
- utils = $inject(AlephaCliUtils);
171
- pm = $inject(PackageManagerUtils);
172
- runner = $inject(Runner);
173
- async runShell(command, options = {}) {
174
- const capture = options.capture;
175
- const output = await this.shell.run(command, {
176
- ...options,
177
- capture: capture ?? this.runner.useDynamicLogger
178
- });
179
- if (capture && !this.runner.useDynamicLogger) this.log.info(output);
180
- return output;
181
- }
182
- /**
183
- * Ensure wrangler is installed in the project.
184
- */
185
- async ensureInstalled(root, run) {
186
- await this.pm.ensureDependency(root, "wrangler", {
187
- dev: true,
188
- exec: async (cmd, opts) => {
189
- run.pause();
190
- try {
191
- await this.utils.exec(cmd, opts);
192
- } finally {
193
- run.resume();
194
- }
195
- }
196
- });
197
- }
198
- /**
199
- * Check if the user is authenticated. Returns the whoami output.
200
- */
201
- async whoami() {
202
- return await this.runShell("wrangler whoami", {
203
- resolve: true,
204
- capture: true
205
- });
206
- }
207
- /**
208
- * Open the browser-based OAuth login flow.
209
- */
210
- async login() {
211
- await this.runShell("wrangler login", { resolve: true });
212
- }
213
- /**
214
- * Get the current auth token from wrangler (auto-refreshes if expired).
215
- */
216
- async getAuthToken() {
217
- const output = await this.shell.run("wrangler auth token --json", {
218
- resolve: true,
219
- capture: true
220
- });
221
- return JSON.parse(output).token;
222
- }
223
- /**
224
- * Deploy a worker via wrangler (handles bundling and upload).
225
- *
226
- * Returns the workers.dev URL if found in the output.
227
- */
228
- async deploy(workerName, configPath) {
229
- return (await this.runShell(`wrangler deploy --name=${workerName} --no-bundle --config=${configPath}`, {
230
- resolve: true,
231
- capture: true
232
- })).match(/https:\/\/[^\s]*\.workers\.dev/)?.[0];
233
- }
234
- /**
235
- * Apply D1 migrations remotely.
236
- */
237
- async d1MigrationsApply(dbName, configPath) {
238
- await this.runShell(`wrangler d1 migrations apply ${dbName} --remote --config=${configPath}`, {
239
- resolve: true,
240
- env: { CI: "1" }
241
- });
242
- }
243
- };
244
- //#endregion
245
- //#region ../../src/cli/platform/services/CloudflareApi.ts
246
- /**
247
- * Thin wrapper over the Cloudflare REST API.
248
- *
249
- * Uses `wrangler auth token` to obtain credentials,
250
- * then calls `fetch()` directly for all CRUD operations.
251
- */
252
- var CloudflareApi = class CloudflareApi {
253
- static BASE = "https://api.cloudflare.com/client/v4";
254
- log = $logger();
255
- alepha = $inject(Alepha);
256
- wrangler = $inject(WranglerApi);
257
- token;
258
- accountId;
259
- jurisdiction;
260
- /**
261
- * Set the Cloudflare data jurisdiction for R2 and D1 resources.
262
- *
263
- * R2 buckets and D1 databases created under a jurisdiction live in a
264
- * separate namespace — every R2 API call (list/create/delete) must include
265
- * the `cf-r2-jurisdiction` header, and D1 create must include the field
266
- * in the request body. Omit / pass `undefined` for the default (global).
267
- */
268
- setJurisdiction(jurisdiction) {
269
- this.jurisdiction = jurisdiction;
270
- }
271
- /**
272
- * Override the Cloudflare account ID (from platform config).
273
- *
274
- * When unset, `resolveAccountId` falls back to `CLOUDFLARE_ACCOUNT_ID` env
275
- * var or the token's single account.
276
- */
277
- setAccountId(accountId) {
278
- this.accountId = accountId;
279
- }
280
- /**
281
- * Obtain the current auth token from wrangler.
282
- */
283
- async resolveToken() {
284
- if (this.token) return this.token;
285
- this.token = await this.wrangler.getAuthToken();
286
- return this.token;
287
- }
288
- /**
289
- * Resolve the Cloudflare account ID.
290
- *
291
- * Calls /accounts and picks the first one. Cached after first call.
292
- */
293
- async resolveAccountId() {
294
- if (this.accountId) return this.accountId;
295
- const fromEnv = process.env.CLOUDFLARE_ACCOUNT_ID;
296
- if (fromEnv) {
297
- this.accountId = fromEnv;
298
- return this.accountId;
299
- }
300
- const res = await this.fetch("/accounts", { schema: t.array(cloudflareAccountSchema) });
301
- if (res.length === 0) throw new AlephaError("No Cloudflare accounts found for this token.");
302
- if (res.length > 1) {
303
- const list = res.map((a) => ` - ${a.id} ${a.name}`).join("\n");
304
- 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}`);
305
- }
306
- this.accountId = res[0].id;
307
- return this.accountId;
308
- }
309
- async listD1() {
310
- const accountId = await this.resolveAccountId();
311
- return await this.paginate(`/accounts/${accountId}/d1/database`, cloudflareD1Schema);
312
- }
313
- async createD1(name, location = "weur") {
314
- const accountId = await this.resolveAccountId();
315
- const body = { name };
316
- if (this.jurisdiction) body.jurisdiction = this.jurisdiction;
317
- else body.primary_location_hint = location;
318
- return await this.fetch(`/accounts/${accountId}/d1/database`, {
319
- method: "POST",
320
- body,
321
- bodySchema: createD1BodySchema,
322
- schema: cloudflareD1Schema
323
- });
324
- }
325
- async deleteD1(databaseId) {
326
- const accountId = await this.resolveAccountId();
327
- await this.fetch(`/accounts/${accountId}/d1/database/${databaseId}`, { method: "DELETE" });
328
- }
329
- async listKV() {
330
- const accountId = await this.resolveAccountId();
331
- return await this.paginate(`/accounts/${accountId}/storage/kv/namespaces`, cloudflareKVSchema, 100);
332
- }
333
- async createKV(title) {
334
- const accountId = await this.resolveAccountId();
335
- return await this.fetch(`/accounts/${accountId}/storage/kv/namespaces`, {
336
- method: "POST",
337
- body: { title },
338
- bodySchema: createKVBodySchema,
339
- schema: cloudflareKVSchema
340
- });
341
- }
342
- async deleteKV(namespaceId) {
343
- const accountId = await this.resolveAccountId();
344
- await this.fetch(`/accounts/${accountId}/storage/kv/namespaces/${namespaceId}`, { method: "DELETE" });
345
- }
346
- async listR2() {
347
- const accountId = await this.resolveAccountId();
348
- return await this.paginateCursor(`/accounts/${accountId}/r2/buckets`, "buckets", cloudflareR2Schema);
349
- }
350
- async createR2(name) {
351
- const accountId = await this.resolveAccountId();
352
- await this.fetch(`/accounts/${accountId}/r2/buckets`, {
353
- method: "POST",
354
- body: { name },
355
- bodySchema: createR2BodySchema
356
- });
357
- }
358
- async deleteR2(name) {
359
- const accountId = await this.resolveAccountId();
360
- await this.fetch(`/accounts/${accountId}/r2/buckets/${name}`, { method: "DELETE" });
361
- }
362
- /**
363
- * Mint a bucket-scoped R2 API token (S3 access key + secret) using the
364
- * current bearer token. Used by teardown to wipe a bucket over the S3
365
- * protocol without requiring users to pre-create R2 access keys.
366
- *
367
- * The returned token should be revoked with `deleteR2Token` as soon as the
368
- * wipe is done.
369
- */
370
- async createR2Token(name, bucket) {
371
- const accountId = await this.resolveAccountId();
372
- return await this.fetch(`/accounts/${accountId}/r2/api-tokens`, {
373
- method: "POST",
374
- body: {
375
- name,
376
- policies: [{
377
- effect: "allow",
378
- permissions: ["admin-read-write"],
379
- buckets: [bucket]
380
- }]
381
- },
382
- bodySchema: createR2TokenBodySchema,
383
- schema: cloudflareR2TokenSchema
384
- });
385
- }
386
- async deleteR2Token(tokenId) {
387
- const accountId = await this.resolveAccountId();
388
- await this.fetch(`/accounts/${accountId}/r2/api-tokens/${tokenId}`, { method: "DELETE" });
389
- }
390
- async listQueues() {
391
- const accountId = await this.resolveAccountId();
392
- return await this.paginate(`/accounts/${accountId}/queues`, cloudflareQueueSchema);
393
- }
394
- async createQueue(name) {
395
- const accountId = await this.resolveAccountId();
396
- return await this.fetch(`/accounts/${accountId}/queues`, {
397
- method: "POST",
398
- body: { queue_name: name },
399
- bodySchema: createQueueBodySchema,
400
- schema: cloudflareQueueSchema
401
- });
402
- }
403
- async deleteQueue(queueId) {
404
- const accountId = await this.resolveAccountId();
405
- await this.fetch(`/accounts/${accountId}/queues/${queueId}`, { method: "DELETE" });
406
- }
407
- async listQueueConsumers(queueId) {
408
- const accountId = await this.resolveAccountId();
409
- return await this.paginate(`/accounts/${accountId}/queues/${queueId}/consumers`, cloudflareQueueConsumerSchema);
410
- }
411
- async deleteQueueConsumer(queueId, consumerService) {
412
- const accountId = await this.resolveAccountId();
413
- const consumer = (await this.listQueueConsumers(queueId)).find((c) => c.service === consumerService);
414
- if (!consumer) return;
415
- await this.fetch(`/accounts/${accountId}/queues/${queueId}/consumers/${consumer.consumer_id}`, { method: "DELETE" });
416
- }
417
- async listHyperdrive() {
418
- const accountId = await this.resolveAccountId();
419
- return await this.paginate(`/accounts/${accountId}/hyperdrive/configs`, cloudflareHyperdriveSchema);
420
- }
421
- async createHyperdrive(name, connectionString) {
422
- const accountId = await this.resolveAccountId();
423
- return await this.fetch(`/accounts/${accountId}/hyperdrive/configs`, {
424
- method: "POST",
425
- body: {
426
- name,
427
- origin: this.parseConnectionString(connectionString)
428
- },
429
- bodySchema: createHyperdriveBodySchema,
430
- schema: cloudflareHyperdriveSchema
431
- });
432
- }
433
- async deleteHyperdrive(configId) {
434
- const accountId = await this.resolveAccountId();
435
- await this.fetch(`/accounts/${accountId}/hyperdrive/configs/${configId}`, { method: "DELETE" });
436
- }
437
- async getWorker(scriptName) {
438
- const accountId = await this.resolveAccountId();
439
- try {
440
- return await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}`, { schema: cloudflareWorkerSchema });
441
- } catch {
442
- return;
443
- }
444
- }
445
- async deleteWorker(scriptName) {
446
- const accountId = await this.resolveAccountId();
447
- await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}`, {
448
- method: "DELETE",
449
- query: { force: "true" }
450
- });
451
- }
452
- async listDeployments(scriptName) {
453
- const accountId = await this.resolveAccountId();
454
- return (await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}/deployments`, {
455
- schema: cloudflareDeploymentListSchema,
456
- query: { per_page: "100" }
457
- })).deployments;
458
- }
459
- async listVersions(scriptName) {
460
- const accountId = await this.resolveAccountId();
461
- return await this.paginateCursor(`/accounts/${accountId}/workers/scripts/${scriptName}/versions`, "items", cloudflareVersionSchema);
462
- }
463
- async listSecrets(scriptName) {
464
- const accountId = await this.resolveAccountId();
465
- return await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}/secrets`, { schema: t.array(cloudflareSecretSchema) });
466
- }
467
- async putSecret(scriptName, name, value) {
468
- const accountId = await this.resolveAccountId();
469
- await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}/secrets`, {
470
- method: "PUT",
471
- body: {
472
- name,
473
- text: value,
474
- type: "secret_text"
475
- },
476
- bodySchema: putSecretBodySchema
477
- });
478
- }
479
- /**
480
- * Fetch the current worker bindings via the script-settings endpoint.
481
- * Used to merge new secrets into the existing binding set in one PATCH
482
- * (avoids the per-secret `putSecret` calls, each of which creates a
483
- * Cloudflare deployment — pushing 7 secrets meant 7 deployment rows).
484
- *
485
- * Secret bindings come back with `name` + `type` but no `text` (they're
486
- * write-only on Cloudflare's side); to preserve them across a PATCH we
487
- * forward each one as `{ type, name }` and Cloudflare keeps the stored
488
- * value.
489
- */
490
- async getWorkerSettings(scriptName) {
491
- const accountId = await this.resolveAccountId();
492
- return await this.fetch(`/accounts/${accountId}/workers/scripts/${scriptName}/settings`);
493
- }
494
- /**
495
- * Replace the worker's binding set in one call (= one Cloudflare
496
- * deployment, regardless of how many secrets are being updated).
497
- *
498
- * The endpoint expects multipart FormData with a `settings` field whose
499
- * value is a JSON-encoded `{ bindings: [...] }` — the `fetch` helper
500
- * above is JSON-only, so this one bypasses it and calls `globalThis.fetch`
501
- * directly. Mirrors what `wrangler secret bulk` does internally.
502
- */
503
- async patchWorkerBindings(scriptName, bindings) {
504
- const accountId = await this.resolveAccountId();
505
- const token = await this.resolveToken();
506
- const url = `${CloudflareApi.BASE}/accounts/${accountId}/workers/scripts/${scriptName}/settings`;
507
- const form = new FormData();
508
- form.set("settings", JSON.stringify({ bindings }));
509
- const response = await globalThis.fetch(url, {
510
- method: "PATCH",
511
- headers: { Authorization: `Bearer ${token}` },
512
- body: form
513
- });
514
- const json = await response.json().catch(() => null);
515
- if (!response.ok || !json?.success) {
516
- const messages = json?.errors?.map((e) => e.message).join(", ");
517
- throw new AlephaError(`Cloudflare API error (PATCH /workers/scripts/${scriptName}/settings): ${messages ?? response.statusText}`);
518
- }
519
- }
520
- async fetch(path, options = {}) {
521
- const token = await this.resolveToken();
522
- const { method = "GET", body, query } = options;
523
- let url = `${CloudflareApi.BASE}${path}`;
524
- if (query) {
525
- const params = new URLSearchParams(query);
526
- url += `?${params.toString()}`;
527
- }
528
- const headers = { Authorization: `Bearer ${token}` };
529
- if (this.jurisdiction && /\/r2\//.test(path)) headers["cf-r2-jurisdiction"] = this.jurisdiction;
530
- const init = {
531
- method,
532
- headers
533
- };
534
- if (body) {
535
- headers["Content-Type"] = "application/json";
536
- const validated = options.bodySchema ? this.alepha.codec.validate(options.bodySchema, body) : body;
537
- init.body = JSON.stringify(validated);
538
- }
539
- const json = await (await globalThis.fetch(url, init)).json();
540
- if (!json.success) throw new AlephaError(`Cloudflare API error (${method} ${path}): ${json.errors.map((e) => e.message).join(", ")}`);
541
- if (options.schema) return this.alepha.codec.validate(options.schema, json.result);
542
- return json.result;
543
- }
544
- /**
545
- * Paginate a page-based list endpoint (`result_info.total_pages`).
546
- *
547
- * Cloudflare defaults to `per_page=20`; we push it to 1000 (max on most
548
- * list endpoints) and loop if more pages exist. Each page is validated
549
- * against the item schema.
550
- */
551
- async paginate(path, itemSchema, perPage = 1e3) {
552
- const results = [];
553
- let page = 1;
554
- while (true) {
555
- const token = await this.resolveToken();
556
- const url = `${CloudflareApi.BASE}${path}?per_page=${perPage}&page=${page}`;
557
- const headers = { Authorization: `Bearer ${token}` };
558
- if (this.jurisdiction && /\/r2\//.test(path)) headers["cf-r2-jurisdiction"] = this.jurisdiction;
559
- const json = await (await globalThis.fetch(url, {
560
- method: "GET",
561
- headers
562
- })).json();
563
- if (!json.success) throw new AlephaError(`Cloudflare API error (GET ${path}): ${json.errors.map((e) => e.message).join(", ")}`);
564
- const validated = this.alepha.codec.validate(t.array(itemSchema), json.result);
565
- results.push(...validated);
566
- const totalPages = json.result_info?.total_pages;
567
- if (!totalPages || page >= totalPages || validated.length === 0) break;
568
- page++;
569
- }
570
- return results;
571
- }
572
- /**
573
- * Paginate a cursor-based list endpoint where `result` is an object
574
- * containing both the items array and a `cursor` field (R2 buckets,
575
- * Workers versions). Returns the flattened item array.
576
- */
577
- async paginateCursor(path, itemsKey, itemSchema, perPage = 1e3) {
578
- const results = [];
579
- let cursor;
580
- while (true) {
581
- const query = { per_page: String(perPage) };
582
- if (cursor) query.cursor = cursor;
583
- const res = await this.fetch(path, { query });
584
- const items = res[itemsKey] ?? [];
585
- const validated = this.alepha.codec.validate(t.array(itemSchema), items);
586
- results.push(...validated);
587
- const nextCursor = res.cursor;
588
- if (!nextCursor || validated.length === 0) break;
589
- cursor = nextCursor;
590
- }
591
- return results;
592
- }
593
- /**
594
- * Parse a postgres:// connection string into Hyperdrive origin fields.
595
- */
596
- parseConnectionString(connectionString) {
597
- const url = new URL(connectionString);
598
- return {
599
- scheme: "postgres",
600
- host: url.hostname,
601
- port: Number(url.port) || 5432,
602
- database: url.pathname.slice(1),
603
- user: decodeURIComponent(url.username),
604
- password: decodeURIComponent(url.password)
605
- };
606
- }
607
- };
608
- //#endregion
609
- //#region ../../src/cli/platform/adapters/PlatformAdapter.ts
610
- /**
611
- * Abstract platform adapter.
612
- *
613
- * Each cloud provider (Cloudflare, AKS, docker-compose) implements this.
614
- * The PlatformOrchestrator calls these methods in the correct order.
615
- */
616
- var PlatformAdapter = class {
617
- /**
618
- * Create/ensure cloud resources exist (DB, buckets, queues).
619
- * Not all adapters provision -- AKS defers to Helm.
620
- */
621
- async provision(_ctx, _run) {}
622
- /**
623
- * Run database migrations.
624
- */
625
- async migrate(_ctx, _run) {}
626
- /**
627
- * Push runtime secrets to the deployed worker(s).
628
- *
629
- * Reads secrets from `.env.{env}` files (parsed, not from process.env),
630
- * filters out vars already handled by bindings (DATABASE_URL, R2, etc.),
631
- * and pushes the rest via the platform's secret management.
632
- */
633
- async secrets(_ctx, _run) {}
634
- };
635
- //#endregion
636
- //#region ../../src/cli/platform/adapters/CloudflareAdapter.ts
637
- /**
638
- * Cloudflare Workers adapter.
639
- *
640
- * Uses the Cloudflare REST API (via CloudflareApi) for resource provisioning
641
- * and teardown, and wrangler CLI (via WranglerApi) for login, deploy,
642
- * D1 migrations, and secret bulk push.
643
- */
644
- var CloudflareAdapter = class CloudflareAdapter extends PlatformAdapter {
645
- log = $logger();
646
- fs = $inject(FileSystemProvider);
647
- shell = $inject(ShellProvider);
648
- cache = $inject(PlatformCacheProvider);
649
- alepha = $inject(Alepha);
650
- envUtils = $inject(EnvUtils);
651
- api = $inject(CloudflareApi);
652
- wrangler = $inject(WranglerApi);
653
- runner = $inject(Runner);
654
- provisionedD1Id;
655
- provisionedHyperdriveId;
656
- provisionedKVIds = /* @__PURE__ */ new Map();
657
- /**
658
- * Check if the user's DATABASE_URL points to an external Postgres database.
659
- * If so, we use Hyperdrive instead of D1.
660
- *
661
- * Reads from `.env.{env}` first, falls back to `process.env`.
662
- */
663
- async isPostgres(ctx) {
664
- return !!((await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`])).DATABASE_URL ?? process.env.DATABASE_URL)?.startsWith("postgres:");
665
- }
666
- /**
667
- * Propagate the environment's data-jurisdiction setting to the API client.
668
- *
669
- * Must be invoked at the top of every entry point (authenticate, build,
670
- * deploy, secrets, provision, migrate, inspect, teardown) because
671
- * CloudflareApi is a singleton reused across env invocations.
672
- */
673
- configureApi(ctx) {
674
- this.api.setJurisdiction(ctx.envConfig.jurisdiction);
675
- this.api.setAccountId(ctx.envConfig.accountId);
676
- }
677
- async runShell(command, options = {}) {
678
- const capture = options.capture;
679
- const output = await this.shell.run(command, {
680
- ...options,
681
- capture: capture ?? this.runner.useDynamicLogger
682
- });
683
- if (capture && !this.runner.useDynamicLogger) this.log.info(output);
684
- return output;
685
- }
686
- async authenticate(ctx, run) {
687
- this.configureApi(ctx);
688
- await run({
689
- name: "authenticate",
690
- handler: async () => {
691
- await this.wrangler.ensureInstalled(ctx.root, run);
692
- let needsLogin = false;
693
- try {
694
- await this.wrangler.getAuthToken();
695
- } catch {
696
- needsLogin = true;
697
- }
698
- if (needsLogin) {
699
- run.pause();
700
- await this.wrangler.login();
701
- run.resume();
702
- }
703
- if (await this.cache.isLoginFresh(ctx.root, "cloudflare")) return;
704
- try {
705
- const accountId = await this.api.resolveAccountId();
706
- await this.cache.recordLogin(ctx.root, "cloudflare", accountId);
707
- } catch {
708
- await this.cache.recordLogin(ctx.root, "cloudflare");
709
- }
710
- }
711
- });
712
- }
713
- async build(ctx, run) {
714
- this.configureApi(ctx);
715
- const appDir = ctx.app.path ? this.fs.join(ctx.root, ctx.app.path) : ctx.root;
716
- const env = {};
717
- if (ctx.app.resources.hasDatabase) {
718
- if (this.provisionedHyperdriveId) {
719
- env.HYPERDRIVE_ID = this.provisionedHyperdriveId;
720
- const pgSchema = (await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`])).POSTGRES_SCHEMA ?? process.env.POSTGRES_SCHEMA;
721
- if (pgSchema) env.POSTGRES_SCHEMA = pgSchema;
722
- } else if (this.provisionedD1Id) env.DATABASE_URL = `d1://${ctx.naming.d1()}:${this.provisionedD1Id}`;
723
- }
724
- if (ctx.app.resources.hasBucket) env.R2_BUCKET_NAME = ctx.naming.r2();
725
- if (ctx.app.resources.hasKV) {
726
- const kvName = ctx.naming.kv();
727
- env.CLOUDFLARE_KV_NAME = kvName;
728
- const kvId = this.provisionedKVIds.get(kvName);
729
- if (kvId) env.CLOUDFLARE_KV_ID = kvId;
730
- }
731
- if (ctx.app.resources.hasQueue) env.CLOUDFLARE_QUEUE_NAME = ctx.naming.queue();
732
- if (ctx.envConfig.domain) {
733
- if (ctx.envConfig.domain.includes("*") && !ctx.envConfig.zone) throw new AlephaError(`Wildcard domain "${ctx.envConfig.domain}" requires "zone" to be set in the environment config (the Cloudflare zone name, e.g. "alepha.dev").`);
734
- env.CLOUDFLARE_DOMAIN = ctx.envConfig.domain;
735
- if (ctx.envConfig.zone) env.CLOUDFLARE_ZONE = ctx.envConfig.zone;
736
- }
737
- if (ctx.envConfig.jurisdiction) env.CLOUDFLARE_JURISDICTION = ctx.envConfig.jurisdiction;
738
- const cmd = ctx.prebuilt ? "alepha build -t cloudflare --prebuilt" : "alepha build -t cloudflare";
739
- await run({
740
- name: cmd,
741
- handler: async () => {
742
- await this.runShell(cmd, {
743
- root: appDir,
744
- env
745
- });
746
- }
747
- });
748
- }
749
- async deploy(ctx, run) {
750
- this.configureApi(ctx);
751
- const workerName = ctx.naming.worker();
752
- const distDir = ctx.app.path ? this.fs.join(ctx.root, ctx.app.path, "dist") : this.fs.join(ctx.root, "dist");
753
- let url;
754
- await run({
755
- name: `deploy worker ${ctx.app.name}`,
756
- handler: async () => {
757
- url = await this.wrangler.deploy(workerName, `${distDir}/wrangler.jsonc`);
758
- }
759
- });
760
- return url;
761
- }
762
- /**
763
- * Vars that are handled by wrangler bindings or build config.
764
- * These should not be pushed as secrets.
765
- */
766
- static EXCLUDED_SECRET_KEYS = new Set([
767
- "DATABASE_URL",
768
- "R2_BUCKET_NAME",
769
- "CLOUDFLARE_DOMAIN",
770
- "CLOUDFLARE_ZONE",
771
- "CLOUDFLARE_JURISDICTION",
772
- "HYPERDRIVE_ID",
773
- "POSTGRES_SCHEMA",
774
- "NODE_ENV"
775
- ]);
776
- async secrets(ctx, run) {
777
- this.configureApi(ctx);
778
- const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
779
- const secrets = {};
780
- for (const [key, value] of Object.entries(envVars)) {
781
- if (!value) continue;
782
- if (CloudflareAdapter.EXCLUDED_SECRET_KEYS.has(key)) continue;
783
- if (key.startsWith("VITE_")) continue;
784
- secrets[key] = value;
785
- }
786
- if (Object.keys(secrets).length === 0) return;
787
- const hash = computeSecretsHash(secrets);
788
- for (const _app of ctx.apps) {
789
- const workerName = ctx.naming.worker();
790
- await run({
791
- name: `push secrets to ${workerName} (bulk)`,
792
- handler: async () => {
793
- const existingBindings = (await this.api.getWorkerSettings(workerName)).bindings ?? [];
794
- if (existingBindings.find((b) => b.type === "plain_text" && b.name === CloudflareAdapter.SECRETS_HASH_BINDING)?.text === hash) {
795
- this.log.info(`Secrets for ${workerName} unchanged (hash ${hash.slice(0, 8)}…), skipping push.`);
796
- return;
797
- }
798
- const overwriting = new Set(Object.keys(secrets));
799
- 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) => ({
800
- type: b.type,
801
- name: b.name
802
- }));
803
- const upsert = Object.entries(secrets).map(([name, text]) => ({
804
- type: "secret_text",
805
- name,
806
- text
807
- }));
808
- await this.api.patchWorkerBindings(workerName, [
809
- ...inherit,
810
- ...upsert,
811
- {
812
- type: "plain_text",
813
- name: CloudflareAdapter.SECRETS_HASH_BINDING,
814
- text: hash
815
- }
816
- ]);
817
- }
818
- });
819
- }
820
- }
821
- /**
822
- * Plain-text binding used to fingerprint the deployed secret set so the
823
- * next `up` can skip the PATCH when nothing has changed.
824
- */
825
- static SECRETS_HASH_BINDING = "ALEPHA_SECRETS_HASH";
826
- async provision(ctx, run) {
827
- this.configureApi(ctx);
828
- const needsDB = ctx.apps.some((a) => a.resources.hasDatabase);
829
- const needsBucket = ctx.apps.some((a) => a.resources.hasBucket);
830
- const postgres = needsDB && await this.isPostgres(ctx);
831
- const tasks = [];
832
- if (needsDB) if (postgres) {
833
- const hdName = ctx.naming.hyperdrive();
834
- const dbUrl = (await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`])).DATABASE_URL ?? process.env.DATABASE_URL;
835
- tasks.push({
836
- name: `provision hyperdrive (${hdName})`,
837
- handler: async () => {
838
- this.provisionedHyperdriveId = await this.ensureHyperdrive(hdName, dbUrl);
839
- }
840
- });
841
- } else {
842
- const dbName = ctx.naming.d1();
843
- tasks.push({
844
- name: `provision d1 (${dbName})`,
845
- handler: async () => {
846
- this.provisionedD1Id = await this.ensureD1(dbName);
847
- }
848
- });
849
- }
850
- if (needsBucket) {
851
- const bucketName = ctx.naming.r2();
852
- tasks.push({
853
- name: `provision r2 (${bucketName})`,
854
- handler: async () => {
855
- await this.ensureR2(bucketName);
856
- }
857
- });
858
- }
859
- for (const app of ctx.apps) {
860
- if (app.resources.hasKV) {
861
- const kvName = ctx.naming.kv();
862
- tasks.push({
863
- name: `provision kv (${kvName})`,
864
- handler: async () => {
865
- this.provisionedKVIds.set(kvName, await this.ensureKV(kvName));
866
- }
867
- });
868
- }
869
- if (app.resources.hasQueue) {
870
- const queueName = ctx.naming.queue();
871
- tasks.push({
872
- name: `provision queue (${queueName})`,
873
- handler: async () => {
874
- await this.ensureQueue(queueName);
875
- }
876
- });
877
- }
878
- }
879
- await run(tasks);
880
- }
881
- async migrate(ctx, run) {
882
- this.configureApi(ctx);
883
- if (!ctx.apps.some((a) => a.resources.hasDatabase)) return;
884
- if (await this.isPostgres(ctx)) await this.migratePostgres(ctx, run);
885
- else await this.migrateD1(ctx, run);
886
- }
887
- async migrateD1(ctx, run) {
888
- const dbName = ctx.naming.d1();
889
- await run({
890
- name: "migrate d1",
891
- handler: async () => {
892
- const migrationsDir = this.fs.join(ctx.root, "migrations", "sqlite");
893
- const env = { DATABASE_URL: this.provisionedD1Id ? `d1://${dbName}:${this.provisionedD1Id}` : `d1://${dbName}` };
894
- if (await this.fs.exists(migrationsDir)) await this.runShell(`alepha db migrations check --mode ${ctx.env}`, {
895
- resolve: true,
896
- env
897
- });
898
- else await this.runShell(`alepha db migrations create --mode ${ctx.env}`, {
899
- resolve: true,
900
- env
901
- });
902
- const distMigrations = this.fs.join(ctx.root, "dist", "migrations");
903
- await this.fs.cp(migrationsDir, distMigrations);
904
- await this.wrangler.d1MigrationsApply(dbName, "dist/wrangler.jsonc");
905
- await this.fs.rm(distMigrations, { recursive: true });
906
- }
907
- });
908
- }
909
- async migratePostgres(ctx, run) {
910
- await run({
911
- name: "migrate postgres",
912
- handler: async () => {
913
- const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
914
- const env = { DATABASE_URL: envVars.DATABASE_URL ?? process.env.DATABASE_URL };
915
- if (envVars.POSTGRES_SCHEMA ?? process.env.POSTGRES_SCHEMA) env.POSTGRES_SCHEMA = envVars.POSTGRES_SCHEMA ?? process.env.POSTGRES_SCHEMA;
916
- await this.runShell(`alepha db migrations apply --mode ${ctx.env}`, {
917
- resolve: true,
918
- env
919
- });
920
- }
921
- });
922
- }
923
- async inspect(ctx, run) {
924
- this.configureApi(ctx);
925
- const state = {
926
- workers: [],
927
- databases: [],
928
- buckets: [],
929
- kvNamespaces: [],
930
- queues: [],
931
- secrets: []
932
- };
933
- const tasks = [];
934
- for (const _app of ctx.apps) {
935
- const name = ctx.naming.worker();
936
- tasks.push({
937
- name: `inspect worker (${name})`,
938
- handler: async () => {
939
- try {
940
- const deployment = await this.getActiveDeployment(name);
941
- if (deployment) state.workers.push({
942
- name,
943
- exists: true,
944
- version: deployment.versionId,
945
- tag: deployment.tag,
946
- createdAt: deployment.createdAt
947
- });
948
- else state.workers.push({
949
- name,
950
- exists: false
951
- });
952
- } catch {
953
- state.workers.push({
954
- name,
955
- exists: false
956
- });
957
- }
958
- }
959
- });
960
- }
961
- if (ctx.apps.some((a) => a.resources.hasDatabase)) if (await this.isPostgres(ctx)) {
962
- const hdName = ctx.naming.hyperdrive();
963
- tasks.push({
964
- name: `inspect hyperdrive (${hdName})`,
965
- handler: async () => {
966
- const existing = (await this.api.listHyperdrive()).find((c) => c.name === hdName);
967
- state.databases.push({
968
- name: hdName,
969
- exists: !!existing,
970
- id: existing?.id,
971
- detail: existing?.origin.host
972
- });
973
- }
974
- });
975
- } else {
976
- const dbName = ctx.naming.d1();
977
- tasks.push({
978
- name: `inspect d1 (${dbName})`,
979
- handler: async () => {
980
- const existing = (await this.api.listD1()).find((db) => db.name === dbName);
981
- state.databases.push({
982
- name: dbName,
983
- exists: !!existing,
984
- id: existing?.uuid
985
- });
986
- }
987
- });
988
- }
989
- if (ctx.apps.some((a) => a.resources.hasBucket)) {
990
- const bucketName = ctx.naming.r2();
991
- tasks.push({
992
- name: `inspect r2 (${bucketName})`,
993
- handler: async () => {
994
- const existing = (await this.api.listR2()).find((b) => b.name === bucketName);
995
- state.buckets.push({
996
- name: bucketName,
997
- exists: !!existing,
998
- id: existing?.creation_date
999
- });
1000
- }
1001
- });
1002
- }
1003
- for (const app of ctx.apps) if (app.resources.hasKV) {
1004
- const kvName = ctx.naming.kv();
1005
- tasks.push({
1006
- name: `inspect kv (${kvName})`,
1007
- handler: async () => {
1008
- const existing = (await this.api.listKV()).find((ns) => ns.title === kvName);
1009
- state.kvNamespaces.push({
1010
- name: kvName,
1011
- exists: !!existing,
1012
- id: existing?.id
1013
- });
1014
- }
1015
- });
1016
- }
1017
- for (const app of ctx.apps) if (app.resources.hasQueue) {
1018
- const queueName = ctx.naming.queue();
1019
- tasks.push({
1020
- name: `inspect queue (${queueName})`,
1021
- handler: async () => {
1022
- const existing = (await this.api.listQueues()).find((q) => q.queue_name === queueName);
1023
- state.queues.push({
1024
- name: queueName,
1025
- exists: !!existing,
1026
- id: existing?.queue_id
1027
- });
1028
- }
1029
- });
1030
- }
1031
- const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
1032
- const expectedSecrets = Object.keys(envVars).filter((key) => envVars[key] && !CloudflareAdapter.EXCLUDED_SECRET_KEYS.has(key) && !key.startsWith("VITE_"));
1033
- if (expectedSecrets.length > 0) {
1034
- const workerName = ctx.naming.worker();
1035
- tasks.push({
1036
- name: "inspect secrets",
1037
- handler: async () => {
1038
- try {
1039
- const deployed = await this.api.listSecrets(workerName);
1040
- const deployedNames = new Set(deployed.map((s) => s.name));
1041
- for (const key of expectedSecrets) state.secrets.push({
1042
- name: key,
1043
- deployed: deployedNames.has(key)
1044
- });
1045
- } catch {
1046
- for (const key of expectedSecrets) state.secrets.push({
1047
- name: key,
1048
- deployed: false
1049
- });
1050
- }
1051
- }
1052
- });
1053
- }
1054
- await run(tasks);
1055
- return state;
1056
- }
1057
- async teardown(ctx, run) {
1058
- this.configureApi(ctx);
1059
- for (const app of ctx.apps) if (app.resources.hasQueue) {
1060
- const workerName = ctx.naming.worker();
1061
- const queueName = ctx.naming.queue();
1062
- await run({
1063
- name: `unbind queue consumer ${queueName}`,
1064
- handler: async () => {
1065
- try {
1066
- const queue = (await this.api.listQueues()).find((q) => q.queue_name === queueName);
1067
- if (queue) await this.api.deleteQueueConsumer(queue.queue_id, workerName);
1068
- } catch (error) {
1069
- this.log.warn(`Failed to unbind queue consumer: ${String(error.message || "")}`);
1070
- }
1071
- }
1072
- });
1073
- }
1074
- for (const _app of ctx.apps) {
1075
- const name = ctx.naming.worker();
1076
- await run({
1077
- name: `delete worker ${name}`,
1078
- handler: async () => {
1079
- try {
1080
- await this.api.deleteWorker(name);
1081
- } catch (error) {
1082
- this.log.warn(`Failed to delete worker ${name}: ${String(error.message || "")}`);
1083
- }
1084
- }
1085
- });
1086
- }
1087
- for (const app of ctx.apps) if (app.resources.hasQueue) {
1088
- const name = ctx.naming.queue();
1089
- await run({
1090
- name: `delete queue ${name}`,
1091
- handler: async () => {
1092
- try {
1093
- const queue = (await this.api.listQueues()).find((q) => q.queue_name === name);
1094
- if (!queue) {
1095
- this.log.debug(`Queue ${name} not found — skipping.`);
1096
- return;
1097
- }
1098
- await this.api.deleteQueue(queue.queue_id);
1099
- } catch (error) {
1100
- this.log.warn(`Failed to delete queue ${name}: ${String(error.message || "")}`);
1101
- }
1102
- }
1103
- });
1104
- }
1105
- for (const app of ctx.apps) if (app.resources.hasKV) {
1106
- const name = ctx.naming.kv();
1107
- await run({
1108
- name: `delete kv ${name}`,
1109
- handler: async () => {
1110
- try {
1111
- const existing = (await this.api.listKV()).find((ns) => ns.title === name);
1112
- if (!existing) {
1113
- this.log.debug(`KV namespace ${name} not found — skipping.`);
1114
- return;
1115
- }
1116
- await this.api.deleteKV(existing.id);
1117
- } catch (error) {
1118
- this.log.warn(`Failed to delete kv ${name}: ${String(error.message || "")}`);
1119
- }
1120
- }
1121
- });
1122
- }
1123
- if (ctx.apps.some((a) => a.resources.hasBucket)) {
1124
- const name = ctx.naming.r2();
1125
- await run({
1126
- name: `delete r2 ${name}`,
1127
- handler: async () => {
1128
- try {
1129
- await this.wipeR2Bucket(name, ctx);
1130
- await this.api.deleteR2(name);
1131
- } catch (error) {
1132
- const msg = String(error.message || "");
1133
- if (msg.includes("does not exist") || msg.includes("NoSuchBucket")) this.log.debug(`Bucket ${name} not found — skipping.`);
1134
- else this.log.warn(`Failed to delete r2 ${name}: ${msg}`);
1135
- }
1136
- }
1137
- });
1138
- }
1139
- if (ctx.apps.some((a) => a.resources.hasDatabase)) if (await this.isPostgres(ctx)) {
1140
- const name = ctx.naming.hyperdrive();
1141
- await run({
1142
- name: `delete hyperdrive ${name}`,
1143
- handler: async () => {
1144
- try {
1145
- const existing = (await this.api.listHyperdrive()).find((c) => c.name === name);
1146
- if (!existing) {
1147
- this.log.debug(`Hyperdrive ${name} not found — skipping.`);
1148
- return;
1149
- }
1150
- await this.api.deleteHyperdrive(existing.id);
1151
- } catch (error) {
1152
- this.log.warn(`Failed to delete hyperdrive ${name}: ${String(error.message || "")}`);
1153
- }
1154
- }
1155
- });
1156
- } else {
1157
- const name = ctx.naming.d1();
1158
- await run({
1159
- name: `delete d1 ${name}`,
1160
- handler: async () => {
1161
- try {
1162
- const existing = (await this.api.listD1()).find((db) => db.name === name);
1163
- if (!existing) {
1164
- this.log.debug(`D1 database ${name} not found — skipping.`);
1165
- return;
1166
- }
1167
- await this.api.deleteD1(existing.uuid);
1168
- } catch (error) {
1169
- this.log.warn(`Failed to delete d1 ${name}: ${String(error.message || "")}`);
1170
- }
1171
- }
1172
- });
1173
- }
1174
- }
1175
- async ensureD1(name) {
1176
- const existing = (await this.api.listD1()).find((db) => db.name === name);
1177
- if (existing) return existing.uuid;
1178
- return (await this.api.createD1(name)).uuid;
1179
- }
1180
- async ensureHyperdrive(name, connectionString) {
1181
- const existing = (await this.api.listHyperdrive()).find((c) => c.name === name);
1182
- if (existing) return existing.id;
1183
- return (await this.api.createHyperdrive(name, connectionString)).id;
1184
- }
1185
- async ensureR2(name) {
1186
- if ((await this.api.listR2()).find((b) => b.name === name)) return;
1187
- await this.api.createR2(name);
1188
- }
1189
- /**
1190
- * Empty an R2 bucket via the S3-compatible API.
1191
- *
1192
- * Cloudflare's REST `DELETE /r2/buckets/:name` rejects non-empty buckets
1193
- * with `BucketNotEmpty`, and the REST API has no object-level endpoints —
1194
- * objects must be listed and deleted over the S3 protocol. To avoid
1195
- * making users pre-create R2 access keys, we mint a short-lived
1196
- * bucket-scoped API token using the wrangler bearer token, wipe the
1197
- * bucket with `s3mini`, then revoke the token.
1198
- *
1199
- * Also aborts any pending multipart uploads — those count as bucket
1200
- * contents from R2's perspective and would otherwise block the delete.
1201
- */
1202
- async wipeR2Bucket(bucketName, ctx) {
1203
- const tokenName = `alepha-teardown-${bucketName}-${Date.now()}`;
1204
- const token = await this.api.createR2Token(tokenName, bucketName);
1205
- try {
1206
- const accountId = await this.api.resolveAccountId();
1207
- const jur = ctx.envConfig.jurisdiction;
1208
- const host = jur ? `${accountId}.${jur}.r2.cloudflarestorage.com` : `${accountId}.r2.cloudflarestorage.com`;
1209
- const client = new S3mini({
1210
- accessKeyId: token.accessKeyId,
1211
- secretAccessKey: token.secretAccessKey,
1212
- region: "auto",
1213
- endpoint: `https://${host}/${bucketName}`
1214
- });
1215
- try {
1216
- const mp = await client.listMultipartUploads();
1217
- if ("listMultipartUploadsResult" in mp) {
1218
- const uploads = mp.listMultipartUploadsResult.uploads ?? [];
1219
- for (const upload of uploads) {
1220
- const u = upload;
1221
- const key = u.Key ?? u.key;
1222
- const uploadId = u.UploadId ?? u.uploadId;
1223
- if (key && uploadId) await client.abortMultipartUpload(key, uploadId);
1224
- }
1225
- }
1226
- } catch (error) {
1227
- this.log.debug(`listMultipartUploads on ${bucketName} failed: ${String(error.message || "")}`);
1228
- }
1229
- let cursor;
1230
- let total = 0;
1231
- while (true) {
1232
- const page = await client.listObjectsPaged(void 0, void 0, 1e3, cursor);
1233
- const objects = page?.objects ?? [];
1234
- if (objects.length === 0) break;
1235
- await client.deleteObjects(objects.map((o) => o.Key));
1236
- total += objects.length;
1237
- cursor = page?.nextContinuationToken;
1238
- if (!cursor) break;
1239
- }
1240
- if (total > 0) this.log.info(`Emptied ${total} object(s) from bucket ${bucketName}.`);
1241
- } finally {
1242
- try {
1243
- await this.api.deleteR2Token(token.id);
1244
- } catch (error) {
1245
- this.log.warn(`Failed to revoke ephemeral R2 token ${token.id}: ${String(error.message || "")}`);
1246
- }
1247
- }
1248
- }
1249
- async ensureKV(name) {
1250
- const existing = (await this.api.listKV()).find((ns) => ns.title === name);
1251
- if (existing) return existing.id;
1252
- return (await this.api.createKV(name)).id;
1253
- }
1254
- async ensureQueue(name) {
1255
- if ((await this.api.listQueues()).find((q) => q.queue_name === name)) return;
1256
- await this.api.createQueue(name);
1257
- }
1258
- /**
1259
- * Get the currently active deployment for a worker.
1260
- */
1261
- async getActiveDeployment(workerName) {
1262
- const latest = [...await this.api.listDeployments(workerName)].sort((a, b) => b.created_on.localeCompare(a.created_on))[0];
1263
- if (!latest?.versions?.[0]) return;
1264
- const activeVersionId = latest.versions[0].version_id;
1265
- const version = (await this.api.listVersions(workerName)).find((v) => v.id === activeVersionId);
1266
- return {
1267
- versionId: activeVersionId,
1268
- tag: version?.annotations?.["workers/tag"],
1269
- createdAt: version?.metadata.created_on
1270
- };
1271
- }
1272
- };
1273
- /**
1274
- * Stable SHA-256 of the secret set. Keys are sorted so reordering `.env`
1275
- * lines does not invalidate the cache. Used as a fingerprint by
1276
- * `CloudflareAdapter.secrets` — see the comment block there.
1277
- */
1278
- function computeSecretsHash(secrets) {
1279
- const sorted = Object.keys(secrets).sort().map((k) => `${k}=${secrets[k]}`).join("\n");
1280
- return createHash("sha256").update(sorted).digest("hex");
1281
- }
1282
- //#endregion
1283
- //#region ../../src/cli/platform/schemas/vercel.ts
1284
- const vercelProjectSchema = t.object({
1285
- id: t.string(),
1286
- name: t.string(),
1287
- accountId: t.string()
1288
- });
1289
- const createProjectBodySchema = t.object({
1290
- name: t.string(),
1291
- framework: t.optional(t.null())
1292
- });
1293
- const vercelDeploymentSchema = t.object({
1294
- uid: t.string(),
1295
- name: t.string(),
1296
- url: t.string(),
1297
- state: t.optional(t.string()),
1298
- readyState: t.optional(t.string()),
1299
- created: t.optional(t.number()),
1300
- target: t.optional(t.string()),
1301
- alias: t.optional(t.array(t.string()))
1302
- });
1303
- const vercelEnvVarSchema = t.object({
1304
- id: t.string(),
1305
- key: t.string(),
1306
- value: t.optional(t.string()),
1307
- type: t.string(),
1308
- target: t.array(t.string())
1309
- });
1310
- const createEnvVarBodySchema = t.object({
1311
- key: t.string(),
1312
- value: t.string(),
1313
- type: t.string(),
1314
- target: t.array(t.string())
1315
- });
1316
- //#endregion
1317
- //#region ../../src/cli/platform/services/VercelCli.ts
1318
- /**
1319
- * Wraps Vercel CLI commands and token management.
1320
- *
1321
- * Used for operations where the Vercel CLI provides value:
1322
- * OAuth login, prebuilt deploy, and auth token extraction.
1323
- */
1324
- var VercelCli = class {
1325
- log = $logger();
1326
- shell = $inject(ShellProvider);
1327
- fs = $inject(FileSystemProvider);
1328
- utils = $inject(AlephaCliUtils);
1329
- pm = $inject(PackageManagerUtils);
1330
- runner = $inject(Runner);
1331
- async runShell(command, options = {}) {
1332
- const capture = options.capture;
1333
- const output = await this.shell.run(command, {
1334
- ...options,
1335
- capture: capture ?? this.runner.useDynamicLogger
1336
- });
1337
- if (capture && !this.runner.useDynamicLogger) this.log.info(output);
1338
- return output;
1339
- }
1340
- /**
1341
- * Ensure vercel CLI is installed in the project.
1342
- */
1343
- async ensureInstalled(root, run) {
1344
- await this.pm.ensureDependency(root, "vercel", {
1345
- dev: true,
1346
- exec: async (cmd, opts) => {
1347
- run.pause();
1348
- try {
1349
- await this.utils.exec(cmd, opts);
1350
- } finally {
1351
- run.resume();
1352
- }
1353
- }
1354
- });
1355
- }
1356
- /**
1357
- * Get the Vercel auth token.
1358
- *
1359
- * Priority:
1360
- * 1. VERCEL_TOKEN environment variable (CI/CD)
1361
- * 2. Vercel CLI auth.json file (local dev)
1362
- */
1363
- async getAuthToken() {
1364
- const envToken = process.env.VERCEL_TOKEN;
1365
- if (envToken) return envToken;
1366
- const authPath = this.getAuthFilePath();
1367
- if (!await this.fs.exists(authPath)) throw new AlephaError("Vercel auth token not found. Run `vercel login` or set VERCEL_TOKEN.");
1368
- const content = await this.fs.readFile(authPath);
1369
- const parsed = JSON.parse(content.toString());
1370
- if (!parsed.token) throw new AlephaError("Vercel auth.json exists but contains no token. Run `vercel login`.");
1371
- return parsed.token;
1372
- }
1373
- /**
1374
- * Validate the current auth token.
1375
- */
1376
- async whoami() {
1377
- return await this.runShell("vercel whoami", {
1378
- resolve: true,
1379
- capture: true
1380
- });
1381
- }
1382
- /**
1383
- * Open the browser-based login flow.
1384
- */
1385
- async login() {
1386
- await this.runShell("vercel login", { resolve: true });
1387
- }
1388
- /**
1389
- * Deploy a prebuilt .vercel/output/ directory.
1390
- *
1391
- * Returns the deployment URL.
1392
- */
1393
- async deploy(distDir, options) {
1394
- const args = [
1395
- "vercel",
1396
- "deploy",
1397
- "--prebuilt"
1398
- ];
1399
- if (options.prod) args.push("--prod");
1400
- if (options.token) args.push(`--token=${options.token}`);
1401
- return (await this.runShell(args.join(" "), {
1402
- resolve: true,
1403
- capture: true,
1404
- root: distDir
1405
- })).trim().split("\n").reverse().find((line) => line.trim().startsWith("https://"))?.trim();
1406
- }
1407
- /**
1408
- * Resolve the path to Vercel CLI auth.json.
1409
- */
1410
- getAuthFilePath() {
1411
- const os = platform$1();
1412
- if (os === "darwin") return join(homedir(), "Library", "Application Support", "com.vercel.cli", "auth.json");
1413
- if (os === "win32") return join(homedir(), "AppData", "Roaming", "xdg.data", "com.vercel.cli", "auth.json");
1414
- return join(homedir(), ".local", "share", "com.vercel.cli", "auth.json");
1415
- }
1416
- };
1417
- //#endregion
1418
- //#region ../../src/cli/platform/services/VercelApi.ts
1419
- /**
1420
- * Thin wrapper over the Vercel REST API.
1421
- *
1422
- * Uses the auth token from VercelCli for all requests.
1423
- */
1424
- var VercelApi = class VercelApi {
1425
- static BASE = "https://api.vercel.com";
1426
- log = $logger();
1427
- alepha = $inject(Alepha);
1428
- vercelCli = $inject(VercelCli);
1429
- token;
1430
- /**
1431
- * Obtain the current auth token from the Vercel CLI.
1432
- */
1433
- async resolveToken() {
1434
- if (this.token) return this.token;
1435
- this.token = await this.vercelCli.getAuthToken();
1436
- return this.token;
1437
- }
1438
- async listProjects() {
1439
- return (await this.fetch("/v10/projects", { schema: t.object({ projects: t.array(vercelProjectSchema) }) })).projects;
1440
- }
1441
- async getProject(nameOrId) {
1442
- try {
1443
- return await this.fetch(`/v9/projects/${encodeURIComponent(nameOrId)}`, { schema: vercelProjectSchema });
1444
- } catch {
1445
- return;
1446
- }
1447
- }
1448
- async createProject(name) {
1449
- return await this.fetch("/v11/projects", {
1450
- method: "POST",
1451
- body: {
1452
- name,
1453
- framework: null
1454
- },
1455
- bodySchema: createProjectBodySchema,
1456
- schema: vercelProjectSchema
1457
- });
1458
- }
1459
- async updateProject(nameOrId, settings) {
1460
- await this.fetch(`/v9/projects/${encodeURIComponent(nameOrId)}`, {
1461
- method: "PATCH",
1462
- body: settings
1463
- });
1464
- }
1465
- async deleteProject(nameOrId) {
1466
- await this.fetch(`/v9/projects/${encodeURIComponent(nameOrId)}`, { method: "DELETE" });
1467
- }
1468
- async listDeployments(projectId, options) {
1469
- const query = { projectId };
1470
- if (options?.limit) query.limit = String(options.limit);
1471
- if (options?.target) query.target = options.target;
1472
- return (await this.fetch("/v6/deployments", {
1473
- query,
1474
- schema: t.object({ deployments: t.array(vercelDeploymentSchema) })
1475
- })).deployments;
1476
- }
1477
- async listEnvVars(projectId) {
1478
- return (await this.fetch(`/v10/projects/${encodeURIComponent(projectId)}/env`, {
1479
- query: { decrypt: "true" },
1480
- schema: t.object({ envs: t.array(vercelEnvVarSchema) })
1481
- })).envs;
1482
- }
1483
- async upsertEnvVars(projectId, vars) {
1484
- for (const v of vars) await this.fetch(`/v10/projects/${encodeURIComponent(projectId)}/env`, {
1485
- method: "POST",
1486
- query: { upsert: "true" },
1487
- body: {
1488
- key: v.key,
1489
- value: v.value,
1490
- type: "encrypted",
1491
- target: v.target
1492
- },
1493
- bodySchema: createEnvVarBodySchema
1494
- });
1495
- }
1496
- async deleteEnvVar(projectId, envVarId) {
1497
- await this.fetch(`/v9/projects/${encodeURIComponent(projectId)}/env/${envVarId}`, { method: "DELETE" });
1498
- }
1499
- async fetch(path, options = {}) {
1500
- const token = await this.resolveToken();
1501
- const { method = "GET", body, query } = options;
1502
- let url = `${VercelApi.BASE}${path}`;
1503
- if (query) {
1504
- const params = new URLSearchParams(query);
1505
- url += `?${params.toString()}`;
1506
- }
1507
- const headers = { Authorization: `Bearer ${token}` };
1508
- const init = {
1509
- method,
1510
- headers
1511
- };
1512
- if (body) {
1513
- headers["Content-Type"] = "application/json";
1514
- const validated = options.bodySchema ? this.alepha.codec.validate(options.bodySchema, body) : body;
1515
- init.body = JSON.stringify(validated);
1516
- }
1517
- const response = await globalThis.fetch(url, init);
1518
- if (response.status === 204) return;
1519
- const json = await response.json();
1520
- if (json.error) throw new AlephaError(`Vercel API error (${method} ${path}): ${json.error.message ?? JSON.stringify(json.error)}`);
1521
- if (!response.ok) throw new AlephaError(`Vercel API error (${method} ${path}): HTTP ${response.status}`);
1522
- if (options.schema) return this.alepha.codec.validate(options.schema, json);
1523
- return json;
1524
- }
1525
- };
1526
- //#endregion
1527
- //#region ../../src/cli/platform/adapters/VercelAdapter.ts
1528
- /**
1529
- * Vercel platform adapter.
1530
- *
1531
- * Uses the Vercel CLI for login and deploy (--prebuilt),
1532
- * and the Vercel REST API for project management, env vars, and inspection.
1533
- *
1534
- * v1 scope: deploy pipeline only. No DB/storage/KV provisioning.
1535
- */
1536
- var VercelAdapter = class VercelAdapter extends PlatformAdapter {
1537
- log = $logger();
1538
- fs = $inject(FileSystemProvider);
1539
- shell = $inject(ShellProvider);
1540
- utils = $inject(AlephaCliUtils);
1541
- cache = $inject(PlatformCacheProvider);
1542
- alepha = $inject(Alepha);
1543
- envUtils = $inject(EnvUtils);
1544
- api = $inject(VercelApi);
1545
- vercelCli = $inject(VercelCli);
1546
- runner = $inject(Runner);
1547
- /**
1548
- * Vars that should not be pushed as env vars.
1549
- * These are either handled by the build or are internal.
1550
- */
1551
- static EXCLUDED_SECRET_KEYS = new Set(["NODE_ENV"]);
1552
- async runShell(command, options = {}) {
1553
- const capture = options.capture;
1554
- const output = await this.shell.run(command, {
1555
- ...options,
1556
- capture: capture ?? this.runner.useDynamicLogger
1557
- });
1558
- if (capture && !this.runner.useDynamicLogger) this.log.info(output);
1559
- return output;
1560
- }
1561
- async authenticate(ctx, run) {
1562
- await run({
1563
- name: "authenticate",
1564
- handler: async () => {
1565
- await this.vercelCli.ensureInstalled(ctx.root, run);
1566
- let needsLogin = false;
1567
- try {
1568
- await this.vercelCli.getAuthToken();
1569
- await this.vercelCli.whoami();
1570
- } catch {
1571
- needsLogin = true;
1572
- }
1573
- if (needsLogin) {
1574
- run.pause();
1575
- await this.vercelCli.login();
1576
- run.resume();
1577
- }
1578
- if (await this.cache.isLoginFresh(ctx.root, "vercel")) return;
1579
- await this.cache.recordLogin(ctx.root, "vercel");
1580
- }
1581
- });
1582
- }
1583
- async build(ctx, run) {
1584
- const appDir = ctx.app.path ? this.fs.join(ctx.root, ctx.app.path) : ctx.root;
1585
- await run({
1586
- name: "alepha build -t vercel",
1587
- handler: async () => {
1588
- await this.runShell("alepha build -t vercel", { root: appDir });
1589
- }
1590
- });
1591
- }
1592
- async deploy(ctx, run) {
1593
- const distDir = ctx.app.path ? this.fs.join(ctx.root, ctx.app.path, "dist") : this.fs.join(ctx.root, "dist");
1594
- const projectName = ctx.naming.worker();
1595
- let url;
1596
- await run({
1597
- name: `deploy ${ctx.app.name}`,
1598
- handler: async () => {
1599
- let project = await this.api.getProject(projectName);
1600
- if (!project) project = await this.api.createProject(projectName);
1601
- await this.api.updateProject(projectName, { framework: null });
1602
- const vercelDir = this.fs.join(distDir, ".vercel");
1603
- await this.fs.mkdir(vercelDir);
1604
- await this.fs.writeFile(this.fs.join(vercelDir, "project.json"), JSON.stringify({
1605
- projectId: project.id,
1606
- orgId: project.accountId
1607
- }, null, 2));
1608
- const token = process.env.VERCEL_TOKEN;
1609
- await this.vercelCli.deploy(distDir, {
1610
- prod: true,
1611
- token
1612
- });
1613
- const latest = (await this.api.listDeployments(project.id, {
1614
- limit: 1,
1615
- target: "production"
1616
- }))[0];
1617
- url = latest?.alias?.[0] ? `https://${latest.alias[0]}` : `https://${projectName}.vercel.app`;
1618
- }
1619
- });
1620
- return url;
1621
- }
1622
- async secrets(ctx, run) {
1623
- const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
1624
- const vars = [];
1625
- for (const [key, value] of Object.entries(envVars)) {
1626
- if (!value) continue;
1627
- if (VercelAdapter.EXCLUDED_SECRET_KEYS.has(key)) continue;
1628
- if (key.startsWith("VITE_")) continue;
1629
- vars.push({
1630
- key,
1631
- value,
1632
- target: ["production", "preview"]
1633
- });
1634
- }
1635
- if (vars.length === 0) return;
1636
- for (const app of ctx.apps) {
1637
- const projectName = ctx.naming.worker();
1638
- await run({
1639
- name: `push env vars to ${projectName}`,
1640
- handler: async () => {
1641
- await this.api.upsertEnvVars(projectName, vars);
1642
- }
1643
- });
1644
- }
1645
- }
1646
- async inspect(ctx, run) {
1647
- const state = {
1648
- workers: [],
1649
- databases: [],
1650
- buckets: [],
1651
- kvNamespaces: [],
1652
- queues: [],
1653
- secrets: []
1654
- };
1655
- const tasks = [];
1656
- for (const app of ctx.apps) {
1657
- const projectName = ctx.naming.worker();
1658
- tasks.push({
1659
- name: `inspect project (${projectName})`,
1660
- handler: async () => {
1661
- const project = await this.api.getProject(projectName);
1662
- if (!project) {
1663
- state.workers.push({
1664
- name: projectName,
1665
- exists: false
1666
- });
1667
- return;
1668
- }
1669
- const latest = (await this.api.listDeployments(project.id, { limit: 1 }))[0];
1670
- state.workers.push({
1671
- name: projectName,
1672
- exists: true,
1673
- version: latest?.uid,
1674
- createdAt: latest?.created ? new Date(latest.created).toISOString() : void 0
1675
- });
1676
- }
1677
- });
1678
- }
1679
- const envVars = await this.envUtils.parseEnv(ctx.root, [`.env.${ctx.env}`]);
1680
- const expectedVars = Object.keys(envVars).filter((key) => envVars[key] && !VercelAdapter.EXCLUDED_SECRET_KEYS.has(key) && !key.startsWith("VITE_"));
1681
- if (expectedVars.length > 0) {
1682
- const projectName = ctx.naming.worker();
1683
- tasks.push({
1684
- name: "inspect env vars",
1685
- handler: async () => {
1686
- try {
1687
- const deployed = await this.api.listEnvVars(projectName);
1688
- const deployedKeys = new Set(deployed.map((v) => v.key));
1689
- for (const key of expectedVars) state.secrets.push({
1690
- name: key,
1691
- deployed: deployedKeys.has(key)
1692
- });
1693
- } catch {
1694
- for (const key of expectedVars) state.secrets.push({
1695
- name: key,
1696
- deployed: false
1697
- });
1698
- }
1699
- }
1700
- });
1701
- }
1702
- await run(tasks);
1703
- return state;
1704
- }
1705
- async teardown(ctx, run) {
1706
- for (const app of ctx.apps) {
1707
- const projectName = ctx.naming.worker();
1708
- await run({
1709
- name: `delete project ${projectName}`,
1710
- handler: async () => {
1711
- try {
1712
- await this.api.deleteProject(projectName);
1713
- } catch (error) {
1714
- this.log.warn(`Failed to delete project ${projectName}: ${String(error.message || "")}`);
1715
- }
1716
- }
1717
- });
1718
- }
1719
- }
1720
- };
1721
- //#endregion
1722
- //#region ../../src/cli/platform/atoms/platformOptions.ts
1723
- /**
1724
- * Platform deployment configuration atom.
1725
- *
1726
- * Filled from the `platform` section of `alepha.config.ts`.
1727
- * Read by `PlatformCommand` to resolve environments and adapters.
1728
- */
1729
- const platformOptions = $atom({
1730
- name: "alepha.cli.platform.options",
1731
- description: "Platform deployment configuration",
1732
- schema: t.optional(t.object({
1733
- /**
1734
- * Project name override. Defaults to root package.json "name".
1735
- */
1736
- name: t.optional(t.text()),
1737
- /**
1738
- * Default environment when --env is omitted.
1739
- *
1740
- * @default "production"
1741
- */
1742
- default: t.optional(t.text()),
1743
- /**
1744
- * Secret store configuration for syncing .env secrets
1745
- * to external providers (e.g. GitHub Actions environments).
1746
- */
1747
- secrets: t.optional(t.object({
1748
- /**
1749
- * Secret store backend.
1750
- */
1751
- store: t.enum(["github"]),
1752
- /**
1753
- * Pattern for resolving environment names in the store.
1754
- * Placeholders: {project}, {env}.
1755
- *
1756
- * @default "{project}-{env}"
1757
- */
1758
- environmentPattern: t.optional(t.text())
1759
- })),
1760
- /**
1761
- * Named environments with their adapter and configuration.
1762
- */
1763
- environments: t.record(t.text({ description: "Environment name (e.g. 'production', 'staging', 'preview'). Used in resource naming and selected via --env." }), t.object({
1764
- adapter: t.enum(["cloudflare", "vercel"]),
1765
- /**
1766
- * Custom domain for the deployed worker (e.g. "api.example.com").
1767
- *
1768
- * On Cloudflare this is attached as a custom-domain route.
1769
- * Omit to use the adapter's default `*.workers.dev` / preview URL.
1770
- *
1771
- * Wildcards are supported for multi-tenant SaaS apps:
1772
- * `"*.club.alepha.dev"` routes every subdomain to the worker.
1773
- * Wildcard patterns require `zone` to be set, and the wildcard DNS
1774
- * record must already exist (proxied) in the Cloudflare zone.
1775
- */
1776
- domain: t.optional(t.text()),
1777
- /**
1778
- * Cloudflare zone name (e.g. "alepha.dev") that owns `domain`.
1779
- *
1780
- * Required when `domain` contains a wildcard (`*`). Ignored for
1781
- * plain custom domains, which Cloudflare resolves automatically.
1782
- */
1783
- zone: t.optional(t.text()),
1784
- /**
1785
- * Cloudflare data jurisdiction for R2 buckets and D1 databases.
1786
- * - "eu": data stays within the EU
1787
- * - "fedramp": FedRAMP-authorized regions
1788
- *
1789
- * Omit for the default (global) jurisdiction.
1790
- */
1791
- jurisdiction: t.optional(t.enum(["eu", "fedramp"])),
1792
- /**
1793
- * Cloudflare account ID to deploy into.
1794
- *
1795
- * Falls back to `CLOUDFLARE_ACCOUNT_ID` env var, then to the
1796
- * token's account when the token is scoped to exactly one.
1797
- * Required when the token has access to multiple accounts.
1798
- */
1799
- accountId: t.optional(t.text())
1800
- }))
1801
- }))
1802
- });
1803
- //#endregion
1804
- //#region ../../src/cli/platform/services/NamingService.ts
1805
- /**
1806
- * Generates deterministic resource names for cloud deployments.
1807
- *
1808
- * Pattern: `<project>-<env>`.
1809
- *
1810
- * All segments are slugified (lowercase, alphanumeric + dashes, max 63
1811
- * chars). One app per workspace — see `alepha platform`.
1812
- */
1813
- var NamingService = class {
1814
- forContext(project, env) {
1815
- return new NamingContext(`${this.slugify(project)}-${this.slugify(env)}`);
1816
- }
1817
- slugify(name) {
1818
- return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 63);
1819
- }
1820
- };
1821
- var NamingContext = class {
1822
- prefix;
1823
- constructor(prefix) {
1824
- this.prefix = prefix;
1825
- }
1826
- worker() {
1827
- return this.prefix;
1828
- }
1829
- d1() {
1830
- return this.prefix;
1831
- }
1832
- hyperdrive() {
1833
- return this.prefix;
1834
- }
1835
- r2() {
1836
- return this.prefix;
1837
- }
1838
- kv() {
1839
- return this.prefix;
1840
- }
1841
- queue() {
1842
- return this.prefix;
1843
- }
1844
- };
1845
- //#endregion
1846
- //#region ../../src/cli/platform/services/PlatformInspector.ts
1847
- /**
1848
- * Reads platform config and resolves project topology.
1849
- *
1850
- * Validates project name and environment configuration. Does NOT
1851
- * introspect app code for resources — that happens at deploy time via
1852
- * ViteBuildProvider.
1853
- *
1854
- * Each app self-declares its platform topology via its own
1855
- * `alepha.config.ts`. Run `alepha platform <op>` from the app's
1856
- * directory; no monorepo-root orchestration here.
1857
- */
1858
- var PlatformInspector = class {
1859
- log = $logger();
1860
- alepha = $inject(Alepha);
1861
- fs = $inject(FileSystemProvider);
1862
- asker = $inject(Asker);
1863
- options = $state(platformOptions);
1864
- naming = $inject(NamingService);
1865
- /**
1866
- * Resolve and validate the full platform configuration.
1867
- */
1868
- async resolveConfig(root) {
1869
- if (!this.options) {
1870
- this.log.warn(` alepha.config.ts not found or missing platform config.
1871
-
1872
- Please add a "platform" section to alepha.config.ts:
1873
-
1874
- export default defineConfig({
1875
- platform: {
1876
- environments: {
1877
- production: { adapter: "cloudflare" },
1878
- },
1879
- },
1880
- });
1881
- `);
1882
- throw new AlephaError("Missing platform configuration.");
1883
- }
1884
- const opts = this.options;
1885
- const project = await this.resolveProjectName(root, opts.name);
1886
- return {
1887
- project: this.naming.slugify(project),
1888
- defaultEnv: opts.default ?? "production",
1889
- environments: opts.environments
1890
- };
1891
- }
1892
- /**
1893
- * Resolve a specific environment, validating it exists.
1894
- */
1895
- async resolveEnvironment(root, envName) {
1896
- const config = await this.resolveConfig(root);
1897
- const envConfig = config.environments[envName];
1898
- if (!envConfig) throw new AlephaError(`Unknown environment "${envName}". Available: ${Object.keys(config.environments).join(", ")}`);
1899
- return envConfig;
1900
- }
1901
- async resolveProjectName(root, configName) {
1902
- if (configName) return configName;
1903
- try {
1904
- const pkgPath = this.fs.join(root, "package.json");
1905
- const pkg = await this.fs.readJsonFile(pkgPath);
1906
- if (pkg.name) return pkg.name;
1907
- } catch {}
1908
- throw new AlephaError("Missing project name. Set \"name\" in alepha.config.ts or add a \"name\" field to package.json.");
1909
- }
1910
- };
1911
- //#endregion
1912
- //#region ../../src/cli/platform/services/PlatformOrchestrator.ts
1913
- /**
1914
- * Orchestrates platform lifecycle operations.
1915
- *
1916
- * Coordinates adapter calls in the correct order for
1917
- * up (build -> migrate -> deploy), down, plan, and status.
1918
- */
1919
- var PlatformOrchestrator = class {
1920
- log = $logger();
1921
- color = $inject(ConsoleColorProvider);
1922
- inspector = $inject(PlatformInspector);
1923
- naming = $inject(NamingService);
1924
- cloudflareAdapter = $inject(CloudflareAdapter);
1925
- vercelAdapter = $inject(VercelAdapter);
1926
- alepha = $inject(Alepha);
1927
- resolveAdapter(adapterName) {
1928
- switch (adapterName) {
1929
- case "cloudflare": return this.cloudflareAdapter;
1930
- case "vercel": return this.vercelAdapter;
1931
- default: throw new AlephaError(`Unknown adapter: "${adapterName}"`);
1932
- }
1933
- }
1934
- async up(options) {
1935
- const { root, env, apps, run, prebuilt } = options;
1936
- const envConfig = await this.inspector.resolveEnvironment(root, env);
1937
- const config = await this.inspector.resolveConfig(root);
1938
- const adapter = this.resolveAdapter(envConfig.adapter);
1939
- const namingCtx = this.naming.forContext(config.project, env);
1940
- const ctx = {
1941
- project: config.project,
1942
- env,
1943
- envConfig,
1944
- apps,
1945
- root,
1946
- naming: namingCtx,
1947
- prebuilt
1948
- };
1949
- await adapter.authenticate(ctx, run);
1950
- await adapter.provision(ctx, run);
1951
- for (const a of apps) await adapter.build({
1952
- ...ctx,
1953
- app: a
1954
- }, run);
1955
- await adapter.migrate(ctx, run);
1956
- const urls = [];
1957
- for (const a of apps) {
1958
- const url = await adapter.deploy({
1959
- ...ctx,
1960
- app: a
1961
- }, run);
1962
- if (url) urls.push(url);
1963
- }
1964
- await adapter.secrets(ctx, run);
1965
- run.end();
1966
- return {
1967
- urls,
1968
- domain: envConfig.domain
1969
- };
1970
- }
1971
- /**
1972
- * Pretty-print the `up()` result to stdout. Matches the formatting the
1973
- * orchestrator used to emit inline; split out so callers that want
1974
- * JSON output can skip this branch.
1975
- */
1976
- printUpSummary(result) {
1977
- const c = this.color;
1978
- if (result.domain) {
1979
- this.log.info("");
1980
- const display = result.domain.includes("*") ? `https://${result.domain} (wildcard route)` : `https://${result.domain}`;
1981
- this.log.info(` ${c.set("GREEN", "→")} ${c.set("CYAN", display)}`);
1982
- this.log.info("");
1983
- } else for (const url of result.urls) {
1984
- this.log.info("");
1985
- this.log.info(` ${c.set("GREEN", "→")} ${c.set("CYAN", url)}`);
1986
- this.log.info("");
1987
- }
1988
- }
1989
- async down(options) {
1990
- const { root, env, apps, run, confirm } = options;
1991
- const envConfig = await this.inspector.resolveEnvironment(root, env);
1992
- const config = await this.inspector.resolveConfig(root);
1993
- const adapter = this.resolveAdapter(envConfig.adapter);
1994
- const namingCtx = this.naming.forContext(config.project, env);
1995
- const ctx = {
1996
- project: config.project,
1997
- env,
1998
- envConfig,
1999
- apps,
2000
- root,
2001
- naming: namingCtx
2002
- };
2003
- if (!this.isTmpEnv(env)) {
2004
- if (await confirm(`Type "${env}" to confirm teardown:`) !== env) {
2005
- this.log.info("Aborted.");
2006
- return false;
2007
- }
2008
- }
2009
- await adapter.authenticate(ctx, run);
2010
- await adapter.teardown(ctx, run);
2011
- run.end();
2012
- return true;
2013
- }
2014
- async plan(options) {
2015
- const { root, env, apps } = options;
2016
- const config = await this.inspector.resolveConfig(root);
2017
- return {
2018
- config,
2019
- naming: this.naming.forContext(config.project, env),
2020
- apps
2021
- };
2022
- }
2023
- async status(options) {
2024
- const { root, env, apps, run } = options;
2025
- const envConfig = await this.inspector.resolveEnvironment(root, env);
2026
- const config = await this.inspector.resolveConfig(root);
2027
- const adapter = this.resolveAdapter(envConfig.adapter);
2028
- const namingCtx = this.naming.forContext(config.project, env);
2029
- const ctx = {
2030
- project: config.project,
2031
- env,
2032
- envConfig,
2033
- apps,
2034
- root,
2035
- naming: namingCtx
2036
- };
2037
- await adapter.authenticate(ctx, run);
2038
- return {
2039
- config,
2040
- state: await adapter.inspect(ctx, run)
2041
- };
2042
- }
2043
- isTmpEnv(env) {
2044
- return env.startsWith("tmp");
2045
- }
2046
- };
2047
- //#endregion
2048
- //#region ../../src/cli/platform/providers/GitHubSecretStore.ts
2049
- /**
2050
- * GitHub Actions secret store backed by the `gh` CLI.
2051
- *
2052
- * Requires the GitHub CLI (`gh`) to be installed and authenticated.
2053
- * Pushes secrets into GitHub Actions environments.
2054
- */
2055
- var GitHubSecretStore = class {
2056
- log = $logger();
2057
- shell = $inject(ShellProvider);
2058
- fs = $inject(FileSystemProvider);
2059
- /**
2060
- * Verify that `gh` is installed and authenticated.
2061
- */
2062
- async ensureAvailable() {
2063
- if (!await this.shell.isInstalled("gh")) throw new AlephaError("GitHub CLI (gh) is not installed. Install it from https://cli.github.com");
2064
- try {
2065
- await this.shell.run("gh auth status", { capture: true });
2066
- } catch {
2067
- throw new AlephaError("GitHub CLI is not authenticated. Run `gh auth login` first.");
2068
- }
2069
- }
2070
- /**
2071
- * Create the GitHub Actions environment if it doesn't exist.
2072
- */
2073
- async ensureEnvironment(environment) {
2074
- await this.shell.run(`gh api --method PUT /repos/{owner}/{repo}/environments/${environment} --silent`, { capture: true });
2075
- this.log.debug(`Ensured environment "${environment}" exists`);
2076
- }
2077
- /**
2078
- * List all secrets in a GitHub Actions environment.
2079
- */
2080
- async list(environment) {
2081
- try {
2082
- const output = await this.shell.run(`gh secret list --env ${environment} --json name,updatedAt`, { capture: true });
2083
- return JSON.parse(output || "[]").map((s) => ({
2084
- name: s.name,
2085
- updatedAt: s.updatedAt
2086
- }));
2087
- } catch (error) {
2088
- this.log.debug("Failed to list secrets", {
2089
- environment,
2090
- error
2091
- });
2092
- return [];
2093
- }
2094
- }
2095
- /**
2096
- * Set a secret in a GitHub Actions environment.
2097
- *
2098
- * Writes a dotenv-formatted file and uses `gh secret set --env-file` to
2099
- * avoid shell pipe issues with NodeShellProvider escaping the `|` character.
2100
- */
2101
- async set(environment, key, value) {
2102
- const tmpFile = `/tmp/alepha-secret-${key}-${Date.now()}`;
2103
- const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
2104
- await this.fs.writeFile(tmpFile, `${key}="${escaped}"\n`);
2105
- try {
2106
- const output = await this.shell.run(`gh secret set -f ${tmpFile} --env ${environment}`, { capture: true });
2107
- this.log.debug(`Secret set: ${key}`, { output });
2108
- } finally {
2109
- await this.fs.rm(tmpFile);
2110
- }
2111
- }
2112
- /**
2113
- * Delete a secret from a GitHub Actions environment.
2114
- */
2115
- async delete(environment, key) {
2116
- await this.shell.run(`gh secret delete ${key} --env ${environment}`, { capture: true });
2117
- }
2118
- };
2119
- //#endregion
2120
- //#region ../../src/cli/platform/services/SecretFilterService.ts
2121
- /**
2122
- * Filters environment variables for secret store syncing.
2123
- *
2124
- * Excludes platform-managed vars (NODE_ENV), build-time vars (VITE_*),
2125
- * and empty values. Keeps everything else — including DATABASE_URL
2126
- * and POSTGRES_SCHEMA which GitHub Actions needs.
2127
- *
2128
- * Also handles renaming GITHUB_* keys since GitHub Actions rejects
2129
- * secret names starting with GITHUB_.
2130
- */
2131
- var SecretFilterService = class SecretFilterService {
2132
- static EXCLUDED_KEYS = new Set(["NODE_ENV"]);
2133
- static GITHUB_PREFIX = "GITHUB_";
2134
- static REMOTE_PREFIX = "APP_GITHUB_";
2135
- /**
2136
- * Return only the entries that should be pushed to a secret store.
2137
- */
2138
- filter(envVars) {
2139
- const result = {};
2140
- for (const [key, value] of Object.entries(envVars)) {
2141
- if (!value) continue;
2142
- if (SecretFilterService.EXCLUDED_KEYS.has(key)) continue;
2143
- if (key.startsWith("VITE_")) continue;
2144
- result[key] = value;
2145
- }
2146
- return result;
2147
- }
2148
- /**
2149
- * Convert a local env key to a remote secret name.
2150
- *
2151
- * GITHUB_* keys are prefixed with APP_ since GitHub Actions rejects
2152
- * secret names starting with GITHUB_.
2153
- */
2154
- toRemoteName(key) {
2155
- if (key.startsWith(SecretFilterService.GITHUB_PREFIX)) return `${SecretFilterService.REMOTE_PREFIX}${key.slice(SecretFilterService.GITHUB_PREFIX.length)}`;
2156
- return key;
2157
- }
2158
- /**
2159
- * Convert a remote secret name back to the local env key.
2160
- */
2161
- toLocalName(remoteName) {
2162
- if (remoteName.startsWith(SecretFilterService.REMOTE_PREFIX)) return `${SecretFilterService.GITHUB_PREFIX}${remoteName.slice(SecretFilterService.REMOTE_PREFIX.length)}`;
2163
- return remoteName;
2164
- }
2165
- };
2166
- //#endregion
2167
6
  //#region ../../src/cli/platform/commands/SecretsCommand.ts
2168
7
  var SecretsCommand = class {
2169
8
  log = $logger();
@@ -2352,6 +191,7 @@ var PlatformCommand = class {
2352
191
  aliases: ["e"],
2353
192
  description: "Target environment"
2354
193
  })),
194
+ tenant: t.optional(t.text({ description: "Tenant slug (apps with tenancy: optional | required). Names resources <tenant>-<project>-<env> and serves <tenant>.<domain>." })),
2355
195
  verbose: t.optional(t.boolean({
2356
196
  aliases: ["v"],
2357
197
  description: "Verbose output"
@@ -2367,7 +207,8 @@ var PlatformCommand = class {
2367
207
  const env = flags.env ?? config.defaultEnv;
2368
208
  const adapterName = config.environments[env]?.adapter ?? "cloudflare";
2369
209
  const apps = await this.resolveApps(root, config, this.isServerless(adapterName));
2370
- const namingCtx = this.naming.forContext(config.project, env);
210
+ const tenant = resolveTenant(config.tenancy, flags.tenant);
211
+ const namingCtx = this.naming.forContext(config.project, env, tenant);
2371
212
  const hasDB = apps.some((a) => a.resources.hasDatabase);
2372
213
  const hasBucket = apps.some((a) => a.resources.hasBucket);
2373
214
  const envVars = await this.envUtils.parseEnv(root, [`.env.${env}`]);
@@ -2462,13 +303,15 @@ var PlatformCommand = class {
2462
303
  const config = await this.inspector.resolveConfig(root);
2463
304
  const env = flags.env ?? config.defaultEnv;
2464
305
  const adapter = config.environments[env]?.adapter ?? "cloudflare";
2465
- const apps = await this.resolveApps(root, config, this.isServerless(adapter));
306
+ const apps = await this.resolveApps(root, config, this.isServerless(adapter), { prebuilt: flags.prebuilt });
2466
307
  const result = await this.orchestrator.up({
2467
308
  root,
2468
309
  env,
2469
- apps,
310
+ entry: apps[0].entry,
311
+ resources: apps[0].resources,
2470
312
  run,
2471
- prebuilt: flags.prebuilt
313
+ prebuilt: flags.prebuilt,
314
+ tenant: flags.tenant
2472
315
  });
2473
316
  if (flags.json) process.stdout.write(`${JSON.stringify({
2474
317
  status: "succeeded",
@@ -2498,8 +341,10 @@ var PlatformCommand = class {
2498
341
  const completed = await this.orchestrator.down({
2499
342
  root,
2500
343
  env: flags.env,
2501
- apps,
344
+ entry: apps[0].entry,
345
+ resources: apps[0].resources,
2502
346
  run,
347
+ tenant: flags.tenant,
2503
348
  confirm: async (prompt) => {
2504
349
  if (flags.yes) return flags.env;
2505
350
  ask.intro("Confirm teardown");
@@ -2528,8 +373,10 @@ var PlatformCommand = class {
2528
373
  const { state } = await this.orchestrator.status({
2529
374
  root,
2530
375
  env,
2531
- apps,
2532
- run
376
+ entry: apps[0].entry,
377
+ resources: apps[0].resources,
378
+ run,
379
+ tenant: flags.tenant
2533
380
  });
2534
381
  if (flags.json) {
2535
382
  run.end();
@@ -2620,19 +467,19 @@ var PlatformCommand = class {
2620
467
  const envConfig = config.environments[env];
2621
468
  const adapter = this.orchestrator.resolveAdapter(envConfig.adapter);
2622
469
  const apps = await this.resolveApps(root, config, this.isServerless(envConfig.adapter));
2623
- const namingCtx = this.naming.forContext(config.project, env);
470
+ const tenant = resolveTenant(config.tenancy, flags.tenant);
471
+ const namingCtx = this.naming.forContext(config.project, env, tenant);
2624
472
  const ctx = {
2625
473
  project: config.project,
2626
474
  env,
2627
475
  envConfig,
2628
- apps,
476
+ entry: apps[0].entry,
477
+ resources: apps[0].resources,
2629
478
  root,
2630
- naming: namingCtx
479
+ naming: namingCtx,
480
+ tenant
2631
481
  };
2632
- for (const app of apps) await adapter.build({
2633
- ...ctx,
2634
- app
2635
- }, run);
482
+ await adapter.build(ctx, run);
2636
483
  }
2637
484
  });
2638
485
  deploy = $command({
@@ -2645,20 +492,20 @@ var PlatformCommand = class {
2645
492
  const envConfig = config.environments[env];
2646
493
  const adapter = this.orchestrator.resolveAdapter(envConfig.adapter);
2647
494
  const apps = await this.resolveApps(root, config, this.isServerless(envConfig.adapter));
2648
- const namingCtx = this.naming.forContext(config.project, env);
495
+ const tenant = resolveTenant(config.tenancy, flags.tenant);
496
+ const namingCtx = this.naming.forContext(config.project, env, tenant);
2649
497
  const ctx = {
2650
498
  project: config.project,
2651
499
  env,
2652
500
  envConfig,
2653
- apps,
501
+ entry: apps[0].entry,
502
+ resources: apps[0].resources,
2654
503
  root,
2655
- naming: namingCtx
504
+ naming: namingCtx,
505
+ tenant
2656
506
  };
2657
507
  await adapter.authenticate(ctx, run);
2658
- for (const app of apps) await adapter.deploy({
2659
- ...ctx,
2660
- app
2661
- }, run);
508
+ await adapter.deploy(ctx, run);
2662
509
  }
2663
510
  });
2664
511
  migrate = $command({
@@ -2671,14 +518,17 @@ var PlatformCommand = class {
2671
518
  const envConfig = config.environments[env];
2672
519
  const adapter = this.orchestrator.resolveAdapter(envConfig.adapter);
2673
520
  const apps = await this.resolveApps(root, config, this.isServerless(envConfig.adapter));
2674
- const namingCtx = this.naming.forContext(config.project, env);
521
+ const tenant = resolveTenant(config.tenancy, flags.tenant);
522
+ const namingCtx = this.naming.forContext(config.project, env, tenant);
2675
523
  const ctx = {
2676
524
  project: config.project,
2677
525
  env,
2678
526
  envConfig,
2679
- apps,
527
+ entry: apps[0].entry,
528
+ resources: apps[0].resources,
2680
529
  root,
2681
- naming: namingCtx
530
+ naming: namingCtx,
531
+ tenant
2682
532
  };
2683
533
  await adapter.authenticate(ctx, run);
2684
534
  await adapter.migrate(ctx, run);
@@ -2689,6 +539,53 @@ var PlatformCommand = class {
2689
539
  }, null, 2)}\n`);
2690
540
  }
2691
541
  });
542
+ dbExport = $command({
543
+ name: "export",
544
+ description: "Export the deployed database to a local snapshot (remote → local dev DB).",
545
+ flags: t.object({
546
+ ...this.envFlags.properties,
547
+ output: t.optional(t.text({ description: "Destination SQLite file. Defaults to the dev DB path (node_modules/.alepha/sqlite.db)." })),
548
+ keepSql: t.optional(t.boolean({ description: "Keep the intermediate .sql dump file." }))
549
+ }),
550
+ handler: async ({ flags, root, run }) => {
551
+ const config = await this.inspector.resolveConfig(root);
552
+ const env = flags.env ?? config.defaultEnv;
553
+ const envConfig = config.environments[env];
554
+ const adapter = this.orchestrator.resolveAdapter(envConfig.adapter);
555
+ const app = await this.resolveApp(root, config, this.isServerless(envConfig.adapter));
556
+ const tenant = resolveTenant(config.tenancy, flags.tenant);
557
+ const namingCtx = this.naming.forContext(config.project, env, tenant);
558
+ const ctx = {
559
+ project: config.project,
560
+ env,
561
+ envConfig,
562
+ entry: app.entry,
563
+ resources: app.resources,
564
+ root,
565
+ naming: namingCtx,
566
+ tenant
567
+ };
568
+ await adapter.authenticate(ctx, run);
569
+ await adapter.exportDb(ctx, run, {
570
+ output: flags.output,
571
+ keepSql: flags.keepSql
572
+ });
573
+ }
574
+ });
575
+ /**
576
+ * `db` subgroup — operations against the *deployed* database (export,
577
+ * migrate). They live under `platform` (not core `alepha db`) because
578
+ * they need the env config, tenancy, adapter, and resource naming.
579
+ */
580
+ db = $command({
581
+ name: "db",
582
+ description: "Deployed-database operations (export, migrate).",
583
+ children: [this.dbExport, this.migrate],
584
+ handler: async ({ help, root }) => {
585
+ await this.inspector.resolveConfig(root);
586
+ help();
587
+ }
588
+ });
2692
589
  platform = $command({
2693
590
  name: "platform",
2694
591
  aliases: ["p"],
@@ -2700,7 +597,7 @@ var PlatformCommand = class {
2700
597
  this.status,
2701
598
  this.build,
2702
599
  this.deploy,
2703
- this.migrate,
600
+ this.db,
2704
601
  this.secretsCommand.secrets
2705
602
  ],
2706
603
  handler: async ({ help, root }) => {
@@ -2718,12 +615,32 @@ var PlatformCommand = class {
2718
615
  * ViteBuildProvider.init() per app. This is expensive -- only done
2719
616
  * for up/down/status, not for plan.
2720
617
  */
2721
- async resolveApps(root, config, isServerless) {
618
+ async resolveApp(root, _config, isServerless, options = {}) {
619
+ if (options.prebuilt) {
620
+ const manifest = await this.readManifest(root);
621
+ if (manifest) return {
622
+ entry: {
623
+ root,
624
+ server: ""
625
+ },
626
+ resources: manifest.resources
627
+ };
628
+ }
2722
629
  const entry = await this.boot.getAppEntry(root);
2723
630
  if (isServerless) process.env.ALEPHA_SERVERLESS = "true";
2724
631
  const appAlepha = await this.viteBuild.init({ entry });
2725
632
  delete process.env.ALEPHA_SERVERLESS;
2726
- const resources = this.detectResources(appAlepha);
633
+ return {
634
+ entry,
635
+ resources: this.detectResources(appAlepha)
636
+ };
637
+ }
638
+ /**
639
+ * @deprecated single-app projects; use `resolveApp` directly.
640
+ * Kept temporarily so existing commands can be migrated one at a time.
641
+ */
642
+ async resolveApps(root, config, isServerless, options = {}) {
643
+ const { entry, resources } = await this.resolveApp(root, config, isServerless, options);
2727
644
  return [{
2728
645
  name: config.project,
2729
646
  path: "",
@@ -2734,6 +651,21 @@ var PlatformCommand = class {
2734
651
  isServerless(adapter) {
2735
652
  return adapter === "vercel" || adapter === "cloudflare";
2736
653
  }
654
+ /**
655
+ * Read `dist/manifest.json` if present. Returns `null` when the file
656
+ * doesn't exist or isn't parseable — caller falls back to the
657
+ * Vite-introspection path.
658
+ */
659
+ async readManifest(root) {
660
+ try {
661
+ const fs = await import("node:fs/promises");
662
+ const path = await import("node:path");
663
+ const raw = await fs.readFile(path.join(root, "dist", "manifest.json"), "utf-8");
664
+ return JSON.parse(raw);
665
+ } catch {
666
+ return null;
667
+ }
668
+ }
2737
669
  detectResources(alepha) {
2738
670
  let hasDatabase = false;
2739
671
  let hasBucket = false;
@@ -2765,174 +697,15 @@ var PlatformCommand = class {
2765
697
  }
2766
698
  };
2767
699
  //#endregion
2768
- //#region ../../src/cli/platform/providers/MemorySecretStore.ts
2769
- /**
2770
- * In-memory implementation of SecretStoreProvider for testing.
2771
- * Records all operations and stores secrets in a nested Map.
2772
- */
2773
- var MemorySecretStore = class {
2774
- /**
2775
- * Secrets keyed by environment, then by key.
2776
- */
2777
- secrets = /* @__PURE__ */ new Map();
2778
- /**
2779
- * All recorded operations.
2780
- */
2781
- calls = [];
2782
- /**
2783
- * When set, ensureAvailable() will throw with this message.
2784
- */
2785
- availableError = null;
2786
- async ensureAvailable() {
2787
- this.calls.push({ method: "ensureAvailable" });
2788
- if (this.availableError) throw new AlephaError(this.availableError);
2789
- }
2790
- async ensureEnvironment(environment) {
2791
- this.calls.push({
2792
- method: "ensureEnvironment",
2793
- environment
2794
- });
2795
- if (!this.secrets.has(environment)) this.secrets.set(environment, /* @__PURE__ */ new Map());
2796
- }
2797
- async list(environment) {
2798
- this.calls.push({
2799
- method: "list",
2800
- environment
2801
- });
2802
- const envSecrets = this.secrets.get(environment);
2803
- if (!envSecrets) return [];
2804
- return Array.from(envSecrets.keys()).map((name) => ({ name }));
2805
- }
2806
- async set(environment, key, value) {
2807
- this.calls.push({
2808
- method: "set",
2809
- environment,
2810
- key,
2811
- value
2812
- });
2813
- let envSecrets = this.secrets.get(environment);
2814
- if (!envSecrets) {
2815
- envSecrets = /* @__PURE__ */ new Map();
2816
- this.secrets.set(environment, envSecrets);
2817
- }
2818
- envSecrets.set(key, value);
2819
- }
2820
- async delete(environment, key) {
2821
- this.calls.push({
2822
- method: "delete",
2823
- environment,
2824
- key
2825
- });
2826
- this.secrets.get(environment)?.delete(key);
2827
- }
2828
- /**
2829
- * Check if set() was called for a given environment and key.
2830
- */
2831
- wasSet(environment, key) {
2832
- return this.calls.some((c) => c.method === "set" && c.environment === environment && c.key === key);
2833
- }
2834
- /**
2835
- * Check if delete() was called for a given environment and key.
2836
- */
2837
- wasDeleted(environment, key) {
2838
- return this.calls.some((c) => c.method === "delete" && c.environment === environment && c.key === key);
2839
- }
2840
- /**
2841
- * Get all set() calls for a given environment.
2842
- */
2843
- getSetCalls(environment) {
2844
- return this.calls.filter((c) => c.method === "set" && c.environment === environment).map((c) => ({
2845
- key: c.key,
2846
- value: c.value
2847
- }));
2848
- }
2849
- /**
2850
- * Reset all state.
2851
- */
2852
- reset() {
2853
- this.secrets.clear();
2854
- this.calls = [];
2855
- this.availableError = null;
2856
- }
2857
- };
2858
- //#endregion
2859
- //#region ../../src/cli/platform/providers/SecretStoreProvider.ts
2860
- /**
2861
- * Abstract provider for managing secrets in an external store.
2862
- *
2863
- * Implementations: GitHubSecretStore, MemorySecretStore
2864
- */
2865
- var SecretStoreProvider = class {};
2866
- //#endregion
2867
- //#region ../../src/cli/platform/schemas/platform.ts
2868
- const platformStatusWorkerSchema = t.object({
2869
- name: t.string(),
2870
- exists: t.boolean(),
2871
- id: t.optional(t.string()),
2872
- detail: t.optional(t.string()),
2873
- version: t.optional(t.string()),
2874
- tag: t.optional(t.string()),
2875
- createdAt: t.optional(t.string())
2876
- });
2877
- const platformStatusResourceSchema = t.object({
2878
- name: t.string(),
2879
- exists: t.boolean(),
2880
- id: t.optional(t.string()),
2881
- detail: t.optional(t.string())
2882
- });
2883
- const platformStatusSecretSchema = t.object({
2884
- name: t.string(),
2885
- deployed: t.boolean()
2886
- });
2887
- const platformStatusSchema = t.object({
2888
- project: t.string(),
2889
- env: t.string(),
2890
- adapter: t.string(),
2891
- workers: t.array(platformStatusWorkerSchema),
2892
- databases: t.array(platformStatusResourceSchema),
2893
- buckets: t.array(platformStatusResourceSchema),
2894
- kvNamespaces: t.array(platformStatusResourceSchema),
2895
- queues: t.array(platformStatusResourceSchema),
2896
- secrets: t.array(platformStatusSecretSchema)
2897
- });
2898
- const platformPlanAppResourcesSchema = t.object({
2899
- hasDatabase: t.boolean(),
2900
- hasBucket: t.boolean(),
2901
- hasKV: t.boolean(),
2902
- hasQueue: t.boolean(),
2903
- hasCron: t.boolean()
2904
- });
2905
- const platformPlanAppSchema = t.object({
2906
- name: t.string(),
2907
- path: t.string(),
2908
- resources: platformPlanAppResourcesSchema
2909
- });
2910
- const platformPlanEnvironmentSchema = t.object({
2911
- adapter: t.string(),
2912
- domain: t.optional(t.string()),
2913
- zone: t.optional(t.string())
2914
- });
2915
- const platformPlanResourceSchema = t.object({
2916
- label: t.string(),
2917
- value: t.string()
2918
- });
2919
- const platformPlanSchema = t.object({
2920
- project: t.string(),
2921
- env: t.string(),
2922
- mode: t.enum(["monorepo", "standalone"]),
2923
- apps: t.array(platformPlanAppSchema),
2924
- environments: t.record(t.string(), platformPlanEnvironmentSchema),
2925
- resources: t.array(platformPlanResourceSchema),
2926
- secretCount: t.number()
2927
- });
2928
- //#endregion
2929
700
  //#region ../../src/cli/platform/index.ts
2930
701
  /**
2931
702
  * CLI plugin for multi-cloud deployment orchestration.
2932
703
  *
2933
- * Manages the full lifecycle of deploying Alepha apps: provision
2934
- * resources, build, migrate databases, deploy, and sync secrets.
2935
- * Supports Cloudflare Workers and Vercel.
704
+ * Wraps `AlephaPlatformLibPlugin` (the framework-agnostic deploy
705
+ * services) with `$command` instances so the orchestration is
706
+ * reachable from `alepha platform …`. Non-CLI consumers (e.g. Alepha
707
+ * Rocket) should depend on `alepha/cli/platform-lib` directly instead
708
+ * of pulling in this command surface.
2936
709
  *
2937
710
  * Commands:
2938
711
  * - `alepha platform plan` — show project topology and resource names
@@ -2940,9 +713,10 @@ const platformPlanSchema = t.object({
2940
713
  * - `alepha platform down` — teardown an environment
2941
714
  * - `alepha platform status` — inspect deployed resources
2942
715
  * - `alepha platform build` — build apps locally
2943
- * - `alepha platform deploy` — deploy to cloud
2944
- * - `alepha platform migrate` — run database migrations
2945
- * - `alepha platform secrets` manage external secret stores
716
+ * - `alepha platform deploy` — deploy to cloud
717
+ * - `alepha platform db migrate` — run database migrations
718
+ * - `alepha platform db export` pull the deployed DB into a local snapshot
719
+ * - `alepha platform secrets` — manage external secret stores
2946
720
  *
2947
721
  * Configuration in `alepha.config.ts`:
2948
722
  *
@@ -2964,21 +738,9 @@ const AlephaCliPlatformPlugin = $module({
2964
738
  name: "alepha.cli.plugins.platform",
2965
739
  services: [
2966
740
  AlephaCli,
741
+ AlephaPlatformLibPlugin,
2967
742
  PlatformCommand,
2968
- SecretsCommand,
2969
- CloudflareAdapter,
2970
- CloudflareApi,
2971
- VercelAdapter,
2972
- VercelApi,
2973
- VercelCli,
2974
- WranglerApi,
2975
- PlatformCacheProvider,
2976
- GitHubSecretStore,
2977
- MemorySecretStore,
2978
- NamingService,
2979
- SecretFilterService,
2980
- PlatformInspector,
2981
- PlatformOrchestrator
743
+ SecretsCommand
2982
744
  ]
2983
745
  });
2984
746
  const platform = (options) => {
@@ -2992,6 +754,6 @@ const platform = (options) => {
2992
754
  };
2993
755
  };
2994
756
  //#endregion
2995
- export { AlephaCliPlatformPlugin, CloudflareAdapter, CloudflareApi, GitHubSecretStore, MemorySecretStore, NamingContext, NamingService, PlatformAdapter, PlatformCacheProvider, PlatformCommand, PlatformInspector, PlatformOrchestrator, SecretFilterService, SecretStoreProvider, SecretsCommand, 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, platform, platformOptions, platformPlanAppResourcesSchema, platformPlanAppSchema, platformPlanEnvironmentSchema, platformPlanResourceSchema, platformPlanSchema, platformStatusResourceSchema, platformStatusSchema, platformStatusSecretSchema, platformStatusWorkerSchema, putSecretBodySchema, vercelDeploymentSchema, vercelEnvVarSchema, vercelProjectSchema };
757
+ export { AlephaCliPlatformPlugin, PlatformCommand, SecretsCommand, platform };
2996
758
 
2997
759
  //# sourceMappingURL=index.js.map