Skip to content

Commit

Permalink
Add dependency graph to README.md (#2743)
Browse files Browse the repository at this point in the history
Similar to the `MetaMask/core` repository, this adds a dependency graph
to the `README.md` file in the root.
  • Loading branch information
Mrtenz authored Sep 19, 2024
1 parent c825683 commit 64de07b
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 5 deletions.
77 changes: 76 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,82 @@
Extend the functionality of MetaMask using
[MetaMask Snaps](https://metamask.io/snaps/).

## Getting started
## Modules

This repository contains the following packages [^fn1]:

<!-- start package list -->

- [`@metamask/create-snap`](packages/create-snap)
- [`@metamask/snaps-browserify-plugin`](packages/snaps-browserify-plugin)
- [`@metamask/snaps-cli`](packages/snaps-cli)
- [`@metamask/snaps-controllers`](packages/snaps-controllers)
- [`@metamask/snaps-execution-environments`](packages/snaps-execution-environments)
- [`@metamask/snaps-jest`](packages/snaps-jest)
- [`@metamask/snaps-rollup-plugin`](packages/snaps-rollup-plugin)
- [`@metamask/snaps-rpc-methods`](packages/snaps-rpc-methods)
- [`@metamask/snaps-sdk`](packages/snaps-sdk)
- [`@metamask/snaps-simulation`](packages/snaps-simulation)
- [`@metamask/snaps-utils`](packages/snaps-utils)
- [`@metamask/snaps-webpack-plugin`](packages/snaps-webpack-plugin)

<!-- end package list -->

Or, in graph form [^fn1]:

<!-- start dependency graph -->

```mermaid
%%{ init: { 'flowchart': { 'curve': 'bumpX' } } }%%
graph LR;
linkStyle default opacity:0.5
create_snap(["@metamask/create-snap"]);
snaps_browserify_plugin(["@metamask/snaps-browserify-plugin"]);
snaps_cli(["@metamask/snaps-cli"]);
snaps_controllers(["@metamask/snaps-controllers"]);
snaps_execution_environments(["@metamask/snaps-execution-environments"]);
snaps_jest(["@metamask/snaps-jest"]);
snaps_rollup_plugin(["@metamask/snaps-rollup-plugin"]);
snaps_rpc_methods(["@metamask/snaps-rpc-methods"]);
snaps_sdk(["@metamask/snaps-sdk"]);
snaps_simulation(["@metamask/snaps-simulation"]);
snaps_utils(["@metamask/snaps-utils"]);
snaps_webpack_plugin(["@metamask/snaps-webpack-plugin"]);
create_snap --> snaps_utils;
snaps_browserify_plugin --> snaps_utils;
snaps_cli --> snaps_sdk;
snaps_cli --> snaps_utils;
snaps_cli --> snaps_webpack_plugin;
snaps_controllers --> snaps_rpc_methods;
snaps_controllers --> snaps_sdk;
snaps_controllers --> snaps_utils;
snaps_execution_environments --> snaps_sdk;
snaps_execution_environments --> snaps_utils;
snaps_jest --> snaps_controllers;
snaps_jest --> snaps_sdk;
snaps_jest --> snaps_simulation;
snaps_jest --> snaps_utils;
snaps_rollup_plugin --> snaps_utils;
snaps_rpc_methods --> snaps_sdk;
snaps_rpc_methods --> snaps_utils;
snaps_simulation --> snaps_controllers;
snaps_simulation --> snaps_rpc_methods;
snaps_simulation --> snaps_sdk;
snaps_simulation --> snaps_utils;
snaps_utils --> snaps_sdk;
snaps_webpack_plugin --> snaps_sdk;
snaps_webpack_plugin --> snaps_utils;
```

<!-- end dependency graph -->

Refer to individual packages for usage instructions.

## Learn more

For instructions on performing common development-related tasks, see
[contributing to the monorepo](./docs/contributing.md).

[^fn1]:
The package list and dependency graph should be programmatically
generated by running `yarn update-readme-content`.
9 changes: 6 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@
"publish-previews": "yarn workspaces foreach --all --parallel --verbose run publish:preview",
"test": "yarn workspaces foreach --all --parallel --verbose run test",
"test:browser": "yarn workspaces foreach --all --verbose run test:browser",
"test:e2e": "yarn workspaces foreach --all --verbose --exclude root run test:e2e"
"test:e2e": "yarn workspaces foreach --all --verbose --exclude root run test:e2e",
"update-readme-content": "tsx ./scripts/update-readme-content.mts"
},
"simple-git-hooks": {
"pre-commit": "yarn lint-staged && yarn dedupe --check"
"pre-commit": "yarn lint-staged && yarn dedupe --check && yarn update-readme-content"
},
"lint-staged": {
"*.{js,ts,tsx,jsx}": [
Expand Down Expand Up @@ -102,6 +103,7 @@
"semver": "^7.5.4",
"simple-git-hooks": "^2.7.0",
"ts-node": "^10.9.1",
"tsx": "^4.19.1",
"typescript": "~5.3.3",
"vite": "^4.3.9"
},
Expand All @@ -120,7 +122,8 @@
"ts-node>@swc/core": true,
"@swc/core": true,
"favicons>sharp": true,
"vite>esbuild": true
"vite>esbuild": true,
"tsx>esbuild": true
}
}
}
194 changes: 194 additions & 0 deletions scripts/update-readme-content.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#!yarn tsx

