DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

pnpm 锁文件读出了零依赖

pnpm-lock.yaml 可能包含环境与项目两个 YAML 文档。依赖图取最后一个,完整软件清单读全部文档,避免解析成功却漏掉项目依赖。

安装依赖正常,自己的依赖解析脚本、扫描器或 SBOM 工具却显示零个项目依赖;也可能直接报 expected a single document in the stream, but found more。读取 pnpm-lock.yaml 时,先检查它是否包含两个 YAML 文档,不能仅凭 lockfileVersion: '9.0' 假设整份文件只有一个文档。

pnpm 的环境文档可记录 configDependencies 和 packageManagerDependencies,排在项目依赖文档之前。pnpm 12 更常见这种布局,但单文档文件仍然存在。旧消费工具只读第一段时,可能读到 pnpm 自身或配置依赖,漏掉真正的项目依赖图。

自己的依赖图,应取最后一个文档

如果脚本使用的是单文档 YAML API,解析器可能在第二个文档处报错。换成多文档 API 后也要检查选择规则:解析成功,却固定取 docs[0],仍可能得到错误的依赖图。

下面示例按 Node.js ES 模块编写,可保存为 .mjs 文件,使用项目已有的 js-yaml 和支持 Array.prototype.at 的 Node.js 版本。它选择最后一个文档作为项目锁文件;应在项目根目录运行,并按自身解析器的接口保留必要验证:

import { readFileSync } from "node:fs";
import { loadAll } from "js-yaml";

const documents = loadAll(readFileSync("pnpm-lock.yaml", "utf8"));
const project = documents.at(-1);
if (!project || typeof project !== "object" || !project.importers) {
  throw new Error("No project importers in pnpm-lock.yaml");
}
console.log(project.importers);

取最后一个文档适用于这里的单文档和环境文档在前的布局。不要为了迁就旧脚本,把已提交锁文件的两段手动调换;现有消费工具、旧提交和其他自动化仍可能按原布局处理。

完整软件清单,需要读取两个文档

项目依赖图与完整安装清单的用途不同。环境文档里的配置依赖也是实际安装的包。如果目标是软件物料清单或漏洞扫描,只取项目文档同样可能漏报配置依赖。

这类工具需要分别解析每个文档,再汇总包清单,并保留包的版本、标识和所属文档。不能把两个对象简单深度合并:两段里可能都有 importers['.'],后写的字段可能覆盖前面的记录。既要保留项目包,也要保留实际存在的环境包。

零告警之前,先核对扫描覆盖

选取项目里确定存在的几个直接依赖和传递依赖,核对输出中是否包含它们及正确版本。结果只列出 pnpm 和若干 @pnpm/exe.* 包,却完全没有项目依赖时,应先检查文档选择,而不是把“扫描成功”理解为“项目没有漏洞”。

外部工具在具体版本上是否支持这种布局,需要核对那个版本的解析规则和输出。不能把预发布版时期某个扫描器的故障,延伸成所有当前版本都失效;命令退出为零、输出非空,也都不能代替依赖覆盖检查。

pmOnFail: ignore 会改变包管理器版本强制检查,且项目使用配置依赖时仍可能生成环境文档。它不能作为所有项目恢复单文档的通用开关。优先修正或升级消费工具的解析能力,再沿用项目原有版本约束与锁文件流程。