alchemy 0.63.1 → 0.65.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 (235) hide show
  1. package/bin/alchemy.js +7664 -5857
  2. package/bin/commands/init.ts +130 -9
  3. package/bin/constants.ts +4 -0
  4. package/bin/services/cdp-manager/cdp-proxy.ts +69 -0
  5. package/bin/services/cdp-manager/server.ts +173 -0
  6. package/bin/services/execute-alchemy.ts +83 -4
  7. package/bin/types.ts +1 -0
  8. package/lib/alchemy.d.ts +4 -0
  9. package/lib/alchemy.d.ts.map +1 -1
  10. package/lib/alchemy.js +10 -4
  11. package/lib/alchemy.js.map +1 -1
  12. package/lib/apply.d.ts +3 -0
  13. package/lib/apply.d.ts.map +1 -1
  14. package/lib/apply.js +11 -1
  15. package/lib/apply.js.map +1 -1
  16. package/lib/cloudflare/bindings.d.ts +3 -1
  17. package/lib/cloudflare/bindings.d.ts.map +1 -1
  18. package/lib/cloudflare/bucket.d.ts +2 -1
  19. package/lib/cloudflare/bucket.d.ts.map +1 -1
  20. package/lib/cloudflare/bucket.js.map +1 -1
  21. package/lib/cloudflare/bundle/cloudflare-internal.d.ts +6 -0
  22. package/lib/cloudflare/bundle/cloudflare-internal.d.ts.map +1 -0
  23. package/lib/cloudflare/bundle/cloudflare-internal.js +39 -0
  24. package/lib/cloudflare/bundle/cloudflare-internal.js.map +1 -0
  25. package/lib/cloudflare/bundle/nodejs-builtin-modules.d.ts +2 -0
  26. package/lib/cloudflare/bundle/nodejs-builtin-modules.d.ts.map +1 -0
  27. package/lib/cloudflare/bundle/nodejs-builtin-modules.js +9 -0
  28. package/lib/cloudflare/bundle/nodejs-builtin-modules.js.map +1 -0
  29. package/lib/cloudflare/bundle/plugin-als-external.d.ts +6 -0
  30. package/lib/cloudflare/bundle/plugin-als-external.d.ts.map +1 -0
  31. package/lib/cloudflare/bundle/plugin-als-external.js +12 -0
  32. package/lib/cloudflare/bundle/plugin-als-external.js.map +1 -0
  33. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.d.ts +4 -1
  34. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.d.ts.map +1 -1
  35. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js +23 -23
  36. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js.map +1 -1
  37. package/lib/cloudflare/bundle/plugin-node-compat.d.ts +11 -0
  38. package/lib/cloudflare/bundle/plugin-node-compat.d.ts.map +1 -0
  39. package/lib/cloudflare/bundle/plugin-node-compat.js +93 -0
  40. package/lib/cloudflare/bundle/plugin-node-compat.js.map +1 -0
  41. package/lib/cloudflare/bundle/plugin-wasm.d.ts.map +1 -1
  42. package/lib/cloudflare/bundle/plugin-wasm.js +20 -11
  43. package/lib/cloudflare/bundle/plugin-wasm.js.map +1 -1
  44. package/lib/cloudflare/compatibility-date.gen.d.ts +1 -1
  45. package/lib/cloudflare/compatibility-date.gen.js +1 -1
  46. package/lib/cloudflare/d1-local-migrations.js.map +1 -1
  47. package/lib/cloudflare/durable-object-namespace.d.ts +1 -0
  48. package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -1
  49. package/lib/cloudflare/durable-object-namespace.js.map +1 -1
  50. package/lib/cloudflare/hyperdrive.d.ts +7 -2
  51. package/lib/cloudflare/hyperdrive.d.ts.map +1 -1
  52. package/lib/cloudflare/hyperdrive.js +39 -57
  53. package/lib/cloudflare/hyperdrive.js.map +1 -1
  54. package/lib/cloudflare/index.d.ts +1 -0
  55. package/lib/cloudflare/index.d.ts.map +1 -1
  56. package/lib/cloudflare/index.js +1 -0
  57. package/lib/cloudflare/index.js.map +1 -1
  58. package/lib/cloudflare/next.d.ts +13 -0
  59. package/lib/cloudflare/next.d.ts.map +1 -0
  60. package/lib/cloudflare/next.js +91 -0
  61. package/lib/cloudflare/next.js.map +1 -0
  62. package/lib/cloudflare/website.d.ts.map +1 -1
  63. package/lib/cloudflare/website.js +80 -82
  64. package/lib/cloudflare/website.js.map +1 -1
  65. package/lib/cloudflare/worker-assets.d.ts +3 -3
  66. package/lib/cloudflare/worker-assets.d.ts.map +1 -1
  67. package/lib/cloudflare/worker-assets.js +22 -47
  68. package/lib/cloudflare/worker-assets.js.map +1 -1
  69. package/lib/cloudflare/worker-bundle.d.ts +6 -0
  70. package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
  71. package/lib/cloudflare/worker-bundle.js +34 -10
  72. package/lib/cloudflare/worker-bundle.js.map +1 -1
  73. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  74. package/lib/cloudflare/worker-metadata.js +1 -0
  75. package/lib/cloudflare/worker-metadata.js.map +1 -1
  76. package/lib/cloudflare/worker.d.ts.map +1 -1
  77. package/lib/cloudflare/worker.js +24 -6
  78. package/lib/cloudflare/worker.js.map +1 -1
  79. package/lib/cloudflare/wrangler.json.d.ts +4 -6
  80. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  81. package/lib/cloudflare/wrangler.json.js +20 -15
  82. package/lib/cloudflare/wrangler.json.js.map +1 -1
  83. package/lib/context.d.ts +3 -1
  84. package/lib/context.d.ts.map +1 -1
  85. package/lib/context.js +2 -2
  86. package/lib/context.js.map +1 -1
  87. package/lib/destroy.d.ts +8 -0
  88. package/lib/destroy.d.ts.map +1 -1
  89. package/lib/destroy.js +20 -4
  90. package/lib/destroy.js.map +1 -1
  91. package/lib/planetscale/api/client.gen.d.ts +1750 -0
  92. package/lib/planetscale/api/client.gen.d.ts.map +1 -0
  93. package/lib/planetscale/api/client.gen.js +2401 -0
  94. package/lib/planetscale/api/client.gen.js.map +1 -0
  95. package/lib/planetscale/api/index.d.ts +3 -0
  96. package/lib/planetscale/api/index.d.ts.map +1 -0
  97. package/lib/planetscale/api/index.js +4 -0
  98. package/lib/planetscale/api/index.js.map +1 -0
  99. package/lib/planetscale/api/types.gen.d.ts +12156 -0
  100. package/lib/planetscale/api/types.gen.d.ts.map +1 -0
  101. package/lib/planetscale/api/types.gen.js +2 -0
  102. package/lib/planetscale/api/types.gen.js.map +1 -0
  103. package/lib/planetscale/branch.d.ts +5 -8
  104. package/lib/planetscale/branch.d.ts.map +1 -1
  105. package/lib/planetscale/branch.js +122 -97
  106. package/lib/planetscale/branch.js.map +1 -1
  107. package/lib/planetscale/database.d.ts +24 -11
  108. package/lib/planetscale/database.d.ts.map +1 -1
  109. package/lib/planetscale/database.js +163 -118
  110. package/lib/planetscale/database.js.map +1 -1
  111. package/lib/planetscale/index.d.ts +2 -1
  112. package/lib/planetscale/index.d.ts.map +1 -1
  113. package/lib/planetscale/index.js +2 -1
  114. package/lib/planetscale/index.js.map +1 -1
  115. package/lib/planetscale/password.d.ts +7 -8
  116. package/lib/planetscale/password.d.ts.map +1 -1
  117. package/lib/planetscale/password.js +74 -57
  118. package/lib/planetscale/password.js.map +1 -1
  119. package/lib/planetscale/role.d.ts +126 -0
  120. package/lib/planetscale/role.d.ts.map +1 -0
  121. package/lib/planetscale/role.js +144 -0
  122. package/lib/planetscale/role.js.map +1 -0
  123. package/lib/planetscale/utils.d.ts +23 -7
  124. package/lib/planetscale/utils.d.ts.map +1 -1
  125. package/lib/planetscale/utils.js +169 -75
  126. package/lib/planetscale/utils.js.map +1 -1
  127. package/lib/scope.d.ts +5 -1
  128. package/lib/scope.d.ts.map +1 -1
  129. package/lib/scope.js +11 -3
  130. package/lib/scope.js.map +1 -1
  131. package/lib/secret.d.ts +0 -3
  132. package/lib/secret.d.ts.map +1 -1
  133. package/lib/secret.js +0 -2
  134. package/lib/secret.js.map +1 -1
  135. package/lib/state/cloudflare-state-store.js +3 -3
  136. package/lib/state/cloudflare-state-store.js.map +1 -1
  137. package/lib/test/bun.d.ts +2 -2
  138. package/lib/test/bun.d.ts.map +1 -1
  139. package/lib/test/vitest.d.ts +2 -2
  140. package/lib/test/vitest.d.ts.map +1 -1
  141. package/lib/test/vitest.js +4 -4
  142. package/lib/test/vitest.js.map +1 -1
  143. package/lib/util/content-type.js +2 -2
  144. package/lib/util/content-type.js.map +1 -1
  145. package/lib/util/dedent.d.ts +3 -14
  146. package/lib/util/dedent.d.ts.map +1 -1
  147. package/lib/util/dedent.js +33 -45
  148. package/lib/util/dedent.js.map +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 +8 -3
  153. package/src/alchemy.ts +14 -6
  154. package/src/apply.ts +13 -1
  155. package/src/cloudflare/bindings.ts +3 -1
  156. package/src/cloudflare/bucket.ts +3 -1
  157. package/src/cloudflare/bundle/cloudflare-internal.ts +42 -0
  158. package/src/cloudflare/bundle/nodejs-builtin-modules.ts +14 -0
  159. package/src/cloudflare/bundle/plugin-als-external.ts +13 -0
  160. package/src/cloudflare/bundle/plugin-hybrid-node-compat.ts +29 -28
  161. package/src/cloudflare/bundle/plugin-node-compat.ts +114 -0
  162. package/src/cloudflare/bundle/plugin-wasm.ts +34 -14
  163. package/src/cloudflare/compatibility-date.gen.ts +1 -1
  164. package/src/cloudflare/d1-local-migrations.ts +2 -2
  165. package/src/cloudflare/durable-object-namespace.ts +1 -0
  166. package/src/cloudflare/hyperdrive.ts +136 -95
  167. package/src/cloudflare/index.ts +1 -0
  168. package/src/cloudflare/next.ts +123 -0
  169. package/src/cloudflare/website.ts +88 -89
  170. package/src/cloudflare/worker-assets.ts +43 -105
  171. package/src/cloudflare/worker-bundle.ts +46 -9
  172. package/src/cloudflare/worker-metadata.ts +2 -0
  173. package/src/cloudflare/worker.ts +35 -7
  174. package/src/cloudflare/wrangler.json.ts +114 -113
  175. package/src/context.ts +5 -3
  176. package/src/destroy.ts +26 -8
  177. package/src/planetscale/api/client.gen.ts +2882 -0
  178. package/src/planetscale/api/index.ts +4 -0
  179. package/src/planetscale/api/types.gen.ts +13041 -0
  180. package/src/planetscale/branch.ts +170 -162
  181. package/src/planetscale/database.ts +263 -243
  182. package/src/planetscale/index.ts +2 -1
  183. package/src/planetscale/password.ts +89 -86
  184. package/src/planetscale/role.ts +278 -0
  185. package/src/planetscale/utils.ts +289 -128
  186. package/src/scope.ts +12 -3
  187. package/src/secret.ts +0 -4
  188. package/src/state/cloudflare-state-store.ts +4 -4
  189. package/src/test/bun.ts +2 -2
  190. package/src/test/vitest.ts +6 -6
  191. package/src/util/content-type.ts +2 -2
  192. package/src/util/dedent.ts +34 -50
  193. package/src/util/memoize.ts +13 -1
  194. package/templates/astro/alchemy.run.ts +1 -3
  195. package/templates/hono/alchemy.run.ts +0 -1
  196. package/templates/nextjs/README.md +85 -0
  197. package/templates/nextjs/_env +1 -0
  198. package/templates/nextjs/_env.example +1 -0
  199. package/templates/nextjs/_gitignore +46 -0
  200. package/templates/nextjs/alchemy.run.ts +16 -0
  201. package/templates/nextjs/eslint.config.mjs +16 -0
  202. package/templates/nextjs/next-env.d.ts +5 -0
  203. package/templates/nextjs/next.config.ts +10 -0
  204. package/templates/nextjs/open-next.config.ts +5 -0
  205. package/templates/nextjs/package.json +31 -0
  206. package/templates/nextjs/src/app/api/hello/route.ts +19 -0
  207. package/templates/nextjs/src/app/favicon.ico +0 -0
  208. package/templates/nextjs/src/app/globals.css +72 -0
  209. package/templates/nextjs/src/app/layout.tsx +19 -0
  210. package/templates/nextjs/src/app/page.tsx +55 -0
  211. package/templates/nextjs/tsconfig.json +33 -0
  212. package/templates/nextjs/types/env.d.ts +14 -0
  213. package/templates/nuxt/alchemy.run.ts +1 -3
  214. package/templates/react-router/alchemy.run.ts +1 -3
  215. package/templates/rwsdk/alchemy.run.ts +0 -2
  216. package/templates/sveltekit/alchemy.run.ts +1 -3
  217. package/templates/tanstack-start/alchemy.run.ts +1 -3
  218. package/templates/typescript/alchemy.run.ts +0 -1
  219. package/templates/vite/alchemy.run.ts +0 -1
  220. package/workers/cloudflare-state-store.js +58 -58
  221. package/lib/cloudflare/bundle/externals.d.ts +0 -3
  222. package/lib/cloudflare/bundle/externals.d.ts.map +0 -1
  223. package/lib/cloudflare/bundle/externals.js +0 -35
  224. package/lib/cloudflare/bundle/externals.js.map +0 -1
  225. package/lib/cloudflare/bundle/plugin-compat-warning.d.ts +0 -10
  226. package/lib/cloudflare/bundle/plugin-compat-warning.d.ts.map +0 -1
  227. package/lib/cloudflare/bundle/plugin-compat-warning.js +0 -67
  228. package/lib/cloudflare/bundle/plugin-compat-warning.js.map +0 -1
  229. package/lib/planetscale/api.d.ts +0 -53
  230. package/lib/planetscale/api.d.ts.map +0 -1
  231. package/lib/planetscale/api.js +0 -84
  232. package/lib/planetscale/api.js.map +0 -1
  233. package/src/cloudflare/bundle/externals.ts +0 -36
  234. package/src/cloudflare/bundle/plugin-compat-warning.ts +0 -94
  235. package/src/planetscale/api.ts +0 -115