import execa from 'execa';
import fs from 'fs';
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';

type Workspace = {
location: string;
name: string;
workspaceDependencies: string[];
};

const DEPENDENCY_GRAPH_START_MARKER = '<!-- start dependency graph -->';
const DEPENDENCY_GRAPH_END_MARKER = '<!-- end dependency graph -->';
const PACKAGE_LIST_START_MARKER = '<!-- start package list -->';
const PACKAGE_LIST_END_MARKER = '<!-- end package list -->';
const README_PATH = path.resolve(
dirname(fileURLToPath(import.meta.url)),
'../README.md',
);

/**
* The entrypoint to this script.
*
* Uses `yarn workspaces list` to:
*
* 1. Retrieve all of the workspace packages in this project and their relationships to each other.
* 2. Produce a Markdown fragment that represents a Mermaid graph.
* 3. Produce a Markdown fragment that represents a list of the workspace packages, and links to them.
* 4. Update the README with the new content.
*/
async function main() {
const workspaces = await retrieveWorkspaces();
await updateReadme(
getDependencyGraph(workspaces),
getPackageList(workspaces),
);

// eslint-disable-next-line no-console
console.log('README content updated.');
}

/**
* Uses the `yarn` executable to gather the Yarn workspaces inside of this
* project (the packages that are matched by the `workspaces` field inside of
* `package.json`).
*
* @returns The list of workspaces.
*/
async function retrieveWorkspaces(): Promise<Workspace[]> {
const { stdout } = await execa('yarn', [
'workspaces',
'list',
'--json',
'--no-private',
'--verbose',
]);

return stdout
.split('\n')
.map((line) => JSON.parse(line))
.filter(
(workspace) => !workspace.location.startsWith('packages/examples/'),
);
}

/**
* Gets the Markdown fragment that represents a Mermaid graph of the
* dependencies between the workspace packages in this project.
*
* @param workspaces - The Yarn workspaces inside of this project.
* @returns The new dependency graph Markdown fragment.
*/
function getDependencyGraph(workspaces: Workspace[]): string {
const nodeLines = buildMermaidNodeLines(workspaces);
const connectionLines = buildMermaidConnectionLines(workspaces);
return assembleMermaidMarkdownFragment(nodeLines, connectionLines);
}

