wrangler 0.0.0-e95e5a0 → 0.0.0-e978986

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 (52) hide show
  1. package/Cloudflare_CA.pem +18 -0
  2. package/README.md +3 -1
  3. package/bin/wrangler.js +36 -4
  4. package/package.json +11 -7
  5. package/pages/functions/buildPlugin.ts +13 -0
  6. package/pages/functions/buildWorker.ts +14 -1
  7. package/pages/functions/template-plugin.ts +3 -2
  8. package/src/__tests__/configuration.test.ts +336 -87
  9. package/src/__tests__/dev.test.tsx +243 -25
  10. package/src/__tests__/helpers/mock-dialogs.ts +41 -1
  11. package/src/__tests__/index.test.ts +79 -72
  12. package/src/__tests__/init.test.ts +182 -131
  13. package/src/__tests__/jest.setup.ts +8 -0
  14. package/src/__tests__/kv.test.ts +173 -146
  15. package/src/__tests__/package-manager.test.ts +154 -7
  16. package/src/__tests__/pages.test.ts +381 -53
  17. package/src/__tests__/parse.test.ts +8 -0
  18. package/src/__tests__/publish.test.ts +682 -89
  19. package/src/__tests__/r2.test.ts +55 -36
  20. package/src/__tests__/secret.test.ts +39 -4
  21. package/src/abort.d.ts +3 -0
  22. package/src/bundle.ts +33 -2
  23. package/src/cfetch/index.ts +21 -4
  24. package/src/cfetch/internal.ts +17 -9
  25. package/src/config/config.ts +1 -1
  26. package/src/config/environment.ts +40 -14
  27. package/src/config/index.ts +162 -0
  28. package/src/config/validation.ts +180 -65
  29. package/src/create-worker-preview.ts +17 -7
  30. package/src/create-worker-upload-form.ts +22 -8
  31. package/src/dev/dev.tsx +7 -6
  32. package/src/dev/local.tsx +6 -0
  33. package/src/dev/remote.tsx +15 -1
  34. package/src/dialogs.tsx +48 -0
  35. package/src/durable.ts +102 -0
  36. package/src/entry.ts +64 -9
  37. package/src/index.tsx +357 -177
  38. package/src/inspect.ts +39 -0
  39. package/src/kv.ts +114 -25
  40. package/src/open-in-browser.ts +5 -12
  41. package/src/package-manager.ts +50 -3
  42. package/src/pages.tsx +271 -110
  43. package/src/parse.ts +23 -4
  44. package/src/proxy.ts +38 -22
  45. package/src/publish.ts +227 -113
  46. package/src/sites.tsx +19 -17
  47. package/src/user.tsx +11 -1
  48. package/src/worker.ts +8 -0
  49. package/templates/new-worker-scheduled.js +17 -0
  50. package/templates/new-worker-scheduled.ts +32 -0
  51. package/templates/new-worker.ts +16 -1
  52. package/wrangler-dist/cli.js +36017 -22687
@@ -2,6 +2,7 @@ import { findUpSync } from "find-up";
2
2
  import { logger } from "../logger";
3
3
  import { parseTOML, readFileSync } from "../parse";
4
4
  import { normalizeAndValidateConfig } from "./validation";
5
+ import type { CfWorkerInit } from "../worker";
5
6
  import type { Config, RawConfig } from "./config";
6
7
 
