<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端工程化中的自動(dòng)化部署

          共 22869字,需瀏覽 46分鐘

           ·

          2020-10-07 20:50

          作者:bilibili

          來(lái)源:SegmentFault 思否社區(qū)



          前言


          在前端工程化中,前端開(kāi)發(fā)人員終于在不斷的提高自己的地位,再也不是簡(jiǎn)單的切圖仔了。當(dāng)然,隨之而來(lái)的就是我們的工作內(nèi)容變得越來(lái)越多,變得越來(lái)越繁瑣。不僅要不斷的學(xué)習(xí)新的前端技術(shù)內(nèi)容,而且還要獨(dú)立維護(hù)前端的部署工作。因此,如何能快速的進(jìn)行工程化內(nèi)容的部署,就是一件非常有價(jià)值的事情。



          快速部署


          對(duì)于前端的部署來(lái)說(shuō),其實(shí)主要就是將編譯后的工程化項(xiàng)目(以vue來(lái)說(shuō)就是將npm run build的dist文件夾)直接部署到對(duì)應(yīng)的服務(wù)器的指定目錄下即可,其他的內(nèi)容我們暫時(shí)不在此處做過(guò)多的講解。

          因此,目前一般會(huì)使用SFTP工具(如:FileZilla:https://www.filezilla.cn/,Cyberduck:https://cyberduck.io/)來(lái)上傳構(gòu)建好的前端代碼到服務(wù)器。這種方式雖然也是比較快的,但是每次要自己進(jìn)行打包編譯=》壓縮=》打開(kāi)對(duì)應(yīng)的工具=》找到對(duì)應(yīng)的路徑,手動(dòng)的將相關(guān)的文件上傳到對(duì)應(yīng)的服務(wù)中。并且為了部署到不同的環(huán)境中(一般項(xiàng)目開(kāi)發(fā)包括:dev開(kāi)發(fā)環(huán)境、test測(cè)試環(huán)境、uat測(cè)試環(huán)境、pro測(cè)試環(huán)境等)還要重復(fù)的進(jìn)行多次的重復(fù)性操作。這種方式不僅繁瑣,而且還降低了我們程序員的身份。所以我們需要用自動(dòng)化部署來(lái)代替手動(dòng) ,這樣一方面可以提高部署效率,另一方面開(kāi)發(fā)人員可以少做一些他們以為的無(wú)用功。



          準(zhǔn)備工作


          話不多說(shuō),說(shuō)干就干。首先,我們先梳理一下一般的部署流程,主要是先將本地的工程化代碼進(jìn)行打包編譯=>通過(guò)ssh登錄到服務(wù)器=>將本地編譯的代碼上傳到服務(wù)器指定的路徑中,具體流程如下:

          因此,通過(guò)代碼實(shí)現(xiàn)自動(dòng)化部署腳本,主要需要實(shí)現(xiàn)如下,下面以vue項(xiàng)目來(lái)為工程項(xiàng)目來(lái)具體講解:

          1. 實(shí)現(xiàn)本地代碼編譯;可直接配置npm run build即可進(jìn)行相關(guān)的打包
          2. 實(shí)現(xiàn)本地編譯的代碼壓縮
          3. 通過(guò)ssh連接遠(yuǎn)程服務(wù)器
          4. 檢查對(duì)應(yīng)的遠(yuǎn)程部署路徑是否存在,不存在需要?jiǎng)?chuàng)建
          5. 上傳本地的壓縮包
          6. 解壓上傳的壓縮包
          7. 刪除上傳的壓縮包
          8. 關(guān)閉ssh連接
          9. 刪除本地的壓縮包




          具體實(shí)現(xiàn)


          為了在能夠?qū)崿F(xiàn)以上的幾個(gè)步驟,我們需要引入一些依賴包,以此來(lái)進(jìn)行相關(guān)步驟的操作和日志的打印輸出

          • chalk:格式化輸出日志的插件,可以通過(guò)配置不同的顏色來(lái)顯示不同的日志打印。https://www.npmjs.com/package/chalk
          • child_process:nodejs的一個(gè)子進(jìn)程模塊,可以用來(lái)創(chuàng)建一個(gè)子進(jìn)程,并執(zhí)行一些任務(wù),可以直接在js里面調(diào)用shell命令。http://nodejs.cn/api/child_process.html
          • jszip:一個(gè)輕量級(jí)的zip壓縮庫(kù),用于壓縮編譯后的腳本。https://stuk.github.io/jszip/
          • ssh2:用于通過(guò)ssh來(lái)連接遠(yuǎn)程服務(wù)器的插件。https://github.com/mscdex/ssh2

          大致流程如下:




          基本配置


          為了能夠?qū)⒉渴鹣嚓P(guān)的內(nèi)容,如服務(wù)器信息,部署的路徑等內(nèi)容進(jìn)行統(tǒng)一的管理,以便進(jìn)行未來(lái)的可視化配置。因此,在項(xiàng)目中創(chuàng)建一個(gè)獨(dú)立的文件夾,統(tǒng)一管理部署相關(guān)的文件,并且建立一個(gè)config.js和ssh.js文件,分別用于配置相關(guān)的部署信息和具體的部署腳本。其中,config.js中的配置具體如下:

            const config = {  // 開(kāi)發(fā)環(huán)境  dev: {    host: '',    username: 'root',    password: '',    catalog: '', // 前端文件壓縮目錄    port: 22, // 服務(wù)器ssh連接端口號(hào)    privateKey: null // 私鑰,私鑰與密碼二選一  },  // 測(cè)試環(huán)境  test: {    host: '', // 服務(wù)器ip地址或域名    username: 'root', // ssh登錄用戶    password: '', // 密碼    catalog: '', // 前端文件壓縮目錄    port: 22, // 服務(wù)器ssh連接端口號(hào)    privateKey: null // 私鑰,私鑰與密碼二選一  },  // 線上環(huán)境  pro: {    host: '', // 服務(wù)器ip地址或域名    username: 'root', // ssh登錄用戶    password: '', // 密碼,請(qǐng)勿將此密碼上傳至git服務(wù)器    catalog: '', // 前端文件壓縮目錄    port: 22, // 服務(wù)器ssh連接端口號(hào)    privateKey: null // 私鑰,私鑰與密碼二選一  }}
          module.exports = { // publishEnv: pro, publishEnv: config.pro, // 發(fā)布環(huán)境 buildDist: 'dist', // 前端文件打包之后的目錄,默認(rèn)dist buildCommand: 'npm run build', // 打包前端文件的命令 readyTimeout: 20000, // ssh連接超時(shí)時(shí)間 deleteFile: true, // 是否刪除線上上傳的dist壓縮包 isNeedBuild: true // s是否需要打包}



          壓縮打包內(nèi)容


          壓縮打包的內(nèi)容,使用JSZIP插件,初始化一個(gè)const zip = new JSZIP(),然后在按照對(duì)應(yīng)的語(yǔ)法進(jìn)行具體的實(shí)現(xiàn),其實(shí)現(xiàn)過(guò)程主要是通過(guò)的先遞歸讀取相關(guān)的文件,然后加入到zip對(duì)象中,最后通過(guò)插件進(jìn)行具體的壓縮,最后寫(xiě)入到磁盤中。具體代碼如下:

          遞歸讀取打包文件 // 讀取文件  readDir (obj, nowPath) {    const files = fs.readdirSync(nowPath) // 讀取目錄中的所有文件及文件夾(同步操作)    files.forEach((fileName, index) => {      // 遍歷檢測(cè)目錄中的文件      // console.log(fileName, index) // 打印當(dāng)前讀取的文件名      const fillPath = nowPath + '/' + fileName      const file = fs.statSync(fillPath) // 獲取一個(gè)文件的屬性      if (file.isDirectory()) {        // 如果是目錄的話,繼續(xù)查詢        const dirlist = zip.folder(fileName) // 壓縮對(duì)象中生成該目錄        this.readDir(dirlist, fillPath) // 重新檢索目錄文件      } else {        obj.file(fileName, fs.readFileSync(fillPath)) // 壓縮目錄添加文件      }    })  }壓縮文件夾下的所有文件// 壓縮文件夾下的所有文件  zipFile (filePath) {    return new Promise((resolve, reject) => {      let desc =         '*******************************************\n' +         '***                正在壓縮              ***\n' +         '*******************************************\n'      console.log(chalk.blue(desc))      this.readDir(zip, filePath)      zip        .generateAsync({          // 設(shè)置壓縮格式,開(kāi)始打包          type: 'nodebuffer', // nodejs用          compression: 'DEFLATE', // 壓縮算法          compressionOptions: {            // 壓縮級(jí)別            level: 9          }        })        .then(content => {          fs.writeFileSync(            path.join(rootDir, '/', this.fileName),            content,            'utf-8'          )          desc =            '*******************************************\n' +            '***                壓縮成功              ***\n' +            '*******************************************\n'          console.log(chalk.green(desc))          resolve({            success: true          })        }).catch(err => {          console.log(chalk.red(err))          reject(err)        })    })  }使用child_process的exec來(lái)運(yùn)行npm run build腳本打包// 打包本地前端文件  buildProject () {    return new Promise((resolve, reject) => {      exec(Config.buildCommand, async (error, stdout, stderr) => {        if (error) {          console.error(error)          reject(error)        } else if (stdout) {          resolve({            stdout,            success: true          })        } else {          console.error(stderr)          reject(stderr)        }      })    })  }連接ssh服務(wù)器通過(guò)ssh2的client來(lái)創(chuàng)建ssh連接 // 連接服務(wù)器  connectServer () {    return new Promise((resolve, reject) => {      const conn = this.conn      conn        .on('ready', () => {          resolve({            success: true          })        })        .on('error', (err) => {          reject(err)        })        .on('end', () => {          const desc =            '*******************************************\n' +            '***           SSH連接已結(jié)束        ***\n' +            '*******************************************\n'          console.log(chalk.green(desc))        })        .on('close', () => {          const desc =            '*******************************************\n' +            '***           SSH連接已關(guān)閉        ***\n' +            '*******************************************\n'          console.log(chalk.green(desc))        })        .connect(this.server)    })  }上傳壓縮的工程文件判斷上傳路徑是否存在// 判斷文件是否存在,如果不存在則進(jìn)行創(chuàng)建文件夾  await sshCon.execSsh(    `    if [[ ! -d ${sshConfig.catalog} ]]; then     mkdir -p ${sshConfig.catalog}    fi    `  )通過(guò)client的sftp講本地的壓縮包上傳到指定的服務(wù)器的對(duì)應(yīng)地址 // 上傳文件  uploadFile ({ localPath, remotePath }) {    return new Promise((resolve, reject) => {      return this.conn.sftp((err, sftp) => {        if (err) {          reject(err)        } else {          sftp.fastPut(localPath, remotePath, (err, result) => {            if (err) {              reject(err)            }            resolve({              success: true,              result            })          })        }      })    })  }解壓上傳的工程文件通過(guò)exec來(lái)執(zhí)行ssh命令的  // 執(zhí)行ssh命令  execSsh (command) {    return new Promise((resolve, reject) => {      return this.conn.exec(command, (err, stream) => {        if (err || !stream) {          reject(err)        } else {          stream            .on('close', (code, signal) => {              resolve({                success: true              })            })            .on('data', function (data) {
          }) .stderr.on('data', function (data) { resolve({ success: false, error: data.toString() }) }) } }) }) }解壓上傳的壓縮文件desc = '*******************************************\n' + '*** 上傳文件成功,開(kāi)始解壓文件 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) const zipRes = await sshCon .execSsh( `unzip -o ${sshConfig.catalog + '/' + fileName} -d ${sshConfig.catalog}` ) .catch((e) => {}) if (!zipRes || !zipRes.success) { console.error('----解壓文件失敗,請(qǐng)手動(dòng)解壓zip文件----') console.error(`----錯(cuò)誤原因:${zipRes.error}----`) return false } else if (Config.deleteFile) { desc = '*******************************************\n' + '*** 解壓文件成功,開(kāi)始刪除上傳的壓縮包 ***\n' + '*******************************************\n' console.log(chalk.green(desc))刪除上傳的工程文件刪除對(duì)應(yīng)的壓縮包 desc = '*******************************************\n' + '*** 解壓文件成功,開(kāi)始刪除上傳的壓縮包 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) // 注意:rm -rf為危險(xiǎn)操作,請(qǐng)勿對(duì)此段代碼做其他非必須更改 const deleteZipRes = await sshCon .execSsh(`rm -rf ${sshConfig.catalog + '/' + fileName}`) .catch((e) => {}) if (!deleteZipRes || !deleteZipRes.success) { console.log(chalk.pink('----刪除文件失敗,請(qǐng)手動(dòng)刪除zip文件----')) console.log(chalk.red(`----錯(cuò)誤原因:${deleteZipRes.error}----`)) return false }關(guān)閉ssh連接封裝關(guān)閉的服務(wù)器連接 // 結(jié)束連接 endConn () { this.conn.end() if (this.connAgent) { this.connAgent.end() } }刪除本地的壓縮包文件封裝刪除本地的壓縮包 // 刪除本地文件 deleteLocalFile () { return new Promise((resolve, reject) => { fs.unlink(path.join(rootDir, '/', this.fileName), function (error) { if (error) { const desc = '*******************************************\n' + '*** 本地文件刪除失敗 ***\n' + '*******************************************\n' console.log(chalk.yellow(desc)) reject(error) } else { const desc = '*******************************************\n' + '*** 刪除成功 ***\n' + '*******************************************\n' console.log(chalk.blue(desc)) resolve({ success: true }) } }) }) }腳本命令的配置在項(xiàng)目的package.json中配置命令"ssh": "node ./build/ssh.js"完整的實(shí)現(xiàn)ssh流程// SSH連接,上傳,解壓,刪除等相關(guān)操作async function sshUpload (sshConfig, fileName) { const sshCon = new SSH(sshConfig) const sshRes = await sshCon.connectServer().catch((e) => { console.error(e) }) if (!sshRes || !sshRes.success) { const desc = '*******************************************\n' + '*** ssh連接失敗 ***\n' + '*******************************************\n' console.log(chalk.red(desc)) return false } let desc = '*******************************************\n' + '*** 連接服務(wù)器成功,開(kāi)始上傳文件 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) // 判斷文件是否存在,如果不存在則進(jìn)行創(chuàng)建文件夾 await sshCon.execSsh( ` if [[ ! -d ${sshConfig.catalog} ]]; then mkdir -p ${sshConfig.catalog} fi ` ) const uploadRes = await sshCon .uploadFile({ localPath: path.join(rootDir, '/', fileName), remotePath: sshConfig.catalog + '/' + fileName }) .catch((e) => { console.error(e) })
          if (!uploadRes || !uploadRes.success) { console.error('----上傳文件失敗,請(qǐng)重新上傳----') return false } desc = '*******************************************\n' + '*** 上傳文件成功,開(kāi)始解壓文件 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) const zipRes = await sshCon .execSsh( `unzip -o ${sshConfig.catalog + '/' + fileName} -d ${sshConfig.catalog}` ) .catch((e) => {}) if (!zipRes || !zipRes.success) { console.error('----解壓文件失敗,請(qǐng)手動(dòng)解壓zip文件----') console.error(`----錯(cuò)誤原因:${zipRes.error}----`) return false } else if (Config.deleteFile) { desc = '*******************************************\n' + '*** 解壓文件成功,開(kāi)始刪除上傳的壓縮包 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) // 注意:rm -rf為危險(xiǎn)操作,請(qǐng)勿對(duì)此段代碼做其他非必須更改 const deleteZipRes = await sshCon .execSsh(`rm -rf ${sshConfig.catalog + '/' + fileName}`) .catch((e) => {}) if (!deleteZipRes || !deleteZipRes.success) { console.log(chalk.pink('----刪除文件失敗,請(qǐng)手動(dòng)刪除zip文件----')) console.log(chalk.red(`----錯(cuò)誤原因:${deleteZipRes.error}----`)) return false } } // 結(jié)束ssh連接 sshCon.endConn() return true}實(shí)際運(yùn)行腳本// 執(zhí)行前端部署;(async () => { const file = new File() let desc = '*******************************************\n' + '*** 開(kāi)始編譯 ***\n' + '*******************************************\n' if (Config.isNeedBuild) { console.log(chalk.green(desc)) // 打包文件 const buildRes = await file .buildProject() .catch((e) => { console.error(e) }) if (!buildRes || !buildRes.success) { desc = '*******************************************\n' + '*** 打包出錯(cuò),請(qǐng)檢查錯(cuò)誤 ***\n' + '*******************************************\n' console.log(chalk.red(desc)) return false } console.log(chalk.blue(buildRes.stdout)) desc = '*******************************************\n' + '*** 編譯成功 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) } // 壓縮文件 const res = await file .zipFile(path.join(rootDir, '/', Config.buildDist)) .catch(() => {}) if (!res || !res.success) return false desc = '*******************************************\n' + '*** 開(kāi)始部署 ***\n' + '*******************************************\n' console.log(chalk.green(desc))
          const bol = await sshUpload(Config.publishEnv, file.fileName) if (bol) { desc = '\n******************************************\n' + '*** 部署成功 ***\n' + '******************************************\n' console.log(chalk.green(desc)) file.stopProgress() } else { process.exit(1) }})()
          完整的ssh.js代碼const { exec } = require('child_process')const path = require('path')const JSZIP = require('jszip')const fs = require('fs')const Client = require('ssh2').Clientconst Config = require('./config.js')const chalk = require('chalk')const zip = new JSZIP()// 前端打包文件的目錄const rootDir = path.resolve(__dirname, '..')
          /** * ssh連接 */class SSH { constructor ({ host, port, username, password, privateKey }) { this.server = { host, port, username, password, privateKey } this.conn = new Client() }
          // 連接服務(wù)器 connectServer () { return new Promise((resolve, reject) => { const conn = this.conn conn .on('ready', () => { resolve({ success: true }) }) .on('error', (err) => { reject(err) }) .on('end', () => { const desc = '*******************************************\n' + '*** SSH連接已結(jié)束 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) }) .on('close', () => { const desc = '*******************************************\n' + '*** SSH連接已關(guān)閉 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) }) .connect(this.server) }) }
          // 上傳文件 uploadFile ({ localPath, remotePath }) { return new Promise((resolve, reject) => { return this.conn.sftp((err, sftp) => { if (err) { reject(err) } else { sftp.fastPut(localPath, remotePath, (err, result) => { if (err) { reject(err) } resolve({ success: true, result }) }) } }) }) }
          // 執(zhí)行ssh命令 execSsh (command) { return new Promise((resolve, reject) => { return this.conn.exec(command, (err, stream) => { if (err || !stream) { reject(err) } else { stream .on('close', (code, signal) => { resolve({ success: true }) }) .on('data', function (data) {
          }) .stderr.on('data', function (data) { resolve({ success: false, error: data.toString() }) }) } }) }) }
          // 結(jié)束連接 endConn () { this.conn.end() if (this.connAgent) { this.connAgent.end() } }}
          /* * 本地操作 * */class File { constructor () { this.fileName = this.formateName() }
          // 刪除本地文件 deleteLocalFile () { return new Promise((resolve, reject) => { fs.unlink(path.join(rootDir, '/', this.fileName), function (error) { if (error) { const desc = '*******************************************\n' + '*** 本地文件刪除失敗 ***\n' + '*******************************************\n' console.log(chalk.yellow(desc)) reject(error) } else { const desc = '*******************************************\n' + '*** 刪除成功 ***\n' + '*******************************************\n' console.log(chalk.blue(desc)) resolve({ success: true }) } }) }) }
          // 讀取文件 readDir (obj, nowPath) { const files = fs.readdirSync(nowPath) // 讀取目錄中的所有文件及文件夾(同步操作) files.forEach((fileName, index) => { // 遍歷檢測(cè)目錄中的文件 // console.log(fileName, index) // 打印當(dāng)前讀取的文件名 const fillPath = nowPath + '/' + fileName const file = fs.statSync(fillPath) // 獲取一個(gè)文件的屬性 if (file.isDirectory()) { // 如果是目錄的話,繼續(xù)查詢 const dirlist = zip.folder(fileName) // 壓縮對(duì)象中生成該目錄 this.readDir(dirlist, fillPath) // 重新檢索目錄文件 } else { obj.file(fileName, fs.readFileSync(fillPath)) // 壓縮目錄添加文件 } }) }
          // 壓縮文件夾下的所有文件 zipFile (filePath) { return new Promise((resolve, reject) => { let desc = '*******************************************\n' + '*** 正在壓縮 ***\n' + '*******************************************\n' console.log(chalk.blue(desc)) this.readDir(zip, filePath) zip .generateAsync({ // 設(shè)置壓縮格式,開(kāi)始打包 type: 'nodebuffer', // nodejs用 compression: 'DEFLATE', // 壓縮算法 compressionOptions: { // 壓縮級(jí)別 level: 9 } }) .then(content => { fs.writeFileSync( path.join(rootDir, '/', this.fileName), content, 'utf-8') desc = '*******************************************\n' + '*** 壓縮成功 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) resolve({ success: true }) }).catch(err => { console.log(chalk.red(err)) reject(err) }) }) }
          // 打包本地前端文件 buildProject () { return new Promise((resolve, reject) => { exec(Config.buildCommand, async (error, stdout, stderr) => { if (error) { console.error(error) reject(error) } else if (stdout) { resolve({ stdout, success: true }) } else { console.error(stderr) reject(stderr) } }) }) }
          // 停止程序之前需刪除本地壓縮包文件 stopProgress () { this.deleteLocalFile() .catch((e) => { console.log(chalk.red('----刪除本地文件失敗,請(qǐng)手動(dòng)刪除----')) console.log(chalk.red(e)) process.exit(1) }) .then(() => { const desc = '*******************************************\n' + '*** 已刪除本地壓縮包文件 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) process.exitCode = 0 }) }
          // 格式化命名文件名稱 formateName () { // 壓縮包的名字 const date = new Date() const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() const timeStr = `${year}_${month}_${day}` return `${Config.buildDist}-${timeStr}-${Math.random() .toString(16) .slice(2)}.zip` }}
          // SSH連接,上傳,解壓,刪除等相關(guān)操作async function sshUpload (sshConfig, fileName) { const sshCon = new SSH(sshConfig) const sshRes = await sshCon.connectServer().catch((e) => { console.error(e) }) if (!sshRes || !sshRes.success) { const desc = '*******************************************\n' + '*** ssh連接失敗 ***\n' + '*******************************************\n' console.log(chalk.red(desc)) return false } let desc = '*******************************************\n' + '*** 連接服務(wù)器成功,開(kāi)始上傳文件 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) // 判斷文件是否存在,如果不存在則進(jìn)行創(chuàng)建文件夾 await sshCon.execSsh( ` if [[ ! -d ${sshConfig.catalog} ]]; then mkdir -p ${sshConfig.catalog} fi `) const uploadRes = await sshCon .uploadFile({ localPath: path.join(rootDir, '/', fileName), remotePath: sshConfig.catalog + '/' + fileName }) .catch((e) => { console.error(e) })
          if (!uploadRes || !uploadRes.success) { console.error('----上傳文件失敗,請(qǐng)重新上傳----') return false } desc = '*******************************************\n' + '*** 上傳文件成功,開(kāi)始解壓文件 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) const zipRes = await sshCon .execSsh( `unzip -o ${sshConfig.catalog + '/' + fileName} -d ${sshConfig.catalog}`) .catch((e) => {}) if (!zipRes || !zipRes.success) { console.error('----解壓文件失敗,請(qǐng)手動(dòng)解壓zip文件----') console.error(`----錯(cuò)誤原因:${zipRes.error}----`) return false } else if (Config.deleteFile) { desc = '*******************************************\n' + '*** 解壓文件成功,開(kāi)始刪除上傳的壓縮包 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) // 注意:rm -rf為危險(xiǎn)操作,請(qǐng)勿對(duì)此段代碼做其他非必須更改 const deleteZipRes = await sshCon .execSsh(`rm -rf ${sshConfig.catalog + '/' + fileName}`) .catch((e) => {}) if (!deleteZipRes || !deleteZipRes.success) { console.log(chalk.pink('----刪除文件失敗,請(qǐng)手動(dòng)刪除zip文件----')) console.log(chalk.red(`----錯(cuò)誤原因:${deleteZipRes.error}----`)) return false } } // 結(jié)束ssh連接 sshCon.endConn() return true}// 執(zhí)行前端部署;(async () => { const file = new File() let desc = '*******************************************\n' + '*** 開(kāi)始編譯 ***\n' + '*******************************************\n' if (Config.isNeedBuild) { console.log(chalk.green(desc)) // 打包文件 const buildRes = await file .buildProject() .catch((e) => { console.error(e) }) if (!buildRes || !buildRes.success) { desc = '*******************************************\n' + '*** 打包出錯(cuò),請(qǐng)檢查錯(cuò)誤 ***\n' + '*******************************************\n' console.log(chalk.red(desc)) return false } console.log(chalk.blue(buildRes.stdout)) desc = '*******************************************\n' + '*** 編譯成功 ***\n' + '*******************************************\n' console.log(chalk.green(desc)) } // 壓縮文件 const res = await file .zipFile(path.join(rootDir, '/', Config.buildDist)) .catch(() => {}) if (!res || !res.success) return false desc = '*******************************************\n' + '*** 開(kāi)始部署 ***\n' + '*******************************************\n' console.log(chalk.green(desc))
          const bol = await sshUpload(Config.publishEnv, file.fileName) if (bol) { desc = '\n******************************************\n' + '*** 部署成功 ***\n' + '******************************************\n' console.log(chalk.green(desc)) file.stopProgress() } else { process.exit(1) }})()



          點(diǎn)擊左下角閱讀原文,到?SegmentFault 思否社區(qū)?和文章作者展開(kāi)更多互動(dòng)和交流。

          -?END -

          瀏覽 29
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  97超碰人人 | 色丁香五月激情 | 黄色成人网站观看 | 欧美日韩激情在线一区二区三区 | 久久新|