前端用nodejs写个自动部署

132 阅读2分钟

前言

因为公司项目比较多,环境分test、uat、pre、pro、每次发包都是喊我,刚开始用的webstorm上面的部署功能还是挺OK的。但是有时候突然几个项目一起发包,编辑器切来切去。。。然后还要自己手动拉代码且分支。。真的太繁琐了,但是我也忍了...后来后端让我每次发包的时候都要加个版本号,这个版本号每次发包还可能会变,也就是说我每次发包我可能要去改动代码,我摆了!@@#@%¥!@于是决定弄个一键部署,看网上都是推荐jenkins,但是我一看,我堂堂一个前端,让我装JAVA!!!不能忍,我要用node!有需要的可以自行魔改复制到自己项目中用,核心点就那几个,基本都能看懂,喜欢的点个赞噢

梳理流程

  1. 询问环境、询问版本号、询问打包的项目、配置node版本(nvm)
  2. 检测本地是否有该项目、拉取所有分支
  3. 根据环境是否与当前分支匹配、不匹配则切换分支重新拉取代码
  4. 安装依赖->写入版本号->打包
  5. 链接服务器->上传打包的项目->同时复制一份打包项目额外上传到服务器作为备份->关闭

需要用到的插件

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()