alchemy 0.44.3 → 0.45.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 (261) hide show
  1. package/bin/alchemy.mjs +0 -13
  2. package/bin/commands/create.ts +1 -10
  3. package/bin/services/vibe-rules.ts +0 -14
  4. package/lib/alchemy.d.ts +3 -3
  5. package/lib/alchemy.d.ts.map +1 -1
  6. package/lib/alchemy.js +5 -2
  7. package/lib/alchemy.js.map +1 -1
  8. package/lib/build-date.d.ts +1 -1
  9. package/lib/build-date.js +1 -1
  10. package/lib/cloudflare/auth.js +1 -1
  11. package/lib/cloudflare/auth.js.map +1 -1
  12. package/lib/cloudflare/bundle/esbuild.d.ts +16 -0
  13. package/lib/cloudflare/bundle/esbuild.d.ts.map +1 -0
  14. package/lib/cloudflare/bundle/esbuild.js +159 -0
  15. package/lib/cloudflare/bundle/esbuild.js.map +1 -0
  16. package/lib/cloudflare/bundle/fs.d.ts +18 -0
  17. package/lib/cloudflare/bundle/fs.d.ts.map +1 -0
  18. package/lib/cloudflare/bundle/fs.js +75 -0
  19. package/lib/cloudflare/bundle/fs.js.map +1 -0
  20. package/lib/cloudflare/bundle/index.d.ts +3 -0
  21. package/lib/cloudflare/bundle/index.d.ts.map +1 -0
  22. package/lib/cloudflare/bundle/index.js +2 -0
  23. package/lib/cloudflare/bundle/index.js.map +1 -0
  24. package/lib/cloudflare/bundle/inline.d.ts +12 -0
  25. package/lib/cloudflare/bundle/inline.d.ts.map +1 -0
  26. package/lib/cloudflare/bundle/inline.js +32 -0
  27. package/lib/cloudflare/bundle/inline.js.map +1 -0
  28. package/lib/cloudflare/bundle/normalize.d.ts +21 -0
  29. package/lib/cloudflare/bundle/normalize.d.ts.map +1 -0
  30. package/lib/cloudflare/bundle/normalize.js +49 -0
  31. package/lib/cloudflare/bundle/normalize.js.map +1 -0
  32. package/lib/cloudflare/bundle/{nodejs-import-warning-plugin.d.ts → plugin-compat-warning.d.ts} +2 -2
  33. package/lib/cloudflare/bundle/plugin-compat-warning.d.ts.map +1 -0
  34. package/lib/cloudflare/bundle/{nodejs-import-warning-plugin.js → plugin-compat-warning.js} +3 -4
  35. package/lib/cloudflare/bundle/plugin-compat-warning.js.map +1 -0
  36. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts +8 -0
  37. package/lib/cloudflare/bundle/plugin-hot-reload.d.ts.map +1 -0
  38. package/lib/cloudflare/bundle/plugin-hot-reload.js +19 -0
  39. package/lib/cloudflare/bundle/plugin-hot-reload.js.map +1 -0
  40. package/lib/cloudflare/bundle/{nodejs-compat.d.ts → plugin-hybrid-node-compat.d.ts} +2 -2
  41. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.d.ts.map +1 -0
  42. package/lib/cloudflare/bundle/{nodejs-compat.js → plugin-hybrid-node-compat.js} +10 -10
  43. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js.map +1 -0
  44. package/lib/cloudflare/bundle/plugin-wasm.d.ts +3 -0
  45. package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -0
  46. package/lib/cloudflare/bundle/plugin-wasm.js +55 -0
  47. package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -0
  48. package/lib/cloudflare/bundle/shared.d.ts +30 -0
  49. package/lib/cloudflare/bundle/shared.d.ts.map +1 -0
  50. package/lib/cloudflare/bundle/shared.js +34 -0
  51. package/lib/cloudflare/bundle/shared.js.map +1 -0
  52. package/lib/cloudflare/bundle/{nodejs-compat-mode.d.ts → validate-node-compat.d.ts} +8 -4
  53. package/lib/cloudflare/bundle/validate-node-compat.d.ts.map +1 -0
  54. package/lib/cloudflare/bundle/{nodejs-compat-mode.js → validate-node-compat.js} +27 -7
  55. package/lib/cloudflare/bundle/validate-node-compat.js.map +1 -0
  56. package/lib/cloudflare/container.d.ts +231 -0
  57. package/lib/cloudflare/container.d.ts.map +1 -1
  58. package/lib/cloudflare/container.js +7 -0
  59. package/lib/cloudflare/container.js.map +1 -1
  60. package/lib/cloudflare/d1-migrations.d.ts +1 -0
  61. package/lib/cloudflare/d1-migrations.d.ts.map +1 -1
  62. package/lib/cloudflare/d1-migrations.js +18 -6
  63. package/lib/cloudflare/d1-migrations.js.map +1 -1
  64. package/lib/cloudflare/d1-state-store.d.ts +10 -0
  65. package/lib/cloudflare/d1-state-store.d.ts.map +1 -0
  66. package/lib/cloudflare/d1-state-store.js +70 -0
  67. package/lib/cloudflare/d1-state-store.js.map +1 -0
  68. package/lib/cloudflare/do-state-store/internal.js +1 -1
  69. package/lib/cloudflare/do-state-store/internal.js.map +1 -1
  70. package/lib/cloudflare/do-state-store/store.js +1 -1
  71. package/lib/cloudflare/do-state-store/store.js.map +1 -1
  72. package/lib/cloudflare/index.d.ts +3 -2
  73. package/lib/cloudflare/index.d.ts.map +1 -1
  74. package/lib/cloudflare/index.js +3 -2
  75. package/lib/cloudflare/index.js.map +1 -1
  76. package/lib/cloudflare/local-dev-cloudflare-shim.d.ts.map +1 -0
  77. package/lib/cloudflare/{bundle/local-dev-cloudflare-shim.js → local-dev-cloudflare-shim.js} +1 -1
  78. package/lib/cloudflare/local-dev-cloudflare-shim.js.map +1 -0
  79. package/lib/cloudflare/miniflare/http-server.d.ts.map +1 -0
  80. package/lib/cloudflare/miniflare/http-server.js.map +1 -0
  81. package/lib/cloudflare/{worker → miniflare}/miniflare-worker-options.d.ts +4 -3
  82. package/lib/cloudflare/miniflare/miniflare-worker-options.d.ts.map +1 -0
  83. package/lib/cloudflare/{worker → miniflare}/miniflare-worker-options.js +6 -3
  84. package/lib/cloudflare/miniflare/miniflare-worker-options.js.map +1 -0
  85. package/lib/cloudflare/miniflare/miniflare.d.ts.map +1 -0
  86. package/lib/cloudflare/{worker → miniflare}/miniflare.js +1 -1
  87. package/lib/cloudflare/miniflare/miniflare.js.map +1 -0
  88. package/lib/cloudflare/miniflare/mixed-mode.d.ts.map +1 -0
  89. package/lib/cloudflare/{worker → miniflare}/mixed-mode.js +1 -2
  90. package/lib/cloudflare/miniflare/mixed-mode.js.map +1 -0
  91. package/lib/cloudflare/user.js +1 -1
  92. package/lib/cloudflare/user.js.map +1 -1
  93. package/lib/cloudflare/worker/shared.d.ts +4 -0
  94. package/lib/cloudflare/worker/shared.d.ts.map +1 -0
  95. package/lib/cloudflare/worker/{get-worker-template.js → shared.js} +10 -1
  96. package/lib/cloudflare/worker/shared.js.map +1 -0
  97. package/lib/cloudflare/worker/tail.d.ts +18 -0
  98. package/lib/cloudflare/worker/tail.d.ts.map +1 -0
  99. package/lib/cloudflare/worker/tail.js +140 -0
  100. package/lib/cloudflare/worker/tail.js.map +1 -0
  101. package/lib/cloudflare/worker-metadata.d.ts +6 -2
  102. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  103. package/lib/cloudflare/worker-metadata.js +7 -23
  104. package/lib/cloudflare/worker-metadata.js.map +1 -1
  105. package/lib/cloudflare/worker-stub.d.ts.map +1 -1
  106. package/lib/cloudflare/worker-stub.js +8 -4
  107. package/lib/cloudflare/worker-stub.js.map +1 -1
  108. package/lib/cloudflare/worker-subdomain.d.ts +31 -0
  109. package/lib/cloudflare/worker-subdomain.d.ts.map +1 -0
  110. package/lib/cloudflare/worker-subdomain.js +55 -0
  111. package/lib/cloudflare/worker-subdomain.js.map +1 -0
  112. package/lib/cloudflare/worker.d.ts +30 -28
  113. package/lib/cloudflare/worker.d.ts.map +1 -1
  114. package/lib/cloudflare/worker.js +641 -554
  115. package/lib/cloudflare/worker.js.map +1 -1
  116. package/lib/fs/file-system-state-store.js +1 -1
  117. package/lib/fs/file-system-state-store.js.map +1 -1
  118. package/lib/scope.d.ts +2 -2
  119. package/lib/scope.d.ts.map +1 -1
  120. package/lib/scope.js +23 -8
  121. package/lib/scope.js.map +1 -1
  122. package/lib/serde.d.ts +1 -1
  123. package/lib/serde.d.ts.map +1 -1
  124. package/lib/serde.js +4 -4
  125. package/lib/serde.js.map +1 -1
  126. package/lib/sqlite/base-sqlite-state-store.d.ts +31 -0
  127. package/lib/sqlite/base-sqlite-state-store.d.ts.map +1 -0
  128. package/lib/sqlite/base-sqlite-state-store.js +124 -0
  129. package/lib/sqlite/base-sqlite-state-store.js.map +1 -0
  130. package/lib/sqlite/index.d.ts +2 -0
  131. package/lib/sqlite/index.d.ts.map +1 -0
  132. package/lib/sqlite/index.js +2 -0
  133. package/lib/sqlite/index.js.map +1 -0
  134. package/lib/sqlite/schema.d.ts +198 -0
  135. package/lib/sqlite/schema.d.ts.map +1 -0
  136. package/lib/sqlite/schema.js +23 -0
  137. package/lib/sqlite/schema.js.map +1 -0
  138. package/lib/sqlite/sqlite-state-store.d.ts +60 -0
  139. package/lib/sqlite/sqlite-state-store.d.ts.map +1 -0
  140. package/lib/sqlite/sqlite-state-store.js +83 -0
  141. package/lib/sqlite/sqlite-state-store.js.map +1 -0
  142. package/lib/test/bun.d.ts.map +1 -1
  143. package/lib/test/bun.js +0 -6
  144. package/lib/test/bun.js.map +1 -1
  145. package/lib/test/vitest.d.ts.map +1 -1
  146. package/lib/test/vitest.js +19 -5
  147. package/lib/test/vitest.js.map +1 -1
  148. package/lib/util/memoize.d.ts +1 -1
  149. package/lib/util/memoize.d.ts.map +1 -1
  150. package/lib/util/memoize.js +11 -1
  151. package/lib/util/memoize.js.map +1 -1
  152. package/package.json +12 -3
  153. package/src/alchemy.ts +8 -6
  154. package/src/build-date.ts +1 -1
  155. package/src/cloudflare/auth.ts +1 -1
  156. package/src/cloudflare/bundle/esbuild.ts +194 -0
  157. package/src/cloudflare/bundle/fs.ts +98 -0
  158. package/src/cloudflare/bundle/index.ts +6 -0
  159. package/src/cloudflare/bundle/inline.ts +42 -0
  160. package/src/cloudflare/bundle/normalize.ts +76 -0
  161. package/src/cloudflare/bundle/{nodejs-import-warning-plugin.ts → plugin-compat-warning.ts} +4 -3
  162. package/src/cloudflare/bundle/plugin-hot-reload.ts +27 -0
  163. package/src/cloudflare/bundle/{nodejs-compat.ts → plugin-hybrid-node-compat.ts} +10 -10
  164. package/src/cloudflare/bundle/plugin-wasm.ts +62 -0
  165. package/src/cloudflare/bundle/shared.ts +73 -0
  166. package/src/cloudflare/bundle/{nodejs-compat-mode.ts → validate-node-compat.ts} +41 -16
  167. package/src/cloudflare/container.ts +296 -0
  168. package/src/cloudflare/d1-migrations.ts +29 -16
  169. package/src/cloudflare/d1-state-store.ts +105 -0
  170. package/src/cloudflare/do-state-store/internal.ts +1 -1
  171. package/src/cloudflare/do-state-store/store.ts +1 -1
  172. package/src/cloudflare/index.ts +3 -2
  173. package/src/cloudflare/{bundle/local-dev-cloudflare-shim.ts → local-dev-cloudflare-shim.ts} +1 -1
  174. package/src/cloudflare/{worker → miniflare}/miniflare-worker-options.ts +14 -6
  175. package/src/cloudflare/{worker → miniflare}/miniflare.ts +1 -1
  176. package/src/cloudflare/{worker → miniflare}/mixed-mode.ts +1 -2
  177. package/src/cloudflare/user.ts +1 -1
  178. package/src/cloudflare/worker/{subdomain.ts → shared.ts} +14 -0
  179. package/src/cloudflare/worker/tail.ts +287 -0
  180. package/src/cloudflare/worker-metadata.ts +18 -26
  181. package/src/cloudflare/worker-stub.ts +9 -4
  182. package/src/cloudflare/worker-subdomain.ts +123 -0
  183. package/src/cloudflare/worker.ts +863 -734
  184. package/src/fs/file-system-state-store.ts +1 -1
  185. package/src/scope.ts +30 -11
  186. package/src/serde.ts +4 -4
  187. package/src/sqlite/base-sqlite-state-store.ts +199 -0
  188. package/src/sqlite/index.ts +1 -0
  189. package/src/sqlite/schema.ts +37 -0
  190. package/src/sqlite/sqlite-state-store.ts +174 -0
  191. package/src/test/bun.ts +0 -8
  192. package/src/test/vitest.ts +19 -8
  193. package/src/util/memoize.ts +13 -1
  194. package/templates/astro/types/env.d.ts +1 -1
  195. package/templates/nuxt/types/env.d.ts +1 -1
  196. package/templates/react-router/types/env.d.ts +1 -1
  197. package/templates/rwsdk/types/env.d.ts +1 -1
  198. package/templates/sveltekit/types/env.d.ts +1 -1
  199. package/templates/tanstack-start/types/env.d.ts +1 -1
  200. package/templates/typescript/types/env.d.ts +1 -1
  201. package/templates/vite/types/env.d.ts +1 -1
  202. package/lib/cloudflare/bundle/alias-plugin.d.ts +0 -6
  203. package/lib/cloudflare/bundle/alias-plugin.d.ts.map +0 -1
  204. package/lib/cloudflare/bundle/alias-plugin.js +0 -36
  205. package/lib/cloudflare/bundle/alias-plugin.js.map +0 -1
  206. package/lib/cloudflare/bundle/build-failures.d.ts +0 -12
  207. package/lib/cloudflare/bundle/build-failures.d.ts.map +0 -1
  208. package/lib/cloudflare/bundle/build-failures.js +0 -45
  209. package/lib/cloudflare/bundle/build-failures.js.map +0 -1
  210. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts +0 -26
  211. package/lib/cloudflare/bundle/bundle-worker-dev.d.ts.map +0 -1
  212. package/lib/cloudflare/bundle/bundle-worker-dev.js +0 -88
  213. package/lib/cloudflare/bundle/bundle-worker-dev.js.map +0 -1
  214. package/lib/cloudflare/bundle/bundle-worker.d.ts +0 -13
  215. package/lib/cloudflare/bundle/bundle-worker.d.ts.map +0 -1
  216. package/lib/cloudflare/bundle/bundle-worker.js +0 -98
  217. package/lib/cloudflare/bundle/bundle-worker.js.map +0 -1
  218. package/lib/cloudflare/bundle/external.d.ts +0 -4
  219. package/lib/cloudflare/bundle/external.d.ts.map +0 -1
  220. package/lib/cloudflare/bundle/external.js +0 -40
  221. package/lib/cloudflare/bundle/external.js.map +0 -1
  222. package/lib/cloudflare/bundle/local-dev-cloudflare-shim.d.ts.map +0 -1
  223. package/lib/cloudflare/bundle/local-dev-cloudflare-shim.js.map +0 -1
  224. package/lib/cloudflare/bundle/nodejs-compat-mode.d.ts.map +0 -1
  225. package/lib/cloudflare/bundle/nodejs-compat-mode.js.map +0 -1
  226. package/lib/cloudflare/bundle/nodejs-compat.d.ts.map +0 -1
  227. package/lib/cloudflare/bundle/nodejs-compat.js.map +0 -1
  228. package/lib/cloudflare/bundle/nodejs-import-warning-plugin.d.ts.map +0 -1
  229. package/lib/cloudflare/bundle/nodejs-import-warning-plugin.js.map +0 -1
  230. package/lib/cloudflare/bundle/wasm-plugin.d.ts +0 -3
  231. package/lib/cloudflare/bundle/wasm-plugin.d.ts.map +0 -1
  232. package/lib/cloudflare/bundle/wasm-plugin.js +0 -53
  233. package/lib/cloudflare/bundle/wasm-plugin.js.map +0 -1
  234. package/lib/cloudflare/worker/get-worker-template.d.ts +0 -2
  235. package/lib/cloudflare/worker/get-worker-template.d.ts.map +0 -1
  236. package/lib/cloudflare/worker/get-worker-template.js.map +0 -1
  237. package/lib/cloudflare/worker/http-server.d.ts.map +0 -1
  238. package/lib/cloudflare/worker/http-server.js.map +0 -1
  239. package/lib/cloudflare/worker/miniflare-worker-options.d.ts.map +0 -1
  240. package/lib/cloudflare/worker/miniflare-worker-options.js.map +0 -1
  241. package/lib/cloudflare/worker/miniflare.d.ts.map +0 -1
  242. package/lib/cloudflare/worker/miniflare.js.map +0 -1
  243. package/lib/cloudflare/worker/mixed-mode.d.ts.map +0 -1
  244. package/lib/cloudflare/worker/mixed-mode.js.map +0 -1
  245. package/lib/cloudflare/worker/subdomain.d.ts +0 -3
  246. package/lib/cloudflare/worker/subdomain.d.ts.map +0 -1
  247. package/lib/cloudflare/worker/subdomain.js +0 -10
  248. package/lib/cloudflare/worker/subdomain.js.map +0 -1
  249. package/src/cloudflare/bundle/alias-plugin.ts +0 -48
  250. package/src/cloudflare/bundle/build-failures.ts +0 -55
  251. package/src/cloudflare/bundle/bundle-worker-dev.ts +0 -129
  252. package/src/cloudflare/bundle/bundle-worker.ts +0 -150
  253. package/src/cloudflare/bundle/external.ts +0 -41
  254. package/src/cloudflare/bundle/wasm-plugin.ts +0 -57
  255. package/src/cloudflare/worker/get-worker-template.ts +0 -14
  256. /package/lib/cloudflare/{bundle/local-dev-cloudflare-shim.d.ts → local-dev-cloudflare-shim.d.ts} +0 -0
  257. /package/lib/cloudflare/{worker → miniflare}/http-server.d.ts +0 -0
  258. /package/lib/cloudflare/{worker → miniflare}/http-server.js +0 -0
  259. /package/lib/cloudflare/{worker → miniflare}/miniflare.d.ts +0 -0
  260. /package/lib/cloudflare/{worker → miniflare}/mixed-mode.d.ts +0 -0
  261. /package/src/cloudflare/{worker → miniflare}/http-server.ts +0 -0
