diff --git a/assistant/src/module/get-bun-path.ts b/assistant/src/module/get-bun-path.ts new file mode 100644 index 0000000..5ddde6d --- /dev/null +++ b/assistant/src/module/get-bun-path.ts @@ -0,0 +1,74 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { execSync } from 'node:child_process'; + +export const getBunPath = (): string => { + // 在不同平台上获取 bun 可执行文件的路径 + const isWindows = process.platform === 'win32'; + const bunExecutableName = isWindows ? 'bun.exe' : 'bun'; + + // 尝试从环境变量中获取 BUN_PATH + if (process.env.BUN_PATH) { + return process.env.BUN_PATH; + } + + // 在 Windows 上,尝试查找通过 npm/nvm 安装的 bun + if (isWindows) { + try { + // 获取全局 node_modules 路径 + const globalNodeModules = execSync('npm root -g', { encoding: 'utf-8' }).trim(); + const bunExePath = path.join(globalNodeModules, 'bun', 'bin', 'bun.exe'); + + if (fs.existsSync(bunExePath)) { + return bunExePath; + } + } catch (error) { + // 忽略错误,继续尝试其他路径 + } + + // 尝试从 which/where 命令获取 bun 路径 + try { + const bunPath = execSync('where bun', { encoding: 'utf-8' }).trim().split('\n')[0]; + if (bunPath && bunPath.endsWith('.exe')) { + return bunPath; + } + // 如果 where bun 返回的是 shell 脚本,查找对应的 .exe + if (bunPath) { + const bunDir = path.dirname(bunPath); + const bunExePath = path.join(bunDir, 'node_modules', 'bun', 'bin', 'bun.exe'); + if (fs.existsSync(bunExePath)) { + return bunExePath; + } + } + } catch (error) { + // 忽略错误 + } + } else { + // Unix-like 系统 + try { + const bunPath = execSync('which bun', { encoding: 'utf-8' }).trim(); + if (bunPath && fs.existsSync(bunPath)) { + return bunPath; + } + } catch (error) { + // 忽略错误 + } + } + + // 常见的 bun 安装路径 + const commonPaths = [ + '/usr/local/bin/bun', + '/usr/bin/bun', + 'C:\\Program Files\\Bun\\bun.exe', + 'C:\\Bun\\bun.exe', + ]; + + for (const p of commonPaths) { + if (fs.existsSync(p)) { + return p; + } + } + + // 如果找不到,返回默认的 bun 名称,假设在 PATH 中 + return bunExecutableName; +} \ No newline at end of file diff --git a/assistant/src/server.ts b/assistant/src/server.ts index 89c7173..aa0d566 100644 --- a/assistant/src/server.ts +++ b/assistant/src/server.ts @@ -4,9 +4,11 @@ import './routes/index.ts'; import getPort, { portNumbers } from 'get-port'; import { program } from 'commander'; -import { spawnSync } from 'child_process'; +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) { @@ -56,29 +58,76 @@ program .option('-p, --port ', '服务端口') .option('-s, --start', '是否启动服务') .option('-e, --interpreter ', '指定使用的解释器', 'bun') - .option('-i, --home', 'home目录') .action(async (options) => { // console.log('当前执行路径:', execPath, inte); if (options.daemon) { const [_interpreter, execPath] = process.argv; const name = options.name; const port = options.port; - let pm2Command = `pm2 start ${execPath} --interpreter ${options.interpreter} --name ${name} -- -s `; + 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}`; } - if (options.home) { - pm2Command += ` --home`; - } - const result = spawnSync(pm2Command, { - shell: true, - stdio: 'inherit', - }); - if (result.error) { - console.error('Error starting server:', result.error); + + 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); } - console.log('以守护进程方式运行'); + process.exit(0); } else if (options.start) { console.log('启动服务', chalk.green(assistantConfig.configDir)); const config = assistantConfig.getCacheAssistantConfig(); diff --git a/assistant/src/test/get-bun.ts b/assistant/src/test/get-bun.ts new file mode 100644 index 0000000..2950da0 --- /dev/null +++ b/assistant/src/test/get-bun.ts @@ -0,0 +1,5 @@ +import { getBunPath } from '../module/get-bun-path.ts'; + +const bunPath = getBunPath(); + +console.log('Detected Bun Path:', bunPath); \ No newline at end of file diff --git a/package.json b/package.json index 548f319..738b214 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kevisual/cli", - "version": "0.0.69", + "version": "0.0.70", "description": "envision 命令行工具", "type": "module", "basename": "/root/cli",