wrangler 2.0.23 → 2.0.26

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 (80) hide show
  1. package/README.md +20 -2
  2. package/bin/wrangler.js +1 -1
  3. package/miniflare-dist/index.mjs +235 -47
  4. package/package.json +11 -6
  5. package/src/__tests__/configuration.test.ts +89 -17
  6. package/src/__tests__/dev.test.tsx +29 -4
  7. package/src/__tests__/generate.test.ts +93 -0
  8. package/src/__tests__/helpers/mock-cfetch.ts +87 -2
  9. package/src/__tests__/index.test.ts +10 -27
  10. package/src/__tests__/init.test.ts +537 -359
  11. package/src/__tests__/jest.setup.ts +34 -1
  12. package/src/__tests__/kv.test.ts +2 -2
  13. package/src/__tests__/metrics.test.ts +5 -0
  14. package/src/__tests__/pages.test.ts +14 -0
  15. package/src/__tests__/publish.test.ts +497 -254
  16. package/src/__tests__/r2.test.ts +173 -71
  17. package/src/__tests__/tail.test.ts +112 -42
  18. package/src/__tests__/user.test.ts +1 -0
  19. package/src/__tests__/validate-dev-props.test.ts +56 -0
  20. package/src/__tests__/whoami.test.tsx +60 -1
  21. package/src/api/dev.ts +7 -0
  22. package/src/bundle.ts +279 -44
  23. package/src/cfetch/internal.ts +73 -2
  24. package/src/config/config.ts +8 -3
  25. package/src/config/environment.ts +40 -8
  26. package/src/config/index.ts +13 -0
  27. package/src/config/validation.ts +102 -8
  28. package/src/create-worker-upload-form.ts +25 -0
  29. package/src/dev/dev.tsx +121 -28
  30. package/src/dev/local.tsx +88 -14
  31. package/src/dev/remote.tsx +39 -8
  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 +107 -80
  36. package/src/generate.ts +112 -14
  37. package/src/index.tsx +212 -4
  38. package/src/init.ts +111 -38
  39. package/src/inspect.ts +90 -5
  40. package/src/metrics/index.ts +1 -0
  41. package/src/metrics/metrics-dispatcher.ts +1 -0
  42. package/src/metrics/metrics-usage-headers.ts +24 -0
  43. package/src/metrics/send-event.ts +2 -2
  44. package/src/miniflare-cli/assets.ts +27 -16
  45. package/src/miniflare-cli/index.ts +124 -2
  46. package/src/module-collection.ts +3 -3
  47. package/src/pages/build.tsx +75 -41
  48. package/src/pages/constants.ts +5 -0
  49. package/src/pages/deployments.tsx +10 -10
  50. package/src/pages/dev.tsx +177 -52
  51. package/src/pages/errors.ts +22 -0
  52. package/src/pages/functions/buildPlugin.ts +4 -0
  53. package/src/pages/functions/buildWorker.ts +4 -0
  54. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  55. package/src/pages/functions/routes-consolidation.ts +73 -0
  56. package/src/pages/functions/routes-transformation.test.ts +271 -0
  57. package/src/pages/functions/routes-transformation.ts +122 -0
  58. package/src/pages/functions.tsx +96 -0
  59. package/src/pages/index.tsx +65 -55
  60. package/src/pages/projects.tsx +9 -3
  61. package/src/pages/publish.tsx +76 -23
  62. package/src/pages/types.ts +9 -0
  63. package/src/pages/upload.tsx +38 -21
  64. package/src/publish.ts +126 -112
  65. package/src/r2.ts +81 -0
  66. package/src/tail/filters.ts +3 -1
  67. package/src/tail/index.ts +15 -2
  68. package/src/tail/printing.ts +43 -3
  69. package/src/user/user.tsx +20 -2
  70. package/src/whoami.tsx +79 -1
  71. package/src/worker.ts +12 -0
  72. package/templates/first-party-worker-module-facade.ts +18 -0
  73. package/templates/format-dev-errors.ts +32 -0
  74. package/templates/pages-template-plugin.ts +16 -4
  75. package/templates/pages-template-worker.ts +16 -5
  76. package/templates/{static-asset-facade.js → serve-static-assets.ts} +21 -7
  77. package/templates/service-bindings-module-facade.js +54 -0
  78. package/templates/service-bindings-sw-facade.js +42 -0
  79. package/wrangler-dist/cli.d.ts +7 -0
  80. package/wrangler-dist/cli.js +40851 -15332
