wrangler 0.0.0-e95bea2 → 0.0.0-e95e5a0

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