@georgewrmarshall/design-system-metrics 1.0.2 → 2.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/.claude/settings.local.json +17 -0
- package/README.md +140 -38
- package/__tests__/fixtures/test-config.json +24 -0
- package/__tests__/fixtures/test-project/ui/pages/current-page.js +13 -0
- package/__tests__/fixtures/test-project/ui/pages/deprecated-page.js +15 -0
- package/__tests__/fixtures/test-project/ui/pages/mixed-page.js +13 -0
- package/__tests__/metrics.test.js +189 -0
- package/config.json +100 -5
- package/index.js +179 -56
- package/jest.config.js +14 -0
- package/package.json +6 -4
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(tree:*)",
|
|
5
|
+
"Bash(node:*)",
|
|
6
|
+
"Bash(yarn install)",
|
|
7
|
+
"Bash(yarn node:*)",
|
|
8
|
+
"WebFetch(domain:github.com)",
|
|
9
|
+
"Bash(yarn test:*)",
|
|
10
|
+
"Bash(npm view:*)",
|
|
11
|
+
"Bash(npm whoami:*)",
|
|
12
|
+
"Bash(npm publish:*)"
|
|
13
|
+
],
|
|
14
|
+
"deny": [],
|
|
15
|
+
"ask": []
|
|
16
|
+
}
|
|
17
|
+
}
|
package/README.md
CHANGED
|
@@ -1,108 +1,210 @@
|
|
|
1
1
|
# **@georgewrmarshall/design-system-metrics**
|
|
2
2
|
|
|
3
|
-
A CLI tool to audit design system component usage across
|
|
3
|
+
A CLI tool to audit design system component usage and track migration progress across MetaMask codebases. Identifies deprecated local component usage and measures adoption of the new MetaMask Design System (MMDS) NPM packages.
|
|
4
4
|
|
|
5
5
|
## **Getting Started**
|
|
6
6
|
|
|
7
7
|
- [Extension](#extension)
|
|
8
8
|
- [Mobile](#mobile)
|
|
9
|
+
- [Design System Packages](#design-system-packages)
|
|
9
10
|
- [CLI Options](#cli-options)
|
|
11
|
+
- [Features](#features)
|
|
12
|
+
- [Output Files](#output-files)
|
|
10
13
|
- [Requirements](#requirements)
|
|
11
14
|
|
|
12
15
|
---
|
|
13
16
|
|
|
14
17
|
### **Extension**
|
|
15
18
|
|
|
16
|
-
1. **Clone the [MetaMask Extension](https://github.com/MetaMask/metamask-extension)** repository if you haven
|
|
19
|
+
1. **Clone the [MetaMask Extension](https://github.com/MetaMask/metamask-extension)** repository if you haven't already:
|
|
17
20
|
|
|
18
21
|
```bash
|
|
19
22
|
git clone https://github.com/MetaMask/metamask-extension.git
|
|
20
23
|
```
|
|
21
24
|
|
|
22
|
-
2. **Run the CLI tool
|
|
25
|
+
2. **Run the CLI tool using npx:**
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
```bash
|
|
28
|
+
npx @georgewrmarshall/design-system-metrics --project extension
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
3. Two files will be generated in the current working directory:
|
|
32
|
+
- `extension-component-metrics-deprecated.csv` - Old local component-library usage (needs migration)
|
|
33
|
+
- `extension-component-metrics-current.csv` - New MMDS NPM package usage (@metamask/design-system-react)
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### **Mobile**
|
|
38
|
+
|
|
39
|
+
1. **Clone the [MetaMask Mobile](https://github.com/MetaMask/metamask-mobile)** repository if you haven't already:
|
|
25
40
|
|
|
26
41
|
```bash
|
|
27
|
-
|
|
42
|
+
git clone https://github.com/MetaMask/metamask-mobile.git
|
|
28
43
|
```
|
|
29
44
|
|
|
30
|
-
|
|
45
|
+
2. **Run the CLI tool using npx:**
|
|
31
46
|
|
|
32
47
|
```bash
|
|
33
|
-
|
|
48
|
+
npx @georgewrmarshall/design-system-metrics --project mobile
|
|
34
49
|
```
|
|
35
50
|
|
|
36
|
-
|
|
51
|
+
3. Two files will be generated in the current working directory:
|
|
52
|
+
- `mobile-component-metrics-deprecated.csv` - Old local component-library usage (needs migration)
|
|
53
|
+
- `mobile-component-metrics-current.csv` - New MMDS NPM package usage (@metamask/design-system-react-native)
|
|
37
54
|
|
|
38
55
|
---
|
|
39
56
|
|
|
40
|
-
### **
|
|
57
|
+
### **Design System Packages**
|
|
58
|
+
|
|
59
|
+
You can also audit the design system NPM packages themselves to track component usage within the design system repos.
|
|
41
60
|
|
|
42
|
-
|
|
61
|
+
#### **design-system-react**
|
|
62
|
+
|
|
63
|
+
1. **Clone the [design-system-react](https://github.com/MetaMask/design-system-react)** repository:
|
|
43
64
|
|
|
44
65
|
```bash
|
|
45
|
-
git clone https://github.com/MetaMask/
|
|
66
|
+
git clone https://github.com/MetaMask/design-system-react.git
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
2. **Run the CLI tool:**
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx @georgewrmarshall/design-system-metrics --project design-system-react
|
|
46
73
|
```
|
|
47
74
|
|
|
48
|
-
|
|
75
|
+
3. One file will be generated:
|
|
76
|
+
- `design-system-react-metrics.csv` - Component usage within the design system package
|
|
77
|
+
|
|
78
|
+
#### **design-system-react-native**
|
|
49
79
|
|
|
50
|
-
|
|
80
|
+
1. **Clone the [design-system-react-native](https://github.com/MetaMask/design-system-react-native)** repository:
|
|
51
81
|
|
|
52
82
|
```bash
|
|
53
|
-
|
|
83
|
+
git clone https://github.com/MetaMask/design-system-react-native.git
|
|
54
84
|
```
|
|
55
85
|
|
|
56
|
-
|
|
86
|
+
2. **Run the CLI tool:**
|
|
57
87
|
|
|
58
88
|
```bash
|
|
59
|
-
|
|
89
|
+
npx @georgewrmarshall/design-system-metrics --project design-system-react-native
|
|
60
90
|
```
|
|
61
91
|
|
|
62
|
-
|
|
92
|
+
3. One file will be generated:
|
|
93
|
+
- `design-system-react-native-metrics.csv` - Component usage within the design system package
|
|
63
94
|
|
|
64
95
|
---
|
|
65
96
|
|
|
66
97
|
### **CLI Options**
|
|
67
98
|
|
|
68
99
|
- **`--project` (Required)**: Specify the project to audit. Options are:
|
|
100
|
+
- `extension`: For MetaMask Extension
|
|
101
|
+
- `mobile`: For MetaMask Mobile
|
|
102
|
+
- `design-system-react`: For design-system-react package
|
|
103
|
+
- `design-system-react-native`: For design-system-react-native package
|
|
69
104
|
|
|
70
|
-
|
|
71
|
-
|
|
105
|
+
Example:
|
|
106
|
+
```bash
|
|
107
|
+
npx @georgewrmarshall/design-system-metrics --project extension
|
|
108
|
+
```
|
|
72
109
|
|
|
73
|
-
|
|
110
|
+
- **`--format` (Optional)**: Specify the output format. Options are `csv` (default) or `json`.
|
|
74
111
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
112
|
+
Example:
|
|
113
|
+
```bash
|
|
114
|
+
npx @georgewrmarshall/design-system-metrics --project extension --format json
|
|
115
|
+
```
|
|
78
116
|
|
|
79
|
-
- **`--
|
|
117
|
+
- **`--sources` (Optional)**: Specify which sources to track. Options are:
|
|
118
|
+
- `all` (default): Track both deprecated and current sources
|
|
119
|
+
- `deprecated`: Only track old local component-library usage
|
|
120
|
+
- `current`: Only track new MMDS NPM package usage
|
|
121
|
+
- Comma-separated: `deprecated,current` (same as `all`)
|
|
80
122
|
|
|
81
|
-
Example:
|
|
123
|
+
Example:
|
|
124
|
+
```bash
|
|
125
|
+
npx @georgewrmarshall/design-system-metrics --project extension --sources deprecated
|
|
126
|
+
```
|
|
82
127
|
|
|
83
|
-
|
|
84
|
-
yarn node index.js --project extension --format json
|
|
85
|
-
```
|
|
128
|
+
- **`--config` (Optional)**: Path to custom configuration file
|
|
86
129
|
|
|
87
|
-
|
|
130
|
+
Example:
|
|
131
|
+
```bash
|
|
132
|
+
npx @georgewrmarshall/design-system-metrics --project extension --config ./custom-config.json
|
|
133
|
+
```
|
|
88
134
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
### **Features**
|
|
138
|
+
|
|
139
|
+
#### **Multi-Source Tracking (Extension & Mobile)**
|
|
140
|
+
For Extension and Mobile projects, the tool tracks component usage from two distinct sources:
|
|
141
|
+
|
|
142
|
+
1. **Deprecated**: Components imported from old local `/component-library` paths (needs migration)
|
|
143
|
+
2. **Current**: Components imported from new MMDS NPM packages:
|
|
144
|
+
- Extension: `@metamask/design-system-react`
|
|
145
|
+
- Mobile: `@metamask/design-system-react-native`
|
|
146
|
+
|
|
147
|
+
This helps you track **migration progress** from deprecated local components to the new design system packages.
|
|
148
|
+
|
|
149
|
+
#### **Design System Package Auditing**
|
|
150
|
+
For design system packages themselves (`design-system-react` and `design-system-react-native`), the tool generates a single report showing all component usage within the package. This helps you understand which components are being used internally.
|
|
151
|
+
|
|
152
|
+
#### **Flexible Configuration**
|
|
153
|
+
Four project configurations are available in `config.json`:
|
|
154
|
+
- `extension` - Tracks Extension repo (deprecated = local, current = @metamask/design-system-react)
|
|
155
|
+
- `mobile` - Tracks Mobile repo (deprecated = local, current = @metamask/design-system-react-native)
|
|
156
|
+
- `design-system-react` - Tracks the design-system-react package itself
|
|
157
|
+
- `design-system-react-native` - Tracks the design-system-react-native package itself
|
|
158
|
+
|
|
159
|
+
Each project can be configured with:
|
|
160
|
+
- Custom file patterns and ignore rules
|
|
161
|
+
- Component lists to audit
|
|
162
|
+
- NPM packages to track (for extension/mobile)
|
|
92
163
|
|
|
93
164
|
---
|
|
94
165
|
|
|
95
|
-
### **
|
|
166
|
+
### **Output Files**
|
|
167
|
+
|
|
168
|
+
#### **Extension & Mobile Projects**
|
|
169
|
+
Two separate CSV or JSON files are generated to track migration progress:
|
|
170
|
+
|
|
171
|
+
**CSV Format:**
|
|
172
|
+
```csv
|
|
173
|
+
Component,Instances,File Paths
|
|
174
|
+
"Button",42,"ui/pages/send/send.js, ui/pages/home/home.js"
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Files Generated:**
|
|
178
|
+
- `{project}-component-metrics-deprecated.csv` - Old local component usage
|
|
179
|
+
- `{project}-component-metrics-current.csv` - New NPM package usage
|
|
180
|
+
|
|
181
|
+
**JSON Format:**
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"Button": {
|
|
185
|
+
"instances": 42,
|
|
186
|
+
"files": ["ui/pages/send/send.js", "ui/pages/home/home.js"]
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
96
190
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
191
|
+
#### **Design System Package Projects**
|
|
192
|
+
One aggregated report showing all component usage:
|
|
193
|
+
|
|
194
|
+
**Files Generated:**
|
|
195
|
+
- `design-system-react-metrics.csv` - All component usage in design-system-react
|
|
196
|
+
- `design-system-react-native-metrics.csv` - All component usage in design-system-react-native
|
|
100
197
|
|
|
101
198
|
---
|
|
102
199
|
|
|
103
|
-
### **
|
|
200
|
+
### **Requirements**
|
|
104
201
|
|
|
105
|
-
|
|
202
|
+
- The tool **only counts components that are imported** from tracked sources:
|
|
203
|
+
- For Extension/Mobile: Local `/component-library` (deprecated) or configured NPM packages (current)
|
|
204
|
+
- For Design System packages: Any imports within the package
|
|
205
|
+
- Components **inside JSDoc comments** are not counted as usage
|
|
206
|
+
- **Test files** are automatically excluded (`*.test.{js,tsx}`)
|
|
207
|
+
- **Node.js** v14 or higher is required
|
|
106
208
|
|
|
107
209
|
---
|
|
108
210
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"projects": {
|
|
3
|
+
"test-project": {
|
|
4
|
+
"rootFolder": "__tests__/fixtures/test-project/ui",
|
|
5
|
+
"ignoreFolders": ["__tests__/fixtures/test-project/ui/components/component-library"],
|
|
6
|
+
"filePattern": "__tests__/fixtures/test-project/ui/**/*.{js,tsx}",
|
|
7
|
+
"outputFile": "__tests__/output/test-metrics.csv",
|
|
8
|
+
"currentPackages": ["@metamask/design-system-react"],
|
|
9
|
+
"deprecatedComponents": [
|
|
10
|
+
"Button",
|
|
11
|
+
"Icon",
|
|
12
|
+
"Modal",
|
|
13
|
+
"TextField",
|
|
14
|
+
"BannerAlert"
|
|
15
|
+
],
|
|
16
|
+
"currentComponents": [
|
|
17
|
+
"Button",
|
|
18
|
+
"Icon",
|
|
19
|
+
"Text",
|
|
20
|
+
"Box"
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button, Icon, Text, Box } from '@metamask/design-system-react';
|
|
3
|
+
|
|
4
|
+
export const CurrentPage = () => {
|
|
5
|
+
return (
|
|
6
|
+
<Box>
|
|
7
|
+
<Button>New Button</Button>
|
|
8
|
+
<Icon name="check" />
|
|
9
|
+
<Text>Welcome</Text>
|
|
10
|
+
<Button variant="primary">Submit</Button>
|
|
11
|
+
</Box>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button, Icon, Modal, TextField } from '../../components/component-library';
|
|
3
|
+
|
|
4
|
+
export const DeprecatedPage = () => {
|
|
5
|
+
return (
|
|
6
|
+
<div>
|
|
7
|
+
<Button>Old Button</Button>
|
|
8
|
+
<Icon name="check" />
|
|
9
|
+
<Modal>
|
|
10
|
+
<TextField label="Name" />
|
|
11
|
+
<Button type="submit">Submit</Button>
|
|
12
|
+
</Modal>
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Button as OldButton } from '../../components/component-library';
|
|
3
|
+
import { Button, Text } from '@metamask/design-system-react';
|
|
4
|
+
|
|
5
|
+
export const MixedPage = () => {
|
|
6
|
+
return (
|
|
7
|
+
<div>
|
|
8
|
+
<OldButton>Old Button</OldButton>
|
|
9
|
+
<Button>New Button</Button>
|
|
10
|
+
<Text>Some text</Text>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
};
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
const fs = require('fs').promises;
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
|
|
5
|
+
const TEST_CONFIG = path.join(__dirname, 'fixtures/test-config.json');
|
|
6
|
+
const OUTPUT_DIR = path.join(__dirname, 'output');
|
|
7
|
+
const DEPRECATED_OUTPUT = path.join(OUTPUT_DIR, 'test-metrics-deprecated.csv');
|
|
8
|
+
const CURRENT_OUTPUT = path.join(OUTPUT_DIR, 'test-metrics-current.csv');
|
|
9
|
+
|
|
10
|
+
describe('Design System Metrics', () => {
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
// Clean output directory before tests
|
|
13
|
+
try {
|
|
14
|
+
execSync(`rm -rf ${OUTPUT_DIR}/*`);
|
|
15
|
+
} catch (err) {
|
|
16
|
+
// Directory might not exist yet
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterAll(async () => {
|
|
21
|
+
// Clean up output files after tests
|
|
22
|
+
try {
|
|
23
|
+
await fs.rm(OUTPUT_DIR, { recursive: true, force: true });
|
|
24
|
+
} catch (err) {
|
|
25
|
+
// Ignore errors
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('Deprecated Component Tracking', () => {
|
|
30
|
+
test('should track deprecated components from local component-library', async () => {
|
|
31
|
+
execSync(
|
|
32
|
+
`yarn node index.js --project test-project --config ${TEST_CONFIG} --sources deprecated`,
|
|
33
|
+
{ stdio: 'pipe' }
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const content = await fs.readFile(DEPRECATED_OUTPUT, 'utf8');
|
|
37
|
+
const lines = content.trim().split('\n');
|
|
38
|
+
|
|
39
|
+
// Check header
|
|
40
|
+
expect(lines[0]).toBe('Component,Instances,File Paths');
|
|
41
|
+
|
|
42
|
+
// Check that deprecated components are tracked
|
|
43
|
+
expect(content).toContain('Button');
|
|
44
|
+
expect(content).toContain('Icon');
|
|
45
|
+
expect(content).toContain('Modal');
|
|
46
|
+
expect(content).toContain('TextField');
|
|
47
|
+
|
|
48
|
+
// Parse CSV and verify counts
|
|
49
|
+
const buttonLine = lines.find(line => line.startsWith('"Button"'));
|
|
50
|
+
expect(buttonLine).toBeDefined();
|
|
51
|
+
// Button appears 2 times in deprecated-page.js
|
|
52
|
+
expect(buttonLine).toContain(',2,');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('should only track components in deprecatedComponents list', async () => {
|
|
56
|
+
execSync(
|
|
57
|
+
`yarn node index.js --project test-project --config ${TEST_CONFIG} --sources deprecated`,
|
|
58
|
+
{ stdio: 'pipe' }
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
const content = await fs.readFile(DEPRECATED_OUTPUT, 'utf8');
|
|
62
|
+
|
|
63
|
+
// BannerAlert is in the list but not used, should still appear with 0 or not tracked
|
|
64
|
+
// Only used components should appear
|
|
65
|
+
expect(content).toContain('Button');
|
|
66
|
+
expect(content).toContain('Icon');
|
|
67
|
+
expect(content).toContain('Modal');
|
|
68
|
+
expect(content).toContain('TextField');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('Current Component Tracking', () => {
|
|
73
|
+
test('should track current components from NPM package', async () => {
|
|
74
|
+
execSync(
|
|
75
|
+
`yarn node index.js --project test-project --config ${TEST_CONFIG} --sources current`,
|
|
76
|
+
{ stdio: 'pipe' }
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const content = await fs.readFile(CURRENT_OUTPUT, 'utf8');
|
|
80
|
+
const lines = content.trim().split('\n');
|
|
81
|
+
|
|
82
|
+
// Check header
|
|
83
|
+
expect(lines[0]).toBe('Component,Instances,File Paths');
|
|
84
|
+
|
|
85
|
+
// Check that current components are tracked
|
|
86
|
+
expect(content).toContain('Button');
|
|
87
|
+
expect(content).toContain('Icon');
|
|
88
|
+
expect(content).toContain('Text');
|
|
89
|
+
expect(content).toContain('Box');
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('should count multiple instances correctly', async () => {
|
|
93
|
+
execSync(
|
|
94
|
+
`yarn node index.js --project test-project --config ${TEST_CONFIG} --sources current`,
|
|
95
|
+
{ stdio: 'pipe' }
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const content = await fs.readFile(CURRENT_OUTPUT, 'utf8');
|
|
99
|
+
const lines = content.trim().split('\n');
|
|
100
|
+
|
|
101
|
+
// Button appears 3 times total in current files (2 in current-page.js, 1 in mixed-page.js)
|
|
102
|
+
const buttonLine = lines.find(line => line.startsWith('"Button"'));
|
|
103
|
+
expect(buttonLine).toBeDefined();
|
|
104
|
+
expect(buttonLine).toContain(',3,');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('Source Separation', () => {
|
|
109
|
+
test('should generate both deprecated and current reports by default', async () => {
|
|
110
|
+
execSync(
|
|
111
|
+
`yarn node index.js --project test-project --config ${TEST_CONFIG}`,
|
|
112
|
+
{ stdio: 'pipe' }
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
// Both files should exist
|
|
116
|
+
const deprecatedExists = await fs
|
|
117
|
+
.access(DEPRECATED_OUTPUT)
|
|
118
|
+
.then(() => true)
|
|
119
|
+
.catch(() => false);
|
|
120
|
+
const currentExists = await fs
|
|
121
|
+
.access(CURRENT_OUTPUT)
|
|
122
|
+
.then(() => true)
|
|
123
|
+
.catch(() => false);
|
|
124
|
+
|
|
125
|
+
expect(deprecatedExists).toBe(true);
|
|
126
|
+
expect(currentExists).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test('should separate Button usage by source', async () => {
|
|
130
|
+
execSync(
|
|
131
|
+
`yarn node index.js --project test-project --config ${TEST_CONFIG}`,
|
|
132
|
+
{ stdio: 'pipe' }
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const deprecatedContent = await fs.readFile(DEPRECATED_OUTPUT, 'utf8');
|
|
136
|
+
const currentContent = await fs.readFile(CURRENT_OUTPUT, 'utf8');
|
|
137
|
+
|
|
138
|
+
// Deprecated Button count (2 in deprecated-page.js)
|
|
139
|
+
// Note: mixed-page.js uses "Button as OldButton" which doesn't match "Button" component name
|
|
140
|
+
const deprecatedLines = deprecatedContent.trim().split('\n');
|
|
141
|
+
const deprecatedButtonLine = deprecatedLines.find(line =>
|
|
142
|
+
line.startsWith('"Button"')
|
|
143
|
+
);
|
|
144
|
+
expect(deprecatedButtonLine).toContain(',2,'); // 2 in deprecated-page
|
|
145
|
+
|
|
146
|
+
// Current Button count (2 in current-page.js + 1 in mixed-page.js)
|
|
147
|
+
const currentLines = currentContent.trim().split('\n');
|
|
148
|
+
const currentButtonLine = currentLines.find(line =>
|
|
149
|
+
line.startsWith('"Button"')
|
|
150
|
+
);
|
|
151
|
+
expect(currentButtonLine).toContain(',3,'); // 2 in current-page + 1 in mixed-page
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe('File Path Tracking', () => {
|
|
156
|
+
test('should track which files contain each component', async () => {
|
|
157
|
+
execSync(
|
|
158
|
+
`yarn node index.js --project test-project --config ${TEST_CONFIG} --sources deprecated`,
|
|
159
|
+
{ stdio: 'pipe' }
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const content = await fs.readFile(DEPRECATED_OUTPUT, 'utf8');
|
|
163
|
+
|
|
164
|
+
// Button should reference the files where it's used
|
|
165
|
+
expect(content).toContain('deprecated-page.js');
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
describe('JSON Output Format', () => {
|
|
170
|
+
test('should generate JSON format when specified', async () => {
|
|
171
|
+
execSync(
|
|
172
|
+
`yarn node index.js --project test-project --config ${TEST_CONFIG} --sources deprecated --format json`,
|
|
173
|
+
{ stdio: 'pipe' }
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const jsonOutput = path.join(OUTPUT_DIR, 'test-metrics-deprecated.json');
|
|
177
|
+
const content = await fs.readFile(jsonOutput, 'utf8');
|
|
178
|
+
const data = JSON.parse(content);
|
|
179
|
+
|
|
180
|
+
// Should be valid JSON
|
|
181
|
+
expect(typeof data).toBe('object');
|
|
182
|
+
|
|
183
|
+
// Should have Button data
|
|
184
|
+
expect(data.Button).toBeDefined();
|
|
185
|
+
expect(data.Button.instances).toBeGreaterThan(0);
|
|
186
|
+
expect(Array.isArray(data.Button.files)).toBe(true);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
package/config.json
CHANGED
|
@@ -4,8 +4,9 @@
|
|
|
4
4
|
"rootFolder": "ui",
|
|
5
5
|
"ignoreFolders": ["ui/components/component-library"],
|
|
6
6
|
"filePattern": "ui/**/*.{js,tsx}",
|
|
7
|
-
"outputFile": "extension-component-
|
|
8
|
-
"
|
|
7
|
+
"outputFile": "extension-component-metrics.csv",
|
|
8
|
+
"currentPackages": ["@metamask/design-system-react"],
|
|
9
|
+
"deprecatedComponents": [
|
|
9
10
|
"AvatarAccount",
|
|
10
11
|
"AvatarBase",
|
|
11
12
|
"AvatarFavicon",
|
|
@@ -50,15 +51,47 @@
|
|
|
50
51
|
"Textarea",
|
|
51
52
|
"TextField",
|
|
52
53
|
"TextFieldSearch"
|
|
54
|
+
],
|
|
55
|
+
"currentComponents": [
|
|
56
|
+
"AvatarAccount",
|
|
57
|
+
"AvatarBase",
|
|
58
|
+
"AvatarFavicon",
|
|
59
|
+
"AvatarGroup",
|
|
60
|
+
"AvatarIcon",
|
|
61
|
+
"AvatarNetwork",
|
|
62
|
+
"AvatarToken",
|
|
63
|
+
"BadgeCount",
|
|
64
|
+
"BadgeIcon",
|
|
65
|
+
"BadgeNetwork",
|
|
66
|
+
"BadgeStatus",
|
|
67
|
+
"BadgeWrapper",
|
|
68
|
+
"Blockies",
|
|
69
|
+
"Box",
|
|
70
|
+
"Button",
|
|
71
|
+
"ButtonBase",
|
|
72
|
+
"ButtonIcon",
|
|
73
|
+
"ButtonHero",
|
|
74
|
+
"Checkbox",
|
|
75
|
+
"Icon",
|
|
76
|
+
"Jazzicon",
|
|
77
|
+
"Maskicon",
|
|
78
|
+
"Text",
|
|
79
|
+
"TextButton"
|
|
53
80
|
]
|
|
54
81
|
},
|
|
55
82
|
"mobile": {
|
|
56
83
|
"rootFolder": "app",
|
|
57
84
|
"ignoreFolders": ["app/component-library"],
|
|
58
85
|
"filePattern": "app/components/**/*.{js,jsx,ts,tsx}",
|
|
59
|
-
"outputFile": "mobile-component-
|
|
60
|
-
"
|
|
86
|
+
"outputFile": "mobile-component-metrics.csv",
|
|
87
|
+
"currentPackages": ["@metamask/design-system-react-native"],
|
|
88
|
+
"deprecatedComponents": [
|
|
61
89
|
"Accordion",
|
|
90
|
+
"AccountBalance",
|
|
91
|
+
"AccountBase",
|
|
92
|
+
"AccountCell",
|
|
93
|
+
"ActionListItem",
|
|
94
|
+
"AggregatedPercentage",
|
|
62
95
|
"Avatar",
|
|
63
96
|
"AvatarAccount",
|
|
64
97
|
"AvatarBase",
|
|
@@ -81,43 +114,105 @@
|
|
|
81
114
|
"BottomSheetHeader",
|
|
82
115
|
"Button",
|
|
83
116
|
"ButtonBase",
|
|
117
|
+
"ButtonFilter",
|
|
118
|
+
"ButtonHero",
|
|
84
119
|
"ButtonIcon",
|
|
85
120
|
"ButtonLink",
|
|
121
|
+
"ButtonPill",
|
|
86
122
|
"ButtonPrimary",
|
|
87
123
|
"ButtonSecondary",
|
|
124
|
+
"ButtonSemantic",
|
|
125
|
+
"ButtonToggle",
|
|
88
126
|
"Card",
|
|
89
127
|
"Cell",
|
|
90
128
|
"CellBase",
|
|
91
129
|
"CellDisplay",
|
|
92
130
|
"CellMultiSelect",
|
|
93
131
|
"CellSelect",
|
|
132
|
+
"CellSelectWithMenu",
|
|
94
133
|
"Checkbox",
|
|
134
|
+
"ConditionalScrollView",
|
|
135
|
+
"ContractBox",
|
|
136
|
+
"ContractBoxBase",
|
|
137
|
+
"CustomInput",
|
|
138
|
+
"CustomSpendCap",
|
|
95
139
|
"Header",
|
|
140
|
+
"HeaderCenter",
|
|
141
|
+
"HeaderWithTitleLeft",
|
|
142
|
+
"HeaderWithTitleLeftScrollable",
|
|
96
143
|
"HelpText",
|
|
97
144
|
"Icon",
|
|
98
145
|
"Input",
|
|
146
|
+
"KeyValueRow",
|
|
99
147
|
"Label",
|
|
100
148
|
"ListItem",
|
|
101
149
|
"ListItemColumn",
|
|
150
|
+
"ListItemMultiSelectButton",
|
|
151
|
+
"ListItemMultiSelectWithMenuButton",
|
|
152
|
+
"Loader",
|
|
153
|
+
"MainActionButton",
|
|
102
154
|
"ModalConfirmation",
|
|
103
|
-
"
|
|
155
|
+
"ModalMandatory",
|
|
156
|
+
"MultichainAccountSelectorList",
|
|
157
|
+
"MultichainAddressRow",
|
|
158
|
+
"MultichainAddressRowsList",
|
|
159
|
+
"MultichainAddWalletActions",
|
|
104
160
|
"MultiSelectItem",
|
|
105
161
|
"Overlay",
|
|
162
|
+
"PercentageChange",
|
|
106
163
|
"PickerAccount",
|
|
107
164
|
"PickerBase",
|
|
108
165
|
"PickerNetwork",
|
|
166
|
+
"QuickActionButton",
|
|
167
|
+
"QuickActionButtons",
|
|
168
|
+
"SegmentedControl",
|
|
109
169
|
"SelectItem",
|
|
170
|
+
"SheetActions",
|
|
110
171
|
"SheetBottom",
|
|
111
172
|
"SheetHeader",
|
|
173
|
+
"Tab",
|
|
112
174
|
"TabBar",
|
|
113
175
|
"TabBarItem",
|
|
176
|
+
"TabEmptyState",
|
|
177
|
+
"Tabs",
|
|
178
|
+
"TabsBar",
|
|
179
|
+
"TabsList",
|
|
114
180
|
"Tag",
|
|
181
|
+
"TagColored",
|
|
115
182
|
"TagUrl",
|
|
116
183
|
"Text",
|
|
117
184
|
"TextField",
|
|
118
185
|
"TextFieldSearch",
|
|
119
186
|
"TextWithPrefixIcon",
|
|
187
|
+
"TitleLeft",
|
|
120
188
|
"Toast"
|
|
189
|
+
],
|
|
190
|
+
"currentComponents": [
|
|
191
|
+
"AvatarAccount",
|
|
192
|
+
"AvatarBase",
|
|
193
|
+
"AvatarFavicon",
|
|
194
|
+
"AvatarGroup",
|
|
195
|
+
"AvatarIcon",
|
|
196
|
+
"AvatarNetwork",
|
|
197
|
+
"AvatarToken",
|
|
198
|
+
"BadgeCount",
|
|
199
|
+
"BadgeIcon",
|
|
200
|
+
"BadgeNetwork",
|
|
201
|
+
"BadgeStatus",
|
|
202
|
+
"BadgeWrapper",
|
|
203
|
+
"Blockies",
|
|
204
|
+
"Box",
|
|
205
|
+
"ButtonAnimated",
|
|
206
|
+
"ButtonBase",
|
|
207
|
+
"Button",
|
|
208
|
+
"ButtonIcon",
|
|
209
|
+
"Checkbox",
|
|
210
|
+
"Icon",
|
|
211
|
+
"Jazzicon",
|
|
212
|
+
"Maskicon",
|
|
213
|
+
"TextButton",
|
|
214
|
+
"Text",
|
|
215
|
+
"TextOrChildren"
|
|
121
216
|
]
|
|
122
217
|
}
|
|
123
218
|
}
|
package/index.js
CHANGED
|
@@ -8,14 +8,12 @@ const traverse = require("@babel/traverse").default;
|
|
|
8
8
|
const { program } = require("commander");
|
|
9
9
|
const chalk = require("chalk");
|
|
10
10
|
|
|
11
|
-
// Load configuration
|
|
12
|
-
const CONFIG_PATH = path.join(__dirname, "config.json");
|
|
13
11
|
let config;
|
|
14
12
|
|
|
15
13
|
// Function to load and parse the configuration file
|
|
16
|
-
const loadConfig = async () => {
|
|
14
|
+
const loadConfig = async (configPath) => {
|
|
17
15
|
try {
|
|
18
|
-
const configContent = await fs.readFile(
|
|
16
|
+
const configContent = await fs.readFile(configPath, "utf8");
|
|
19
17
|
config = JSON.parse(configContent);
|
|
20
18
|
} catch (err) {
|
|
21
19
|
console.error(
|
|
@@ -27,23 +25,56 @@ const loadConfig = async () => {
|
|
|
27
25
|
|
|
28
26
|
// Define CLI options using Commander
|
|
29
27
|
program
|
|
30
|
-
.version("
|
|
31
|
-
.description("Design System Metrics CLI Tool")
|
|
28
|
+
.version("2.0.0")
|
|
29
|
+
.description("Design System Metrics CLI Tool - Track component usage from multiple sources")
|
|
32
30
|
.requiredOption(
|
|
33
31
|
"-p, --project <name>",
|
|
34
32
|
"Specify the project to audit (e.g., extension, mobile)"
|
|
35
33
|
)
|
|
36
34
|
.option("-f, --format <type>", "Output format (csv, json)", "csv")
|
|
35
|
+
.option(
|
|
36
|
+
"-s, --sources <types>",
|
|
37
|
+
"Comma-separated list of sources to track (deprecated, current, all)",
|
|
38
|
+
"all"
|
|
39
|
+
)
|
|
40
|
+
.option(
|
|
41
|
+
"-c, --config <path>",
|
|
42
|
+
"Path to custom config file",
|
|
43
|
+
path.join(__dirname, "config.json")
|
|
44
|
+
)
|
|
37
45
|
.parse(process.argv);
|
|
38
46
|
|
|
39
47
|
const options = program.opts();
|
|
40
48
|
|
|
41
49
|
// Initialize component instances and file mappings
|
|
42
|
-
|
|
43
|
-
let
|
|
50
|
+
// Structure: Map<componentName, Map<source, { count, files }>>
|
|
51
|
+
let componentMetrics = new Map();
|
|
52
|
+
|
|
53
|
+
// Helper function to track component usage by source
|
|
54
|
+
const trackComponent = (componentName, source, filePath) => {
|
|
55
|
+
if (!componentMetrics.has(componentName)) {
|
|
56
|
+
componentMetrics.set(componentName, new Map());
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const sourceMetrics = componentMetrics.get(componentName);
|
|
60
|
+
if (!sourceMetrics.has(source)) {
|
|
61
|
+
sourceMetrics.set(source, { count: 0, files: [] });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const metrics = sourceMetrics.get(source);
|
|
65
|
+
metrics.count++;
|
|
66
|
+
metrics.files.push(filePath);
|
|
67
|
+
};
|
|
44
68
|
|
|
45
69
|
// Function to process a single file
|
|
46
|
-
const processFile = async (
|
|
70
|
+
const processFile = async (
|
|
71
|
+
filePath,
|
|
72
|
+
deprecatedComponentsSet,
|
|
73
|
+
currentComponentsSet,
|
|
74
|
+
currentPackages
|
|
75
|
+
) => {
|
|
76
|
+
// Track imports by source: Map<componentName, source>
|
|
77
|
+
const componentImports = new Map();
|
|
47
78
|
try {
|
|
48
79
|
const content = await fs.readFile(filePath, "utf8");
|
|
49
80
|
|
|
@@ -51,23 +82,61 @@ const processFile = async (filePath, componentsSet, importedComponentsSet) => {
|
|
|
51
82
|
const ast = babelParser.parse(content, {
|
|
52
83
|
sourceType: "module",
|
|
53
84
|
plugins: ["jsx", "typescript"],
|
|
85
|
+
attachComment: true, // Enable comment attachment for JSDoc parsing
|
|
54
86
|
});
|
|
55
87
|
|
|
56
|
-
// Traverse the AST to find import declarations from
|
|
88
|
+
// Traverse the AST to find import declarations from multiple sources
|
|
57
89
|
traverse(ast, {
|
|
58
90
|
ImportDeclaration({ node }) {
|
|
59
91
|
const importPath = node.source.value;
|
|
60
92
|
console.log(`Import path detected: ${importPath}`);
|
|
93
|
+
|
|
94
|
+
let source = null;
|
|
95
|
+
|
|
96
|
+
// Check if it's from local component library (DEPRECATED)
|
|
61
97
|
if (importPath.includes("/component-library")) {
|
|
98
|
+
source = "deprecated";
|
|
99
|
+
}
|
|
100
|
+
// Check if it's from current NPM packages
|
|
101
|
+
else if (currentPackages && currentPackages.length > 0) {
|
|
102
|
+
for (const pkg of currentPackages) {
|
|
103
|
+
if (importPath === pkg || importPath.startsWith(`${pkg}/`)) {
|
|
104
|
+
source = "current";
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// If we found a relevant import source, track the components
|
|
111
|
+
if (source) {
|
|
62
112
|
node.specifiers.forEach((specifier) => {
|
|
113
|
+
let componentName = null;
|
|
114
|
+
|
|
63
115
|
if (specifier.type === "ImportDefaultSpecifier") {
|
|
64
|
-
|
|
116
|
+
componentName = specifier.local.name;
|
|
65
117
|
console.log(
|
|
66
|
-
`Default imported component: ${
|
|
118
|
+
`Default imported component: ${componentName} (${source})`
|
|
67
119
|
);
|
|
68
120
|
} else if (specifier.type === "ImportSpecifier") {
|
|
69
|
-
|
|
70
|
-
console.log(`Named imported component: ${
|
|
121
|
+
componentName = specifier.local.name;
|
|
122
|
+
console.log(`Named imported component: ${componentName} (${source})`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (componentName) {
|
|
126
|
+
// Check if component is in deprecated list (for local imports)
|
|
127
|
+
if (
|
|
128
|
+
source === "deprecated" &&
|
|
129
|
+
deprecatedComponentsSet.has(componentName)
|
|
130
|
+
) {
|
|
131
|
+
componentImports.set(componentName, source);
|
|
132
|
+
}
|
|
133
|
+
// Check if component is in current list (for NPM imports)
|
|
134
|
+
else if (
|
|
135
|
+
source === "current" &&
|
|
136
|
+
currentComponentsSet.has(componentName)
|
|
137
|
+
) {
|
|
138
|
+
componentImports.set(componentName, source);
|
|
139
|
+
}
|
|
71
140
|
}
|
|
72
141
|
});
|
|
73
142
|
}
|
|
@@ -99,19 +168,13 @@ const processFile = async (filePath, componentsSet, importedComponentsSet) => {
|
|
|
99
168
|
|
|
100
169
|
console.log(`JSX component detected: ${componentName}`);
|
|
101
170
|
|
|
102
|
-
if
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const count = componentInstances.get(componentName) || 0;
|
|
107
|
-
componentInstances.set(componentName, count + 1);
|
|
108
|
-
|
|
109
|
-
const files = componentFiles.get(componentName) || [];
|
|
110
|
-
files.push(filePath);
|
|
111
|
-
componentFiles.set(componentName, files);
|
|
171
|
+
// Check if this component was imported and track its usage
|
|
172
|
+
if (componentImports.has(componentName)) {
|
|
173
|
+
const source = componentImports.get(componentName);
|
|
174
|
+
trackComponent(componentName, source, filePath);
|
|
112
175
|
|
|
113
176
|
console.log(
|
|
114
|
-
`Matched JSX component: ${componentName},
|
|
177
|
+
`Matched JSX component: ${componentName}, Source: ${source}`
|
|
115
178
|
);
|
|
116
179
|
}
|
|
117
180
|
}
|
|
@@ -126,7 +189,7 @@ const processFile = async (filePath, componentsSet, importedComponentsSet) => {
|
|
|
126
189
|
|
|
127
190
|
// Main function to coordinate the audit
|
|
128
191
|
const main = async () => {
|
|
129
|
-
await loadConfig();
|
|
192
|
+
await loadConfig(options.config);
|
|
130
193
|
|
|
131
194
|
const projectName = options.project;
|
|
132
195
|
const projectConfig = config.projects[projectName];
|
|
@@ -140,10 +203,18 @@ const main = async () => {
|
|
|
140
203
|
process.exit(1);
|
|
141
204
|
}
|
|
142
205
|
|
|
143
|
-
const {
|
|
144
|
-
|
|
206
|
+
const {
|
|
207
|
+
rootFolder,
|
|
208
|
+
ignoreFolders,
|
|
209
|
+
filePattern,
|
|
210
|
+
outputFile,
|
|
211
|
+
deprecatedComponents = [],
|
|
212
|
+
currentComponents = [],
|
|
213
|
+
currentPackages = [],
|
|
214
|
+
} = projectConfig;
|
|
145
215
|
|
|
146
|
-
const
|
|
216
|
+
const deprecatedComponentsSet = new Set(deprecatedComponents);
|
|
217
|
+
const currentComponentsSet = new Set(currentComponents);
|
|
147
218
|
|
|
148
219
|
console.log(chalk.blue(`\nStarting audit for project: ${projectName}\n`));
|
|
149
220
|
|
|
@@ -162,44 +233,96 @@ const main = async () => {
|
|
|
162
233
|
|
|
163
234
|
// Process files concurrently
|
|
164
235
|
await Promise.all(
|
|
165
|
-
files.map((file) =>
|
|
236
|
+
files.map((file) =>
|
|
237
|
+
processFile(
|
|
238
|
+
file,
|
|
239
|
+
deprecatedComponentsSet,
|
|
240
|
+
currentComponentsSet,
|
|
241
|
+
currentPackages
|
|
242
|
+
)
|
|
243
|
+
)
|
|
166
244
|
);
|
|
167
245
|
|
|
168
|
-
console.log(chalk.green("\nComponent
|
|
246
|
+
console.log(chalk.green("\nComponent Migration Metrics:"));
|
|
169
247
|
|
|
170
|
-
|
|
171
|
-
let
|
|
248
|
+
// Parse sources filter from CLI
|
|
249
|
+
let requestedSources = ["deprecated", "current"];
|
|
250
|
+
if (options.sources && options.sources !== "all") {
|
|
251
|
+
requestedSources = options.sources.split(",").map((s) => s.trim());
|
|
252
|
+
}
|
|
172
253
|
|
|
173
|
-
|
|
174
|
-
const instanceCount = componentInstances.get(componentName) || 0;
|
|
175
|
-
const filePaths = componentFiles.get(componentName) || [];
|
|
176
|
-
console.log(`${chalk.cyan(componentName)}: ${instanceCount}`);
|
|
254
|
+
const sourceTypes = requestedSources;
|
|
177
255
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
256
|
+
for (const sourceType of sourceTypes) {
|
|
257
|
+
// Filter component metrics for this source type
|
|
258
|
+
const sourceMetrics = new Map();
|
|
259
|
+
|
|
260
|
+
// Determine which component set to use based on source type
|
|
261
|
+
const componentSet =
|
|
262
|
+
sourceType === "deprecated"
|
|
263
|
+
? deprecatedComponentsSet
|
|
264
|
+
: currentComponentsSet;
|
|
265
|
+
|
|
266
|
+
componentSet.forEach((componentName) => {
|
|
267
|
+
const componentSources = componentMetrics.get(componentName);
|
|
268
|
+
if (componentSources) {
|
|
269
|
+
const metrics = componentSources.get(sourceType);
|
|
270
|
+
if (metrics) {
|
|
271
|
+
sourceMetrics.set(componentName, metrics);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Skip if no metrics for this source
|
|
277
|
+
if (sourceMetrics.size === 0) {
|
|
278
|
+
console.log(
|
|
279
|
+
chalk.yellow(
|
|
280
|
+
`No ${sourceType} components found, skipping report.`
|
|
281
|
+
)
|
|
282
|
+
);
|
|
283
|
+
continue;
|
|
187
284
|
}
|
|
188
|
-
});
|
|
189
285
|
|
|
190
|
-
|
|
191
|
-
|
|
286
|
+
// Generate output file name
|
|
287
|
+
const baseFileName = outputFile.replace(/\.(csv|json)$/, "");
|
|
288
|
+
const sourceOutputFile = `${baseFileName}-${sourceType}.${options.format.toLowerCase()}`;
|
|
289
|
+
|
|
290
|
+
console.log(
|
|
291
|
+
chalk.blue(
|
|
292
|
+
`\n${sourceType.toUpperCase()} Components (${sourceType === "deprecated" ? "Local component-library" : currentPackages.join(", ")}):`
|
|
293
|
+
)
|
|
294
|
+
);
|
|
295
|
+
|
|
192
296
|
if (options.format.toLowerCase() === "json") {
|
|
193
|
-
|
|
297
|
+
const jsonOutput = {};
|
|
298
|
+
sourceMetrics.forEach((metrics, componentName) => {
|
|
299
|
+
console.log(`${chalk.cyan(componentName)}: ${metrics.count}`);
|
|
300
|
+
jsonOutput[componentName] = {
|
|
301
|
+
instances: metrics.count,
|
|
302
|
+
files: metrics.files,
|
|
303
|
+
};
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
await fs.writeFile(
|
|
307
|
+
sourceOutputFile,
|
|
308
|
+
JSON.stringify(jsonOutput, null, 2)
|
|
309
|
+
);
|
|
194
310
|
} else {
|
|
195
|
-
|
|
311
|
+
// CSV format
|
|
312
|
+
let csvContent = "Component,Instances,File Paths\n";
|
|
313
|
+
|
|
314
|
+
sourceMetrics.forEach((metrics, componentName) => {
|
|
315
|
+
console.log(`${chalk.cyan(componentName)}: ${metrics.count}`);
|
|
316
|
+
csvContent += `"${componentName}",${metrics.count},"${metrics.files.join(", ")}"\n`;
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
await fs.writeFile(sourceOutputFile, csvContent);
|
|
196
320
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
);
|
|
200
|
-
} catch (err) {
|
|
201
|
-
console.error(chalk.red(`Error writing to file: ${err.message}`));
|
|
321
|
+
|
|
322
|
+
console.log(chalk.green(`Metrics written to ${sourceOutputFile}`));
|
|
202
323
|
}
|
|
324
|
+
|
|
325
|
+
console.log(chalk.green("\n✓ All reports generated successfully!\n"));
|
|
203
326
|
} catch (err) {
|
|
204
327
|
console.error(chalk.red(`Error reading files: ${err.message}`));
|
|
205
328
|
}
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
testEnvironment: 'node',
|
|
3
|
+
testMatch: ['**/__tests__/**/*.test.js'],
|
|
4
|
+
testPathIgnorePatterns: [
|
|
5
|
+
'/node_modules/',
|
|
6
|
+
'/__tests__/fixtures/',
|
|
7
|
+
'/__tests__/output/',
|
|
8
|
+
],
|
|
9
|
+
coveragePathIgnorePatterns: [
|
|
10
|
+
'/node_modules/',
|
|
11
|
+
'/__tests__/fixtures/',
|
|
12
|
+
'/__tests__/output/',
|
|
13
|
+
],
|
|
14
|
+
};
|
package/package.json
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@georgewrmarshall/design-system-metrics",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A CLI tool to audit design system component usage across
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "A CLI tool to audit design system component usage from local libraries, NPM packages, and track deprecated components across MetaMask codebases",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"packageManager": "yarn@4.3.1",
|
|
7
7
|
"bin": "./index.js",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"start": "node index.js --project extension"
|
|
9
|
+
"start": "node index.js --project extension",
|
|
10
|
+
"test": "jest",
|
|
11
|
+
"test:watch": "jest --watch"
|
|
10
12
|
},
|
|
11
13
|
"keywords": [
|
|
12
14
|
"cli",
|
|
@@ -28,4 +30,4 @@
|
|
|
28
30
|
"devDependencies": {
|
|
29
31
|
"jest": "^29.7.0"
|
|
30
32
|
}
|
|
31
|
-
}
|
|
33
|
+
}
|