mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-16 08:25:14 +02:00
Initial open-source release
This commit is contained in:
commit
1a42152e6f
1199 changed files with 257054 additions and 0 deletions
90
packages/cli/src/setup-interrupt.ts
Normal file
90
packages/cli/src/setup-interrupt.ts
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
import { stdin } from 'node:process';
|
||||
import type { Key } from 'node:readline';
|
||||
import { cancel, confirm, isCancel as isClackCancel } from '@clack/prompts';
|
||||
|
||||
export class KloSetupExitError extends Error {
|
||||
constructor() {
|
||||
super('KLO setup exit requested');
|
||||
this.name = 'KloSetupExitError';
|
||||
}
|
||||
}
|
||||
|
||||
export interface SetupInterruptTracker {
|
||||
track<T>(run: () => Promise<T>): Promise<T>;
|
||||
wasCtrlC(): boolean;
|
||||
}
|
||||
|
||||
interface SetupInterruptOptions {
|
||||
confirmExit?: () => Promise<boolean | symbol>;
|
||||
isCancel?: (value: unknown) => value is symbol;
|
||||
tracker?: SetupInterruptTracker;
|
||||
}
|
||||
|
||||
const NON_INTERACTIVE_SETUP_MESSAGE =
|
||||
'Interactive setup requires a terminal. Re-run this command in a TTY, or pass --no-input with the required options.';
|
||||
|
||||
function createSetupInterruptTracker(input: NodeJS.ReadStream = stdin): SetupInterruptTracker {
|
||||
let ctrlCPressed = false;
|
||||
const onKeypress = (char: string | undefined, key: Key) => {
|
||||
if (char === '\x03' || key.sequence === '\x03') {
|
||||
ctrlCPressed = true;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
async track(run) {
|
||||
ctrlCPressed = false;
|
||||
input.on('keypress', onKeypress);
|
||||
try {
|
||||
return await run();
|
||||
} finally {
|
||||
input.off('keypress', onKeypress);
|
||||
}
|
||||
},
|
||||
wasCtrlC() {
|
||||
return ctrlCPressed;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function defaultConfirmExit(): Promise<boolean | symbol> {
|
||||
return await confirm({
|
||||
message: 'Exit setup wizard?',
|
||||
active: 'Yes, exit',
|
||||
inactive: 'No, continue setup',
|
||||
initialValue: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function isKloSetupExitError(error: unknown): error is KloSetupExitError {
|
||||
return error instanceof KloSetupExitError;
|
||||
}
|
||||
|
||||
export async function withSetupInterruptConfirmation<T>(
|
||||
prompt: () => Promise<T | symbol>,
|
||||
options: SetupInterruptOptions = {},
|
||||
): Promise<T | symbol> {
|
||||
if (!options.tracker && stdin.isTTY !== true) {
|
||||
throw new Error(NON_INTERACTIVE_SETUP_MESSAGE);
|
||||
}
|
||||
|
||||
const isCancel = options.isCancel ?? isClackCancel;
|
||||
const tracker = options.tracker ?? createSetupInterruptTracker();
|
||||
const confirmExit = options.confirmExit ?? defaultConfirmExit;
|
||||
|
||||
while (true) {
|
||||
const value = await tracker.track(prompt);
|
||||
if (!isCancel(value)) {
|
||||
return value;
|
||||
}
|
||||
if (!tracker.wasCtrlC()) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const shouldExit = await confirmExit();
|
||||
if (isCancel(shouldExit) || shouldExit === true) {
|
||||
cancel('Setup cancelled.');
|
||||
throw new KloSetupExitError();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue