@zeroheight/adoption-cli 2.0.1 → 2.1.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/CHANGELOG.md +8 -0
- package/README.md +16 -4
- package/dist/ast/analyze.d.ts +21 -0
- package/dist/ast/analyze.js +90 -0
- package/dist/cli.js +1 -1
- package/dist/commands/analyze.utils.d.ts +2 -0
- package/dist/common/api.d.ts +4 -2
- package/dist/common/api.js +24 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## [2.1.0](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/2.1.0) - 8th November 2024
|
|
4
|
+
|
|
5
|
+
- Start collecting component property usage data as part of component analyzing
|
|
6
|
+
|
|
7
|
+
## [2.0.2](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/2.0.2) - 29th October 2024
|
|
8
|
+
|
|
9
|
+
- Update readme
|
|
10
|
+
|
|
3
11
|
## [2.0.1](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/2.0.1) - 21st October 2024
|
|
4
12
|
|
|
5
13
|
- Update help text and link
|
package/README.md
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
CLI for measuring component usage to view in your [zeroheight](https://zeroheight.com/) account.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**For full walk through on how to use this CLI tool, head over to our [help centre article](https://zeroheight.com/help/article/adoption-private-beta-overview/).**
|
|
6
|
+
|
|
7
|
+
[Release notes](https://www.npmjs.com/package/@zeroheight/adoption-cli?activeTab=code)
|
|
6
8
|
|
|
7
9
|
## Install
|
|
8
10
|
|
|
@@ -37,7 +39,9 @@ export ZEROHEIGHT_ACCESS_TOKEN="your-access-token"
|
|
|
37
39
|
|
|
38
40
|
### Component usage analysis
|
|
39
41
|
|
|
40
|
-
|
|
42
|
+
[Help center article](https://zeroheight.com/help/article/component-usage/)
|
|
43
|
+
|
|
44
|
+
In the React repository in which you wish to get usage metrics around how components from your design system packages are being used, run the following command:
|
|
41
45
|
|
|
42
46
|
```bash
|
|
43
47
|
zh-adoption analyze
|
|
@@ -81,7 +85,11 @@ zh-adoption analyze --interactive false -r "My Repo"
|
|
|
81
85
|
|
|
82
86
|
### Monitor repo
|
|
83
87
|
|
|
84
|
-
|
|
88
|
+
[Help center article](https://zeroheight.com/help/article/package-version-monitoring/)
|
|
89
|
+
|
|
90
|
+
This will analyze your code repository to help you understand what packages are being used and at what version.
|
|
91
|
+
|
|
92
|
+
In the repository in which you wish to monitor, run the following command:
|
|
85
93
|
|
|
86
94
|
```bash
|
|
87
95
|
zh-adoption monitor-repo
|
|
@@ -101,7 +109,11 @@ zh-adoption monitor-repo -d ./webApp
|
|
|
101
109
|
|
|
102
110
|
### Track package
|
|
103
111
|
|
|
104
|
-
|
|
112
|
+
[Help center article](https://zeroheight.com/help/article/package-version-monitoring/)
|
|
113
|
+
|
|
114
|
+
This will give zeroheight the name and current version of your design system package so you can compare it to the version being used by your other code repositories.
|
|
115
|
+
|
|
116
|
+
In the repository containing your design system package, run the following command:
|
|
105
117
|
|
|
106
118
|
```bash
|
|
107
119
|
zh-adoption track-package
|
package/dist/ast/analyze.d.ts
CHANGED
|
@@ -1,18 +1,39 @@
|
|
|
1
1
|
import type { Node, Program, Identifier } from "acorn";
|
|
2
|
+
type AttrValueType = "JSXExpressionContainer" | "NumericLiteral" | "StringLiteral" | "BooleanLiteral" | "Identifier" | "TemplateLiteral" | "ArrowFunctionExpression" | "StaticMemberExpression" | "CallExpression" | "TSAsExpression" | "ObjectExpression" | "ArrayExpression" | "BinaryExpression" | undefined;
|
|
2
3
|
export interface PlainIdentifier extends Node {
|
|
3
4
|
type: "Identifier";
|
|
4
5
|
local: Identifier;
|
|
5
6
|
}
|
|
7
|
+
export interface Props {
|
|
8
|
+
name: string;
|
|
9
|
+
type?: AttrValueType[];
|
|
10
|
+
values: [string | number | typeof RUNTIME_VALUE | null | undefined];
|
|
11
|
+
}
|
|
6
12
|
export type VisitorState = {
|
|
7
13
|
components: Map<string, number>;
|
|
14
|
+
props: Map<string, Props[]>;
|
|
8
15
|
imports: Map<string, {
|
|
9
16
|
package: string;
|
|
10
17
|
name: string;
|
|
11
18
|
}>;
|
|
12
19
|
};
|
|
20
|
+
type PropValues = string | number | null | undefined;
|
|
21
|
+
export type ComponentProps = {
|
|
22
|
+
[propName: string]: {
|
|
23
|
+
type?: AttrValueType[];
|
|
24
|
+
count?: number;
|
|
25
|
+
values: PropValues[];
|
|
26
|
+
};
|
|
27
|
+
};
|
|
13
28
|
export type RawUsage = {
|
|
14
29
|
name: string;
|
|
15
30
|
count: number;
|
|
16
31
|
package: string;
|
|
32
|
+
props: ComponentProps;
|
|
17
33
|
};
|
|
34
|
+
declare const RUNTIME_VALUE = "zh-runtime-value";
|
|
35
|
+
export declare const ARRAY_VALUE = "zh-array-value";
|
|
36
|
+
export declare const BINARY_VALUE = "zh-binary-value";
|
|
37
|
+
export declare const OBJECT_VALUE = "zh-object-value";
|
|
18
38
|
export declare function analyze(ast: Program): RawUsage[];
|
|
39
|
+
export {};
|
package/dist/ast/analyze.js
CHANGED
|
@@ -1,4 +1,16 @@
|
|
|
1
1
|
import * as walk from "acorn-walk";
|
|
2
|
+
const DYNAMIC_EXPRESSIONS = [
|
|
3
|
+
"Identifier",
|
|
4
|
+
"TemplateLiteral",
|
|
5
|
+
"ArrowFunctionExpression",
|
|
6
|
+
"StaticMemberExpression",
|
|
7
|
+
"CallExpression",
|
|
8
|
+
"TSAsExpression",
|
|
9
|
+
];
|
|
10
|
+
const RUNTIME_VALUE = "zh-runtime-value";
|
|
11
|
+
export const ARRAY_VALUE = "zh-array-value";
|
|
12
|
+
export const BINARY_VALUE = "zh-binary-value";
|
|
13
|
+
export const OBJECT_VALUE = "zh-object-value";
|
|
2
14
|
function transformVisitorStateToRawUsage(visitorState) {
|
|
3
15
|
const transformedUsageMap = new Map();
|
|
4
16
|
for (const [name, count] of visitorState.components) {
|
|
@@ -10,10 +22,30 @@ function transformVisitorStateToRawUsage(visitorState) {
|
|
|
10
22
|
name: actualName,
|
|
11
23
|
count: 0,
|
|
12
24
|
package: importInfo?.package ?? "",
|
|
25
|
+
props: {},
|
|
13
26
|
};
|
|
14
27
|
transformedUsageMap.set(key, {
|
|
15
28
|
...currentValue,
|
|
16
29
|
count: currentValue.count + count,
|
|
30
|
+
props: {},
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
for (const [name, props] of visitorState.props) {
|
|
34
|
+
const importInfo = visitorState.imports.get(name);
|
|
35
|
+
const actualName = importInfo?.name ?? name;
|
|
36
|
+
const packageName = importInfo?.package ?? "";
|
|
37
|
+
const key = `package:${packageName} name:${actualName}`;
|
|
38
|
+
const currentValue = transformedUsageMap.get(key);
|
|
39
|
+
const componentProps = currentValue.props;
|
|
40
|
+
props.forEach((prop) => {
|
|
41
|
+
componentProps[prop.name] = {
|
|
42
|
+
type: Array.from(new Set([...(componentProps[prop.name]?.type ?? []), ...(prop.type ?? [])])),
|
|
43
|
+
values: [...(componentProps[prop.name]?.values ?? []), ...prop.values],
|
|
44
|
+
};
|
|
45
|
+
});
|
|
46
|
+
transformedUsageMap.set(key, {
|
|
47
|
+
...currentValue,
|
|
48
|
+
props: componentProps,
|
|
17
49
|
});
|
|
18
50
|
}
|
|
19
51
|
return Array.from(transformedUsageMap.values());
|
|
@@ -21,6 +53,7 @@ function transformVisitorStateToRawUsage(visitorState) {
|
|
|
21
53
|
export function analyze(ast) {
|
|
22
54
|
const visitorState = {
|
|
23
55
|
components: new Map(),
|
|
56
|
+
props: new Map(),
|
|
24
57
|
imports: new Map(),
|
|
25
58
|
};
|
|
26
59
|
const visitorFunctions = {
|
|
@@ -84,6 +117,63 @@ export function analyze(ast) {
|
|
|
84
117
|
const elName = el.name.name;
|
|
85
118
|
// Ignore html tags e.g. div, h1, span
|
|
86
119
|
if (elName?.[0] !== elName?.[0]?.toLocaleLowerCase()) {
|
|
120
|
+
if (el.attributes.length > 0) {
|
|
121
|
+
const attrProps = [];
|
|
122
|
+
el.attributes.forEach((attr) => {
|
|
123
|
+
// We won't have a name if it is a JSXSpreadAttribute
|
|
124
|
+
if (attr.name) {
|
|
125
|
+
if (attr.value?.type === "JSXExpressionContainer") {
|
|
126
|
+
const expressionType = attr.value.expression.type;
|
|
127
|
+
// These are props determined at runtime so we cannot parse them
|
|
128
|
+
if (expressionType &&
|
|
129
|
+
DYNAMIC_EXPRESSIONS.includes(expressionType)) {
|
|
130
|
+
attrProps.push({
|
|
131
|
+
name: attr.name.name,
|
|
132
|
+
type: [expressionType],
|
|
133
|
+
values: [RUNTIME_VALUE],
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
else if (expressionType && expressionType === 'ObjectExpression') {
|
|
137
|
+
attrProps.push({
|
|
138
|
+
name: attr.name.name,
|
|
139
|
+
type: [expressionType],
|
|
140
|
+
values: [OBJECT_VALUE],
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
else if (expressionType && expressionType === 'ArrayExpression') {
|
|
144
|
+
attrProps.push({
|
|
145
|
+
name: attr.name.name,
|
|
146
|
+
type: [expressionType],
|
|
147
|
+
values: [ARRAY_VALUE],
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
else if (expressionType && expressionType === 'BinaryExpression') {
|
|
151
|
+
attrProps.push({
|
|
152
|
+
name: attr.name.name,
|
|
153
|
+
type: [expressionType],
|
|
154
|
+
values: [BINARY_VALUE],
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
attrProps.push({
|
|
159
|
+
name: attr.name.name,
|
|
160
|
+
type: [expressionType],
|
|
161
|
+
values: [attr.value.expression.value || attr.value.expression.name],
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
attrProps.push({
|
|
167
|
+
name: attr.name.name,
|
|
168
|
+
type: [attr.value?.type],
|
|
169
|
+
values: [attr.value?.value],
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
const existingProps = state.props.get(elName) ?? [];
|
|
175
|
+
state.props.set(elName, [...attrProps, ...existingProps]);
|
|
176
|
+
}
|
|
87
177
|
state.components.set(elName, (state.components.get(elName) ?? 0) + 1);
|
|
88
178
|
}
|
|
89
179
|
node.children.forEach((child) => {
|
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ const { output, cleanup } = render(React.createElement(HelpInfo, null));
|
|
|
12
12
|
program
|
|
13
13
|
.name("zh-adoption")
|
|
14
14
|
.description("CLI for measuring design system usage usage in your products")
|
|
15
|
-
.version("2.0
|
|
15
|
+
.version("2.1.0")
|
|
16
16
|
.addHelpText("before", output)
|
|
17
17
|
.addCommand(analyzeCommand())
|
|
18
18
|
.addCommand(authCommand())
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { RawUsageMap } from "./analyze.js";
|
|
2
|
+
import { ComponentProps } from "../ast/analyze.js";
|
|
2
3
|
export interface ComponentUsageRecord {
|
|
3
4
|
name: string;
|
|
4
5
|
files: string[];
|
|
5
6
|
count: number;
|
|
6
7
|
package: string;
|
|
8
|
+
props: ComponentProps;
|
|
7
9
|
}
|
|
8
10
|
/**
|
|
9
11
|
* Get a list of files matching extensions, skips hidden files and node_modules
|
package/dist/common/api.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ComponentProps } from "../ast/analyze.js";
|
|
1
2
|
import { RawUsageMap } from "../commands/analyze.js";
|
|
2
3
|
import { Credentials } from "./config.js";
|
|
3
4
|
export declare enum ResponseStatus {
|
|
@@ -73,10 +74,10 @@ interface ComponentUsageDetailsSuccess {
|
|
|
73
74
|
};
|
|
74
75
|
}
|
|
75
76
|
export declare function submitUsageData(usage: RawUsageMap, repoName: string, credentials: Credentials): Promise<APIResponse<ComponentUsageDetailsSuccess>>;
|
|
76
|
-
interface
|
|
77
|
+
interface RepoNamesSuccess {
|
|
77
78
|
repo_names: string[];
|
|
78
79
|
}
|
|
79
|
-
export declare function getExistingRepoNames(credentials: Credentials): Promise<APIResponse<
|
|
80
|
+
export declare function getExistingRepoNames(credentials: Credentials): Promise<APIResponse<RepoNamesSuccess>>;
|
|
80
81
|
interface AuthTokensResponse {
|
|
81
82
|
scopes: string[];
|
|
82
83
|
email: string;
|
|
@@ -90,4 +91,5 @@ interface AuthTokensResponse {
|
|
|
90
91
|
};
|
|
91
92
|
}
|
|
92
93
|
export declare function getAuthDetails(credentials: Credentials): Promise<APIResponse<AuthTokensResponse>>;
|
|
94
|
+
export declare function mergeUsageProps(newProps: ComponentProps, currentProps?: ComponentProps): any;
|
|
93
95
|
export {};
|
package/dist/common/api.js
CHANGED
|
@@ -71,13 +71,35 @@ async function request(path, credentials, init) {
|
|
|
71
71
|
}
|
|
72
72
|
return await response.json();
|
|
73
73
|
}
|
|
74
|
+
export function mergeUsageProps(newProps, currentProps) {
|
|
75
|
+
if (!currentProps)
|
|
76
|
+
return newProps;
|
|
77
|
+
if (!newProps)
|
|
78
|
+
return currentProps;
|
|
79
|
+
const props = Array.from(new Set([...Object.keys(currentProps), ...Object.keys(newProps)]));
|
|
80
|
+
const mergedProps = {};
|
|
81
|
+
props.forEach((prop) => {
|
|
82
|
+
const existingProp = currentProps[prop] ?? {};
|
|
83
|
+
const newProp = newProps[prop];
|
|
84
|
+
const mergedValues = [
|
|
85
|
+
...(existingProp?.values ?? []),
|
|
86
|
+
...(newProp?.values ?? []),
|
|
87
|
+
];
|
|
88
|
+
mergedProps[prop] = {
|
|
89
|
+
type: Array.from(new Set([...(existingProp.type ?? []), ...(newProp?.type ?? [])])),
|
|
90
|
+
count: mergedValues.length,
|
|
91
|
+
values: mergedValues,
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
return mergedProps;
|
|
95
|
+
}
|
|
74
96
|
/**
|
|
75
97
|
* Transform usage map grouped by file into usage map grouped by component
|
|
76
98
|
*/
|
|
77
99
|
function transformUsageByName(usage) {
|
|
78
100
|
const transformedUsageMap = new Map();
|
|
79
101
|
for (const [file, rawUsage] of usage) {
|
|
80
|
-
for (const { count, package: packageName, name } of rawUsage) {
|
|
102
|
+
for (const { count, package: packageName, name, props } of rawUsage) {
|
|
81
103
|
const key = `package:${packageName} name:${name}`;
|
|
82
104
|
const currentValue = transformedUsageMap.get(key);
|
|
83
105
|
const newFileList = [...(currentValue?.files ?? []), file]; // Add file to list
|
|
@@ -86,6 +108,7 @@ function transformUsageByName(usage) {
|
|
|
86
108
|
files: Array.from(new Set(newFileList)), // Ensure unique file paths
|
|
87
109
|
count: count + (currentValue?.count ?? 0),
|
|
88
110
|
package: packageName,
|
|
111
|
+
props: mergeUsageProps(props, currentValue?.props ?? {}),
|
|
89
112
|
});
|
|
90
113
|
}
|
|
91
114
|
}
|