@pnpm/hooks.read-package-hook 1100.1.2 → 1100.2.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/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @pnpm/hooks.read-package-hook
2
2
 
3
+ ## 1100.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - `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).
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies:
12
+ - @pnpm/types@1101.7.0
13
+
14
+ ## 1100.1.3
15
+
16
+ ### Patch Changes
17
+
18
+ - Republished every package: the tarballs published by the v11.13.1 through v11.16.0 releases were missing most of their compiled files due to a packing bug [#13164](https://github.com/pnpm/pnpm/issues/13164).
19
+
20
+ - Updated dependencies:
21
+ - @pnpm/config.matcher@1100.0.2
22
+ - @pnpm/config.parse-overrides@1100.1.1
23
+ - @pnpm/deps.peer-range@1100.1.1
24
+ - @pnpm/error@1100.1.0
25
+ - @pnpm/resolving.parse-wanted-dependency@1100.0.2
26
+ - @pnpm/types@1101.6.0
27
+
3
28
  ## 1100.1.2
4
29
 
5
30
  ### Patch Changes
@@ -0,0 +1,2 @@
1
+ import type { ReadPackageHook } from '@pnpm/types';
2
+ export declare function createOptionalDependenciesRemover(toBeRemoved: string[]): ReadPackageHook;
@@ -0,0 +1,17 @@
1
+ import { createMatcher } from '@pnpm/config.matcher';
2
+ export function createOptionalDependenciesRemover(toBeRemoved) {
3
+ if (!toBeRemoved.length)
4
+ return (manifest) => manifest;
5
+ const shouldBeRemoved = createMatcher(toBeRemoved);
6
+ return (manifest) => removeOptionalDependencies(manifest, shouldBeRemoved);
7
+ }
8
+ function removeOptionalDependencies(manifest, shouldBeRemoved) {
9
+ for (const optionalDependency in manifest.optionalDependencies) {
10
+ if (shouldBeRemoved(optionalDependency)) {
11
+ delete manifest.optionalDependencies[optionalDependency];
12
+ delete manifest.dependencies?.[optionalDependency];
13
+ }
14
+ }
15
+ return manifest;
16
+ }
17
+ //# sourceMappingURL=createOptionalDependenciesRemover.js.map
@@ -0,0 +1,2 @@
1
+ import type { PackageExtension, ReadPackageHook } from '@pnpm/types';
2
+ export declare function createPackageExtender(packageExtensions: Record<string, PackageExtension>): ReadPackageHook;
@@ -0,0 +1,36 @@
1
+ import { parseWantedDependency } from '@pnpm/resolving.parse-wanted-dependency';
2
+ import semver from 'semver';
3
+ export function createPackageExtender(packageExtensions) {
4
+ const extensionsByPkgName = new Map();
5
+ for (const selector in packageExtensions) {
6
+ const packageExtension = packageExtensions[selector];
7
+ const { alias, bareSpecifier } = parseWantedDependency(selector);
8
+ if (!extensionsByPkgName.has(alias)) {
9
+ extensionsByPkgName.set(alias, []);
10
+ }
11
+ extensionsByPkgName.get(alias).push({ packageExtension, range: bareSpecifier });
12
+ }
13
+ return extendPkgHook.bind(null, extensionsByPkgName);
14
+ }
15
+ function extendPkgHook(extensionsByPkgName, manifest) {
16
+ const extensions = extensionsByPkgName.get(manifest.name);
17
+ if (extensions == null)
18
+ return manifest;
19
+ extendPkg(manifest, extensions);
20
+ return manifest;
21
+ }
22
+ function extendPkg(manifest, extensions) {
23
+ for (const { range, packageExtension } of extensions) {
24
+ if (range != null && !semver.satisfies(manifest.version, range))
25
+ continue;
26
+ for (const field of ['dependencies', 'optionalDependencies', 'peerDependencies', 'peerDependenciesMeta']) {
27
+ if (!packageExtension[field])
28
+ continue;
29
+ manifest[field] = {
30
+ ...packageExtension[field],
31
+ ...manifest[field],
32
+ }; // eslint-disable-line @typescript-eslint/no-explicit-any
33
+ }
34
+ }
35
+ }
36
+ //# sourceMappingURL=createPackageExtender.js.map
@@ -0,0 +1,15 @@
1
+ import type { PackageExtension, ReadPackageHook } from '@pnpm/types';
2
+ import { type CreateVersionsOverriderOptions, type VersionOverrideWithoutRawSelector } from './createVersionsOverrider.js';
3
+ export declare function getEffectivePackageExtensions({ ignoreCompatibilityDb, packageExtensions, }: {
4
+ ignoreCompatibilityDb?: boolean;
5
+ packageExtensions?: Record<string, PackageExtension>;
6
+ }): Record<string, PackageExtension> | undefined;
7
+ export declare function createReadPackageHook({ ignoreCompatibilityDb, lockfileDir, overrides, convergeDeclaredRanges, ignoredOptionalDependencies, packageExtensions, readPackageHook, }: {
8
+ ignoreCompatibilityDb?: boolean;
9
+ lockfileDir: string;
10
+ overrides?: VersionOverrideWithoutRawSelector[];
11
+ convergeDeclaredRanges?: CreateVersionsOverriderOptions['convergeDeclaredRanges'];
12
+ ignoredOptionalDependencies?: string[];
13
+ packageExtensions?: Record<string, PackageExtension>;
14
+ readPackageHook?: ReadPackageHook[] | ReadPackageHook;
15
+ }): ReadPackageHook | undefined;
@@ -0,0 +1,79 @@
1
+ import { packageExtensions as compatPackageExtensions } from '@yarnpkg/extensions';
2
+ import { isEmpty, pipeWith } from 'ramda';
3
+ import { createOptionalDependenciesRemover } from './createOptionalDependenciesRemover.js';
4
+ import { createPackageExtender } from './createPackageExtender.js';
5
+ import { createVersionsOverrider } from './createVersionsOverrider.js';
6
+ const PACKAGE_EXTENSION_FIELDS = [
7
+ 'dependencies',
8
+ 'optionalDependencies',
9
+ 'peerDependencies',
10
+ 'peerDependenciesMeta',
11
+ ];
12
+ export function getEffectivePackageExtensions({ ignoreCompatibilityDb, packageExtensions, }) {
13
+ const effectivePackageExtensions = {};
14
+ if (!ignoreCompatibilityDb) {
15
+ mergePackageExtensions(effectivePackageExtensions, compatPackageExtensions);
16
+ }
17
+ if (!isEmpty(packageExtensions ?? {})) {
18
+ mergePackageExtensions(effectivePackageExtensions, Object.entries(packageExtensions));
19
+ }
20
+ return isEmpty(effectivePackageExtensions) ? undefined : effectivePackageExtensions;
21
+ }
22
+ export function createReadPackageHook({ ignoreCompatibilityDb, lockfileDir, overrides, convergeDeclaredRanges, ignoredOptionalDependencies, packageExtensions, readPackageHook, }) {
23
+ const hooks = [];
24
+ const effectivePackageExtensions = getEffectivePackageExtensions({
25
+ ignoreCompatibilityDb,
26
+ packageExtensions,
27
+ });
28
+ if (effectivePackageExtensions != null) {
29
+ hooks.push(createPackageExtender(effectivePackageExtensions));
30
+ }
31
+ if (Array.isArray(readPackageHook)) {
32
+ hooks.push(...readPackageHook);
33
+ }
34
+ else if (readPackageHook) {
35
+ hooks.push(readPackageHook);
36
+ }
37
+ if (!isEmpty(overrides ?? {})) {
38
+ hooks.push(createVersionsOverrider(overrides, lockfileDir, { convergeDeclaredRanges }));
39
+ }
40
+ if (ignoredOptionalDependencies && !isEmpty(ignoredOptionalDependencies)) {
41
+ hooks.push(createOptionalDependenciesRemover(ignoredOptionalDependencies));
42
+ }
43
+ if (hooks.length === 0) {
44
+ return undefined;
45
+ }
46
+ const readPackageAndExtend = hooks.length === 1
47
+ ? hooks[0]
48
+ : ((pkg, dir) => pipeWith(async (f, res) => f(await res, dir), hooks)(pkg, dir)); // eslint-disable-line @typescript-eslint/no-explicit-any
49
+ return readPackageAndExtend;
50
+ }
51
+ function mergePackageExtensions(target, entries) {
52
+ for (const [selector, packageExtension] of entries) {
53
+ target[selector] = mergePackageExtension(target[selector], packageExtension);
54
+ }
55
+ }
56
+ function mergePackageExtension(previous, next) {
57
+ if (previous == null)
58
+ return clonePackageExtension(next);
59
+ const merged = clonePackageExtension(previous);
60
+ for (const field of PACKAGE_EXTENSION_FIELDS) {
61
+ if (next[field] == null)
62
+ continue;
63
+ merged[field] = {
64
+ ...next[field],
65
+ ...merged[field],
66
+ };
67
+ }
68
+ return merged;
69
+ }
70
+ function clonePackageExtension(packageExtension) {
71
+ const cloned = {};
72
+ for (const field of PACKAGE_EXTENSION_FIELDS) {
73
+ if (packageExtension[field] == null)
74
+ continue;
75
+ cloned[field] = { ...packageExtension[field] };
76
+ }
77
+ return cloned;
78
+ }
79
+ //# sourceMappingURL=createReadPackageHook.js.map
@@ -0,0 +1,29 @@
1
+ import type { VersionOverride as VersionOverrideBase } from '@pnpm/config.parse-overrides';
2
+ import type { ReadPackageHook } from '@pnpm/types';
3
+ export type VersionOverrideWithoutRawSelector = Omit<VersionOverrideBase, 'selector'>;
4
+ export interface CreateVersionsOverriderOptions {
5
+ /**
6
+ * Populated with every declared semver range seen for packages that have a
7
+ * convergence override, whether or not the override's version satisfied it.
8
+ * Feeds the staleness check for convergence overrides after a full
9
+ * resolution. Edges claimed by an explicit override are not recorded — the
10
+ * convergence override never governs them.
11
+ */
12
+ convergeDeclaredRanges?: Map<string, Set<string>>;
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;
29
+ export declare function createVersionsOverrider(overrides: VersionOverrideWithoutRawSelector[], rootDir: string, opts?: CreateVersionsOverriderOptions): ReadPackageHook;
@@ -0,0 +1,162 @@
1
+ import path from 'node:path';
2
+ import { isValidPeerRange } from '@pnpm/deps.peer-range';
3
+ import normalizePath from 'normalize-path';
4
+ import { partition } from 'ramda';
5
+ import semver from 'semver';
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
+ }
26
+ export function createVersionsOverrider(overrides, rootDir, opts) {
27
+ const { versionOverrides, genericVersionOverrides, convergeVersions } = splitOverrides(overrides, rootDir);
28
+ return ((manifest, dir) => {
29
+ const versionOverridesWithParent = versionOverrides.filter(({ parentPkg }) => {
30
+ return (parentPkg.name === manifest.name &&
31
+ (!parentPkg.bareSpecifier || semver.satisfies(manifest.version, parentPkg.bareSpecifier)));
32
+ });
33
+ overrideDepsOfPkg({ manifest, dir }, versionOverridesWithParent, genericVersionOverrides, {
34
+ convergeVersions,
35
+ convergeDeclaredRanges: opts?.convergeDeclaredRanges,
36
+ });
37
+ return manifest;
38
+ });
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
+ }
52
+ function createLocalTarget(override, rootDir) {
53
+ let protocol;
54
+ if (override.newBareSpecifier.startsWith('file:')) {
55
+ protocol = 'file:';
56
+ }
57
+ else if (override.newBareSpecifier.startsWith('link:')) {
58
+ protocol = 'link:';
59
+ }
60
+ else {
61
+ return undefined;
62
+ }
63
+ const pkgPath = override.newBareSpecifier.substring(protocol.length);
64
+ const specifiedViaRelativePath = !path.isAbsolute(pkgPath);
65
+ const absolutePath = specifiedViaRelativePath ? path.join(rootDir, pkgPath) : pkgPath;
66
+ return { absolutePath, specifiedViaRelativePath, protocol };
67
+ }
68
+ function overrideDepsOfPkg({ manifest, dir }, versionOverrides, genericVersionOverrides, convergeOpts) {
69
+ const { dependencies, optionalDependencies, devDependencies, peerDependencies } = manifest;
70
+ const _overrideDeps = overrideDeps.bind(null, { versionOverrides, genericVersionOverrides, dir, convergeOpts });
71
+ for (const deps of [dependencies, optionalDependencies, devDependencies]) {
72
+ if (deps) {
73
+ _overrideDeps(deps, undefined);
74
+ }
75
+ }
76
+ if (peerDependencies) {
77
+ if (!manifest.dependencies)
78
+ manifest.dependencies = {};
79
+ _overrideDeps(manifest.dependencies, peerDependencies);
80
+ }
81
+ }
82
+ function overrideDeps({ versionOverrides, genericVersionOverrides, dir, convergeOpts }, deps, peerDeps) {
83
+ for (const [name, bareSpecifier] of Object.entries(peerDeps ?? deps)) {
84
+ const versionOverride = pickVersionOverride({ versionOverrides, genericVersionOverrides }, name, bareSpecifier);
85
+ if (!versionOverride) {
86
+ convergeDep(convergeOpts, { deps, peerDeps }, name, bareSpecifier);
87
+ continue;
88
+ }
89
+ if (versionOverride.newBareSpecifier === '-') {
90
+ if (peerDeps) {
91
+ delete peerDeps[versionOverride.targetPkg.name];
92
+ }
93
+ else {
94
+ delete deps[versionOverride.targetPkg.name];
95
+ }
96
+ continue;
97
+ }
98
+ const newBareSpecifier = resolveOverriddenBareSpecifier(versionOverride, dir);
99
+ if (peerDeps == null || !isValidPeerRange(newBareSpecifier)) {
100
+ deps[versionOverride.targetPkg.name] = newBareSpecifier;
101
+ }
102
+ else if (isValidPeerRange(newBareSpecifier)) {
103
+ peerDeps[versionOverride.targetPkg.name] = newBareSpecifier;
104
+ }
105
+ }
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
+ }
131
+ /**
132
+ * A convergence override (`"pkg@": "<version>"`) rewrites a dependency edge
133
+ * only when its version satisfies the edge's declared range, so incompatible
134
+ * consumers keep their own resolution. Only plain semver ranges participate:
135
+ * `workspace:`, `catalog:`, `npm:`, git/URL, and dist-tag specifiers have no
136
+ * defined "satisfies" relation and are left untouched.
137
+ */
138
+ function convergeBareSpecifier(convergeVersions, name, bareSpecifier) {
139
+ const convergeVersion = convergeVersions.get(name);
140
+ if (convergeVersion == null || semver.validRange(bareSpecifier, true) == null)
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;
153
+ }
154
+ function resolveLocalOverride({ specifiedViaRelativePath, absolutePath }, pkgDir) {
155
+ return specifiedViaRelativePath && pkgDir
156
+ ? normalizePath(path.relative(pkgDir, absolutePath))
157
+ : absolutePath;
158
+ }
159
+ function pickMostSpecificVersionOverride(versionOverrides) {
160
+ return versionOverrides.sort((a, b) => isIntersectingRange(b.targetPkg.bareSpecifier ?? '', a.targetPkg.bareSpecifier ?? '') ? -1 : 1)[0];
161
+ }
162
+ //# sourceMappingURL=createVersionsOverrider.js.map
package/lib/index.d.ts ADDED
@@ -0,0 +1,2 @@
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
@@ -0,0 +1 @@
1
+ export declare function isIntersectingRange(range1: string | undefined, range2: string): boolean;
@@ -0,0 +1,9 @@
1
+ import semver from 'semver';
2
+ export function isIntersectingRange(range1, range2) {
3
+ return !range1 ||
4
+ range2 === range1 ||
5
+ semver.validRange(range2) != null &&
6
+ semver.validRange(range1) != null &&
7
+ semver.intersects(range2, range1);
8
+ }
9
+ //# sourceMappingURL=isIntersectingRange.js.map
@@ -0,0 +1 @@
1
+ export declare function isSubRange(superRange: string | undefined, subRange: string): boolean;
@@ -0,0 +1,9 @@
1
+ import semver from 'semver';
2
+ export function isSubRange(superRange, subRange) {
3
+ return !superRange ||
4
+ subRange === superRange ||
5
+ semver.validRange(subRange) != null &&
6
+ semver.validRange(superRange) != null &&
7
+ semver.subset(subRange, superRange);
8
+ }
9
+ //# sourceMappingURL=isSubRange.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pnpm/hooks.read-package-hook",
3
- "version": "1100.1.2",
3
+ "version": "1100.2.0",
4
4
  "description": "Creates the default package reader hook used by pnpm",
5
5
  "keywords": [
6
6
  "pnpm",
@@ -27,24 +27,24 @@
27
27
  "!*.map"
28
28
  ],
29
29
  "dependencies": {
30
- "@pnpm/config.matcher": "1100.0.1",
31
- "@pnpm/config.parse-overrides": "1100.1.0",
32
- "@pnpm/deps.peer-range": "1100.1.0",
33
- "@pnpm/error": "1100.0.1",
34
- "@pnpm/resolving.parse-wanted-dependency": "1100.0.1",
35
- "@pnpm/types": "1101.5.0",
30
+ "@pnpm/config.matcher": "1100.0.2",
31
+ "@pnpm/config.parse-overrides": "1100.1.1",
32
+ "@pnpm/deps.peer-range": "1100.1.1",
33
+ "@pnpm/error": "1100.1.0",
34
+ "@pnpm/resolving.parse-wanted-dependency": "1100.0.2",
35
+ "@pnpm/types": "1101.7.0",
36
36
  "@yarnpkg/extensions": "2.0.6",
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.2",
43
+ "@pnpm/hooks.read-package-hook": "1100.2.0",
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.0"
48
48
  },
49
49
  "engines": {
50
50
  "node": ">=22.13"