Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple versions for external mismatch rule #131

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7ac2da2
support dual package versions
Feb 1, 2022
25afbd4
add more unit tests
Feb 1, 2022
df2ca33
minor fix - reduce diffs
Feb 2, 2022
262531b
Merge pull request #2 from atlassian-forks/support-dual-versions
cheesehary Feb 3, 2022
57f481b
add changeset
Feb 3, 2022
e9e4dae
Merge pull request #3 from atlassian-forks/support-dual-versions
cheesehary Feb 3, 2022
fe8dc45
fix the version to the closest allowed one
Feb 10, 2022
a2f12b5
ditch for loop when finding the closest range
Feb 14, 2022
3ce71a6
Merge pull request #4 from atlassian-forks/fix-to-closest-allowed-ver…
cheesehary Feb 16, 2022
8ca87f4
fix semver range operations
Feb 16, 2022
df44a3a
mixture of versions and ranges in unit tests
Feb 17, 2022
50c6a8e
Merge pull request #5 from atlassian-forks/fix-semver-range
cheesehary Feb 17, 2022
4ac0631
Fix getVersionFromRange function used by EXTERNAL_MISMATCH rule
mblaszczyk-atlassian Jun 13, 2023
19c91de
Merge pull request #7 from atlassian-forks/MONO-136-fix-semver-range-…
Blasz Jun 13, 2023
244e124
support dual package versions
Feb 1, 2022
2b8d465
add more unit tests
Feb 1, 2022
4ed37cd
minor fix - reduce diffs
Feb 2, 2022
25be4f7
add changeset
Feb 3, 2022
3ecac79
fix the version to the closest allowed one
Feb 10, 2022
d4554a5
ditch for loop when finding the closest range
Feb 14, 2022
b9008ee
fix semver range operations
Feb 16, 2022
981f9bb
mixture of versions and ranges in unit tests
Feb 17, 2022
3b1c39b
Fix getVersionFromRange function used by EXTERNAL_MISMATCH rule
mblaszczyk-atlassian Jun 13, 2023
6896d76
Merge branch 'Thinkmill-main' into v24-sync
Sep 23, 2024
2034c99
Merge pull request #9 from atlassian-forks/v24-sync
ReDrUm Sep 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/five-crews-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@manypkg/cli": minor
---

Add an option to allow multiple versions for external mismatch check
11 changes: 9 additions & 2 deletions packages/cli/src/checks/EXTERNAL_MISMATCH.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
makeCheck,
getMostCommonRangeMap,
getClosestAllowedRange,
NORMAL_DEPENDENCY_TYPES,
} from "./utils";
import { Package } from "@manypkg/get-packages";
Expand All @@ -15,7 +16,7 @@ type ErrorType = {
};

