wrangler 2.0.22 → 2.0.25

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 (75) hide show
  1. package/README.md +20 -2
  2. package/bin/wrangler.js +1 -1
  3. package/miniflare-dist/index.mjs +643 -7
  4. package/package.json +17 -5
  5. package/src/__tests__/configuration.test.ts +89 -17
  6. package/src/__tests__/dev.test.tsx +121 -8
  7. package/src/__tests__/generate.test.ts +93 -0
  8. package/src/__tests__/helpers/mock-cfetch.ts +54 -2
  9. package/src/__tests__/index.test.ts +10 -27
  10. package/src/__tests__/jest.setup.ts +31 -1
  11. package/src/__tests__/kv.test.ts +82 -61
  12. package/src/__tests__/metrics.test.ts +5 -0
  13. package/src/__tests__/publish.test.ts +573 -254
  14. package/src/__tests__/r2.test.ts +173 -71
  15. package/src/__tests__/tail.test.ts +93 -39
  16. package/src/__tests__/user.test.ts +1 -0
  17. package/src/__tests__/validate-dev-props.test.ts +56 -0
  18. package/src/__tests__/version.test.ts +35 -0
  19. package/src/__tests__/whoami.test.tsx +60 -1
  20. package/src/api/dev.ts +49 -9
  21. package/src/bundle.ts +298 -37
  22. package/src/cfetch/internal.ts +34 -2
  23. package/src/config/config.ts +15 -3
  24. package/src/config/environment.ts +40 -8
  25. package/src/config/index.ts +13 -0
  26. package/src/config/validation.ts +111 -9
  27. package/src/create-worker-preview.ts +3 -1
  28. package/src/create-worker-upload-form.ts +25 -0
  29. package/src/dev/dev.tsx +145 -31
  30. package/src/dev/local.tsx +116 -24
  31. package/src/dev/remote.tsx +39 -12
  32. package/src/dev/use-esbuild.ts +28 -0
  33. package/src/dev/validate-dev-props.ts +31 -0
  34. package/src/dev-registry.tsx +160 -0
  35. package/src/dev.tsx +148 -67
  36. package/src/generate.ts +112 -14
  37. package/src/index.tsx +252 -7
  38. package/src/inspect.ts +90 -5
  39. package/src/metrics/index.ts +1 -0
  40. package/src/metrics/metrics-dispatcher.ts +1 -0
  41. package/src/metrics/metrics-usage-headers.ts +24 -0
  42. package/src/metrics/send-event.ts +2 -2
  43. package/src/miniflare-cli/assets.ts +546 -0
  44. package/src/miniflare-cli/index.ts +157 -6
  45. package/src/module-collection.ts +3 -3
  46. package/src/pages/build.tsx +36 -28
  47. package/src/pages/constants.ts +4 -0
  48. package/src/pages/deployments.tsx +10 -10
  49. package/src/pages/dev.tsx +155 -651
  50. package/src/pages/functions/buildPlugin.ts +4 -0
  51. package/src/pages/functions/buildWorker.ts +4 -0
  52. package/src/pages/functions/routes-consolidation.test.ts +66 -0
  53. package/src/pages/functions/routes-consolidation.ts +29 -0
  54. package/src/pages/functions/routes-transformation.test.ts +271 -0
  55. package/src/pages/functions/routes-transformation.ts +125 -0
  56. package/src/pages/projects.tsx +9 -3
  57. package/src/pages/publish.tsx +57 -15
  58. package/src/pages/types.ts +9 -0
  59. package/src/pages/upload.tsx +38 -21
  60. package/src/publish.ts +139 -112
  61. package/src/r2.ts +81 -0
  62. package/src/tail/index.ts +15 -2
  63. package/src/tail/printing.ts +41 -3
  64. package/src/user/choose-account.tsx +20 -11
  65. package/src/user/user.tsx +20 -2
  66. package/src/whoami.tsx +79 -1
  67. package/src/worker.ts +12 -0
  68. package/templates/first-party-worker-module-facade.ts +18 -0
  69. package/templates/format-dev-errors.ts +32 -0
  70. package/templates/pages-shim.ts +9 -0
  71. package/templates/{static-asset-facade.js → serve-static-assets.ts} +21 -7
  72. package/templates/service-bindings-module-facade.js +51 -0
  73. package/templates/service-bindings-sw-facade.js +39 -0
  74. package/wrangler-dist/cli.d.ts +38 -3
  75. package/wrangler-dist/cli.js +45244 -25199
