wrangler 0.0.0-e043479 → 0.0.0-e0a0509

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 (96) hide show
  1. package/README.md +27 -14
  2. package/bin/wrangler.js +13 -1
  3. package/miniflare-dist/index.mjs +4886 -0
  4. package/package.json +38 -25
  5. package/pages/functions/buildPlugin.ts +92 -0
  6. package/pages/functions/buildWorker.ts +80 -4
  7. package/pages/functions/filepath-routing.test.ts +136 -25
  8. package/pages/functions/filepath-routing.ts +14 -3
  9. package/pages/functions/routes.ts +6 -1
  10. package/pages/functions/template-plugin.ts +146 -0
  11. package/pages/functions/template-worker.ts +21 -8
  12. package/src/__tests__/config-cache.test.ts +36 -0
  13. package/src/__tests__/configuration.test.ts +1294 -355
  14. package/src/__tests__/dev.test.tsx +592 -12
  15. package/src/__tests__/guess-worker-format.test.ts +37 -18
  16. package/src/__tests__/helpers/mock-account-id.ts +4 -0
  17. package/src/__tests__/helpers/mock-cfetch.ts +8 -4
  18. package/src/__tests__/helpers/mock-console.ts +20 -3
  19. package/src/__tests__/helpers/mock-dialogs.ts +26 -1
  20. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  21. package/src/__tests__/helpers/mock-istty.ts +27 -0
  22. package/src/__tests__/helpers/mock-kv.ts +9 -16
  23. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  24. package/src/__tests__/helpers/run-in-tmp.ts +10 -0
  25. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  26. package/src/__tests__/https-options.test.ts +130 -0
  27. package/src/__tests__/index.test.ts +129 -753
  28. package/src/__tests__/init.test.ts +1516 -0
  29. package/src/__tests__/jest.setup.ts +13 -5
  30. package/src/__tests__/kv.test.ts +208 -174
  31. package/src/__tests__/logger.test.ts +115 -0
  32. package/src/__tests__/pages.test.ts +327 -0
  33. package/src/__tests__/parse.test.ts +4 -0
  34. package/src/__tests__/publish.test.ts +3313 -505
  35. package/src/__tests__/r2.test.ts +38 -26
  36. package/src/__tests__/route.test.ts +45 -0
  37. package/src/__tests__/secret.test.ts +55 -34
  38. package/src/__tests__/tail.test.ts +146 -20
  39. package/src/__tests__/user.test.ts +131 -0
  40. package/src/__tests__/whoami.test.tsx +26 -7
  41. package/src/bundle.ts +43 -7
  42. package/src/cfetch/internal.ts +9 -5
  43. package/src/cli.ts +5 -11
  44. package/src/config/README.md +7 -7
  45. package/src/config/config.ts +69 -105
  46. package/src/config/environment.ts +93 -4
  47. package/src/config/index.ts +15 -4
  48. package/src/config/validation-helpers.ts +228 -23
  49. package/src/config/validation.ts +559 -154
  50. package/src/config-cache.ts +45 -0
  51. package/src/{api/preview.ts → create-worker-preview.ts} +41 -13
  52. package/src/{api/form_data.ts → create-worker-upload-form.ts} +33 -10
  53. package/src/dev/dev-vars.ts +36 -0
  54. package/src/dev/dev.tsx +418 -0
  55. package/src/dev/local.tsx +312 -0
  56. package/src/dev/remote.tsx +213 -0
  57. package/src/dev/use-esbuild.ts +118 -0
  58. package/src/dialogs.tsx +16 -6
  59. package/src/entry.ts +222 -12
  60. package/src/environment-variables.ts +3 -1
  61. package/src/errors.ts +11 -0
  62. package/src/https-options.ts +123 -0
  63. package/src/index.tsx +878 -482
  64. package/src/inspect.ts +25 -11
  65. package/src/intl-polyfill.d.ts +139 -0
  66. package/src/{kv.tsx → kv.ts} +7 -27
  67. package/src/logger.ts +66 -0
  68. package/src/miniflare-cli/README.md +30 -0
  69. package/src/miniflare-cli/enum-keys.ts +17 -0
  70. package/src/miniflare-cli/index.ts +41 -0
  71. package/src/miniflare-cli/request-context.ts +40 -0
  72. package/src/module-collection.ts +94 -6
  73. package/src/open-in-browser.ts +18 -5
  74. package/src/package-manager.ts +9 -8
  75. package/src/pages.tsx +892 -62
  76. package/src/parse.ts +4 -2
  77. package/src/proxy.ts +128 -55
  78. package/src/publish.ts +305 -199
  79. package/src/selfsigned.d.ts +29 -0
  80. package/src/sites.tsx +88 -33
  81. package/src/tail/index.ts +2 -2
  82. package/src/tail/printing.ts +13 -8
  83. package/src/update-check.ts +19 -0
  84. package/src/user.tsx +244 -104
  85. package/src/whoami.tsx +2 -1
  86. package/src/{api/worker.ts → worker.ts} +15 -29
  87. package/templates/gitignore +171 -0
  88. package/templates/static-asset-facade.js +1 -1
  89. package/wrangler-dist/cli.js +23568 -50489
  90. package/src/__tests__/helpers/mock-user.ts +0 -27
  91. package/src/__tests__/logout.test.ts +0 -50
  92. package/src/__tests__/sentry.test.ts +0 -146
  93. package/src/dev.tsx +0 -960
  94. package/src/guess-worker-format.ts +0 -68
  95. package/src/reporting.ts +0 -115
  96. package/wrangler-dist/cli.js.map +0 -7
@@ -19,11 +19,24 @@ import {
19
19
  validateOptionalTypedArray,
20
20
  validateRequiredProperty,
21
21
  validateTypedArray,
22
+ all,
23
+ isMutuallyExclusiveWith,
24
+ inheritableInLegacyEnvironments,
25
+ appendEnvName,
26
+ getBindingNames,
27
+ isValidName,
22
28
  } from "./validation-helpers";
23
- import type { Config, DeprecatedUpload, RawConfig } from "./config";
24
- import type { RawEnvironment, Environment, Rule } from "./environment";
29
+ import type { Config, DevConfig, RawConfig, RawDevConfig } from "./config";
30
+ import type {
31
+ RawEnvironment,
32
+ DeprecatedUpload,
33
+ Environment,
34
+ Rule,
35
+ } from "./environment";
25
36
  import type { ValidatorFn } from "./validation-helpers";
