wrangler 0.0.0-e3cab74 → 0.0.0-e452a35

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 (102) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +27 -14
  3. package/bin/wrangler.js +49 -5
  4. package/miniflare-dist/index.mjs +4886 -0
  5. package/package.json +42 -28
  6. package/pages/functions/buildPlugin.ts +105 -0
  7. package/pages/functions/buildWorker.ts +93 -4
  8. package/pages/functions/filepath-routing.test.ts +136 -25
  9. package/pages/functions/filepath-routing.ts +14 -3
  10. package/pages/functions/routes.ts +6 -1
  11. package/pages/functions/template-plugin.ts +147 -0
  12. package/pages/functions/template-worker.ts +21 -8
  13. package/src/__tests__/config-cache.test.ts +36 -0
  14. package/src/__tests__/configuration.test.ts +1514 -367
  15. package/src/__tests__/dev.test.tsx +870 -84
  16. package/src/__tests__/guess-worker-format.test.ts +37 -18
  17. package/src/__tests__/helpers/mock-account-id.ts +22 -4
  18. package/src/__tests__/helpers/mock-cfetch.ts +8 -4
  19. package/src/__tests__/helpers/mock-console.ts +35 -10
  20. package/src/__tests__/helpers/mock-dialogs.ts +26 -1
  21. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  22. package/src/__tests__/helpers/mock-istty.ts +27 -0
  23. package/src/__tests__/helpers/mock-kv.ts +9 -16
  24. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  25. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  26. package/src/__tests__/helpers/run-in-tmp.ts +10 -0
  27. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  28. package/src/__tests__/https-options.test.ts +130 -0
  29. package/src/__tests__/index.test.ts +137 -747
  30. package/src/__tests__/init.test.ts +1557 -0
  31. package/src/__tests__/jest.setup.ts +28 -8
  32. package/src/__tests__/kv.test.ts +598 -258
  33. package/src/__tests__/logger.test.ts +115 -0
  34. package/src/__tests__/pages.test.ts +657 -0
  35. package/src/__tests__/parse.test.ts +8 -0
  36. package/src/__tests__/publish.test.ts +4003 -941
  37. package/src/__tests__/r2.test.ts +69 -34
  38. package/src/__tests__/route.test.ts +45 -0
  39. package/src/__tests__/secret.test.ts +228 -73
  40. package/src/__tests__/tail.test.ts +146 -20
  41. package/src/__tests__/user.test.ts +131 -0
  42. package/src/__tests__/whoami.test.tsx +26 -7
  43. package/src/abort.d.ts +3 -0
  44. package/src/bundle.ts +74 -7
  45. package/src/cfetch/index.ts +21 -4
  46. package/src/cfetch/internal.ts +20 -14
  47. package/src/cli.ts +5 -11
  48. package/src/config/README.md +7 -7
  49. package/src/config/config.ts +69 -100
  50. package/src/config/environment.ts +119 -4
  51. package/src/config/index.ts +177 -4
  52. package/src/config/validation-helpers.ts +228 -23
  53. package/src/config/validation.ts +687 -174
  54. package/src/config-cache.ts +45 -0
  55. package/src/{api/preview.ts → create-worker-preview.ts} +74 -36
  56. package/src/{api/form_data.ts → create-worker-upload-form.ts} +55 -18
  57. package/src/dev/dev-vars.ts +36 -0
  58. package/src/dev/dev.tsx +422 -0
  59. package/src/dev/local.tsx +318 -0
  60. package/src/dev/remote.tsx +227 -0
  61. package/src/dev/use-esbuild.ts +118 -0
  62. package/src/dialogs.tsx +16 -6
  63. package/src/durable.ts +102 -0
  64. package/src/entry.ts +279 -14
  65. package/src/environment-variables.ts +3 -1
  66. package/src/errors.ts +11 -0
  67. package/src/https-options.ts +123 -0
  68. package/src/index.tsx +1268 -806
  69. package/src/inspect.ts +64 -11
  70. package/src/intl-polyfill.d.ts +139 -0
  71. package/src/{kv.tsx → kv.ts} +195 -62
  72. package/src/logger.ts +66 -0
  73. package/src/miniflare-cli/README.md +30 -0
  74. package/src/miniflare-cli/enum-keys.ts +17 -0
  75. package/src/miniflare-cli/index.ts +41 -0
  76. package/src/miniflare-cli/request-context.ts +40 -0
  77. package/src/module-collection.ts +94 -6
  78. package/src/open-in-browser.ts +9 -3
  79. package/src/package-manager.ts +9 -8
  80. package/src/pages.tsx +1057 -75
  81. package/src/parse.ts +24 -5
  82. package/src/proxy.ts +143 -54
  83. package/src/publish.ts +444 -229
  84. package/src/selfsigned.d.ts +29 -0
  85. package/src/sites.tsx +98 -44
  86. package/src/tail/index.ts +2 -2
  87. package/src/tail/printing.ts +13 -8
  88. package/src/update-check.ts +19 -0
  89. package/src/user.tsx +263 -109
  90. package/src/whoami.tsx +2 -1
  91. package/src/{api/worker.ts → worker.ts} +24 -30
  92. package/templates/gitignore +171 -0
  93. package/templates/new-worker.ts +16 -1
  94. package/templates/static-asset-facade.js +1 -1
  95. package/wrangler-dist/cli.js +55345 -69246
  96. package/src/__tests__/helpers/mock-user.ts +0 -27
  97. package/src/__tests__/logout.test.ts +0 -50
  98. package/src/__tests__/sentry.test.ts +0 -146
  99. package/src/dev.tsx +0 -961
  100. package/src/guess-worker-format.ts +0 -68
  101. package/src/reporting.ts +0 -115
  102. 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,98 @@ export function normalizeAndValidateConfig(
75
94
  "boolean"
76
95
  );
77
96
 