/**
* Builds a piece of the Mermaid graph by defining a node for each workspace
* package within this project.
*
* @param workspaces - The Yarn workspaces inside of this project.
* @returns A set of lines that will go into the final Mermaid graph.
*/
function buildMermaidNodeLines(workspaces: Workspace[]): string[] {
return workspaces.map((workspace) => {
const fullPackageName = workspace.name;
const shortPackageName = fullPackageName
.replace(/^@metamask\//u, '')
.replace(/-/gu, '_');
return `${shortPackageName}(["${fullPackageName}"]);`;
});
}

/**
* Builds a piece of the Mermaid graph by defining connections between nodes
* that correspond to dependencies between workspace packages within this
* project.
*
* @param workspaces - The Yarn workspaces inside of this project.
* @returns A set of lines that will go into the final Mermaid graph.
*/
function buildMermaidConnectionLines(workspaces: Workspace[]): string[] {
const connections: string[] = [];
workspaces.forEach((workspace) => {
const fullPackageName = workspace.name;
const shortPackageName = fullPackageName
.replace(/^@metamask\//u, '')
.replace(/-/gu, '_');
workspace.workspaceDependencies.forEach((dependency) => {
const shortDependencyName = dependency
.replace(/^packages\//u, '')
.replace(/-/gu, '_');
connections.push(`${shortPackageName} --> ${shortDependencyName};`);
});
});
return connections;
}

/**
* Creates the Mermaid graph from the given node lines and connection lines,
* wrapping it in a triple-backtick directive so that it can be embedded within
* a Markdown document.
*
* @param nodeLines - The set of nodes in the graph as lines.
* @param connectionLines - The set of connections in the graph as lines.
* @returns The graph in string format.
*/
function assembleMermaidMarkdownFragment(
nodeLines: string[],
connectionLines: string[],
): string {
return [
'```mermaid',
"%%{ init: { 'flowchart': { 'curve': 'bumpX' } } }%%",
'graph LR;',
'linkStyle default opacity:0.5',
...nodeLines.map((line) => ` ${line}`),
...connectionLines.map((line) => ` ${line}`),
'```',
].join('\n');
}

/**
* Gets the Markdown fragment that represents a list of the workspace packages
* in this project.
*
* @param workspaces - The Yarn workspaces inside of this project.
* @returns The new package list Markdown fragment.
*/
function getPackageList(workspaces: Workspace[]): string {
return workspaces
.sort((a, b) => a.name.localeCompare(b.name))
.map((workspace) => `- [\`${workspace.name}\`](${workspace.location})`)
.join('\n');
}

/**
* Updates the dependency graph section in the README with the given Markdown
* fragment.
*
* @param newGraph - The new dependency graph Markdown fragment.
* @param newPackageList - The new package list Markdown fragment.
*/
async function updateReadme(newGraph: string, newPackageList: string) {
const readmeContent = await fs.promises.readFile(README_PATH, 'utf8');

// Dependency graph
let newReadmeContent = readmeContent.replace(
new RegExp(
`(${DEPENDENCY_GRAPH_START_MARKER}).+(${DEPENDENCY_GRAPH_END_MARKER})`,
'su',
),
(_match, startMarker, endMarker) =>
[startMarker, '', newGraph, '', endMarker].join('\n'),
);

// Package list
newReadmeContent = newReadmeContent.replace(
new RegExp(
`(${PACKAGE_LIST_START_MARKER}).+(${PACKAGE_LIST_END_MARKER})`,
'su',
),
(_match, startMarker, endMarker) =>
[startMarker, '', newPackageList, '', endMarker].join('\n'),
);

await fs.promises.writeFile(README_PATH, newReadmeContent);
}

await main();
4 changes: 3 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"compilerOptions": {
"esModuleInterop": true,
"noEmit": true,
"resolveJsonModule": true
"resolveJsonModule": true,
"module": "Node16",
"moduleResolution": "Node16"
},
"files": [],
"include": ["scripts"]
Expand Down
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -20246,6 +20246,7 @@ __metadata:
semver: "npm:^7.5.4"
simple-git-hooks: "npm:^2.7.0"
ts-node: "npm:^10.9.1"
tsx: "npm:^4.19.1"
typescript: "npm:~5.3.3"
vite: "npm:^4.3.9"
languageName: unknown
Expand Down

0 comments on commit 64de07b

Please sign in to comment.