wrangler 0.0.0-e88a54e → 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 (88) hide show
  1. package/README.md +25 -14
  2. package/bin/wrangler.js +13 -1
  3. package/miniflare-dist/index.mjs +4828 -21
  4. package/package.json +29 -24
  5. package/pages/functions/buildPlugin.ts +92 -0
  6. package/pages/functions/buildWorker.ts +80 -4
  7. package/pages/functions/filepath-routing.test.ts +11 -0
  8. package/pages/functions/filepath-routing.ts +8 -0
  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 +530 -201
  14. package/src/__tests__/dev.test.tsx +284 -18
  15. package/src/__tests__/guess-worker-format.test.ts +28 -1
  16. package/src/__tests__/helpers/mock-cfetch.ts +5 -1
  17. package/src/__tests__/helpers/mock-console.ts +20 -3
  18. package/src/__tests__/helpers/mock-dialogs.ts +26 -1
  19. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  20. package/src/__tests__/helpers/mock-istty.ts +27 -0
  21. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  22. package/src/__tests__/helpers/run-in-tmp.ts +7 -0
  23. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  24. package/src/__tests__/https-options.test.ts +5 -2
  25. package/src/__tests__/index.test.ts +27 -769
  26. package/src/__tests__/init.test.ts +1516 -0
  27. package/src/__tests__/jest.setup.ts +4 -4
  28. package/src/__tests__/kv.test.ts +133 -61
  29. package/src/__tests__/logger.test.ts +115 -0
  30. package/src/__tests__/pages.test.ts +311 -26
  31. package/src/__tests__/publish.test.ts +2789 -618
  32. package/src/__tests__/r2.test.ts +26 -10
  33. package/src/__tests__/route.test.ts +4 -4
  34. package/src/__tests__/secret.test.ts +55 -34
  35. package/src/__tests__/tail.test.ts +146 -20
  36. package/src/__tests__/user.test.ts +131 -0
  37. package/src/__tests__/whoami.test.tsx +14 -9
  38. package/src/bundle.ts +30 -3
  39. package/src/cfetch/internal.ts +0 -2
  40. package/src/cli.ts +5 -11
  41. package/src/config/README.md +1 -1
  42. package/src/config/config.ts +20 -4
  43. package/src/config/environment.ts +32 -4
  44. package/src/config/index.ts +4 -3
  45. package/src/config/validation-helpers.ts +96 -7
  46. package/src/config/validation.ts +388 -54
  47. package/src/config-cache.ts +45 -0
  48. package/src/create-worker-preview.ts +22 -13
  49. package/src/create-worker-upload-form.ts +32 -9
  50. package/src/dev/dev-vars.ts +36 -0
  51. package/src/dev/dev.tsx +132 -78
  52. package/src/dev/local.tsx +101 -21
  53. package/src/dev/remote.tsx +23 -13
  54. package/src/dev/use-esbuild.ts +23 -5
  55. package/src/dialogs.tsx +16 -6
  56. package/src/entry.ts +83 -24
  57. package/src/environment-variables.ts +3 -1
  58. package/src/errors.ts +11 -0
  59. package/src/https-options.ts +3 -2
  60. package/src/index.tsx +689 -394
  61. package/src/inspect.ts +17 -11
  62. package/src/intl-polyfill.d.ts +139 -0
  63. package/src/logger.ts +66 -0
  64. package/src/miniflare-cli/index.ts +4 -0
  65. package/src/miniflare-cli/request-context.ts +40 -0
  66. package/src/module-collection.ts +7 -7
  67. package/src/open-in-browser.ts +5 -4
  68. package/src/package-manager.ts +9 -8
  69. package/src/pages.tsx +875 -56
  70. package/src/parse.ts +1 -1
  71. package/src/proxy.ts +28 -19
  72. package/src/publish.ts +263 -141
  73. package/src/sites.tsx +27 -14
  74. package/src/tail/index.ts +2 -2
  75. package/src/tail/printing.ts +13 -8
  76. package/src/update-check.ts +19 -0
  77. package/src/user.tsx +116 -41
  78. package/src/whoami.tsx +2 -1
  79. package/src/worker.ts +15 -6
  80. package/templates/gitignore +171 -0
  81. package/templates/static-asset-facade.js +1 -1
  82. package/wrangler-dist/cli.js +21162 -50227
  83. package/miniflare-dist/index.mjs.map +0 -7
  84. package/src/__tests__/dev2.test.tsx +0 -154
  85. package/src/__tests__/logout.test.ts +0 -52
  86. package/src/__tests__/sentry.test.ts +0 -146
  87. package/src/reporting.ts +0 -131
  88. package/wrangler-dist/cli.js.map +0 -7
