ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

@commitlint/config-pnpm-scopes:为 pnpm workspace 仓库自动生成 scope 枚举的 commitlint 共享配置

2026/9/20 22:51:01 拓冰建站 浏览量
@commitlint/config-pnpm-scopes:为 pnpm workspace 仓库自动生成 scope 枚举的 commitlint 共享配置 commitlint/config-pnpm-scopes为 pnpm workspace 仓库自动生成 scope 枚举的 commitlint 共享配置【免费下载链接】commitlint Lint commit messages项目地址: https://gitcode.com/gh_mirrors/co/commitlint导读commitlint/config-pnpm-scopes是 commitlint 官方维护的一个共享配置shareable config它专门服务于使用 pnpm workspace 的 monorepo 项目配置启用后commitlint 会自动扫描pnpm-workspace.yaml中声明的所有工作区包把每个包名作为合法的scope作用域白名单从而强制提交信息中的scope必须来自当前仓库的真实包名。读完本文你将掌握该配置的安装与接入方法、其底层自动发现包名的完整实现原理、与scope-enum规则的联动机制以及面对scope/a这类 scoped 包名时的处理规则。一、这是什么为 pnpm workspaces 量身定制的共享配置在 pnpm monorepo 中常见的提交格式是type(scope): subject例如build(api): change something in apis build。如果 scope 写错成仓库中不存在的包名例如test(foo)提交应被拦截。手工维护一份包名枚举清单既繁琐又容易过期而 commitlint/config-pnpm-scopes 的价值在于它把「当前 pnpm workspace 中有哪些包」这件事完全自动化每次运行校验时动态读取仓库结构生成scope-enum规则的枚举值。该配置的定位是Shareablecommitlintconfig enforcing pnpm workspaces names as scopes.它需要配合 commitlint/cli命令行校验与 commitlint/prompt-cli交互式提交工具一起使用。包的元信息见 package.json模块类型为 ESMtype: module声明了 Node.js22.12.0的引擎要求运行时依赖pnpm/read-project-manifest与read-yaml-file两个库前者用于精确解析各包的 manifest 文件后者用于解析pnpm-workspace.yaml。二、快速开始安装与接入在仓库根目录执行以下两条命令即可完成安装与配置生成npm install --save-dev commitlint/config-pnpm-scopes commitlint/cli echo module.exports {extends: [commitlint/config-pnpm-scopes]}; commitlint.config.js核心步骤拆解安装两个包commitlint/config-pnpm-scopes提供规则commitlint/cli提供commitlint命令行入口写入配置文件在commitlint.config.js中通过extends引入该共享配置module.exports采用 CommonJS 导出与 commitlint 的常规配置加载机制一致。之后的提交校验会按 Configuration guide 中描述的流程加载配置extends中的配置项会被递归合并进当前配置而本包通过extends暴露的rules与utils会一并生效。提示若想同时保留其他约定如 conventional commits 的 type 枚举可以将本配置与其他共享配置并列放入extends数组例如extends: [commitlint/config-conventional, commitlint/config-pnpm-scopes]。三、工作原理从 pnpm-workspace.yaml 到 scope 枚举配置的真正实现非常精简核心源码见 index.ts。整个配置对象只包含两个键export default { utils: { getProjects }, rules: { scope-enum: (ctx {}) getProjects(ctx).then((packages: any) [2, always, packages]), }, };utils.getProjects向 commitlint 的插件/配置机制暴露「获取包列表」的工具函数便于其他配置或插件复用rules[scope-enum]定义规则其返回值为三元组[2, always, packages]其中2表示错误级别severity即不满足时报错而非警告、always表示修饰符modifier即 scope 必须命中枚举、packages为动态计算出的合法 scope 列表。3.1 读取 workspace 声明requirePackagesManifest负责读取根目录的pnpm-workspace.yamlfunction requirePackagesManifest(dir: any) { return readYamlFile(path.join(dir, pnpm-workspace.yaml)).catch((err: any) { if (err.code ENOENT) { return null; } throw err; }); }文件缺失ENOENT时返回null不抛错——这意味着没有pnpm-workspace.yaml的普通仓库也能正常加载该配置其他读取错误如 YAML 语法错误会被向上抛出让用户在配置阶段就发现问题。3.2 将 packages 模式规范化为 manifest 匹配路径normalizePatterns把pnpm-workspace.yaml中的 glob 模式统一补全为指向包清单文件的模式function normalizePatterns(patterns: any) { const normalizedPatterns []; for (const pattern of patterns) { normalizedPatterns.push(pattern.replace(/\/?$/, /package.{json,json5,yaml})); } return normalizedPatterns; }例如声明packages: [packages/*]时实际用于 glob 的模式为packages/*/package.{json,json5,yaml}即同时覆盖package.json、package.json5、package.yaml三种包清单格式。3.3 用 glob 发现所有包并解析 manifestfindWorkspacePackages是发现流程的核心function findWorkspacePackages(cwd: any) { return requirePackagesManifest(cwd) .then(async (manifest: any) { const patterns normalizePatterns((manifest manifest.packages) || [**]); const entries: string[] []; for (const pattern of patterns) { for await (const entry of glob(pattern, { cwd, exclude: (p) p.includes(node_modules) || p.includes(bower_components), })) { entries.push(entry); } } return entries; }) .then((entries: any) { const paths Array.from(new Set(entries.map((entry: any) path.join(cwd, entry)))); return Promise.all(paths.map((manifestPath: any) readExactProjectManifest(manifestPath))); }) .then((manifests: any) { return manifests.map((manifest: any) manifest.manifest); }); }值得注意的实现细节默认兜底模式若pnpm-workspace.yaml缺失或其packages字段为空则使用[**]即递归扫描整个工作目录下的所有包清单文件排除目录glob 过程中显式排除node_modules与bower_components避免把依赖目录误判为工作区包去重通过Set对匹配到的 manifest 路径去重防止多个模式命中同一文件精确解析使用pnpm/read-project-manifest的readExactProjectManifest解析每个 manifest——该库能正确处理 JSON、JSON5、YAML 等格式并返回标准化的 manifest 对象。3.4 提取包名并构造 scope 白名单最后getProjects把 manifest 列表收敛为一组 scopefunction getProjects(context: any) { const ctx context || {}; const cwd ctx.cwd || process.cwd(); return findWorkspacePackages(cwd).then((projects: any) { const scopes projects.reduce((acc: any, project: any) { const name project.name; if (name) { acc.add(name.charAt(0) ? name.split(/)[1] : name); } return acc; }, new Set()); scopes.add(global); return Array.from(scopes).sort(); }); }四条关键规则忽略无name字段的包manifest 中没有name的包不会进入 scope 白名单scoped 包名取/后段对于scope/a这种 npm scoped 包名只保留a作为合法 scope——提交时写a(...)而非scope/a(...)这与 commitlint 的 scope 语法兼容性更好该处理方式与 commitlint/config-lerna-scopes 等同类配置保持一致始终追加global无论仓库里有多少包global永远是一个合法 scope用于表达「与具体包无关的全局性改动」排序输出最终列表按字典序排序保证枚举值稳定、可读。3.5 执行目录从哪来getProjects(context)的上下文ctx若提供了cwd则以其为扫描根目录否则回退到process.cwd()。这意味着在 commitlint/cli 的--cwd参数、lint 阶段传入的上下文等因素影响下配置会基于实际执行目录发现工作区包。四、运行示例实际校验效果原文档给出了一个完整的三段式示例。假设commitlint.config.js内容为{ extends: [commitlint/config-pnpm-scopes] }仓库结构如下三个包api、app、webpackages ├── api ├── app └── web那么1. scope 命中合法包名校验通过❯ echo build(api): change something in apis build | commitlint无任何输出即校验通过。2. scope 不在枚举内校验失败❯ echo test(foo): this wont pass | commitlint ⧗ --- input --- test(foo): this wont pass ✖ scope must be one of [api, app, web] [scope-enum] ✖ found 1 problems, 0 warningsfoo不在[api, app, web]中因此命中scope-enum规则以错误级别severity 2报告 1 个问题。3. 不带 scope 的提交不受影响❯ echo ci: do some general maintenance | commitlint校验通过。原因见scope-enum规则实现当提交信息没有 scope 时规则直接放行见下文第五节的规则源码。示例中的错误消息格式scope must be one of [api, app, web] [scope-enum]正是由 commitlint/rules/src/scope-enum.ts 中errorMessage [scope must,be one of [${scopes.join(, )}]]生成的枚举列表来自配置动态计算出的包名数组。五、底层联动scope-enum 规则如何消费枚举本配置的规则输出会被 commitlint 核心机制转化为 commitlint/rules 中scopeEnum规则的参数。其核心逻辑如下export const scopeEnum: SyncRulestring[] | { scopes: string[]; delimiters?: string[] } ( { scope }, when always, value [], ) { const scopes Array.isArray(value) ? value : value.scopes; if (!scope || !scopes.length) { return [true, ]; } // 按 / \ , 等分隔符切分 scope逐个校验是否在枚举中 // when always 时所有切分出的 scope 都必须在枚举中或整体命中枚举 };从实现中可以确认两点行为无 scope 直接通过!scope时返回[true, ]所以像ci: do some general maintenance这样不带 scope 的提交不受scope-enum约束如需强制必须写 scope应配合scope-empty规则多级 scope 支持规则会按/、\、,等分隔符切分 scope 后逐段校验scope-enum的完整参数说明与相关规则可查阅 Rules reference 与 Rules configuration。六、测试验证行为由用例锁定该包的行为有完整的测试覆盖见 index.test.ts测试基于仓库自带的三个 fixture 目录fixtures测试点断言结果对应 fixture配置导出rules键且包含scope-enumconfig.rules[scope-enum]为函数—规则三元组severity 为2modifier 为always—空 workspace 仓库枚举值为[global]empty普通 pnpm 仓库包名a、b枚举值为[a, b, global]basicscoped 仓库包名scope/a、scope/b枚举值仍为[a, b, global]前缀被剥离scoped其中 basic 的 workspace 声明 为packages: [packages/*]对应包的name见 packages/a/package.jsonname: a而 scoped fixture 的包 声明为name: scope/a验证了「scoped 包名取/后段」的行为。七、同类配置横向对比与选型commitlint 仓库中还有多个基于 monorepo 结构生成 scope 的共享配置可以从源码结构上对比各自的适用场景配置依赖的仓库元数据适用场景commitlint/config-pnpm-scopespnpm-workspace.yamlpnpm workspace 项目commitlint/config-lerna-scopeslerna.json的packages字段并向后兼容 npm/yarn workspaceslerna 管理的 monorepocommitlint/config-workspace-scopes原生 npm/yarn workspaces 声明非 pnpm/lerna 的 workspace 项目commitlint/config-nx-scopesnx.json/ project graphNx 管理的仓库commitlint/config-rush-scopesrush.jsonRush 管理的仓库如果你的项目使用 pnpm workspace直接选用本文介绍的配置即可如果用的是原生 npm/yarn workspaces则应考虑commitlint/config-workspace-scopeslerna 配置在检测到原生 workspaces 时也会输出迁移提示见 config-lerna-scopes 源码。八、小结commitlint/config-pnpm-scopes通过「动态计算scope-enum枚举值」的方式把 pnpm workspace 包名与提交规范强绑定接入只需一条安装命令和一行extends配置其实现index.ts展示了读取pnpm-workspace.yaml、规范化 glob 模式、遍历包清单、剥离 scoped 前缀并追加global的完整链路且行为由 index.test.ts 中的多组 fixture 用例锁定。对于以 pnpm 组织 monorepo 的团队这是让 commitlint scope 校验与仓库结构保持同步的即插即用方案。【免费下载链接】commitlint Lint commit messages项目地址: https://gitcode.com/gh_mirrors/co/commitlint创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考