alchemy 0.45.6 → 0.46.1

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 (352) hide show
  1. package/README.md +2 -2
  2. package/bin/alchemy.mjs +19280 -15136
  3. package/bin/alchemy.ts +14 -56
  4. package/bin/commands/create.ts +307 -137
  5. package/bin/commands/deploy.ts +23 -0
  6. package/bin/commands/destroy.ts +19 -0
  7. package/bin/commands/dev.ts +21 -0
  8. package/bin/commands/login.ts +63 -0
  9. package/bin/commands/run.ts +29 -0
  10. package/bin/services/dependencies.ts +0 -5
  11. package/bin/services/execute-alchemy.ts +160 -0
  12. package/bin/services/git.ts +27 -0
  13. package/bin/services/github-workflow.ts +229 -0
  14. package/bin/services/package-manager.ts +51 -68
  15. package/bin/services/template-manager.ts +10 -21
  16. package/bin/trpc.ts +3 -0
  17. package/bin/types.ts +4 -2
  18. package/lib/apply.d.ts +2 -0
  19. package/lib/apply.d.ts.map +1 -1
  20. package/lib/apply.js +26 -8
  21. package/lib/apply.js.map +1 -1
  22. package/lib/build-date.d.ts +1 -1
  23. package/lib/build-date.js +1 -1
  24. package/lib/cloudflare/ai.d.ts +8 -4
  25. package/lib/cloudflare/ai.d.ts.map +1 -1
  26. package/lib/cloudflare/ai.js +6 -8
  27. package/lib/cloudflare/ai.js.map +1 -1
  28. package/lib/cloudflare/analytics-engine.d.ts +11 -7
  29. package/lib/cloudflare/analytics-engine.d.ts.map +1 -1
  30. package/lib/cloudflare/analytics-engine.js +11 -9
  31. package/lib/cloudflare/analytics-engine.js.map +1 -1
  32. package/lib/cloudflare/{types.d.ts → api-response.d.ts} +6 -1
  33. package/lib/cloudflare/api-response.d.ts.map +1 -0
  34. package/lib/cloudflare/api-response.js +38 -0
  35. package/lib/cloudflare/api-response.js.map +1 -0
  36. package/lib/cloudflare/api.d.ts +9 -5
  37. package/lib/cloudflare/api.d.ts.map +1 -1
  38. package/lib/cloudflare/api.js +6 -46
  39. package/lib/cloudflare/api.js.map +1 -1
  40. package/lib/cloudflare/auth.d.ts +16 -35
  41. package/lib/cloudflare/auth.d.ts.map +1 -1
  42. package/lib/cloudflare/auth.js +50 -129
  43. package/lib/cloudflare/auth.js.map +1 -1
  44. package/lib/cloudflare/browser-rendering.d.ts +12 -3
  45. package/lib/cloudflare/browser-rendering.d.ts.map +1 -1
  46. package/lib/cloudflare/browser-rendering.js +12 -2
  47. package/lib/cloudflare/browser-rendering.js.map +1 -1
  48. package/lib/cloudflare/bundle/fs.d.ts.map +1 -1
  49. package/lib/cloudflare/bundle/fs.js +3 -0
  50. package/lib/cloudflare/bundle/fs.js.map +1 -1
  51. package/lib/cloudflare/bundle/internal-worker-bundle.d.ts +6 -0
  52. package/lib/cloudflare/bundle/internal-worker-bundle.d.ts.map +1 -0
  53. package/lib/cloudflare/bundle/internal-worker-bundle.js +14 -0
  54. package/lib/cloudflare/bundle/internal-worker-bundle.js.map +1 -0
  55. package/lib/cloudflare/compatibility-presets.d.ts +22 -0
  56. package/lib/cloudflare/compatibility-presets.d.ts.map +1 -0
  57. package/lib/cloudflare/compatibility-presets.js +27 -0
  58. package/lib/cloudflare/compatibility-presets.js.map +1 -0
  59. package/lib/cloudflare/container.d.ts +5 -1
  60. package/lib/cloudflare/container.d.ts.map +1 -1
  61. package/lib/cloudflare/container.js +42 -2
  62. package/lib/cloudflare/container.js.map +1 -1
  63. package/lib/cloudflare/dispatch-namespace.d.ts +5 -0
  64. package/lib/cloudflare/dispatch-namespace.d.ts.map +1 -1
  65. package/lib/cloudflare/dispatch-namespace.js +8 -0
  66. package/lib/cloudflare/dispatch-namespace.js.map +1 -1
  67. package/lib/cloudflare/durable-object-namespace.d.ts +23 -15
  68. package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -1
  69. package/lib/cloudflare/durable-object-namespace.js +22 -21
  70. package/lib/cloudflare/durable-object-namespace.js.map +1 -1
  71. package/lib/cloudflare/hyperdrive.d.ts +19 -5
  72. package/lib/cloudflare/hyperdrive.d.ts.map +1 -1
  73. package/lib/cloudflare/hyperdrive.js +14 -0
  74. package/lib/cloudflare/hyperdrive.js.map +1 -1
  75. package/lib/cloudflare/images.d.ts +27 -17
  76. package/lib/cloudflare/images.d.ts.map +1 -1
  77. package/lib/cloudflare/images.js +13 -8
  78. package/lib/cloudflare/images.js.map +1 -1
  79. package/lib/cloudflare/index.d.ts +1 -3
  80. package/lib/cloudflare/index.d.ts.map +1 -1
  81. package/lib/cloudflare/index.js +1 -3
  82. package/lib/cloudflare/index.js.map +1 -1
  83. package/lib/cloudflare/miniflare/miniflare.d.ts +3 -3
  84. package/lib/cloudflare/miniflare/miniflare.d.ts.map +1 -1
  85. package/lib/cloudflare/miniflare/miniflare.js +7 -7
  86. package/lib/cloudflare/miniflare/miniflare.js.map +1 -1
  87. package/lib/cloudflare/miniflare/{mixed-mode.d.ts → remote-binding-proxy.d.ts} +5 -5
  88. package/lib/cloudflare/miniflare/remote-binding-proxy.d.ts.map +1 -0
  89. package/lib/cloudflare/miniflare/{mixed-mode.js → remote-binding-proxy.js} +16 -52
  90. package/lib/cloudflare/miniflare/remote-binding-proxy.js.map +1 -0
  91. package/lib/cloudflare/oauth.d.ts +22 -0
  92. package/lib/cloudflare/oauth.d.ts.map +1 -0
  93. package/lib/cloudflare/oauth.js +213 -0
  94. package/lib/cloudflare/oauth.js.map +1 -0
  95. package/lib/cloudflare/state.d.ts +26 -0
  96. package/lib/cloudflare/state.d.ts.map +1 -0
  97. package/lib/cloudflare/state.js +14 -0
  98. package/lib/cloudflare/state.js.map +1 -0
  99. package/lib/cloudflare/user.d.ts +2 -2
  100. package/lib/cloudflare/user.d.ts.map +1 -1
  101. package/lib/cloudflare/user.js +24 -47
  102. package/lib/cloudflare/user.js.map +1 -1
  103. package/lib/cloudflare/vectorize-index.d.ts.map +1 -1
  104. package/lib/cloudflare/vectorize-index.js +5 -2
  105. package/lib/cloudflare/vectorize-index.js.map +1 -1
  106. package/lib/cloudflare/version-metadata.d.ts +15 -3
  107. package/lib/cloudflare/version-metadata.d.ts.map +1 -1
  108. package/lib/cloudflare/version-metadata.js +11 -2
  109. package/lib/cloudflare/version-metadata.js.map +1 -1
  110. package/lib/cloudflare/vite.d.ts.map +1 -1
  111. package/lib/cloudflare/vite.js +2 -1
  112. package/lib/cloudflare/vite.js.map +1 -1
  113. package/lib/cloudflare/worker-metadata.d.ts +13 -2
  114. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  115. package/lib/cloudflare/worker-metadata.js +10 -15
  116. package/lib/cloudflare/worker-metadata.js.map +1 -1
  117. package/lib/cloudflare/worker-subdomain.d.ts +7 -0
  118. package/lib/cloudflare/worker-subdomain.d.ts.map +1 -1
  119. package/lib/cloudflare/worker-subdomain.js +17 -22
  120. package/lib/cloudflare/worker-subdomain.js.map +1 -1
  121. package/lib/cloudflare/{worker/tail.d.ts → worker-tail.d.ts} +2 -2
  122. package/lib/cloudflare/worker-tail.d.ts.map +1 -0
  123. package/lib/cloudflare/{worker/tail.js → worker-tail.js} +11 -9
  124. package/lib/cloudflare/worker-tail.js.map +1 -0
  125. package/lib/cloudflare/worker.d.ts +19 -8
  126. package/lib/cloudflare/worker.d.ts.map +1 -1
  127. package/lib/cloudflare/worker.js +35 -10
  128. package/lib/cloudflare/worker.js.map +1 -1
  129. package/lib/cloudflare/workflow.d.ts +22 -11
  130. package/lib/cloudflare/workflow.d.ts.map +1 -1
  131. package/lib/cloudflare/workflow.js +23 -18
  132. package/lib/cloudflare/workflow.js.map +1 -1
  133. package/lib/context.d.ts +2 -2
  134. package/lib/context.d.ts.map +1 -1
  135. package/lib/docker/api.d.ts.map +1 -1
  136. package/lib/docker/api.js +30 -8
  137. package/lib/docker/api.js.map +1 -1
  138. package/lib/fs/file-system-state-store.d.ts +5 -20
  139. package/lib/fs/file-system-state-store.d.ts.map +1 -1
  140. package/lib/fs/file-system-state-store.js +5 -121
  141. package/lib/fs/file-system-state-store.js.map +1 -1
  142. package/lib/fs/index.d.ts +0 -1
  143. package/lib/fs/index.d.ts.map +1 -1
  144. package/lib/fs/index.js +0 -1
  145. package/lib/fs/index.js.map +1 -1
  146. package/lib/llms.cloudflare.txt +6 -5
  147. package/lib/planetscale/password.js +1 -1
  148. package/lib/planetscale/password.js.map +1 -1
  149. package/lib/scope.d.ts.map +1 -1
  150. package/lib/scope.js +3 -6
  151. package/lib/scope.js.map +1 -1
  152. package/lib/state/cloudflare-state-store.d.ts +34 -0
  153. package/lib/state/cloudflare-state-store.d.ts.map +1 -0
  154. package/lib/state/cloudflare-state-store.js +116 -0
  155. package/lib/state/cloudflare-state-store.js.map +1 -0
  156. package/lib/state/d1-state-store.d.ts +15 -0
  157. package/lib/state/d1-state-store.d.ts.map +1 -0
  158. package/lib/{cloudflare → state}/d1-state-store.js +31 -27
  159. package/lib/state/d1-state-store.js.map +1 -0
  160. package/lib/{cloudflare/do-state-store/types.d.ts → state/dofs-state-store/api.d.ts} +1 -1
  161. package/lib/state/dofs-state-store/api.d.ts.map +1 -0
  162. package/lib/state/dofs-state-store/api.js +1 -0
  163. package/lib/state/dofs-state-store/api.js.map +1 -0
  164. package/lib/{cloudflare/do-state-store/store.d.ts → state/dofs-state-store/index.d.ts} +10 -5
  165. package/lib/state/dofs-state-store/index.d.ts.map +1 -0
  166. package/lib/{cloudflare/do-state-store/store.js → state/dofs-state-store/index.js} +15 -11
  167. package/lib/state/dofs-state-store/index.js.map +1 -0
  168. package/lib/{cloudflare/do-state-store/internal.d.ts → state/dofs-state-store/store.d.ts} +4 -5
  169. package/lib/state/dofs-state-store/store.d.ts.map +1 -0
  170. package/lib/{cloudflare/do-state-store/internal.js → state/dofs-state-store/store.js} +7 -22
  171. package/lib/state/dofs-state-store/store.js.map +1 -0
  172. package/lib/state/file-system-state-store.d.ts +21 -0
  173. package/lib/state/file-system-state-store.d.ts.map +1 -0
  174. package/lib/state/file-system-state-store.js +122 -0
  175. package/lib/state/file-system-state-store.js.map +1 -0
  176. package/lib/state/index.d.ts +7 -0
  177. package/lib/state/index.d.ts.map +1 -0
  178. package/lib/state/index.js +7 -0
  179. package/lib/state/index.js.map +1 -0
  180. package/lib/state/migrations.d.ts +2 -0
  181. package/lib/state/migrations.d.ts.map +1 -0
  182. package/lib/state/migrations.js +3 -0
  183. package/lib/state/migrations.js.map +1 -0
  184. package/lib/state/operations.d.ts +36 -0
  185. package/lib/state/operations.d.ts.map +1 -0
  186. package/lib/state/operations.js +126 -0
  187. package/lib/state/operations.js.map +1 -0
  188. package/lib/state/proxy.d.ts +53 -0
  189. package/lib/state/proxy.d.ts.map +1 -0
  190. package/lib/state/proxy.js +65 -0
  191. package/lib/state/proxy.js.map +1 -0
  192. package/lib/{cloudflare → state}/r2-rest-state-store.d.ts +3 -1
  193. package/lib/state/r2-rest-state-store.d.ts.map +1 -0
  194. package/lib/{cloudflare → state}/r2-rest-state-store.js +6 -4
  195. package/lib/state/r2-rest-state-store.js.map +1 -0
  196. package/lib/state/schema.d.ts.map +1 -0
  197. package/lib/state/schema.js.map +1 -0
  198. package/lib/{sqlite → state}/sqlite-state-store.d.ts +5 -3
  199. package/lib/state/sqlite-state-store.d.ts.map +1 -0
  200. package/lib/{sqlite → state}/sqlite-state-store.js +15 -6
  201. package/lib/state/sqlite-state-store.js.map +1 -0
  202. package/lib/test/vitest.d.ts.map +1 -1
  203. package/lib/test/vitest.js +3 -6
  204. package/lib/test/vitest.js.map +1 -1
  205. package/lib/util/detect-node-runtime.d.ts +3 -0
  206. package/lib/util/detect-node-runtime.d.ts.map +1 -0
  207. package/lib/util/detect-node-runtime.js +10 -0
  208. package/lib/util/detect-node-runtime.js.map +1 -0
  209. package/lib/util/detect-package-manager.d.ts +2 -2
  210. package/lib/util/detect-package-manager.d.ts.map +1 -1
  211. package/lib/util/detect-package-manager.js +15 -5
  212. package/lib/util/detect-package-manager.js.map +1 -1
  213. package/lib/util/http-server.d.ts.map +1 -0
  214. package/lib/{cloudflare/miniflare → util}/http-server.js +1 -1
  215. package/lib/util/http-server.js.map +1 -0
  216. package/lib/util/memoize.d.ts +1 -0
  217. package/lib/util/memoize.d.ts.map +1 -1
  218. package/lib/util/memoize.js +2 -8
  219. package/lib/util/memoize.js.map +1 -1
  220. package/lib/util/neverthrow.d.ts +16 -0
  221. package/lib/util/neverthrow.d.ts.map +1 -0
  222. package/lib/util/neverthrow.js +46 -0
  223. package/lib/util/neverthrow.js.map +1 -0
  224. package/package.json +18 -16
  225. package/src/apply.ts +31 -10
  226. package/src/build-date.ts +1 -1
  227. package/src/cloudflare/ai.ts +10 -5
  228. package/src/cloudflare/analytics-engine.ts +20 -11
  229. package/src/cloudflare/{types.ts → api-response.ts} +31 -0
  230. package/src/cloudflare/api.ts +18 -57
  231. package/src/cloudflare/auth.ts +79 -211
  232. package/src/cloudflare/browser-rendering.ts +16 -2
  233. package/src/cloudflare/bundle/fs.ts +5 -0
  234. package/src/cloudflare/bundle/internal-worker-bundle.ts +21 -0
  235. package/src/cloudflare/compatibility-presets.ts +34 -0
  236. package/src/cloudflare/container.ts +70 -3
  237. package/src/cloudflare/dispatch-namespace.ts +28 -0
  238. package/src/cloudflare/durable-object-namespace.ts +38 -27
  239. package/src/cloudflare/hyperdrive.ts +19 -5
  240. package/src/cloudflare/images.ts +32 -17
  241. package/src/cloudflare/index.ts +1 -3
  242. package/src/cloudflare/miniflare/miniflare.ts +10 -7
  243. package/src/cloudflare/miniflare/{mixed-mode.ts → remote-binding-proxy.ts} +25 -75
  244. package/src/cloudflare/oauth.ts +300 -0
  245. package/src/cloudflare/state.ts +35 -0
  246. package/src/cloudflare/user.ts +40 -73
  247. package/src/cloudflare/vectorize-index.ts +7 -2
  248. package/src/cloudflare/version-metadata.ts +19 -2
  249. package/src/cloudflare/vite.ts +2 -1
  250. package/src/cloudflare/worker-metadata.ts +18 -43
  251. package/src/cloudflare/worker-subdomain.ts +43 -37
  252. package/src/cloudflare/{worker/tail.ts → worker-tail.ts} +13 -10
  253. package/src/cloudflare/worker.ts +74 -23
  254. package/src/cloudflare/workflow.ts +38 -19
  255. package/src/context.ts +2 -2
  256. package/src/docker/api.ts +33 -8
  257. package/src/fs/file-system-state-store.ts +5 -148
  258. package/src/fs/index.ts +0 -1
  259. package/src/llms.cloudflare.txt +6 -5
  260. package/src/planetscale/password.ts +1 -1
  261. package/src/scope.ts +3 -6
  262. package/src/state/cloudflare-state-store.ts +170 -0
  263. package/src/state/d1-state-store.ts +102 -0
  264. package/src/{cloudflare/do-state-store/store.ts → state/dofs-state-store/index.ts} +20 -13
  265. package/src/{cloudflare/do-state-store/internal.ts → state/dofs-state-store/store.ts} +9 -27
  266. package/src/state/file-system-state-store.ts +149 -0
  267. package/src/state/index.ts +6 -0
  268. package/src/state/migrations.ts +9 -0
  269. package/src/state/operations.ts +152 -0
  270. package/src/state/proxy.ts +132 -0
  271. package/src/{cloudflare → state}/r2-rest-state-store.ts +11 -8
  272. package/src/{sqlite → state}/sqlite-state-store.ts +18 -10
  273. package/src/test/vitest.ts +8 -6
  274. package/src/util/detect-node-runtime.ts +12 -0
  275. package/src/util/detect-package-manager.ts +19 -6
  276. package/src/{cloudflare/miniflare → util}/http-server.ts +1 -1
  277. package/src/util/memoize.ts +2 -8
  278. package/src/util/neverthrow.ts +64 -0
  279. package/templates/astro/package.json +3 -3
  280. package/templates/nuxt/package.json +3 -3
  281. package/templates/react-router/package.json +3 -3
  282. package/templates/rwsdk/alchemy.run.ts +1 -1
  283. package/templates/rwsdk/package.json +3 -3
  284. package/templates/sveltekit/package.json +3 -3
  285. package/templates/tanstack-start/package.json +3 -3
  286. package/templates/typescript/package.json +3 -2
  287. package/templates/typescript/tsconfig.json +1 -0
  288. package/templates/vite/package.json +3 -3
  289. package/workers/cloudflare-state-store.js +5638 -0
  290. package/workers/cloudflare-state-store.ts +108 -0
  291. package/workers/{do-state-store.js → dofs-state-store.js} +4 -4
  292. package/workers/{do-state-store.ts → dofs-state-store.ts} +12 -8
  293. package/workers/{mixed-mode-proxy-worker.js → remote-binding-proxy.js} +3 -3
  294. package/workers/remote-binding-proxy.ts +45 -0
  295. package/lib/cloudflare/d1-state-store.d.ts +0 -10
  296. package/lib/cloudflare/d1-state-store.d.ts.map +0 -1
  297. package/lib/cloudflare/d1-state-store.js.map +0 -1
  298. package/lib/cloudflare/do-state-store/index.d.ts +0 -2
  299. package/lib/cloudflare/do-state-store/index.d.ts.map +0 -1
  300. package/lib/cloudflare/do-state-store/index.js +0 -2
  301. package/lib/cloudflare/do-state-store/index.js.map +0 -1
  302. package/lib/cloudflare/do-state-store/internal.d.ts.map +0 -1
  303. package/lib/cloudflare/do-state-store/internal.js.map +0 -1
  304. package/lib/cloudflare/do-state-store/store.d.ts.map +0 -1
  305. package/lib/cloudflare/do-state-store/store.js.map +0 -1
  306. package/lib/cloudflare/do-state-store/types.d.ts.map +0 -1
  307. package/lib/cloudflare/do-state-store/types.js +0 -1
  308. package/lib/cloudflare/do-state-store/types.js.map +0 -1
  309. package/lib/cloudflare/miniflare/http-server.d.ts.map +0 -1
  310. package/lib/cloudflare/miniflare/http-server.js.map +0 -1
  311. package/lib/cloudflare/miniflare/mixed-mode.d.ts.map +0 -1
  312. package/lib/cloudflare/miniflare/mixed-mode.js.map +0 -1
  313. package/lib/cloudflare/r2-rest-state-store.d.ts.map +0 -1
  314. package/lib/cloudflare/r2-rest-state-store.js.map +0 -1
  315. package/lib/cloudflare/types.d.ts.map +0 -1
  316. package/lib/cloudflare/types.js +0 -19
  317. package/lib/cloudflare/types.js.map +0 -1
  318. package/lib/cloudflare/worker/shared.d.ts +0 -4
  319. package/lib/cloudflare/worker/shared.d.ts.map +0 -1
  320. package/lib/cloudflare/worker/shared.js +0 -21
  321. package/lib/cloudflare/worker/shared.js.map +0 -1
  322. package/lib/cloudflare/worker/tail.d.ts.map +0 -1
  323. package/lib/cloudflare/worker/tail.js.map +0 -1
  324. package/lib/sqlite/base-sqlite-state-store.d.ts +0 -31
  325. package/lib/sqlite/base-sqlite-state-store.d.ts.map +0 -1
  326. package/lib/sqlite/base-sqlite-state-store.js +0 -124
  327. package/lib/sqlite/base-sqlite-state-store.js.map +0 -1
  328. package/lib/sqlite/index.d.ts +0 -2
  329. package/lib/sqlite/index.d.ts.map +0 -1
  330. package/lib/sqlite/index.js +0 -2
  331. package/lib/sqlite/index.js.map +0 -1
  332. package/lib/sqlite/schema.d.ts.map +0 -1
  333. package/lib/sqlite/schema.js.map +0 -1
  334. package/lib/sqlite/sqlite-state-store.d.ts.map +0 -1
  335. package/lib/sqlite/sqlite-state-store.js.map +0 -1
  336. package/src/cloudflare/d1-state-store.ts +0 -105
  337. package/src/cloudflare/do-state-store/index.ts +0 -1
  338. package/src/cloudflare/worker/shared.ts +0 -30
  339. package/src/sqlite/base-sqlite-state-store.ts +0 -199
  340. package/src/sqlite/index.ts +0 -1
  341. package/templates/typescript/src/worker.js +0 -5
  342. package/workers/mixed-mode-proxy-worker.ts +0 -46
  343. /package/drizzle/{0000_busy_mauler.sql → default/0000_busy_mauler.sql} +0 -0
  344. /package/drizzle/{0001_concerned_iceman.sql → default/0001_concerned_iceman.sql} +0 -0
  345. /package/drizzle/{meta → default/meta}/0000_snapshot.json +0 -0
  346. /package/drizzle/{meta → default/meta}/0001_snapshot.json +0 -0
  347. /package/drizzle/{meta → default/meta}/_journal.json +0 -0
  348. /package/lib/{sqlite → state}/schema.d.ts +0 -0
  349. /package/lib/{sqlite → state}/schema.js +0 -0
  350. /package/lib/{cloudflare/miniflare → util}/http-server.d.ts +0 -0
  351. /package/src/{cloudflare/do-state-store/types.ts → state/dofs-state-store/api.ts} +0 -0
  352. /package/src/{sqlite → state}/schema.ts +0 -0
