From a1aee3760ea75f34f08090d40767f8a08b191a4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=87=E5=A4=A9?= Date: Mon, 28 Sep 2020 19:20:33 +0800 Subject: [PATCH] 1.0.0 --- Readme.md | 42 +++++++++++++ index.js | 100 ++++++++++++++++++++++++++++++ lib/init.js | 151 +++++++++++++++++++++++++++++++++++++++++++++ lib/pm2.js | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++ lib/tools.js | 20 ++++++ package.json | 12 ++++ 6 files changed, 495 insertions(+) create mode 100644 Readme.md create mode 100644 index.js create mode 100644 lib/init.js create mode 100644 lib/pm2.js create mode 100644 lib/tools.js create mode 100644 package.json diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000..45d9ff4 --- /dev/null +++ b/Readme.md @@ -0,0 +1,42 @@ +# Five.js框架控制台工具 +> 一键安装、管理Five.js框架的脚本工具。 +>> 仅支持Linux/MacOS + +## 安装 +> 需要全局安装 +```bash +npm i five-cli -g +``` + +## 用法 +> 用法: `five-cli [command] args...` + ++ Commands: + * init - 初始化一个版本(最低3.0.0),不指定则为最新版 + * start [prod|dev] - 运行当前的应用(可指定以开发环境还是生产环境启动) + * stop - 停止当前应用 + * st|status - 查看当前应用状态 + * r|restart - 重启当前应用 + * del|delete - 删除当前应用 + * -h - 查看帮助文档 + * -v - 查看工具的版本 + + +## 更新日志 + + +### v0.4.1 +* 优化项目构建 +* 支持指令简写 +* 优化启动逻辑 + + +### v0.2.0 +* 从 v0.2版开始, 将只支持 linux/macos, 且框架自动为最新的3.x。 +* [优化] 优化five-cli start [dev|prod] 指令 + + +### v0.1.2 +* 下载地址改用github +* 兼容windows +* 优化操作提示 \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..bee0002 --- /dev/null +++ b/index.js @@ -0,0 +1,100 @@ +/** + * 脚手架 + * @author yutent + * @date 2020/09/28 11:35:16 + */ + +const fs = require('iofs') +const chalk = require('chalk') +const path = require('path') + +const { do_init } = require('./lib/init') +const { run, stop, restart, remove, status } = require('./lib/pm2') +const { exec } = require('./lib/tools') + +var pkg = fs.cat(path.join(__dirname, './package.json')) +var { arch, platform, version } = process +var os = { linux: 'Linux', darwin: 'MacOS', win: 'Windows' } +var cwd = process.cwd() +var args = process.argv.slice(2) +var action = args.shift() + +pkg = JSON.parse(pkg) + +function print(...args) { + args[0] = args[0].padEnd(20, ' ') + if (args.length > 1) { + args.splice(1, 0, ' - ') + } + console.log.apply(null, args) +} + +function logo() { + return chalk.green.bold(` ____ + __ _ _ __ ___ | ___| + / _\` | '_ \` _ \\|___ \\ +| (_| | | | | | |___) | + \\__, |_| |_| |_|____/ + |___/`) +} + +function print_help() { + print('='.repeat(64)) + print(`${logo()} v${pkg.version}, 作者: 宇天`) + print('-'.repeat(64)) + print('node版本: ' + version) + print('pm2 版本: v' + exec('pm2 -v').trim()) + print(`当前系统: ${os[platform]}(${arch})`) + print('当前路径: ' + chalk.red.underline(cwd)) + print('='.repeat(64)) + print('用法: five-cli [command] args...') + print('Commands:') + print(' init', '初始化框架') + print(' start [prod|dev]', '运行当前的应用(可指定运行环境)') + print(' stop', '停止当前应用') + print(' st|status', '查看当前应用状态') + print(' r|restart', '重启当前应用') + print(' del|delete', '删除当前应用') + print(' -h', '查看帮助文档') + print(' -v', '查看工具的版本') + process.exit() +} + +switch (action) { + case 'init': + do_init(cwd) + break + + case 'start': + run(cwd, args[0]) + break + + case 'stop': + stop(cwd) + break + + case 'st': + case 'status': + status(cwd) + break + + case 'r': + case 'restart': + restart(cwd) + break + + case 'del': + case 'delete': + remove(cwd) + break + + case '-v': + print(pkg.version) + process.exit() + break + + default: + print_help() + // console.log(exec('pm2 -v').trim()) + break +} diff --git a/lib/init.js b/lib/init.js new file mode 100644 index 0000000..bc9eccd --- /dev/null +++ b/lib/init.js @@ -0,0 +1,151 @@ +/** + * 初始化项目 + * @author yutent + * @date 2020/09/28 15:45:46 + */ + +const fs = require('iofs') +const path = require('path') +const chalk = require('chalk') + +const { exec } = require('./tools') + +function encode(obj) { + return JSON.stringify(obj, null, 2) +} + +exports.do_init = function(dir) { + var files = fs.ls(dir) + if (files && files.length) { + console.log(chalk.red('当前目录非空, 初始化失败...')) + process.exit() + } + + console.log(chalk.green('初始化目录...')) + fs.mkdir(path.join(dir, 'apps')) + fs.mkdir(path.join(dir, 'models')) + fs.mkdir(path.join(dir, 'config')) + fs.mkdir(path.join(dir, 'data')) + fs.mkdir(path.join(dir, 'public')) + fs.mkdir(path.join(dir, 'views')) + + fs.echo( + encode({ + name: 'fivejs-instance', + type: 'module', + main: 'app.js' + }), + path.join(dir, 'package.json') + ) + + // git忽略配置 + fs.echo( + ` +.Spotlight-V100 +.Trashes +.DS_Store +.AppleDouble +.LSOverride +._* +.idea +.vscode + +node_modules/ +data/logs +public/upload/ +package-lock.json +app.yaml +`, + path.join(dir, '.gitignore') + ) + + // 应用控制器 + fs.echo( + ` + +import Controller from '@gm5/controller' + +// import Model from '../models/index' + +// 所有的应用, 都要继承Controller +// 以获取跟框架交互的能力 +export default class Index extends Controller { + + + // 这个main方法是可选的, 如果有定义, 会自动先调用 + // 可以在这里做任何需要预处理的事, 支持async/await + __main__(){ + // this.model = new Model(this.ctx.ins('mysql')) + } + + async indexAction(){ + this.response.end('It works!') + } +} + `, + path.join(dir, 'apps/index.js') + ) + + // 数据模型 + fs.echo( + ` +// 数据模型|存储交互 +export default class Index { + + constructor(conn){ + // this.db = conn.emit(false, 'test') + } + + list(){ + // return this.db.table('user').withFields(['id', 'name']).getAll() + } +} + `, + path.join(dir, 'models/index.js') + ) + + // 入口js + fs.echo( + ` +import Five from '@gm5/core' + +const app = new Five() + +// 可开启session支持 +// app.set({ session: { enabled: true, type: 'redis' } }) + +// 可开启模板引擎的支持 +// app.set({ views: { enabled: true, dir: './views' } }) + +// 预加载应用, 这一步是必须的, 且需要在listen方法前调用 +app.preload('./apps/') + +// 中间件示例 +// app.use((req, res, next) => { +// if (req.method !== 'GET') { +// res.error('', 401) +// } +// next() +// }) + +// 安装拓展包, 可以应用中通过 this.context.$$log调用到, +// 在中间件, 或后面的其他拓展包中, 可以直接 this.$$log 调用 +// app.install({ +// name: 'log', +// install: function() { +// return new Logs('run_time.log', './data/logs/') +// } +// }) + +app.listen(3000) + + + `, + path.join(dir, 'app.js') + ) + + console.log(chalk.green('目录初始化完成, 依赖安装中...')) + exec('npm i @gm5/core') + console.log(chalk.blue('依赖安装完成 ^_^')) + process.exit() +} diff --git a/lib/pm2.js b/lib/pm2.js new file mode 100644 index 0000000..92d4894 --- /dev/null +++ b/lib/pm2.js @@ -0,0 +1,170 @@ +/** + * 进程管理 + * @author yutent + * @date 2020/09/28 15:45:46 + */ + +const fs = require('iofs') +const path = require('path') +const os = require('os') +const chalk = require('chalk') + +const { exec, read } = require('./tools') + +var cpus = os.cpus() + +exports.run = async function(dir, env) { + var confFile = path.join(dir, 'app.yaml') + var run_env = process.env.NODE_ENV || 'development' + var name, cluster, instances + + if (env === 'prod') { + run_env = 'production' + } + + if (fs.exists(confFile)) { + console.log( + chalk.yellow('应用可能已经启动, 请确认是否在列表中, 以免重复启动!') + ) + console.log(exec('pm2 ls')) + var act = await read( + '请确认操作, 如已在列表中, 请回车或按Ctrl + C取消, 输入任意内容将会重新启动: ' + ) + if (act) { + var data = fs.cat(confFile).toString() + data = data.replace(/NODE_ENV: [a-z]+/, `NODE_ENV: ${run_env}`) + fs.echo(data, confFile) + console.log(exec('pm2 start app.yaml')) + console.log(chalk.blue('应用启动成功!!!')) + } + process.exit() + } + + console.log('首次运行,请根据提示完成配置') + + // --------------- + name = await read('(请输入应用唯一名称, 不能含中文): ') + if (name === '') { + console.log(chalk.yellow('没有输入, 自动使用随机名称')) + name = 'five-demo-' + ~~(Math.random() * 99) + } + console.log(`当前应用名称为: ${name}`) + + // --------------- + cluster = await read('(是否开启集群模式, 生产环境建议开启, y/n): ') + if (cluster === '') { + console.log(chalk.yellow('没有输入, 默认不开启集群模式')) + cluster = 'fork' + } else { + if (cluster === 'y') { + cluster = 'cluster' + } else { + cluster = 'fork' + } + } + console.log(`当前运行模式为: ${cluster}`) + + // --------------- + if (cluster === 'cluster') { + instances = await read(`(请设置开启的线程数: 0-${cpus}, 0为自动): `) + instances = +instances + + if (instances === 0) { + instances = 'max' + } else { + if (instances > cpus) { + instances = cpus + } else if (instances < 0) { + instances = 1 + } + } + instances = `instances: ${instances}` + } else { + instances = '' + } + + fs.echo( + ` +script: node --experimental-json-modules ./app.js +cwd: ./ +watch: true +name: ${name} +ignore_watch: [data, public, package.json, package-lock.json, node_modules, .git, .gitignore, app.yaml] +exec_mode: ${cluster} +${instances} +error_file: ./data/logs/error.log +out_file: ./data/logs/out.log +merge_logs: true +min_uptime: 60s +max_restarts: 1 +max_memory_restart: 300M +env: + NODE_ENV: ${run_env} + + `, + confFile + ) + + console.log(chalk.blue('配置完成, 启动中...')) + exec('pm2 start app.yaml') + console.log(chalk.blue('应用成功启动, 打开浏览器体验一下吧.')) + process.exit() +} + +exports.stop = async function(dir) { + var confFile = path.join(dir, 'app.yaml') + if (fs.exists(confFile)) { + var confirm = await read('(你确定要停止应用吗? y/n): ') + if (confirm === 'y') { + exec('pm2 stop app.yaml') + console.log(exec('pm2 status app.yaml')) + console.log(chalk.blue('应用已经停止.')) + } + } else { + console.log(chalk.yellow('应用尚未配置启动...')) + } + process.exit() +} + +exports.restart = async function(dir) { + var confFile = path.join(dir, 'app.yaml') + if (fs.exists(confFile)) { + var confirm = await read('(你确定要重启应用吗? y/n): ') + if (confirm === 'y') { + console.log(exec('pm2 restart app.yaml')) + console.log(chalk.blue('应用已经重启.')) + } + } else { + console.log(chalk.yellow('应用尚未配置启动...')) + } + process.exit() +} + +exports.remove = async function(dir) { + var confFile = path.join(dir, 'app.yaml') + if (fs.exists(confFile)) { + console.log(chalk.yellow('你确定要删除应用吗? ')) + console.log( + chalk.yellow('(该操作只是从守护进程列表中移除当前应用,不会删除任何文件)') + ) + var confirm = await read('(请确认操作 y/n): ') + if (confirm === 'y') { + exec('pm2 delete app.yaml') + console.log(exec('pm2 status app.yaml')) + console.log(chalk.blue('应用已经删除.')) + } + } else { + console.log(chalk.yellow('应用尚未配置启动...')) + } + process.exit() +} + +exports.status = async function(dir) { + var confFile = path.join(dir, 'app.yaml') + if (fs.exists(confFile)) { + console.log(exec('pm2 status app.yaml')) + } else { + console.log(chalk.yellow('应用尚未配置启动...')) + } + process.exit() +} diff --git a/lib/tools.js b/lib/tools.js new file mode 100644 index 0000000..9705a60 --- /dev/null +++ b/lib/tools.js @@ -0,0 +1,20 @@ +const child = require('child_process') +const readline = require('readline') + +exports.exec = function(cmd) { + return child.execSync(cmd).toString() +} + +var rl = readline.createInterface({ + input: process.stdin, + output: process.stdout +}) + +exports.read = function(msg) { + return new Promise(r => { + rl.question(msg, _ => { + r(_.trim()) + rl.pause() + }) + }) +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..362f8a0 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "@gm5/cli", + "description": "Five.js框架控制台工具", + "version": "0.7.0", + "author": { + "name": "yutent" + }, + "bin": { + "five-cli": "index.js" + }, + "license": "MIT" +}