From 9fd538f4490db3f60fe759a6a79b80351fe43615 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Tue, 7 Feb 2023 16:11:24 -0600 Subject: [PATCH] fix(#6165): respect ctrl + c when spinner is active --- .changeset/thirty-points-crash.md | 5 + src/spinner/index.ts | 173 ++++++++++++++++-------------- 2 files changed, 99 insertions(+), 79 deletions(-) create mode 100644 .changeset/thirty-points-crash.md diff --git a/.changeset/thirty-points-crash.md b/.changeset/thirty-points-crash.md new file mode 100644 index 0000000..211ce01 --- /dev/null +++ b/.changeset/thirty-points-crash.md @@ -0,0 +1,5 @@ +--- +"@astrojs/cli-kit": patch +--- + +Respect Ctrl + C when spinner is active diff --git a/src/spinner/index.ts b/src/spinner/index.ts index 409e7f1..8560e8d 100644 --- a/src/spinner/index.ts +++ b/src/spinner/index.ts @@ -1,46 +1,46 @@ -import readline from 'node:readline'; -import chalk from 'chalk'; -import { createLogUpdate } from 'log-update'; -import { erase, cursor } from 'sisteransi'; -import { sleep } from '../utils/index.js' +import readline from "node:readline"; +import chalk from "chalk"; +import { createLogUpdate } from "log-update"; +import { erase, cursor } from "sisteransi"; +import { sleep } from "../utils/index.js"; const COLORS = [ - '#883AE3', - '#7B30E7', - '#6B22EF', - '#5711F8', - '#3640FC', - '#2387F1', - '#3DA9A3', - '#47DA93' + "#883AE3", + "#7B30E7", + "#6B22EF", + "#5711F8", + "#3640FC", + "#2387F1", + "#3DA9A3", + "#47DA93", ].reverse(); const FULL_FRAMES = [ - ...Array.from({ length: COLORS.length - 1 }, () => COLORS[0]), - ...COLORS, - ...Array.from({ length: COLORS.length - 1 }, () => COLORS[COLORS.length - 1]), - ...[...COLORS].reverse() -] + ...Array.from({ length: COLORS.length - 1 }, () => COLORS[0]), + ...COLORS, + ...Array.from({ length: COLORS.length - 1 }, () => COLORS[COLORS.length - 1]), + ...[...COLORS].reverse(), +]; const frame = (offset = 0) => { - const frames = FULL_FRAMES.slice(offset, offset + (COLORS.length - 2)) - if (frames.length < COLORS.length - 2) { - const filled = new Array(COLORS.length - frames.length - 2).fill(COLORS[0]) - frames.push(...filled) - } - return frames; -} + const frames = FULL_FRAMES.slice(offset, offset + (COLORS.length - 2)); + if (frames.length < COLORS.length - 2) { + const filled = new Array(COLORS.length - frames.length - 2).fill(COLORS[0]); + frames.push(...filled); + } + return frames; +}; // get a reference to scroll through while loading // visual representation of what this generates: // gradientColors: "..xxXX" // referenceGradient: "..xxXXXXxx....xxXX" -const GRADIENT = [ - ...FULL_FRAMES.map((_, i) => frame(i)), -].reverse() +const GRADIENT = [...FULL_FRAMES.map((_, i) => frame(i))].reverse(); function getGradientAnimFrames() { - return GRADIENT.map(colors => ' ' + colors.map(g => chalk.hex(g)('█')).join('')); + return GRADIENT.map( + (colors) => " " + colors.map((g) => chalk.hex(g)("█")).join("") + ); } /** @@ -48,62 +48,77 @@ function getGradientAnimFrames() { * @param text display text next to rocket * @returns Ora spinner for running .stop() */ -async function gradient(text: string, { stdin = process.stdin, stdout = process.stdout } = {}) { - const logUpdate = createLogUpdate(stdout); - let i = 0; - const frames = getGradientAnimFrames(); - let interval: NodeJS.Timeout; +async function gradient( + text: string, + { stdin = process.stdin, stdout = process.stdout } = {} +) { + const logUpdate = createLogUpdate(stdout); + let i = 0; + const frames = getGradientAnimFrames(); + let interval: NodeJS.Timeout; - const rl = readline.createInterface({ input: stdin, escapeCodeTimeout: 50 }); - readline.emitKeypressEvents(stdin, rl); + const rl = readline.createInterface({ input: stdin, escapeCodeTimeout: 50 }); + readline.emitKeypressEvents(stdin, rl); + if (stdin.isTTY) stdin.setRawMode(true); + const keypress = (char: string) => { + if (char === "\x03") { + spinner.stop(); + process.exit(0); + } if (stdin.isTTY) stdin.setRawMode(true); - const keypress = () => { - if (stdin.isTTY) stdin.setRawMode(true); - stdout.write(cursor.hide + erase.lines(2)); - }; + stdout.write(cursor.hide + erase.lines(1)); + }; - let done = false; - const spinner = { - start() { - stdout.write(cursor.hide); - stdin.on('keypress', keypress); - logUpdate(`${frames[0]} ${text}`); + let done = false; + const spinner = { + start() { + stdout.write(cursor.hide); + stdin.on("keypress", keypress); + logUpdate(`${frames[0]} ${text}`); - const loop = async () => { - if (done) return; - if (i < frames.length - 1) { - i++; - } else { - i = 0; - } - let frame = frames[i] - logUpdate(`${frame} ${text}`); - if (!done) await sleep(90); - loop(); - } + const loop = async () => { + if (done) return; + if (i < frames.length - 1) { + i++; + } else { + i = 0; + } + let frame = frames[i]; + logUpdate(`${frame} ${text}`); + if (!done) await sleep(90); + loop(); + }; - loop(); - }, - stop() { - done = true; - stdin.removeListener('keypress', keypress); - clearInterval(interval); - logUpdate.clear(); - } - } - spinner.start(); - return spinner; + loop(); + }, + stop() { + done = true; + stdin.removeListener("keypress", keypress); + clearInterval(interval); + logUpdate.clear(); + rl.close(); + }, + }; + spinner.start(); + return spinner; } -export async function spinner({ start, end, while: update = () => sleep(100) }: { start: string, end: string, while: (...args: any) => Promise }, { stdin = process.stdin, stdout = process.stdout } = {}) { - const act = update(); - const tooslow = Object.create(null); - const result = await Promise.race([sleep(500).then(() => tooslow), act]); - if (result === tooslow) { - const loading = await gradient(chalk.green(start), { stdin, stdout }); - await act; - loading.stop(); - }; - stdout.write(`${' '.repeat(5)} ${chalk.green('✔')} ${chalk.green(end)}\n`) +export async function spinner( + { + start, + end, + while: update = () => sleep(100), + }: { start: string; end: string; while: (...args: any) => Promise }, + { stdin = process.stdin, stdout = process.stdout } = {} +) { + const act = update(); + const tooslow = Object.create(null); + const result = await Promise.race([sleep(500).then(() => tooslow), act]); + if (result === tooslow) { + const loading = await gradient(chalk.green(start), { stdin, stdout }); + await act; + loading.stop(); + } + stdout.write(`${" ".repeat(5)} ${chalk.green("✔")} ${chalk.green(end)}\n`); }