UNPKG

accs-cli

Version:

ACCS CLI — Full-featured developer tool for scaffolding, running, building, and managing multi-language projects

233 lines (197 loc) 6.54 kB
/** * Run files command - Execute different file types */ import { execa } from 'execa'; import path from 'path'; import chalk from 'chalk'; import { logger } from '../utils/logger.js'; import { FileUtils } from '../utils/file-utils.js'; import { SystemCheck } from '../utils/system-check.js'; const RUNNERS = { '.js': { command: 'node', args: [] }, '.mjs': { command: 'node', args: [] }, '.ts': { command: 'tsx', args: [] }, '.py': { command: 'python3', args: [], fallback: 'python' }, '.php': { command: 'php', args: [] }, '.html': { command: 'open', args: [], description: 'Open in browser' }, '.sh': { command: 'bash', args: [] }, '.ps1': { command: 'powershell', args: ['-ExecutionPolicy', 'Bypass', '-File'] } }; export function runCommand(program) { program .command('run') .argument('<file>', 'File to run') .option('-a, --args <args...>', 'Arguments to pass to the script') .option('-e, --env <env>', 'Environment variables (JSON string)') .option('-w, --watch', 'Watch file for changes and re-run') .option('-v, --verbose', 'Verbose output') .description('Run JavaScript, Python, PHP, HTML, or shell scripts') .action(async (file, options) => { try { await runFile(file, options); } catch (error) { logger.error('Failed to run file:', error.message); process.exit(1); } }); } async function runFile(file, options) { // Resolve file path const filePath = path.resolve(file); if (!FileUtils.exists(filePath)) { throw new Error(`File not found: ${file}`); } if (!FileUtils.isFile(filePath)) { throw new Error(`Path is not a file: ${file}`); } const extension = FileUtils.getExtension(filePath); const runner = RUNNERS[extension]; if (!runner) { throw new Error(`Unsupported file type: ${extension}. Supported types: ${Object.keys(RUNNERS).join(', ')}`); } // Check if runner command exists let command = runner.command; const commandExists = await SystemCheck.commandExists(command); if (!commandExists && runner.fallback) { const fallbackExists = await SystemCheck.commandExists(runner.fallback); if (fallbackExists) { command = runner.fallback; } else { throw new Error(`Neither ${runner.command} nor ${runner.fallback} is installed`); } } else if (!commandExists) { throw new Error(`${runner.command} is not installed`); } // Special handling for HTML files if (extension === '.html') { await openHtmlFile(filePath, options); return; } // Prepare execution const args = [...runner.args, filePath]; if (options.args) { args.push(...options.args); } // Parse environment variables let env = process.env; if (options.env) { try { const extraEnv = JSON.parse(options.env); env = { ...process.env, ...extraEnv }; } catch (error) { logger.warn('Invalid environment variables JSON, ignoring'); } } if (options.watch) { await runWithWatch(command, args, filePath, env, options); } else { await executeSingleRun(command, args, env, options); } } async function executeSingleRun(command, args, env) { logger.info(`Running: ${chalk.cyan(command)} ${args.join(' ')}`); logger.separator(); const startTime = Date.now(); try { const subprocess = execa(command, args, { env, stdio: 'inherit', shell: true }); await subprocess; const duration = Date.now() - startTime; logger.separator(); logger.success(`Completed in ${duration}ms`); } catch (error) { const duration = Date.now() - startTime; logger.separator(); logger.error(`Process failed after ${duration}ms`); if (error.exitCode !== undefined) { logger.error(`Exit code: ${error.exitCode}`); } throw error; } } async function runWithWatch(command, args, filePath, env, options) { logger.info(`Watching file: ${chalk.cyan(filePath)}`); logger.info(`Command: ${chalk.gray(command)} ${args.join(' ')}`); logger.info('Press Ctrl+C to stop watching'); logger.separator(); const chokidar = await import('chokidar'); const watcher = chokidar.watch(filePath, { persistent: true, ignoreInitial: false }); let currentProcess = null; const runCommand = async () => { // Kill existing process if running if (currentProcess && !currentProcess.killed) { currentProcess.kill('SIGTERM'); await new Promise(resolve => { currentProcess.on('exit', resolve); setTimeout(resolve, 1000); // Force kill after 1 second }); } const timestamp = new Date().toLocaleTimeString(); logger.info(`[${timestamp}] Running: ${chalk.cyan(command)} ${args.join(' ')}`); try { currentProcess = execa(command, args, { env, stdio: 'inherit', shell: true }); await currentProcess; logger.success(`[${timestamp}] Completed successfully`); } catch (error) { if (!error.killed) { logger.error(`[${timestamp}] Process failed`); if (options.verbose) { logger.error(error.message); } } } }; watcher.on('change', () => { logger.info(`File changed: ${chalk.yellow(path.basename(filePath))}`); runCommand(); }); watcher.on('ready', () => { runCommand(); }); // Handle process exit process.on('SIGINT', () => { logger.info('\nStopping file watcher...'); if (currentProcess && !currentProcess.killed) { currentProcess.kill('SIGTERM'); } watcher.close(); process.exit(0); }); } async function openHtmlFile(filePath) { const absolutePath = path.resolve(filePath); const fileUrl = `file://${absolutePath}`; logger.info(`Opening HTML file in browser: ${chalk.cyan(fileUrl)}`); try { const { default: open } = await import('open'); await open(fileUrl); logger.success('File opened in default browser'); } catch (error) { // Fallback to system commands try { const isWindows = process.platform === 'win32'; const isMac = process.platform === 'darwin'; if (isWindows) { await execa('start', [fileUrl], { shell: true }); } else if (isMac) { await execa('open', [fileUrl]); } else { await execa('xdg-open', [fileUrl]); } logger.success('File opened in default browser'); } catch (fallbackError) { logger.error('Failed to open HTML file in browser'); logger.info(`Manual: ${fileUrl}`); } } }