wrangler 0.0.0-e95bea2 → 0.0.0-ea8e701

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 (55) hide show
  1. package/README.md +3 -3
  2. package/bin/wrangler.js +10 -1
  3. package/miniflare-dist/index.mjs +79 -0
  4. package/miniflare-dist/index.mjs.map +7 -0
  5. package/package.json +7 -6
  6. package/pages/functions/filepath-routing.test.ts +125 -25
  7. package/pages/functions/filepath-routing.ts +6 -3
  8. package/src/__tests__/configuration.test.ts +826 -223
  9. package/src/__tests__/dev.test.tsx +145 -9
  10. package/src/__tests__/guess-worker-format.test.ts +26 -1
  11. package/src/__tests__/helpers/mock-account-id.ts +4 -0
  12. package/src/__tests__/helpers/mock-kv.ts +9 -16
  13. package/src/__tests__/helpers/mock-oauth-flow.ts +178 -0
  14. package/src/__tests__/helpers/run-in-tmp.ts +3 -0
  15. package/src/__tests__/index.test.ts +9 -13
  16. package/src/__tests__/jest.setup.ts +5 -0
  17. package/src/__tests__/kv.test.ts +43 -71
  18. package/src/__tests__/pages.test.ts +48 -10
  19. package/src/__tests__/publish.test.ts +1456 -327
  20. package/src/__tests__/r2.test.ts +4 -4
  21. package/src/__tests__/tail.test.ts +78 -4
  22. package/src/__tests__/user.test.ts +83 -0
  23. package/src/__tests__/whoami.test.tsx +16 -7
  24. package/src/bundle.ts +15 -2
  25. package/src/cfetch/internal.ts +9 -5
  26. package/src/config/README.md +6 -6
  27. package/src/config/config.ts +17 -78
  28. package/src/config/environment.ts +67 -1
  29. package/src/config/index.ts +13 -3
  30. package/src/config/validation-helpers.ts +177 -11
  31. package/src/config/validation.ts +305 -100
  32. package/src/dev/dev.tsx +76 -41
  33. package/src/dev/local.tsx +105 -114
  34. package/src/dev/remote.tsx +2 -0
  35. package/src/dev/use-esbuild.ts +4 -0
  36. package/src/entry.ts +103 -6
  37. package/src/index.tsx +251 -189
  38. package/src/intl-polyfill.d.ts +139 -0
  39. package/src/{kv/index.tsx → kv.ts} +8 -28
  40. package/src/miniflare-cli/README.md +30 -0
  41. package/src/miniflare-cli/enum-keys.ts +17 -0
  42. package/src/miniflare-cli/index.ts +37 -0
  43. package/src/module-collection.ts +58 -53
  44. package/src/pages.tsx +26 -10
  45. package/src/proxy.ts +5 -3
  46. package/src/publish.ts +17 -21
  47. package/src/sites.tsx +67 -25
  48. package/src/tail/index.ts +1 -1
  49. package/src/tail/printing.ts +2 -2
  50. package/src/user.tsx +161 -81
  51. package/wrangler-dist/cli.js +3418 -1437
  52. package/wrangler-dist/cli.js.map +3 -3
  53. package/src/__tests__/dev2.test.tsx +0 -152
  54. package/src/__tests__/helpers/mock-user.ts +0 -27
  55. package/src/__tests__/logout.test.ts +0 -50
@@ -19,17 +19,23 @@ import {
19
19
  validateOptionalTypedArray,
20
20
  validateRequiredProperty,
21
21
  validateTypedArray,
22
+ all,
23
+ isMutuallyExclusiveWith,
24
+ inheritableInLegacyEnvironments,
25
+ appendEnvName,
26
+ getBindingNames,
22
27
  } from "./validation-helpers";
28
+ import type { Config, DevConfig, RawConfig, RawDevConfig } from "./config";
23
29
  import type {
24
- Config,
30
+ RawEnvironment,
25
31
  DeprecatedUpload,
26
- DevConfig,
27
- RawConfig,
28
- RawDevConfig,
29
- } from "./config";
30
- import type { RawEnvironment, Environment, Rule } from "./environment";
32
+ Environment,
33
+ Rule,
34
+ } from "./environment";
31
35
  import type { ValidatorFn } from "./validation-helpers";
