Skip to content

Commit

Permalink
feat: add whoami command
Browse files Browse the repository at this point in the history
  • Loading branch information
petebacondarwin committed Jan 23, 2022
1 parent d54d92f commit 18912c0
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/cyan-comics-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"wrangler": patch
---

Add whoami command
2 changes: 2 additions & 0 deletions packages/wrangler/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe("wrangler", () => {
Commands:
wrangler init [name] 📥 Create a wrangler.toml configuration file
wrangler whoami 🕵️ Retrieve your user info and test your auth config
wrangler dev <filename> 👂 Start a local server for developing your worker
wrangler publish [script] 🆙 Publish your Worker to Cloudflare.
wrangler tail [name] 🦚 Starts a log tailing session for a deployed Worker.
Expand Down Expand Up @@ -49,6 +50,7 @@ describe("wrangler", () => {
Commands:
wrangler init [name] 📥 Create a wrangler.toml configuration file
wrangler whoami 🕵️ Retrieve your user info and test your auth config
wrangler dev <filename> 👂 Start a local server for developing your worker
wrangler publish [script] 🆙 Publish your Worker to Cloudflare.
wrangler tail [name] 🦚 Starts a log tailing session for a deployed Worker.
Expand Down
100 changes: 100 additions & 0 deletions packages/wrangler/src/__tests__/whoami.test.tsx
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"
);
}
9 changes: 5 additions & 4 deletions packages/wrangler/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import onExit from "signal-exit";
import { setTimeout } from "node:timers/promises";
import * as fs from "node:fs";
import { execa } from "execa";
import { WhoAmI } from "./whoami";

const resetColor = "\x1b[0m";
const fgGreenColor = "\x1b[32m";
Expand Down Expand Up @@ -363,11 +364,11 @@ export async function main(argv: string[]): Promise<void> {
// whoami
wrangler.command(
"whoami",
false, // we don't need to show this the menu
// "🕵️ Retrieve your user info and test your auth config",
"🕵️ Retrieve your user info and test your auth config",
() => {},
(args) => {
console.log(":whoami", args);
async () => {
const { waitUntilExit } = render(<WhoAmI></WhoAmI>);
await waitUntilExit();
}
);

Expand Down
79 changes: 79 additions & 0 deletions packages/wrangler/src/whoami.tsx
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 &apos;{props.email}&apos;!
</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,
}));
}

0 comments on commit 18912c0

Please sign in to comment.