@@ -22,6 +22,9 @@ import {
22
22
  all,
23
23
  isMutuallyExclusiveWith,
24
24
  inheritableInLegacyEnvironments,
25
+ appendEnvName,
26
+ getBindingNames,
27
+ isValidName,
25
28
  } from "./validation-helpers";
26
29
  import type { Config, DevConfig, RawConfig, RawDevConfig } from "./config";
27
30
  import type {
@@ -32,6 +35,8 @@ import type {
32
35
  } from "./environment";
33
36
  import type { ValidatorFn } from "./validation-helpers";
34
37
 
38
+ const ENGLISH = new Intl.ListFormat("en");
39
+
35
40
  /**
36
41
  * Validate the given `rawConfig` object that was loaded from `configPath`.
37
42
  *
@@ -43,7 +48,7 @@ import type { ValidatorFn } from "./validation-helpers";
43
48
  export function normalizeAndValidateConfig(
44
49
  rawConfig: RawConfig,
45
50
  configPath: string | undefined,
46
- envName: string | undefined
51
+ args: unknown
47
52
  ): {
48
53
  config: Config;
49
54
  diagnostics: Diagnostics;
@@ -57,26 +62,28 @@ export function normalizeAndValidateConfig(
57
62
  deprecated(
58
63
  diagnostics,
59
64
  rawConfig,
60
- "type",
61
- "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.",
62
- 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"
63
69
  );
64
70
 
65
71
  deprecated(
66
72
  diagnostics,
67
73
  rawConfig,
68
- "webpack_config",
69
- "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.",
70
- 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"
71
78
  );
72
79
 
73
80
  deprecated(
74
81
  diagnostics,
75
82
  rawConfig,
76
- `site.entry-point`,
77
- `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.`,
78
- false,
79
- 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"
80
87
  );
81
88
 
82
89
  validateOptionalProperty(
@@ -87,8 +94,34 @@ export function normalizeAndValidateConfig(
87
94
  "boolean"
88
95
  );
89
96
 
97
+ validateOptionalProperty(
98
+ diagnostics,
99
+ "",
100
+ "minify",
101
+ rawConfig.minify,
102
+ "boolean"
103
+ );
104
+
105
+ validateOptionalProperty(
106
+ diagnostics,
107
+ "",
108
+ "node_compat",
109
+ rawConfig.node_compat,
110
+ "boolean"
111
+ );
112
+
90
113
  // TODO: set the default to false to turn on service environments as the default
91
- const isLegacyEnv = rawConfig.legacy_env ?? true;
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
+ }
92
125
 
93
126
  const topLevelEnv = normalizeAndValidateEnvironment(
94
127
  diagnostics,
@@ -96,13 +129,16 @@ export function normalizeAndValidateConfig(
96
129
  rawConfig
97
130
  );
98
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
+
99
135
  let activeEnv = topLevelEnv;
100
136
  if (envName !== undefined) {
137
+ const envDiagnostics = new Diagnostics(
138
+ `"env.${envName}" environment configuration`
139
+ );
101
140
  const rawEnv = rawConfig.env?.[envName];
102
141
  if (rawEnv !== undefined) {
103
- const envDiagnostics = new Diagnostics(
104
- `"env.${envName}" environment configuration`
105
- );
106
142
  activeEnv = normalizeAndValidateEnvironment(
107
143
  envDiagnostics,
108
144
  configPath,
@@ -114,6 +150,18 @@ export function normalizeAndValidateConfig(
114
150
  );
115
151
  diagnostics.addChild(envDiagnostics);
116
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
+ );
117
165
  const envNames = rawConfig.env
118
166
  ? `The available configured environment names are: ${JSON.stringify(
119
167
  Object.keys(rawConfig.env)
@@ -147,7 +195,12 @@ export function normalizeAndValidateConfig(
147
195
  diagnostics,
148
196
  rawConfig.migrations ?? []
149
197
  ),
150
- site: normalizeAndValidateSite(diagnostics, rawConfig.site),
198
+ site: normalizeAndValidateSite(
199
+ diagnostics,
200
+ configPath,
201
+ rawConfig,
202
+ activeEnv.main
203
+ ),
151
204
  wasm_modules: normalizeAndValidateModulePaths(
152
205
  diagnostics,
153
206
  configPath,
@@ -160,13 +213,21 @@ export function normalizeAndValidateConfig(
160
213
  "text_blobs",
161
214
  rawConfig.text_blobs
162
215
  ),
216
+ data_blobs: normalizeAndValidateModulePaths(
217
+ diagnostics,
218
+ configPath,
219
+ "data_blobs",
220
+ rawConfig.data_blobs
221
+ ),
163
222
  };
164
223
 
224
+ validateBindingsHaveUniqueNames(diagnostics, config);
225
+
165
226
  validateAdditionalProperties(
166
227
  diagnostics,
167
228
  "top-level",
168
229
  Object.keys(rawConfig),
169
- [...Object.keys(config), "env", "miniflare"]
230
+ [...Object.keys(config), "env"]
170
231
  );
171
232
 
172
233
  return { config, diagnostics };
@@ -389,26 +450,91 @@ function normalizeAndValidateMigrations(
389
450
  */
390
451
  function normalizeAndValidateSite(
391
452
  diagnostics: Diagnostics,
392
- rawSite: Config["site"]
453
+ configPath: string | undefined,
454
+ rawConfig: RawConfig,
455
+ mainEntryPoint: string | undefined
393
456
  ): Config["site"] {
394
- if (rawSite !== undefined) {
395
- const { bucket, include = [], exclude = [], ...rest } = rawSite;
396
- 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
+ ]);
397
463
  validateRequiredProperty(diagnostics, "site", "bucket", bucket, "string");
398
464
  validateTypedArray(diagnostics, "sites.include", include, "string");
399
465
  validateTypedArray(diagnostics, "sites.exclude", exclude, "string");
400
- 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
+ };
401
527
  }
