1 | "use strict";
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | var __importStar = (this && this.__importStar) || function (mod) {
|
7 | if (mod && mod.__esModule) return mod;
|
8 | var result = {};
|
9 | if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
|
10 | result["default"] = mod;
|
11 | return result;
|
12 | };
|
13 | var __importDefault = (this && this.__importDefault) || function (mod) {
|
14 | return (mod && mod.__esModule) ? mod : { "default": mod };
|
15 | };
|
16 | Object.defineProperty(exports, "__esModule", { value: true });
|
17 | const fs = __importStar(require("fs-extra"));
|
18 | const lockfile = __importStar(require("@yarnpkg/lockfile"));
|
19 | const path = __importStar(require("path"));
|
20 | const utils = __importStar(require("./utils"));
|
21 | const commander_1 = __importDefault(require("commander"));
|
22 |
|
23 |
|
24 |
|
25 | function flat(arr) {
|
26 | return arr.reduce((acc, val) => acc.concat(val), []);
|
27 | }
|
28 |
|
29 |
|
30 |
|
31 | function readYarn(basePath = '.') {
|
32 | let file = fs.readFileSync(path.join(basePath, 'yarn.lock'), 'utf8');
|
33 | let json = lockfile.parse(file);
|
34 | if (json.type !== 'success') {
|
35 | throw new Error('Error reading file');
|
36 | }
|
37 | return json.object;
|
38 | }
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 | function getNode(yarnData, pkgName) {
|
47 | if (!(pkgName in yarnData)) {
|
48 | console.error(`Could not find ${pkgName} in yarn.lock file. Ignore if this is a top-level package.`);
|
49 | return undefined;
|
50 | }
|
51 | let name = pkgName[0] + pkgName.slice(1).split('@')[0];
|
52 | let version = yarnData[pkgName].version;
|
53 | let pkgNode = `${name}@${version}`;
|
54 | return pkgNode;
|
55 | }
|
56 |
|
57 |
|
58 |
|
59 | function buildYarnGraph(yarnData) {
|
60 |
|
61 | const dependsOn = Object.create(null);
|
62 | Object.keys(yarnData).forEach(pkgName => {
|
63 | let pkg = yarnData[pkgName];
|
64 | let pkgNode = getNode(yarnData, pkgName);
|
65 |
|
66 |
|
67 | if (dependsOn[pkgNode] !== undefined) {
|
68 | return;
|
69 | }
|
70 | dependsOn[pkgNode] = [];
|
71 | let deps = pkg.dependencies;
|
72 | if (deps) {
|
73 | Object.keys(deps).forEach(depName => {
|
74 | let depNode = getNode(yarnData, `${depName}@${deps[depName]}`);
|
75 | dependsOn[pkgNode].push(depNode);
|
76 | });
|
77 | }
|
78 | });
|
79 | return dependsOn;
|
80 | }
|
81 |
|
82 |
|
83 |
|
84 | function subgraph(graph, nodes) {
|
85 | let sub = Object.create(null);
|
86 |
|
87 | let newNodes = nodes;
|
88 | while (newNodes.length > 0) {
|
89 | let old = newNodes;
|
90 | newNodes = [];
|
91 | old.forEach(i => {
|
92 | if (!(i in sub)) {
|
93 | sub[i] = graph[i];
|
94 | newNodes.push(...sub[i]);
|
95 | }
|
96 | });
|
97 | }
|
98 | return sub;
|
99 | }
|
100 |
|
101 |
|
102 |
|
103 | function pkgData(packagePath) {
|
104 | packagePath = path.join(packagePath, 'package.json');
|
105 | let data;
|
106 | try {
|
107 | data = utils.readJSONFile(packagePath);
|
108 | }
|
109 | catch (e) {
|
110 | console.error('Skipping package ' + packagePath);
|
111 | return {};
|
112 | }
|
113 | return data;
|
114 | }
|
115 | function convertDot(g, graphOptions, distinguishRoots = false, distinguishLeaves = false) {
|
116 | let edges = flat(Object.keys(g).map(a => g[a].map(b => [a, b]))).sort();
|
117 | let nodes = Object.keys(g).sort();
|
118 |
|
119 |
|
120 | let dot = `
|
121 | digraph DEPS {
|
122 | ${graphOptions || ''}
|
123 | ${nodes.map(node => `"${node}";`).join(' ')}
|
124 | ${edges.map(([a, b]) => `"${a}" -> "${b}"`).join('\n ')}
|
125 | }
|
126 | `;
|
127 | return dot;
|
128 | }
|
129 | function main({ dependencies, devDependencies, jupyterlab, lerna, lernaExclude, lernaInclude, path, phosphor, topLevel }) {
|
130 | let yarnData = readYarn(path);
|
131 | let graph = buildYarnGraph(yarnData);
|
132 | let paths = [path];
|
133 | if (lerna !== false) {
|
134 | paths.push(...utils.getLernaPaths(path).sort());
|
135 | }
|
136 |
|
137 | let data = paths.map(p => pkgData(p));
|
138 |
|
139 | const topLevelNames = new Set(data.map(d => d.name));
|
140 |
|
141 | if (lernaInclude) {
|
142 | let re = new RegExp(lernaInclude);
|
143 | data = data.filter(d => d.name && d.name.match(re));
|
144 | }
|
145 | if (lernaExclude) {
|
146 | let re = new RegExp(lernaExclude);
|
147 | data = data.filter(d => d.name && !d.name.match(re));
|
148 | }
|
149 | const depKinds = [];
|
150 | if (devDependencies) {
|
151 | depKinds.push('devDependencies');
|
152 | }
|
153 | if (dependencies) {
|
154 | depKinds.push('dependencies');
|
155 | }
|
156 | |
157 |
|
158 |
|
159 | const dependencyRoots = data.map(d => {
|
160 | let roots = [];
|
161 | for (let depKind of depKinds) {
|
162 | let deps = d[depKind];
|
163 | if (deps === undefined) {
|
164 | continue;
|
165 | }
|
166 | let nodes = Object.keys(deps)
|
167 | .map(i => {
|
168 |
|
169 |
|
170 | if (!topLevelNames.has(i)) {
|
171 | return getNode(yarnData, `${i}@${deps[i]}`);
|
172 | }
|
173 | })
|
174 | .filter(i => i !== undefined);
|
175 | roots.push(...nodes);
|
176 | }
|
177 | return roots;
|
178 | });
|
179 |
|
180 | let sub = subgraph(graph, flat(dependencyRoots));
|
181 |
|
182 | if (topLevel) {
|
183 | data.forEach((d, i) => {
|
184 | sub[`${d.name}@${d.version}`] = dependencyRoots[i];
|
185 | });
|
186 | }
|
187 |
|
188 | if (!phosphor) {
|
189 | Object.keys(sub).forEach(v => {
|
190 | sub[v] = sub[v].filter(w => !w.startsWith('@phosphor/'));
|
191 | });
|
192 | Object.keys(sub).forEach(v => {
|
193 | if (v.startsWith('@phosphor/')) {
|
194 | delete sub[v];
|
195 | }
|
196 | });
|
197 | }
|
198 |
|
199 |
|
200 |
|
201 |
|
202 | if (!jupyterlab) {
|
203 | Object.keys(sub).forEach(v => {
|
204 | sub[v] = sub[v].filter(w => !w.startsWith('@jupyterlab/'));
|
205 | });
|
206 | Object.keys(sub).forEach(v => {
|
207 | if (v.startsWith('@jupyterlab/') && sub[v].length === 0) {
|
208 | delete sub[v];
|
209 | }
|
210 | });
|
211 | }
|
212 | return sub;
|
213 | }
|
214 | commander_1.default
|
215 | .description(`Print out the dependency graph in dot graph format.`)
|
216 | .option('--lerna', 'Include dependencies in all lerna packages')
|
217 | .option('--lerna-include <regex>', 'A regex for package names to include in dependency roots')
|
218 | .option('--lerna-exclude <regex>', 'A regex for lerna package names to exclude from dependency roots (can override the include regex)')
|
219 | .option('--path [path]', 'Path to package or monorepo to investigate', '.')
|
220 | .option('--no-jupyterlab', 'Do not include dependency connections TO @jupyterlab org packages nor isolated @jupyterlab org packages')
|
221 | .option('--no-phosphor', 'Do not include @phosphor org packages')
|
222 | .option('--no-devDependencies', 'Do not include dev dependencies')
|
223 | .option('--no-dependencies', 'Do not include normal dependencies')
|
224 | .option('--no-top-level', 'Do not include the top-level packages')
|
225 | .option('--graph-options <options>', 'dot graph options (such as "ratio=0.25; concentrate=true;")')
|
226 | .action(args => {
|
227 | let graph = main(args);
|
228 | console.log(convertDot(graph, args.graphOptions));
|
229 | console.error(`Nodes: ${Object.keys(graph).length}`);
|
230 | });
|
231 | commander_1.default.parse(process.argv);
|
232 |
|
\ | No newline at end of file |