公司内部持续集成用的Jenkins,办公通讯用的钉钉,代码维护用的GitLab。
持续集成的构建详情在日常开发中需要频繁查看,过程是否报错,提交的概要。
但是旧有的版本流程,只有记录了合入主干的时侯,输出一个签入签出的文本,
所以有时候还需要借助运维大佬帮忙找为毛失败,很浪费大伙的时间,成本太高。
所以我就在想,如何简化这个过程,让效率更高。
当看到钉钉支持卡片化和 markdown 化推送信息的时侯,我就知道游戏开始了。
在这个东西出来之前,构建信息都需要走这么几个步骤。
一步到位,不需要去关注其他,也不用占用其他人的时间来帮你定位一些很基础的信息。
没有用到第三方库,都是用 node 的内置 api 实现文件读取操作及 http 请求。
如何读取执行根目录的配置文件呢?
主要用到了 process.cwd
查询执行路径, 实现读取 package.json 和独立配置文件的参数
const fs = require("fs"); const path = require("path"); const process = require("process"); const jk2dtFile = path.resolve(process.cwd(), "./jk2dtrc.js"); const pkgFile = require(path.resolve(process.cwd(), "./package.json")); let importConfig = {}; if (fs.existsSync(jk2dtFile)) { process.stdout.write("jk2dt配置文件存在 /n"); const config = require(jk2dtFile); importConfig = config; } else { if (fs.existsSync(pkgFile)) { process.stdout.write("jk2dt配置文件不存在,尝试从 package.json 读取 /n"); if (pkgFile.jk2dt && typeof pkgFile.jk2dt === "object") { importConfig = pkgFile.jk2dt; } else { process.stdout.write("package.json也没有对应配置项,采用默认配置 /n"); } } } module.exports = importConfig; 复制代码
在 markdown 里面提供一些占位符,来达到定制化的效果,最简单粗暴的模板替换的姿势
{{TIPS_BANNER}} {{GitRepoDesc}} ### --- {{GitRepoName}} --- **构建分支:** {{GitRepoBranchUrl}} {{PkgVersion}} {{GitBuildCommitLink}} {{GitRepoChangeLog}} {{RepoRecentTitle}} {{RepoRecentCommitMsg}} {{GitRepoActionType}} ### --- Jenkins --- **执行人:** {{PushBy}} **构建任务:** {{JK_JOBS_NAME}} **构建日志:** {{JK_JOBS_CONSOLE}} **构建状态:** {{JK_JOBS_STATUS}} **构建时间:** {{JK_JOBS_TIME}} 复制代码
const fs = require("fs"); const path = require("path"); function mdTemplateStr({ TipsBanner, TemplateName, PkgVersion, JobInfo: { JOB_NAME, JOB_BUILD_DISPLAY_NAME, JOB_BUILD_URL, JOB_STATUS, JOB_END_TIME }, GitInfo: { RepoUrl, RepoBranch, RepoName, RepoDesc, RepoBranchUrl, RepoChangeLog, RepoPushMan, RepoActionType, RepoRecentCommitMsg, BuildCommitMDLink } }) { const PlacehoderVar = { "{{TIPS_BANNER}}": TipsBanner, "{{JK_JOBS_NAME}}": JOB_NAME, "{{JK_JOBS_TIME}}": JOB_END_TIME, "{{JK_JOBS_CONSOLE}}": `[${JOB_BUILD_DISPLAY_NAME}](${JOB_BUILD_URL})`, "{{JK_JOBS_STATUS}}": JOB_STATUS, "{{GitRepoName}}": RepoName, "{{GitRepoDesc}}": RepoDesc, "{{GitRepoBranch}}": RepoBranch, "{{GitRepoBranchUrl}}": RepoBranchUrl, "{{PkgVersion}}": PkgVersion ? `**打包版本:** ${PkgVersion}` : "", "{{RepoRecentTitle}}": RepoRecentCommitMsg ? "**提交概要:**" : "", "{{RepoRecentCommitMsg}}": RepoRecentCommitMsg, "{{GitRepoChangeLog}}": RepoBranch === "master" ? `**变更日志:** [CHANGELOG](${RepoChangeLog})` : "", "{{GitBuildCommitLink}}": BuildCommitMDLink ? `**构建提交:** ${BuildCommitMDLink}` : "", "{{GitRepoActionType}}": RepoActionType ? `**推送行为:** ${RepoActionType}` : "", "{{PushBy}}": RepoPushMan }; let mdStr = fs.readFileSync( path.join(__dirname, `../template/${TemplateName}.md`) ); mdStr = mdStr.toString(); for (const [k, v] of Object.entries(PlacehoderVar)) { const re = new RegExp(k, "g"); mdStr = mdStr.replace(re, v); } return mdStr; } module.exports = mdTemplateStr; 复制代码
grep
查询 changlog.md (忽略大小写),用 execSync
同步执行 shell const path = require("path"); const pkgFile = require(path.resolve(process.cwd(), "./package.json")); const projectExecShellPath = process.cwd(); const fs = require("fs"); const { execSync } = require("child_process"); function rootExistChangelogFile() { const CHANGELOG = path.resolve(process.cwd(), "./CHANGELOG.md"); try { if (fs.existsSync(CHANGELOG)) { return true; } else { return !!execSync( `ls -l ${projectExecShellPath} | grep -i "changelog.md"` ).toString(); } return false; } catch (error) { return false; } } 复制代码
/** * 获取包的dist-tags */ function getPackageDistTag(branch) { if ( !pkgFile || !pkgFile.name || !pkgFile.main || ["master", "dev", "develop", "next"].indexOf(branch) === -1 ) { return ""; } let distTag; switch (branch) { case "master": distTag = "latest"; break; case "dev": distTag = "dev"; break; case "develop": distTag = "dev"; break; case "next": distTag = "next"; break; default: distTag = "dev"; break; } const execShell = `npm show ${pkgFile.name} dist-tags.${distTag} 2>/dev/null`; try { return execSync(execShell).toString(); } catch (error) { return ""; } } 复制代码
grep
function getLastNCommit(n = 5, branch) { if (n <= 0 || !branch) { return ""; } const readLineFilterResult = branch === "master" ? 'grep -E -i -v "lerna"' : 'grep -E -i -v "lerna|into|merge"'; const lineModify = "sed 's/^/> /g' |sed 's/$////n/g' "; const execShell = `git log --oneline -${n} ${branch}| ${readLineFilterResult} | ${lineModify} `; try { return execSync(execShell).toString(); } catch (error) { return ""; } } 复制代码
支持字符串和数组,强校验
function findValidAK(dict) { let tempObj = {}; for (let [k, v] of Object.entries(dict)) { if (isType.isString(v) && v) { tempObj[k] = v; } if ( isType.isObj(v) && Array.isArray(v.success) && Array.isArray(v.error) && v.success.length > 0 && v.error.length > 0 ) { tempObj[k] = v; } } return tempObj; } 复制代码
/** * * @param {*} obj - 对象 * @return {boolean} - 布尔值 * @description - 判断是否为Promise */ function isThenable(obj) { return ( !!obj && (typeof obj === "object" || typeof obj === "function") && typeof obj.then === "function" ); } function isString(o) { //是否字符串 return Object.prototype.toString.call(o).slice(8, -1) === "String"; } function isNumber(o) { //是否数字 return Object.prototype.toString.call(o).slice(8, -1) === "Number"; } function isObj(o) { //是否对象 return Object.prototype.toString.call(o).slice(8, -1) === "Object"; } module.exports = { isThenable, isString, isObj, isNumber }; 复制代码