@pnpm/installing.deps-resolver 1100.0.9 → 1100.1.0

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/lib/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Catalogs } from '@pnpm/catalogs.types';
2
2
  import type { LockfileObject } from '@pnpm/lockfile.types';
3
+ import type { ResolutionPolicyViolation } from '@pnpm/resolving.resolver-base';
3
4
  import { type DependenciesField, type PeerDependencyIssuesByProjects, type PinnedVersion, type ProjectManifest } from '@pnpm/types';
4
5
  import { getWantedDependencies, type WantedDependency } from './getWantedDependencies.js';
5
6
  import { type UpdateMatchingFunction } from './resolveDependencies.js';
@@ -37,6 +38,17 @@ export interface ResolveDependenciesResult {
37
38
  peerDependencyIssuesByProjects: PeerDependencyIssuesByProjects;
38
39
  waitTillAllFetchingsFinish: () => Promise<void>;
39
40
  wantedToBeSkippedPackageIds: Set<string>;
41
+ /**
42
+ * Policy violations collected inline during resolution — each
43
+ * resolver pushes to the list whenever it picks a version that
44
+ * trips one of its own checks (today: `minimumReleaseAge`). The
45
+ * install command reacts via `handleResolutionPolicyViolations`
46
+ * (prompt / abort) and `mutateModules` forwards the array out so
47
+ * the auto-persist path at the install's tail can drain it into
48
+ * the workspace manifest. Empty when no policy is active or no
49
+ * pick violates.
50
+ */
51
+ resolutionPolicyViolations: ResolutionPolicyViolation[];
40
52
  }
41
53
  export declare function resolveDependencies(importers: ImporterToResolve[], opts: ResolveDependenciesOptions & {
42
54
  defaultUpdateDepth: number;
@@ -51,4 +63,15 @@ export declare function resolveDependencies(importers: ImporterToResolve[], opts
51
63
  allowUnusedPatches?: boolean;
52
64
  enableGlobalVirtualStore?: boolean;
53
65
  allProjectIds: string[];
66
+ /**
67
+ * Generic checkpoint invoked between `resolveDependencyTree` and
68
+ * `resolvePeers` once any inline-collected policy violations have
69
+ * been gathered. Callers can prompt, persist, or throw based on
70
+ * the violations. Throwing unwinds before any peer-dep work,
71
+ * lockfile write, package.json update, or modules-dir change.
72
+ * Intentionally policy-neutral: each resolver owns its violation
73
+ * codes and the hook implementer (install command) decides what
74
+ * to do with them.
75
+ */
76
+ handleResolutionPolicyViolations?: (violations: readonly ResolutionPolicyViolation[]) => Promise<void>;
54
77
  }): Promise<ResolveDependenciesResult>;
package/lib/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import path from 'node:path';
2
2
  import { packageManifestLogger, } from '@pnpm/core-loggers';
3
- import { iterateHashedGraphNodes } from '@pnpm/deps.graph-hasher';
3
+ import { findRuntimeNodeVersion, iterateHashedGraphNodes } from '@pnpm/deps.graph-hasher';
4
4
  import { isRuntimeDepPath } from '@pnpm/deps.path';
5
+ import { PnpmError } from '@pnpm/error';
5
6
  import { verifyPatches } from '@pnpm/patching.config';
6
7
  import { safeReadPackageJsonFromDir } from '@pnpm/pkg-manifest.reader';
7
8
  import { getAllDependenciesFromManifest, getSpecFromPackageManifest, } from '@pnpm/pkg-manifest.utils';
@@ -29,7 +30,29 @@ export async function resolveDependencies(importers, opts) {
29
30
  noDependencySelectors: importers.every(({ wantedDependencies }) => wantedDependencies.length === 0),
30
31
  });
31
32
  const projectsToResolve = await Promise.all(importers.map(async (project) => _toResolveImporter(project)));
