@zeroheight/adoption-cli 2.0.2 → 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 +4 -0
- 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,9 @@
|
|
|
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
|
+
|
|
3
7
|
## [2.0.2](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/2.0.2) - 29th October 2024
|
|
4
8
|
|
|
5
9
|
- Update readme
|
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
|
}
|