pnpm 按注册表声明 `time` 字段:让 `resolutionMode: time-based` 的完整元数据回退按需生效
pnpm 按注册表声明time字段让resolutionMode: time-based的完整元数据回退按需生效【免费下载链接】pnpmFast, disk space efficient package manager项目地址: https://gitcode.com/gh_mirrors/pn/pnpm导读pnpm 新增了按注册表per-registry声明简略元数据abbreviated metadata是否携带time字段的能力在pnpm-workspace.yaml的registries中为某个注册表声明supportsTimeField: true后resolutionMode: time-based只会从确实缺少time字段的注册表拉取体积大得多的完整元数据文档其余注册表继续使用简略文档。读完本文你将掌握该配置的完整写法、新旧行为差异、优先级规则以及它在 pnpm 源码TypeScript 与 Rust 双实现中从配置解析到解析器决策的完整调用链。背景时间基解析与time字段pnpm 在resolutionMode: time-based以及minimumReleaseAge等发布时间门控策略下需要知道每个包版本的实际发布时间。这个时间来自注册表元数据文档中的time字段——一个从版本号映射到 ISO 时间戳的对象。问题在于npm 公共注册表registry.npmjs.org的简略元数据packument 的缩写形式不包含time字段。为了拿到发布时间pnpm 只能回退去拉取包含全部信息的完整元数据文档而后者体积远大于简略版会显著拖慢解析并消耗更多带宽与存储。旧行为全有或全无的全局开关在本次改动之前是否因为时间基解析而拉取完整元数据由一个全局布尔配置registrySupportsTimeField统一回答对每一个注册表一视同仁不设置它默认false项目只要开启时间基解析所有注册表都要回退到完整元数据设置为true则假定所有注册表的简略元数据都带time字段可registry.npmjs.org根本不提供解析结果就会缺少time而失败。正如变更记录.changeset/per-registry-time-field.md所总结的一个同时从公共注册表和 Verdaccio 私有实例解析依赖的项目要么为所有注册表付出完整元数据的代价要么声称 npmjs 提供了它并不提供的time字段——两种选择都不合理。新能力按注册表声明supportsTimeField现在注册表可以声明它的简略元数据携带time字段这样resolutionMode: time-based只会从真正需要它的注册表读取完整元数据resolutionMode: time-based registries: https://npm.internal.example/: supportsTimeField: true声明了supportsTimeField: true的注册表如上例的内部注册表时间基解析继续走轻量的简略元数据未声明的注册表如registry.npmjs.org保持旧逻辑按全局registrySupportsTimeField设置回答需要时回退到完整元数据。关键语义registrySupportsTimeField仍然是未声明注册表的答案——声明只对声明者本身生效不会豁免其他注册表。配置详解与优先级字段位置与校验该声明位于pnpm-workspace.yaml的registries映射下作为每个注册表 URL 的对象字段。配置读取端在 getOptionsFromRootManifest.ts 中把supportsTimeField列入注册表声明字段集合const REGISTRY_DECLARATION_FIELDS new Set([serverType, supportsTimeField, scopes, prefix])并且与serverType等字段一样做布尔断言校验非布尔值会直接报错if (supportsTimeField ! null) { assertBoolean(supportsTimeField, ${settingPath}.supportsTimeField) }在 Rust 实现侧该字段同样被解析进注册表选项结构体见 registry_options.rs 与工作区 YAML 解析的 ecosystems.rs。优先级声明 全局设置 默认 false答案的判定逻辑集中在 normalize-registries/src/index.ts 的registrySupportsTimeField函数export function registrySupportsTimeField ( registryContext: PickRegistryContext, registryOptionsByUrl { registrySupportsTimeField?: boolean }, registry: string ): boolean { return registryContext.registryOptionsByUrl?.[normalizeRegistryUrl(registry)]?.supportsTimeField ?? registryContext.registrySupportsTimeField ?? false }三层回退一目了然该注册表 URL 在registryOptionsByUrl中的supportsTimeField声明按规范化后的 URL 匹配全局registrySupportsTimeField设置默认false。注意第 1 层用normalizeRegistryUrl(registry)做键匹配因此声明时 URL 尾部有没有斜杠都不影响命中对应测试见后文 shouldFetchFullMetadata.test.ts。声明覆盖全局设置双向生效测试 shouldFetchFullMetadata.test.ts 验证了声明与全局设置的关系是双向覆盖全局设置registrySupportsTimeField: true但某注册表显式声明supportsTimeField: false→ 该注册表仍需完整元数据声明优先未声明注册表 → 遵循全局设置。从配置到解析内部实现调用链1. 存储控制器按注册表回答是否需要完整元数据核心决策函数位于 createNewStoreController.ts。首先是一个无歧义的全局策略函数function fullMetadataPolicy (opts: FullMetadataPolicyOptions, supportsTimeField: boolean): boolean { return opts.fetchFullMetadata ?? ( opts.supportedArchitectures?.libc ! null || opts.trustPolicy no-downgrade || (opts.resolutionMode time-based !supportsTimeField) ) }注意完整元数据的需求来源不止时间基解析还包括显式fetchFullMetadata开关、supportedArchitectures.libc配置npm 简略元数据不含libc、trustPolicy: no-downgrade信任检查需要读取简略元数据永远不携带的_npmUser信任证据。这三个理由适用于所有注册表不因任何声明而豁免。needsFullMetadataForRegistry则把同一策略逐注册表问一遍并用Map做了记忆化每个注册表只算一次export function needsFullMetadataForRegistry ( opts: FullMetadataPolicyOptions ): (registry: string) boolean { const answers new Mapstring, boolean() return (registry: string): boolean { let answer answers.get(registry) if (answer null) { answer fullMetadataPolicy(opts, registrySupportsTimeField(opts, registry)) answers.set(registry, answer) } return answer } }2. npm 解析器把逐注册表能力接入解析器工厂在 npm-resolver/src/index.ts 中新增了一个回调选项needsFullMetadataFor/** * Asked instead of {link ResolverFactoryOptions.fullMetadata} when the * caller can answer per registry — a registry that declares * supportsTimeField needs no full metadata for a time-based resolution * even when the others do. */ needsFullMetadataFor?: (registry: string) boolean包选择阶段pickPackage.ts按当前包的注册表 URL 决定拉取哪种文档const policyWantsFullMetadata ctx.needsFullMetadataFor?.(opts.registry) ?? ctx.fullMetadata true const fullMetadata opts.optional true || policyWantsFullMetadata此外元数据缓存键也把fullMetadata/filterMetadata纳入其中见getPkgMetaCacheKey确保同一注册表的简略与完整文档镜像各自独立缓存互不污染。3. 过滤镜像的一致性按最苛刻的注册表决定有一个容易被忽视的细节完整 packument 一旦被拉取会以 pnpm 过滤后的形式存储与读取剥离time等用不到的大字段。由于这个过滤镜像是全客户端选一次的shouldFilterMetadata必须按最苛刻的注册表回答——即假设supportsTimeField为false时的策略结果见 createNewStoreController.ts。测试 shouldFetchFullMetadata.test.ts 专门验证了这种全局豁免但声明不豁免的场景shouldFetchFullMetadata返回false但needsFullMetadataForRegistry对该注册表返回true此时shouldFilterMetadata必须为true保证完整文档落入正确的过滤镜像。声明如何同步到 pnpr 服务器变更记录特别提到该声明也会发送给 pnpr 服务器由服务器在代表客户端执行的解析中应用同样的逐注册表逻辑。这在 normalize-registries/src/index.ts 的toRegistryDeclarations中实现——它把配置读取时拆分成的查找表重新组装为声明结构供客户端向 pnpr 服务器描述自己的注册表/** * Rebuilds the declarations from the lookups they were split into, for a * client that has to describe its registries to a pnpr server. */ export function toRegistryDeclarations (context: PartialRegistryContext): Recordstring, RegistryDeclaration组装逻辑buildRegistryDeclarations会把registryOptionsByUrl中的serverType与supportsTimeField逐一写入对应 URL 的声明对象index.tsfor (const [registry, options] of Object.entries(context.registryOptionsByUrl ?? {})) { if (options.serverType ! null) declarationFor(registry).serverType options.serverType if (options.supportsTimeField ! null) declarationFor(registry).supportsTimeField options.supportsTimeField }默认注册表scope不参与该声明结构它作为请求自身的registry字段单独传输用户未改道的内置路由如jsr→npm.jsr.io也不会被声明避免每次请求都把一个本不解析 JSR 包的路由塞进 pnpr 服务器的白名单。Rust 侧的对应实现作为 pnpm 的 Rust 移植方向同名能力在pnpm/crates下同样落地settings.rs 与 registry_options.rs解析registrySupportsTimeField设置与逐注册表声明lockfile/resolution/registry.rs注释明确指出该结构中的时间字段支持标志是对registrySupportsTimeField设置的答案resolving-npm-resolver/src/lookup_context.rs解析验证器上下文按需携带逐注册表时间支持信息package-manager/src/resolution_policy.rs解析策略中消费该标志。行为矩阵速查场景时间基解析下该注册表拉取声明supportsTimeField: true简略元数据未声明全局registrySupportsTimeField: true简略元数据未声明全局未设置默认完整元数据声明supportsTimeField: false覆盖全局true完整元数据trustPolicy: no-downgrade/supportedArchitectures.libc任意完整元数据声明不豁免小结registries.url.supportsTimeField让 pnpm 的时间基解析从全局一刀切进化为按注册表精准决策Verdaccio、GitHub Packages 等自带time字段的私有实例可以声明后继续走轻量简略元数据而registry.npmjs.org等不提供该字段的注册表才回退到完整文档。配置只需三行 YAML源码侧的registrySupportsTimeField→needsFullMetadataForRegistry→needsFullMetadataFor→pickPackage调用链清晰可循并有完整的单测矩阵shouldFetchFullMetadata.test.ts、toResolvedRegistryDeclarations.test.ts覆盖优先级、双向覆盖、尾斜杠规范化与过滤镜像一致性等边界语义。【免费下载链接】pnpmFast, disk space efficient package manager项目地址: https://gitcode.com/gh_mirrors/pn/pnpm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考