alchemy 0.63.1 → 0.64.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 (157) hide show
  1. package/bin/alchemy.js +96 -86
  2. package/lib/alchemy.d.ts +4 -0
  3. package/lib/alchemy.d.ts.map +1 -1
  4. package/lib/alchemy.js +10 -4
  5. package/lib/alchemy.js.map +1 -1
  6. package/lib/apply.d.ts +3 -0
  7. package/lib/apply.d.ts.map +1 -1
  8. package/lib/apply.js +11 -1
  9. package/lib/apply.js.map +1 -1
  10. package/lib/cloudflare/bindings.d.ts +3 -1
  11. package/lib/cloudflare/bindings.d.ts.map +1 -1
  12. package/lib/cloudflare/bucket.d.ts +2 -1
  13. package/lib/cloudflare/bucket.d.ts.map +1 -1
  14. package/lib/cloudflare/bucket.js.map +1 -1
  15. package/lib/cloudflare/bundle/cloudflare-internal.d.ts +6 -0
  16. package/lib/cloudflare/bundle/cloudflare-internal.d.ts.map +1 -0
  17. package/lib/cloudflare/bundle/cloudflare-internal.js +39 -0
  18. package/lib/cloudflare/bundle/cloudflare-internal.js.map +1 -0
  19. package/lib/cloudflare/bundle/nodejs-builtin-modules.d.ts +2 -0
  20. package/lib/cloudflare/bundle/nodejs-builtin-modules.d.ts.map +1 -0
  21. package/lib/cloudflare/bundle/nodejs-builtin-modules.js +9 -0
  22. package/lib/cloudflare/bundle/nodejs-builtin-modules.js.map +1 -0
  23. package/lib/cloudflare/bundle/plugin-als-external.d.ts +6 -0
  24. package/lib/cloudflare/bundle/plugin-als-external.d.ts.map +1 -0
  25. package/lib/cloudflare/bundle/plugin-als-external.js +12 -0
  26. package/lib/cloudflare/bundle/plugin-als-external.js.map +1 -0
  27. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.d.ts +4 -1
  28. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.d.ts.map +1 -1
  29. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js +23 -23
  30. package/lib/cloudflare/bundle/plugin-hybrid-node-compat.js.map +1 -1
  31. package/lib/cloudflare/bundle/plugin-node-compat.d.ts +11 -0
  32. package/lib/cloudflare/bundle/plugin-node-compat.d.ts.map +1 -0
  33. package/lib/cloudflare/bundle/plugin-node-compat.js +93 -0
  34. package/lib/cloudflare/bundle/plugin-node-compat.js.map +1 -0
  35. package/lib/cloudflare/d1-local-migrations.js.map +1 -1
  36. package/lib/cloudflare/durable-object-namespace.d.ts +1 -0
  37. package/lib/cloudflare/durable-object-namespace.d.ts.map +1 -1
  38. package/lib/cloudflare/durable-object-namespace.js.map +1 -1
  39. package/lib/cloudflare/website.d.ts.map +1 -1
  40. package/lib/cloudflare/website.js +80 -82
  41. package/lib/cloudflare/website.js.map +1 -1
  42. package/lib/cloudflare/worker-bundle.d.ts +6 -0
  43. package/lib/cloudflare/worker-bundle.d.ts.map +1 -1
  44. package/lib/cloudflare/worker-bundle.js +34 -10
  45. package/lib/cloudflare/worker-bundle.js.map +1 -1
  46. package/lib/cloudflare/worker-metadata.d.ts.map +1 -1
  47. package/lib/cloudflare/worker-metadata.js +1 -0
  48. package/lib/cloudflare/worker-metadata.js.map +1 -1
  49. package/lib/cloudflare/worker.d.ts.map +1 -1
  50. package/lib/cloudflare/worker.js +24 -6
  51. package/lib/cloudflare/worker.js.map +1 -1
  52. package/lib/cloudflare/wrangler.json.d.ts +2 -4
  53. package/lib/cloudflare/wrangler.json.d.ts.map +1 -1
  54. package/lib/cloudflare/wrangler.json.js +16 -14
  55. package/lib/cloudflare/wrangler.json.js.map +1 -1
  56. package/lib/context.d.ts +3 -1
  57. package/lib/context.d.ts.map +1 -1
  58. package/lib/context.js +2 -2
  59. package/lib/context.js.map +1 -1
  60. package/lib/destroy.d.ts +8 -0
  61. package/lib/destroy.d.ts.map +1 -1
  62. package/lib/destroy.js +20 -4
  63. package/lib/destroy.js.map +1 -1
  64. package/lib/planetscale/api/client.gen.d.ts +1750 -0
  65. package/lib/planetscale/api/client.gen.d.ts.map +1 -0
  66. package/lib/planetscale/api/client.gen.js +2401 -0
  67. package/lib/planetscale/api/client.gen.js.map +1 -0
  68. package/lib/planetscale/api/index.d.ts +3 -0
  69. package/lib/planetscale/api/index.d.ts.map +1 -0
  70. package/lib/planetscale/api/index.js +4 -0
  71. package/lib/planetscale/api/index.js.map +1 -0
  72. package/lib/planetscale/api/types.gen.d.ts +12156 -0
  73. package/lib/planetscale/api/types.gen.d.ts.map +1 -0
  74. package/lib/planetscale/api/types.gen.js +2 -0
  75. package/lib/planetscale/api/types.gen.js.map +1 -0
  76. package/lib/planetscale/branch.d.ts +5 -8
  77. package/lib/planetscale/branch.d.ts.map +1 -1
  78. package/lib/planetscale/branch.js +111 -97
  79. package/lib/planetscale/branch.js.map +1 -1
  80. package/lib/planetscale/database.d.ts +2 -6
  81. package/lib/planetscale/database.d.ts.map +1 -1
  82. package/lib/planetscale/database.js +157 -118
  83. package/lib/planetscale/database.js.map +1 -1
  84. package/lib/planetscale/index.d.ts +1 -1
  85. package/lib/planetscale/index.d.ts.map +1 -1
  86. package/lib/planetscale/index.js +1 -1
  87. package/lib/planetscale/index.js.map +1 -1
  88. package/lib/planetscale/password.d.ts +4 -7
  89. package/lib/planetscale/password.d.ts.map +1 -1
  90. package/lib/planetscale/password.js +71 -56
  91. package/lib/planetscale/password.js.map +1 -1
  92. package/lib/planetscale/utils.d.ts +8 -6
  93. package/lib/planetscale/utils.d.ts.map +1 -1
  94. package/lib/planetscale/utils.js +85 -70
  95. package/lib/planetscale/utils.js.map +1 -1
  96. package/lib/scope.d.ts +5 -1
  97. package/lib/scope.d.ts.map +1 -1
  98. package/lib/scope.js +11 -3
  99. package/lib/scope.js.map +1 -1
  100. package/lib/util/dedent.d.ts +3 -14
  101. package/lib/util/dedent.d.ts.map +1 -1
  102. package/lib/util/dedent.js +28 -52
  103. package/lib/util/dedent.js.map +1 -1
  104. package/package.json +4 -3
  105. package/src/alchemy.ts +14 -6
  106. package/src/apply.ts +13 -1
  107. package/src/cloudflare/bindings.ts +3 -1
  108. package/src/cloudflare/bucket.ts +3 -1
  109. package/src/cloudflare/bundle/cloudflare-internal.ts +42 -0
  110. package/src/cloudflare/bundle/nodejs-builtin-modules.ts +14 -0
  111. package/src/cloudflare/bundle/plugin-als-external.ts +13 -0
  112. package/src/cloudflare/bundle/plugin-hybrid-node-compat.ts +29 -28
  113. package/src/cloudflare/bundle/plugin-node-compat.ts +114 -0
  114. package/src/cloudflare/d1-local-migrations.ts +2 -2
  115. package/src/cloudflare/durable-object-namespace.ts +1 -0
  116. package/src/cloudflare/website.ts +88 -89
  117. package/src/cloudflare/worker-bundle.ts +46 -9
  118. package/src/cloudflare/worker-metadata.ts +2 -0
  119. package/src/cloudflare/worker.ts +35 -7
  120. package/src/cloudflare/wrangler.json.ts +108 -110
  121. package/src/context.ts +5 -3
  122. package/src/destroy.ts +26 -8
  123. package/src/planetscale/api/client.gen.ts +2882 -0
  124. package/src/planetscale/api/index.ts +4 -0
  125. package/src/planetscale/api/types.gen.ts +13041 -0
  126. package/src/planetscale/branch.ts +152 -169
  127. package/src/planetscale/database.ts +195 -204
  128. package/src/planetscale/index.ts +1 -1
  129. package/src/planetscale/password.ts +86 -85
  130. package/src/planetscale/utils.ts +141 -122
  131. package/src/scope.ts +12 -3
  132. package/src/util/dedent.ts +33 -61
  133. package/templates/astro/alchemy.run.ts +1 -3
  134. package/templates/hono/alchemy.run.ts +0 -1
  135. package/templates/nuxt/alchemy.run.ts +1 -3
  136. package/templates/react-router/alchemy.run.ts +1 -3
  137. package/templates/rwsdk/alchemy.run.ts +0 -2
  138. package/templates/sveltekit/alchemy.run.ts +1 -3
  139. package/templates/tanstack-start/alchemy.run.ts +1 -3
  140. package/templates/typescript/alchemy.run.ts +0 -1
  141. package/templates/vite/alchemy.run.ts +0 -1
  142. package/workers/cloudflare-state-store.js +58 -58
  143. package/lib/cloudflare/bundle/externals.d.ts +0 -3
  144. package/lib/cloudflare/bundle/externals.d.ts.map +0 -1
  145. package/lib/cloudflare/bundle/externals.js +0 -35
  146. package/lib/cloudflare/bundle/externals.js.map +0 -1
  147. package/lib/cloudflare/bundle/plugin-compat-warning.d.ts +0 -10
  148. package/lib/cloudflare/bundle/plugin-compat-warning.d.ts.map +0 -1
  149. package/lib/cloudflare/bundle/plugin-compat-warning.js +0 -67
  150. package/lib/cloudflare/bundle/plugin-compat-warning.js.map +0 -1
  151. package/lib/planetscale/api.d.ts +0 -53
  152. package/lib/planetscale/api.d.ts.map +0 -1
  153. package/lib/planetscale/api.js +0 -84
  154. package/lib/planetscale/api.js.map +0 -1
  155. package/src/cloudflare/bundle/externals.ts +0 -36
  156. package/src/cloudflare/bundle/plugin-compat-warning.ts +0 -94
  157. package/src/planetscale/api.ts +0 -115
