Skip to content

Commit 4e3b757

Browse files
feat(peerDependencyMeta): support peerDependencyMeta in package.json to ignore optional peer dependencies
1 parent 66f54ba commit 4e3b757

File tree

2 files changed

+74
-68
lines changed

2 files changed

+74
-68
lines changed

src/checkPeerDependencies.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,27 +23,35 @@ function getAllNestedPeerDependencies(options: CliOptions): Dependency[] {
2323

2424
let recursiveCount = 0;
2525

26-
const reportPeerDependencyStatus = (dep: Dependency, byDepender: boolean, force: boolean) => {
26+
const reportPeerDependencyStatus = (dep: Dependency, byDepender: boolean, showSatisfiedDep: boolean, showOptionalDep: boolean) => {
2727
const message = byDepender ?
28-
`${dep.depender}@${dep.dependerVersion} requires ${dep.name} ${dep.version}` :
29-
`${dep.name} ${dep.version} is required by ${dep.depender}@${dep.dependerVersion}`;
28+
`${dep.depender.name}@${dep.depender.version} requires ${dep.name} ${dep.version}` :
29+
`${dep.name} ${dep.version} is required by ${dep.depender.name}@${dep.depender.version}`;
3030

3131
if (dep.semverSatisfies) {
32-
if (force) {
32+
if (showSatisfiedDep) {
3333
console.log(` ✅ ${message} (${dep.installedVersion} is installed)`);
3434
}
3535
} else if (dep.isYalc) {
3636
console.log(` ☑️ ${message} (${dep.installedVersion} is installed via yalc)`);
37+
} else if (dep.installedVersion && dep.isPeerOptionalDependency) {
38+
if (showOptionalDep) {
39+
console.log(` ☑️ ${message}) OPTIONAL (${dep.installedVersion} is installed)`);
40+
}
3741
} else if (dep.installedVersion) {
3842
console.log(` ❌ ${message}) (${dep.installedVersion} is installed)`);
43+
} else if (dep.isPeerOptionalDependency) {
44+
if (showOptionalDep) {
45+
console.log(` ☑️ ${message} OPTIONAL (${dep.name} is not installed)`);
46+
}
3947
} else {
4048
console.log(` ❌ ${message} (${dep.name} is not installed)`);
4149
}
4250
};
4351

4452
function findSolutions(problems: Dependency[], allNestedPeerDependencies: Dependency[]) {
4553
console.log();
46-
console.log('Searching for solutions...');
54+
console.log(`Searching for solutions for ${problems.length} missing dependencies...`);
4755
console.log();
4856
const resolutions: Resolution[] = findPossibleResolutions(problems, allNestedPeerDependencies);
4957
const resolutionsWithSolutions = resolutions.filter(r => r.resolution);
@@ -104,16 +112,17 @@ function report(options: CliOptions, allNestedPeerDependencies: Dependency[]) {
104112
allNestedPeerDependencies.forEach(dep => {
105113
const isUnsatisfied = (dep: Dependency) => !dep.semverSatisfies && !dep.isYalc;
106114
const relatedPeerDeps = allNestedPeerDependencies.filter(other => other.name === dep.name && other !== dep);
107-
const force = options.verbose || relatedPeerDeps.some(isUnsatisfied);
108-
reportPeerDependencyStatus(dep, options.orderBy === 'depender', force);
115+
const showIfSatisfied = options.verbose || relatedPeerDeps.some(isUnsatisfied);
116+
const showOptionalDep = options.verbose;
117+
reportPeerDependencyStatus(dep, options.orderBy === 'depender', showIfSatisfied, showOptionalDep);
109118
});
110119
}
111120

112121
export function checkPeerDependencies(packageManager: string, options: CliOptions) {
113122
const allNestedPeerDependencies = getAllNestedPeerDependencies(options);
114123
report(options, allNestedPeerDependencies);
115124

116-
const problems = allNestedPeerDependencies.filter(dep => !dep.semverSatisfies && !dep.isYalc);
125+
const problems = allNestedPeerDependencies.filter(dep => !dep.semverSatisfies && !dep.isYalc && !dep.isPeerOptionalDependency);
117126

118127
if (!problems.length) {
119128
console.log(' ✅ All peer dependencies are met');

src/packageUtils.ts

Lines changed: 57 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -16,41 +16,44 @@ interface PackageJson {
1616
peerDependencies: {
1717
[key: string]: string;
1818
};
19+
// deprecated: use peerDependenciesMeta.foo.dev
20+
peerDevDependencies: string[];
21+
// See: https://github.com/yarnpkg/rfcs/blob/master/accepted/0000-optional-peer-dependencies.md
22+
peerDependenciesMeta: {
23+
[key: string]: {
24+
optional?: boolean;
25+
// non-standard
26+
dev?: boolean;
27+
};
28+
};
1929
optionalDependencies: {
2030
[key: string]: string;
2131
};
22-
23-
// What is a peerDevDependency? This is not a standard.
24-
// This is an array of package names found in `peerDependencies` which should be installed as devDependencies.
25-
// This addresses a specific use case: to provide downstream projects with package building opinions such as
26-
// a specific version of react and rollup and typescript.
27-
// Example:
28-
// peerDevDependencies: ["rollup", "typescript"]
29-
peerDevDependencies: string[];
3032
}
3133

3234
export interface Dependency {
3335
name: string;
3436
version: string;
35-
depender: string;
36-
dependerPath: string;
37-
dependerVersion: string;
37+
depender: PackageMeta;
38+
type: 'dependencies' | 'devDependencies' | 'peerDependencies' | 'optionalDependencies';
39+
isPeerOptionalDependency: boolean;
40+
isPeerDevDependency: boolean;
3841
installedVersion?: string | undefined;
3942
semverSatisfies?: boolean;
4043
isYalc?: boolean;
41-
isPeerDevDependency?: boolean;
4244
}
4345

44-
interface PackageDependencies {
45-
packageName: string;
46+
interface PackageMeta {
47+
name: string;
48+
version: string;
49+
packagePath: string;
4650
dependencies: Dependency[];
4751
devDependencies: Dependency[];
48-
peerDependencies: Dependency[];
4952
optionalDependencies: Dependency[];
50-
peerDevDependencies: string[];
53+
peerDependencies: Dependency[];
5154
}
5255

53-
type DependencyWalkVisitor = (packagePath: string, packageJson: PackageJson, packageDependencies: PackageDependencies) => void;
56+
type DependencyWalkVisitor = (packagePath: string, packageJson: PackageJson, packageMeta: PackageMeta) => void;
5457

5558
export function gatherPeerDependencies(packagePath, options: CliOptions): Dependency[] {
5659
let peerDeps: Dependency[] = [];
@@ -80,7 +83,7 @@ export function walkPackageDependencyTree(packagePath: string, visitor: Dependen
8083
}
8184

8285
const packageJson = readJson(packageJsonPath) as PackageJson;
83-
const packageDependencies = getPackageDependencies(packagePath, packageJson);
86+
const packageDependencies = getPackageMeta(packagePath, packageJson);
8487

8588
if (options.debug) {
8689
console.log(packageJsonPath);
@@ -114,37 +117,39 @@ export function walkPackageDependencyTree(packagePath: string, visitor: Dependen
114117
if (isRootPackage || !options.runOnlyOnRootDependencies) packageDependencies.dependencies.forEach(walkDependency)
115118
}
116119

117-
function buildDependencyArray(packagePath: string, packageJson: PackageJson, dependenciesObject: any): Dependency[] {
118-
return Object.keys(dependenciesObject).map(name => ({
119-
name: name,
120-
version: dependenciesObject[name],
121-
depender: packageJson.name,
122-
dependerVersion: packageJson.version,
123-
dependerPath: packagePath,
124-
}));
120+
function buildDependencyArray(type: Dependency["type"], pkgJson: PackageJson, depender: PackageMeta): Dependency[] {
121+
const dependenciesObject = pkgJson[type] || {};
122+
const peerDependenciesMeta = pkgJson.peerDependenciesMeta || {};
123+
// backwards compat
124+
const peerDevDependencies = pkgJson.peerDevDependencies || [];
125+
126+
const packageNames = Object.keys(dependenciesObject);
127+
128+
return packageNames.map(name => {
129+
const isPeerOptionalDependency= !!peerDependenciesMeta[name]?.optional;
130+
const isPeerDevDependency = !!peerDependenciesMeta[name]?.dev || !!peerDevDependencies.includes(name);
131+
132+
return {
133+
name,
134+
type,
135+
version: dependenciesObject[name],
136+
isPeerDevDependency,
137+
isPeerOptionalDependency,
138+
depender,
139+
};
140+
});
125141
}
126142

127-
export function getPackageDependencies(packagePath: string, packageJson: PackageJson): PackageDependencies {
128-
const {
129-
name,
130-
dependencies = {},
131-
devDependencies = {},
132-
optionalDependencies = {},
133-
peerDependencies = {},
134-
peerDevDependencies = []
135-
} = packageJson;
136-
137-
const applyPeerDevDependencies = (dep: Dependency): Dependency =>
138-
({ ...dep, isPeerDevDependency: peerDevDependencies.includes && peerDevDependencies.includes(dep.name) });
139-
140-
return {
141-
packageName: name,
142-
dependencies: buildDependencyArray(packagePath, packageJson, dependencies),
143-
devDependencies: buildDependencyArray(packagePath, packageJson, devDependencies),
144-
optionalDependencies: buildDependencyArray(packagePath, packageJson, optionalDependencies),
145-
peerDependencies: buildDependencyArray(packagePath, packageJson, peerDependencies).map(applyPeerDevDependencies),
146-
peerDevDependencies,
147-
};
143+
export function getPackageMeta(packagePath: string, packageJson: PackageJson): PackageMeta {
144+
const { name, version} = packageJson;
145+
const packageMeta = { name, version, packagePath } as PackageMeta;
146+
147+
packageMeta.dependencies = buildDependencyArray("dependencies", packageJson, packageMeta);
148+
packageMeta.devDependencies = buildDependencyArray("devDependencies", packageJson, packageMeta);
149+
packageMeta.optionalDependencies = buildDependencyArray("optionalDependencies", packageJson, packageMeta);
150+
packageMeta.peerDependencies = buildDependencyArray("peerDependencies", packageJson, packageMeta);
151+
152+
return packageMeta;
148153
}
149154

150155
export function resolvePackageDir(basedir: string, packageName: string) {
@@ -181,17 +186,9 @@ export function getInstalledVersion(dep: Dependency): string | undefined {
181186

182187

183188
export function isSameDep(a: Dependency, b: Dependency) {
184-
const keys: Array<keyof Dependency> = [
185-
"name",
186-
"version",
187-
"depender",
188-
"dependerPath",
189-
"dependerVersion",
190-
"installedVersion",
191-
"semverSatisfies",
192-
"isYalc",
193-
"isPeerDevDependency",
194-
];
195-
196-
return keys.every(key => a[key] === b[key]);
189+
const keys: Array<keyof Dependency> = [ "name", "version", "installedVersion", "semverSatisfies", "isYalc", "isPeerDevDependency", ];
190+
return keys.every(key => a[key] === b[key]) &&
191+
a.depender.name === b.depender.name &&
192+
a.depender.version === b.depender.version &&
193+
a.depender.packagePath === b.depender.packagePath;
197194
}

0 commit comments

Comments
 (0)