infly-libs
Version:
工具组件库
523 lines (468 loc) • 21.2 kB
JavaScript
/**
* 自动压缩构建打包后的发版文件
* @description
* npm run build:prod 脚本会自动压缩构建打包后的发版文件,并将版本号写入到 version-config.json 文件中。
* npm run build:zip 脚本会自动删除旧的压缩文件,并将新的压缩文件命名为 {platformName}-{buildFoldName}-{baseName}-{version}-{timestamp}.zip
* 依赖文件:
* version-config.json
* package.json
* "build:prod": "vue-cli-service build && node build/zip-dist.js",
"build:zip": "vue-cli-service build && node build/zip-dist.js",
*/
global.logColor = {
success: "\n\x1b[32m%s\x1b[0m",
error: "\n\x1b[31m%s\x1b[0m",
warning: "\n\x1b[33m%s\x1b[0m",
link: "\x1b[34m%s\x1b[0m",
};
const { exec } = require("child_process");
const { zipSync } = require("./zip.js");
const { buildList } = require("../../script/dataInit.js");
const { scriptWrite, targetProjectFileResolve, currentProjectFileResolve } = require("../../tools/file-process.js");
const { init: previewInit } = require("../../tools/project-preview.js");
const { getCurrentBranch, getLatestCommitInfo, getLastCommitByFile, checkGitStatus, autoGitProcess } = require("../git-automation");
const { postVersionFileAndMsg } = require("../../script/webhook.js");
const path = require("path");
const fs = require("fs");
const isBuilZipScript = process.env.npm_lifecycle_event.includes("build:zip");
const versionFileName = "package.json";
const oldVersionFileName = "version-config.json";
const npmPackageConfigKey = "infly";
const currentBranch = getCurrentBranch();
const projectRoot = process.cwd();
// 依赖包的配置
const originConfigPath = currentProjectFileResolve("template-version-config.json"); // 模板项目文件
const currentProjectPackageJSON = currentProjectFileResolve("../package.json"); // 模板项目文件
const currentPackageJsonConfig = JSON.parse(fs.readFileSync(currentProjectPackageJSON, "utf-8"));
const { infly: currentInflyConfig } = currentPackageJsonConfig || {};
// 安装依赖的项目配置
const targetProjectVersionConfigPath = targetProjectFileResolve(oldVersionFileName);
const targetProjectVueConfigPath = targetProjectFileResolve("vue.config.js");
const targetProjectPackageJSON = targetProjectFileResolve("package.json");
const targetPackageJsonConfig = JSON.parse(fs.readFileSync(targetProjectPackageJSON, "utf-8"));
const { name: projectName, version: projectVersion = "", infly: targetInflyConfig } = targetPackageJsonConfig || {};
const [major = "", minor = "", patch = ""] = projectVersion.split(".");
const isExistTargetProjectVersionConfig = fs.existsSync(targetProjectVersionConfigPath); // 安装依赖项目下是否存在旧版本控制文件
const isExistVueConfigFile = fs.existsSync(targetProjectVueConfigPath); // 安装依赖项目下是否存在vue.config.js文件
const isMaster = currentBranch === "master"; // 是否在master分支
const dealBranch = isMaster ? "master" : "release";
const targetProjectVueConfig = isExistVueConfigFile ? require(targetProjectVueConfigPath) : {};
const { outputDir: targetProjectOutputDir, configureWebpack } = targetProjectVueConfig || {};
const { updateDevText, versionText = "" } =
{
master: {
updateDevText: "正式环境",
},
release: {
updateDevText: "测试环境",
versionText: "测试环境版本",
},
}[dealBranch] || {}; // 更新环境和处理分支
function init() {
try {
const configPath = path.resolve(projectRoot, versionFileName);
validateFileExists(configPath, versionFileName);
let buildConfig = JSON.parse(fs.readFileSync(configPath, "utf-8")) || {};
if (buildConfig[npmPackageConfigKey]) {
buildConfig = buildConfig[npmPackageConfigKey];
}
validateConfig(buildConfig);
// 准备构建信息
const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, ".");
const { zipOptions, versionConfig } = buildConfig || {};
const {
buildFoldName: zipBuildFoldName, // 构建后文件名称
outputPath, // 输出路径
gitAuto, // 是否自动提交git
gitAutoCommitText = "", // git自动提交信息
enablePreview, // 启用预览
openExplorer, // 启用打开资源管理器
webhookUrl, // 企业微信机器人链接
webhookAtUser, // 企业微信机器人@用户
enableClipboard, // 启用粘贴板
disableUpdateVersionEnv, // 禁止更新版本信息环境
} = zipOptions || {};
const buildFoldName = zipBuildFoldName || targetProjectOutputDir || "dist";
const { mainVersion: verMainVersion, platformName, baseName, versionList = [], links } = versionConfig || {};
const mainVersion = verMainVersion || major; // 主版本号
const [lastestVersionItem] = versionList || [];
const { lastVersion = projectVersion || "1.0.0" } = lastestVersionItem || {};
// 处理版本信息
const zipFiles = handleZipFiles(`-${buildFoldName}-v`);
const { deleteOldFile } = updateVersionList(zipFiles, { buildConfig, configPath });
// 处理所需版本文件信息
const baseFileName = `${platformName}-${buildFoldName}-${baseName}`;
const newVersion = isMaster ? incrementVersion(lastVersion, mainVersion) : versionText;
const newFileName = baseFileName.replace("{version}", newVersion).replace("{timestamp}", timestamp);
const gitInfo = getLatestCommitInfo() || {};
const { branch, commitMsg } = gitInfo || {};
const tempLink = typeof links === "object" ? links[dealBranch] : links || "";
const publishText = `${platformName}:\n更新到:${tempLink}\n更新时间:${timestamp}\n版本号:v${newVersion}\n分支:${branch}\n更新环境:${updateDevText}\n更新内容:\n${commitMsg}`;
const newZipFileInfo = [
{
version: newVersion,
description: "",
timestamp,
fileName: newFileName,
ps: "【自动压缩命令生成的版本】",
gitInfo,
},
];
// 处理文件路径
const outputDir = path.resolve(projectRoot, "./", outputPath);
const outputPathFull = path.join(outputDir, newFileName);
const distPath = path.resolve(projectRoot, "./" + buildFoldName);
const indexHtmlPath = path.join(distPath, "index.html");
validateFileExists(distPath, "distPath");
validateFileExists(indexHtmlPath, "indexHtmlPath");
zipSync(distPath, outputPathFull, true);
validateFileExists(outputPathFull, "outputPathFull");
updateVersionList(newZipFileInfo, { opt: "add", buildConfig, configPath });
if (gitAuto) {
checkGitStatus(buildFoldName, newFileName, { deleteOldFile, versionFileName });
autoGitProcess(gitInfo, { gitAutoCommitText: gitAutoCommitText ? gitAutoCommitText.replace("{version}", newVersion) : "" });
}
if (enableClipboard) {
copyToClipboard(publishText);
}
if (enablePreview) {
previewInit(true);
}
if (openExplorer) {
exec(`explorer ${outputDir}`);
}
if (webhookUrl) {
postVersionFileAndMsg(publishText, { projectName, newFileName, gitInfo, ...zipOptions });
}
} catch (error) {
console.error(global.logColor.error, `❌ 压缩脚本执行出错, 错误信息:\n${error}`);
process.exit(1);
}
}
/**
* 校验配置文件
* @param {Object} buildConfig
*/
function validateConfig(buildConfig) {
if (!buildConfig) {
console.error(global.logColor.error, `❌ ZIP压缩已禁用,版本控制文件${versionFileName}配置错误`);
process.exit(1);
}
if (buildConfig?.zipOptions?.enabled === false) {
console.warn(global.logColor.error, `⚠️ ZIP压缩已禁用,版本控制文件${versionFileName}中的enabled属性设置为false`);
process.exit(0);
}
if (!/^release/.test(currentBranch) && currentBranch !== "master") {
console.error(global.logColor.error, `❌ 当前分支非release、release/xx、master等可构建分支,请检查`);
process.exit(1);
}
if (currentBranch !== "master") {
console.warn(global.logColor.warning, `⚠️ 当前分支为 ${currentBranch},非测试需求请在 master 分支上执行此脚本`);
}
}
/**
* 校验文件是否存在
* @param {String} checkPath - 检查路径
* @param {String} logKey - 校验文案键值
*/
function validateFileExists(checkPath, logKey) {
const { error, success, link } = {
[versionFileName]: {
error: `版本控制文件${versionFileName}不存在,请检查`,
},
distPath: {
error: "构建文件夹不存在,请检查构建配置",
},
indexHtmlPath: {
error: "index.html文件不存在,请检查构建配置",
},
outputPathFull: {
error: "压缩文件没有成功创建,请检查构建配置",
success: `压缩完成,已成功创建文件`,
link: checkPath,
},
configPath: {
error: `新版本写入${versionFileName}失败,请检查版本文件是否存在`,
success: `新版本写入${versionFileName}成功,请点击文件链接确认版本信息无误`,
link: checkPath,
},
}[logKey];
if (!fs.existsSync(checkPath)) {
console.error(global.logColor.error, `❌ ${error}`);
process.exit(1);
}
if (success) {
console.log(global.logColor.success, `✅ ${success}`);
}
if (link) {
console.log(global.logColor.link, `${link}`);
}
}
/**
* 处理压缩文件
* @param {String} checkFileName - 检查文件名
* @param {Function} callback - 回调函数
* @returns
*/
function handleZipFiles(checkFileName) {
const files = fs.readdirSync(projectRoot);
const zipFiles = [];
files.forEach((file) => {
if (path.extname(file).toLowerCase() === ".zip" && path.basename(file).includes(checkFileName)) {
const [platformName, versionTimestamp = ""] = file.split(checkFileName);
const [version, timestamp] = versionTimestamp.split("-");
zipFiles.push({
version,
description: "",
timestamp,
fileName: file,
});
}
});
return zipFiles;
}
/**
* 将版本数组转为对象,方便查找匹配
* @param {Array} versionList
* @returns
*/
function createVersionMap(versionList = []) {
return versionList.reduce((acc, item) => {
acc[item.fileName] = item.version;
return acc;
}, {});
}
/**
* 检查是否禁用更新版本文件环境
* @param {Boolean} disableUpdateVersionEnv - 禁用环境配置
*/
function checkIsDisableUpdateVersionEnv(disableUpdateVersionEnv) {
let isDisableUpdateVersionEnv = false;
if (Array.isArray(disableUpdateVersionEnv)) {
disableUpdateVersionEnv.forEach((item) => {
if (process.env.npm_lifecycle_script.includes(`--mode ${item}`)) {
isDisableUpdateVersionEnv = disableUpdateVersionEnv.includes(item);
}
});
}
return isDisableUpdateVersionEnv;
}
/**
* 更新版本文件列表
* @param {Array} zipFiles - ZIP 文件列表
* @param {Object} extraParams - 额外传参
* @returns
*/
function updateVersionList(zipFiles = [], extraParams) {
const { opt = "update", buildConfig, configPath } = extraParams || {};
const { versionConfig, zipOptions } = buildConfig || {};
const { versionList = [] } = versionConfig || {};
const { disableUpdateVersionEnv } = zipOptions || {};
const isDisableUpdateVersionEnv = checkIsDisableUpdateVersionEnv(disableUpdateVersionEnv);
const versionMap = createVersionMap(versionList);
const deleteOldFile = [];
const [newVersionItem] = zipFiles || [];
const { version: newVersion } = newVersionItem || {};
let allVersionExist = true; // 是否所有版本都存在于 version-config.json 中
if (zipFiles.length === 0) {
console.warn(global.logColor.warning, "⚠️ 没有 ZIP 文件,跳过版本更新。");
} else {
zipFiles.forEach((zipFile) => {
const { version, fileName, ps = "【非压缩命令生成的版本】", gitInfo = getLastCommitByFile(fileName) } = zipFile;
const singleVersionInfo = { ...zipFile, ps, gitInfo };
// 如果存在没有写入版本配置文件的压缩文件,则进行写入操作
if (!versionMap[fileName] && Array.isArray(versionList)) {
allVersionExist = false;
buildConfig.versionConfig.versionList.unshift(singleVersionInfo);
console.log(global.logColor.success, `✅ 新版本${version}写入${versionFileName}文件更新成功`);
}
// 如果是更新,压缩脚本进行打包则删除全部旧文件
if (isBuilZipScript && opt === "update") {
deleteOldFile.push(fileName);
fs.unlinkSync(path.join(projectRoot, fileName));
console.log(global.logColor.success, `✅ 已删除 ZIP 文件: ${fileName}`);
}
});
}
if (versionList.length > 0) {
sortVersion(buildConfig.versionConfig.versionList);
}
if (!isDisableUpdateVersionEnv) {
let tempWriteConfig = buildConfig;
if (targetPackageJsonConfig[npmPackageConfigKey]) {
const { name: oldProjectName, version: oldVersion, ...otherPkgConfig } = targetPackageJsonConfig;
if (buildConfig.versionConfig.versionList) {
buildConfig.versionConfig.versionList = [];
}
targetPackageJsonConfig[npmPackageConfigKey] = buildConfig || {};
tempWriteConfig = { name: projectName, version: newVersion, ...otherPkgConfig };
}
fs.writeFileSync(configPath, JSON.stringify(tempWriteConfig, null, 2));
}
if (allVersionExist) {
console.log(global.logColor.success, `✅ 所有文件版本已存在于${versionFileName}中,无需更新`);
} else if (!isBuilZipScript || opt !== "update") {
// 如果是自动压缩的命令,只在完成压缩更新版本后统一输出,更新的时候不输出减少重复信息
validateFileExists(configPath, "configPath");
}
// 如果不是自动压缩的命令,在更新版本json文件后退出
if (!isBuilZipScript) {
console.warn(global.logColor.warning, "⚠️ 此次仅进行版本号更新,不进行压缩。");
process.exit(0);
}
return { deleteOldFile };
}
/**
* 进行版本大小排序
* @param {Array} versionList
* @returns
*/
function sortVersion(versionList = []) {
return versionList.sort((a, b) => {
const aParts = a.version.split(".");
const bParts = b.version.split(".");
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
const aPart = parseInt(aParts[i] || "0", 10);
const bPart = parseInt(bParts[i] || "0", 10);
if (aPart !== bPart) {
return bPart - aPart;
}
}
return 0;
});
}
/**
* 递增版本号
* @param {String} version - 版本号
* @param {String} mainVersion - 主版本号
* @returns
*/
function incrementVersion(version = "", mainVersion = "") {
const parts = version.split(".");
for (let i = parts.length - 1; i >= 1; i--) {
if (parseInt(parts[i]) < 9) {
parts[i] = (parseInt(parts[i]) + 1).toString();
break;
} else {
parts[i] = "0";
parts[i - 1] = (parseInt(parts[i - 1]) + 1).toString();
break;
}
}
if (mainVersion) {
parts[0] = mainVersion;
}
return parts.join(".");
}
/**
* 将发版文案复制到剪贴板
* @param {String} text - 要复制的文本
*/
function copyToClipboard(text) {
return new Promise((resolve, reject) => {
let command;
if (process.platform === "win32") {
command = "clip";
} else if (process.platform === "darwin") {
command = "pbcopy";
} else {
// Linux/WSL
// 优先用 xclip,其次 xsel
command = "xclip -selection clipboard || xsel --clipboard";
}
const child = exec(command, (error) => {
if (error) {
console.error(global.logColor.error, `❌ 文本复制失败: ${error}\n请确保已安装 xclip 或 xsel(Linux),或手动复制:\n${text}`);
reject(error);
} else {
console.log(global.logColor.success, `✅ 文本已复制到剪贴板\n${text}`);
resolve(text);
}
});
// 确保使用UTF-8编码写入
if (process.platform === "win32") {
child.stdin.write(Buffer.from(text, "utf8"));
} else {
child.stdin.write(text, "utf8");
}
child.stdin.end();
});
}
/**
* 脚本配置初始化
* @returns
*/
function scriptInit() {
let tempInflyConfig = {};
if (!isExistVueConfigFile || !fs.existsSync(targetProjectPackageJSON)) {
return;
}
scriptWrite(targetProjectPackageJSON, buildList);
if (isExistTargetProjectVersionConfig) {
tempInflyConfig = JSON.parse(fs.readFileSync(targetProjectVersionConfigPath, "utf-8"));
const { versionConfig = {}, zipOptions = {} } = tempInflyConfig || {};
if (versionConfig.versionList) {
tempInflyConfig.versionConfig.versionList = []; // 删除旧版本列表
}
if (zipOptions.buildFoldName) {
tempInflyConfig.zipOptions.buildFoldName = "";
}
}
tempInflyConfig = { ...currentInflyConfig, ...tempInflyConfig };
scriptWrite(targetProjectPackageJSON, tempInflyConfig, npmPackageConfigKey);
if (isExistTargetProjectVersionConfig) {
console.log(global.logColor.success, `✅ ${versionFileName}配置迁移成功, 可手动删除旧版本控制文件`);
}
}
/**
* 复制版本管理文件模板到根目录
* @returns
*/
function copyVersionConfigFile() {
if (!isExistVueConfigFile) {
return;
}
const { name: projectTitle } = configureWebpack || {};
if (!isExistTargetProjectVersionConfig && fs.existsSync(originConfigPath)) {
// 创建可读流和可写流
const readStream = fs.createReadStream(originConfigPath);
const writeStream = fs.createWriteStream(targetProjectVersionConfigPath);
readStream.pipe(writeStream);
writeStream.on("finish", () => {
// 第二阶段:修改复制后的文件
fs.readFile(targetProjectVersionConfigPath, "utf8", (err, data) => {
if (!targetProjectOutputDir && !projectTitle) {
return;
}
if (err) {
console.error(global.logColor.error, `❌ 读取复制文件失败: ${err.message}`);
return;
}
const tempVersionConfig = JSON.parse(data);
if (targetProjectOutputDir) {
tempVersionConfig.zipOptions.buildFoldName = targetProjectOutputDir;
}
if (projectTitle) {
tempVersionConfig.versionConfig.platformName = projectTitle;
}
const tempVersionConfigStr = JSON.stringify(tempVersionConfig, null, 2);
fs.writeFile(targetProjectVersionConfigPath, tempVersionConfigStr, (err) => {
if (err) {
console.error(global.logColor.error, `❌ 读取vue.config.js配置更新失败: ${err.message}`);
} else {
console.log(global.logColor.success, `✅ 读取vue.config.js配置更新成功`);
}
});
});
console.log(global.logColor.success, `✅ 版本JSON文件已成功复制到根目录:${targetProjectVersionConfigPath}`);
});
writeStream.on("error", (err) => {
console.error(global.logColor.error, `❌ 复制版本JSON文件失败:${err.message}`);
});
}
}
module.exports = {
init,
scriptInit,
copyVersionConfigFile,
};