wrangler 2.6.1 → 2.7.0

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 (130) hide show
  1. package/bin/wrangler.js +9 -1
  2. package/miniflare-dist/index.mjs +1 -1
  3. package/package.json +12 -10
  4. package/src/__tests__/api-dev.test.ts +65 -36
  5. package/src/__tests__/api-devregistry.test.js +14 -6
  6. package/src/__tests__/configuration.test.ts +2 -31
  7. package/src/__tests__/{d1.test.ts → d1/d1.test.ts} +48 -5
  8. package/src/__tests__/d1/splitter.test.ts +255 -0
  9. package/src/__tests__/delete.test.ts +5 -2
  10. package/src/__tests__/deployments.test.ts +20 -6
  11. package/src/__tests__/dev.test.tsx +52 -19
  12. package/src/__tests__/generate.test.ts +7 -4
  13. package/src/__tests__/helpers/mock-auth-domain.ts +20 -0
  14. package/src/__tests__/helpers/mock-cfetch.ts +2 -57
  15. package/src/__tests__/helpers/mock-dialogs.ts +70 -86
  16. package/src/__tests__/helpers/mock-oauth-flow.ts +64 -49
  17. package/src/__tests__/helpers/mock-process.ts +8 -13
  18. package/src/__tests__/helpers/msw/blob-worker.cjs +19 -0
  19. package/src/__tests__/helpers/msw/read-file-sync.js +61 -0
  20. package/src/__tests__/index.test.ts +46 -42
  21. package/src/__tests__/init.test.ts +782 -522
  22. package/src/__tests__/jest.setup.ts +20 -24
  23. package/src/__tests__/kv.test.ts +286 -173
  24. package/src/__tests__/logout.test.ts +1 -1
  25. package/src/__tests__/metrics.test.ts +5 -7
  26. package/src/__tests__/middleware.scheduled.test.ts +40 -30
  27. package/src/__tests__/middleware.test.ts +144 -120
  28. package/src/__tests__/pages.test.ts +1618 -1161
  29. package/src/__tests__/publish.test.ts +174 -125
  30. package/src/__tests__/r2.test.ts +2 -2
  31. package/src/__tests__/secret.test.ts +183 -126
  32. package/src/__tests__/tail.test.ts +6 -0
  33. package/src/__tests__/tsconfig-sanity.ts +12 -0
  34. package/src/__tests__/tsconfig.json +8 -0
  35. package/src/__tests__/tsconfig.tsbuildinfo +1 -0
  36. package/src/__tests__/whoami.test.tsx +1 -96
  37. package/src/api/dev.ts +78 -41
  38. package/src/api/index.ts +1 -1
  39. package/src/{bundle-reporter.tsx → bundle-reporter.ts} +0 -0
  40. package/src/cfetch/index.ts +0 -2
  41. package/src/cfetch/internal.ts +16 -18
  42. package/src/cli.ts +2 -2
  43. package/src/config/index.ts +2 -1
  44. package/src/config/validation.ts +1 -2
  45. package/src/create-worker-upload-form.ts +2 -2
  46. package/src/d1/{delete.tsx → delete.ts} +0 -0
  47. package/src/d1/execute.tsx +8 -37
  48. package/src/d1/migrations/apply.tsx +32 -19
  49. package/src/d1/migrations/{index.tsx → index.ts} +0 -0
  50. package/src/d1/splitter.ts +161 -0
  51. package/src/d1/{types.tsx → types.ts} +0 -0
  52. package/src/delete.ts +3 -8
  53. package/src/deployments.ts +6 -0
  54. package/src/deprecated/index.ts +2 -295
  55. package/src/dev/dev.tsx +2 -2
  56. package/src/dev/{get-local-persistence-path.tsx → get-local-persistence-path.ts} +0 -0
  57. package/src/dev/local.tsx +16 -4
  58. package/src/dev/remote.tsx +28 -1
  59. package/src/dev/start-server.ts +19 -11
  60. package/src/dev/use-esbuild.ts +1 -1
  61. package/src/{dev-registry.tsx → dev-registry.ts} +0 -0
  62. package/src/dev.tsx +35 -11
  63. package/src/dialogs.ts +136 -0
  64. package/src/dispatch-namespace.ts +1 -1
  65. package/src/docs/index.ts +97 -0
  66. package/src/environment-variables/factory.ts +88 -0
  67. package/src/environment-variables/misc-variables.ts +30 -0
  68. package/src/generate/index.ts +300 -0
  69. package/src/{index.tsx → index.ts} +16 -10
  70. package/src/init.ts +106 -60
  71. package/src/jest.d.ts +4 -0
  72. package/src/logger.ts +15 -3
  73. package/src/metrics/metrics-config.ts +1 -1
  74. package/src/metrics/send-event.ts +2 -1
  75. package/src/miniflare-cli/assets.ts +4 -0
  76. package/src/miniflare-cli/index.ts +1 -5
  77. package/src/miniflare-cli/tsconfig.json +9 -0
  78. package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -0
  79. package/src/miniflare-cli/types.ts +11 -0
  80. package/src/pages/{build.tsx → build.ts} +0 -0
  81. package/src/pages/{deployment-tails.tsx → deployment-tails.ts} +0 -0
  82. package/src/pages/{dev.tsx → dev.ts} +53 -55
  83. package/src/pages/functions/buildWorker.ts +1 -1
  84. package/src/pages/functions/tsconfig.json +8 -0
  85. package/src/pages/functions/tsconfig.tsbuildinfo +1 -0
  86. package/src/pages/{functions.tsx → functions.ts} +0 -0
  87. package/src/pages/{hash.tsx → hash.ts} +0 -0
  88. package/src/pages/{index.tsx → index.ts} +0 -0
  89. package/src/pages/projects.tsx +3 -5
  90. package/src/pages/publish.tsx +16 -5
  91. package/src/pages/upload.tsx +27 -6
  92. package/src/publish/publish.ts +9 -7
  93. package/src/pubsub/{pubsub-commands.tsx → pubsub-commands.ts} +1 -1
  94. package/src/secret/index.ts +1 -1
  95. package/src/{sites.tsx → sites.ts} +0 -0
  96. package/src/tail/index.ts +2 -3
  97. package/src/tsconfig-sanity.ts +16 -0
  98. package/src/user/access.ts +0 -1
  99. package/src/user/auth-variables.ts +113 -0
  100. package/src/user/choose-account.tsx +1 -31
  101. package/src/user/index.ts +0 -1
  102. package/src/user/{user.tsx → user.ts} +107 -73
  103. package/src/{whoami.tsx → whoami.ts} +37 -71
  104. package/templates/__tests__/tsconfig-sanity.ts +12 -0
  105. package/templates/__tests__/tsconfig.json +8 -0
  106. package/templates/__tests__/tsconfig.tsbuildinfo +1 -0
  107. package/templates/d1-beta-facade.js +36 -0
  108. package/templates/facade.d.ts +14 -0
  109. package/templates/first-party-worker-module-facade.ts +4 -3
  110. package/templates/format-dev-errors.ts +7 -6
  111. package/templates/init-tests/test-jest-new-worker.js +3 -5
  112. package/templates/init-tests/test-vitest-new-worker.js +3 -5
  113. package/templates/init-tests/test-vitest-new-worker.ts +25 -0
  114. package/templates/middleware/loader-modules.ts +0 -2
  115. package/templates/middleware/loader-sw.ts +6 -0
  116. package/templates/pages-dev-pipeline.ts +4 -1
  117. package/templates/pages-shim.ts +4 -1
  118. package/templates/pages-template-plugin.ts +12 -7
  119. package/templates/serve-static-assets.ts +16 -14
  120. package/templates/tsconfig-sanity.ts +11 -0
  121. package/templates/tsconfig.init.json +106 -0
  122. package/templates/tsconfig.json +5 -103
  123. package/templates/tsconfig.tsbuildinfo +1 -0
  124. package/wrangler-dist/cli.d.ts +58 -60
  125. package/wrangler-dist/cli.js +34498 -55459
  126. package/wrangler-dist/wasm-sync.wasm +0 -0
  127. package/src/__tests__/dialogs.test.tsx +0 -40
  128. package/src/dialogs.tsx +0 -168
  129. package/src/environment-variables.ts +0 -50
  130. package/src/user/env-vars.ts +0 -46