@@ -0,0 +1,300 @@
1
+ import kleur from "kleur";
2
+ import { err, ok, okAsync, ResultAsync } from "neverthrow";
3
+ import assert from "node:assert";
4
+ import crypto from "node:crypto";
5
+ import fs from "node:fs/promises";
6
+ import os from "node:os";
7
+ import path from "node:path";
8
+ import open from "open";
9
+ import { HTTPServer } from "../util/http-server.ts";
10
+ import { memoize } from "../util/memoize.ts";
11
+ import {
12
+ detached,
13
+ ensure,
14
+ fetchJSON,
15
+ HTTPError,
16
+ singleFlight,
17
+ } from "../util/neverthrow.ts";
18
+ import { createXdgAppPaths } from "../util/xdg-paths.ts";
19
+
20
+ const CLIENT_ID = "54d11594-84e4-41aa-b438-e81b8fa78ee7";
21
+ const REDIRECT_URI = "http://localhost:8976/oauth/callback";
22
+ export const DEFAULT_SCOPES = [
23
+ "account:read",
24
+ "user:read",
25
+ "workers:write",
26
+ "workers_kv:write",
27
+ "workers_routes:write",
28
+ "workers_scripts:write",
29
+ "workers_tail:read",
30
+ "d1:write",
31
+ "pages:write",
32
+ "zone:read",
33
+ "ssl_certs:write",
34
+ "ai:write",
35
+ "queues:write",
36
+ "pipelines:write",
37
+ "secrets_store:write",
38
+ "containers:write",
39
+ "cloudchamber:write",
40
+ ];
41
+ const ALCHEMY_BASE_URL = "https://alchemy.run";
42
+
43
+ interface WranglerConfig {
44
+ oauth_token: string;
45
+ refresh_token: string;
46
+ expiration_time: Date;
47
+ scopes: string[];
48
+ }
49
+
50
+ let cached: WranglerConfig | undefined;
51
+
52
+ export const getRefreshedWranglerConfig = singleFlight(() =>
53
+ readWranglerConfig()
54
+ .andThen((config) => {
55
+ if (config.expiration_time.getTime() > Date.now() + 10 * 1000) {
56
+ return ok(config);
57
+ }
58
+ return fetchToken({
59
+ grant_type: "refresh_token",
60
+ refresh_token: config.refresh_token,
61
+ client_id: CLIENT_ID,
62
+ });
63
+ })
64
+ .orElse((error) => {
65
+ if (isInteractive()) {
66
+ return wranglerLogin();
67
+ }
68
+ return err(error);
69
+ }),
70
+ );
71
+
72
+ const getWranglerConfigPath = memoize(async () => {
73
+ const xdgConfigDir = createXdgAppPaths(".wrangler").config();
74
+ const legacyConfigDir = path.join(os.homedir(), ".wrangler");
75
+ const configDir = (await fs
76
+ .stat(legacyConfigDir)
77
+ .then((stat) => stat.isDirectory())
78
+ .catch(() => false))
79
+ ? legacyConfigDir
80
+ : xdgConfigDir;
81
+ return path.join(configDir, "config", "default.toml");
82
+ });
83
+
84
+ const readWranglerConfig = () => {
85
+ if (cached) {
86
+ return okAsync(cached);
87
+ }
88
+ return ResultAsync.fromPromise(
89
+ getWranglerConfigPath().then(async (path): Promise<WranglerConfig> => {
90
+ const text = await fs.readFile(path, "utf-8");
91
+ const toml = await import("@iarna/toml");
92
+ const config = toml.parse(text.replace(/\r\n/g, "\n"));
93
+ assert(typeof config.oauth_token === "string");
94
+ assert(typeof config.refresh_token === "string");
95
+ assert(config.expiration_time instanceof Date);
96
+ assert(Array.isArray(config.scopes));
97
+ return {
98
+ oauth_token: config.oauth_token,
99
+ refresh_token: config.refresh_token,
100
+ expiration_time: new Date(config.expiration_time),
101
+ scopes: config.scopes as string[],
102
+ };
103
+ }),
104
+ () => new Error("Cloudflare credentials are missing or invalid."),
105
+ );
106
+ };
107
+
108
+ const writeWranglerConfig = (config: WranglerConfig) => {
109
+ cached = config;
110
+ return ResultAsync.fromSafePromise(getWranglerConfigPath()).map(
111
+ async (configPath) => {
112
+ const toml = await import("@iarna/toml");
113
+ await fs.writeFile(
114
+ configPath,
115
+ toml.stringify({
116
+ oauth_token: config.oauth_token,
117
+ refresh_token: config.refresh_token,
118
+ expiration_time: config.expiration_time,
119
+ scopes: config.scopes,
120
+ }),
121
+ );
122
+ },
123
+ );
124
+ };
125
+
126
+ export const wranglerLogin = (
127
+ scopes: string[] = DEFAULT_SCOPES,
128
+ log: (message: string) => void = console.log, // using console.log instead of logger.log to avoid bundling hell with CLI
129
+ ) => {
130
+ const challenge = generateAuthorizationURL(scopes);
131
+ log(
132
+ [
133
+ "Opening browser to authorize with Cloudflare...",
134
+ "",
135
+ kleur.gray(
136
+ "If you are not automatically redirected, please open the following URL in your browser:",
137
+ ),
138
+ challenge.url,
139
+ ].join("\n"),
140
+ );
141
+ open(challenge.url);
142
+ const { result, ok, err } = detached<WranglerConfig, OAuthError>();
143
+ const renderResponse = async (type: "success" | "error") => {
144
+ return Response.redirect(`${ALCHEMY_BASE_URL}/auth/${type}`);
145
+ };
146
+ const server = new HTTPServer({
147
+ port: 8976,
148
+ fetch: async (request) => {
149
+ const url = new URL(request.url);
150
+ if (url.pathname !== "/oauth/callback") {
151
+ return new Response("Not found", { status: 404 });
152
+ }
153
+ const error = url.searchParams.get("error");
154
+ const error_description = url.searchParams.get("error_description");
155
+ const code = url.searchParams.get("code");
156
+ const state = url.searchParams.get("state");
157
+ if (error) {
158
+ err(
159
+ new OAuthError({
160
+ error,
161
+ error_description: error_description ?? "Unknown error",
162
+ status_code: 400,
163
+ }),
164
+ );
165
+ return renderResponse("error");
166
+ }
167
+ if (!code || !state || state !== challenge.state) {
168
+ err(
169
+ new OAuthError({
170
+ error: "invalid_request",
171
+ error_description: "Invalid request",
172
+ status_code: 400,
173
+ }),
174
+ );
175
+ return renderResponse("error");
176
+ }
177
+ const tokens = await fetchToken({
178
+ grant_type: "authorization_code",
179
+ code,
180
+ code_verifier: challenge.verifier,
181
+ client_id: CLIENT_ID,
182
+ redirect_uri: REDIRECT_URI,
183
+ });
184
+ if (tokens.isErr()) {
185
+ err(tokens.error);
186
+ return renderResponse("error");
187
+ }
188
+ ok(tokens.value);
189
+ return renderResponse("success");
190
+ },
191
+ });
192
+ const timeout = setTimeout(
193
+ () => {
194
+ err(
195
+ new OAuthError({
196
+ error: "timeout",
197
+ error_description: "Authorization timed out after 5 minutes",
198
+ status_code: 408,
199
+ }),
200
+ );
201
+ },
202
+ 1000 * 60 * 5,
203
+ );
204
+ return ensure(result, () => {
205
+ clearTimeout(timeout);
206
+ void server.stop();
207
+ });
208
+ };
209
+
210
+ const generateAuthorizationURL = (scopes: string[]) => {
211
+ const state = crypto.randomBytes(32).toString("base64url");
212
+ const verifier = crypto.randomBytes(96).toString("base64url");
213
+ const challenge = crypto
214
+ .createHash("sha256")
215
+ .update(verifier)
216
+ .digest("base64url");
217
+ const url = new URL("https://dash.cloudflare.com/oauth2/auth");
218
+ url.search = new URLSearchParams({
219
+ response_type: "code",
220
+ client_id: CLIENT_ID,
221
+ redirect_uri: "http://localhost:8976/oauth/callback",
222
+ scope: [...scopes, "offline_access"].join(" "),
223
+ state,
224
+ code_challenge: challenge,
225
+ code_challenge_method: "S256",
226
+ }).toString();
227
+ return { url: url.toString(), state, verifier };
228
+ };
229
+
230
+ interface OAuthTokens {
231
+ access_token: string;
232
+ refresh_token: string;
233
+ expires_in: number;
234
+ scope: string;
235
+ }
236
+
237
+ interface OAuthErrorParams {
238
+ error: string;
239
+ error_description: string;
240
+ error_verbose?: string;
241
+ error_hint?: string;
242
+ status_code: number;
243
+ }
244
+
245
+ class OAuthError extends Error {
246
+ constructor(params: OAuthErrorParams) {
247
+ super(
248
+ `Cloudflare authentication failed with error ${params.error} (${params.status_code}): ${params.error_description}`,
249
+ );
250
+ }
251
+ }
252
+
253
+ const fetchToken = (
254
+ body:
255
+ | {
256
+ grant_type: "refresh_token";
257
+ refresh_token: string;
258
+ client_id: string;
259
+ }
260
+ | {
261
+ grant_type: "authorization_code";
262
+ code: string;
263
+ code_verifier: string;
264
+ client_id: string;
265
+ redirect_uri: string;
266
+ },
267
+ ) => {
268
+ return fetchJSON<OAuthTokens, OAuthErrorParams>(
269
+ "https://dash.cloudflare.com/oauth2/token",
270
+ {
271
+ method: "POST",
272
+ headers: {
273
+ "Content-Type": "application/x-www-form-urlencoded",
274
+ },
275
+ body: new URLSearchParams(body),
276
+ },
277
+ )
278
+ .map(
279
+ (tokens): WranglerConfig => ({
280
+ oauth_token: tokens.access_token,
281
+ refresh_token: tokens.refresh_token,
282
+ expiration_time: new Date(Date.now() + tokens.expires_in * 1000),
283
+ scopes: tokens.scope.split(" "),
284
+ }),
285
+ )
286
+ .mapErr((error) =>
287
+ error instanceof HTTPError
288
+ ? new OAuthError({
289
+ error: "http_error",
290
+ error_description: error.message,
291
+ status_code: error.status,
292
+ })
293
+ : new OAuthError(error),
294
+ )
295
+ .andTee((config) => writeWranglerConfig(config));
296
+ };
297
+
298
+ const isInteractive = () => {
299
+ return process.stdin.isTTY && process.stdout.isTTY && !process.env.CI;
300
+ };
@@ -0,0 +1,35 @@
1
+ import {
2
+ D1StateStore as _D1StateStore,
3
+ DOFSStateStore as _DOFSStateStore,
4
+ R2RestStateStore as _R2RestStateStore,
5
+ } from "../state/index.ts";
6
+
7
+ /**
8
+ * @deprecated Use `R2RestStateStore` from `alchemy/state` instead.
9
+ */
10
+ export const R2RestStateStore = _R2RestStateStore;
11
+
12
+ /**
13
+ * @deprecated Use `R2RestStateStore` from `alchemy/state` instead.
14
+ */
15
+ export type R2RestStateStore = _R2RestStateStore;
16
+
17
+ /**
18
+ * @deprecated This state store is no longer recommended for production use. Please use {@link https://alchemy.run/guides/cloudflare-state-store/ CloudflareStateStore} from `alchemy/state` instead.
19
+ */
20
+ export const DOStateStore = _DOFSStateStore;
21
+
22
+ /**
23
+ * @deprecated Use `DOStateStore` from `alchemy/state` instead.
24
+ */
25
+ export type DOStateStore = _DOFSStateStore;
26
+
27
+ /**
28
+ * @deprecated Use `D1StateStore` from `alchemy/state` instead.
29
+ */
30
+ export const D1StateStore = _D1StateStore;
31
+
32
+ /**
33
+ * @deprecated Use `D1StateStore` from `alchemy/state` instead.
34
+ */
35
+ export type D1StateStore = _D1StateStore;
@@ -1,6 +1,7 @@
1
1
  import type { Secret } from "../secret.ts";
