前言
因为公司项目比较多,环境分test、uat、pre、pro、每次发包都是喊我,刚开始用的webstorm上面的部署功能还是挺OK的。但是有时候突然几个项目一起发包,编辑器切来切去。。。然后还要自己手动拉代码且分支。。真的太繁琐了,但是我也忍了...后来后端让我每次发包的时候都要加个版本号,这个版本号每次发包还可能会变,也就是说我每次发包我可能要去改动代码,我摆了!@@#@%¥!@于是决定弄个一键部署,看网上都是推荐jenkins,但是我一看,我堂堂一个前端,让我装JAVA!!!不能忍,我要用node!有需要的可以自行魔改复制到自己项目中用,核心点就那几个,基本都能看懂,喜欢的点个赞噢
梳理流程
- 询问环境、询问版本号、询问打包的项目、配置node版本(nvm)
- 检测本地是否有该项目、拉取所有分支
- 根据环境是否与当前分支匹配、不匹配则切换分支重新拉取代码
- 安装依赖->写入版本号->打包
- 链接服务器->上传打包的项目->同时复制一份打包项目额外上传到服务器作为备份->关闭
需要用到的插件
inquirer(命令交互)
execa(执行cmd命令)
ora(loading动画)
ssh2-sftp-client(连接服务器)
代码
#!/usr/bin/env node
import * as fs from "fs";
import pkg from 'inquirer'
import {execa} from "execa";
import ora from "ora";
import ssh from 'ssh2-sftp-client'
import * as path from "path";
const __dirname = path.resolve();
const sshObj = {
dev: {
path: '/data/static/store-shop', // 你的项目路径
login: {
username: '服务器名字',
password: '服务器密码',
host: '主机IP',
port: '端口号'
}
},
uat: {
path: '/data/static/store-shop',// 你的项目路径
login: {
username: '服务器名字',
password: '服务器密码',
host: '主机IP',
port: '端口号'
}
},
pre: {
path: '/data/static/store-shop',// 你的项目路径
login: {
username: '服务器名字',
password: '服务器密码',
host: '主机IP',
port: '端口号'
}
},
pro: {
path: '/data/static/store-shop',// 你的项目路径
login: {
username: '服务器名字',
password: '服务器密码',
host: '主机IP',
port: '端口号'
}
}
}
// 获取当天时间
function getYearMonth(){
const date = new Date();
const year = date.getFullYear();//年
let month = date.getMonth() + 1;//月
let strDate = date.getDate();//日
if (month >= 1 && month <= 9) { month = "0" + month;}
if (strDate >= 0 && strDate <= 9) {strDate = "0" + strDate;}
return year+'-'+month+'-'+strDate
}
const environment = [
{
"name": 'project', // 选项名称
"message": "请选择项目名称", // 选项提示语
"pageSize": 100,
"type": "list", // 选项类型 另外还有 confirm list 等
"choices": [ // 具体的选项
{
"name": "项目名字(实例)",
"value": {
label: 'kxb-ui-erp-storeshop(实例)你的目录名字,建议和上面同名',
value: 'https://你的仓库地址.git',
nodeVersion: '16.15.1', // node版本
versionPath: '/src/config/version.ts', // 写入版本号,可以不需要这个
}
},
]
},
{
type: 'input',
message: '请输入需要打包的版本号:',
name: 'version',
default: "1.1.0" // 默认值
},
{
"name": 'envType', // 选项名称
"message": "请选择环境类型", // 选项提示语
"pageSize": 100,
"type": "list", // 选项类型 另外还有 confirm list 等
"choices": [ // 具体的选项
{
"name": "dev 测试环境",
"value": {
fileName: 'dist-dev',
env: 'dev',
branch: 'develop'
},
},
{
"name": "uat 产品验收环境",
"value": {
fileName: 'dist-uat',
env: 'uat',
branch: 'master'
}
},
{
"name": "pre 预生产环境",
"value": {
fileName: 'dist-pre',
env: 'pre',
branch: 'release'
}
},
{
"name": "pro 生产环境",
"value": {
fileName: 'dist-pro',
env: 'pro',
branch: 'release'
}
},
]
}
]
function envFn() {
return pkg.prompt(environment).then(data => uploadFn(data))
}
async function uploadFn({version, envType, project}) {
const isExists = fs.existsSync(project.label)
const loading = ora('Loading unicorns');
loading.text = '正在拉取代码。。。';
loading.color = 'green';
loading.start();
if (!isExists) {
loading.text = '正在获取仓库代码。。。';
loading.start();
await execa('git', ['clone', project.value]).finally(() => {
loading.stop()
})
}
loading.text = '检测到已有仓库代码,正在获取所有的分支。。。';
loading.start();
await execa('git', ['fetch'], {cwd: `./${project.label}`}).finally(() => {
loading.stop()
})
loading.text = '正在查询当前分支。。。';
loading.start();
const isBranch = await execa('git', ['branch'], {cwd: `./${project.label}`})
.then(data => data.stdout === `* ${envType.branch}`)
if (isBranch) {
loading.text = '正在拉取最新仓库代码。。。';
loading.start();
await execa('git', ['pull'], {cwd: `./${project.label}`}).finally(() => {
loading.stop()
})
} else {
loading.text = `正在切换${envType.branch}分支。。。`;
loading.start();
await execa('git', ['checkout', envType.branch], {cwd: `./${project.label}`}).finally(() => {
loading.stop()
})
loading.text = `正在更新${envType.branch}分支仓库代码。。。`;
loading.start();
await execa('git', ['pull'], {cwd: `./${project.label}`}).finally(() => {
loading.stop()
})
}
loading.stop()
await execa('nvm', ['use', project.nodeVersion], {cwd: `./${project.label}`})
loading.text = '安装依赖。。。'
loading.start();
await execa('npm', ['install'], {cwd: `./${project.label}`}).finally(() => {
loading.stop()
})
// 写入版本号
const writePath = path.resolve(__dirname, `${project.label}${project.versionPath}`)
const isPath = fs.existsSync(writePath)
isPath && fs.unlink(writePath, ()=>{
console.log('删除version文件并写入')
})
fs.writeFile(
writePath,
`export const version = '${version}'`,
{flag: 'as+'},
()=> {}
)
loading.text = '写入版本号并执行打包。。。'
loading.start();
await execa('npm', ['run', `build:${envType.env}`], {cwd: `./${project.label}`}).finally(() => {
loading.stop()
})
loading.text = '复制一份备份文件。。。'
loading.start();
await copyDir(`${project.label}/${envType.fileName}`, `${project.label}/${envType.fileName}[${getYearMonth()}]`)
loading.text = '连接服务器。。。'
loading.start();
const sftp = new ssh()
await sftp.connect(sshObj[envType.env].login)
.then(() => {
console.log('上传文件中...')
return sftp.uploadDir(path.resolve(__dirname, `${project.label}/${envType.fileName}`), sshObj[envType.env].path + '/dist')
})
.then(() => {
console.log('上传备份文件中...')
return sftp.uploadDir(path.resolve(__dirname, `${project.label}/${envType.fileName}`), sshObj[envType.env].path + `/dist[${getYearMonth()}]`)
})
.then(() => {
console.log('上传完毕...')
})
.finally(() => {
console.log('关闭服务器...')
loading.stop()
sftp.end()
})
}
envFn()