@pnpm/hooks.read-package-hook 1100.1.3 → 1100.2.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,23 @@
1
1
  # @pnpm/hooks.read-package-hook
2
2
 
3
+ ## 1100.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies:
8
+ - @pnpm/types@1101.8.0
9
+
10
+ ## 1100.2.0
11
+
12
+ ### Minor Changes
13
+
14
+ - `overrides` now also govern peers that pnpm auto-installs. Previously an override only rewrote dependencies declared in a manifest, so a peer nobody declares — installed because `autoInstallPeers` is on — resolved against its declared peer range and could bring in a second copy of the very package the override pinned. For example, with `overrides: { react: npm:react@19.2.0 }` and a lone `lucide-react` dependency, pnpm installed `react@18.3.1`; it now installs the pinned `react@19.2.0` [#13320](https://github.com/pnpm/pnpm/issues/13320).
15
+
16
+ ### Patch Changes
17
+
18
+ - Updated dependencies:
19
+ - @pnpm/types@1101.7.0
20
+
3
21
  ## 1100.1.3
4
22
 
5
23
  ### Patch Changes
@@ -11,4 +11,19 @@ export interface CreateVersionsOverriderOptions {
11
11
  */
12
12
  convergeDeclaredRanges?: Map<string, Set<string>>;
13
13
  }
14
+ /**
15
+ * Resolves the specifier an override imposes on a single dependency edge, or
16
+ * `undefined` when no override claims it. `'-'` means the edge is removed.
17
+ *
18
+ * Edges that have no declaring manifest — a peer pnpm auto-installs — reach
19
+ * the overrides through this function instead of through the read-package
20
+ * hook, so parent-scoped overrides (`parent>child`) never apply to them.
21
+ */
22
+ export type DependencyOverrider = (name: string, bareSpecifier: string, dir?: string) => string | undefined;
23
+ /**
24
+ * `undefined` when no override in the set could ever claim an undeclared
25
+ * dependency, so the resolver skips the per-peer call in the common case of a
26
+ * project with no overrides — or with parent-scoped ones only.
27
+ */
28
+ export declare function createDependencyOverrider(overrides: VersionOverrideWithoutRawSelector[], rootDir: string): DependencyOverrider | undefined;
14
29
  export declare function createVersionsOverrider(overrides: VersionOverrideWithoutRawSelector[], rootDir: string, opts?: CreateVersionsOverriderOptions): ReadPackageHook;
@@ -4,13 +4,27 @@ import normalizePath from 'normalize-path';
4
4
  import { partition } from 'ramda';
5
5
  import semver from 'semver';
6
6
  import { isIntersectingRange } from './isIntersectingRange.js';
7
+ /**
8
+ * `undefined` when no override in the set could ever claim an undeclared
9
+ * dependency, so the resolver skips the per-peer call in the common case of a
10
+ * project with no overrides — or with parent-scoped ones only.
11
+ */
12
+ export function createDependencyOverrider(overrides, rootDir) {
13
+ const { genericVersionOverrides, convergeVersions } = splitOverrides(overrides, rootDir);
14
+ if (genericVersionOverrides.length === 0 && convergeVersions.size === 0)
15
+ return undefined;
16
+ return (name, bareSpecifier, dir) => {
17
+ const versionOverride = pickVersionOverride({ versionOverrides: [], genericVersionOverrides }, name, bareSpecifier);
18
+ if (versionOverride) {
19
+ return versionOverride.newBareSpecifier === '-'
20
+ ? '-'
21
+ : resolveOverriddenBareSpecifier(versionOverride, dir);
22
+ }
23
+ return convergeBareSpecifier(convergeVersions, name, bareSpecifier);
24
+ };
25
+ }
7
26
  export function createVersionsOverrider(overrides, rootDir, opts) {
8
- const [convergeOverrides, explicitOverrides] = partition(({ converge }) => converge === true, overrides);
9
- const [versionOverrides, genericVersionOverrides] = partition(({ parentPkg }) => parentPkg != null, explicitOverrides.map((override) => ({
10
- ...override,
11
- localTarget: createLocalTarget(override, rootDir),
12
- })));
13
- const convergeVersions = new Map(convergeOverrides.map((override) => [override.targetPkg.name, override.newBareSpecifier]));
27
+ const { versionOverrides, genericVersionOverrides, convergeVersions } = splitOverrides(overrides, rootDir);
14
28
  return ((manifest, dir) => {
15
29
  const versionOverridesWithParent = versionOverrides.filter(({ parentPkg }) => {
16
30
  return (parentPkg.name === manifest.name &&
@@ -23,6 +37,18 @@ export function createVersionsOverrider(overrides, rootDir, opts) {
23
37
  return manifest;
24
38
  });
25
39
  }
40
+ function splitOverrides(overrides, rootDir) {
41
+ const [convergeOverrides, explicitOverrides] = partition(({ converge }) => converge === true, overrides);
42
+ const [versionOverrides, genericVersionOverrides] = partition(({ parentPkg }) => parentPkg != null, explicitOverrides.map((override) => ({
43
+ ...override,
44
+ localTarget: createLocalTarget(override, rootDir),
45
+ })));
46
+ return {
47
+ versionOverrides,
48
+ genericVersionOverrides,
49
+ convergeVersions: new Map(convergeOverrides.map((override) => [override.targetPkg.name, override.newBareSpecifier])),
50
+ };
51
+ }
26
52
  function createLocalTarget(override, rootDir) {
27
53
  let protocol;
28
54
  if (override.newBareSpecifier.startsWith('file:')) {
@@ -55,8 +81,7 @@ function overrideDepsOfPkg({ manifest, dir }, versionOverrides, genericVersionOv
55
81
  }
56
82
  function overrideDeps({ versionOverrides, genericVersionOverrides, dir, convergeOpts }, deps, peerDeps) {
57
83
  for (const [name, bareSpecifier] of Object.entries(peerDeps ?? deps)) {
58
- const versionOverride = pickMostSpecificVersionOverride(versionOverrides.filter(({ targetPkg }) => targetPkg.name === name && isIntersectingRange(targetPkg.bareSpecifier, bareSpecifier))) ??
59
- pickMostSpecificVersionOverride(genericVersionOverrides.filter(({ targetPkg }) => targetPkg.name === name && isIntersectingRange(targetPkg.bareSpecifier, bareSpecifier)));
84
+ const versionOverride = pickVersionOverride({ versionOverrides, genericVersionOverrides }, name, bareSpecifier);
60
85
  if (!versionOverride) {
61
86
  convergeDep(convergeOpts, { deps, peerDeps }, name, bareSpecifier);
62
87
  continue;
@@ -70,9 +95,7 @@ function overrideDeps({ versionOverrides, genericVersionOverrides, dir, converge
70
95
  }
71
96
  continue;
72
97
  }
73
- const newBareSpecifier = versionOverride.localTarget
74
- ? `${versionOverride.localTarget.protocol}${resolveLocalOverride(versionOverride.localTarget, dir)}`
75
- : versionOverride.newBareSpecifier;
98
+ const newBareSpecifier = resolveOverriddenBareSpecifier(versionOverride, dir);
76
99
  if (peerDeps == null || !isValidPeerRange(newBareSpecifier)) {
77
100
  deps[versionOverride.targetPkg.name] = newBareSpecifier;
78
101
  }
@@ -81,6 +104,30 @@ function overrideDeps({ versionOverrides, genericVersionOverrides, dir, converge
81
104
  }
82
105
  }
83
106
  }
107
+ function convergeDep(convergeOpts, { deps, peerDeps }, name, bareSpecifier) {
108
+ recordConvergeDeclaredRange(convergeOpts, name, bareSpecifier);
109
+ const convergeVersion = convergeBareSpecifier(convergeOpts.convergeVersions, name, bareSpecifier);
110
+ if (convergeVersion == null)
111
+ return;
112
+ if (peerDeps == null) {
113
+ deps[name] = convergeVersion;
114
+ }
115
+ else {
116
+ peerDeps[name] = convergeVersion;
117
+ }
118
+ }
119
+ function recordConvergeDeclaredRange({ convergeVersions, convergeDeclaredRanges }, name, bareSpecifier) {
120
+ if (convergeDeclaredRanges == null)
121
+ return;
122
+ if (!convergeVersions.has(name) || semver.validRange(bareSpecifier, true) == null)
123
+ return;
124
+ let ranges = convergeDeclaredRanges.get(name);
125
+ if (ranges == null) {
126
+ ranges = new Set();
127
+ convergeDeclaredRanges.set(name, ranges);
128
+ }
129
+ ranges.add(bareSpecifier);
130
+ }
84
131
  /**
85
132
  * A convergence override (`"pkg@": "<version>"`) rewrites a dependency edge
86
133
  * only when its version satisfies the edge's declared range, so incompatible
@@ -88,26 +135,21 @@ function overrideDeps({ versionOverrides, genericVersionOverrides, dir, converge
88
135
  * `workspace:`, `catalog:`, `npm:`, git/URL, and dist-tag specifiers have no
89
136
  * defined "satisfies" relation and are left untouched.
90
137
  */
91
- function convergeDep({ convergeVersions, convergeDeclaredRanges }, { deps, peerDeps }, name, bareSpecifier) {
138
+ function convergeBareSpecifier(convergeVersions, name, bareSpecifier) {
92
139
  const convergeVersion = convergeVersions.get(name);
93
140
  if (convergeVersion == null || semver.validRange(bareSpecifier, true) == null)
94
- return;
95
- if (convergeDeclaredRanges != null) {
96
- let ranges = convergeDeclaredRanges.get(name);
97
- if (ranges == null) {
98
- ranges = new Set();
99
- convergeDeclaredRanges.set(name, ranges);
100
- }
101
- ranges.add(bareSpecifier);
102
- }
103
- if (!semver.satisfies(convergeVersion, bareSpecifier, true))
104
- return;
105
- if (peerDeps == null) {
106
- deps[name] = convergeVersion;
107
- }
108
- else {
109
- peerDeps[name] = convergeVersion;
110
- }
141
+ return undefined;
142
+ return semver.satisfies(convergeVersion, bareSpecifier, true) ? convergeVersion : undefined;
143
+ }
144
+ function pickVersionOverride({ versionOverrides, genericVersionOverrides }, name, bareSpecifier) {
145
+ const matches = (override) => override.targetPkg.name === name && isIntersectingRange(override.targetPkg.bareSpecifier, bareSpecifier);
146
+ return pickMostSpecificVersionOverride(versionOverrides.filter(matches)) ??
147
+ pickMostSpecificVersionOverride(genericVersionOverrides.filter(matches));
148
+ }
149
+ function resolveOverriddenBareSpecifier(versionOverride, dir) {
150
+ return versionOverride.localTarget
151
+ ? `${versionOverride.localTarget.protocol}${resolveLocalOverride(versionOverride.localTarget, dir)}`
152
+ : versionOverride.newBareSpecifier;
111
153
  }
112
154
  function resolveLocalOverride({ specifiedViaRelativePath, absolutePath }, pkgDir) {
113
155
  return specifiedViaRelativePath && pkgDir
package/lib/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { createReadPackageHook, getEffectivePackageExtensions } from './createReadPackageHook.js';
2
+ export { createDependencyOverrider, type DependencyOverrider } from './createVersionsOverrider.js';
package/lib/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { createReadPackageHook, getEffectivePackageExtensions } from './createReadPackageHook.js';
2
+ export { createDependencyOverrider } from './createVersionsOverrider.js';
2
3
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/hooks.read-package-hook",
3
- "version": "1100.1.3",
3
+ "version": "1100.2.1",
4
4
  "description": "Creates the default package reader hook used by pnpm",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -32,19 +32,19 @@
32
32
  "@pnpm/deps.peer-range": "1100.1.1",
33
33
  "@pnpm/error": "1100.1.0",
34
34
  "@pnpm/resolving.parse-wanted-dependency": "1100.0.2",
35
- "@pnpm/types": "1101.6.0",
36
- "@yarnpkg/extensions": "2.0.6",
35
+ "@pnpm/types": "1101.8.0",
36
+ "@yarnpkg/extensions": "2.0.7",
37
37
  "normalize-path": "^3.0.0",
38
38
  "ramda": "npm:@pnpm/ramda@0.28.1",
39
- "semver": "^7.8.4"
39
+ "semver": "^7.8.5"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@jest/globals": "30.4.1",
43
- "@pnpm/hooks.read-package-hook": "1100.1.3",
43
+ "@pnpm/hooks.read-package-hook": "1100.2.1",
44
44
  "@types/normalize-path": "^3.0.2",
45
- "@types/ramda": "0.31.1",
45
+ "@types/ramda": "0.32.0",
46
46
  "@types/semver": "7.7.1",
47
- "@yarnpkg/core": "4.8.0"
47
+ "@yarnpkg/core": "4.9.1"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=22.13"