Skip to content

Commit

Permalink
Improve handling of local package version inconsistencies (#347)
Browse files Browse the repository at this point in the history
  • Loading branch information
bmish authored Feb 18, 2022
1 parent d9b9e58 commit 9d43f90
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 37 deletions.
78 changes: 52 additions & 26 deletions lib/dependency-versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,38 +119,40 @@ export function calculateMismatchingVersions(
return [];
}

// Calculate unique versions seen for this dependency.
const uniqueVersions = [
...new Set(
versionObjectsForDep
.filter((versionObject) => !versionObject.isLocalPackageVersion)
.map((versionObject) => versionObject.version)
),
].sort();

// If we saw more than one unique version for this dependency, we found an inconsistency.
if (uniqueVersions.length > 1) {
const uniqueVersionsWithInfo = versionsObjectsWithSortedPackages(
uniqueVersions,
versionObjectsForDep
);
return { dependency, versions: uniqueVersionsWithInfo };
}
// Check what versions we have seen for this dependency.
let versions = versionObjectsForDep
.filter((versionObject) => !versionObject.isLocalPackageVersion)
.map((versionObject) => versionObject.version);

// If we saw a local package version that isn't compatible with the local package's actual version, we found an inconsistency.
// Check if this dependency is a local package.
const localPackageVersions = versionObjectsForDep
.filter((object) => object.isLocalPackageVersion)
.map((object) => object.version);
.filter((versionObject) => versionObject.isLocalPackageVersion)
.map((versionObject) => versionObject.version);

if (
localPackageVersions.length === 1 &&
uniqueVersions.length === 1 &&
!semver.satisfies(localPackageVersions[0], uniqueVersions[0])
versions.some(
(uniqueVersion) =>
!semver.satisfies(localPackageVersions[0], uniqueVersion)
)
) {
// If we saw a version for this dependency that isn't compatible with its actual local package version, add the local package version to the list of versions seen.
versions = [...versions, ...localPackageVersions];
}

// Calculate unique versions seen for this dependency.
const uniqueVersions = [...new Set(versions)].sort(compareRangesSafe);

// If we saw more than one unique version for this dependency, we found an inconsistency.
if (uniqueVersions.length > 1) {
const uniqueVersionsWithInfo = versionsObjectsWithSortedPackages(
[...uniqueVersions, ...localPackageVersions],
uniqueVersions,
versionObjectsForDep
);
return { dependency, versions: uniqueVersionsWithInfo };
return {
dependency,
versions: uniqueVersionsWithInfo,
};
}

return [];
Expand Down Expand Up @@ -254,7 +256,9 @@ export function fixMismatchingVersions(
} {
const fixed = [];
const notFixed = [];
// Loop through each dependency that has a mismatching versions.
for (const mismatchingVersion of mismatchingVersions) {
// Decide what version we should fix to.
const versions = mismatchingVersion.versions.map(
(object) => object.version
);
Expand All @@ -267,6 +271,21 @@ export function fixMismatchingVersions(
continue;
}

// If this dependency is from a local package and the version we want to fix to is higher than the actual package version, skip it.
const localPackage = packages.find(
(package_) => package_.name === mismatchingVersion.dependency
);
if (
localPackage &&
localPackage.packageJson.version &&
compareRanges(fixedVersion, localPackage.packageJson.version) > 0
) {
// Skip this dependency.
notFixed.push(mismatchingVersion);
continue;
}

// Update the dependency version in each package.json.
let isFixed = false;
for (const package_ of packages) {
if (
Expand Down Expand Up @@ -304,8 +323,6 @@ export function fixMismatchingVersions(

if (isFixed) {
fixed.push(mismatchingVersion);
} else {
notFixed.push(mismatchingVersion);
}
}

Expand All @@ -315,6 +332,15 @@ export function fixMismatchingVersions(
};
}

// This version doesn't throw for when we want to ignore invalid versions that might be present.
export function compareRangesSafe(a: string, b: string): 0 | -1 | 1 {
try {
return compareRanges(a, b);
} catch {
return 0;
}
}

export function compareRanges(a: string, b: string): 0 | -1 | 1 {
// Strip range and coerce to normalized version.
const aVersion = semver.coerce(a.replace(/^[\^~]/, ''));
Expand Down
10 changes: 2 additions & 8 deletions lib/output.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import chalk from 'chalk';
import type { MismatchingDependencyVersions } from './dependency-versions.js';
import { compareRanges, getLatestVersion } from './dependency-versions.js';
import { compareRangesSafe, getLatestVersion } from './dependency-versions.js';
import { table } from 'table';

export function mismatchingVersionsToOutput(
Expand All @@ -23,13 +23,7 @@ export function mismatchingVersionsToOutput(
);

const rows = object.versions
.sort((a, b) => {
try {
return compareRanges(b.version, a.version);
} catch {
return 0;
}
})
.sort((a, b) => compareRangesSafe(b.version, a.version))
.map((versionObject) => {
const usageCount = versionObject.packages.length;
const packageNames = versionObject.packages.map(
Expand Down
41 changes: 38 additions & 3 deletions test/lib/dependency-versions-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
filterOutIgnoredDependencies,
fixMismatchingVersions,
compareRanges,
compareRangesSafe,
getLatestVersion,
} from '../../lib/dependency-versions.js';
import { getPackages } from '../../lib/workspace.js';
Expand Down Expand Up @@ -572,7 +573,7 @@ describe('Utils | dependency-versions', function () {
name: 'package1',
version: '1.0.0',
dependencies: {
package2: '^1.0.0',
package2: '^1.0.0', // Lower than actual version of this local package.
},
}),
},
Expand All @@ -585,6 +586,14 @@ describe('Utils | dependency-versions', function () {
},
}),
},
package3: {
'package.json': JSON.stringify({
name: 'package3',
dependencies: {
package1: '^0.0.0', // Lower than actual version of this local package.
},
}),
},
});
});

Expand All @@ -611,8 +620,13 @@ describe('Utils | dependency-versions', function () {
'package2/package.json',
'utf-8'
);
const packageJson3Contents = readFileSync(
'package3/package.json',
'utf-8'
);
const packageJson1: PackageJson = JSON.parse(packageJson1Contents);
const packageJson2: PackageJson = JSON.parse(packageJson2Contents);
const packageJson3: PackageJson = JSON.parse(packageJson3Contents);

expect(
packageJson1.dependencies && packageJson1.dependencies['package2']
Expand All @@ -622,19 +636,27 @@ describe('Utils | dependency-versions', function () {
packageJson2.dependencies && packageJson2.dependencies['package1']
).toStrictEqual('^2.0.0');

expect(
packageJson3.dependencies && packageJson3.dependencies['package1']
).toStrictEqual('^0.0.0');

expect(notFixed).toStrictEqual([
{
// Not fixed since found version higher than actual version of this local package.
dependency: 'package1',
versions: [
{
version: '^2.0.0',
packages: [expect.objectContaining({ path: 'package2' })],
version: '^0.0.0',
packages: [expect.objectContaining({ path: 'package3' })],
},
{
version: '1.0.0',
packages: [expect.objectContaining({ path: 'package1' })],
},
{
version: '^2.0.0',
packages: [expect.objectContaining({ path: 'package2' })],
},
],
},
]);
Expand Down Expand Up @@ -706,6 +728,19 @@ describe('Utils | dependency-versions', function () {
});
});

describe('#compareRangesSafe', function () {
it('behaves correctly', function () {
expect(compareRangesSafe('1.2.3', '1.2.2')).toStrictEqual(1);
expect(compareRangesSafe('4.0.0', '5.0.0')).toStrictEqual(-1);
expect(compareRangesSafe('6', '6')).toStrictEqual(0);
});

it('does not throw with invalid ranges', function () {
expect(compareRangesSafe('foo', '~6.0.0')).toStrictEqual(0);
expect(compareRangesSafe('~6.0.0', 'foo')).toStrictEqual(0);
});
});

describe('#getLatestVersion', function () {
it('correctly chooses the higher range', function () {
// Just basic sanity checks to ensure the data is passed through to `compareRanges` which has extensive tests.
Expand Down

0 comments on commit 9d43f90

Please sign in to comment.