infly-libs
Version:
工具组件库
407 lines (364 loc) • 15.4 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 } = require("../../tools/file-process.js");
const { init: previewInit } = require("../../tools/project-preview.js");
const { getCurrentBranch, getLatestCommitInfo, getLastCommitByFile, checkGitStatus, autoGitProcess } = require("../git-automation");
const path = require("path");
const fs = require("fs");
const isBuilZipScript = ["build:zip", "build:zipInit"].includes(process.env.npm_lifecycle_event);
const versionFileName = "version-config.json";
const currentBranch = getCurrentBranch();
const projectRoot = process.cwd();
const originConfigPath = path.join(__dirname, "template-version-config.json");
const projectVersionConfig = projectFileResolve(versionFileName);
const projectVueConfig = projectFileResolve("vue.config.js");
const projectPackageJSON = projectFileResolve("package.json");
function projectFileResolve(fileName) {
// 初始脚本自动执行路径会自动读取node_modules
const absoluteProjectRoot = projectRoot.replace(/\\node_modules.*$/g, "");
return path.resolve(absoluteProjectRoot, fileName);
}
function init() {
try {
const configPath = path.resolve(projectRoot, versionFileName);
validateFileExists(configPath, versionFileName);
const buildConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
validateConfig(buildConfig);
// 准备构建信息
const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, ".");
const {
zipOptions: { buildFoldName, outputPath, gitAuto, gitAutoCommitText = "", enablePreview, openExplorer },
versionConfig: { mainVersion, platformName, baseName, versionList, links }
} = buildConfig;
// 处理版本信息
const zipFiles = handleZipFiles(`-${buildFoldName}-v`);
const { deleteOldFile } = updateVersionList(zipFiles, {
buildConfig,
configPath
});
// 处理所需版本文件信息
const baseFileName = `${platformName}-${buildFoldName}-${baseName}`;
const lastVersion = versionList[0] ? versionList[0].version : "1.0.0";
const newVersion = incrementVersion(lastVersion, mainVersion);
const newFileName = baseFileName.replace("{version}", newVersion).replace("{timestamp}", timestamp);
const gitInfo = getLatestCommitInfo();
const publishText = `${platformName}:\n更新到:${links}\n更新时间:${timestamp}\n版本号:v${newVersion}\n分支:${gitInfo.branch}\n更新内容:\n${gitInfo.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 });
autoGitProcess(gitInfo, { gitAutoCommitText: gitAutoCommitText ? gitAutoCommitText.replace("{version}", newVersion) : "" });
}
copyToClipboard(publishText).then(() => {
if (enablePreview) {
previewInit(true);
}
});
if (openExplorer) {
exec(`explorer ${outputDir}`);
}
} 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 (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 {Array} zipFiles - ZIP 文件列表
* @param {Object} extraParams - 额外传参
* @returns
*/
function updateVersionList(zipFiles = [], extraParams) {
const { opt = "update", buildConfig, configPath } = extraParams || {};
const { versionConfig } = buildConfig || {};
const { versionList = [] } = versionConfig || {};
const versionMap = createVersionMap(versionList);
const deleteOldFile = [];
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}`);
}
});
}
sortVersion(buildConfig.versionConfig.versionList);
fs.writeFileSync(configPath, JSON.stringify(buildConfig, 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) => {
const command = process.platform === "win32" ? "clip" : "pbcopy";
const child = exec(`chcp 65001 > nul && ${command}`, (error) => {
if (error) {
console.error(global.logColor.error, `❌ 文本复制失败: ${error}`);
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();
});
}
function handlePackageJOSN() {
if (!fs.existsSync(projectVueConfig)) {
return;
}
scriptWrite(projectPackageJSON, buildList);
}
/**
* 复制版本管理文件模板到根目录
* @returns
*/
function copyVersionConfigFile() {
if (!fs.existsSync(projectVueConfig)) {
return;
}
const vueConfig = require(projectVueConfig);
const { outputDir, configureWebpack } = vueConfig || {};
const { name: projectTitle } = configureWebpack || {};
if (!fs.existsSync(projectVersionConfig)) {
// 创建可读流和可写流
const readStream = fs.createReadStream(originConfigPath);
const writeStream = fs.createWriteStream(projectVersionConfig);
readStream.pipe(writeStream);
writeStream.on("finish", () => {
// 第二阶段:修改复制后的文件
fs.readFile(projectVersionConfig, "utf8", (err, data) => {
if (!outputDir && !projectTitle) {
return;
}
if (err) {
console.error(global.logColor.error, `❌ 读取复制文件失败: ${err.message}`);
return;
}
const tempVersionConfig = JSON.parse(data);
if (outputDir) {
tempVersionConfig.zipOptions.buildFoldName = outputDir;
}
if (projectTitle) {
tempVersionConfig.versionConfig.platformName = projectTitle;
}
const tempVersionConfigStr = JSON.stringify(tempVersionConfig, null, 2);
fs.writeFile(projectVersionConfig, 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文件已成功复制到根目录:${projectVersionConfig}`);
});
writeStream.on("error", (err) => {
console.error(global.logColor.error, `❌ 复制版本JSON文件失败:${err.message}`);
});
}
}
module.exports = {
init,
handlePackageJOSN,
copyVersionConfigFile
};