@ 受影响/ 锁定文件
锁定文件解析 效果 v4: 包( 包头)bun.lock页:1package-lock.json页:1pnpm-lock.yaml和纱线 Berry(yarn.lock(所有规范化为一个) Lockfile 设计模型,加上该模型的纯完整性检查与工作空间表以及两个锁文件比较后的纯性. 四种格式,一种模式,没有IO和外部运行时间依赖.
预
1.0.0。 。 。 . 此软件包是@effected/*在稳定基础上建造的工具包 效应v4(effect~4.1.0) 并仍在0.x发展。 稳定效果 制作一个工具箱1.0.0可能,不是自动的。 为了保持你的effect财务报告和审定财务报表@effect/*该工具包在线上构建和测试、安装@effected/pnpm-plugin-effected。 。 。 .稳定:不稳定. 此软件包的 API 表面尚未考虑 完成并可能改变
0.x释放 定一个确切的版本——甚至 标记软件包 稳定 在此之前1.0.0可以引入突破性更改 和精确的针 变成一个类型检查错误 而不是一个 跑步时间惊喜。 完全政策: 释放策略。 。 。 .
为什么 @ 受影响/ 锁定文件
每个软件包管理器用不同的方言来写出其锁文件——JSONC为bun,JSON为npm,YAML为pnpm和纱线——每个软件包编码,工作空间边缘和完整性数据不同. 想要回答“哪个版本”的工具 typescript 在此解析 。 这个软件包将所有四个都正常化为一个模型,所以无论哪个软件包管理器生产文件,都会被问一次.
每一个切入点都把内容当作一个 字符串。该软件包完全不执行IO:读取文件,寻找工作空间根并检测Repo使用的哪个软件包管理器属于它的消费者,而保存它们意味着解析器是一个纯函数,你可以从固定器,网络响应或git blob中驱动. 输入错误总是通过打出错误的通道退出,绝不是缺陷,锁文件无法使用的两种方式是不同的标记而不是模糊 reason 绳子。 Yarn支持仅是Berry, 执行: 经典 v1 内容键入失败, 而不是被错误的正常化成看起来可信的东西 .
安装
npm install @effected/lockfiles @effected/jsonc @effected/npm @effected/semver @effected/yaml effect
pnpm add @effected/lockfiles @effected/jsonc @effected/npm @effected/semver @effected/yaml effect
需要节点.js~~24.11.0. effect v4, (英语), @effected/jsonc, (中文(简体) ), @effected/npm, (中文(简体) ), @effected/semver 财务报告和审定财务报表 @effected/yaml - JSONC 和 YAML 引擎、 共享的依赖指定词汇和 SemVer 范围检查器都通过这些兄弟姐妹到达, 所以没有外部 effect 财务报告和审定财务报表 @effected/* 到达你的树。 自动安装对等器的软件包管理器会将其拉入; 如果不安装, 将明确添加到您的清单中 .
全体 @effected/* 软件包仅为无害环境管理:只公布出口地图 import 条件,所以 require() 包括解析 CJS 模式的工具 —— 与节点失败 ERR_PACKAGE_PATH_NOT_EXPORTED 而不是装入不存在的 CJS 建筑。 从ES模块导入.
快开始
import { Lockfile, LockfileIntegrity, WorkspaceManifest } from "@effected/lockfiles";
import { Effect } from "effect";
declare const content: string; // lockfile text, read by the caller
const program = Effect.gen(function* () {
// The only fallible boundary in the package.
const lockfile = yield* Lockfile.parse(content, { format: "pnpm" });
// pnpm workspace packages come back keyed by importer path; rewrite them
// once you have read the manifests. Total and pure — no error channel.
const named = lockfile.withImporterNames(new Map([["packages/core", "@acme/core"]]));
// Total lookups over the model.
const versions = named.packagesNamed("typescript").map((p) => p.version);
// Pure integrity checking — no Effect, no error channel, no IO.
const report = LockfileIntegrity.compare(named, [
WorkspaceManifest.make({ name: "@acme/core", dependencies: { lodash: "^4.17.0" } }),
]);
return { versions, workspaces: named.workspacePackages.length, valid: report.valid };
});
Effect.runPromise(program).then(console.log);
// { versions: [...resolved typescript versions], workspaces: <count>, valid: true | false }
格式
format 是一种文字 filenameFor 编号 : fromFilename 映射到字面文字和磁盘上的文件之间, 所以一个检测到包管理器的消费者就不需要硬码文件名 .
| 格式 | 文件名 | 读取 | 页:1 |
|---|---|---|---|
"bun" | bun.lock | 约克 | 信任的依赖关系 BunExtension |
"npm" | package-lock.json | 贾森 | lockfileVersion 3+ — v2 及更古老的失败键入 |
"pnpm" | pnpm-lock.yaml | 也门 | lockfileVersion 9+ — 旧的已键入失败 。 保存在目录上的 PnpmExtension; 见下文 |
"yarn" | yarn.lock | 也门 | 只有 Berry — 经典 v1 键入失败 |
支持的锁定文件版本
门在上 锁定文件格式版本,这是锁文件记录的唯一版本 :
| 格式 | 最低 | 旧投入 |
|---|---|---|
| 小时 | lockfileVersion 第9条 | LockfileParseError, (中文(简体) ), stage: "validation" |
| 无 | lockfileVersion 3个 | LockfileParseError, (中文(简体) ), stage: "validation" |
| 面包,纱线 | 无门 | —— 说 |
这个 cause 是结构化的 { _tag: "UnsupportedLockfileVersion", format, lockfileVersion, minimumSupported, message },因此消费者可以区分"你的锁文件太老了"和"你的锁文件是错的",而不解析出道词. isUnsupportedLockfileVersion 是一个导出并缩小其范围 —— 在标签上有区别, 绝不在 message,这是摘要而不是合同:
import { isUnsupportedLockfileVersion, Lockfile } from "@effected/lockfiles";
import { Effect } from "effect";
declare const content: string;
const program = Lockfile.parse(content, { format: "pnpm" }).pipe(
Effect.catchTag("LockfileParseError", (error) =>
isUnsupportedLockfileVersion(error.cause)
? Effect.fail(`lockfileVersion ${error.cause.lockfileVersion}; need ${error.cause.minimumSupported}+`)
: Effect.fail("malformed lockfile"),
),
);
// a pre-v9 pnpm lockfile takes the first branch, a truncated one the second
cause 刻意保持开放的通道:它携带着任何下放的解析引擎所抛出的东西,因此宣布它为封闭的联盟将呈现一个开放的通道作为详尽的通道. 前提就是它是什么 使它缩小诚实.
旧格式被拒绝而不是解析,因为它们无法回答模型现在提出的问题:pre-v9 pnpm没有 snapshots: npm v1/v2 树没有每层嵌入的记录,所以两者都会解码为无分辨率数据的行.
已测试反对 pnpm 11.22.0,npm 11.19.0,bun 1.3.14和纱线 4.9.1——与大门分开的主张,是故意的. 一个锁文件没有记录哪个软件包管理器写它, 映射是多到一( pnpm 9, 10和 11 全部写格式) 9.0因此,即使原则上也无法执行管理者版本要求.
错误
Lockfile.parse 这是唯一可谬误的入门点, 它失败了两种方式.
| 标记 | 手段 | 字段 |
|---|---|---|
LockfileParseError | 内容不是该格式的有效锁文件 . | format, (中文(简体) ), stage (单位:千美元)"syntax" 当文字本身没有分析的时候, "validation" 当时,它已分辨了,但无定形, cause (结构,从来没有弦) |
LockfileFramingError | 文本解析了, 但无法在其中找到单个锁定文件 . | format, (中文(简体) ), documents, (中文(简体) ), reason (单位:千美元)"noLockfileDocument", (中文(简体) ), "noImporters", (中文(简体) ), "unexpectedDocuments"(中文(简体) ) |
框架错误存在的原因是 pnpm-lock.yaml 是一个YAML 流线,而不是YAML文档。 pnpm 11 每当工作空间使用时, 在锁定文件前写一个配置依赖性序言 configDependencies,因此文件载有两个文件——序言宣布 lockfileVersion, (中文(简体) ), importers 财务报告和审定财务报表 packages 因此,一个只读第一个文档的解析器 得到一个锁文件 验证 并描述一个空的工作空间。 这是一个错误的答案 完全像一个正确的答案。 这里的规则是定位和决定性,而不是heuristic: pnpm 将序言作前缀,所以锁文件是 最后一个 文档。 没有锁定文件的流未输入 。 它从不堕入空地 Lockfile。 。 。 .
线条定义了任何文档框架, 所以多文档 yarn.lock 内容失败 "unexpectedDocuments" 而不是默默不作声地摘掉第一份文件。 如果格式未说明规则,则该软件包拒绝猜测.
实例和决议
一行进去 lockfile.packages 是一个软件包 实例没有一个包裹。 在不同的同伴下安装两次的依赖是两行 instanceId 是那行的锁文件内在身份 带有逐字记录 当作是 不透明:在模型中查找一个id,从不分拆或图案相匹配,因为其拼写是格式的业务,每个管理器不同.
resolved 将每个从属名称——和每个对等名称的锁文件记录一个解析度——到 instanceId 它实际上决定了,这就是让消费者走 真正的图表,而不是重新溶解范围本身. peerDependencies 财务报告和审定财务报表 peerDependenciesMeta 将声明作为书面形式,默认为 {}。 。 。 .
import { Option } from "effect";
const [instance] = lockfile.packagesNamed("react-dom");
const reactId = instance?.resolved["react"];
const react = reactId === undefined ? Option.none() : lockfile.packageByInstanceId(reactId);
// Option.some(ResolvedPackage) — the version this particular react-dom instance resolved to
// Option.none() when the lockfile records no row under that id
packageByInstanceId 是寻找 而不是扫描 lockfile.packages:它用先呼法建立懒惰的指数,再用它,所以以边走一行的图形边缘保持线性而不是四相. 重复的id解析出第一行,因此一个畸形的锁文件给出了一个稳定的答案而不是迭代顺序.
unresolvedEdges 命名锁定文件的边缘 记录 但模型无法命名。 所列名字没有遗漏——没有答复,因此失踪 resolved 键不能被读取为“什么都没有”。 在信任空结果之前, 关乎完整性的门会检查数组 .
比较两个锁文件
LockfileDiff.compare(before, after) 报告两个解析的锁文件之间的移动情况——安装后更新工具报告的内容。 这是完全和纯洁的,像 LockfileIntegrity.compare:没有效果,没有错误频道,没有IO.
import { Lockfile, LockfileDiff } from "@effected/lockfiles";
import { Effect } from "effect";
declare const beforeText: string; // the lockfile before `pnpm update`
declare const afterText: string; // and after
const program = Effect.gen(function* () {
const before = yield* Lockfile.parse(beforeText, { format: "pnpm" });
const after = yield* Lockfile.parse(afterText, { format: "pnpm" });
const diff = LockfileDiff.compare(before, after);
for (const change of diff.catalogs) {
// A side that does not exist, or a version that does not resolve, is absent.
const from = change.version.before ?? "-";
const to = change.version.after ?? "-";
console.log(`${change.catalog}/${change.name} ${change.kind}: ${from} -> ${to}`);
}
return diff.importers.filter((change) => change.specifier.after?._tag !== "catalog");
});
// With a default catalog entry `lodash: ^4.17.21` resolved at 4.17.23 before
// and 4.17.24 after, and a new `zod` entry, this prints:
// default/lodash changed: 4.17.23 -> 4.17.24
// default/zod added: - -> 3.25.76
catalogs持有一个CatalogChange每个目录条目添加、删除或更改:catalog(单位:千美元)"default"对于每种格式的默认目录,name, (中文(简体) ),specifier财务报告和审定财务报表version作为{ before, after }, (中文(简体) ),kind,以及consumers- 进口商通过下列途径填写条目:catalog:,从before一个已删除条目的锁文件.importers持有一个ImporterChange增加、删除或更改的每个进口依赖importer路径,name财务报告和审定财务报表depType一起来.specifier侧面是分类的,比如:ImporterDependency这样_tag告诉一个catalog:参考文献分开.- 不存在的一面是不存在的,一个版本也是无法解决的. 记录是
changed当它的描述符不同,或者当它的版本在两侧解决而不同时;一个版本仅在一侧解决,本身并不是变化. hasChanges实值false当两个阵列均为空时.
如果一个解析的版本在格式上存在不同,而比较则使其正常化,因此每个格式的答案都是一样的:
| 格式 | 目录条目版本 | 导入者依赖版本 |
|---|---|---|
| 小时 | 记录在条目中 | 记录进口依赖(工作空间链接为: link:<path>(中文(简体) ) |
| 包头 | 该条目的消费者决心使用(不同意不给版本的消费者);没有消费者,当锁文件完全持有一个时,包的版本 | 读取通过进口商的解析边缘到其软件包条目 |
| 无 | - (npm 没有目录) | 读取通过进口商的解析边缘到其软件包条目 |
| 线条 | —— 说 | - (yarn记录没有进口商,所以两个线条锁文件比较为空) |
在一个目录的凸起显示 catalogs,并输入 importers 对已解析的版本已移动的消费者; 过滤器打开 specifier.after?._tag !== "catalog" 将两者分开,如上.
特征
Lockfile.parse(content, { format })—— 软件包唯一的易落边界; 失败LockfileParseError或者说LockfileFramingError。 。 。 .Lockfile#withImporterNames(names)——pnpm的纯二级:一旦消费者读取了货单,就重写进口商-路径名和每个依赖边缘的两端.Lockfile#packagesNamed(name), (中文(简体) ),Lockfile#packageByInstanceId(id)(单位:千美元)Option- 回答,在一个懒散的指数之上)和Lockfile#workspacePackages- 整个看模型.LockfileFormat与filenameFor编号 :fromFilename——格式字面及其映射到锁定文件文件名.LockfileIntegrity.compare(lockfile, manifests)- 完全、纯完整性检查,无出错频道;报告载有valid, (中文(简体) ),missingWorkspaces, (中文(简体) ),extraWorkspaces财务报告和审定财务报表unsatisfiedConstraints严格检查是设计上最好的:workspace:编号 :link:编号 :file:其范围未被描述为SemVer的规格和行被跳过 .LockfileDiff.compare(before, after)总数,比较前后纯度:目录条目(CatalogChange)和进口依赖(ImporterChange) 添加、去除或更改,解决的版本在格式上实现正常化。 见 比较两个锁文件。 。 。 .WorkspaceManifest——完整性检查的显示输入形状:一个包名外加四个可选的依赖性记录. 故意是一种平庸的价值,而不是一个严格的表率模型,因此消费者可以从任何事物中获得.ResolvedPackage- 一个包裹 实例: 名称、版本、可选完整性散列、工作空间旗和相对路径、依赖性地图、对等声明(peerDependencies, (中文(简体) ),peerDependenciesMeta),不透明instanceId,则resolved名称到信息地图和unresolvedEdges模型无法命名 .isUnsupportedLockfileVersion编辑UnsupportedLockfileVersion类型 —— 缩小 a 的上游LockfileParseError.cause所以"太老"和"变形"在标签上被分开而不是在道具上.WorkspaceDependencya 包括:from编号 :to工作空间软件包之间的边缘depType拼写为@effected/npm是因为DependencyField词汇及其宣布的制约.PnpmExtension财务报告和审定财务报表BunExtension——格式特定数据,如pnpm目录和bun信任的依赖性,保存在模型的可选性上extension字段.