@@ -2,10 +2,10 @@ import crypto from "node:crypto";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { AsyncQueue } from "../util/async-queue.ts";
5
- import { getContentType } from "../util/content-type.ts";
6
- import { CloudflareApiError } from "./api-error.ts";
5
+ import { withExponentialBackoff } from "../util/retry.ts";
6
+ import { extractCloudflareResult } from "./api-response.ts";
7
7
  import type { CloudflareApi } from "./api.ts";
8
- import type { Assets } from "./assets.ts";
8
+ import type { AssetFile, Assets } from "./assets.ts";
9
9
  import type { AssetsConfig, WorkerProps } from "./worker.ts";
10
10
 
11
11
  export interface AssetUploadResult {
@@ -21,32 +21,6 @@ interface FileMetadata {
21
21
  size: number;
22
22
  }
23
23
 
24
- /**
25
- * Response from the assets upload session API
26
- */
27
- interface UploadSessionResponse {
28
- result: {
29
- jwt: string;
30
- buckets: string[][];
31
- };
32
- success: boolean;
33
- errors: any[];
34
- messages: any[];
35
- }
36
-
37
- /**
38
- * Response from the file upload API
39
- */
40
- interface UploadResponse {
41
- result: {
42
- jwt: string;
43
- buckets?: string[][];
44
- };
45
- success: boolean;
46
- errors: any[];
47
- messages: any[];
48
- }
49
-
50
24
  /**
51
25
  * Uploads assets to Cloudflare and returns a completion token
52
26
  *
@@ -73,64 +47,41 @@ export async function uploadAssets(
73
47
  // Process the assets configuration once at the beginning
74
48
  const processedConfig = createAssetConfig(assetConfig);
75
49
 
76
- const { manifest, filePathsByHash } = await prepareAssetManifest(assets);
50
+ const { manifest, filesByHash } = await prepareAssetManifest(assets);
77
51
 
78
52
  // Start the upload session
79
- const uploadSessionResponse = await api.post(
80
- namespace
81
- ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${namespace}/scripts/${workerName}/assets-upload-session`
82
- : `/accounts/${api.accountId}/workers/scripts/${workerName}/assets-upload-session`,
83
- JSON.stringify({ manifest }),
84
- {
85
- headers: { "Content-Type": "application/json" },
86
- },
53
+ const uploadSession = await extractCloudflareResult<{
54
+ jwt: string;
55
+ buckets: string[][];
56
+ }>(
57
+ `create assets upload session for worker "${workerName}"`,
58
+ api.post(
59
+ namespace
60
+ ? `/accounts/${api.accountId}/workers/dispatch/namespaces/${namespace}/scripts/${workerName}/assets-upload-session`
61
+ : `/accounts/${api.accountId}/workers/scripts/${workerName}/assets-upload-session`,
62
+ { manifest },
63
+ ),
87
64
  );
88
-
89
- if (!uploadSessionResponse.ok) {
90
- throw new Error(
91
- `Failed to start assets upload session: ${uploadSessionResponse.status} ${uploadSessionResponse.statusText} - ${await uploadSessionResponse.text()}`,
92
- );
93
- }
94
-
95
- const sessionData =
96
- (await uploadSessionResponse.json()) as UploadSessionResponse;
97
-
98
- if (!sessionData?.success) {
99
- if (sessionData?.errors) {
100
- throw new CloudflareApiError(
101
- `Failed to start assets upload session:\n${sessionData.errors
102
- .map((error) => `- ${error.code}: ${error.message}`)
103
- .join("\n")}`,
104
- uploadSessionResponse,
105
- );
106
- }
107
- throw new CloudflareApiError(
108
- `Failed to start assets upload session: ${uploadSessionResponse.statusText}`,
109
- uploadSessionResponse,
110
- );
111
- }
112
-
113
65
  // If there are no buckets, assets are already uploaded or empty
114
- if (!sessionData.result.buckets || sessionData.result.buckets.length === 0) {
66
+ if (!uploadSession.buckets || uploadSession.buckets.length === 0) {
115
67
  return {
116
- completionToken: sessionData.result.jwt,
68
+ completionToken: uploadSession.jwt,
117
69
  assetConfig: processedConfig,
118
70
  };
119
71
  }
120
72
 
121
73
  // Upload the files in batches as specified by the API
122
- let completionToken = sessionData.result.jwt;
123
- const buckets = sessionData.result.buckets;
74
+ let completionToken = uploadSession.jwt;
75
+ const buckets = uploadSession.buckets;
124
76
 
125
77
  const queue = new AsyncQueue(3);
126
78
  await Promise.all(
127
79
  buckets.map((bucket) =>
128
80
  queue.add(async () => {
129
- const jwt = await uploadBucket(
130
- api,
131
- sessionData.result.jwt,
132
- bucket,
133
- filePathsByHash,
81
+ const jwt = await withExponentialBackoff(
82
+ async () =>
83
+ await uploadBucket(api, uploadSession.jwt, bucket, filesByHash),
84
+ () => true,
134
85
  );
135
86
  if (jwt) {
136
87
  completionToken = jwt;
@@ -183,68 +134,55 @@ export function createAssetConfig(config?: AssetsConfig): AssetsConfig {
183
134
  * Prepares the asset manifest for the assets upload session
184
135
  *
185
136
  * @param assets Assets resource containing files to upload
186
- * @returns Asset manifest and file paths by hash
137
+ * @returns Asset manifest and files by hash
187
138
  */
188
139
  export async function prepareAssetManifest(assets: Assets) {
189
140
  const manifest: Record<string, FileMetadata> = {};
190
- const filePathsByHash = new Map<string, string>();
141
+ const filesByHash = new Map<string, AssetFile>();
191
142
  await Promise.all(
192
143
  assets.files.map(async (file) => {
193
144
  const { hash, size } = await calculateFileMetadata(file.filePath);
194
145
  const key = file.path.startsWith("/") ? file.path : `/${file.path}`;
195
146
  manifest[key] = { hash, size };
196
- filePathsByHash.set(hash, file.filePath);
147
+ filesByHash.set(hash, file);
197
148
  }),
198
149
  );
199
- return { manifest, filePathsByHash };
150
+ return { manifest, filesByHash };
200
151
  }
201
152
 
202
153
  async function uploadBucket(
203
154
  api: CloudflareApi,
204
155
  jwt: string,
205
156
  bucket: string[],
206
- files: Map<string, string>,
157
+ filesByHash: Map<string, AssetFile>,
207
158
  ) {
208
159
  const formData = new FormData();
209
160
  await Promise.all(
210
161
  bucket.map(async (fileHash) => {
211
- const filePath = files.get(fileHash);
212
- if (!filePath) {
162
+ const file = filesByHash.get(fileHash);
163
+ if (!file) {
213
164
  throw new Error(`Could not find file with hash ${fileHash}`);
214
165
  }
215
- const fileContent = await fs.readFile(filePath);
166
+ const fileContent = await fs.readFile(file.filePath);
216
167
  const blob = new Blob([fileContent.toString("base64")], {
217
- type: getContentType(filePath) ?? "application/null",
168
+ type: file.contentType,
218
169
  });
219
170
  formData.append(fileHash, blob, fileHash);
220
171
  }),
221
172
  );
222
- const uploadResponse = await api.post(
223
- `/accounts/${api.accountId}/workers/assets/upload?base64=true`,
224
- formData,
225
- {
226
- headers: {
227
- Authorization: `Bearer ${jwt}`,
228
- "Content-Type": "multipart/form-data",
173
+ const uploadResult = await extractCloudflareResult<{ jwt?: string }>(
174
+ "upload asset files",
175
+ api.post(
176
+ `/accounts/${api.accountId}/workers/assets/upload?base64=true`,
177
+ formData,
178
+ {
179
+ headers: {
180
+ Authorization: `Bearer ${jwt}`,
181
+ },
229
182
  },
230
- },
183
+ ),
231
184
  );
232
-
233
- if (!uploadResponse.ok) {
234
- throw new Error(
235
- `Failed to upload asset files: ${uploadResponse.status} ${uploadResponse.statusText}`,
236
- );
237
- }
238
-
239
- const uploadData = (await uploadResponse.json()) as UploadResponse;
240
- if (!uploadData.success) {
241
- const error = uploadData.errors[0];
242
- const message = error.message;
243
- const code = error.code;
244
- throw new Error(`Failed to upload asset files: ${message} (Code: ${code})`);
245
- }
246
-
247
- return uploadData.result.jwt;
185
+ return uploadResult.jwt;
248
186
  }
249
187
 
250
188
  /**
@@ -4,11 +4,12 @@ import { err, ok, type Result } from "neverthrow";
4
4
  import fs from "node:fs/promises";
5
5
  import path from "pathe";
6
6
  import { logger } from "../util/logger.ts";
7
- import { external, external_als } from "./bundle/externals.ts";
7
+ import { cloudflareInternalPlugin } from "./bundle/cloudflare-internal.ts";
8
8
  import { esbuildPluginAlias } from "./bundle/plugin-alias.ts";
9
- import { esbuildPluginCompatWarning } from "./bundle/plugin-compat-warning.ts";
9
+ import { esbuildPluginAsyncLocalStorage } from "./bundle/plugin-als-external.ts";
10
10
  import { createHotReloadPlugin } from "./bundle/plugin-hot-reload.ts";
11
11
  import { esbuildPluginHybridNodeCompat } from "./bundle/plugin-hybrid-node-compat.ts";
12
+ import { esbuildPluginNodeCompatImports } from "./bundle/plugin-node-compat.ts";
12
13
  import { createWasmPlugin } from "./bundle/plugin-wasm.ts";
13
14
  import { validateNodeCompat } from "./bundle/validate-node-compat.ts";
14
15
 
@@ -76,6 +77,8 @@ export function normalizeWorkerBundle(props: {
76
77
  cwd: props.cwd,
77
78
  outdir: props.outdir,
78
79
  sourcemap: props.sourceMap !== false ? true : undefined,
80
+ compatibilityDate: props.compatibilityDate,
81
+ compatibilityFlags: props.compatibilityFlags,
79
82
  ...props.bundle,
80
83
  }),
81
84
  );
@@ -248,6 +251,8 @@ export namespace WorkerBundleSource {
248
251
  entrypoint: string;
249
252
  cwd: string;
250
253
  outdir: string;
254
+ compatibilityDate: string;
255
+ compatibilityFlags: string[];
251
256
  }
252
257
 
253
258
  export class ESBuild implements WorkerBundleSource {
@@ -328,6 +333,8 @@ export namespace WorkerBundleSource {
328
333
  nodeCompat,
329
334
  cwd,
330
335
  format,
336
+ compatibilityDate: _compatibilityDate,
337
+ compatibilityFlags: _compatibilityFlags,
331
338
  ...props
332
339
  } = this.props;
333
340
  return {
@@ -356,19 +363,49 @@ export namespace WorkerBundleSource {
356
363
  conditions: props.conditions ?? ["workerd", "worker", "browser"],
357
364
  plugins: [
358
365
  esbuildPluginAlias(props.alias ?? {}, this.props.cwd),
359
- nodeCompat === "v2"
360
- ? esbuildPluginHybridNodeCompat()
361
- : esbuildPluginCompatWarning(nodeCompat ?? null),
366
+ cloudflareInternalPlugin,
367
+ ...this.getNodeJSCompatPlugins({
368
+ mode: nodeCompat,
369
+ compatibilityDate: this.props.compatibilityDate,
370
+ compatibilityFlags: this.props.compatibilityFlags,
371
+ }),
362
372
  ...(props.plugins ?? []),
363
373
  ...additionalPlugins,
364
374
  ],
365
- external: [
366
- ...(nodeCompat === "als" ? external_als : external),
367
- ...(props.external ?? []),
368
- ],
375
+ external: [...(props.external ?? [])],
369
376
  } satisfies esbuild.BuildOptions;
370
377
  }
371
378
 
379
+ /**
380
+ * Returns the list of ESBuild plugins to use for a given compat mode.
381
+ */
382
+ private getNodeJSCompatPlugins({
383
+ mode,
384
+ compatibilityDate,
385
+ compatibilityFlags,
386
+ }: {
387
+ mode: "als" | "v2" | null;
388
+ compatibilityDate?: string;
389
+ compatibilityFlags?: string[];
390
+ }): esbuild.Plugin[] {
391
+ switch (mode) {
392
+ case "als":
393
+ return [
394
+ esbuildPluginAsyncLocalStorage,
395
+ esbuildPluginNodeCompatImports(mode),
396
+ ];
397
+ case "v2":
398
+ return [
399
+ esbuildPluginHybridNodeCompat({
400
+ compatibilityDate,
401
+ compatibilityFlags,
402
+ }),
403
+ ];
404
+ case null:
405
+ return [esbuildPluginNodeCompatImports(mode)];
406
+ }
407
+ }
408
+
372
409
  private async formatBuildOutput(
373
410
  metafile: esbuild.Metafile,
374
411
  ): Promise<WorkerBundle> {
@@ -442,6 +442,8 @@ export async function prepareWorkerMetadata(
442
442
  type: "r2_bucket",
443
443
  name: bindingName,
444
444
  bucket_name: binding.name,
445
+ jurisdiction:
446
+ binding.jurisdiction === "default" ? undefined : binding.jurisdiction,
445
447
  });
446
448
  } else if (binding.type === "secrets_store_secret") {
447
449
  meta.bindings.push({
@@ -709,14 +709,29 @@ const _Worker = Resource(
709
709
  id: string,
710
710
  props: WorkerProps<B>,
711
711
  ) {
712
- const adopt = props.adopt ?? this.scope.adopt;
712
+ let adopt = props.adopt ?? this.scope.adopt;
713
+ const workerName =
714
+ props.name ?? this.output?.name ?? this.scope.createPhysicalName(id);
715
+ if (this.phase === "create" && !props.adopt) {
716
+ // it is possible that this worker already exists and was created by the old Website wrapper with a nested scope
717
+ // we need to detect this and set adopt=true so that the previous version will be adopted seamlessly
718
+
719
+ // HEURISTIC: `this.scope` would previously be the nested outer scope alchemy.run
720
+ // so, if `this.scope` has a child of `wrangler.jsonc`, then it is likely that it was created by the old Website wrapper with a nested scope
721
+ if (await this.scope.has("wrangler.jsonc", "cloudflare::WranglerJson")) {
722
+ logger.warn(
723
+ `Migrating Worker '${workerName}' from the legacy Website wrapper.`,
724
+ );
725
+ props.adopt = true;
726
+ }
727
+ }
728
+
713
729
  const options = (() => {
714
730
  if (props.projectRoot) {
715
731
  logger.warn("projectRoot is deprecated, use cwd instead");
716
732
  props.cwd = props.projectRoot;
717
733
  }
718
- const name =
719
- props.name ?? this.output?.name ?? this.scope.createPhysicalName(id);
734
+
720
735
  const cwd = path.resolve(props.cwd ?? process.cwd());
721
736
  const compatibilityDate =
722
737
  props.compatibilityDate ?? DEFAULT_COMPATIBILITY_DATE;
@@ -739,7 +754,8 @@ const _Worker = Resource(
739
754
  cwd,
740
755
  compatibilityDate,
741
756
  compatibilityFlags,
742
- outdir: props.bundle?.outdir ?? path.join(cwd, ".alchemy", "out", name),
757
+ outdir:
758
+ props.bundle?.outdir ?? path.join(cwd, ".alchemy", "out", workerName),
743
759
  sourceMap: "sourceMap" in props ? props.sourceMap : undefined,
744
760
  });
745
761
 
@@ -756,19 +772,19 @@ const _Worker = Resource(
756
772
  containers.push(binding);
757
773
  } else if (
758
774
  binding.type === "workflow" &&
759
- (!binding.scriptName || binding.scriptName === name)
775
+ (!binding.scriptName || binding.scriptName === workerName)
760
776
  ) {
761
777
  workflows.push(binding);
762
778
  } else if (
763
779
  binding.type === "durable_object_namespace" &&
764
- (!binding.scriptName || binding.scriptName === name)
780
+ (!binding.scriptName || binding.scriptName === workerName)
765
781
  ) {
766
782
  durableObjects.push(binding);
767
783
  }
768
784
  }
769
785
 
770
786
  return {
771
- name,
787
+ name: workerName,
772
788
  cwd,
773
789
  compatibilityDate,
774
790
  compatibilityFlags,
@@ -780,7 +796,19 @@ const _Worker = Resource(
780
796
  durableObjects,
781
797
  };
782
798
  })();
799
+
783
800
  if (this.phase === "delete") {
801
+ // Heuristic: we must detect the case where this is the Worker wrapped in the old Website nested scope and not delete it
802
+ // we must not delete it because what we're actually doing is migrating to a flat worker
803
+ // we will achieve this by checking for a sibling resource with ID wrangler.jsonc and type cloudflare::WranglerJson
804
+ if (await this.scope.has("wrangler.jsonc", "cloudflare::WranglerJson")) {
805
+ // skip deletion
806
+ logger.warn(
807
+ `Migrating Worker '${options.name}' from the legacy Website wrapper.`,
808
+ );
809
+ return this.destroy(true);
810
+ }
811
+
784
812
  if (options.bundle.isOk()) {
785
813
  await options.bundle.value.delete?.();
786
814
  }