Skip to content

Commit 205b5f6

Browse files
fixup! fix: do not crash in wrangler dev if user has multiple accounts
1 parent c77cd65 commit 205b5f6

File tree

4 files changed

+92
-68
lines changed

4 files changed

+92
-68
lines changed

packages/wrangler/src/__tests__/helpers/mock-process.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,20 @@
66
let writeSpy: jest.SpyInstance;
77

88
function captureLastWriteCall(spy: jest.SpyInstance): Buffer {
9-
const buffer = spy.mock.calls.pop()?.pop() ?? Buffer.alloc(0);
9+
const calls = spy.mock.calls;
10+
if (calls.length > 1) {
11+
throw new Error(
12+
"Unexpected calls to `stdout.write()`: " + JSON.stringify(calls)
13+
);
14+
}
15+
const buffer = calls[0]?.[0] ?? Buffer.alloc(0);
1016
if (buffer instanceof Buffer) {
1117
return buffer;
1218
} else {
1319
throw new Error(
14-
`Unexpected value passed to process.stdout.write(): "${buffer}"`
20+
`Unexpected non-Buffer passed to \`stdout.write()\`: "${JSON.stringify(
21+
buffer
22+
)}"`
1523
);
1624
}
1725
}

packages/wrangler/src/dev/remote.tsx

+24-3
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ import useInspector from "../inspect";
88
import { logger } from "../logger";
99
import { usePreviewServer } from "../proxy";
1010
import { syncAssets } from "../sites";
11-
import { ChooseAccount, requireApiToken } from "../user";
11+
import { ChooseAccount, getAccountChoices, requireApiToken } from "../user";
1212
import type { CfPreviewToken } from "../create-worker-preview";
1313
import type { AssetPaths } from "../sites";
14+
import type { ChooseAccountItem } from "../user";
1415
import type { CfModule, CfWorkerInit, CfScriptFormat } from "../worker";
1516
import type { EsbuildBundle } from "./use-esbuild";
1617

