import { app, assistantConfig } from './app.ts'; import { proxyRoute, proxyWs } from './services/proxy/proxy-page-index.ts'; import './routes/index.ts'; import getPort, { portNumbers } from 'get-port'; import { program } from 'commander'; import { spawnSync } from 'node:child_process'; import path from 'node:path' import chalk from 'chalk'; import { AssistantApp } from './lib.ts'; import { getBunPath } from './module/get-bun-path.ts'; export const runServer = async (port?: number, listenPath = '127.0.0.1') => { let _port: number | undefined; if (port) { _port = await getPort({ port }); if (_port !== port) { console.log(`Port ${port} is not available`); _port = undefined; } } if (!_port) { // 检车端口可用性 const isPortAvailable = await getPort({ port: portNumbers(51015, 52000) }); if (!isPortAvailable) { console.log(`Port ${isPortAvailable} is not available`); process.exit(1); } _port = isPortAvailable; } const hasSocket = listenPath.includes('.sock'); if (hasSocket) { app.listen(listenPath, () => { console.log(`Server is running on ${listenPath}`); }); } else { app.listen(_port, listenPath, () => { const protocol = assistantConfig.getHttps().protocol; console.log(`Server is running on ${protocol}://${listenPath}:${_port}`); }); } app.server.on(proxyRoute); proxyWs(); const manager = new AssistantApp(assistantConfig, app); setTimeout(() => { manager.load({ runtime: 'client' }).then(() => { console.log('Assistant App Loaded'); }); }, 1000); return { app, port: _port, }; }; program .description('启动服务') .option('-d, --daemon', '是否以守护进程方式运行') .option('-n, --name ', '服务名称', 'assistant-server') .option('-p, --port ', '服务端口') .option('-s, --start', '是否启动服务') .option('-e, --interpreter ', '指定使用的解释器', 'bun') .action(async (options) => { // console.log('当前执行路径:', execPath, inte); if (options.daemon) { const [_interpreter, execPath] = process.argv; const name = options.name; const port = options.port; const runPath = path.resolve(execPath); // Windows 下需要对路径进行转义处理 const escapePath = (p: string) => { // 将反斜杠转换为正斜杠,PM2 在 Windows 上更好地支持正斜杠 let normalized = p.replace(/\\/g, '/'); // 如果路径包含空格,用引号包裹 return normalized.includes(' ') ? `"${normalized}"` : normalized; }; // 获取解释器路径 let interpreterPath = options.interpreter; if (options.interpreter === 'bun') { interpreterPath = getBunPath(); console.log(chalk.gray('Bun 路径:'), interpreterPath); } // 构建 pm2 命令字符串 let pm2Command = `pm2 start ${escapePath(runPath)} --interpreter ${escapePath(interpreterPath)} --name ${name} -- -s`; if (port) { pm2Command += ` -p ${port}`; } console.log(chalk.gray('执行命令:'), pm2Command); console.log(chalk.gray('脚本路径:'), runPath); try { // 先删除可能存在的同名进程 console.log(chalk.yellow('尝试删除旧进程...')); spawnSync(`pm2 delete ${name}`, [], { shell: true, stdio: 'pipe', // 忽略删除时的输出 }); const result = spawnSync(pm2Command, [], { stdio: 'inherit', shell: true, windowsHide: false, }); if (result.error) { console.error(chalk.red('Error starting server:'), result.error.message); console.log(chalk.yellow('\n提示: 请检查:')); console.log(' 1. pm2 是否已安装: npm install -g pm2'); console.log(' 2. bun 是否已安装且在 PATH 中'); console.log(' 3. 尝试手动执行:', pm2Command); process.exit(1); } if (result.status !== 0) { console.error(chalk.red(`PM2 exited with code ${result.status}`)); console.log(chalk.yellow('\n查看详细日志:'), `pm2 logs ${name}`); console.log(chalk.yellow('查看进程状态:'), 'pm2 list'); process.exit(result.status || 1); } console.log(chalk.green('✓ 以守护进程方式运行')); console.log(chalk.gray('查看日志:'), `pm2 logs ${name}`); } catch (error) { console.error(chalk.red('Error starting server:'), error.message); process.exit(1); } process.exit(0); } else if (options.start) { console.log('启动服务', chalk.green(assistantConfig.configDir)); const config = assistantConfig.getCacheAssistantConfig(); const listenPort = options.port || config?.server?.port; const listenPath = config?.server?.path || '::'; const server = await runServer(listenPort, listenPath); } else { console.log('请使用 -s 参数启动服务'); } }); export const runParser = async (argv: string[]) => { try { program.parse(argv); } catch (error) { console.error('执行错误:', error.message); } };