wrangler 0.0.0-e3cab74 → 0.0.0-e3e3243

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 (66) hide show
  1. package/README.md +1 -1
  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 +10 -7
  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 +959 -324
  9. package/src/__tests__/dev.test.tsx +512 -84
  10. package/src/__tests__/guess-worker-format.test.ts +19 -17
  11. package/src/__tests__/helpers/mock-account-id.ts +22 -4
  12. package/src/__tests__/helpers/mock-cfetch.ts +3 -3
  13. package/src/__tests__/helpers/mock-console.ts +17 -9
  14. package/src/__tests__/helpers/mock-kv.ts +9 -16
  15. package/src/__tests__/helpers/mock-stdin.ts +103 -0
  16. package/src/__tests__/helpers/run-in-tmp.ts +3 -0
  17. package/src/__tests__/https-options.test.ts +127 -0
  18. package/src/__tests__/index.test.ts +128 -11
  19. package/src/__tests__/jest.setup.ts +16 -4
  20. package/src/__tests__/kv.test.ts +364 -125
  21. package/src/__tests__/logout.test.ts +15 -13
  22. package/src/__tests__/pages.test.ts +82 -0
  23. package/src/__tests__/publish.test.ts +1838 -735
  24. package/src/__tests__/r2.test.ts +4 -4
  25. package/src/__tests__/route.test.ts +45 -0
  26. package/src/__tests__/secret.test.ts +167 -68
  27. package/src/__tests__/tail.test.ts +2 -4
  28. package/src/__tests__/whoami.test.tsx +21 -7
  29. package/src/bundle.ts +27 -5
  30. package/src/cfetch/internal.ts +11 -5
  31. package/src/config/README.md +6 -6
  32. package/src/config/config.ts +54 -101
  33. package/src/config/environment.ts +67 -1
  34. package/src/config/index.ts +13 -3
  35. package/src/config/validation-helpers.ts +207 -23
  36. package/src/config/validation.ts +344 -129
  37. package/src/{api/preview.ts → create-worker-preview.ts} +39 -20
  38. package/src/{api/form_data.ts → create-worker-upload-form.ts} +1 -1
  39. package/src/dev/dev.tsx +392 -0
  40. package/src/dev/local.tsx +232 -0
  41. package/src/dev/remote.tsx +203 -0
  42. package/src/dev/use-esbuild.ts +104 -0
  43. package/src/entry.ts +192 -9
  44. package/src/https-options.ts +122 -0
  45. package/src/index.tsx +577 -515
  46. package/src/inspect.ts +9 -1
  47. package/src/intl-polyfill.d.ts +139 -0
  48. package/src/{kv.tsx → kv.ts} +97 -53
  49. package/src/miniflare-cli/README.md +30 -0
  50. package/src/miniflare-cli/enum-keys.ts +17 -0
  51. package/src/miniflare-cli/index.ts +37 -0
  52. package/src/module-collection.ts +90 -2
  53. package/src/open-in-browser.ts +14 -2
  54. package/src/pages.tsx +26 -10
  55. package/src/proxy.ts +104 -40
  56. package/src/publish.ts +53 -71
  57. package/src/reporting.ts +25 -9
  58. package/src/selfsigned.d.ts +29 -0
  59. package/src/sites.tsx +68 -29
  60. package/src/user.tsx +163 -85
  61. package/src/{api/worker.ts → worker.ts} +1 -24
  62. package/wrangler-dist/cli.js +5933 -3842
  63. package/wrangler-dist/cli.js.map +3 -3
  64. package/src/__tests__/helpers/mock-user.ts +0 -27
  65. package/src/dev.tsx +0 -961
  66. package/src/guess-worker-format.ts +0 -68
