Newer
Older
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const glob = require("glob");
const minimatch = require("minimatch");
const babel = require("@babel/core");
const readline = require("readline");
const PATTERN = "{enterprise/,}frontend/src/**/*.{js,jsx,ts,tsx}";
// after webpack.config.js
const ALIAS = {
metabase: "frontend/src/metabase",
"metabase-lib": "frontend/src/metabase-lib",
"metabase-enterprise": "enterprise/frontend/src/metabase-enterprise",
"metabase-types": "frontend/src/metabase-types",
};
function files() {
return glob.sync(PATTERN);
}
function dependencies() {
const deps = files().map(filename => {
const contents = fs.readFileSync(filename, "utf-8");
const importList = [];
try {
const file = babel.transformSync(contents, {
filename,
presets: ["@babel/preset-typescript"],
ast: true,
code: false,
});
babel.traverse(file.ast, {
enter(path) {
if (path.node.type === "ImportDeclaration") {
importList.push(path.node.source.value);
}
if (path.node.type === "CallExpression") {
const callee = path.node.callee;
const args = path.node.arguments;
if (callee.type === "Identifier" && callee.name === "require") {
if (args.length === 1 && args[0].type === "StringLiteral") {
importList.push(args[0].value);
}
}
}
},
});
} catch (e) {
console.error(filename, e.toString());
process.exit(-1);
n;
}
const base = path.dirname(filename) + path.sep;
const absoluteImportList = importList
.map(name => {
const absName = name[0] === "." ? path.normalize(base + name) : name;
const parts = absName.split(path.sep);
const realPath = ALIAS[parts[0]];
parts[0] = realPath ? realPath : parts[0];
const realName = parts.join(path.sep);
return realName;
})
.map(getFilePathFromImportPath)
.filter(name => minimatch(name, PATTERN));
return { source: filename, dependencies: absoluteImportList.sort() };
});
return deps;
}
function getFilePathFromImportPath(name) {
const scriptsExtensions = ["js", "ts"];
const scriptsExtensionsWithJsx = [...scriptsExtensions, "jsx", "tsx"];
for (let extension of scriptsExtensionsWithJsx) {
const path = `${name}.${extension}`;
if (fs.existsSync(path)) {
return path;
}
}
const isDirectory = fs.existsSync(name) && fs.lstatSync(name).isDirectory();
for (let extension of scriptsExtensions) {
const indexScriptPath = `${name}/index.${extension}`;
if (isDirectory && fs.existsSync(indexScriptPath)) {
return indexScriptPath;
}
}
return name;
}
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
function dependents() {
let dependents = {};
dependencies().forEach(dep => {
const { source, dependencies } = dep;
dependencies.forEach(d => {
if (!dependents[d]) {
dependents[d] = [];
}
dependents[d].push(source);
});
});
return dependents;
}
function getDependents(sources) {
const allDependents = dependents();
let filteredDependents = [];
sources.forEach(name => {
const list = allDependents[name];
if (list && Array.isArray(list) && list.length > 0) {
filteredDependents.push(...list);
}
});
return Array.from(new Set(filteredDependents)).sort(); // unique
}
function filterDependents() {
const rl = readline.createInterface({ input: process.stdin });
const start = async () => {
let sources = [];
for await (const line of rl) {
const name = line.trim();
if (name.length > 0) {
sources.push(name);
}
}
const filteredDependents = getDependents(sources);
console.log(filteredDependents.join("\n"));
};
start();
}
function filterAllDependents() {
const rl = readline.createInterface({ input: process.stdin });
const start = async () => {
let sources = [];
for await (const line of rl) {
const name = line.trim();
if (name.length > 0) {
sources.push(name);
}
}
let filteredDependents = getDependents(sources);
const allDependents = dependents();
for (let i = 0; i < filteredDependents.length; ++i) {
const name = filteredDependents[i];
const list = allDependents[name];
if (list && Array.isArray(list) && list.length > 0) {
const newAddition = list.filter(e => filteredDependents.indexOf(e) < 0);
filteredDependents.push(...newAddition);
}
}
console.log(filteredDependents.sort().join("\n"));
};
start();
}
function countDependents() {
const allDependents = dependents();
const sources = Object.keys(allDependents).sort();
const tally = sources.map(name => {
return { name, count: allDependents[name].length };
});
console.log(tally.map(({ name, count }) => `${count} ${name}`).join("\n"));
}
function countAllDependents() {
const allDependents = dependents();
const sources = Object.keys(allDependents).sort();
const tally = sources.map(name => {
const list = allDependents[name];
for (let i = 0; i < list.length; ++i) {
const deps = allDependents[list[i]];
if (deps && Array.isArray(deps) && deps.length > 1) {
const newAddition = deps.filter(e => list.indexOf(e) < 0);
list.push(...newAddition);
}
}
return { name, count: list.length };
});
console.log(tally.map(({ name, count }) => `${count} ${name}`).join("\n"));
}
function matrix() {
const allDependents = dependents();
const sources = Object.keys(allDependents).sort();
const width = Math.max(...sources.map(s => s.length));
const rows = sources.map(name => {
const list = allDependents[name];
const checks = sources.map(dep => (list.indexOf(dep) < 0 ? " " : "x"));
return name.padEnd(width) + " | " + checks.join("");
});
console.log(rows.join("\n"));
}
const USAGE = `
parse-deps cmd
cmd must be one of:
files Display list of source files
dependencies Show the dependencies of each source file
dependents Show the dependents of each source file
filter-dependents Filter direct dependents based on stdin
filter-all-dependents Filter all indirect and direct dependents based on stdin
count-dependents List the total count of direct dependents
count-all-dependents List the total count of its direct and indirect dependents
matrix Display 2-D matrix of dependent relationship
`;
function main(args) {
const cmd = args[0];
if (cmd) {
switch (cmd.toLowerCase()) {
case "files":
console.log(files().join("\n"));
break;
case "dependencies":
console.log(JSON.stringify(dependencies(), null, 2));
break;
case "dependents":
console.log(JSON.stringify(dependents(), null, 2));
break;
case "filter-dependents":
filterDependents();
break;
case "filter-all-dependents":
filterAllDependents();
break;
case "count-dependents":
countDependents();
break;
case "count-all-dependents":
countAllDependents();
break;
case "matrix":
matrix();
break;
default:
console.log(USAGE);
break;
}
} else {
console.log(USAGE);
}
}
let args = process.argv;
args.shift();
args.shift();
main(args);