import { program, Command } from '@/program.ts'; import { checkFileExists, envisionPath, getConfig, writeConfig } from '@/module/index.ts'; import path from 'path'; import fs from 'fs'; import { chalk } from '@/module/chalk.ts'; import inquirer from 'inquirer'; import { spawn, spawnSync } from 'child_process'; import { checkPm2, installDep, installDeps } from '@/uitls/npm.ts'; const command = new Command('init').description('初始化应用').action((optison) => { console.log('init'); }); // program.addCommand(command); const setMainAppConfig = async (mainAppPath: string) => { const config = getConfig(); config.mainAppPath = mainAppPath; writeConfig(config); }; const initMain = async () => { const mainAppPath = path.join(envisionPath, 'main-app'); const pkgPath = path.join(mainAppPath, 'package.json'); if (!checkFileExists(pkgPath)) { fs.mkdirSync(mainAppPath, { recursive: true }); const pkg = { name: 'main-app', version: '1.0.0', type: 'module', main: 'dist/app.mjs', scripts: { start: 'node dist/app.mjs', pm2: 'pm2 start dist/app.mjs --name main-app', }, dependencies: { '@kevisual/router': 'latest', '@kevisual/local-app-manager': 'latest', '@kevisual/use-config': 'latest', }, }; fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)); console.log(chalk.green('初始化 main-app 成功')); } // 在对应的路径,安装依赖 installDeps({ appPath: mainAppPath, sync: true }); // 创建 dist/app.mjs const code = `import { App } from '@kevisual/router'; import { app as LocalApp } from '@kevisual/local-app-manager'; import { useConfig } from '@kevisual/use-config'; const config = useConfig(); const host = config.host || '127.0.0.1'; LocalApp.listen(config.port, host, () => { console.log(\`LocalApp is running on http://\${host}:\${config.port}\`); }); `; const codeDistPath = path.join(mainAppPath, 'dist'); if (!checkFileExists(codeDistPath)) { fs.mkdirSync(codeDistPath, { recursive: true }); } fs.writeFileSync(path.join(codeDistPath, 'app.mjs'), code); console.log(chalk.green('创建 app.mjs 成功')); // 创建 app.config.json5 const answers = await inquirer.prompt([ { type: 'input', name: 'port', message: '请输入端口号', default: '11015', }, { type: 'input', name: 'host', message: '请输入host', default: '127.0.0.1', }, { type: 'input', name: 'appsPath', message: '请输入apps路径', default: path.join(mainAppPath, 'apps'), }, ]); const json5 = { port: Number(answers.port), host: answers.host, appsPath: answers.appsPath, }; fs.writeFileSync(path.join(mainAppPath, 'app.config.json5'), JSON.stringify(json5, null, 2)); // console.log(chalk.green('创建 app.config.json5 成功')); setMainAppConfig(mainAppPath); writeConfig({ ...getConfig(), appsPath: answers.appsPath }); }; const checkPid = (pid: number) => { try { process.kill(pid, 0); return true; } catch (err) { if (err.code === 'ESRCH') { // 进程不存在 console.log(`进程 ${pid} 不存在`); return false; } else if (err.code === 'EPERM') { // 没有权限访问该进程 console.log(`没有权限检查进程 ${pid}`); return true; } else { // 其他错误 console.error(`检查进程 ${pid} 时出错:`, err); return false; } } }; const mainApp = new Command('main') .description('初始化main的应用') // .option('-i, --init', '初始化main应用') .option('-s, --start', '启动main应用') .option('-r, --restart', '重启main应用') .option('-e, --exit', '停止main应用') .option('-p, --pm2', '使用pm2管理,只作为启动') .action(async (options) => { if (options.init) { await initMain(); return; } else if (!options.start && !options.restart && !options.exit && !options.pm2) { console.warn(chalk.yellow('请使用 --init 初始化 main 应用')); } const runChild = () => { const logDir = path.join(envisionPath, 'log'); const logFile = path.join(logDir, 'child.log'); // 确保日志目录存在 fs.mkdirSync(logDir, { recursive: true }); // 如果日志文件不存在,创建一个 if (!checkFileExists(logFile)) { fs.writeFileSync(logFile, ''); } // 如果日志文件大于 10M,重命名 const stats = fs.statSync(logFile); if (stats.size > 1024 * 1024 * 10) { fs.renameSync(logFile, path.join(logDir, `child-${Date.now()}.log`)); fs.writeFileSync(logFile, ''); } // 打开日志文件(追加模式) const logStream = fs.openSync(logFile, 'a'); if (options.pm2) { if (!checkPm2()) { console.log('安装pm2'); installDep({ isGlobal: true, sync: true, dep: 'pm2' }); console.log(chalk.green('安装pm2成功')); } } let comm = options.pm2 ? 'pm2' : 'node'; const args = options.pm2 ? ['start', 'dist/app.mjs', '--name', 'main-app'] : ['dist/app.mjs']; const childProcess = spawn(comm, args, { cwd: getConfig().mainAppPath, stdio: ['ignore', logStream, logStream], // 忽略 stdio, 重定向到文件 detached: true, // 使子进程独立运行 }); childProcess.unref(); // 使子进程独立运行 if (!options.pm2) { writeConfig({ ...getConfig(), mainAppPid: childProcess.pid }); } }; const config = getConfig(); if (!config.mainAppPath) { console.log('请先初始化 main 应用'); return; } if (options.start) { if (config.mainAppPid) { // 检查是否有进程 if (checkPid(config.mainAppPid)) { console.log('main app 已经启动'); return; } } runChild(); } if (options.restart) { if (config.mainAppPid) { // 检查是否有进程 if (checkPid(config.mainAppPid)) { process.kill(config.mainAppPid); } } runChild(); } else if (options.exit) { if (config.mainAppPid) { // 检查是否有进程 if (checkPid(config.mainAppPid)) { process.kill(config.mainAppPid); } writeConfig({ ...config, mainAppPid: null }); console.log('main app 已经停止'); } } }); const mainPathCommand = new Command('path').action(() => { const config = getConfig(); const appPath = path.resolve(config.mainAppPath); console.log(`cd ${appPath}`); }); mainApp.addCommand(mainPathCommand); const mainLogCommand = new Command('log') .option('-t, --tail', '查看日志') .option('-f, --follow', '跟踪日志') .description('查看main的日志') .action((options) => { const logDir = path.join(envisionPath, 'log'); const logFile = path.join(logDir, 'child.log'); if (!checkFileExists(logFile)) { console.log('日志文件不存在'); return; } if (options.tail) { const childProcess = spawn('tail', ['-n', '20', '-f', logFile]); childProcess.stdout?.pipe(process.stdout); childProcess.stderr?.pipe(process.stderr); return; } if (options.follow) { const childProcess = spawn('tail', ['-n', '50', '-f', logFile]); childProcess.stdout?.pipe(process.stdout); childProcess.stderr?.pipe(process.stderr); return; } const log = fs.readFileSync(logFile, 'utf-8'); console.log(log); }); mainApp.addCommand(mainLogCommand); program.addCommand(mainApp);