diff --git a/README.md b/README.md index 1d6a22ecd8..5f2ebf1973 100644 --- a/README.md +++ b/README.md @@ -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]: + + + +- [`@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) + + + +Or, in graph form [^fn1]: + + + +```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; +``` + + + +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`. diff --git a/package.json b/package.json index 27a0492c00..d0094d414b 100644 --- a/package.json +++ b/package.json @@ -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}": [ @@ -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" }, @@ -120,7 +122,8 @@ "ts-node>@swc/core": true, "@swc/core": true, "favicons>sharp": true, - "vite>esbuild": true + "vite>esbuild": true, + "tsx>esbuild": true } } } diff --git a/scripts/update-readme-content.mts b/scripts/update-readme-content.mts new file mode 100755 index 0000000000..ddc5c678ff --- /dev/null +++ b/scripts/update-readme-content.mts @@ -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 = ''; +const DEPENDENCY_GRAPH_END_MARKER = ''; +const PACKAGE_LIST_START_MARKER = ''; +const PACKAGE_LIST_END_MARKER = ''; +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 { + 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(); diff --git a/tsconfig.json b/tsconfig.json index 905ecb440a..addd4ea089 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,9 @@ "compilerOptions": { "esModuleInterop": true, "noEmit": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "module": "Node16", + "moduleResolution": "Node16" }, "files": [], "include": ["scripts"] diff --git a/yarn.lock b/yarn.lock index c1b7448b96..2d92ee1d09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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