@@ -1,6 +1,8 @@
1
+ import kleur from "kleur";
1
2
  import { spawn } from "node:child_process";
2
3
  import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync, } from "node:fs";
3
- import path from "node:path";
4
+ import { isDeepStrictEqual } from "node:util";
5
+ import path from "pathe";
4
6
  import { BUILD_DATE } from "../build-date.js";
5
7
  import { InnerResourceScope, Resource, ResourceKind } from "../resource.js";
6
8
  import { getBindKey, tryGetBinding } from "../runtime/bind.js";
@@ -9,7 +11,6 @@ import { bootstrapPlugin } from "../runtime/plugin.js";
9
11
  import { Scope } from "../scope.js";
10
12
  import { Secret, secret } from "../secret.js";
11
13
  import { serializeScope } from "../serde.js";
12
- import { getContentType } from "../util/content-type.js";
13
14
  import { DeferredPromise } from "../util/deferred-promise.js";
14
15
  import { logger } from "../util/logger.js";
15
16
  import { withExponentialBackoff } from "../util/retry.js";
@@ -17,14 +18,15 @@ import { CloudflareApiError, handleApiError } from "./api-error.js";
17
18
  import { createCloudflareApi, } from "./api.js";
18
19
  import { Json, } from "./bindings.js";
