UNPKG

@xec-sh/cli

Version:

Xec: The Universal Shell for TypeScript

313 lines β€’ 11 kB
import chalk from 'chalk'; import * as clack from '@clack/prompts'; import { ConfigurationManager } from '../config/configuration-manager.js'; export class InteractiveHelpers { static { this.cancelled = false; } static { this.configManager = null; } static async getConfigManager() { if (!this.configManager) { this.configManager = new ConfigurationManager(); await this.configManager.load(); } return this.configManager; } static setupCancelHandlers() { process.on('SIGINT', () => { this.cancelled = true; clack.outro(chalk.gray('Cancelled')); process.exit(0); }); } static isCancelled(value) { return clack.isCancel(value) || this.cancelled; } static async selectTarget(options) { const configManager = await this.getConfigManager(); const config = configManager.getConfig(); const targets = []; if (config.targets) { for (const [name, targetConfig] of Object.entries(config.targets)) { if (!targetConfig || !targetConfig.type) continue; const targetType = targetConfig.type; if (options.type && options.type !== 'all') { const typeMap = { 'ssh': 'ssh', 'docker': 'docker', 'k8s': 'k8s', 'local': 'local' }; if (targetType !== typeMap[options.type]) { continue; } } targets.push({ id: `targets.${name}`, type: targetType, name, config: targetConfig, source: 'configured', }); } } if ((options.type === 'all' || options.type === 'local') && !targets.some(t => t.type === 'local')) { targets.push({ id: 'local', type: 'local', name: 'local', config: { type: 'local' }, source: 'configured', }); } const targetOptions = targets.map(target => ({ value: target, label: `${this.getTargetIcon(target.type)} ${target.id} ${chalk.gray(`(${target.type})`)}`, })); if (options.allowCustom) { targetOptions.push({ value: { custom: true }, label: chalk.cyan('β†’ Enter custom target...'), }); } if (targetOptions.length === 0) { clack.log.warning('No targets configured. Use "xec new profile" to create a configuration.'); return null; } if (options.allowMultiple) { const selected = await clack.multiselect({ message: options.message, options: targetOptions, required: true, }); if (this.isCancelled(selected)) return null; if (Array.isArray(selected) && selected.some((t) => typeof t === 'object' && t && 'custom' in t && t.custom)) { const customTarget = await this.enterCustomTarget(); if (!customTarget) return null; return [customTarget]; } return selected; } else { const selected = await clack.select({ message: options.message, options: targetOptions, }); if (this.isCancelled(selected)) return null; if (typeof selected === 'object' && selected && 'custom' in selected && selected.custom) { return await this.enterCustomTarget(); } return selected; } } static async enterCustomTarget() { const targetType = await clack.select({ message: 'Select target type:', options: [ { value: 'ssh', label: 'πŸ–₯️ SSH Host' }, { value: 'docker', label: '🐳 Docker Container' }, { value: 'k8s', label: '☸️ Kubernetes Pod' }, ], }); if (this.isCancelled(targetType)) return null; if (this.isCancelled(targetType)) return null; switch (targetType) { case 'ssh': { const hostInput = await clack.text({ message: 'Enter SSH host:', placeholder: 'user@hostname or hostname', validate: (value) => { if (!value || value.trim().length === 0) { return 'Host cannot be empty'; } return undefined; }, }); if (this.isCancelled(hostInput)) return null; const hostStr = String(hostInput); const [user, host] = hostStr.includes('@') ? hostStr.split('@') : [process.env['USER'] || 'root', hostStr]; return { id: `ssh:${hostStr}`, type: 'ssh', name: hostStr, config: { type: 'ssh', host, username: user, }, source: 'configured', }; } case 'docker': { const container = await clack.text({ message: 'Enter container name or ID:', placeholder: 'myapp', validate: (value) => { if (!value || value.trim().length === 0) { return 'Container name cannot be empty'; } return undefined; }, }); if (this.isCancelled(container)) return null; const containerStr = String(container); return { id: `docker:${containerStr}`, type: 'docker', name: containerStr, config: { type: 'docker', name: containerStr, }, source: 'configured', }; } case 'k8s': { const namespace = await clack.text({ message: 'Enter namespace:', placeholder: 'default', initialValue: 'default', }); if (this.isCancelled(namespace)) return null; const pod = await clack.text({ message: 'Enter pod name:', placeholder: 'myapp-pod', validate: (value) => { if (!value || value.trim().length === 0) { return 'Pod name cannot be empty'; } return undefined; }, }); if (this.isCancelled(pod)) return null; const namespaceStr = String(namespace); const podStr = String(pod); return { id: `k8s:${namespaceStr}/${podStr}`, type: 'k8s', name: podStr, config: { type: 'k8s', name: podStr, namespace: namespaceStr, }, source: 'configured', }; } } } static getTargetIcon(type) { switch (type) { case 'ssh': return 'πŸ–₯️ '; case 'docker': return '🐳'; case 'k8s': return '☸️ '; case 'local': return 'πŸ’»'; default: return 'πŸ“¦'; } } static async confirmAction(message, defaultValue = false) { const result = await clack.confirm({ message, initialValue: defaultValue, }); return !this.isCancelled(result) && result; } static async selectFromList(message, items, getLabelFn, allowCustom = false) { if (items.length === 0) { clack.log.warning('No items available'); return null; } const options = items.map(item => ({ value: item, label: getLabelFn(item), })); if (allowCustom) { options.push({ value: { custom: true }, label: chalk.cyan('β†’ Enter custom value...'), }); } const selected = await clack.select({ message, options: options, }); if (this.isCancelled(selected)) return null; return selected; } static async inputText(message, options = {}) { const result = await clack.text({ message, placeholder: options.placeholder, initialValue: options.initialValue, validate: options.validate, }); if (this.isCancelled(result)) return null; return result; } static async selectMultiple(message, items, getLabelFn, required = true) { if (items.length === 0) { clack.log.warning('No items available'); return null; } const options = items.map(item => ({ value: item, label: getLabelFn(item), })); const selected = await clack.multiselect({ message, options: options, required, }); if (this.isCancelled(selected)) return null; return selected; } static startInteractiveMode(title) { this.setupCancelHandlers(); clack.intro(chalk.bgBlue(` ${title} `)); } static endInteractiveMode(message) { if (message) { clack.outro(chalk.green(message)); } else { clack.outro(chalk.green('βœ“ Done!')); } } static showError(message) { clack.log.error(chalk.red(message)); } static showSuccess(message) { clack.log.success(chalk.green(message)); } static showInfo(message) { clack.log.info(chalk.blue(message)); } static showWarning(message) { clack.log.warning(chalk.yellow(message)); } static createSpinner(message) { const spinner = clack.spinner(); spinner.start(message); return spinner; } } //# sourceMappingURL=interactive-helpers.js.map