7
8
  export type {
@@ -61,3 +62,164 @@ export function findWranglerToml(
61
62
  const configPath = findUpSync("wrangler.toml", { cwd: referencePath });
62
63
  return configPath;
63
64
  }
65
+
66
+ /**
67
+ * Print all the bindings a worker using a given config would have access to
68
+ */
69
+ export function printBindings(bindings: CfWorkerInit["bindings"]) {
70
+ const truncate = (item: string | Record<string, unknown>) => {
71
+ const s = typeof item === "string" ? item : JSON.stringify(item);
72
+ const maxLength = 40;
73
+ if (s.length < maxLength) {
74
+ return s;
75
+ }
76
+
77
+ return `${s.substring(0, maxLength - 3)}...`;
78
+ };
79
+
80
+ const output: { type: string; entries: { key: string; value: string }[] }[] =
81
+ [];
82
+
83
+ const {
84
+ data_blobs,
85
+ durable_objects,
86
+ kv_namespaces,
87
+ r2_buckets,
88
+ services,
89
+ text_blobs,
90
+ unsafe,
91
+ vars,
92
+ wasm_modules,
93
+ } = bindings;
94
+
95
+ if (data_blobs !== undefined && Object.keys(data_blobs).length > 0) {
96
+ output.push({
97
+ type: "Data Blobs",
98
+ entries: Object.entries(data_blobs).map(([key, value]) => ({
99
+ key,
100
+ value: truncate(value),
101
+ })),
102
+ });
103
+ }
104
+
105
+ if (durable_objects !== undefined && durable_objects.bindings.length > 0) {
106
+ output.push({
107
+ type: "Durable Objects",
108
+ entries: durable_objects.bindings.map(
109
+ ({ name, class_name, script_name, environment }) => {
110
+ let value = class_name;
111
+ if (script_name) {
112
+ value += ` (defined in ${script_name})`;
113
+ }
114
+ if (environment) {
115
+ value += ` - ${environment}`;
116
+ }
117
+
118
+ return {
119
+ key: name,
120
+ value,
121
+ };
122
+ }
123
+ ),
124
+ });
125
+ }
126
+
127
+ if (kv_namespaces !== undefined && kv_namespaces.length > 0) {
128
+ output.push({
129
+ type: "KV Namespaces",
130
+ entries: kv_namespaces.map(({ binding, id }) => {
131
+ return {
132
+ key: binding,
133
+ value: id,
134
+ };
135
+ }),
136
+ });
137
+ }
138
+
139
+ if (r2_buckets !== undefined && r2_buckets.length > 0) {
140
+ output.push({
141
+ type: "R2 Buckets",
142
+ entries: r2_buckets.map(({ binding, bucket_name }) => {
143
+ return {
144
+ key: binding,
145
+ value: bucket_name,
146
+ };
147
+ }),
148
+ });
149
+ }
150
+
151
+ if (services !== undefined && services.length > 0) {
152
+ output.push({
153
+ type: "Services",
154
+ entries: services.map(({ binding, service, environment }) => {
155
+ let value = service;
156
+ if (environment) {
157
+ value += ` - ${environment}`;
158
+ }
159
+
160
+ return {
161
+ key: binding,
162
+ value,
163
+ };
164
+ }),
165
+ });
166
+ }
167
+
168
+ if (text_blobs !== undefined && Object.keys(text_blobs).length > 0) {
169
+ output.push({
170
+ type: "Text Blobs",
171
+ entries: Object.entries(text_blobs).map(([key, value]) => ({
172
+ key,
173
+ value: truncate(value),
174
+ })),
175
+ });
176
+ }
177
+
178
+ if (unsafe !== undefined && unsafe.length > 0) {
179
+ output.push({
180
+ type: "Unsafe",
181
+ entries: unsafe.map(({ name, type }) => ({
182
+ key: type,
183
+ value: name,
184
+ })),
185
+ });
186
+ }
187
+
188
+ if (vars !== undefined && Object.keys(vars).length > 0) {
189
+ output.push({
190
+ type: "Vars",
191
+ entries: Object.entries(vars).map(([key, value]) => ({
192
+ key,
193
+ value: `"${truncate(`${value}`)}"`,
194
+ })),
195
+ });
196
+ }
197
+
198
+ if (wasm_modules !== undefined && Object.keys(wasm_modules).length > 0) {
199
+ output.push({
200
+ type: "Wasm Modules",
201
+ entries: Object.entries(wasm_modules).map(([key, value]) => ({
202
+ key,
203
+ value: truncate(value),
204
+ })),
205
+ });
206
+ }
207
+
208
+ if (output.length === 0) {
209
+ return;
210
+ }
211
+
212
+ const message = [
213
+ `Your worker has access to the following bindings:`,
214
+ ...output
215
+ .map((bindingGroup) => {
216
+ return [
217
+ `- ${bindingGroup.type}:`,
218
+ bindingGroup.entries.map(({ key, value }) => ` - ${key}: ${value}`),
219
+ ];
220
+ })
221
+ .flat(2),
222
+ ].join("\n");
223
+
224
+ logger.log(message);
225
+ }
@@ -94,22 +94,6 @@ export function normalizeAndValidateConfig(
94
94
  "boolean"
95
95
  );
96
96
 
97
- validateOptionalProperty(
98
- diagnostics,
99
- "",
100
- "minify",
101
- rawConfig.minify,
102
- "boolean"
103
- );
104
-
105
- validateOptionalProperty(
106
- diagnostics,
107
- "",
108
- "node_compat",
109
- rawConfig.node_compat,
110
- "boolean"
111
- );
112
-
113
97
  // TODO: set the default to false to turn on service environments as the default
114
98
  const isLegacyEnv =
115
99
  (args as { "legacy-env": boolean | undefined })["legacy-env"] ??
@@ -193,7 +177,8 @@ export function normalizeAndValidateConfig(
193
177
  dev: normalizeAndValidateDev(diagnostics, rawConfig.dev ?? {}),
194
178
  migrations: normalizeAndValidateMigrations(
195
179
  diagnostics,
196
- rawConfig.migrations ?? []
180
+ rawConfig.migrations ?? [],
181
+ activeEnv.durable_objects
197
182
  ),
198
183
  site: normalizeAndValidateSite(
199
184
  diagnostics,
@@ -352,7 +337,7 @@ function normalizeAndValidateDev(
352
337
  ): DevConfig {
353
338
  const {
354
339
  ip = "localhost",
355
- port = 8787,
340
+ port,
356
341
  local_protocol = "http",
357
342
  upstream_protocol = "https",
358
343
  host,
@@ -387,7 +372,8 @@ function normalizeAndValidateDev(
387
372
  */
388
373
  function normalizeAndValidateMigrations(
389
374
  diagnostics: Diagnostics,
390
- rawMigrations: Config["migrations"]
375
+ rawMigrations: Config["migrations"],
376
+ durableObjects: Config["durable_objects"]
391
377
  ): Config["migrations"] {
392
378
  if (!Array.isArray(rawMigrations)) {
393
379
  diagnostics.errors.push(
@@ -398,29 +384,38 @@ function normalizeAndValidateMigrations(
398
384
  return [];
399
385
  } else {
400
386
  for (let i = 0; i < rawMigrations.length; i++) {
401
- const migration = rawMigrations[i];
387
+ const { tag, new_classes, renamed_classes, deleted_classes, ...rest } =
388
+ rawMigrations[i];
389
+
390
+ validateAdditionalProperties(
391
+ diagnostics,
392
+ "migrations",
393
+ Object.keys(rest),
394
+ []
395
+ );
396
+
402
397
  validateRequiredProperty(
403
398
  diagnostics,
404
399
  `migrations[${i}]`,
405
400
  `tag`,
406
- migration.tag,
401
+ tag,
407
402
  "string"
408
403
  );
409
404
  validateOptionalTypedArray(
410
405
  diagnostics,
411
406
  `migrations[${i}].new_classes`,
412
- migration.new_classes,
407
+ new_classes,
413
408
  "string"
414
409
  );
415
- if (migration.renamed_classes !== undefined) {
416
- if (!Array.isArray(migration.renamed_classes)) {
410
+ if (renamed_classes !== undefined) {
411
+ if (!Array.isArray(renamed_classes)) {
417
412
  diagnostics.errors.push(
418
413
  `Expected "migrations[${i}].renamed_classes" to be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
419
- migration.renamed_classes
414
+ renamed_classes
420
415
  )}.`
421
416
  );
422
417
  } else if (
423
- migration.renamed_classes.some(
418
+ renamed_classes.some(
424
419
  (c) =>
425
420
  typeof c !== "object" ||
426
421
  !isRequiredProperty(c, "from", "string") ||
@@ -429,7 +424,7 @@ function normalizeAndValidateMigrations(
429
424
  ) {
430
425
  diagnostics.errors.push(
431
426
  `Expected "migrations[${i}].renamed_classes" to be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
432
- migration.renamed_classes
427
+ renamed_classes
433
428
  )}.`
434
429
  );
435
430
  }
@@ -437,10 +432,49 @@ function normalizeAndValidateMigrations(
437
432
  validateOptionalTypedArray(
438
433
  diagnostics,
439
434
  `migrations[${i}].deleted_classes`,
440
- migration.deleted_classes,
435
+ deleted_classes,
441
436
  "string"
442
437
  );
443
438
  }
439
+
440
+ if (
441
+ Array.isArray(durableObjects?.bindings) &&
442
+ durableObjects.bindings.length > 0
443
+ ) {
444
+ // intrinsic [durable_objects] implies [migrations]
445
+ const exportedDurableObjects = (durableObjects.bindings || []).filter(
446
+ (binding) => !binding.script_name
447
+ );
448
+ if (exportedDurableObjects.length > 0 && rawMigrations.length === 0) {
449
+ if (
450
+ !exportedDurableObjects.some(
451
+ (exportedDurableObject) =>
452
+ typeof exportedDurableObject.class_name !== "string"
453
+ )
454
+ ) {
455
+ const durableObjectClassnames = exportedDurableObjects.map(
456
+ (durable) => durable.class_name
457
+ );
458
+
459
+ diagnostics.warnings.push(
460
+ `In wrangler.toml, you have configured [durable_objects] exported by this Worker (${durableObjectClassnames.join(
461
+ ", "
462
+ )}), 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:
463
+
464
+ \`\`\`
465
+ [[migrations]]
466
+ tag = "v1" # Should be unique for each entry
467
+ new_classes = [${durableObjectClassnames
468
+ .map((name) => `"${name}"`)
469
+ .join(", ")}]
470
+ \`\`\`
471
+
472
+ Refer to https://developers.cloudflare.com/workers/learning/using-durable-objects/#durable-object-migrations-in-wranglertoml for more details.`
473
+ );
474
+ }
475
+ }
476
+ }
477
+
444
478
  return rawMigrations;
445
479
  }
446
480
  }
@@ -562,19 +596,37 @@ function normalizeAndValidateModulePaths(
562
596
  * or an object that looks like {pattern: string, zone_id: string }
563
597
  */
564
598
  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
- );
599
+ if (!item) {
600
+ return false;
601
+ }
602
+ if (typeof item === "string") {
603
+ return true;
604
+ }
605
+ if (typeof item === "object") {
606
+ if (!hasProperty(item, "pattern") || typeof item.pattern !== "string") {
607
+ return false;
608
+ }
609
+
610
+ const otherKeys = Object.keys(item).length - 1; // minus one to subtract "pattern"
611
+
612
+ const hasZoneId =
613
+ hasProperty(item, "zone_id") && typeof item.zone_id === "string";
614
+ const hasZoneName =
615
+ hasProperty(item, "zone_name") && typeof item.zone_name === "string";
616
+ const hasCustomDomainFlag =
617
+ hasProperty(item, "custom_domain") &&
618
+ typeof item.custom_domain === "boolean";
619
+
620
+ if (otherKeys === 2 && hasCustomDomainFlag && (hasZoneId || hasZoneName)) {
621
+ return true;
622
+ } else if (
623
+ otherKeys === 1 &&
624
+ (hasZoneId || hasZoneName || hasCustomDomainFlag)
625
+ ) {
626
+ return true;
627
+ }
628
+ }
629
+ return false;
578
630
  }
579
631
 
580
632
  /**
@@ -583,7 +635,7 @@ function isValidRouteValue(item: unknown): boolean {
583
635
  const isRoute: ValidatorFn = (diagnostics, field, value) => {
584
636
  if (value !== undefined && !isValidRouteValue(value)) {
585
637
  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(
638
+ `Expected "${field}" to be either a string, or an object with shape { pattern, custom_domain, zone_id | zone_name }, but got ${JSON.stringify(
587
639
  value
588
640
  )}.`
589
641
  );
@@ -613,7 +665,7 @@ const isRouteArray: ValidatorFn = (diagnostics, field, value) => {
613
665
  }
614
666
  if (invalidRoutes.length > 0) {
615
667
  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(
668
+ `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(
617
669
  invalidRoutes,
618
670
  null,
619
671
  2
@@ -696,27 +748,12 @@ function normalizeAndValidateEnvironment(
696
748
  diagnostics,
697
749
  rawEnv,
698
750
  "experimental_services",
699
- `The "experimental_services" field is no longer supported. Instead, use [[unsafe.bindings]] to enable experimental features. Add this to your wrangler.toml:\n` +
700
- "```\n" +
701
- TOML.stringify({
702
- unsafe: {
703
- bindings: (rawEnv?.experimental_services || []).map(
704
- (serviceDefinition) => {
705
- return {
706
- name: serviceDefinition.name,
707
- type: "service",
708
- service: serviceDefinition.service,
709
- environment: serviceDefinition.environment,
710
- };
711
- }
712
- ),
713
- },
714
- }) +
715
- "```",
751
+ `The "experimental_services" field is no longer supported. Simply rename the [experimental_services] field to [services].`,
716
752
  true
717
753
  );
718
754
 
719
755
  experimental(diagnostics, rawEnv, "unsafe");
756
+ experimental(diagnostics, rawEnv, "services");
720
757
 
721
758
  const route = validateRoute(diagnostics, topLevelEnv, rawEnv);
722
759
 
@@ -880,6 +917,16 @@ function normalizeAndValidateEnvironment(
880
917
  validateBindingArray(envName, validateR2Binding),
881
918
  []
882
919
  ),
920
+ services: notInheritable(
921
+ diagnostics,
922
+ topLevelEnv,
923
+ rawConfig,
924
+ rawEnv,
925
+ envName,
926
+ "services",
927
+ validateBindingArray(envName, validateServiceBinding),
928
+ []
929
+ ),
883
930
  unsafe: notInheritable(
884
931
  diagnostics,
885
932
  topLevelEnv,
@@ -893,8 +940,22 @@ function normalizeAndValidateEnvironment(
893
940
  }
894
941
  ),
895
942
  zone_id: rawEnv.zone_id,
896
- minify: rawEnv.minify,
897
- node_compat: rawEnv.node_compat,
943
+ minify: inheritable(
944
+ diagnostics,
945
+ topLevelEnv,
946
+ rawEnv,
947
+ "minify",
948
+ isBoolean,
949
+ undefined
950
+ ),
951
+ node_compat: inheritable(
952
+ diagnostics,
953
+ topLevelEnv,
954
+ rawEnv,
955
+ "node_compat",
956
+ isBoolean,
957
+ undefined
958
+ ),
898
959
  };
899
960
 
900
961
  return environment;
@@ -1014,7 +1075,7 @@ const validateRule: ValidatorFn = (diagnostics, field, value) => {
1014
1075
 
1015
1076
  if (!isOptionalProperty(rule, "fallthrough", "boolean")) {
1016
1077
  diagnostics.errors.push(
1017
- `binding should, optionally, have a boolean "fallthrough" field.`
1078
+ `the field "fallthrough", when present, should be a boolean.`
1018
1079
  );
1019
1080
  isValid = false;
1020
1081
  }
@@ -1135,7 +1196,7 @@ const validateDurableObjectBinding: ValidatorFn = (
1135
1196
  return false;
1136
1197
  }
1137
1198
 
1138
- // Durable Object bindings must have a name and class_name, and optionally a script_name.
1199
+ // Durable Object bindings must have a name and class_name, and optionally a script_name and an environment.
1139
1200
  let isValid = true;
1140
1201
  if (!isRequiredProperty(value, "name", "string")) {
1141
1202
  diagnostics.errors.push(`binding should have a string "name" field.`);
@@ -1147,7 +1208,21 @@ const validateDurableObjectBinding: ValidatorFn = (
1147
1208
  }
1148
1209
  if (!isOptionalProperty(value, "script_name", "string")) {
1149
1210
  diagnostics.errors.push(
1150
- `binding should, optionally, have a string "script_name" field.`
1211
+ `the field "script_name", when present, should be a string.`
1212
+ );
1213
+ isValid = false;
1214
+ }
1215
+ // environment requires a script_name
1216
+ if (!isOptionalProperty(value, "environment", "string")) {
1217
+ diagnostics.errors.push(
1218
+ `the field "environment", when present, should be a string.`
1219
+ );
1220
+ isValid = false;
1221
+ }
1222
+
1223
+ if ("environment" in value && !("script_name" in value)) {
1224
+ diagnostics.errors.push(
1225
+ `binding should have a "script_name" field if "environment" is present.`
1151
1226
  );
1152
1227
  isValid = false;
1153
1228
  }
@@ -1178,8 +1253,13 @@ const validateUnsafeBinding: ValidatorFn = (diagnostics, field, value) => {
1178
1253
  const safeBindings = [
1179
1254
  "plain_text",
1180
1255
  "json",
1256
+ "wasm_module",
1257
+ "data_blob",
1258
+ "text_blob",
1181
1259
  "kv_namespace",
1182
1260
  "durable_object_namespace",
1261
+ "r2_bucket",
1262
+ "service",
1183
1263
  ];
1184
1264
 
1185
1265
  if (safeBindings.includes(value.type)) {
@@ -1422,3 +1502,38 @@ const validateBindingsHaveUniqueNames = (
1422
1502
 
1423
1503
  return !hasDuplicates;
1424
1504
  };
1505
+ const validateServiceBinding: ValidatorFn = (diagnostics, field, value) => {
1506
+ if (typeof value !== "object" || value === null) {
1507
+ diagnostics.errors.push(
1508
+ `"services" bindings should be objects, but got ${JSON.stringify(value)}`
1509
+ );
1510
+ return false;
1511
+ }
1512
+ let isValid = true;
1513
+ // Service bindings must have a binding, service, and environment.
1514
+ if (!isRequiredProperty(value, "binding", "string")) {
1515
+ diagnostics.errors.push(
1516
+ `"${field}" bindings should have a string "binding" field but got ${JSON.stringify(
1517
+ value
1518
+ )}.`
1519
+ );
1520
+ isValid = false;
1521
+ }
1522
+ if (!isRequiredProperty(value, "service", "string")) {
1523
+ diagnostics.errors.push(
1524
+ `"${field}" bindings should have a string "service" field but got ${JSON.stringify(
1525
+ value
1526
+ )}.`
1527
+ );
1528
+ isValid = false;
1529
+ }
1530
+ if (!isOptionalProperty(value, "environment", "string")) {
1531
+ diagnostics.errors.push(
1532
+ `"${field}" bindings should have a string "environment" field but got ${JSON.stringify(
1533
+ value
1534
+ )}.`
1535
+ );
1536
+ isValid = false;
1537
+ }
1538
+ return isValid;
1539
+ };
@@ -66,7 +66,12 @@ async function sessionToken(
66
66
  ? `/zones/${ctx.zone.id}/workers/edge-preview`
67
67
  : `/accounts/${accountId}/workers/subdomain/edge-preview`;
68
68
 
69
- const { exchange_url } = await fetchResult<{ exchange_url: string }>(initUrl);
69
+ const { exchange_url } = await fetchResult<{ exchange_url: string }>(
70
+ initUrl,
71
+ undefined,
72
+ undefined,
73
+ abortSignal
74
+ );
70
75
  const { inspector_websocket, prewarm, token } = (await (
71
76
  await fetch(exchange_url, { signal: abortSignal })
72
77
  ).json()) as { inspector_websocket: string; token: string; prewarm: string };
@@ -119,13 +124,18 @@ async function createPreviewToken(
119
124
  const formData = createWorkerUploadForm(worker);
120
125
  formData.set("wrangler-session-config", JSON.stringify(mode));
121
126
 
122
- const { preview_token } = await fetchResult<{ preview_token: string }>(url, {
123
- method: "POST",
124
- body: formData,
125
- headers: {
126
- "cf-preview-upload-config-token": value,
127
+ const { preview_token } = await fetchResult<{ preview_token: string }>(
128
+ url,
129
+ {
130
+ method: "POST",
131
+ body: formData,
132
+ headers: {
133
+ "cf-preview-upload-config-token": value,
134
+ },
127
135
  },
128
- });
136
+ undefined,
137
+ abortSignal
138
+ );
129
139
 
130
140
  return {
131
141
  value: preview_token,
@@ -32,20 +32,24 @@ export interface WorkerMetadata {
32
32
  compatibility_flags?: string[];
33
33
  usage_model?: "bundled" | "unbound";
34
34
  migrations?: CfDurableObjectMigrations;
35
+ // If you add any new binding types here, also add it to safeBindings
36
+ // under validateUnsafeBinding in config/validation.ts
35
37
  bindings: (
36
- | { type: "kv_namespace"; name: string; namespace_id: string }
37
38
  | { type: "plain_text"; name: string; text: string }
38
39
  | { type: "json"; name: string; json: unknown }
39
40
  | { type: "wasm_module"; name: string; part: string }
40
41
  | { type: "text_blob"; name: string; part: string }
41
42
  | { type: "data_blob"; name: string; part: string }
43
+ | { type: "kv_namespace"; name: string; namespace_id: string }
42
44
  | {
43
45
  type: "durable_object_namespace";
44
46
  name: string;
45
47
  class_name: string;
46
48
  script_name?: string;
49
+ environment?: string;
47
50
  }
48
51
  | { type: "r2_bucket"; name: string; bucket_name: string }
52
+ | { type: "service"; name: string; service: string; environment?: string }
49
53
  )[];
50
54
  }
51
55
 
@@ -67,6 +71,14 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
67
71
 
68
72
  const metadataBindings: WorkerMetadata["bindings"] = [];
69
73
 
74
+ Object.entries(bindings.vars || {})?.forEach(([key, value]) => {
75
+ if (typeof value === "string") {
76
+ metadataBindings.push({ name: key, type: "plain_text", text: value });
77
+ } else {
78
+ metadataBindings.push({ name: key, type: "json", json: value });
79
+ }
80
+ });
81
+
70
82
  bindings.kv_namespaces?.forEach(({ id, binding }) => {
71
83
  metadataBindings.push({
72
84
  name: binding,
@@ -76,12 +88,13 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
76
88
  });
77
89
 
78
90
  bindings.durable_objects?.bindings.forEach(
79
- ({ name, class_name, script_name }) => {
91
+ ({ name, class_name, script_name, environment }) => {
80
92
  metadataBindings.push({
81
93
  name,
82
94
  type: "durable_object_namespace",
83
95
  class_name: class_name,
84
96
  ...(script_name && { script_name }),
97
+ ...(environment && { environment }),
85
98
  });
86
99
  }
87
100
  );
@@ -94,12 +107,13 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
94
107
  });
95
108
  });
96
109
 
97
- Object.entries(bindings.vars || {})?.forEach(([key, value]) => {
98
- if (typeof value === "string") {
99
- metadataBindings.push({ name: key, type: "plain_text", text: value });
100
- } else {
101
- metadataBindings.push({ name: key, type: "json", json: value });
102
- }
110
+ bindings.services?.forEach(({ binding, service, environment }) => {
111
+ metadataBindings.push({
112
+ name: binding,
113
+ type: "service",
114
+ service,
115
+ ...(environment && { environment }),
116
+ });
103
117
  });
104
118
 
105
119
  for (const [name, filePath] of Object.entries(bindings.wasm_modules || {})) {