wrangler 0.0.0-e5d3690 → 0.0.0-e88a54e

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 (98) hide show
  1. package/bin/wrangler.js +2 -2
  2. package/miniflare-dist/index.mjs +79 -0
  3. package/miniflare-dist/index.mjs.map +7 -0
  4. package/package.json +32 -16
  5. package/pages/functions/buildWorker.ts +1 -1
  6. package/pages/functions/filepath-routing.test.ts +212 -28
  7. package/pages/functions/filepath-routing.ts +83 -130
  8. package/pages/functions/routes.ts +11 -18
  9. package/pages/functions/template-worker.ts +3 -9
  10. package/src/__tests__/configuration.test.ts +2796 -0
  11. package/src/__tests__/dev.test.tsx +488 -59
  12. package/src/__tests__/dev2.test.tsx +154 -0
  13. package/src/__tests__/guess-worker-format.test.ts +58 -0
  14. package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
  15. package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
  16. package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
  17. package/src/__tests__/helpers/mock-account-id.ts +48 -0
  18. package/src/__tests__/helpers/mock-bin.ts +36 -0
  19. package/src/__tests__/{mock-cfetch.ts → helpers/mock-cfetch.ts} +48 -14
  20. package/src/__tests__/helpers/mock-console.ts +70 -0
  21. package/src/__tests__/{mock-dialogs.ts → helpers/mock-dialogs.ts} +1 -1
  22. package/src/__tests__/helpers/mock-kv.ts +33 -0
  23. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  24. package/src/__tests__/helpers/mock-web-socket.ts +29 -0
  25. package/src/__tests__/helpers/run-in-tmp.ts +32 -0
  26. package/src/__tests__/helpers/run-wrangler.ts +16 -0
  27. package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
  28. package/src/__tests__/https-options.test.ts +127 -0
  29. package/src/__tests__/index.test.ts +777 -75
  30. package/src/__tests__/jest.setup.ts +53 -6
  31. package/src/__tests__/kv.test.ts +674 -491
  32. package/src/__tests__/logout.test.ts +52 -0
  33. package/src/__tests__/package-manager.test.ts +206 -0
  34. package/src/__tests__/pages.test.ts +44 -0
  35. package/src/__tests__/parse.test.ts +322 -0
  36. package/src/__tests__/publish.test.ts +2327 -295
  37. package/src/__tests__/r2.test.ts +198 -0
  38. package/src/__tests__/route.test.ts +45 -0
  39. package/src/__tests__/secret.test.ts +425 -0
  40. package/src/__tests__/sentry.test.ts +146 -0
  41. package/src/__tests__/tail.test.ts +564 -0
  42. package/src/__tests__/whoami.test.tsx +26 -56
  43. package/src/bundle.ts +142 -0
  44. package/src/cfetch/index.ts +13 -19
  45. package/src/cfetch/internal.ts +91 -23
  46. package/src/cli.ts +10 -0
  47. package/src/config/README.md +107 -0
  48. package/src/config/config.ts +218 -0
  49. package/src/config/diagnostics.ts +80 -0
  50. package/src/config/environment.ts +366 -0
  51. package/src/config/index.ts +62 -0
  52. package/src/config/validation-helpers.ts +486 -0
  53. package/src/config/validation.ts +1090 -0
  54. package/src/{api/preview.ts → create-worker-preview.ts} +61 -23
  55. package/src/create-worker-upload-form.ts +217 -0
  56. package/src/dev/dev.tsx +364 -0
  57. package/src/dev/local.tsx +232 -0
  58. package/src/dev/remote.tsx +203 -0
  59. package/src/dev/use-esbuild.ts +100 -0
  60. package/src/dialogs.tsx +2 -2
  61. package/src/entry.ts +208 -0
  62. package/src/environment-variables.ts +33 -0
  63. package/src/https-options.ts +122 -0
  64. package/src/index.tsx +1175 -777
  65. package/src/inspect.ts +95 -21
  66. package/src/{kv.tsx → kv.ts} +104 -61
  67. package/src/miniflare-cli/README.md +30 -0
  68. package/src/miniflare-cli/enum-keys.ts +17 -0
  69. package/src/miniflare-cli/index.ts +37 -0
  70. package/src/module-collection.ts +229 -35
  71. package/src/open-in-browser.ts +25 -0
  72. package/src/package-manager.ts +126 -0
  73. package/src/pages.tsx +39 -32
  74. package/src/parse.ts +183 -0
  75. package/src/paths.ts +26 -0
  76. package/src/proxy.ts +177 -39
  77. package/src/publish.ts +274 -309
  78. package/src/r2.ts +50 -0
  79. package/src/reporting.ts +131 -0
  80. package/src/selfsigned.d.ts +29 -0
  81. package/src/sites.tsx +95 -58
  82. package/src/tail/filters.ts +279 -0
  83. package/src/tail/index.ts +299 -0
  84. package/src/tail/printing.ts +62 -0
  85. package/src/user.tsx +233 -134
  86. package/src/{api/worker.ts → worker.ts} +45 -37
  87. package/templates/new-worker.js +15 -0
  88. package/templates/new-worker.ts +15 -0
  89. package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
  90. package/templates/tsconfig.json +105 -0
  91. package/wrangler-dist/cli.js +130352 -112740
  92. package/wrangler-dist/cli.js.map +3 -3
  93. package/src/__tests__/run-in-tmp.ts +0 -19
  94. package/src/__tests__/run-wrangler.ts +0 -32
  95. package/src/api/form_data.ts +0 -131
  96. package/src/config.ts +0 -646
  97. package/src/dev.tsx +0 -779
  98. package/src/tail.tsx +0 -73
