diff --git a/Readme.md b/Readme.md index 56b4308..cb14da8 100644 --- a/Readme.md +++ b/Readme.md @@ -4,48 +4,11 @@ / _` | '_ ` _ \|___ \ _____ / __| | | | (_| | | | | | |___) |_____| (__| | | \__, |_| |_| |_|____/ \___|_|_| - |___/ 一键安装、管理Five.js框架的脚本工具 + |___/ 一键安装 Five.js 框架的脚本工具 ``` ## 安装 ```bash -npm i -g @gm5/cli +npm create five ``` - -## 用法 -> 用法: `five-cli [command] args...` - -+ Commands: - * init - 初始化框架 - * start [prod|dev] - 运行当前的应用(可指定运行环境) - * stop - 停止当前应用 - * st|status - 查看当前应用状态 - * r|restart - 重启当前应用 - * del|delete - 删除当前应用 - * -h - 查看帮助文档 - * -v - 查看工具的版本 - - -## 更新日志 - -### v1.0.0 -* 脚本重构,支持windows -* 支持最新框架 - - -### 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 index ab504a0..10ff3d3 100644 --- a/index.js +++ b/index.js @@ -1,109 +1,151 @@ -#!/usr/bin/env node - +#!/usr/bin/node /** - * 脚手架 + * * @author yutent - * @date 2020/09/28 11:35:16 + * @date 2022/10/10 15:17:36 */ -const fs = require('iofs') -const chalk = require('chalk') -const path = require('path') +import { red, cyan, blue, grey } from 'kolorist' +import prompts from 'prompts' +import fs from 'iofs' +import { resolve, join, dirname } from 'path' -var pm2 -var pkg = require('./package.json') -var { do_init } = require('./lib/init') -var { start, stop, restart, remove, status } = require('./lib/pm2') +import { + writePackageJson, + writeGitIgnore, + writePrettierrc, + writePm2File +} from './lib/demo-config.js' +import { writeMainJs, writeAppJs, writeModelJs } from './lib/demo-js.js' -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() +const CURRENT_DIR = process.cwd() +const root = dirname(import.meta.url.slice(process.platform === 'win32' ? 10 : 7)) +const { version } = JSON.parse(fs.cat(join(root, './package.json'))) -try { - pm2 = require('pm2/package.json') -} catch (error) { - print('#'.repeat(64)) - console.log(chalk.red('脚手架使用pm2作为守护进程, 请先安装pm2')) - console.log(chalk.green('npm i -g pm2')) - print('#'.repeat(64)) - process.exit() -} +const DEFAULT_NAME = 'gm5-app' -function print(...args) { - args[0] = args[0].padEnd(20, ' ') - if (args.length > 1) { - args.splice(1, 0, ' - ') +let args = process.argv.slice(2) +let targetDir = '' + +function isEmpty(dir) { + let list = fs.ls(dir) + if (list && list.length) { + return false } - console.log.apply(null, args) + return true } -function logo() { - return chalk.green.bold(` ____ - __ _ _ __ ___ | ___| - / _\` | '_ \` _ \\|___ \\ -| (_| | | | | | |___) | - \\__, |_| |_| |_|____/ - |___/`) +function sleep(num = 1) { + return new Promise(resolve => setTimeout(resolve, num * 1000)) } -function print_help() { - print('='.repeat(64)) - print(`${logo()} v${pkg.version}, 作者: 宇天`) - print('-'.repeat(64)) - print('node版本: ' + version) - print('pm2 版本: v' + pm2.version) - 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', '查看工具的版本') +function printHelp() { + console.log('Usage: create-five {command} [arguments]') + console.log(' ', 'create-five init', '一个快速创建Five.js项目的小工具') + console.log(' ', 'create-five -h[--help]', '打印帮助信息') + console.log() process.exit() } -switch (action) { - case 'init': - do_init(cwd) - break +!(async function () { + switch (args[0]) { + case '-v': + case '--version': + console.log('v' + version) + break - case 'start': - start(cwd, args[0]) - break + case '-h': + case '--help': + printHelp() + break - case 'stop': - stop(cwd) - break + default: + let res = await prompts([ + { + name: 'projectName', + type: 'text', + message: '项目名称(也是目录名, 只能为英文、数字、-、.):', + initial: DEFAULT_NAME, + validate: val => /^[a-zA-Z\d\-\.]+$/.test(val), + onState: ({ value }) => (targetDir = join(CURRENT_DIR, value)) + }, + { + name: 'shouldOverwrite', + type: _ => (isEmpty(targetDir) ? null : 'toggle'), + message: _ => `目录 ${cyan(targetDir)} 非空, 是否${red('删除')}目录下所有的文件?`, + initial: false, + active: '是', + inactive: '否' + }, + { + name: 'confirmCheck', + type: shouldOverwrite => { + if (shouldOverwrite === false) { + console.log(red('✖') + ' 操作取消~~') + process.exit(1) + } + return null + } + } + ]) - case 'st': - case 'status': - status(cwd) - break + console.log() - case 'r': - case 'restart': - restart(cwd) - break + if (res.projectName === '.') { + res.projectName = DEFAULT_NAME + } - case 'del': - case 'delete': - remove(cwd) - break + console.log('指定的项目名为: %s', cyan(res.projectName)) + console.log('项目目录为: %s', cyan(targetDir)) - case '-v': - print(pkg.version) - process.exit() - break + if (res.shouldOverwrite) { + console.log(red('目录非空, 1s 后将清空目录~~')) + await sleep(1) + let list = fs.ls(targetDir) + list.forEach(it => fs.rm(it, true)) + } else { + console.log(red('程序将在 1s 后初始化项目~~')) + await sleep(1) + } - default: - print_help() - break -} + console.log(cyan('\n初始化项目...')) + + fs.mkdir(join(targetDir, 'apps')) + fs.mkdir(join(targetDir, 'models')) + fs.mkdir(join(targetDir, 'config')) + fs.mkdir(join(targetDir, 'data')) + fs.mkdir(join(targetDir, 'public')) + fs.mkdir(join(targetDir, 'views')) + + console.log('[c---------]', '10%') + + writePackageJson(join(targetDir, 'package.json'), res.projectName) + writeGitIgnore(join(targetDir, '.gitignore')) + writePrettierrc(join(targetDir, '.prettierrc.yaml')) + writePm2File(join(targetDir, 'app.dev.yaml'), { + name: res.projectName, + env: 'development', + cluster: false + }) + writePm2File(join(targetDir, 'app.yaml'), { + name: res.projectName, + cluster: false + }) + + console.log('[ooc-------]', '30%') + + console.log('[oooooc----]', '60%') + + writeMainJs(join(targetDir, 'app.js')) + writeAppJs(join(targetDir, 'apps/index.js')) + writeModelJs(join(targetDir, 'models/index.js')) + + console.log('[oooooooooo]', '100%') + console.log(cyan('初始化完成, 可依次执行以下命令启动项目: ')) + console.log(blue('npm i')) + console.log(blue('pm2 start app.dev.yaml'), grey('# 开发模式')) + console.log(blue('pm2 start app.yaml'), grey('# 生产模式')) + + break + } +})() diff --git a/lib/demo-config.js b/lib/demo-config.js new file mode 100644 index 0000000..4634298 --- /dev/null +++ b/lib/demo-config.js @@ -0,0 +1,92 @@ +/** + * {} + * @author yutent + * @date 2022/10/10 16:49:07 + */ + +import fs from 'iofs' + +export function writePackageJson(file, name) { + fs.echo( + JSON.stringify( + { + name: name || 'gm5-app', + type: 'module', + main: 'app.js', + dependencies: { + '@gm5/core': '^1.1.4' + } + }, + null, + 2 + ), + file + ) +} + +export function writeGitIgnore(file) { + fs.echo( + ` +*.sublime-project +*.sublime-workspace + +node_modules +package-lock.json +data/logs +public/upload/ + +._* +.idea +.vscode +.Spotlight-V100 +.Trashes +.DS_Store +.AppleDouble +.LSOverride + +`, + file + ) +} + +export function writePrettierrc(file) { + fs.echo( + ` +jsxBracketSameLine: true +jsxSingleQuote: true +semi: false +singleQuote: true +printWidth: 100 +useTabs: false +tabWidth: 2 +trailingComma: none +bracketSpacing: true +arrowParens: avoid +`, + file + ) +} + +export function writePm2File(file, { env = 'production', name = 'gm5-app', cluster = true }) { + 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 ? 'cluster' : 'fork'} +${cluster ? 'instances: max' : ''} +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: ${env} + + `, + file + ) +} diff --git a/lib/demo-js.js b/lib/demo-js.js new file mode 100644 index 0000000..67b8bf3 --- /dev/null +++ b/lib/demo-js.js @@ -0,0 +1,107 @@ +/** + * {} + * @author yutent + * @date 2022/10/10 17:00:29 + */ + +import fs from 'iofs' + +export function writeMainJs(file) { + fs.echo( + ` +import Five from '@gm5/core' + +const app = new Five() + +// app.set({ +// // 可开启session支持 +// session: { enabled: true, type: 'redis' } +// +// // 可开启模板引擎的支持 +// views: { enabled: true, dir: './views' } +// }) + + + +// 预加载应用, 这一步是必须的, 且需要在listen方法前调用 +app.preload('./apps/') + +// 中间件示例 +// 这里的回调, 如果不用箭头函数的话, this指向app +// app.use((req, res, next) => { +// if (req.method !== 'GET') { +// return res.error('', 401) +// } +// // 这一步很重要, 如果没有执行 next(), 则程序就会在这个回调里终止 +// // 不会再往后执行其他的了 +// next() +// }) + +// 安装拓展包, 可以应用中通过 this.context.$$mysql 调用到, +// 在中间件, 或后面的其他拓展包中, 可以直接 this.$$mysql 调用 +// app.install({ +// name: 'mysql', +// install: function() { +// return new Mysqli(conf) +// } +// }) + +app.listen(3000) + +`, + file + ) +} + +export function writeModelJs(file) { + fs.echo( + ` + +// 数据模型|存储交互 +export default class Index { + + constructor(conn){ + // this.db = conn.emit(false, 'test') + } + + list(){ + // return this.db.table('user').withFields(['id', 'name']).getAll() + } +} + + +`, + file + ) +} + +export function writeAppJs(file) { + fs.echo( + ` + +import Controller from '@gm5/controller' + +// import Model from '../models/index.js' + +// 所有的应用, 都要继承Controller +// 以获取跟框架交互的能力 +export default class Index extends Controller { + + // 这个main方法是可选的, 如果有定义, 会自动先调用 + // 可以在这里做任何需要预处理的事, 支持async/await + // 若返回 false, 则可以终止程序往下执行 + // 这个特性, 可用于权限验证等操作 + __main__() { + // this.model = new Model(this.context.$$mysql) + } + + async indexAction(){ + this.response.end('It works!') + } +} + + +`, + file + ) +} diff --git a/lib/init.js b/lib/init.js deleted file mode 100644 index 65c5252..0000000 --- a/lib/init.js +++ /dev/null @@ -1,164 +0,0 @@ -/** - * 初始化项目 - * @author yutent - * @date 2020/09/28 15:45:46 - */ - -const fs = require('iofs') -const path = require('path') -const chalk = require('chalk') - -const { exec, read } = require('./tools') -const { start } = require('./pm2') - -function encode(obj) { - return JSON.stringify(obj, null, 2) -} - -exports.do_init = async 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.js' - -// 所有的应用, 都要继承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('目录初始化完成!')) - - var confirm = await read('是否立即安装依赖? y/n: ') - if (confirm === 'y') { - console.log(chalk.green('依赖安装中...')) - exec('npm i @gm5/core') - console.log(chalk.blue('依赖安装完成 ^_^')) - } - - confirm = await read('是否立即运行? y/n: ') - - if (confirm === 'y') { - start(dir) - } else { - process.exit() - } -} diff --git a/lib/pm2.js b/lib/pm2.js deleted file mode 100644 index b2b7833..0000000 --- a/lib/pm2.js +++ /dev/null @@ -1,168 +0,0 @@ -/** - * 进程管理 - * @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.start = 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('应用可能已经启动, 请确认是否在列表中, 以免重复启动!') - ) - 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) - 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(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') { - 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(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)) { - exec('pm2 status app.yaml') - } else { - console.log(chalk.yellow('应用尚未配置启动...')) - } - process.exit() -} diff --git a/lib/tools.js b/lib/tools.js deleted file mode 100644 index e363727..0000000 --- a/lib/tools.js +++ /dev/null @@ -1,22 +0,0 @@ -const child = require('child_process') -const readline = require('readline') - -exports.exec = function(cmd) { - return child.execSync(cmd, { - stdio: [process.stdin, process.stdout, process.stdout] - }) -} - -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 index e6d7e6e..f307d0d 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,16 @@ { - "name": "@gm5/cli", - "description": "Five.js框架控制台工具", + "name": "create-five", + "description": "一个快速创建Five.js项目的小工具", + "type": "module", "version": "1.0.0", "author": "yutent ", "bin": { - "five-cli": "index.js" + "create-five": "index.js" }, "dependencies": { - "chalk": "^4.0.0", - "iofs": "^1.5.0" + "iofs": "^1.5.0", + "prompts": "^2.4.2", + "kolorist": "^1.6.0" }, "repository": { "type": "git",