graph-dependencies.js 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. /**
  4. * This script generates a transitive reduction of the dependency graph among
  5. * jupyterlab packages using the graphviz library. It works from package.json
  6. * dependencies, so does not capture the nuances of how plugins depend on each
  7. * other, such as optional dependencies. Still, it can be useful to have a way
  8. * to generate such a graph to get an idea of how packages depend on other
  9. * packages.
  10. *
  11. * You must have graphviz installed to run this script. Run from the docs
  12. * directory:
  13. *
  14. * % node scripts/graph-dependencies.js
  15. */
  16. const childProcess = require('child_process');
  17. const fs = require('fs-extra');
  18. const glob = require('glob');
  19. const path = require('path');
  20. const url = require('url');
  21. const basePath = path.resolve('..');
  22. const baseUrl = 'https://github.com/jupyterlab/jupyterlab/tree/master/packages';
  23. const packages = glob.sync(path.join(basePath, 'packages/*'));
  24. // Begin the graph specification
  25. let text = ['digraph G {', 'node [shape=box];'];
  26. packages.forEach(function (packagePath) {
  27. // Load the package.json data.
  28. const dataPath = path.join(packagePath, 'package.json');
  29. let data;
  30. try {
  31. data = require(dataPath);
  32. } catch (e) {
  33. return;
  34. }
  35. const name = data.name ?? 'UNKNOWN';
  36. // Don't include private packages.
  37. if (data.private === true) {
  38. return;
  39. }
  40. // Only include packages in the @jupyterlab namespace.
  41. if (!name.startsWith('@jupyterlab')) {
  42. return;
  43. }
  44. // In order to cut down on the number of graph nodes,
  45. // don't include "*-extension" packages.
  46. if (name.endsWith('-extension')) {
  47. return;
  48. }
  49. // Don't include the metapackage.
  50. if (name === '@jupyterlab/metapackage') {
  51. return;
  52. }
  53. const shortName = name.split('/')[1];
  54. const urlLink = url.resolve(
  55. baseUrl,
  56. 'packages/' + path.basename(packagePath)
  57. );
  58. // Remove the '@jupyterlab' part of the name.
  59. text.push(`"${shortName}" [URL="${urlLink}"];\n`);
  60. const deps = data.dependencies ?? [];
  61. for (let dep in deps) {
  62. // Only include JupyterLab dependencies
  63. if (dep.startsWith('@jupyterlab')) {
  64. text.push(`"${shortName}" -> "${dep.split('/')[1]}";\n`);
  65. }
  66. }
  67. });
  68. text.push('}');
  69. fs.writeFileSync('./dependencies.gv', text.join('\n'));
  70. childProcess.execSync(
  71. 'cat dependencies.gv | tred | dot -Tsvg -o dependency-graph.svg'
  72. );
  73. fs.unlinkSync('./dependencies.gv');