- 开发工具
- CLI
- 文档
【免费下载链接】conventional-changelog
Generate changelogs and release notes from a project's commit messages and metadata.
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:
| 字段 | 类型 | 含义 |
|---|---|---|
header | string \| null | 提交信息第一行(原始文本) |
type、scope、subject | string \| null | 由headerPattern捕获组按headerCorrespondence命名拆分出的头部 |
merge | string \| null | 匹配到的 merge/PR 头(配置mergePattern后) |
body | string \| null | 正文部分 |
footer | string \| null | footer 部分 |
notes | { title, text }[] | 重要备注,典型如破坏性变更 |
references | CommitReference[] | issue 引用列表 |
mentions | string[] | 消息中的@-mention 用户名 |
revert | Record<string, string \| null> \| null | revert 提交被回退的目标字段 |
其中references的每一项结构(CommitReference):
| 字段 | 说明 |
|---|---|
raw | 原始引用文本,如#42 |
action | 引用动作关键词(如Closes),无则null |
owner | 被引用的 owner(owner/repository形式时),或null |
repository | 被引用的仓库名,或null |
issue | issue 编号 |
prefix | issue 前缀,如# |
除固定字段外,Commit 对象还叠加了一个CommitMeta(任意键值映射),用于承载fieldPattern匹配出的自定义块字段——这解释了为什么console.log(commit)会出现文档中未列出的扩展键。
解析原理:parse() 的逐行状态机
CommitParser.parse()的实现位于 CommitParser.ts,整体流程可以概括为一条自上而下的"消费行"状态机:
- 预处理:
trimNewLines去除首尾空行,按\r?\n切行;若配置了commentChar,还会用 truncateToScissor 截断 git 的剪刀线(<commentChar> ------------------------ >8 ------------------------,即git log中用于忽略 patch 的标记)之后的内容,并逐行过滤以commentChar开头的注释行;同时gpgFilter会剔除gpg:签名行。 parseMerge():若首行匹配mergePattern,把该行记入commit.merge,并按mergeCorrespondence命名捕获组,随后 conventional 头部从下一行开始解析。parseHeader():优先用breakingHeaderPattern匹配头部,再退回headerPattern;匹配成功后通过 assignMatchedCorrespondence 把捕获组按headerCorrespondence(默认['type', 'scope', 'subject'])写入 commit。若正则使用了命名捕获组,也可以直接用组名赋值。parseReferences():对 header、body、footer 中出现的文本反复匹配引用模式,抽出action + 前缀 + issue;如果文本里是 URL(内置url正则)则跳过,避免把链接误判为引用。- 主循环(逐行):在仍有行可读时循环调用——
parseMeta():匹配fieldPattern的-xxx-自定义块,把块内行追加到commit[字段名]上;parseNotes():当前行匹配notes正则(由noteKeywords构造)时,开启一条 note 并持续吞入后续行,直到遇到新的 note、meta 块或 footer token;parseBodyAndFooter():其余行按是否命中footerToken正则归入body或footer,并继续提取 references。
- 收尾:
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:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
headerPattern | RegExp | /^(\w*)(?:\(([\w$@.\-*/ ]*)\))?: (.*)$/ | 匹配 conventional 头部并拆分捕获组 |
headerCorrespondence | string[] | ['type', 'scope', 'subject'] | 头部捕获组的命名,顺序与headerPattern捕获组一一对应 |
breakingHeaderPattern | RegExp | 无 | 匹配破坏性变更头部(如feat!: ...),命中后生成BREAKING CHANGEnote |
mergePattern | RegExp | 无 | 匹配 merge/PR 头;命中后下一行才作为 conventional 头部解析 |
mergeCorrespondence | string[] | 无 | mergePattern捕获组命名 |
revertPattern | RegExp | /^Revert\s"([\s\S]*)"\s*This reverts commit (\w*)\.?/ | 匹配 revert 提交(对应git revert生成的默认消息) |
revertCorrespondence | string[] | ['header', 'hash'] | revertPattern捕获组命名 |
fieldPattern | RegExp | /^-(?=.*\w)(.*?)-$/ | 匹配 body 中的自定义-字段-块;要求字段名至少含一个单词字符,避免把 YAML 文档标记---误判为字段 |
noteKeywords | (string \| RegExp)[] | ['BREAKING CHANGE', 'BREAKING-CHANGE'] | 触发 note 的关键词,忽略大小写 |
notesPattern | (text) => RegExp | 无 | 自定义 note 正则构造函数,入参是关键词合并后的文本 |
issuePrefixes | (string \| RegExp)[] | ['#'] | issue 前缀列表,如gh-123中gh-即为前缀 |
issuePrefixesCaseSensitive | boolean | false | issue 前缀是否区分大小写 |
referenceActions | (string \| RegExp)[] | close、closes、closed、fix、fixes、fixed、resolve、resolves、resolved | 引用 issue 的动作关键词,忽略大小写 |
commentChar | string | 无 | 注释符;配置后启用注释过滤与剪刀线截断 |
warn | boolean \| (message) => void | noop | 仅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.txtCLI 选项一览(源码中的帮助文本 HELP):
| 选项 | 说明 |
|---|---|
-s, --separator | 提交之间的分隔符,默认为三个换行\n\n\n |
-p, --header-pattern | 头部匹配正则 |
-c, --header-correspondence | 逗号分隔的捕获组命名 |
-r, --reference-actions | 逗号分隔的 issue 引用动作关键词 |
-i, --issue-prefixes | 逗号分隔的 issue 前缀 |
--issue-prefixes-case-sensitive | issue 前缀区分大小写 |
-n, --note-keywords | 逗号分隔的 note 关键词 |
-f, --field-pattern | 自定义字段块匹配正则 |
--revert-pattern | revert 匹配正则 |
--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.
相关推荐
caveman-commit:为 feishin 生成精炼 Conventional Commits 提交信息的 Agent Skill 实战指南
caveman commit:为 feishin 生成精炼 Conventional Commits 提交信息的 Agent Skill 实战指南 本篇指南围绕
桌面应用音视频前端RedisInsight 提交信息规范实战:基于 Conventional Commits 的 Commit Message 编写指南
RedisInsight 提交信息规范实战:基于 Conventional Commits 的 Commit Message 编写指南 本文以 RedisIns
数据库客户端桌面应用后端前端数据可视化Feishin 提交信息规范实战:caveman-commit 技能与 Conventional Commits 落地指南
Feishin 提交信息规范实战:caveman commit 技能与 Conventional Commits 落地指南 导读 本文以 Feishin(一款现
桌面应用音视频前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考