97
+ // TODO: set the default to false to turn on service environments as the default
98
+ const isLegacyEnv =
99
+ (args as { "legacy-env": boolean | undefined })["legacy-env"] ??
100
+ rawConfig.legacy_env ??
101
+ true;
102
+
103
+ // TODO: remove this once service environments goes GA.
104
+ if (!isLegacyEnv) {
105
+ diagnostics.warnings.push(
106
+ "Experimental: Service environments are in beta, and their behaviour is guaranteed to change in the future. DO NOT USE IN PRODUCTION."
107
+ );
108
+ }
109
+
110
+ const topLevelEnv = normalizeAndValidateEnvironment(
111
+ diagnostics,
112
+ configPath,
113
+ rawConfig
114
+ );
115
+
116
+ //TODO: find a better way to define the type of Args that can be passed to the normalizeAndValidateConfig()
117
+ const envName = (args as { env: string | undefined }).env;
118
+
119
+ let activeEnv = topLevelEnv;
120
+ if (envName !== undefined) {
121
+ const envDiagnostics = new Diagnostics(
122
+ `"env.${envName}" environment configuration`
123
+ );
124
+ const rawEnv = rawConfig.env?.[envName];
125
+ if (rawEnv !== undefined) {
126
+ activeEnv = normalizeAndValidateEnvironment(
127
+ envDiagnostics,
128
+ configPath,
129
+ rawEnv,
130
+ envName,
131
+ topLevelEnv,
132
+ isLegacyEnv,
133
+ rawConfig
134
+ );
135
+ diagnostics.addChild(envDiagnostics);
136
+ } else {
137
+ // An environment was specified, but no configuration for it was found.
138
+ // To cover any legacy environment cases, where the `envName` is used,
139
+ // Let's create a fake active environment with the specified `envName`.
140
+ activeEnv = normalizeAndValidateEnvironment(
141
+ envDiagnostics,
142
+ configPath,
143
+ {},
144
+ envName,
145
+ topLevelEnv,
146
+ isLegacyEnv,
147
+ rawConfig
148
+ );
149
+ const envNames = rawConfig.env
150
+ ? `The available configured environment names are: ${JSON.stringify(
151
+ Object.keys(rawConfig.env)
152
+ )}\n`
153
+ : "";
154
+ const message =
155
+ `No environment found in configuration with name "${envName}".\n` +
156
+ `Before using \`--env=${envName}\` there should be an equivalent environment section in the configuration.\n` +
157
+ `${envNames}\n` +
158
+ `Consider adding an environment configuration section to the wrangler.toml file:\n` +
159
+ "```\n[env." +
160
+ envName +
161
+ "]\n```\n";
162
+
163
+ if (envNames.length > 0) {
164
+ diagnostics.errors.push(message);
165
+ } else {
166
+ // Only warn (rather than error) if there are not actually any environments configured in wrangler.toml.
167
+ diagnostics.warnings.push(message);
168
+ }
169
+ }
170
+ }
171
+
78
172
  // Process the top-level default environment configuration.
