@xec-sh/cli
Version:
Xec: The Universal Shell for TypeScript
291 lines • 10.8 kB
JavaScript
import path from 'path';
import fs from 'fs-extra';
import { fileURLToPath } from 'url';
import { Command } from 'commander';
import { CommandRegistry } from '@xec-sh/core';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export class CommandManager {
constructor() {
this.commands = new Map();
this.registry = new CommandRegistry();
}
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 {};
}
buildRegistry(program) {
const registry = new CommandRegistry();
if (program.name() && program.name() !== 'xec') {
registry.register(this.extractCommandInfo(program));
}
program.commands.forEach(cmd => {
registry.register(this.extractCommandInfo(cmd));
if (cmd.commands && cmd.commands.length > 0) {
cmd.commands.forEach(subCmd => {
const info = this.extractCommandInfo(subCmd);
info.command = `${cmd.name()} ${info.command}`;
registry.register(info);
});
}
});
this.registry = registry;
return registry;
}
extractCommandInfo(cmd) {
const name = cmd.name();
const description = cmd.description();
const aliases = cmd.aliases();
const usage = cmd.usage() || `xec ${name} [options]`;
return {
command: name,
description,
aliases,
usage
};
}
registerCliCommands(program) {
return this.buildRegistry(program);
}
findCommand(program, nameOrAlias) {
if (!nameOrAlias || !program.commands) {
return null;
}
const searchTerm = nameOrAlias.toLowerCase();
for (const cmd of program.commands) {
if (cmd.name().toLowerCase() === searchTerm) {
return cmd;
}
}
for (const cmd of program.commands) {
const aliases = cmd.aliases();
if (aliases && aliases.some(alias => alias.toLowerCase() === searchTerm)) {
return cmd;
}
}
return null;
}
getRegistry() {
return this.registry;
}
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 managerInstance;
export function getCommandManager() {
if (!managerInstance) {
managerInstance = new CommandManager();
}
return managerInstance;
}
export async function discoverAllCommands() {
const manager = getCommandManager();
return manager.discoverAll();
}
export function buildCommandRegistry(program) {
const manager = getCommandManager();
return manager.buildRegistry(program);
}
export function registerCliCommands(program) {
const manager = getCommandManager();
return manager.registerCliCommands(program);
}
export function findCommand(program, nameOrAlias) {
if (!nameOrAlias || !program || !program.commands) {
return null;
}
const searchTerm = nameOrAlias.toLowerCase();
for (const cmd of program.commands) {
if (cmd.name().toLowerCase() === searchTerm) {
return cmd;
}
}
for (const cmd of program.commands) {
const aliases = cmd.aliases();
if (aliases && aliases.some(alias => alias.toLowerCase() === searchTerm)) {
return cmd;
}
}
return null;
}
export { getCommandManager as getCommandDiscovery };
//# sourceMappingURL=command-manager.js.map