wrangler 0.0.0-e88a54e → 0.0.0-e95e5a0

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 (88) hide show
  1. package/README.md +25 -14
  2. package/bin/wrangler.js +13 -1
  3. package/miniflare-dist/index.mjs +4828 -21
  4. package/package.json +29 -24
  5. package/pages/functions/buildPlugin.ts +92 -0
  6. package/pages/functions/buildWorker.ts +80 -4
  7. package/pages/functions/filepath-routing.test.ts +11 -0
  8. package/pages/functions/filepath-routing.ts +8 -0
  9. package/pages/functions/routes.ts +6 -1
  10. package/pages/functions/template-plugin.ts +146 -0
  11. package/pages/functions/template-worker.ts +21 -8
  12. package/src/__tests__/config-cache.test.ts +36 -0
  13. package/src/__tests__/configuration.test.ts +530 -201
  14. package/src/__tests__/dev.test.tsx +284 -18
  15. package/src/__tests__/guess-worker-format.test.ts +28 -1
  16. package/src/__tests__/helpers/mock-cfetch.ts +5 -1
  17. package/src/__tests__/helpers/mock-console.ts +20 -3
  18. package/src/__tests__/helpers/mock-dialogs.ts +26 -1
  19. package/src/__tests__/helpers/mock-http-server.ts +46 -0
  20. package/src/__tests__/helpers/mock-istty.ts +27 -0
  21. package/src/__tests__/helpers/mock-oauth-flow.ts +255 -0
  22. package/src/__tests__/helpers/run-in-tmp.ts +7 -0
  23. package/src/__tests__/helpers/write-worker-source.ts +31 -0
  24. package/src/__tests__/https-options.test.ts +5 -2
  25. package/src/__tests__/index.test.ts +27 -769
  26. package/src/__tests__/init.test.ts +1516 -0
  27. package/src/__tests__/jest.setup.ts +4 -4
  28. package/src/__tests__/kv.test.ts +133 -61
  29. package/src/__tests__/logger.test.ts +115 -0
  30. package/src/__tests__/pages.test.ts +311 -26
  31. package/src/__tests__/publish.test.ts +2789 -618
  32. package/src/__tests__/r2.test.ts +26 -10
  33. package/src/__tests__/route.test.ts +4 -4
  34. package/src/__tests__/secret.test.ts +55 -34
  35. package/src/__tests__/tail.test.ts +146 -20
  36. package/src/__tests__/user.test.ts +131 -0
  37. package/src/__tests__/whoami.test.tsx +14 -9
  38. package/src/bundle.ts +30 -3
  39. package/src/cfetch/internal.ts +0 -2
  40. package/src/cli.ts +5 -11
  41. package/src/config/README.md +1 -1
  42. package/src/config/config.ts +20 -4
  43. package/src/config/environment.ts +32 -4
  44. package/src/config/index.ts +4 -3
  45. package/src/config/validation-helpers.ts +96 -7
  46. package/src/config/validation.ts +388 -54
  47. package/src/config-cache.ts +45 -0
  48. package/src/create-worker-preview.ts +22 -13
  49. package/src/create-worker-upload-form.ts +32 -9
  50. package/src/dev/dev-vars.ts +36 -0
  51. package/src/dev/dev.tsx +132 -78
  52. package/src/dev/local.tsx +101 -21
  53. package/src/dev/remote.tsx +23 -13
  54. package/src/dev/use-esbuild.ts +23 -5
  55. package/src/dialogs.tsx +16 -6
  56. package/src/entry.ts +83 -24
  57. package/src/environment-variables.ts +3 -1
  58. package/src/errors.ts +11 -0
  59. package/src/https-options.ts +3 -2
  60. package/src/index.tsx +689 -394
  61. package/src/inspect.ts +17 -11
  62. package/src/intl-polyfill.d.ts +139 -0
  63. package/src/logger.ts +66 -0
  64. package/src/miniflare-cli/index.ts +4 -0
  65. package/src/miniflare-cli/request-context.ts +40 -0
  66. package/src/module-collection.ts +7 -7
  67. package/src/open-in-browser.ts +5 -4
  68. package/src/package-manager.ts +9 -8
  69. package/src/pages.tsx +875 -56
  70. package/src/parse.ts +1 -1
  71. package/src/proxy.ts +28 -19
  72. package/src/publish.ts +263 -141
  73. package/src/sites.tsx +27 -14
  74. package/src/tail/index.ts +2 -2
  75. package/src/tail/printing.ts +13 -8
  76. package/src/update-check.ts +19 -0
  77. package/src/user.tsx +116 -41
  78. package/src/whoami.tsx +2 -1
  79. package/src/worker.ts +15 -6
  80. package/templates/gitignore +171 -0
  81. package/templates/static-asset-facade.js +1 -1
  82. package/wrangler-dist/cli.js +21162 -50227
  83. package/miniflare-dist/index.mjs.map +0 -7
  84. package/src/__tests__/dev2.test.tsx +0 -154
  85. package/src/__tests__/logout.test.ts +0 -52
  86. package/src/__tests__/sentry.test.ts +0 -146
  87. package/src/reporting.ts +0 -131
  88. package/wrangler-dist/cli.js.map +0 -7