@@ -35,6 +36,8 @@ export function Remote(props: {
3536
host: string | undefined;
3637
}) {
3738
const [accountId, setAccountId] = useState(props.accountId);
39+
const [accountChoices, setAccountChoices] = useState<ChooseAccountItem[]>();
40+
3841
const previewToken = useWorker({
3942
name: props.name,
4043
bundle: props.bundle,
@@ -72,9 +75,27 @@ export function Remote(props: {
7275

7376
const errorHandler = useErrorHandler();
7477

75-
return !accountId ? (
78+
useEffect(() => {
79+
if (accountChoices !== undefined) {
80+
return;
81+
}
82+
getAccountChoices().then(
83+
(accounts) => {
84+
if (accounts.length === 1) {
85+
setAccountId(accounts[0].id);
86+
} else {
87+
setAccountChoices(accounts);
88+
}
89+
},
90+
(err) => {
91+
errorHandler(err);
92+
}
93+
);
94+
});
95+
96+
return accountId === undefined && accountChoices !== undefined ? (
7697
<ChooseAccount
77-
isInteractive={true}
98+
accounts={accountChoices}
7899
onSelect={(selectedAccountId) => setAccountId(selectedAccountId)}
79100
onError={(err) => errorHandler(err)}
80101
></ChooseAccount>
+25-47
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Text } from "ink";
22
import SelectInput from "ink-select-input";
3-
import React, { useEffect, useRef, useState } from "react";
3+
import React from "react";
44
import { fetchListResult } from "../cfetch";
55
import { logger } from "../logger";
66
import { getCloudflareAccountIdFromEnv } from "./env-vars";
@@ -11,57 +11,15 @@ export type ChooseAccountItem = {
1111
};
1212

1313
export function ChooseAccount(props: {
14-
isInteractive: boolean;
14+
accounts: ChooseAccountItem[];
1515
onSelect: (accountId: string) => void;
1616
onError: (error: Error) => void;
1717
}) {
18-
const [accounts, setAccounts] = useState<ChooseAccountItem[]>([]);
19-
const getAccountsPromiseRef =
20-
useRef<Promise<{ account: ChooseAccountItem }[]>>();
21-
22-
useEffect(() => {
23-
async function selectAccount() {
24-
const accountIdFromEnv = getCloudflareAccountIdFromEnv();
25-
if (accountIdFromEnv) {
26-
props.onSelect(accountIdFromEnv);
27-
} else {
28-
getAccountsPromiseRef.current ??= fetchListResult<{
29-
account: ChooseAccountItem;
30-
}>(`/memberships`);
31-
const response = await getAccountsPromiseRef.current;
32-
if (response.length === 0) {
33-
props.onError(
34-
new Error(
35-
"Failed to automatically retrieve account IDs for the logged in user.\n" +
36-
"In a non-interactive environment, it is mandatory to specify an account ID, either by assigning its value to CLOUDFLARE_ACCOUNT_ID, or as `account_id` in your `wrangler.toml` file."
37-
)
38-
);
39-
} else if (response.length === 1) {
40-
props.onSelect(response[0].account.id);
41-
} else if (props.isInteractive) {
42-
setAccounts(response.map((x) => x.account));
43-
} else {
44-
props.onError(
45-
new Error(
46-
"More than one account available but unable to select one in non-interactive mode.\n" +
47-
`Please set the appropriate \`account_id\` in your \`wrangler.toml\` file.\n` +
48-
`Available accounts are ("<name>" - "<id>"):\n` +
49-
response
50-
.map((x) => ` "${x.account.name}" - "${x.account.id}")`)
51-
.join("\n")
52-
)
53-
);
54-
}
55-
}
56-
}
57-
selectAccount().catch((err) => props.onError(err));
58-
}, [props]);
59-
60-
return accounts.length > 0 ? (
18+
return (
6119
<>
6220
<Text bold>Select an account from below:</Text>
6321
<SelectInput
64-
items={accounts.map((item) => ({
22+
items={props.accounts.map((item) => ({
6523
key: item.id,
6624
label: item.name,
6725
value: item,
@@ -72,5 +30,25 @@ export function ChooseAccount(props: {
7230
}}
7331
/>
7432
</>
75-
) : null;
33+
);
34+
}
35+
36+
export async function getAccountChoices(): Promise<ChooseAccountItem[]> {
37+
const accountIdFromEnv = getCloudflareAccountIdFromEnv();
38+
if (accountIdFromEnv) {
39+
return [{ id: accountIdFromEnv, name: "" }];
40+
} else {
41+
const response = await fetchListResult<{
42+
account: ChooseAccountItem;
43+
}>(`/memberships`);
44+
const accounts = response.map((r) => r.account);
45+
if (accounts.length === 0) {
46+
throw new Error(
47+
"Failed to automatically retrieve account IDs for the logged in user.\n" +
48+
"In a non-interactive environment, it is mandatory to specify an account ID, either by assigning its value to CLOUDFLARE_ACCOUNT_ID, or as `account_id` in your `wrangler.toml` file."
49+
);
50+
} else {
51+
return accounts;
52+
}
53+
}
7654
}

packages/wrangler/src/user/user.tsx

+33-16
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ import { purgeConfigCaches } from "../config-cache";
223223
import { logger } from "../logger";
224224
import openInBrowser from "../open-in-browser";
225225
import { parseTOML, readFileSync } from "../parse";
226-
import { ChooseAccount } from "./choose-account";
226+
import { ChooseAccount, getAccountChoices } from "./choose-account";
227227
import { getCloudflareAPITokenFromEnv } from "./env-vars";
228228
import { generateAuthUrl } from "./generate-auth-url";
229229
import { generateRandomState } from "./generate-random-state";
@@ -1068,21 +1068,38 @@ export async function getAccountId(
10681068
): Promise<string | undefined> {
10691069
const apiToken = getAPIToken();
10701070
if (!apiToken) return;
1071-
return await new Promise((resolve, reject) => {
1072-
const { unmount } = render(
1073-
<ChooseAccount
1074-
isInteractive={isInteractive}
1075-
onSelect={async (selected) => {
1076-
resolve(selected);
1077-
unmount();
1078-
}}
1079-
onError={(err) => {
1080-
reject(err);
1081-
unmount();
1082-
}}
1083-
/>
1084-
);
1085-
});
1071+
1072+
const accounts = await getAccountChoices();
1073+
if (accounts.length === 1) {
1074+
return accounts[0].id;
1075+
}
1076+
1077+
if (isInteractive) {
1078+
return await new Promise((resolve, reject) => {
1079+
const { unmount } = render(
1080+
<ChooseAccount
1081+
accounts={accounts}
1082+
onSelect={async (selected) => {
1083+
resolve(selected);
1084+
unmount();
1085+
}}
1086+
onError={(err) => {
1087+
reject(err);
1088+
unmount();
1089+
}}
1090+
/>
1091+
);
1092+
});
1093+
}
1094+
1095+
throw new Error(
1096+
"More than one account available but unable to select one in non-interactive mode.\n" +
1097+
`Please set the appropriate \`account_id\` in your \`wrangler.toml\` file.\n` +
1098+
`Available accounts are ("<name>" - "<id>"):\n` +
1099+
accounts
1100+
.map((account) => ` "${account.name}" - "${account.id}")`)
1101+
.join("\n")
1102+
);
10861103
}
10871104

10881105
/**

0 commit comments

Comments
 (0)