@manypkg/cli
Version:
Manypkg is a linter for `package.json` files in Yarn, npm, Lerna, pnpm or Rush monorepos.
195 lines (172 loc) • 5.21 kB
text/typescript
import type { Package } from "@manypkg/get-packages";
import * as semver from "semver";
import { highest } from "sembear";
export const NORMAL_DEPENDENCY_TYPES = [
"dependencies",
"devDependencies",
"optionalDependencies",
] as const;
export const DEPENDENCY_TYPES = [
"dependencies",
"devDependencies",
"optionalDependencies",
"peerDependencies",
] as const;
export type Options = {
defaultBranch?: string;
ignoredRules?: string[];
workspaceProtocol?: "allow" | "require";
};
type RootCheck<ErrorType> = {
type: "root";
validate: (
rootPackage: Package,
allPackages: Map<string, Package>,
options: Options
) => ErrorType[];
fix?: (
error: ErrorType,
options: Options
) => void | { requiresInstall: boolean };
print: (error: ErrorType, options: Options) => string;
};
type RootCheckWithFix<ErrorType> = {
type: "root";
validate: (
rootPackage: Package,
allPackages: Map<string, Package>,
options: Options
) => ErrorType[];
fix: (
error: ErrorType,
options: Options
) => void | { requiresInstall: boolean };
print: (error: ErrorType, options: Options) => string;
};
type AllCheck<ErrorType> = {
type: "all";
validate: (
workspace: Package,
allWorkspaces: Map<string, Package>,
rootWorkspace: Package | undefined,
options: Options
) => ErrorType[];
fix?: (
error: ErrorType,
options: Options
) => void | { requiresInstall: boolean };
print: (error: ErrorType, options: Options) => string;
};
type AllCheckWithFix<ErrorType> = {
type: "all";
validate: (
workspace: Package,
allWorkspaces: Map<string, Package>,
rootWorkspace: Package | undefined,
options: Options
) => ErrorType[];
fix: (
error: ErrorType,
options: Options
) => void | { requiresInstall: boolean };
print: (error: ErrorType, options: Options) => string;
};
export function sortObject(prevObj: { [key: string]: string }) {
let newObj: { [key: string]: string } = {};
for (let key of Object.keys(prevObj).sort()) {
newObj[key] = prevObj[key];
}
return newObj;
}
export function sortDeps(pkg: Package) {
for (let depType of DEPENDENCY_TYPES) {
let prevDeps = pkg.packageJson[depType];
if (prevDeps) {
pkg.packageJson[depType] = sortObject(prevDeps);
}
}
}
function weakMemoize<Arg, Ret>(func: (arg: Arg) => Ret): (arg: Arg) => Ret {
let cache = new WeakMap<any, any>();
return (arg) => {
if (cache.has(arg)) {
return cache.get(arg);
}
let ret = func(arg);
cache.set(arg, ret);
return ret;
};
}
export let getMostCommonRangeMap = weakMemoize(function getMostCommonRanges(
allPackages: Map<string, Package>
) {
let dependencyRangesMapping = new Map<string, { [key: string]: number }>();
for (let [pkgName, pkg] of allPackages) {
for (let depType of NORMAL_DEPENDENCY_TYPES) {
let deps = pkg.packageJson[depType];
if (deps) {
for (let depName in deps) {
const depSpecifier = deps[depName];
if (!allPackages.has(depName)) {
if (!semver.validRange(deps[depName])) {
continue;
}
let dependencyRanges = dependencyRangesMapping.get(depName) || {};
const specifierCount = dependencyRanges[depSpecifier] || 0;
dependencyRanges[depSpecifier] = specifierCount + 1;
dependencyRangesMapping.set(depName, dependencyRanges);
}
}
}
}
}
let mostCommonRangeMap = new Map<string, string>();
for (let [depName, specifierMap] of dependencyRangesMapping) {
const specifierMapEntryArray = Object.entries(specifierMap);
const [first] = specifierMapEntryArray;
const maxValue = specifierMapEntryArray.reduce((acc, value) => {
if (acc[1] === value[1]) {
// If all dependency ranges occurances are equal, pick the highest.
// It's impossible to infer intention of the developer
// when all ranges occur an equal amount of times
const highestRange = highest([acc[0], value[0]]);
return [highestRange, acc[1]];
}
if (acc[1] > value[1]) {
return acc;
}
return value;
}, first);
mostCommonRangeMap.set(depName, maxValue[0]);
}
return mostCommonRangeMap;
});
export function versionRangeToRangeType(versionRange: string) {
if (versionRange.charAt(0) === "^") return "^";
if (versionRange.charAt(0) === "~") return "~";
return "";
}
export function isArrayEqual(arrA: Array<string>, arrB: Array<string>) {
for (var i = 0; i < arrA.length; i++) {
if (arrA[i] !== arrB[i]) {
return false;
}
}
return true;
}
function makeCheck<ErrorType>(
check: RootCheckWithFix<ErrorType>
): RootCheckWithFix<ErrorType>;
function makeCheck<ErrorType>(
check: AllCheckWithFix<ErrorType>
): AllCheckWithFix<ErrorType>;
function makeCheck<ErrorType>(
check: RootCheck<ErrorType>
): RootCheck<ErrorType>;
function makeCheck<ErrorType>(check: AllCheck<ErrorType>): AllCheck<ErrorType>;
function makeCheck<ErrorType>(
check: RootCheck<ErrorType> | AllCheck<ErrorType>
): RootCheck<ErrorType> | AllCheck<ErrorType> {
return check;
}
export { makeCheck };