@@ -0,0 +1,29 @@
1
+ declare module "selfsigned" {
2
+ import type { pki } from "node-forge";
3
+
4
+ export type Attributes = pki.CertificateField[];
5
+
6
+ export interface Options {
7
+ days?: number;
8
+ keySize?: number;
9
+ extensions?: unknown[];
10
+ algorithm?: string;
11
+ pkcs7?: boolean;
12
+ clientCertificate?: boolean;
13
+ clientCertificateCN?: string;
14
+ }
15
+
16
+ export interface Certificate {
17
+ private: string;
18
+ public: string;
19
+ cert: string;
20
+ fingerprint: string;
21
+ }
22
+
23
+ export function generate(attrs?: Attributes, options?: Options): Certificate;
24
+ export function generate(
25
+ attrs: Attributes,
26
+ options: Options,
27
+ callback: (err: Error, certificate: Certificate) => void
28
+ ): void;
29
+ }
package/src/sites.tsx CHANGED
@@ -7,28 +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";
13
+ import type { KeyValue } from "./kv";
12
14
  import type { XXHashAPI } from "xxhash-wasm";
13
15
 
14
16
  /** Paths to always ignore. */
15
- const ALWAYS_IGNORE = ["node_modules"];
16
- const HIDDEN_FILES_TO_INCLUDE = [
17
+ const ALWAYS_IGNORE = new Set(["node_modules"]);
18
+ const HIDDEN_FILES_TO_INCLUDE = new Set([
17
19
  ".well-known", // See https://datatracker.ietf.org/doc/html/rfc8615
18
- ];
20
+ ]);
19
21
 
