2024/01/16 修改:
新版本1.1.0已发布,解决了macos系统路径异常导致找不到envConfig.js文件的问题
2023/11/09 修改:
npm包上传完成,可以通过仓库地址查看ahd-deploy - npm (npmjs.com)
gitee
https://gitee.com/legend-of-dongge/deploy.git
下载依赖
npm i ahd-deploy -D
按照自己的实际情况去填写 每个配置项都必填
在 根文件夹 创建一个名为 “envConfig.js” 的文件,内容为
exports.envSetting = [
{
envName: '测试环境',
maxBuffer: 5000 * 1024,
buildCommand: 'npm run build',//打包命令
buildFileName: 'dist',//打包之后的文件夹名称
remoteFileRoot: '/remote/show/wms/',//远程文件根目录 地址栏地址
remoteFileName: 'wms-ui',//远程文件夹名称
backFileRoot: '/remote/show/wms-backup/',//远程备份文件根目录 地址栏地址
configServer: {
host: '12.222.34.53',//登录地址
username: 'root',//用户名
port: 22,//端口
password: 'root',//登录密码
privateKeyPath: '/home/steel/.ssh/id_rsa'
}
},
{
envName: '正式环境',
maxBuffer: 5000 * 1024,
buildCommand: 'npm run build',//打包命令
buildFileName: 'dist',//打包之后的文件夹名称
remoteFileRoot: '/remote/show/wms/',//远程文件根目录 地址栏地址
remoteFileName: 'wms-ui',//远程文件夹名称
backFileRoot: '/remote/show/wms-backup/',//远程备份文件根目录 地址栏地址
configServer: {
host: '12.222.34.53',//登录地址
username: 'root',//用户名
port: 22,//端口
password: 'root',//登录密码
privateKeyPath: '/home/steel/.ssh/id_rsa'
}
}
]
这里只配置了两个环境,如果需要更多环境在数组中添加即可
在package.json 的 script 中添加
"deploy": "node ./node_modules/ahd-deploy",
如:
"scripts": {
"serve": "vue-cli-service serve",
"build:": "vue-cli-service build",
"deploy": "node ./node_modules/ahd-deploy",
},
最后运行 npm run deploy 执行部署
npm run deploy
———————————————————————————————————————————
代码内容及释义
电脑需要安装node.js 下载地址
基于node.js 一定要有一点nodejs基础
修改主代码中的configGlobal即可,先上源码
文件夹目录:
app.js------------------------主代码
util.js-------------------------美化控制台输出样式
package.json--------------依赖
app.js
const fs = require('fs');//引入模块
const path = require('path')//引入模块
const inquirer = require('inquirer');// 询问消息npm install --save inquirer@^8.0.0
const ora = require('ora');// 加载动画ora6.0以上版本不支持require方式引入,如果在node中使用,需要使用5.0版本
const childProcess = require('child_process');//windows cmd 模块
var targz = require('tar.gz');//windows 压缩格式为tar.gz模块
const { NodeSSH } = require('node-ssh');//链接ssh模块
const ssh = new NodeSSH();//创建实例ssh
const util = require('./util');//引入样式
try{
var { envSetting } = require(path.join(process.cwd()) + path.sep +'envConfig');//引入环境变量
}catch(e){
util.red('引入配置文件'+path.join(process.cwd()) + path.sep +'envConfig.js'+'报错,请查看是否手动配置该文件,详情见:https://www.npmjs.com/package/ahd-deploy');
return;
}
var fileNameSave = '';// 声明服务器备份文件夹名称
var configGlobal;// 声明环境对象在选择发布环境时赋值
/********************封装方法********************/
// 询问框
const confirmDeploy = (message) => {
return inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message
}
])
};
/********************封装方法********************/
// 执行打包命令
async function runBuild() {
const spinner = ora('正在打包中\n');
spinner.start();
await childProcess.exec(
configGlobal.buildCommand,
{ cwd: `${path.join(process.cwd())+ path.sep}`, maxBuffer: configGlobal.maxBuffer },
(e) => {
spinner.stop();
if (e === null) {
util.green('正在打包-成功');
// del/f/s/q C:\xunlei\xldl.dll 删除文件
buildZarGz();
} else {
if (e.message.includes('Command failed:')) {
util.red('打包命令错误');
} else {
util.red(e.message);
}
}
}
)
};
// 压缩文件为tar.gz
async function buildZarGz() {
// 创建我们需要的所有流
var read = targz().createReadStream(`${path.join(process.cwd())+ path.sep}` + configGlobal.buildFileName);
var write = fs.createWriteStream(`${path.join(process.cwd())+ path.sep}` + configGlobal.buildFileName + '.tar.gz');
read.pipe(write);
read.on('close', () => {
putServe()
})
};
// 链接到服务器
async function putServe() {
const spinner = ora('登录服务器中...\n');
spinner.start();
ssh.connect(configGlobal.configServer)
.then(() => {
spinner.stop();
util.green('登录服务器-成功');
remoteToBeTarGz()
}).catch((err) => {
spinner.stop();
util.red('SSH登录失败:\n', err);
});
};
// 压缩远程文件
async function remoteToBeTarGz() {
const spinner = ora('压缩远程文件中\n');
spinner.start();
await ssh.execCommand(
`tar -zcvf ${configGlobal.remoteFileRoot}backdist.tar.gz ${configGlobal.remoteFileRoot + configGlobal.remoteFileName}`
).then(function () {
spinner.stop();
// util.green('压缩远程文件-成功')
createFile1()
})
};
// 创建文件夹
async function createFile1() {
fileNameSave = configGlobal.remoteFileName + util.NowDate()
await ssh.execCommand(
`mkdir ${configGlobal.backFileRoot}` + fileNameSave
).then(function () {
util.green('创建备份文件夹-成功')
remoteRemoveBackFile()
})
};
// 解压远程文件到备份目录
async function remoteRemoveBackFile() {
let num = (configGlobal.remoteFileRoot + configGlobal.remoteFileName).split('/').length - 1
await ssh.execCommand(
`tar -zxvf ${configGlobal.remoteFileRoot}backdist.tar.gz --strip-components ${num} -C ${configGlobal.backFileRoot}` + fileNameSave
).then(function () {
util.green('备份文件-成功')
remoteDeleteFile()
})
};
// 删除远程文件
async function remoteDeleteFile() {
await ssh.execCommand(
`rm -rf ${configGlobal.remoteFileRoot + configGlobal.remoteFileName}`
).then(function () {
remoteDeleteFileTarGz()
})
};
// 删除远程文件压缩包
async function remoteDeleteFileTarGz() {
await ssh.execCommand(
`rm -f ${configGlobal.remoteFileRoot}backdist.tar.gz`
).then(function () {
createFile()
})
};
// 创建文件夹
async function createFile() {
await ssh.execCommand(
`mkdir ${configGlobal.remoteFileRoot + configGlobal.remoteFileName}`
).then(function () {
fileGoToServer()
})
};
// 上传文件到服务器
async function fileGoToServer() {
const spinner = ora('本地压缩文件上传到服务器\n');
spinner.start();
await ssh.putFiles([{ local: `${path.join(process.cwd())+ path.sep}` + configGlobal.buildFileName + '.tar.gz', remote: configGlobal.remoteFileRoot + configGlobal.buildFileName + '.tar.gz' }]).then(function () {
spinner.stop();
util.green("本地压缩文件上传到服务器-成功")
tarRemoteFile()
}, function (error) {
spinner.stop();
util.red("文件上传失败,错误信息:\n", error)
})
};
// 解压远程文件
async function tarRemoteFile() {
try {
const spinner = ora('解压到' + configGlobal.remoteFileName + '文件夹\n');
spinner.start();
await ssh.execCommand(
`tar -zxvf ${configGlobal.remoteFileRoot + configGlobal.buildFileName + '.tar.gz'} --strip-components 1 -C ${configGlobal.remoteFileRoot + configGlobal.remoteFileName}`
).then(function () {
spinner.stop();
util.green('解压到' + configGlobal.remoteFileName + '文件夹-成功')
remoteDeleteFileTarGz2()
})
} catch (e) {
console.log(e)
}
};
// 删除远程文件压缩包
async function remoteDeleteFileTarGz2() {
await ssh.execCommand(
`rm -f ${configGlobal.remoteFileRoot}dist.tar.gz`
).then(function () {
util.green('删除上传的压缩包-成功')
deleteLocalFile()
})
};
// 删除本地文件压缩包
function deleteLocalFile() {
fs.unlinkSync(`${path.join(process.cwd())+ path.sep}` + configGlobal.buildFileName + '.tar.gz', (error) => {
if (error) {
util.red(error);
util.red('本地打包文件删除失败,请手动删除');
} else {
util.green('删除本地的压缩包-成功')
}
});
util.green('断开ssh连接')
ssh.dispose()
console.timeEnd('总耗时');
// 结束node进程
process.exit();
};
//启动 自执行函数
(async function () {
// 环境配置校验
if (envSetting.length === 0) {
return util.red('请编辑环境配置在进行部署')
} else if (envSetting.length > 1) {
for (var i = 1; i < envSetting.length; i++) {
if (envSetting[0].envName === envSetting[i].envName) {
return util.red('环境名称重复 ' + path.join(process.cwd()) + path.sep +'envConfig.js (envName属性重复)')
}
}
}
// 打印提示
util.red('************************************************************************************************************')
util.red(String.raw`
__ __ __ ___
/\ \ /\ \ /\ \ /\_ \
__ \ \ \___ \_\ \ \_\ \ __ _____\//\ \ ___ __ __
/'__.\ \ \ _ .\ /'_. \ _______ /'_. \ /'__.\/\ '__.\\ \ \ / __.\/\ \/\ \
/\ \L\.\_\ \ \ \ \/\ \L\ \/\______\/\ \L\ \/\ __/\ \ \L\ \\_\ \_/\ \L\ \ \ \_\ \
\ \__/.\_\\ \_\ \_\ \___,_\/______/\ \___,_\ \____\\ \ ,__//\____\ \____/\/.____ \
\/__/\/_/ \/_/\/_/\/__,_ / \/__,_ /\/____/ \ \ \/ \/____/\/___/ ./___/> \
\ \_\ /\___/
\/_/ \/__/
`);
util.red('************************************************************************************************************')
util.purple('注意:')
util.purple('仅支持linux系统登录部署\n')
util.blue('流程:')
util.blue('本地打包➡ 本地生成压缩包➡ 登录服务器➡ 压缩远程文件➡ 将压缩的文件解压到备份文件夹➡ 删除压缩远程文件及远程文件夹 \n➡ 创建新的远程文件夹➡ 本地压缩包上传服务器➡ 解压到远程文件夹➡ 删除上传的远程压缩包➡ 删除本地压缩包➡ 完成')
util.red('************************************************************************************************************')
const answers = await confirmDeploy(
`是否开始部署项目?`
)
if (answers.confirm) {
var choices = envSetting.map(item => {
return item.envName
})
inquirer.prompt([
{
type: "list",
message: "请选择发布环境:",
name: "environment",
default: "",
// 前缀
prefix: "☆",
// 后缀
suffix: "",
choices
}
]).then(res => {
configGlobal = envSetting[choices.indexOf(res.environment)]
util.green(res.environment + ' 打包开始:' + configGlobal.buildCommand);
console.time('总耗时');
runBuild()
});
} else {
util.red('已取消部署')
}
})();
util.js
exports.underLine = (value) => {
console.log(`\u001b[21m${value}\u001b[0m`);
}
exports.gray = (value) => {
console.log(`\u001b[30m${value}\u001b[0m`);
}
exports.red = (value) => {
console.log(`\u001b[31m${value}\u001b[0m`);
}
exports.green = (value) => {
console.log(`\u001b[32m${value}\u001b[0m`);
}
exports.yellow = (value) => {
console.log(`\u001b[33m${value}\u001b[0m`);
}
exports.blue = (value) => {
console.log(`\u001b[34m${value}\u001b[0m`);
}
exports.purple = (value) => {
console.log(`\u001b[35m${value}\u001b[0m`);
}
exports.blueSky = (value) => {
console.log(`\u001b[36m${value}\u001b[0m`);
}
exports.white = (value) => {
console.log(`\u001b[37m${value}\u001b[0m`);
}
exports.NowDate = () => {
// 获取年月日
let now = new Date();
let year = now.getFullYear(); //获取完整的年份(4位,1970-????)
let month = now.getMonth() + 1; //获取当前月份(0-11,0代表1月)
let today = now.getDate(); //获取当前日(1-31)
let hour = now.getHours(); //获取当前小时数(0-23)
let minute = now.getMinutes(); //获取当前分钟数(0-59)
let second = now.getSeconds(); //获取当前秒数(0-59)
let nowTime = "";
nowTime =
year +
fillZero(month) +
fillZero(today) + '-' +
fillZero(hour) +
fillZero(minute) +
fillZero(second);
return nowTime;
}
//日期补零
function fillZero(str) {
var realNum;
if (str < 10) {
realNum = "0" + str;
} else {
realNum = '' + str;
}
return realNum;
}
package.json
{
"scripts": {
"start": "node ./app.js"
},
"dependencies": {
"archiver": "^5.0.2",
"inquirer": "^8.0.0",
"node-ssh": "^11.0.0",
"ora": "^5.4.1",
"tar.gz": "^1.0.7"
}
}
开发思路参考了 前端自动化部署项目到服务器(deploy-cli-service)
之前作者的解压方式并不适用于我这边的开发方式 ,作者运用了unzip进行解压 ,我这边是内网开发,服务器也是不联网的,我改变了linux自带的解压工具能解压的方式tar.gz压缩包,再配合使用一些linux指令,进行文件的自动备份和上传
初写自动化发布,很多地方都是现学现卖,有不完善的地方欢迎指出。也可以进入主页加qq进行交流