32
- const { dependenciesTree, outdatedDependencies, resolvedImporters, resolvedPkgsById, wantedToBeSkippedPackageIds, appliedPatches, time, allPeerDepNames, } = await resolveDependencyTree(projectsToResolve, opts);
33
+ const { dependenciesTree, outdatedDependencies, resolvedImporters, resolvedPkgsById, wantedToBeSkippedPackageIds, appliedPatches, time, allPeerDepNames, resolutionPolicyViolations, } = await resolveDependencyTree(projectsToResolve, opts);
34
+ // Resolver-policy gate between main resolution and peer-dep
35
+ // resolution: every resolver records its own policy violations
36
+ // inline as it picks each version, and we hand the accumulated
37
+ // list to the install command's hook. The hook throws to abort
38
+ // cleanly — nothing on disk has changed yet, and we haven't paid
39
+ // the cost of peer resolution. Dispatch stays policy-neutral: each
40
+ // resolver owns its violation codes, and the hook implementer
41
+ // decides what to do with them.
42
+ //
43
+ // If violations fired but no hook was wired, throw rather than
44
+ // silently dropping them — the resolver-policy contract is "every
45
+ // pick that trips a check produces a violation that gets handled";
46
+ // a missing handler means the caller forgot to opt in and would
47
+ // otherwise see policy-rejected versions land in the lockfile.
48
+ if (resolutionPolicyViolations.length > 0) {
49
+ if (!opts.handleResolutionPolicyViolations) {
50
+ throw new PnpmError('RESOLUTION_POLICY_VIOLATIONS_UNHANDLED', `${resolutionPolicyViolations.length} resolution-policy ${resolutionPolicyViolations.length === 1 ? 'violation was' : 'violations were'} produced but no handleResolutionPolicyViolations callback was wired to react to them.`, {
51
+ hint: 'Internal: resolveDependencies needs a handleResolutionPolicyViolations callback whenever a policy that can produce violations (today: minimumReleaseAge) is active. Wire setupPolicyHandlers (in @pnpm/installing.commands) or supply a callback directly.',
52
+ });
53
+ }
54
+ await opts.handleResolutionPolicyViolations(resolutionPolicyViolations);
55
+ }
33
56
  opts.storeController.clearResolutionCache();
34
57
  // We only check whether patches were applied in cases when the whole lockfile was reanalyzed.
35
58
  if (opts.patchedDependencies &&
@@ -211,6 +234,7 @@ export async function resolveDependencies(importers, opts) {
211
234
  peerDependencyIssuesByProjects,
212
235
  waitTillAllFetchingsFinish,
213
236
  wantedToBeSkippedPackageIds,
237
+ resolutionPolicyViolations,
214
238
  };
215
239
  }
