@effected/lockfiles 0.6.0 → 0.6.1

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.
Files changed (2) hide show
  1. package/internal/pnpm.js +43 -3
  2. package/package.json +1 -1
package/internal/pnpm.js CHANGED
@@ -135,6 +135,31 @@ const resolveLinkTarget = (importerPath, target) => {
135
135
  }
136
136
  return segments.length === 0 ? void 0 : segments.join("/");
137
137
  };
138
+ /** The specifier protocol pnpm resolves through the workspace itself. @internal */
139
+ const WORKSPACE_PREFIX = "workspace:";
140
+ /**
141
+ * The importer a `link:` target lands INSIDE, longest path first.
142
+ *
143
+ * pnpm's `publishConfig.linkDirectory` makes a workspace link point at the
144
+ * package's publish directory rather than its root — `link:../bundler/dist/dev/pkg`
145
+ * where `packages/bundler` is the importer — so the target itself is a build
146
+ * output that is no importer and never will be. The owning importer is the
147
+ * longest ancestor of the target that IS one; longest-first so a nested importer
148
+ * beats its parent.
149
+ *
150
+ * The root importer (`"."`) is deliberately unreachable here: it is an ancestor
151
+ * of every path in the workspace, so admitting it would resolve every stray link
152
+ * to the root rather than leaving it unnamed.
153
+ *
154
+ * @internal
155
+ */
156
+ const owningImporter = (target, importerPaths) => {
157
+ const segments = target.split("/");
158
+ for (let end = segments.length - 1; end > 0; end--) {
159
+ const candidate = segments.slice(0, end).join("/");
160
+ if (importerPaths.has(candidate)) return candidate;
161
+ }
162
+ };
138
163
  /**
139
164
  * Resolve one importer's outgoing edges. pnpm records `{ specifier, version }`
140
165
  * per importer dependency, where the version is either a registry version (the
@@ -142,9 +167,22 @@ const resolveLinkTarget = (importerPath, target) => {
142
167
  * naming another importer — normalized against this importer's own path. Both
143
168
  * are checked against the lockfile's instance ids before being emitted.
144
169
  *
170
+ * A `link:` target that is no importer gets one further chance, and only under
171
+ * a `workspace:` SPECIFIER: pnpm resolved that edge through the workspace, so
172
+ * the target directory is known to belong to a workspace package, and
173
+ * {@link owningImporter} names it. The specifier is what makes this a
174
+ * resolution rather than a guess — a hand-written `link:../vendor/stub`
175
+ * specifier names a directory pnpm never claimed was a workspace package, and
176
+ * attributing it to whichever importer happens to contain it would answer with
177
+ * the wrong package's peers instead of admitting it could not name the edge.
178
+ *
179
+ * The snapshot path ({@link resolveEdges}) deliberately does NOT do this: a
180
+ * snapshot body records `name: link:<path>` with no specifier beside it, so the
181
+ * evidence this rule turns on is not available there.
182
+ *
145
183
  * @internal
146
184
  */
147
- const resolveImporterEdges = (importer, importerPath, instanceIds) => {
185
+ const resolveImporterEdges = (importer, importerPath, instanceIds, importerPaths) => {
148
186
  const edges = /* @__PURE__ */ new Map();
149
187
  const unnameable = /* @__PURE__ */ new Set();
150
188
  for (const group of importerDepGroups(importer)) {
@@ -153,7 +191,8 @@ const resolveImporterEdges = (importer, importerPath, instanceIds) => {
153
191
  if (name === "") continue;
154
192
  if (info.version.startsWith(LINK_PREFIX)) {
155
193
  const target = resolveLinkTarget(importerPath, info.version.slice(5));
156
- if (target !== void 0 && instanceIds.has(target)) edges.set(name, target);
194
+ const owner = target === void 0 ? void 0 : instanceIds.has(target) ? target : info.specifier.startsWith(WORKSPACE_PREFIX) ? owningImporter(target, importerPaths) : void 0;
195
+ if (owner !== void 0) edges.set(name, owner);
157
196
  else unnameable.add(name);
158
197
  continue;
159
198
  }
@@ -213,6 +252,7 @@ const toFields = (raw) => Effect.gen(function* () {
213
252
  ...raw.packages ? Object.keys(raw.packages) : [],
214
253
  ...Object.keys(raw.importers)
215
254
  ]);
255
+ const importerPaths = new Set(Object.keys(raw.importers).filter((path) => path !== "." && path !== ""));
216
256
  const emitted = /* @__PURE__ */ new Set();
217
257
  for (const [importerPath, importer] of Object.entries(raw.importers)) {
218
258
  if (importerPath === "." || importerPath === "") continue;
@@ -222,7 +262,7 @@ const toFields = (raw) => Effect.gen(function* () {
222
262
  instanceId: importerPath,
223
263
  isWorkspace: true,
224
264
  relativePath: importerPath,
225
- ...resolveImporterEdges(importer, importerPath, instanceIds)
265
+ ...resolveImporterEdges(importer, importerPath, instanceIds, importerPaths)
226
266
  }));
227
267
  }
228
268
  const emit = (instanceId, meta, edges) => Effect.gen(function* () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effected/lockfiles",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
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": [