UNPKG

infly-libs

Version:

工具组件库

523 lines (468 loc) 21.2 kB
/** * 自动压缩构建打包后的发版文件 * @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, };