32
36
 
37
+ const ENGLISH = new Intl.ListFormat("en");
38
+
33
39
  /**
34
40
  * Validate the given `rawConfig` object that was loaded from `configPath`.
35
41
  *
@@ -40,7 +46,8 @@ import type { ValidatorFn } from "./validation-helpers";
40
46
  */
41
47
  export function normalizeAndValidateConfig(
42
48
  rawConfig: RawConfig,
43
- configPath: string | undefined
49
+ configPath: string | undefined,
50
+ args: unknown
44
51
  ): {
45
52
  config: Config;
46
53
  diagnostics: Diagnostics;
@@ -76,12 +83,6 @@ export function normalizeAndValidateConfig(
76
83
  true
77
84
  );
78
85
 
79
- const { deprecatedUpload, ...build } = normalizeAndValidateBuild(
80
- diagnostics,
81
- rawConfig,
82
- rawConfig.build ?? {}
83
- );
84
-
85
86
  validateOptionalProperty(
86
87
  diagnostics,
87
88
  "",
@@ -90,23 +91,79 @@ export function normalizeAndValidateConfig(
90
91
  "boolean"
91
92
  );
92
93
 
94
+ // TODO: set the default to false to turn on service environments as the default
95
+ const isLegacyEnv =
96
+ (args as { "legacy-env": boolean | undefined })["legacy-env"] ??
97
+ rawConfig.legacy_env ??
98
+ true;
99
+
100
+ const topLevelEnv = normalizeAndValidateEnvironment(
101
+ diagnostics,
102
+ configPath,
103
+ rawConfig
104
+ );
105
+
106
+ //TODO: find a better way to define the type of Args that can be passed to the normalizeAndValidateConfig()
107
+ const envName = (args as { env: string | undefined }).env;
108
+
109
+ let activeEnv = topLevelEnv;
110
+ if (envName !== undefined) {
111
+ const envDiagnostics = new Diagnostics(
112
+ `"env.${envName}" environment configuration`
113
+ );
114
+ const rawEnv = rawConfig.env?.[envName];
115
+ if (rawEnv !== undefined) {
116
+ activeEnv = normalizeAndValidateEnvironment(
117
+ envDiagnostics,
118
+ configPath,
119
+ rawEnv,
120
+ envName,
121
+ topLevelEnv,
122
+ isLegacyEnv,
123
+ rawConfig
124
+ );
125
+ diagnostics.addChild(envDiagnostics);
126
+ } else {
127
+ // An environment was specified, but no configuration for it was found.
128
+ // To cover any legacy environment cases, where the `envName` is used,
129
+ // Let's create a fake active environment with the specified `envName`.
130
+ activeEnv = normalizeAndValidateEnvironment(
131
+ envDiagnostics,
132
+ configPath,
133
+ {},
134
+ envName,
135
+ topLevelEnv,
136
+ isLegacyEnv,
137
+ rawConfig
138
+ );
139
+ const envNames = rawConfig.env
140
+ ? `The available configured environment names are: ${JSON.stringify(
141
+ Object.keys(rawConfig.env)
142
+ )}\n`
143
+ : "";
144
+ const message =
145
+ `No environment found in configuration with name "${envName}".\n` +
146
+ `Before using \`--env=${envName}\` there should be an equivalent environment section in the configuration.\n` +
147
+ `${envNames}\n` +
148
+ `Consider adding an environment configuration section to the wrangler.toml file:\n` +
149
+ "```\n[env." +
150
+ envName +
151
+ "]\n```\n";
152
+
153
+ if (envNames.length > 0) {
154
+ diagnostics.errors.push(message);
155
+ } else {
156
+ // Only warn (rather than error) if there are not actually any environments configured in wrangler.toml.
157
+ diagnostics.warnings.push(message);
158
+ }
159
+ }
160
+ }
161
+
93
162
  // Process the top-level default environment configuration.
94
163
  const config: Config = {
95
164
  configPath,
96
- main: normalizeAndValidateMainField(
97
- diagnostics,
98
- configPath,
99
- rawConfig.main,
100
- deprecatedUpload
101
- ),
102
- // TODO: set the default to false to turn on service environments as the default
103
- legacy_env: rawConfig.legacy_env ?? true,
104
- ...normalizeAndValidateEnvironment(
105
- diagnostics,
106
- rawConfig,
107
- deprecatedUpload.rules
108
- ),
109
- env: {}, // Will be filled below by `normalizeAndValidateEnvironment()`.
165
+ legacy_env: isLegacyEnv,
166
+ ...activeEnv,
110
167
  dev: normalizeAndValidateDev(diagnostics, rawConfig.dev ?? {}),
111
168
  migrations: normalizeAndValidateMigrations(
112
169
  diagnostics,
@@ -125,33 +182,15 @@ export function normalizeAndValidateConfig(
125
182
  "text_blobs",
126
183
  rawConfig.text_blobs
127
184
  ),
128
- build,
129
185
  };
130
186
 
131
- // Process each of the specific environment configurations.
132
- if (rawConfig.env !== undefined) {
133
- for (const envName of Object.keys(rawConfig.env)) {
134
- const envDiagnostics = new Diagnostics(
135
- `"env.${envName}" environment configuration`
136
- );
137
- const environment = normalizeAndValidateEnvironment(
138
- envDiagnostics,
139
- rawConfig.env[envName] as RawEnvironment,
140
- deprecatedUpload.rules,
141
- envName,
142
- config,
143
- rawConfig
144
- );
145
- config.env[envName] = environment;
146
- diagnostics.addChild(envDiagnostics);
147
- }
148
- }
187
+ validateBindingsHaveUniqueNames(diagnostics, config);
149
188
 
150
189
  validateAdditionalProperties(
151
190
  diagnostics,
152
191
  "top-level",
153
192
  Object.keys(rawConfig),
154
- Object.keys(config)
193
+ [...Object.keys(config), "env", "miniflare"]
155
194
  );
156
195
 
157
196
  return { config, diagnostics };
@@ -162,8 +201,9 @@ export function normalizeAndValidateConfig(
162
201
  */
163
202
  function normalizeAndValidateBuild(
164
203
  diagnostics: Diagnostics,
165
- rawConfig: RawConfig,
166
- rawBuild: Config["build"]
204
+ rawEnv: RawEnvironment,
205
+ rawBuild: Config["build"],
206
+ configPath: string | undefined
167
207
  ): Config["build"] & { deprecatedUpload: DeprecatedUpload } {
168
208
  const { command, cwd, watch_dir, upload, ...rest } = rawBuild;
169
209
  const deprecatedUpload: DeprecatedUpload = { ...upload };
@@ -181,13 +221,13 @@ function normalizeAndValidateBuild(
181
221
 
182
222
  deprecated(
183
223
  diagnostics,
184
- rawConfig,
224
+ rawEnv,
185
225
  "build.upload.format",
186
226
  "The format is inferred automatically from the code.",
187
227
  true
188
228
  );
189
229
 
190
- if (rawConfig.main !== undefined && rawBuild.upload?.main) {
230
+ if (rawEnv.main !== undefined && rawBuild.upload?.main) {
191
231
  diagnostics.errors.push(
192
232
  `Don't define both the \`main\` and \`build.upload.main\` fields in your configuration.\n` +
193
233
  `They serve the same purpose: to point to the entry-point of your worker.\n` +
@@ -196,7 +236,7 @@ function normalizeAndValidateBuild(
196
236
  } else {
197
237
  deprecated(
198
238
  diagnostics,
199
- rawConfig,
239
+ rawEnv,
200
240
  "build.upload.main",
201
241
  `Delete the \`build.upload.main\` and \`build.upload.dir\` fields.\n` +
202
242
  `Then add the top level \`main\` field to your configuration file:\n` +
@@ -211,24 +251,41 @@ function normalizeAndValidateBuild(
211
251
 
212
252
  deprecated(
213
253
  diagnostics,
214
- rawConfig,
254
+ rawEnv,
215
255
  "build.upload.dir",
216
256
  `Use the top level "main" field or a command-line argument to specify the entry-point for the Worker.`,
217
257
  true
218
258
  );
219
259
  }
220
260
 
221
- return { command, cwd, watch_dir, deprecatedUpload };
261
+ return {
262
+ command,
263
+ watch_dir:
264
+ // - `watch_dir` only matters when `command` is defined, so we apply
265
+ // a default only when `command` is defined
266
+ // - `configPath` will always be defined since `build` can only
267
+ // be configured in `wrangler.toml`, but who knows, that may
268
+ // change in the future, so we do a check anyway
269
+ command
270
+ ? configPath
271
+ ? path.relative(
272
+ process.cwd(),
273
+ path.join(path.dirname(configPath), watch_dir || "./src")
274
+ )
275
+ : watch_dir || "./src"
276
+ : watch_dir,
277
+ cwd,
278
+ deprecatedUpload,
279
+ };
222
280
  }
223
281
 
224
282
  /**
225
283
  * Validate the `main` field and return the normalized values.
226
284
  */
227
285
  function normalizeAndValidateMainField(
228
- diagnostics: Diagnostics,
229
286
  configPath: string | undefined,
230
287
  rawMain: string | undefined,
231
- deprecatedUpload: { dir?: string; main?: string }
288
+ deprecatedUpload: DeprecatedUpload | undefined
232
289
  ): string | undefined {
233
290
  const configDir = path.dirname(configPath ?? "wrangler.toml");
234
291
  if (rawMain !== undefined) {
@@ -236,13 +293,13 @@ function normalizeAndValidateMainField(
236
293
  const directory = path.resolve(configDir);
237
294
  return path.resolve(directory, rawMain);
238
295
  } else {
239
- diagnostics.errors.push(
240
- `Expected "main" to be a string but got ${JSON.stringify(rawMain)}`
241
- );
242
- return;
296
+ return rawMain;
243
297
  }
244
- } else if (deprecatedUpload.main !== undefined) {
245
- const directory = path.resolve(configDir, deprecatedUpload.dir || "./dist");
298
+ } else if (deprecatedUpload?.main !== undefined) {
299
+ const directory = path.resolve(
300
+ configDir,
301
+ deprecatedUpload?.dir || "./dist"
302
+ );
246
303
  return path.resolve(directory, deprecatedUpload.main);
247
304
  } else {
248
305
  return;
@@ -257,7 +314,7 @@ function normalizeAndValidateDev(
257
314
  rawDev: RawDevConfig
258
315
  ): DevConfig {
259
316
  const {
260
- ip = "127.0.0.1",
317
+ ip = "localhost",
261
318
  port = 8787,
262
319
  local_protocol = "http",
263
320
  upstream_protocol = "https",
@@ -403,26 +460,28 @@ function normalizeAndValidateModulePaths(
403
460
  */
404
461
  function normalizeAndValidateEnvironment(
405
462
  diagnostics: Diagnostics,
406
- topLevelEnv: RawEnvironment,
407
- deprecatedRules: Environment["rules"] | undefined
463
+ configPath: string | undefined,
464
+ topLevelEnv: RawEnvironment
408
465
  ): Environment;
409
466
  /**
410
467
  * Validate the named environment configuration and return the normalized values.
411
468
  */
412
469
  function normalizeAndValidateEnvironment(
413
470
  diagnostics: Diagnostics,
471
+ configPath: string | undefined,
414
472
  rawEnv: RawEnvironment,
415
- deprecatedRules: Environment["rules"] | undefined,
416
473
  envName: string,
417
- config: Config,
474
+ topLevelEnv: Environment,
475
+ isLegacyEnv: boolean,
418
476
  rawConfig: RawConfig
419
477
  ): Environment;
420
478
  function normalizeAndValidateEnvironment(
421
479
  diagnostics: Diagnostics,
480
+ configPath: string | undefined,
422
481
  rawEnv: RawEnvironment,
423
- deprecatedRules: Environment["rules"] | undefined,
424
482
  envName = "top level",
425
- config?: Config | undefined,
483
+ topLevelEnv?: Environment | undefined,
484
+ isLegacyEnv?: boolean,
426
485
  rawConfig?: RawConfig | undefined
427
486
  ): Environment {
428
487
  deprecated(
@@ -463,7 +522,7 @@ function normalizeAndValidateEnvironment(
463
522
 
464
523
  const route = inheritable(
465
524
  diagnostics,
466
- config,
525
+ topLevelEnv,
467
526
  rawEnv,
468
527
  "route",
469
528
  isString,
@@ -471,26 +530,33 @@ function normalizeAndValidateEnvironment(
471
530
  );
472
531
  const routes = inheritable(
473
532
  diagnostics,
474
- config,
533
+ topLevelEnv,
475
534
  rawEnv,
476
535
  "routes",
477
- isStringArray,
536
+ all(isStringArray, isMutuallyExclusiveWith(rawEnv, "route")),
478
537
  undefined
479
538
  );
480
539
  const workers_dev = inheritable(
481
540
  diagnostics,
482
- config,
541
+ topLevelEnv,
483
542
  rawEnv,
484
543
  "workers_dev",
485
544
  isBoolean,
486
- !(routes || route)
545
+ undefined
546
+ );
547
+
548
+ const { deprecatedUpload, ...build } = normalizeAndValidateBuild(
549
+ diagnostics,
550
+ rawEnv,
551
+ rawEnv.build ?? topLevelEnv?.build ?? {},
552
+ configPath
487
553
  );
488
554
 
489
555
  const environment: Environment = {
490
556
  // Inherited fields
491
557
  account_id: inheritable(
492
558
  diagnostics,
493
- config,
559
+ topLevelEnv,
494
560
  rawEnv,
495
561
  "account_id",
496
562
  isString,
@@ -498,7 +564,7 @@ function normalizeAndValidateEnvironment(
498
564
  ),
499
565
  compatibility_date: inheritable(
500
566
  diagnostics,
501
- config,
567
+ topLevelEnv,
502
568
  rawEnv,
503
569
  "compatibility_date",
504
570
  isString,
@@ -506,7 +572,7 @@ function normalizeAndValidateEnvironment(
506
572
  ),
507
573
  compatibility_flags: inheritable(
508
574
  diagnostics,
509
- config,
575
+ topLevelEnv,
510
576
  rawEnv,
511
577
  "compatibility_flags",
512
578
  isStringArray,
@@ -514,7 +580,7 @@ function normalizeAndValidateEnvironment(
514
580
  ),
515
581
  jsx_factory: inheritable(
516
582
  diagnostics,
517
- config,
583
+ topLevelEnv,
518
584
  rawEnv,
519
585
  "jsx_factory",
520
586
  isString,
@@ -522,25 +588,52 @@ function normalizeAndValidateEnvironment(
522
588
  ),
523
589
  jsx_fragment: inheritable(
524
590
  diagnostics,
525
- config,
591
+ topLevelEnv,
526
592
  rawEnv,
527
593
  "jsx_fragment",
528
594
  isString,
529
595
  "React.Fragment"
530
596
  ),
597
+ tsconfig: validateAndNormalizeTsconfig(
598
+ diagnostics,
599
+ topLevelEnv,
600
+ rawEnv,
601
+ configPath
602
+ ),
531
603
  rules: validateAndNormalizeRules(
532
604
  diagnostics,
533
- config,
605
+ topLevelEnv,
534
606
  rawEnv,
535
- deprecatedRules,
607
+ deprecatedUpload?.rules,
536
608
  envName
537
609
  ),
538
- name: inheritable(diagnostics, config, rawEnv, "name", isString, undefined),
610
+ name: inheritableInLegacyEnvironments(
611
+ diagnostics,
612
+ isLegacyEnv,
613
+ topLevelEnv,
614
+ rawEnv,
615
+ "name",
616
+ isString,
617
+ appendEnvName(envName),
618
+ undefined
619
+ ),
620
+ main: normalizeAndValidateMainField(
621
+ configPath,
622
+ inheritable(
623
+ diagnostics,
624
+ topLevelEnv,
625
+ rawEnv,
626
+ "main",
627
+ isString,
628
+ undefined
629
+ ),
630
+ deprecatedUpload
631
+ ),
539
632
  route,
540
633
  routes,
541
634
  triggers: inheritable(
542
635
  diagnostics,
543
- config,
636
+ topLevelEnv,
544
637
  rawEnv,
545
638
  "triggers",
546
639
  isObjectWith("crons"),
@@ -548,17 +641,18 @@ function normalizeAndValidateEnvironment(
548
641
  ),
549
642
  usage_model: inheritable(
550
643
  diagnostics,
551
- config,
644
+ topLevelEnv,
552
645
  rawEnv,
553
646
  "usage_model",
554
647
  isOneOf("bundled", "unbound"),
555
648
  undefined
556
649
  ),
650
+ build,
557
651
  workers_dev,
558
652
  // Not inherited fields
559
653
  vars: notInheritable(
560
654
  diagnostics,
561
- config,
655
+ topLevelEnv,
562
656
  rawConfig,
563
657
  rawEnv,
564
658
  envName,
@@ -568,7 +662,7 @@ function normalizeAndValidateEnvironment(
568
662
  ),
569
663
  durable_objects: notInheritable(
570
664
  diagnostics,
571
- config,
665
+ topLevelEnv,
572
666
  rawConfig,
573
667
  rawEnv,
574
668
  envName,
@@ -580,7 +674,7 @@ function normalizeAndValidateEnvironment(
580
674
  ),
581
675
  kv_namespaces: notInheritable(
582
676
  diagnostics,
583
- config,
677
+ topLevelEnv,
584
678
  rawConfig,
585
679
  rawEnv,
586
680
  envName,
@@ -590,7 +684,7 @@ function normalizeAndValidateEnvironment(
590
684
  ),
591
685
  r2_buckets: notInheritable(
592
686
  diagnostics,
593
- config,
687
+ topLevelEnv,
594
688
  rawConfig,
595
689
  rawEnv,
596
690
  envName,
@@ -600,7 +694,7 @@ function normalizeAndValidateEnvironment(
600
694
  ),
601
695
  unsafe: notInheritable(
602
696
  diagnostics,
603
- config,
697
+ topLevelEnv,
604
698
  rawConfig,
605
699
  rawEnv,
606
700
  envName,
@@ -616,14 +710,37 @@ function normalizeAndValidateEnvironment(
616
710
  return environment;
617
711
  }
618
712
 
713
+ function validateAndNormalizeTsconfig(
714
+ diagnostics: Diagnostics,
715
+ topLevelEnv: Environment | undefined,
716
+ rawEnv: RawEnvironment,
717
+ configPath: string | undefined
718
+ ) {
719
+ const tsconfig = inheritable(
720
+ diagnostics,
721
+ topLevelEnv,
722
+ rawEnv,
723
+ "tsconfig",
724
+ isString,
725
+ undefined
726
+ );
727
+
728
+ return configPath && tsconfig
729
+ ? path.relative(
730
+ process.cwd(),
731
+ path.join(path.dirname(configPath), tsconfig)
732
+ )
733
+ : tsconfig;
734
+ }
735
+
619
736
  const validateAndNormalizeRules = (
620
737
  diagnostics: Diagnostics,
621
- config: Config | undefined,
738
+ topLevelEnv: Environment | undefined,
622
739
  rawEnv: RawEnvironment,
623
740
  deprecatedRules: Rule[] | undefined,
624
741
  envName: string
625
742
  ): Rule[] => {
626
- if (config === undefined) {
743
+ if (topLevelEnv === undefined) {
627
744
  // Only create errors/warnings for the top-level environment
628
745
  if (rawEnv.rules && deprecatedRules) {
629
746
  diagnostics.errors.push(
@@ -641,7 +758,7 @@ const validateAndNormalizeRules = (
641
758
 
642
759
  return inheritable(
643
760
  diagnostics,
644
- config,
761
+ topLevelEnv,
645
762
  rawEnv,
646
763
  "rules",
647
764
  validateRules(envName),
@@ -813,14 +930,6 @@ const validateBindingsProperty =
813
930
  return isValid;
814
931
  };
815
932
 
816
- /**
817
- * Get the names of the bindings collection in `value`.
818
- */
819
- const getBindingNames = (value: unknown): string[] =>
820
- ((value as { bindings: { name: string }[] })?.bindings ?? []).map(
821
- (binding) => binding.name
822
- );
823
-
824
933
  /**
825
934
  * Check that the given field is a valid "durable_object" binding object.
826
935
  */
@@ -1025,3 +1134,99 @@ const validateR2Binding: ValidatorFn = (diagnostics, field, value) => {
1025
1134
  }
1026
1135
  return isValid;
1027
1136
  };
1137
+
1138
+ /**
1139
+ * Check that bindings whose names might conflict, don't.
1140
+ *
1141
+ * We don't want to have, for example, a KV namespace named "DATA"
1142
+ * and a Durable Object also named "DATA". Then it would be ambiguous
1143
+ * what exactly would live at `env.DATA` (or in the case of service workers,
1144
+ * the `DATA` global).
1145
+ */
1146
+ const validateBindingsHaveUniqueNames = (
1147
+ diagnostics: Diagnostics,
1148
+ {
1149
+ durable_objects,
1150
+ kv_namespaces,
1151
+ r2_buckets,
1152
+ text_blobs,
1153
+ unsafe,
1154
+ vars,
1155
+ wasm_modules,
1156
+ }: Partial<Config>
1157
+ ): boolean => {
1158
+ let hasDuplicates = false;
1159
+
1160
+ const bindingsGroupedByType = {
1161
+ "Durable Object": getBindingNames(durable_objects),
1162
+ "KV Namespace": getBindingNames(kv_namespaces),
1163
+ "R2 Bucket": getBindingNames(r2_buckets),
1164
+ "Text Blob": getBindingNames(text_blobs),
1165
+ Unsafe: getBindingNames(unsafe),
1166
+ "Environment Variable": getBindingNames(vars),
1167
+ "WASM Module": getBindingNames(wasm_modules),
1168
+ } as Record<string, string[]>;
1169
+
1170
+ const bindingsGroupedByName: Record<string, string[]> = {};
1171
+
1172
+ for (const bindingType in bindingsGroupedByType) {
1173
+ const bindingNames = bindingsGroupedByType[bindingType];
1174
+
1175
+ for (const bindingName of bindingNames) {
1176
+ if (!(bindingName in bindingsGroupedByName)) {
1177
+ bindingsGroupedByName[bindingName] = [];
1178
+ }
1179
+
1180
+ bindingsGroupedByName[bindingName].push(bindingType);
1181
+ }
1182
+ }
1183
+
1184
+ for (const bindingName in bindingsGroupedByName) {
1185
+ const bindingTypes = bindingsGroupedByName[bindingName];
1186
+ if (bindingTypes.length < 2) {
1187
+ // there's only one (or zero) binding(s) with this name, which is fine, actually
1188
+ continue;
1189
+ }
1190
+
1191
+ hasDuplicates = true;
1192
+
1193
+ // there's two types of duplicates we want to look for:
1194
+ // - bindings with the same name of the same type (e.g. two Durable Objects both named "OBJ")
1195
+ // - bindings with the same name of different types (a KV namespace and DO both named "DATA")
1196
+
1197
+ const sameType = bindingTypes
1198
+ // filter once to find duplicate binding types
1199
+ .filter((type, i) => bindingTypes.indexOf(type) !== i)
1200
+ // filter twice to only get _unique_ duplicate binding types
1201
+ .filter(
1202
+ (type, i, duplicateBindingTypes) =>
1203
+ duplicateBindingTypes.indexOf(type) === i
1204
+ );
1205
+
1206
+ const differentTypes = bindingTypes.filter(
1207
+ (type, i) => bindingTypes.indexOf(type) === i
1208
+ );
1209
+
1210
+ if (differentTypes.length > 1) {
1211
+ // we have multiple different types using the same name
1212
+ diagnostics.errors.push(
1213
+ `${bindingName} assigned to ${ENGLISH.format(differentTypes)} bindings.`
1214
+ );
1215
+ }
1216
+
1217
+ sameType.forEach((bindingType) => {
1218
+ diagnostics.errors.push(
1219
+ `${bindingName} assigned to multiple ${bindingType} bindings.`
1220
+ );
1221
+ });
1222
+ }
1223
+
1224
+ if (hasDuplicates) {
1225
+ const problem =
1226
+ "Bindings must have unique names, so that they can all be referenced in the worker.";
1227
+ const resolution = "Please change your bindings to have unique names.";
1228
+ diagnostics.errors.push(`${problem}\n${resolution}`);
1229
+ }
1230
+
1231
+ return !hasDuplicates;
1232
+ };