wrangler 0.0.0-e95bea2 → 0.0.0-ea8e701

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 (55) hide show
  1. package/README.md +3 -3
  2. package/bin/wrangler.js +10 -1
  3. package/miniflare-dist/index.mjs +79 -0
  4. package/miniflare-dist/index.mjs.map +7 -0
  5. package/package.json +7 -6
  6. package/pages/functions/filepath-routing.test.ts +125 -25
  7. package/pages/functions/filepath-routing.ts +6 -3
  8. package/src/__tests__/configuration.test.ts +826 -223
  9. package/src/__tests__/dev.test.tsx +145 -9
  10. package/src/__tests__/guess-worker-format.test.ts +26 -1
  11. package/src/__tests__/helpers/mock-account-id.ts +4 -0
  12. package/src/__tests__/helpers/mock-kv.ts +9 -16
  13. package/src/__tests__/helpers/mock-oauth-flow.ts +178 -0
  14. package/src/__tests__/helpers/run-in-tmp.ts +3 -0
  15. package/src/__tests__/index.test.ts +9 -13
  16. package/src/__tests__/jest.setup.ts +5 -0
  17. package/src/__tests__/kv.test.ts +43 -71
  18. package/src/__tests__/pages.test.ts +48 -10
  19. package/src/__tests__/publish.test.ts +1456 -327
  20. package/src/__tests__/r2.test.ts +4 -4
  21. package/src/__tests__/tail.test.ts +78 -4
  22. package/src/__tests__/user.test.ts +83 -0
  23. package/src/__tests__/whoami.test.tsx +16 -7
  24. package/src/bundle.ts +15 -2
  25. package/src/cfetch/internal.ts +9 -5
  26. package/src/config/README.md +6 -6
  27. package/src/config/config.ts +17 -78
  28. package/src/config/environment.ts +67 -1
  29. package/src/config/index.ts +13 -3
  30. package/src/config/validation-helpers.ts +177 -11
  31. package/src/config/validation.ts +305 -100
  32. package/src/dev/dev.tsx +76 -41
  33. package/src/dev/local.tsx +105 -114
  34. package/src/dev/remote.tsx +2 -0
  35. package/src/dev/use-esbuild.ts +4 -0
  36. package/src/entry.ts +103 -6
  37. package/src/index.tsx +251 -189
  38. package/src/intl-polyfill.d.ts +139 -0
  39. package/src/{kv/index.tsx → kv.ts} +8 -28
  40. package/src/miniflare-cli/README.md +30 -0
  41. package/src/miniflare-cli/enum-keys.ts +17 -0
  42. package/src/miniflare-cli/index.ts +37 -0
  43. package/src/module-collection.ts +58 -53
  44. package/src/pages.tsx +26 -10
  45. package/src/proxy.ts +5 -3
  46. package/src/publish.ts +17 -21
  47. package/src/sites.tsx +67 -25
  48. package/src/tail/index.ts +1 -1
  49. package/src/tail/printing.ts +2 -2
  50. package/src/user.tsx +161 -81
  51. package/wrangler-dist/cli.js +3418 -1437
  52. package/wrangler-dist/cli.js.map +3 -3
  53. package/src/__tests__/dev2.test.tsx +0 -152
  54. package/src/__tests__/helpers/mock-user.ts +0 -27
  55. package/src/__tests__/logout.test.ts +0 -50
package/src/sites.tsx CHANGED
@@ -7,29 +7,27 @@ import {
7
7
  listNamespaceKeys,
8
8
  listNamespaces,
9
9
  putBulkKeyValue,
10
+ deleteBulkKeyValue,
10
11
  } from "./kv";
11
12
  import type { Config } from "./config";
12
13
  import type { KeyValue } from "./kv";
13
14
  import type { XXHashAPI } from "xxhash-wasm";
14
15
 
15
16
  /** Paths to always ignore. */
16
- const ALWAYS_IGNORE = ["node_modules"];
17
- const HIDDEN_FILES_TO_INCLUDE = [
17
+ const ALWAYS_IGNORE = new Set(["node_modules"]);
18
+ const HIDDEN_FILES_TO_INCLUDE = new Set([
18
19
  ".well-known", // See https://datatracker.ietf.org/doc/html/rfc8615
19
- ];
20
+ ]);
20
21
 
