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.
Showing
5 changed files
with
191 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,100 @@ | ||
import os from "node:os"; | ||
import path from "node:path"; | ||
import { render } from "ink-testing-library"; | ||
import React from "react"; | ||
import { WhoAmI } from "../whoami"; | ||
import { runInTempDir } from "./run-in-tmp"; | ||
import { mkdirSync, writeFileSync } from "node:fs"; | ||
import { setMockResponse } from "./mock-cfetch"; | ||
|
||
const ORIGINAL_CF_API_TOKEN = process.env.CF_API_TOKEN; | ||
const ORIGINAL_CF_ACCOUNT_ID = process.env.CF_ACCOUNT_ID; | ||
|
||
describe("WhoAmI", () => { | ||
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; | ||
}); | ||
|
||
describe("not authenticated", () => { | ||
it("should display a helpful message if there is no config file", async () => { | ||
const { lastFrame, unmount } = render(<WhoAmI></WhoAmI>); | ||
|
||
expect(lastFrame()).toMatchInlineSnapshot(` | ||
"Getting User settings... | ||
You are not authenticated. Please run \`wrangler login\`." | ||
`); | ||
|
||
unmount(); | ||
}); | ||
|
||
it("should display a helpful message if there is an empty config file", async () => { | ||
writeUserConfig(); | ||
const { lastFrame, unmount } = render(<WhoAmI></WhoAmI>); | ||
|
||
expect(lastFrame()).toMatchInlineSnapshot(` | ||
"Getting User settings... | ||
You are not authenticated. Please run \`wrangler login\`." | ||
`); | ||
|
||
unmount(); | ||
}); | ||
}); | ||
|
||
describe("authenticated via OAuth token", () => { | ||
fit("should display the user's email and accounts", async () => { | ||
writeUserConfig("some-oauth-token"); | ||
setMockResponse("/user", () => ({ email: "[email protected]" })); | ||
setMockResponse("/accounts", () => [ | ||
{ name: "Account One", id: "account-1" }, | ||
{ name: "Account Two", id: "account-2" }, | ||
{ name: "Account Three", id: "account-3" }, | ||
]); | ||
|
||
const { lastFrame, unmount } = render(<WhoAmI></WhoAmI>); | ||
|
||
expect(lastFrame()).toMatchInlineSnapshot(` | ||
"Getting User settings... | ||
👋 You are logged in with an OAuth Token, associated with the email '[email protected]'!" | ||
`); | ||
|
||
unmount(); | ||
}); | ||
}); | ||
}); | ||
|
||
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,79 @@ | ||
import { Text } from "ink"; | ||
import Table from "ink-table"; | ||
import React, { useEffect, useState } from "react"; | ||
import { useErrorHandler } from "react-error-boundary"; | ||
import { fetchListResult, fetchResult } from "./cfetch"; | ||
import { getAPIToken } from "./user"; | ||
|
||
export function WhoAmI() { | ||
const [apiToken, setApiToken] = useState<string>(""); | ||
const handleError = useErrorHandler(); | ||
useEffect(() => { | ||
getAPIToken().then((result) => setApiToken(result ?? ""), handleError); | ||
}); | ||
return ( | ||
<> | ||
<Text>Getting User settings...</Text> | ||
{apiToken ? ( | ||
<Authenticated apiToken={apiToken}></Authenticated> | ||
) : ( | ||
<NotAuthenticated></NotAuthenticated> | ||
)} | ||
</> | ||
); | ||
} | ||
|
||
function NotAuthenticated() { | ||
return <Text>You are not authenticated. Please run `wrangler login`.</Text>; | ||
} | ||
|
||
function Authenticated(props: { apiToken: string }) { | ||
const [email, setEmail] = useState<string>(""); | ||
const [accounts, setAccounts] = useState<AccountInfo[]>([]); | ||
const errorHandler = useErrorHandler(); | ||
|
||
useEffect(() => { | ||
getEmail().then((result) => setEmail(result), errorHandler); | ||
getAccounts().then((result) => setAccounts(result), errorHandler); | ||
}, [props.apiToken, errorHandler]); | ||
|
||
return ( | ||
<> | ||
<Email tokenType="OAuth" email={email}></Email> | ||
<Accounts accounts={accounts}></Accounts> | ||
</> | ||
); | ||
} | ||
|
||
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[] }) { | ||
return <Table data={props.accounts}></Table>; | ||
} | ||
|
||
async function getEmail(): Promise<string> { | ||
const { email } = await fetchResult<{ email: string }>("/user"); | ||
return email; | ||
} | ||
|
||
type AccountInfo = { | ||
"Account Name": string; | ||
"Account ID": string; | ||
}; | ||
|
||
async function getAccounts() { | ||
const accounts = await fetchListResult<{ name: string; id: string }>( | ||
"/accounts" | ||
); | ||
return accounts.map((account) => ({ | ||
"Account Name": account.name, | ||
"Account ID": account.id, | ||
})); | ||
} |