@effected/lockfiles 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/internal/pnpm.js +84 -6
- package/package.json +2 -2
- package/tsdoc-metadata.json +1 -1
package/internal/pnpm.js
CHANGED
|
@@ -14,7 +14,8 @@ const PnpmImporter = Schema.Struct({
|
|
|
14
14
|
dependencies: PnpmImporterDeps,
|
|
15
15
|
devDependencies: PnpmImporterDeps,
|
|
16
16
|
peerDependencies: PnpmImporterDeps,
|
|
17
|
-
optionalDependencies: PnpmImporterDeps
|
|
17
|
+
optionalDependencies: PnpmImporterDeps,
|
|
18
|
+
publishDirectory: Schema.optionalKey(Schema.String)
|
|
18
19
|
});
|
|
19
20
|
/**
|
|
20
21
|
* The version gate's own input: `lockfileVersion` and nothing else.
|
|
@@ -89,9 +90,23 @@ const parsePnpm = (content) => Effect.gen(function* () {
|
|
|
89
90
|
* resolution, which names no instance in this lockfile, from becoming a
|
|
90
91
|
* plausible lie.
|
|
91
92
|
*
|
|
93
|
+
* A failed composition gets one second reading: the recorded version ITSELF as
|
|
94
|
+
* an instance id. That is how pnpm spells an `npm:` alias — the dependency
|
|
95
|
+
* `typescript-classic: npm:typescript@^6.0.3` is recorded as
|
|
96
|
+
* `typescript-classic: typescript@6.0.3`, the referenced instance's own key,
|
|
97
|
+
* peer suffix included when one applies. Still compose-then-verify, not a
|
|
98
|
+
* guess: a plain version (`6.0.3`) can never be an instance id, because ids
|
|
99
|
+
* always carry a name, so the reading admits exactly the alias shape.
|
|
100
|
+
*
|
|
101
|
+
* Unlike {@link resolveImporterEdges}, there is no `workspace:`-specifier
|
|
102
|
+
* ancestor walk here — a snapshot body records no specifier, so that evidence
|
|
103
|
+
* does not exist on this path. The publish-directory map needs no specifier:
|
|
104
|
+
* the importer entry's own `publishDirectory` declaration is exact evidence,
|
|
105
|
+
* available to both paths, and is consulted unconditionally.
|
|
106
|
+
*
|
|
92
107
|
* @internal
|
|
93
108
|
*/
|
|
94
|
-
const resolveEdges = (sections, instanceIds) => {
|
|
109
|
+
const resolveEdges = (sections, instanceIds, publishDirTargets) => {
|
|
95
110
|
const edges = /* @__PURE__ */ new Map();
|
|
96
111
|
const unnameable = /* @__PURE__ */ new Set();
|
|
97
112
|
for (const section of sections) {
|
|
@@ -101,11 +116,13 @@ const resolveEdges = (sections, instanceIds) => {
|
|
|
101
116
|
if (version.startsWith(LINK_PREFIX)) {
|
|
102
117
|
const target = resolveLinkTarget("", version.slice(LINK_PREFIX.length));
|
|
103
118
|
if (target !== void 0 && instanceIds.has(target)) edges.set(name, target);
|
|
119
|
+
else if (target !== void 0 && publishDirTargets.has(target)) edges.set(name, publishDirTargets.get(target));
|
|
104
120
|
else unnameable.add(name);
|
|
105
121
|
continue;
|
|
106
122
|
}
|
|
107
123
|
const bare = `${name}@${version}`;
|
|
108
124
|
if (instanceIds.has(bare)) edges.set(name, bare);
|
|
125
|
+
else if (instanceIds.has(version)) edges.set(name, version);
|
|
109
126
|
else unnameable.add(name);
|
|
110
127
|
}
|
|
111
128
|
}
|
|
@@ -121,6 +138,13 @@ const LINK_PREFIX = "link:";
|
|
|
121
138
|
* workspace edge can name the workspace importer's instance id.
|
|
122
139
|
* Total: a target that walks above the root yields `undefined`.
|
|
123
140
|
*
|
|
141
|
+
* A target that normalizes to NOTHING — `link:.` recorded root-relative —
|
|
142
|
+
* also yields `undefined` rather than the root importer's ".": the root
|
|
143
|
+
* importer deliberately emits no `ResolvedPackage` row (its path cannot serve
|
|
144
|
+
* as a package name), so mapping the empty target to "." would hand back an id
|
|
145
|
+
* no row carries, and no pnpm output recording that shape has been observed.
|
|
146
|
+
* The edge stays honestly unnameable instead.
|
|
147
|
+
*
|
|
124
148
|
* @internal
|
|
125
149
|
*/
|
|
126
150
|
const resolveLinkTarget = (importerPath, target) => {
|
|
@@ -135,6 +159,31 @@ const resolveLinkTarget = (importerPath, target) => {
|
|
|
135
159
|
}
|
|
136
160
|
return segments.length === 0 ? void 0 : segments.join("/");
|
|
137
161
|
};
|
|
162
|
+
/** The specifier protocol pnpm resolves through the workspace itself. @internal */
|
|
163
|
+
const WORKSPACE_PREFIX = "workspace:";
|
|
164
|
+
/**
|
|
165
|
+
* The importer a `link:` target lands INSIDE, longest path first.
|
|
166
|
+
*
|
|
167
|
+
* pnpm's `publishConfig.linkDirectory` makes a workspace link point at the
|
|
168
|
+
* package's publish directory rather than its root — `link:../bundler/dist/dev/pkg`
|
|
169
|
+
* where `packages/bundler` is the importer — so the target itself is a build
|
|
170
|
+
* output that is no importer and never will be. The owning importer is the
|
|
171
|
+
* longest ancestor of the target that IS one; longest-first so a nested importer
|
|
172
|
+
* beats its parent.
|
|
173
|
+
*
|
|
174
|
+
* The root importer (`"."`) is deliberately unreachable here: it is an ancestor
|
|
175
|
+
* of every path in the workspace, so admitting it would resolve every stray link
|
|
176
|
+
* to the root rather than leaving it unnamed.
|
|
177
|
+
*
|
|
178
|
+
* @internal
|
|
179
|
+
*/
|
|
180
|
+
const owningImporter = (target, importerPaths) => {
|
|
181
|
+
const segments = target.split("/");
|
|
182
|
+
for (let end = segments.length - 1; end > 0; end--) {
|
|
183
|
+
const candidate = segments.slice(0, end).join("/");
|
|
184
|
+
if (importerPaths.has(candidate)) return candidate;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
138
187
|
/**
|
|
139
188
|
* Resolve one importer's outgoing edges. pnpm records `{ specifier, version }`
|
|
140
189
|
* per importer dependency, where the version is either a registry version (the
|
|
@@ -142,9 +191,28 @@ const resolveLinkTarget = (importerPath, target) => {
|
|
|
142
191
|
* naming another importer — normalized against this importer's own path. Both
|
|
143
192
|
* are checked against the lockfile's instance ids before being emitted.
|
|
144
193
|
*
|
|
194
|
+
* A `link:` target that is no importer gets two further chances. First the
|
|
195
|
+
* publish-directory map: an importer that DECLARES `publishDirectory` has told
|
|
196
|
+
* the lockfile exactly which build directory its links point at, so a target
|
|
197
|
+
* matching a declared publish directory names that importer on evidence, no
|
|
198
|
+
* specifier required — {@link resolveEdges} consults the same map for the same
|
|
199
|
+
* reason. Then, and only under a `workspace:` SPECIFIER, the ancestor walk:
|
|
200
|
+
* pnpm resolved that edge through the workspace, so the target directory is
|
|
201
|
+
* known to belong to a workspace package, and {@link owningImporter} names it.
|
|
202
|
+
* The specifier is what makes the walk a resolution rather than a guess — a
|
|
203
|
+
* hand-written `link:../vendor/stub` specifier names a directory pnpm never
|
|
204
|
+
* claimed was a workspace package, and attributing it to whichever importer
|
|
205
|
+
* happens to contain it would answer with the wrong package's peers instead of
|
|
206
|
+
* admitting it could not name the edge. The snapshot path has no specifier and
|
|
207
|
+
* therefore no walk; the publish-directory map is the evidence it does get.
|
|
208
|
+
*
|
|
209
|
+
* A failed `name@version` composition gets the same alias reading as
|
|
210
|
+
* {@link resolveEdges}: the recorded version itself as an instance id, which is
|
|
211
|
+
* how pnpm spells `npm:` aliases.
|
|
212
|
+
*
|
|
145
213
|
* @internal
|
|
146
214
|
*/
|
|
147
|
-
const resolveImporterEdges = (importer, importerPath, instanceIds) => {
|
|
215
|
+
const resolveImporterEdges = (importer, importerPath, instanceIds, importerPaths, publishDirTargets) => {
|
|
148
216
|
const edges = /* @__PURE__ */ new Map();
|
|
149
217
|
const unnameable = /* @__PURE__ */ new Set();
|
|
150
218
|
for (const group of importerDepGroups(importer)) {
|
|
@@ -153,12 +221,14 @@ const resolveImporterEdges = (importer, importerPath, instanceIds) => {
|
|
|
153
221
|
if (name === "") continue;
|
|
154
222
|
if (info.version.startsWith(LINK_PREFIX)) {
|
|
155
223
|
const target = resolveLinkTarget(importerPath, info.version.slice(5));
|
|
156
|
-
|
|
224
|
+
const owner = target === void 0 ? void 0 : instanceIds.has(target) ? target : publishDirTargets.get(target) ?? (info.specifier.startsWith(WORKSPACE_PREFIX) ? owningImporter(target, importerPaths) : void 0);
|
|
225
|
+
if (owner !== void 0) edges.set(name, owner);
|
|
157
226
|
else unnameable.add(name);
|
|
158
227
|
continue;
|
|
159
228
|
}
|
|
160
229
|
const bare = `${name}@${info.version}`;
|
|
161
230
|
if (instanceIds.has(bare)) edges.set(name, bare);
|
|
231
|
+
else if (instanceIds.has(info.version)) edges.set(name, info.version);
|
|
162
232
|
else unnameable.add(name);
|
|
163
233
|
}
|
|
164
234
|
}
|
|
@@ -213,6 +283,14 @@ const toFields = (raw) => Effect.gen(function* () {
|
|
|
213
283
|
...raw.packages ? Object.keys(raw.packages) : [],
|
|
214
284
|
...Object.keys(raw.importers)
|
|
215
285
|
]);
|
|
286
|
+
const importerPaths = new Set(Object.keys(raw.importers).filter((path) => path !== "." && path !== ""));
|
|
287
|
+
const publishDirTargets = /* @__PURE__ */ new Map();
|
|
288
|
+
for (const [importerPath, importer] of Object.entries(raw.importers)) {
|
|
289
|
+
if (importerPath === "") continue;
|
|
290
|
+
if (importer.publishDirectory === void 0) continue;
|
|
291
|
+
const key = resolveLinkTarget(importerPath, importer.publishDirectory);
|
|
292
|
+
if (key !== void 0) publishDirTargets.set(key, importerPath);
|
|
293
|
+
}
|
|
216
294
|
const emitted = /* @__PURE__ */ new Set();
|
|
217
295
|
for (const [importerPath, importer] of Object.entries(raw.importers)) {
|
|
218
296
|
if (importerPath === "." || importerPath === "") continue;
|
|
@@ -222,7 +300,7 @@ const toFields = (raw) => Effect.gen(function* () {
|
|
|
222
300
|
instanceId: importerPath,
|
|
223
301
|
isWorkspace: true,
|
|
224
302
|
relativePath: importerPath,
|
|
225
|
-
...resolveImporterEdges(importer, importerPath, instanceIds)
|
|
303
|
+
...resolveImporterEdges(importer, importerPath, instanceIds, importerPaths, publishDirTargets)
|
|
226
304
|
}));
|
|
227
305
|
}
|
|
228
306
|
const emit = (instanceId, meta, edges) => Effect.gen(function* () {
|
|
@@ -240,7 +318,7 @@ const toFields = (raw) => Effect.gen(function* () {
|
|
|
240
318
|
...integrity !== void 0 ? { integrity } : {},
|
|
241
319
|
isWorkspace: false,
|
|
242
320
|
...peerDeclarations(meta?.peerDependencies, meta?.peerDependenciesMeta),
|
|
243
|
-
...resolveEdges(edges, instanceIds)
|
|
321
|
+
...resolveEdges(edges, instanceIds, publishDirTargets)
|
|
244
322
|
}));
|
|
245
323
|
});
|
|
246
324
|
if (snapshots) for (const [key, snapshot] of Object.entries(snapshots)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effected/lockfiles",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Pure lockfile parsing for bun, npm, pnpm and yarn Berry into one unified Effect schema model, with pure integrity checking against workspace manifests.",
|
|
6
6
|
"keywords": [
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@effected/jsonc": "^0.7.0",
|
|
44
|
-
"@effected/npm": "^0.11.
|
|
44
|
+
"@effected/npm": "^0.11.1",
|
|
45
45
|
"@effected/semver": "^0.5.0",
|
|
46
46
|
"@effected/yaml": "^0.10.0",
|
|
47
47
|
"effect": "4.0.0-rc.109"
|