wrangler 2.0.6 → 2.0.9

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 (46) hide show
  1. package/README.md +1 -1
  2. package/bin/wrangler.js +16 -4
  3. package/package.json +6 -4
  4. package/pages/functions/buildPlugin.ts +13 -0
  5. package/pages/functions/buildWorker.ts +13 -0
  6. package/src/__tests__/configuration.test.ts +132 -60
  7. package/src/__tests__/dev.test.tsx +168 -67
  8. package/src/__tests__/helpers/mock-dialogs.ts +41 -1
  9. package/src/__tests__/index.test.ts +25 -10
  10. package/src/__tests__/init.test.ts +252 -131
  11. package/src/__tests__/kv.test.ts +16 -16
  12. package/src/__tests__/package-manager.test.ts +154 -7
  13. package/src/__tests__/pages.test.ts +442 -38
  14. package/src/__tests__/parse.test.ts +5 -1
  15. package/src/__tests__/publish.test.ts +377 -84
  16. package/src/__tests__/secret.test.ts +4 -4
  17. package/src/__tests__/whoami.test.tsx +34 -0
  18. package/src/abort.d.ts +3 -0
  19. package/src/cfetch/index.ts +21 -4
  20. package/src/cfetch/internal.ts +20 -18
  21. package/src/config/config.ts +1 -1
  22. package/src/config/index.ts +162 -0
  23. package/src/config/validation.ts +77 -29
  24. package/src/create-worker-preview.ts +32 -22
  25. package/src/dev/dev.tsx +6 -16
  26. package/src/dev/remote.tsx +40 -16
  27. package/src/dialogs.tsx +48 -0
  28. package/src/durable.ts +102 -0
  29. package/src/index.tsx +291 -207
  30. package/src/inspect.ts +39 -0
  31. package/src/kv.ts +74 -25
  32. package/src/open-in-browser.ts +5 -12
  33. package/src/package-manager.ts +50 -3
  34. package/src/pages.tsx +218 -61
  35. package/src/parse.ts +21 -4
  36. package/src/proxy.ts +38 -22
  37. package/src/publish.ts +166 -108
  38. package/src/sites.tsx +8 -8
  39. package/src/user.tsx +12 -1
  40. package/src/whoami.tsx +3 -2
  41. package/src/worker.ts +2 -1
  42. package/src/zones.ts +73 -0
  43. package/templates/new-worker-scheduled.js +17 -0
  44. package/templates/new-worker-scheduled.ts +32 -0
  45. package/templates/new-worker.ts +16 -1
  46. package/wrangler-dist/cli.js +33066 -20052