19
20
  import { isBucket } from "./bucket.js";
20
- import { createWorkerDevContext } from "./bundle/bundle-worker-dev.js";
21
- import { bundleWorkerScript, } from "./bundle/bundle-worker.js";
21
+ import { normalizeWorkerBundle, } from "./bundle/index.js";
22
+ import { wrap } from "./bundle/normalize.js";
22
23
  import { ContainerApplication } from "./container.js";
23
24
  import { CustomDomain } from "./custom-domain.js";
24
25
  import { isD1Database } from "./d1-database.js";
25
26
  import { DurableObjectNamespace, isDurableObjectNamespace, } from "./durable-object-namespace.js";
26
27
  import { isQueueEventSource, } from "./event-source.js";
27
28
  import { isKVNamespace } from "./kv-namespace.js";
29
+ import { miniflareServer } from "./miniflare/miniflare.js";
28
30
  import { isPipeline } from "./pipeline.js";
29
31
  import { QueueConsumer, deleteQueueConsumer, listQueueConsumersForWorker, } from "./queue-consumer.js";
30
32
  import { isQueue } from "./queue.js";
@@ -33,8 +35,8 @@ import { isVectorizeIndex } from "./vectorize-index.js";
33
35
  import { uploadAssets } from "./worker-assets.js";
34
36
  import { bumpMigrationTagVersion, prepareWorkerMetadata, } from "./worker-metadata.js";
35
37
  import { WorkerStub, isWorkerStub } from "./worker-stub.js";
36
- import { miniflareServer } from "./worker/miniflare.js";
37
- import { getAccountSubdomain } from "./worker/subdomain.js";
38
+ import { WorkerSubdomain, disableWorkerSubdomain } from "./worker-subdomain.js";
39
+ import { createTail } from "./worker/tail.js";
38
40
  import { Workflow, isWorkflow, upsertWorkflow } from "./workflow.js";
