目前就我所呆的公司來講,前端的發版都是開發完以後執行編譯,而後經過 ftp
上傳到服務器中。項目多起來以後,加上測試環境和正式環境的分離,致使管理混亂。並且整個流程也很麻煩,要一步步手動去作。html
因此一直就有一個想法,能不能作一個像 Vue Cli
同樣的自動化工具,能夠經過命令輸入命令和選擇選項,進行自動化的編譯和發版。說幹就幹立馬就開發了一個 ~~~前端
GitHub 地址: https://github.com/zuley/zuley-clinode
chalk
美化命令行,進行着色等commander
解析用戶命令行輸入inquirer
命令行交互功能,像用戶提問等。node-ssh
ssh 模塊ora
命令行環境的 loading 效果shelljs
從新包裝了 child_process
子進程模塊,調用系統命令更方便。npm init
建立一個空項目app
,做爲主入口文件。在入口文件中輸入如下代碼git
const program = require('commander') const inquirer = require('inquirer') const chalk = require('chalk') program .command('module') .alias('m') .description('建立新的模塊') .option('-n, --name [moduleName]', '模塊名稱') .action(option => { console.log('Hello World', option.name) }) program.parse(process.argv) 複製代碼
執行看效果github
$ node app m -n zuley // 輸出:Hello World zuley 複製代碼
上面的示例輸入起來太麻煩,須要進入項目所在目錄才能執行文件,如今咱們須要一個簡單的方式 zuley m -n zuley
shell
一、配置 package.json
中的 bin
字段npm
"bin": { "zuley": "app" } 複製代碼
二、註冊全局命令json
而後執行註冊符號連接,它會把 zuley
這個字段複製到 npm
全局模塊安裝文件夾 node_modules
內,也就是將 zuley
的路徑加入環境變量 PATH
中。bash
若是是MAC,則須要加上 sudo
前綴,使用管理員權限。服務器
本文中全部使用了
sudo
的地方,均是MAC系統限制,win用戶須要刪除此句。
評論有人反饋說不加
sudo
也能夠,能夠先嚐試不加,若是有報錯再加。
$ npm link # or mac $ sudo npm link 複製代碼
三、聲明爲可執行應用
在入口文件的最上方加入聲明,聲明這是一個可執行的應用。
#! /usr/bin/env node ...code 複製代碼
4、執行命令
$ zuley m -n zuley
複製代碼
實際開發中,咱們以 src
爲源碼目錄,自動發佈系統作爲其中的一個模塊,放置在模塊目錄中。
|-/src/ // 源碼目錄 |---/modules/ 模塊目錄 |-----/Automation/ // 發佈模塊目錄 |-------index.js // 模塊入口文件 |-------config.promps.js // 選項配置文件 |-------config.service.js // 服務器配置文件 |-app // 入口文件 複製代碼
app
中載入自動發佈系統模塊#! /usr/bin/env node // 導入自動化任務模塊 require('./src/modules/Automation/index') 複製代碼
編寫模塊入口文件 /src/modules/Automation/index.js
執行命令以後,咱們須要提供命令行界面,讓用戶輸入或者選擇選項來肯定接下來的操做。這裏用到了 inquirer
包。
具體用例請去 npm
或者自行搜索學習。
如下是代碼片斷
// 導入選項配置 const prompsConfig = require('./config.promps') // 項目名稱 let { name } = await inquirer.prompt(prompsConfig.name) // 項目渠道 let source = '' if (prompsConfig.source[name].length > 0) { source = await inquirer.prompt(prompsConfig.source[name]) source = source.source } // 項目環境 let { type } = await inquirer.prompt(prompsConfig.type) // 確認選項 log('請確認你選擇瞭如下選項') log(chalk.green('項目名稱:') + chalk.red(TEXTDATA[name])) log(chalk.green('項目渠道:') + chalk.red(TEXTDATA[source])) log(chalk.green('項目環境:') + chalk.red(TEXTDATA[type])) 複製代碼
這裏使用了 shelljs
包。shelljs
從新包裝了 child_process
子進程模塊,調用系統命令更方便。
本文中全部使用了
sudo
的地方,均是MAC系統限制,win用戶須要刪除此句。
如下是代碼片斷
async function compile (config, type) { // 進入項目本地目錄 shell.cd(config.localPath) if (type === 'TEST') { log('測試環境編譯') shell.exec(`sudo npm run test`) } else { log('正式環境編譯') shell.exec(`npm run build`) } log('編譯完成') } 複製代碼
上傳文件使用了 node-ssh
包,該包封裝了一些簡單易用的方法,具體能夠查詢官網或者搜索教程。
如下是代碼片斷
/** * 上傳文件 * @param {Object} config 項目配置 */ async function updateFile (config) { // 存儲失敗序列 let failed = [] // 存儲成功序列 let successful = [] let spinner = ora('準備上傳文件').start() // 上傳文件夾 let status = await ssh.putDirectory(config.localPath + '/dist', config.remotePath, { // 遞歸 recursive: true, // 併發數 concurrency: 10, tick (localPath, remotePath, error) { if (error) { failed.push(localPath) } else { spinner.text = '正在上傳文件:' + localPath successful.push(localPath) } } }) spinner.stop() if (status) { log(chalk.green('完成上傳')) } else { log(chalk.red('上傳失敗')) } if (failed.length > 0) { log(`一共有${chalk.red(failed.length)}個上傳失敗的文件`) log(failed) } } 複製代碼
本例只作簡單的演示,實際應用還須要擴展上傳失敗的處理。好比斷點續傳,失敗文件續傳等。
並且單個上傳會很慢,能夠先運行命令壓縮後再上傳,再執行下服務器命令解壓文件。
若是上傳失敗,檢查遠程目錄是否有權限,使用命令修改權限。
$ chmod -R 777 [目錄]
複製代碼
一、index.js
const program = require('commander') const inquirer = require('inquirer') const chalk = require('chalk') const ora = require('ora') const shell = require('shelljs') const node_ssh = require('node-ssh') const ssh = new node_ssh() // 導入選項配置 const prompsConfig = require('./config.promps') // 導入服務器配置 const serviceConfig = require('./config.service') const log = console.log // 字段字典 const TEXTDATA = { 'A': '項目A', 'B': '項目B', 'PC': 'PC 網站', 'WX': '微信公衆號', 'TEST': '測試環境', 'PROD': '正式環境', '': '其餘' } // 添加一個名字爲 a 別名爲 automation 的命令模塊 program .command('a') .alias('automation') .description('前端自動化發佈系統') .action(async option => { // 項目名稱 let { name } = await inquirer.prompt(prompsConfig.name) // 項目渠道 let source = '' if (prompsConfig.source[name].length > 0) { source = await inquirer.prompt(prompsConfig.source[name]) source = source.source } // 項目環境 let { type } = await inquirer.prompt(prompsConfig.type) // 確認選項 log('請確認你選擇瞭如下選項') log(chalk.green('項目名稱:') + chalk.red(TEXTDATA[name])) log(chalk.green('項目渠道:') + chalk.red(TEXTDATA[source])) log(chalk.green('項目環境:') + chalk.red(TEXTDATA[type])) // 獲取配置 let config = serviceConfig[`${name}-${source}-${type}`] log(`使用服務器配置:${name}-${source}-${type}`) // 編譯項目 compile(config, type) // 鏈接服務器 await ConnectService(config) // 上傳文件 await updateFile(config) }) program.parse(process.argv) /** * 鏈接服務器 * @param {Object} config 項目配置 */ async function ConnectService (config) { log('嘗試鏈接服務:' + chalk.red(config.service.host)) let spinner = ora('正在鏈接') spinner.start() await ssh.connect(config.service) spinner.stop() log(chalk.green('成功鏈接到服務器')) } /** * 上傳文件 * @param {Object} config 項目配置 */ async function updateFile (config) { // 存儲失敗序列 let failed = [] // 存儲成功序列 let successful = [] let spinner = ora('準備上傳文件').start() // 上傳文件夾 let status = await ssh.putDirectory(config.localPath + '/dist', config.remotePath, { // 遞歸 recursive: true, // 併發數 concurrency: 10, tick (localPath, remotePath, error) { if (error) { failed.push(localPath) } else { spinner.text = '正在上傳文件:' + localPath successful.push(localPath) } } }) spinner.stop() if (status) { log(chalk.green('完成上傳')) } else { log(chalk.red('上傳失敗')) } if (failed.length > 0) { log(`一共有${chalk.red(failed.length)}個上傳失敗的文件`) log(failed) } } /** * 編譯源碼 * @param {Object} config 項目配置 * @param {String} type 編譯類型 TEST or PROD */ async function compile (config, type) { // 進入項目本地目錄 shell.cd(config.localPath) if (type === 'TEST') { log('測試環境編譯') shell.exec(`sudo npm run test`) } else { log('正式環境編譯') shell.exec(`sudo npm run build`) } log('編譯完成') } 複製代碼
二、選項配置源碼:config.promps.js
此文件配置用戶在命令行中輸入或者選擇的數據,供後續拼接生成 A-WX-TEST
類的字段。
A-WX-TEST
表明用戶選擇了:項目A-微信-測試環境
程序將經過此拼接字段,去 config.service.js
中獲取項目配置
/** * 自動化模塊 - 選項配置文件 */ module.exports = { // 項目名字 name: [ { type: 'list', name: 'name', message: '請選擇要發佈的項目', choices: [ { name: '項目A', value: 'A' }, { name: '項目B', value: 'B' } ] } ], // 項目渠道 source: { 'A': [ { type: 'list', name: 'source', message: '請選擇要發佈的渠道', choices: [ { name: 'PC網站', value: 'PC' }, { name: '微信', value: 'WX' } ] } ], 'B': [ { type: 'list', name: 'source', message: '請選擇要發佈的渠道', choices: [ { name: 'PC網站', value: 'PC' }, { name: '微信', value: 'WX' } ] } ] }, // 項目環境 type: [ { type: 'list', name: 'type', message: '請選擇發佈環境', choices: [ { name: '測試環境', value: 'TEST' }, { name: '正式環境', value: 'PROD' } ] } ] } 複製代碼
三、服務器配置源碼:config.service.js
服務器配置只配置了兩個做爲演示,實際看現實狀況補充。
在 GitHub
上拉取項目測試的時候,記得必定要修改次文件。
A-WX-TEST
這個字段表明用戶輸入的選項,具體看 config.promps.js
// 服務器 A const serviceA = { // 服務器 IP host: 'xxx.xxx.xxx.xxx', // ssh 帳號 username: 'xxx', // ss 密碼 password: 'xxxxxx' } module.exports = { // 項目A,微信測試環境 'A-WX-TEST': { service: serviceA, // 本地目錄 localPath: '/Users/zuley/ChuDaoNew/minshengjingwu-h5', // 遠程目錄 remotePath: '/root/html/test' }, // 項目A,微信正式環境 'A-WX-PROD': { service: serviceA, // 本地目錄 localPath: '/Users/zuley/ChuDaoNew/minshengjingwu-h5', // 遠程目錄 remotePath: '/root/html/prod' } } 複製代碼
執行如下命令便可啓動自動化
$ zuley a
複製代碼
輸入有誤想終止命令可輸入
$ Ctrl+C
複製代碼