Skip to content

Commit 6048b25

Browse files
authored
Merge pull request microsoft#1624 from microsoft/octogonz/rush-optional-peer-deps
[rush] Improve shrinkwrap-deps.json logic to support optional peer dependencies
2 parents cad26ca + a74215e commit 6048b25

6 files changed

Lines changed: 82 additions & 14 deletions

File tree

apps/rush-lib/assets/rush-init/common/config/rush/common-versions.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* package managers, this tended to reduce duplication of indirect dependencies. However, it can sometimes cause
3434
* trouble for indirect dependencies with incompatible peerDependencies ranges.
3535
*
36-
* The default value is true. As of Rush 5.17, the default is false if you are using PNPM 4.x or newer.
36+
* The default value is true.
3737
*
3838
* After modifying this field, it's recommended to run "rush update --full" so that the package manager
3939
* will recalculate all version selections.

apps/rush-lib/src/api/CommonVersionsConfiguration.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,7 @@ export class CommonVersionsConfiguration {
175175
* package managers, this tended to reduce duplication of indirect dependencies. However, it can sometimes cause
176176
* trouble for indirect dependencies with incompatible peerDependencies ranges.
177177
*
178-
* If the value is `undefined`, then the default value is `true`. As of Rush 5.17, the default is false
179-
* if you are using PNPM 4.x or newer.
178+
* If the value is `undefined`, then the default value is `true`.
180179
*/
181180
public get implicitlyPreferredVersions(): boolean | undefined {
182181
return this._implicitlyPreferredVersions;

apps/rush-lib/src/logic/InstallManager.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -561,12 +561,8 @@ export class InstallManager {
561561
if (this._rushConfiguration.commonVersions.implicitlyPreferredVersions !== undefined) {
562562
// Use the manually configured setting
563563
useImplicitlyPinnedVersions = this._rushConfiguration.commonVersions.implicitlyPreferredVersions;
564-
} else if (this._rushConfiguration.packageManager === "pnpm" &&
565-
semver.major(this._rushConfiguration.packageManagerToolVersion) >= 4) {
566-
// Default to false for PNPM 4.x or newer.
567-
useImplicitlyPinnedVersions = false;
568564
} else {
569-
// Default to true for other package managers.
565+
// Default to true.
570566
useImplicitlyPinnedVersions = true;
571567
}
572568

apps/rush-lib/src/logic/pnpm/PnpmProjectDependencyManifest.ts

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -193,14 +193,39 @@ export class PnpmProjectDependencyManifest {
193193
// if it's been hoisted up as a top-level dependency
194194
const topLevelDependencySpecifier: DependencySpecifier | undefined =
195195
this._pnpmShrinkwrapFile.getTopLevelDependencyVersion(peerDependencyName);
196+
197+
// Sometimes peer dependencies are hoisted but are not represented in the shrinkwrap file
198+
// (such as when implicitlyPreferredVersions is false) so we need to find the correct key
199+
// and add it ourselves
200+
if (!topLevelDependencySpecifier) {
201+
const peerDependencyKeys: { [peerDependencyName: string]: string } =
202+
this._parsePeerDependencyKeysFromSpecifier(specifier);
203+
if (peerDependencyKeys.hasOwnProperty(peerDependencyName)) {
204+
this._addDependencyInternal(
205+
peerDependencyName,
206+
peerDependencyKeys[peerDependencyName],
207+
shrinkwrapEntry
208+
);
209+
continue;
210+
}
211+
}
212+
196213
if (!topLevelDependencySpecifier || !semver.valid(topLevelDependencySpecifier.versionSpecifier)) {
197-
const errorMessage: string =
198-
`Could not find peer dependency '${peerDependencyName}' that satisfies version '${dependencySemVer}'`
199-
if (this._project.rushConfiguration.pnpmOptions && this._project.rushConfiguration.pnpmOptions.strictPeerDependencies) {
200-
throw new InternalError(errorMessage);
214+
if (
215+
!this._project.rushConfiguration.pnpmOptions ||
216+
!this._project.rushConfiguration.pnpmOptions.strictPeerDependencies ||
217+
(
218+
shrinkwrapEntry.peerDependenciesMeta &&
219+
shrinkwrapEntry.peerDependenciesMeta.hasOwnProperty(peerDependencyName) &&
220+
shrinkwrapEntry.peerDependenciesMeta[peerDependencyName].optional
221+
)
222+
) {
223+
// We couldn't find the peer dependency, but we determined it's by design, skip this dependency...
224+
continue;
201225
}
202-
console.log(`${errorMessage}, skipping...`);
203-
continue;
226+
throw new InternalError(
227+
`Could not find peer dependency '${peerDependencyName}' that satisfies version '${dependencySemVer}'`
228+
);
204229
}
205230

206231
this._addDependencyInternal(
@@ -211,4 +236,32 @@ export class PnpmProjectDependencyManifest {
211236
}
212237
}
213238
}
239+
240+
/**
241+
* The version specifier for a dependency can sometimes come in the form of
242+
* '{semVer}_peerDep1@1.2.3+peerDep2@4.5.6'. This is parsed and returned as a dictionary mapping
243+
* the peer dependency to it's appropriate PNPM dependency key.
244+
*/
245+
private _parsePeerDependencyKeysFromSpecifier(specifier: string): { [peerDependencyName: string]: string } {
246+
const parsedPeerDependencyKeys: { [peerDependencyName: string]: string } = {};
247+
248+
const specifierMatches: RegExpExecArray | null = /^[^_]+_(.+)$/.exec(specifier);
249+
if (specifierMatches) {
250+
const combinedPeerDependencies: string = specifierMatches[1];
251+
// Parse "eslint@6.6.0+typescript@3.6.4" --> ["eslint@6.6.0", "typescript@3.6.4"]
252+
const peerDependencies: string[] = combinedPeerDependencies.split("+");
253+
for (const peerDependencySpecifier of peerDependencies) {
254+
// Parse "eslint@6.6.0" --> "eslint", "6.6.0"
255+
const peerMatches: RegExpExecArray | null = /^([^+@]+)@(.+)$/.exec(peerDependencySpecifier);
256+
if (peerMatches) {
257+
const peerDependencyName: string = peerMatches[1];
258+
const peerDependencyVersion: string = peerMatches[2];
259+
const peerDependencyKey: string = `/${peerDependencyName}/${peerDependencyVersion}`;
260+
parsedPeerDependencyKeys[peerDependencyName] = peerDependencyKey;
261+
}
262+
}
263+
}
264+
265+
return parsedPeerDependencyKeys;
266+
}
214267
}

apps/rush-lib/src/logic/pnpm/PnpmShrinkwrapFile.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ const SHRINKWRAP_YAML_FORMAT: yaml.DumpOptions = {
1515
sortKeys: true
1616
};
1717

18+
export interface IPeerDependenciesMetaYaml {
19+
optional?: boolean;
20+
}
21+
1822
export interface IPnpmShrinkwrapDependencyYaml {
1923
/** Information about the resolved package */
2024
resolution: {
@@ -29,6 +33,11 @@ export interface IPnpmShrinkwrapDependencyYaml {
2933
optionalDependencies: { [dependency: string]: string };
3034
/** The list of peer dependencies and the resolved version */
3135
peerDependencies: { [dependency: string]: string };
36+
/**
37+
* Used to indicate optional peer dependencies, as described in this RFC:
38+
* https://github.com/yarnpkg/rfcs/blob/master/accepted/0000-optional-peer-dependencies.md
39+
*/
40+
peerDependenciesMeta: { [dependency: string]: IPeerDependenciesMetaYaml };
3241
}
3342

3443
/**
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@microsoft/rush",
5+
"comment": "Fix dependency mapping when implicitlyPreferredVersions is false",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush",
10+
"email": "3473356+D4N14L@users.noreply.github.com"
11+
}

0 commit comments

Comments
 (0)