216
240
  function addDirectDependenciesToLockfile(newManifest, projectSnapshot, linkedPackages, directDependencies, excludeLinksFromLockfile) {
@@ -323,7 +347,15 @@ function extendGraph(graph, opts) {
323
347
  const pkgMetaIter = iterateGraphPkgMetaEntries(graph, !opts.enableGlobalVirtualStore);
324
348
  // Only use allowBuild for engine-agnostic hash optimization when GVS is on
325
349
  const allowBuild = opts.enableGlobalVirtualStore ? opts.allowBuild : undefined;
326
- for (const { pkgMeta: { depPath }, hash } of iterateHashedGraphNodes(graph, pkgMetaIter, allowBuild, opts.supportedArchitectures)) {
350
+ // Anchor every snapshot's engine hash to the project-pinned Node
351
+ // version (from `engines.runtime` / `devEngines.runtime`) when the
352
+ // resolver produced one — the graph carries it as a
353
+ // `node@runtime:<version>` key. Without this, GVS slots for
354
+ // approved-build packages would hash under the runner's
355
+ // `process.version` instead of the script-runner Node, splitting
356
+ // the cache between pinned and non-pinned installs on the same host.
357
+ const nodeVersion = findRuntimeNodeVersion(Object.keys(graph));
358
+ for (const { pkgMeta: { depPath }, hash } of iterateHashedGraphNodes(graph, pkgMetaIter, allowBuild, opts.supportedArchitectures, nodeVersion)) {
327
359
  const modules = path.join(opts.globalVirtualStoreDir, hash, 'node_modules');
328
360
  const node = graph[depPath];
329
361
  Object.assign(node, {
@@ -2,7 +2,7 @@ import { type CatalogResolver } from '@pnpm/catalogs.resolver';
2
2
  import type { LockfileObject, PackageSnapshot, ResolvedDependencies } from '@pnpm/lockfile.types';
3
3
  import { type PatchGroupRecord } from '@pnpm/patching.config';
4
4
  import type { PatchInfo } from '@pnpm/patching.types';
5
- import { type DirectoryResolution, type PkgResolutionId, type PreferredVersions, type Resolution, type WorkspacePackages } from '@pnpm/resolving.resolver-base';
5
+ import { type DirectoryResolution, type PkgResolutionId, type PreferredVersions, type Resolution, type ResolutionPolicyViolation, type WorkspacePackages } from '@pnpm/resolving.resolver-base';
6
6
  import type { PackageResponse, PkgRequestFetchResult, StoreController } from '@pnpm/store.controller-types';
7
7
  import type { AllowBuild, AllowedDeprecatedVersions, PackageManifest, PackageVersionPolicy, PinnedVersion, PkgIdWithPatchHash, ReadPackageHook, Registries, SupportedArchitectures, TrustPolicy } from '@pnpm/types';
8
8
  import { type WantedDependency } from './getNonDevWantedDependencies.js';
@@ -111,6 +111,16 @@ export interface ResolutionContext {
111
111
  hoistPeers?: boolean;
112
112
  maximumPublishedBy?: Date;
113
113
  publishedByExclude?: PackageVersionPolicy;
114
+ /**
115
+ * Shared accumulator the resolver pushes into when an inline policy
116
+ * check (today: minimumReleaseAge in `npm-resolver`) flags a pick.
117
+ * resolveDependencyTree hands the populated array back to the install
118
+ * command via its return so the post-tree gate can prompt / abort /
119
+ * persist without re-walking the resolved tree. Each verifier code
120
+ * (`MINIMUM_RELEASE_AGE_VIOLATION`, `TRUST_DOWNGRADE`, …) is the
121
+ * contract surface for downstream UX.
122
+ */
123
+ resolutionPolicyViolations: ResolutionPolicyViolation[];
114
124
  trustPolicy?: TrustPolicy;
115
125
  trustPolicyExclude?: PackageVersionPolicy;
116
126
  trustPolicyIgnoreAfter?: number;
@@ -759,6 +759,7 @@ async function resolveDependency(wantedDependency, ctx, options) {
759
759
  name: currentPkg.name,
760
760
  resolution: currentPkg.resolution,
761
761
  version: currentPkg.version,
762
+ publishedAt: currentPkg.pkgId ? ctx.wantedLockfile.time?.[currentPkg.pkgId] : undefined,
762
763
  }
763
764
  : undefined,
764
765
  expectedPkg: currentPkg,
@@ -798,7 +799,7 @@ async function resolveDependency(wantedDependency, ctx, options) {
798
799
  bareSpecifier: wantedDependency.bareSpecifier,
799
800
  version: wantedDependency.alias ? wantedDependency.bareSpecifier : undefined,
800
801
  };
801
- if (wantedDependency.optional && err.code !== 'ERR_PNPM_TRUST_DOWNGRADE' && err.code !== 'ERR_PNPM_NO_MATURE_MATCHING_VERSION') {
802
+ if (wantedDependency.optional && err.code !== 'ERR_PNPM_TRUST_DOWNGRADE') {
802
803
  skippedOptionalDependencyLogger.debug({
803
804
  details: err.toString(),
804
805
  package: wantedDependencyDetails,
@@ -821,6 +822,13 @@ async function resolveDependency(wantedDependency, ctx, options) {
821
822
  rawSpec: wantedDependency.bareSpecifier,
822
823
  },
823
824
  });
825
+ // Resolver-inline policy violations (e.g. minimumReleaseAge) flow up
826
+ // here; collect them onto the shared context so resolveDependencyTree
827
+ // can hand the full set to the install command between
828
+ // resolveDependencyTree and resolvePeers.
829
+ if (pkgResponse.body.policyViolation) {
830
+ ctx.resolutionPolicyViolations.push(pkgResponse.body.policyViolation);
831
+ }
824
832
  // Check if exotic dependencies are disallowed in subdependencies
825
833
  if (ctx.blockExoticSubdeps &&
826
834
  options.currentDepth > 0 &&
@@ -1,7 +1,7 @@
1
1
  import type { Catalogs } from '@pnpm/catalogs.types';
2
2
  import type { LockfileObject } from '@pnpm/lockfile.types';
3
3
  import type { PatchGroupRecord } from '@pnpm/patching.config';
4
- import type { PreferredVersions, Resolution, WorkspacePackages } from '@pnpm/resolving.resolver-base';
4
+ import type { PreferredVersions, Resolution, ResolutionPolicyViolation, WorkspacePackages } from '@pnpm/resolving.resolver-base';
5
5
  import type { StoreController } from '@pnpm/store.controller-types';
6
6
  import type { AllowBuild, AllowedDeprecatedVersions, PinnedVersion, PkgResolutionId, ProjectId, ProjectManifest, ProjectRootDir, ReadPackageHook, Registries, SupportedArchitectures, TrustPolicy } from '@pnpm/types';
7
7
  import type { WantedDependency } from './getNonDevWantedDependencies.js';
@@ -119,5 +119,13 @@ export interface ResolveDependencyTreeResult {
119
119
  wantedToBeSkippedPackageIds: Set<string>;
120
120
  appliedPatches: Set<string>;
121
121
  time?: Record<string, string>;
122
+ /**
123
+ * Policy violations collected inline during resolution — the
124
+ * resolver pushes to this list whenever it picks a package that
125
+ * trips one of its own checks (today: `minimumReleaseAge`). The
126
+ * shape mirrors `ResolutionPolicyViolation`; downstream callers
127
+ * filter by `code` to decide what to do.
128
+ */
129
+ resolutionPolicyViolations: ResolutionPolicyViolation[];
122
130
  }
123
131
  export declare function resolveDependencyTree<T>(importers: Array<ImporterToResolveGeneric<T>>, opts: ResolveDependenciesOptions): Promise<ResolveDependencyTreeResult>;
@@ -1,6 +1,5 @@
1
1
  import { resolveFromCatalog } from '@pnpm/catalogs.resolver';
2
- import { createPackageVersionPolicy } from '@pnpm/config.version-policy';
3
- import { PnpmError } from '@pnpm/error';
2
+ import { createPackageVersionPolicyOrThrow, getPublishedByPolicy } from '@pnpm/config.version-policy';
4
3
  import { globalWarn } from '@pnpm/logger';
5
4
  import { BUILTIN_NAMED_REGISTRIES } from '@pnpm/resolving.npm-resolver';
6
5
  import { partition } from 'ramda';
@@ -10,6 +9,7 @@ import { resolveRootDependencies, } from './resolveDependencies.js';
10
9
  export async function resolveDependencyTree(importers, opts) {
11
10
  const wantedToBeSkippedPackageIds = new Set();
12
11
  const autoInstallPeers = opts.autoInstallPeers === true;
12
+ const { publishedBy, publishedByExclude } = getPublishedByPolicy(opts);
13
13
  const ctx = {
14
14
  allowBuild: opts.allowBuild,
15
15
  autoInstallPeers,
@@ -54,23 +54,14 @@ export async function resolveDependencyTree(importers, opts) {
54
54
  missingPeersOfChildrenByPkgId: {},
55
55
  hoistPeers: autoInstallPeers || opts.dedupePeerDependents,
56
56
  allPeerDepNames: new Set(),
57
- maximumPublishedBy: opts.minimumReleaseAge ? new Date(Date.now() - opts.minimumReleaseAge * 60 * 1000) : undefined,
58
- publishedByExclude: opts.minimumReleaseAgeExclude ? createPackageVersionPolicyByExclude(opts.minimumReleaseAgeExclude, 'minimumReleaseAgeExclude') : undefined,
57
+ maximumPublishedBy: publishedBy,
58
+ publishedByExclude,
59
59
  trustPolicy: opts.trustPolicy,
60
- trustPolicyExclude: opts.trustPolicyExclude ? createPackageVersionPolicyByExclude(opts.trustPolicyExclude, 'trustPolicyExclude') : undefined,
60
+ trustPolicyExclude: opts.trustPolicyExclude ? createPackageVersionPolicyOrThrow(opts.trustPolicyExclude, 'trustPolicyExclude') : undefined,
61
61
  trustPolicyIgnoreAfter: opts.trustPolicyIgnoreAfter,
62
62
  blockExoticSubdeps: opts.blockExoticSubdeps,
63
+ resolutionPolicyViolations: [],
63
64
  };
64
- function createPackageVersionPolicyByExclude(patterns, key) {
65
- try {
66
- return createPackageVersionPolicy(patterns);
67
- }
68
- catch (err) {
69
- if (!err || typeof err !== 'object' || !('message' in err))
70
- throw err;
71
- throw new PnpmError(`INVALID_${key.replace(/([A-Z])/g, '_$1').toUpperCase()}`, `Invalid value in ${key}: ${err.message}`);
72
- }
73
- }
74
65
  const resolveArgs = importers.map((importer) => {
75
66
  const projectSnapshot = opts.wantedLockfile.importers[importer.id];
76
67
  // This may be optimized.
@@ -179,6 +170,7 @@ export async function resolveDependencyTree(importers, opts) {
179
170
  appliedPatches: ctx.appliedPatches,
180
171
  time,
181
172
  allPeerDepNames: ctx.allPeerDepNames,
173
+ resolutionPolicyViolations: ctx.resolutionPolicyViolations,
182
174
  };
183
175
  }
184
176
  function buildTree(ctx, parentId, parentIds, children, depth, installable) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/installing.deps-resolver",
3
- "version": "1100.0.9",
3
+ "version": "1100.1.0",
4
4
  "description": "Resolves dependency graph of a package",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -39,30 +39,30 @@
39
39
  "semver": "^7.7.2",
40
40
  "semver-range-intersect": "^0.3.1",
41
41
  "version-selector-type": "^3.0.0",
42
+ "@pnpm/catalogs.resolver": "1100.0.0",
43
+ "@pnpm/catalogs.types": "1100.0.0",
42
44
  "@pnpm/constants": "1100.0.0",
43
- "@pnpm/config.version-policy": "1100.0.3",
44
- "@pnpm/core-loggers": "1100.0.2",
45
- "@pnpm/deps.graph-hasher": "1100.1.5",
45
+ "@pnpm/config.version-policy": "1100.1.0",
46
46
  "@pnpm/deps.path": "1100.0.3",
47
- "@pnpm/catalogs.types": "1100.0.0",
47
+ "@pnpm/deps.graph-hasher": "1100.2.0",
48
48
  "@pnpm/error": "1100.0.0",
49
- "@pnpm/fetching.pick-fetcher": "1100.0.6",
50
- "@pnpm/catalogs.resolver": "1100.0.0",
51
- "@pnpm/lockfile.pruner": "1100.0.5",
52
- "@pnpm/lockfile.types": "1100.0.5",
53
- "@pnpm/lockfile.preferred-versions": "1100.0.8",
54
- "@pnpm/lockfile.utils": "1100.0.7",
55
- "@pnpm/patching.config": "1100.0.3",
56
- "@pnpm/hooks.types": "1100.0.6",
57
- "@pnpm/patching.types": "1100.0.0",
49
+ "@pnpm/fetching.pick-fetcher": "1100.0.7",
50
+ "@pnpm/hooks.types": "1100.0.7",
51
+ "@pnpm/lockfile.preferred-versions": "1100.0.10",
52
+ "@pnpm/lockfile.pruner": "1100.0.6",
53
+ "@pnpm/lockfile.utils": "1100.0.8",
58
54
  "@pnpm/deps.peer-range": "1100.0.1",
59
- "@pnpm/pkg-manifest.utils": "1100.1.2",
60
- "@pnpm/resolving.npm-resolver": "1101.1.0",
55
+ "@pnpm/patching.config": "1100.0.3",
61
56
  "@pnpm/pkg-manifest.reader": "1100.0.3",
62
- "@pnpm/types": "1101.1.0",
57
+ "@pnpm/lockfile.types": "1100.0.6",
58
+ "@pnpm/patching.types": "1100.0.0",
59
+ "@pnpm/resolving.resolver-base": "1100.2.0",
60
+ "@pnpm/pkg-manifest.utils": "1100.1.4",
61
+ "@pnpm/resolving.npm-resolver": "1101.2.0",
62
+ "@pnpm/store.controller-types": "1100.1.0",
63
63
  "@pnpm/workspace.spec-parser": "1100.0.0",
64
- "@pnpm/store.controller-types": "1100.0.6",
65
- "@pnpm/resolving.resolver-base": "1100.1.3"
64
+ "@pnpm/types": "1101.1.0",
65
+ "@pnpm/core-loggers": "1100.1.0"
66
66
  },
67
67
  "peerDependencies": {
68
68
  "@pnpm/logger": ">=1001.0.0 <1002.0.0"
@@ -72,7 +72,7 @@
72
72
  "@types/normalize-path": "^3.0.2",
73
73
  "@types/ramda": "0.31.1",
74
74
  "@types/semver": "7.7.1",
75
- "@pnpm/installing.deps-resolver": "1100.0.9",
75
+ "@pnpm/installing.deps-resolver": "1100.1.0",
76
76
  "@pnpm/logger": "1100.0.0"
77
77
  },
78
78
  "engines": {