@@ -217,6 +217,11 @@ interface EnvironmentInheritable {
217
217
  namespace: string;
218
218
  }[];
219
219
 
220
+ /**
221
+ * Designates this worker as an internal-only "first-party" worker.
222
+ */
223
+ first_party_worker: boolean | undefined;
224
+
220
225
  /**
221
226
  * TODO: remove this as it has been deprecated.
222
227
  *
@@ -224,6 +229,24 @@ interface EnvironmentInheritable {
224
229
  * So we need to include it in this type so it is available.
225
230
  */
226
231
  zone_id?: string;
232
+
233
+ /**
234
+ * Specify a compiled capnp schema to use
235
+ * Then add a binding per field in the top level message that you will send to logfwdr
236
+ *
237
+ * @default `{schema:undefined,bindings:[]}`
238
+ * @inheritable
239
+ */
240
+ logfwdr: {
241
+ /** capnp schema filename */
242
+ schema: string | undefined;
243
+ bindings: {
244
+ /** The binding name used to refer to logfwdr */
245
+ name: string;
246
+ /** The destination for this logged message */
247
+ destination: string;
248
+ }[];
249
+ };
227
250
  }
228
251
 
229
252
  /**
@@ -329,14 +352,16 @@ interface EnvironmentNonInheritable {
329
352
  * @default `[]`
330
353
  * @nonInheritable
331
354
  */
332
- services: {
333
- /** The binding name used to refer to the bound service. */
334
- binding: string;
335
- /** The name of the service. */
336
- service: string;
337
- /** The environment of the service (e.g. production, staging, etc). */
338
- environment?: string;
339
- }[];
355
+ services:
356
+ | {
357
+ /** The binding name used to refer to the bound service. */
358
+ binding: string;
359
+ /** The name of the service. */
360
+ service: string;
361
+ /** The environment of the service (e.g. production, staging, etc). */
362
+ environment?: string;
363
+ }[]
364
+ | undefined;
340
365
 
341
366
  /**
342
367
  * "Unsafe" tables for features that aren't directly supported by wrangler.
@@ -375,6 +400,13 @@ interface EnvironmentDeprecated {
375
400
  */
376
401
  zone_id?: string;
377
402
 
