传统的手工部署需要经历:
1.打包,本地运行npm run build打包生成dist文件夹。
2.ssh连接服务器,切换路径到web对应目录下。
3.上传代码到web目录,一般通过xshell或者xftp完成。
使用node-ssh实现
node-ssh是一个基于ssh2的轻量级npm包,主要用于ssh连接服务器、上传文件、执行命令。
const { ssh}= require('node-ssh')
const ssh = new node_ssh()
用到的api:
1.ssh.connect:连接服务器
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:执行远端服务器命令
ssh.execCommand('hh_client --json', { cwd:'/var/www' }).then(function(result) {
console.log('STDOUT: ' + result.stdout)
console.log('STDERR: ' + result.stderr)
})
成果
部署过程
1. 打包代码
2. 压缩成zip
3. 连接服务器
4. 上传zip
5. 解压并删除zip文件
6. 删除本地zip包
const path = require('path')
const fs = require('fs')
const shell = require('shelljs') // 执行shell命令
const ora = require('ora') //loading
const zipFile = require('compressing')// 压缩zip
const { NodeSSH } = require('node-ssh') // ssh连接服务器
// 提示方法
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) 项目开始打包')
loading.start()
underlineLog(script)
const res = await shell.exec(script) //执行打包指令
loading.stop()
if (res.code == 0) {
successLog('打包成功')
resolve()
} else {
errorLog('项目打包失败!')
process.exit(1)//退出流程
}
} catch (err) {
errorLog(err)
process.exit(1)//退出流程
}
})
}
// 第二步 开始压缩zip
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)//退出流程
}
}
// 第三步 连接服务器 ssh
const connectSSH = async (config) => {
infoLog('(3) 连接服务器')
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)
}
}
// 第五步 开始解压
const unzipFile = async (webDir, distPath) => {
try {
infoLog(`(5) 开始解压zip包`)
await runCommand(`cd ${webDir}`, webDir)
await runCommand(`unzip -o ${distPath}.zip && rm -f ${distPath}.zip`, webDir)
successLog('解压成功')
SSH.dispose() //断开连接
} catch (err) {
errorLog(`zip包解压失败 ${err}`)
process.exit(1)
}
}
// 第六步 删除本地dist.zip
const deleteZip = async () => {
return new Promise((resolve, reject) => {
infoLog(`(6) 开始删除本地zip包`)
fs.unlink(distZipPath, err => {
if(err) {
console.log(err)
errorLog('删除zip失败')
process.exit(1)
}
successLog('删除成功')
resolve()
})
})
}
// 运行命令
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)}项目${underlineLog(name)}部署成功了^_^\n`)
process.exit(0)
} catch (err) {
errorLog(` 部署失败 ${err}`)
process.exit(1)
}
}
module.exports = deploy
脚手架实践
问题:
上面的方案已经可以完成一个项目的自动化部署,但是再有一个新的项目要接入自动化部署,是不是又得把整个文件拷贝过去,是不是非常麻烦?
因此可以将自动化部署做成一个脚手架fe-deploy-cli,支持生成部署配置模板、脚本部署,只需一条命令即可部署到对应环境中
1) 新建一个项目deploy-cli,并创建文件夹bin,文件加载有个deploy-cli.js
2) deploy-cli.js内容
#!/usr/bin/env node
// 件头部必须有 #!/usr/bin/env node 这么一行,意思是使用 node 进行脚本的解释程序,那下面的就可以使用 node 的语法了;
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)},请运行${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)}项目${underlineLog(name)}部署`)
deploy(config)
}
})
}
run(process.argv.slice(2))
3)package.json包中添加配置
"bin": {
"deploy": "./bin/deploy-cli.js"
},
4)将 deploy-cli 目录打成一个全局包
cmd中执行
npm install . -g
这里可能会存在权限问题,需要用管理员权限运行cmd
5) 之后就可以可以执行的指令了
最后
参考资料: