wrangler 0.0.0-e178d6fb → 0.0.0-e1c2f5b9

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 (239) hide show
  1. package/README.md +16 -2
  2. package/bin/wrangler.js +31 -11
  3. package/miniflare-dist/index.mjs +1340 -330
  4. package/package.json +40 -13
  5. package/src/__tests__/access.test.ts +25 -0
  6. package/src/__tests__/api-dev.test.ts +238 -0
  7. package/src/__tests__/api-devregistry.test.js +64 -0
  8. package/src/__tests__/api.test.ts +80 -0
  9. package/src/__tests__/configuration.test.ts +683 -470
  10. package/src/__tests__/d1.test.ts +123 -0
  11. package/src/__tests__/delete.test.ts +263 -0
  12. package/src/__tests__/deployments.test.ts +105 -0
  13. package/src/__tests__/dev.test.tsx +453 -70
  14. package/src/__tests__/dialogs.test.tsx +40 -0
  15. package/src/__tests__/generate.test.ts +235 -0
  16. package/src/__tests__/get-host-from-url.test.ts +16 -0
  17. package/src/__tests__/helpers/mock-auth-domain.ts +20 -0
  18. package/src/__tests__/helpers/mock-cfetch.ts +92 -26
  19. package/src/__tests__/helpers/mock-console.ts +11 -2
  20. package/src/__tests__/helpers/mock-dialogs.ts +1 -1
  21. package/src/__tests__/helpers/mock-get-zone-from-host.ts +8 -0
  22. package/src/__tests__/helpers/mock-known-routes.ts +7 -0
  23. package/src/__tests__/helpers/mock-kv.ts +1 -1
  24. package/src/__tests__/helpers/mock-oauth-flow.ts +74 -53
  25. package/src/__tests__/helpers/mock-process.ts +8 -13
  26. package/src/__tests__/helpers/msw/handlers/access.ts +13 -0
  27. package/src/__tests__/helpers/msw/handlers/deployments.ts +59 -0
  28. package/src/__tests__/helpers/msw/handlers/namespaces.ts +104 -0
  29. package/src/__tests__/helpers/msw/handlers/oauth.ts +31 -0
  30. package/src/__tests__/helpers/msw/handlers/r2.ts +80 -0
  31. package/src/__tests__/helpers/msw/handlers/script.ts +75 -0
  32. package/src/__tests__/helpers/msw/handlers/user.ts +63 -0
  33. package/src/__tests__/helpers/msw/handlers/zones.ts +22 -0
  34. package/src/__tests__/helpers/msw/index.ts +21 -0
  35. package/src/__tests__/helpers/worker-scripts/child-wrangler.toml +1 -0
  36. package/src/__tests__/helpers/worker-scripts/hello-world-worker.js +5 -0
  37. package/src/__tests__/helpers/worker-scripts/hello-world-wrangler.toml +1 -0
  38. package/src/__tests__/helpers/worker-scripts/parent-worker.js +8 -0
  39. package/src/__tests__/helpers/worker-scripts/parent-wrangler.toml +5 -0
  40. package/src/__tests__/index.test.ts +103 -97
  41. package/src/__tests__/init.test.ts +1484 -382
  42. package/src/__tests__/jest.setup.ts +88 -5
  43. package/src/__tests__/kv.test.ts +655 -540
  44. package/src/__tests__/logout.test.ts +47 -0
  45. package/src/__tests__/metrics.test.ts +95 -45
  46. package/src/__tests__/middleware.scheduled.test.ts +145 -0
  47. package/src/__tests__/middleware.test.ts +750 -0
  48. package/src/__tests__/pages-deployment-tail.test.ts +884 -0
  49. package/src/__tests__/pages.test.ts +1301 -119
  50. package/src/__tests__/paths.test.ts +39 -0
  51. package/src/__tests__/publish.test.ts +2266 -1180
  52. package/src/__tests__/pubsub.test.ts +211 -88
  53. package/src/__tests__/queues.test.ts +522 -0
  54. package/src/__tests__/r2.test.ts +363 -203
  55. package/src/__tests__/secret.test.ts +261 -59
  56. package/src/__tests__/tail.test.ts +352 -151
  57. package/src/__tests__/test-old-node-version.js +3 -3
  58. package/src/__tests__/tsconfig-sanity.ts +12 -0
  59. package/src/__tests__/tsconfig.json +8 -0
  60. package/src/__tests__/tsconfig.tsbuildinfo +1 -0
  61. package/src/__tests__/type-generation.test.ts +233 -0
  62. package/src/__tests__/user.test.ts +59 -73
  63. package/src/__tests__/utils-collectKeyValues.test.ts +47 -0
  64. package/src/__tests__/validate-dev-props.test.ts +56 -0
  65. package/src/__tests__/whoami.test.tsx +114 -66
  66. package/src/__tests__/worker-namespace.test.ts +154 -155
  67. package/src/api/dev.ts +170 -38
  68. package/src/api/index.ts +1 -0
  69. package/src/bundle-reporter.tsx +41 -2
  70. package/src/bundle.ts +668 -81
  71. package/src/cfetch/index.ts +40 -3
  72. package/src/cfetch/internal.ts +144 -32
  73. package/src/cli.ts +4 -3
  74. package/src/config/config.ts +27 -3
  75. package/src/config/environment.ts +131 -7
  76. package/src/config/index.ts +128 -8
  77. package/src/config/validation-helpers.ts +19 -6
  78. package/src/config/validation.ts +411 -14
  79. package/src/config-cache.ts +3 -2
  80. package/src/create-worker-preview.ts +31 -6
  81. package/src/create-worker-upload-form.ts +84 -22
  82. package/src/d1/backups.tsx +223 -0
  83. package/src/d1/constants.ts +2 -0
  84. package/src/d1/create.tsx +63 -0
  85. package/src/d1/delete.tsx +56 -0
  86. package/src/d1/execute.tsx +352 -0
  87. package/src/d1/formatTimeAgo.ts +14 -0
  88. package/src/d1/index.ts +101 -0
  89. package/src/d1/list.tsx +48 -0
  90. package/src/d1/migrations/apply.tsx +196 -0
  91. package/src/d1/migrations/create.tsx +77 -0
  92. package/src/d1/migrations/helpers.ts +147 -0
  93. package/src/d1/migrations/index.tsx +3 -0
  94. package/src/d1/migrations/list.tsx +79 -0
  95. package/src/d1/migrations/options.ts +18 -0
  96. package/src/d1/options.ts +22 -0
  97. package/src/d1/types.tsx +24 -0
  98. package/src/d1/utils.ts +48 -0
  99. package/src/delete.ts +108 -0
  100. package/src/deployments.ts +83 -0
  101. package/src/deprecated/index.ts +145 -0
  102. package/src/dev/dev-vars.ts +11 -8
  103. package/src/dev/dev.tsx +220 -58
  104. package/src/dev/get-local-persistence-path.tsx +31 -0
  105. package/src/dev/local.tsx +740 -181
  106. package/src/dev/remote.tsx +340 -123
  107. package/src/dev/start-server.ts +543 -0
  108. package/src/dev/use-esbuild.ts +61 -7
  109. package/src/dev/validate-dev-props.ts +31 -0
  110. package/src/dev-registry.tsx +201 -0
  111. package/src/dev.tsx +614 -258
  112. package/src/dialogs.tsx +41 -8
  113. package/src/{worker-namespace.ts → dispatch-namespace.ts} +23 -22
  114. package/src/docs/index.ts +97 -0
  115. package/src/entry.ts +3 -2
  116. package/src/environment-variables/factory.ts +88 -0
  117. package/src/environment-variables/misc-variables.ts +30 -0
  118. package/src/generate/index.ts +300 -0
  119. package/src/git-client.ts +100 -7
  120. package/src/index.tsx +249 -1560
  121. package/src/init.ts +626 -150
  122. package/src/inspect.ts +137 -32
  123. package/src/{metrics/is-ci.ts → is-ci.ts} +0 -0
  124. package/src/is-interactive.ts +4 -0
  125. package/src/jest.d.ts +4 -0
  126. package/src/{kv.ts → kv/helpers.ts} +3 -3
  127. package/src/kv/index.ts +596 -0
  128. package/src/logger.ts +27 -8
  129. package/src/metrics/index.ts +1 -0
  130. package/src/metrics/metrics-config.ts +4 -4
  131. package/src/metrics/metrics-dispatcher.ts +1 -0
  132. package/src/metrics/metrics-usage-headers.ts +24 -0
  133. package/src/metrics/send-event.ts +13 -8
  134. package/src/miniflare-cli/assets.ts +172 -465
  135. package/src/miniflare-cli/index.ts +149 -24
  136. package/src/miniflare-cli/tsconfig.json +9 -0
  137. package/src/miniflare-cli/tsconfig.tsbuildinfo +1 -0
  138. package/src/miniflare-cli/types.ts +11 -0
  139. package/src/module-collection.ts +12 -3
  140. package/src/pages/build.tsx +108 -42
  141. package/src/pages/constants.ts +11 -0
  142. package/src/pages/deployment-tails.tsx +284 -0
  143. package/src/pages/deployments.tsx +9 -31
  144. package/src/pages/dev.tsx +417 -108
  145. package/src/pages/errors.ts +66 -0
  146. package/src/pages/functions/buildPlugin.ts +86 -84
  147. package/src/pages/functions/buildWorker.ts +121 -106
  148. package/src/pages/functions/routes-consolidation.test.ts +250 -0
  149. package/src/pages/functions/routes-consolidation.ts +73 -0
  150. package/src/pages/functions/routes-transformation.test.ts +282 -0
  151. package/src/pages/functions/routes-transformation.ts +115 -0
  152. package/src/pages/functions/routes-validation.test.ts +403 -0
  153. package/src/pages/functions/routes-validation.ts +202 -0
  154. package/src/pages/functions/tsconfig.json +8 -0
  155. package/src/pages/functions/tsconfig.tsbuildinfo +1 -0
  156. package/src/pages/functions.tsx +82 -0
  157. package/src/pages/hash.tsx +13 -0
  158. package/src/pages/index.tsx +73 -55
  159. package/src/pages/projects.tsx +9 -3
  160. package/src/pages/prompt-select-project.tsx +31 -0
  161. package/src/pages/publish.tsx +183 -60
  162. package/src/pages/types.ts +1 -0
  163. package/src/pages/upload.tsx +107 -56
  164. package/src/pages/utils.ts +13 -2
  165. package/src/paths.ts +49 -4
  166. package/src/proxy.ts +375 -157
  167. package/src/publish/index.ts +270 -0
  168. package/src/{publish.ts → publish/publish.ts} +431 -188
  169. package/src/pubsub/pubsub-commands.tsx +4 -3
  170. package/src/queues/cli/commands/consumer/add.ts +71 -0
  171. package/src/queues/cli/commands/consumer/index.ts +22 -0
  172. package/src/queues/cli/commands/consumer/remove.ts +38 -0
  173. package/src/queues/cli/commands/create.ts +25 -0
  174. package/src/queues/cli/commands/delete.ts +26 -0
  175. package/src/queues/cli/commands/index.ts +36 -0
  176. package/src/queues/cli/commands/list.ts +25 -0
  177. package/src/queues/client.ts +135 -0
  178. package/src/queues/utils.ts +18 -0
  179. package/src/r2/constants.ts +4 -0
  180. package/src/r2/helpers.ts +131 -0
  181. package/src/r2/index.ts +289 -0
  182. package/src/routes.ts +140 -0
  183. package/src/secret/index.ts +379 -0
  184. package/src/sites.tsx +3 -3
  185. package/src/tail/createTail.ts +379 -0
  186. package/src/tail/filters.ts +4 -6
  187. package/src/tail/index.ts +199 -284
  188. package/src/tail/printing.ts +43 -3
  189. package/src/tsconfig-sanity.ts +16 -0
  190. package/src/type-generation.ts +181 -0
  191. package/src/user/access.ts +68 -0
  192. package/src/user/auth-variables.ts +113 -0
  193. package/src/user/choose-account.tsx +1 -1
  194. package/src/user/index.ts +0 -1
  195. package/src/user/user.tsx +116 -49
  196. package/src/utils/collectKeyValues.ts +14 -0
  197. package/src/whoami.tsx +80 -2
  198. package/src/worker.ts +69 -9
  199. package/src/yargs-types.ts +24 -0
  200. package/src/zones.ts +124 -9
  201. package/templates/__tests__/pages-dev-util.test.ts +128 -0
  202. package/templates/__tests__/tsconfig-sanity.ts +12 -0
  203. package/templates/__tests__/tsconfig.json +8 -0
  204. package/templates/__tests__/tsconfig.tsbuildinfo +1 -0
  205. package/templates/d1-beta-facade.js +205 -0
  206. package/templates/facade.d.ts +14 -0
  207. package/templates/first-party-worker-module-facade.ts +19 -0
  208. package/templates/format-dev-errors.ts +33 -0
  209. package/templates/init-tests/test-jest-new-worker.js +23 -0
  210. package/templates/init-tests/test-vitest-new-worker.js +24 -0
  211. package/templates/init-tests/test-vitest-new-worker.ts +25 -0
  212. package/templates/middleware/common.ts +62 -0
  213. package/templates/middleware/loader-modules.ts +82 -0
  214. package/templates/middleware/loader-sw.ts +233 -0
  215. package/templates/middleware/middleware-miniflare3-json-error.ts +20 -0
  216. package/templates/middleware/middleware-pretty-error.ts +40 -0
  217. package/templates/middleware/middleware-scheduled.ts +15 -0
  218. package/templates/new-worker.js +1 -1
  219. package/templates/pages-dev-pipeline.ts +31 -0
  220. package/templates/pages-dev-util.ts +52 -0
  221. package/templates/pages-shim.ts +4 -1
  222. package/templates/pages-template-plugin.ts +21 -5
  223. package/templates/pages-template-worker.ts +37 -9
  224. package/templates/{static-asset-facade.js → serve-static-assets.ts} +23 -7
  225. package/templates/service-bindings-module-facade.js +54 -0
  226. package/templates/service-bindings-sw-facade.js +42 -0
  227. package/templates/tsconfig-sanity.ts +11 -0
  228. package/templates/tsconfig.init.json +106 -0
  229. package/templates/tsconfig.json +5 -102
  230. package/templates/tsconfig.tsbuildinfo +1 -0
  231. package/wrangler-dist/cli.d.ts +621 -42
  232. package/wrangler-dist/cli.js +144441 -102606
  233. package/src/environment-variables.ts +0 -35
  234. package/src/faye-websocket.d.ts +0 -6
  235. package/src/generate.ts +0 -33
  236. package/src/miniflare-cli/enum-keys.ts +0 -17
  237. package/src/preview.ts +0 -31
  238. package/src/r2.ts +0 -50
  239. package/src/user/env-vars.ts +0 -46
