fix: update envision-cli tools for proxy and run app
This commit is contained in:
		| @@ -1,3 +1,4 @@ | ||||
| import { App } from '@kevisual/router'; | ||||
| import { app } from './app.ts'; | ||||
|  | ||||
| import './route/system-config/index.ts'; | ||||
| @@ -15,3 +16,7 @@ export const runApp = async (msg: Message) => { | ||||
|   } | ||||
|   return res; | ||||
| }; | ||||
|  | ||||
| export const loadApp = async (mainApp: App) => { | ||||
|   mainApp.importApp(app); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										23
									
								
								src/app.ts
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								src/app.ts
									
									
									
									
									
								
							| @@ -2,30 +2,11 @@ import { App } from '@kevisual/router'; | ||||
| import fs from 'fs'; | ||||
| import { getConfig, getPidList, pidFilePath, checkFileExists } from './module/get-config.ts'; | ||||
| import { sequelize } from './module/sequelize.ts'; | ||||
|  | ||||
| import { spawn } from 'child_process'; | ||||
| export { sequelize }; | ||||
|  | ||||
| export const app = new App(); | ||||
|  | ||||
| app | ||||
|   .route({ | ||||
|     path: 'ping', | ||||
|   }) | ||||
|   .define(async (ctx) => { | ||||
|     ctx.body = 'pong'; | ||||
|   }) | ||||
|   .addTo(app); | ||||
|  | ||||
| app | ||||
|   .route({ | ||||
|     path: 'demo', | ||||
|     key: '01', | ||||
|   }) | ||||
|   .define(async (ctx) => { | ||||
|     ctx.body = 'Hello, World!'; | ||||
|   }) | ||||
|   .addTo(app); | ||||
|  | ||||
| // 处理进程退出或中断信号,确保删除 pid 文件 | ||||
| const cleanUp = () => { | ||||
|   const pidList = getPidList(); | ||||
| @@ -63,5 +44,5 @@ export const createApp = async () => { | ||||
|   app.listen(21015, () => { | ||||
|     console.log('Server is running on port 21015', 'http://localhost:21015/api/router', 'server id', process.pid); | ||||
|   }); | ||||
|   import('./route.ts'); | ||||
|   // import('./route.ts'); | ||||
| }; | ||||
|   | ||||
| @@ -121,3 +121,11 @@ const uploadFiles = async (files: string[], directory: string, { key, version }: | ||||
|   }); | ||||
| }; | ||||
| app.addCommand(command); | ||||
|  | ||||
| const local = new Command('local') | ||||
|   .description('本地部署') | ||||
|   .option('-k, --key <key>', 'key') | ||||
|   .action(() => { | ||||
|     console.log('local deploy'); | ||||
|   }); | ||||
| app.addCommand(local); | ||||
|   | ||||
							
								
								
									
										216
									
								
								src/command/init.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								src/command/init.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,216 @@ | ||||
