wrangler 2.0.7 → 2.0.11

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.
@@ -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
 
@@ -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;
@@ -85,9 +86,9 @@ function throwFetchError(
85
86
  response: FetchResult<unknown>
86
87
  ): never {
87
88
  const error = new ParseError({
88
- text: "Received a bad response from the API",
89
+ text: `A request to the Cloudflare API (${resource}) failed.`,
89
90
  notes: response.errors.map((err) => ({
90
- text: err.code ? `${err.message} [code: ${err.code}]` : err.message,
91
+ text: renderError(err),
91
92
  })),
92
93
  });
93
94
  // add the first error code directly to this error
@@ -104,3 +105,17 @@ function hasCursor(result_info: unknown): result_info is { cursor: string } {
104
105
  const cursor = (result_info as { cursor: string } | undefined)?.cursor;
105
106
  return cursor !== undefined && cursor !== null && cursor !== "";
106
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
 
@@ -29,10 +31,15 @@ export async function fetchInternal<ResponseType>(
29
31
  queryParams?: URLSearchParams,
30
32
  abortSignal?: AbortSignal
31
33
  ): Promise<ResponseType> {
34
+ assert(
35
+ resource.startsWith("/"),
36
+ `CF API fetch - resource path must start with a "/" but got "${resource}"`
37
+ );
32
38
  await requireLoggedIn();
33
39
  const apiToken = requireApiToken();
34
40
  const headers = cloneHeaders(init.headers);
35
41
  addAuthorizationHeaderIfUnspecified(headers, apiToken);
42
+ addUserAgent(headers);
36
43
 
37
44
  const queryString = queryParams ? `?${queryParams.toString()}` : "";
38
45
  const method = init.method ?? "GET";
@@ -88,14 +95,6 @@ async function requireLoggedIn(): Promise<void> {
88
95
  }
89
96
  }
90
97
 
91
- function requireApiToken(): string {
92
- const authToken = getAPIToken();
93
- if (!authToken) {
94
- throw new Error("No API token found.");
95
- }
96
- return authToken;
97
- }
98
-
99
98
  function addAuthorizationHeaderIfUnspecified(
100
99
  headers: Record<string, string>,
101
100
  apiToken: string
@@ -105,6 +104,10 @@ function addAuthorizationHeaderIfUnspecified(
105
104
  }
106
105
  }
107
106
 
107
+ function addUserAgent(headers: Record<string, string>): void {
108
+ headers["User-Agent"] = `wrangler/${wranglerVersion}`;
109
+ }
110
+
108
111
  /**
109
112
  * The implementation for fetching a kv value from the cloudflare API.
110
113
  * We special-case this one call, because it's the only API call that
@@ -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>;
@@ -277,12 +277,21 @@ export const isObjectWith =
277
277
  !properties.every((prop) => prop in value))
278
278
  ) {
279
279
  diagnostics.errors.push(
280
- `Expected "${field}" to be of type object, containing properties ${properties}, but got ${JSON.stringify(
280
+ `Expected "${field}" to be of type object, containing only properties ${properties}, but got ${JSON.stringify(
281
281
  value
282
282
  )}.`
283
283
  );
284
284
  return false;
285
285
  }
286
+ // it's an object with the field as desired,
287
+ // but let's also check for unexpected fields
288
+ if (value !== undefined) {
289
+ const restFields = Object.keys(value).filter(
290
+ (key) => !properties.includes(key)
291
+ );
292
+ validateAdditionalProperties(diagnostics, field, restFields, []);
293
+ }
294
+
286
295
  return true;
287
296
  };
288
297
 
@@ -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"] ??
@@ -400,29 +384,38 @@ function normalizeAndValidateMigrations(
400
384
  return [];
401
385
  } else {
402
386
  for (let i = 0; i < rawMigrations.length; i++) {
403
- 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
+
404
397
  validateRequiredProperty(
405
398
  diagnostics,
406
399
  `migrations[${i}]`,
407
400
  `tag`,
408
- migration.tag,
401
+ tag,
409
402
  "string"
410
403
  );
411
404
  validateOptionalTypedArray(
412
405
  diagnostics,
413
406
  `migrations[${i}].new_classes`,
414
- migration.new_classes,
407
+ new_classes,
415
408
  "string"
416
409
  );
417
- if (migration.renamed_classes !== undefined) {
418
- if (!Array.isArray(migration.renamed_classes)) {
410
+ if (renamed_classes !== undefined) {
411
+ if (!Array.isArray(renamed_classes)) {
419
412
  diagnostics.errors.push(
420
413
  `Expected "migrations[${i}].renamed_classes" to be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
421
- migration.renamed_classes
414
+ renamed_classes
422
415
  )}.`
423
416
  );
424
417
  } else if (
425
- migration.renamed_classes.some(
418
+ renamed_classes.some(
426
419
  (c) =>
427
420
  typeof c !== "object" ||
428
421
  !isRequiredProperty(c, "from", "string") ||
@@ -431,7 +424,7 @@ function normalizeAndValidateMigrations(
431
424
  ) {
432
425
  diagnostics.errors.push(
433
426
  `Expected "migrations[${i}].renamed_classes" to be an array of "{from: string, to: string}" objects but got ${JSON.stringify(
434
- migration.renamed_classes
427
+ renamed_classes
435
428
  )}.`
436
429
  );
437
430
  }
@@ -439,7 +432,7 @@ function normalizeAndValidateMigrations(
439
432
  validateOptionalTypedArray(
440
433
  diagnostics,
441
434
  `migrations[${i}].deleted_classes`,
442
- migration.deleted_classes,
435
+ deleted_classes,
443
436
  "string"
444
437
  );
445
438
  }
@@ -453,13 +446,32 @@ function normalizeAndValidateMigrations(
453
446
  (binding) => !binding.script_name
454
447
  );
455
448
  if (exportedDurableObjects.length > 0 && rawMigrations.length === 0) {
456
- diagnostics.warnings.push(
457
- `In wrangler.toml, you have configured [durable_objects] exported by this Worker (${exportedDurableObjects
458
- .map((durable) => durable.class_name || "(unnamed)")
459
- .join(
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(
460
461
  ", "
461
- )}), but no [migrations] for them. This may not work as expected until you add a [migrations] section to your wrangler.toml. Refer to https://developers.cloudflare.com/workers/learning/using-durable-objects/#durable-object-migrations-in-wranglertoml for more details.`
462
- );
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
+ }
463
475
  }
464
476
  }
465
477
 
@@ -928,8 +940,22 @@ function normalizeAndValidateEnvironment(
928
940
  }
929
941
  ),
930
942
  zone_id: rawEnv.zone_id,
931
- minify: rawEnv.minify,
932
- 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
+ ),
933
959
  };
934
960
 
935
961
  return environment;
@@ -63,7 +63,7 @@ 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
69
  const { exchange_url } = await fetchResult<{ exchange_url: string }>(
@@ -111,7 +111,7 @@ async function createPreviewToken(
111
111
  );
112
112
 
113
113
  const { accountId } = account;
114
- const scriptId = ctx.zone ? randomId() : worker.name || host.split(".")[0];
114
+ const scriptId = worker.name || (ctx.zone ? randomId() : host.split(".")[0]);
115
115
  const url =
116
116
  ctx.env && !ctx.legacyEnv
117
117
  ? `/accounts/${accountId}/workers/services/${scriptId}/environments/${ctx.env}/edge-preview`
@@ -139,19 +139,19 @@ async function createPreviewToken(
139
139
 
140
140
  return {
141
141
  value: preview_token,
142
- host: ctx.zone
143
- ? ctx.zone.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,
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),
155
155
 
156
156
  inspectorUrl,
157
157
  prewarmUrl,
package/src/dev/dev.tsx CHANGED
@@ -9,12 +9,10 @@ import { withErrorBoundary, useErrorHandler } from "react-error-boundary";
9
9
  import onExit from "signal-exit";
10
10
  import tmp from "tmp-promise";
11
11
  import { fetch } from "undici";
12
- import { printBindings } from "../config";
13
12
  import { runCustomBuild } from "../entry";
14
13
  import { openInspector } from "../inspect";
15
14
  import { logger } from "../logger";
16
15
  import openInBrowser from "../open-in-browser";
17
- import { getAPIToken } from "../user";
18
16
  import { Local } from "./local";
19
17
  import { Remote } from "./remote";
20
18
  import { useEsbuild } from "./use-esbuild";
@@ -55,16 +53,11 @@ export type DevProps = {
55
53
  };
56
54
  env: string | undefined;
57
55
  legacyEnv: boolean;
58
- zone:
59
- | {
60
- id: string;
61
- host: string;
62
- }
63
- | undefined;
56
+ zone: string | undefined;
57
+ host: string | undefined;
64
58
  };
65
59
 
66
60
  export function DevImplementation(props: DevProps): JSX.Element {
67
- const apiToken = props.initialMode === "remote" ? getAPIToken() : undefined;
68
61
  const directory = useTmpDir();
69
62
 
70
63
  useCustomBuild(props.entry, props.build);
@@ -106,24 +99,20 @@ export function DevImplementation(props: DevProps): JSX.Element {
106
99
  nodeCompat: props.nodeCompat,
107
100
  });
108
101
 
109
- printBindings(props.bindings);
110
-
111
102
  // only load the UI if we're running in a supported environment
112
103
  const { isRawModeSupported } = useStdin();
113
104
  return isRawModeSupported ? (
114
- <InteractiveDevSession {...props} bundle={bundle} apiToken={apiToken} />
105
+ <InteractiveDevSession {...props} bundle={bundle} />
115
106
  ) : (
116
107
  <DevSession
117
108
  {...props}
118
109
  bundle={bundle}
119
- apiToken={apiToken}
120
110
  local={props.initialMode === "local"}
121
111
  />
122
112
  );
123
113
  }
124
114
 
125
115
  type InteractiveDevSessionProps = DevProps & {
126
- apiToken: string | undefined;
127
116
  bundle: EsbuildBundle | undefined;
128
117
  };
129
118
 
@@ -186,7 +175,6 @@ function DevSession(props: DevSessionProps) {
186
175
  bundle={props.bundle}
187
176
  format={props.entry.format}
188
177
  accountId={props.accountId}
189
- apiToken={props.apiToken}
190
178
  bindings={props.bindings}
191
179
  assetPaths={props.assetPaths}
192
180
  public={props.public}
@@ -200,6 +188,7 @@ function DevSession(props: DevSessionProps) {
200
188
  env={props.env}
201
189
  legacyEnv={props.legacyEnv}
202
190
  zone={props.zone}
191
+ host={props.host}
203
192
  />
204
193
  );
205
194
  }
@@ -1,4 +1,3 @@
1
- import assert from "node:assert";
2
1
  import { readFile } from "node:fs/promises";
3
2
  import path from "node:path";
4
3
  import { useState, useEffect, useRef } from "react";
@@ -7,6 +6,7 @@ import useInspector from "../inspect";
7
6
  import { logger } from "../logger";
8
7
  import { usePreviewServer } from "../proxy";
9
8
  import { syncAssets } from "../sites";
9
+ import { requireApiToken, requireAuth } from "../user";
10
10
  import type { CfPreviewToken } from "../create-worker-preview";
11
11
  import type { AssetPaths } from "../sites";
12
12
  import type { CfModule, CfWorkerInit, CfScriptFormat } from "../worker";
@@ -23,24 +23,21 @@ export function Remote(props: {
23
23
  localProtocol: "https" | "http";
24
24
  inspectorPort: number;
25
25
  accountId: string | undefined;
26
- apiToken: string | undefined;
27
26
  bindings: CfWorkerInit["bindings"];
28
27
  compatibilityDate: string;
29
28
  compatibilityFlags: string[] | undefined;
30
29
  usageModel: "bundled" | "unbound" | undefined;
31
30
  env: string | undefined;
32
31
  legacyEnv: boolean | undefined;
33
- zone: { id: string; host: string } | undefined;
32
+ zone: string | undefined;
33
+ host: string | undefined;
34
34
  }) {
35
- assert(props.accountId, "accountId is required");
36
- assert(props.apiToken, "apiToken is required");
37
35
  const previewToken = useWorker({
38
36
  name: props.name,
39
37
  bundle: props.bundle,
40
38
  format: props.format,
41
39
  modules: props.bundle ? props.bundle.modules : [],
42
40
  accountId: props.accountId,
43
- apiToken: props.apiToken,
44
41
  bindings: props.bindings,
45
42
  assetPaths: props.assetPaths,
46
43
  port: props.port,
@@ -50,6 +47,7 @@ export function Remote(props: {
50
47
  env: props.env,
51
48
  legacyEnv: props.legacyEnv,
52
49
  zone: props.zone,
50
+ host: props.host,
53
51
  });
54
52
 
55
53
  usePreviewServer({
@@ -73,8 +71,7 @@ export function useWorker(props: {
73
71
  bundle: EsbuildBundle | undefined;
74
72
  format: CfScriptFormat | undefined;
75
73
  modules: CfModule[];
76
- accountId: string;
77
- apiToken: string;
74
+ accountId: string | undefined;
78
75
  bindings: CfWorkerInit["bindings"];
79
76
  assetPaths: AssetPaths | undefined;
80
77
  port: number;
@@ -83,7 +80,8 @@ export function useWorker(props: {
83
80
  usageModel: "bundled" | "unbound" | undefined;
84
81
  env: string | undefined;
85
82
  legacyEnv: boolean | undefined;
86
- zone: { id: string; host: string } | undefined;
83
+ zone: string | undefined;
84
+ host: string | undefined;
87
85
  }): CfPreviewToken | undefined {
88
86
  const {
89
87
  name,
@@ -91,7 +89,6 @@ export function useWorker(props: {
91
89
  format,
92
90
  modules,
93
91
  accountId,
94
- apiToken,
95
92
  bindings,
96
93
  assetPaths,
97
94
  compatibilityDate,
@@ -105,6 +102,9 @@ export function useWorker(props: {
105
102
  // something's "happened" in our system; We make a ref and
106
103
  // mark it once we log our initial message. Refs are vars!
107
104
  const startedRef = useRef(false);
105
+ // This ref holds the actual accountId being used, the `accountId` prop could be undefined
106
+ // as it is only what is retrieved from the wrangler.toml config.
107
+ const accountIdRef = useRef(accountId);
108
108
 
109
109
  useEffect(() => {
110
110
  const abortController = new AbortController();
@@ -119,8 +119,13 @@ export function useWorker(props: {
119
119
  logger.log("⎔ Detected changes, restarted server.");
120
120
  }
121
121
 
122
+ // Ensure we have an account id, even if it means logging in here.
123
+ accountIdRef.current = await requireAuth({
124
+ account_id: accountIdRef.current,
125
+ });
126
+
122
127
  const assets = await syncAssets(
123
- accountId,
128
+ accountIdRef.current,
124
129
  // When we're using the newer service environments, we wouldn't
125
130
  // have added the env name on to the script name. However, we must
126
131
  // include it in the kv namespace name regardless (since there's no
@@ -173,10 +178,15 @@ export function useWorker(props: {
173
178
  await createWorkerPreview(
174
179
  init,
175
180
  {
176
- accountId,
177
- apiToken,
181
+ accountId: accountIdRef.current,
182
+ apiToken: requireApiToken(),
183
+ },
184
+ {
185
+ env: props.env,
186
+ legacyEnv: props.legacyEnv,
187
+ zone: props.zone,
188
+ host: props.host,
178
189
  },
179
- { env: props.env, legacyEnv: props.legacyEnv, zone: props.zone },
180
190
  abortController.signal
181
191
  )
182
192
  );
@@ -193,7 +203,7 @@ export function useWorker(props: {
193
203
  "Error: You need to register a workers.dev subdomain before running the dev command in remote mode";
194
204
  const solutionMessage =
195
205
  "You can either enable local mode by pressing l, or register a workers.dev subdomain here:";
196
- const onboardingLink = `https://dash.cloudflare.com/${accountId}/workers/onboarding`;
206
+ const onboardingLink = `https://dash.cloudflare.com/${accountIdRef.current}/workers/onboarding`;
197
207
  logger.error(
198
208
  `${errorMessage}\n${solutionMessage}\n${onboardingLink}`
199
209
  );
@@ -211,7 +221,6 @@ export function useWorker(props: {
211
221
  bundle,
212
222
  format,
213
223
  accountId,
214
- apiToken,
215
224
  port,
216
225
  assetPaths,
217
226
  compatibilityDate,
@@ -222,6 +231,7 @@ export function useWorker(props: {
222
231
  props.env,
223
232
  props.legacyEnv,
224
233
  props.zone,
234
+ props.host,
225
235
  ]);
226
236
  return token;
227
237
  }
package/src/dialogs.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  import chalk from "chalk";
2
2
  import { Box, Text, useInput, render } from "ink";
3
+ import SelectInput from "ink-select-input";
3
4
  import TextInput from "ink-text-input";
4
5
  import * as React from "react";
5
6
  import { useState } from "react";
@@ -85,3 +86,50 @@ export async function prompt(
85
86
  );
86
87
  });
87
88
  }
89
+
90
+ type SelectOption = {
91
+ value: string;
92
+ label: string;
93
+ };
94
+
95
+ type SelectProps = {
96
+ text: string;
97
+ options: SelectOption[];
98
+ initialIndex: number;
99
+ onSelect: (value: string) => void;
100
+ };
101
+
102
+ function Select(props: SelectProps) {
103
+ return (
104
+ <Box flexDirection="column">
105
+ <Text>{props.text}</Text>
106
+ <SelectInput
107
+ initialIndex={props.initialIndex}
108
+ items={props.options}
109
+ onSelect={async (selected) => {
110
+ props.onSelect(selected.value);
111
+ }}
112
+ />
113
+ </Box>
114
+ );
115
+ }
116
+
117
+ export function select(
118
+ text: string,
119
+ options: SelectOption[],
120
+ initialIndex: number
121
+ ): Promise<string> {
122
+ return new Promise((resolve) => {
123
+ const { unmount } = render(
124
+ <Select
125
+ text={text}
126
+ options={options}
127
+ initialIndex={initialIndex}
128
+ onSelect={(option: string) => {
129
+ unmount();
130
+ resolve(option);
131
+ }}
132
+ />
133
+ );
134
+ });
135
+ }