diff --git a/frontend/parse-deps.js b/frontend/parse-deps.js
new file mode 100644
index 0000000000000000000000000000000000000000..0524c39d9ea36b6e6564f1bfd59c7b2b3b650613
--- /dev/null
+++ b/frontend/parse-deps.js
@@ -0,0 +1,258 @@
+#!/usr/bin/env node
+
+const fs = require("fs");
+const path = require("path");
+
+const glob = require("glob");
+const minimatch = require("minimatch");
+const parser = require("@babel/parser");
+const traverse = require("@babel/traverse").default;
+const readline = require("readline");
+
+const PATTERN = "{enterprise/,}frontend/src/**/*.{js,jsx}";
+
+// 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 options = {
+      allowImportExportEverywhere: true,
+      allowReturnOutsideFunction: true,
+      decoratorsBeforeExport: true,
+      sourceType: "unambiguous",
+      plugins: ["jsx", "flow", "decorators-legacy", "exportDefaultFrom"],
+    };
+    const importList = [];
+    try {
+      const ast = parser.parse(contents, options);
+      traverse(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(name => {
+        if (fs.existsSync(name)) {
+          if (
+            fs.lstatSync(name).isDirectory() &&
+            fs.existsSync(name + "/index.js")
+          ) {
+            return name + "/index.js";
+          }
+          return name;
+        } else if (fs.existsSync(name + ".js")) {
+          return name + ".js";
+        } else if (fs.existsSync(name + ".jsx")) {
+          return name + ".jsx";
+        }
+        return name;
+      })
+      .filter(name => minimatch(name, PATTERN));
+
+    return { source: fileName, dependencies: absoluteImportList.sort() };
+  });
+  return deps;
+}
+
+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);