@@ -0,0 +1,114 @@
1
+ import type { Plugin } from "esbuild";
2
+ import type { NodeJSCompatMode } from "miniflare";
3
+ import { relative } from "node:path";
4
+ import chalk from "picocolors";
5
+ import { dedent } from "../../util/dedent.ts";
6
+ import { logger } from "../../util/logger.ts";
7
+ import { NODEJS_MODULES_RE } from "./nodejs-builtin-modules.ts";
8
+
9
+ /**
10
+ * An esbuild plugin that will:
11
+ * - mark any `node:...` imports as external
12
+ * - warn if there are node imports (if not in v1 mode)
13
+ *
14
+ * Applies to: null, als, legacy and v1 modes.
15
+ */
16
+ export const esbuildPluginNodeCompatImports = (
17
+ mode: NodeJSCompatMode,
18
+ ): Plugin => ({
19
+ name: "nodejs_compat-imports",
20
+ setup(pluginBuild) {
21
+ // Infinite loop detection
22
+ const seen = new Set<string>();
23
+
24
+ // Prevent multiple warnings per package
25
+ const warnedPackages = new Map<string, string[]>();
26
+
27
+ pluginBuild.onStart(() => {
28
+ seen.clear();
29
+ warnedPackages.clear();
30
+ });
31
+ pluginBuild.onResolve(
32
+ { filter: NODEJS_MODULES_RE },
33
+ async ({ path, kind, resolveDir, importer }) => {
34
+ const specifier = `${path}:${kind}:${resolveDir}:${importer}`;
35
+ if (seen.has(specifier)) {
36
+ return;
37
+ }
38
+
39
+ seen.add(specifier);
40
+ // Try to resolve this import as a normal package
41
+ const result = await pluginBuild.resolve(path, {
42
+ kind,
43
+ resolveDir,
44
+ importer,
45
+ });
46
+
47
+ if (result.errors.length > 0) {
48
+ // esbuild couldn't resolve the package
49
+ // We should warn the user, but not fail the build
50
+ const pathWarnedPackages = warnedPackages.get(path) ?? [];
51
+ pathWarnedPackages.push(importer);
52
+ warnedPackages.set(path, pathWarnedPackages);
53
+
54
+ return { external: true };
55
+ }
56
+ // This is a normal package—don't treat it specially
57
+ return result;
58
+ },
59
+ );
60
+
61
+ /**
62
+ * If we are bundling a "Service Worker" formatted Worker, imports of external modules,
63
+ * which won't be inlined/bundled by esbuild, are invalid.
64
+ *
65
+ * This `onEnd()` handler will error if it identifies node.js external imports.
66
+ */
67
+ pluginBuild.onEnd(() => {
68
+ if (
69
+ pluginBuild.initialOptions.format === "iife" &&
70
+ warnedPackages.size > 0
71
+ ) {
72
+ const paths = new Intl.ListFormat("en-US").format(
73
+ Array.from(warnedPackages.keys())
74
+ .map((p) => `"${p}"`)
75
+ .sort(),
76
+ );
77
+ return {
78
+ errors: [
79
+ {
80
+ text: dedent`
81
+ Unexpected external import of ${paths}.
82
+ Your worker has no default export, which means it is assumed to be a Service Worker format Worker.
83
+ Did you mean to create a ES Module format Worker?
84
+ If so, try adding \`export default { ... }\` in your entry-point.
85
+ See https://developers.cloudflare.com/workers/reference/migrate-to-module-workers/.
86
+ `,
87
+ },
88
+ ],
89
+ };
90
+ }
91
+ });
92
+
93
+ // Wait until the build finishes to log warnings, so that all files which import a package
94
+ // can be collated
95
+ pluginBuild.onEnd(() => {
96
+ if (mode !== "v1") {
97
+ warnedPackages.forEach((importers: string[], path: string) => {
98
+ logger.warn(
99
+ dedent`
100
+ The package "${path}" wasn't found on the file system but is built into node.
101
+ Your Worker may throw errors at runtime unless you enable the "nodejs_compat" compatibility flag. Refer to https://developers.cloudflare.com/workers/runtime-apis/nodejs/ for more details. Imported from:
102
+ ${toList(importers, pluginBuild.initialOptions.absWorkingDir)}`,
103
+ );
104
+ });
105
+ }
106
+ });
107
+ },
108
+ });
109
+
110
+ function toList(items: string[], absWorkingDir: string | undefined): string {
111
+ return items
112
+ .map((i) => ` - ${chalk.blue(relative(absWorkingDir ?? "/", i))}`)
113
+ .join("\n");
114
+ }
@@ -20,8 +20,8 @@ export const applyLocalD1Migrations = async (
20
20
  });