402
528
  return undefined;
403
529
  }
404
530
 
405
531
  /**
406
- * 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.
407
533
  */
408
534
  function normalizeAndValidateModulePaths(
409
535
  diagnostics: Diagnostics,
410
536
  configPath: string | undefined,
411
- field: "wasm_modules" | "text_blobs",
537
+ field: "wasm_modules" | "text_blobs" | "data_blobs",
412
538
  rawMapping: Record<string, string> | undefined
413
539
  ): Record<string, string> | undefined {
414
540
  if (rawMapping === undefined) {
@@ -431,6 +557,102 @@ function normalizeAndValidateModulePaths(
431
557
  return mapping;
432
558
  }
433
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
+
434
656
  /**
435
657
  * Validate top-level environment configuration and return the normalized values.
436
658
  */
@@ -496,29 +718,17 @@ function normalizeAndValidateEnvironment(
496
718
 
497
719
  experimental(diagnostics, rawEnv, "unsafe");
498
720
 
499
- const route = inheritable(
500
- diagnostics,
501
- topLevelEnv,
502
- rawEnv,
503
- "route",
504
- isString,
505
- undefined
506
- );
507
- const routes = inheritable(
508
- diagnostics,
509
- topLevelEnv,
510
- rawEnv,
511
- "routes",
512
- all(isStringArray, isMutuallyExclusiveWith(rawEnv, "route")),
513
- undefined
514
- );
721
+ const route = validateRoute(diagnostics, topLevelEnv, rawEnv);
722
+
723
+ const routes = validateRoutes(diagnostics, topLevelEnv, rawEnv);
724
+
515
725
  const workers_dev = inheritable(
516
726
  diagnostics,
517
727
  topLevelEnv,
518
728
  rawEnv,
519
729
  "workers_dev",
520
730
  isBoolean,
521
- !(routes || route)
731
+ undefined
522
732
  );
523
733
 
524
734
  const { deprecatedUpload, ...build } = normalizeAndValidateBuild(
@@ -530,12 +740,14 @@ function normalizeAndValidateEnvironment(
530
740
 
531
741
  const environment: Environment = {
532
742
  // Inherited fields
533
- account_id: inheritable(
743
+ account_id: inheritableInLegacyEnvironments(
534
744
  diagnostics,
745
+ isLegacyEnv,
535
746
  topLevelEnv,
536
747
  rawEnv,
537
748
  "account_id",
538
749
  isString,
750
+ undefined,
539
751
  undefined
540
752
  ),
541
753
  compatibility_date: inheritable(
@@ -570,6 +782,12 @@ function normalizeAndValidateEnvironment(
570
782
  isString,
571
783
  "React.Fragment"
572
784
  ),
785
+ tsconfig: validateAndNormalizeTsconfig(
786
+ diagnostics,
787
+ topLevelEnv,
788
+ rawEnv,
789
+ configPath
790
+ ),
573
791
  rules: validateAndNormalizeRules(
574
792
  diagnostics,
575
793
  topLevelEnv,
@@ -583,7 +801,8 @@ function normalizeAndValidateEnvironment(
583
801
  topLevelEnv,
584
802
  rawEnv,
585
803
  "name",
586
- isString,
804
+ isValidName,
805
+ appendEnvName(envName),
587
806
  undefined
588
807
  ),
589
808
  main: normalizeAndValidateMainField(
@@ -674,11 +893,36 @@ function normalizeAndValidateEnvironment(
674
893
  }
675
894
  ),
676
895
  zone_id: rawEnv.zone_id,
896
+ minify: rawEnv.minify,
897
+ node_compat: rawEnv.node_compat,
677
898
  };
678
899
 
679
900
  return environment;
680
901
  }
681
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
+
682
926
  const validateAndNormalizeRules = (
683
927
  diagnostics: Diagnostics,
684
928
  topLevelEnv: Environment | undefined,
@@ -694,7 +938,7 @@ const validateAndNormalizeRules = (
694
938
  );
695
939
  } else if (deprecatedRules) {
696
940
  diagnostics.warnings.push(
697
- `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` +
698
942
  "```\n" +
699
943
  TOML.stringify({ rules: deprecatedRules }) +
700
944
  "```"
@@ -867,7 +1111,7 @@ const validateBindingsProperty =
867
1111
  diagnostics.warnings.push(
868
1112
  `The following bindings are at the top level, but not on "env.${envName}".\n` +
869
1113
  `This is not what you probably want, since "${field}" configuration is not inherited by environments.\n` +
870
- `Please add a binding for each to "${fieldPath}.bindings".` +
1114
+ `Please add a binding for each to "${fieldPath}.bindings":\n` +
871
1115
  missingBindings.map((name) => `- ${name}`).join("\n")
872
1116
  );
873
1117
  }
@@ -876,14 +1120,6 @@ const validateBindingsProperty =
876
1120
  return isValid;
877
1121
  };
878
1122
 
879
- /**
880
- * Get the names of the bindings collection in `value`.
881
- */
882
- const getBindingNames = (value: unknown): string[] =>
883
- ((value as { bindings: { name: string }[] })?.bindings ?? []).map(
884
- (binding) => binding.name
885
- );
886
-
887
1123
  /**
888
1124
  * Check that the given field is a valid "durable_object" binding object.
889
1125
  */
@@ -1054,7 +1290,7 @@ const validateKVBinding: ValidatorFn = (diagnostics, field, value) => {
1054
1290
  const validateR2Binding: ValidatorFn = (diagnostics, field, value) => {
1055
1291
  if (typeof value !== "object" || value === null) {
1056
1292
  diagnostics.errors.push(
1057
- `"kv_namespaces" bindings should be objects, but got ${JSON.stringify(
1293
+ `"r2_buckets" bindings should be objects, but got ${JSON.stringify(
1058
1294
  value
1059
1295
  )}`
1060
1296
  );
@@ -1088,3 +1324,101 @@ const validateR2Binding: ValidatorFn = (diagnostics, field, value) => {
1088
1324
  }
1089
1325
  return isValid;
1090
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
+ };
@@ -0,0 +1,45 @@
1
+ import { mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
2
+ import { dirname, join } from "path";
3
+
4
+ let cacheMessageShown = false;
5
+
6
+ const cacheFolder = "node_modules/.cache/wrangler";
7
+
8
+ const showCacheMessage = () => {
9
+ if (!cacheMessageShown) {
10
+ console.log(
11
+ `Using cached values in '${cacheFolder}'. This is used as a temporary store to improve the developer experience for some commands. It may be purged at any time. It doesn't contain any sensitive information, but it should not be commited into source control.`
12
+ );
13
+ cacheMessageShown = true;
14
+ }
15
+ };
16
+
17
+ export const getConfigCache = <T>(fileName: string): Partial<T> => {
18
+ try {
19
+ const configCacheLocation = join(cacheFolder, fileName);
20
+ const configCache = JSON.parse(readFileSync(configCacheLocation, "utf-8"));
21
+ showCacheMessage();
22
+ return configCache;
23
+ } catch {
24
+ return {};
25
+ }
26
+ };
27
+
28
+ export const saveToConfigCache = <T>(
29
+ fileName: string,
30
+ newValues: Partial<T>
31
+ ) => {
32
+ const configCacheLocation = join(cacheFolder, fileName);
33
+ const existingValues = getConfigCache(fileName);
34
+
35
+ mkdirSync(dirname(configCacheLocation), { recursive: true });
36
+ writeFileSync(
37
+ configCacheLocation,
38
+ JSON.stringify({ ...existingValues, ...newValues }, null, 2)
39
+ );
40
+ showCacheMessage();
41
+ };
42
+
43
+ export const purgeConfigCaches = () => {
44
+ rmSync(cacheFolder, { recursive: true, force: true });
45
+ };