403
+ /**
404
+ * Legacy way of defining KVNamespaces that is no longer supported.
405
+ *
406
+ * @deprecated DO NOT USE. This was a legacy bug from wrangler 1, that we do not want to support.
407
+ */
408
+ "kv-namespaces"?: string;
409
+
378
410
  /**
379
411
  * A list of services that your worker should be bound to.
380
412
  *
@@ -85,6 +85,7 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
85
85
  durable_objects,
86
86
  kv_namespaces,
87
87
  r2_buckets,
88
+ logfwdr,
88
89
  services,
89
90
  text_blobs,
90
91
  unsafe,
@@ -149,6 +150,18 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
149
150
  });
150
151
  }
151
152
 
153
+ if (logfwdr !== undefined && logfwdr.bindings.length > 0) {
154
+ output.push({
155
+ type: "logfwdr",
156
+ entries: logfwdr.bindings.map((binding) => {
157
+ return {
158
+ key: binding.name,
159
+ value: binding.destination,
160
+ };
161
+ }),
162
+ });
163
+ }
164
+
152
165
  if (services !== undefined && services.length > 0) {
153
166
  output.push({
154
167
  type: "Services",
@@ -357,8 +357,9 @@ function normalizeAndValidateDev(
357
357
  rawDev: RawDevConfig
358
358
  ): DevConfig {
359
359
  const {
360
- ip = "localhost",
360
+ ip = "0.0.0.0",
361
361
  port,
362
+ inspector_port,
362
363
  local_protocol = "http",
363
364
  upstream_protocol = "https",
364
365
  host,
@@ -368,6 +369,13 @@ function normalizeAndValidateDev(
368
369
 
369
370
  validateOptionalProperty(diagnostics, "dev", "ip", ip, "string");
370
371
  validateOptionalProperty(diagnostics, "dev", "port", port, "number");
372
+ validateOptionalProperty(
373
+ diagnostics,
374
+ "dev",
375
+ "inspector_port",
376
+ inspector_port,
377
+ "number"
378
+ );
371
379
  validateOptionalProperty(
372
380
  diagnostics,
373
381
  "dev",
@@ -385,7 +393,7 @@ function normalizeAndValidateDev(
385
393
  ["http", "https"]
386
394
  );
387
395
  validateOptionalProperty(diagnostics, "dev", "host", host, "string");
388
- return { ip, port, local_protocol, upstream_protocol, host };
396
+ return { ip, port, inspector_port, local_protocol, upstream_protocol, host };
389
397
  }
390
398
 
391
399
  /**
@@ -590,25 +598,68 @@ function normalizeAndValidateAssets(
590
598
  diagnostics: Diagnostics,
591
599
  configPath: string | undefined,
592
600
  rawConfig: RawConfig
593
- ) {
594
- if (
595
- typeof rawConfig?.assets === "string" ||
596
- rawConfig?.assets === undefined
597
- ) {
598
- return rawConfig?.assets;
601
+ ): Config["assets"] {
602
+ // Even though the type doesn't say it,
603
+ // we allow for a string input in the config,
604
+ // so let's normalise it
605
+ if (typeof rawConfig?.assets === "string") {
606
+ return {
607
+ bucket: rawConfig.assets,
608
+ include: [],
609
+ exclude: [],
610
+ browser_TTL: undefined,
611
+ serve_single_page_app: false,
612
+ };
599
613
  }
600
614
 
601
- const { bucket, include = [], exclude = [], ...rest } = rawConfig.assets;
615
+ if (rawConfig?.assets === undefined) {
616
+ return undefined;
617
+ }
618
+
619
+ if (typeof rawConfig.assets !== "object") {
620
+ diagnostics.errors.push(
621
+ `Expected the \`assets\` field to be a string or an object, but got ${typeof rawConfig.assets}.`
622
+ );
623
+ return undefined;
624
+ }
625
+
626
+ const {
627
+ bucket,
628
+ include = [],
629
+ exclude = [],
630
+ browser_TTL,
631
+ serve_single_page_app,
632
+ ...rest
633
+ } = rawConfig.assets;
602
634
 
603
635
  validateAdditionalProperties(diagnostics, "assets", Object.keys(rest), []);
636
+
604
637
  validateRequiredProperty(diagnostics, "assets", "bucket", bucket, "string");
605
638
  validateTypedArray(diagnostics, "assets.include", include, "string");
606
639
  validateTypedArray(diagnostics, "assets.exclude", exclude, "string");
607
640
 
641
+ validateOptionalProperty(
642
+ diagnostics,
643
+ "assets",
644
+ "browser_TTL",
645
+ browser_TTL,
646
+ "number"
647
+ );
648
+
649
+ validateOptionalProperty(
650
+ diagnostics,
651
+ "assets",
652
+ "serve_single_page_app",
653
+ serve_single_page_app,
654
+ "boolean"
655
+ );
656
+
608
657
  return {
609
658
  bucket,
610
659
  include,
611
660
  exclude,
661
+ browser_TTL,
662
+ serve_single_page_app,
612
663
  };
613
664
  }
614
665
 
@@ -822,6 +873,13 @@ function normalizeAndValidateEnvironment(
822
873
  isLegacyEnv?: boolean,
823
874
  rawConfig?: RawConfig | undefined
824
875
  ): Environment {
876
+ deprecated(
877
+ diagnostics,
878
+ rawEnv,
879
+ "kv-namespaces",
880
+ `The "kv-namespaces" field is no longer supported, please rename to "kv_namespaces"`,
881
+ true
882
+ );
825
883
  deprecated(
826
884
  diagnostics,
827
885
  rawEnv,
@@ -1037,6 +1095,17 @@ function normalizeAndValidateEnvironment(
1037
1095
  validateBindingArray(envName, validateWorkerNamespaceBinding),
1038
1096
  []
1039
1097
  ),
1098
+ logfwdr: inheritable(
1099
+ diagnostics,
1100
+ topLevelEnv,
1101
+ rawEnv,
1102
+ "logfwdr",
1103
+ validateBindingsProperty(envName, validateCflogfwdrBinding),
1104
+ {
1105
+ schema: undefined,
1106
+ bindings: [],
1107
+ }
1108
+ ),
1040
1109
  unsafe: notInheritable(
1041
1110
  diagnostics,
1042
1111
  topLevelEnv,
@@ -1074,6 +1143,14 @@ function normalizeAndValidateEnvironment(
1074
1143
  isBoolean,
1075
1144
  undefined
1076
1145
  ),
1146
+ first_party_worker: inheritable(
1147
+ diagnostics,
1148
+ topLevelEnv,
1149
+ rawEnv,
1150
+ "first_party_worker",
1151
+ isBoolean,
1152
+ undefined
1153
+ ),
1077
1154
  };
1078
1155
 
1079
1156
  return environment;
@@ -1410,6 +1487,30 @@ const validateDurableObjectBinding: ValidatorFn = (
1410
1487
  return isValid;
1411
1488
  };
1412
1489
 
1490
+ const validateCflogfwdrBinding: ValidatorFn = (diagnostics, field, value) => {
1491
+ if (typeof value !== "object" || value === null) {
1492
+ diagnostics.errors.push(
1493
+ `Expected "${field}" to be an object but got ${JSON.stringify(value)}`
1494
+ );
1495
+ return false;
1496
+ }
1497
+
1498
+ let isValid = true;
1499
+ if (!isRequiredProperty(value, "name", "string")) {
1500
+ diagnostics.errors.push(`binding should have a string "name" field.`);
1501
+ isValid = false;
1502
+ }
1503
+
1504
+ if (!isRequiredProperty(value, "destination", "string")) {
1505
+ diagnostics.errors.push(
1506
+ `binding should have a string "destination" field.`
1507
+ );
1508
+ isValid = false;
1509
+ }
1510
+
1511
+ return isValid;
1512
+ };
1513
+
1413
1514
  /**
1414
1515
  * Check that the given field is a valid "unsafe" binding object.
1415
1516
  *
@@ -1440,6 +1541,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => {
1440
1541
  "durable_object_namespace",
1441
1542
  "r2_bucket",
1442
1543
  "service",
1544
+ "logfwdr",
1443
1545
  ];
1444
1546
 
1445
1547
  if (safeBindings.includes(value.type)) {
@@ -247,7 +247,9 @@ export async function createWorkerPreview(
247
247
  }
248
248
  },
249
249
  (err) => {
250
- logger.warn("worker failed to prewarm: ", err);
250
+ if ((err as { code: string }).code !== "ABORT_ERR") {
251
+ logger.warn("worker failed to prewarm: ", err);
252
+ }
251
253
  }
252
254
  );
253
255
 
@@ -32,6 +32,7 @@ export interface WorkerMetadata {
32
32
  compatibility_flags?: string[];
33
33
  usage_model?: "bundled" | "unbound";
34
34
  migrations?: CfDurableObjectMigrations;
35
+ capnp_schema?: string;
35
36
  // If you add any new binding types here, also add it to safeBindings
36
37
  // under validateUnsafeBinding in config/validation.ts
37
38
  bindings: (
@@ -51,6 +52,11 @@ export interface WorkerMetadata {
51
52
  | { type: "r2_bucket"; name: string; bucket_name: string }
52
53
  | { type: "service"; name: string; service: string; environment?: string }
53
54
  | { type: "namespace"; name: string; namespace: string }
55
+ | {
56
+ type: "logfwdr";
57
+ name: string;
58
+ destination: string;
59
+ }
54
60
  )[];
55
61
  }
56
62
 
@@ -125,6 +131,14 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
125
131
  });
126
132
  });
127
133
 
134
+ bindings.logfwdr?.bindings.forEach(({ name, destination }) => {
135
+ metadataBindings.push({
136
+ name: name,
137
+ type: "logfwdr",
138
+ destination,
139
+ });
140
+ });
141
+
128
142
  for (const [name, filePath] of Object.entries(bindings.wasm_modules || {})) {
129
143
  metadataBindings.push({
130
144
  name,
@@ -240,6 +254,7 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
240
254
  ...(compatibility_flags && { compatibility_flags }),
241
255
  ...(usage_model && { usage_model }),
242
256
  ...(migrations && { migrations }),
257
+ capnp_schema: bindings.logfwdr?.schema,
243
258
  };
244
259
 
245
260
  formData.set("metadata", JSON.stringify(metadata));
@@ -259,5 +274,15 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
259
274
  );
260
275
  }
261
276
 
277
+ if (bindings.logfwdr && bindings.logfwdr.schema) {
278
+ const filePath = bindings.logfwdr.schema;
279
+ formData.set(
280
+ filePath,
281
+ new File([readFileSync(filePath)], filePath, {
282
+ type: "application/octet-stream",
283
+ })
284
+ );
285
+ }
286
+
262
287
  return formData;
263
288
  }
package/src/dev/dev.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import * as path from "node:path";
3
+ import * as util from "node:util";
3
4
  import { watch } from "chokidar";
4
5
  import clipboardy from "clipboardy";
5
6
  import commandExists from "command-exists";
@@ -9,6 +10,12 @@ import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
9
10
  import onExit from "signal-exit";
10
11
  import tmp from "tmp-promise";
11
12
  import { fetch } from "undici";
13
+ import {
14
+ getRegisteredWorkers,
15
+ startWorkerRegistry,
16
+ stopWorkerRegistry,
17
+ unregisterWorker,
18
+ } from "../dev-registry";
12
19
  import { runCustomBuild } from "../entry";
13
20
  import { openInspector } from "../inspect";
14
21
  import { logger } from "../logger";
@@ -16,12 +23,103 @@ import openInBrowser from "../open-in-browser";
16
23
  import { Local } from "./local";
17
24
  import { Remote } from "./remote";
18
25
  import { useEsbuild } from "./use-esbuild";
26
+ import { validateDevProps } from "./validate-dev-props";
19
27
  import type { Config } from "../config";
20
28
  import type { Route } from "../config/environment";
29
+ import type { WorkerRegistry } from "../dev-registry";
21
30
  import type { Entry } from "../entry";
31
+ import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli";
22
32
  import type { AssetPaths } from "../sites";
23
33
  import type { CfWorkerInit } from "../worker";
24
34
 
35
+ /**
36
+ * This hooks establishes a connection with the dev registry,
37
+ * and periodically updates itself with details of workers currently
38
+ * running a dev session on this system.
39
+ */
40
+ function useDevRegistry(
41
+ name: string | undefined,
42
+ services: Config["services"] | undefined,
43
+ durableObjects: Config["durable_objects"] | undefined,
44
+ mode: "local" | "remote"
45
+ ): WorkerRegistry {
46
+ const [workers, setWorkers] = useState<WorkerRegistry>({});
47
+
48
+ useEffect(() => {
49
+ // Let's try to start registry
50
+ // TODO: we should probably call this in a loop
51
+ // in case the registry dies elsewhere
52
+ startWorkerRegistry().catch((err) => {
53
+ logger.error("failed to start worker registry", err);
54
+ });
55
+
56
+ const serviceNames = (services || []).map(
57
+ (serviceBinding) => serviceBinding.service
58
+ );
59
+ const durableObjectServices = (
60
+ durableObjects || { bindings: [] }
61
+ ).bindings.map((durableObjectBinding) => durableObjectBinding.script_name);
62
+
63
+ const interval =
64
+ // TODO: enable this for remote mode as well
65
+ // https://github.com/cloudflare/wrangler2/issues/1182
66
+ mode === "local"
67
+ ? setInterval(() => {
68
+ getRegisteredWorkers().then(
69
+ (workerDefinitions: WorkerRegistry | undefined) => {
70
+ // We only want the workers that we're bound to
71
+ // so let's filter out the others
72
+ const filteredWorkers = Object.fromEntries(
73
+ Object.entries(workerDefinitions || {}).filter(
74
+ ([key, _value]) =>
75
+ serviceNames.includes(key) ||
76
+ durableObjectServices.includes(key)
77
+ )
78
+ );
79
+ setWorkers((prevWorkers) => {
80
+ if (!util.isDeepStrictEqual(filteredWorkers, prevWorkers)) {
81
+ return filteredWorkers;
82
+ }
83
+ return prevWorkers;
84
+ });
85
+ },
86
+ (err) => {
87
+ logger.warn("Failed to get worker definitions", err);
88
+ }
89
+ );
90
+ }, 300)
91
+ : undefined;
92
+
93
+ return () => {
94
+ interval && clearInterval(interval);
95
+ Promise.allSettled([
96
+ name ? unregisterWorker(name) : Promise.resolve(),
97
+ stopWorkerRegistry(),
98
+ ]).then(
99
+ ([unregisterResult, stopRegistryResult]) => {
100
+ if (unregisterResult.status === "rejected") {
101
+ logger.error(
102
+ "Failed to unregister worker",
103
+ unregisterResult.reason
104
+ );
105
+ }
106
+ if (stopRegistryResult.status === "rejected") {
107
+ logger.error(
108
+ "Failed to stop worker registry",
109
+ stopRegistryResult.reason
110
+ );
111
+ }
112
+ },
113
+ (err) => {
114
+ logger.error("Failed to clear dev registry effect", err);
115
+ }
116
+ );
117
+ };
118
+ }, [name, services, durableObjects, mode]);
119
+
120
+ return workers;
121
+ }
122
+
25
123
  export type DevProps = {
26
124
  name: string | undefined;
27
125
  noBundle: boolean;
@@ -39,11 +137,13 @@ export type DevProps = {
39
137
  localProtocol: "https" | "http";
40
138
  localUpstream: string | undefined;
41
139
  enableLocalPersistence: boolean;
140
+ liveReload: boolean;
42
141
  bindings: CfWorkerInit["bindings"];
43
142
  define: Config["define"];
44
143
  crons: Config["triggers"]["crons"];
45
144
  isWorkersSite: boolean;
46
145
  assetPaths: AssetPaths | undefined;
146
+ assetsConfig: Config["assets"];
47
147
  compatibilityDate: string;
48
148
  compatibilityFlags: string[] | undefined;
49
149
  usageModel: "bundled" | "unbound" | undefined;
@@ -57,38 +157,17 @@ export type DevProps = {
57
157
  routes: Route[] | undefined;
58
158
  inspect: boolean;
59
159
  logLevel: "none" | "error" | "log" | "warn" | "debug" | undefined;
160
+ logPrefix?: string;
60
161
  onReady: (() => void) | undefined;
61
162
  showInteractiveDevSession: boolean | undefined;
163
+ forceLocal: boolean | undefined;
164
+ enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
165
+ firstPartyWorker: boolean | undefined;
166
+ sendMetrics: boolean | undefined;
62
167
  };
63
168
 
64
169
  export function DevImplementation(props: DevProps): JSX.Element {
65
- if (
66
- !props.isWorkersSite &&
67
- props.assetPaths &&
68
- props.entry.format === "service-worker"
69
- ) {
70
- throw new Error(
71
- "You cannot use the service-worker format with an `assets` directory yet. For information on how to migrate to the module-worker format, see: https://developers.cloudflare.com/workers/learning/migrating-to-module-workers/"
72
- );
73
- }
74
-
75
- if (props.bindings.wasm_modules && props.entry.format === "modules") {
76
- throw new Error(
77
- "You cannot configure [wasm_modules] with an ES module worker. Instead, import the .wasm module directly in your code"
78
- );
79
- }
80
-
81
- if (props.bindings.text_blobs && props.entry.format === "modules") {
82
- throw new Error(
83
- "You cannot configure [text_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[rules]` in your wrangler.toml"
84
- );
85
- }
86
-
87
- if (props.bindings.data_blobs && props.entry.format === "modules") {
88
- throw new Error(
89
- "You cannot configure [data_blobs] with an ES module worker. Instead, import the file directly in your code, and optionally configure `[rules]` in your wrangler.toml"
90
- );
91
- }
170
+ validateDevProps(props);
92
171
 
93
172
  // only load the UI if we're running in a supported environment
94
173
  const { isRawModeSupported } = useStdin();
@@ -111,6 +190,7 @@ function InteractiveDevSession(props: DevProps) {
111
190
  inspectorPort: props.inspectorPort,
112
191
  inspect: props.inspect,
113
192
  localProtocol: props.localProtocol,
193
+ forceLocal: props.forceLocal,
114
194
  });
115
195
 
116
196
  useTunnel(toggles.tunnel);
@@ -127,8 +207,12 @@ function InteractiveDevSession(props: DevProps) {
127
207
  <Text> open Devtools, </Text>
128
208
  </>
129
209
  ) : null}
130
- <Text bold={true}>[l]</Text>
131
- <Text> {toggles.local ? "turn off" : "turn on"} local mode, </Text>
210
+ {!props.forceLocal ? (
211
+ <>
212
+ <Text bold={true}>[l]</Text>
213
+ <Text> {toggles.local ? "turn off" : "turn on"} local mode, </Text>
214
+ </>
215
+ ) : null}
132
216
  <Text bold={true}>[c]</Text>
133
217
  <Text> clear console, </Text>
134
218
  <Text bold={true}>[x]</Text>
@@ -147,6 +231,13 @@ function DevSession(props: DevSessionProps) {
147
231
 
148
232
  const directory = useTmpDir();
149
233
 
234
+ const workerDefinitions = useDevRegistry(
235
+ props.name,
236
+ props.bindings.services,
237
+ props.bindings.durable_objects,
238
+ props.local ? "local" : "remote"
239
+ );
240
+
150
241
  const bundle = useEsbuild({
151
242
  entry: props.entry,
152
243
  destination: directory,
@@ -161,6 +252,11 @@ function DevSession(props: DevSessionProps) {
161
252
  nodeCompat: props.nodeCompat,
162
253
  define: props.define,
163
254
  noBundle: props.noBundle,
255
+ assets: props.assetsConfig,
256
+ workerDefinitions,
257
+ services: props.bindings.services,
258
+ durableObjects: props.bindings.durable_objects || { bindings: [] },
259
+ firstPartyWorkerDevFacade: props.firstPartyWorker,
164
260
  });
165
261
 
166
262
  return props.local ? (
@@ -170,20 +266,24 @@ function DevSession(props: DevSessionProps) {
170
266
  format={props.entry.format}
171
267
  compatibilityDate={props.compatibilityDate}
172
268
  compatibilityFlags={props.compatibilityFlags}
269
+ usageModel={props.usageModel}
173
270
  bindings={props.bindings}
271
+ workerDefinitions={workerDefinitions}
174
272
  assetPaths={props.assetPaths}
175
- isWorkersSite={props.isWorkersSite}
176
273
  port={props.port}
177
274
  ip={props.ip}
178
275
  rules={props.rules}
179
276
  inspectorPort={props.inspectorPort}
180
277
  enableLocalPersistence={props.enableLocalPersistence}
278
+ liveReload={props.liveReload}
181
279
  crons={props.crons}
182
280
  localProtocol={props.localProtocol}
183
281
  localUpstream={props.localUpstream}
184
282
  logLevel={props.logLevel}
283
+ logPrefix={props.logPrefix}
185
284
  inspect={props.inspect}
186
285
  onReady={props.onReady}
286
+ enablePagesAssetsServiceBinding={props.enablePagesAssetsServiceBinding}
187
287
  />
188
288
  ) : (
189
289
  <Remote
@@ -198,6 +298,8 @@ function DevSession(props: DevSessionProps) {
198
298
  ip={props.ip}
199
299
  localProtocol={props.localProtocol}
200
300
  inspectorPort={props.inspectorPort}
301
+ // TODO: @threepointone #1167
302
+ // liveReload={props.liveReload}
201
303
  inspect={props.inspect}
202
304
  compatibilityDate={props.compatibilityDate}
203
305
  compatibilityFlags={props.compatibilityFlags}
@@ -208,6 +310,8 @@ function DevSession(props: DevSessionProps) {
208
310
  host={props.host}
209
311
  routes={props.routes}
210
312
  onReady={props.onReady}
313
+ sourceMapPath={bundle?.sourceMapPath}
314
+ sendMetrics={props.sendMetrics}
211
315
  />
212
316
  );
213
317
  }
@@ -359,8 +463,17 @@ function useHotkeys(props: {
359
463
  inspectorPort: number;
360
464
  inspect: boolean;
361
465
  localProtocol: "http" | "https";
466
+ forceLocal: boolean | undefined;
362
467
  }) {
363
- const { initial, port, ip, inspectorPort, inspect, localProtocol } = props;
468
+ const {
469
+ initial,
470
+ port,
471
+ ip,
472
+ inspectorPort,
473
+ inspect,
474
+ localProtocol,
475
+ forceLocal,
476
+ } = props;
364
477
  // UGH, we should put port in context instead
365
478
  const [toggles, setToggles] = useState(initial);
366
479
  const { exit } = useApp();
@@ -392,6 +505,7 @@ function useHotkeys(props: {
392
505
  }
393
506
  // toggle local
394
507
  case "l":
508
+ if (forceLocal) return;
395
509
  setToggles((previousToggles) => ({
396
510
  ...previousToggles,
397
511
  local: !previousToggles.local,