package/src/user/user.tsx CHANGED
@@ -224,17 +224,52 @@ import {
224
224
  saveToConfigCache,
225
225
  } from "../config-cache";
226
226
  import { getGlobalWranglerConfigPath } from "../global-wrangler-config-path";
227
+ import { CI } from "../is-ci";
227
228
  import isInteractive from "../is-interactive";
228
229
  import { logger } from "../logger";
229
230
  import openInBrowser from "../open-in-browser";
230
231
  import { parseTOML, readFileSync } from "../parse";
232
+ import { domainUsesAccess } from "./access";
233
+ import {
234
+ getAuthDomainFromEnv,
235
+ getAuthUrlFromEnv,
236
+ getClientIdFromEnv,
237
+ getCloudflareAccessToken,
238
+ getCloudflareAPITokenFromEnv,
239
+ getCloudflareGlobalAuthEmailFromEnv,
240
+ getCloudflareGlobalAuthKeyFromEnv,
241
+ getRevokeUrlFromEnv,
242
+ getTokenUrlFromEnv,
243
+ } from "./auth-variables";
231
244
  import { ChooseAccount, getAccountChoices } from "./choose-account";
232
- import { getAuthFromEnv } from "./env-vars";
233
245
  import { generateAuthUrl } from "./generate-auth-url";
