拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Renovate 的 semver-coerced 版本方案:用宽松语义化版本识别非标准版本号

Renovate 的 semver-coerced 版本方案用宽松语义化版本识别非标准版本号【免费下载链接】renovateHome of the Renovate CLI: Cross-platform Dependency Automation by Mend.io项目地址: https://gitcode.com/GitHub_Trending/re/renovateRenovate 在识别依赖版本时默认使用semver-coercedCoerced Semantic Versioning版本方案它是 Semantic Versioning 2.0 的一个宽容变体能够把v1、2.1、~3.1、1.1-foo这类非严格 SemVer 的输入强制类型转换coerce为严格的 SemVer 版本。本文以仓库中的方案说明文档 readme.md 为主体结合其完整实现 lib/modules/versioning/semver-coerced/index.ts 与测试 lib/modules/versioning/semver-coerced/index.spec.ts讲清楚它的转换规则、每个 API 函数的实际行为、它作为 Renovate 默认版本方案的使用场景以及与严格semver方案的差异边界。一、什么是 semver-coerced宽松转换规则根据方案文档的定义Renovate 的 Coerced Semantic Versioning 是一个启用了版本转换coercion的 SemVer 2.0 宽容变体。它把非严格 SemVer 格式的版本输入非常宽容地翻译为严格 SemVer官方文档给出的典型转换示例如下原始输入转换后coerced说明v11.0.0前缀v 仅主版本号2.12.1.0缺少补丁号patch~3.13.1.0携带范围前缀~1.1-foo1.1.0预发布后缀 缺少补丁号转换规则的具体语义来自 npmsemver包的 Coercion 机制从字符串中提取数字序列补齐缺失的 minor/patch 段为0忽略前缀字符与常见范围符。文档同时给出了一条重要边界由于该方案非常宽容它并不会对版本范围range本身做转换。范围相关的函数只接受严格的 SemVer 作为输入其范围行为与 Renovate 的semver版本方案完全等价。也就是说宽容只作用于版本号一侧而^1.0.0、~1.2.3这样的 range 表达必须严格合法。这一边界在源码中体现为模块导出的supportsRanges false常量index.ts并向下游声明它不提供独立的范围计算能力。二、与严格 semver 方案的对比Renovate 仓库内置了严格版semver方案lib/modules/versioning/semver/index.ts两者 API 结构相同区别集中在严格输入与coerce 输入严格semver方案直接复用semver包的valid、compare、maxSatisfying等函数输入必须是标准 SemVer 字符串17.04.0这类非常规写法会直接判定无效semver-coerced方案的每个函数在调用semver包之前都会先执行semver.coerce()把输入转换一遍。从 semver/index.ts 中可以看到一条值得注意的注释If this is left as an alias, inputs like 17.04.0 throw errors如果直接作为别名使用17.04.0这类输入会抛错。semver-coerced沿用了同样的处理方式——isVersion不是简单转引isValid而是显式定义为isValid(input)即coerce 之后能否得到合法 SemVer从而避免非常规版本在解析阶段直接抛异常而是优雅地返回布尔值。semver-coerced的isBreaking也复用了严格方案的实现先把两个版本各自 coerce 为字符串再委托给 semver 方案的 isBreaking。这意味着破坏性判断遵循 SemVer 的通行约定主版本号变化算破坏性更新而 v0.x 阶段的所有 minor 变化如0.1→0.2.1以及任何包含不稳定版本号的升级都被视为潜在破坏性。测试用例 index.spec.ts 印证了这些行为isBreaking(1.0, 1.0.1)为falseisBreaking(1.0.0, 2)为trueisBreaking(0.1, 0.2.1)为true。三、源码剖析各 API 函数如何宽容semver-coerced模块最终导出一个完整的VersioningApi对象index.ts#L153-L171覆盖了 types.ts 中定义的版本方案接口。下面按功能分组说明关键实现。1. 校验类isValid / isVersion / isSingleVersionfunction isValid(version: string): boolean { return !!semver.valid(semver.coerce(version)); }isValid的逻辑是coerce 之后再valid因此~1.2.3、^1.2.3、1.2.3这类范围串会被认为包含一个合法版本测试 isValid 用例 确认了这一点同时它也会正确拒绝 GitHub 仓库引用如renovatebot/renovate#master。isSingleVersion则解决了一个更微妙的问题coercion 既接受版本也接受范围所以必须人工判断字符串是否以v或数字开头index.ts#L109-L119const startsWithNumberRegex regEx(^\\d); function isSingleVersion(version: string): boolean { // Since coercion accepts ranges as well as versions, we have to manually // check that the version string starts with either v or a digit. if (!version.startsWith(v) !startsWithNumberRegex.exec(version)) { return false; } return !!semver.valid(semver.coerce(version)); }测试 isSingleVersion 用例 表明裸版本1.2.3、带预发布号的1.2.3-alpha.1返回true而1.2.3、 1.2.3、~1.0返回false。该函数供 Renovate 的依赖固定pinning功能区分单版本与范围。2. 版本拆解类getMajor / getMinor / getPatch三个函数实现完全同构先 coerce 再取对应段位无法转换时返回nullindex.ts#L41-L54function getPatch(a: string | SemVer): number | null { const aCoerced semver.coerce(a); return aCoerced ? semver.patch(aCoerced) : null; }测试表 getPatch 用例 展示了宽容度的具体边界输入getPatch 结果v2.10缺失的 patch 补 0v1.0.4-alpha4忽略预发布段取 patchver1.2.33容忍非标准ver前缀two1.00abcnull完全无法 coerce3. 比较类equals / isGreaterThan / sortVersions这三个函数都是先 coerce 双方再交给semver包比较equals(v1.0, 1.0.0)返回trueequals(xxx, 1.2.3)返回falseequals 测试isGreaterThan在任一版本无法 coerce 时保守地返回false例如isGreaterThan(e.e.e, 4.1.0)为falsesortVersions使用semver.compare对v1.0与1.x这样的等价非严格版本返回0对无法转换的版本也返回0而非报错sortVersions 测试。4. 稳定性判断isStableisStable是这个方案中唯一没有直接依赖semver.coerce的函数它先用一条正则提取版本骨架再用semver-stable判断index.ts#L14-L32function isStable(version: string): boolean { // matching a version with the semver prefix // v1.2.3, 1.2.3, v1.2, 1.2, v1, 1 const regx regEx( /^v?(?major\d)(?minor\.\d)?(?patch\.\d)?(?others.)?/, ); const m regx.exec(version); if (!m?.groups) { return false; } const major m.groups.major; const newMinor m.groups.minor ?? .0; const newPatch m.groups.patch ?? .0; const others m.groups.others ?? ; const fixed major newMinor newPatch others; return stable.is(fixed); }正则^v?(\d)(\.\d)?(\.\d)?(.)?允许的版本形如v1.2.3、1.2.3、v1.2、1.2、v1、1随后把缺失的 minor/patch 补.0把剩余部分预发布后缀等拼回交给semver-stable判断是否为稳定版。测试表 isStable 用例 说明1.0.0、v1.3.5、v2.1、3.4、v2、2均为稳定而1.0.0-alpha、1.0.0-rc2、v1.0-alpha等带预发布标识的不是稳定版two1.0、ver1.2.3、r3.0这类无法匹配该正则前缀的输入直接返回false——注意这里比getPatch更严格说明稳定判断和版本拆解使用了不同的宽容尺度。5. 范围匹配类matches / getSatisfyingVersion / minSatisfyingVersion / isLessThanRange这一组函数再次印证了文档中range 不转换、只接受严格 SemVer的边界——被转换的永远是 version 一侧range 保持原样交给semver包function matches(version: string, range: string): boolean { const coercedVersion semver.coerce(version); return coercedVersion ? semver.satisfies(coercedVersion, range) : false; }getSatisfyingVersion与minSatisfyingVersion处理版本列表时会稍作区分前者对已经是合法 SemVer 的版本保持原样、只对非法版本 coerceindex.ts#L71-L82后者则统一 coerce。测试 用例 展示getSatisfyingVersion([v1.0, 1.0.4-foo], ^1.0)返回1.0.0——v1.0被 coerce 成1.0.0参与匹配。6. 值生成getNewValuegetNewValue处理更新时新值的书写格式逻辑与严格semver方案一致index.ts#L128-L137function getNewValue({ currentValue, currentVersion, newVersion, }: NewValueConfig): string { if (currentVersion v${currentValue}) { return newVersion.replace(regEx(/^v/), ); } return newVersion; }即当原始文件里写的是v1.0.0currentVersion为v1.0.0currentValue为1.0.0时生成的新值会去掉v前缀与文件既有风格保持一致。测试 getNewValue 用例 中currentValue: 1.0.0、currentVersion: v1.0.0、newVersion: v1.1.0时返回1.1.0。四、semver-coerced 在 Renovate 中的实际地位1. 全局默认版本方案从源码结构看semver-coerced是 Renovate 的默认版本方案版本方案注册入口 lib/modules/versioning/index.ts 直接将其导出为defaultVersioningexport const defaultVersioning semverCoerced;且 lib/modules/datasource/index.spec.ts 中的测试确认getDefaultVersioning(undefined)返回semver-coerced。2. 自定义管理器customManagers的兜底方案官方配置文档 configuration-options.md 说明当自定义管理器配置中没有versioning字段且所用数据源没有自带默认版本方案时Renovate 默认回落到semver-coerced。这也是 customManagers 文档 建议显式设置 versioning的原因。regex与jsonata两个自定义管理器的文档同样标注了该默认值例如 custom/regex 管理器文档Aversioningcapture group, or aversioningTemplateconfig field. If neither are present, Renovate defaults tosemver-coerced3. 指定数据源的默认方案gitlab-tags / gitlab-releases两个数据源文档均声明默认使用semver-coercedgitlab-releases/readme.md、gitlab-tags/readme.md因为 Git tag 经常写成v1、1.0等非标准形式宽松转换正好适配gomod 管理器lib/modules/manager/gomod/extract.ts 对gopkg.in风格的依赖显式指定versioning: semver-coerced其对应测试 gomod/extract.spec.ts 中多处以%goMod: semver-coerced断言提取结果typst 数据源lib/modules/datasource/typst/index.ts 直接导入 semver-coerced 作为其版本方案repology 数据源的文档则给出了一个反例提醒repology/readme.md因为发行包版本号常常不符合semver-coerced的规范需要根据包类型手动指定版本方案——说明该方案的宽松也有边界并非万能。4. 作为配置值显式使用semver-coerced也是一个可以直接写进配置的合法方案 ID。例如在 constraintsVersioning 配置示例 中用 SemVer 风格而非 Ruby 风格的范围来定义约束时{ constraints: { rubygems: ^1.3 }, constraintsVersioning: { rubygems: semver-coerced } }此外same-major 版本方案 也在内部复用 semver-coerced 的 API可见它是 Renovate 版本体系里的一个基础构件。五、行为边界与使用建议综合文档与源码测试使用semver-coerced时需要注意以下边界宽容只在版本一侧v1、2.1、~3.1等版本号会被转换但 range^1.0、2等必须严格合法模块的supportsRanges false声明了它不提供范围计算能力范围相关函数的行为与严格semver方案等价。coercion 对范围串也算有效版本isValid(~1.2.3)返回true因此区分单版本与范围要依赖isSingleVersion必须v开头或数字开头。不同函数宽容尺度不一getPatch(ver1.2.3)返回3而isStable(ver1.2.3)返回false——稳定性判断使用的正则更严格不能以某一个函数的宽容度推断整体行为。无法转换时优雅降级equals、isGreaterThan、matches等在输入无法 coerce 时返回falsegetMajor/getMinor/getPatch返回nullsortVersions返回0整体设计偏向保守不误判。破坏性判断复用 SemVer 约定major 变化算破坏性v0.x 的所有 minor 变化都算破坏性不稳定版本参与的升级一律视为潜在破坏性源自 semver 方案 isBreaking。六、小结semver-coerced是 Renovate 默认启用的版本方案其本质是在semver包的每个版本判断入口前置一次semver.coerce()从而让v1、2.1、~3.1这类在 Git tag、发布页、自定义文件中极为常见的非标准版本号进入 Renovate 的比较、匹配与更新流水线。它的实现集中在 lib/modules/versioning/semver-coerced/index.ts 一个文件内约 170 行行为边界则由 同目录测试 逐函数锁定。理解它的版本宽容、范围严格这一核心不对称性是正确配置versioning选项、以及排查为什么我的版本号没被识别这类问题的关键。【免费下载链接】renovateHome of the Renovate CLI: Cross-platform Dependency Automation by Mend.io项目地址: https://gitcode.com/GitHub_Trending/re/renovate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门