| 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'; | ||||
|  | ||||
| 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', | ||||
|     }; | ||||
|     fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)); | ||||
|     console.log(chalk.green('初始化 main-app 成功')); | ||||
|   } | ||||
|   // 在对应的路径,安装依赖 | ||||
|   const needDeps = ['@kevisual/router', '@kevisual/local-app-manager', '@kevisual/use-config']; | ||||
|   let hasPnpm = false; | ||||
|   try { | ||||
|     spawnSync('pnpm', ['--version']); | ||||
|     hasPnpm = true; | ||||
|   } catch (e) { | ||||
|     hasPnpm = false; | ||||
|   } | ||||
|   for (let dep of needDeps) { | ||||
|     console.log(chalk.green('安装依赖', dep)); | ||||
|     const npm = hasPnpm ? 'pnpm' : 'npm'; | ||||
|     spawnSync(npm, ['install', dep], { cwd: mainAppPath, stdio: 'inherit' }); | ||||
|   } | ||||
|   // 创建 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 app = new App(); | ||||
| app.importApp(LocalApp); | ||||
| const host = config.host || '127.0.0.1'; | ||||
| app.listen(config.port, host, () => { | ||||
|   console.log(\`app 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); | ||||
| }; | ||||
| 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应用') | ||||
|   .action(async (options) => { | ||||
|     if (options.init) { | ||||
|       await initMain(); | ||||
|       return; | ||||
|     } | ||||
|     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'); | ||||
|       const childProcess = spawn('node', ['dist/app.mjs'], { | ||||
|         cwd: getConfig().mainAppPath, | ||||
|         stdio: ['ignore', logStream, logStream], // 忽略 stdio, 重定向到文件 | ||||
|         detached: true, // 使子进程独立运行 | ||||
|       }); | ||||
|       childProcess.unref(); // 使子进程独立运行 | ||||
|       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.stop) { | ||||
|       if (config.mainAppPid) { | ||||
|         // 检查是否有进程 | ||||
|         if (checkPid(config.mainAppPid)) { | ||||
|           process.kill(config.mainAppPid); | ||||
|         } | ||||
|         writeConfig({ ...config, mainAppPid: null }); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
|  | ||||
| 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); | ||||
| @@ -7,69 +7,12 @@ import { getConfig } from '@/module/get-config.ts'; | ||||
| import fs from 'fs'; | ||||
| import inquirer from 'inquirer'; | ||||
|  | ||||
| const command = new Command('npm') | ||||
|   .description('npm command show publish and set .npmrc') | ||||
|   .option('-p --publish <publish>', 'publish') | ||||
|   .action(async (options) => { | ||||
|     const { publish } = options || {}; | ||||
|     const config = getConfig(); | ||||
|     let cmd = ''; | ||||
|     const execPath = process.cwd(); | ||||
|     if (publish) { | ||||
|       const packageJson = path.resolve(execPath, 'package.json'); | ||||
|       switch (publish) { | ||||
|         case 'me': | ||||
|           cmd = 'npm publish --registry https://npm.xiongxiao.me'; | ||||
|           console.log(chalk.green(cmd)); | ||||
|           break; | ||||
|         case 'npm': | ||||
|           cmd = 'npm publish --registry https://registry.npmjs.org'; | ||||
|           console.log(chalk.green(cmd)); | ||||
|           break; | ||||
|         default: | ||||
|           cmd = 'npm publish --registry https://npm.xiongxiao.me'; | ||||
|           console.log(chalk.green(cmd)); | ||||
|           break; | ||||
|       } | ||||
|       if (fileIsExist(packageJson)) { | ||||
|         const keys = Object.keys(config).filter((key) => key.includes('NPM_TOKEN')); | ||||
|         const tokenEnv = keys.reduce((prev, key) => { | ||||
|           return { | ||||
|             ...prev, | ||||
|             [key]: config[key], | ||||
|           }; | ||||
|         }, {}); | ||||
|         const child = spawn(cmd, { | ||||
|           shell: true, | ||||
|           cwd: execPath, | ||||
|           env: { | ||||
|             ...process.env, // 保留当前环境变量 | ||||
|             ...tokenEnv, | ||||
|           }, | ||||
|         }); | ||||
|         child.stdout.on('data', (data) => { | ||||
|           console.log(chalk.green(`${data}`)); | ||||
|         }); | ||||
|         child.stderr.on('data', (data) => { | ||||
|           // 过滤掉 'npm notice' 或者其他信息 | ||||
|           if (data.toString().includes('npm notice')) { | ||||
|             console.log(chalk.yellow(`notice: ${data}`)); | ||||
|           } else { | ||||
|             console.error(`stderr: ${data}`); | ||||
|           } | ||||
|         }); | ||||
|         child.on('close', (code) => { | ||||
|           // console.log(`child process exited with code ${code}`); | ||||
|         }); | ||||
|       } else { | ||||
|         console.error(chalk.red('package.json not found')); | ||||
|       } | ||||
|     } | ||||
|   }); | ||||
| const command = new Command('npm').description('npm command show publish and set .npmrc').action(async (options) => {}); | ||||
| const publish = new Command('publish') | ||||
|   .argument('[registry]') | ||||
|   .option('-p --proxy', 'proxy') | ||||
|   .description('publish npm') | ||||
|   .action(async (registry) => { | ||||
|   .action(async (registry, options) => { | ||||
|     const answer = await inquirer.prompt([ | ||||
|       { | ||||
|         type: 'list', | ||||
| @@ -92,6 +35,18 @@ const publish = new Command('publish') | ||||
|     const config = getConfig(); | ||||
|     let cmd = ''; | ||||
|     const execPath = process.cwd(); | ||||
|     let setEnv = {}; | ||||
|     const proxyEnv = { | ||||
|       https_proxy: 'http://127.0.0.1:7890', | ||||
|       http_proxy: 'http://127.0.0.1:7890', | ||||
|       all_proxy: 'socks5://127.0.0.1:7890', | ||||
|       ...config?.proxy, | ||||
|     }; | ||||
|     if (options?.proxy) { | ||||
|       setEnv = { | ||||
|         ...proxyEnv, | ||||
|       }; | ||||
|     } | ||||
|     if (registry) { | ||||
|       const packageJson = path.resolve(execPath, 'package.json'); | ||||
|       switch (registry) { | ||||
| @@ -122,6 +77,7 @@ const publish = new Command('publish') | ||||
|           env: { | ||||
|             ...process.env, // 保留当前环境变量 | ||||
|             ...tokenEnv, | ||||
|             ...setEnv, | ||||
|           }, | ||||
|         }); | ||||
|         child.stdout.on('data', (data) => { | ||||
| @@ -187,7 +143,6 @@ const npmrc = new Command('set') | ||||
|           writeFlag = true; | ||||
|         } | ||||
|       } | ||||
|       return; | ||||
|     } else { | ||||
|       writeFlag = true; | ||||
|     } | ||||
|   | ||||
							
								
								
									
										30
									
								
								src/command/proxy.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/command/proxy.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| import { program, Command } from '@/program.ts'; | ||||
| import { chalk } from '@/module/chalk.ts'; | ||||
| import inquirer from 'inquirer'; | ||||
|  | ||||
| const command = new Command('proxy') | ||||
|   .description('执行代理相关的命令') | ||||
|   .option('-s, --start', '启动代理') | ||||
|   .option('-u, --unset', '关闭代理') | ||||
|   .action((options) => { | ||||
|     const proxyShell = 'export https_proxy=http://127.0.0.1:7890 http_proxy=http://127.0.0.1:7890 all_proxy=socks5://127.0.0.1:7890'; | ||||
|     const unProxyShell = 'unset https_proxy http_proxy all_proxy'; | ||||
|  | ||||
|     if (options.start) { | ||||
|       console.log(chalk.green('启动代理')); | ||||
|       console.log(chalk.green('执行以下命令以启用代理:')); | ||||
|       console.log(`\n  ${chalk.yellow(proxyShell)}\n`); | ||||
|       console.log(`请运行以下命令应用代理:`); | ||||
|       console.log(chalk.cyan(`eval "$(${process.argv[1]} proxy -s)"`)); | ||||
|     } else if (options.unset) { | ||||
|       console.log(chalk.green('关闭代理')); | ||||
|       console.log(chalk.green('执行以下命令以禁用代理:')); | ||||
|       console.log(`\n  ${chalk.yellow(unProxyShell)}\n`); | ||||
|       console.log(`请运行以下命令取消代理:`); | ||||
|       console.log(chalk.cyan(`eval "$(${process.argv[1]} proxy -u)"`)); | ||||
|     } else { | ||||
|       console.log(chalk.red('请提供选项 -s 或 -u')); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
| program.addCommand(command); | ||||
| @@ -1,7 +1,4 @@ | ||||
| import { program as app, Command } from '@/program.ts'; | ||||
| import { getConfig, writeConfig, checkFileExists } from '@/module/index.ts'; | ||||
| import { createApp } from '@/app.ts'; | ||||
| import fs from 'fs'; | ||||
| import inquirer from 'inquirer'; | ||||
| import { query } from '../module/index.ts'; | ||||
| import chalk from 'chalk'; | ||||
|   | ||||
| @@ -10,10 +10,13 @@ import './command/web.ts'; | ||||
| import './command/router.ts'; | ||||
| import './command/npm.ts'; | ||||
| import './command/publish.ts'; | ||||
| import './command/init.ts'; | ||||
| import './command/proxy.ts'; | ||||
|  | ||||
| // program.parse(process.argv); | ||||
|  | ||||
| export const runParser = async (argv: string[]) => { | ||||
|   // program.parse(process.argv); | ||||
|   // console.log('argv', argv); | ||||
|   program.parse(argv); | ||||
| }; | ||||
|   | ||||
| @@ -1,8 +1,13 @@ | ||||
| import { program, Command } from 'commander'; | ||||
| import fs from 'fs'; | ||||
|  | ||||
| // 将多个子命令加入主程序中 | ||||
| program.name('app').description('A CLI tool with envison').version('0.0.3'); | ||||
| let version = '0.0.1'; | ||||
| try { | ||||
|   // @ts-ignore | ||||
|   if (VERSION) version = VERSION; | ||||
| } catch (e) {} | ||||
| // @ts-ignore | ||||
| program.name('app').description('A CLI tool with envison').version(version); | ||||
|  | ||||
| const ls = new Command('ls').description('List files in the current directory').action(() => { | ||||
|   console.log('List files'); | ||||
|   | ||||
| @@ -1,3 +1 @@ | ||||
| import './route/user/list.ts'; | ||||
|  | ||||
| import './route/system-config/index.ts'; | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { app } from '@/app.ts'; | ||||
| import { Config } from './model/config.ts'; | ||||
| import { CustomError } from '@kevisual/router'; | ||||
| app | ||||
|   .route({ | ||||
|     path: 'config', | ||||
| @@ -35,7 +34,7 @@ app | ||||
|   .define(async (ctx) => { | ||||
|     const { data } = ctx.query; | ||||
|     if (!data) { | ||||
|       throw new CustomError('data is required'); | ||||
|       ctx.throw(400, 'data is required'); | ||||
|     } | ||||
|     let config = await Config.findOne({ | ||||
|       where: { key: 'me' }, // 自定义条件 | ||||
|   | ||||
| @@ -1,11 +0,0 @@ | ||||
| import { app } from '@/app.ts'; | ||||
|  | ||||
| app | ||||
|   .route({ | ||||
|     path: 'user', | ||||
|     key: 'list', | ||||
|   }) | ||||
|   .define(async (ctx) => { | ||||
|     ctx.body = 'user list'; | ||||
|   }) | ||||
|   .addTo(app); | ||||
		Reference in New Issue
	
	Block a user