234
246
  import { generateRandomState } from "./generate-random-state";
235
- import type { ApiCredentials } from "./env-vars";
236
247
  import type { ParsedUrlQuery } from "node:querystring";
237
248
 
249
+ export type ApiCredentials =
250
+ | {
251
+ apiToken: string;
252
+ }
253
+ | {
254
+ authKey: string;
255
+ authEmail: string;
256
+ };
257
+
258
+ /**
259
+ * Try to read an API token or Global Auth from the environment.
260
+ */
261
+ export function getAuthFromEnv(): ApiCredentials | undefined {
262
+ const globalApiKey = getCloudflareGlobalAuthKeyFromEnv();
263
+ const globalApiEmail = getCloudflareGlobalAuthEmailFromEnv();
264
+ const apiToken = getCloudflareAPITokenFromEnv();
265
+
266
+ if (globalApiKey && globalApiEmail) {
267
+ return { authKey: globalApiKey, authEmail: globalApiEmail };
268
+ } else if (apiToken) {
269
+ return { apiToken };
270
+ }
271
+ }
272
+
238
273
  /**
239
274
  * An implementation of rfc6749#section-4.1 and rfc7636.
240
275
  */
@@ -262,6 +297,7 @@ interface State extends AuthTokens {
262
297
  interface AuthTokens {
263
298
  accessToken?: AccessToken;
264
299
  refreshToken?: RefreshToken;
300
+ scopes?: Scope[];
265
301
  /** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
266
302
  apiToken?: string;
267
303
  }
@@ -279,6 +315,7 @@ export interface UserAuthConfig {
279
315
  oauth_token?: string;
280
316
  refresh_token?: string;
281
317
  expiration_time?: string;
318
+ scopes?: string[];
282
319
  /** @deprecated - this field was only provided by the deprecated `wrangler1 config` command. */
283
320
  api_token?: string;
284
321
  }
@@ -306,6 +343,7 @@ const Scopes = {
306
343
  "workers_scripts:write":
307
344
  "See and change Cloudflare Workers scripts, durable objects, subdomains, triggers, and tail data.",
308
345
  "workers_tail:read": "See Cloudflare Workers tail and script data.",
346
+ "d1:write": "See and change D1 Databases.",
309
347
  "pages:write":
310
348
  "See and change Cloudflare Pages projects, settings and deployments.",
311
349
  "zone:read": "Grants read level access to account zone.",
@@ -326,11 +364,6 @@ export function validateScopeKeys(
326
364
  return scopes.every((scope) => scope in Scopes);
327
365
  }
328
366
 
329
- const CLIENT_ID = "54d11594-84e4-41aa-b438-e81b8fa78ee7";
330
- const AUTH_URL = "https://dash.cloudflare.com/oauth2/auth";
331
- const TOKEN_URL = "https://dash.cloudflare.com/oauth2/token";
332
- const REVOKE_URL = "https://dash.cloudflare.com/oauth2/revoke";
333
-
334
367
  /**
335
368
  * To allow OAuth callbacks in environments such as WebContainer we need to
336
369
  * create a host URL which only resolves `localhost` to a WebContainer
@@ -355,7 +388,7 @@ function getAuthTokens(config?: UserAuthConfig): AuthTokens | undefined {
355
388
  if (getAuthFromEnv()) return;
356
389
 
357
390
  // otherwise try loading from the user auth config file.
358
- const { oauth_token, refresh_token, expiration_time, api_token } =
391
+ const { oauth_token, refresh_token, expiration_time, scopes, api_token } =
359
392
  config || readAuthConfigFile();
360
393
 
361
394
  if (oauth_token) {
@@ -366,6 +399,7 @@ function getAuthTokens(config?: UserAuthConfig): AuthTokens | undefined {
366
399
  expiry: expiration_time ?? "2000-01-01:00:00:00+00:00",
367
400
  },
368
401
  refreshToken: { value: refresh_token ?? "" },
402
+ scopes: scopes as Scope[],
369
403
  };
370
404
  } else if (api_token) {
371
405
  logger.warn(
@@ -381,7 +415,7 @@ function getAuthTokens(config?: UserAuthConfig): AuthTokens | undefined {
381
415
  }
382
416
 
383
417
  /**
384
- * Run the initialisation of the auth state, in the case that something changed.
418
+ * Run the initialization of the auth state, in the case that something changed.
385
419
  *
386
420
  * This runs automatically whenever `writeAuthConfigFile` is run, so generally
387
421
  * you won't need to call it yourself.
@@ -620,8 +654,8 @@ export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
620
654
  });
621
655
 
622
656
  return generateAuthUrl({
623
- authUrl: AUTH_URL,
624
- clientId: CLIENT_ID,
657
+ authUrl: getAuthUrlFromEnv(),
658
+ clientId: getClientIdFromEnv(),
625
659
  callbackUrl: CALLBACK_URL,
626
660
  scopes,
627
661
  stateQueryParam,
@@ -648,19 +682,14 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
648
682
  logger.warn("No refresh token is present.");
649
683
  }
650
684
 
651
- const body =
652
- `grant_type=refresh_token&` +
653
- `refresh_token=${LocalState.refreshToken?.value}&` +
654
- `client_id=${CLIENT_ID}`;
655
-
656
- const response = await fetch(TOKEN_URL, {
657
- method: "POST",
658
- body,
659
- headers: {
660
- "Content-Type": "application/x-www-form-urlencoded",
661
- },
685
+ const params = new URLSearchParams({
686
+ grant_type: "refresh_token",
687
+ refresh_token: LocalState.refreshToken?.value ?? "",
688
+ client_id: getClientIdFromEnv(),
662
689
  });
663
690
 
691
+ const response = await fetchAuthToken(params);
692
+
664
693
  if (response.status >= 400) {
665
694
  let tokenExchangeResErr = undefined;
666
695
 
@@ -738,20 +767,15 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
738
767
  logger.warn("No authorization grant code is being passed.");
739
768
  }
740
769
 
741
- const body =
742
- `grant_type=authorization_code&` +
743
- `code=${encodeURIComponent(authorizationCode || "")}&` +
744
- `redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
745
- `client_id=${encodeURIComponent(CLIENT_ID)}&` +
746
- `code_verifier=${codeVerifier}`;
747
-
748
- const response = await fetch(TOKEN_URL, {
749
- method: "POST",
750
- body,
751
- headers: {
752
- "Content-Type": "application/x-www-form-urlencoded",
753
- },
770
+ const params = new URLSearchParams({
771
+ grant_type: `authorization_code`,
772
+ code: authorizationCode ?? "",
773
+ redirect_uri: CALLBACK_URL,
774
+ client_id: getClientIdFromEnv(),
775
+ code_verifier: codeVerifier,
754
776
  });
777
+
778
+ const response = await fetchAuthToken(params);
755
779
  if (!response.ok) {
756
780
  const { error } = (await response.json()) as { error: string };
757
781
  // .catch((_) => ({ error: "invalid_json" }));
@@ -870,15 +894,17 @@ export function readAuthConfigFile(): UserAuthConfig {
870
894
 
871
895
  type LoginProps = {
872
896
  scopes?: Scope[];
897
+ browser: boolean;
873
898
  };
874
899
 
875
900
  export async function loginOrRefreshIfRequired(): Promise<boolean> {
876
901
  // TODO: if there already is a token, then try refreshing
877
902
  // TODO: ask permission before opening browser
903
+ const { isCI } = CI;
878
904
  if (!getAPIToken()) {
879
905
  // Not logged in.
880
906
  // If we are not interactive, we cannot ask the user to login
881
- return isInteractive() && (await login());
907
+ return isInteractive() && !isCI() && (await login());
882
908
  } else if (isAccessTokenExpired()) {
883
909
  // We're logged in, but the refresh token seems to have expired,
884
910
  // so let's try to refresh it
@@ -888,14 +914,16 @@ export async function loginOrRefreshIfRequired(): Promise<boolean> {
888
914
  return true;
889
915
  } else {
890
916
  // If the refresh token isn't valid, then we ask the user to login again
891
- return isInteractive() && (await login());
917
+ return isInteractive() && !isCI() && (await login());
892
918
  }
893
919
  } else {
894
920
  return true;
895
921
  }
896
922
  }
897
923
 
898
- export async function login(props?: LoginProps): Promise<boolean> {
924
+ export async function login(
925
+ props: LoginProps = { browser: true }
926
+ ): Promise<boolean> {
899
927
  logger.log("Attempting to login via OAuth...");
900
928
  const urlToOpen = await getAuthURL(props?.scopes);
901
929
  let server: http.Server;
@@ -908,7 +936,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
908
936
  server.close();
909
937
  clearTimeout(loginTimeoutHandle);
910
938
  resolve(false);
911
- }, 60000); // wait for 60 seconds for the user to authorize
939
+ }, 120000); // wait for 120 seconds for the user to authorize
912
940
  });
913
941
 
914
942
  const loginPromise = new Promise<boolean>((resolve, reject) => {
@@ -959,6 +987,7 @@ export async function login(props?: LoginProps): Promise<boolean> {
959
987
  oauth_token: exchange.token?.value ?? "",
960
988
  expiration_time: exchange.token?.expiry,
961
989
  refresh_token: exchange.refreshToken?.value,
990
+ scopes: exchange.scopes,
962
991
  });
963
992
  res.writeHead(307, {
964
993
  Location:
@@ -979,9 +1008,12 @@ export async function login(props?: LoginProps): Promise<boolean> {
979
1008
 
980
1009
  server.listen(8976);
981
1010
  });
982
-
983
- logger.log(`Opening a link in your default browser: ${urlToOpen}`);
984
- await openInBrowser(urlToOpen);
1011
+ if (props?.browser) {
1012
+ logger.log(`Opening a link in your default browser: ${urlToOpen}`);
1013
+ await openInBrowser(urlToOpen);
1014
+ } else {
1015
+ logger.log(`Visit this link to authenticate: ${urlToOpen}`);
1016
+ }
985
1017
 
986
1018
  return Promise.race([timerPromise, loginPromise]);
987
1019
  }
@@ -1003,8 +1035,14 @@ async function refreshToken(): Promise<boolean> {
1003
1035
  expiry: "",
1004
1036
  },
1005
1037
  refreshToken: { value: refresh_token } = {},
1038
+ scopes,
1006
1039
  } = await exchangeRefreshTokenForAccessToken();
1007
- writeAuthConfigFile({ oauth_token, expiration_time, refresh_token });
1040
+ writeAuthConfigFile({
1041
+ oauth_token,
1042
+ expiration_time,
1043
+ refresh_token,
1044
+ scopes,
1045
+ });
1008
1046
  return true;
1009
1047
  } catch (err) {
1010
1048
  return false;
@@ -1019,11 +1057,11 @@ export async function logout(): Promise<void> {
1019
1057
  }
1020
1058
 
1021
1059
  const body =
1022
- `client_id=${encodeURIComponent(CLIENT_ID)}&` +
1060
+ `client_id=${encodeURIComponent(getClientIdFromEnv())}&` +
1023
1061
  `token_type_hint=refresh_token&` +
1024
1062
  `token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
1025
1063
 
1026
- const response = await fetch(REVOKE_URL, {
1064
+ const response = await fetch(getRevokeUrlFromEnv(), {
1027
1065
  method: "POST",
1028
1066
  body,
1029
1067
  headers: {
@@ -1036,11 +1074,11 @@ export async function logout(): Promise<void> {
1036
1074
  );
1037
1075
  }
1038
1076
  const body =
1039
- `client_id=${encodeURIComponent(CLIENT_ID)}&` +
1077
+ `client_id=${encodeURIComponent(getClientIdFromEnv())}&` +
1040
1078
  `token_type_hint=refresh_token&` +
1041
1079
  `token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
1042
1080
 
1043
- const response = await fetch(REVOKE_URL, {
1081
+ const response = await fetch(getRevokeUrlFromEnv(), {
1044
1082
  method: "POST",
1045
1083
  body,
1046
1084
  headers: {
@@ -1078,7 +1116,7 @@ export async function getAccountId(): Promise<string | undefined> {
1078
1116
  return accounts[0].id;
1079
1117
  }
1080
1118
 
1081
- if (isInteractive()) {
1119
+ if (isInteractive() && !CI.isCI()) {
1082
1120
  const account = await new Promise<{ id: string; name: string }>(
1083
1121
  (resolve, reject) => {
1084
1122
  const { unmount } = render(
@@ -1118,7 +1156,7 @@ export async function requireAuth(config: {
1118
1156
  }): Promise<string> {
1119
1157
  const loggedIn = await loginOrRefreshIfRequired();
1120
1158
  if (!loggedIn) {
1121
- if (!isInteractive()) {
1159
+ if (!isInteractive() || CI.isCI()) {
1122
1160
  throw new Error(
1123
1161
  "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."
1124
1162
  );
@@ -1169,3 +1207,32 @@ export function getAccountFromCache():
1169
1207
  "wrangler-account.json"
1170
1208
  ).account;
1171
1209
  }
1210
+
1211
+ /**
1212
+ * Get the scopes of the following token, will only return scopes
1213
+ * if the token is an OAuth token.
1214
+ */
1215
+ export function getScopes(): Scope[] | undefined {
1216
+ return LocalState.scopes;
1217
+ }
1218
+
1219
+ /**
1220
+ * Make a request to the Cloudflare OAuth endpoint to get a token.
1221
+ *
1222
+ * Note that the `body` of the POST request is form-urlencoded so
1223
+ * can be represented by a URLSearchParams object.
1224
+ */
1225
+ async function fetchAuthToken(body: URLSearchParams) {
1226
+ const headers: Record<string, string> = {
1227
+ "Content-Type": "application/x-www-form-urlencoded",
1228
+ };
1229
+ if (await domainUsesAccess(getAuthDomainFromEnv())) {
1230
+ // We are trying to access the staging API so we need an "access token".
1231
+ headers["Cookie"] = `CF_Authorization=${await getCloudflareAccessToken()}`;
1232
+ }
1233
+ return await fetch(getTokenUrlFromEnv(), {
1234
+ method: "POST",
1235
+ body: body.toString(),
1236
+ headers,
1237
+ });
1238
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * a function that takes an array of strings in `key:value` format
3
+ * (typically from yargs or config) and returns back
4
+ * a neat object with the keys and values
5
+ */
6
+ export function collectKeyValues(array?: string[]) {
7
+ return (
8
+ array?.reduce<Record<string, string>>((recordsToCollect, v) => {
9
+ const [key, ...value] = v.split(":");
10
+ recordsToCollect[key] = value.join(":");
11
+ return recordsToCollect;
12
+ }, {}) || {}
13
+ );
14
+ }
package/src/whoami.tsx CHANGED
@@ -1,9 +1,9 @@
1
1
  import { Text, render } from "ink";
2
2
  import Table from "ink-table";
3
- import React from "react";
3
+ import React, { Fragment } from "react";
4
4
  import { fetchListResult, fetchResult } from "./cfetch";
5
5
  import { logger } from "./logger";
6
- import { getAPIToken, getAuthFromEnv } from "./user";
6
+ import { getAPIToken, getAuthFromEnv, getScopes } from "./user";
7
7
 
8
8
  export async function whoami() {
9
9
  logger.log("Getting User settings...");
@@ -17,6 +17,10 @@ export function WhoAmI({ user }: { user: UserInfo | undefined }) {
17
17
  <>
18
18
  <Email tokenType={user.authType} email={user.email}></Email>
19
19
  <Accounts accounts={user.accounts}></Accounts>
20
+ <Permissions
21
+ tokenType={user.authType}
22
+ tokenPermissions={user.tokenPermissions}
23
+ />
20
24
  </>
21
25
  ) : (
22
26
  <Text>You are not authenticated. Please run `wrangler login`.</Text>
@@ -46,17 +50,51 @@ function Accounts(props: { accounts: AccountInfo[] }) {
46
50
  return <Table data={accounts}></Table>;
47
51
  }
48
52
 
53
+ function Permissions(props: {
54
+ tokenPermissions: string[] | undefined;
55
+ tokenType: string;
56
+ }) {
57
+ const permissions =
58
+ props.tokenPermissions?.map((scope) => scope.split(":")) || [];
59
+ return props.tokenType === "OAuth Token" ? (
60
+ props.tokenPermissions ? (
61
+ <>
62
+ <Text>
63
+ 🔓 Token Permissions: If scopes are missing, you may need to logout
64
+ and re-login.
65
+ </Text>
66
+ <Text>Scope (Access)</Text>
67
+ {permissions.map(([scope, access], index) => (
68
+ <Fragment key={`${scope}${index}`}>
69
+ <Text>
70
+ - {scope} {access && `(${access})`}
71
+ </Text>
72
+ </Fragment>
73
+ ))}
74
+ </>
75
+ ) : null
76
+ ) : (
77
+ <Text>
78
+ 🔓 To see token permissions visit
79
+ https://dash.cloudflare.com/profile/api-tokens
80
+ </Text>
81
+ );
82
+ }
83
+
49
84
  export interface UserInfo {
50
85
  apiToken: string;
51
86
  authType: string;
52
87
  email: string | undefined;
53
88
  accounts: AccountInfo[];
89
+ tokenPermissions: string[] | undefined;
54
90
  }
55
91
 
56
92
  export async function getUserInfo(): Promise<UserInfo | undefined> {
57
93
  const apiToken = getAPIToken();
58
94
  if (!apiToken) return;
59
95
 
96
+ const tokenPermissions = await getTokenPermissions();
97
+
60
98
  const usingEnvAuth = !!getAuthFromEnv();
61
99
  const usingGlobalAuthKey = "authKey" in apiToken;
62
100
  return {
@@ -68,6 +106,7 @@ export async function getUserInfo(): Promise<UserInfo | undefined> {
68
106
  : "OAuth Token",
69
107
  email: "authEmail" in apiToken ? apiToken.authEmail : await getEmail(),
70
108
  accounts: await getAccounts(),
109
+ tokenPermissions,
71
110
  };
72
111
  }
73
112
 
@@ -89,3 +128,42 @@ type AccountInfo = { name: string; id: string };
89
128
  async function getAccounts(): Promise<AccountInfo[]> {
90
129
  return await fetchListResult<AccountInfo>("/accounts");
91
130
  }
131
+
132
+ async function getTokenPermissions(): Promise<string[] | undefined> {
133
+ // Tokens can either be API tokens or Oauth tokens.
134
+ // Here we only extract permissions from OAuth tokens.
135
+
136
+ return getScopes() as string[];
137
+
138
+ // In future we may be able to get the token permissions on an API token,
139
+ // but currently we cannot as that permission is not able to be added to
140
+ // an API token.
141
+
142
+ // try {
143
+ // // First we get the token identifier (only returned on API tokens)
144
+ // const { id } = await fetchResult<{ id: string }>("/user/tokens/verify");
145
+
146
+ // // Get the token permissions for the current token
147
+ // const { policies } = await fetchResult<{
148
+ // policies: { id: string; name: string }[];
149
+ // }>(`/user/tokens/${id}`);
150
+
151
+ // return policies.map((p) => p.name);
152
+ // } catch (e) {
153
+ // if ((e as { code?: number }).code === 1000) {
154
+ // // Invalid token - Oauth token
155
+
156
+ // // Get scopes
157
+ // const scopes = getScopes() as string[];
158
+ // // We may not get scopes back if they are not currently cached,
159
+ // // however next time an access token is requested,
160
+ // // the scopes will be added to the cache.
161
+ // return scopes;
162
+ // } else if ((e as { code?: number }).code === 9109) {
163
+ // // Token cannot view its permissions
164
+ // return undefined;
165
+ // } else {
166
+ // throw e;
167
+ // }
168
+ // }
169
+ }
package/src/worker.ts CHANGED
@@ -1,9 +1,11 @@
1
+ import type { Environment } from "./config";
1
2
  import type { Route } from "./config/environment";
2
3
  import type { ApiCredentials } from "./user";
3
4
 
4
5
  /**
5
6
  * A Cloudflare account.
6
7
  */
8
+
7
9
  export interface CfAccount {
8
10
  /**
9
11
  * An API token.
@@ -65,14 +67,14 @@ export interface CfModule {
65
67
  /**
66
68
  * A map of variable names to values.
67
69
  */
68
- interface CfVars {
70
+ export interface CfVars {
69
71
  [key: string]: unknown;
70
72
  }
71
73
 
72
74
  /**
73
75
  * A KV namespace.
74
76
  */
75
- interface CfKvNamespace {
77
+ export interface CfKvNamespace {
76
78
  binding: string;
77
79
  id: string;
78
80
  }
@@ -81,7 +83,7 @@ interface CfKvNamespace {
81
83
  * A binding to a wasm module (in service-worker format)
82
84
  */
83
85
 
84
- interface CfWasmModuleBindings {
86
+ export interface CfWasmModuleBindings {
85
87
  [key: string]: string;
86
88
  }
87
89
 
@@ -89,7 +91,7 @@ interface CfWasmModuleBindings {
89
91
  * A binding to a text blob (in service-worker format)
90
92
  */
91
93
 
92
- interface CfTextBlobBindings {
94
+ export interface CfTextBlobBindings {
93
95
  [key: string]: string;
94
96
  }
95
97
 
@@ -97,36 +99,71 @@ interface CfTextBlobBindings {
97
99
  * A binding to a data blob (in service-worker format)
98
100
  */
99
101
 
100
- interface CfDataBlobBindings {
102
+ export interface CfDataBlobBindings {
101
103
  [key: string]: string;
102
104
  }
103
105
 
104
106
  /**
105
107
  * A Durable Object.
106
108
  */
107
- interface CfDurableObject {
109
+ export interface CfDurableObject {
108
110
  name: string;
109
111
  class_name: string;
110
112
  script_name?: string;
111
113
  environment?: string;
112
114
  }
113
115
 
114
- interface CfR2Bucket {
116
+ export interface CfQueue {
117
+ binding: string;
118
+ queue_name: string;
119
+ }
120
+
121
+ export interface CfR2Bucket {
115
122
  binding: string;
116
123
  bucket_name: string;
117
124
  }
118
125
 
126
+ export const D1_BETA_PREFIX = `__D1_BETA__` as const;
127
+ export type D1PrefixedBinding = `${typeof D1_BETA_PREFIX}${string}`;
128
+
129
+ // TODO: figure out if this is duplicated in packages/wrangler/src/config/environment.ts
130
+ export interface CfD1Database {
131
+ // For now, all D1 bindings are alpha
132
+ binding: D1PrefixedBinding;
133
+ database_id: string;
134
+ database_name?: string;
135
+ preview_database_id?: string;
136
+ database_internal_env?: string;
137
+ migrations_table?: string;
138
+ migrations_dir?: string;
139
+ }
140
+
119
141
  interface CfService {
120
142
  binding: string;
121
143
  service: string;
122
144
  environment?: string;
123
145
  }
124
146
 
125
- interface CfWorkerNamespace {
147
+ interface CfAnalyticsEngineDataset {
148
+ binding: string;
149
+ dataset?: string;
150
+ }
151
+
152
+ interface CfDispatchNamespace {
126
153
  binding: string;
127
154
  namespace: string;
128
155
  }
129
156
 
157
+ interface CfLogfwdr {
158
+ schema: string | undefined;
159
+ bindings: CfLogfwdrBinding[];
160
+ }
161
+
162
+ interface CfLogfwdrBinding {
163
+ name: string;
164
+ destination: string;
165
+ }
166
+
130
167
  interface CfUnsafeBinding {
131
168
  name: string;
132
169
  type: string;
@@ -171,15 +208,21 @@ export interface CfWorkerInit {
171
208
  text_blobs: CfTextBlobBindings | undefined;
172
209
  data_blobs: CfDataBlobBindings | undefined;
173
210
  durable_objects: { bindings: CfDurableObject[] } | undefined;
211
+ queues: CfQueue[] | undefined;
174
212
  r2_buckets: CfR2Bucket[] | undefined;
213
+ d1_databases: CfD1Database[] | undefined;
175
214
  services: CfService[] | undefined;
176
- worker_namespaces: CfWorkerNamespace[] | undefined;
215
+ analytics_engine_datasets: CfAnalyticsEngineDataset[] | undefined;
216
+ dispatch_namespaces: CfDispatchNamespace[] | undefined;
217
+ logfwdr: CfLogfwdr | undefined;
177
218
  unsafe: CfUnsafeBinding[] | undefined;
178
219
  };
179
220
  migrations: CfDurableObjectMigrations | undefined;
180
221
  compatibility_date: string | undefined;
181
222
  compatibility_flags: string[] | undefined;
182
223
  usage_model: "bundled" | "unbound" | undefined;
224
+ keepVars: boolean | undefined;
225
+ logpush: boolean | undefined;
183
226
  }
184
227
 
185
228
  export interface CfWorkerContext {
@@ -188,4 +231,21 @@ export interface CfWorkerContext {
188
231
  zone: string | undefined;
189
232
  host: string | undefined;
190
233
  routes: Route[] | undefined;
234
+ sendMetrics: boolean | undefined;
235
+ }
236
+
237
+ // Prefix binding with identifier which will then get picked up by the D1 shim.
238
+ // Once the D1 Api is out of beta, this function can be removed.
239
+ export function identifyD1BindingsAsBeta(
240
+ dbs: Environment["d1_databases"]
241
+ ): CfD1Database[] | undefined {
242
+ return dbs?.map((db) => ({
243
+ ...db,
244
+ binding: `${D1_BETA_PREFIX}${db.binding}`,
245
+ }));
246
+ }
247
+
248
+ // Remove beta prefix
249
+ export function removeD1BetaPrefix(binding: D1PrefixedBinding): string {
250
+ return binding.slice(D1_BETA_PREFIX.length);
191
251
  }
@@ -0,0 +1,24 @@
1
+ import type { ArgumentsCamelCase, Argv } from "yargs";
2
+
3
+ /**
4
+ * Yargs options included in every wrangler command.
5
+ */
6
+ export interface CommonYargsOptions {
7
+ v: boolean | undefined;
8
+ config: string | undefined;
9
+ env: string | undefined;
10
+ }
11
+
12
+ export type YargvToInterface<T> = T extends Argv<infer P>
13
+ ? ArgumentsCamelCase<P>
14
+ : never;
15
+
16
+ /**
17
+ * Given some Yargs Options function factory, extract the interface
18
+ * that corresponds to the yargs arguments
19
+ */
20
+ export type YargsOptionsToInterface<
21
+ T extends (yargs: Argv<CommonYargsOptions>) => Argv
22
+ > = T extends (yargs: Argv<CommonYargsOptions>) => Argv<infer P>
23
+ ? ArgumentsCamelCase<P>
24
+ : never;