.npm-extension

重要的、根本拥有的清单修复

选择命令行版本:

描述

🌐 Description

一个由 root 拥有的 .npm-extension.mjs.npm-extension.cjs 文件让项目可以在 npm 解析依赖树之前,强制修复第三方依赖的清单。它导出一个 transformManifest(pkg, context) 函数,这个函数接收一个候选依赖清单,并返回 npm 应该使用的有效清单。

🌐 A root-owned .npm-extension.mjs or .npm-extension.cjs file lets a project imperatively repair the manifests of third-party dependencies before npm resolves the dependency tree. It exports a transformManifest(pkg, context) function that receives a candidate dependency manifest and returns the effective manifest npm should use.

.npm-extension 是陈述式 packageExtensions 字段的命令式对应项,并且在 packageExtensions 之前的相同预解析阶段运行。对于简单、仅数据的修复,优先使用 packageExtensions;当你需要评论和链接来解释修复、条件逻辑、以代码表达的重复修复、删除或范围重写、过时修复保护,或 package.json 之外的策略位置时,就使用 .npm-extension

示例

🌐 Example

// .npm-extension.mjs
export function transformManifest(pkg, context) {
if (pkg.name === "foo" && pkg.version.startsWith("1.")) {
pkg.dependencies = { ...pkg.dependencies, bar: "^2.0.0" };
context.log(`added bar to ${pkg.name}@${pkg.version}`);
}
return pkg;
}

.cjs 表单使用的是 CommonJS 导出方式:

🌐 The .cjs form uses CommonJS exports instead:

// .npm-extension.cjs
module.exports = {
transformManifest(pkg, context) {
return pkg;
},
};

transformManifest 函数

🌐 The transformManifest function

transformManifest(pkg, context) 会接收到一个彻底隔离的候选依赖清单副本。它可以修改并返回该副本,也可以返回一个新的清单对象。它必须同步返回一个清单对象;返回 nullundefined、原始值、数组或 Promise 都会导致安装失败。

context 参数故意设置得很小:

🌐 The context argument is intentionally small:

  • context.log(message) 写了一条 npm 调试日志消息。
  • context.root 是项目根目录的绝对路径。
  • context.extensionPoint 是字符串 "transformManifest"

npm 不提供注册表、抓取、锁文件或解压的帮助。保持扩展文件是自包含的,或者仅限于 Node 内置功能;npm 不保证在加载文件时项目依赖可用。

🌐 npm provides no registry, fetch, lockfile, or extraction helpers. Keep the extension file self-contained or limited to Node builtins; npm does not guarantee that project dependencies are available when the file is loaded.

支持的突变

🌐 Supported mutations

只有四个影响分辨率的字段可以更改:

🌐 Only the four resolution-affecting fields may change:

  • dependencies
  • optionalDependencies
  • peerDependencies
  • peerDependenciesMeta

在这些字段中,你可以添加、替换或删除条目。更改任何其他字段(例如 scriptsbinenginesoscpuexportsmain)都会被拒绝,安装会失败,并显示 .npm-extension 和正在处理的包的错误。包的 tar 文件和已安装的 node_modules/<pkg>/package.json 永远不会被重写。

发现与 extension-file

🌐 Discovery and extension-file

npm 会在项目根目录(工作区项目的工作区根目录)查找单个 .npm-extension.mjs.npm-extension.cjs。如果两个文件都存在就会出错。依赖或非根工作区中的 .npm-extension 文件会被忽略;非根工作区文件会发出警告。

🌐 npm looks for a single .npm-extension.mjs or .npm-extension.cjs at the project root (the workspace root in a workspace project). Having both files present is an error. A .npm-extension file in a dependency or in a non-root workspace is ignored; a non-root workspace file produces a warning.

extension-file 配置会选择一个不同的项目本地文件。它必须在项目根目录下解析,并使用 .mjs.cjs 扩展名,而且只有在项目配置或命令行中才会生效——绝不会从用户、全局或内置配置中生效。

🌐 The extension-file config selects a different project-local file. It must resolve inside the project root and use a .mjs or .cjs extension, and it is honored only from project config or the command line — never from user, global, or builtin config.

packageExtensionsoverrides 的互动

🌐 Interaction with packageExtensions and overrides

当两者同时存在时,transformManifest 会先运行,然后 packageExtensions 会应用到它的输出上。除非你打算依赖这种顺序,否则避免同时针对同一个包使用两者。overrides 仍然控制任何边的最终解析目标,包括由 transformManifest 创建的边。

🌐 When both are present, transformManifest runs first and packageExtensions is applied to its output. Avoid targeting the same package with both unless you intend to rely on that ordering. overrides still controls the final resolution target of any edge, including edges created by transformManifest.

锁文件和 npm ci

🌐 Lockfile and npm ci

.npm-extension 影响的锁文件会在其根条目上记录一个 npmExtensionHash(所选文件字节和模块格式的摘要),并在每个受影响的包条目上记录最小的 npmExtensionApplied 来源信息。扩展状态需要 lockfileVersion: 4

🌐 A lockfile influenced by .npm-extension records an npmExtensionHash (a digest of the selected file's bytes and module format) on its root entry, and minimal npmExtensionApplied provenance on each affected package entry. Extension state requires lockfileVersion: 4.

更改文件内容会让 npm install 重新解析受影响的包。npm ci 不会 导入或执行 .npm-extension;它只是将记录的哈希值与文件进行比对,并重新生成锁定的依赖图,如果文件和锁文件不一致(或者一个有扩展状态而另一个没有)就会失败。

🌐 Changing the file's contents makes npm install re-resolve the affected packages. npm ci does not import or execute .npm-extension; it verifies the recorded hash against the file and reifies the locked graph, failing if the file and lockfile disagree (or if one has extension state and the other does not).

哈希只证明安装使用了与生成锁文件相同的扩展文件字节。它并不能让任意的 JavaScript 变得确定:依赖环境变量、网络、时钟或者扩展导入文件的扩展输出仍可能产生不可复现的安装。把 .npm-extension 当作可信、确定的项目代码,只在你信任的仓库中启用它。

🌐 The hash proves only that the install uses the same extension file bytes that generated the lockfile. It does not make arbitrary JavaScript deterministic: extension output that depends on environment variables, the network, the clock, or files imported by the extension can still produce non-reproducible installs. Treat .npm-extension as trusted, deterministic project code, and only enable it in repositories you trust.

禁用

🌐 Disabling

ignore-extension 设置为跳过导入和执行 .npm-extensionignore-scripts 意味着 ignore-extension,因为两者都会禁用 root 所拥有的安装时代码。即使禁用了执行,npm ci 仍会验证文件哈希。

🌐 Set ignore-extension to skip importing and executing .npm-extension. ignore-scripts implies ignore-extension, since both disable root-owned install-time code. npm ci still verifies the file hash even when execution is disabled.

发布

🌐 Publishing

.npm-extension.mjs.npm-extension.cjs 是项目配置,而不是包内容。即使包的 files 列表会包含它,npm 也会在由 npm packnpm publish 生成的包压缩文件中排除根文件,所以一个公共包可以在它的仓库中保留 .npm-extension 供本地使用,而不用发布它。

也可以看看

🌐 See also