彻底告别 GitHub 依赖:手把手教你定制 Changesets Changelog 生成器对接内网 GitLab
在现代前端 Monorepo 工程实践中,changesets 几乎是管理版本发布和 Changelog 生成的标准工具。然而,官方提供的 @changesets/changelog-github 插件深度绑定了 GitHub 的 URL 结构和 API。
对于大多数企业内网环境,我们使用的是私有部署的 GitLab。直接使用官方插件会导致 Changelog 中的提交记录链接、Merge Request 链接全部指向 GitHub 甚至变成死链。
本文将带你深度定制一个符合 GitLab 规范的 Changelog 生成器。
1. 核心原理:Changesets 需要什么?
@changesets/cli 在生成 CHANGELOG.md 时,会尝试调用配置中指定的 changelog 模块。这个模块必须导出一个符合 ChangelogFunctions 接口的对象,包含两个核心方法:
- getReleaseLine: 处理每个包在发布时生成的具体变更行(即我们在
.changeset/*.md中写的描述)。 - getDependencyReleaseLine: 处理依赖更新时的描述行。
我们的目标就是重新实现这两个函数,使其能够解析 GitLab 的提交信息并生成正确的内网链接。
2. 环境准备
首先,在你的工程目录下(通常是 packages/ 目录下)创建一个新的工具包,例如 my-changelog-generator:
mkdir -p packages/my-changelog-generator
cd packages/my-changelog-generator
npm init -y
安装必要的依赖:
npm install @changesets/types @changesets/get-github-info
# 虽然我们要对接 GitLab,但有时参考 GitHub 的解析逻辑会有帮助
3. 编写核心逻辑
创建 index.ts(或 index.js),我们将重点实现 GitLab 的链接拼接逻辑。
import { ChangelogFunctions } from "@changesets/types";
// 假设你的 GitLab 内网地址是 https://gitlab.company.com
const GITLAB_BASE_URL = "https://gitlab.company.com";
const changelogFunctions: ChangelogFunctions = {
getReleaseLine: async (changeset, type, options) => {
// 1. 获取项目路径,例如 "fe-group/my-project"
const repo = options?.repo;
if (!repo) {
throw new Error('请在 .changeset/config.json 的 changelog 配置中提供 "repo" 参数');
}
// 2. 解析第一行描述
const [firstLine, ...futureLines] = changeset.summary
.split("\n")
.map((l) => l.trimEnd());
// 3. 获取提交者和 MR 信息 (此处可以通过 git log 获取,或从 CI 环境变量读取)
// 为简化演示,我们假设 commit 信息可以从 changeset 对象中提取或通过 options 传入
const commitLink = `[[${changeset.id.slice(0, 7)}](${GITLAB_BASE_URL}/${repo}/-/commit/${changeset.id})]`;
// 4. 拼接最终的一行变更记录
let res = `- ${commitLink} ${firstLine}`;
if (futureLines.length > 0) {
res += `\n${futureLines.map((l) => ` ${l}`).join("\n")}`;
}
return res;
},
getDependencyReleaseLine: async (changesets, dependenciesUpdated, options) => {
if (dependenciesUpdated.length === 0) return "";
const repo = options?.repo;
const changesetLinks = changesets.map(
(c) => `[[${c.id.slice(0, 7)}](${GITLAB_BASE_URL}/${repo}/-/commit/${c.id})]`
);
const updatedDepenenciesList = dependenciesUpdated.map(
(dependency) => ` - ${dependency.name}@${dependency.newVersion}`
);
return [
`- 依赖更新 ${changesetLinks.join(", ")}`,
...updatedDepenenciesList,
].join("\n");
},
};
export default changelogFunctions;
4. 进阶:如何获取 GitLab Merge Request ID?
在 GitLab CI 流程中,我们往往希望能像 GitHub 那样自动标注 !123 这样的 MR 编号。
我们可以利用 GitLab CI 的环境变量 CI_MERGE_REQUEST_IID。但由于 Changeset 可能是在本地执行的,更通用的做法是通过 git 命令在本地解析:
import { execSync } from "child_process";
function getMergeRequestIid(commitHash: string) {
try {
// 搜索包含该 commit 的 merge commit 信息
const message = execSync(`git log --merges --format=%s --contains ${commitHash}`).toString();
// GitLab 默认的 merge 消息通常包含 "Merge branch '...' into '...' \n\n See merge request !123"
const match = message.match(/merge request !(\d+)/);
return match ? match[1] : null;
} catch (e) {
return null;
}
}
将此逻辑集成进 getReleaseLine 中,就可以生成类似 [MR !123](https://gitlab.../-/merge_requests/123) 的链接。
5. 在项目中使用
由于是内网工具,你不需要将其发布到公共 NPM。你可以直接在根目录的 .changeset/config.json 中引用本地路径。
首先,编译你的 TS 代码为 JS,然后修改配置:
{
"$schema": "https://unpkg.com/@changesets/config/schema.json",
"changelog": ["./packages/my-changelog-generator/dist/index.js", { "repo": "my-group/my-repo" }],
"commit": false,
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch"
}
注意: 路径必须以 ./ 或 ../ 开头,Changesets 才会识别为本地文件引用。
6. 避坑指南
- 权限问题:如果你的脚本需要通过 GitLab API 获取用户信息,记得在 CI 环境变量中注入
GITLAB_TOKEN。 - 换行符转换:在处理
changeset.summary时,注意 Windows 和 Linux 环境下的换行符兼容性。 - Monorepo 引用:如果你使用的是 pnpm workspace,建议将自定义生成器作为一个正常的 workspace package,并在根目录执行编译。
总结
通过自定义 Changesets Changelog 生成器,我们不仅解决了内网链接失效的问题,还可以根据公司的规范(比如关联 Jira 单号、自动生成 Release Note 分类)进行深度定制。这种工程化小细节的优化,能显著提升团队全链路研发的幸福感。