39
41
  export function isWorker(resource) {
40
42
  return resource[ResourceKind] === "cloudflare::Worker";
@@ -224,174 +226,60 @@ export const DEFAULT_COMPATIBILITY_DATE = BUILD_DATE;
224
226
  export const _Worker = Resource("cloudflare::Worker", {
225
227
  alwaysUpdate: true,
226
228
  }, async function (id, props) {
227
- if (props.noBundle && !props.entrypoint) {
228
- throw new Error("entrypoint must be provided when noBundle is true");
229
- }
230
- // Use the provided name
231
- const workerName = props.name ?? id;
232
229
  if (props.projectRoot) {
233
230
  logger.warn("projectRoot is deprecated, use cwd instead");
234
231
  props.cwd = props.projectRoot;
235
232
  }
236
- const cwd = props.cwd ? path.resolve(props.cwd) : process.cwd();
233
+ const cwd = path.resolve(props.cwd ?? process.cwd());
237
234
  const relativeCwd = cwd === process.cwd() ? undefined : path.relative(process.cwd(), cwd);
235
+ const workerName = props.name ?? id;
238
236
  const compatibilityDate = props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE;
239
237
  const compatibilityFlags = props.compatibilityFlags ?? [];
240
- if (this.scope.dev && this.phase !== "delete" && props.dev !== false) {
241
- // Get current timestamp
242
- const now = Date.now();
243
- if (typeof props.dev === "object" && "command" in props.dev) {
244
- upsertDevCommand({
245
- id,
246
- command: props.dev.command,
247
- cwd: props.dev.cwd ?? cwd,
248
- env: props.env ?? {},
249
- });
250
- return this({
251
- type: "service",
252
- id,
253
- entrypoint: props.entrypoint,
254
- name: workerName,
255
- cwd: relativeCwd,
256
- compatibilityDate,
257
- compatibilityFlags,
258
- format: props.format || "esm", // Include format in the output
259
- bindings: props.bindings ?? {},
260
- env: props.env,
261
- observability: props.observability,
262
- createdAt: now,
263
- updatedAt: now,
264
- eventSources: props.eventSources,
265
- url: props.dev.url,
266
- dev: props.dev,
267
- // Include assets configuration in the output
268
- assets: props.assets,
269
- // Include cron triggers in the output
270
- crons: props.crons,
271
- // phantom property
272
- Env: undefined,
273
- });
274
- }
275
- const sharedOptions = {
276
- name: workerName,
277
- compatibilityDate,
278
- compatibilityFlags,
279
- bindings: props.bindings ?? {},
280
- port: typeof props.dev === "object" ? props.dev.port : undefined,
281
- };
238
+ const dispatchNamespace = typeof props.namespace === "string"
239
+ ? props.namespace
240
+ : props.namespace?.namespaceName;
241
+ const dev = normalizeDev(this, props.dev);
242
+ const [bundle, error] = wrap(() => normalizeWorkerBundle({
243
+ entrypoint: props.entrypoint,
244
+ script: props.script,
245
+ format: props.format,
246
+ noBundle: props.noBundle,
247
+ rules: "rules" in props ? props.rules : undefined,
248
+ bundle: props.bundle,
249
+ cwd,
250
+ compatibilityDate,
251
+ compatibilityFlags,
252
+ outdir: props.bundle?.outdir ??
253
+ path.join(process.cwd(), ".alchemy", ...this.scope.chain, id),
254
+ sourceMap: "sourceMap" in props ? props.sourceMap : undefined,
255
+ }));
256
+ if (dev.local) {
282
257
  let url;
283
- // If entrypoint is provided, set up hot reloading with esbuild context
284
- if (props.entrypoint) {
285
- const startPromise = new DeferredPromise();
286
- await createWorkerDevContext(workerName, {
287
- ...props,
288
- entrypoint: props.entrypoint,
289
- cwd,
290
- compatibilityDate,
291
- compatibilityFlags,
292
- }, {
293
- onBuildStart: () => {
294
- logger.task("miniflare-server", {
295
- message: `${startPromise.status === "pending" ? "Building" : "Rebuilding"}`,
296
- status: "pending",
297
- resource: id,
298
- prefix: "build",
299
- prefixColor: "cyanBright",
300
- });
301
- },
302
- onBuildEnd: async (newScript) => {
303
- try {
304
- // Hot reload callback - update the miniflare worker
305
- const server = await miniflareServer.push({
306
- ...sharedOptions,
307
- script: newScript,
308
- });
309
- if (startPromise.status === "pending") {
310
- logger.task("miniflare-server", {
311
- message: `Started Miniflare server at ${server.url}`,
312
- status: "success",
313
- resource: id,
314
- prefix: "ready",
315
- prefixColor: "cyanBright",
316
- });
317
- startPromise.resolve(server.url);
318
- }
319
- else {
320
- logger.task("miniflare-server", {
321
- message: `Updated Miniflare server at ${server.url}`,
322
- status: "success",
323
- resource: id,
324
- prefix: "reload",
325
- prefixColor: "cyanBright",
326
- });
327
- }
328
- }
329
- catch (error) {
330
- if (startPromise.status === "pending") {
331
- logger.error(error);
332
- logger.task("miniflare-server", {
333
- message: "Failed to start Miniflare server",
334
- status: "failure",
335
- resource: id,
336
- prefix: "error",
337
- prefixColor: "redBright",
338
- });
339
- startPromise.reject(new Error(`Failed to start Miniflare server for worker "${workerName}"`, { cause: error }));
340
- }
341
- else {
342
- logger.task("miniflare-server", {
343
- message: "Failed to update Miniflare server",
344
- status: "failure",
345
- resource: id,
346
- prefix: "error",
347
- prefixColor: "redBright",
348
- });
349
- logger.error(error);
350
- }
351
- }
352
- },
353
- onBuildError: (errors) => {
354
- if (startPromise.status === "pending") {
355
- logger.task("miniflare-server", {
356
- message: "Failed to build worker",
357
- status: "failure",
358
- resource: id,
359
- prefix: "error",
360
- prefixColor: "redBright",
361
- });
362
- startPromise.reject(errors);
363
- }
364
- else {
365
- logger.task("miniflare-server", {
366
- message: "Failed to rebuild worker",
367
- status: "failure",
368
- resource: id,
369
- prefix: "error",
370
- prefixColor: "redBright",
371
- });
372
- logger.error(errors);
373
- }
374
- },
375
- });
376
- url = await startPromise.value;
258
+ if (error) {
259
+ throw error;
377
260
  }
378
- else {
379
- // Fallback to one-time bundling for inline scripts
380
- const scriptContent = props.script ??
381
- (await bundleWorkerScript({
382
- name: workerName,
383
- ...props,
384
- cwd,
261
+ switch (dev.type) {
262
+ case "command":
263
+ createDevCommand({
264
+ id,
265
+ command: dev.command,
266
+ cwd: dev.cwd ?? process.cwd(),
267
+ env: props.env ?? {},
268
+ });
269
+ url = dev.url;
270
+ break;
271
+ case "miniflare": {
272
+ url = await createMiniflare({
273
+ id,
274
+ workerName,
385
275
  compatibilityDate,
386
276
  compatibilityFlags,
387
- }));
388
- const server = await miniflareServer.push({
389
- ...sharedOptions,
390
- script: typeof scriptContent === "string"
391
- ? scriptContent
392
- : scriptContent[props.entrypoint].toString(),
393
- });
394
- url = server.url;
277
+ bindings: props.bindings,
278
+ bundle,
279
+ port: dev.port,
280
+ });
281
+ break;
282
+ }
395
283
  }
396
284
  return this({
397
285
  type: "service",
@@ -402,11 +290,11 @@ export const _Worker = Resource("cloudflare::Worker", {
402
290
  compatibilityDate,
403
291
  compatibilityFlags,
404
292
  format: props.format || "esm", // Include format in the output
405
- bindings: props.bindings ?? {},
293
+ bindings: normalizeExportBindings(workerName, props.bindings),
406
294
  env: props.env,
407
295
  observability: props.observability,
408
- createdAt: now,
409
- updatedAt: now,
296
+ createdAt: this.output?.createdAt ?? Date.now(),
297
+ updatedAt: Date.now(),
410
298
  eventSources: props.eventSources,
411
299
  url,
412
300
  dev: props.dev,
@@ -418,59 +306,48 @@ export const _Worker = Resource("cloudflare::Worker", {
418
306
  Env: undefined,
419
307
  });
420
308
  }
421
- // Create Cloudflare API client with automatic account discovery
422
309
  const api = await createCloudflareApi(props);
423
- const uploadWorkerScript = async (props) => {
424
- // Get the script content - either from props.script, or by bundling
425
- const scriptBundle = props.script ??
426
- (await bundleWorkerScript({
427
- ...props,
428
- name: workerName,
429
- cwd,
430
- compatibilityDate,
431
- compatibilityFlags,
432
- }));
433
- // Find any assets bindings
434
- const assetsBindings = [];
435
- const workflowsBindings = [];
436
- const containersBindings = [];
437
- if (props.bindings) {
438
- for (const [bindingName, binding] of Object.entries(props.bindings)) {
439
- if (typeof binding === "object") {
440
- if (binding.type === "assets") {
441
- assetsBindings.push({ name: bindingName, assets: binding });
442
- }
443
- else if (binding.type === "workflow") {
444
- workflowsBindings.push(binding);
445
- }
446
- else if (binding.type === "container") {
447
- containersBindings.push(binding);
448
- }
449
- }
310
+ if (this.phase === "delete") {
311
+ await bundle?.delete?.();
312
+ if (!props.version) {
313
+ await deleteQueueConsumers(api, workerName);
314
+ await deleteWorker(api, {
315
+ scriptName: workerName,
316
+ dispatchNamespace,
317
+ });
318
+ }
319
+ return this.destroy();
320
+ }
321
+ else if (error) {
322
+ throw error;
323
+ }
324
+ let assetsBinding;
325
+ const workflowsBindings = [];
326
+ const containersBindings = [];
327
+ for (const binding of Object.values(props.bindings ?? {})) {
328
+ if (typeof binding === "object") {
329
+ if (binding.type === "assets") {
330
+ assetsBinding = binding;
331
+ }
332
+ else if (binding.type === "workflow") {
333
+ workflowsBindings.push(binding);
334
+ }
335
+ else if (binding.type === "container") {
336
+ containersBindings.push(binding);
450
337
  }
451
338
  }
452
- // Get dispatch namespace if specified
453
- const dispatchNamespace = props.namespace
454
- ? typeof props.namespace === "string"
455
- ? props.namespace
456
- : props.namespace.namespace
457
- : undefined;
458
- // Upload any assets and get completion tokens
459
- let assetUploadResult;
460
- if (assetsBindings.length > 0) {
461
- // We'll use the first asset binding for now
462
- // In the future, we might want to support multiple asset bindings
463
- const assetBinding = assetsBindings[0];
464
- // Upload the assets and get the completion token
465
- assetUploadResult = await uploadAssets(api, {
339
+ }
340
+ const putWorkerWithAssets = async (props, scriptBundle) => {
341
+ const assetUploadResult = assetsBinding
342
+ ? await uploadAssets(api, {
466
343
  workerName,
467
- assets: assetBinding.assets,
344
+ assets: assetsBinding,
468
345
  assetConfig: props.assets,
469
346
  namespace: dispatchNamespace,
470
- });
471
- }
347
+ })
348
+ : undefined;
472
349
  // Deploy worker (either as version or live worker)
473
- const versionResult = await putWorker(api, {
350
+ return await putWorker(api, {
474
351
  ...props,
475
352
  workerName,
476
353
  scriptBundle,
@@ -480,111 +357,14 @@ export const _Worker = Resource("cloudflare::Worker", {
480
357
  compatibilityFlags,
481
358
  assetUploadResult,
482
359
  });
483
- versionResult.versionId;
484
- for (const workflow of workflowsBindings) {
485
- if (workflow.scriptName === undefined ||
486
- workflow.scriptName === workerName) {
487
- await upsertWorkflow(api, {
488
- workflowName: workflow.workflowName,
489
- className: workflow.className,
490
- scriptName: workflow.scriptName ?? workerName,
491
- });
492
- }
493
- }
494
- for (const container of containersBindings) {
495
- async function findNamespaceId() {
496
- const response = await api.get(`/accounts/${api.accountId}/workers/scripts/${workerName}/versions/${versionResult.deploymentId}`);
497
- const result = (await response.json());
498
- const namespaceId = result.result.resources.bindings.find((binding) => binding.type === "durable_object_namespace" &&
499
- binding.class_name === container.className)?.namespace_id;
500
- if (!namespaceId) {
501
- throw new Error(`Namespace ID not found for container ${container.id}`);
502
- }
503
- return namespaceId;
504
- }
505
- await ContainerApplication(container.id, {
506
- accountId: props.accountId,
507
- apiKey: props.apiKey,
508
- apiToken: props.apiToken,
509
- baseUrl: props.baseUrl,
510
- email: props.email,
511
- image: container.image,
512
- name: container.id,
513
- instanceType: container.instanceType,
514
- observability: container.observability,
515
- durableObjects: {
516
- namespaceId: await findNamespaceId(),
517
- },
518
- schedulingPolicy: container.schedulingPolicy,
519
- });
520
- }
521
- await Promise.all(props.eventSources?.map((eventSource) => {
522
- if (isQueue(eventSource) || isQueueEventSource(eventSource)) {
523
- const queue = isQueue(eventSource)
524
- ? eventSource
525
- : eventSource.queue;
526
- return QueueConsumer(`${queue.id}-consumer`, {
527
- queue,
528
- scriptName: workerName,
529
- accountId: api.accountId,
530
- adopt: props.adopt,
531
- settings: isQueueEventSource(eventSource)
532
- ? eventSource.settings
533
- : queue.dlq
534
- ? { deadLetterQueue: queue.dlq }
535
- : undefined,
536
- });
537
- }
538
- throw new Error(`Unsupported event source type: ${eventSource}`);
539
- }) ?? []);
540
- // Handle worker URL if requested
541
- let workerUrl;
542
- if (props.version) {
543
- // For versions, use the preview URL if available
544
- workerUrl = versionResult?.previewUrl;
545
- }
546
- else if (!props.namespace) {
547
- // namespaces don't support URLs
548
- // For regular workers, use the normal URL configuration
549
- workerUrl = await configureURL(this, api, workerName, props.url ?? true);
550
- }
551
- // Get current timestamp
552
- const now = Date.now();
553
- // Update cron triggers
554
- if (props.crons) {
555
- const res = await api.put(`/accounts/${api.accountId}/workers/scripts/${workerName}/schedules`, props.crons.map((cron) => ({ cron })));
556
- if (!res.ok) {
557
- await handleApiError(res, "updating cron triggers", "worker", workerName);
558
- }
559
- }
560
- return { scriptBundle, workerUrl, now, versionResult };
561
360
  };
562
- if (this.phase === "delete") {
563
- // only delete the worker if it's not a version
564
- if (!props.version) {
565
- // Delete any queue consumers attached to this worker first
566
- await deleteQueueConsumers(api, workerName);
567
- await withExponentialBackoff(() => deleteWorker(api, {
568
- workerName,
569
- namespace: typeof props.namespace === "string"
570
- ? props.namespace
571
- : props.namespace?.namespaceId,
572
- url: this.output.url,
573
- }), (err) => (err.status === 400 &&
574
- err.message.includes("is still referenced by service bindings in Workers")) ||
575
- err.status === 500 ||
576
- err.status === 503, 10, 100);
577
- }
578
- return this.destroy();
579
- }
580
361
  if (this.phase === "create") {
581
362
  if (props.version) {
582
363
  // When version is specified, we adopt existing workers or create them if they don't exist
583
- const workerExists = await checkWorkerExists(api, workerName);
584
- if (!workerExists) {
364
+ if (!(await workerExists(api, workerName))) {
585
365
  // Create the base worker first if it doesn't exist
586
366
  const baseWorkerProps = { ...props, version: undefined };
587
- await uploadWorkerScript(baseWorkerProps);
367
+ await putWorkerWithAssets(baseWorkerProps, await bundle.create());
588
368
  }
589
369
  // We always "adopt" when publishing versions
590
370
  }
@@ -592,73 +372,136 @@ export const _Worker = Resource("cloudflare::Worker", {
592
372
  await assertWorkerDoesNotExist(api, workerName);
593
373
  }
594
374
  }
595
- const { workerUrl, now } = await uploadWorkerScript(props);
596
- function ensureNoDuplicates(name, items) {
597
- const duplicates = items.filter((pattern, index) => items.indexOf(pattern) !== index);
598
- if (duplicates.length > 0) {
599
- throw new Error(`Duplicate ${name} found: ${duplicates.join(", ")}`);
600
- }
601
- }
602
- let createdDomains;
603
- if (props.domains?.length) {
604
- ensureNoDuplicates("Custom Domain", props.domains.map((domain) => typeof domain === "string" ? domain : domain.domainName));
605
- createdDomains = await Promise.all(props.domains.map(async (customDomain) => {
606
- const domainName = typeof customDomain === "string"
607
- ? customDomain
608
- : customDomain.domainName;
609
- return await CustomDomain(domainName, {
610
- workerName,
611
- name: domainName,
612
- zoneId: typeof customDomain === "string"
613
- ? undefined
614
- : customDomain.zoneId,
615
- adopt: typeof customDomain === "string"
616
- ? (props.adopt ?? false)
617
- : (customDomain.adopt ?? props.adopt),
618
- });
375
+ let putWorkerResult;
376
+ if (dev.type === "remote") {
377
+ // todo(john): clean this up and add log tail
378
+ const controller = new AbortController();
379
+ cleanups.push(() => controller.abort());
380
+ const watcher = await bundle.watch(controller.signal);
381
+ const promise = new DeferredPromise();
382
+ void watcher.pipeTo(new WritableStream({
383
+ async write(chunk) {
384
+ switch (chunk.type) {
385
+ case "start":
386
+ logger.task("", {
387
+ message: "start",
388
+ status: "pending",
389
+ resource: id,
390
+ prefix: "build",
391
+ prefixColor: "cyanBright",
392
+ });
393
+ break;
394
+ case "end": {
395
+ if (promise.status === "pending") {
396
+ await putWorkerWithAssets(props, chunk.result)
397
+ .then((result) => promise.resolve(result))
398
+ .catch((error) => {
399
+ controller.abort();
400
+ promise.reject(error);
401
+ });
402
+ return;
403
+ }
404
+ logger.task("", {
405
+ message: "reload",
406
+ status: "success",
407
+ resource: id,
408
+ prefix: "build",
409
+ prefixColor: "cyanBright",
410
+ });
411
+ await putWorker(api, {
412
+ ...props,
413
+ workerName,
414
+ scriptBundle: chunk.result,
415
+ dispatchNamespace,
416
+ version: props.version,
417
+ compatibilityDate,
418
+ compatibilityFlags,
419
+ assetUploadResult: assetsBinding
420
+ ? {
421
+ keepAssets: true,
422
+ assetConfig: props.assets,
423
+ }
424
+ : undefined,
425
+ unstable_cacheWorkerSettings: true,
426
+ });
427
+ logger.task("", {
428
+ message: "updated",
429
+ status: "success",
430
+ resource: id,
431
+ prefix: "build",
432
+ prefixColor: "greenBright",
433
+ });
434
+ break;
435
+ }
436
+ case "error":
437
+ logger.task("", {
438
+ message: "error",
439
+ status: "failure",
440
+ resource: id,
441
+ prefix: "build",
442
+ prefixColor: "redBright",
443
+ });
444
+ logger.error(chunk.errors);
445
+ break;
446
+ }
447
+ },
619
448
  }));
449
+ putWorkerResult = await promise.value;
450
+ const tail = await createTail(api, id, workerName);
451
+ cleanups.push(() => tail.close());
620
452
  }
621
- // Create routes if provided and capture their outputs
622
- let createdRoutes;
623
- if (props.routes && props.routes.length > 0) {
624
- ensureNoDuplicates("Route", props.routes.map((route) => typeof route === "string" ? route : route.pattern));
625
- // Create Route resources for each route and capture their outputs
626
- createdRoutes = await Promise.all(props.routes.map(async (routeConfig) => {
627
- const pattern = typeof routeConfig === "string" ? routeConfig : routeConfig.pattern;
628
- return await Route(pattern, {
629
- pattern,
630
- script: workerName,
631
- zoneId: typeof routeConfig === "string" ? undefined : routeConfig.zoneId, // Route resource will handle inference if not provided
632
- adopt: typeof routeConfig === "string"
633
- ? (props.adopt ?? false)
634
- : (routeConfig.adopt ?? props.adopt),
635
- accountId: props.accountId,
636
- apiKey: props.apiKey,
637
- apiToken: props.apiToken,
638
- baseUrl: props.baseUrl,
639
- email: props.email,
640
- });
641
- }));
453
+ else {
454
+ putWorkerResult = await putWorkerWithAssets(props, await bundle.create());
642
455
  }
643
- function exportBindings() {
644
- return Object.fromEntries(Object.entries(props.bindings ?? {}).map(([bindingName, binding]) => [
645
- bindingName,
646
- isDurableObjectNamespace(binding) &&
647
- binding.scriptName === undefined
648
- ? new DurableObjectNamespace(binding.id, {
649
- ...binding,
650
- // re-export this binding mapping to the host worker (this worker)
651
- scriptName: workerName,
652
- })
653
- : isWorkflow(binding) && binding.scriptName === undefined
654
- ? new Workflow(binding.id, {
655
- ...binding,
656
- // re-export this binding mapping to the host worker (this worker)
657
- scriptName: workerName,
658
- })
659
- : binding,
660
- ]));
456
+ const tasks = [];
457
+ for (const workflow of workflowsBindings) {
458
+ if (workflow.scriptName === undefined ||
459
+ workflow.scriptName === workerName) {
460
+ tasks.push(upsertWorkflow(api, {
461
+ workflowName: workflow.workflowName,
462
+ className: workflow.className,
463
+ scriptName: workflow.scriptName ?? workerName,
464
+ }));
465
+ }
661
466
  }
467
+ if (containersBindings.length > 0) {
468
+ tasks.push(getVersionMetadata(api, workerName, putWorkerResult.deployment_id).then((versionMetadata) => provisionContainers(api, {
469
+ scriptName: workerName,
470
+ containers: containersBindings,
471
+ bindings: versionMetadata.resources.bindings,
472
+ })));
473
+ }
474
+ if (!isDeepStrictEqual(props.crons, this.output?.crons)) {
475
+ tasks.push(api.put(`/accounts/${api.accountId}/workers/scripts/${workerName}/schedules`, props.crons?.map((cron) => ({ cron })) ?? []));
476
+ }
477
+ tasks.push(provisionEventSources(api, {
478
+ scriptName: workerName,
479
+ eventSources: props.eventSources,
480
+ }));
481
+ const [domains, routes, subdomain] = await Promise.all([
482
+ // TODO: can you provision domains and routes in parallel, or is there a dependency?
483
+ provisionDomains(api, {
484
+ scriptName: workerName,
485
+ adopt: props.adopt,
486
+ domains: props.domains,
487
+ }),
488
+ provisionRoutes(api, {
489
+ scriptName: workerName,
490
+ adopt: props.adopt,
491
+ routes: props.routes,
492
+ }),
493
+ provisionSubdomain(api, {
494
+ scriptName: workerName,
495
+ enable: props.url ?? dispatchNamespace === undefined,
496
+ previewVersionId: props.version && putWorkerResult.metadata.has_preview
497
+ ? putWorkerResult.id
498
+ : undefined,
499
+ retain: !!props.version,
500
+ forceDelete: this.phase === "create" && !!props.adopt && props.url === false,
501
+ }),
502
+ ...tasks,
503
+ ]);
504
+ const now = Date.now();
662
505
  // Construct the output
663
506
  return this({
664
507
  ...props,
@@ -670,22 +513,22 @@ export const _Worker = Resource("cloudflare::Worker", {
670
513
  compatibilityDate,
671
514
  compatibilityFlags,
672
515
  format: props.format || "esm", // Include format in the output
673
- bindings: exportBindings(),
516
+ bindings: normalizeExportBindings(workerName, props.bindings),
674
517
  env: props.env,
675
518
  observability: props.observability,
676
- createdAt: now,
519
+ createdAt: this.output?.createdAt ?? now,
677
520
  updatedAt: now,
678
521
  eventSources: props.eventSources,
679
- url: workerUrl,
522
+ url: subdomain?.url,
680
523
  dev: props.dev,
681
524
  // Include assets configuration in the output
682
525
  assets: props.assets,
683
526
  // Include cron triggers in the output
684
527
  crons: props.crons,
685
528
  // Include the created routes in the output
686
- routes: createdRoutes,
529
+ routes,
687
530
  // Include the created domains in the output
688
- domains: createdDomains,
531
+ domains,
689
532
  // Include the dispatch namespace in the output
690
533
  namespace: props.namespace,
691
534
  // Include version information in the output
@@ -694,31 +537,324 @@ export const _Worker = Resource("cloudflare::Worker", {
694
537
  Env: undefined,
695
538
  });
696
539
  });
697
- export async function deleteWorker(api, props) {
698
- const workerName = props.workerName;
699
- // Delete worker
700
- const deleteResponse = await api.delete(props.namespace
701
- ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${props.namespace}/scripts/${workerName}?force=true`
702
- : `/accounts/${api.accountId}/workers/scripts/${workerName}?force=true`);
703
- // Check for success (2xx status code)
704
- if (!deleteResponse.ok && deleteResponse.status !== 404) {
705
- await handleApiError(deleteResponse, "delete", "worker", workerName);
706
- }
707
- // Disable the URL if it was enabled
708
- if (props.url) {
709
- try {
710
- await api.post(`/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`, JSON.stringify({ enabled: false }), {
711
- headers: { "Content-Type": "application/json" },
540
+ const cleanups = [];
541
+ let exiting = false;
542
+ process.on("SIGINT", async () => {
543
+ if (cleanups.length > 0) {
544
+ if (!exiting) {
545
+ exiting = true;
546
+ logger.log(`\n${kleur.gray("Exiting...")}`);
547
+ }
548
+ // TODO: for some reason this runs twice...
549
+ // and this whole thing feels hacky anyway
550
+ await Promise.allSettled(cleanups.map((cleanup) => cleanup()));
551
+ }
552
+ process.exit(0);
553
+ });
554
+ const normalizeDev = (ctx, dev) => {
555
+ if (!ctx.scope.dev || ctx.phase === "delete" || dev === false) {
556
+ return {
557
+ type: "none",
558
+ local: false,
559
+ };
560
+ }
561
+ const devObj = dev === true ? {} : (dev ?? {});
562
+ if ("command" in devObj) {
563
+ // Commands are always local
564
+ return {
565
+ type: "command",
566
+ ...devObj,
567
+ local: true,
568
+ };
569
+ }
570
+ if (devObj.remote === false || ctx.scope.dev === "prefer-local") {
571
+ return {
572
+ type: "miniflare",
573
+ port: devObj.port,
574
+ local: true,
575
+ };
576
+ }
577
+ return {
578
+ type: "remote",
579
+ local: false,
580
+ };
581
+ };
582
+ const assertUnique = (inputs, key, message) => {
583
+ const ids = inputs.map((input) => input[key]);
584
+ const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index);
585
+ if (duplicates.length > 0) {
586
+ throw new Error(`Duplicate ${message} found: ${duplicates.join(", ")}`);
587
+ }
588
+ };
589
+ const normalizeExportBindings = (scriptName, bindings = {}) => {
590
+ return Object.fromEntries(Object.entries(bindings).map(([bindingName, binding]) => [
591
+ bindingName,
592
+ isDurableObjectNamespace(binding) && binding.scriptName === undefined
593
+ ? new DurableObjectNamespace(binding.id, {
594
+ ...binding,
595
+ // re-export this binding mapping to the host worker (this worker)
596
+ scriptName,
597
+ })
598
+ : isWorkflow(binding) && binding.scriptName === undefined
599
+ ? new Workflow(binding.id, {
600
+ ...binding,
601
+ // re-export this binding mapping to the host worker (this worker)
602
+ scriptName,
603
+ })
604
+ : binding,
605
+ ]));
606
+ };
607
+ const normalizeApiOptions = (api) => ({
608
+ accountId: api.accountId,
609
+ apiKey: api.apiKey,
610
+ apiToken: api.apiToken,
611
+ email: api.email,
612
+ baseUrl: api.baseUrl,
613
+ });
614
+ async function provisionContainers(api, props) {
615
+ if (!props.containers?.length) {
616
+ return;
617
+ }
618
+ return await Promise.all(props.containers.map((container) => {
619
+ const namespaceId = props.bindings.find((binding) => binding.type === "durable_object_namespace" &&
620
+ binding.class_name === container.className)?.namespace_id;
621
+ if (!namespaceId) {
622
+ throw new Error(`Namespace ID not found for container ${container.id}`);
623
+ }
624
+ return ContainerApplication(container.id, {
625
+ image: container.image,
626
+ name: container.id,
627
+ instanceType: container.instanceType,
628
+ observability: container.observability,
629
+ durableObjects: {
630
+ namespaceId,
631
+ },
632
+ schedulingPolicy: container.schedulingPolicy,
633
+ adopt: container.adopt,
634
+ ...normalizeApiOptions(api),
635
+ });
636
+ }));
637
+ }
638
+ async function provisionEventSources(api, props) {
639
+ if (!props.eventSources?.length) {
640
+ return;
641
+ }
642
+ return await Promise.all(props.eventSources.map((eventSource) => {
643
+ if (isQueue(eventSource)) {
644
+ return QueueConsumer(`${eventSource.id}-consumer`, {
645
+ queue: eventSource,
646
+ scriptName: props.scriptName,
647
+ settings: eventSource.dlq
648
+ ? { deadLetterQueue: eventSource.dlq }
649
+ : undefined,
650
+ ...normalizeApiOptions(api),
712
651
  });
713
652
  }
714
- catch (error) {
715
- logger.warn("Failed to disable worker URL during deletion:", error);
653
+ if (isQueueEventSource(eventSource)) {
654
+ return QueueConsumer(`${eventSource.queue.id}-consumer`, {
655
+ queue: eventSource.queue,
656
+ scriptName: props.scriptName,
657
+ settings: eventSource.settings,
658
+ ...normalizeApiOptions(api),
659
+ });
716
660
  }
661
+ throw new Error(`Unsupported event source: ${eventSource}`);
662
+ }));
663
+ }
664
+ async function provisionDomains(api, props) {
665
+ if (!props.domains?.length) {
666
+ return;
717
667
  }
718
- // Return minimal output for deleted state
719
- return;
668
+ const domains = props.domains.map((domain) => {
669
+ if (typeof domain === "string") {
670
+ return {
671
+ name: domain,
672
+ zoneId: undefined,
673
+ adopt: props.adopt,
674
+ };
675
+ }
676
+ return {
677
+ name: domain.domainName,
678
+ zoneId: domain.zoneId,
679
+ adopt: domain.adopt ?? props.adopt,
680
+ };
681
+ });
682
+ assertUnique(domains, "name", "Custom Domain");
683
+ return await Promise.all(domains.map(async (domain) => {
684
+ return await CustomDomain(domain.name, {
685
+ workerName: props.scriptName,
686
+ name: domain.name,
687
+ zoneId: domain.zoneId,
688
+ adopt: domain.adopt,
689
+ ...normalizeApiOptions(api),
690
+ });
691
+ }));
692
+ }
693
+ async function provisionRoutes(api, props) {
694
+ if (!props.routes?.length) {
695
+ return;
696
+ }
697
+ const routes = props.routes.map((route) => {
698
+ if (typeof route === "string") {
699
+ return {
700
+ pattern: route,
701
+ adopt: props.adopt,
702
+ };
703
+ }
704
+ return {
705
+ pattern: route.pattern,
706
+ zoneId: route.zoneId,
707
+ adopt: route.adopt ?? props.adopt,
708
+ };
709
+ });
710
+ assertUnique(routes, "pattern", "Route");
711
+ return await Promise.all(routes.map(async (route) => {
712
+ return await Route(route.pattern, {
713
+ pattern: route.pattern,
714
+ script: props.scriptName,
715
+ zoneId: route.zoneId,
716
+ adopt: route.adopt,
717
+ ...normalizeApiOptions(api),
718
+ });
719
+ }));
720
720
  }
721
- function upsertDevCommand(props) {
721
+ async function provisionSubdomain(api, props) {
722
+ if (props.enable) {
723
+ return await WorkerSubdomain("url", {
724
+ scriptName: props.scriptName,
725
+ previewVersionId: props.previewVersionId,
726
+ retain: props.retain,
727
+ ...normalizeApiOptions(api),
728
+ });
729
+ }
730
+ if (props.forceDelete) {
731
+ await disableWorkerSubdomain(api, props.scriptName);
732
+ }
733
+ }
734
+ async function createMiniflare(props) {
735
+ const sharedOptions = {
736
+ name: props.workerName,
737
+ compatibilityDate: props.compatibilityDate,
738
+ compatibilityFlags: props.compatibilityFlags,
739
+ bindings: props.bindings,
740
+ port: props.port,
741
+ };
742
+ const startPromise = new DeferredPromise();
743
+ const controller = new AbortController();
744
+ cleanups.push(() => controller.abort());
745
+ (await props.bundle.watch(controller.signal))
746
+ .pipeThrough(new TransformStream({
747
+ async transform(chunk, controller) {
748
+ switch (chunk.type) {
749
+ case "start": {
750
+ controller.enqueue({ type: "build-start" });
751
+ break;
752
+ }
753
+ case "end": {
754
+ controller.enqueue({ type: "build-end" });
755
+ try {
756
+ const server = await miniflareServer.push({
757
+ ...sharedOptions,
758
+ bundle: chunk.result,
759
+ });
760
+ controller.enqueue({
761
+ type: "miniflare-ready",
762
+ url: server.url,
763
+ });
764
+ }
765
+ catch (error) {
766
+ controller.enqueue({
767
+ type: "miniflare-error",
768
+ error: error,
769
+ });
770
+ }
771
+ break;
772
+ }
773
+ case "error": {
774
+ controller.enqueue({
775
+ type: "build-error",
776
+ errors: chunk.errors,
777
+ });
778
+ break;
779
+ }
780
+ }
781
+ },
782
+ }))
783
+ .pipeTo(new WritableStream({
784
+ write: async (chunk) => {
785
+ switch (chunk.type) {
786
+ case "build-start": {
787
+ logger.task("", {
788
+ message: "start",
789
+ status: "pending",
790
+ resource: props.id,
791
+ prefix: "build",
792
+ prefixColor: "cyanBright",
793
+ });
794
+ break;
795
+ }
796
+ case "build-end": {
797
+ logger.task("", {
798
+ message: startPromise.status === "pending" ? "load" : "reload",
799
+ status: "success",
800
+ resource: props.id,
801
+ prefix: "build",
802
+ prefixColor: "greenBright",
803
+ });
804
+ break;
805
+ }
806
+ case "build-error": {
807
+ logger.task("", {
808
+ message: "error",
809
+ status: "failure",
810
+ resource: props.id,
811
+ prefix: "build",
812
+ prefixColor: "redBright",
813
+ });
814
+ if (startPromise.status === "pending") {
815
+ controller.abort();
816
+ startPromise.reject(chunk.errors);
817
+ }
818
+ else {
819
+ logger.error(chunk.errors);
820
+ }
821
+ break;
822
+ }
823
+ case "miniflare-ready": {
824
+ logger.task("", {
825
+ message: `ready at ${chunk.url}`,
826
+ status: "success",
827
+ resource: props.id,
828
+ prefix: "miniflare",
829
+ prefixColor: "greenBright",
830
+ });
831
+ if (startPromise.status === "pending") {
832
+ startPromise.resolve(chunk.url);
833
+ }
834
+ break;
835
+ }
836
+ case "miniflare-error": {
837
+ logger.task("", {
838
+ message: "error",
839
+ status: "failure",
840
+ resource: props.id,
841
+ prefix: "miniflare",
842
+ prefixColor: "redBright",
843
+ });
844
+ if (startPromise.status === "pending") {
845
+ startPromise.reject(chunk.error);
846
+ }
847
+ else {
848
+ logger.error(chunk.error);
849
+ }
850
+ break;
851
+ }
852
+ }
853
+ },
854
+ }));
855
+ return await startPromise.value;
856
+ }
857
+ function createDevCommand(props) {
722
858
  const persistFile = path.join(process.cwd(), ".alchemy", `${props.id}.pid`);
723
859
  if (existsSync(persistFile)) {
724
860
  const pid = Number.parseInt(readFileSync(persistFile, "utf8"));
@@ -745,104 +881,92 @@ function upsertDevCommand(props) {
745
881
  },
746
882
  stdio: ["inherit", "inherit", "inherit"],
747
883
  });
748
- process.on("SIGINT", () => {
884
+ cleanups.push(() => {
749
885
  try {
750
886
  unlinkSync(persistFile);
751
887
  }
752
888
  catch {
753
889
  // ignore
754
890
  }
755
- proc.kill("SIGINT");
756
- process.exit(0);
891
+ proc.kill();
757
892
  });
758
893
  if (proc.pid) {
759
894
  mkdirSync(path.dirname(persistFile), { recursive: true });
760
895
  writeFileSync(persistFile, proc.pid.toString());
761
896
  }
762
897
  }
898
+ async function prepareWorkerUpload(api, props) {
899
+ const scriptMetadata = await prepareWorkerMetadata(api, props);
900
+ if (props.format === "cjs") {
901
+ scriptMetadata.body_part = props.scriptBundle.entrypoint;
902
+ }
903
+ else {
904
+ scriptMetadata.main_module = props.scriptBundle.entrypoint;
905
+ }
906
+ const body = new FormData();
907
+ for await (const file of props.scriptBundle.files) {
908
+ body.append(file.name, file);
909
+ }
910
+ // Prepare metadata - add version annotations if this is a version
911
+ const finalMetadata = props.version
912
+ ? {
913
+ ...scriptMetadata,
914
+ // Exclude migrations for worker versions - they're not allowed
915
+ migrations: undefined,
916
+ annotations: {
917
+ "workers/tag": props.version,
918
+ "workers/message": `Version ${props.version}`,
919
+ },
920
+ }
921
+ : {
922
+ ...scriptMetadata,
923
+ migrations: scriptMetadata.migrations
924
+ ? {
925
+ ...scriptMetadata.migrations,
926
+ old_tag: props.migrationTag,
927
+ new_tag: bumpMigrationTagVersion(props.migrationTag),
928
+ }
929
+ : undefined,
930
+ };
931
+ body.append("metadata", new Blob([JSON.stringify(finalMetadata)], {
932
+ type: "application/json",
933
+ }));
934
+ let endpoint;
935
+ let method;
936
+ if (props.version) {
937
+ if (props.dispatchNamespace) {
938
+ throw new Error("Worker Preview Versions are not supported in Workers for Platforms");
939
+ }
940
+ // Upload worker version using the versions API
941
+ endpoint = `/accounts/${api.accountId}/workers/scripts/${props.workerName}/versions`;
942
+ method = "POST";
943
+ }
944
+ else {
945
+ // Upload worker script with bindings
946
+ endpoint = props.dispatchNamespace
947
+ ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${props.dispatchNamespace}/scripts/${props.workerName}`
948
+ : `/accounts/${api.accountId}/workers/scripts/${props.workerName}`;
949
+ method = "PUT";
950
+ }
951
+ return {
952
+ body,
953
+ endpoint,
954
+ method,
955
+ };
956
+ }
763
957
  export async function putWorker(api, props) {
764
958
  const {
765
959
  //
766
- dispatchNamespace, migrationTag, workerName, scriptBundle, version, } = props;
767
- const scriptMetadata = await prepareWorkerMetadata(api, props);
960
+ workerName, version, } = props;
768
961
  return withExponentialBackoff(async () => {
769
- const scriptName = scriptMetadata.main_module ?? scriptMetadata.body_part;
770
- // Create FormData for the upload
771
- const formData = new FormData();
772
- function addFile(fileName, content) {
773
- const contentType = getContentType(fileName) ?? "application/null";
774
- formData.append(fileName, new Blob([content], {
775
- type: contentType === "application/javascript" &&
776
- scriptMetadata.main_module
777
- ? "application/javascript+module"
778
- : contentType,
779
- }), fileName);
780
- }
781
- if (typeof scriptBundle === "string") {
782
- addFile(scriptName, scriptBundle);
783
- }
784
- else {
785
- for (const [fileName, content] of Object.entries(scriptBundle)) {
786
- addFile(fileName, content);
787
- }
788
- }
789
- // Prepare metadata - add version annotations if this is a version
790
- const finalMetadata = version
791
- ? {
792
- ...scriptMetadata,
793
- // Exclude migrations for worker versions - they're not allowed
794
- migrations: undefined,
795
- annotations: {
796
- "workers/tag": version,
797
- "workers/message": `Version ${version}`,
798
- },
799
- }
800
- : {
801
- ...scriptMetadata,
802
- migrations: scriptMetadata.migrations
803
- ? {
804
- ...scriptMetadata.migrations,
805
- old_tag: migrationTag,
806
- new_tag: bumpMigrationTagVersion(migrationTag),
807
- }
808
- : undefined,
809
- };
810
- if (process.env.DEBUG) {
811
- console.log(`metadata(${scriptName}):`, finalMetadata);
812
- }
813
- // Add metadata as JSON
814
- formData.append("metadata", new Blob([JSON.stringify(finalMetadata)], {
815
- type: "application/json",
816
- }));
817
- // Determine endpoint and HTTP method
818
- let endpoint;
819
- let method;
820
- if (version) {
821
- if (dispatchNamespace) {
822
- throw new Error("Worker Preview Versions are not supported in Workers for Platforms");
823
- }
824
- // Upload worker version using the versions API
825
- endpoint = `/accounts/${api.accountId}/workers/scripts/${workerName}/versions`;
826
- method = "POST";
827
- }
828
- else {
829
- // Upload worker script with bindings
830
- endpoint = dispatchNamespace
831
- ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${dispatchNamespace}/scripts/${workerName}`
832
- : `/accounts/${api.accountId}/workers/scripts/${workerName}`;
833
- method = "PUT";
834
- }
835
- const uploadResponse = method === "PUT"
836
- ? await api.put(endpoint, formData, {
837
- headers: {
838
- "Content-Type": "multipart/form-data",
839
- },
840
- })
841
- : await api.post(endpoint, formData, {
842
- headers: {
843
- "Content-Type": "multipart/form-data",
844
- },
845
- });
962
+ const { body, endpoint, method } = await prepareWorkerUpload(api, props);
963
+ const uploadResponse = await api.fetch(endpoint, {
964
+ method,
965
+ headers: {
966
+ "Content-Type": "multipart/form-data",
967
+ },
968
+ body,
969
+ });
846
970
  // Check if the upload was successful
847
971
  if (!uploadResponse.ok) {
848
972
  try {
@@ -876,33 +1000,7 @@ export async function putWorker(api, props) {
876
1000
  }
877
1001
  }
878
1002
  const responseData = (await uploadResponse.json());
879
- const result = responseData.result;
880
- // Handle version response
881
- if (props.version) {
882
- // Get the account's workers.dev subdomain to construct preview URL
883
- let previewUrl;
884
- if (result.metadata?.has_preview) {
885
- // Need to get the subdomain
886
- const subdomainResponse = await api.get(`/accounts/${api.accountId}/workers/subdomain`);
887
- if (subdomainResponse.ok) {
888
- const subdomainData = await subdomainResponse.json();
889
- const subdomain = subdomainData.result?.subdomain;
890
- if (subdomain) {
891
- // Preview URL format: <FIRST_8_CHARS_OF_VERSION_ID>-<WORKER_NAME>.<SUBDOMAIN>.workers.dev
892
- const versionPrefix = result.id.substring(0, 8);
893
- previewUrl = `https://${versionPrefix}-${workerName}.${subdomain}.workers.dev`;
894
- }
895
- }
896
- }
897
- return {
898
- versionId: result.id,
899
- previewUrl,
900
- deploymentId: result.deployment_id,
901
- };
902
- }
903
- return {
904
- deploymentId: result.deployment_id,
905
- };
1003
+ return responseData.result;
906
1004
  }, (err) => err.status === 404 ||
907
1005
  err.status === 500 ||
908
1006
  err.status === 503 ||
@@ -911,68 +1009,57 @@ export async function putWorker(api, props) {
911
1009
  err.status === 400 &&
912
1010
  err.message.match(/binding.*failed to generate/)), 10, 100);
913
1011
  }
914
- export async function checkWorkerExists(api, workerName) {
915
- const response = await api.get(`/accounts/${api.accountId}/workers/scripts/${workerName}`);
916
- return response.status === 200;
1012
+ async function workerExists(api, scriptName) {
1013
+ const res = await api.get(`/accounts/${api.accountId}/workers/scripts/${scriptName}`);
1014
+ return res.status === 200;
917
1015
  }
918
- export async function assertWorkerDoesNotExist(api, workerName) {
919
- const response = await api.get(`/accounts/${api.accountId}/workers/scripts/${workerName}`);
1016
+ async function assertWorkerDoesNotExist(api, scriptName) {
1017
+ const response = await api.get(`/accounts/${api.accountId}/workers/scripts/${scriptName}`);
920
1018
  if (response.status === 404) {
921
1019
  return true;
922
1020
  }
923
1021
  if (response.status === 200) {
924
- const metadata = await getWorkerScriptMetadata(api, workerName);
1022
+ const metadata = await getScriptMetadata(api, scriptName);
925
1023
  if (!metadata) {
926
1024
  throw new Error(`Worker exists but failed to fetch metadata: ${response.status} ${response.statusText}`);
927
1025
  }
928
- throw new Error(`Worker with name '${workerName}' already exists. Please use a unique name.`);
1026
+ throw new Error(`Worker with name '${scriptName}' already exists. Please use a unique name.`);
929
1027
  }
930
1028
  throw new Error(`Error checking if worker exists: ${response.status} ${response.statusText} ${await response.text()}`);
931
1029
  }
932
- export async function configureURL(ctx, api, workerName, url) {
933
- let workerUrl;
934
- if (url) {
935
- // Enable the workers.dev subdomain for this worker
936
- await api.post(`/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`, { enabled: true, previews_enabled: true }, {
937
- headers: { "Content-Type": "application/json" },
938
- });
939
- // Get the account's workers.dev subdomain
940
- const subdomain = await getAccountSubdomain(api);
941
- if (subdomain) {
942
- workerUrl = `https://${workerName}.${subdomain}.workers.dev`;
943
- }
944
- }
945
- else if (url === false && ctx.output?.url) {
946
- // Explicitly disable URL if it was previously enabled
947
- const response = await api.post(`/accounts/${api.accountId}/workers/scripts/${workerName}/subdomain`, JSON.stringify({ enabled: false }), {
948
- headers: { "Content-Type": "application/json" },
949
- });
950
- if (!response.ok) {
951
- throw new Error(`Failed to disable worker URL: ${response.status} ${response.statusText}`);
952
- }
953
- }
954
- return workerUrl;
955
- }
956
- export async function getWorkerScriptMetadata(api, workerName) {
957
- const response = await api.get(`/accounts/${api.accountId}/workers/services/${workerName}`);
958
- if (response.status === 404) {
959
- return undefined;
1030
+ async function getScriptMetadata(api, scriptName) {
1031
+ const res = await api.get(`/accounts/${api.accountId}/workers/services/${scriptName}`);
1032
+ if (res.status === 404) {
1033
+ return;
960
1034
  }
961
- if (!response.ok) {
962
- throw new Error(`Error getting worker script metadata: ${response.status} ${response.statusText}`);
1035
+ if (!res.ok) {
1036
+ throw new Error(`Error getting worker script metadata: ${res.status} ${res.statusText}`);
963
1037
  }
964
- return (await response.json()).result;
1038
+ const json = (await res.json());
1039
+ return json.result;
965
1040
  }
966
- /**
967
- * Lists and deletes all queue consumers for a specific worker
968
- * @param ctx Worker context containing eventSources
969
- * @param api CloudflareApi instance
970
- * @param workerName Name of the worker script
971
- */
972
- async function deleteQueueConsumers(api, workerName) {
973
- const consumers = await listQueueConsumersForWorker(api, workerName);
1041
+ async function deleteQueueConsumers(api, scriptName) {
1042
+ const consumers = await listQueueConsumersForWorker(api, scriptName);
974
1043
  await Promise.all(consumers.map(async (consumer) => {
975
1044
  await deleteQueueConsumer(api, consumer.queueId, consumer.consumerId);
976
1045
  }));
977
1046
  }
1047
+ export async function deleteWorker(api, props) {
1048
+ await withExponentialBackoff(async () => {
1049
+ const deleteResponse = await api.delete(props.dispatchNamespace
1050
+ ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${props.dispatchNamespace}/scripts/${props.scriptName}?force=true`
1051
+ : `/accounts/${api.accountId}/workers/scripts/${props.scriptName}?force=true`);
1052
+ if (!deleteResponse.ok && deleteResponse.status !== 404) {
1053
+ await handleApiError(deleteResponse, "delete", "worker", props.scriptName);
1054
+ }
1055
+ }, (err) => (err.status === 400 &&
1056
+ err.message.includes("is still referenced by service bindings in Workers")) ||
1057
+ err.status === 500 ||
1058
+ err.status === 503, 10, 100);
1059
+ }
1060
+ async function getVersionMetadata(api, scriptName, deploymentId) {
1061
+ const response = await api.get(`/accounts/${api.accountId}/workers/scripts/${scriptName}/versions/${deploymentId}`);
1062
+ const result = (await response.json());
1063
+ return result.result;
1064
+ }
978
1065
  //# sourceMappingURL=worker.js.map