package/src/user.tsx CHANGED
@@ -1,6 +1,6 @@
1
1
  /* Based heavily on code from https://github.com/BitySA/oauth2-auth-code-pkce */
2
2
 
3
- /*
3
+ /*
4
4
 
5
5
  Apache License
6
6
  Version 2.0, January 2004
@@ -205,25 +205,43 @@
205
205
  limitations under the License.
206
206
  */
207
207
 
208
- import React from "react";
209
- import { render, Text } from "ink";
210
- import Table from "ink-table";
211
- import SelectInput from "ink-select-input";
212
- import fetch from "node-fetch";
208
+ import assert from "node:assert";
213
209
  import { webcrypto as crypto } from "node:crypto";
214
- import { TextEncoder } from "node:util";
215
- import open from "open";
216
- import url from "node:url";
210
+ import { writeFileSync, mkdirSync, rmSync } from "node:fs";
217
211
  import http from "node:http";
218
- import { readFile, writeFile, rm, mkdir } from "node:fs/promises";
219
- import path from "node:path";
220
- import process from "node:process";
221
212
  import os from "node:os";
213
+ import path from "node:path";
214
+ import url from "node:url";
215
+ import { TextEncoder } from "node:util";
222
216
  import TOML from "@iarna/toml";
223
- import assert from "node:assert";
217
+ import { render, Text } from "ink";
218
+ import SelectInput from "ink-select-input";
219
+ import Table from "ink-table";
220
+ import React from "react";
221
+ import { fetch } from "undici";
222
+ import { getCloudflareApiBaseUrl } from "./cfetch";
223
+ import { getEnvironmentVariableFactory } from "./environment-variables";
224
+ import openInBrowser from "./open-in-browser";
225
+ import { readFileSync } from "./parse";
226
+ import type { Item as SelectInputItem } from "ink-select-input/build/SelectInput";
224
227
  import type { ParsedUrlQuery } from "node:querystring";
225
- import { CF_API_BASE_URL } from "./cfetch";
226
- import type { Response } from "node-fetch";
228
+ import type { Response } from "undici";
229
+
230
+ /**
231
+ * Try to read the API token from the environment.
232
+ */
233
+ const getCloudflareAPITokenFromEnv = getEnvironmentVariableFactory({
234
+ variableName: "CLOUDFLARE_API_TOKEN",
235
+ deprecatedName: "CF_API_TOKEN",
236
+ });
237
+
238
+ /**
239
+ * Try to read the account ID from the environment.
240
+ */
241
+ const getCloudflareAccountIdFromEnv = getEnvironmentVariableFactory({
242
+ variableName: "CLOUDFLARE_ACCOUNT_ID",
243
+ deprecatedName: "CF_ACCOUNT_ID",
244
+ });
227
245
 
