UNPKG

@xec-sh/cli

Version:

Xec: The Universal Shell for TypeScript

208 lines 8.09 kB
import path from 'path'; import fs from 'fs-extra'; import { fileURLToPath } from 'url'; import { Command } from 'commander'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export class CommandDiscovery { constructor() { this.commands = new Map(); } async discoverAll() { const builtIn = await this.discoverBuiltInCommands(); const dynamic = await this.discoverDynamicCommands(); const allCommands = new Map(); builtIn.forEach(cmd => allCommands.set(cmd.name, cmd)); dynamic.forEach(cmd => allCommands.set(cmd.name, cmd)); this.commands = allCommands; return Array.from(allCommands.values()); } async discoverBuiltInCommands() { const commandsDir = path.join(__dirname, '../commands'); const commands = []; if (!await fs.pathExists(commandsDir)) { return commands; } const files = await fs.readdir(commandsDir); for (const file of files) { if (!file.endsWith('.js') && !file.endsWith('.ts')) continue; if (file.endsWith('.d.ts')) continue; const basename = path.basename(file, path.extname(file)); if (basename.includes('.test') || basename.includes('.spec')) continue; const filePath = path.join(commandsDir, file); const description = await this.extractCommandDescription(filePath, basename); commands.push({ name: basename, type: 'built-in', path: filePath, description, loaded: true }); } return commands; } async discoverDynamicCommands() { const commands = []; const commandDirs = this.getDynamicCommandDirectories(); for (const dir of commandDirs) { if (await fs.pathExists(dir)) { await this.discoverCommandsInDirectory(dir, commands, ''); } } return commands; } async discoverCommandsInDirectory(dir, commands, prefix) { try { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { const newPrefix = prefix ? `${prefix}:${entry.name}` : entry.name; await this.discoverCommandsInDirectory(fullPath, commands, newPrefix); } else if (this.isCommandFile(entry.name)) { const basename = path.basename(entry.name, path.extname(entry.name)); const commandName = prefix ? `${prefix}:${basename}` : basename; const description = await this.extractCommandDescription(fullPath, commandName); commands.push({ name: commandName, type: 'dynamic', path: fullPath, description, loaded: false }); } } } catch (error) { } } getDynamicCommandDirectories() { const dirs = [ path.join(process.cwd(), '.xec', 'commands'), path.join(process.cwd(), '.xec', 'cli') ]; let currentDir = process.cwd(); for (let i = 0; i < 3; i++) { const parentDir = path.dirname(currentDir); if (parentDir === currentDir) break; const parentCommandsDir = path.join(parentDir, '.xec', 'commands'); if (!dirs.includes(parentCommandsDir)) { dirs.push(parentCommandsDir); } currentDir = parentDir; } if (process.env['XEC_COMMANDS_PATH']) { const additionalPaths = process.env['XEC_COMMANDS_PATH'].split(':'); dirs.push(...additionalPaths); } return dirs; } isCommandFile(filename) { const ext = path.extname(filename); const basename = path.basename(filename, ext); if (basename.startsWith('.') || basename.endsWith('.test') || basename.endsWith('.spec')) { return false; } return ['.js', '.mjs', '.ts', '.tsx'].includes(ext); } async extractCommandDescription(filePath, commandName) { try { const content = await fs.readFile(filePath, 'utf-8'); const patterns = [ /\/\*\*[\s\S]*?\*\s*(.+?)[\s\S]*?\*\/[\s\S]*?(?:export|class|function)/, /\.description\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/, /description\s*:\s*['"`]([^'"`]+)['"`]/, /\/\/\s*Command:\s*(.+)/i, /\/\/\s*Description:\s*(.+)/i ]; for (const pattern of patterns) { const match = content.match(pattern); if (match && match[1]) { return match[1].trim(); } } const constructorPattern = /description:\s*['"`]([^'"`]+)['"`]/; const constructorMatch = content.match(constructorPattern); if (constructorMatch && constructorMatch[1]) { return constructorMatch[1].trim(); } const defaultDescriptions = { 'config': 'Manage xec configuration', 'copy': 'Copy files between local and remote systems', 'forward': 'Set up port forwarding and tunnels', 'in': 'Execute commands in containers or pods', 'inspect': 'Inspect xec resources and configuration', 'logs': 'View logs from various sources', 'new': 'Create new xec resources', 'on': 'Execute commands on remote hosts via SSH', 'run': 'Run scripts and evaluate code', 'secrets': 'Manage encrypted secrets', 'watch': 'Watch files and execute commands on changes' }; return defaultDescriptions[commandName]; } catch { return undefined; } } async loadCommandModule(filePath) { try { const module = await import(fileURLToPath(new URL(`file://${filePath}`, import.meta.url))); if (module.metadata) { return module.metadata; } if (module.default || module.command) { const program = new Command(); const commandFn = module.default || module.command; if (typeof commandFn === 'function') { commandFn(program); if (program.commands.length > 0) { const cmd = program.commands[0]; if (cmd) { return { description: cmd.description(), aliases: cmd.aliases(), usage: cmd.usage() }; } } } } } catch { } return {}; } getCommands() { return Array.from(this.commands.values()); } getBuiltInCommands() { return this.getCommands().filter(cmd => cmd.type === 'built-in'); } getDynamicCommands() { return this.getCommands().filter(cmd => cmd.type === 'dynamic'); } findCommand(name) { return this.commands.get(name); } hasCommand(name) { return this.commands.has(name); } } let discoveryInstance; export function getCommandDiscovery() { if (!discoveryInstance) { discoveryInstance = new CommandDiscovery(); } return discoveryInstance; } export async function discoverAllCommands() { const discovery = getCommandDiscovery(); return discovery.discoverAll(); } //# sourceMappingURL=command-discovery.js.map