前端自動化:Node 命令行前端自動構建發佈系統

目前就我所呆的公司來講,前端的發版都是開發完以後執行編譯,而後經過 ftp 上傳到服務器中。項目多起來以後,加上測試環境和正式環境的分離,致使管理混亂。並且整個流程也很麻煩,要一步步手動去作。html

因此一直就有一個想法,能不能作一個像 Vue Cli 同樣的自動化工具,能夠經過命令輸入命令和選擇選項,進行自動化的編譯和發版。說幹就幹立馬就開發了一個 ~~~前端

GitHub 地址: https://github.com/zuley/zuley-clinode

1、技術棧

  • chalk 美化命令行,進行着色等
  • commander 解析用戶命令行輸入
  • inquirer 命令行交互功能,像用戶提問等。
  • node-ssh ssh 模塊
  • ora 命令行環境的 loading 效果
  • shelljs 從新包裝了 child_process 子進程模塊,調用系統命令更方便。

2、建立項目

  1. 直接使用 npm init 建立一個空項目
  2. 在根目錄建立一個不帶後綴的系統文件 app,做爲主入口文件。

3、簡單命令行示例

在入口文件中輸入如下代碼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
複製代碼

4、以全局方式運行

上面的示例輸入起來太麻煩,須要進入項目所在目錄才能執行文件,如今咱們須要一個簡單的方式 zuley m -n zuleyshell

一、配置 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
複製代碼

5、項目架構

實際開發中,咱們以 src爲源碼目錄,自動發佈系統作爲其中的一個模塊,放置在模塊目錄中。

|-/src/ // 源碼目錄
|---/modules/ 模塊目錄
|-----/Automation/ // 發佈模塊目錄
|-------index.js // 模塊入口文件
|-------config.promps.js  // 選項配置文件
|-------config.service.js // 服務器配置文件
|-app  // 入口文件
複製代碼

6、在入口文件 app 中載入自動發佈系統模塊

#! /usr/bin/env node

// 導入自動化任務模塊
require('./src/modules/Automation/index')
複製代碼

7、編寫模塊入口功能

編寫模塊入口文件 /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]))
複製代碼

二、執行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('編譯完成')
}
複製代碼

三、上傳文件

上傳文件使用了 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 上拉取項目測試的時候,記得必定要修改次文件。

  1. 修改服務器爲本身的服務器和ssh帳號密碼
  2. 修改項目的本地目錄和遠程目錄
  3. 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'
  }
}

複製代碼

8、執行自動化發佈

執行如下命令便可啓動自動化

$ zuley a
複製代碼

輸入有誤想終止命令可輸入

$ Ctrl+C
複製代碼

9、參考文章

GitHub 地址: https://github.com/zuley/zuley-cli

跟着老司機玩轉Node命令行

chalk:美化命令行,進行着色

commander:解析用戶命令行輸入

inquirer:命令行交互功能,像用戶提問等

node-ssh:ssh 模塊

ora:命令行環境的 loading 效果

shelljs:更方便的調用系統命令

相關文章
相關標籤/搜索