
Renovate Datasource 开发指南从 getReleases 到 getDigest 的完整实现解析【免费下载链接】renovateHome of the Renovate CLI: Cross-platform Dependency Automation by Mend.io项目地址: https://gitcode.com/GitHub_Trending/re/renovateRenovate 依赖自动化工具的核心能力之一是能从各类软件源准确获取依赖包的发布版本。本文以仓库中lib/modules/datasource/readme.md为骨架结合lib/modules/datasource目录下的源码与测试系统讲解 Datasource 的定义、接口契约、注册机制与实现范式。读完本文你将掌握如何为 Renovate 编写一个全新的 Datasource、理解getReleases与getDigest两个关键接口的输入输出契约并能定位到源码中的每一处实现细节。Datasource 在 Renovate 中的定位Datasource 是 Renovate 中负责“数据来源”的一类模块核心职责是获取软件包的已发布版本列表。当 Renovate 检测到某个依赖例如 npm 包、Docker 镜像、Go module、Maven 构件后会通过对应的 Datasource 查询该包有哪些可用版本进而结合版本策略versioning决定是否需要发起更新。在仓库结构中所有 Datasource 都位于 lib/modules/datasource 目录下目前注册了 80 余种数据源见 api.ts覆盖 npm、pypi、maven、docker、go、crate、rubygems、terraform-provider 等主流生态也包括 github-tags、git-tags、git-refs 这类通用 Git 数据源。每种 Datasource 以独立子目录组织通常包含index.ts实现、readme.md文档、*.spec.ts测试与必要的 schema/类型文件。Datasource 与版本策略versioning是解耦的Datasource 只负责“有哪些版本”versioning 负责“这些版本如何比较排序”二者的协作发生在版本过滤与排序阶段见下文“调用链”一节。必须遵循的类式编程风格仓库文档明确要求新增 Datasource 必须采用基于类的编程风格class-based programming style并以java-version数据源作为参考实现。类式风格的落地形态在 datasource.ts 中定义——所有 Datasource 继承抽象基类Datasource该类已封装了大量公共基础设施export abstract class Datasource implements DatasourceApi { public readonly id: string; protected constructor(id: string) { this.id id; this.http new Http(id); } caching: boolean | undefined; customRegistrySupport true; defaultConfig: Recordstring, unknown | undefined; defaultRegistryUrls?: string[] | (() string[]); defaultVersioning?: string | undefined; registryStrategy: RegistryStrategy | undefined first; releaseTimestampSupport false; sourceUrlSupport: SourceUrlSupport none; protected http: Http; abstract getReleases( getReleasesConfig: GetReleasesConfig, ): PromiseReleaseResult | null; getDigest?(config: DigestConfig, newValue?: string): Promisestring | null; handleHttpErrors(_err: HttpError): void {} protected handleGenericErrors(err: Error): never { ... } postprocessRelease(...): PromisePostprocessReleaseResult { ... } }从源码可见基类提供的能力http基于id初始化的 HTTP 客户端所有网络请求都应经由它发出从而自动获得主机规则hostRules、限速与错误归类支持handleGenericErrors统一错误处理将 429 与 5xx 响应转换为ExternalHostError便于上层重试或告警一组声明式属性见下文“声明式能力”小节描述该数据源的注册表、缓存与元数据行为。子类只需实现getReleases必选按需实现getDigest可选并在构造时调用super(id)传入数据源 ID。以 java-version 的实现 index.ts 为例export class JavaVersionDatasource extends Datasource { static readonly id datasource; constructor() { super(datasource); } override readonly customRegistrySupport false; override readonly defaultRegistryUrls [adoptiumRegistryUrl]; override readonly caching true; getReleases(config: GetReleasesConfig): PromiseReleaseResult | null { return withCache( { namespace: datasource-${datasource}, key: ${config.registryUrl}:${config.packageName}, fallback: true, }, () this._getReleases(config), ); } }这里可以看到类式风格的完整要素静态id、构造时super(id)、声明式属性覆盖customRegistrySupport、defaultRegistryUrls、caching以及getReleases的规范化实现。注册到 api.ts让新数据源生效文档强调新增 Datasource 后必须将其加入 api.ts 的 API 注册表中否则无法被使用。注册机制非常简单——api.ts内部维护一个Mapstring, DatasourceApi每个数据源实例以id为键注册const api new Mapstring, DatasourceApi(); export default api; api.set(ApkDatasource.id, new ApkDatasource()); api.set(ArtifactoryDatasource.id, new ArtifactoryDatasource()); // ... 每个 datasource 一行 api.set(JavaVersionDatasource.id, new JavaVersionDatasource());数据源被查找到的入口在 common.tsexport function getDatasourceFor(datasource: string): DatasourceApi | null { if (datasource?.startsWith(custom.)) { return getDatasourceFor(CustomDatasource.id); } return datasources.get(datasource) ?? null; }注意custom.前缀会被统一路由到CustomDatasource这是用户自定义数据源通过customDatasources配置的实现入口。文档中还提到一个非常有用的排错线索如果在 Vitest 测试中出现Unused HTTP mocks错误而 mock 的 URL 本身正确首先要检查新数据源是否已正确注册。这是因为数据源未注册时测试中 mock 的请求永远不会被发起HTTP mock 框架便会报告“存在未使用的 mock”。从源码看fetchReleases在 index.ts 中会先校验getDatasourceFor(datasourceName)未注册时直接logger.warn(Unknown datasource)并返回null根本不会触发网络请求从而产生上述现象。getReleases数据源的最小必需接口文档明确指出一个 Datasource 的最小导出接口是名为getReleases的函数它以 lookup 配置为输入。输入lookup config输入配置的核心字段在 types.ts 的GetReleasesConfig中定义export interface GetReleasesConfig { customDatasources?: Recordstring, CustomDatasourceConfig; datasource?: string; packageName: string; registryUrl?: string; currentValue?: string; constraints?: PartialRecordConstraintName, string; constraintsVersioning?: PartialRecordAdditionalConstraintName, string; constraintsFiltering?: ConstraintsFilter; }文档强调的两个核心字段字段含义说明packageName包的完整名称含 scope 如有例如foo/bar对于带查询参数的场景包名甚至可以携带参数如 java-version 的java-jre?oslinuxarchitecturex64registryUrls待尝试的注册表 URL 数组由上层解析后注入如果数据源不支持自定义注册表则使用defaultRegistryUrls输出ReleaseResultgetReleases返回一个包含版本列表及相关元数据的对象完整类型为 types.ts 中的ReleaseResult其中每个元素是Releasetypes.ts。文档列出的返回字段整理如下字段类型必填说明releasesRelease[]是唯一必填匹配到的版本数组每个 release 至少含version字符串deprecationMessagestring否包被弃用的提示信息描述sourceUrlstring否源码仓库的 HTTP URL例如 GitHub 上的仓库地址homepagestring否包的首页 URL若与sourceUrl相同理想情况下应为空changelogUrlstring否指向包变更日志的 URL可能是 Markdown 文件若未提供Renovate 会在sourceUrl中搜索变更日志文件tagsobject否标签到版本的映射例如tags: { latest: 3.0.0 }仅被followTags功能使用Release类型还包含大量增强字段releaseTimestamp发布时间用于最小发布年龄等策略、isStable、isDeprecated、checksumUrl、downloadUrl、gitRef、newDigest、registryUrl多注册表合并时标记每个 release 的来源等。ReleaseResult层面还有isPrivate决定结果能否进入公共缓存、registryUrl、sourceDirectory、mostRecentTimestamp等元数据字段。一个最小实现的形状综合契约一个最小可用的getReleases实现大致如下async getReleases(config: GetReleasesConfig): PromiseReleaseResult | null { const { packageName, registryUrl } config; // 1. 基于 registryUrl packageName 构造 API 请求 // 2. 解析响应映射为 Release[]每个 release 至少要有 version // 3. 补充 sourceUrl / homepage / deprecationMessage / tags 等元数据 return { releases: [{ version: 1.0.0 }, { version: 1.0.1 }], sourceUrl: https://example.com/repo, homepage: , // 与 sourceUrl 相同时留空 changelogUrl: undefined, // 缺省时 Renovate 会去 sourceUrl 里找 CHANGELOG tags: { latest: 1.0.1 }, }; // 找不到任何版本时返回 null }实际场景中返回null无结果与返回空releases都会被上层视作无可用版本在 index.ts 中!dep || dequal(dep, { releases: [] })都会被归一化为null。调用链从 getPkgReleases 到注册表策略理解getReleases在何处被调用有助于把握数据源在整个 Renovate 流程中的位置。核心入口是 index.ts 中的getPkgReleasesgetPkgReleases(config) └─ getRawPkgReleases(config) # 校验 datasource / packageName └─ fetchCachedReleases(config) # 内存缓存去重同一包同一配置只抓一次 └─ fetchReleases(config) # 解析注册表 URL、选择 registry 策略 ├─ firstRegistry() # 只用第一个注册表 ├─ huntRegistries() # 按顺序尝试返回第一个非 null 结果 └─ mergeRegistries() # 全部查询后合并、按版本去重 └─ applyDatasourceFilters() # extractVersion / versionCompatibility / # filterValidVersions / 排序去重 / constraints注册表 URL 的解析在resolveRegistryUrlsindex.ts优先使用用户配置的registryUrls否则依次回退到defaultRegistryUrls参数、数据源声明的defaultRegistryUrls所有 URL 会经过trimTrailingSlash清洗。若数据源设置了customRegistrySupport false如 java-version用户自定义注册表会被忽略并给出警告。registry 策略RegistryStrategy定义于 types.ts决定了多注册表时的行为可在配置中通过registryStrategy覆盖默认取数据源声明值基类默认为firstfetchReleases兜底为hunt策略行为first仅查询第一个注册表配置了多个时记录警告并忽略其余hunt按顺序尝试返回第一个非 null 结果401/403/404 等错误会跳过继续尝试下一个ExternalHostError立即中止merge查询所有注册表版本按 versioning 排序合并去重tags 合并时同键取后值任一注册表返回结果后其他注册表的限流错误不会丢弃已有结果版本结果返回前还会经过 common.ts 中的一系列过滤applyExtractVersion用extractVersion正则从原始版本中提取真正版本号、applyVersionCompatibility、filterValidVersions按所选 versioning 剔除非法版本、sortAndRemoveDuplicates排序去重以及applyConstraintsFiltering严格约束过滤。getDigest支持摘要的数据源文档指出支持 digest摘要的数据源可以导出getDigest函数。典型场景是 Docker 镜像的镜像摘要SHA256 digest与 Git 提交哈希commit hash——这类“版本”并非自增数字而是内容寻址的摘要值需要单独查询。接口签名定义于 types.tsgetDigest?(config: DigestConfig, newValue?: string): Promisestring | null;两个输入参数参数说明config与getReleases同构的包配置DigestConfig包含packageName、lookupName、registryUrl、currentValue、currentDigest描述“要为哪个包取摘要”newValue需要获取 digest 对应的版本或值例如 Docker tag返回值约定成功时返回表示摘要值的字符串未找到摘要时返回null。上层调用封装在 index.ts 的getDigest先通过getDatasourceFor找到数据源若数据源未实现getDigest则直接返回nullsupportsDigests函数index.ts则用于在流程早期判断某数据源是否支持 digest。值得注意的一个细节getDigestConfig会优先使用replacementName作为packageName且 registryUrl 会优先沿用getReleases查询结果这保证了“先找版本、再按同一注册表取摘要”的语义一致性。Docker 数据源是getDigest的典型实现者——docker/index.ts 中通过查询 registry 的 manifest 接口获取sha256:摘要git 类数据源如 git-refs则直接解析 Git 对象的 commit hash。Renovate 的“固定 digest”功能如将 Docker 镜像固定到不可变摘要正是依赖这条路径。参考实现深度拆解java-version 数据源文档指定 java-version 为类式风格的参考实现值得深入剖析其完整工作方式。它从 Adoptium 公开 API 获取 Java 运行时版本列表相关代码位于 lib/modules/datasource/java-version 目录。packageName 的扩展语法java-version/common.ts 中的parsePackage将packageName解析为带参数的查询export function parsePackage(packageName: string): PackageConfig { const u new URL(packageName, defaultRegistryUrl); const useSystem u.searchParams.get(system) true; return { imageType: getImageType(trimLeadingSlash(u.pathname)), architecture: u.searchParams.get(architecture) ?? getSystemArchitecture(useSystem), os: u.searchParams.get(os) ?? getSystemOs(useSystem), }; }包名首段决定镜像类型java-jre→ JRE其他含java、java-jdk→ JDK可通过查询参数过滤os与architecture例如packageName java-jre?oslinuxarchitecturex64只返回 Linux x64 的 JRE 版本使用java?systemtrue时数据源会读取process.arch/process.platform自动探测当前系统架构与操作系统映射逻辑x64/arm64 → aarch64、darwin → mac、win32 → windows 等也在 common.ts 中。分页抓取实现java-version/adoptium.ts 展示了真实的 HTTP 调用与分页处理let url ${adoptiumRegistryUrl}v3/info/release_versions?page_size${pageSize}image_type${pkgConfig.imageType}projectjdkrelease_typegasort_methodDATEsort_orderDESC;请求固定携带image_typejre|jdkprojectjdkrelease_typegasort_methodDATEsort_orderDESC过滤参数即只取 JDK 项目的 GAGeneral Availability正式版按日期降序排列。数据源最多抓取 50 页、每页 50 条pageSize 50总计最多 2500 个版本当某页返回不足一页或超过 50 页上限时停止adoptium.ts。分页结束的判断还利用了 404page ! 0时若请求返回 404 说明没有更多页正常结束adoptium.ts。响应经 schema.ts 中的 zod schema 校验后将每个版本的semver字段映射为Release的version。LTS 版本会附带 JRE因此java-jre也能拿到版本列表而非 LTS 版本可能不提供 JRE 产物——这是该数据源的业务语义写在与之一一对应的 java-version/readme.md 中。数据源级缓存java-version 的getReleases还演示了withCache的用法——按registryUrl:packageName缓存结果并开启fallback缓存过期后仍可用旧值兜底避免瞬时故障导致无版本可用。同时它在类上声明override readonly caching true表示结果可进入 datasource 索引层的公共包缓存index.ts 中按datasource-releases-${id}命名空间缓存 15 分钟前提是数据源能正确返回isPrivate标志——私有包结果默认不进入公共缓存除非管理员通过cachePrivatePackages强制开启。测试与常见问题每个数据源目录都配套*.spec.ts测试文件例如 java-version/index.spec.ts 与 index.spec.tsdatasource 索引层的策略/缓存测试。测试通常使用仓库统一的httpMock机制 mock 网络响应配合__fixtures__目录中的 JSON 样例数据。排查实践中的两条关键经验Unused HTTP mocks报错优先检查新数据源是否已加入 api.ts。未注册的数据源在fetchReleases阶段就被拦截提示Unknown datasourcemock 的 URL 永远不会被请求于是测试框架报“mock 未被使用”。若 mock URL 本身正确注册即修复。注册表 URL 解析若请求没有打到预期地址检查resolveRegistryUrls的优先级——用户配置的registryUrls优先于数据源的defaultRegistryUrlscustomRegistrySupport false的数据源如 java-version会忽略用户传入的自定义注册表。结语Renovate 的 Datasource 体系遵循“小而稳”的契约设计类式基类统一了 HTTP、错误处理与声明式元数据getReleases返回结构化的ReleaseResult让上层可以统一执行版本过滤、排序、去重与约束筛选getDigest则为 Docker 摘要、Git commit 这类非自增版本提供了补充路径。参考实现 java-version 完整展示了从包名解析、分页抓取、schema 校验到缓存命中的全流程。对希望扩展 Renovate 生态的开发者而言遵循本文所述接口与范式即可在 lib/modules/datasource 中新增一个可被任何 manager 复用的数据源。【免费下载链接】renovateHome of the Renovate CLI: Cross-platform Dependency Automation by Mend.io项目地址: https://gitcode.com/GitHub_Trending/re/renovate创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考