@xec-sh/core
Version:
Universal shell execution engine
183 lines • 6.19 kB
JavaScript
import { createInterface } from 'node:readline';
export class InteractiveSession {
constructor(engine, options = {}) {
this.engine = engine;
this.options = options;
this.rl = createInterface({
input: this.options.input || process.stdin,
output: this.options.output || process.stdout,
terminal: this.options.terminal ?? true
});
}
async question(prompt, options = {}) {
const { defaultValue, choices, validate, mask, multiline } = options;
let displayPrompt = prompt;
if (choices && choices.length > 0) {
displayPrompt += '\n' + choices.map((c, i) => ` ${i + 1}. ${c}`).join('\n');
displayPrompt += '\nChoice: ';
}
else if (defaultValue) {
displayPrompt += ` (${defaultValue}): `;
}
else {
displayPrompt += ': ';
}
return new Promise((resolve, reject) => {
const askQuestion = () => {
this.rl.question(displayPrompt, async (answer) => {
answer = answer.trim() || defaultValue || '';
if (choices && choices.length > 0) {
const choiceIndex = parseInt(answer) - 1;
if (choiceIndex >= 0 && choiceIndex < choices.length) {
const selectedChoice = choices[choiceIndex];
if (selectedChoice !== undefined) {
answer = selectedChoice;
}
}
else if (!choices.includes(answer)) {
console.log('Invalid choice. Please try again.');
return askQuestion();
}
}
if (validate) {
const validationResult = validate(answer);
if (validationResult !== true) {
console.log(typeof validationResult === 'string' ? validationResult : 'Invalid input');
return askQuestion();
}
}
resolve(answer);
});
};
askQuestion();
});
}
async confirm(prompt, defaultValue = false) {
const answer = await this.question(`${prompt} (${defaultValue ? 'Y/n' : 'y/N'})`, { defaultValue: defaultValue ? 'y' : 'n' });
return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
}
async select(prompt, choices) {
return this.question(prompt, { choices });
}
async multiselect(prompt, choices) {
const displayPrompt = prompt + '\n' +
choices.map((c, i) => ` ${i + 1}. ${c}`).join('\n') +
'\nEnter numbers separated by commas: ';
const answer = await this.question(displayPrompt);
const indices = answer.split(',').map(s => parseInt(s.trim()) - 1);
return indices
.filter(i => i >= 0 && i < choices.length)
.map(i => choices[i])
.filter((choice) => choice !== undefined);
}
async password(prompt) {
return new Promise((resolve) => {
const rl = createInterface({
input: process.stdin,
output: process.stdout,
terminal: true
});
rl.stdoutMuted = true;
rl.question(prompt + ': ', (password) => {
rl.close();
console.log();
resolve(password);
});
rl._writeToOutput = function (char) {
if (!rl.stdoutMuted) {
rl.output.write(char);
}
};
});
}
close() {
this.rl.close();
}
}
export async function question(engine, prompt, options) {
const session = new InteractiveSession(engine);
try {
return await session.question(prompt, options);
}
finally {
session.close();
}
}
export async function confirm(engine, prompt, defaultValue) {
const session = new InteractiveSession(engine);
try {
return await session.confirm(prompt, defaultValue);
}
finally {
session.close();
}
}
export async function select(engine, prompt, choices) {
const session = new InteractiveSession(engine);
try {
return await session.select(prompt, choices);
}
finally {
session.close();
}
}
export async function password(engine, prompt) {
const session = new InteractiveSession(engine);
try {
return await session.password(prompt);
}
finally {
session.close();
}
}
export class Spinner {
constructor(text) {
this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
this.currentFrame = 0;
this.interval = null;
this.text = '';
if (text)
this.text = text;
}
start(text) {
if (text)
this.text = text;
this.interval = setInterval(() => {
process.stdout.write(`\r${this.frames[this.currentFrame]} ${this.text}`);
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
}, 80);
}
update(text) {
this.text = text;
}
succeed(text) {
this.stop();
console.log(`\r✓ ${text || this.text}`);
}
fail(text) {
this.stop();
console.log(`\r✗ ${text || this.text}`);
}
stop() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
process.stdout.write('\r' + ' '.repeat(this.text.length + 4) + '\r');
}
}
}
export async function withSpinner(text, fn) {
const spinner = new Spinner(text);
spinner.start();
try {
const result = await fn();
spinner.succeed();
return result;
}
catch (error) {
spinner.fail();
throw error;
}
}
export { createInteractiveSession } from './interactive-process.js';
//# sourceMappingURL=interactive.js.map