资讯动态

从第三方 CHANGELOG 到 PR 变更日志:深入 Renovate 的 changelog 解析管线(以 adapter-utils.md 测试夹具为例)

发布时间:2026/9/13 11:26:57 来源:尧图企业网站定制
从第三方 CHANGELOG 到 PR 变更日志深入 Renovate 的 changelog 解析管线以 adapter-utils.md 测试夹具为例【免费下载链接】renovateHome of the Renovate CLI: Cross-platform Dependency Automation by Mend.io项目地址: https://gitcode.com/GitHub_Trending/re/renovateRenovate 在升级依赖并创建 Pull Request 时会在 PR 正文中附带变更日志changelog帮助开发者快速判断这次升级改了什么。本文以仓库中一份真实的测试夹具 adapter-utils.md 为分析样本结合 release-notes.ts、gitlab/index.ts 与 release-notes.spec.ts 的源码实现完整讲解 Renovate 是如何发现、拉取、切分、匹配并清洗第三方项目的 CHANGELOG 文件最终把指定版本的变更条目拼进 PR 正文的。读完本文你将理解 Renovate 变更日志管线的完整调用链、标题解析与版本匹配策略以及它如何用真实世界样本来驱动单元测试。一、先认识这份文档一份真实的 GitLab 项目 CHANGELOG 样本adapter-utils.md 位于lib/workers/repository/update/pr/changelog/__fixtures__/目录与angular-js.md、jest.md、js-yaml.md、yargs.md等一同作为变更日志解析的测试输入。它本身并非 Renovate 的说明文档而是itentialopensource/adapter-utils 项目 CHANGELOG.md 的逐字副本被用作release-notes.spec.ts中 GitLab 平台解析用例的 fixture 数据。这份样本的独特价值在于它的不规整性早期版本段# Current Version: 4.3.1及之前是典型的Keep-a-Changelog 风格## New Features、## Improvements、## Bug Fixes、## Deprecation、## Security分类齐全且版本以__3.10.0 [12-05-2018]__这种加粗内联形式嵌在条目里后期版本段## 4.33.0 [05-15-2020]起切换为逐版本平铺的紧凑格式每个版本一个二级标题含日期、条目、Closes ADAPT-xxx工单引用与See merge request itentialopensource/adapter-utils!177合并请求引用。这种同一文件内部混用两种风格的结构恰好是解析器最容易出错、也最值得测试覆盖的场景。二、样本的数据形态Renovate 需要解析的四种结构要素逐行阅读 adapter-utils.md可以归纳出 Renovate 解析器必须处理的全部结构要素2.1 逐版本平铺段新版格式第 2 行起## 4.33.0 [05-15-2020] * add new auth, fix accept header and base path in mock Closes ADAPT-207 See merge request itentialopensource/adapter-utils!177 --- ## 4.32.3 [04-30-2020] * set username and password in token entitypath Closes ADAPT-198 See merge request itentialopensource/adapter-utils!176这段结构包含版本标题## 版本号 [YYYY-MM-DD]版本号与日期之间是空格而非链接语法条目列表以*开头的无序列表描述本次变更内容工单引用Closes ADAPT-207形式的单行文本合并请求引用See merge request itentialopensource/adapter-utils!177分隔线---将相邻版本隔开。fixture 覆盖了从4.33.0 [05-15-2020]到4.24.0 [11-01-2019]的完整平铺段每一条都保持标题 条目 引用 分隔线的统一骨架例如4.30.14记录add in double check for starting slashes in the path4.17.2记录update regex for , and ()4.9.3记录add filter to response after field is found, based on respFilter in schema。2.2 分类标题段旧版格式从# Current Version: 4.3.1 [03-26-2019]开始样本切换到另一种组织方式# Current Version: 4.3.1 [03-26-2019] ## New Features ## Improvements ## Bug Fixes ## Deprecation ## Security在每个分类下版本信息以__版本 [日期]__内联加粗形式出现在条目开头例如* __3.10.0 [12-05-2018]__ - New methods have been add to: ... * __3.9.0 [12-04-2018]__ - The external name on schemas can now be at the same level or lower * __2.1.0 [08-17-2018]__ - These libraries now support token re-use and expiration ...这一段的版本号不在标题里而在正文条目里的特征正是 Renovate 需要在正文中搜索版本号的原因见 4.3 节。2.3 大版本发布列表如 2.0.0样本在## New Features下还包含一次大版本集中发布说明2.0.0 [08-13-2018]一口气列出 PH-16044、PH-16024、PH-16125、PH-15075、PH-14311、PH-16053、PH-16141、PH-16239、PH-16268、PH-15718 共 10 项改动涵盖通用调用、mock 数据、代理能力、Base64 认证、加解密、action 数组化、entitypath 语法变更等。2.4 外层壳a name锚点与文末#\n##哨兵源码 gitlab/index.ts 在取回 changelog 原文后会执行const changelogMd ${fileRes.body}\n#\n##;即在文末追加\n#\n##两个伪标题作为哨兵保证最后一节之后一定存在更低层级的标题使切分逻辑见 4.1 节能正确闭合最后一段。同时 release-notes.ts 会先用正则剔除 Keep-a-Changelog 常见的a name.../a锚点行避免其干扰标题切分。三、前置阶段Renovate 如何发现并拉取 CHANGELOG 文件在解析任何内容之前Renovate 必须先回答两个问题changelog 文件在哪里以及要不要拉取它3.1 跳过名单与缓存release-notes.ts 定义了repositoriesToSkipMdFetching当前包含facebook/react-native与react/react-native——这两个仓库被显式跳过 MD 抓取回退到 GitHub release 接口shouldSkipChangelogMd见 release-notes.ts。文件查找结果还带有两层缓存getReleaseNotesMdFile使用内存缓存memCache缓存键为getReleaseNotesMdFilev2-repository-sourceDirectory-apiBaseUrlrelease-notes.ts最终解析结果则进入changelog-platform-notesv2命名空间的包缓存release-notes.ts缓存时长由releaseNotesCacheMinutes决定发布不足一周缓存 55 分钟、不足半年约 1 天、更久约 10 天。3.2 GitLab 侧的文件定位对于 GitLab 仓库gitlab/index.ts 的getReleaseNotesMd按以下顺序工作调用GET /projects/url编码仓库/repository/tree?per_page100可选pathsourceDirectory列出仓库树并开启分页paginate: true从返回的 tree 节点中过滤出type blob的文件用changelog-filename-regex正则匹配文件名筛出形如CHANGELOG.md、CHANGELOG、CHANGELOG.json的候选若有多个候选用 common.ts 的compareChangelogFilePath排序优先.md/.markdown/.mkd其次.txt/.text其余类型垫底——这正是为了避免出现CHANGELOG.json而错过CHANGELOG.md的问题取排序后第一个文件调用GET /projects/仓库/repository/blobs/blob_id/raw拉取原始文本在文末拼接\n#\n##哨兵后返回{ changelogFile, changelogMd }。adapter-utils.md这个 fixture 的取值场景正是模拟上述第 5 步拿到的原始内容。四、核心解析sectionize 切分、标题匹配与正文提取这是整条管线的灵魂实现在 release-notes.ts 的getReleaseNotesMd中。4.1 按标题级别切分sectionizefunction sectionize(text: string, level: number): string[] { const tokens markdown.parse(text, {}); tokens.forEach((token) { if (token.type heading_open) { const lev token.tag.substring(1); if (lev level) { sections.push([lev, token.map![0]]); } } }); sections.push([-1, lines.length]); // 取出所有恰好等于 level 的节 }解析器使用markdown-it仅启用heading、lheading、fence三个规则见 release-notes.ts把整个 changelog 文本 token 化然后从 level 1 到 level 7 依次尝试只要某个级别能切出至少 2 节就按该级别遍历每个 section。这样无论样本用的是#如# Current Version: 4.3.1还是##如## 4.33.0 [05-15-2020]都能被正确切分。对adapter-utils.md而言顶层# Current Version: 4.3.1与# Previous Version: 1.3.2构成 level 1 的两节而平铺段的## 4.33.0 [05-15-2020]系列则会在 level 2 被切出。\n#\n##哨兵保证了最后一个版本节4.24.0之后有更低级标题用于闭合。4.2 标题内版本匹配Look for version in title对每个 section解析器做三件事const deParenthesizedSection section.replace(regEx(/[[\]()]/g), ); const [heading] deParenthesizedSection.split(newlineRegex); const title heading.replace(regEx(/^\s*#*\s*/), ).split( ).filter(isTruthy); const body section.replace(regEx(/.*?\n(?:-{3,}\n)?/), ).trim();去括号化把所有[、]、(、)替换为空格避免链接语法干扰分词取标题分词剥掉开头的#按空格拆词去正文前缀用.*?\n(?:-{3,}\n)?把标题行及紧随其后的---分隔线从正文中剥离。随后遍历标题中的每个词只要某个词包含目标版本号且不是 URL即命中if (word.includes(version) !isHttpUrl(word)) { return { body: await linkifyBody(project, body), url, notesSourceUrl }; }对## 4.33.0 [05-15-2020]而言标题分词后是[4.33.0, [05-15-2020]]去括号后目标版本4.33.0直接命中正文即* add new auth, fix accept header and base path in mock\n\nCloses ADAPT-207\n\nSee merge request itentialopensource/adapter-utils!177。4.3 正文内版本匹配monorepo 与内联版本场景fixture 中的旧版格式__3.10.0 [12-05-2018]__内联加粗、# Current Version: 4.3.1这类不含日期版本对无法靠标题命中。解析器为此实现了第二套策略release-notes.ts标题中必须含YYYY-MM-DD日期格式releasesRegex规避没有日期的普通标题正文中必须同时出现packageName与目标version适用于 monorepo 中包名与版本散落在条目里的情况逐行检查时跳过 Markdown 链接引用定义[1.2.3]: https://…形式即 Keep-a-Changelog 文末常见的 compare 链接表否则每一行都会伪命中同样排除含 URL 的行。测试用例parses when version contained in the body 0.14.0与ignores trailing link reference definitions when searching body见 release-notes.spec.ts正是为验证这套策略而设。五、输出后处理massageBody、linkify 与 URL 生成命中后返回结构遵循 types.ts 中的ChangeLogNotes接口body、url、notesSourceUrl。其中notesSourceUrl由source.getNotesSourceUrl(baseUrl, repository, changelogFile)生成。对 GitLabgitlab/source.ts 的GitLabChangeLogSource以gitlab-tags为 datasourcegetAPIBaseUrl返回baseUrlapi/v4/最终拼接出如baseUrlitentialopensource/adapter-utils/blob/HEAD/CHANGELOG.md的源文件地址url是锚点链接由getReleaseNotesMdAnchorUrl(notesSourceUrl, parenthesizedHeading)对原始标题保留括号做 slug 化生成如#4330-05-15-2020。测试中对4.33.0的断言正是…/CHANGELOG.md#4330-05-15-2020body经过 massageBody 清洗统一\r\n、剔除 semantic-release 的a name行与 compare 链接、把#/##/####逐级降为###/####/#####代码块内的#会被保护不处理最后 trim 空白。最后linkifyBody会把正文中的仓库引用如itentialopensource/adapter-utils!177这类 merge request 引用转成可点击链接。在getReleaseNotes路径GitLab release API 而非 MD 文件中releaseNotesResult会为 GitLab 拼接baseUrlrepository/tags/tag作为 URL并对非https://gitlab.com/的实例执行同样的 linkifyrelease-notes.ts。六、测试如何验证release-notes.spec.ts 中的适配器样本用例fixture 的最终价值落在测试上。在 release-notes.spec.ts 的parses adapter-utils 4.33.0用例中用httpMock模拟 GitLab 的两个接口GET /api/v4/projects/itentialopensource%2Fadapter-utils/repository/tree?per_page100返回gitlabTreeResponseGET …/repository/blobs/abcd/raw返回本 fixture 的内容以repository: itentialopensource/adapter-utils、version: 4.33.0、gitRef: 4.33.0调用getReleaseNotesMd断言结果notesSourceUrl指向…/blob/HEAD/CHANGELOG.mdurl带#4330-05-15-2020锚点正文以- add new auth, fix accept header and base path in mock\n开头注意massageBody的降级规则把原文档中的## 4.33.0节标题降为####后从正文剥离正文从第一条*条目开始且*被linkify处理为-列表项正文包含Closes ADAPT-207与See merge request itentialopensource/adapter-utils!177正文以***结尾相邻版本不泄漏正文既不包含ADAPT-198也不包含set username and password in token entitypath——这正是sectionize精确切分的直接验证。同文件还包含handles gitlab sourceDirectory用例release-notes.spec.ts把 tree 响应中的每个文件路径加上packages/foo/前缀后同一份 fixture 应被解析出notesSourceUrl指向…/blob/HEAD/packages/foo/CHANGELOG.md验证了sourceDirectory场景下同一 changelog 内容、不同来源 URL的正确性。七、更完整的调用链从版本列表到 PR 正文将上述环节串起来完整的调用链是addReleaseNotesrelease-notes.ts遍历每个待展示的版本先查包缓存未命中则依次尝试getReleaseNotesMd解析 CHANGELOG 文件→getReleaseNotesGitLab/GitHub release API→ 退化为 compare URL缓存时长按版本发布日期动态决定当config.fetchChangeLogs pr时会受平台 PR 正文长度上限约束累计正文达到platform.maxBodyLength()后停止继续抓取更旧版本因为按从新到旧顺序最新变更始终可见。adapter-utils.md作为 fixture精确覆盖了第 2 步中GitLab 头部平铺格式 旧版分类格式 文末哨兵 合并请求引用这一组合是 Renovate 保证不同格式 changelog 都能被正确解析的基石性测试样本。八、小结与延伸阅读围绕 adapter-utils.md 这 1000 余行的真实 changelog 样本可以总结出 Renovate 变更日志解析的三条设计原则格式无关通过 1~7 级标题逐级探测切分兼容 Keep-a-Changelog、逐版本平铺、内联加粗版本等异构风格精确不泄漏sectionize 哨兵标题 链接引用定义过滤确保只提取目标版本、不污染相邻版本真实样本驱动测试直接用真实项目的 changelog 全文让解析器始终面对现实世界的脏数据。如果你希望进一步深入可以从以下文件继续阅读解析与清洗主逻辑release-notes.tsGitLab 侧文件定位与 release 列表gitlab/index.tschangelog 文件优先级排序common.ts数据结构定义types.ts覆盖 adapter-utils 样本的测试用例release-notes.spec.ts其他同类测试样本jest.md、yargs.md、js-yaml.md、gitter-webapp.md、angular-js.md均在fixtures目录下【免费下载链接】renovateHome of the Renovate CLI: Cross-platform Dependency Automation by Mend.io项目地址: https://gitcode.com/GitHub_Trending/re/renovate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价