228
246
  /**
229
247
  * An implementation of rfc6749#section-4.1 and rfc7636.
@@ -234,17 +252,45 @@ interface PKCECodes {
234
252
  codeVerifier: string;
235
253
  }
236
254
 
237
- interface State {
238
- accessToken?: AccessToken; // persist
255
+ /**
256
+ * The module level state of the authentication flow.
257
+ */
258
+ interface State extends AuthTokens {
239
259
  authorizationCode?: string;
240
260
  codeChallenge?: string;
241
261
  codeVerifier?: string;
242
262
  hasAuthCodeBeenExchangedForAccessToken?: boolean;
243
- refreshToken?: RefreshToken; // persist
244
263
  stateQueryParam?: string;
245
264
  scopes?: Scope[];
246
265
  }
247
266
 
267
+ /**
268
+ * The tokens related to authentication.
269
+ */
270
+ interface AuthTokens {
271
+ accessToken?: AccessToken;
272
+ refreshToken?: RefreshToken;
273
+ /** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
274
+ apiToken?: string;
275
+ }
276
+
277
+ /**
278
+ * The path to the config file that holds user authentication data,
279
+ * relative to the user's home directory.
280
+ */
281
+ export const USER_AUTH_CONFIG_FILE = ".wrangler/config/default.toml";
282
+
283
+ /**
284
+ * The data that may be read from the `USER_CONFIG_FILE`.
285
+ */
286
+ export interface UserAuthConfig {
287
+ oauth_token?: string;
288
+ refresh_token?: string;
289
+ expiration_time?: string;
290
+ /** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
291
+ api_token?: string;
292
+ }
293
+
248
294
  interface RefreshToken {
249
295
  value: string;
250
296
  }
@@ -268,6 +314,8 @@ const Scopes = {
268
314
  "workers_scripts:write":
269
315
  "See and change Cloudflare Workers scripts, durable objects, subdomains, triggers, and tail data.",
270
316
  "workers_tail:read": "See Cloudflare Workers tail and script data.",
317
+ "pages:write":
318
+ "See and change Cloudflare Pages projects, settings and deployments.",
271
319
  "zone:read": "Grants read level access to account zone.",
272
320
  } as const;
273
321
 
@@ -276,14 +324,14 @@ const Scopes = {
276
324
  *
277
325
  * "offline_access" is automatically included.
278
326
  */
279
- type Scope = keyof typeof Scopes | "offline_access";
327
+ type Scope = keyof typeof Scopes;
280
328
 
281
329
  const ScopeKeys = Object.keys(Scopes) as Scope[];
282
330
 
283
331
  export function validateScopeKeys(
284
332
  scopes: string[]
285
333
  ): scopes is typeof ScopeKeys {
286
- return scopes.every((scope) => Scopes[scope]);
334
+ return scopes.every((scope) => scope in Scopes);
287
335
  }
288
336
 
289
337
  const CLIENT_ID = "54d11594-84e4-41aa-b438-e81b8fa78ee7";
@@ -292,62 +340,63 @@ const TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
292
340
  const CALLBACK_URL = "http://localhost:8976/oauth/callback";
293
341
  const REVOKE_URL = "https://dash.cloudflare.com/oauth2/revoke";
294
342
 
295
- const LocalState: State = {};
296
- let initialised = false;
343
+ let LocalState: State = getAuthTokens();
297
344
 
298
- // we do this because we have some async stuff
299
- // TODO: this should just happen in the top level
300
- // and we should figure out how to do top level await
301
- export async function initialise(): Promise<void> {
345
+ /**
346
+ * Compute the current auth tokens.
347
+ */
348
+ function getAuthTokens(): AuthTokens {
302
349
  // get refreshToken/accessToken from fs if exists
303
350
  try {
304
- // if CF_API_TOKEN available, use that
305
- if (process.env.CF_API_TOKEN) {
306
- LocalState.accessToken = {
307
- value: process.env.CF_API_TOKEN,
308
- expiry: "3021-12-31T23:59:59+00:00",
351
+ // if the environment variable is available, use that
352
+ const apiTokenFromEnv = getCloudflareAPITokenFromEnv();
353
+ if (apiTokenFromEnv) {
354
+ return {
355
+ accessToken: {
356
+ value: apiTokenFromEnv,
357
+ expiry: "3021-12-31T23:59:59+00:00",
358
+ },
309
359
  };
310
- initialised = true;
311
- return;
312
360
  }
313
361
 
314
- const toml = TOML.parse(
315
- await readFile(path.join(os.homedir(), ".wrangler/config/default.toml"), {
316
- encoding: "utf-8",
317
- })
318
- );
319
- const { oauth_token, refresh_token, expiration_time } = toml as {
320
- oauth_token: string;
321
- refresh_token: string;
322
- expiration_time: string;
323
- };
362
+ // otherwise try loading from the user auth config file.
363
+ const { oauth_token, refresh_token, expiration_time, api_token } =
364
+ readAuthConfigFile();
365
+
324
366
  if (oauth_token) {
325
- LocalState.accessToken = { value: oauth_token, expiry: expiration_time };
326
- }
327
- if (refresh_token) {
328
- LocalState.refreshToken = { value: refresh_token };
367
+ return {
368
+ accessToken: {
369
+ value: oauth_token,
370
+ // If there is no `expiration_time` field then set it to an old date, to cause it to expire immediately.
371
+ expiry: expiration_time ?? "2000-01-01:00:00:00+00:00",
372
+ },
373
+ refreshToken: { value: refresh_token ?? "" },
374
+ };
375
+ } else if (api_token) {
376
+ return { apiToken: api_token };
377
+ } else {
378
+ return {};
329
379
  }
330
- } catch (err) {
331
- // no config yet, let's chill
332
- // console.error(err);
380
+ } catch {
381
+ return {};
333
382
  }
334
- initialised = true;
335
383
  }
336
384
 
337
- // ugh. TODO: see fix from above.
338
- function throwIfNotInitialised() {
339
- if (initialised === false) {
340
- throw new Error(
341
- "did you forget to call initialise() from the user module?"
342
- );
343
- }
385
+ /**
386
+ * Run the initialisation of the auth state, in the case that something changed.
387
+ */
388
+ export function reinitialiseAuthTokens(): void {
389
+ LocalState = getAuthTokens();
344
390
  }
345
391
 
346
392
  export function getAPIToken(): string | undefined {
347
- if (process.env.CF_API_TOKEN) {
348
- return process.env.CF_API_TOKEN;
393
+ if (LocalState.apiToken) {
394
+ console.warn(
395
+ "It looks like you have used Wrangler 1's `config` command to login with an API token.\n" +
396
+ "This is no longer supported in the current version of Wrangler.\n" +
397
+ "If you wish to authenticate via an API token then please set the `CLOUDFLARE_API_TOKEN` environment variable."
398
+ );
349
399
  }
350
- throwIfNotInitialised();
351
400
  return LocalState.accessToken?.value;
352
401
  }
353
402
 
@@ -561,6 +610,7 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
561
610
  `?response_type=code&` +
562
611
  `client_id=${encodeURIComponent(CLIENT_ID)}&` +
563
612
  `redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
613
+ // @ts-expect-error we add offline_access manually
564
614
  `scope=${encodeURIComponent(scopes.concat("offline_access").join(" "))}&` +
565
615
  `state=${stateQueryParam}&` +
566
616
  `code_challenge=${encodeURIComponent(codeChallenge)}&` +
@@ -568,6 +618,17 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
568
618
  );
569
619
  }
570
620
 
621
+ type TokenResponse =
622
+ | {
623
+ access_token: string;
624
+ expires_in: number;
625
+ refresh_token: string;
626
+ scope: string;
627
+ }
628
+ | {
629
+ error: string;
630
+ };
631
+
571
632
  /**
572
633
  * Refresh an access token from the remote service.
573
634
  */
@@ -592,13 +653,12 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
592
653
  throw await response.json();
593
654
  } else {
594
655
  try {
595
- const json = await response.json();
596
- const { access_token, expires_in, refresh_token, scope } = json as {
597
- access_token: string;
598
- expires_in: number;
599
- refresh_token: string;
600
- scope: string;
601
- };
656
+ const json = (await response.json()) as TokenResponse;
657
+ if ("error" in json) {
658
+ throw json.error;
659
+ }
660
+
661
+ const { access_token, expires_in, refresh_token, scope } = json;
602
662
  let scopes: Scope[] = [];
603
663
 
604
664
  const accessToken: AccessToken = {
@@ -626,20 +686,24 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
626
686
  refreshToken: LocalState.refreshToken,
627
687
  };
628
688
  return accessContext;
629
- } catch (err) {
630
- const error = err?.error || "There was a network error.";
689
+ } catch (error) {
631
690
  switch (error) {
632
691
  case "invalid_grant":
633
- console.log(
692
+ console.warn(
634
693
  "Expired! Auth code or refresh token needs to be renewed."
635
694
  );
636
695
  // alert("Redirecting to auth server to obtain a new auth grant code.");
637
696
  // TODO: return refreshAuthCodeOrRefreshToken();
638
697
  break;
639
698
  default:
699
+ console.error(error);
640
700
  break;
641
701
  }
642
- throw toErrorClass(error);
702
+ if (typeof error === "string") {
703
+ throw toErrorClass(error);
704
+ } else {
705
+ throw error;
706
+ }
643
707
  }
644
708
  }
645
709
  }
@@ -680,13 +744,11 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
680
744
  }
681
745
  throw toErrorClass(error);
682
746
  }
683
- const json = await response.json();
684
- const { access_token, expires_in, refresh_token, scope } = json as {
685
- access_token: string;
686
- expires_in: number;
687
- refresh_token: string;
688
- scope: string;
689
- };
747
+ const json = (await response.json()) as TokenResponse;
748
+ if ("error" in json) {
749
+ throw new Error(json.error);
750
+ }
751
+ const { access_token, expires_in, refresh_token, scope } = json;
690
752
  let scopes: Scope[] = [];
691
753
  LocalState.hasAuthCodeBeenExchangedForAccessToken = true;
692
754
 
@@ -770,31 +832,37 @@ function generateRandomState(lengthOfState: number): string {
770
832
  .join("");
771
833
  }
772
834
 
773
- async function writeToConfigFile(tokenData: AccessContext) {
774
- await mkdir(path.join(os.homedir(), ".wrangler/config/"), {
835
+ export function writeAuthConfigFile(config: UserAuthConfig) {
836
+ mkdirSync(path.join(os.homedir(), ".wrangler/config/"), {
775
837
  recursive: true,
776
838
  });
777
- await writeFile(
778
- path.join(os.homedir(), ".wrangler/config/default.toml"),
779
- `
780
- oauth_token = "${tokenData.token?.value || ""}"
781
- refresh_token = "${tokenData.refreshToken?.value}"
782
- expiration_time = "${tokenData.token?.expiry}"
783
- `,
839
+ writeFileSync(
840
+ path.join(os.homedir(), USER_AUTH_CONFIG_FILE),
841
+ TOML.stringify(config as TOML.JsonMap),
784
842
  { encoding: "utf-8" }
785
843
  );
786
844
  }
787
845
 
846
+ function readAuthConfigFile(): UserAuthConfig {
847
+ const toml = TOML.parse(
848
+ readFileSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE))
849
+ );
850
+ return toml;
851
+ }
852
+
788
853
  type LoginProps = {
789
854
  scopes?: Scope[];
790
855
  };
791
856
 
792
- export async function loginOrRefreshIfRequired(): Promise<boolean> {
857
+ export async function loginOrRefreshIfRequired(
858
+ isInteractive = true
859
+ ): Promise<boolean> {
793
860
  // TODO: if there already is a token, then try refreshing
794
861
  // TODO: ask permission before opening browser
795
- if (!LocalState.accessToken) {
796
- // not logged in.
797
- return await login();
862
+ if (!getAPIToken()) {
863
+ // Not logged in.
864
+ // If we are not interactive, we cannot ask the user to login
865
+ return isInteractive && (await login());
798
866
  } else if (isAccessTokenExpired()) {
799
867
  return await refreshToken();
800
868
  } else {
@@ -803,19 +871,20 @@ export async function loginOrRefreshIfRequired(): Promise<boolean> {
803
871
  }
804
872
 
805
873
  export async function login(props?: LoginProps): Promise<boolean> {
874
+ console.log("Attempting to login via OAuth...");
806
875
  const urlToOpen = await getAuthURL(props?.scopes);
807
- await open(urlToOpen);
808
- // TODO: log url only if on system where it's unreliable/unavailable
809
- // console.log(`💁 Opened ${urlToOpen}`);
810
- let server;
811
- let loginTimeoutHandle;
876
+ await openInBrowser(urlToOpen);
877
+ let server: http.Server;
878
+ let loginTimeoutHandle: NodeJS.Timeout;
812
879
  const timerPromise = new Promise<boolean>((resolve) => {
813
880
  loginTimeoutHandle = setTimeout(() => {
814
- console.error("Timed out waiting for authorization code.");
881
+ console.error(
882
+ "Timed out waiting for authorization code, please try again."
883
+ );
815
884
  server.close();
816
885
  clearTimeout(loginTimeoutHandle);
817
886
  resolve(false);
818
- }, 60000); // wait for 30 seconds for the user to authorize
887
+ }, 60000); // wait for 60 seconds for the user to authorize
819
888
  });
820
889
 
821
890
  const loginPromise = new Promise<boolean>((resolve, reject) => {
@@ -846,8 +915,9 @@ export async function login(props?: LoginProps): Promise<boolean> {
846
915
  finish(false);
847
916
  });
848
917
  console.log(
849
- "Error: Consent denied. You must grant consent to Wrangler in order to login. If you don't want to do this consider passing an API token with CF_API_TOKEN variable"
850
- ); // TODO: implement wrangler config lol
918
+ "Error: Consent denied. You must grant consent to Wrangler in order to login.\n" +
919
+ "If you don't want to do this consider passing an API token via the `CLOUDFLARE_API_TOKEN` environment variable"
920
+ );
851
921
 
852
922
  return;
853
923
  } else {
@@ -860,8 +930,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
860
930
  finish(false, new ErrorNoAuthCode());
861
931
  return;
862
932
  } else {
863
- const tokenData = await exchangeAuthCodeForAccessToken();
864
- await writeToConfigFile(tokenData);
933
+ const exchange = await exchangeAuthCodeForAccessToken();
934
+ writeAuthConfigFile({
935
+ oauth_token: exchange.token?.value ?? "",
936
+ expiration_time: exchange.token?.expiry,
937
+ refresh_token: exchange.refreshToken?.value,
938
+ });
865
939
  res.writeHead(307, {
866
940
  Location:
867
941
  "https://welcome.developers.workers.dev/wrangler-oauth-consent-granted",
@@ -869,9 +943,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
869
943
  res.end(() => {
870
944
  finish(true);
871
945
  });
872
- console.log(
873
- `Successfully configured. You can find your configuration file at: ${os.homedir()}/.wrangler/config/default.toml`
874
- );
946
+ console.log(`Successfully logged in.`);
875
947
 
876
948
  return;
877
949
  }
@@ -888,18 +960,22 @@ export async function login(props?: LoginProps): Promise<boolean> {
888
960
  /**
889
961
  * Checks to see if the access token has expired.
890
962
  */
891
- export function isAccessTokenExpired(): boolean {
892
- throwIfNotInitialised();
963
+ function isAccessTokenExpired(): boolean {
893
964
  const { accessToken } = LocalState;
894
965
  return Boolean(accessToken && new Date() >= new Date(accessToken.expiry));
895
966
  }
896
967
 
897
- export async function refreshToken(): Promise<boolean> {
898
- throwIfNotInitialised();
968
+ async function refreshToken(): Promise<boolean> {
899
969
  // refresh
900
970
  try {
901
- const refreshed = await exchangeRefreshTokenForAccessToken();
902
- await writeToConfigFile(refreshed);
971
+ const {
972
+ token: { value: oauth_token, expiry: expiration_time } = {
973
+ value: "",
974
+ expiry: "",
975
+ },
976
+ refreshToken: { value: refresh_token } = {},
977
+ } = await exchangeRefreshTokenForAccessToken();
978
+ writeAuthConfigFile({ oauth_token, expiration_time, refresh_token });
903
979
  return true;
904
980
  } catch (err) {
905
981
  console.error(err);
@@ -908,10 +984,28 @@ export async function refreshToken(): Promise<boolean> {
908
984
  }
909
985
 
910
986
  export async function logout(): Promise<void> {
911
- throwIfNotInitialised();
912
- if (!LocalState.refreshToken) {
913
- console.log("Not logged in, exiting...");
914
- return;
987
+ if (!LocalState.accessToken) {
988
+ if (!LocalState.refreshToken) {
989
+ console.log("Not logged in, exiting...");
990
+ return;
991
+ }
992
+
993
+ const body =
994
+ `client_id=${encodeURIComponent(CLIENT_ID)}&` +
995
+ `token_type_hint=refresh_token&` +
996
+ `token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
997
+
998
+ const response = await fetch(REVOKE_URL, {
999
+ method: "POST",
1000
+ body,
1001
+ headers: {
1002
+ "Content-Type": "application/x-www-form-urlencoded",
1003
+ },
1004
+ });
1005
+ await response.text(); // blank text? would be nice if it was something meaningful
1006
+ console.log(
1007
+ "💁 Wrangler is configured with an OAuth token. The token has been successfully revoked"
1008
+ );
915
1009
  }
916
1010
  const body =
917
1011
  `client_id=${encodeURIComponent(CLIENT_ID)}&` +
@@ -926,20 +1020,13 @@ export async function logout(): Promise<void> {
926
1020
  },
927
1021
  });
928
1022
  await response.text(); // blank text? would be nice if it was something meaningful
929
- console.log(
930
- "💁 Wrangler is configured with an OAuth token. The token has been successfully revoked"
931
- );
932
- // delete the file
933
- await rm(path.join(os.homedir(), ".wrangler/config/default.toml"));
934
- console.log(
935
- `Removing ${os.homedir()}/.wrangler/config/default.toml.. success!`
936
- );
1023
+ rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
1024
+ console.log(`Successfully logged out.`);
937
1025
  }
938
1026
 
939
- export function listScopes(): void {
940
- throwIfNotInitialised();
941
- console.log("💁 Available scopes:");
942
- const data = ScopeKeys.map((scope) => ({
1027
+ export function listScopes(message = "💁 Available scopes:"): void {
1028
+ console.log(message);
1029
+ const data = ScopeKeys.map((scope: Scope) => ({
943
1030
  Scope: scope,
944
1031
  Description: Scopes[scope],
945
1032
  }));
@@ -947,17 +1034,20 @@ export function listScopes(): void {
947
1034
  // TODO: maybe a good idea to show usage here
948
1035
  }
949
1036
 
950
- export async function getAccountId() {
1037
+ export async function getAccountId(
1038
+ isInteractive = true
1039
+ ): Promise<string | undefined> {
951
1040
  const apiToken = getAPIToken();
952
1041
  if (!apiToken) return;
953
1042
 
954
- if (process.env.CF_ACCOUNT_ID) {
955
- return process.env.CF_ACCOUNT_ID;
1043
+ const accountIdFromEnv = getCloudflareAccountIdFromEnv();
1044
+ if (accountIdFromEnv) {
1045
+ return accountIdFromEnv;
956
1046
  }
957
1047
 
958
1048
  let response: Response;
959
1049
  try {
960
- response = await fetch(`${CF_API_BASE_URL}/memberships`, {
1050
+ response = await fetch(`${getCloudflareApiBaseUrl()}/memberships`, {
961
1051
  method: "GET",
962
1052
  headers: {
963
1053
  Authorization: "Bearer " + apiToken,
@@ -977,7 +1067,7 @@ export async function getAccountId() {
977
1067
  if (responseJSON.success === true) {
978
1068
  if (responseJSON.result.length === 1) {
979
1069
  accountId = responseJSON.result[0].account.id;
980
- } else {
1070
+ } else if (isInteractive) {
981
1071
  accountId = await new Promise((resolve) => {
982
1072
  const accounts = responseJSON.result.map((x) => x.account);
983
1073
  const { unmount } = render(
@@ -990,6 +1080,15 @@ export async function getAccountId() {
990
1080
  />
991
1081
  );
992
1082
  });
1083
+ } else {
1084
+ throw new Error(
1085
+ "More than one account available but unable to select one in non-interactive mode.\n" +
1086
+ `Please set the appropriate \`account_id\` in your \`wrangler.toml\` file.\n` +
1087
+ `Available accounts are ("<name>" - "<id>"):\n` +
1088
+ responseJSON.result
1089
+ .map((x) => ` "${x.account.name}" - "${x.account.id}")`)
1090
+ .join("\n")
1091
+ );
993
1092
  }
994
1093
  }
995
1094
  return accountId;
@@ -1001,7 +1100,7 @@ type ChooseAccountItem = {
1001
1100
  };
1002
1101
  export function ChooseAccount(props: {
1003
1102
  accounts: ChooseAccountItem[];
1004
- onSelect: (item) => void;
1103
+ onSelect: (item: SelectInputItem<ChooseAccountItem>) => void;
1005
1104
  }) {
1006
1105
  return (
1007
1106
  <>