forked from cloudflare/workers-sdk
-
Notifications
You must be signed in to change notification settings - Fork 2
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 cloudflare#274
- Loading branch information
1 parent
007dc36
commit e5deb88
Showing
5 changed files
with
215 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,135 @@ | ||
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()).toMatchInlineSnapshot(` | ||
"👋 You are logged in with an OAuth Token, associated with the email '[email protected]'! | ||
[1m┌[22m[1m───────────────[22m[1m┬[22m[1m────────────[22m[1m┐[22m | ||
[1m│[22m[1m[34m Account Name [22m[39m[1m│[22m[1m[34m Account ID [22m[39m[1m│[22m | ||
[1m├[22m[1m───────────────[22m[1m┼[22m[1m────────────[22m[1m┤[22m | ||
[1m│[22m Account One [1m│[22m account-1 [1m│[22m | ||
[1m├[22m[1m───────────────[22m[1m┼[22m[1m────────────[22m[1m┤[22m | ||
[1m│[22m Account Two [1m│[22m account-2 [1m│[22m | ||
[1m├[22m[1m───────────────[22m[1m┼[22m[1m────────────[22m[1m┤[22m | ||
[1m│[22m Account Three [1m│[22m account-3 [1m│[22m | ||
[1m└[22m[1m───────────────[22m[1m┴[22m[1m────────────[22m[1m┘[22m" | ||
`); | ||
}); | ||
}); | ||
|
||
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"); | ||
} |