50+行代码搞定一行命令更新Npm包

2,180 阅读2分钟

前言

Github Actions实现仓库自动同步Gitee并更新文档 - 掘金 (juejin.cn)文章前言中描述了笔者开源一个工具库。在持续更新库的时候,发现每一次更新medash - npm (npmjs.com)版本时,存在需要手动Git Push、执行rollup打包命令以及更新package.json文件中version字段值等操作。为解放双手,写了50+行代码。

实现终端交互选择版本号

版本号修改只考虑四种情况:

  1. 更新主版本号, 如1.0.0修改成2.0.0
  2. 更新子版本号,如1.0.0修改成1.1.0
  3. 阶段版本号,如1.0.0修改成1.0.1
  4. Beta版本,如1.0.0修改成1.0.0-beta.1

版本号的命名规则是由. 进行拼接,所以可以使用正则将版本信息获取出来。

首先,版本号信息存储在package.jsonversion 字段值中,代码是使用了TypeScript中文网 · TypeScript——JavaScript的超集 (tslang.cn)。Ts执行时,es6模块化会被编译成node环境中支持的CommonJS模块化。即import pkg from "../package.json"; 会被转化成const pkg = require("../package.json") 。 CommonJS标准规定,导入Json文件时,将会有把Json反序列化的操作,所以读取的json 已经被反序列化,不需要再JSON.parse 操作。

import pkg from "../package.json";
const version = pkg.version;
const reg = /([1-9])\.([0-9])\.([0-9])(?:(\-\w*)\.([1-9]+))?/g;
const execs = reg.exec(version) as Array<any>;

然后,npm install inquirer 安装inquirer ,它是一个提供终端交互的工具包。

按照上述版本修改的四种情况,程序提供的版本号选择列表选择可以分成两种情况:

  1. 版本号含有beta;
  2. 版本号没有含有beta。

是否含有beta,通过对execs[4] 是否存在来判断。

//...
const onSelectVersion = async () => {
    const beta = execs[4];
    //处理版本含有beta的情况
    const lists = beta ? getBetaVersionLists(beta) : getVersionlists();
    inquirer.prompt([{
        name: 'list',
        type: 'list',
        message: '请选择发布的版本:',
        choices: lists,
        default: [lists[0]]
    }])
    //...
}

处理含有beta的情况

const getBetaVersionLists = (beta) => ([
    getVersion([execs[1], execs[2], execs[3]]),
    getVersion([execs[1], execs[2], execs[3]]) + `${beta}.${addOne(execs[5])}`
])

处理没有beta的情况

const getVersionlists = () => ([
    getVersion([addOne(execs[1]), execs[2], execs[3]]),
    getVersion([execs[1], addOne(execs[2]), execs[3]]),
    getVersion([execs[1], execs[2], addOne(execs[3])])
])

其中getVersionaddOne 方法分别用于拼接完整的版本号和数字+1

const addOne = (num) => Number(num) + 1;
const getVersion = ([major, minor, patch]) => `v${major}.${minor}.${patch}`

不存在beta的效果:

存在beta的效果:

注意点:代码语言是使用TypeScript,所以执行时需要ts-node - npm (npmjs.com)工具帮我们编译下。

npm run release 是自定义命令,在文件package.json的scripts下配置即可。

//...
    "scripts": {
        //..
        "release": "ts-node scripts/release.ts"
    },
//...

最后,导入node文件模块,将选择确定后的版本号重新赋值给version,再写入package.json

//...
import path from "path";
import fs from "fs";
//...
const onSelectVersion = async () => {
    const beta = execs[4];
    //处理版本含有beta的情况
    const lists = beta ? getBetaVersionLists(beta) : getVersionlists();
    inquirer.prompt([{
        name: 'list',
        type: 'list',
        message: '请选择发布的版本:',
        choices: lists,
        default: [lists[0]]
    }]).then(async ({ list }) => {
        pkg.version = list
        //...
        fs.writeFile(path.join(__dirname, '../package.json'), String(JSON.stringify(pkg)), 'utf8', async (error) => {
            if (error) {
                return;
            }
            //...
        });
    })
}

好了,一个版本选择的功能就完成了。

跟Shell语言说再见

版本选择完成,接下来需要处理Git一系列命令提交代码、Git自动打Tag以及向Npm更新等操作。

需要处理命令有:

git add .
git commit -m xx
git tag **
git push origin ** //向远程仓库提交tag
git push origin branch

npm run build //rullop打包
npm publish //npm 发版

其中npm run build 将执行rullop打包

在终端中自动化执行一些命令,这个时候就需要Shell,创建Shell文件去完成对应的逻辑。作为前端工程师就需要去学习下Shell,有学习成本。

秉承能用Js实现的最终用Js实现的想法,node child_process 子进程模块中提供了可执行Shell命令的API。

当然还有更便捷的工具zx,由google开源。

npm i zx安装zx,导入zx,#!/usr/bin/env zx 是zx要求的。

#!/usr/bin/env zx
/...
import { $ } from 'zx';

正则匹配获取当前Git 分支:

const onSelectVersion = async () => {
    const beta = execs[4];
    //处理版本含有beta的情况
    const lists = beta ? getBetaVersionLists(beta) : getVersionlists();
    inquirer.prompt([{
   //...
    }]).then(async ({ list }) => {
        pkg.version = list
        let branch = await $`git branch`;
        const { stdout } = branch;
        const reg = /\*\D(.+)\D/g;
        branch = (reg.exec(stdout) as any[])[1];
        fs.writeFile(path.join(__dirname, '../package.json'), String(JSON.stringify(pkg)), 'utf8', async (error) => {
            if (error) {
                return;
            }
            //...
        });
    })
}

按顺序执行命令

const onSelectVersion = async () => {
     //..
    inquirer.prompt([{
     //...
    }]).then(async ({ list }) => {
      //...
        fs.writeFile(path.join(__dirname, '../package.json'), String(JSON.stringify(pkg)), 'utf8', async (error) => {
            if (error) {
                return;
            }
            await $`git add .`;
            await $`git commit -m ${list}`;
            await $`git tag ${list}`;
            await $`git push origin ${list}`;
            await $`git push origin ${branch}`;
            await $`npm run build&&npm publish`;
        });
    })
}

zx方法$ 原理是使用了 child_process 子进程模块中spawn 方法

到此,一个简简单单的脚本就完成了,算上空行一共就50+行。

最后

实现逻辑非常简单,没有什么难点。工作中肯定也会有一些需要反复做的事件,我们需要多去思考能否将其简化,学会做一个"会偷懒"的工具人。

感谢阅读,文章涉及的代码已开源,欢迎下载尝试release.ts