node.js实现前端自动化部署项目到服务器的实现源码

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进行交流

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值