@georgewrmarshall/design-system-metrics 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +10 -0
- package/.gitattributes +4 -0
- package/README.md +117 -0
- package/config.json +123 -0
- package/index.js +195 -0
- package/package.json +30 -0
package/.editorconfig
ADDED
package/.gitattributes
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# **@georgewrmarshall/design-system-metrics**
|
|
2
|
+
|
|
3
|
+
A CLI tool to audit design system component usage across multiple MetaMask codebases
|
|
4
|
+
|
|
5
|
+
## **Getting Started**
|
|
6
|
+
|
|
7
|
+
- [Extension](#extension)
|
|
8
|
+
- [Mobile](#mobile)
|
|
9
|
+
- [CLI Options](#cli-options)
|
|
10
|
+
- [Requirements](#requirements)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
### **Extension**
|
|
15
|
+
|
|
16
|
+
1. **Clone the [MetaMask Extension](https://github.com/MetaMask/metamask-extension)** repository if you haven’t already:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
git clone https://github.com/MetaMask/metamask-extension.git
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
2. **Run the CLI tool from the `@georgewrmarshall/design-system-metrics` package:**
|
|
23
|
+
|
|
24
|
+
Navigate to the `@georgewrmarshall/design-system-metrics` package directory:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cd /path/to/@georgewrmarshall/design-system-metrics
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
3. **Run the CLI tool for the MetaMask Extension:**
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
yarn node index.js --project extension
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
4. A file called `extension-component-adoption-metrics.csv` will be generated in the current working directory if there are no errors.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### **Mobile**
|
|
41
|
+
|
|
42
|
+
1. **Clone the [MetaMask Mobile](https://github.com/MetaMask/metamask-mobile)** repository if you haven’t already:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
git clone https://github.com/MetaMask/metamask-mobile.git
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
2. **Run the CLI tool from the `@georgewrmarshall/design-system-metrics` package:**
|
|
49
|
+
|
|
50
|
+
Navigate to the `@georgewrmarshall/design-system-metrics` package directory:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cd /path/to/@georgewrmarshall/design-system-metrics
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
3. **Run the CLI tool for the MetaMask Mobile project:**
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
yarn node index.js --project mobile
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
4. A file called `mobile-component-adoption-metrics.csv` will be generated in the current working directory if there are no errors.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### **CLI Options**
|
|
67
|
+
|
|
68
|
+
- **`--project` (Required)**: Specify the project to audit. Options are:
|
|
69
|
+
|
|
70
|
+
- `extension`: For MetaMask Extension
|
|
71
|
+
- `mobile`: For MetaMask Mobile
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
yarn node index.js --project extension
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- **`--format` (Optional)**: Specify the output format. Options are `csv` (default) or `json`.
|
|
80
|
+
|
|
81
|
+
Example:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
yarn node index.js --project extension --format json
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
- **Custom Configuration**: By default, the tool uses a `config.json` file to define the component list and ignore patterns. You can also pass a custom configuration file by adding the `--config` option:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
yarn node index.js --project extension --config /path/to/custom-config.json
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
### **Requirements**
|
|
96
|
+
|
|
97
|
+
- The tool **ignores deprecated components** (e.g., `component-library/deprecated`).
|
|
98
|
+
- The tool **does not count duplicate components** when imported from different locations (e.g., `<Button` from `../../ui/button` versus `<Button` from `../../component-library`).
|
|
99
|
+
- Components **inside JSDoc comments** are not counted (e.g., `@deprecated <Box /> is deprecated in favour of <Box />`).
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
### **Example Output**
|
|
104
|
+
|
|
105
|
+
Upon running the CLI tool, a CSV file will be generated in the root of the repository (e.g., `extension-component-adoption-metrics.csv` or `mobile-component-adoption-metrics.csv`), listing the components and the number of instances where they are used.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### **Contributing**
|
|
110
|
+
|
|
111
|
+
If you wish to contribute to the tool, ensure you are running the latest version of **Yarn (v4.x)** and **Node.js**. You can make adjustments to the `config.json` file or update the CLI logic for tracking additional components or repositories.
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
### **License**
|
|
116
|
+
|
|
117
|
+
This project is licensed under the [MIT License](LICENSE).
|
package/config.json
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
{
|
|
2
|
+
"projects": {
|
|
3
|
+
"extension": {
|
|
4
|
+
"rootFolder": "ui",
|
|
5
|
+
"ignoreFolders": ["ui/components/component-library"],
|
|
6
|
+
"filePattern": "ui/**/*.{js,tsx}",
|
|
7
|
+
"outputFile": "extension-component-adoption-metrics.csv",
|
|
8
|
+
"components": [
|
|
9
|
+
"AvatarAccount",
|
|
10
|
+
"AvatarBase",
|
|
11
|
+
"AvatarFavicon",
|
|
12
|
+
"AvatarIcon",
|
|
13
|
+
"AvatarNetwork",
|
|
14
|
+
"AvatarToken",
|
|
15
|
+
"BadgeWrapper",
|
|
16
|
+
"BannerAlert",
|
|
17
|
+
"BannerBase",
|
|
18
|
+
"BannerTip",
|
|
19
|
+
"Box",
|
|
20
|
+
"Button",
|
|
21
|
+
"ButtonBase",
|
|
22
|
+
"ButtonIcon",
|
|
23
|
+
"ButtonLink",
|
|
24
|
+
"ButtonPrimary",
|
|
25
|
+
"ButtonSecondary",
|
|
26
|
+
"Checkbox",
|
|
27
|
+
"Container",
|
|
28
|
+
"FormTextField",
|
|
29
|
+
"HeaderBase",
|
|
30
|
+
"HelpText",
|
|
31
|
+
"Icon",
|
|
32
|
+
"Input",
|
|
33
|
+
"Label",
|
|
34
|
+
"Modal",
|
|
35
|
+
"ModalBody",
|
|
36
|
+
"ModalContent",
|
|
37
|
+
"ModalFocus",
|
|
38
|
+
"ModalFooter",
|
|
39
|
+
"ModalHeader",
|
|
40
|
+
"ModalOverlay",
|
|
41
|
+
"PickerNetwork",
|
|
42
|
+
"Popover",
|
|
43
|
+
"PopoverHeader",
|
|
44
|
+
"SelectButton",
|
|
45
|
+
"SelectOption",
|
|
46
|
+
"SelectWrapper",
|
|
47
|
+
"Tag",
|
|
48
|
+
"TagUrl",
|
|
49
|
+
"Text",
|
|
50
|
+
"TextField",
|
|
51
|
+
"TextFieldSearch"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"mobile": {
|
|
55
|
+
"rootFolder": "app",
|
|
56
|
+
"ignoreFolders": ["app/component-library"],
|
|
57
|
+
"filePattern": "app/components/**/*.{js,tsx}",
|
|
58
|
+
"outputFile": "mobile-component-adoption-metrics.csv",
|
|
59
|
+
"components": [
|
|
60
|
+
"Accordion",
|
|
61
|
+
"Avatar",
|
|
62
|
+
"AvatarAccount",
|
|
63
|
+
"AvatarBase",
|
|
64
|
+
"AvatarFavicon",
|
|
65
|
+
"AvatarGroup",
|
|
66
|
+
"AvatarIcon",
|
|
67
|
+
"AvatarNetwork",
|
|
68
|
+
"AvatarToken",
|
|
69
|
+
"Badge",
|
|
70
|
+
"BadgeBase",
|
|
71
|
+
"BadgeNetwork",
|
|
72
|
+
"BadgeStatus",
|
|
73
|
+
"BadgeWrapper",
|
|
74
|
+
"Banner",
|
|
75
|
+
"BannerAlert",
|
|
76
|
+
"BannerBase",
|
|
77
|
+
"BannerTip",
|
|
78
|
+
"BottomSheet",
|
|
79
|
+
"BottomSheetFooter",
|
|
80
|
+
"BottomSheetHeader",
|
|
81
|
+
"Button",
|
|
82
|
+
"ButtonBase",
|
|
83
|
+
"ButtonIcon",
|
|
84
|
+
"ButtonLink",
|
|
85
|
+
"ButtonPrimary",
|
|
86
|
+
"ButtonSecondary",
|
|
87
|
+
"Card",
|
|
88
|
+
"Cell",
|
|
89
|
+
"CellBase",
|
|
90
|
+
"CellDisplay",
|
|
91
|
+
"CellMultiSelect",
|
|
92
|
+
"CellSelect",
|
|
93
|
+
"Checkbox",
|
|
94
|
+
"Header",
|
|
95
|
+
"HelpText",
|
|
96
|
+
"Icon",
|
|
97
|
+
"Input",
|
|
98
|
+
"Label",
|
|
99
|
+
"ListItem",
|
|
100
|
+
"ListItemColumn",
|
|
101
|
+
"ModalConfirmation",
|
|
102
|
+
"ModalMadatory",
|
|
103
|
+
"MultiSelectItem",
|
|
104
|
+
"Overlay",
|
|
105
|
+
"PickerAccount",
|
|
106
|
+
"PickerBase",
|
|
107
|
+
"PickerNetwork",
|
|
108
|
+
"SelectItem",
|
|
109
|
+
"SheetBottom",
|
|
110
|
+
"SheetHeader",
|
|
111
|
+
"TabBar",
|
|
112
|
+
"TabBarItem",
|
|
113
|
+
"Tag",
|
|
114
|
+
"TagUrl",
|
|
115
|
+
"Text",
|
|
116
|
+
"TextField",
|
|
117
|
+
"TextFieldSearch",
|
|
118
|
+
"TextWithPrefixIcon",
|
|
119
|
+
"Toast"
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs").promises;
|
|
4
|
+
const { glob } = require("glob");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const babelParser = require("@babel/parser");
|
|
7
|
+
const traverse = require("@babel/traverse").default;
|
|
8
|
+
const { program } = require("commander");
|
|
9
|
+
const chalk = require("chalk");
|
|
10
|
+
|
|
11
|
+
// Load configuration
|
|
12
|
+
const CONFIG_PATH = path.join(__dirname, "config.json");
|
|
13
|
+
let config;
|
|
14
|
+
|
|
15
|
+
// Function to load and parse the configuration file
|
|
16
|
+
const loadConfig = async () => {
|
|
17
|
+
try {
|
|
18
|
+
const configContent = await fs.readFile(CONFIG_PATH, "utf8");
|
|
19
|
+
config = JSON.parse(configContent);
|
|
20
|
+
} catch (err) {
|
|
21
|
+
console.error(
|
|
22
|
+
chalk.red(`Failed to load configuration file: ${err.message}`)
|
|
23
|
+
);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Define CLI options using Commander
|
|
29
|
+
program
|
|
30
|
+
.version("1.0.0")
|
|
31
|
+
.description("Design System Metrics CLI Tool")
|
|
32
|
+
.requiredOption(
|
|
33
|
+
"-p, --project <name>",
|
|
34
|
+
"Specify the project to audit (e.g., extension, mobile)"
|
|
35
|
+
)
|
|
36
|
+
.option("-f, --format <type>", "Output format (csv, json)", "csv")
|
|
37
|
+
.parse(process.argv);
|
|
38
|
+
|
|
39
|
+
const options = program.opts();
|
|
40
|
+
|
|
41
|
+
// Initialize component instances and file mappings
|
|
42
|
+
let componentInstances = new Map();
|
|
43
|
+
let componentFiles = new Map();
|
|
44
|
+
|
|
45
|
+
// Function to process a single file
|
|
46
|
+
const processFile = async (filePath, componentsSet, importedComponentsSet) => {
|
|
47
|
+
try {
|
|
48
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
49
|
+
|
|
50
|
+
// Parse the file content into an AST
|
|
51
|
+
const ast = babelParser.parse(content, {
|
|
52
|
+
sourceType: "module",
|
|
53
|
+
plugins: ["jsx", "typescript"],
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Traverse the AST to find import declarations from the component library
|
|
57
|
+
traverse(ast, {
|
|
58
|
+
ImportDeclaration({ node }) {
|
|
59
|
+
const importPath = node.source.value;
|
|
60
|
+
if (importPath.includes("/component-library")) {
|
|
61
|
+
node.specifiers.forEach((specifier) => {
|
|
62
|
+
if (specifier.type === "ImportSpecifier") {
|
|
63
|
+
importedComponentsSet.add(specifier.imported.name);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Traverse the AST to find JSX elements
|
|
71
|
+
traverse(ast, {
|
|
72
|
+
JSXElement({ node }) {
|
|
73
|
+
const openingElement = node.openingElement;
|
|
74
|
+
if (
|
|
75
|
+
openingElement &&
|
|
76
|
+
openingElement.name &&
|
|
77
|
+
(openingElement.name.type === "JSXIdentifier" ||
|
|
78
|
+
openingElement.name.type === "JSXMemberExpression")
|
|
79
|
+
) {
|
|
80
|
+
let componentName = "";
|
|
81
|
+
|
|
82
|
+
if (openingElement.name.type === "JSXIdentifier") {
|
|
83
|
+
componentName = openingElement.name.name;
|
|
84
|
+
} else if (openingElement.name.type === "JSXMemberExpression") {
|
|
85
|
+
// Handle namespaced components like <UI.Button>
|
|
86
|
+
let current = openingElement.name;
|
|
87
|
+
while (current.object) {
|
|
88
|
+
current = current.object;
|
|
89
|
+
}
|
|
90
|
+
componentName = current.name;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (
|
|
94
|
+
componentsSet.has(componentName) &&
|
|
95
|
+
importedComponentsSet.has(componentName)
|
|
96
|
+
) {
|
|
97
|
+
const count = componentInstances.get(componentName) || 0;
|
|
98
|
+
componentInstances.set(componentName, count + 1);
|
|
99
|
+
|
|
100
|
+
const files = componentFiles.get(componentName) || [];
|
|
101
|
+
files.push(filePath);
|
|
102
|
+
componentFiles.set(componentName, files);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
} catch (err) {
|
|
108
|
+
console.error(
|
|
109
|
+
chalk.yellow(`Error processing file ${filePath}: ${err.message}`)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Main function to coordinate the audit
|
|
115
|
+
const main = async () => {
|
|
116
|
+
await loadConfig();
|
|
117
|
+
|
|
118
|
+
const projectName = options.project;
|
|
119
|
+
const projectConfig = config.projects[projectName];
|
|
120
|
+
|
|
121
|
+
if (!projectConfig) {
|
|
122
|
+
console.error(
|
|
123
|
+
chalk.red(
|
|
124
|
+
`Project "${projectName}" is not defined in the configuration file.`
|
|
125
|
+
)
|
|
126
|
+
);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const { rootFolder, ignoreFolders, filePattern, outputFile, components } =
|
|
131
|
+
projectConfig;
|
|
132
|
+
|
|
133
|
+
const componentsSet = new Set(components);
|
|
134
|
+
|
|
135
|
+
console.log(chalk.blue(`\nStarting audit for project: ${projectName}\n`));
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const files = await glob(filePattern, {
|
|
139
|
+
ignore: [
|
|
140
|
+
...ignoreFolders.map((folder) => path.join(folder, "**")),
|
|
141
|
+
`${rootFolder}/**/*.test.{js,tsx}`,
|
|
142
|
+
],
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
if (files.length === 0) {
|
|
146
|
+
console.log(chalk.yellow("No files matched the provided pattern."));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Process files concurrently
|
|
151
|
+
await Promise.all(
|
|
152
|
+
files.map((file) => processFile(file, componentsSet, new Set()))
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
console.log(chalk.green("\nComponent Adoption Metrics:"));
|
|
156
|
+
|
|
157
|
+
let csvContent = "Component,Instances,File Paths\n";
|
|
158
|
+
let jsonOutput = {};
|
|
159
|
+
|
|
160
|
+
componentsSet.forEach((componentName) => {
|
|
161
|
+
const instanceCount = componentInstances.get(componentName) || 0;
|
|
162
|
+
const filePaths = componentFiles.get(componentName) || [];
|
|
163
|
+
console.log(`${chalk.cyan(componentName)}: ${instanceCount}`);
|
|
164
|
+
|
|
165
|
+
if (options.format.toLowerCase() === "json") {
|
|
166
|
+
jsonOutput[componentName] = {
|
|
167
|
+
instances: instanceCount,
|
|
168
|
+
files: filePaths,
|
|
169
|
+
};
|
|
170
|
+
} else {
|
|
171
|
+
csvContent += `"${componentName}",${instanceCount},"${filePaths.join(
|
|
172
|
+
", "
|
|
173
|
+
)}"\n`;
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Write the metrics to the specified output file
|
|
178
|
+
try {
|
|
179
|
+
if (options.format.toLowerCase() === "json") {
|
|
180
|
+
await fs.writeFile(outputFile, JSON.stringify(jsonOutput, null, 2));
|
|
181
|
+
} else {
|
|
182
|
+
await fs.writeFile(outputFile, csvContent);
|
|
183
|
+
}
|
|
184
|
+
console.log(
|
|
185
|
+
chalk.green(`\nComponent metrics written to ${outputFile}\n`)
|
|
186
|
+
);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
console.error(chalk.red(`Error writing to file: ${err.message}`));
|
|
189
|
+
}
|
|
190
|
+
} catch (err) {
|
|
191
|
+
console.error(chalk.red(`Error reading files: ${err.message}`));
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@georgewrmarshall/design-system-metrics",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A CLI tool to audit design system component usage across multiple MetaMask codebases",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"packageManager": "yarn@4.3.1",
|
|
7
|
+
"bin": "./index.js",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"start": "node index.js --project extension"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [
|
|
12
|
+
"cli",
|
|
13
|
+
"audit",
|
|
14
|
+
"components",
|
|
15
|
+
"ast",
|
|
16
|
+
"babel"
|
|
17
|
+
],
|
|
18
|
+
"author": "George Marshall",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@babel/parser": "^7.25.7",
|
|
22
|
+
"@babel/traverse": "^7.25.7",
|
|
23
|
+
"chalk": "^4.1.2",
|
|
24
|
+
"commander": "^12.1.0",
|
|
25
|
+
"glob": "^11.0.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"jest": "^29.7.0"
|
|
29
|
+
}
|
|
30
|
+
}
|