@@ -40,6 +40,10 @@ export function Options(yargs: Argv) {
40
40
  type: "string",
41
41
  description: "The name of the project you want to deploy to",
42
42
  },
43
+ "skip-caching": {
44
+ type: "boolean",
45
+ description: "Skip asset caching which speeds up builds",
46
+ },
43
47
  })
44
48
  .epilogue(pagesBetaWarning);
45
49
  }
@@ -47,6 +51,7 @@ export function Options(yargs: Argv) {
47
51
  export const Handler = async ({
48
52
  directory,
49
53
  outputManifestPath,
54
+ skipCaching,
50
55
  }: UploadArgs) => {
51
56
  if (!directory) {
52
57
  throw new FatalError("Must specify a directory.", 1);
@@ -59,6 +64,7 @@ export const Handler = async ({
59
64
  const manifest = await upload({
60
65
  directory,
61
66
  jwt: process.env.CF_PAGES_UPLOAD_JWT,
67
+ skipCaching: skipCaching ?? false,
62
68
  });
63
69
 
64
70
  if (outputManifestPath) {
@@ -74,8 +80,14 @@ export const upload = async (
74
80
  | {
75
81
  directory: string;
76
82
  jwt: string;
83
+ skipCaching: boolean;
84
+ }
85
+ | {
86
+ directory: string;
87
+ accountId: string;
88
+ projectName: string;
89
+ skipCaching: boolean;
77
90
  }
78
- | { directory: string; accountId: string; projectName: string }
79
91
  ) => {
80
92
  async function fetchJwt(): Promise<string> {
81
93
  if ("jwt" in args) {
@@ -184,7 +196,12 @@ export const upload = async (
184
196
  const start = Date.now();
185
197
 
186
198
  let attempts = 0;
187
- const getMissingHashes = async (): Promise<string[]> => {
199
+ const getMissingHashes = async (skipCaching: boolean): Promise<string[]> => {
200
+ if (skipCaching) {
201
+ console.debug("Force skipping cache");
202
+ return files.map(({ hash }) => hash);
203
+ }
204
+
188
205
  try {
189
206
  return await fetchResult<string[]>(`/pages/assets/check-missing`, {
190
207
  method: "POST",
@@ -207,13 +224,13 @@ export const upload = async (
207
224
  // Looks like the JWT expired, fetch another one
208
225
  jwt = await fetchJwt();
209
226
  }
210
- return getMissingHashes();
227
+ return getMissingHashes(skipCaching);
211
228
  } else {
212
229
  throw e;
213
230
  }
214
231
  }
215
232
  };
216
- const missingHashes = await getMissingHashes();
233
+ const missingHashes = await getMissingHashes(args.skipCaching);
217
234
 
218
235
  const sortedFiles = files
219
236
  .filter((file) => missingHashes.includes(file.hash))
@@ -283,7 +300,8 @@ export const upload = async (
283
300
  );
284
301
 
285
302
  try {
286
- return await fetchResult(`/pages/assets/upload`, {
303
+ console.debug("POST /pages/assets/upload");
304
+ const res = await fetchResult(`/pages/assets/upload`, {
287
305
  method: "POST",
288
306
  headers: {
289
307
  "Content-Type": "application/json",
@@ -291,8 +309,10 @@ export const upload = async (
291
309
  },
292
310
  body: JSON.stringify(payload),
293
311
  });
312
+ console.debug("result:", res);
294
313
  } catch (e) {
295
314
  if (attempts < MAX_UPLOAD_ATTEMPTS) {
315
+ console.debug("failed:", e, "retrying...");
296
316
  // Exponential backoff, 1 second first time, then 2 second, then 4 second etc.
297
317
  await new Promise((resolvePromise) =>
298
318
  setTimeout(resolvePromise, Math.pow(2, attempts++) * 1000)
@@ -304,12 +324,13 @@ export const upload = async (
304
324
  }
305
325
  return doUpload();
306
326
  } else {
327
+ console.debug("failed:", e);
307
328
  throw e;
308
329
  }
309
330
  }
310
331
  };
311
332
 
312
- queue.add(() =>
333
+ void queue.add(() =>
313
334
  doUpload().then(
314
335
  () => {
315
336
  counter += bucket.files.length;
@@ -12,7 +12,7 @@ import {
12
12
  import { fetchListResult, fetchResult } from "../cfetch";
13
13
  import { printBindings } from "../config";
14
14
  import { createWorkerUploadForm } from "../create-worker-upload-form";
15
- import { confirm, fromDashMessagePrompt } from "../dialogs";
15
+ import { confirm } from "../dialogs";
16
16
  import { getMigrationsToUpload } from "../durable";
17
17
  import { logger } from "../logger";
18
18
  import { getMetricsUsageHeaders } from "../metrics";
@@ -269,12 +269,14 @@ export default async function publish(props: Props): Promise<void> {
269
269
  };
270
270
  };
271
271
 
272
- if (
273
- (await fromDashMessagePrompt(
274
- default_environment.script.last_deployed_from
275
- )) === false
276
- )
277
- return;
272
+ if (default_environment.script.last_deployed_from === "dash") {
273
+ logger.warn(
274
+ `You are about to publish a Workers Service that was last published via the Cloudflare Dashboard.\nEdits that have been made via the dashboard will be overridden by your local code and config.`
275
+ );
276
+ if (!(await confirm("Would you like to continue?"))) {
277
+ return;
278
+ }
279
+ }
278
280
  } catch (e) {
279
281
  // code: 10090, message: workers.api.error.service_not_found
280
282
  // is thrown from the above fetchResult on the first publish of a Worker
@@ -49,7 +49,7 @@ export function pubSubCommands(
49
49
  namespace.description = args.description;
50
50
  }
51
51
 
52
- logger.log(`Creating Pub/SubNamespace ${args.name}...`);
52
+ logger.log(`Creating Pub/Sub Namespace ${args.name}...`);
53
53
  await pubsub.createPubSubNamespace(accountId, namespace);
54
54
  logger.log(`Success! Created Pub/Sub Namespace ${args.name}`);
55
55
  await metrics.sendMetricsEvent("create pubsub namespace", {
@@ -58,7 +58,7 @@ export const secret = (secretYargs: Argv<CommonYargsOptions>) => {
58
58
  const isInteractive = process.stdin.isTTY;
59
59
  const secretValue = trimTrailingWhitespace(
60
60
  isInteractive
61
- ? await prompt("Enter a secret value:", "password")
61
+ ? await prompt("Enter a secret value:", { isSecret: true })
62
62
  : await readFromStdin()
63
63
  );
64
64
 
File without changes
package/src/tail/index.ts CHANGED
@@ -3,7 +3,7 @@ import onExit from "signal-exit";
3
3
 
4
4
  import { fetchResult, fetchScriptContent } from "../cfetch";
5
5
  import { readConfig } from "../config";
6
- import { tailDOLogPrompt } from "../dialogs";
6
+ import { confirm } from "../dialogs";
7
7
  import {
8
8
  isLegacyEnv,
9
9
  printWranglerBanner,
@@ -144,8 +144,7 @@ export async function tailHandler(args: TailArgs) {
144
144
  `Beginning log collection requires restarting the Durable Objects associated with ${scriptName}. Any WebSocket connections or other non-persisted state will be lost as part of this restart.`
145
145
  );
146
146
 
147
- const shouldContinue = await tailDOLogPrompt();
148
- if (!shouldContinue) {
147
+ if (!(await confirm("Would you like to continue?"))) {
149
148
  return;
150
149
  }
151
150
  }
@@ -0,0 +1,16 @@
1
+ // `@types/node` should be included
2
+ Buffer.from("test");
3
+
4
+ // @ts-expect-error `@types/jest` should NOT be included
5
+ test("test");
6
+
7
+ // @ts-expect-error `@cloudflare/workers-types` should NOT be included
8
+ const _handler: ExportedHandler = {};
9
+ // @ts-expect-error `@cloudflare/workers-types` should NOT be included
10
+ new HTMLRewriter();
11
+
12
+ // @ts-expect-error `fetch` should NOT be included as our minimum supported
13
+ // Node version is 16.13.0 which does not include `fetch` on the global scope
14
+ void fetch("http://localhost/");
15
+
16
+ export {};
@@ -21,7 +21,6 @@ export async function domainUsesAccess(domain: string): Promise<boolean> {
21
21
  try {
22
22
  const controller = new AbortController();
23
23
  const cancel = setTimeout(() => {
24
- logger.debug("Timed out");
25
24
  controller.abort();
26
25
  }, 1000);
27
26
 
@@ -0,0 +1,113 @@
1
+ import { getEnvironmentVariableFactory } from "../environment-variables/factory";
2
+ import { getCloudflareApiEnvironmentFromEnv } from "../environment-variables/misc-variables";
3
+ import { getAccessToken } from "./access";
4
+
5
+ /**
6
+ * `CLOUDFLARE_ACCOUNT_ID` overrides the account inferred from the current user.
7
+ */
8
+ export const getCloudflareAccountIdFromEnv = getEnvironmentVariableFactory({
9
+ variableName: "CLOUDFLARE_ACCOUNT_ID",
10
+ deprecatedName: "CF_ACCOUNT_ID",
11
+ });
12
+
13
+ export const getCloudflareAPITokenFromEnv = getEnvironmentVariableFactory({
14
+ variableName: "CLOUDFLARE_API_TOKEN",
15
+ deprecatedName: "CF_API_TOKEN",
16
+ });
17
+ export const getCloudflareGlobalAuthKeyFromEnv = getEnvironmentVariableFactory({
18
+ variableName: "CLOUDFLARE_API_KEY",
19
+ deprecatedName: "CF_API_KEY",
20
+ });
21
+ export const getCloudflareGlobalAuthEmailFromEnv =
22
+ getEnvironmentVariableFactory({
23
+ variableName: "CLOUDFLARE_EMAIL",
24
+ deprecatedName: "CF_EMAIL",
25
+ });
26
+
27
+ /**
28
+ * `WRANGLER_CLIENT_ID` is a UUID that is used to identify Wrangler
29
+ * to the Cloudflare APIs.
30
+ *
31
+ * Normally you should not need to set this explicitly.
32
+ * If you want to switch to the staging environment set the
33
+ * `WRANGLER_USE_STAGING` environment variable instead.
34
+ */
35
+ export const getClientIdFromEnv = getEnvironmentVariableFactory({
36
+ variableName: "WRANGLER_CLIENT_ID",
37
+ defaultValue: () =>
38
+ getCloudflareApiEnvironmentFromEnv() === "staging"
39
+ ? "4b2ea6cc-9421-4761-874b-ce550e0e3def"
40
+ : "54d11594-84e4-41aa-b438-e81b8fa78ee7",
41
+ });
42
+
43
+ /**
44
+ * `WRANGLER_AUTH_DOMAIN` is the URL base domain that is used
45
+ * to access OAuth URLs for the Cloudflare APIs.
46
+ *
47
+ * Normally you should not need to set this explicitly.
48
+ * If you want to switch to the staging environment set the
49
+ * `WRANGLER_USE_STAGING` environment variable instead.
50
+ */
51
+ export const getAuthDomainFromEnv = getEnvironmentVariableFactory({
52
+ variableName: "WRANGLER_AUTH_DOMAIN",
53
+ defaultValue: () =>
54
+ getCloudflareApiEnvironmentFromEnv() === "staging"
55
+ ? "dash.staging.cloudflare.com"
56
+ : "dash.cloudflare.com",
57
+ });
58
+
59
+ /**
60
+ * `WRANGLER_AUTH_URL` is the path that is used to access OAuth
61
+ * for the Cloudflare APIs.
62
+ *
63
+ * Normally you should not need to set this explicitly.
64
+ * If you want to switch to the staging environment set the
65
+ * `WRANGLER_USE_STAGING` environment variable instead.
66
+ */
67
+ export const getAuthUrlFromEnv = getEnvironmentVariableFactory({
68
+ variableName: "WRANGLER_AUTH_URL",
69
+ defaultValue: () => `https://${getAuthDomainFromEnv()}/oauth2/auth`,
70
+ });
71
+
72
+ /**
73
+ * `WRANGLER_TOKEN_URL` is the path that is used to exchange an OAuth
74
+ * token for an API token.
75
+ *
76
+ * Normally you should not need to set this explicitly.
77
+ * If you want to switch to the staging environment set the
78
+ * `WRANGLER_USE_STAGING` environment variable instead.
79
+ */
80
+ export const getTokenUrlFromEnv = getEnvironmentVariableFactory({
81
+ variableName: "WRANGLER_TOKEN_URL",
82
+ defaultValue: () => `https://${getAuthDomainFromEnv()}/oauth2/token`,
83
+ });
84
+
85
+ /**
86
+ * `WRANGLER_REVOKE_URL` is the path that is used to exchange an OAuth
87
+ * refresh token for a new OAuth token.
88
+ *
89
+ * Normally you should not need to set this explicitly.
90
+ * If you want to switch to the staging environment set the
91
+ * `WRANGLER_USE_STAGING` environment variable instead.
92
+ */
93
+ export const getRevokeUrlFromEnv = getEnvironmentVariableFactory({
94
+ variableName: "WRANGLER_REVOKE_URL",
95
+ defaultValue: () => `https://${getAuthDomainFromEnv()}/oauth2/revoke`,
96
+ });
97
+
98
+ /**
99
+ * Set the `WRANGLER_CF_AUTHORIZATION_TOKEN` to the CF_Authorization token found at https://dash.staging.cloudflare.com/bypass-limits
100
+ * if you want to access the staging environment, triggered by `WRANGLER_API_ENVIRONMENT=staging`.
101
+ */
102
+ export const getCloudflareAccessToken = async () => {
103
+ const env = getEnvironmentVariableFactory({
104
+ variableName: "WRANGLER_CF_AUTHORIZATION_TOKEN",
105
+ })();
106
+
107
+ // If the environment variable is defined, go ahead and use it.
108
+ if (env !== undefined) {
109
+ return env;
110
+ }
111
+
112
+ return getAccessToken(getAuthDomainFromEnv());
113
+ };
@@ -1,41 +1,11 @@
1
- import { Text } from "ink";
2
- import SelectInput from "ink-select-input";
3
- import React from "react";
4
1
  import { fetchListResult } from "../cfetch";
5
- import { logger } from "../logger";
6
- import { getCloudflareAccountIdFromEnv } from "./env-vars";
2
+ import { getCloudflareAccountIdFromEnv } from "./auth-variables";
7
3
 
8
4
  export type ChooseAccountItem = {
9
5
  id: string;
10
6
  name: string;
11
7
  };
12
8
 
13
- /**
14
- * A component that allows the user to select from a list of available accounts.
15
- */
16
- export function ChooseAccount(props: {
17
- accounts: ChooseAccountItem[];
18
- onSelect: (account: { name: string; id: string }) => void;
19
- onError: (error: Error) => void;
20
- }) {
21
- return (
22
- <>
23
- <Text bold>Select an account from below:</Text>
24
- <SelectInput
25
- items={props.accounts.map((item) => ({
26
- key: item.id,
27
- label: item.name,
28
- value: item,
29
- }))}
30
- onSelect={(item) => {
31
- logger.log(`Using account: "${item.value.name} - ${item.value.id}"`);
32
- props.onSelect({ id: item.value.id, name: item.value.name });
33
- }}
34
- />
35
- </>
36
- );
37
- }
38
-
39
9
  /**
40
10
  * Infer a list of available accounts for the current user.
41
11
  */
package/src/user/index.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  export * from "./user";
2
- export * from "./env-vars";
3
2
  export * from "./choose-account";
@@ -214,28 +214,61 @@ import url from "node:url";
214
214
  import { TextEncoder } from "node:util";
215
215
  import TOML from "@iarna/toml";
216
216
  import { HostURL } from "@webcontainer/env";
217
- import { render } from "ink";
218
- import Table from "ink-table";
219
- import React from "react";
220
217
  import { fetch } from "undici";
221
218
  import {
222
219
  getConfigCache,
223
220
  purgeConfigCaches,
224
221
  saveToConfigCache,
225
222
  } from "../config-cache";
223
+ import { NoDefaultValueProvided, select } from "../dialogs";
226
224
  import { getGlobalWranglerConfigPath } from "../global-wrangler-config-path";
227
225
  import { CI } from "../is-ci";
228
226
  import isInteractive from "../is-interactive";
229
227
  import { logger } from "../logger";
230
228
  import openInBrowser from "../open-in-browser";
231
229
  import { parseTOML, readFileSync } from "../parse";
232
- import { ChooseAccount, getAccountChoices } from "./choose-account";
233
- import { getAuthFromEnv } from "./env-vars";
230
+ import { domainUsesAccess } from "./access";
231
+ import {
232
+ getAuthDomainFromEnv,
233
+ getAuthUrlFromEnv,
234
+ getClientIdFromEnv,
235
+ getCloudflareAccessToken,
236
+ getCloudflareAPITokenFromEnv,
237
+ getCloudflareGlobalAuthEmailFromEnv,
238
+ getCloudflareGlobalAuthKeyFromEnv,
239
+ getRevokeUrlFromEnv,
240
+ getTokenUrlFromEnv,
241
+ } from "./auth-variables";
242
+ import { getAccountChoices } from "./choose-account";
234
243
  import { generateAuthUrl } from "./generate-auth-url";
235
244
  import { generateRandomState } from "./generate-random-state";
236
- import type { ApiCredentials } from "./env-vars";
245
+ import type { ChooseAccountItem } from "./choose-account";
237
246
  import type { ParsedUrlQuery } from "node:querystring";
238
247
 
248
+ export type ApiCredentials =
249
+ | {
250
+ apiToken: string;
251
+ }
252
+ | {
253
+ authKey: string;
254
+ authEmail: string;
255
+ };
256
+
257
+ /**
258
+ * Try to read an API token or Global Auth from the environment.
259
+ */
260
+ export function getAuthFromEnv(): ApiCredentials | undefined {
261
+ const globalApiKey = getCloudflareGlobalAuthKeyFromEnv();
262
+ const globalApiEmail = getCloudflareGlobalAuthEmailFromEnv();
263
+ const apiToken = getCloudflareAPITokenFromEnv();
264
+
265
+ if (globalApiKey && globalApiEmail) {
266
+ return { authKey: globalApiKey, authEmail: globalApiEmail };
267
+ } else if (apiToken) {
268
+ return { apiToken };
269
+ }
270
+ }
271
+
239
272
  /**
240
273
  * An implementation of rfc6749#section-4.1 and rfc7636.
241
274
  */
@@ -330,11 +363,6 @@ export function validateScopeKeys(
330
363
  return scopes.every((scope) => scope in Scopes);
331
364
  }
332
365
 
333
- const CLIENT_ID = "54d11594-84e4-41aa-b438-e81b8fa78ee7";
334
- const AUTH_URL = "https://dash.cloudflare.com/oauth2/auth";
335
- const TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
336
- const REVOKE_URL = "https://dash.cloudflare.com/oauth2/revoke";
337
-
338
366
  /**
339
367
  * To allow OAuth callbacks in environments such as WebContainer we need to
340
368
  * create a host URL which only resolves `localhost` to a WebContainer
@@ -386,7 +414,7 @@ function getAuthTokens(config?: UserAuthConfig): AuthTokens | undefined {
386
414
  }
387
415
 
388
416
  /**
389
- * Run the initialisation of the auth state, in the case that something changed.
417
+ * Run the initialization of the auth state, in the case that something changed.
390
418
  *
391
419
  * This runs automatically whenever `writeAuthConfigFile` is run, so generally
392
420
  * you won't need to call it yourself.
@@ -625,8 +653,8 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
625
653
  });
626
654
 
627
655
  return generateAuthUrl({
628
- authUrl: AUTH_URL,
629
- clientId: CLIENT_ID,
656
+ authUrl: getAuthUrlFromEnv(),
657
+ clientId: getClientIdFromEnv(),
630
658
  callbackUrl: CALLBACK_URL,
631
659
  scopes,
632
660
  stateQueryParam,
@@ -653,19 +681,14 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
653
681
  logger.warn("No refresh token is present.");
654
682
  }
655
683
 
656
- const body =
657
- `grant_type=refresh_token&` +
658
- `refresh_token=${LocalState.refreshToken?.value}&` +
659
- `client_id=${CLIENT_ID}`;
660
-
661
- const response = await fetch(TOKEN_URL, {
662
- method: "POST",
663
- body,
664
- headers: {
665
- "Content-Type": "application/x-www-form-urlencoded",
666
- },
684
+ const params = new URLSearchParams({
685
+ grant_type: "refresh_token",
686
+ refresh_token: LocalState.refreshToken?.value ?? "",
687
+ client_id: getClientIdFromEnv(),
667
688
  });
668
689
 
690
+ const response = await fetchAuthToken(params);
691
+
669
692
  if (response.status >= 400) {
670
693
  let tokenExchangeResErr = undefined;
671
694
 
@@ -743,20 +766,15 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
743
766
  logger.warn("No authorization grant code is being passed.");
744
767
  }
745
768
 
746
- const body =
747
- `grant_type=authorization_code&` +
748
- `code=${encodeURIComponent(authorizationCode || "")}&` +
749
- `redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
750
- `client_id=${encodeURIComponent(CLIENT_ID)}&` +
751
- `code_verifier=${codeVerifier}`;
752
-
753
- const response = await fetch(TOKEN_URL, {
754
- method: "POST",
755
- body,
756
- headers: {
757
- "Content-Type": "application/x-www-form-urlencoded",
758
- },
769
+ const params = new URLSearchParams({
770
+ grant_type: `authorization_code`,
771
+ code: authorizationCode ?? "",
772
+ redirect_uri: CALLBACK_URL,
773
+ client_id: getClientIdFromEnv(),
774
+ code_verifier: codeVerifier,
759
775
  });
776
+
777
+ const response = await fetchAuthToken(params);
760
778
  if (!response.ok) {
761
779
  const { error } = (await response.json()) as { error: string };
762
780
  // .catch((_) => ({ error: "invalid_json" }));
@@ -1038,11 +1056,11 @@ export async function logout(): Promise<void> {
1038
1056
  }
1039
1057
 
1040
1058
  const body =
1041
- `client_id=${encodeURIComponent(CLIENT_ID)}&` +
1059
+ `client_id=${encodeURIComponent(getClientIdFromEnv())}&` +
1042
1060
  `token_type_hint=refresh_token&` +
1043
1061
  `token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
1044
1062
 
1045
- const response = await fetch(REVOKE_URL, {
1063
+ const response = await fetch(getRevokeUrlFromEnv(), {
1046
1064
  method: "POST",
1047
1065
  body,
1048
1066
  headers: {
@@ -1055,11 +1073,11 @@ export async function logout(): Promise<void> {
1055
1073
  );
1056
1074
  }
1057
1075
  const body =
1058
- `client_id=${encodeURIComponent(CLIENT_ID)}&` +
1076
+ `client_id=${encodeURIComponent(getClientIdFromEnv())}&` +
1059
1077
  `token_type_hint=refresh_token&` +
1060
1078
  `token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
1061
1079
 
1062
- const response = await fetch(REVOKE_URL, {
1080
+ const response = await fetch(getRevokeUrlFromEnv(), {
1063
1081
  method: "POST",
1064
1082
  body,
1065
1083
  headers: {
@@ -1077,8 +1095,7 @@ export function listScopes(message = "💁 Available scopes:"): void {
1077
1095
  Scope: scope,
1078
1096
  Description: Scopes[scope],
1079
1097
  }));
1080
- const { unmount } = render(<Table data={data} />);
1081
- unmount();
1098
+ logger.table(data);
1082
1099
  // TODO: maybe a good idea to show usage here
1083
1100
  }
1084
1101
 
@@ -1097,36 +1114,32 @@ export async function getAccountId(): Promise<string | undefined> {
1097
1114
  return accounts[0].id;
1098
1115
  }
1099
1116
 
1100
- if (isInteractive() && !CI.isCI()) {
1101
- const account = await new Promise<{ id: string; name: string }>(
1102
- (resolve, reject) => {
1103
- const { unmount } = render(
1104
- <ChooseAccount
1105
- accounts={accounts}
1106
- onSelect={async (selected) => {
1107
- saveAccountToCache(selected);
1108
- resolve(selected);
1109
- unmount();
1110
- }}
1111
- onError={(err) => {
1112
- reject(err);
1113
- unmount();
1114
- }}
1115
- />
1116
- );
1117
- }
1118
- );
1119
- return account.id;
1117
+ try {
1118
+ const accountID = await select("Select an account", {
1119
+ choices: accounts.map((account) => ({
1120
+ title: account.name,
1121
+ value: account.id,
1122
+ })),
1123
+ });
1124
+ const account = accounts.find(
1125
+ (a) => a.id === accountID
1126
+ ) as ChooseAccountItem;
1127
+ saveAccountToCache({ id: account.id, name: account.name });
1128
+ return accountID;
1129
+ } catch (e) {
1130
+ // Did we try to select an account in CI or a non-interactive terminal?
1131
+ if (e instanceof NoDefaultValueProvided) {
1132
+ throw new Error(
1133
+ `More than one account available but unable to select one in non-interactive mode.
1134
+ Please set the appropriate \`account_id\` in your \`wrangler.toml\` file.
1135
+ Available accounts are (\`<name>\`: \`<account_id>\`):
1136
+ ${accounts
1137
+ .map((account) => ` \`${account.name}\`: \`${account.id}\``)
1138
+ .join("\n")}`
1139
+ );
1140
+ }
1141
+ throw e;
1120
1142
  }
1121
-
1122
- throw new Error(
1123
- "More than one account available but unable to select one in non-interactive mode.\n" +
1124
- `Please set the appropriate \`account_id\` in your \`wrangler.toml\` file.\n` +
1125
- `Available accounts are ("<name>" - "<id>"):\n` +
1126
- accounts
1127
- .map((account) => ` "${account.name}" - "${account.id}")`)
1128
- .join("\n")
1129
- );
1130
1143
  }
1131
1144
 
1132
1145
  /**
@@ -1196,3 +1209,24 @@ export function getAccountFromCache():
1196
1209
  export function getScopes(): Scope[] | undefined {
1197
1210
  return LocalState.scopes;
1198
1211
  }
1212
+
1213
+ /**
1214
+ * Make a request to the Cloudflare OAuth endpoint to get a token.
1215
+ *
1216
+ * Note that the `body` of the POST request is form-urlencoded so
1217
+ * can be represented by a URLSearchParams object.
1218
+ */
1219
+ async function fetchAuthToken(body: URLSearchParams) {
1220
+ const headers: Record<string, string> = {
1221
+ "Content-Type": "application/x-www-form-urlencoded",
1222
+ };
1223
+ if (await domainUsesAccess(getAuthDomainFromEnv())) {
1224
+ // We are trying to access the staging API so we need an "access token".
1225
+ headers["Cookie"] = `CF_Authorization=${await getCloudflareAccessToken()}`;
1226
+ }
1227
+ return await fetch(getTokenUrlFromEnv(), {
1228
+ method: "POST",
1229
+ body: body.toString(),
1230
+ headers,
1231
+ });
1232
+ }