export default makeCheck<ErrorType>({
validate: (workspace, allWorkspace) => {
validate: (workspace, allWorkspace, rootWorkspace, options) => {
let errors: ErrorType[] = [];
let mostCommonRangeMap = getMostCommonRangeMap(allWorkspace);
for (let depType of NORMAL_DEPENDENCY_TYPES) {
Expand All @@ -25,17 +26,23 @@ export default makeCheck<ErrorType>({
for (let depName in deps) {
let range = deps[depName];
let mostCommonRange = mostCommonRangeMap.get(depName);
const allowedVersions =
options.allowedDependencyVersions &&
options.allowedDependencyVersions[depName];
if (
mostCommonRange !== undefined &&
mostCommonRange !== range &&
!(allowedVersions && allowedVersions.includes(range)) &&
validRange(range)
) {
errors.push({
type: "EXTERNAL_MISMATCH",
workspace,
dependencyName: depName,
dependencyRange: range,
mostCommonDependencyRange: mostCommonRange,
mostCommonDependencyRange: allowedVersions
? getClosestAllowedRange(range, allowedVersions)
: mostCommonRange,
});
}
}
Expand Down
144 changes: 141 additions & 3 deletions packages/cli/src/checks/__tests__/EXTERNAL_MISMATCH.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ it("should error if the ranges are valid and they are not equal", () => {
`);
});

it("should error and return the correct mostCommonDependencyRange when the ranges are valid, they are not equal and there are more than 2", () => {
it("should error and return the correct expectedRange when the ranges are valid, they are not equal and there are more than 2", () => {
let ws = getWS();

ws.get("pkg-1")!.packageJson.dependencies = { something: "1.0.0" };
Expand Down Expand Up @@ -92,7 +92,7 @@ it("should error and return the correct mostCommonDependencyRange when the range
`);
});

it("should error and return the correct mostCommonDependencyRange when the ranges are valid, but the 2nd dependnecy is most common", () => {
it("should error and return the correct expectedRange when the ranges are valid, but the 2nd dependnecy is most common", () => {
let ws = getWS();

ws.get("pkg-1")!.packageJson.dependencies = { something: "2.0.0" };
Expand Down Expand Up @@ -143,7 +143,7 @@ it("should error and return the correct mostCommonDependencyRange when the range
expect(errors.length).toEqual(0);
});

it("should error and return the correct mostCommonDependencyRange when the ranges are valid, but everything wants a different version", () => {
it("should error and return the correct expectedRange when the ranges are valid, but everything wants a different version", () => {
let ws = getWS();

ws.get("pkg-1")!.packageJson.dependencies = { something: "1.0.0" };
Expand Down Expand Up @@ -234,3 +234,141 @@ it("should not error if the value is not a valid semver range", () => {
errors = internalMismatch.validate(ws.get("pkg-1")!, ws, rootWorkspace, {});
expect(errors.length).toEqual(0);
});

it("should not error if the range is included in the allowedDependencyVersions option", () => {
let ws = getWS();

ws.get("pkg-1")!.packageJson.dependencies = { something: "1.0.0" };

let pkg2 = getFakeWS("pkg-2");
pkg2.packageJson.dependencies = {
something: "2.0.0",
};
ws.set("pkg-2", pkg2);

const options = {
allowedDependencyVersions: {
something: ["1.0.0", "2.0.0"],
},
};

let errors = internalMismatch.validate(pkg2, ws, rootWorkspace, options);
expect(errors.length).toEqual(0);

errors = internalMismatch.validate(
ws.get("pkg-1")!,
ws,
rootWorkspace,
options
);
expect(errors.length).toEqual(0);
});

it("should error and fix the version to the closest allowed one when adding an allowed major", () => {
const options = {
allowedDependencyVersions: {
something: ["^1.0.0", "2.0.0"],
},
};

let ws = getWS();

ws.get("pkg-1")!.packageJson.dependencies = { something: "^1.0.0" };

// version 1.0.0 is the most commonly used one
let pkg1a = getFakeWS("pkg-1a");
pkg1a.packageJson.dependencies = {
something: "^1.0.0",
};
ws.set("pkg-1a", pkg1a);

// version 2.0.0 is allowed
let pkg2 = getFakeWS("pkg-2");
pkg2.packageJson.dependencies = {
something: "2.0.0",
};
ws.set("pkg-2", pkg2);

// try to add version 2.1.0
let pkg2a = getFakeWS("pkg-2a");
pkg2a.packageJson.dependencies = {
something: "^2.1.0",
};
ws.set("pkg-2a", pkg2a);

let errors = internalMismatch.validate(pkg2a, ws, rootWorkspace, options);
expect(errors.length).toEqual(1);
expect(errors[0]).toEqual(
expect.objectContaining({
dependencyName: "something",
dependencyRange: "^2.1.0",
mostCommonDependencyRange: "2.0.0",
})
);
internalMismatch.fix(errors[0], options);
expect(pkg2a.packageJson.dependencies.something).toEqual("2.0.0");

// try to add version 1.0.1
let pkg1b = getFakeWS("pkg-1b");
pkg1b.packageJson.dependencies = {
something: "^1.0.1",
};
ws.set("pkg-1b", pkg1b);

errors = internalMismatch.validate(pkg1b, ws, rootWorkspace, options);
expect(errors.length).toEqual(1);
expect(errors[0]).toEqual(
expect.objectContaining({
dependencyName: "something",
dependencyRange: "^1.0.1",
mostCommonDependencyRange: "^1.0.0",
})
);
internalMismatch.fix(errors[0], options);
expect(pkg1b.packageJson.dependencies.something).toEqual("^1.0.0");
});

it("should error and fix the version to the highest allowed one when adding a newer major", () => {
const options = {
allowedDependencyVersions: {
something: ["1.0.0", "^2.0.0"],
},
};

let ws = getWS();

ws.get("pkg-1")!.packageJson.dependencies = { something: "1.0.0" };

// version 1.0.0 is the most commonly used one
let pkg1a = getFakeWS("pkg-1a");
pkg1a.packageJson.dependencies = {
something: "1.0.0",
};
ws.set("pkg-1a", pkg1a);

// version 2.0.0 is allowed
let pkg2 = getFakeWS("pkg-2");
pkg2.packageJson.dependencies = {
something: "^2.0.0",
};
ws.set("pkg-2", pkg2);

// try to add version 3.0.0
let pkg3 = getFakeWS("pkg-3");
pkg3.packageJson.dependencies = {
something: "3.0.0",
};
ws.set("pkg-3", pkg3);

let errors = internalMismatch.validate(pkg3, ws, rootWorkspace, options);
expect(errors.length).toEqual(1);
expect(errors[0]).toEqual(
expect.objectContaining({
dependencyName: "something",
dependencyRange: "3.0.0",
mostCommonDependencyRange: "^2.0.0",
})
);
internalMismatch.fix(errors[0], options);
expect(pkg3.packageJson.dependencies.something).toEqual("^2.0.0");
});
29 changes: 29 additions & 0 deletions packages/cli/src/checks/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Package } from "@manypkg/get-packages";
import * as semver from "semver";
import { highest } from "sembear";
import * as logger from "../logger";
import { ExitError } from "../errors";

export const NORMAL_DEPENDENCY_TYPES = [
"dependencies",
Expand All @@ -19,6 +21,7 @@ export type Options = {
defaultBranch?: string;
ignoredRules?: string[];
workspaceProtocol?: "allow" | "require";
allowedDependencyVersions?: { [dependency: string]: string[] };
};

type RootCheck<ErrorType> = {
Expand Down Expand Up @@ -185,6 +188,32 @@ export function isArrayEqual(arrA: Array<string>, arrB: Array<string>) {
return true;
}

export function getClosestAllowedRange(
range: string,
allowedVersions: string[]
) {
const major = semver.major(getVersionFromRange(range));
const allowedVersionsWithSameMajor = allowedVersions.filter(
(version) => semver.major(getVersionFromRange(version)) === major
);
const possibleRanges =
allowedVersionsWithSameMajor.length > 0
? allowedVersionsWithSameMajor
: allowedVersions;
return possibleRanges.sort((a, b) =>
semver.gt(getVersionFromRange(a), getVersionFromRange(b)) ? -1 : 1
)[0];
}

function getVersionFromRange(range: string) {
const minVersion = semver.minVersion(range);
if (minVersion) {
return minVersion;
}
logger.error(`Invalid range: ${range}`);
throw new ExitError(1);
}

function makeCheck<ErrorType>(
check: RootCheckWithFix<ErrorType>
): RootCheckWithFix<ErrorType>;
Expand Down