package/src/sites.tsx CHANGED
@@ -9,6 +9,7 @@ import {
9
9
  putBulkKeyValue,
10
10
  deleteBulkKeyValue,
11
11
  } from "./kv";
12
+ import { logger } from "./logger";
12
13
  import type { Config } from "./config";
13
14
  import type { KeyValue } from "./kv";
14
15
  import type { XXHashAPI } from "xxhash-wasm";
@@ -82,7 +83,7 @@ async function createKVNamespaceIfNotAlreadyExisting(
82
83
 
83
84
  // else we make the namespace
84
85
  const id = await createNamespace(accountId, title);
85
- console.log(`🌀 Created namespace for Workers Site "${title}"`);
86
+ logger.log(`🌀 Created namespace for Workers Site "${title}"`);
86
87
 
87
88
  return {
88
89
  created: true,
@@ -105,7 +106,8 @@ export async function syncAssets(
105
106
  accountId: string,
106
107
  scriptName: string,
107
108
  siteAssets: AssetPaths | undefined,
108
- preview: boolean
109
+ preview: boolean,
110
+ dryRun: boolean | undefined
109
111
  ): Promise<{
110
112
  manifest: { [filePath: string]: string } | undefined;
111
113
  namespace: string | undefined;
@@ -114,17 +116,23 @@ export async function syncAssets(
114
116
  return { manifest: undefined, namespace: undefined };
115
117
  }
116
118
 
119
+ if (dryRun) {
120
+ logger.log("(Note: doing a dry run, not uploading or deleting anything.)");
121
+ return { manifest: undefined, namespace: undefined };
122
+ }
123
+
117
124
  const title = `__${scriptName}-workers_sites_assets${
118
125
  preview ? "_preview" : ""
119
126
  }`;
127
+
120
128
  const { id: namespace } = await createKVNamespaceIfNotAlreadyExisting(
121
129
  title,
122
130
  accountId
123
131
  );
124
132
 
125
133
  // let's get all the keys in this namespace
126
- const result = await listNamespaceKeys(accountId, namespace);
127
- const keys = new Set(result.map((x) => x.name));
134
+ const namespaceKeysResponse = await listNamespaceKeys(accountId, namespace);
135
+ const namespaceKeys = new Set(namespaceKeysResponse.map((x) => x.name));
128
136
 
129
137
  const manifest: Record<string, string> = {};
130
138
  const toUpload: KeyValue[] = [];
@@ -147,42 +155,47 @@ export async function syncAssets(
147
155
  }
148
156
 
149
157
  await validateAssetSize(absAssetFile, assetFile);
150
- console.log(`reading ${assetFile}...`);
158
+ logger.log(`Reading ${assetFile}...`);
151
159
  const content = await readFile(absAssetFile, "base64");
152
160
 
153
161
  const assetKey = hashAsset(hasher, assetFile, content);
154
162
  validateAssetKey(assetKey);
155
163
 
156
164
  // now put each of the files into kv
157
- if (!keys.has(assetKey)) {
158
- console.log(`uploading as ${assetKey}...`);
165
+ if (!namespaceKeys.has(assetKey)) {
166
+ logger.log(`Uploading as ${assetKey}...`);
159
167
  toUpload.push({
160
168
  key: assetKey,
161
169
  value: content,
162
170
  base64: true,
163
171
  });
164
172
  } else {
165
- console.log(`skipping - already uploaded`);
173
+ logger.log(`Skipping - already uploaded.`);
166
174
  }
167
175
 
168
176
  // remove the key from the set so we know what we've already uploaded
169
- keys.delete(assetKey);
177
+ namespaceKeys.delete(assetKey);
170
178
  manifest[path.relative(siteAssets.assetDirectory, absAssetFile)] = assetKey;
171
179
  }
172
180
 
173
181
  // keys now contains all the files we're deleting
174
- for (const key of keys) {
175
- console.log(`deleting ${key} from the asset store...`);
182
+ for (const key of namespaceKeys) {
183
+ logger.log(`Deleting ${key} from the asset store...`);
176
184
  }
177
185
 
178
186
  await Promise.all([
179
187
  // upload all the new assets
180
188
  putBulkKeyValue(accountId, namespace, toUpload, () => {}),
181
189
  // delete all the unused assets
182
- deleteBulkKeyValue(accountId, namespace, Array.from(keys), () => {}),
190
+ deleteBulkKeyValue(
191
+ accountId,
192
+ namespace,
193
+ Array.from(namespaceKeys),
194
+ () => {}
195
+ ),
183
196
  ]);
184
197
 
185
- console.log("↗️ Done syncing assets");
198
+ logger.log("↗️ Done syncing assets");
186
199
 
187
200
  return { manifest, namespace };
188
201
  }
@@ -264,7 +277,7 @@ export function getAssetPaths(
264
277
  assetDirectory = config.site?.bucket,
265
278
  includePatterns = config.site?.include ?? [],
266
279
  excludePatterns = config.site?.exclude ?? []
267
- ): undefined | AssetPaths {
280
+ ): AssetPaths | undefined {
268
281
  const baseDirectory = path.resolve(
269
282
  path.dirname(config.configPath ?? "wrangler.toml")
270
283
  );
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
  }[];
@@ -179,7 +179,7 @@ export type TailEventMessage = {
179
179
  * Until workers-types exposes individual types for export, we'll have
180
180
  * to just re-define these types ourselves.
181
181
  */
182
- event: RequestEvent | ScheduledEvent;
182
+ event: RequestEvent | ScheduledEvent | undefined | null;
183
183
  };
184
184
 
185
185
  /**
@@ -1,3 +1,4 @@
1
+ import { logger } from "../logger";
1
2
  import type { RequestEvent, ScheduledEvent, TailEventMessage } from ".";
2
3
  import type { Outcome } from "./filters";
3
4
  import type WebSocket from "ws";
@@ -12,25 +13,29 @@ export function prettyPrintLogs(data: WebSocket.RawData): void {
12
13
  ).toLocaleString();
13
14
  const outcome = prettifyOutcome(eventMessage.outcome);
14
15
 
15
- console.log(`"${cronPattern}" @ ${datetime} - ${outcome}`);
16
+ logger.log(`"${cronPattern}" @ ${datetime} - ${outcome}`);
16
17
  } else {
17
- const requestMethod = eventMessage.event.request.method.toUpperCase();
18
- const url = eventMessage.event.request.url;
18
+ const requestMethod = eventMessage.event?.request.method.toUpperCase();
19
+ const url = eventMessage.event?.request.url;
19
20
  const outcome = prettifyOutcome(eventMessage.outcome);
20
21
  const datetime = new Date(eventMessage.eventTimestamp).toLocaleString();
21
22
 
22
- console.log(`${requestMethod} ${url} - ${outcome} @ ${datetime}`);
23
+ logger.log(
24
+ url
25
+ ? `${requestMethod} ${url} - ${outcome} @ ${datetime}`
26
+ : `[missing request] - ${outcome} @ ${datetime}`
27
+ );
23
28
  }
24
29
 
25
30
  if (eventMessage.logs.length > 0) {
26
31
  eventMessage.logs.forEach(({ level, message }) => {
27
- console.log(` (${level}) ${message}`);
32
+ logger.log(` (${level})`, ...message);
28
33
  });
29
34
  }
30
35
 
31
36
  if (eventMessage.exceptions.length > 0) {
32
37
  eventMessage.exceptions.forEach(({ name, message }) => {
33
- console.error(` ${name}: ${message}`);
38
+ logger.error(` ${name}:`, message);
34
39
  });
35
40
  }
36
41
  }
@@ -40,9 +45,9 @@ export function jsonPrintLogs(data: WebSocket.RawData): void {
40
45
  }
41
46
 
42
47
  function isScheduledEvent(
43
- event: RequestEvent | ScheduledEvent
48
+ event: RequestEvent | ScheduledEvent | undefined | null
44
49
  ): event is ScheduledEvent {
45
- return "cron" in event;
50
+ return Boolean(event && "cron" in event);
46
51
  }
47
52
 
48
53
  function prettifyOutcome(outcome: Outcome): string {
@@ -0,0 +1,19 @@
1
+ import chalk from "chalk";
2
+ import checkForUpdate from "update-check";
3
+ import pkg from "../package.json";
4
+
5
+ export async function updateCheck(): Promise<string> {
6
+ let update = null;
7
+ try {
8
+ // default cache for update check is 1 day
9
+ update = await checkForUpdate(pkg, {
10
+ distTag: pkg.version.startsWith("0.0.0") ? "beta" : "latest",
11
+ });
12
+ } catch (err) {
13
+ // ignore error
14
+ }
15
+
16
+ if (update) return `(update available ${chalk.green(update.latest)})`;
17
+
18
+ return "";
19
+ }
package/src/user.tsx CHANGED
@@ -220,9 +220,11 @@ import Table from "ink-table";
220
220
  import React from "react";
221
221
  import { fetch } from "undici";
222
222
  import { getCloudflareApiBaseUrl } from "./cfetch";
223
+ import { purgeConfigCaches } from "./config-cache";
223
224
  import { getEnvironmentVariableFactory } from "./environment-variables";
225
+ import { logger } from "./logger";
224
226
  import openInBrowser from "./open-in-browser";
225
- import { readFileSync } from "./parse";
227
+ import { parseTOML, readFileSync } from "./parse";
226
228
  import type { Item as SelectInputItem } from "ink-select-input/build/SelectInput";
227
229
  import type { ParsedUrlQuery } from "node:querystring";
228
230
  import type { Response } from "undici";
@@ -345,7 +347,7 @@ let LocalState: State = getAuthTokens();
345
347
  /**
346
348
  * Compute the current auth tokens.
347
349
  */
348
- function getAuthTokens(): AuthTokens {
350
+ function getAuthTokens(config?: UserAuthConfig): AuthTokens {
349
351
  // get refreshToken/accessToken from fs if exists
350
352
  try {
351
353
  // if the environment variable is available, use that
@@ -361,7 +363,7 @@ function getAuthTokens(): AuthTokens {
361
363
 
362
364
  // otherwise try loading from the user auth config file.
363
365
  const { oauth_token, refresh_token, expiration_time, api_token } =
364
- readAuthConfigFile();
366
+ config || readAuthConfigFile();
365
367
 
366
368
  if (oauth_token) {
367
369
  return {
@@ -384,20 +386,44 @@ function getAuthTokens(): AuthTokens {
384
386
 
385
387
  /**
386
388
  * Run the initialisation of the auth state, in the case that something changed.
389
+ *
390
+ * This runs automatically whenever `writeAuthConfigFile` is run, so generally
391
+ * you won't need to call it yourself.
387
392
  */
388
- export function reinitialiseAuthTokens(): void {
389
- LocalState = getAuthTokens();
393
+ export function reinitialiseAuthTokens(): void;
394
+
395
+ /**
396
+ * Reinitialise auth state from an in-memory config, skipping
397
+ * over the part where we write a file and then read it back into memory
398
+ */
399
+ export function reinitialiseAuthTokens(config: UserAuthConfig): void;
400
+
401
+ export function reinitialiseAuthTokens(config?: UserAuthConfig): void {
402
+ LocalState = getAuthTokens(config);
390
403
  }
391
404
 
392
405
  export function getAPIToken(): string | undefined {
393
406
  if (LocalState.apiToken) {
394
- console.warn(
407
+ logger.warn(
395
408
  "It looks like you have used Wrangler 1's `config` command to login with an API token.\n" +
396
409
  "This is no longer supported in the current version of Wrangler.\n" +
397
410
  "If you wish to authenticate via an API token then please set the `CLOUDFLARE_API_TOKEN` environment variable."
398
411
  );
399
412
  }
400
- return LocalState.accessToken?.value;
413
+
414
+ const localAPIToken = getCloudflareAPITokenFromEnv();
415
+
416
+ if (
417
+ !process.stdout.isTTY &&
418
+ !localAPIToken &&
419
+ !LocalState.accessToken?.value
420
+ ) {
421
+ throw new Error(
422
+ "In a non-interactive environment, it's necessary to set a CLOUDFLARE_API_TOKEN environment variable for wrangler to work. Please go to https://developers.cloudflare.com/api/tokens/create/ for instructions on how to create an api token, and assign its value to CLOUDFLARE_API_TOKEN."
423
+ );
424
+ }
425
+
426
+ return localAPIToken ?? LocalState.accessToken?.value;
401
427
  }
402
428
 
403
429
  interface AccessContext {
@@ -584,8 +610,8 @@ function isReturningFromAuthServer(query: ParsedUrlQuery): boolean {
584
610
 
585
611
  const stateQueryParam = query.state;
586
612
  if (stateQueryParam !== state.stateQueryParam) {
587
- console.warn(
588
- "state query string parameter doesn't match the one sent! Possible malicious activity somewhere."
613
+ logger.warn(
614
+ "Received query string parameter doesn't match the one sent! Possible malicious activity somewhere."
589
615
  );
590
616
  throw new ErrorInvalidReturnedStateParam();
591
617
  }
@@ -610,8 +636,8 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
610
636
  `?response_type=code&` +
611
637
  `client_id=${encodeURIComponent(CLIENT_ID)}&` +
612
638
  `redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
613
- // @ts-expect-error we add offline_access manually
614
- `scope=${encodeURIComponent(scopes.concat("offline_access").join(" "))}&` +
639
+ // we add offline_access manually for every request
640
+ `scope=${encodeURIComponent([...scopes, "offline_access"].join(" "))}&` +
615
641
  `state=${stateQueryParam}&` +
616
642
  `code_challenge=${encodeURIComponent(codeChallenge)}&` +
617
643
  `code_challenge_method=S256`
@@ -634,7 +660,7 @@ type TokenResponse =
634
660
  */
635
661
  async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
636
662
  if (!LocalState.refreshToken) {
637
- console.warn("No refresh token is present.");
663
+ logger.warn("No refresh token is present.");
638
664
  }
639
665
 
640
666
  const body =
@@ -649,8 +675,27 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
649
675
  "Content-Type": "application/x-www-form-urlencoded",
650
676
  },
651
677
  });
678
+
652
679
  if (response.status >= 400) {
653
- throw await response.json();
680
+ let tokenExchangeResErr = undefined;
681
+
682
+ try {
683
+ tokenExchangeResErr = await response.text();
684
+ tokenExchangeResErr = JSON.parse(tokenExchangeResErr);
685
+ } catch (e) {
686
+ // If it can't parse to JSON ignore the error
687
+ }
688
+
689
+ if (tokenExchangeResErr !== undefined) {
690
+ // We will throw the parsed error if it parsed correctly, otherwise we throw an unknown error.
691
+ throw typeof tokenExchangeResErr === "string"
692
+ ? new Error(tokenExchangeResErr)
693
+ : tokenExchangeResErr;
694
+ } else {
695
+ throw new ErrorUnknown(
696
+ "Failed to parse Error from exchangeRefreshTokenForAccessToken"
697
+ );
698
+ }
654
699
  } else {
655
700
  try {
656
701
  const json = (await response.json()) as TokenResponse;
@@ -687,18 +732,6 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
687
732
  };
688
733
  return accessContext;
689
734
  } catch (error) {
690
- switch (error) {
691
- case "invalid_grant":
692
- console.warn(
693
- "Expired! Auth code or refresh token needs to be renewed."
694
- );
695
- // alert("Redirecting to auth server to obtain a new auth grant code.");
696
- // TODO: return refreshAuthCodeOrRefreshToken();
697
- break;
698
- default:
699
- console.error(error);
700
- break;
701
- }
702
735
  if (typeof error === "string") {
703
736
  throw toErrorClass(error);
704
737
  } else {
@@ -715,9 +748,9 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
715
748
  const { authorizationCode, codeVerifier = "" } = LocalState;
716
749
 
717
750
  if (!codeVerifier) {
718
- console.warn("No code verifier is being sent.");
751
+ logger.warn("No code verifier is being sent.");
719
752
  } else if (!authorizationCode) {
720
- console.warn("No authorization grant code is being passed.");
753
+ logger.warn("No authorization grant code is being passed.");
721
754
  }
722
755
 
723
756
  const body =
@@ -738,7 +771,7 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
738
771
  const { error } = (await response.json()) as { error: string };
739
772
  // .catch((_) => ({ error: "invalid_json" }));
740
773
  if (error === "invalid_grant") {
741
- console.log("Expired! Auth code or refresh token needs to be renewed.");
774
+ logger.log("Expired! Auth code or refresh token needs to be renewed.");
742
775
  // alert("Redirecting to auth server to obtain a new auth grant code.");
743
776
  // TODO: return refreshAuthCodeOrRefreshToken();
744
777
  }
@@ -832,6 +865,10 @@ function generateRandomState(lengthOfState: number): string {
832
865
  .join("");
833
866
  }
834
867
 
868
+ /**
869
+ * Writes a a wrangler config file (auth credentials) to disk,
870
+ * and updates the user auth state with the new credentials.
871
+ */
835
872
  export function writeAuthConfigFile(config: UserAuthConfig) {
836
873
  mkdirSync(path.join(os.homedir(), ".wrangler/config/"), {
837
874
  recursive: true,
@@ -841,10 +878,12 @@ export function writeAuthConfigFile(config: UserAuthConfig) {
841
878
  TOML.stringify(config as TOML.JsonMap),
842
879
  { encoding: "utf-8" }
843
880
  );
881
+
882
+ reinitialiseAuthTokens();
844
883
  }
845
884
 
846
- function readAuthConfigFile(): UserAuthConfig {
847
- const toml = TOML.parse(
885
+ export function readAuthConfigFile(): UserAuthConfig {
886
+ const toml = parseTOML(
848
887
  readFileSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE))
849
888
  );
850
889
  return toml;
@@ -864,21 +903,29 @@ export async function loginOrRefreshIfRequired(
864
903
  // If we are not interactive, we cannot ask the user to login
865
904
  return isInteractive && (await login());
866
905
  } else if (isAccessTokenExpired()) {
867
- return await refreshToken();
906
+ // We're logged in, but the refresh token seems to have expired,
907
+ // so let's try to refresh it
908
+ const didRefresh = await refreshToken();
909
+ if (didRefresh) {
910
+ // The token was refreshed, so we're done here
911
+ return true;
912
+ } else {
913
+ // If the refresh token isn't valid, then we ask the user to login again
914
+ return isInteractive && (await login());
915
+ }
868
916
  } else {
869
917
  return true;
870
918
  }
871
919
  }
872
920
 
873
921
  export async function login(props?: LoginProps): Promise<boolean> {
874
- console.log("Attempting to login via OAuth...");
922
+ logger.log("Attempting to login via OAuth...");
875
923
  const urlToOpen = await getAuthURL(props?.scopes);
876
- await openInBrowser(urlToOpen);
877
924
  let server: http.Server;
878
925
  let loginTimeoutHandle: NodeJS.Timeout;
879
926
  const timerPromise = new Promise<boolean>((resolve) => {
880
927
  loginTimeoutHandle = setTimeout(() => {
881
- console.error(
928
+ logger.error(
882
929
  "Timed out waiting for authorization code, please try again."
883
930
  );
884
931
  server.close();
@@ -914,7 +961,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
914
961
  res.end(() => {
915
962
  finish(false);
916
963
  });
917
- console.log(
964
+ logger.error(
918
965
  "Error: Consent denied. You must grant consent to Wrangler in order to login.\n" +
919
966
  "If you don't want to do this consider passing an API token via the `CLOUDFLARE_API_TOKEN` environment variable"
920
967
  );
@@ -943,7 +990,9 @@ export async function login(props?: LoginProps): Promise<boolean> {
943
990
  res.end(() => {
944
991
  finish(true);
945
992
  });
946
- console.log(`Successfully logged in.`);
993
+ logger.log(`Successfully logged in.`);
994
+
995
+ purgeConfigCaches();
947
996
 
948
997
  return;
949
998
  }
@@ -954,6 +1003,8 @@ export async function login(props?: LoginProps): Promise<boolean> {
954
1003
  server.listen(8976);
955
1004
  });
956
1005
 
1006
+ await openInBrowser(urlToOpen);
1007
+
957
1008
  return Promise.race([timerPromise, loginPromise]);
958
1009
  }
959
1010
 
@@ -978,7 +1029,6 @@ async function refreshToken(): Promise<boolean> {
978
1029
  writeAuthConfigFile({ oauth_token, expiration_time, refresh_token });
979
1030
  return true;
980
1031
  } catch (err) {
981
- console.error(err);
982
1032
  return false;
983
1033
  }
984
1034
  }
@@ -986,7 +1036,7 @@ async function refreshToken(): Promise<boolean> {
986
1036
  export async function logout(): Promise<void> {
987
1037
  if (!LocalState.accessToken) {
988
1038
  if (!LocalState.refreshToken) {
989
- console.log("Not logged in, exiting...");
1039
+ logger.log("Not logged in, exiting...");
990
1040
  return;
991
1041
  }
992
1042
 
@@ -1003,7 +1053,7 @@ export async function logout(): Promise<void> {
1003
1053
  },
1004
1054
  });
1005
1055
  await response.text(); // blank text? would be nice if it was something meaningful
1006
- console.log(
1056
+ logger.log(
1007
1057
  "💁 Wrangler is configured with an OAuth token. The token has been successfully revoked"
1008
1058
  );
1009
1059
  }
@@ -1021,11 +1071,11 @@ export async function logout(): Promise<void> {
1021
1071
  });
1022
1072
  await response.text(); // blank text? would be nice if it was something meaningful
1023
1073
  rmSync(path.join(os.homedir(), USER_AUTH_CONFIG_FILE));
1024
- console.log(`Successfully logged out.`);
1074
+ logger.log(`Successfully logged out.`);
1025
1075
  }
1026
1076
 
1027
1077
  export function listScopes(message = "💁 Available scopes:"): void {
1028
- console.log(message);
1078
+ logger.log(message);
1029
1079
  const data = ScopeKeys.map((scope: Scope) => ({
1030
1080
  Scope: scope,
1031
1081
  Description: Scopes[scope],
@@ -1090,6 +1140,11 @@ export async function getAccountId(
1090
1140
  .join("\n")
1091
1141
  );
1092
1142
  }
1143
+ } else {
1144
+ if (!isInteractive)
1145
+ throw new Error(
1146
+ `Failed to automatically retrieve account IDs for the logged in user. In a non-interactive environment, it is mandatory to specify an account ID, either by assigning its value to CLOUDFLARE_ACCOUNT_ID, or as \`account_id\` in your \`wrangler.toml\` file.`
1147
+ );
1093
1148
  }
1094
1149
  return accountId;
1095
1150
  }
@@ -1116,3 +1171,23 @@ export function ChooseAccount(props: {
1116
1171
  </>
1117
1172
  );
1118
1173
  }
1174
+
1175
+ /**
1176
+ * Ensure that a user is logged in, and a valid account_id is available.
1177
+ */
1178
+ export async function requireAuth(config: {
1179
+ account_id?: string;
1180
+ }): Promise<string> {
1181
+ const isInteractive = process.stdin.isTTY;
1182
+ const loggedIn = await loginOrRefreshIfRequired(isInteractive);
1183
+ if (!loggedIn) {
1184
+ // didn't login, let's just quit
1185
+ throw new Error("Did not login, quitting...");
1186
+ }
1187
+ const accountId = config.account_id || (await getAccountId(isInteractive));
1188
+ if (!accountId) {
1189
+ throw new Error("No account id found, quitting...");
1190
+ }
1191
+
1192
+ return accountId;
1193
+ }
package/src/whoami.tsx CHANGED
@@ -2,10 +2,11 @@ import { Text, render } from "ink";
2
2
  import Table from "ink-table";
3
3
  import React from "react";
4
4
  import { fetchListResult, fetchResult } from "./cfetch";
5
+ import { logger } from "./logger";
5
6
  import { getAPIToken } from "./user";
6
7
 
7
8
  export async function whoami() {
8
- console.log("Getting User settings...");
9
+ logger.log("Getting User settings...");
9
10
  const user = await getUserInfo();
10
11
  render(<WhoAmI user={user}></WhoAmI>);
11
12
  }
package/src/worker.ts CHANGED
@@ -75,7 +75,7 @@ interface CfKvNamespace {
75
75
  }
76
76
 
77
77
  /**
78
- * A binding to a wasm module (in service worker format)
78
+ * A binding to a wasm module (in service-worker format)
79
79
  */
80
80
 
81
81
  interface CfWasmModuleBindings {
@@ -83,13 +83,21 @@ interface CfWasmModuleBindings {
83
83
  }
84
84
 
85
85
  /**
86
- * A binding to a text blob (in service worker format)
86
+ * A binding to a text blob (in service-worker format)
87
87
  */
88
88
 
89
89
  interface CfTextBlobBindings {
90
90
  [key: string]: string;
91
91
  }
92
92
 
93
+ /**
94
+ * A binding to a data blob (in service-worker format)
95
+ */
96
+
97
+ interface CfDataBlobBindings {
98
+ [key: string]: string;
99
+ }
100
+
93
101
  /**
94
102
  * A Durable Object.
95
103
  */
@@ -137,7 +145,7 @@ export interface CfWorkerInit {
137
145
  /**
138
146
  * The list of additional modules.
139
147
  */
140
- modules: undefined | CfModule[];
148
+ modules: CfModule[] | undefined;
141
149
  /**
142
150
  * All the bindings
143
151
  */
@@ -146,14 +154,15 @@ export interface CfWorkerInit {
146
154
  kv_namespaces: CfKvNamespace[] | undefined;
147
155
  wasm_modules: CfWasmModuleBindings | undefined;
148
156
  text_blobs: CfTextBlobBindings | undefined;
157
+ data_blobs: CfDataBlobBindings | undefined;
149
158
  durable_objects: { bindings: CfDurableObject[] } | undefined;
150
159
  r2_buckets: CfR2Bucket[] | undefined;
151
160
  unsafe: CfUnsafeBinding[] | undefined;
152
161
  };
153
- migrations: undefined | CfDurableObjectMigrations;
162
+ migrations: CfDurableObjectMigrations | undefined;
154
163
  compatibility_date: string | undefined;
155
- compatibility_flags: undefined | string[];
156
- usage_model: undefined | "bundled" | "unbound";
164
+ compatibility_flags: string[] | undefined;
165
+ usage_model: "bundled" | "unbound" | undefined;
157
166
  }
158
167
 
159
168
  export interface CfWorkerContext {