-
Notifications
You must be signed in to change notification settings - Fork 734
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This commit adds a new `whoami` command to the CLI, which aligns with Wrangler 1. Resolves #274
- Loading branch information
1 parent
79ebeb6
commit 20377e8
Showing
5 changed files
with
210 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"wrangler": patch | ||
--- | ||
|
||
Add whoami command |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
import React from "react"; | ||
import os from "node:os"; | ||
import path from "node:path"; | ||
import { render } from "ink-testing-library"; | ||
import type { UserInfo } from "../whoami"; | ||
import { getUserInfo, WhoAmI } from "../whoami"; | ||
import { runInTempDir } from "./run-in-tmp"; | ||
import { mkdirSync, writeFileSync } from "node:fs"; | ||
import { setMockResponse } from "./mock-cfetch"; | ||
import { initialise } from "../user"; | ||
|
||
const ORIGINAL_CF_API_TOKEN = process.env.CF_API_TOKEN; | ||
const ORIGINAL_CF_ACCOUNT_ID = process.env.CF_ACCOUNT_ID; | ||
|
||
describe("getUserInfo()", () => { | ||
runInTempDir(); | ||
|
||
beforeEach(() => { | ||
// Clear the environment variables, so we can control them in the tests | ||
delete process.env.CF_API_TOKEN; | ||
delete process.env.CF_ACCOUNT_ID; | ||
// Override where the home directory is so that we can specify a user config | ||
mkdirSync("./home"); | ||
jest.spyOn(os, "homedir").mockReturnValue("./home"); | ||
}); | ||
|
||
afterEach(() => { | ||
// Reset any changes to the environment variables | ||
process.env.CF_API_TOKEN = ORIGINAL_CF_API_TOKEN; | ||
process.env.CF_ACCOUNT_ID = ORIGINAL_CF_ACCOUNT_ID; | ||
}); | ||
|
||
it("should return undefined if there is no config file", async () => { | ||
await initialise(); | ||
const userInfo = await getUserInfo(); | ||
expect(userInfo).toBeUndefined(); | ||
}); | ||
|
||
it("should return undefined if there is an empty config file", async () => { | ||
writeUserConfig(); | ||
await initialise(); | ||
const userInfo = await getUserInfo(); | ||
expect(userInfo).toBeUndefined(); | ||
}); | ||
|
||
it("should return the user's email and accounts if authenticated via config token", async () => { | ||
writeUserConfig("some-oauth-token"); | ||
setMockResponse("/user", () => { | ||
return { email: "[email protected]" }; | ||
}); | ||
setMockResponse("/accounts", () => { | ||
return [ | ||
{ name: "Account One", id: "account-1" }, | ||
{ name: "Account Two", id: "account-2" }, | ||
{ name: "Account Three", id: "account-3" }, | ||
]; | ||
}); | ||
|
||
await initialise(); | ||
const userInfo = await getUserInfo(); | ||
|
||
expect(userInfo).toEqual({ | ||
authType: "OAuth", | ||
apiToken: "some-oauth-token", | ||
email: "[email protected]", | ||
accounts: [ | ||
{ name: "Account One", id: "account-1" }, | ||
{ name: "Account Two", id: "account-2" }, | ||
{ name: "Account Three", id: "account-3" }, | ||
], | ||
}); | ||
}); | ||
}); | ||
|
||
describe("WhoAmI component", () => { | ||
it("should return undefined if there is no user", async () => { | ||
const { lastFrame } = render(<WhoAmI user={undefined}></WhoAmI>); | ||
|
||
expect(lastFrame()).toMatchInlineSnapshot( | ||
`"You are not authenticated. Please run \`wrangler login\`."` | ||
); | ||
}); | ||
|
||
it("should display the user's email and accounts", async () => { | ||
const user: UserInfo = { | ||
authType: "OAuth", | ||
apiToken: "some-oauth-token", | ||
email: "[email protected]", | ||
accounts: [ | ||
{ name: "Account One", id: "account-1" }, | ||
{ name: "Account Two", id: "account-2" }, | ||
{ name: "Account Three", id: "account-3" }, | ||
], | ||
}; | ||
|
||
const { lastFrame } = render(<WhoAmI user={user}></WhoAmI>); | ||
|
||
expect(lastFrame()).toContain( | ||
"You are logged in with an OAuth Token, associated with the email '[email protected]'!" | ||
); | ||
expect(lastFrame()).toMatch(/Account Name .+ Account ID/); | ||
expect(lastFrame()).toMatch(/Account One .+ account-1/); | ||
expect(lastFrame()).toMatch(/Account Two .+ account-2/); | ||
expect(lastFrame()).toMatch(/Account Three .+ account-3/); | ||
}); | ||
}); | ||
|
||
function writeUserConfig( | ||
oauth_token?: string, | ||
refresh_token?: string, | ||
expiration_time?: string | ||
) { | ||
const lines: string[] = []; | ||
if (oauth_token) { | ||
lines.push(`oauth_token = "${oauth_token}"`); | ||
} | ||
if (refresh_token) { | ||
lines.push(`refresh_token = "${refresh_token}"`); | ||
} | ||
if (expiration_time) { | ||
lines.push(`expiration_time = "${expiration_time}"`); | ||
} | ||
const configPath = path.join(os.homedir(), ".wrangler/config"); | ||
mkdirSync(configPath, { recursive: true }); | ||
writeFileSync( | ||
path.join(configPath, "default.toml"), | ||
lines.join("\n"), | ||
"utf-8" | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import { Text, render } from "ink"; | ||
import Table from "ink-table"; | ||
import React from "react"; | ||
import { fetchListResult, fetchResult } from "./cfetch"; | ||
import { getAPIToken } from "./user"; | ||
|
||
export async function whoami() { | ||
console.log("Getting User settings..."); | ||
const user = await getUserInfo(); | ||
render(<WhoAmI user={user}></WhoAmI>); | ||
} | ||
|
||
export function WhoAmI({ user }: { user: UserInfo | undefined }) { | ||
return user ? ( | ||
<> | ||
<Email tokenType={user.authType} email={user.email}></Email> | ||
<Accounts accounts={user.accounts}></Accounts> | ||
</> | ||
) : ( | ||
<Text>You are not authenticated. Please run `wrangler login`.</Text> | ||
); | ||
} | ||
|
||
function Email(props: { tokenType: string; email: string }) { | ||
return ( | ||
<Text> | ||
👋 You are logged in with an {props.tokenType} Token, associated with the | ||
email '{props.email}'! | ||
</Text> | ||
); | ||
} | ||
|
||
function Accounts(props: { accounts: AccountInfo[] }) { | ||
const accounts = props.accounts.map((account) => ({ | ||
"Account Name": account.name, | ||
"Account ID": account.id, | ||
})); | ||
return <Table data={accounts}></Table>; | ||
} | ||
|
||
export interface UserInfo { | ||
apiToken: string; | ||
authType: string; | ||
email: string; | ||
accounts: AccountInfo[]; | ||
} | ||
|
||
export async function getUserInfo(): Promise<UserInfo | undefined> { | ||
const apiToken = getAPIToken(); | ||
return apiToken | ||
? { | ||
apiToken, | ||
authType: "OAuth", | ||
email: await getEmail(), | ||
accounts: await getAccounts(), | ||
} | ||
: undefined; | ||
} | ||
|
||
async function getEmail(): Promise<string> { | ||
const { email } = await fetchResult<{ email: string }>("/user"); | ||
return email; | ||
} | ||
|
||
type AccountInfo = { name: string; id: string }; | ||
|
||
async function getAccounts(): Promise<AccountInfo[]> { | ||
return await fetchListResult<AccountInfo>("/accounts"); | ||
} |