@@ -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,7 +357,7 @@ 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
362
  inspector_port,
363
363
  local_protocol = "http",
@@ -598,25 +598,68 @@ function normalizeAndValidateAssets(
598
598
  diagnostics: Diagnostics,
599
599
  configPath: string | undefined,
600
600
  rawConfig: RawConfig
601
- ) {
602
- if (
603
- typeof rawConfig?.assets === "string" ||
604
- rawConfig?.assets === undefined
605
- ) {
606
- 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
+ };
607
613
  }
608
614
 
609
- 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;
610
634
 
611
635
  validateAdditionalProperties(diagnostics, "assets", Object.keys(rest), []);
636
+
612
637
  validateRequiredProperty(diagnostics, "assets", "bucket", bucket, "string");
613
638
  validateTypedArray(diagnostics, "assets.include", include, "string");
614
639
  validateTypedArray(diagnostics, "assets.exclude", exclude, "string");
615
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
+
616
657
  return {
617
658
  bucket,
618
659
  include,
619
660
  exclude,
661
+ browser_TTL,
662
+ serve_single_page_app,
620
663
  };
621
664
  }
622
665
 
@@ -830,6 +873,13 @@ function normalizeAndValidateEnvironment(
830
873
  isLegacyEnv?: boolean,
831
874
  rawConfig?: RawConfig | undefined
832
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
+ );
833
883
  deprecated(
834
884
  diagnostics,
835
885
  rawEnv,
@@ -1045,6 +1095,17 @@ function normalizeAndValidateEnvironment(
1045
1095
  validateBindingArray(envName, validateWorkerNamespaceBinding),
1046
1096
  []
1047
1097
  ),
1098
+ logfwdr: inheritable(
1099
+ diagnostics,
1100
+ topLevelEnv,
1101
+ rawEnv,
1102
+ "logfwdr",
1103
+ validateBindingsProperty(envName, validateCflogfwdrBinding),
1104
+ {
1105
+ schema: undefined,
1106
+ bindings: [],
1107
+ }
1108
+ ),
1048
1109
  unsafe: notInheritable(
1049
1110
  diagnostics,
1050
1111
  topLevelEnv,
@@ -1082,6 +1143,14 @@ function normalizeAndValidateEnvironment(
1082
1143
  isBoolean,
1083
1144
  undefined
1084
1145
  ),
1146
+ first_party_worker: inheritable(
1147
+ diagnostics,
1148
+ topLevelEnv,
1149
+ rawEnv,
1150
+ "first_party_worker",
1151
+ isBoolean,
1152
+ undefined
1153
+ ),
1085
1154
  };
1086
1155
 
1087
1156
  return environment;
@@ -1418,6 +1487,30 @@ const validateDurableObjectBinding: ValidatorFn = (
1418
1487
  return isValid;
1419
1488
  };
1420
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
+
1421
1514
  /**
1422
1515
  * Check that the given field is a valid "unsafe" binding object.
1423
1516
  *
@@ -1448,6 +1541,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => {
1448
1541
  "durable_object_namespace",
1449
1542
  "r2_bucket",
1450
1543
  "service",
1544
+ "logfwdr",
1451
1545
  ];
1452
1546
 
1453
1547
  if (safeBindings.includes(value.type)) {
@@ -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,13 +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";
22
31
  import type { EnablePagesAssetsServiceBindingOptions } from "../miniflare-cli";
23
32
  import type { AssetPaths } from "../sites";
24
33
  import type { CfWorkerInit } from "../worker";
25
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
+
26
123
  export type DevProps = {
27
124
  name: string | undefined;
28
125
  noBundle: boolean;
@@ -46,6 +143,7 @@ export type DevProps = {
46
143
  crons: Config["triggers"]["crons"];
47
144
  isWorkersSite: boolean;
48
145
  assetPaths: AssetPaths | undefined;
146
+ assetsConfig: Config["assets"];
49
147
  compatibilityDate: string;
50
148
  compatibilityFlags: string[] | undefined;
51
149
  usageModel: "bundled" | "unbound" | undefined;
@@ -64,36 +162,12 @@ export type DevProps = {
64
162
  showInteractiveDevSession: boolean | undefined;
65
163
  forceLocal: boolean | undefined;
66
164
  enablePagesAssetsServiceBinding?: EnablePagesAssetsServiceBindingOptions;
165
+ firstPartyWorker: boolean | undefined;
166
+ sendMetrics: boolean | undefined;
67
167
  };
68
168
 
69
169
  export function DevImplementation(props: DevProps): JSX.Element {
70
- if (
71
- !props.isWorkersSite &&
72
- props.assetPaths &&
73
- props.entry.format === "service-worker"
74
- ) {
75
- throw new Error(
76
- "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/"
77
- );
78
- }
79
-
80
- if (props.bindings.wasm_modules && props.entry.format === "modules") {
81
- throw new Error(
82
- "You cannot configure [wasm_modules] with an ES module worker. Instead, import the .wasm module directly in your code"
83
- );
84
- }
85
-
86
- if (props.bindings.text_blobs && props.entry.format === "modules") {
87
- throw new Error(
88
- "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"
89
- );
90
- }
91
-
92
- if (props.bindings.data_blobs && props.entry.format === "modules") {
93
- throw new Error(
94
- "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"
95
- );
96
- }
170
+ validateDevProps(props);
97
171
 
98
172
  // only load the UI if we're running in a supported environment
99
173
  const { isRawModeSupported } = useStdin();
@@ -157,6 +231,13 @@ function DevSession(props: DevSessionProps) {
157
231
 
158
232
  const directory = useTmpDir();
159
233
 
234
+ const workerDefinitions = useDevRegistry(
235
+ props.name,
236
+ props.bindings.services,
237
+ props.bindings.durable_objects,
238
+ props.local ? "local" : "remote"
239
+ );
240
+
160
241
  const bundle = useEsbuild({
161
242
  entry: props.entry,
162
243
  destination: directory,
@@ -171,6 +252,11 @@ function DevSession(props: DevSessionProps) {
171
252
  nodeCompat: props.nodeCompat,
172
253
  define: props.define,
173
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,
174
260
  });
175
261
 
176
262
  return props.local ? (
@@ -180,9 +266,10 @@ function DevSession(props: DevSessionProps) {
180
266
  format={props.entry.format}
181
267
  compatibilityDate={props.compatibilityDate}
182
268
  compatibilityFlags={props.compatibilityFlags}
269
+ usageModel={props.usageModel}
183
270
  bindings={props.bindings}
271
+ workerDefinitions={workerDefinitions}
184
272
  assetPaths={props.assetPaths}
185
- isWorkersSite={props.isWorkersSite}
186
273
  port={props.port}
187
274
  ip={props.ip}
188
275
  rules={props.rules}
@@ -223,6 +310,8 @@ function DevSession(props: DevSessionProps) {
223
310
  host={props.host}
224
311
  routes={props.routes}
225
312
  onReady={props.onReady}
313
+ sourceMapPath={bundle?.sourceMapPath}
314
+ sendMetrics={props.sendMetrics}
226
315
  />
227
316
  );
228
317
  }
@@ -404,6 +493,10 @@ function useHotkeys(props: {
404
493
  break;
405
494
  // open browser
406
495
  case "b": {
496
+ if (ip === "0.0.0.0") {
497
+ await openInBrowser(`${localProtocol}://127.0.0.1:${port}`);
498
+ return;
499
+ }
407
500
  await openInBrowser(`${localProtocol}://${ip}:${port}`);
408
501
  break;
409
502
  }