dependency-tree.ts 3.6 KB

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