@xec-sh/cli
Version:
Xec: The Universal Shell for TypeScript
209 lines • 8.46 kB
JavaScript
import fs from 'fs';
import process from 'process';
import { Command } from 'commander';
import { fileURLToPath } from 'url';
import { join, dirname } from 'path';
import { checkForCommandTypo } from '@xec-sh/core';
import { handleError } from './utils/error-handler.js';
import { customizeHelp } from './utils/help-customizer.js';
import { TaskManager, ConfigurationManager } from './config/index.js';
import { isDirectCommand, executeDirectCommand } from './utils/direct-execution.js';
import { loadDynamicCommands, registerCliCommands } from './utils/cli-command-manager.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export function createProgram() {
const program = new Command();
const pkg = JSON.parse(fs.readFileSync(join(__dirname, '../package.json'), 'utf-8'));
program
.name('xec')
.description('Xec - universal execution shell')
.version(pkg.version)
.option('-v, --verbose', 'Enable verbose output')
.option('-q, --quiet', 'Suppress output')
.option('--cwd <path>', 'Set current working directory')
.option('--no-color', 'Disable colored output')
.option('-e, --eval <code>', 'Evaluate code')
.option('--repl', 'Start interactive REPL')
.hook('preAction', (thisCommand) => {
const opts = thisCommand.opts();
if (opts['cwd']) {
process.chdir(opts['cwd']);
}
if (opts['noColor']) {
process.env['NO_COLOR'] = '1';
}
});
return program;
}
export async function loadCommands(program) {
const commandsDir = join(__dirname, './commands');
if (fs.existsSync(commandsDir)) {
const files = fs.readdirSync(commandsDir);
for (const file of files) {
if (file.endsWith('.js')) {
const commandPath = join(commandsDir, file);
const module = await import(commandPath);
if (module.default && typeof module.default === 'function') {
module.default(program);
}
}
}
}
const dynamicCommandNames = await loadDynamicCommands(program);
return dynamicCommandNames;
}
export async function run(argv = process.argv) {
const program = createProgram();
const configManager = new ConfigurationManager();
const taskManager = new TaskManager({ configManager });
await taskManager.load();
const dynamicCommandNames = await loadCommands(program);
customizeHelp(program, dynamicCommandNames);
const commandRegistry = registerCliCommands(program);
const commandNames = program.commands.map(cmd => cmd.name())
.concat(program.commands.flatMap(cmd => cmd.aliases() || []));
try {
const args = argv.slice(2);
const firstArg = args[0];
if (args.includes('-e') || args.includes('--eval')) {
const evalIndex = args.indexOf('-e') !== -1 ? args.indexOf('-e') : args.indexOf('--eval');
const code = args[evalIndex + 1];
if (!code) {
throw new Error('Code is required for eval');
}
const scriptArgs = args.slice(evalIndex + 2);
await evalCodeDirectly(code, scriptArgs, {});
return;
}
if (args.includes('--repl')) {
await startReplDirectly({});
return;
}
if (firstArg && !firstArg.startsWith('-') && firstArg !== 'help') {
const potentialFile = firstArg;
if (potentialFile.endsWith('.js') || potentialFile.endsWith('.ts') ||
potentialFile.endsWith('.mjs')) {
const scriptArgs = args.slice(1);
await runScriptDirectly(potentialFile, scriptArgs, {});
return;
}
if (fs.existsSync(potentialFile)) {
const stats = fs.statSync(potentialFile);
if (stats.isFile()) {
const scriptArgs = args.slice(1);
await runScriptDirectly(potentialFile, scriptArgs, {});
return;
}
}
}
if (firstArg && !firstArg.startsWith('-') && await taskManager.exists(firstArg)) {
const taskName = firstArg;
const taskArgs = args.slice(1);
const params = {};
const remainingArgs = [];
for (let i = 0; i < taskArgs.length; i++) {
const arg = taskArgs[i];
if (!arg)
continue;
if (arg.startsWith('--') && arg.includes('=')) {
const [key, value] = arg.substring(2).split('=', 2);
if (key) {
params[key] = value || '';
}
}
else if (arg.startsWith('--') && i + 1 < taskArgs.length) {
const nextArg = taskArgs[i + 1];
if (nextArg && !nextArg.startsWith('-')) {
const key = arg.substring(2);
params[key] = nextArg;
i++;
}
else {
remainingArgs.push(arg);
}
}
else {
remainingArgs.push(arg);
}
}
try {
const result = await taskManager.run(taskName, params);
if (!result.success) {
console.error(`Task '${taskName}' failed`);
process.exit(1);
}
}
catch (error) {
handleError(error, {
verbose: args.includes('-v') || args.includes('--verbose'),
quiet: args.includes('-q') || args.includes('--quiet'),
output: 'text'
});
process.exit(1);
}
return;
}
const taskList = await taskManager.list();
const taskNames = taskList.map((t) => t.name);
if (args.length > 0 && isDirectCommand(args, commandNames, taskNames)) {
const options = {
verbose: args.includes('-v') || args.includes('--verbose'),
quiet: args.includes('-q') || args.includes('--quiet'),
cwd: undefined,
};
const cwdIndex = args.indexOf('--cwd');
if (cwdIndex !== -1 && args[cwdIndex + 1]) {
options.cwd = args[cwdIndex + 1];
args.splice(cwdIndex, 2);
}
const cleanArgs = args.filter(arg => !arg.startsWith('-') ||
(arg.startsWith('-') && !['--verbose', '-v', '--quiet', '-q'].includes(arg)));
await executeDirectCommand(cleanArgs, options);
return;
}
program.on('command:*', () => {
const unknownCommand = program.args[0];
console.error(`✖ Unknown command '${unknownCommand}'`);
if (unknownCommand) {
const suggestion = checkForCommandTypo(unknownCommand, commandRegistry);
if (suggestion) {
console.error('');
console.error(suggestion);
}
}
console.error('');
console.error(`Run 'xec --help' for a list of available commands`);
process.exit(1);
});
if (argv.length === 2) {
argv.push('--help');
}
await program.parseAsync(argv);
}
catch (error) {
handleError(error, {
verbose: program.opts()['verbose'],
quiet: program.opts()['quiet'],
output: 'text'
});
}
}
async function runScriptDirectly(scriptPath, args, options) {
const scriptModule = await import('./commands/run.js');
const { runScript } = scriptModule;
await runScript(scriptPath, args, options);
}
async function evalCodeDirectly(code, args, options) {
const scriptModule = await import('./commands/run.js');
const { evalCode } = scriptModule;
await evalCode(code, args, options);
}
async function startReplDirectly(options) {
const scriptModule = await import('./commands/run.js');
const { startRepl } = scriptModule;
await startRepl(options);
}
if (import.meta.url === `file://${process.argv[1]}`) {
run();
}
//# sourceMappingURL=main.js.map