前端自動化部署伺服器, 告別繁瑣部署過程

阿波同學發表於2019-11-15

永恆的前言


簡單實用的前端部署, 一條命令搞定, 省去繁瑣的步驟!

主要是** nodejs shelljs(命令列命令) node-ssh(連線伺服器)**

專案git 地址

(這個git專案是我自己搭的一個比較low的vue腳手架,整合ts)

(第一次寫文章, 文筆不行, 多多包涵,有不對的地方儘管指出)

(主要看 自動部署 在 upload 目錄 )

如何在你專案中使用( 五步曲 )

第一步, 把專案裡 upload 資料夾複製到你專案根目錄

image.png

第二步, 下載該js相關依賴

npm 或 cnpm i chalk ora shelljs node-ssh inquirer compressing -D

第三步, 開啟 upload/config.js 配置檔案, 配置ssh地址, 使用者名稱, 驗證方式,需要上傳的目錄

image.png

第四步, 在你專案中 package.json 檔案中 加上 "deploy": "node ./upload/upload.js"

image.png

最後 在命令列輸入 npm run deploy 選擇釋出環境 然後就ojbk了

image.png
image.png
image.png
image.png

大功告成~~


來看主要檔案 upload.js

專案依賴

const chalk = require('chalk') //命令列顏色
const ora = require('ora') // 載入流程動畫
const spinner_style = require('./spinner_style') //載入動畫樣式
const shell = require('shelljs') // 執行shell命令
const node_ssh = require('node-ssh') // ssh連線伺服器
const inquirer = require('inquirer') //命令列互動
const zipFile = require('compressing')// 壓縮zip
const fs = require('fs') // nodejs內建檔案模組
const path = require('path') // nodejs內建路徑模組
const CONFIG = require('./config') // 配置
複製程式碼

一些常量,變數和logs

const SSH = new node_ssh();
let config; // 用於儲存 inquirer 命令列互動後選擇正式|測試版的配置

//logs
const defaultLog = log => console.log(chalk.blue(`---------------- ${log} ----------------`));
const errorLog = log => console.log(chalk.red(`---------------- ${log} ----------------`));
const successLog = log => console.log(chalk.green(`---------------- ${log} ----------------`));

//資料夾目錄
const distDir = path.resolve(__dirname, '../dist'); //待打包
const distZipPath = path.resolve(__dirname, `../dist.zip`);
//打包後地址(dist.zip是檔名,不需要更改, 主要在config中配置 PATH 即可)
複製程式碼

打包

首先 執行專案打包命令

//專案打包程式碼 npm run build 
const compileDist = async () => {
  const loading = ora( defaultLog('專案開始打包') ).start();
  loading.spinner = spinner_style.arrow4;
  shell.cd(path.resolve(__dirname, '../'));
  const res = await shell.exec('npm run build'); //執行shell 打包命令
  loading.stop();
  if(res.code === 0) {
    successLog('專案打包成功!');
  } else {
    errorLog('專案打包失敗, 請重試!');
    process.exit(); //退出流程
  }
}
複製程式碼

壓縮

然後 對打包的程式碼 /dist 目錄打包 (如果不是dist, 請更改上面的 disDir 常量結尾的dist)

//壓縮程式碼
const zipDist = async ()=>{
  defaultLog('專案開始壓縮');
  try {
    await zipFile.zip.compressDir(distDir, distZipPath)
    successLog('壓縮成功!');
  } catch (error) {
    errorLog(error);
    errorLog('壓縮失敗, 退出程式!');
    process.exit(); //退出流程
  }
}
複製程式碼

連線伺服器

再然後 通過ssh連線伺服器 有兩種方式: 一是通過祕鑰連線(推薦), 二是密碼連線 祕鑰連線需要把本機公鑰放伺服器指定目錄 (在upload/config.js 有說明)

image.png

//連線伺服器
const connectSSH = async ()=>{
  const loading = ora( defaultLog('正在連線伺服器') ).start();
  loading.spinner = spinner_style.arrow4;
  try {
    await SSH.connect({
      host: config.SERVER_PATH,
      username: config.SSH_USER,
      // privateKey: config.PRIVATE_KEY, //祕鑰登入(推薦) 方式一
      password: config.PASSWORD // 密碼登入 方式二
    });
    successLog('SSH連線成功!'); 
  } catch (error) {
    errorLog(error);
    errorLog('SSH連線失敗!');
    process.exit(); //退出流程
  }
  loading.stop();
}
複製程式碼

