-
Notifications
You must be signed in to change notification settings - Fork 537
/
get-dependency-graph.ts
126 lines (103 loc) · 3.2 KB
/
get-dependency-graph.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
// 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 };
}