@@ -148,7 +148,7 @@ describe("wrangler secret", () => {
148
148
  }
149
149
  expect(std.out).toMatchInlineSnapshot(`
150
150
  "
151
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
151
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
152
152
  `);
153
153
  expect(std.err).toMatchInlineSnapshot(`
154
154
  "X [ERROR] Missing script name
@@ -204,7 +204,7 @@ describe("wrangler secret", () => {
204
204
 
205
205
  expect(std.out).toMatchInlineSnapshot(`
206
206
  "
207
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
207
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
208
208
  `);
209
209
  expect(std.warn).toMatchInlineSnapshot(`""`);
210
210
  });
@@ -372,7 +372,7 @@ describe("wrangler secret", () => {
372
372
  }
373
373
  expect(std.out).toMatchInlineSnapshot(`
374
374
  "
375
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
375
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
376
376
  `);
377
377
  expect(std.err).toMatchInlineSnapshot(`
378
378
  "X [ERROR] Missing script name
@@ -468,7 +468,7 @@ describe("wrangler secret", () => {
468
468
  }
469
469
  expect(std.out).toMatchInlineSnapshot(`
470
470
  "
471
- If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new."
471
+ If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new/choose"
472
472
  `);
473
473
  expect(std.err).toMatchInlineSnapshot(`
474
474
  "X [ERROR] Missing script name
@@ -9,6 +9,8 @@ import { runInTempDir } from "./helpers/run-in-tmp";
9
9
  import type { UserInfo } from "../whoami";
10
10
 
11
11
  describe("getUserInfo()", () => {
12
+ const ENV_COPY = process.env;
13
+
12
14
  runInTempDir({ homedir: "./home" });
13
15
  const std = mockConsoleMethods();
14
16
  const { setIsTTY } = useMockIsTTY();
@@ -17,6 +19,10 @@ describe("getUserInfo()", () => {
17
19
  setIsTTY(true);
18
20
  });
19
21
 
22
+ afterEach(() => {
23
+ process.env = ENV_COPY;
24
+ });
25
+
20
26
  it("should return undefined if there is no config file", async () => {
21
27
  const userInfo = await getUserInfo();
22
28
  expect(userInfo).toBeUndefined();
@@ -28,6 +34,34 @@ describe("getUserInfo()", () => {
28
34
  expect(userInfo).toBeUndefined();
29
35
  });
30
36
 
37
+ it("should say it's using an API token when one is set", async () => {
38
+ process.env = {
39
+ CLOUDFLARE_API_TOKEN: "123456789",
40
+ };
41
+ setMockResponse("/user", () => {
42
+ return { email: "user@example.com" };
43
+ });
44
+ setMockResponse("/accounts", () => {
45
+ return [
46
+ { name: "Account One", id: "account-1" },
47
+ { name: "Account Two", id: "account-2" },
48
+ { name: "Account Three", id: "account-3" },
49
+ ];
50
+ });
51
+
52
+ const userInfo = await getUserInfo();
53
+ expect(userInfo).toEqual({
54
+ authType: "API",
55
+ apiToken: "123456789",
56
+ email: "user@example.com",
57
+ accounts: [
58
+ { name: "Account One", id: "account-1" },
59
+ { name: "Account Two", id: "account-2" },
60
+ { name: "Account Three", id: "account-3" },
61
+ ],
62
+ });
63
+ });
64
+
31
65
  it("should return the user's email and accounts if authenticated via config token", async () => {
32
66
  writeAuthConfigFile({ oauth_token: "some-oauth-token" });
33
67
 
package/src/abort.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ declare interface AbortSignal {
2
+ addEventListener(event: "abort", handler: () => void);
3
+ }
@@ -10,6 +10,7 @@ export { getCloudflareAPIBaseURL as getCloudflareApiBaseUrl } from "./internal";
10
10
  export interface FetchError {
11
11
  code: number;
12
12
  message: string;
13
+ error_chain?: FetchError[];
13
14
  }
14
15
  export interface FetchResult<ResponseType = unknown> {
15
16
  success: boolean;
@@ -27,12 +28,14 @@ export { fetchKVGetValue } from "./internal";
27
28
  export async function fetchResult<ResponseType>(
28
29
  resource: string,
29
30
  init: RequestInit = {},
30
- queryParams?: URLSearchParams
31
+ queryParams?: URLSearchParams,
32
+ abortSignal?: AbortSignal
31
33
  ): Promise<ResponseType> {
32
34
  const json = await fetchInternal<FetchResult<ResponseType>>(
33
35
  resource,
34
36
  init,
35
- queryParams
37
+ queryParams,
38
+ abortSignal
36
39
  );
37
40
  if (json.success) {
38
41
  return json.result;
@@ -83,9 +86,9 @@ function throwFetchError(
83
86
  response: FetchResult<unknown>
84
87
  ): never {
85
88
  const error = new ParseError({
86
- text: "Received a bad response from the API",
89
+ text: `A request to the Cloudflare API (${resource}) failed.`,
87
90
  notes: response.errors.map((err) => ({
88
- text: err.code ? `${err.message} [code: ${err.code}]` : err.message,
91
+ text: renderError(err),
89
92
  })),
90
93
  });
91
94
  // add the first error code directly to this error
@@ -102,3 +105,17 @@ function hasCursor(result_info: unknown): result_info is { cursor: string } {
102
105
  const cursor = (result_info as { cursor: string } | undefined)?.cursor;
103
106
  return cursor !== undefined && cursor !== null && cursor !== "";
104
107
  }
108
+
109
+ function renderError(err: FetchError, level = 0): string {
110
+ const chainedMessages =
111
+ err.error_chain
112
+ ?.map(
113
+ (chainedError) =>
114
+ `\n${" ".repeat(level)}- ${renderError(chainedError, level + 1)}`
115
+ )
116
+ .join("\n") ?? "";
117
+ return (
118
+ (err.code ? `${err.message} [code: ${err.code}]` : err.message) +
119
+ chainedMessages
120
+ );
121
+ }
@@ -1,7 +1,9 @@
1
+ import assert from "node:assert";
1
2
  import { fetch, Headers } from "undici";
3
+ import { version as wranglerVersion } from "../../package.json";
2
4
  import { getEnvironmentVariableFactory } from "../environment-variables";
3
5
  import { ParseError, parseJSON } from "../parse";
4
- import { getAPIToken, loginOrRefreshIfRequired } from "../user";
6
+ import { loginOrRefreshIfRequired, requireApiToken } from "../user";
5
7
  import type { URLSearchParams } from "node:url";
6
8
  import type { RequestInit, HeadersInit } from "undici";
7
9
 
@@ -26,12 +28,18 @@ export const getCloudflareAPIBaseURL = getEnvironmentVariableFactory({
26
28
  export async function fetchInternal<ResponseType>(
27
29
  resource: string,
28
30
  init: RequestInit = {},
29
- queryParams?: URLSearchParams
31
+ queryParams?: URLSearchParams,
32
+ abortSignal?: AbortSignal
30
33
  ): Promise<ResponseType> {
34
+ assert(
35
+ resource.startsWith("/"),
36
+ `CF API fetch - resource path must start with a "/" but got "${resource}"`
37
+ );
31
38
  await requireLoggedIn();
32
39
  const apiToken = requireApiToken();
33
40
  const headers = cloneHeaders(init.headers);
34
- addAuthorizationHeader(headers, apiToken);
41
+ addAuthorizationHeaderIfUnspecified(headers, apiToken);
42
+ addUserAgent(headers);
35
43
 
36
44
  const queryString = queryParams ? `?${queryParams.toString()}` : "";
37
45
  const method = init.method ?? "GET";
@@ -41,11 +49,12 @@ export async function fetchInternal<ResponseType>(
41
49
  method,
42
50
  ...init,
43
51
  headers,
52
+ signal: abortSignal,
44
53
  }
45
54
  );
46
55
  const jsonText = await response.text();
47
56
  try {
48
- return parseJSON(jsonText) as ResponseType;
57
+ return parseJSON<ResponseType>(jsonText);
49
58
  } catch (err) {
50
59
  throw new ParseError({
51
60
  text: "Received a malformed response from the API",
@@ -86,24 +95,17 @@ async function requireLoggedIn(): Promise<void> {
86
95
  }
87
96
  }
88
97
 
89
- function requireApiToken(): string {
90
- const authToken = getAPIToken();
91
- if (!authToken) {
92
- throw new Error("No API token found.");
93
- }
94
- return authToken;
95
- }
96
-
97
- function addAuthorizationHeader(
98
+ function addAuthorizationHeaderIfUnspecified(
98
99
  headers: Record<string, string>,
99
100
  apiToken: string
100
101
  ): void {
101
- if ("Authorization" in headers) {
102
- throw new Error(
103
- "The request already specifies an authorisation header - cannot add a new one."
104
- );
102
+ if (!("Authorization" in headers)) {
103
+ headers["Authorization"] = `Bearer ${apiToken}`;
105
104
  }
106
- headers["Authorization"] = `Bearer ${apiToken}`;
105
+ }
106
+
107
+ function addUserAgent(headers: Record<string, string>): void {
108
+ headers["User-Agent"] = `wrangler/${wranglerVersion}`;
107
109
  }
108
110
 
109
111
  /**
@@ -184,7 +184,7 @@ export interface DevConfig {
184
184
  /**
185
185
  * Host to forward requests to, defaults to the host of the first route of project
186
186
  */
187
- host: Config["route"];
187
+ host: string | undefined;
188
188
  }
189
189
 
190
190
  export type RawDevConfig = Partial<DevConfig>;
@@ -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,
@@ -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
  }
@@ -906,8 +940,22 @@ function normalizeAndValidateEnvironment(
906
940
  }
907
941
  ),
908
942
  zone_id: rawEnv.zone_id,
909
- minify: rawEnv.minify,
910
- 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
+ ),
911
959
  };
912
960
 
913
961
  return environment;
@@ -63,10 +63,15 @@ async function sessionToken(
63
63
  ): Promise<CfPreviewToken> {
64
64
  const { accountId } = account;
65
65
  const initUrl = ctx.zone
66
- ? `/zones/${ctx.zone.id}/workers/edge-preview`
66
+ ? `/zones/${ctx.zone}/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 };
@@ -106,7 +111,7 @@ async function createPreviewToken(
106
111
  );
107
112
 
108
113
  const { accountId } = account;
109
- const scriptId = ctx.zone ? randomId() : worker.name || host.split(".")[0];
114
+ const scriptId = worker.name || (ctx.zone ? randomId() : host.split(".")[0]);
110
115
  const url =
111
116
  ctx.env && !ctx.legacyEnv
112
117
  ? `/accounts/${accountId}/workers/services/${scriptId}/environments/${ctx.env}/edge-preview`
@@ -119,29 +124,34 @@ 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,
132
- host: ctx.zone
133
- ? ctx.zone.host
134
- : worker.name
135
- ? `${
136
- worker.name
137
- // TODO: this should also probably have the env prefix
138
- // but it doesn't appear to work yet, instead giving us the
139
- // "There is nothing here yet" screen
140
- // ctx.env && !ctx.legacyEnv
141
- // ? `${ctx.env}.${worker.name}`
142
- // : worker.name
143
- }.${host.split(".").slice(1).join(".")}`
144
- : host,
142
+ host:
143
+ ctx.host ??
144
+ (worker.name
145
+ ? `${
146
+ worker.name
147
+ // TODO: this should also probably have the env prefix
148
+ // but it doesn't appear to work yet, instead giving us the
149
+ // "There is nothing here yet" screen
150
+ // ctx.env && !ctx.legacyEnv
151
+ // ? `${ctx.env}.${worker.name}`
152
+ // : worker.name
153
+ }.${host.split(".").slice(1).join(".")}`
154
+ : host),
145
155
 
146
156
  inspectorUrl,
147
157
  prewarmUrl,