79
173
  const config: Config = {
80
174
  configPath,
81
- main: normalizeAndValidateMainField(
175
+ legacy_env: isLegacyEnv,
176
+ ...activeEnv,
177
+ dev: normalizeAndValidateDev(diagnostics, rawConfig.dev ?? {}),
178
+ migrations: normalizeAndValidateMigrations(
82
179
  diagnostics,
83
- configPath,
84
- rawConfig.main,
85
- deprecatedUpload
180
+ rawConfig.migrations ?? [],
181
+ activeEnv.durable_objects
86
182
  ),
87
- // TODO: set the default to false to turn on service environments as the default
88
- legacy_env: rawConfig.legacy_env ?? true,
89
- ...normalizeAndValidateEnvironment(
183
+ site: normalizeAndValidateSite(
90
184
  diagnostics,
185
+ configPath,
91
186
  rawConfig,
92
- deprecatedUpload.rules
93
- ),
94
- env: {}, // Will be filled below by `normalizeAndValidateEnvironment()`.
95
- dev: normalizeAndValidateDev(diagnostics, rawConfig.dev ?? {}),
96
- migrations: normalizeAndValidateMigrations(
97
- diagnostics,
98
- rawConfig.migrations ?? []
187
+ activeEnv.main
99
188
  ),
100
- site: normalizeAndValidateSite(diagnostics, rawConfig.site),
101
189
  wasm_modules: normalizeAndValidateModulePaths(
102
190
  diagnostics,
103
191
  configPath,
@@ -110,33 +198,21 @@ export function normalizeAndValidateConfig(
110
198
  "text_blobs",
111
199
  rawConfig.text_blobs
112
200
  ),
113
- build,
201
+ data_blobs: normalizeAndValidateModulePaths(
202
+ diagnostics,
203
+ configPath,
204
+ "data_blobs",
205
+ rawConfig.data_blobs
206
+ ),
114
207
  };
115
208
 
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
- }
209
+ validateBindingsHaveUniqueNames(diagnostics, config);
134
210
 
135
211
  validateAdditionalProperties(
136
212
  diagnostics,
137
213
  "top-level",
138
214
  Object.keys(rawConfig),
139
- Object.keys(config)
215
+ [...Object.keys(config), "env"]
140
216
  );
141
217
 
142
218
  return { config, diagnostics };
@@ -147,8 +223,9 @@ export function normalizeAndValidateConfig(
147
223
  */
148
224
  function normalizeAndValidateBuild(
149
225
  diagnostics: Diagnostics,
150
- rawConfig: RawConfig,
151
- rawBuild: Config["build"]
226
+ rawEnv: RawEnvironment,
227
+ rawBuild: Config["build"],
228
+ configPath: string | undefined
152
229
  ): Config["build"] & { deprecatedUpload: DeprecatedUpload } {
153
230
  const { command, cwd, watch_dir, upload, ...rest } = rawBuild;
154
231
  const deprecatedUpload: DeprecatedUpload = { ...upload };
@@ -166,13 +243,13 @@ function normalizeAndValidateBuild(
166
243
 
167
244
  deprecated(
168
245
  diagnostics,
169
- rawConfig,
246
+ rawEnv,
170
247
  "build.upload.format",
171
248
  "The format is inferred automatically from the code.",
172
249
  true
173
250
  );
174
251
 
175
- if (rawConfig.main !== undefined && rawBuild.upload?.main) {
252
+ if (rawEnv.main !== undefined && rawBuild.upload?.main) {
176
253
  diagnostics.errors.push(
177
254
  `Don't define both the \`main\` and \`build.upload.main\` fields in your configuration.\n` +
178
255
  `They serve the same purpose: to point to the entry-point of your worker.\n` +
@@ -181,7 +258,7 @@ function normalizeAndValidateBuild(
181
258
  } else {
182
259
  deprecated(
183
260
  diagnostics,
184
- rawConfig,
261
+ rawEnv,
185
262
  "build.upload.main",
186
263
  `Delete the \`build.upload.main\` and \`build.upload.dir\` fields.\n` +
187
264
  `Then add the top level \`main\` field to your configuration file:\n` +
@@ -196,24 +273,41 @@ function normalizeAndValidateBuild(
196
273
 
197
274
  deprecated(
198
275
  diagnostics,
199
- rawConfig,
276
+ rawEnv,
200
277
  "build.upload.dir",
201
278
  `Use the top level "main" field or a command-line argument to specify the entry-point for the Worker.`,
202
279
  true
203
280
  );
204
281
  }
205
282
 
206
- return { command, cwd, watch_dir, deprecatedUpload };
283
+ return {
284
+ command,
285
+ watch_dir:
286
+ // - `watch_dir` only matters when `command` is defined, so we apply
287
+ // a default only when `command` is defined
288
+ // - `configPath` will always be defined since `build` can only
289
+ // be configured in `wrangler.toml`, but who knows, that may
290
+ // change in the future, so we do a check anyway
291
+ command
292
+ ? configPath
293
+ ? path.relative(
294
+ process.cwd(),
295
+ path.join(path.dirname(configPath), watch_dir || "./src")
296
+ )
297
+ : watch_dir || "./src"
298
+ : watch_dir,
299
+ cwd,
300
+ deprecatedUpload,
301
+ };
207
302
  }
208
303
 
209
304
  /**
210
305
  * Validate the `main` field and return the normalized values.
211
306
  */
212
307
  function normalizeAndValidateMainField(
213
- diagnostics: Diagnostics,
214
308
  configPath: string | undefined,
215
309
  rawMain: string | undefined,
216
- deprecatedUpload: { dir?: string; main?: string }
310
+ deprecatedUpload: DeprecatedUpload | undefined
217
311
  ): string | undefined {
218
312
  const configDir = path.dirname(configPath ?? "wrangler.toml");
219
313
  if (rawMain !== undefined) {
@@ -221,13 +315,13 @@ function normalizeAndValidateMainField(
221
315
  const directory = path.resolve(configDir);
222
316
  return path.resolve(directory, rawMain);
223
317
  } else {
224
- diagnostics.errors.push(
225
- `ERROR: "main" should be a string but got ${JSON.stringify(rawMain)}`
226
- );
227
- return;
318
+ return rawMain;
228
319
  }
229
- } else if (deprecatedUpload.main !== undefined) {
230
- const directory = path.resolve(configDir, deprecatedUpload.dir || "./dist");
320
+ } else if (deprecatedUpload?.main !== undefined) {
321
+ const directory = path.resolve(
322
+ configDir,
323
+ deprecatedUpload?.dir || "./dist"
324
+ );
231
325
  return path.resolve(directory, deprecatedUpload.main);
232
326
  } else {
233
327
  return;
@@ -239,13 +333,14 @@ function normalizeAndValidateMainField(
239
333
  */
240
334
  function normalizeAndValidateDev(
241
335
  diagnostics: Diagnostics,
242
- rawDev: Config["dev"]
243
- ): Config["dev"] {
336
+ rawDev: RawDevConfig
337
+ ): DevConfig {
244
338
  const {
245
- ip = "127.0.0.1",
246
- port = 8787,
339
+ ip = "localhost",
340
+ port,
247
341
  local_protocol = "http",
248
342
  upstream_protocol = "https",
343
+ host,
249
344
  ...rest
250
345
  } = rawDev;
251
346
  validateAdditionalProperties(diagnostics, "dev", Object.keys(rest), []);
@@ -257,16 +352,19 @@ function normalizeAndValidateDev(
257
352
  "dev",
258
353
  "local_protocol",
259
354
  local_protocol,
260
- "string"
355
+ "string",
356
+ ["http", "https"]
261
357
  );
262
358
  validateOptionalProperty(
263
359
  diagnostics,
264
360
  "dev",
265
361
  "upstream_protocol",
266
362
  upstream_protocol,
267
- "string"
363
+ "string",
364
+ ["http", "https"]
268
365
  );
269
- return { ip, port, local_protocol, upstream_protocol };
366
+ validateOptionalProperty(diagnostics, "dev", "host", host, "string");
367
+ return { ip, port, local_protocol, upstream_protocol, host };
270
368
  }
271
369
 
272
370
  /**
@@ -274,7 +372,8 @@ function normalizeAndValidateDev(
274
372
  */
275
373
  function normalizeAndValidateMigrations(
276
374
  diagnostics: Diagnostics,
277
- rawMigrations: Config["migrations"]
375
+ rawMigrations: Config["migrations"],
376
+ durableObjects: Config["durable_objects"]
278
377
  ): Config["migrations"] {
279
378
  if (!Array.isArray(rawMigrations)) {
280
379
  diagnostics.errors.push(
@@ -302,7 +401,7 @@ function normalizeAndValidateMigrations(
302
401
  if (migration.renamed_classes !== undefined) {
303
402
  if (!Array.isArray(migration.renamed_classes)) {
304
403
  diagnostics.errors.push(
305
- `"migrations[${i}].renamed_classes" should be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
404
+ `Expected "migrations[${i}].renamed_classes" to be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
306
405
  migration.renamed_classes
307
406
  )}.`
308
407
  );
@@ -315,7 +414,7 @@ function normalizeAndValidateMigrations(
315
414
  )
316
415
  ) {
317
416
  diagnostics.errors.push(
318
- `"migrations[${i}].renamed_classes" should be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
417
+ `Expected "migrations[${i}].renamed_classes" to be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
319
418
  migration.renamed_classes
320
419
  )}.`
321
420
  );
@@ -328,6 +427,45 @@ function normalizeAndValidateMigrations(
328
427
  "string"
329
428
  );
330
429
  }
430
+
431
+ if (
432
+ Array.isArray(durableObjects?.bindings) &&
433
+ durableObjects.bindings.length > 0
434
+ ) {
435
+ // intrinsic [durable_objects] implies [migrations]
436
+ const exportedDurableObjects = (durableObjects.bindings || []).filter(
437
+ (binding) => !binding.script_name
438
+ );
439
+ if (exportedDurableObjects.length > 0 && rawMigrations.length === 0) {
440
+ if (
441
+ !exportedDurableObjects.some(
442
+ (exportedDurableObject) =>
443
+ typeof exportedDurableObject.class_name !== "string"
444
+ )
445
+ ) {
446
+ const durableObjectClassnames = exportedDurableObjects.map(
447
+ (durable) => durable.class_name
448
+ );
449
+
450
+ diagnostics.warnings.push(
451
+ `In wrangler.toml, you have configured [durable_objects] exported by this Worker (${durableObjectClassnames.join(
452
+ ", "
453
+ )}), but no [migrations] for them. This may not work as expected until you add a [migrations] section to your wrangler.toml. Add this configuration to your wrangler.toml:
454
+
455
+ \`\`\`
456
+ [[migrations]]
457
+ tag = "v1" # Should be unique for each entry
458
+ new_classes = [${durableObjectClassnames
459
+ .map((name) => `"${name}"`)
460
+ .join(", ")}]
461
+ \`\`\`
462
+
463
+ Refer to https://developers.cloudflare.com/workers/learning/using-durable-objects/#durable-object-migrations-in-wranglertoml for more details.`
464
+ );
465
+ }
466
+ }
467
+ }
468
+
331
469
  return rawMigrations;
332
470
  }
333
471
  }
@@ -337,39 +475,91 @@ function normalizeAndValidateMigrations(
337
475
  */
338
476
  function normalizeAndValidateSite(
339
477
  diagnostics: Diagnostics,
340
- rawSite: Config["site"]
478
+ configPath: string | undefined,
479
+ rawConfig: RawConfig,
480
+ mainEntryPoint: string | undefined
341
481
  ): Config["site"] {
342
- if (rawSite !== undefined) {
343
- const {
344
- bucket,
345
- "entry-point": entryPoint,
346
- include = [],
347
- exclude = [],
348
- ...rest
349
- } = rawSite;
350
- validateAdditionalProperties(diagnostics, "site", Object.keys(rest), []);
482
+ if (rawConfig?.site !== undefined) {
483
+ const { bucket, include = [], exclude = [], ...rest } = rawConfig.site;
484
+
485
+ validateAdditionalProperties(diagnostics, "site", Object.keys(rest), [
486
+ "entry-point",
487
+ ]);
351
488
  validateRequiredProperty(diagnostics, "site", "bucket", bucket, "string");
489
+ validateTypedArray(diagnostics, "sites.include", include, "string");
490
+ validateTypedArray(diagnostics, "sites.exclude", exclude, "string");
352
491
  validateOptionalProperty(
353
492
  diagnostics,
354
493
  "site",
355
- "entry_point",
356
- entryPoint,
494
+ "entry-point",
495
+ rawConfig.site["entry-point"],
357
496
  "string"
358
497
  );
359
- validateTypedArray(diagnostics, "sites.include", include, "string");
360
- validateTypedArray(diagnostics, "sites.exclude", exclude, "string");
361
- return { bucket, "entry-point": entryPoint, include, exclude };
498
+
499
+ deprecated(
500
+ diagnostics,
501
+ rawConfig,
502
+ `site.entry-point`,
503
+ `Delete the \`site.entry-point\` field, then add the top level \`main\` field to your configuration file:\n` +
504
+ `\`\`\`\n` +
505
+ `main = "${path.join(
506
+ String(rawConfig.site["entry-point"]) || "workers-site",
507
+ path.extname(String(rawConfig.site["entry-point"]) || "workers-site")
508
+ ? ""
509
+ : "index.js"
510
+ )}"\n` +
511
+ `\`\`\``,
512
+ false,
513
+ undefined,
514
+ "warning"
515
+ );
516
+
517
+ let siteEntryPoint = rawConfig.site["entry-point"];
518
+
519
+ if (!mainEntryPoint && !siteEntryPoint) {
520
+ // this means that we're defaulting to "workers-site"
521
+ // so let's add the deprecation warning
522
+ diagnostics.warnings.push(
523
+ `Because you've defined a [site] configuration, we're defaulting to "workers-site" for the deprecated \`site.entry-point\`field.\n` +
524
+ `Add the top level \`main\` field to your configuration file:\n` +
525
+ `\`\`\`\n` +
526
+ `main = "workers-site/index.js"\n` +
527
+ `\`\`\``
528
+ );
529
+ siteEntryPoint = "workers-site";
530
+ } else if (mainEntryPoint && siteEntryPoint) {
531
+ diagnostics.errors.push(
532
+ `Don't define both the \`main\` and \`site.entry-point\` fields in your configuration.\n` +
533
+ `They serve the same purpose: to point to the entry-point of your worker.\n` +
534
+ `Delete the deprecated \`site.entry-point\` field from your config.`
535
+ );
536
+ }
537
+
538
+ if (configPath && siteEntryPoint) {
539
+ // rewrite the path to be relative to the working directory
540
+ siteEntryPoint = path.relative(
541
+ process.cwd(),
542
+ path.join(path.dirname(configPath), siteEntryPoint)
543
+ );
544
+ }
545
+
546
+ return {
547
+ bucket,
548
+ "entry-point": siteEntryPoint,
549
+ include,
550
+ exclude,
551
+ };
362
552
  }
363
553
  return undefined;
364
554
  }
365
555
 
366
556
  /**
367
- * Map the paths of the `wasm_modules` or `text_blobs` configuration to be relative to the current working directory.
557
+ * Map the paths of the `wasm_modules`, `text_blobs` or `data_blobs` configuration to be relative to the current working directory.
368
558
  */
369
559
  function normalizeAndValidateModulePaths(
370
560
  diagnostics: Diagnostics,
371
561
  configPath: string | undefined,
372
- field: "wasm_modules" | "text_blobs",
562
+ field: "wasm_modules" | "text_blobs" | "data_blobs",
373
563
  rawMapping: Record<string, string> | undefined
374
564
  ): Record<string, string> | undefined {
375
565
  if (rawMapping === undefined) {
@@ -392,31 +582,147 @@ function normalizeAndValidateModulePaths(
392
582
  return mapping;
393
583
  }
394
584
 
585
+ /**
586
+ * Check whether a value has the shape of a route, which can be a string
587
+ * or an object that looks like {pattern: string, zone_id: string }
588
+ */
589
+ function isValidRouteValue(item: unknown): boolean {
590
+ if (!item) {
591
+ return false;
592
+ }
593
+ if (typeof item === "string") {
594
+ return true;
595
+ }
596
+ if (typeof item === "object") {
597
+ if (!hasProperty(item, "pattern") || typeof item.pattern !== "string") {
598
+ return false;
599
+ }
600
+
601
+ const otherKeys = Object.keys(item).length - 1; // minus one to subtract "pattern"
602
+
603
+ const hasZoneId =
604
+ hasProperty(item, "zone_id") && typeof item.zone_id === "string";
605
+ const hasZoneName =
606
+ hasProperty(item, "zone_name") && typeof item.zone_name === "string";
607
+ const hasCustomDomainFlag =
608
+ hasProperty(item, "custom_domain") &&
609
+ typeof item.custom_domain === "boolean";
610
+
611
+ if (otherKeys === 2 && hasCustomDomainFlag && (hasZoneId || hasZoneName)) {
612
+ return true;
613
+ } else if (
614
+ otherKeys === 1 &&
615
+ (hasZoneId || hasZoneName || hasCustomDomainFlag)
616
+ ) {
617
+ return true;
618
+ }
619
+ }
620
+ return false;
621
+ }
622
+
623
+ /**
624
+ * Validate that the field is a route.
625
+ */
626
+ const isRoute: ValidatorFn = (diagnostics, field, value) => {
627
+ if (value !== undefined && !isValidRouteValue(value)) {
628
+ diagnostics.errors.push(
629
+ `Expected "${field}" to be either a string, or an object with shape { pattern, custom_domain, zone_id | zone_name }, but got ${JSON.stringify(
630
+ value
631
+ )}.`
632
+ );
633
+ return false;
634
+ }
635
+ return true;
636
+ };
637
+
638
+ /**
639
+ * Validate that the field is an array of routes.
640
+ */
641
+ const isRouteArray: ValidatorFn = (diagnostics, field, value) => {
642
+ if (value === undefined) {
643
+ return true;
644
+ }
645
+ if (!Array.isArray(value)) {
646
+ diagnostics.errors.push(
647
+ `Expected "${field}" to be an array but got ${JSON.stringify(value)}.`
648
+ );
649
+ return false;
650
+ }
651
+ const invalidRoutes = [];
652
+ for (const item of value) {
653
+ if (!isValidRouteValue(item)) {
654
+ invalidRoutes.push(item);
655
+ }
656
+ }
657
+ if (invalidRoutes.length > 0) {
658
+ diagnostics.errors.push(
659
+ `Expected "${field}" to be an array of either strings or objects with the shape { pattern, custom_domain, zone_id | zone_name }, but these weren't valid: ${JSON.stringify(
660
+ invalidRoutes,
661
+ null,
662
+ 2
663
+ )}.`
664
+ );
665
+ }
666
+ return invalidRoutes.length === 0;
667
+ };
668
+
669
+ function validateRoute(
670
+ diagnostics: Diagnostics,
671
+ topLevelEnv: Environment | undefined,
672
+ rawEnv: RawEnvironment
673
+ ): Config["route"] {
674
+ return inheritable(
675
+ diagnostics,
676
+ topLevelEnv,
677
+ rawEnv,
678
+ "route",
679
+ isRoute,
680
+ undefined
681
+ );
682
+ }
683
+
684
+ function validateRoutes(
685
+ diagnostics: Diagnostics,
686
+ topLevelEnv: Environment | undefined,
687
+ rawEnv: RawEnvironment
688
+ ): Config["routes"] {
689
+ return inheritable(
690
+ diagnostics,
691
+ topLevelEnv,
692
+ rawEnv,
693
+ "routes",
694
+ all(isRouteArray, isMutuallyExclusiveWith(rawEnv, "route")),
695
+ undefined
696
+ );
697
+ }
698
+
395
699
  /**
396
700
  * Validate top-level environment configuration and return the normalized values.
397
701
  */
398
702
  function normalizeAndValidateEnvironment(
399
703
  diagnostics: Diagnostics,
400
- topLevelEnv: RawEnvironment,
401
- deprecatedRules: Environment["rules"] | undefined
704
+ configPath: string | undefined,
705
+ topLevelEnv: RawEnvironment
402
706
  ): Environment;
403
707
  /**
404
708
  * Validate the named environment configuration and return the normalized values.
405
709
  */
406
710
  function normalizeAndValidateEnvironment(
407
711
  diagnostics: Diagnostics,
712
+ configPath: string | undefined,
408
713
  rawEnv: RawEnvironment,
409
- deprecatedRules: Environment["rules"] | undefined,
410
714
  envName: string,
411
- config: Config,
715
+ topLevelEnv: Environment,
716
+ isLegacyEnv: boolean,
412
717
  rawConfig: RawConfig
413
718
  ): Environment;
414
719
  function normalizeAndValidateEnvironment(
415
720
  diagnostics: Diagnostics,
721
+ configPath: string | undefined,
416
722
  rawEnv: RawEnvironment,
417
- deprecatedRules: Environment["rules"] | undefined,
418
723
  envName = "top level",
419
- config?: Config | undefined,
724
+ topLevelEnv?: Environment | undefined,
725
+ isLegacyEnv?: boolean,
420
726
  rawConfig?: RawConfig | undefined
421
727
  ): Environment {
422
728
  deprecated(
@@ -433,66 +739,48 @@ function normalizeAndValidateEnvironment(
433
739
  diagnostics,
434
740
  rawEnv,
435
741
  "experimental_services",
436
- `The "experimental_services" field is no longer supported. Instead, use [[unsafe.bindings]] to enable experimental features. Add this to your wrangler.toml:\n` +
437
- "```\n" +
438
- TOML.stringify({
439
- unsafe: {
440
- bindings: (rawEnv?.experimental_services || []).map(
441
- (serviceDefinition) => {
442
- return {
443
- name: serviceDefinition.name,
444
- type: "service",
445
- service: serviceDefinition.service,
446
- environment: serviceDefinition.environment,
447
- };
448
- }
449
- ),
450
- },
451
- }) +
452
- "```",
742
+ `The "experimental_services" field is no longer supported. Simply rename the [experimental_services] field to [services].`,
453
743
  true
454
744
  );
455
745
 
456
746
  experimental(diagnostics, rawEnv, "unsafe");
747
+ experimental(diagnostics, rawEnv, "services");
457
748
 
458
- const route = inheritable(
459
- diagnostics,
460
- config,
461
- rawEnv,
462
- "route",
463
- isString,
464
- undefined
465
- );
466
- const routes = inheritable(
749
+ const route = validateRoute(diagnostics, topLevelEnv, rawEnv);
750
+
751
+ const routes = validateRoutes(diagnostics, topLevelEnv, rawEnv);
752
+
753
+ const workers_dev = inheritable(
467
754
  diagnostics,
468
- config,
755
+ topLevelEnv,
469
756
  rawEnv,
470
- "routes",
471
- isStringArray,
757
+ "workers_dev",
758
+ isBoolean,
472
759
  undefined
473
760
  );
474
- const workers_dev = inheritable(
761
+
762
+ const { deprecatedUpload, ...build } = normalizeAndValidateBuild(
475
763
  diagnostics,
476
- config,
477
764
  rawEnv,
478
- "workers_dev",
479
- isBoolean,
480
- !(routes || route)
765
+ rawEnv.build ?? topLevelEnv?.build ?? {},
766
+ configPath
481
767
  );
482
768
 
483
769
  const environment: Environment = {
484
770
  // Inherited fields
485
- account_id: inheritable(
771
+ account_id: inheritableInLegacyEnvironments(
486
772
  diagnostics,
487
- config,
773
+ isLegacyEnv,
774
+ topLevelEnv,
488
775
  rawEnv,
489
776
  "account_id",
490
777
  isString,
778
+ undefined,
491
779
  undefined
492
780
  ),
493
781
  compatibility_date: inheritable(
494
782
  diagnostics,
495
- config,
783
+ topLevelEnv,
496
784
  rawEnv,
497
785
  "compatibility_date",
498
786
  isString,
@@ -500,7 +788,7 @@ function normalizeAndValidateEnvironment(
500
788
  ),
501
789
  compatibility_flags: inheritable(
502
790
  diagnostics,
503
- config,
791
+ topLevelEnv,
504
792
  rawEnv,
505
793
  "compatibility_flags",
506
794
  isStringArray,
@@ -508,7 +796,7 @@ function normalizeAndValidateEnvironment(
508
796
  ),
509
797
  jsx_factory: inheritable(
510
798
  diagnostics,
511
- config,
799
+ topLevelEnv,
512
800
  rawEnv,
513
801
  "jsx_factory",
514
802
  isString,
@@ -516,25 +804,52 @@ function normalizeAndValidateEnvironment(
516
804
  ),
517
805
  jsx_fragment: inheritable(
518
806
  diagnostics,
519
- config,
807
+ topLevelEnv,
520
808
  rawEnv,
521
809
  "jsx_fragment",
522
810
  isString,
523
811
  "React.Fragment"
524
812
  ),
813
+ tsconfig: validateAndNormalizeTsconfig(
814
+ diagnostics,
815
+ topLevelEnv,
816
+ rawEnv,
817
+ configPath
818
+ ),
525
819
  rules: validateAndNormalizeRules(
526
820
  diagnostics,
527
- config,
821
+ topLevelEnv,
528
822
  rawEnv,
529
- deprecatedRules,
823
+ deprecatedUpload?.rules,
530
824
  envName
531
825
  ),
532
- name: inheritable(diagnostics, config, rawEnv, "name", isString, undefined),
826
+ name: inheritableInLegacyEnvironments(
827
+ diagnostics,
828
+ isLegacyEnv,
829
+ topLevelEnv,
830
+ rawEnv,
831
+ "name",
832
+ isValidName,
833
+ appendEnvName(envName),
834
+ undefined
835
+ ),
836
+ main: normalizeAndValidateMainField(
837
+ configPath,
838
+ inheritable(
839
+ diagnostics,
840
+ topLevelEnv,
841
+ rawEnv,
842
+ "main",
843
+ isString,
844
+ undefined
845
+ ),
846
+ deprecatedUpload
847
+ ),
533
848
  route,
534
849
  routes,
535
850
  triggers: inheritable(
536
851
  diagnostics,
537
- config,
852
+ topLevelEnv,
538
853
  rawEnv,
539
854
  "triggers",
540
855
  isObjectWith("crons"),
@@ -542,17 +857,18 @@ function normalizeAndValidateEnvironment(
542
857
  ),
543
858
  usage_model: inheritable(
544
859
  diagnostics,
545
- config,
860
+ topLevelEnv,
546
861
  rawEnv,
547
862
  "usage_model",
548
863
  isOneOf("bundled", "unbound"),
549
864
  undefined
550
865
  ),
866
+ build,
551
867
  workers_dev,
552
868
  // Not inherited fields
553
869
  vars: notInheritable(
554
870
  diagnostics,
555
- config,
871
+ topLevelEnv,
556
872
  rawConfig,
557
873
  rawEnv,
558
874
  envName,
@@ -562,7 +878,7 @@ function normalizeAndValidateEnvironment(
562
878
  ),
563
879
  durable_objects: notInheritable(
564
880
  diagnostics,
565
- config,
881
+ topLevelEnv,
566
882
  rawConfig,
567
883
  rawEnv,
568
884
  envName,
@@ -574,7 +890,7 @@ function normalizeAndValidateEnvironment(
574
890
  ),
575
891
  kv_namespaces: notInheritable(
576
892
  diagnostics,
577
- config,
893
+ topLevelEnv,
578
894
  rawConfig,
579
895
  rawEnv,
580
896
  envName,
@@ -584,7 +900,7 @@ function normalizeAndValidateEnvironment(
584
900
  ),
585
901
  r2_buckets: notInheritable(
586
902
  diagnostics,
587
- config,
903
+ topLevelEnv,
588
904
  rawConfig,
589
905
  rawEnv,
590
906
  envName,
@@ -592,9 +908,19 @@ function normalizeAndValidateEnvironment(
592
908
  validateBindingArray(envName, validateR2Binding),
593
909
  []
594
910
  ),
911
+ services: notInheritable(
912
+ diagnostics,
913
+ topLevelEnv,
914
+ rawConfig,
915
+ rawEnv,
916
+ envName,
917
+ "services",
918
+ validateBindingArray(envName, validateServiceBinding),
919
+ []
920
+ ),
595
921
  unsafe: notInheritable(
596
922
  diagnostics,
597
- config,
923
+ topLevelEnv,
598
924
  rawConfig,
599
925
  rawEnv,
600
926
  envName,
@@ -605,27 +931,66 @@ function normalizeAndValidateEnvironment(
605
931
  }
606
932
  ),
607
933
  zone_id: rawEnv.zone_id,
934
+ minify: inheritable(
935
+ diagnostics,
936
+ topLevelEnv,
937
+ rawEnv,
938
+ "minify",
939
+ isBoolean,
940
+ undefined
941
+ ),
942
+ node_compat: inheritable(
943
+ diagnostics,
944
+ topLevelEnv,
945
+ rawEnv,
946
+ "node_compat",
947
+ isBoolean,
948
+ undefined
949
+ ),
608
950
  };
609
951
 
610
952
  return environment;
611
953
  }
612
954
 
955
+ function validateAndNormalizeTsconfig(
956
+ diagnostics: Diagnostics,
957
+ topLevelEnv: Environment | undefined,
958
+ rawEnv: RawEnvironment,
959
+ configPath: string | undefined
960
+ ) {
961
+ const tsconfig = inheritable(
962
+ diagnostics,
963
+ topLevelEnv,
964
+ rawEnv,
965
+ "tsconfig",
966
+ isString,
967
+ undefined
968
+ );
969
+
970
+ return configPath && tsconfig
971
+ ? path.relative(
972
+ process.cwd(),
973
+ path.join(path.dirname(configPath), tsconfig)
974
+ )
975
+ : tsconfig;
976
+ }
977
+
613
978
  const validateAndNormalizeRules = (
614
979
  diagnostics: Diagnostics,
615
- config: Config | undefined,
980
+ topLevelEnv: Environment | undefined,
616
981
  rawEnv: RawEnvironment,
617
982
  deprecatedRules: Rule[] | undefined,
618
983
  envName: string
619
984
  ): Rule[] => {
620
- if (config === undefined) {
985
+ if (topLevelEnv === undefined) {
621
986
  // Only create errors/warnings for the top-level environment
622
987
  if (rawEnv.rules && deprecatedRules) {
623
988
  diagnostics.errors.push(
624
- `ERROR: You cannot configure both [rules] and [build.upload.rules] in your wrangler.toml. Delete the \`build.upload\` section.`
989
+ `You cannot configure both [rules] and [build.upload.rules] in your wrangler.toml. Delete the \`build.upload\` section.`
625
990
  );
626
991
  } else if (deprecatedRules) {
627
992
  diagnostics.warnings.push(
628
- `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` +
993
+ `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` +
629
994
  "```\n" +
630
995
  TOML.stringify({ rules: deprecatedRules }) +
631
996
  "```"
@@ -635,7 +1000,7 @@ const validateAndNormalizeRules = (
635
1000
 
636
1001
  return inheritable(
637
1002
  diagnostics,
638
- config,
1003
+ topLevelEnv,
639
1004
  rawEnv,
640
1005
  "rules",
641
1006
  validateRules(envName),
@@ -671,7 +1036,9 @@ const validateRules =
671
1036
 
672
1037
  const validateRule: ValidatorFn = (diagnostics, field, value) => {
673
1038
  if (typeof value !== "object" || value === null) {
674
- diagnostics.errors.push(`ERROR: should be an object.`);
1039
+ diagnostics.errors.push(
1040
+ `"${field}" should be an object but got ${JSON.stringify(value)}.`
1041
+ );
675
1042
  return false;
676
1043
  }
677
1044
  // Rules must have a type string and glob string array, and optionally a fallthrough boolean.
@@ -688,7 +1055,7 @@ const validateRule: ValidatorFn = (diagnostics, field, value) => {
688
1055
  ])
689
1056
  ) {
690
1057
  diagnostics.errors.push(
691
- `ERROR: bindings should have a string "type" field, which contains one of "ESModule", "CommonJS", "CompiledWasm", "Text", or "Data".`
1058
+ `bindings should have a string "type" field, which contains one of "ESModule", "CommonJS", "CompiledWasm", "Text", or "Data".`
692
1059
  );
693
1060
  isValid = false;
694
1061
  }
@@ -699,7 +1066,7 @@ const validateRule: ValidatorFn = (diagnostics, field, value) => {
699
1066
 
700
1067
  if (!isOptionalProperty(rule, "fallthrough", "boolean")) {
701
1068
  diagnostics.errors.push(
702
- `ERROR: should, optionally, have a boolean "fallthrough" field.`
1069
+ `the field "fallthrough", when present, should be a boolean.`
703
1070
  );
704
1071
  isValid = false;
705
1072
  }
@@ -796,7 +1163,7 @@ const validateBindingsProperty =
796
1163
  diagnostics.warnings.push(
797
1164
  `The following bindings are at the top level, but not on "env.${envName}".\n` +
798
1165
  `This is not what you probably want, since "${field}" configuration is not inherited by environments.\n` +
799
- `Please add a binding for each to "${fieldPath}.bindings".` +
1166
+ `Please add a binding for each to "${fieldPath}.bindings":\n` +
800
1167
  missingBindings.map((name) => `- ${name}`).join("\n")
801
1168
  );
802
1169
  }
@@ -805,42 +1172,48 @@ const validateBindingsProperty =
805
1172
  return isValid;
806
1173
  };
807
1174
 
808
- /**
809
- * Get the names of the bindings collection in `value`.
810
- */
811
- const getBindingNames = (value: unknown): string[] =>
812
- ((value as { bindings: { name: string }[] })?.bindings ?? []).map(
813
- (binding) => binding.name
814
- );
815
-
816
1175
  /**
817
1176
  * Check that the given field is a valid "durable_object" binding object.
818
1177
  */
819
1178
  const validateDurableObjectBinding: ValidatorFn = (
820
1179
  diagnostics,
821
- _field,
1180
+ field,
822
1181
  value
823
1182
  ) => {
824
1183
  if (typeof value !== "object" || value === null) {
825
- diagnostics.errors.push(`ERROR: should be an object.`);
1184
+ diagnostics.errors.push(
1185
+ `Expected "${field}" to be an object but got ${JSON.stringify(value)}`
1186
+ );
826
1187
  return false;
827
1188
  }
828
1189
 
829
- // Durable Object bindings must have a name and class_name, and optionally a script_name.
1190
+ // Durable Object bindings must have a name and class_name, and optionally a script_name and an environment.
830
1191
  let isValid = true;
831
1192
  if (!isRequiredProperty(value, "name", "string")) {
832
- diagnostics.errors.push(
833
- `ERROR: bindings should have a string "name" field.`
834
- );
1193
+ diagnostics.errors.push(`binding should have a string "name" field.`);
835
1194
  isValid = false;
836
1195
  }
837
1196
  if (!isRequiredProperty(value, "class_name", "string")) {
838
- diagnostics.errors.push(`ERROR: should have a string "class_name" field.`);
1197
+ diagnostics.errors.push(`binding should have a string "class_name" field.`);
839
1198
  isValid = false;
840
1199
  }
841
1200
  if (!isOptionalProperty(value, "script_name", "string")) {
842
1201
  diagnostics.errors.push(
843
- `ERROR: should, optionally, have a string "script_name" field.`
1202
+ `the field "script_name", when present, should be a string.`
1203
+ );
1204
+ isValid = false;
1205
+ }
1206
+ // environment requires a script_name
1207
+ if (!isOptionalProperty(value, "environment", "string")) {
1208
+ diagnostics.errors.push(
1209
+ `the field "environment", when present, should be a string.`
1210
+ );
1211
+ isValid = false;
1212
+ }
1213
+
1214
+ if ("environment" in value && !("script_name" in value)) {
1215
+ diagnostics.errors.push(
1216
+ `binding should have a "script_name" field if "environment" is present.`
844
1217
  );
845
1218
  isValid = false;
846
1219
  }
@@ -853,24 +1226,31 @@ const validateDurableObjectBinding: ValidatorFn = (
853
1226
  *
854
1227
  * TODO: further validation of known unsafe bindings.
855
1228
  */
856
- const validateUnsafeBinding: ValidatorFn = (diagnostics, _field, value) => {
1229
+ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => {
857
1230
  if (typeof value !== "object" || value === null) {
858
- diagnostics.errors.push(`ERROR: should be an object.`);
1231
+ diagnostics.errors.push(
1232
+ `Expected ${field} to be an object but got ${JSON.stringify(value)}.`
1233
+ );
859
1234
  return false;
860
1235
  }
861
1236
 
862
1237
  let isValid = true;
863
1238
  // Unsafe bindings must have a name and type.
864
1239
  if (!isRequiredProperty(value, "name", "string")) {
865
- diagnostics.errors.push(`ERROR: should have a string "name" field.`);
1240
+ diagnostics.errors.push(`binding should have a string "name" field.`);
866
1241
  isValid = false;
867
1242
  }
868
1243
  if (isRequiredProperty(value, "type", "string")) {
869
1244
  const safeBindings = [
870
1245
  "plain_text",
871
1246
  "json",
1247
+ "wasm_module",
1248
+ "data_blob",
1249
+ "text_blob",
872
1250
  "kv_namespace",
873
1251
  "durable_object_namespace",
1252
+ "r2_bucket",
1253
+ "service",
874
1254
  ];
875
1255
 
876
1256
  if (safeBindings.includes(value.type)) {
@@ -881,7 +1261,7 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, _field, value) => {
881
1261
  );
882
1262
  }
883
1263
  } else {
884
- diagnostics.errors.push(`ERROR: should have a string "type" field.`);
1264
+ diagnostics.errors.push(`binding should have a string "type" field.`);
885
1265
  isValid = false;
886
1266
  }
887
1267
  return isValid;
@@ -981,7 +1361,7 @@ const validateKVBinding: ValidatorFn = (diagnostics, field, value) => {
981
1361
  const validateR2Binding: ValidatorFn = (diagnostics, field, value) => {
982
1362
  if (typeof value !== "object" || value === null) {
983
1363
  diagnostics.errors.push(
984
- `"kv_namespaces" bindings should be objects, but got ${JSON.stringify(
1364
+ `"r2_buckets" bindings should be objects, but got ${JSON.stringify(
985
1365
  value
986
1366
  )}`
987
1367
  );
@@ -1015,3 +1395,136 @@ const validateR2Binding: ValidatorFn = (diagnostics, field, value) => {
1015
1395
  }
1016
1396
  return isValid;
1017
1397
  };
1398
+
1399
+ /**
1400
+ * Check that bindings whose names might conflict, don't.
1401
+ *
1402
+ * We don't want to have, for example, a KV namespace named "DATA"
1403
+ * and a Durable Object also named "DATA". Then it would be ambiguous
1404
+ * what exactly would live at `env.DATA` (or in the case of service-workers,
1405
+ * the `DATA` global).
1406
+ */
1407
+ const validateBindingsHaveUniqueNames = (
1408
+ diagnostics: Diagnostics,
1409
+ {
1410
+ durable_objects,
1411
+ kv_namespaces,
1412
+ r2_buckets,
1413
+ text_blobs,
1414
+ unsafe,
1415
+ vars,
1416
+ wasm_modules,
1417
+ data_blobs,
1418
+ }: Partial<Config>
1419
+ ): boolean => {
1420
+ let hasDuplicates = false;
1421
+
1422
+ const bindingsGroupedByType = {
1423
+ "Durable Object": getBindingNames(durable_objects),
1424
+ "KV Namespace": getBindingNames(kv_namespaces),
1425
+ "R2 Bucket": getBindingNames(r2_buckets),
1426
+ "Text Blob": getBindingNames(text_blobs),
1427
+ Unsafe: getBindingNames(unsafe),
1428
+ "Environment Variable": getBindingNames(vars),
1429
+ "WASM Module": getBindingNames(wasm_modules),
1430
+ "Data Blob": getBindingNames(data_blobs),
1431
+ } as Record<string, string[]>;
1432
+
1433
+ const bindingsGroupedByName: Record<string, string[]> = {};
1434
+
1435
+ for (const bindingType in bindingsGroupedByType) {
1436
+ const bindingNames = bindingsGroupedByType[bindingType];
1437
+
1438
+ for (const bindingName of bindingNames) {
1439
+ if (!(bindingName in bindingsGroupedByName)) {
1440
+ bindingsGroupedByName[bindingName] = [];
1441
+ }
1442
+
1443
+ bindingsGroupedByName[bindingName].push(bindingType);
1444
+ }
1445
+ }
1446
+
1447
+ for (const bindingName in bindingsGroupedByName) {
1448
+ const bindingTypes = bindingsGroupedByName[bindingName];
1449
+ if (bindingTypes.length < 2) {
1450
+ // there's only one (or zero) binding(s) with this name, which is fine, actually
1451
+ continue;
1452
+ }
1453
+
1454
+ hasDuplicates = true;
1455
+
1456
+ // there's two types of duplicates we want to look for:
1457
+ // - bindings with the same name of the same type (e.g. two Durable Objects both named "OBJ")
1458
+ // - bindings with the same name of different types (a KV namespace and DO both named "DATA")
1459
+
1460
+ const sameType = bindingTypes
1461
+ // filter once to find duplicate binding types
1462
+ .filter((type, i) => bindingTypes.indexOf(type) !== i)
1463
+ // filter twice to only get _unique_ duplicate binding types
1464
+ .filter(
1465
+ (type, i, duplicateBindingTypes) =>
1466
+ duplicateBindingTypes.indexOf(type) === i
1467
+ );
1468
+
1469
+ const differentTypes = bindingTypes.filter(
1470
+ (type, i) => bindingTypes.indexOf(type) === i
1471
+ );
1472
+
1473
+ if (differentTypes.length > 1) {
1474
+ // we have multiple different types using the same name
1475
+ diagnostics.errors.push(
1476
+ `${bindingName} assigned to ${ENGLISH.format(differentTypes)} bindings.`
1477
+ );
1478
+ }
1479
+
1480
+ sameType.forEach((bindingType) => {
1481
+ diagnostics.errors.push(
1482
+ `${bindingName} assigned to multiple ${bindingType} bindings.`
1483
+ );
1484
+ });
1485
+ }
1486
+
1487
+ if (hasDuplicates) {
1488
+ const problem =
1489
+ "Bindings must have unique names, so that they can all be referenced in the worker.";
1490
+ const resolution = "Please change your bindings to have unique names.";
1491
+ diagnostics.errors.push(`${problem}\n${resolution}`);
1492
+ }
1493
+
1494
+ return !hasDuplicates;
1495
+ };
1496
+ const validateServiceBinding: ValidatorFn = (diagnostics, field, value) => {
1497
+ if (typeof value !== "object" || value === null) {
1498
+ diagnostics.errors.push(
1499
+ `"services" bindings should be objects, but got ${JSON.stringify(value)}`
1500
+ );
1501
+ return false;
1502
+ }
1503
+ let isValid = true;
1504
+ // Service bindings must have a binding, service, and environment.
1505
+ if (!isRequiredProperty(value, "binding", "string")) {
1506
+ diagnostics.errors.push(
1507
+ `"${field}" bindings should have a string "binding" field but got ${JSON.stringify(
1508
+ value
1509
+ )}.`
1510
+ );
1511
+ isValid = false;
1512
+ }
1513
+ if (!isRequiredProperty(value, "service", "string")) {
1514
+ diagnostics.errors.push(
1515
+ `"${field}" bindings should have a string "service" field but got ${JSON.stringify(
1516
+ value
1517
+ )}.`
1518
+ );
1519
+ isValid = false;
1520
+ }
1521
+ if (!isOptionalProperty(value, "environment", "string")) {
1522
+ diagnostics.errors.push(
1523
+ `"${field}" bindings should have a string "environment" field but got ${JSON.stringify(
1524
+ value
1525
+ )}.`
1526
+ );
1527
+ isValid = false;
1528
+ }
1529
+ return isValid;
1530
+ };