21
21
  try {
22
22
  await miniflare.ready;
23
- const db = await miniflare.getD1Database("DB");
24
- const session = db.withSession("first-primary");
23
+ const db: D1Database = await miniflare.getD1Database("DB");
24
+ const session: D1DatabaseSession = db.withSession("first-primary");
25
25
  await session
26
26
  .prepare(
27
27
  `CREATE TABLE IF NOT EXISTS ${options.migrationsTable} (
@@ -19,6 +19,7 @@ export type DurableObjectNamespace<T = any> = {
19
19
  environment?: string;
20
20
  sqlite?: boolean;
21
21
  namespaceId?: string;
22
+ /** @internal */
22
23
  __service__: T;
23
24
  };
24
25
 
@@ -1,7 +1,6 @@
1
1
  import assert from "node:assert";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
- import { alchemy } from "../alchemy.ts";
5
4
  import { Exec } from "../os/index.ts";
6
5
  import { Scope } from "../scope.ts";
7
6
  import { isSecret } from "../secret.ts";
@@ -255,101 +254,101 @@ export async function Website<B extends Bindings>(
255
254
  entrypoint: path.relative(paths.cwd, paths.entrypoint),
256
255
  } as WorkerProps<B> & { name: string };
257
256
 
258
- return await alchemy.run(id, { parent: Scope.current }, async (scope) => {
259
- if (!workerProps.entrypoint) {
260
- await fs.mkdir(path.dirname(paths.entrypoint), { recursive: true });
261
- const content =
262
- script ??
263
- dedent`
264
- export default {
265
- async fetch(request, env) {
266
- return new Response("Not Found", { status: 404 });
267
- },
268
- };`;
269
- await fs.writeFile(paths.entrypoint, content);
270
- }
257
+ if (!workerProps.entrypoint) {
258
+ await fs.mkdir(path.dirname(paths.entrypoint), { recursive: true });
259
+ const content =
260
+ script ??
261
+ dedent`
262
+ export default {
263
+ async fetch(request, env) {
264
+ return new Response("Not Found", { status: 404 });
265
+ },
266
+ };`;
267
+ await fs.writeFile(paths.entrypoint, content);
268
+ }
271
269
 
272
- await writeMiniflareSymlink(paths.cwd);
270
+ await writeMiniflareSymlink(paths.cwd);
273
271
 
274
- await WranglerJson("wrangler.jsonc", {
275
- path: path.relative(paths.cwd, paths.wrangler.path),
276
- worker,
277
- assets: {
278
- binding: "ASSETS",
279
- directory: path.relative(paths.cwd, paths.assets),
280
- },
281
- main: path.relative(paths.cwd, paths.wrangler.main),
282
- secrets,
283
- transform: {
284
- wrangler: props.wrangler?.transform,
285
- },
286
- });
272
+ await WranglerJson({
273
+ path: path.relative(paths.cwd, paths.wrangler.path),
274
+ worker,
275
+ assets: {
276
+ binding: "ASSETS",
277
+ directory: path.relative(paths.cwd, paths.assets),
278
+ },
279
+ main: path.relative(paths.cwd, paths.wrangler.main),
280
+ secrets,
281
+ transform: {
282
+ wrangler: props.wrangler?.transform,
283
+ },
284
+ });
287
285
 
288
- if (build && !scope.local) {
289
- await Exec("build", {
290
- cwd: path.relative(process.cwd(), paths.cwd),
291
- command: build.command,
292
- env: {
293
- ...env,
294
- ...(typeof build === "object" ? build.env : {}),
295
- NODE_ENV: "production",
296
- },
297
- memoize: typeof build === "object" ? build.memoize : undefined,
298
- });
299
- }
286
+ const scope = Scope.current;
300
287
 
301
- let url: string | undefined;
302
- if (dev && scope.local) {
303
- url = await scope.spawn(name, {
304
- cmd: typeof dev === "string" ? dev : dev.command,
305
- cwd: paths.cwd,
306
- extract: (line) => {
307
- const URL_REGEX =
308
- /http:\/\/(localhost|0\.0\.0\.0|127\.0\.0\.1|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):\d+\/?/;
309
- const match = line
310
- .replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "")
311
- .match(URL_REGEX);
312
- if (match) {
313
- return match[0];
314
- }
315
- },
316
- env: {
317
- ...Object.fromEntries(
318
- Object.entries(env ?? {}).flatMap(([key, value]) => {
319
- if (isSecret(value)) {
320
- return [[key, value.unencrypted]];
321
- }
322
- if (typeof value === "string") {
323
- return [[key, value]];
324
- }
325
- return [];
326
- }),
327
- ),
328
- ...(typeof dev === "object" ? dev.env : {}),
329
- FORCE_COLOR: "1",
330
- ...process.env,
331
- // NOTE: we must set this to ensure the user does not accidentally set `NODE_ENV=production`
332
- // which breaks `vite dev` (it won't, for example, re-write `process.env.TSS_APP_BASE` in the `.js` client side bundle)
333
- NODE_ENV: "development",
334
- },
335
- });
336
- }
288
+ if (build && !scope.local) {
289
+ await Exec(`${id}-build`, {
290
+ cwd: path.relative(process.cwd(), paths.cwd),
291
+ command: build.command,
292
+ env: {
293
+ ...env,
294
+ ...(typeof build === "object" ? build.env : {}),
295
+ NODE_ENV: "production",
296
+ },
297
+ memoize: typeof build === "object" ? build.memoize : undefined,
298
+ });
299
+ }
337
300
 
338
- return (await Worker("worker", {
339
- ...worker,
340
- bindings: {
341
- ...worker.bindings,
342
- ...(!scope.local
343
- ? {
344
- ASSETS: await Assets("assets", {
345
- path: path.relative(process.cwd(), paths.assets),
346
- }),
301
+ let url: string | undefined;
302
+ if (dev && scope.local) {
303
+ url = await scope.spawn(name, {
304
+ cmd: typeof dev === "string" ? dev : dev.command,
305
+ cwd: paths.cwd,
306
+ extract: (line) => {
307
+ const URL_REGEX =
308
+ /http:\/\/(localhost|0\.0\.0\.0|127\.0\.0\.1|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}):\d+\/?/;
309
+ const match = line
310
+ .replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "")
311
+ .match(URL_REGEX);
312
+ if (match) {
313
+ return match[0];
314
+ }
315
+ },
316
+ env: {
317
+ ...Object.fromEntries(
318
+ Object.entries(env ?? {}).flatMap(([key, value]) => {
319
+ if (isSecret(value)) {
320
+ return [[key, value.unencrypted]];
321
+ }
322
+ if (typeof value === "string") {
323
+ return [[key, value]];
347
324
  }
348
- : {}),
325
+ return [];
326
+ }),
327
+ ),
328
+ ...(typeof dev === "object" ? dev.env : {}),
329
+ FORCE_COLOR: "1",
330
+ ...process.env,
331
+ // NOTE: we must set this to ensure the user does not accidentally set `NODE_ENV=production`
332
+ // which breaks `vite dev` (it won't, for example, re-write `process.env.TSS_APP_BASE` in the `.js` client side bundle)
333
+ NODE_ENV: "development",
349
334
  },
350
- dev: url ? { url } : undefined,
351
- })) as Website<B>;
352
- });
335
+ });
336
+ }
337
+
338
+ return (await Worker(id, {
339
+ ...worker,
340
+ bindings: {
341
+ ...worker.bindings,
342
+ ...(!scope.local
343
+ ? {
344
+ ASSETS: await Assets("assets", {
345
+ path: path.relative(process.cwd(), paths.assets),
346
+ }),
347
+ }
348
+ : {}),
349
+ },
350
+ dev: url ? { url } : undefined,
351
+ })) as Website<B>;
353
352
  }
354
353
 
355
354
  async function writeMiniflareSymlink(cwd: string) {
@@ -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
  }