dependency-tree.ts 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import yargs from "yargs/yargs";
  2. import path from "path";
  3. import { hideBin } from "yargs/helpers";
  4. const args = yargs(hideBin(process.argv))
  5. .option("package", {
  6. type: "string",
  7. describe: "The full or relative path to the package.json file",
  8. require: true
  9. })
  10. .option("format", {
  11. choices: ["tree", "csv"],
  12. demandOption: true,
  13. default: "tree"
  14. })
  15. .option("filter", {
  16. choices: ["none", "oracle"],
  17. demandOption: true,
  18. default: "none"
  19. })
  20. .help()
  21. .argv;
  22. interface Dictionary<T> {
  23. [index: string]: T
  24. }
  25. interface Package {
  26. dependencies: Dictionary<string>;
  27. devDependencies?: Dictionary<string>;
  28. }
  29. type PackageLockDependencies = Dictionary<{
  30. version: string;
  31. dev: boolean;
  32. requires?: Dictionary<string>;
  33. dependencies?: PackageLockDependencies;
  34. }>
  35. interface PackageLock {
  36. dependencies: PackageLockDependencies;
  37. }
  38. type Tree<T> = Leaf<T> | Node<T>;
  39. type Node<T> = {
  40. type: "node";
  41. value: T;
  42. children: Tree<T>[];
  43. }
  44. type Leaf<T> = {
  45. type: "leaf";
  46. value: T;
  47. }
  48. type PackageLockTree = Tree<{ name: string, version: string, dev: boolean }>;
  49. const constructDepTree = (packageName: string, context: PackageLockDependencies, stack: string[] = []): PackageLockTree => {
  50. const dependency = context[packageName];
  51. const value = { name: packageName, version: dependency.version, dev: dependency.dev };
  52. if (!dependency.requires) {
  53. return { type: "leaf", value };
  54. }
  55. return {
  56. type: "node",
  57. value,
  58. children: Object.keys(dependency.requires).filter(k => stack.every(s => s !== k))
  59. .map(requiredDepName => constructDepTree(requiredDepName, { ...context, ...dependency.dependencies }, [...stack, packageName]))
  60. };
  61. }
  62. const displayDepTree = (format: (node: PackageLockTree, parents: string[]) => string) =>
  63. (filter: (node: PackageLockTree, parents: string[]) => boolean) =>
  64. (tree: PackageLockTree) => {
  65. const iter = (tree: PackageLockTree, stack: string[] = []) => {
  66. if (filter(tree, stack)) {
  67. console.log(format(tree, stack));
  68. }
  69. if (tree.type === "node") {
  70. tree.children.forEach(t => iter(t, [...stack, `${tree.value.name}@${tree.value.version}`]))
  71. }
  72. }
  73. iter(tree);
  74. }
  75. Promise.all<Package, PackageLock>([
  76. import(args.package),
  77. import(`${path.dirname(args.package)}/package-lock.json`)
  78. ])
  79. .then(([json, jsonLock]) => {
  80. const dependencyTree = Object.keys(json.dependencies)
  81. .concat(Object.keys(json.devDependencies ?? {}))
  82. .map(depName => constructDepTree(depName, jsonLock.dependencies));
  83. if (args.format === "csv") {
  84. console.log("name,version,root,parent,type");
  85. }
  86. const format = args.format === "tree"
  87. ? (node: PackageLockTree, parents: string[]) => `${" ".repeat(parents.length)}${node.value.name}@${node.value.version}${node.value.dev ? " (dev)" : ""}`
  88. : (node: PackageLockTree, parents: string[]) => `${node.value.name},${node.value.version},${parents[0] ?? ""},${parents[parents.length - 1] ?? ""},${node.value.dev ? "devDependency" : "dependency"}`;
  89. const filter = args.filter === "oracle"
  90. ? (node: PackageLockTree, parents: string[]) => (parents.length === 0 && !node.value.name.startsWith("@os")) || (!node.value.name.startsWith("@o") && parents.length > 1 && parents.every(p => p.startsWith("@os")))
  91. : (node: PackageLockTree, parents: string[]) => true;
  92. const display = displayDepTree(format)(filter);
  93. dependencyTree.forEach(t => display(t));
  94. })
  95. .catch(reson => {
  96. console.error(reson);
  97. });