@lcap/cli
Version:
utils for lcap
239 lines • 9.98 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.release = void 0;
const path_1 = __importDefault(require("path"));
const recursive_readdir_1 = __importDefault(require("recursive-readdir"));
const lodash_1 = require("lodash");
const promises_1 = require("fs/promises");
const undici_1 = require("undici");
const rest_1 = require("@gitbeaker/rest");
const utils_1 = require("../utils");
const constant_1 = require("./constant");
const HOST = 'https://g.hz.netease.com';
async function getFilesDiff(gitlab, remoteFiles, filesInDisk) {
/** 文件编码格式 */
const encoding = 'base64';
/** 只在远端的文件 */
const filesOnlyInRemote = remoteFiles.filter((file) => !filesInDisk.find((remote) => remote.remotePath === file));
/** 只在硬盘里的文件 */
const filesOnlyInDisk = filesInDisk.filter(({ remotePath: remote }) => !remoteFiles.includes(remote));
/** 同时存在两端的文件 */
const filesBothIn = filesInDisk.filter(({ remotePath: remote }) => remoteFiles.includes(remote));
/** 更新操作数据 */
const actions = [];
/** 并发控制器 */
const agent = new undici_1.Agent({
connections: 1000,
pipelining: 2,
autoSelectFamily: true,
connectTimeout: 120000,
autoSelectFamilyAttemptTimeout: 120000,
headersTimeout: 120000,
bodyTimeout: 120000,
keepAliveTimeout: 120000,
});
async function getFileMeta(file) {
const { headers } = await agent.request({
origin: HOST,
path: `/api/v4/projects/${encodeURIComponent(constant_1.AssetsReleaseId)}/repository/files/${encodeURIComponent(file)}?ref=master`,
method: 'HEAD',
headers: {
'User-Agent': 'Undici/1.0',
'PRIVATE-TOKEN': gitlab.headers['PRIVATE-TOKEN'], //'ze4vCDGm7LkvBMfn2CsV',
},
}).catch((err) => {
return {
headers: {},
};
});
return {
blobId: headers['x-gitlab-blob-id'],
contentSha256: headers['x-gitlab-content-sha256'],
encoding: headers['x-gitlab-encoding'],
ref: headers['x-gitlab-ref'],
size: Number(headers['x-gitlab-size']),
commitId: headers['x-gitlab-commit-id'],
lastCommitId: headers['x-gitlab-last-commit-id'],
fileName: headers['x-gitlab-file-name'],
filePath: headers['x-gitlab-file-path'] ?? file,
};
}
// 只存在于远端的文件要删除
for (const file of filesOnlyInRemote) {
actions.push({
action: 'delete',
filePath: file,
});
}
// 只存在于硬盘的文件要创建
for (const file of filesOnlyInDisk) {
actions.push({
action: 'create',
filePath: file.remotePath,
content: file.content.toString('base64'),
encoding,
});
}
// 同时存在于这两者的需要更新
for (const files of (0, lodash_1.chunk)(filesBothIn, 200)) {
await Promise.all(files.map(async (file) => {
const meta = await getFileMeta(file.remotePath);
const add = () => {
actions.push({
action: 'update',
encoding,
content: file.content.toString('base64'),
filePath: file.remotePath,
});
};
if (meta.size !== file.size) {
add();
return;
}
const hashInDisk = (0, utils_1.createSHA256)(file.content);
if (meta.contentSha256 !== hashInDisk) {
add();
return;
}
}));
}
await agent.close();
await agent.destroy();
return (0, lodash_1.chunk)(actions, 12);
}
/** 关闭匹配前缀的分支和合并请求 */
async function deleteBranchAndMr(gitlab, prefix) {
const restMr = await gitlab.MergeRequests.all({
AssetsReleaseId: constant_1.AssetsReleaseId,
search: prefix,
state: 'opened',
});
if (restMr.length > 0) {
utils_1.logger.info(`发现 ${restMr.length} 个重复的旧合并请求,开始关闭...`);
for (const mrs of (0, lodash_1.chunk)(restMr, 5)) {
await Promise.all(mrs.map(({ iid }) => gitlab.MergeRequests.edit(constant_1.AssetsReleaseId, iid, {
stateEvent: 'close',
})));
}
}
else {
utils_1.logger.info(`没有发现重复的旧合并请求!`);
}
const restAutoBranch = await gitlab.Branches.all(constant_1.AssetsReleaseId, {
search: prefix,
});
if (restAutoBranch.length > 0) {
utils_1.logger.info(`发现 ${restAutoBranch.length} 个重复的旧分支,开始删除...`);
for (const branches of (0, lodash_1.chunk)(restAutoBranch, 5)) {
await Promise.all(branches.map(({ name }) => gitlab.Branches.remove(constant_1.AssetsReleaseId, name)));
}
}
else {
utils_1.logger.info(`没有发现重复的旧分支!`);
}
}
async function autoMr(root, gitlab, data) {
const version = await (0, utils_1.getVersion)(root);
utils_1.logger.info(`当前物料 ${utils_1.color.yellow(data.kind.toUpperCase())},版本 ${utils_1.color.yellow(version)}`);
const basePathInRemote = constant_1.AssetPath.ide(version);
utils_1.logger.info(`检索远程仓库代码...`);
const filesInRemote = await gitlab.Repositories.allRepositoryTrees(constant_1.AssetsReleaseId, {
path: basePathInRemote,
recursive: true,
ref: constant_1.AssetRef,
});
const fileNamesInRemote = filesInRemote
.filter((item) => item.type === 'blob')
.map((item) => item.path);
utils_1.logger.info(`读取本地文件...`);
const filesInDisk = await (0, recursive_readdir_1.default)(data.distDir);
const filesData = await Promise.all((filesInDisk).map(async (file) => {
const diskPath = path_1.default.join(root, file);
const remotePath = path_1.default.join(basePathInRemote, path_1.default.relative(data.distDir, file));
const fileBuffer = await (0, promises_1.readFile)(diskPath);
const size = fileBuffer.byteLength;
return {
diskPath,
remotePath,
size,
content: fileBuffer,
};
}));
if (filesData.length === 0) {
utils_1.logger.info(`未发现本地数据,进程退出。`);
if (data.output) {
utils_1.logger.info(`MR 结果写入上下文...`);
await (0, utils_1.writeContext)(root, data.output, { mr_url: 'NONE' });
}
return;
}
utils_1.logger.info(`正在准备 Diff 数据...`);
const filesDiff = await getFilesDiff(gitlab, fileNamesInRemote, filesData);
if (filesDiff.length === 0) {
utils_1.logger.info(`文件全部相同,不需要提交!`);
return;
}
const searchFix = `${data.kind}@${version}`;
const newBranchPrefix = `auto-release/${searchFix}`;
utils_1.logger.info(`开始删除旧的分支和合并请求...`);
// 发生错误不影响后面的流程
await deleteBranchAndMr(gitlab, searchFix).catch((e) => {
utils_1.logger.error(`删除旧分支和合并请求时出错,跳过此流程!`);
utils_1.logger.error(e);
});
utils_1.logger.info(`开始创建分支...`);
const newBranchName = `${newBranchPrefix}/${Date.now()}`;
const branchData = await gitlab.Branches.create(constant_1.AssetsReleaseId, newBranchName, constant_1.AssetRef);
utils_1.logger.info(`分支创建完成,新分支名为 ${utils_1.color.yellow(newBranchName)},分支链接为:${branchData.webUrl}`);
utils_1.logger.info(`开始创建 MR...`);
const params = {
version,
time: Date.now(),
kind: data.kind,
};
const mergeRequestData = await gitlab.MergeRequests.create(constant_1.AssetsReleaseId, newBranchName, constant_1.AssetRef, (0, utils_1.formatMessageByParams)(data.mrTitle, params), {
description: (0, utils_1.formatMessageByParams)(data.mrMessage, params),
squash: true,
removeSourceBranch: true,
labels: 'auto-release',
});
utils_1.logger.info(`MR 创建成功,MR 链接为:${utils_1.color.blackBright(mergeRequestData.webUrl)}`);
utils_1.logger.info(`开始提交代码...`);
utils_1.spinner.start();
for (let i = 0; i < filesDiff.length; i++) {
const group = filesDiff[i];
utils_1.spinner.text = `提交代码:${i + 1} / ${filesDiff.length}...`;
await gitlab.Commits.create(constant_1.AssetsReleaseId, newBranchName, `auto-release: 🔧 自动提交 ${version} 版本 ${data.kind.toUpperCase()} 物料`, group, {
authorName: data.authorName,
authorEmail: data.authorEmail,
force: false,
});
}
utils_1.spinner.stop();
utils_1.spinner.clear();
utils_1.logger.info(`代码提交成功!`);
if (data.output) {
utils_1.logger.info(`MR 结果写入上下文...`);
await (0, utils_1.writeContext)(root, data.output, {
mr_url: mergeRequestData.webUrl,
});
}
utils_1.logger.info(`代码自动合并结束!`);
}
async function release(root, opt) {
const tokenData = await (0, utils_1.getSecretByOption)(root, opt);
const gitlab = new rest_1.Gitlab({
token: tokenData,
host: HOST,
camelize: true,
queryTimeout: 30 * 60 * 1000,
});
// 存一下 token
gitlab.headers['PRIVATE-TOKEN'] = 'ze4vCDGm7LkvBMfn2CsV';
return autoMr(root, gitlab, opt);
}
exports.release = release;
//# sourceMappingURL=api.js.map