127 lines
3.2 KiB
Plaintext
127 lines
3.2 KiB
Plaintext
// This is a modified version of the graph-getting in bolt
|
|
import Range from "semver/classes/range";
|
|
import chalk from "chalk";
|
|
import { Packages, Package } from "@manypkg/get-packages";
|
|
import { PackageJSON } from "@changesets/types";
|
|
|
|
const DEPENDENCY_TYPES = [
|
|
"dependencies",
|
|
"devDependencies",
|
|
"peerDependencies",
|
|
"optionalDependencies",
|
|
] as const;
|
|
|
|
const getAllDependencies = (config: PackageJSON) => {
|
|
const allDependencies = new Map<string, string>();
|
|
|
|
for (const type of DEPENDENCY_TYPES) {
|
|
const deps = config[type];
|
|
if (!deps) continue;
|
|
|
|
for (const name of Object.keys(deps)) {
|
|
const depRange = deps[name];
|
|
if (
|
|
(depRange.startsWith("link:") || depRange.startsWith("file:")) &&
|
|
type === "devDependencies"
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
allDependencies.set(name, depRange);
|
|
}
|
|
}
|
|
|
|
return allDependencies;
|
|
};
|
|
|
|
const isProtocolRange = (range: string) => range.indexOf(":") !== -1;
|
|
|
|
const getValidRange = (potentialRange: string) => {
|
|
if (isProtocolRange(potentialRange)) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return new Range(potentialRange);
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
export default function getDependencyGraph(
|
|
packages: Packages,
|
|
opts?: {
|
|
bumpVersionsWithWorkspaceProtocolOnly?: boolean;
|
|
}
|
|
): {
|
|
graph: Map<string, { pkg: Package; dependencies: Array<string> }>;
|
|
valid: boolean;
|
|
} {
|
|
const graph = new Map<
|
|
string,
|
|
{ pkg: Package; dependencies: Array<string> }
|
|
>();
|
|
let valid = true;
|
|
|
|
const packagesByName: { [key: string]: Package } = {
|
|
[packages.root.packageJson.name]: packages.root,
|
|
};
|
|
|
|
const queue = [packages.root];
|
|
|
|
for (const pkg of packages.packages) {
|
|
queue.push(pkg);
|
|
packagesByName[pkg.packageJson.name] = pkg;
|
|
}
|
|
|
|
for (const pkg of queue) {
|
|
const { name } = pkg.packageJson;
|
|
const dependencies = [];
|
|
const allDependencies = getAllDependencies(pkg.packageJson);
|
|
|
|
for (let [depName, depRange] of allDependencies) {
|
|
const match = packagesByName[depName];
|
|
if (!match) continue;
|
|
|
|
const expected = match.packageJson.version;
|
|
const usesWorkspaceRange = depRange.startsWith("workspace:");
|
|
|
|
if (usesWorkspaceRange) {
|
|
depRange = depRange.replace(/^workspace:/, "");
|
|
|
|
if (depRange === "*" || depRange === "^" || depRange === "~") {
|
|
dependencies.push(depName);
|
|
continue;
|
|
}
|
|
} else if (opts?.bumpVersionsWithWorkspaceProtocolOnly === true) {
|
|
continue;
|
|
}
|
|
|
|
const range = getValidRange(depRange);
|
|
|
|
if ((range && !range.test(expected)) || isProtocolRange(depRange)) {
|
|
valid = false;
|
|
console.error(
|
|
`Package ${chalk.cyan(
|
|
`"${name}"`
|
|
)} must depend on the current version of ${chalk.cyan(
|
|
`"${depName}"`
|
|
)}: ${chalk.green(`"${expected}"`)} vs ${chalk.red(`"${depRange}"`)}`
|
|
);
|
|
continue;
|
|
}
|
|
|
|
// `depRange` could have been a tag and if a tag has been used there might have been a reason for that
|
|
// we should not count this as a local monorepro dependant
|
|
if (!range) {
|
|
continue;
|
|
}
|
|
|
|
dependencies.push(depName);
|
|
}
|
|
|
|
graph.set(name, { pkg, dependencies });
|
|
}
|
|
return { graph, valid };
|
|
}
|