前端自動(dòng)部署腳本cli開(kāi)發(fā)

傳統(tǒng)的手工部署需要經(jīng)歷:

1.打包,本地運(yùn)行npm run build打包生成dist文件夾。

2.ssh連接服務(wù)器,切換路徑到web對(duì)應(yīng)目錄下。

3.上傳代碼到web目錄,一般通過(guò)xshell或者xftp完成。

使用node-ssh實(shí)現(xiàn)

node-ssh是一個(gè)基于ssh2的輕量級(jí)npm包,主要用于ssh連接服務(wù)器、上傳文件、執(zhí)行命令。

const { ssh}= require('node-ssh')

const ssh = new node_ssh()


用到的api:

1.ssh.connect:連接服務(wù)器

ssh.connect({

? host: 'localhost',

? username: 'steel',

? privateKey: '/home/steel/.ssh/id_rsa'

})

2.ssh.putFile:上傳文件

ssh.putFile('/home/steel/Lab/localPath', '/home/steel/Lab/remotePath').then(function() {

? ? console.log("The File thing is done")

? }, function(error) {

? ? console.log("Something's wrong")

? ? console.log(error)

? })

3.ssh.execCommand:執(zhí)行遠(yuǎn)端服務(wù)器命令

ssh.execCommand('hh_client --json', { cwd:'/var/www' }).then(function(result) {

? ? console.log('STDOUT: ' + result.stdout)

? ? console.log('STDERR: ' + result.stderr)

? })


成果


部署過(guò)程

1. 打包代碼

2. 壓縮成zip

3. 連接服務(wù)器

4. 上傳zip

5. 解壓并刪除zip文件

6. 刪除本地zip包

const?path?=?require('path')

const?fs?=?require('fs')

const?shell?=?require('shelljs')?//?執(zhí)行shell命令

const?ora?=?require('ora')?//loading

const?zipFile?=?require('compressing')//?壓縮zip

const?{?NodeSSH?}?=?require('node-ssh')?//?ssh連接服務(wù)器

//?提示方法

const?{?successLog,?errorLog,?infoLog,?underlineLog?}?=?require('../utils/index')

//?變量

const?projectDir?=?process.cwd()

const?SSH?=?new?NodeSSH()

let?distDirPath,?distZipPath

//?第一步?打包代碼?build

const?execBuild?=?async?(script)?=>?{

??return?new?Promise(async?(resolve,?reject)?=>?{

????try?{

??????const?loading?=?ora('(1)?項(xiàng)目開(kāi)始打包')

??????loading.start()

??????underlineLog(script)

??????const?res?=?await?shell.exec(script)?//執(zhí)行打包指令

??????loading.stop()

??????if?(res.code?==?0)?{

????????successLog('打包成功')

????????resolve()

??????}?else?{

????????errorLog('項(xiàng)目打包失敗!')

????????process.exit(1)//退出流程

??????}

????}?catch?(err)?{

??????errorLog(err)

??????process.exit(1)//退出流程

????}

??})

}

//?第二步?開(kāi)始?jí)嚎szip

const?startZip?=?async?(distPath)?=>?{

??try?{

????infoLog('(2)?壓縮成zip')

????distDirPath?=?path.resolve(projectDir,?distPath)

????distZipPath?=?path.resolve(projectDir,?distPath?+?'.zip')

????await?zipFile.zip.compressDir(distDirPath,?distZipPath)

????successLog('壓縮成功')

??}?catch?(err)?{

????errorLog(err)

????process.exit(1)//退出流程

??}

}

//?第三步?連接服務(wù)器?ssh

const?connectSSH?=?async?(config)?=>?{

??infoLog('(3)?連接服務(wù)器')

??try?{

????await?SSH.connect({

??????host:?config.host,

??????port:?config.port,

??????username:?config.username,

??????password:?config.password,

??????privateKey:?config.privateKey,

??????passphrase:?config.passphrase

????})

????successLog('連接成功!')

??}?catch?(error)?{

????successLog('SSH連接失敗!')

????errorLog(err)

????process.exit(1)//退出流程

??}

}

//?第四步?打包代碼

const?uploadFile?=?async?(webDir,?distPath)?=>?{

??try?{

????infoLog(`(4)上傳zip至目錄${underlineLog(webDir)}`)

????await?SSH.putFile(distZipPath,?`${webDir}/${distPath}.zip`)

????successLog('上傳成功')

??}?catch?(err)?{

????errorLog(`zip包上傳失敗?${err}`)

????process.exit(1)

??}

}

//?第五步?開(kāi)始解壓

const?unzipFile?=?async?(webDir,?distPath)?=>?{

??try?{

????infoLog(`(5)?開(kāi)始解壓zip包`)

????await?runCommand(`cd?${webDir}`,?webDir)

????await?runCommand(`unzip?-o?${distPath}.zip?&&?rm?-f?${distPath}.zip`,?webDir)

????successLog('解壓成功')

????SSH.dispose()?//斷開(kāi)連接

??}?catch?(err)?{

????errorLog(`zip包解壓失敗?${err}`)

????process.exit(1)

??}

}