上傳檔案

緊接著 通過ssh執行線上命令 進行目標目錄清空, 然後上傳zip到伺服器 並解壓 等操作

//線上執行命令
/**
 * 
 * @param {String} command 命令操作 如 ls
 */
const runCommand = async (command)=> {
  const result = await SSH.exec(command, [], { cwd: config.PATH})
  // defaultLog(result);
}

//清空線上目標目錄裡的舊檔案
const clearOldFile = async () =>{
  const commands = ['ls', 'rm -rf *'];
  await Promise.all(commands.map(async (it)=>{
    return await runCommand(it);
  }));
}

//傳送zip檔案到伺服器
const uploadZipBySSH = async () =>{
  //連線ssh
  await connectSSH();
  //線上目標檔案清空
  await clearOldFile();
  const loading = ora( defaultLog('準備上傳檔案') ).start();
  loading.spinner = spinner_style.arrow4;
  try {
    await SSH.putFiles([{ local: distZipPath, remote: config.PATH + '/dist.zip' }]); //local 本地 ; remote 伺服器 ;
    successLog('上傳成功!'); 
    loading.text = '正在解壓檔案';
    await runCommand('unzip ./dist.zip'); //解壓
    await runCommand(`rm -rf ${config.PATH}/dist.zip`); //解壓完刪除線上壓縮包
    //將目標目錄的dist裡面檔案移出到目標檔案  
    //舉個例子 假如我們部署在 /test/html 這個目錄下 只有一個網站, 那麼上傳解壓後的檔案在 /test/html/dist 裡
    //需要將 dist 目錄下的檔案 移出到 /test/html ;  多網站情況, 如 /test/html/h5  或者 /test/html/admin 都和上面同樣道理
    await runCommand(`mv -f ${config.PATH}/dist/*  ${config.PATH}`); 
    await runCommand(`rm -rf ${config.PATH}/dist`); //移出後刪除 dist 資料夾
    SSH.dispose(); //斷開連線
  } catch (error) {
    errorLog(error);
    errorLog('上傳失敗!');
    process.exit(); //退出流程
  }
  loading.stop();
}
複製程式碼

整合

把這些整合在一個函式

//------------釋出程式---------------
const runUploadTask = async () => {
  console.log(chalk.yellow(`--------->  歡迎使用 波哥牌 2020年自動部署工具  <---------`));
  //打包
  await compileDist();
  //壓縮
  await zipDist();
  //連線伺服器上傳檔案
  await uploadZipBySSH(); 
  successLog('大吉大利, 部署成功!'); 
  process.exit();
}
複製程式碼

釋出前的檢查配置

// 開始前的配置檢查
/**
 * 
 * @param {Object} conf 配置物件
 */
const checkConfig = (conf) =>{
  const checkArr = Object.entries(conf);
  checkArr.map(it=>{
    const key = it[0];
    if(key === 'PATH' && conf[key] === '/') { //上傳zip前會清空目標目錄內所有檔案
      errorLog('PATH 不能是伺服器根目錄!'); 
      process.exit(); //退出流程
    }
    if(!conf[key]) {
      errorLog(`配置項 ${key} 不能為空`); 
      process.exit(); //退出流程
    }
  })
}
複製程式碼

釋出

執行互動 選擇釋出環境 然後啟動釋出程式

// 執行互動後 啟動釋出程式
inquirer
  .prompt([{
    type: 'list',
    message: '請選擇釋出環境',
    name: 'env',
    choices: [{
      name: '測試環境',
      value: 'development'
    },{
      name: '正式環境',
      value: 'production'
    }]
  }])
  .then(answers => {
    config = CONFIG[answers.env];
    checkConfig(config); // 檢查
    runUploadTask(); // 釋出
  });
複製程式碼

大功告成


結尾

咳咳, 放心, 不會有公眾號啥廣告, 也不會求打賞, 如果您覺得對您有一點點幫助 點個贊或者去GitHub點個star 那就非常感謝了 專案git 地址

相關文章