26
37
 
38
+ const ENGLISH = new Intl.ListFormat("en");
39
+
27
40
  /**
28
41
  * Validate the given `rawConfig` object that was loaded from `configPath`.
29
42
  *
@@ -34,7 +47,8 @@ import type { ValidatorFn } from "./validation-helpers";
34
47
  */
35
48
  export function normalizeAndValidateConfig(
36
49
  rawConfig: RawConfig,
37
- configPath: string | undefined
50
+ configPath: string | undefined,
51
+ args: unknown
38
52
  ): {
39
53
  config: Config;
40
54
  diagnostics: Diagnostics;
@@ -48,23 +62,28 @@ export function normalizeAndValidateConfig(
48
62
  deprecated(
49
63
  diagnostics,
50
64
  rawConfig,
51
- "type",
52
- "DO NOT USE THIS. Most common features now work out of the box with wrangler, including modules, jsx, typescript, etc. If you need anything more, use a custom build.",
53
- true
65
+ "miniflare",
66
+ "Wrangler does not use configuration in the `miniflare` section. Unless you are using Miniflare directly you can remove this section.",
67
+ true,
68
+ "😶 Ignored"
54
69
  );
55
70
 
56
71
  deprecated(
57
72
  diagnostics,
58
73
  rawConfig,
59
- "webpack_config",
60
- "DO NOT USE THIS. Most common features now work out of the box with wrangler, including modules, jsx, typescript, etc. If you need anything more, use a custom build.",
61
- true
74
+ "type",
75
+ "Most common features now work out of the box with wrangler, including modules, jsx, typescript, etc. If you need anything more, use a custom build.",
76
+ true,
77
+ "😶 Ignored"
62
78
  );
63
79
 
64
- const { deprecatedUpload, ...build } = normalizeAndValidateBuild(
80
+ deprecated(
65
81
  diagnostics,
66
82
  rawConfig,
67
- rawConfig.build ?? {}
83
+ "webpack_config",
84
+ "Most common features now work out of the box with wrangler, including modules, jsx, typescript, etc. If you need anything more, use a custom build.",
85
+ true,
86
+ "😶 Ignored"
68
87
  );
69
88
 
70
89
  validateOptionalProperty(
@@ -75,29 +94,113 @@ export function normalizeAndValidateConfig(
75
94
  "boolean"
76
95
  );
77
96
 
97
+ validateOptionalProperty(
98
+ diagnostics,
99
+ "",
100
+ "minify",
101
+ rawConfig.minify,
102
+ "boolean"
103
+ );
104
+
105
+ validateOptionalProperty(
106
+ diagnostics,
107
+ "",
108
+ "node_compat",
109
+ rawConfig.node_compat,
110
+ "boolean"
111
+ );
112
+
113
+ // TODO: set the default to false to turn on service environments as the default
114
+ const isLegacyEnv =
115
+ (args as { "legacy-env": boolean | undefined })["legacy-env"] ??
116
+ rawConfig.legacy_env ??
117
+ true;
118
+
119
+ // TODO: remove this once service environments goes GA.
120
+ if (!isLegacyEnv) {
121
+ diagnostics.warnings.push(
122
+ "Experimental: Service environments are in beta, and their behaviour is guaranteed to change in the future. DO NOT USE IN PRODUCTION."
123
+ );
124
+ }
125
+
126
+ const topLevelEnv = normalizeAndValidateEnvironment(
127
+ diagnostics,
128
+ configPath,
129
+ rawConfig
130
+ );
131
+
132
+ //TODO: find a better way to define the type of Args that can be passed to the normalizeAndValidateConfig()
133
+ const envName = (args as { env: string | undefined }).env;
134
+
135
+ let activeEnv = topLevelEnv;
136
+ if (envName !== undefined) {
137
+ const envDiagnostics = new Diagnostics(
138
+ `"env.${envName}" environment configuration`
139
+ );
140
+ const rawEnv = rawConfig.env?.[envName];
141
+ if (rawEnv !== undefined) {
142
+ activeEnv = normalizeAndValidateEnvironment(
143
+ envDiagnostics,
144
+ configPath,
145
+ rawEnv,
146
+ envName,
147
+ topLevelEnv,
148
+ isLegacyEnv,
149
+ rawConfig
150
+ );
151
+ diagnostics.addChild(envDiagnostics);
152
+ } else {
153
+ // An environment was specified, but no configuration for it was found.
154
+ // To cover any legacy environment cases, where the `envName` is used,
155
+ // Let's create a fake active environment with the specified `envName`.
156
+ activeEnv = normalizeAndValidateEnvironment(
157
+ envDiagnostics,
158
+ configPath,
159
+ {},
160
+ envName,
161
+ topLevelEnv,
162
+ isLegacyEnv,
163
+ rawConfig
164
+ );
165
+ const envNames = rawConfig.env
166
+ ? `The available configured environment names are: ${JSON.stringify(
167
+ Object.keys(rawConfig.env)
168
+ )}\n`
169
+ : "";
170
+ const message =
171
+ `No environment found in configuration with name "${envName}".\n` +
172
+ `Before using \`--env=${envName}\` there should be an equivalent environment section in the configuration.\n` +
173
+ `${envNames}\n` +
174
+ `Consider adding an environment configuration section to the wrangler.toml file:\n` +
175
+ "```\n[env." +
176
+ envName +
177
+ "]\n```\n";
178
+
179
+ if (envNames.length > 0) {
180
+ diagnostics.errors.push(message);
181
+ } else {
182
+ // Only warn (rather than error) if there are not actually any environments configured in wrangler.toml.
183
+ diagnostics.warnings.push(message);
184
+ }
185
+ }
186
+ }
187
+
78
188
  // Process the top-level default environment configuration.
79
189
  const config: Config = {
80
190
  configPath,
81
- main: normalizeAndValidateMainField(
82
- diagnostics,
83
- configPath,
84
- rawConfig.main,
85
- deprecatedUpload
86
- ),
87
- // TODO: set the default to false to turn on service environments as the default
88
- legacy_env: rawConfig.legacy_env ?? true,
89
- ...normalizeAndValidateEnvironment(
90
- diagnostics,
91
- rawConfig,
92
- deprecatedUpload.rules
93
- ),
94
- env: {}, // Will be filled below by `normalizeAndValidateEnvironment()`.
191
+ legacy_env: isLegacyEnv,
192
+ ...activeEnv,
95
193
  dev: normalizeAndValidateDev(diagnostics, rawConfig.dev ?? {}),
96
194
  migrations: normalizeAndValidateMigrations(
97
195
  diagnostics,
98
196
  rawConfig.migrations ?? []
99
197
  ),
100
- site: normalizeAndValidateSite(diagnostics, rawConfig.site),
198
+ site: normalizeAndValidateSite(
199
+ diagnostics,
200
+ configPath,
201
+ rawConfig,
202
+ activeEnv.main
203
+ ),
101
204
  wasm_modules: normalizeAndValidateModulePaths(
102
205
  diagnostics,
103
206
  configPath,
@@ -110,33 +213,21 @@ export function normalizeAndValidateConfig(
110
213
  "text_blobs",
111
214
  rawConfig.text_blobs
112
215
  ),
113
- build,
216
+ data_blobs: normalizeAndValidateModulePaths(
217
+ diagnostics,
218
+ configPath,
219
+ "data_blobs",
220
+ rawConfig.data_blobs
221
+ ),
114
222
  };
115
223
 
116
- // Process each of the specific environment configurations.
117
- if (rawConfig.env !== undefined) {
118
- for (const envName of Object.keys(rawConfig.env)) {
119
- const envDiagnostics = new Diagnostics(
120
- `"env.${envName}" environment configuration`
121
- );
122
- const environment = normalizeAndValidateEnvironment(
123
- envDiagnostics,
124
- rawConfig.env[envName] as RawEnvironment,
125
- deprecatedUpload.rules,
126
- envName,
127
- config,
128
- rawConfig
129
- );
130
- config.env[envName] = environment;
131
- diagnostics.addChild(envDiagnostics);
132
- }
133
- }
224
+ validateBindingsHaveUniqueNames(diagnostics, config);
134
225
 
135
226
  validateAdditionalProperties(
136
227
  diagnostics,
137
228
  "top-level",
138
229
  Object.keys(rawConfig),
139
- Object.keys(config)
230
+ [...Object.keys(config), "env"]
140
231
  );
141
232
 
142
233
  return { config, diagnostics };
@@ -147,8 +238,9 @@ export function normalizeAndValidateConfig(
147
238
  */
148
239
  function normalizeAndValidateBuild(
149
240
  diagnostics: Diagnostics,
150
- rawConfig: RawConfig,
151
- rawBuild: Config["build"]
241
+ rawEnv: RawEnvironment,
242
+ rawBuild: Config["build"],
243
+ configPath: string | undefined
152
244
  ): Config["build"] & { deprecatedUpload: DeprecatedUpload } {
153
245
  const { command, cwd, watch_dir, upload, ...rest } = rawBuild;
154
246
  const deprecatedUpload: DeprecatedUpload = { ...upload };
@@ -166,13 +258,13 @@ function normalizeAndValidateBuild(
166
258
 
167
259
  deprecated(
168
260
  diagnostics,
169
- rawConfig,
261
+ rawEnv,
170
262
  "build.upload.format",
171
263
  "The format is inferred automatically from the code.",
172
264
  true
173
265
  );
174
266
 
175
- if (rawConfig.main !== undefined && rawBuild.upload?.main) {
267
+ if (rawEnv.main !== undefined && rawBuild.upload?.main) {
176
268
  diagnostics.errors.push(
177
269
  `Don't define both the \`main\` and \`build.upload.main\` fields in your configuration.\n` +
178
270
  `They serve the same purpose: to point to the entry-point of your worker.\n` +
@@ -181,7 +273,7 @@ function normalizeAndValidateBuild(
181
273
  } else {
182
274
  deprecated(
183
275
  diagnostics,
184
- rawConfig,
276
+ rawEnv,
185
277
  "build.upload.main",
186
278
  `Delete the \`build.upload.main\` and \`build.upload.dir\` fields.\n` +
187
279
  `Then add the top level \`main\` field to your configuration file:\n` +
@@ -196,24 +288,41 @@ function normalizeAndValidateBuild(
196
288
 
197
289
  deprecated(
198
290
  diagnostics,
199
- rawConfig,
291
+ rawEnv,
200
292
  "build.upload.dir",
201
293
  `Use the top level "main" field or a command-line argument to specify the entry-point for the Worker.`,
202
294
  true
203
295
  );
204
296
  }
205
297
 
206
- return { command, cwd, watch_dir, deprecatedUpload };
298
+ return {
299
+ command,
300
+ watch_dir:
301
+ // - `watch_dir` only matters when `command` is defined, so we apply
302
+ // a default only when `command` is defined
303
+ // - `configPath` will always be defined since `build` can only
304
+ // be configured in `wrangler.toml`, but who knows, that may
305
+ // change in the future, so we do a check anyway
306
+ command
307
+ ? configPath
308
+ ? path.relative(
309
+ process.cwd(),
310
+ path.join(path.dirname(configPath), watch_dir || "./src")
311
+ )
312
+ : watch_dir || "./src"
313
+ : watch_dir,
314
+ cwd,
315
+ deprecatedUpload,
316
+ };
207
317
  }
208
318
 
209
319
  /**
210
320
  * Validate the `main` field and return the normalized values.
211
321
  */
212
322
  function normalizeAndValidateMainField(
213
- diagnostics: Diagnostics,
214
323
  configPath: string | undefined,
215
324
  rawMain: string | undefined,
216
- deprecatedUpload: { dir?: string; main?: string }
325
+ deprecatedUpload: DeprecatedUpload | undefined
217
326
  ): string | undefined {
218
327
  const configDir = path.dirname(configPath ?? "wrangler.toml");
219
328
  if (rawMain !== undefined) {
@@ -221,13 +330,13 @@ function normalizeAndValidateMainField(
221
330
  const directory = path.resolve(configDir);
222
331
  return path.resolve(directory, rawMain);
223
332
  } else {
224
- diagnostics.errors.push(
225
- `ERROR: "main" should be a string but got ${JSON.stringify(rawMain)}`
226
- );
227
- return;
333
+ return rawMain;
228
334
  }
229
- } else if (deprecatedUpload.main !== undefined) {
230
- const directory = path.resolve(configDir, deprecatedUpload.dir || "./dist");
335
+ } else if (deprecatedUpload?.main !== undefined) {
336
+ const directory = path.resolve(
337
+ configDir,
338
+ deprecatedUpload?.dir || "./dist"
339
+ );
231
340
  return path.resolve(directory, deprecatedUpload.main);
232
341
  } else {
233
342
  return;
@@ -239,11 +348,11 @@ function normalizeAndValidateMainField(
239
348
  */
240
349
  function normalizeAndValidateDev(
241
350
  diagnostics: Diagnostics,
242
- rawDev: Config["dev"]
243
- ): Config["dev"] {
351
+ rawDev: RawDevConfig
352
+ ): DevConfig {
244
353
  const {
245
- ip = "127.0.0.1",
246
- port = 8787,
354
+ ip = "localhost",
355
+ port,
247
356
  local_protocol = "http",
248
357
  upstream_protocol = "https",
249
358
  host,
@@ -258,14 +367,16 @@ function normalizeAndValidateDev(
258
367
  "dev",
259
368
  "local_protocol",
260
369
  local_protocol,
261
- "string"
370
+ "string",
371
+ ["http", "https"]
262
372
  );
263
373
  validateOptionalProperty(
264
374
  diagnostics,
265
375
  "dev",
266
376
  "upstream_protocol",
267
377
  upstream_protocol,
268
- "string"
378
+ "string",
379
+ ["http", "https"]
269
380
  );
270
381
  validateOptionalProperty(diagnostics, "dev", "host", host, "string");
271
382
  return { ip, port, local_protocol, upstream_protocol, host };
@@ -304,7 +415,7 @@ function normalizeAndValidateMigrations(
304
415
  if (migration.renamed_classes !== undefined) {
305
416
  if (!Array.isArray(migration.renamed_classes)) {
306
417
  diagnostics.errors.push(
307
- `"migrations[${i}].renamed_classes" should be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
418
+ `Expected "migrations[${i}].renamed_classes" to be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
308
419
  migration.renamed_classes
309
420
  )}.`
310
421
  );
@@ -317,7 +428,7 @@ function normalizeAndValidateMigrations(
317
428
  )
318
429
  ) {
319
430
  diagnostics.errors.push(
320
- `"migrations[${i}].renamed_classes" should be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
431
+ `Expected "migrations[${i}].renamed_classes" to be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
321
432
  migration.renamed_classes
322
433
  )}.`
323
434
  );
@@ -339,39 +450,91 @@ function normalizeAndValidateMigrations(
339
450
  */
340
451
  function normalizeAndValidateSite(
341
452
  diagnostics: Diagnostics,
342
- rawSite: Config["site"]
453
+ configPath: string | undefined,
454
+ rawConfig: RawConfig,
455
+ mainEntryPoint: string | undefined
343
456
  ): Config["site"] {
344
- if (rawSite !== undefined) {
345
- const {
346
- bucket,
347
- "entry-point": entryPoint,
348
- include = [],
349
- exclude = [],
350
- ...rest
351
- } = rawSite;
352
- validateAdditionalProperties(diagnostics, "site", Object.keys(rest), []);
457
+ if (rawConfig?.site !== undefined) {
458
+ const { bucket, include = [], exclude = [], ...rest } = rawConfig.site;
459
+
460
+ validateAdditionalProperties(diagnostics, "site", Object.keys(rest), [
461
+ "entry-point",
462
+ ]);
353
463
  validateRequiredProperty(diagnostics, "site", "bucket", bucket, "string");
464
+ validateTypedArray(diagnostics, "sites.include", include, "string");
465
+ validateTypedArray(diagnostics, "sites.exclude", exclude, "string");
354
466
  validateOptionalProperty(
355
467
  diagnostics,
356
468
  "site",
357
- "entry_point",
358
- entryPoint,
469
+ "entry-point",
470
+ rawConfig.site["entry-point"],
359
471
  "string"
360
472
  );
361
- validateTypedArray(diagnostics, "sites.include", include, "string");
362
- validateTypedArray(diagnostics, "sites.exclude", exclude, "string");
363
- return { bucket, "entry-point": entryPoint, include, exclude };
473
+
474
+ deprecated(
475
+ diagnostics,
476
+ rawConfig,
477
+ `site.entry-point`,
478
+ `Delete the \`site.entry-point\` field, then add the top level \`main\` field to your configuration file:\n` +
479
+ `\`\`\`\n` +
480
+ `main = "${path.join(
481
+ String(rawConfig.site["entry-point"]) || "workers-site",
482
+ path.extname(String(rawConfig.site["entry-point"]) || "workers-site")
483
+ ? ""
484
+ : "index.js"
485
+ )}"\n` +
486
+ `\`\`\``,
487
+ false,
488
+ undefined,
489
+ "warning"
490
+ );
491
+
492
+ let siteEntryPoint = rawConfig.site["entry-point"];
493
+
494
+ if (!mainEntryPoint && !siteEntryPoint) {
495
+ // this means that we're defaulting to "workers-site"
496
+ // so let's add the deprecation warning
497
+ diagnostics.warnings.push(
498
+ `Because you've defined a [site] configuration, we're defaulting to "workers-site" for the deprecated \`site.entry-point\`field.\n` +
499
+ `Add the top level \`main\` field to your configuration file:\n` +
500
+ `\`\`\`\n` +
501
+ `main = "workers-site/index.js"\n` +
502
+ `\`\`\``
503
+ );
504
+ siteEntryPoint = "workers-site";
505
+ } else if (mainEntryPoint && siteEntryPoint) {
506
+ diagnostics.errors.push(
507
+ `Don't define both the \`main\` and \`site.entry-point\` fields in your configuration.\n` +
508
+ `They serve the same purpose: to point to the entry-point of your worker.\n` +
509
+ `Delete the deprecated \`site.entry-point\` field from your config.`
510
+ );
511
+ }
512
+
513
+ if (configPath && siteEntryPoint) {
514
+ // rewrite the path to be relative to the working directory
515
+ siteEntryPoint = path.relative(
516
+ process.cwd(),
517
+ path.join(path.dirname(configPath), siteEntryPoint)
518
+ );
519
+ }
520
+
521
+ return {
522
+ bucket,
523
+ "entry-point": siteEntryPoint,
524
+ include,
525
+ exclude,
526
+ };
364
527
  }
365
528
  return undefined;
366
529
  }
367
530
 
368
531
  /**
369
- * Map the paths of the `wasm_modules` or `text_blobs` configuration to be relative to the current working directory.
532
+ * Map the paths of the `wasm_modules`, `text_blobs` or `data_blobs` configuration to be relative to the current working directory.
370
533
  */
371
534
  function normalizeAndValidateModulePaths(
372
535
  diagnostics: Diagnostics,
373
536
  configPath: string | undefined,
374
- field: "wasm_modules" | "text_blobs",
537
+ field: "wasm_modules" | "text_blobs" | "data_blobs",
375
538
  rawMapping: Record<string, string> | undefined
376
539
  ): Record<string, string> | undefined {
377
540
  if (rawMapping === undefined) {
@@ -394,31 +557,129 @@ function normalizeAndValidateModulePaths(
394
557
  return mapping;
395
558
  }
396
559
 
560
+ /**
561
+ * Check whether a value has the shape of a route, which can be a string
562
+ * or an object that looks like {pattern: string, zone_id: string }
563
+ */
564
+ function isValidRouteValue(item: unknown): boolean {
565
+ return (
566
+ !!item &&
567
+ (typeof item === "string" ||
568
+ (typeof item === "object" &&
569
+ hasProperty(item, "pattern") &&
570
+ typeof item.pattern === "string" &&
571
+ // it could have a zone_name
572
+ ((hasProperty(item, "zone_name") &&
573
+ typeof item.zone_name === "string") ||
574
+ // or a zone_id
575
+ (hasProperty(item, "zone_id") && typeof item.zone_id === "string")) &&
576
+ Object.keys(item).length === 2))
577
+ );
578
+ }
579
+
580
+ /**
581
+ * Validate that the field is a route.
582
+ */
583
+ const isRoute: ValidatorFn = (diagnostics, field, value) => {
584
+ if (value !== undefined && !isValidRouteValue(value)) {
585
+ diagnostics.errors.push(
586
+ `Expected "${field}" to be either a string, or an object with shape { pattern, zone_id | zone_name }, but got ${JSON.stringify(
587
+ value
588
+ )}.`
589
+ );
590
+ return false;
591
+ }
592
+ return true;
593
+ };
594
+
595
+ /**
596
+ * Validate that the field is an array of routes.
597
+ */
598
+ const isRouteArray: ValidatorFn = (diagnostics, field, value) => {
599
+ if (value === undefined) {
600
+ return true;
601
+ }
602
+ if (!Array.isArray(value)) {
603
+ diagnostics.errors.push(
604
+ `Expected "${field}" to be an array but got ${JSON.stringify(value)}.`
605
+ );
606
+ return false;
607
+ }
608
+ const invalidRoutes = [];
609
+ for (const item of value) {
610
+ if (!isValidRouteValue(item)) {
611
+ invalidRoutes.push(item);
612
+ }
613
+ }
614
+ if (invalidRoutes.length > 0) {
615
+ diagnostics.errors.push(
616
+ `Expected "${field}" to be an array of either strings or objects with the shape { pattern, zone_id | zone_name }, but these weren't valid: ${JSON.stringify(
617
+ invalidRoutes,
618
+ null,
619
+ 2
620
+ )}.`
621
+ );
622
+ }
623
+ return invalidRoutes.length === 0;
624
+ };
625
+
626
+ function validateRoute(
627
+ diagnostics: Diagnostics,
628
+ topLevelEnv: Environment | undefined,
629
+ rawEnv: RawEnvironment
630
+ ): Config["route"] {
631
+ return inheritable(
632
+ diagnostics,
633
+ topLevelEnv,
634
+ rawEnv,
635
+ "route",
636
+ isRoute,
637
+ undefined
638
+ );
639
+ }
640
+
641
+ function validateRoutes(
642
+ diagnostics: Diagnostics,
643
+ topLevelEnv: Environment | undefined,
644
+ rawEnv: RawEnvironment
645
+ ): Config["routes"] {
646
+ return inheritable(
647
+ diagnostics,
648
+ topLevelEnv,
649
+ rawEnv,
650
+ "routes",
651
+ all(isRouteArray, isMutuallyExclusiveWith(rawEnv, "route")),
652
+ undefined
653
+ );
654
+ }
655
+
397
656
  /**
398
657
  * Validate top-level environment configuration and return the normalized values.
399
658
  */
400
659
  function normalizeAndValidateEnvironment(
401
660
  diagnostics: Diagnostics,
402
- topLevelEnv: RawEnvironment,
403
- deprecatedRules: Environment["rules"] | undefined
661
+ configPath: string | undefined,
662
+ topLevelEnv: RawEnvironment
404
663
  ): Environment;
405
664
  /**
406
665
  * Validate the named environment configuration and return the normalized values.
407
666
  */
408
667
  function normalizeAndValidateEnvironment(
409
668
  diagnostics: Diagnostics,
669
+ configPath: string | undefined,
410
670
  rawEnv: RawEnvironment,
411
- deprecatedRules: Environment["rules"] | undefined,
412
671
  envName: string,
413
- config: Config,
672
+ topLevelEnv: Environment,
673
+ isLegacyEnv: boolean,
414
674
  rawConfig: RawConfig
415
675
  ): Environment;
416
676
  function normalizeAndValidateEnvironment(
417
677
  diagnostics: Diagnostics,
678
+ configPath: string | undefined,
418
679
  rawEnv: RawEnvironment,
419
- deprecatedRules: Environment["rules"] | undefined,
420
680
  envName = "top level",
421
- config?: Config | undefined,
681
+ topLevelEnv?: Environment | undefined,
682
+ isLegacyEnv?: boolean,
422
683
  rawConfig?: RawConfig | undefined
423
684
  ): Environment {
424
685
  deprecated(
@@ -457,44 +718,41 @@ function normalizeAndValidateEnvironment(
457
718
 
458
719
  experimental(diagnostics, rawEnv, "unsafe");
459
720
 
460
- const route = inheritable(
461
- diagnostics,
462
- config,
463
- rawEnv,
464
- "route",
465
- isString,
466
- undefined
467
- );
468
- const routes = inheritable(
721
+ const route = validateRoute(diagnostics, topLevelEnv, rawEnv);
722
+
723
+ const routes = validateRoutes(diagnostics, topLevelEnv, rawEnv);
724
+
725
+ const workers_dev = inheritable(
469
726
  diagnostics,
470
- config,
727
+ topLevelEnv,
471
728
  rawEnv,
472
- "routes",
473
- isStringArray,
729
+ "workers_dev",
730
+ isBoolean,
474
731
  undefined
475
732
  );
476
- const workers_dev = inheritable(
733
+
734
+ const { deprecatedUpload, ...build } = normalizeAndValidateBuild(
477
735
  diagnostics,
478
- config,
479
736
  rawEnv,
480
- "workers_dev",
481
- isBoolean,
482
- !(routes || route)
737
+ rawEnv.build ?? topLevelEnv?.build ?? {},
738
+ configPath
483
739
  );
484
740
 
485
741
  const environment: Environment = {
486
742
  // Inherited fields
487
- account_id: inheritable(
743
+ account_id: inheritableInLegacyEnvironments(
488
744
  diagnostics,
489
- config,
745
+ isLegacyEnv,
746
+ topLevelEnv,
490
747
  rawEnv,
491
748
  "account_id",
492
749
  isString,
750
+ undefined,
493
751
  undefined
494
752
  ),
495
753
  compatibility_date: inheritable(
496
754
  diagnostics,
497
- config,
755
+ topLevelEnv,
498
756
  rawEnv,
499
757
  "compatibility_date",
500
758
  isString,
@@ -502,7 +760,7 @@ function normalizeAndValidateEnvironment(
502
760
  ),
503
761
  compatibility_flags: inheritable(
504
762
  diagnostics,
505
- config,
763
+ topLevelEnv,
506
764
  rawEnv,
507
765
  "compatibility_flags",
508
766
  isStringArray,
@@ -510,7 +768,7 @@ function normalizeAndValidateEnvironment(
510
768
  ),
511
769
  jsx_factory: inheritable(
512
770
  diagnostics,
513
- config,
771
+ topLevelEnv,
514
772
  rawEnv,
515
773
  "jsx_factory",
516
774
  isString,
@@ -518,25 +776,52 @@ function normalizeAndValidateEnvironment(
518
776
  ),
519
777
  jsx_fragment: inheritable(
520
778
  diagnostics,
521
- config,
779
+ topLevelEnv,
522
780
  rawEnv,
523
781
  "jsx_fragment",
524
782
  isString,
525
783
  "React.Fragment"
526
784
  ),
785
+ tsconfig: validateAndNormalizeTsconfig(
786
+ diagnostics,
787
+ topLevelEnv,
788
+ rawEnv,
789
+ configPath
790
+ ),
527
791
  rules: validateAndNormalizeRules(
528
792
  diagnostics,
529
- config,
793
+ topLevelEnv,
530
794
  rawEnv,
531
- deprecatedRules,
795
+ deprecatedUpload?.rules,
532
796
  envName
533
797
  ),
534
- name: inheritable(diagnostics, config, rawEnv, "name", isString, undefined),
798
+ name: inheritableInLegacyEnvironments(
799
+ diagnostics,
800
+ isLegacyEnv,
801
+ topLevelEnv,
802
+ rawEnv,
803
+ "name",
804
+ isValidName,
805
+ appendEnvName(envName),
806
+ undefined
807
+ ),
808
+ main: normalizeAndValidateMainField(
809
+ configPath,
810
+ inheritable(
811
+ diagnostics,
812
+ topLevelEnv,
813
+ rawEnv,
814
+ "main",
815
+ isString,
816
+ undefined
817
+ ),
818
+ deprecatedUpload
819
+ ),
535
820
  route,
536
821
  routes,
537
822
  triggers: inheritable(
538
823
  diagnostics,
539
- config,
824
+ topLevelEnv,
540
825
  rawEnv,
541
826
  "triggers",
542
827
  isObjectWith("crons"),
@@ -544,17 +829,18 @@ function normalizeAndValidateEnvironment(
544
829
  ),
545
830
  usage_model: inheritable(
546
831
  diagnostics,
547
- config,
832
+ topLevelEnv,
548
833
  rawEnv,
549
834
  "usage_model",
550
835
  isOneOf("bundled", "unbound"),
551
836
  undefined
552
837
  ),
838
+ build,
553
839
  workers_dev,
554
840
  // Not inherited fields
555
841
  vars: notInheritable(
556
842
  diagnostics,
557
- config,
843
+ topLevelEnv,
558
844
  rawConfig,
559
845
  rawEnv,
560
846
  envName,
@@ -564,7 +850,7 @@ function normalizeAndValidateEnvironment(
564
850
  ),
565
851
  durable_objects: notInheritable(
566
852
  diagnostics,
567
- config,
853
+ topLevelEnv,
568
854
  rawConfig,
569
855
  rawEnv,
570
856
  envName,
@@ -576,7 +862,7 @@ function normalizeAndValidateEnvironment(
576
862
  ),
577
863
  kv_namespaces: notInheritable(
578
864
  diagnostics,
579
- config,
865
+ topLevelEnv,
580
866
  rawConfig,
581
867
  rawEnv,
582
868
  envName,
@@ -586,7 +872,7 @@ function normalizeAndValidateEnvironment(
586
872
  ),
587
873
  r2_buckets: notInheritable(
588
874
  diagnostics,
589
- config,
875
+ topLevelEnv,
590
876
  rawConfig,
591
877
  rawEnv,
592
878
  envName,
@@ -596,7 +882,7 @@ function normalizeAndValidateEnvironment(
596
882
  ),
597
883
  unsafe: notInheritable(
598
884
  diagnostics,
599
- config,
885
+ topLevelEnv,
600
886
  rawConfig,
601
887
  rawEnv,
602
888
  envName,
@@ -607,27 +893,52 @@ function normalizeAndValidateEnvironment(
607
893
  }
608
894
  ),
609
895
  zone_id: rawEnv.zone_id,
896
+ minify: rawEnv.minify,
897
+ node_compat: rawEnv.node_compat,
610
898
  };
611
899
 
612
900
  return environment;
613
901
  }
614
902
 
903
+ function validateAndNormalizeTsconfig(
904
+ diagnostics: Diagnostics,
905
+ topLevelEnv: Environment | undefined,
906
+ rawEnv: RawEnvironment,
907
+ configPath: string | undefined
908
+ ) {
909
+ const tsconfig = inheritable(
910
+ diagnostics,
911
+ topLevelEnv,
912
+ rawEnv,
913
+ "tsconfig",
914
+ isString,
915
+ undefined
916
+ );
917
+
918
+ return configPath && tsconfig
919
+ ? path.relative(
920
+ process.cwd(),
921
+ path.join(path.dirname(configPath), tsconfig)
922
+ )
923
+ : tsconfig;
924
+ }
925
+
615
926
  const validateAndNormalizeRules = (
616
927
  diagnostics: Diagnostics,
617
- config: Config | undefined,
928
+ topLevelEnv: Environment | undefined,
618
929
  rawEnv: RawEnvironment,
619
930
  deprecatedRules: Rule[] | undefined,
620
931
  envName: string
621
932
  ): Rule[] => {
622
- if (config === undefined) {
933
+ if (topLevelEnv === undefined) {
623
934
  // Only create errors/warnings for the top-level environment
624
935
  if (rawEnv.rules && deprecatedRules) {
625
936
  diagnostics.errors.push(
626
- `ERROR: You cannot configure both [rules] and [build.upload.rules] in your wrangler.toml. Delete the \`build.upload\` section.`
937
+ `You cannot configure both [rules] and [build.upload.rules] in your wrangler.toml. Delete the \`build.upload\` section.`
627
938
  );
628
939
  } else if (deprecatedRules) {
629
940
  diagnostics.warnings.push(
630
- `DEPRECATION: The \`build.upload.rules\` config field is no longer used, the rules should be specified via the \`rules\` config field. Delete the \`build.upload\` field from the configuration file, and add this:\n` +
941
+ `Deprecation: The \`build.upload.rules\` config field is no longer used, the rules should be specified via the \`rules\` config field. Delete the \`build.upload\` field from the configuration file, and add this:\n` +
631
942
  "```\n" +
632
943
  TOML.stringify({ rules: deprecatedRules }) +
633
944
  "```"
@@ -637,7 +948,7 @@ const validateAndNormalizeRules = (
637
948
 
638
949
  return inheritable(
639
950
  diagnostics,
640
- config,
951
+ topLevelEnv,
641
952
  rawEnv,
642
953
  "rules",
643
954
  validateRules(envName),
@@ -673,7 +984,9 @@ const validateRules =
673
984
 
674
985
  const validateRule: ValidatorFn = (diagnostics, field, value) => {
675
986
  if (typeof value !== "object" || value === null) {
676
- diagnostics.errors.push(`ERROR: should be an object.`);
987
+ diagnostics.errors.push(
988
+ `"${field}" should be an object but got ${JSON.stringify(value)}.`
989
+ );
677
990
  return false;
678
991
  }
679
992
  // Rules must have a type string and glob string array, and optionally a fallthrough boolean.
@@ -690,7 +1003,7 @@ const validateRule: ValidatorFn = (diagnostics, field, value) => {
690
1003
  ])
691
1004
  ) {
692
1005
  diagnostics.errors.push(
693
- `ERROR: bindings should have a string "type" field, which contains one of "ESModule", "CommonJS", "CompiledWasm", "Text", or "Data".`
1006
+ `bindings should have a string "type" field, which contains one of "ESModule", "CommonJS", "CompiledWasm", "Text", or "Data".`
694
1007
  );
695
1008
  isValid = false;
696
1009
  }
@@ -701,7 +1014,7 @@ const validateRule: ValidatorFn = (diagnostics, field, value) => {
701
1014
 
702
1015
  if (!isOptionalProperty(rule, "fallthrough", "boolean")) {
703
1016
  diagnostics.errors.push(
704
- `ERROR: should, optionally, have a boolean "fallthrough" field.`
1017
+ `binding should, optionally, have a boolean "fallthrough" field.`
705
1018
  );
706
1019
  isValid = false;
707
1020
  }
@@ -798,7 +1111,7 @@ const validateBindingsProperty =
798
1111
  diagnostics.warnings.push(
799
1112
  `The following bindings are at the top level, but not on "env.${envName}".\n` +
800
1113
  `This is not what you probably want, since "${field}" configuration is not inherited by environments.\n` +
801
- `Please add a binding for each to "${fieldPath}.bindings".` +
1114
+ `Please add a binding for each to "${fieldPath}.bindings":\n` +
802
1115
  missingBindings.map((name) => `- ${name}`).join("\n")
803
1116
  );
804
1117
  }
@@ -807,42 +1120,34 @@ const validateBindingsProperty =
807
1120
  return isValid;
808
1121
  };
809
1122
 
810
- /**
811
- * Get the names of the bindings collection in `value`.
812
- */
813
- const getBindingNames = (value: unknown): string[] =>
814
- ((value as { bindings: { name: string }[] })?.bindings ?? []).map(
815
- (binding) => binding.name
816
- );
817
-
818
1123
  /**
819
1124
  * Check that the given field is a valid "durable_object" binding object.
820
1125
  */
821
1126
  const validateDurableObjectBinding: ValidatorFn = (
822
1127
  diagnostics,
823
- _field,
1128
+ field,
824
1129
  value
825
1130
  ) => {
826
1131
  if (typeof value !== "object" || value === null) {
827
- diagnostics.errors.push(`ERROR: should be an object.`);
1132
+ diagnostics.errors.push(
1133
+ `Expected "${field}" to be an object but got ${JSON.stringify(value)}`
1134
+ );
828
1135
  return false;
829
1136
  }
830
1137
 
831
1138
  // Durable Object bindings must have a name and class_name, and optionally a script_name.
832
1139
  let isValid = true;
833
1140
  if (!isRequiredProperty(value, "name", "string")) {
834
- diagnostics.errors.push(
835
- `ERROR: bindings should have a string "name" field.`
836
- );
1141
+ diagnostics.errors.push(`binding should have a string "name" field.`);
837
1142
  isValid = false;
838
1143
  }
839
1144
  if (!isRequiredProperty(value, "class_name", "string")) {
840
- diagnostics.errors.push(`ERROR: should have a string "class_name" field.`);
1145
+ diagnostics.errors.push(`binding should have a string "class_name" field.`);
841
1146
  isValid = false;
842
1147
  }
843
1148
  if (!isOptionalProperty(value, "script_name", "string")) {
844
1149
  diagnostics.errors.push(
845
- `ERROR: should, optionally, have a string "script_name" field.`
1150
+ `binding should, optionally, have a string "script_name" field.`
846
1151
  );
847
1152
  isValid = false;
848
1153
  }
@@ -855,16 +1160,18 @@ const validateDurableObjectBinding: ValidatorFn = (
855
1160
  *
856
1161
  * TODO: further validation of known unsafe bindings.
857
1162
  */
858
- const validateUnsafeBinding: ValidatorFn = (diagnostics, _field, value) => {
1163
+ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => {
859
1164
  if (typeof value !== "object" || value === null) {
860
- diagnostics.errors.push(`ERROR: should be an object.`);
1165
+ diagnostics.errors.push(
1166
+ `Expected ${field} to be an object but got ${JSON.stringify(value)}.`
1167
+ );
861
1168
  return false;
862
1169
  }
863
1170
 
864
1171
  let isValid = true;
865
1172
  // Unsafe bindings must have a name and type.
866
1173
  if (!isRequiredProperty(value, "name", "string")) {
867
- diagnostics.errors.push(`ERROR: should have a string "name" field.`);
1174
+ diagnostics.errors.push(`binding should have a string "name" field.`);
868
1175
  isValid = false;
869
1176
  }
870
1177
  if (isRequiredProperty(value, "type", "string")) {
@@ -883,7 +1190,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, _field, value) => {
883
1190
  );
884
1191
  }
885
1192
  } else {
886
- diagnostics.errors.push(`ERROR: should have a string "type" field.`);
1193
+ diagnostics.errors.push(`binding should have a string "type" field.`);
887
1194
  isValid = false;
888
1195
  }
889
1196
  return isValid;
@@ -983,7 +1290,7 @@ const validateKVBinding: ValidatorFn = (diagnostics, field, value) => {
983
1290
  const validateR2Binding: ValidatorFn = (diagnostics, field, value) => {
984
1291
  if (typeof value !== "object" || value === null) {
985
1292
  diagnostics.errors.push(
986
- `"kv_namespaces" bindings should be objects, but got ${JSON.stringify(
1293
+ `"r2_buckets" bindings should be objects, but got ${JSON.stringify(
987
1294
  value
988
1295
  )}`
989
1296
  );
@@ -1017,3 +1324,101 @@ const validateR2Binding: ValidatorFn = (diagnostics, field, value) => {
1017
1324
  }
1018
1325
  return isValid;
1019
1326
  };
1327
+
1328
+ /**
1329
+ * Check that bindings whose names might conflict, don't.
1330
+ *
1331
+ * We don't want to have, for example, a KV namespace named "DATA"
1332
+ * and a Durable Object also named "DATA". Then it would be ambiguous
1333
+ * what exactly would live at `env.DATA` (or in the case of service-workers,
1334
+ * the `DATA` global).
1335
+ */
1336
+ const validateBindingsHaveUniqueNames = (
1337
+ diagnostics: Diagnostics,
1338
+ {
1339
+ durable_objects,
1340
+ kv_namespaces,
1341
+ r2_buckets,
1342
+ text_blobs,
1343
+ unsafe,
1344
+ vars,
1345
+ wasm_modules,
1346
+ data_blobs,
1347
+ }: Partial<Config>
1348
+ ): boolean => {
1349
+ let hasDuplicates = false;
1350
+
1351
+ const bindingsGroupedByType = {
1352
+ "Durable Object": getBindingNames(durable_objects),
1353
+ "KV Namespace": getBindingNames(kv_namespaces),
1354
+ "R2 Bucket": getBindingNames(r2_buckets),
1355
+ "Text Blob": getBindingNames(text_blobs),
1356
+ Unsafe: getBindingNames(unsafe),
1357
+ "Environment Variable": getBindingNames(vars),
1358
+ "WASM Module": getBindingNames(wasm_modules),
1359
+ "Data Blob": getBindingNames(data_blobs),
1360
+ } as Record<string, string[]>;
1361
+
1362
+ const bindingsGroupedByName: Record<string, string[]> = {};
1363
+
1364
+ for (const bindingType in bindingsGroupedByType) {
1365
+ const bindingNames = bindingsGroupedByType[bindingType];
1366
+
1367
+ for (const bindingName of bindingNames) {
1368
+ if (!(bindingName in bindingsGroupedByName)) {
1369
+ bindingsGroupedByName[bindingName] = [];
1370
+ }
1371
+
1372
+ bindingsGroupedByName[bindingName].push(bindingType);
1373
+ }
1374
+ }
1375
+
1376
+ for (const bindingName in bindingsGroupedByName) {
1377
+ const bindingTypes = bindingsGroupedByName[bindingName];
1378
+ if (bindingTypes.length < 2) {
1379
+ // there's only one (or zero) binding(s) with this name, which is fine, actually
1380
+ continue;
1381
+ }
1382
+
1383
+ hasDuplicates = true;
1384
+
1385
+ // there's two types of duplicates we want to look for:
1386
+ // - bindings with the same name of the same type (e.g. two Durable Objects both named "OBJ")
1387
+ // - bindings with the same name of different types (a KV namespace and DO both named "DATA")
1388
+
1389
+ const sameType = bindingTypes
1390
+ // filter once to find duplicate binding types
1391
+ .filter((type, i) => bindingTypes.indexOf(type) !== i)
1392
+ // filter twice to only get _unique_ duplicate binding types
1393
+ .filter(
1394
+ (type, i, duplicateBindingTypes) =>
1395
+ duplicateBindingTypes.indexOf(type) === i
1396
+ );
1397
+
1398
+ const differentTypes = bindingTypes.filter(
1399
+ (type, i) => bindingTypes.indexOf(type) === i
1400
+ );
1401
+
1402
+ if (differentTypes.length > 1) {
1403
+ // we have multiple different types using the same name
1404
+ diagnostics.errors.push(
1405
+ `${bindingName} assigned to ${ENGLISH.format(differentTypes)} bindings.`
1406
+ );
1407
+ }
1408
+
1409
+ sameType.forEach((bindingType) => {
1410
+ diagnostics.errors.push(
1411
+ `${bindingName} assigned to multiple ${bindingType} bindings.`
1412
+ );
1413
+ });
1414
+ }
1415
+
1416
+ if (hasDuplicates) {
1417
+ const problem =
1418
+ "Bindings must have unique names, so that they can all be referenced in the worker.";
1419
+ const resolution = "Please change your bindings to have unique names.";
1420
+ diagnostics.errors.push(`${problem}\n${resolution}`);
1421
+ }
1422
+
1423
+ return !hasDuplicates;
1424
+ };