wrangler 0.0.0-e33294b0 → 0.0.0-e33bea9b

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 (63) hide show
  1. package/bin/wrangler.js +1 -1
  2. package/miniflare-dist/index.mjs +21 -4
  3. package/package.json +8 -8
  4. package/src/__tests__/configuration.test.ts +235 -6
  5. package/src/__tests__/d1/execute.test.ts +1 -1
  6. package/src/__tests__/dev.test.tsx +16 -1
  7. package/src/__tests__/guess-worker-format.test.ts +35 -0
  8. package/src/__tests__/init.test.ts +9 -8
  9. package/src/__tests__/middleware.test.ts +2 -1
  10. package/src/__tests__/pages/functions-build.test.ts +91 -41
  11. package/src/__tests__/pages/publish.test.ts +136 -61
  12. package/src/__tests__/publish.test.ts +169 -14
  13. package/src/__tests__/test-old-node-version.js +3 -3
  14. package/src/__tests__/tsconfig.tsbuildinfo +1 -1
  15. package/src/__tests__/type-generation.test.ts +1 -0
  16. package/src/__tests__/user.test.ts +1 -1
  17. package/src/api/dev.ts +3 -4
  18. package/src/api/pages/publish.tsx +38 -51
  19. package/src/bundle.ts +27 -9
  20. package/src/config/environment.ts +9 -4
  21. package/src/config/index.ts +12 -2
  22. package/src/config/validation-helpers.ts +1 -1
  23. package/src/config/validation.ts +58 -4
  24. package/src/create-worker-preview.ts +19 -8
  25. package/src/create-worker-upload-form.ts +10 -2
  26. package/src/dev/dev.tsx +4 -4
  27. package/src/dev/local.tsx +18 -21
  28. package/src/dev/start-server.ts +10 -8
  29. package/src/dev/use-esbuild.ts +9 -5
  30. package/src/dev/validate-dev-props.ts +9 -0
  31. package/src/dev.tsx +27 -13
  32. package/src/entry.ts +1 -2
  33. package/src/index.ts +1 -1
  34. package/src/init.ts +1 -0
  35. package/src/inspect.ts +16 -1
  36. package/src/miniflare-cli/assets.ts +8 -7
  37. package/src/miniflare-cli/index.ts +1 -1
  38. package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -1
  39. package/src/pages/build.ts +277 -99
  40. package/src/pages/buildFunctions.ts +20 -10
  41. package/src/pages/dev.ts +21 -21
  42. package/src/pages/errors.ts +1 -0
  43. package/src/pages/functions/buildPlugin.ts +56 -34
  44. package/src/pages/functions/buildWorker.ts +32 -23
  45. package/src/pages/functions/tsconfig.tsbuildinfo +1 -1
  46. package/src/pages/publish.tsx +1 -10
  47. package/src/pages/upload.tsx +5 -5
  48. package/src/paths.ts +1 -1
  49. package/src/publish/index.ts +1 -1
  50. package/src/publish/publish.ts +19 -8
  51. package/src/r2/helpers.ts +1 -0
  52. package/src/secret/index.ts +1 -1
  53. package/src/type-generation.ts +1 -1
  54. package/src/user/user.ts +1 -1
  55. package/src/worker.ts +8 -1
  56. package/templates/d1-beta-facade.js +5 -2
  57. package/templates/gitignore +1 -0
  58. package/templates/middleware/middleware-miniflare3-json-error.ts +17 -5
  59. package/templates/pages-template-plugin.ts +7 -3
  60. package/templates/tsconfig.init.json +1 -2
  61. package/templates/tsconfig.tsbuildinfo +1 -1
  62. package/wrangler-dist/cli.d.ts +523 -14
  63. package/wrangler-dist/cli.js +11473 -4544