21
22
  async function* getFilesInFolder(dirPath: string): AsyncIterable<string> {
22
23
  const files = await readdir(dirPath, { withFileTypes: true });
23
24
  for (const file of files) {
24
25
  // Skip files that we never want to process.
25
- if (ALWAYS_IGNORE.some((p) => file.name === p)) {
26
+ if (ALWAYS_IGNORE.has(file.name)) {
26
27
  continue;
27
28
  }
28
29
  // Skip hidden files (starting with .) except for some special ones
29
- if (
30
- file.name.startsWith(".") &&
31
- !HIDDEN_FILES_TO_INCLUDE.some((p) => file.name === p)
32
- ) {
30
+ if (file.name.startsWith(".") && !HIDDEN_FILES_TO_INCLUDE.has(file.name)) {
33
31
  continue;
34
32
  }
35
33
  // TODO: follow symlinks??
@@ -129,32 +127,36 @@ export async function syncAssets(
129
127
  const keys = new Set(result.map((x) => x.name));
130
128
 
131
129
  const manifest: Record<string, string> = {};
132
- const upload: KeyValue[] = [];
130
+ const toUpload: KeyValue[] = [];
133
131
 
134
132
  const include = createPatternMatcher(siteAssets.includePatterns, false);
135
133
  const exclude = createPatternMatcher(siteAssets.excludePatterns, true);
136
134
  const hasher = await xxhash();
137
135
 
138
- // TODO: this can be more efficient by parallelising
139
- for await (const file of getFilesInFolder(siteAssets.baseDirectory)) {
140
- if (!include(file)) {
136
+ const assetDirectory = path.join(
137
+ siteAssets.baseDirectory,
138
+ siteAssets.assetDirectory
139
+ );
140
+ for await (const absAssetFile of getFilesInFolder(assetDirectory)) {
141
+ const assetFile = path.relative(siteAssets.baseDirectory, absAssetFile);
142
+ if (!include(assetFile)) {
141
143
  continue;
142
144
  }
143
- if (exclude(file)) {
145
+ if (exclude(assetFile)) {
144
146
  continue;
145
147
  }
146
148
 
147
- await validateAssetSize(file);
148
- console.log(`reading ${file}...`);
149
- const content = await readFile(file, "base64");
149
+ await validateAssetSize(absAssetFile, assetFile);
150
+ console.log(`reading ${assetFile}...`);
151
+ const content = await readFile(absAssetFile, "base64");
150
152
 
151
- const assetKey = await hashAsset(hasher, file, content);
153
+ const assetKey = hashAsset(hasher, assetFile, content);
152
154
  validateAssetKey(assetKey);
153
155
 
154
156
  // now put each of the files into kv
155
157
  if (!keys.has(assetKey)) {
156
158
  console.log(`uploading as ${assetKey}...`);
157
- upload.push({
159
+ toUpload.push({
158
160
  key: assetKey,
159
161
  value: content,
160
162
  base64: true,
@@ -162,9 +164,26 @@ export async function syncAssets(
162
164
  } else {
163
165
  console.log(`skipping - already uploaded`);
164
166
  }
165
- manifest[path.relative(siteAssets.baseDirectory, file)] = assetKey;
167
+
168
+ // remove the key from the set so we know what we've already uploaded
169
+ keys.delete(assetKey);
170
+ manifest[path.relative(siteAssets.assetDirectory, absAssetFile)] = assetKey;
166
171
  }
167
- await putBulkKeyValue(accountId, namespace, upload, () => {});
172
+
173
+ // keys now contains all the files we're deleting
174
+ for (const key of keys) {
175
+ console.log(`deleting ${key} from the asset store...`);
176
+ }
177
+
178
+ await Promise.all([
179
+ // upload all the new assets
180
+ putBulkKeyValue(accountId, namespace, toUpload, () => {}),
181
+ // delete all the unused assets
182
+ deleteBulkKeyValue(accountId, namespace, Array.from(keys), () => {}),
183
+ ]);
184
+
185
+ console.log("↗️ Done syncing assets");
186
+
168
187
  return { manifest, namespace };
169
188
  }
170
189
 
@@ -180,11 +199,14 @@ function createPatternMatcher(
180
199
  }
181
200
  }
182
201
 
183
- async function validateAssetSize(filePath: string) {
184
- const { size } = await stat(filePath);
202
+ async function validateAssetSize(
203
+ absFilePath: string,
204
+ relativeFilePath: string
205
+ ) {
206
+ const { size } = await stat(absFilePath);
185
207
  if (size > 25 * 1024 * 1024) {
186
208
  throw new Error(
187
- `File ${filePath} is too big, it should be under 25 MiB. See https://developers.cloudflare.com/workers/platform/limits#kv-limits`
209
+ `File ${relativeFilePath} is too big, it should be under 25 MiB. See https://developers.cloudflare.com/workers/platform/limits#kv-limits`
188
210
  );
189
211
  }
190
212
  }
@@ -210,8 +232,23 @@ function urlSafe(filePath: string): string {
210
232
  * Information about the assets that should be uploaded
211
233
  */
212
234
  export interface AssetPaths {
235
+ /**
236
+ * Absolute path to the root of the project.
237
+ *
238
+ * This is the directory containing wrangler.toml or cwd if no config.
239
+ */
213
240
  baseDirectory: string;
241
+ /**
242
+ * The path to the assets directory, relative to the `baseDirectory`.
243
+ */
244
+ assetDirectory: string;
245
+ /**
246
+ * An array of patterns that match files that should be uploaded.
247
+ */
214
248
  includePatterns: string[];
249
+ /**
250
+ * An array of patterns that match files that should not be uploaded.
251
+ */
215
252
  excludePatterns: string[];
216
253
  }
217
254
 
@@ -224,13 +261,18 @@ export interface AssetPaths {
224
261
  */
225
262
  export function getAssetPaths(
226
263
  config: Config,
227
- baseDirectory = config.site?.bucket,
264
+ assetDirectory = config.site?.bucket,
228
265
  includePatterns = config.site?.include ?? [],
229
266
  excludePatterns = config.site?.exclude ?? []
230
267
  ): undefined | AssetPaths {
231
- return baseDirectory
268
+ const baseDirectory = path.resolve(
269
+ path.dirname(config.configPath ?? "wrangler.toml")
270
+ );
271
+
272
+ return assetDirectory
232
273
  ? {
233
274
  baseDirectory,
275
+ assetDirectory,
234
276
  includePatterns,
235
277
  excludePatterns,
236
278
  }
package/src/tail/index.ts CHANGED
@@ -161,7 +161,7 @@ export type TailEventMessage = {
161
161
  * Any logs sent out by the worker
162
162
  */
163
163
  logs: {
164
- message: unknown;
164
+ message: unknown[];
165
165
  level: string; // TODO: make this a union of possible values
166
166
  timestamp: number;
167
167
  }[];
@@ -24,13 +24,13 @@ export function prettyPrintLogs(data: WebSocket.RawData): void {
24
24
 
25
25
  if (eventMessage.logs.length > 0) {
26
26
  eventMessage.logs.forEach(({ level, message }) => {
27
- console.log(` (${level}) ${message}`);
27
+ console.log(` (${level})`, ...message);
28
28
  });
29
29
  }
30
30
 
31
31
  if (eventMessage.exceptions.length > 0) {
32
32
  eventMessage.exceptions.forEach(({ name, message }) => {
33
- console.error(` ${name}: ${message}`);
33
+ console.error(` ${name}:`, message);
34
34
  });
35
35
  }
36
36
  }
package/src/user.tsx CHANGED
@@ -207,7 +207,7 @@
207
207
 
208
208
  import assert from "node:assert";
209
209
  import { webcrypto as crypto } from "node:crypto";
210
- import { readFile, writeFile, rm, mkdir } from "node:fs/promises";
210
+ import { writeFileSync, mkdirSync, rmSync } from "node:fs";
211
211
  import http from "node:http";
212
212
  import os from "node:os";
213
213
  import path from "node:path";
@@ -222,6 +222,7 @@ import { fetch } from "undici";
222
222
  import { getCloudflareApiBaseUrl } from "./cfetch";
223
223
  import { getEnvironmentVariableFactory } from "./environment-variables";
224
224
  import openInBrowser from "./open-in-browser";
225
+ import { readFileSync } from "./parse";
225
226
  import type { Item as SelectInputItem } from "ink-select-input/build/SelectInput";
226
227
  import type { ParsedUrlQuery } from "node:querystring";
227
228
  import type { Response } from "undici";
@@ -251,17 +252,45 @@ interface PKCECodes {
251
252
  codeVerifier: string;
252
253
  }
253
254
 
254
- interface State {
255
- accessToken?: AccessToken; // persist
255
+ /**
256
+ * The module level state of the authentication flow.
257
+ */
258
+ interface State extends AuthTokens {
256
259
  authorizationCode?: string;
257
260
  codeChallenge?: string;
258
261
  codeVerifier?: string;
259
262
  hasAuthCodeBeenExchangedForAccessToken?: boolean;
260
- refreshToken?: RefreshToken; // persist
261
263
  stateQueryParam?: string;
262
264
  scopes?: Scope[];
263
265
  }
264
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
+
265
294
  interface RefreshToken {
266
295
  value: string;
267
296
  }
@@ -311,60 +340,74 @@ const TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
311
340
  const CALLBACK_URL = "http://localhost:8976/oauth/callback";
312
341
  const REVOKE_URL = "https://dash.cloudflare.com/oauth2/revoke";
313
342
 
314
- const LocalState: State = {};
315
- let initialised = false;
343
+ let LocalState: State = getAuthTokens();
316
344
 
317
- // we do this because we have some async stuff
318
- // TODO: this should just happen in the top level
319
- // and we should figure out how to do top level await
320
- export async function initialise(): Promise<void> {
345
+ /**
346
+ * Compute the current auth tokens.
347
+ */
348
+ function getAuthTokens(config?: UserAuthConfig): AuthTokens {
321
349
  // get refreshToken/accessToken from fs if exists
322
350
  try {
323
351
  // if the environment variable is available, use that
324
352
  const apiTokenFromEnv = getCloudflareAPITokenFromEnv();
325
353
  if (apiTokenFromEnv) {
326
- LocalState.accessToken = {
327
- value: apiTokenFromEnv,
328
- expiry: "3021-12-31T23:59:59+00:00",
354
+ return {
355
+ accessToken: {
356
+ value: apiTokenFromEnv,
357
+ expiry: "3021-12-31T23:59:59+00:00",
358
+ },
329
359
  };
330
- initialised = true;
331
- return;
332
360
  }
333
361
 
334
- const toml = TOML.parse(
335
- await readFile(path.join(os.homedir(), ".wrangler/config/default.toml"), {
336
- encoding: "utf-8",
337
- })
338
- );
339
- const { oauth_token, refresh_token, expiration_time } = toml as {
340
- oauth_token: string;
341
- refresh_token: string;
342
- expiration_time: string;
343
- };
362
+ // otherwise try loading from the user auth config file.
363
+ const { oauth_token, refresh_token, expiration_time, api_token } =
364
+ config || readAuthConfigFile();
365
+
344
366
  if (oauth_token) {
345
- LocalState.accessToken = { value: oauth_token, expiry: expiration_time };
346
- }
347
- if (refresh_token) {
348
- 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 {};
349
379
  }
350
- } catch (err) {
351
- // no config yet, let's chill
352
- // console.error(err);
380
+ } catch {
381
+ return {};
353
382
  }
354
- initialised = true;
355
383
  }
356
384
 
357
- // ugh. TODO: see fix from above.
358
- function throwIfNotInitialised() {
359
- if (initialised === false) {
360
- throw new Error(
361
- "did you forget to call initialise() from the user module?"
362
- );
363
- }
385
+ /**
386
+ * Run the initialisation of the auth state, in the case that something changed.
387
+ *
388
+ * This runs automatically whenever `writeAuthConfigFile` is run, so generally
389
+ * you won't need to call it yourself.
390
+ */
391
+ export function reinitialiseAuthTokens(): void;
392
+
393
+ /**
394
+ * Reinitialise auth state from an in-memory config, skipping
395
+ * over the part where we write a file and then read it back into memory
396
+ */
397
+ export function reinitialiseAuthTokens(config: UserAuthConfig): void;
398
+
399
+ export function reinitialiseAuthTokens(config?: UserAuthConfig): void {
400
+ LocalState = getAuthTokens(config);
364
401
  }
365
402
 
366
403
  export function getAPIToken(): string | undefined {
367
- throwIfNotInitialised();
404
+ if (LocalState.apiToken) {
405
+ console.warn(
406
+ "It looks like you have used Wrangler 1's `config` command to login with an API token.\n" +
407
+ "This is no longer supported in the current version of Wrangler.\n" +
408
+ "If you wish to authenticate via an API token then please set the `CLOUDFLARE_API_TOKEN` environment variable."
409
+ );
410
+ }
368
411
  return LocalState.accessToken?.value;
369
412
  }
370
413
 
@@ -578,8 +621,8 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
578
621
  `?response_type=code&` +
579
622
  `client_id=${encodeURIComponent(CLIENT_ID)}&` +
580
623
  `redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
581
- // @ts-expect-error we add offline_access manually
582
- `scope=${encodeURIComponent(scopes.concat("offline_access").join(" "))}&` +
624
+ // we add offline_access manually for every request
625
+ `scope=${encodeURIComponent([...scopes, "offline_access"].join(" "))}&` +
583
626
  `state=${stateQueryParam}&` +
584
627
  `code_challenge=${encodeURIComponent(codeChallenge)}&` +
585
628
  `code_challenge_method=S256`
@@ -800,19 +843,28 @@ function generateRandomState(lengthOfState: number): string {
800
843
  .join("");
801
844
  }
802
845
 
803
- async function writeToConfigFile(tokenData: AccessContext) {
804
- await mkdir(path.join(os.homedir(), ".wrangler/config/"), {
846
+ /**
847
+ * Writes a a wrangler config file (auth credentials) to disk,
848
+ * and updates the user auth state with the new credentials.
849
+ */
850
+ export function writeAuthConfigFile(config: UserAuthConfig) {
851
+ mkdirSync(path.join(os.homedir(), ".wrangler/config/"), {
805
852
  recursive: true,
806
853
  });
807
- await writeFile(
808
- path.join(os.homedir(), ".wrangler/config/default.toml"),
809
- `
810
- oauth_token = "${tokenData.token?.value || ""}"
811
- refresh_token = "${tokenData.refreshToken?.value}"
812
- expiration_time = "${tokenData.token?.expiry}"
813
- `,
854
+ writeFileSync(
855
+ path.join(os.homedir(), USER_AUTH_CONFIG_FILE),
856
+ TOML.stringify(config as TOML.JsonMap),
814
857
  { encoding: "utf-8" }
815
858
  );
859
+
860
+ reinitialiseAuthTokens();
861
+ }
862
+
863
+ export function readAuthConfigFile(): UserAuthConfig {
864
+ const toml = TOML.parse(
865
+ readFileSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE))
866
+ );
867
+ return toml;
816
868
  }
817
869
 
818
870
  type LoginProps = {
@@ -824,20 +876,29 @@ export async function loginOrRefreshIfRequired(
824
876
  ): Promise<boolean> {
825
877
  // TODO: if there already is a token, then try refreshing
826
878
  // TODO: ask permission before opening browser
827
- if (!LocalState.accessToken) {
879
+ if (!getAPIToken()) {
828
880
  // Not logged in.
829
881
  // If we are not interactive, we cannot ask the user to login
830
882
  return isInteractive && (await login());
831
883
  } else if (isAccessTokenExpired()) {
832
- return await refreshToken();
884
+ // We're logged in, but the refresh token seems to have expired,
885
+ // so let's try to refresh it
886
+ const didRefresh = await refreshToken();
887
+ if (didRefresh) {
888
+ // The token was refreshed, so we're done here
889
+ return true;
890
+ } else {
891
+ // If the refresh token isn't valid, then we ask the user to login again
892
+ return isInteractive && (await login());
893
+ }
833
894
  } else {
834
895
  return true;
835
896
  }
836
897
  }
837
898
 
838
899
  export async function login(props?: LoginProps): Promise<boolean> {
900
+ console.log("Attempting to login via OAuth...");
839
901
  const urlToOpen = await getAuthURL(props?.scopes);
840
- await openInBrowser(urlToOpen);
841
902
  let server: http.Server;
842
903
  let loginTimeoutHandle: NodeJS.Timeout;
843
904
  const timerPromise = new Promise<boolean>((resolve) => {
@@ -894,8 +955,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
894
955
  finish(false, new ErrorNoAuthCode());
895
956
  return;
896
957
  } else {
897
- const tokenData = await exchangeAuthCodeForAccessToken();
898
- await writeToConfigFile(tokenData);
958
+ const exchange = await exchangeAuthCodeForAccessToken();
959
+ writeAuthConfigFile({
960
+ oauth_token: exchange.token?.value ?? "",
961
+ expiration_time: exchange.token?.expiry,
962
+ refresh_token: exchange.refreshToken?.value,
963
+ });
899
964
  res.writeHead(307, {
900
965
  Location:
901
966
  "https://welcome.developers.workers.dev/wrangler-oauth-consent-granted",
@@ -903,9 +968,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
903
968
  res.end(() => {
904
969
  finish(true);
905
970
  });
906
- console.log(
907
- `Successfully configured. You can find your configuration file at: ${os.homedir()}/.wrangler/config/default.toml`
908
- );
971
+ console.log(`Successfully logged in.`);
909
972
 
910
973
  return;
911
974
  }
@@ -916,24 +979,30 @@ export async function login(props?: LoginProps): Promise<boolean> {
916
979
  server.listen(8976);
917
980
  });
918
981
 
982
+ await openInBrowser(urlToOpen);
983
+
919
984
  return Promise.race([timerPromise, loginPromise]);
920
985
  }
921
986
 
922
987
  /**
923
988
  * Checks to see if the access token has expired.
924
989
  */
925
- export function isAccessTokenExpired(): boolean {
926
- throwIfNotInitialised();
990
+ function isAccessTokenExpired(): boolean {
927
991
  const { accessToken } = LocalState;
928
992
  return Boolean(accessToken && new Date() >= new Date(accessToken.expiry));
929
993
  }
930
994
 
931
- export async function refreshToken(): Promise<boolean> {
932
- throwIfNotInitialised();
995
+ async function refreshToken(): Promise<boolean> {
933
996
  // refresh
934
997
  try {
935
- const refreshed = await exchangeRefreshTokenForAccessToken();
936
- await writeToConfigFile(refreshed);
998
+ const {
999
+ token: { value: oauth_token, expiry: expiration_time } = {
1000
+ value: "",
1001
+ expiry: "",
1002
+ },
1003
+ refreshToken: { value: refresh_token } = {},
1004
+ } = await exchangeRefreshTokenForAccessToken();
1005
+ writeAuthConfigFile({ oauth_token, expiration_time, refresh_token });
937
1006
  return true;
938
1007
  } catch (err) {
939
1008
  console.error(err);
@@ -942,10 +1011,28 @@ export async function refreshToken(): Promise<boolean> {
942
1011
  }
943
1012
 
944
1013
  export async function logout(): Promise<void> {
945
- throwIfNotInitialised();
946
- if (!LocalState.refreshToken) {
947
- console.log("Not logged in, exiting...");
948
- return;
1014
+ if (!LocalState.accessToken) {
1015
+ if (!LocalState.refreshToken) {
1016
+ console.log("Not logged in, exiting...");
1017
+ return;
1018
+ }
1019
+
1020
+ const body =
1021
+ `client_id=${encodeURIComponent(CLIENT_ID)}&` +
1022
+ `token_type_hint=refresh_token&` +
1023
+ `token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
1024
+
1025
+ const response = await fetch(REVOKE_URL, {
1026
+ method: "POST",
1027
+ body,
1028
+ headers: {
1029
+ "Content-Type": "application/x-www-form-urlencoded",
1030
+ },
1031
+ });
1032
+ await response.text(); // blank text? would be nice if it was something meaningful
1033
+ console.log(
1034
+ "💁 Wrangler is configured with an OAuth token. The token has been successfully revoked"
1035
+ );
949
1036
  }
950
1037
  const body =
951
1038
  `client_id=${encodeURIComponent(CLIENT_ID)}&` +
@@ -960,19 +1047,12 @@ export async function logout(): Promise<void> {
960
1047
  },
961
1048
  });
962
1049
  await response.text(); // blank text? would be nice if it was something meaningful
963
- console.log(
964
- "💁 Wrangler is configured with an OAuth token. The token has been successfully revoked"
965
- );
966
- // delete the file
967
- await rm(path.join(os.homedir(), ".wrangler/config/default.toml"));
968
- console.log(
969
- `Removing ${os.homedir()}/.wrangler/config/default.toml.. success!`
970
- );
1050
+ rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
1051
+ console.log(`Successfully logged out.`);
971
1052
  }
972
1053
 
973
- export function listScopes(): void {
974
- throwIfNotInitialised();
975
- console.log("💁 Available scopes:");
1054
+ export function listScopes(message = "💁 Available scopes:"): void {
1055
+ console.log(message);
976
1056
  const data = ScopeKeys.map((scope: Scope) => ({
977
1057
  Scope: scope,
978
1058
  Description: Scopes[scope],