wrangler 0.0.0-e928f94 → 0.0.0-e95bea2
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.
- package/bin/wrangler.js +2 -2
- package/package.json +46 -18
- package/pages/functions/buildWorker.ts +62 -0
- package/pages/functions/filepath-routing.test.ts +123 -0
- package/pages/functions/filepath-routing.ts +178 -0
- package/pages/functions/identifiers.ts +78 -0
- package/pages/functions/routes.ts +151 -0
- package/pages/functions/template-worker.ts +130 -0
- package/src/__tests__/configuration.test.ts +2217 -0
- package/src/__tests__/dev.test.tsx +405 -0
- package/src/__tests__/dev2.test.tsx +152 -0
- package/src/__tests__/guess-worker-format.test.ts +58 -0
- package/src/__tests__/{clipboardy-mock.js → helpers/clipboardy-mock.js} +0 -0
- package/src/__tests__/helpers/cmd-shim.d.ts +11 -0
- package/src/__tests__/helpers/faye-websocket.d.ts +6 -0
- package/src/__tests__/helpers/mock-account-id.ts +44 -0
- package/src/__tests__/helpers/mock-bin.ts +36 -0
- package/src/__tests__/helpers/mock-cfetch.ts +207 -0
- package/src/__tests__/helpers/mock-console.ts +70 -0
- package/src/__tests__/helpers/mock-dialogs.ts +65 -0
- package/src/__tests__/helpers/mock-kv.ts +40 -0
- package/src/__tests__/helpers/mock-stdin.ts +103 -0
- package/src/__tests__/helpers/mock-user.ts +27 -0
- package/src/__tests__/helpers/mock-web-socket.ts +29 -0
- package/src/__tests__/helpers/run-in-tmp.ts +29 -0
- package/src/__tests__/helpers/run-wrangler.ts +16 -0
- package/src/__tests__/helpers/write-wrangler-toml.ts +17 -0
- package/src/__tests__/https-options.test.ts +127 -0
- package/src/__tests__/index.test.ts +957 -119
- package/src/__tests__/jest.setup.ts +69 -0
- package/src/__tests__/kv.test.ts +1408 -0
- package/src/__tests__/logout.test.ts +50 -0
- package/src/__tests__/package-manager.test.ts +206 -0
- package/src/__tests__/pages.test.ts +44 -0
- package/src/__tests__/parse.test.ts +322 -0
- package/src/__tests__/publish.test.ts +2453 -0
- package/src/__tests__/r2.test.ts +198 -0
- package/src/__tests__/route.test.ts +45 -0
- package/src/__tests__/secret.test.ts +425 -0
- package/src/__tests__/sentry.test.ts +146 -0
- package/src/__tests__/tail.test.ts +564 -0
- package/src/__tests__/whoami.test.tsx +86 -0
- package/src/bundle.ts +142 -0
- package/src/cfetch/index.ts +104 -0
- package/src/cfetch/internal.ts +135 -0
- package/src/cli.ts +16 -7
- package/src/config/README.md +107 -0
- package/src/config/config.ts +279 -0
- package/src/config/diagnostics.ts +80 -0
- package/src/config/environment.ts +305 -0
- package/src/config/index.ts +52 -0
- package/src/config/validation-helpers.ts +388 -0
- package/src/config/validation.ts +1027 -0
- package/src/{api/preview.ts → create-worker-preview.ts} +63 -25
- package/src/create-worker-upload-form.ts +217 -0
- package/src/dev/dev.tsx +357 -0
- package/src/dev/local.tsx +241 -0
- package/src/dev/remote.tsx +201 -0
- package/src/dev/use-esbuild.ts +100 -0
- package/src/dialogs.tsx +5 -13
- package/src/entry.ts +159 -0
- package/src/environment-variables.ts +33 -0
- package/src/https-options.ts +122 -0
- package/src/index.tsx +1374 -903
- package/src/inspect.ts +617 -0
- package/src/kv/index.tsx +339 -0
- package/src/module-collection.ts +215 -26
- package/src/open-in-browser.ts +25 -0
- package/src/package-manager.ts +126 -0
- package/src/pages.tsx +452 -250
- package/src/parse.ts +183 -0
- package/src/paths.ts +26 -0
- package/src/proxy.ts +438 -0
- package/src/publish.ts +300 -292
- package/src/r2.ts +50 -0
- package/src/reporting.ts +131 -0
- package/src/selfsigned.d.ts +29 -0
- package/src/sites.tsx +181 -58
- package/src/tail/filters.ts +279 -0
- package/src/tail/index.ts +299 -0
- package/src/tail/printing.ts +62 -0
- package/src/user.tsx +166 -130
- package/src/whoami.tsx +69 -0
- package/src/worker.ts +163 -0
- package/templates/new-worker.js +15 -0
- package/templates/new-worker.ts +15 -0
- package/{static-asset-facade.js → templates/static-asset-facade.js} +0 -0
- package/templates/tsconfig.json +105 -0
- package/wrangler-dist/cli.js +131958 -115793
- package/wrangler-dist/cli.js.map +3 -3
- package/src/__tests__/fixtures/init/.gitkeep +0 -0
- package/src/__tests__/mock-cfetch.js +0 -46
- package/src/api/form_data.ts +0 -141
- package/src/api/inspect.ts +0 -441
- package/src/api/worker.ts +0 -161
- package/src/cfetch.ts +0 -72
- package/src/config.ts +0 -122
- package/src/dev.tsx +0 -724
- package/src/kv.tsx +0 -211
- package/src/tail.tsx +0 -71
package/src/user.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Based heavily on code from https://github.com/BitySA/oauth2-auth-code-pkce */
|
|
2
2
|
|
|
3
|
-
/*
|
|
3
|
+
/*
|
|
4
4
|
|
|
5
5
|
Apache License
|
|
6
6
|
Version 2.0, January 2004
|
|
@@ -205,25 +205,42 @@
|
|
|
205
205
|
limitations under the License.
|
|
206
206
|
*/
|
|
207
207
|
|
|
208
|
-
import
|
|
209
|
-
import { render } from "ink";
|
|
210
|
-
import Table from "ink-table";
|
|
211
|
-
import SelectInput from "ink-select-input";
|
|
212
|
-
import fetch from "node-fetch";
|
|
208
|
+
import assert from "node:assert";
|
|
213
209
|
import { webcrypto as crypto } from "node:crypto";
|
|
214
|
-
import { TextEncoder } from "node:util";
|
|
215
|
-
import open from "open";
|
|
216
|
-
import url from "node:url";
|
|
217
|
-
import http from "node:http";
|
|
218
210
|
import { readFile, writeFile, rm, mkdir } from "node:fs/promises";
|
|
219
|
-
import
|
|
220
|
-
import process from "node:process";
|
|
211
|
+
import http from "node:http";
|
|
221
212
|
import os from "node:os";
|
|
213
|
+
import path from "node:path";
|
|
214
|
+
import url from "node:url";
|
|
215
|
+
import { TextEncoder } from "node:util";
|
|
222
216
|
import TOML from "@iarna/toml";
|
|
223
|
-
import
|
|
217
|
+
import { render, Text } from "ink";
|
|
218
|
+
import SelectInput from "ink-select-input";
|
|
219
|
+
import Table from "ink-table";
|
|
220
|
+
import React from "react";
|
|
221
|
+
import { fetch } from "undici";
|
|
222
|
+
import { getCloudflareApiBaseUrl } from "./cfetch";
|
|
223
|
+
import { getEnvironmentVariableFactory } from "./environment-variables";
|
|
224
|
+
import openInBrowser from "./open-in-browser";
|
|
225
|
+
import type { Item as SelectInputItem } from "ink-select-input/build/SelectInput";
|
|
224
226
|
import type { ParsedUrlQuery } from "node:querystring";
|
|
225
|
-
import {
|
|
226
|
-
|
|
227
|
+
import type { Response } from "undici";
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Try to read the API token from the environment.
|
|
231
|
+
*/
|
|
232
|
+
const getCloudflareAPITokenFromEnv = getEnvironmentVariableFactory({
|
|
233
|
+
variableName: "CLOUDFLARE_API_TOKEN",
|
|
234
|
+
deprecatedName: "CF_API_TOKEN",
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Try to read the account ID from the environment.
|
|
239
|
+
*/
|
|
240
|
+
const getCloudflareAccountIdFromEnv = getEnvironmentVariableFactory({
|
|
241
|
+
variableName: "CLOUDFLARE_ACCOUNT_ID",
|
|
242
|
+
deprecatedName: "CF_ACCOUNT_ID",
|
|
243
|
+
});
|
|
227
244
|
|
|
228
245
|
/**
|
|
229
246
|
* An implementation of rfc6749#section-4.1 and rfc7636.
|
|
@@ -254,38 +271,39 @@ interface AccessToken {
|
|
|
254
271
|
expiry: string;
|
|
255
272
|
}
|
|
256
273
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
"
|
|
271
|
-
"
|
|
272
|
-
|
|
273
|
-
"
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
274
|
+
const Scopes = {
|
|
275
|
+
"account:read":
|
|
276
|
+
"See your account info such as account details, analytics, and memberships.",
|
|
277
|
+
"user:read":
|
|
278
|
+
"See your user info such as name, email address, and account memberships.",
|
|
279
|
+
"workers:write":
|
|
280
|
+
"See and change Cloudflare Workers data such as zones, KV storage, namespaces, scripts, and routes.",
|
|
281
|
+
"workers_kv:write":
|
|
282
|
+
"See and change Cloudflare Workers KV Storage data such as keys and namespaces.",
|
|
283
|
+
"workers_routes:write":
|
|
284
|
+
"See and change Cloudflare Workers data such as filters and routes.",
|
|
285
|
+
"workers_scripts:write":
|
|
286
|
+
"See and change Cloudflare Workers scripts, durable objects, subdomains, triggers, and tail data.",
|
|
287
|
+
"workers_tail:read": "See Cloudflare Workers tail and script data.",
|
|
288
|
+
"pages:write":
|
|
289
|
+
"See and change Cloudflare Pages projects, settings and deployments.",
|
|
290
|
+
"zone:read": "Grants read level access to account zone.",
|
|
291
|
+
} as const;
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* The possible keys for a Scope.
|
|
295
|
+
*
|
|
296
|
+
* "offline_access" is automatically included.
|
|
297
|
+
*/
|
|
298
|
+
type Scope = keyof typeof Scopes;
|
|
299
|
+
|
|
300
|
+
const ScopeKeys = Object.keys(Scopes) as Scope[];
|
|
301
|
+
|
|
302
|
+
export function validateScopeKeys(
|
|
303
|
+
scopes: string[]
|
|
304
|
+
): scopes is typeof ScopeKeys {
|
|
305
|
+
return scopes.every((scope) => scope in Scopes);
|
|
306
|
+
}
|
|
289
307
|
|
|
290
308
|
const CLIENT_ID = "54d11594-84e4-41aa-b438-e81b8fa78ee7";
|
|
291
309
|
const AUTH_URL = "https://dash.cloudflare.com/oauth2/auth";
|
|
@@ -298,14 +316,15 @@ let initialised = false;
|
|
|
298
316
|
|
|
299
317
|
// we do this because we have some async stuff
|
|
300
318
|
// TODO: this should just happen in the top level
|
|
301
|
-
//
|
|
319
|
+
// and we should figure out how to do top level await
|
|
302
320
|
export async function initialise(): Promise<void> {
|
|
303
|
-
// get
|
|
321
|
+
// get refreshToken/accessToken from fs if exists
|
|
304
322
|
try {
|
|
305
|
-
// if
|
|
306
|
-
|
|
323
|
+
// if the environment variable is available, use that
|
|
324
|
+
const apiTokenFromEnv = getCloudflareAPITokenFromEnv();
|
|
325
|
+
if (apiTokenFromEnv) {
|
|
307
326
|
LocalState.accessToken = {
|
|
308
|
-
value:
|
|
327
|
+
value: apiTokenFromEnv,
|
|
309
328
|
expiry: "3021-12-31T23:59:59+00:00",
|
|
310
329
|
};
|
|
311
330
|
initialised = true;
|
|
@@ -344,11 +363,7 @@ function throwIfNotInitialised() {
|
|
|
344
363
|
}
|
|
345
364
|
}
|
|
346
365
|
|
|
347
|
-
export function getAPIToken(): string {
|
|
348
|
-
if (process.env.CF_API_TOKEN) {
|
|
349
|
-
return process.env.CF_API_TOKEN;
|
|
350
|
-
}
|
|
351
|
-
|
|
366
|
+
export function getAPIToken(): string | undefined {
|
|
352
367
|
throwIfNotInitialised();
|
|
353
368
|
return LocalState.accessToken?.value;
|
|
354
369
|
}
|
|
@@ -548,7 +563,7 @@ function isReturningFromAuthServer(query: ParsedUrlQuery): boolean {
|
|
|
548
563
|
return true;
|
|
549
564
|
}
|
|
550
565
|
|
|
551
|
-
export async function getAuthURL(scopes
|
|
566
|
+
export async function getAuthURL(scopes = ScopeKeys): Promise<string> {
|
|
552
567
|
const { codeChallenge, codeVerifier } = await generatePKCECodes();
|
|
553
568
|
const stateQueryParam = generateRandomState(RECOMMENDED_STATE_LENGTH);
|
|
554
569
|
|
|
@@ -558,39 +573,44 @@ export async function getAuthURL(scopes?: string[]): Promise<string> {
|
|
|
558
573
|
stateQueryParam,
|
|
559
574
|
});
|
|
560
575
|
|
|
561
|
-
// TODO: verify that the scopes passed are legit
|
|
562
|
-
|
|
563
576
|
return (
|
|
564
577
|
AUTH_URL +
|
|
565
578
|
`?response_type=code&` +
|
|
566
579
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
567
580
|
`redirect_uri=${encodeURIComponent(CALLBACK_URL)}&` +
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
)}&` +
|
|
581
|
+
// @ts-expect-error we add offline_access manually
|
|
582
|
+
`scope=${encodeURIComponent(scopes.concat("offline_access").join(" "))}&` +
|
|
571
583
|
`state=${stateQueryParam}&` +
|
|
572
584
|
`code_challenge=${encodeURIComponent(codeChallenge)}&` +
|
|
573
585
|
`code_challenge_method=S256`
|
|
574
586
|
);
|
|
575
587
|
}
|
|
576
588
|
|
|
589
|
+
type TokenResponse =
|
|
590
|
+
| {
|
|
591
|
+
access_token: string;
|
|
592
|
+
expires_in: number;
|
|
593
|
+
refresh_token: string;
|
|
594
|
+
scope: string;
|
|
595
|
+
}
|
|
596
|
+
| {
|
|
597
|
+
error: string;
|
|
598
|
+
};
|
|
599
|
+
|
|
577
600
|
/**
|
|
578
601
|
* Refresh an access token from the remote service.
|
|
579
602
|
*/
|
|
580
603
|
async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if (!refreshToken) {
|
|
604
|
+
if (!LocalState.refreshToken) {
|
|
584
605
|
console.warn("No refresh token is present.");
|
|
585
606
|
}
|
|
586
607
|
|
|
587
|
-
const url = TOKEN_URL;
|
|
588
608
|
const body =
|
|
589
609
|
`grant_type=refresh_token&` +
|
|
590
|
-
`refresh_token=${refreshToken?.value}&` +
|
|
610
|
+
`refresh_token=${LocalState.refreshToken?.value}&` +
|
|
591
611
|
`client_id=${CLIENT_ID}`;
|
|
592
612
|
|
|
593
|
-
const response = await fetch(
|
|
613
|
+
const response = await fetch(TOKEN_URL, {
|
|
594
614
|
method: "POST",
|
|
595
615
|
body,
|
|
596
616
|
headers: {
|
|
@@ -601,13 +621,12 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
601
621
|
throw await response.json();
|
|
602
622
|
} else {
|
|
603
623
|
try {
|
|
604
|
-
const json = await response.json();
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
};
|
|
624
|
+
const json = (await response.json()) as TokenResponse;
|
|
625
|
+
if ("error" in json) {
|
|
626
|
+
throw json.error;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
const { access_token, expires_in, refresh_token, scope } = json;
|
|
611
630
|
let scopes: Scope[] = [];
|
|
612
631
|
|
|
613
632
|
const accessToken: AccessToken = {
|
|
@@ -617,10 +636,9 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
617
636
|
LocalState.accessToken = accessToken;
|
|
618
637
|
|
|
619
638
|
if (refresh_token) {
|
|
620
|
-
|
|
639
|
+
LocalState.refreshToken = {
|
|
621
640
|
value: refresh_token,
|
|
622
641
|
};
|
|
623
|
-
LocalState.refreshToken = refreshToken;
|
|
624
642
|
}
|
|
625
643
|
|
|
626
644
|
if (scope) {
|
|
@@ -636,20 +654,24 @@ async function exchangeRefreshTokenForAccessToken(): Promise<AccessContext> {
|
|
|
636
654
|
refreshToken: LocalState.refreshToken,
|
|
637
655
|
};
|
|
638
656
|
return accessContext;
|
|
639
|
-
} catch (
|
|
640
|
-
const error = err?.error || "There was a network error.";
|
|
657
|
+
} catch (error) {
|
|
641
658
|
switch (error) {
|
|
642
659
|
case "invalid_grant":
|
|
643
|
-
console.
|
|
660
|
+
console.warn(
|
|
644
661
|
"Expired! Auth code or refresh token needs to be renewed."
|
|
645
662
|
);
|
|
646
663
|
// alert("Redirecting to auth server to obtain a new auth grant code.");
|
|
647
664
|
// TODO: return refreshAuthCodeOrRefreshToken();
|
|
648
665
|
break;
|
|
649
666
|
default:
|
|
667
|
+
console.error(error);
|
|
650
668
|
break;
|
|
651
669
|
}
|
|
652
|
-
|
|
670
|
+
if (typeof error === "string") {
|
|
671
|
+
throw toErrorClass(error);
|
|
672
|
+
} else {
|
|
673
|
+
throw error;
|
|
674
|
+
}
|
|
653
675
|
}
|
|
654
676
|
}
|
|
655
677
|
}
|
|
@@ -666,7 +688,6 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
666
688
|
console.warn("No authorization grant code is being passed.");
|
|
667
689
|
}
|
|
668
690
|
|
|
669
|
-
const url = TOKEN_URL;
|
|
670
691
|
const body =
|
|
671
692
|
`grant_type=authorization_code&` +
|
|
672
693
|
`code=${encodeURIComponent(authorizationCode || "")}&` +
|
|
@@ -674,7 +695,7 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
674
695
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
675
696
|
`code_verifier=${codeVerifier}`;
|
|
676
697
|
|
|
677
|
-
const response = await fetch(
|
|
698
|
+
const response = await fetch(TOKEN_URL, {
|
|
678
699
|
method: "POST",
|
|
679
700
|
body,
|
|
680
701
|
headers: {
|
|
@@ -691,13 +712,11 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
691
712
|
}
|
|
692
713
|
throw toErrorClass(error);
|
|
693
714
|
}
|
|
694
|
-
const json = await response.json();
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
scope: string;
|
|
700
|
-
};
|
|
715
|
+
const json = (await response.json()) as TokenResponse;
|
|
716
|
+
if ("error" in json) {
|
|
717
|
+
throw new Error(json.error);
|
|
718
|
+
}
|
|
719
|
+
const { access_token, expires_in, refresh_token, scope } = json;
|
|
701
720
|
let scopes: Scope[] = [];
|
|
702
721
|
LocalState.hasAuthCodeBeenExchangedForAccessToken = true;
|
|
703
722
|
|
|
@@ -709,10 +728,9 @@ async function exchangeAuthCodeForAccessToken(): Promise<AccessContext> {
|
|
|
709
728
|
LocalState.accessToken = accessToken;
|
|
710
729
|
|
|
711
730
|
if (refresh_token) {
|
|
712
|
-
|
|
731
|
+
LocalState.refreshToken = {
|
|
713
732
|
value: refresh_token,
|
|
714
733
|
};
|
|
715
|
-
LocalState.refreshToken = refreshToken;
|
|
716
734
|
}
|
|
717
735
|
|
|
718
736
|
if (scope) {
|
|
@@ -798,15 +816,18 @@ expiration_time = "${tokenData.token?.expiry}"
|
|
|
798
816
|
}
|
|
799
817
|
|
|
800
818
|
type LoginProps = {
|
|
801
|
-
scopes?:
|
|
819
|
+
scopes?: Scope[];
|
|
802
820
|
};
|
|
803
821
|
|
|
804
|
-
export async function loginOrRefreshIfRequired(
|
|
822
|
+
export async function loginOrRefreshIfRequired(
|
|
823
|
+
isInteractive = true
|
|
824
|
+
): Promise<boolean> {
|
|
805
825
|
// TODO: if there already is a token, then try refreshing
|
|
806
826
|
// TODO: ask permission before opening browser
|
|
807
827
|
if (!LocalState.accessToken) {
|
|
808
|
-
//
|
|
809
|
-
|
|
828
|
+
// Not logged in.
|
|
829
|
+
// If we are not interactive, we cannot ask the user to login
|
|
830
|
+
return isInteractive && (await login());
|
|
810
831
|
} else if (isAccessTokenExpired()) {
|
|
811
832
|
return await refreshToken();
|
|
812
833
|
} else {
|
|
@@ -816,18 +837,18 @@ export async function loginOrRefreshIfRequired(): Promise<boolean> {
|
|
|
816
837
|
|
|
817
838
|
export async function login(props?: LoginProps): Promise<boolean> {
|
|
818
839
|
const urlToOpen = await getAuthURL(props?.scopes);
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
let server;
|
|
823
|
-
let loginTimeoutHandle;
|
|
840
|
+
await openInBrowser(urlToOpen);
|
|
841
|
+
let server: http.Server;
|
|
842
|
+
let loginTimeoutHandle: NodeJS.Timeout;
|
|
824
843
|
const timerPromise = new Promise<boolean>((resolve) => {
|
|
825
844
|
loginTimeoutHandle = setTimeout(() => {
|
|
826
|
-
console.error(
|
|
845
|
+
console.error(
|
|
846
|
+
"Timed out waiting for authorization code, please try again."
|
|
847
|
+
);
|
|
827
848
|
server.close();
|
|
828
849
|
clearTimeout(loginTimeoutHandle);
|
|
829
850
|
resolve(false);
|
|
830
|
-
}, 60000); // wait for
|
|
851
|
+
}, 60000); // wait for 60 seconds for the user to authorize
|
|
831
852
|
});
|
|
832
853
|
|
|
833
854
|
const loginPromise = new Promise<boolean>((resolve, reject) => {
|
|
@@ -858,8 +879,9 @@ export async function login(props?: LoginProps): Promise<boolean> {
|
|
|
858
879
|
finish(false);
|
|
859
880
|
});
|
|
860
881
|
console.log(
|
|
861
|
-
"Error: Consent denied. You must grant consent to Wrangler in order to login
|
|
862
|
-
|
|
882
|
+
"Error: Consent denied. You must grant consent to Wrangler in order to login.\n" +
|
|
883
|
+
"If you don't want to do this consider passing an API token via the `CLOUDFLARE_API_TOKEN` environment variable"
|
|
884
|
+
);
|
|
863
885
|
|
|
864
886
|
return;
|
|
865
887
|
} else {
|
|
@@ -921,15 +943,14 @@ export async function refreshToken(): Promise<boolean> {
|
|
|
921
943
|
|
|
922
944
|
export async function logout(): Promise<void> {
|
|
923
945
|
throwIfNotInitialised();
|
|
924
|
-
|
|
925
|
-
if (!refreshToken) {
|
|
946
|
+
if (!LocalState.refreshToken) {
|
|
926
947
|
console.log("Not logged in, exiting...");
|
|
927
948
|
return;
|
|
928
949
|
}
|
|
929
950
|
const body =
|
|
930
951
|
`client_id=${encodeURIComponent(CLIENT_ID)}&` +
|
|
931
952
|
`token_type_hint=refresh_token&` +
|
|
932
|
-
`token=${encodeURIComponent(refreshToken?.value || "")}`;
|
|
953
|
+
`token=${encodeURIComponent(LocalState.refreshToken?.value || "")}`;
|
|
933
954
|
|
|
934
955
|
const response = await fetch(REVOKE_URL, {
|
|
935
956
|
method: "POST",
|
|
@@ -952,25 +973,28 @@ export async function logout(): Promise<void> {
|
|
|
952
973
|
export function listScopes(): void {
|
|
953
974
|
throwIfNotInitialised();
|
|
954
975
|
console.log("💁 Available scopes:");
|
|
955
|
-
const data =
|
|
976
|
+
const data = ScopeKeys.map((scope: Scope) => ({
|
|
956
977
|
Scope: scope,
|
|
957
|
-
Description:
|
|
978
|
+
Description: Scopes[scope],
|
|
958
979
|
}));
|
|
959
980
|
render(<Table data={data} />);
|
|
960
981
|
// TODO: maybe a good idea to show usage here
|
|
961
982
|
}
|
|
962
983
|
|
|
963
|
-
export async function getAccountId(
|
|
984
|
+
export async function getAccountId(
|
|
985
|
+
isInteractive = true
|
|
986
|
+
): Promise<string | undefined> {
|
|
964
987
|
const apiToken = getAPIToken();
|
|
965
988
|
if (!apiToken) return;
|
|
966
989
|
|
|
967
|
-
|
|
968
|
-
|
|
990
|
+
const accountIdFromEnv = getCloudflareAccountIdFromEnv();
|
|
991
|
+
if (accountIdFromEnv) {
|
|
992
|
+
return accountIdFromEnv;
|
|
969
993
|
}
|
|
970
994
|
|
|
971
995
|
let response: Response;
|
|
972
996
|
try {
|
|
973
|
-
response = await fetch(`${
|
|
997
|
+
response = await fetch(`${getCloudflareApiBaseUrl()}/memberships`, {
|
|
974
998
|
method: "GET",
|
|
975
999
|
headers: {
|
|
976
1000
|
Authorization: "Bearer " + apiToken,
|
|
@@ -978,19 +1002,19 @@ export async function getAccountId() {
|
|
|
978
1002
|
});
|
|
979
1003
|
} catch (err) {
|
|
980
1004
|
// probably offline
|
|
1005
|
+
return;
|
|
981
1006
|
}
|
|
982
|
-
|
|
983
|
-
let accountId: string;
|
|
984
|
-
|
|
985
|
-
const responseJSON: {
|
|
1007
|
+
|
|
1008
|
+
let accountId: string | undefined;
|
|
1009
|
+
const responseJSON = (await response.json()) as {
|
|
986
1010
|
success: boolean;
|
|
987
1011
|
result: { id: string; account: { id: string; name: string } }[];
|
|
988
|
-
}
|
|
1012
|
+
};
|
|
989
1013
|
|
|
990
1014
|
if (responseJSON.success === true) {
|
|
991
1015
|
if (responseJSON.result.length === 1) {
|
|
992
1016
|
accountId = responseJSON.result[0].account.id;
|
|
993
|
-
} else {
|
|
1017
|
+
} else if (isInteractive) {
|
|
994
1018
|
accountId = await new Promise((resolve) => {
|
|
995
1019
|
const accounts = responseJSON.result.map((x) => x.account);
|
|
996
1020
|
const { unmount } = render(
|
|
@@ -1003,6 +1027,15 @@ export async function getAccountId() {
|
|
|
1003
1027
|
/>
|
|
1004
1028
|
);
|
|
1005
1029
|
});
|
|
1030
|
+
} else {
|
|
1031
|
+
throw new Error(
|
|
1032
|
+
"More than one account available but unable to select one in non-interactive mode.\n" +
|
|
1033
|
+
`Please set the appropriate \`account_id\` in your \`wrangler.toml\` file.\n` +
|
|
1034
|
+
`Available accounts are ("<name>" - "<id>"):\n` +
|
|
1035
|
+
responseJSON.result
|
|
1036
|
+
.map((x) => ` "${x.account.name}" - "${x.account.id}")`)
|
|
1037
|
+
.join("\n")
|
|
1038
|
+
);
|
|
1006
1039
|
}
|
|
1007
1040
|
}
|
|
1008
1041
|
return accountId;
|
|
@@ -1014,16 +1047,19 @@ type ChooseAccountItem = {
|
|
|
1014
1047
|
};
|
|
1015
1048
|
export function ChooseAccount(props: {
|
|
1016
1049
|
accounts: ChooseAccountItem[];
|
|
1017
|
-
onSelect: (item) => void;
|
|
1050
|
+
onSelect: (item: SelectInputItem<ChooseAccountItem>) => void;
|
|
1018
1051
|
}) {
|
|
1019
1052
|
return (
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1053
|
+
<>
|
|
1054
|
+
<Text bold>Select an account from below:</Text>
|
|
1055
|
+
<SelectInput
|
|
1056
|
+
items={props.accounts.map((item) => ({
|
|
1057
|
+
key: item.id,
|
|
1058
|
+
label: item.name,
|
|
1059
|
+
value: item,
|
|
1060
|
+
}))}
|
|
1061
|
+
onSelect={props.onSelect}
|
|
1062
|
+
/>
|
|
1063
|
+
</>
|
|
1028
1064
|
);
|
|
1029
1065
|
}
|
package/src/whoami.tsx
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Text, render } from "ink";
|
|
2
|
+
import Table from "ink-table";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { fetchListResult, fetchResult } from "./cfetch";
|
|
5
|
+
import { getAPIToken } from "./user";
|
|
6
|
+
|
|
7
|
+
export async function whoami() {
|
|
8
|
+
console.log("Getting User settings...");
|
|
9
|
+
const user = await getUserInfo();
|
|
10
|
+
render(<WhoAmI user={user}></WhoAmI>);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function WhoAmI({ user }: { user: UserInfo | undefined }) {
|
|
14
|
+
return user ? (
|
|
15
|
+
<>
|
|
16
|
+
<Email tokenType={user.authType} email={user.email}></Email>
|
|
17
|
+
<Accounts accounts={user.accounts}></Accounts>
|
|
18
|
+
</>
|
|
19
|
+
) : (
|
|
20
|
+
<Text>You are not authenticated. Please run `wrangler login`.</Text>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function Email(props: { tokenType: string; email: string }) {
|
|
25
|
+
return (
|
|
26
|
+
<Text>
|
|
27
|
+
👋 You are logged in with an {props.tokenType} Token, associated with the
|
|
28
|
+
email '{props.email}'!
|
|
29
|
+
</Text>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function Accounts(props: { accounts: AccountInfo[] }) {
|
|
34
|
+
const accounts = props.accounts.map((account) => ({
|
|
35
|
+
"Account Name": account.name,
|
|
36
|
+
"Account ID": account.id,
|
|
37
|
+
}));
|
|
38
|
+
return <Table data={accounts}></Table>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface UserInfo {
|
|
42
|
+
apiToken: string;
|
|
43
|
+
authType: string;
|
|
44
|
+
email: string;
|
|
45
|
+
accounts: AccountInfo[];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function getUserInfo(): Promise<UserInfo | undefined> {
|
|
49
|
+
const apiToken = getAPIToken();
|
|
50
|
+
return apiToken
|
|
51
|
+
? {
|
|
52
|
+
apiToken,
|
|
53
|
+
authType: "OAuth",
|
|
54
|
+
email: await getEmail(),
|
|
55
|
+
accounts: await getAccounts(),
|
|
56
|
+
}
|
|
57
|
+
: undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function getEmail(): Promise<string> {
|
|
61
|
+
const { email } = await fetchResult<{ email: string }>("/user");
|
|
62
|
+
return email;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type AccountInfo = { name: string; id: string };
|
|
66
|
+
|
|
67
|
+
async function getAccounts(): Promise<AccountInfo[]> {
|
|
68
|
+
return await fetchListResult<AccountInfo>("/accounts");
|
|
69
|
+
}
|