@@ -76,6 +76,7 @@ const bindingsConfigMock: Partial<Config> = {
76
76
  wasm_modules: { MODULE1: "module1.wasm", MODULE2: "module2.wasm" },
77
77
  unsafe: {
78
78
  bindings: [{ name: "testing_unsafe", type: "plain_text" }],
79
+ metadata: { some_key: "some_value" },
79
80
  },
80
81
  rules: [
81
82
  {
@@ -85,7 +85,7 @@ describe("User", () => {
85
85
  await expect(
86
86
  requireAuth({} as Config)
87
87
  ).rejects.toThrowErrorMatchingInlineSnapshot(
88
- `"In a non-interactive environment, it's necessary to set a CLOUDFLARE_API_TOKEN environment variable for wrangler to work. Please go to https://developers.cloudflare.com/api/tokens/create/ for instructions on how to create an api token, and assign its value to CLOUDFLARE_API_TOKEN."`
88
+ `"In a non-interactive environment, it's necessary to set a CLOUDFLARE_API_TOKEN environment variable for wrangler to work. Please go to https://developers.cloudflare.com/fundamentals/api/get-started/create-token/ for instructions on how to create an api token, and assign its value to CLOUDFLARE_API_TOKEN."`
89
89
  );
90
90
  });
91
91
 
package/src/api/dev.ts CHANGED
@@ -18,7 +18,7 @@ export interface UnstableDevOptions {
18
18
  site?: string; // Root folder of static assets for Workers Sites
19
19
  siteInclude?: string[]; // Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.
20
20
  siteExclude?: string[]; // Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.
21
- nodeCompat?: boolean; // Enable node.js compatibility
21
+ nodeCompat?: boolean; // Enable Node.js compatibility
22
22
  compatibilityDate?: string; // Date to use for compatibility checks
23
23
  compatibilityFlags?: string[]; // Flags to use for compatibility checks
24
24
  persist?: boolean; // Enable persistence for local mode, using default path: .wrangler/state
@@ -43,7 +43,6 @@ export interface UnstableDevOptions {
43
43
  preview_bucket_name?: string;
44
44
  }[];
45
45
  logLevel?: "none" | "info" | "error" | "log" | "warn" | "debug"; // Specify logging level [choices: "debug", "info", "log", "warn", "error", "none"] [default: "log"]
46
- logPrefix?: string;
47
46
  inspect?: boolean;
48
47
  local?: boolean;
49
48
  accountId?: string;
@@ -163,7 +162,7 @@ export async function unstable_dev(
163
162
  site: options?.site, // Root folder of static assets for Workers Sites
164
163
  siteInclude: options?.siteInclude, // Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.
165
164
  siteExclude: options?.siteExclude, // Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.
166
- nodeCompat: options?.nodeCompat, // Enable node.js compatibility
165
+ nodeCompat: options?.nodeCompat, // Enable Node.js compatibility
167
166
  persist: options?.persist, // Enable persistence for local mode, using default path: .wrangler/state
168
167
  persistTo: options?.persistTo, // Specify directory to use for local persistence (implies --persist)
169
168
  experimentalJsonConfig: undefined,
@@ -252,7 +251,7 @@ export async function unstable_dev(
252
251
  site: options?.site, // Root folder of static assets for Workers Sites
253
252
  siteInclude: options?.siteInclude, // Array of .gitignore-style patterns that match file or directory names from the sites directory. Only matched items will be uploaded.
254
253
  siteExclude: options?.siteExclude, // Array of .gitignore-style patterns that match file or directory names from the sites directory. Matched items will not be uploaded.
255
- nodeCompat: options?.nodeCompat, // Enable node.js compatibility
254
+ nodeCompat: options?.nodeCompat, // Enable Node.js compatibility
256
255
  persist: options?.persist, // Enable persistence for local mode, using default path: .wrangler/state
257
256
  persistTo: options?.persistTo, // Specify directory to use for local persistence (implies --persist)
258
257
  experimentalJsonConfig: undefined,
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { tmpdir } from "node:os";
3
- import { dirname, join, resolve as resolvePath } from "node:path";
3
+ import { join, resolve as resolvePath } from "node:path";
4
4
  import { cwd } from "node:process";
5
5
  import { File, FormData } from "undici";
6
6
  import { fetchResult } from "../../cfetch";
@@ -69,14 +69,6 @@ interface PagesPublishOptions {
69
69
  */
70
70
  bundle?: boolean;
71
71
 
72
- /**
73
- * Whether to process non-JS module imports or not, such as
74
- * wasm/text/binary, when we run bundling on `functions` or
75
- * `_worker.js`
76
- * Default: false
77
- */
78
- experimentalWorkerBundle?: boolean;
79
-
80
72
  // TODO: Allow passing in the API key and plumb it through
81
73
  // to the API calls so that the publish function does not
82
74
  // rely on the `CLOUDFLARE_API_KEY` environment variable
@@ -98,7 +90,6 @@ export async function publish({
98
90
  commitDirty,
99
91
  functionsDirectory: customFunctionsDirectory,
100
92
  bundle,
101
- experimentalWorkerBundle,
102
93
  }: PagesPublishOptions) {
103
94
  let _headers: string | undefined,
104
95
  _redirects: string | undefined,
@@ -137,6 +128,11 @@ export async function publish({
137
128
  isProduction = project.production_branch === branch;
138
129
  }
139
130
 
131
+ const deploymentConfig =
132
+ project.deployment_configs[isProduction ? "production" : "preview"];
133
+ const nodejsCompat =
134
+ deploymentConfig.compatibility_flags?.includes("nodejs_compat");
135
+
140
136
  /**
141
137
  * Evaluate if this is an Advanced Mode or Pages Functions project. If Advanced Mode, we'll
142
138
  * go ahead and upload `_worker.js` as is, but if Pages Functions, we need to attempt to build
@@ -160,7 +156,6 @@ export async function publish({
160
156
  );
161
157
 
162
158
  if (!_workerJS && existsSync(functionsDirectory)) {
163
- const outfile = join(tmpdir(), `./functionsWorker-${Math.random()}.js`);
164
159
  const outputConfigPath = join(
165
160
  tmpdir(),
166
161
  `functions-filepath-routing-config-${Math.random()}.json`
@@ -168,18 +163,20 @@ export async function publish({
168
163
 
169
164
  try {
170
165
  workerBundle = await buildFunctions({
171
- outfile,
172
166
  outputConfigPath,
173
167
  functionsDirectory,
174
168
  onEnd: () => {},
175
- buildOutputDirectory: dirname(outfile),
169
+ buildOutputDirectory: directory,
176
170
  routesOutputPath,
177
171
  local: false,
178
172
  d1Databases,
179
- experimentalWorkerBundle,
173
+ nodejsCompat,
180
174
  });
181
175
 
182
- builtFunctions = readFileSync(outfile, "utf-8");
176
+ builtFunctions = readFileSync(
177
+ workerBundle.resolvedEntryPointPath,
178
+ "utf-8"
179
+ );
183
180
  filepathRoutingConfig = readFileSync(outputConfigPath, "utf-8");
184
181
  } catch (e) {
185
182
  if (e instanceof FunctionsNoRoutesError) {
@@ -247,10 +244,7 @@ export async function publish({
247
244
  * – this includes its routing and middleware characteristics.
248
245
  */
249
246
  if (_workerJS) {
250
- let workerFileContents = _workerJS;
251
- const enableBundling = bundle || experimentalWorkerBundle;
252
-
253
- if (enableBundling) {
247
+ if (bundle) {
254
248
  const outfile = join(tmpdir(), `./bundledWorker-${Math.random()}.mjs`);
255
249
  workerBundle = await buildRawWorker({
256
250
  workerScriptPath,
@@ -261,30 +255,29 @@ export async function publish({
261
255
  watch: false,
262
256
  onEnd: () => {},
263
257
  betaD1Shims: d1Databases,
264
- experimentalWorkerBundle,
258
+ nodejsCompat,
265
259
  });
266
- workerFileContents = readFileSync(outfile, "utf8");
267
260
  } else {
268
261
  await checkRawWorker(workerScriptPath, () => {});
262
+ // TODO: Replace this with the cool new no-bundle stuff when that lands: https://github.com/cloudflare/workers-sdk/pull/2769
263
+ workerBundle = {
264
+ modules: [],
265
+ dependencies: {},
266
+ stop: undefined,
267
+ resolvedEntryPointPath: workerScriptPath,
268
+ bundleType: "esm",
269
+ };
269
270
  }
270
271
 
271
- if (experimentalWorkerBundle) {
272
- const workerBundleContents = await createUploadWorkerBundleContents(
273
- workerBundle as BundleResult
274
- );
272
+ const workerBundleContents = await createUploadWorkerBundleContents(
273
+ workerBundle as BundleResult
274
+ );
275
275
 
276
- formData.append(
277
- "_worker.bundle",
278
- new File([workerBundleContents], "_worker.bundle")
279
- );
280
- logger.log(`✨ Uploading Worker bundle`);
281
- } else {
282
- formData.append(
283
- "_worker.js",
284
- new File([workerFileContents], "_worker.js")
285
- );
286
- logger.log(`✨ Uploading _worker.js`);
287
- }
276
+ formData.append(
277
+ "_worker.bundle",
278
+ new File([workerBundleContents], "_worker.bundle")
279
+ );
280
+ logger.log(`✨ Uploading Worker bundle`);
288
281
 
289
282
  if (_routesCustom) {
290
283
  // user provided a custom _routes.json file
@@ -310,21 +303,15 @@ export async function publish({
310
303
  * https://developers.cloudflare.com/pages/platform/functions/
311
304
  */
312
305
  if (builtFunctions && !_workerJS) {
313
- if (experimentalWorkerBundle) {
314
- const workerBundleContents = await createUploadWorkerBundleContents(
315
- workerBundle as BundleResult
316
- );
306
+ const workerBundleContents = await createUploadWorkerBundleContents(
307
+ workerBundle as BundleResult
308
+ );
317
309
 
318
- formData.append(
319
- "_worker.bundle",
320
- new File([workerBundleContents], "_worker.bundle")
321
- );
322
- logger.log(`✨ Uploading Functions bundle`);
323
- } else {
324
- // if Functions were build successfully, proceed to uploading the build file
325
- formData.append("_worker.js", new File([builtFunctions], "_worker.js"));
326
- logger.log(`✨ Uploading Functions`);
327
- }
310
+ formData.append(
311
+ "_worker.bundle",
312
+ new File([workerBundleContents], "_worker.bundle")
313
+ );
314
+ logger.log(`✨ Uploading Functions bundle`);
328
315
 
329
316
  if (_routesCustom) {
330
317
  // user provided a custom _routes.json file
package/src/bundle.ts CHANGED
@@ -85,6 +85,15 @@ Add "node_compat = true" to your wrangler.toml file to enable Node compatibility
85
85
  }
86
86
  }
87
87
 
88
+ const nodejsCompatPlugin: esbuild.Plugin = {
89
+ name: "nodejs_compat Plugin",
90
+ setup(pluginBuild) {
91
+ pluginBuild.onResolve({ filter: /node:.*/ }, () => {
92
+ return { external: true };
93
+ });
94
+ },
95
+ };
96
+
88
97
  /**
89
98
  * Generate a bundle for the worker identified by the arguments passed in.
90
99
  */
@@ -98,11 +107,13 @@ export async function bundleWorker(
98
107
  doBindings: DurableObjectBindings;
99
108
  jsxFactory?: string;
100
109
  jsxFragment?: string;
110
+ entryName?: string;
101
111
  rules: Config["rules"];
102
112
  watch?: esbuild.WatchMode | boolean;
103
113
  tsconfig?: string;
104
114
  minify?: boolean;
105
- nodeCompat?: boolean;
115
+ legacyNodeCompat?: boolean;
116
+ nodejsCompat?: boolean;
106
117
  define: Config["define"];
107
118
  checkFetch: boolean;
108
119
  services?: Config["services"];
@@ -127,11 +138,13 @@ export async function bundleWorker(
127
138
  doBindings,
128
139
  jsxFactory,
129
140
  jsxFragment,
141
+ entryName,
130
142
  rules,
131
143
  watch,
132
144
  tsconfig,
133
145
  minify,
134
- nodeCompat,
146
+ legacyNodeCompat,
147
+ nodejsCompat,
135
148
  checkFetch,
136
149
  local,
137
150
  assets,
@@ -277,7 +290,7 @@ export async function bundleWorker(
277
290
  currentEntry,
278
291
  tmpDir.path,
279
292
  betaD1Shims,
280
- local,
293
+ local && !experimentalLocal,
281
294
  doBindings
282
295
  );
283
296
  }),
@@ -324,16 +337,19 @@ export async function bundleWorker(
324
337
  bundle: true,
325
338
  absWorkingDir: entry.directory,
326
339
  outdir: destination,
340
+ entryNames: entryName || path.parse(entry.file).name,
327
341
  ...(isOutfile
328
342
  ? {
329
343
  outdir: undefined,
330
344
  outfile: destination,
345
+ entryNames: undefined,
331
346
  }
332
347
  : {}),
333
348
  inject,
334
349
  external: ["__STATIC_CONTENT_MANIFEST"],
335
350
  format: entry.format === "modules" ? "esm" : "iife",
336
- target: "es2020",
351
+ // Our workerd runtime uses the same V8 version as recent Chrome, which is highly ES2022 compliant: https://kangax.github.io/compat-table/es2016plus/
352
+ target: "es2022",
337
353
  sourcemap: sourcemap ?? true, // this needs to use ?? to accept false
338
354
  // Include a reference to the output folder in the sourcemap.
339
355
  // This is omitted by default, but we need it to properly resolve source paths in error output.
@@ -346,7 +362,7 @@ export async function bundleWorker(
346
362
  // use process.env["NODE_ENV" + ""] so that esbuild doesn't replace it
347
363
  // when we do a build of wrangler. (re: https://github.com/cloudflare/workers-sdk/issues/1477)
348
364
  "process.env.NODE_ENV": `"${process.env["NODE_ENV" + ""]}"`,
349
- ...(nodeCompat ? { global: "globalThis" } : {}),
365
+ ...(legacyNodeCompat ? { global: "globalThis" } : {}),
350
366
  ...(checkFetch ? { fetch: "checkedFetch" } : {}),
351
367
  ...options.define,
352
368
  },
@@ -359,9 +375,10 @@ export async function bundleWorker(
359
375
  },
360
376
  plugins: [
361
377
  moduleCollector.plugin,
362
- ...(nodeCompat
378
+ ...(legacyNodeCompat
363
379
  ? [NodeGlobalsPolyfills({ buffer: true }), NodeModulesPolyfills()]
364
380
  : []),
381
+ ...(nodejsCompat ? [nodejsCompatPlugin] : []),
365
382
  ...(plugins || []),
366
383
  ],
367
384
  ...(jsxFactory && { jsxFactory }),
@@ -377,7 +394,8 @@ export async function bundleWorker(
377
394
  try {
378
395
  result = await esbuild.build(buildOptions);
379
396
  } catch (e) {
380
- if (!nodeCompat && isBuildFailure(e)) rewriteNodeCompatBuildFailure(e);
397
+ if (!legacyNodeCompat && isBuildFailure(e))
398
+ rewriteNodeCompatBuildFailure(e);
381
399
  throw e;
382
400
  }
383
401
 
@@ -802,7 +820,7 @@ async function applyD1BetaFacade(
802
820
  entry: Entry,
803
821
  tmpDirPath: string,
804
822
  betaD1Shims: string[],
805
- local: boolean,
823
+ miniflare2: boolean,
806
824
  doBindings: DurableObjectBindings
807
825
  ): Promise<Entry> {
808
826
  let entrypointPath = path.resolve(
@@ -853,7 +871,7 @@ async function applyD1BetaFacade(
853
871
  ],
854
872
  define: {
855
873
  __D1_IMPORTS__: JSON.stringify(betaD1Shims),
856
- __LOCAL_MODE__: JSON.stringify(local),
874
+ __LOCAL_MODE__: JSON.stringify(miniflare2),
857
875
  },
858
876
  outfile: targetPath,
859
877
  });
@@ -463,7 +463,6 @@ interface EnvironmentNonInheritable {
463
463
  * NOTE: This field is not automatically inherited from the top level environment,
464
464
  * and so must be specified in every named environment.
465
465
  *
466
- * @default `{ bindings: [] }`
467
466
  * @nonInheritable
468
467
  */
469
468
  unsafe: {
@@ -471,14 +470,20 @@ interface EnvironmentNonInheritable {
471
470
  * A set of bindings that should be put into a Worker's upload metadata without changes. These
472
471
  * can be used to implement bindings for features that haven't released and aren't supported
473
472
  * directly by wrangler or miniflare.
474
- *
475
- * @default []
476
473
  */
477
- bindings: {
474
+ bindings?: {
478
475
  name: string;
479
476
  type: string;
480
477
  [key: string]: unknown;
481
478
  }[];
479
+
480
+ /**
481
+ * Arbitrary key/value pairs that will be included in the uploaded metadata. Values specified
482
+ * here will always be applied to metadata last, so can add new or override existing fields.
483
+ */
484
+ metadata?: {
485
+ [key: string]: string;
486
+ };
482
487
  };
483
488
 
484
489
  mtls_certificates: {
@@ -255,10 +255,10 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
255
255
  });
256
256
  }
257
257
 
258
- if (unsafe !== undefined && unsafe.length > 0) {
258
+ if (unsafe?.bindings !== undefined && unsafe.bindings.length > 0) {
259
259
  output.push({
260
260
  type: "Unsafe",
261
- entries: unsafe.map(({ name, type }) => ({
261
+ entries: unsafe.bindings.map(({ name, type }) => ({
262
262
  key: type,
263
263
  value: name,
264
264
  })),
@@ -319,6 +319,16 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
319
319
  });
320
320
  }
321
321
 
322
+ if (unsafe?.metadata !== undefined) {
323
+ output.push({
324
+ type: "Unsafe Metadata",
325
+ entries: Object.entries(unsafe.metadata).map(([key, value]) => ({
326
+ key,
327
+ value: `${value}`,
328
+ })),
329
+ });
330
+ }
331
+
322
332
  if (output.length === 0) {
323
333
  return;
324
334
  }
@@ -550,7 +550,7 @@ export const getBindingNames = (value: unknown): string[] => {
550
550
  } else if (isNamespaceList(value)) {
551
551
  return value.map(({ binding }) => binding);
552
552
  } else if (isRecord(value)) {
553
- return Object.keys(value);
553
+ return Object.keys(value).filter((k) => value[k] !== undefined);
554
554
  } else {
555
555
  return [];
556
556
  }
@@ -1162,10 +1162,8 @@ function normalizeAndValidateEnvironment(
1162
1162
  rawEnv,
1163
1163
  envName,
1164
1164
  "unsafe",
1165
- validateBindingsProperty(envName, validateUnsafeBinding),
1166
- {
1167
- bindings: [],
1168
- }
1165
+ validateUnsafeSettings(envName),
1166
+ {}
1169
1167
  ),
1170
1168
  zone_id: rawEnv.zone_id,
1171
1169
  no_bundle: inheritable(
@@ -1495,6 +1493,62 @@ const validateBindingsProperty =
1495
1493
  return isValid;
1496
1494
  };
1497
1495
 
1496
+ const validateUnsafeSettings =
1497
+ (envName: string): ValidatorFn =>
1498
+ (diagnostics, field, value, config) => {
1499
+ const fieldPath =
1500
+ config === undefined ? `${field}` : `env.${envName}.${field}`;
1501
+
1502
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
1503
+ diagnostics.errors.push(
1504
+ `The field "${fieldPath}" should be an object but got ${JSON.stringify(
1505
+ value
1506
+ )}.`
1507
+ );
1508
+ return false;
1509
+ }
1510
+
1511
+ // At least one of bindings and metadata must exist
1512
+ if (!hasProperty(value, "bindings") && !hasProperty(value, "metadata")) {
1513
+ diagnostics.errors.push(
1514
+ `The field "${fieldPath}" should contain at least one of "bindings" or "metadata" properties but got ${JSON.stringify(
1515
+ value
1516
+ )}.`
1517
+ );
1518
+ return false;
1519
+ }
1520
+
1521
+ // unsafe.bindings
1522
+ if (hasProperty(value, "bindings") && value.bindings !== undefined) {
1523
+ const validateBindingsFn = validateBindingsProperty(
1524
+ envName,
1525
+ validateUnsafeBinding
1526
+ );
1527
+ const valid = validateBindingsFn(diagnostics, field, value, config);
1528
+ if (!valid) {
1529
+ return false;
1530
+ }
1531
+ }
1532
+
1533
+ // unsafe.metadata
1534
+ if (
1535
+ hasProperty(value, "metadata") &&
1536
+ value.metadata !== undefined &&
1537
+ (typeof value.metadata !== "object" ||
1538
+ value.metadata === null ||
1539
+ Array.isArray(value.metadata))
1540
+ ) {
1541
+ diagnostics.errors.push(
1542
+ `The field "${fieldPath}.metadata" should be an object but got ${JSON.stringify(
1543
+ value.metadata
1544
+ )}.`
1545
+ );
1546
+ return false;
1547
+ }
1548
+
1549
+ return true;
1550
+ };
1551
+
1498
1552
  /**
1499
1553
  * Check that the given field is a valid "durable_object" binding object.
1500
1554
  */
@@ -109,6 +109,14 @@ function randomId(): string {
109
109
  });
110
110
  }
111
111
 
112
+ // URLs are often relative to the zone. Sometimes the base zone
113
+ // will be grey-clouded, and so the host must be swapped out for
114
+ // the worker route host, which is more likely to be orange-clouded
115
+ function switchHost(originalUrl: string, host?: string): URL {
116
+ const url = new URL(originalUrl);
117
+ url.hostname = host ?? url.hostname;
118
+ return url;
119
+ }
112
120
  /**
113
121
  * Generates a preview session token.
114
122
  */
@@ -129,9 +137,13 @@ export async function createPreviewSession(
129
137
  abortSignal
130
138
  );
131
139
 
132
- logger.debug(`-- START EXCHANGE API REQUEST: GET ${exchange_url}`);
140
+ const switchedExchangeUrl = switchHost(exchange_url, ctx.host).toString();
141
+
142
+ logger.debug(`-- START EXCHANGE API REQUEST: GET ${switchedExchangeUrl}`);
133
143
  logger.debug("-- END EXCHANGE API REQUEST");
134
- const exchangeResponse = await fetch(exchange_url, { signal: abortSignal });
144
+ const exchangeResponse = await fetch(switchedExchangeUrl, {
145
+ signal: abortSignal,
146
+ });
135
147
  const bodyText = await exchangeResponse.text();
136
148
  logger.debug(
137
149
  "-- START EXCHANGE API RESPONSE:",
@@ -147,16 +159,15 @@ export async function createPreviewSession(
147
159
  token: string;
148
160
  prewarm: string;
149
161
  }>(bodyText);
150
-
151
- const { host } = new URL(inspector_websocket);
152
- const query = `cf_workers_preview_token=${token}`;
162
+ const inspector = new URL(inspector_websocket);
163
+ inspector.searchParams.append("cf_workers_preview_token", token);
153
164
 
154
165
  return {
155
166
  id: randomId(),
156
167
  value: token,
157
- host,
158
- inspectorUrl: new URL(`${inspector_websocket}?${query}`),
159
- prewarmUrl: new URL(prewarm),
168
+ host: ctx.host ?? inspector.host,
169
+ inspectorUrl: switchHost(inspector.href, ctx.host),
170
+ prewarmUrl: switchHost(prewarm, ctx.host),
160
171
  };
161
172
  }
162
173
 
@@ -65,6 +65,8 @@ export interface WorkerMetadata {
65
65
  bindings: WorkerMetadataBinding[];
66
66
  keep_bindings?: WorkerMetadataBinding["type"][];
67
67
  logpush?: boolean;
68
+ // Allow unsafe.metadata to add arbitary properties at runtime
69
+ [key: string]: unknown;
68
70
  }
69
71
 
70
72
  /**
@@ -284,9 +286,9 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
284
286
  }
285
287
  }
286
288
 
287
- if (bindings.unsafe) {
289
+ if (bindings.unsafe?.bindings) {
288
290
  // @ts-expect-error unsafe bindings don't need to match a specific type here
289
- metadataBindings.push(...bindings.unsafe);
291
+ metadataBindings.push(...bindings.unsafe.bindings);
290
292
  }
291
293
 
292
294
  const metadata: WorkerMetadata = {
@@ -303,6 +305,12 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
303
305
  ...(logpush !== undefined && { logpush }),
304
306
  };
305
307
 
308
+ if (bindings.unsafe?.metadata !== undefined) {
309
+ for (const key of Object.keys(bindings.unsafe.metadata)) {
310
+ metadata[key] = bindings.unsafe.metadata[key];
311
+ }
312
+ }
313
+
306
314
  formData.set("metadata", JSON.stringify(metadata));
307
315
 
308
316
  if (main.type === "commonjs" && modules && modules.length > 0) {
package/src/dev/dev.tsx CHANGED
@@ -138,7 +138,8 @@ export type DevProps = {
138
138
  compatibilityFlags: string[] | undefined;
139
139
  usageModel: "bundled" | "unbound" | undefined;
140
140
  minify: boolean | undefined;
141
- nodeCompat: boolean | undefined;
141
+ legacyNodeCompat: boolean | undefined;
142
+ nodejsCompat: boolean | undefined;
142
143
  build: Config["build"];
143
144
  env: string | undefined;
144
145
  legacyEnv: boolean;
@@ -146,7 +147,6 @@ export type DevProps = {
146
147
  host: string | undefined;
147
148
  routes: Route[] | undefined;
148
149
  inspect: boolean;
149
- logPrefix?: string;
150
150
  onReady: ((ip: string, port: number) => void) | undefined;
151
151
  showInteractiveDevSession: boolean | undefined;
152
152
  forceLocal: boolean | undefined;
@@ -278,7 +278,8 @@ function DevSession(props: DevSessionProps) {
278
278
  ),
279
279
  tsconfig: props.tsconfig,
280
280
  minify: props.minify,
281
- nodeCompat: props.nodeCompat,
281
+ legacyNodeCompat: props.legacyNodeCompat,
282
+ nodejsCompat: props.nodejsCompat,
282
283
  betaD1Shims,
283
284
  define: props.define,
284
285
  noBundle: props.noBundle,
@@ -341,7 +342,6 @@ function DevSession(props: DevSessionProps) {
341
342
  queueConsumers={props.queueConsumers}
342
343
  localProtocol={props.localProtocol}
343
344
  localUpstream={props.localUpstream}
344
- logPrefix={props.logPrefix}
345
345
  inspect={props.inspect}
346
346
  onReady={announceAndOnReady}
347
347
  enablePagesAssetsServiceBinding={props.enablePagesAssetsServiceBinding}