-
-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(#6165): respect ctrl + c when spinner is active
- Loading branch information
1 parent
b5b76bb
commit 9fd538f
Showing
2 changed files
with
99 additions
and
79 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 @@ | ||
--- | ||
"@astrojs/cli-kit": patch | ||
--- | ||
|
||
Respect Ctrl + C when spinner is active |
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 |
---|---|---|
@@ -1,109 +1,124 @@ | ||
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("") | ||
); | ||
} | ||
|
||
/** | ||
* Generate loading spinner with rocket flames! | ||
* @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<any> }, { 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<any> }, | ||
{ 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`); | ||
} |