2
2
  import { logger } from "../util/logger.ts";
3
- import { CloudflareApiError } from "./api-error.ts";
3
+ import { memoize } from "../util/memoize.ts";
4
+ import { extractCloudflareResult } from "./api-response.ts";
4
5
  import {
5
6
  getCloudflareAuthHeaders,
6
7
  type CloudflareAuthOptions,
@@ -55,82 +56,48 @@ export interface CloudflareOrganization {
55
56
  roles: string[];
56
57
  }
57
58
 
58
- const accountCache: Record<string, CloudflareAccount[]> = {};
59
-
60
- export async function getCloudflareAccounts(
61
- options: CloudflareAuthOptions,
62
- ): Promise<CloudflareAccount[]> {
63
- const cacheKey = JSON.stringify({
64
- apiKey: options.apiKey?.unencrypted,
65
- apiToken: options.apiToken?.unencrypted,
66
- email: options.email,
67
- });
68
- if (accountCache[cacheKey]) {
69
- return accountCache[cacheKey];
70
- }
71
-
72
- const headers = await getCloudflareAuthHeaders(options);
73
- const accounts = await fetch(
74
- "https://api.cloudflare.com/client/v4/accounts",
75
- {
76
- headers,
77
- },
78
- );
79
-
80
- if (accounts.ok) {
81
- return (accountCache[cacheKey] ??= ((await accounts.json()) as any).result);
82
- } else {
83
- throw new CloudflareApiError(
84
- `Failed to get accounts for authorized user, please make sure you're authenticated (see: https://alchemy.run/guides/cloudflare/) or explicitly set the Cloudflare Account ID (see: https://alchemy.run/guides/cloudflare/#account-id)`,
85
- accounts,
59
+ export const getCloudflareAccountId = memoize(
60
+ async (options: CloudflareAuthOptions): Promise<string> => {
61
+ const headers = await getCloudflareAuthHeaders(options);
62
+ const accounts = await extractCloudflareResult<CloudflareAccount[]>(
63
+ "get accounts for authorized user",
64
+ fetch("https://api.cloudflare.com/client/v4/accounts", {
65
+ headers,
66
+ }),
86
67
  );
87
- }
88
- }
89
-
90
- const emailCache: Record<string, string> = {};
91
-
92
- export async function getUserEmailFromApiKey(apiKey: string): Promise<string> {
93
- if (emailCache[apiKey]) {
94
- return emailCache[apiKey];
95
- }
96
-
97
- try {
98
- const baseUrl = "https://api.cloudflare.com/client/v4";
99
-
100
- // Call the /user endpoint to get user information
101
- const response = await fetch(`${baseUrl}/user`, {
102
- headers: {
103
- "Content-Type": "application/json",
104
- "X-Auth-Key": apiKey,
105
- },
106
- });
107
-
108
- if (!response.ok) {
68
+ if (!accounts[0]) {
109
69
  throw new Error(
110
- `Failed to get user information: ${response.status} ${response.statusText}`,
70
+ "No accounts found for authorized user. Please make sure you're authenticated (see: https://alchemy.run/guides/cloudflare/) or explicitly set the Cloudflare Account ID (see: https://alchemy.run/guides/cloudflare/#account-id)",
111
71
  );
112
72
  }
113
-
114
- const data = (await response.json()) as {
115
- success: boolean;
116
- result: {
117
- id: string;
118
- email: string;
119
- name: string;
120
- [key: string]: any;
121
- };
122
- };
123
-
124
- if (!data.success || !data.result || !data.result.email) {
125
- throw new Error("Cloudflare API did not return valid user information");
73
+ if (accounts.length > 1) {
74
+ logger.warnOnce(
75
+ [
76
+ "Multiple Cloudflare accounts found for authorized user:",
77
+ accounts.map((a, i) => `${i + 1}: ${a.name} (${a.id})`).join("\n"),
78
+ "The first account will be used by default. To use a different account, explicitly set the Cloudflare Account ID (see: https://alchemy.run/guides/cloudflare/#account-id)",
79
+ ].join("\n"),
80
+ );
126
81
  }
82
+ return accounts[0].id;
83
+ },
84
+ );
127
85
 
128
- emailCache[apiKey] = data.result.email;
129
- return data.result.email;
130
- } catch (error) {
131
- logger.error("Error retrieving email from Cloudflare API:", error);
132
- throw new Error(
133
- "Failed to automatically discover email for API Key authentication",
86
+ export const getUserEmailFromApiKey = memoize(
87
+ async (apiKey: string): Promise<string> => {
88
+ const result = await extractCloudflareResult<{
89
+ id: string;
90
+ name: string;
91
+ email: string;
92
+ }>(
93
+ "automatically discover email for API Key authentication",
94
+ fetch("https://api.cloudflare.com/client/v4/user", {
95
+ headers: {
96
+ "Content-Type": "application/json",
97
+ "X-Auth-Key": apiKey,
98
+ },
99
+ }),
134
100
  );
135
- }
136
- }
101
+ return result.email;
102
+ },
103
+ );
@@ -169,7 +169,7 @@ const _VectorizeIndex = Resource(
169
169
  if (
170
170
  props.adopt &&
171
171
  error instanceof CloudflareApiError &&
172
- error.message.includes("already exists")
172
+ error.message.includes("vectorize.index.duplicate_name")
173
173
  ) {
174
174
  logger.log(`Index ${indexName} already exists, adopting it`);
175
175
  // Find the existing index
@@ -321,7 +321,12 @@ export async function deleteIndex(
321
321
  `/accounts/${api.accountId}/vectorize/v2/indexes/${indexName}`,
322
322
  );
323
323
 
324
- if (!deleteResponse.ok && deleteResponse.status !== 404) {
324
+ if (
325
+ !deleteResponse.ok &&
326
+ // not 404 (Not Found) or 410 (Gone)
327
+ deleteResponse.status !== 404 &&
328
+ deleteResponse.status !== 410
329
+ ) {
325
330
  const errorData: any = await deleteResponse.json().catch(() => ({
326
331
  errors: [{ message: deleteResponse.statusText }],
327
332
  }));
@@ -1,6 +1,23 @@
1
1
  /**
2
+ * Type representing a version metadata binding.
2
3
  * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/
3
4
  */
4
- export class VersionMetadata {
5
- public readonly type = "version_metadata";
5
+ export type VersionMetadata = {
6
+ type: "version_metadata";
7
+ };
8
+
9
+ /**
10
+ * Creates a version metadata binding that provides access to version information at runtime.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const metadata = VersionMetadata();
15
+ * ```
16
+ *
17
+ * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/version-metadata/
18
+ */
19
+ export function VersionMetadata(): VersionMetadata {
20
+ return {
21
+ type: "version_metadata",
22
+ };
6
23
  }
@@ -18,12 +18,13 @@ export async function Vite<B extends Bindings>(
18
18
  props: ViteProps<B>,
19
19
  ): Promise<Vite<B>> {
20
20
  const defaultAssets = path.join("dist", "client");
21
- const packageManager = detectPackageManager();
21
+ const packageManager = await detectPackageManager();
22
22
  const devCommand = {
23
23
  npm: "npx vite dev",
24
24
  bun: "bun vite dev",
25
25
  pnpm: "pnpm vite dev",
26
26
  yarn: "yarn vite dev",
27
+ deno: "deno vite dev",
27
28
  }[packageManager];
28
29
  return Website(id, {
29
30
  ...props,
@@ -1,6 +1,7 @@
1
1
  import { assertNever } from "../util/assert-never.ts";
2
2
  import { logger } from "../util/logger.ts";
3
3
  import { memoize } from "../util/memoize.ts";
4
+ import { extractCloudflareResult } from "./api-response.ts";
4
5
  import type { CloudflareApi } from "./api.ts";
5
6
  import {
6
7
  Self,
@@ -200,7 +201,7 @@ export interface WorkerMetadata {
200
201
 
201
202
  export async function prepareWorkerMetadata(
202
203
  api: CloudflareApi,
203
- props: WorkerProps & {
204
+ props: Omit<WorkerProps, "entrypoint"> & {
204
205
  compatibilityDate: string;
205
206
  compatibilityFlags: string[];
206
207
  workerName: string;
@@ -210,6 +211,7 @@ export async function prepareWorkerMetadata(
210
211
  keepAssets?: boolean;
211
212
  assetConfig?: AssetsConfig;
212
213
  };
214
+ tags?: string[];
213
215
  unstable_cacheWorkerSettings?: boolean;
214
216
  },
215
217
  ): Promise<WorkerMetadata> {
@@ -226,7 +228,7 @@ export async function prepareWorkerMetadata(
226
228
  // we use Cloudflare Worker tags to store a mapping between Alchemy's stable identifier and the binding name
227
229
  // e.g.
228
230
  // {
229
- // BINDING_NAME: new DurableObjectNamespace("stable-id")
231
+ // BINDING_NAME: DurableObjectNamespace("stable-id")
230
232
  // }
231
233
  // will be stored as alchemy:do:stable-id:BINDING_NAME
232
234
  // TODO(sam): should we base64 encode to ensure no `:` collision risk?
@@ -323,6 +325,7 @@ export async function prepareWorkerMetadata(
323
325
  ),
324
326
  // encode the migraiton tag if there is one so we can avoid the failed PutWorker after adoption
325
327
  ...(newMigrationTag ? [`alchemy:migration-tag:${newMigrationTag}`] : []),
328
+ ...(props.tags ?? []),
326
329
  ],
327
330
  migrations: {
328
331
  old_tag: oldMigrationTag,
@@ -640,6 +643,7 @@ interface WorkerSettings {
640
643
  compatibility_date: string;
641
644
  compatibility_flags: string[];
642
645
  migrations: SingleStepMigration | MultiStepMigration;
646
+ tags: string[];
643
647
  [key: string]: any;
644
648
  }
645
649
 
@@ -648,51 +652,22 @@ const getWorkerSettingsWithCache = memoize(
648
652
  (api, workerName) => `${api.accountId}:${workerName}`,
649
653
  );
650
654
 
651
- async function getWorkerSettings(
655
+ export async function getWorkerSettings(
652
656
  api: CloudflareApi,
653
657
  workerName: string,
654
658
  ): Promise<WorkerSettings | undefined> {
655
659
  // Fetch the bindings for a worker by calling the Cloudflare API endpoint:
656
660
  // GET /accounts/:account_id/workers/scripts/:script_name/bindings
657
661
  // See: https://developers.cloudflare.com/api/resources/workers/subresources/scripts/subresources/script_and_version_settings/methods/get/
658
- const response = await api.get(
659
- `/accounts/${api.accountId}/workers/scripts/${workerName}/settings`,
660
- );
661
- if (response.status === 404) {
662
- return undefined;
663
- }
664
- if (!response.ok) {
665
- throw new Error(
666
- `Error getting worker bindings: ${response.status} ${response.statusText}`,
667
- );
668
- }
669
- // The result is an object with a "result" property containing the bindings array
670
- const { result, success, errors } = (await response.json()) as {
671
- result: {
672
- bindings: WorkerBindingSpec[];
673
- compatibility_date: string;
674
- compatibility_flags: string[];
675
- migrations: SingleStepMigration | MultiStepMigration;
676
- [key: string]: any;
677
- };
678
- success: boolean;
679
- errors: Array<{
680
- code: number;
681
- message: string;
682
- documentation_url: string;
683
- [key: string]: any;
684
- }>;
685
- messages: Array<{
686
- code: number;
687
- message: string;
688
- documentation_url: string;
689
- [key: string]: any;
690
- }>;
691
- };
692
- if (!success) {
693
- throw new Error(
694
- `Error getting worker bindings: ${response.status} ${response.statusText}\nErrors:\n${errors.map((e) => `- [${e.code}] ${e.message} (${e.documentation_url})`).join("\n")}`,
695
- );
696
- }
697
- return result;
662
+ return await extractCloudflareResult<WorkerSettings>(
663
+ `get worker settings for ${workerName}`,
664
+ api.get(
665
+ `/accounts/${api.accountId}/workers/scripts/${workerName}/settings`,
666
+ ),
667
+ ).catch((error) => {
668
+ if (error.status === 404) {
669
+ return undefined;
670
+ }
671
+ throw error;
672
+ });
698
673
  }