20
22
  async function* getFilesInFolder(dirPath: string): AsyncIterable<string> {
21
23
  const files = await readdir(dirPath, { withFileTypes: true });
22
24
  for (const file of files) {
23
25
  // Skip files that we never want to process.
24
- if (ALWAYS_IGNORE.some((p) => file.name === p)) {
26
+ if (ALWAYS_IGNORE.has(file.name)) {
25
27
  continue;
26
28
  }
27
29
  // Skip hidden files (starting with .) except for some special ones
28
- if (
29
- file.name.startsWith(".") &&
30
- !HIDDEN_FILES_TO_INCLUDE.some((p) => file.name === p)
31
- ) {
30
+ if (file.name.startsWith(".") && !HIDDEN_FILES_TO_INCLUDE.has(file.name)) {
32
31
  continue;
33
32
  }
34
33
  // TODO: follow symlinks??
@@ -128,36 +127,36 @@ export async function syncAssets(
128
127
  const keys = new Set(result.map((x) => x.name));
129
128
 
130
129
  const manifest: Record<string, string> = {};
131
- const upload: {
132
- key: string;
133
- value: string;
134
- base64: boolean;
135
- }[] = [];
130
+ const toUpload: KeyValue[] = [];
136
131
 
137
132
  const include = createPatternMatcher(siteAssets.includePatterns, false);
138
133
  const exclude = createPatternMatcher(siteAssets.excludePatterns, true);
139
134
  const hasher = await xxhash();
140
135
 
141
- // TODO: this can be more efficient by parallelising
142
- for await (const file of getFilesInFolder(siteAssets.baseDirectory)) {
143
- 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)) {
144
143
  continue;
145
144
  }
146
- if (exclude(file)) {
145
+ if (exclude(assetFile)) {
147
146
  continue;
148
147
  }
149
148
 
150
- await validateAssetSize(file);
151
- console.log(`reading ${file}...`);
152
- const content = await readFile(file, "base64");
149
+ await validateAssetSize(absAssetFile, assetFile);
150
+ console.log(`reading ${assetFile}...`);
151
+ const content = await readFile(absAssetFile, "base64");
153
152
 
154
- const assetKey = await hashAsset(hasher, file, content);
153
+ const assetKey = hashAsset(hasher, assetFile, content);
155
154
  validateAssetKey(assetKey);
156
155
 
157
156
  // now put each of the files into kv
158
157
  if (!keys.has(assetKey)) {
159
158
  console.log(`uploading as ${assetKey}...`);
160
- upload.push({
159
+ toUpload.push({
161
160
  key: assetKey,
162
161
  value: content,
163
162
  base64: true,
@@ -165,9 +164,26 @@ export async function syncAssets(
165
164
  } else {
166
165
  console.log(`skipping - already uploaded`);
167
166
  }
168
- 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;
169
171
  }
170
- await putBulkKeyValue(accountId, namespace, JSON.stringify(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
+
171
187
  return { manifest, namespace };
172
188
  }
173
189
 
@@ -183,11 +199,14 @@ function createPatternMatcher(
183
199
  }
184
200
  }
185
201
 
186
- async function validateAssetSize(filePath: string) {
187
- const { size } = await stat(filePath);
202
+ async function validateAssetSize(
203
+ absFilePath: string,
204
+ relativeFilePath: string
205
+ ) {
206
+ const { size } = await stat(absFilePath);
188
207
  if (size > 25 * 1024 * 1024) {
189
208
  throw new Error(
190
- `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`
191
210
  );
192
211
  }
193
212
  }
@@ -213,8 +232,23 @@ function urlSafe(filePath: string): string {
213
232
  * Information about the assets that should be uploaded
214
233
  */
215
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
+ */
216
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
+ */
217
248
  includePatterns: string[];
249
+ /**
250
+ * An array of patterns that match files that should not be uploaded.
251
+ */
218
252
  excludePatterns: string[];
219
253
  }
220
254
 
@@ -227,13 +261,18 @@ export interface AssetPaths {
227
261
  */
228
262
  export function getAssetPaths(
229
263
  config: Config,
230
- baseDirectory = config.site?.bucket,
264
+ assetDirectory = config.site?.bucket,
231
265
  includePatterns = config.site?.include ?? [],
232
266
  excludePatterns = config.site?.exclude ?? []
233
267
  ): undefined | AssetPaths {
234
- return baseDirectory
268
+ const baseDirectory = path.resolve(
269
+ path.dirname(config.configPath ?? "wrangler.toml")
270
+ );
271
+
272
+ return assetDirectory
235
273
  ? {
236
274
  baseDirectory,
275
+ assetDirectory,
237
276
  includePatterns,
238
277
  excludePatterns,
239
278
  }
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
  }
@@ -285,6 +314,8 @@ const Scopes = {
285
314
  "workers_scripts:write":
286
315
  "See and change Cloudflare Workers scripts, durable objects, subdomains, triggers, and tail data.",
287
316
  "workers_tail:read": "See Cloudflare Workers tail and script data.",
317
+ "pages:write":
318
+ "See and change Cloudflare Pages projects, settings and deployments.",
288
319
  "zone:read": "Grants read level access to account zone.",
289
320
  } as const;
290
321
 
@@ -309,60 +340,63 @@ const TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
309
340
  const CALLBACK_URL = "http://localhost:8976/oauth/callback";
310
341
  const REVOKE_URL = "https://dash.cloudflare.com/oauth2/revoke";
311
342
 
312
- const LocalState: State = {};
313
- let initialised = false;
343
+ let LocalState: State = getAuthTokens();
314
344
 
315
- // we do this because we have some async stuff
316
- // TODO: this should just happen in the top level
317
- // and we should figure out how to do top level await
318
- export async function initialise(): Promise<void> {
345
+ /**
346
+ * Compute the current auth tokens.
347
+ */
348
+ function getAuthTokens(): AuthTokens {
319
349
  // get refreshToken/accessToken from fs if exists
320
350
  try {
321
351
  // if the environment variable is available, use that
322
352
  const apiTokenFromEnv = getCloudflareAPITokenFromEnv();
323
353
  if (apiTokenFromEnv) {
324
- LocalState.accessToken = {
325
- value: apiTokenFromEnv,
326
- 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
+ },
327
359
  };
328
- initialised = true;
329
- return;
330
360
  }
331
361
 
332
- const toml = TOML.parse(
333
- await readFile(path.join(os.homedir(), ".wrangler/config/default.toml"), {
334
- encoding: "utf-8",
335
- })
336
- );
337
- const { oauth_token, refresh_token, expiration_time } = toml as {
338
- oauth_token: string;
339
- refresh_token: string;
340
- expiration_time: string;
341
- };
362
+ // otherwise try loading from the user auth config file.
363
+ const { oauth_token, refresh_token, expiration_time, api_token } =
364
+ readAuthConfigFile();
365
+
342
366
  if (oauth_token) {
343
- LocalState.accessToken = { value: oauth_token, expiry: expiration_time };
344
- }
345
- if (refresh_token) {
346
- 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 {};
347
379
  }
348
- } catch (err) {
349
- // no config yet, let's chill
350
- // console.error(err);
380
+ } catch {
381
+ return {};
351
382
  }
352
- initialised = true;
353
383
  }
354
384
 
355
- // ugh. TODO: see fix from above.
356
- function throwIfNotInitialised() {
357
- if (initialised === false) {
358
- throw new Error(
359
- "did you forget to call initialise() from the user module?"
360
- );
361
- }
385
+ /**
386
+ * Run the initialisation of the auth state, in the case that something changed.
387
+ */
388
+ export function reinitialiseAuthTokens(): void {
389
+ LocalState = getAuthTokens();
362
390
  }
363
391
 
364
392
  export function getAPIToken(): string | undefined {
365
- throwIfNotInitialised();
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
+ );
399
+ }
366
400
  return LocalState.accessToken?.value;
367
401
  }
368
402
 
@@ -576,8 +610,8 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
576
610
  `?response_type=code&` +
577
611
  `client_id=${encodeURIComponent(CLIENT_ID)}&` +
578
612
  `redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
579
- // @ts-expect-error we add offline_access manually
580
- `scope=${encodeURIComponent(scopes.concat("offline_access").join(" "))}&` +
613
+ // we add offline_access manually for every request
614
+ `scope=${encodeURIComponent([...scopes, "offline_access"].join(" "))}&` +
581
615
  `state=${stateQueryParam}&` +
582
616
  `code_challenge=${encodeURIComponent(codeChallenge)}&` +
583
617
  `code_challenge_method=S256`
@@ -798,39 +832,55 @@ function generateRandomState(lengthOfState: number): string {
798
832
  .join("");
799
833
  }
800
834
 
801
- async function writeToConfigFile(tokenData: AccessContext) {
802
- await mkdir(path.join(os.homedir(), ".wrangler/config/"), {
835
+ export function writeAuthConfigFile(config: UserAuthConfig) {
836
+ mkdirSync(path.join(os.homedir(), ".wrangler/config/"), {
803
837
  recursive: true,
804
838
  });
805
- await writeFile(
806
- path.join(os.homedir(), ".wrangler/config/default.toml"),
807
- `
808
- oauth_token = "${tokenData.token?.value || ""}"
809
- refresh_token = "${tokenData.refreshToken?.value}"
810
- expiration_time = "${tokenData.token?.expiry}"
811
- `,
839
+ writeFileSync(
840
+ path.join(os.homedir(), USER_AUTH_CONFIG_FILE),
841
+ TOML.stringify(config as TOML.JsonMap),
812
842
  { encoding: "utf-8" }
813
843
  );
814
844
  }
815
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
+
816
853
  type LoginProps = {
817
854
  scopes?: Scope[];
818
855
  };
819
856
 
820
- export async function loginOrRefreshIfRequired(): Promise<boolean> {
857
+ export async function loginOrRefreshIfRequired(
858
+ isInteractive = true
859
+ ): Promise<boolean> {
821
860
  // TODO: if there already is a token, then try refreshing
822
861
  // TODO: ask permission before opening browser
823
- if (!LocalState.accessToken) {
824
- // not logged in.
825
- 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());
826
866
  } else if (isAccessTokenExpired()) {
827
- return await refreshToken();
867
+ // We're logged in, but the refresh token seems to have expired,
868
+ // so let's try to refresh it
869
+ const didRefresh = await refreshToken();
870
+ if (didRefresh) {
871
+ // The token was refreshed, so we're done here
872
+ return true;
873
+ } else {
874
+ // If the refresh token isn't valid, then we ask the user to login again
875
+ return isInteractive && (await login());
876
+ }
828
877
  } else {
829
878
  return true;
830
879
  }
831
880
  }
832
881
 
833
882
  export async function login(props?: LoginProps): Promise<boolean> {
883
+ console.log("Attempting to login via OAuth...");
834
884
  const urlToOpen = await getAuthURL(props?.scopes);
835
885
  await openInBrowser(urlToOpen);
836
886
  let server: http.Server;
@@ -889,8 +939,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
889
939
  finish(false, new ErrorNoAuthCode());
890
940
  return;
891
941
  } else {
892
- const tokenData = await exchangeAuthCodeForAccessToken();
893
- await writeToConfigFile(tokenData);
942
+ const exchange = await exchangeAuthCodeForAccessToken();
943
+ writeAuthConfigFile({
944
+ oauth_token: exchange.token?.value ?? "",
945
+ expiration_time: exchange.token?.expiry,
946
+ refresh_token: exchange.refreshToken?.value,
947
+ });
894
948
  res.writeHead(307, {
895
949
  Location:
896
950
  "https://welcome.developers.workers.dev/wrangler-oauth-consent-granted",
@@ -898,9 +952,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
898
952
  res.end(() => {
899
953
  finish(true);
900
954
  });
901
- console.log(
902
- `Successfully configured. You can find your configuration file at: ${os.homedir()}/.wrangler/config/default.toml`
903
- );
955
+ console.log(`Successfully logged in.`);
904
956
 
905
957
  return;
906
958
  }
@@ -917,18 +969,22 @@ export async function login(props?: LoginProps): Promise<boolean> {
917
969
  /**
918
970
  * Checks to see if the access token has expired.
919
971
  */
920
- export function isAccessTokenExpired(): boolean {
921
- throwIfNotInitialised();
972
+ function isAccessTokenExpired(): boolean {
922
973
  const { accessToken } = LocalState;
923
974
  return Boolean(accessToken && new Date() >= new Date(accessToken.expiry));
924
975
  }
925
976
 
926
- export async function refreshToken(): Promise<boolean> {
927
- throwIfNotInitialised();
977
+ async function refreshToken(): Promise<boolean> {
928
978
  // refresh
929
979
  try {
930
- const refreshed = await exchangeRefreshTokenForAccessToken();
931
- await writeToConfigFile(refreshed);
980
+ const {
981
+ token: { value: oauth_token, expiry: expiration_time } = {
982
+ value: "",
983
+ expiry: "",
984
+ },
985
+ refreshToken: { value: refresh_token } = {},
986
+ } = await exchangeRefreshTokenForAccessToken();
987
+ writeAuthConfigFile({ oauth_token, expiration_time, refresh_token });
932
988
  return true;
933
989
  } catch (err) {
934
990
  console.error(err);
@@ -937,10 +993,28 @@ export async function refreshToken(): Promise<boolean> {
937
993
  }
938
994
 
939
995
  export async function logout(): Promise<void> {
940
- throwIfNotInitialised();
941
- if (!LocalState.refreshToken) {
942
- console.log("Not logged in, exiting...");
943
- return;
996
+ if (!LocalState.accessToken) {
997
+ if (!LocalState.refreshToken) {
998
+ console.log("Not logged in, exiting...");
999
+ return;
1000
+ }
1001
+
1002
+ const body =
1003
+ `client_id=${encodeURIComponent(CLIENT_ID)}&` +
1004
+ `token_type_hint=refresh_token&` +
1005
+ `token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
1006
+
1007
+ const response = await fetch(REVOKE_URL, {
1008
+ method: "POST",
1009
+ body,
1010
+ headers: {
1011
+ "Content-Type": "application/x-www-form-urlencoded",
1012
+ },
1013
+ });
1014
+ await response.text(); // blank text? would be nice if it was something meaningful
1015
+ console.log(
1016
+ "💁 Wrangler is configured with an OAuth token. The token has been successfully revoked"
1017
+ );
944
1018
  }
945
1019
  const body =
946
1020
  `client_id=${encodeURIComponent(CLIENT_ID)}&` +
@@ -955,19 +1029,12 @@ export async function logout(): Promise<void> {
955
1029
  },
956
1030
  });
957
1031
  await response.text(); // blank text? would be nice if it was something meaningful
958
- console.log(
959
- "💁 Wrangler is configured with an OAuth token. The token has been successfully revoked"
960
- );
961
- // delete the file
962
- await rm(path.join(os.homedir(), ".wrangler/config/default.toml"));
963
- console.log(
964
- `Removing ${os.homedir()}/.wrangler/config/default.toml.. success!`
965
- );
1032
+ rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
1033
+ console.log(`Successfully logged out.`);
966
1034
  }
967
1035
 
968
- export function listScopes(): void {
969
- throwIfNotInitialised();
970
- console.log("💁 Available scopes:");
1036
+ export function listScopes(message = "💁 Available scopes:"): void {
1037
+ console.log(message);
971
1038
  const data = ScopeKeys.map((scope: Scope) => ({
972
1039
  Scope: scope,
973
1040
  Description: Scopes[scope],
@@ -976,7 +1043,9 @@ export function listScopes(): void {
976
1043
  // TODO: maybe a good idea to show usage here
977
1044
  }
978
1045
 
979
- export async function getAccountId() {
1046
+ export async function getAccountId(
1047
+ isInteractive = true
1048
+ ): Promise<string | undefined> {
980
1049
  const apiToken = getAPIToken();
981
1050
  if (!apiToken) return;
982
1051
 
@@ -1007,7 +1076,7 @@ export async function getAccountId() {
1007
1076
  if (responseJSON.success === true) {
1008
1077
  if (responseJSON.result.length === 1) {
1009
1078
  accountId = responseJSON.result[0].account.id;
1010
- } else {
1079
+ } else if (isInteractive) {
1011
1080
  accountId = await new Promise((resolve) => {
1012
1081
  const accounts = responseJSON.result.map((x) => x.account);
1013
1082
  const { unmount } = render(
@@ -1020,6 +1089,15 @@ export async function getAccountId() {
1020
1089
  />
1021
1090
  );
1022
1091
  });
1092
+ } else {
1093
+ throw new Error(
1094
+ "More than one account available but unable to select one in non-interactive mode.\n" +
1095
+ `Please set the appropriate \`account_id\` in your \`wrangler.toml\` file.\n` +
1096
+ `Available accounts are ("<name>" - "<id>"):\n` +
1097
+ responseJSON.result
1098
+ .map((x) => ` "${x.account.name}" - "${x.account.id}")`)
1099
+ .join("\n")
1100
+ );
1023
1101
  }
1024
1102
  }
1025
1103
  return accountId;
@@ -1,7 +1,3 @@
1
- import { fetch } from "undici";
2
- import { previewToken } from "./preview";
3
- import type { CfPreviewToken } from "./preview";
4
-
5
1
  /**
6
2
  * A Cloudflare account.
7
3
  */
@@ -163,24 +159,5 @@ export interface CfWorkerInit {
163
159
  export interface CfWorkerContext {
164
160
  env: string | undefined;
165
161
  legacyEnv: boolean | undefined;
166
- zone: string | undefined;
167
- }
168
-
169
- /**
170
- * A stub to create a Cloudflare Worker preview.
171
- *
172
- * @example
173
- * const {value, host} = await createWorker(init, acct);
174
- */
175
- export async function createWorker(
176
- init: CfWorkerInit,
177
- account: CfAccount,
178
- ctx: CfWorkerContext
179
- ): Promise<CfPreviewToken> {
180
- const token = await previewToken(account, init, ctx);
181
- const response = await fetch(token.prewarmUrl.href, { method: "POST" });
182
- if (!response.ok) {
183
- // console.error("worker failed to prewarm: ", response.statusText);
184
- }
185
- return token;
162
+ zone: { id: string; host: string } | undefined;
186
163
  }