☰
conventional-commits-parser 实战指南:把原始 Commit 消息解析为结构化对象的完整方案
2026/9/25 5:37:17 网站建设 项目流程
  • 开发工具
  • CLI
  • 文档

【免费下载链接】conventional-changelog

Generate changelogs and release notes from a project's commit messages and metadata.

项目地址:https://gitcode.com/gh_mirrors/co/conventional-changelog
点击查看免费下载

conventional-commits-parser是 conventional-changelog 工具链中负责"读懂 commit"的第一级流水线:它把一条原始的 conventional commit 文本拆解为包含type、scope、subject、notes(破坏性变更)、references(issue 引用)、mentions等字段的纯 JS 对象。读完本文,你将掌握它的三种入口 API(CommitParser、parseCommits、parseCommitsStream)、全部可配置解析选项及其默认值、CLI 用法,以及从源码层面理解逐行解析状态机的工作方式,从而为自己的 commit 规范定制解析器。

它在整个 changelog 流水线中的位置

从项目文档结构看,changelog 生成遵循parser → filter → writer的三段式流水线:parser 负责解析,conventional-commits-filter负责过滤,conventional-changelog-writer负责按模板写出。conventional-commits-parser输出的结构化对象,正是后两级的直接输入;顶层的 conventional-changelog 在读取项目提交历史时,底层使用的也是它。原始 commit 数据通常来自 git,例如@conventional-changelog/git-client的getRawCommits()。

相关文档入口:

  • 包级说明:packages/conventional-commits-parser/README.md
  • 在线文档(仓库内 Markdown 源):Introduction、JS API、CLI

安装与快速上手

该包是ESM-only、要求Node.js >= 22(见 package.json 中的engines字段),因此只能以import方式使用,package.json中type: module且exports指向模块入口。

# pnpm pnpm add conventional-commits-parser # yarn yarn add conventional-commits-parser # npm npm i conventional-commits-parser

最基础的使用方式(继承自 README 的示例):

import { CommitParser } from 'conventional-commits-parser' const parser = new CommitParser() const commit = parser.parse( 'feat(scope): broadcast $destroy event on scope destruction\nCloses #1' ) console.log(commit)

输出结构:

{ type: 'feat', scope: 'scope', subject: 'broadcast $destroy event on scope destruction', header: 'feat(scope): broadcast $destroy event on scope destruction', footer: 'Closes #1', references: [{ action: 'Closes', issue: '1', raw: '#1', prefix: '#', // owner、repository 等字段 }], // merge / revert / notes / mentions 等字段 }

解析结果 Commit 对象解剖

parse()返回的对象由 createCommitObject 初始化为固定的基础字段,再按选项追加自定义字段。基础字段定义在 types.ts:

字段类型含义
headerstring \| null提交信息第一行(原始文本)
type、scope、subjectstring \| null由headerPattern捕获组按headerCorrespondence命名拆分出的头部
mergestring \| null匹配到的 merge/PR 头(配置mergePattern后)
bodystring \| null正文部分
footerstring \| nullfooter 部分
notes{ title, text }[]重要备注,典型如破坏性变更
referencesCommitReference[]issue 引用列表
mentionsstring[]消息中的@-mention 用户名
revertRecord<string, string \| null> \| nullrevert 提交被回退的目标字段

其中references的每一项结构(CommitReference):

字段说明
raw原始引用文本,如#42
action引用动作关键词(如Closes),无则null
owner被引用的 owner(owner/repository形式时),或null
repository被引用的仓库名,或null
issueissue 编号
prefixissue 前缀,如#

除固定字段外,Commit 对象还叠加了一个CommitMeta(任意键值映射),用于承载fieldPattern匹配出的自定义块字段——这解释了为什么console.log(commit)会出现文档中未列出的扩展键。

解析原理:parse() 的逐行状态机

CommitParser.parse()的实现位于 CommitParser.ts,整体流程可以概括为一条自上而下的"消费行"状态机:

  1. 预处理:trimNewLines去除首尾空行,按\r?\n切行;若配置了commentChar,还会用 truncateToScissor 截断 git 的剪刀线(<commentChar> ------------------------ >8 ------------------------,即git log中用于忽略 patch 的标记)之后的内容,并逐行过滤以commentChar开头的注释行;同时gpgFilter会剔除gpg:签名行。
  2. parseMerge():若首行匹配mergePattern,把该行记入commit.merge,并按mergeCorrespondence命名捕获组,随后 conventional 头部从下一行开始解析。
  3. parseHeader():优先用breakingHeaderPattern匹配头部,再退回headerPattern;匹配成功后通过 assignMatchedCorrespondence 把捕获组按headerCorrespondence(默认['type', 'scope', 'subject'])写入 commit。若正则使用了命名捕获组,也可以直接用组名赋值。
  4. parseReferences():对 header、body、footer 中出现的文本反复匹配引用模式,抽出action + 前缀 + issue;如果文本里是 URL(内置url正则)则跳过,避免把链接误判为引用。
  5. 主循环(逐行):在仍有行可读时循环调用——
    • parseMeta():匹配fieldPattern的-xxx-自定义块,把块内行追加到commit[字段名]上;
    • parseNotes():当前行匹配notes正则(由noteKeywords构造)时,开启一条 note 并持续吞入后续行,直到遇到新的 note、meta 块或 footer token;
    • parseBodyAndFooter():其余行按是否命中footerToken正则归入body或footer,并继续提取 references。
  6. 收尾:parseBreakingHeader()在头部命中breakingHeaderPattern且尚无 note 时,把捕获内容补为一条BREAKING CHANGEnote;parseMentions()用硬编码的/@([\w-]+)/g收集 @提及(见 regex.ts);parseRevert()用revertPattern识别 revert 提交;最后cleanupCommit()修剪 body/footer/notes 的空行,并按action + raw(小写)对 references去重。

几个值得注意的判定细节:

  • footer 的判定遵循 git trailer 约定:getFooterTokenRegex 生成的正则为^(?:BREAKING CHANGE|[\w-]+)(?::\s+|\s+(?:issuePrefixes))+.+(忽略大小写),即"行首是单词 token,后跟冒号或 issue 前缀"才算 footer token;缩进的key: value行不算 footer,会继续留在 body 里。
  • references 去重:同一提交里重复出现相同的Closes #1只保留一次(cleanupCommit)。
  • 空输入直接抛错:parse()对空白字符串抛TypeError('Expected a raw commit')。

全部解析选项与默认值

三个入口函数接受同一套选项,默认值集中在 options.ts,选项的类型与逐条说明见 types.ts:

选项类型默认值说明
headerPatternRegExp/^(\w*)(?:\(([\w$@.\-*/ ]*)\))?: (.*)$/匹配 conventional 头部并拆分捕获组
headerCorrespondencestring[]['type', 'scope', 'subject']头部捕获组的命名,顺序与headerPattern捕获组一一对应
breakingHeaderPatternRegExp无匹配破坏性变更头部(如feat!: ...),命中后生成BREAKING CHANGEnote
mergePatternRegExp无匹配 merge/PR 头;命中后下一行才作为 conventional 头部解析
mergeCorrespondencestring[]无mergePattern捕获组命名
revertPatternRegExp/^Revert\s"([\s\S]*)"\s*This reverts commit (\w*)\.?/匹配 revert 提交(对应git revert生成的默认消息)
revertCorrespondencestring[]['header', 'hash']revertPattern捕获组命名
fieldPatternRegExp/^-(?=.*\w)(.*?)-$/匹配 body 中的自定义-字段-块;要求字段名至少含一个单词字符,避免把 YAML 文档标记---误判为字段
noteKeywords(string \| RegExp)[]['BREAKING CHANGE', 'BREAKING-CHANGE']触发 note 的关键词,忽略大小写
notesPattern(text) => RegExp无自定义 note 正则构造函数,入参是关键词合并后的文本
issuePrefixes(string \| RegExp)[]['#']issue 前缀列表,如gh-123中gh-即为前缀
issuePrefixesCaseSensitivebooleanfalseissue 前缀是否区分大小写
referenceActions(string \| RegExp)[]close、closes、closed、fix、fixes、fixed、resolve、resolves、resolved引用 issue 的动作关键词,忽略大小写
commentCharstring无注释符;配置后启用注释过滤与剪刀线截断
warnboolean \| (message) => voidnoop仅parseCommits/parseCommitsStream有效;true表示严格模式(解析失败即抛出)

这些选项如何落地为正则,可以在 regex.ts 中看到:noteKeywords经joinOr合并(字符串自动转义)后拼成^(?:\*\s+)?(关键词): (文本)形式的忽略大小写正则;referenceActions生成(动作)(\s+(.*?))(?=下一个动作|$)的迭代匹配;issuePrefixes生成引用片段正则(前缀)(issue 编号),编号字符集为[\w-]+,且以空白、行尾或,;.)]作为边界。

一个来自项目文档的易错点:默认headerPattern不捕获!破坏性语法——要识别feat!: ...这种头部简写,需要显式传入breakingHeaderPattern,或者在 footer 里写BREAKING CHANGE:;仓库内的 preset(如conventional-changelog-conventionalcommits)会替你配好这些。

批量解析:parseCommits 与 parseCommitsStream

单条解析用CommitParser;处理整段提交历史(典型来源是git-client的getRawCommits())时,stream.ts 提供两个批量入口:

import { parseCommits, parseCommitsStream } from 'conventional-commits-parser' import { pipeline } from 'node:stream/promises' import { Readable } from 'node:stream' // 方式一:异步生成器函数,配合 stream.pipeline await pipeline( rawCommits, parseCommits(options), async function* (commits) { for await (const commit of commits) { console.log(commit) } } ) // 方式二:Node.js Transform 流 Readable.from(rawCommits) .pipe(parseCommitsStream()) .on('data', commit => console.log(commit))

两者的实现关系很直接:parseCommitsStream(options)内部就是Transform.from(parseCommits(options))。parseCommits返回一个 async generator 函数,遍历可迭代/异步可迭代的原始提交,逐条调用CommitParser#parse;解析失败时的行为由warn选项控制——默认静默跳过(noop),传true则立即抛出(严格模式),传函数则把错误字符串交给你的回调处理(stream.ts)。这也解释了为什么流式入口适合"尽力而为"地解析整段git log输出。

CLI 用法

package.json的publishConfig.bin声明了可执行命令conventional-commits-parser,其实现位于 cli/index.ts。它的设计目标是"练习写 commit 消息或从文件解析消息":不带文件参数进入交互式 shell,带参数则解析文件内容,也可从 stdin 读取。

Usage conventional-commits-parser [-s <commit-separator>] conventional-commits-parser [-s <commit-separator>] <path> [<path> ...] cat <path> | conventional-commits-parser [-s <commit-separator>] Example conventional-commits-parser conventional-commits-parser log.txt cat log.txt | conventional-commits-parser conventional-commits-parser log2.txt -s '===' >> parsed.txt

CLI 选项一览(源码中的帮助文本 HELP):

选项说明
-s, --separator提交之间的分隔符,默认为三个换行\n\n\n
-p, --header-pattern头部匹配正则
-c, --header-correspondence逗号分隔的捕获组命名
-r, --reference-actions逗号分隔的 issue 引用动作关键词
-i, --issue-prefixes逗号分隔的 issue 前缀
--issue-prefixes-case-sensitiveissue 前缀区分大小写
-n, --note-keywords逗号分隔的 note 关键词
-f, --field-pattern自定义字段块匹配正则
--revert-patternrevert 匹配正则
--revert-correspondence逗号分隔的 revert 字段命名
-v, --verbose解析失败时打印警告(而非静默)

命令行字符串到解析器选项的转换逻辑见 cli/options.ts:正则类选项用new RegExp(...)构造,列表类选项按逗号切分并去除空白;-v开启时warn绑定console.warn,未开启时 CLI 默认以严格模式(warn: true)运行,即任何一条提交解析失败都会使命令以非零码退出。输入侧按"指定文件 → TTY 交互 → stdin"三种来源分流(cli/index.ts),再经pipeline(inputStream, parseCommits(options), stringify, process.stdout)完成解析与输出。仓库测试目录中的 log1.txt 等 fixture 就是这类文件的实际形态,可用于对照 CLI 解析行为。

定制解析器:识别自定义前缀与动作

如果你的仓库使用GH-17这类前缀,或者用refs这样的动作词,只需构造时传入选项即可:

import { CommitParser } from 'conventional-commits-parser' const parser = new CommitParser({ issuePrefixes: ['#', 'GH-'], referenceActions: ['closes', 'fixes', 'refs'] }) parser.parse('fix(api): handle timeouts\n\nrefs GH-17')

此时references中会得到{ raw: 'GH-17', prefix: 'GH-', issue: '17', action: 'refs', ... }。类似地,通过覆盖headerPattern/headerCorrespondence可以适配非标准头部,通过noteKeywords可以追加企业自定义的破坏性变更标记词,通过commentChar(如#)可以让解析器忽略git log --format=...输出中携带的注释与>8剪刀线内容。

小结

conventional-commits-parser以CommitParser#parse的逐行状态机为核心,把自由文本的 commit 消息规范化为可被 filter、writer 直接消费的结构化对象;parseCommits/parseCommitsStream提供了面向整段历史的流式批量能力与可配置的容错策略,CLI 则提供了文件/stdin 解析与 commit 练习场景。所有行为——默认正则、捕获组命名、footer 判定、引用去重——都集中在 src/options.ts、src/regex.ts 与 src/CommitParser.ts 中,行为细节可进一步对照 CommitParser.spec.ts 与 stream.spec.ts 的测试用例验证。

  • 开发工具
  • CLI
  • 文档

【免费下载链接】conventional-changelog

Generate changelogs and release notes from a project's commit messages and metadata.

项目地址:https://gitcode.com/gh_mirrors/co/conventional-changelog
点击查看免费下载
上一篇:如何在Joplin中配置VSCode作为外部编辑器:完整指南
下一篇:Odoo测试自动化终极指南:pytest单元测试与端到端测试框架详解 🚀

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询