目前就我所呆的公司來說,前端的發版都是開發完之後執行編譯,然後通過 ftp
上傳到伺服器中。專案多起來之後,加上測試環境和正式環境的分離,導致管理混亂。而且整個流程也很麻煩,要一步步手動去做。
所以一直就有一個想法,能不能做一個像 Vue Cli
一樣的自動化工具,可以通過命令輸入命令和選擇選項,進行自動化的編譯和發版。說幹就幹立馬就開發了一個 ~~~
GitHub 地址: https://github.com/zuley/zuley-cli
一、技術棧
chalk
美化命令列,進行著色等commander
解析使用者命令列輸入inquirer
命令列互動功能,像使用者提問等。node-ssh
ssh 模組ora
命令列環境的 loading 效果shelljs
重新包裝了child_process
子程式模組,呼叫系統命令更方便。
二、建立專案
- 直接使用
npm init
建立一個空專案 - 在根目錄建立一個不帶字尾的系統檔案
app
,作為主入口檔案。
三、簡單命令列示例
在入口檔案中輸入以下程式碼
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)
複製程式碼
執行看效果
$ node app m -n zuley // 輸出:Hello World zuley
複製程式碼
四、以全域性方式執行
上面的示例輸入起來太麻煩,需要進入專案所在目錄才能執行檔案,現在我們需要一個簡單的方式 zuley m -n zuley
1、配置 package.json
中的 bin
欄位
"bin": {
"zuley": "app"
}
複製程式碼
2、註冊全域性命令
然後執行註冊符號連結,它會把 zuley
這個欄位複製到 npm
全域性模組安裝資料夾 node_modules
內,也就是將 zuley
的路徑加入環境變數 PATH
中。
如果是MAC,則需要加上 sudo
字首,使用管理員許可權。
本文中所有使用了
sudo
的地方,均是MAC系統限制,win使用者需要刪除此句。
評論有人反饋說不加
sudo
也可以,可以先嚐試不加,如果有報錯再加。
$ npm link
# or mac
$ sudo npm link
複製程式碼
3、宣告為可執行應用
在入口檔案的最上方加入宣告,宣告這是一個可執行的應用。
#! /usr/bin/env node
...code
複製程式碼
四、執行命令
$ 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
1、命令列詢問選項
執行命令之後,我們需要提供命令列介面,讓使用者輸入或者選擇選項來確定接下來的操作。這裡用到了 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]))
複製程式碼
2、執行shell命令編譯專案
這裡使用了 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('編譯完成')
}
複製程式碼
3、上傳檔案
上傳檔案使用了 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 [目錄]
複製程式碼
4、原始碼
1、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('編譯完成')
}
複製程式碼
2、選項配置原始碼: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'
}
]
}
]
}
複製程式碼
3、伺服器配置原始碼:config.service.js
伺服器配置只配置了兩個作為演示,實際看現實情況補充。
在 GitHub
上拉取專案測試的時候,記得一定要修改次檔案。
- 修改伺服器為自己的伺服器和ssh賬號密碼
- 修改專案的本地目錄和遠端目錄
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
複製程式碼