@pnpm/deps.graph-hasher 1100.3.0 → 1100.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @pnpm/calc-dep-state
2
2
 
3
+ ## 1100.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixed global virtual store hashes for dependency cycles. Every package that transitively depends on an allowed build now includes the engine in its store path, independent of traversal order [pnpm/pnpm#14341](https://github.com/pnpm/pnpm/issues/14341).
8
+
9
+ - Updated dependencies:
10
+ - @pnpm/lockfile.types@1100.1.1
11
+ - @pnpm/lockfile.utils@1102.1.1
12
+
3
13
  ## 1100.3.0
4
14
 
5
15
  ### Minor Changes
package/lib/index.d.ts CHANGED
@@ -126,11 +126,11 @@ export interface GraphNodeHashOptions {
126
126
  lockfileDir?: string;
127
127
  }
128
128
  export declare function iterateHashedGraphNodes<T extends PkgMeta>(graph: DepsGraph<DepPath>, pkgMetaIterator: PkgMetaIterator<T>, opts?: GraphNodeHashOptions): IterableIterator<HashedDepPath<T>>;
129
- export declare function calcGraphNodeHash<T extends PkgMeta>({ graph, cache, builtDepPaths, buildRequiredCache, supportedArchitectures, nodeVersion, lockfileDir }: {
129
+ export declare function calcGraphNodeHash<T extends PkgMeta>({ graph, cache, buildRequiredDepPaths, supportedArchitectures, nodeVersion, lockfileDir }: {
130
130
  graph: DepsGraph<DepPath>;
131
131
  cache: DepsStateCache;
132
- builtDepPaths?: Set<DepPath>;
133
- buildRequiredCache?: Record<string, boolean>;
132
+ /** See {@link computeBuildRequiredDepPaths}. */
133
+ buildRequiredDepPaths?: Set<DepPath>;
134
134
  supportedArchitectures?: SupportedArchitectures;
135
135
  /** See {@link GraphNodeHashOptions.nodeVersion}. */
136
136
  nodeVersion?: string;
@@ -152,3 +152,18 @@ export interface PkgMetaAndSnapshot extends PkgMeta {
152
152
  }
153
153
  export declare function iteratePkgMeta(lockfile: LockfileObject, graph: DepsGraph<DepPath>): PkgMetaIterator<PkgMetaAndSnapshot>;
154
154
  export declare function lockfileToDepGraph(lockfile: LockfileObject, supportedArchitectures?: SupportedArchitectures, lockfileDir?: string): DepsGraph<DepPath>;
155
+ /**
156
+ * Expand `builtDepPaths` to every node that is, or transitively depends
157
+ * on, one of them.
158
+ *
159
+ * The result gates the engine string in {@link calcGraphNodeHash}: only a
160
+ * package that may run a build script — or that depends on one — keeps the
161
+ * engine in its GVS hash. It is computed as one graph-wide reverse closure,
162
+ * so a package inside a dependency cycle gets the same answer no matter
163
+ * which node the hasher reaches it from.
164
+ *
165
+ * A path with no node in `graph` is kept and still marks whatever depends on
166
+ * it: the built set comes from the allowBuild policy rather than the graph,
167
+ * so the two can disagree.
168
+ */
169
+ export declare function computeBuildRequiredDepPaths(graph: DepsGraph<DepPath>, builtDepPaths: Set<DepPath>): Set<DepPath>;
package/lib/index.js CHANGED
@@ -167,11 +167,11 @@ function createDepGraphHashContext(supportedArchitectures) {
167
167
  };
168
168
  }
169
169
  export function* iterateHashedGraphNodes(graph, pkgMetaIterator, opts = {}) {
170
- let builtDepPaths;
170
+ let buildRequiredDepPaths;
171
171
  let entries;
172
172
  if (opts.allowBuild != null) {
173
173
  const pkgMetaList = Array.from(pkgMetaIterator);
174
- builtDepPaths = computeBuiltDepPaths(pkgMetaList, opts.allowBuild);
174
+ buildRequiredDepPaths = computeBuildRequiredDepPaths(graph, computeBuiltDepPaths(pkgMetaList, opts.allowBuild));
175
175
  entries = pkgMetaList;
176
176
  }
177
177
  else {
@@ -180,8 +180,7 @@ export function* iterateHashedGraphNodes(graph, pkgMetaIterator, opts = {}) {
180
180
  const ctx = {
181
181
  graph,
182
182
  cache: {},
183
- builtDepPaths,
184
- buildRequiredCache: builtDepPaths !== undefined ? {} : undefined,
183
+ buildRequiredDepPaths,
185
184
  supportedArchitectures: opts.supportedArchitectures,
186
185
  nodeVersion: opts.nodeVersion,
187
186
  lockfileDir: opts.lockfileDir,
@@ -193,15 +192,14 @@ export function* iterateHashedGraphNodes(graph, pkgMetaIterator, opts = {}) {
193
192
  };
194
193
  }
195
194
  }
196
- export function calcGraphNodeHash({ graph, cache, builtDepPaths, buildRequiredCache, supportedArchitectures, nodeVersion, lockfileDir }, pkgMeta) {
195
+ export function calcGraphNodeHash({ graph, cache, buildRequiredDepPaths, supportedArchitectures, nodeVersion, lockfileDir }, pkgMeta) {
197
196
  const { name, version, depPath } = pkgMeta;
198
- // When builtDepPaths is provided (derived from the allowBuilds config),
199
- // we only include the engine name for packages that are allowed to build
200
- // or transitively depend on a package that is allowed to build.
197
+ // When buildRequiredDepPaths is provided (derived from the allowBuilds
198
+ // config), we only include the engine name for packages that are allowed
199
+ // to build or transitively depend on a package that is allowed to build.
201
200
  // This makes GVS hashes engine-agnostic for pure-JS packages,
202
201
  // so they survive Node.js upgrades and architecture changes.
203
- const includeEngine = builtDepPaths === undefined ||
204
- transitivelyRequiresBuild(graph, builtDepPaths, buildRequiredCache ??= {}, depPath, new Set());
202
+ const includeEngine = buildRequiredDepPaths === undefined || buildRequiredDepPaths.has(depPath);
205
203
  // A snapshot that declares `engines.runtime` carries the desugared
206
204
  // `node@runtime:<version>` pin as a child; that's the Node the bin
207
205
  // linker spawns for its lifecycle scripts, so it has to drive the
@@ -347,30 +345,47 @@ function computeBuiltDepPaths(entries, allowBuild) {
347
345
  }
348
346
  return builtDepPaths;
349
347
  }
350
- function transitivelyRequiresBuild(graph, builtDepPaths, cache, depPath, parents) {
351
- if (depPath in cache)
352
- return cache[depPath];
353
- if (builtDepPaths.has(depPath)) {
354
- cache[depPath] = true;
355
- return true;
356
- }
357
- const node = graph[depPath];
358
- if (!node) {
359
- cache[depPath] = false;
360
- return false;
361
- }
362
- if (parents.has(depPath)) {
363
- return false;
348
+ /**
349
+ * Expand `builtDepPaths` to every node that is, or transitively depends
350
+ * on, one of them.
351
+ *
352
+ * The result gates the engine string in {@link calcGraphNodeHash}: only a
353
+ * package that may run a build script — or that depends on one — keeps the
354
+ * engine in its GVS hash. It is computed as one graph-wide reverse closure,
355
+ * so a package inside a dependency cycle gets the same answer no matter
356
+ * which node the hasher reaches it from.
357
+ *
358
+ * A path with no node in `graph` is kept and still marks whatever depends on
359
+ * it: the built set comes from the allowBuild policy rather than the graph,
360
+ * so the two can disagree.
361
+ */
362
+ export function computeBuildRequiredDepPaths(graph, builtDepPaths) {
363
+ const buildRequiredDepPaths = new Set(builtDepPaths);
364
+ if (builtDepPaths.size === 0)
365
+ return buildRequiredDepPaths;
366
+ const parentsByChild = new Map();
367
+ for (const parent of Object.keys(graph)) {
368
+ for (const child of Object.values(graph[parent].children)) {
369
+ const parents = parentsByChild.get(child);
370
+ if (parents == null) {
371
+ parentsByChild.set(child, [parent]);
372
+ }
373
+ else {
374
+ parents.push(parent);
375
+ }
376
+ }
364
377
  }
365
- const nextParents = new Set([...parents, depPath]);
366
- for (const childDepPath of Object.values(node.children)) {
367
- if (transitivelyRequiresBuild(graph, builtDepPaths, cache, childDepPath, nextParents)) {
368
- cache[depPath] = true;
369
- return true;
378
+ const pending = Array.from(builtDepPaths);
379
+ while (pending.length > 0) {
380
+ const child = pending.pop();
381
+ for (const parent of parentsByChild.get(child) ?? []) {
382
+ if (!buildRequiredDepPaths.has(parent)) {
383
+ buildRequiredDepPaths.add(parent);
384
+ pending.push(parent);
385
+ }
370
386
  }
371
387
  }
372
- cache[depPath] = false;
373
- return false;
388
+ return buildRequiredDepPaths;
374
389
  }
375
390
  function lockfileDepsToGraphChildren(deps, lockfileDir, linkTargetNodes) {
376
391
  const children = {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/deps.graph-hasher",
3
- "version": "1100.3.0",
3
+ "version": "1100.3.1",
4
4
  "description": "Calculates the state of a dependency",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -30,15 +30,15 @@
30
30
  "@pnpm/crypto.object-hasher": "1100.0.1",
31
31
  "@pnpm/deps.path": "1101.0.1",
32
32
  "@pnpm/engine.runtime.system-version": "1100.0.11",
33
- "@pnpm/lockfile.types": "1100.1.0",
34
- "@pnpm/lockfile.utils": "1102.1.0",
33
+ "@pnpm/lockfile.types": "1100.1.1",
34
+ "@pnpm/lockfile.utils": "1102.1.1",
35
35
  "@pnpm/resolving.resolver-base": "1101.2.0",
36
36
  "@pnpm/types": "1102.1.0",
37
37
  "detect-libc": "^2.1.2"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@jest/globals": "30.4.1",
41
- "@pnpm/deps.graph-hasher": "1100.3.0"
41
+ "@pnpm/deps.graph-hasher": "1100.3.1"
42
42
  },
43
43
  "engines": {
44
44
  "node": ">=22.13"