中台包自动化发布

259 阅读3分钟

前言

中台化的过程中往往会遇到包依赖包的问题,比如A和B2个工程都需要a包,a包又依赖b包,往往更新b包的一个小功能,就需要手动修改b包/a包/A工程/B工程里面package.json的版本。手动修改极其痛苦,特别在开发阶段需要频繁修改。虽然可以通过lerna在本地进行配置。但是lerna需要将这些工程/包放在一个目录下,管理起来非常麻烦。

解决方案

  1. 记录工程和包之间的依赖关系。这个过程可以放在jenkins打包的过程中,动态获取项目的包依赖。可以从package.json中获取包名字,从package-lock.json文件中获取包的具体版本,具体实现如下

#!/usr/bin/env node

const http = require('http');
const packageJson = require('./package.json');
const packageLockJson = require('./package-lock.json');
const { dependencies = {}, devDependencies = {} } = packageJson;
const dependenciesKeys = Object.keys(dependencies);
const devDependenciesKey = Object.keys(devDependencies);
// 获取项目中的所有依赖包,包含了dependencies和devDependencies
const packageKeys = [...dependenciesKeys, ...devDependenciesKey];
// 获取package-lock.json中安装的依赖包
const packageLockDependencies = packageLockJson && packageLockJson.dependencies;
// 获取依赖包的信息
const npmList = packageKeys.reduce((total, it) => {
    total.push({
        name: it,
        version: packageLockDependencies[it] && packageLockDependencies[it].version
    });
    return total;
}, []);
const appName = process.argv[2];
const env = process.argv[3];
const contents = JSON.stringify({
    appName,
    npmList
});

/** 请求配置 */
const option = {
    method: 'post',
    host: xx,
    prot: '80',
    path: '/xx',
    headers: {
        'Content-Type': 'application/json',
        'Content-Length': contents.length,
    }
};

const dispose = function(response) {
    let body = '';
    response.on('data', function(data) {
    body += data;
});

response.on('end', function() {
        console.log(body);
    });
};

const req = http.request(option, dispose);
req.write(contents);// 发送内容
req.end();
  1. 包和包之间的依赖关系也和第一步类似,在发布到npm的时候动态记录。如果比较简单的化就是手动登记就可以了。

  2. 中台发布的过程中将需要发布的包和项目拉进来,前2步做的关联关系主要是为了这一步。有了关联关系后,我们只需要打包最底层的包,通过脚本自动修改package.json的版本就可以一层一层往外打,省去了手动修改的麻烦。

image.png

  1. 调用gitlab api获取当前包的package.json文件(本文使用egg作为服务端框架)

/**

* 获取修改后的package.json的内容

* @param {*} ctx ctx

* @param {*} projectId 仓库id

* @param {*} npmName 包的名称

* @param {*} npmVersion 包的version

*/

const getNewPackageJson = async (ctx, projectId, npmName, npmVersion, isCurrent = false, ref = 'master') => {
    const result = await ctx.curl(`http://git.xx.cn/api/v4/projects/${projectId}/repository/files/package.json?ref=${ref}`, {
        method: 'GET',
        headers: config.headers,
    });

    // 如果没有获取到package.json内容
    if (result.status !== 200) {
        ctx.throw('获取不到package.json内容,请确认工程名以及工程下是否存在package.json文件');
    }
    const { content } = JSON.parse(result.data.toString('utf8'));
    const packageJson = utils.decodeBase64(content); // package.json的内容
    console.log(packageJson)
};

// util.js

const decodeBase64 = base64 => {
    let buf;
    if (Buffer.from && Buffer.from !== Uint8Array.from) {
        buf = Buffer.from(base64, 'base64');
    } else {
        if (typeof notNumber === 'number') {
        throw new Error('The "size" argument must be not of type number.');
        }
        buf = new Buffer(base64, 'base64');
    }
    const decodeValue = new Buffer(buf, 'base64').toString();
    // 编码转字符串
    return decodeValue;
};

  1. 动态修改依赖信息

const fs = require('fs');
const path = require('path');
const packageJson = fs.readFileSync(path.resolve(__dirname, './package.json'), 'utf-8');
let JsonParse = JSON.parse(packageJson)
JsonParse.version = '1.0.0'
JsonParse.dependencies.lodash = '5.0.0'
// json.stringify的第三个参数表示前面空几格
fs.writeFileSync(path.resolve(__dirname, './package.json'), JSON.stringify(JsonParse, null, 4));
  1. 至此,中台包的发布就已经完成了。

AST解决package.json前面空格问题

在实际使用中发现package.json文件有些包/文件是空4格,有些则是2格。部分同学还是喜欢手动修改,从而导致package.json因为空格的问题经常冲突。

解决方案: 将package.json文件转为ast树,然后获取version字段前面的空格数a,然后JSON.stringify的第三个参数就用a,具体实现如下

const fs = require('fs');
const path = require('path');
const { parse } = require('@babel/parser');
const { default: traverse } = require("@babel/traverse");
const packageJson = fs.readFileSync(path.resolve(__dirname, './package.json'), 'utf-8');

/**
* 获取空格数
* @param {*}} packageJson
* @param {*} callback
*/
const getSpace = (packageJson, callback) => {
    let space = 2;
    const obj = "const a = " + packageJson;
    const visitor = {
        ObjectProperty(_path){
            const { node } = _path;
            const key = node.key.value;
            if(key === 'version'){
            try{
                space = node.key.loc.start.column;
            }catch(e){
                space = 2;
            }
            callback(space);
          }
       }
    }
    const ast = parse(obj);
    traverse(ast, visitor);
}
getSpace(packageJson, (space) => {
    const JsonParse = JSON.parse(packageJson);
    // 修改对应的版本
    JsonParse.version = '1.0.0';
    JsonParse.age = '2.0.0';
    // JSON.stringify的第三个参数为动态获取
    fs.writeFileSync(path.resolve(__dirname, './package.json'), JSON.stringify(JsonParse, null, space));
})