-
-
Notifications
You must be signed in to change notification settings - Fork 198
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add dependency graph to the README + script to gen (#976)
Use `yarn workspaces list` to grab all workspaces and their dependencies to each other, then use Graphviz to render a graph.
- Loading branch information
Showing
7 changed files
with
315 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
"type": "git", | ||
"url": "https://github.com/MetaMask/controllers.git" | ||
}, | ||
"files": [], | ||
"workspaces": [ | ||
"packages/*" | ||
], | ||
|
@@ -16,6 +17,7 @@ | |
"build:docs": "yarn workspaces foreach --parallel --interlaced --verbose run build:docs", | ||
"build:watch": "yarn run build --watch", | ||
"changelog:validate": "yarn workspaces foreach --parallel --interlaced --verbose run changelog:validate", | ||
"generate-dependency-graph": "ts-node scripts/generate-dependency-graph.ts", | ||
"lint": "yarn lint:eslint && yarn lint:misc --check && yarn constraints", | ||
"lint:eslint": "eslint . --cache --ext js,ts", | ||
"lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn constraints --fix", | ||
|
@@ -36,6 +38,7 @@ | |
"@metamask/eslint-config-jest": "^9.0.0", | ||
"@metamask/eslint-config-nodejs": "^9.0.0", | ||
"@metamask/eslint-config-typescript": "^9.0.1", | ||
"@types/node": "^14.14.31", | ||
"@typescript-eslint/eslint-plugin": "^4.33.0", | ||
"@typescript-eslint/parser": "^4.33.0", | ||
"eslint": "^7.24.0", | ||
|
@@ -46,12 +49,15 @@ | |
"eslint-plugin-jsdoc": "^36.1.0", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-prettier": "^3.4.1", | ||
"execa": "^5.0.0", | ||
"isomorphic-fetch": "^3.0.0", | ||
"prettier": "^2.6.2", | ||
"prettier-plugin-packagejson": "^2.2.17", | ||
"rimraf": "^3.0.2", | ||
"simple-git-hooks": "^2.8.0", | ||
"typescript": "~4.6.3" | ||
"ts-node": "^10.9.1", | ||
"typescript": "~4.6.3", | ||
"which": "^3.0.0" | ||
}, | ||
"packageManager": "[email protected]", | ||
"engines": { | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
#!yarn ts-node | ||
|
||
import fs from 'fs'; | ||
import os from 'os'; | ||
import path from 'path'; | ||
import execa from 'execa'; | ||
import which from 'which'; | ||
|
||
/** | ||
* Retrieves the path to `dot`, which is one of the tools within the Graphviz | ||
* toolkit to render a graph. | ||
* | ||
* @returns The path if `dot` exists or else null. | ||
*/ | ||
async function getDotExecutablePath() { | ||
try { | ||
return await which('dot'); | ||
} catch (error) { | ||
if (error.message === 'dot not found') { | ||
return null; | ||
} | ||
throw error; | ||
} | ||
} | ||
|
||
/** | ||
* Uses `yarn workspaces list` to retrieve all of the workspace packages in this | ||
* repo and their relationship to each other, produces code that can be | ||
* passed to the `dot` tool, and writes that to a file. | ||
* | ||
* @param dotFilePath - The path to the file that will be written and ultimately | ||
* passed to `dot`. | ||
*/ | ||
async function generateGraphDotFile(dotFilePath) { | ||
const { stdout } = await execa('yarn', [ | ||
'workspaces', | ||
'list', | ||
'--json', | ||
'--verbose', | ||
]); | ||
|
||
const modules = stdout | ||
.split('\n') | ||
.map((line) => JSON.parse(line)) | ||
.slice(1); | ||
|
||
const nodes = modules.map((mod) => { | ||
const fullPackageName = mod.name; | ||
const shortPackageName = fullPackageName | ||
.replace(/^@metamask\//u, '') | ||
.replace(/-/gu, '_'); | ||
return ` ${shortPackageName} [label="${fullPackageName}"];`; | ||
}); | ||
|
||
const connections = []; | ||
modules.forEach((mod) => { | ||
const fullPackageName = mod.name; | ||
const shortPackageName = fullPackageName | ||
.replace(/^@metamask\//u, '') | ||
.replace(/-/gu, '_'); | ||
mod.workspaceDependencies.forEach((dependency) => { | ||
const shortDependencyName = dependency | ||
.replace(/^packages\//u, '') | ||
.replace(/-/gu, '_'); | ||
connections.push(` ${shortPackageName} -> ${shortDependencyName};`); | ||
}); | ||
}); | ||
|
||
const graphSource = [ | ||
'digraph G {', | ||
' rankdir="LR";', | ||
...nodes, | ||
...connections, | ||
'}', | ||
].join('\n'); | ||
|
||
await fs.promises.writeFile(dotFilePath, graphSource); | ||
} | ||
|
||
/** | ||
* Uses `dot` to render the dependency graph. | ||
* | ||
* @param dotExecutablePath - The path to `dot`. | ||
* @param dotFilePath - The path to file that instructs `dot` how to render the | ||
* graph. | ||
* @param graphFilePath - The path to the image file that will be written. | ||
*/ | ||
async function renderGraph(dotExecutablePath, dotFilePath, graphFilePath) { | ||
await execa(dotExecutablePath, [ | ||
dotFilePath, | ||
'-T', | ||
'png', | ||
'-o', | ||
graphFilePath, | ||
]); | ||
} | ||
|
||
/** | ||
* The entrypoint to this script. | ||
*/ | ||
async function main() { | ||
const tempDirectory = await fs.promises.mkdtemp( | ||
path.join(os.tmpdir(), 'controllers-'), | ||
); | ||
const dotFilePath = path.join(tempDirectory, 'dependency-graph.dot'); | ||
const graphFilePath = path.resolve( | ||
__dirname, | ||
'../assets/dependency-graph.png', | ||
); | ||
const dotExecutablePath = await getDotExecutablePath(); | ||
|
||
if (dotExecutablePath) { | ||
await generateGraphDotFile(dotFilePath); | ||
await renderGraph(dotExecutablePath, dotFilePath, graphFilePath); | ||
console.log(`Done! Graph written to ${graphFilePath}.`); | ||
} else { | ||
throw new Error( | ||
"It looks like you don't have Graphviz installed. You'll need to install this to generate the dependency graph.", | ||
); | ||
} | ||
} | ||
|
||
main().catch((error) => { | ||
console.error(error); | ||
process.exitCode = 1; | ||
}); |
Oops, something went wrong.