wrangler 0.0.0-e3cab74 → 0.0.0-e3ce82b

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