//?第六步?刪除本地dist.zip

const?deleteZip?=?async?()?=>?{

??return?new?Promise((resolve,?reject)?=>?{

????infoLog(`(6)?開(kāi)始刪除本地zip包`)

????fs.unlink(distZipPath,?err?=>?{

??????if(err)?{

????????console.log(err)

????????errorLog('刪除zip失敗')

????????process.exit(1)

??????}

??????successLog('刪除成功')

??????resolve()

????})

??})

}

//?運(yùn)行命令

async?function?runCommand(command,?webDir)?{

??await?SSH.execCommand(command,?{?cwd:?webDir?})

}

const?deploy?=?async?(config)?=>?{

??let?{?projectName,?name,?script,?distPath,?webDir?}?=?config

??try{

????if?(!script)?script?=?'npm?run?build'

????await?execBuild(script)

????if?(!distPath)?distPath?=?'dist'

????await?startZip(distPath)

????await?connectSSH(config)

????await?uploadFile(webDir,?distPath)

????await?unzipFile(webDir,?distPath)

????await?deleteZip()

????successLog(`\n?恭喜您,${underlineLog(projectName)}項(xiàng)目${underlineLog(name)}部署成功了^_^\n`)

????process.exit(0)

??}?catch?(err)?{

????errorLog(`??部署失敗?${err}`)

????process.exit(1)

??}

}

module.exports?=?deploy


腳手架實(shí)踐

問(wèn)題:

上面的方案已經(jīng)可以完成一個(gè)項(xiàng)目的自動(dòng)化部署,但是再有一個(gè)新的項(xiàng)目要接入自動(dòng)化部署,是不是又得把整個(gè)文件拷貝過(guò)去,是不是非常麻煩?

因此可以將自動(dòng)化部署做成一個(gè)腳手架fe-deploy-cli,支持生成部署配置模板、腳本部署,只需一條命令即可部署到對(duì)應(yīng)環(huán)境中

查閱使用 npm 制作命令行腳本工具

1) 新建一個(gè)項(xiàng)目deploy-cli,并創(chuàng)建文件夾bin,文件加載有個(gè)deploy-cli.js



2) deploy-cli.js內(nèi)容

#!/usr/bin/env?node

//?件頭部必須有?#!/usr/bin/env?node?這么一行,意思是使用?node?進(jìn)行腳本的解釋程序,那下面的就可以使用?node?的語(yǔ)法了;

const?packageJson?=?require('../package.json');

const?fs?=?require('fs')

//?const?path?=?require('path')

const?{?checkDeployConfig,?underlineLog,?infoLog?}?=?require('../utils/index');

const?deploy?=?require('../lib/deploy');

const?deployInit?=?require('../lib/init');

const?deployPath?=?process.cwd()

const?deployConfigPath?=?`${deployPath}/deploy.config.js`;

const?version?=?packageJson.version

//?run

function??run(argv)?{

??if?(argv[0]?===?'-v'?||?argv[0]?===?'--version')?{

????console.log(`??version?is?${version}`);

??}?else?if?(argv[0]?===?'-h'?||?argv[0]?===?'--help')?{

????console.log('??usage:\n');

????console.log('??-v?--version?[show?version]\n');

????console.log('??init??生成配置文件deploy.config.js');

??}?else?if?(argv[0]?===?'init')?{

????deployInit(deployConfigPath)

??}?else?{

????//?判斷配置文件是否存在

????if?(fs.existsSync(deployConfigPath))?{

??????deployMian(argv);

????}?else?{

??????infoLog(`缺少部署文件${underlineLog(deploy.confog.js)},請(qǐng)運(yùn)行${underlineLog('deploy?init')}下載部署配置`);

????}

??}

}

async?function?deployMian(argv)?{

??const?deployConfigs?=?checkDeployConfig(deployConfigPath)

??deployConfigs.forEach(config?=>?{

????const?{?projectName,?name,?command?}?=?config

????if(command?===?argv[0])?{

??????console.log(`${underlineLog(projectName)}項(xiàng)目${underlineLog(name)}部署`)

??????deploy(config)

????}

??})

}

run(process.argv.slice(2))

3)package.json包中添加配置

"bin":?{

????"deploy":?"./bin/deploy-cli.js"

??},

4)將 deploy-cli 目錄打成一個(gè)全局包
cmd中執(zhí)行?

npm install . -g

這里可能會(huì)存在權(quán)限問(wèn)題,需要用管理員權(quán)限運(yùn)行cmd

5) 之后就可以可以執(zhí)行的指令了



最后

成果z-deploy-cli

參考資料:

使用 npm 制作命令行腳本工具

前端自動(dòng)化部署項(xiàng)目到服務(wù)器 -- 一行命令搞定,摒棄傳統(tǒng)的手工部署 npm run build

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容