diff --git a/src/commands/kubeconfig.ts b/src/commands/kubeconfig.ts index 0e7b844..a7f9528 100644 --- a/src/commands/kubeconfig.ts +++ b/src/commands/kubeconfig.ts @@ -11,7 +11,7 @@ You should have received a copy of the GNU General Public License along with @p0 import { retryWithSleep } from "../common/retry"; import { authenticate } from "../drivers/auth"; import { guard } from "../drivers/firestore"; -import { Ansi, print2 } from "../drivers/stdio"; +import { AnsiSgr, print2 } from "../drivers/stdio"; import { awsCloudAuth, profileName, @@ -153,8 +153,8 @@ const kubeconfigAction = async ( if (process.env.AWS_ACCESS_KEY_ID) { print2( - `${Ansi.Yellow}Warning: AWS credentials were detected in your environment, which may cause kubectl errors. ` + - `To avoid issues, unset with \`unset AWS_ACCESS_KEY_ID\`.${Ansi.Reset}` + `${AnsiSgr.Yellow}Warning: AWS credentials were detected in your environment, which may cause kubectl errors. ` + + `To avoid issues, unset with \`unset AWS_ACCESS_KEY_ID\`.${AnsiSgr.Reset}` ); } }; diff --git a/src/commands/ls.ts b/src/commands/ls.ts index a0ef4e0..33c5b28 100644 --- a/src/commands/ls.ts +++ b/src/commands/ls.ts @@ -11,7 +11,7 @@ You should have received a copy of the GNU General Public License along with @p0 import { fetchCommand } from "../drivers/api"; import { authenticate } from "../drivers/auth"; import { guard } from "../drivers/firestore"; -import { print2, print1, Ansi } from "../drivers/stdio"; +import { print2, print1, AnsiSgr, spinUntil } from "../drivers/stdio"; import { max, orderBy } from "lodash"; import pluralize from "pluralize"; import yargs from "yargs"; @@ -53,10 +53,10 @@ const ls = async ( }> ) => { const authn = await authenticate(); - const data = await fetchCommand(authn, args, [ - "ls", - ...args.arguments, - ]); + const data = await spinUntil( + "Listing accessible resources", + fetchCommand(authn, args, ["ls", ...args.arguments]) + ); const allArguments = [...args._, ...args.arguments]; if (data && "ok" in data && data.ok) { @@ -89,8 +89,8 @@ const ls = async ( isSameValue ? item.key : maxLength > 30 - ? `${item.key}\n ${Ansi.Dim}${tagPart}${Ansi.Reset}` - : `${item.key.padEnd(maxLength)}${Ansi.Dim} - ${tagPart}${Ansi.Reset}` + ? `${item.key}\n ${AnsiSgr.Dim}${tagPart}${AnsiSgr.Reset}` + : `${item.key.padEnd(maxLength)}${AnsiSgr.Dim} - ${tagPart}${AnsiSgr.Reset}` }` ); } diff --git a/src/commands/shared/request.ts b/src/commands/shared/request.ts index ad426d9..c3be37d 100644 --- a/src/commands/shared/request.ts +++ b/src/commands/shared/request.ts @@ -11,7 +11,7 @@ You should have received a copy of the GNU General Public License along with @p0 import { fetchCommand } from "../../drivers/api"; import { authenticate } from "../../drivers/auth"; import { doc } from "../../drivers/firestore"; -import { print2 } from "../../drivers/stdio"; +import { print2, spinUntil } from "../../drivers/stdio"; import { Authn } from "../../types/identity"; import { PluginRequest, Request, RequestResponse } from "../../types/request"; import { onSnapshot } from "firebase/firestore"; @@ -99,10 +99,13 @@ export const request = ): Promise | undefined> => { const resolvedAuthn = authn ?? (await authenticate()); const { userCredential } = resolvedAuthn; - const data = await fetchCommand>(resolvedAuthn, args, [ - command, - ...args.arguments, - ]); + const data = await spinUntil( + "Requesting access", + fetchCommand>(resolvedAuthn, args, [ + command, + ...args.arguments, + ]) + ); if (data && "ok" in data && "message" in data && data.ok) { const logMessage = diff --git a/src/drivers/stdio.ts b/src/drivers/stdio.ts index 9d7ab2f..6b78b67 100644 --- a/src/drivers/stdio.ts +++ b/src/drivers/stdio.ts @@ -15,6 +15,7 @@ You should have received a copy of the GNU General Public License along with @p0 * - Better testing * - Later redirection / duplication */ +import { sleep } from "../util"; import { mapValues } from "lodash"; /** Used to output machine-readable text to stdout @@ -39,7 +40,50 @@ export function print2(message: any) { const AnsiCodes = { Reset: "00", Dim: "02", + Green: "32", Yellow: "33", } as const; -export const Ansi = mapValues(AnsiCodes, (v) => `\u001b[${v}m`); +const Ansi = (value: string) => `\u001b[${value}`; + +/** Creates an ANSI Select Graphic Rendition code */ +export const AnsiSgr = mapValues(AnsiCodes, (v) => Ansi(v + "m")); + +/** Resets the terminal cursor to the beginning of the line */ +export function reset2() { + process.stderr.write(Ansi("0G")); +} + +/** Clears the current terminal line */ +export function clear2() { + // Replaces text with spaces + process.stderr.write(Ansi("2K")); + reset2(); +} + +const Spin = { + items: ["⠇", "⠋", "⠙", "⠸", "⠴", "⠦"], + delayMs: 200, +}; + +/** Prints a Unicode spinner until a promise resolves */ +export const spinUntil = async (message: string, promise: Promise) => { + let isDone = false; + let ix = 0; + void promise.finally(() => (isDone = true)).catch(() => {}); + while (!isDone) { + await sleep(Spin.delayMs); + if (isDone) break; + clear2(); + process.stderr.write( + AnsiSgr.Green + + Spin.items[ix % Spin.items.length] + + " " + + message + + AnsiSgr.Reset + ); + ix++; + } + clear2(); + return await promise; +};