@zeroheight/adoption-cli 2.4.3 → 3.0.1
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 +13 -0
- package/README.md +14 -4
- package/dist/ast/analyze.d.ts +11 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +18 -5
- package/dist/commands/analyze.d.ts +5 -3
- package/dist/commands/analyze.js +9 -4
- package/dist/commands/analyze.utils.d.ts +10 -11
- package/dist/commands/analyze.utils.js +87 -0
- package/dist/commands/track-package.utils.js +2 -1
- package/dist/common/api.d.ts +20 -2
- package/dist/common/api.js +27 -1
- package/dist/components/analyze/analyze.d.ts +7 -4
- package/dist/components/analyze/analyze.js +330 -112
- package/dist/components/analyze/non-interactive-analyze.d.ts +9 -3
- package/dist/components/analyze/non-interactive-analyze.js +57 -19
- package/dist/components/auth/no-credentials-onboarding.js +3 -1
- package/dist/components/color-usage-table.d.ts +7 -0
- package/dist/components/color-usage-table.js +38 -0
- package/dist/components/latest-version-info.d.ts +9 -0
- package/dist/components/latest-version-info.js +27 -0
- package/dist/components/ui/continue-prompt.js +4 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Release notes
|
|
2
2
|
|
|
3
|
+
## [3.0.1](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/3.0.1) - 25th March 2025
|
|
4
|
+
|
|
5
|
+
- Fix bug where color analysis didn't occur when using non-interactive mode
|
|
6
|
+
|
|
7
|
+
## [3.0.0](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/3.0.0) - 25th March 2025
|
|
8
|
+
|
|
9
|
+
- `analyze` command updated to collect date about color usage
|
|
10
|
+
- For non-interactive command new flags introduced: `--color-usage` and `--component-usage`
|
|
11
|
+
- Improvement to usability of interactive `analyze` command
|
|
12
|
+
- Remove `--dry-run` from interactive `analyze`
|
|
13
|
+
- Additional prompt added if using an outdated version of the CLI tool
|
|
14
|
+
- Allow `track-package` command to use package where version is unspecified
|
|
15
|
+
|
|
3
16
|
## [2.4.3](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/2.4.3) - 2nd January 2025
|
|
4
17
|
|
|
5
18
|
- `track-package --packages` option can be used to allow tracking specific packages within monorepos in non-interactive mode
|
package/README.md
CHANGED
|
@@ -50,17 +50,27 @@ There are 4 levels of increasing severity:
|
|
|
50
50
|
|
|
51
51
|
---
|
|
52
52
|
|
|
53
|
+
### Color usage analysis
|
|
54
|
+
|
|
55
|
+
[Help center article](https://zeroheight.com/help/article/color-usage/)
|
|
56
|
+
|
|
57
|
+
In the repository in which you wish to get usage metrics around how raw color values are being used, run the following command:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
zh-adoption analyze --color-usage
|
|
61
|
+
```
|
|
62
|
+
|
|
53
63
|
### Component usage analysis
|
|
54
64
|
|
|
55
65
|
[Help center article](https://zeroheight.com/help/article/component-usage/)
|
|
56
66
|
|
|
57
|
-
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:
|
|
67
|
+
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:
|
|
58
68
|
|
|
59
69
|
```bash
|
|
60
|
-
zh-adoption analyze
|
|
70
|
+
zh-adoption analyze --component-usage
|
|
61
71
|
```
|
|
62
72
|
|
|
63
|
-
|
|
73
|
+
### Analyze command options
|
|
64
74
|
|
|
65
75
|
`-e` / `--extensions`
|
|
66
76
|
|
|
@@ -72,7 +82,7 @@ zh-adoption analyze -e "**/*.{js,jsx,ts,tsx}"
|
|
|
72
82
|
|
|
73
83
|
`-i` / `--ignore`
|
|
74
84
|
|
|
75
|
-
Provide a glob pattern to ignore files, directories or file extensions when
|
|
85
|
+
Provide a glob pattern to ignore files, directories or file extensions when analyzing.
|
|
76
86
|
|
|
77
87
|
```bash
|
|
78
88
|
zh-adoption analyze -i "**/*.{test,spec}.*"
|
package/dist/ast/analyze.d.ts
CHANGED
|
@@ -31,6 +31,17 @@ export type RawUsage = {
|
|
|
31
31
|
package: string;
|
|
32
32
|
props: ComponentProps;
|
|
33
33
|
};
|
|
34
|
+
export type RawColorUsage = {
|
|
35
|
+
hex: string[];
|
|
36
|
+
rgb: string[];
|
|
37
|
+
hsla: string[];
|
|
38
|
+
oklab: string[];
|
|
39
|
+
hwb: string[];
|
|
40
|
+
lab: string[];
|
|
41
|
+
lch: string[];
|
|
42
|
+
colorSpace: string[];
|
|
43
|
+
totalCount: number;
|
|
44
|
+
};
|
|
34
45
|
declare const RUNTIME_VALUE = "zh-runtime-value";
|
|
35
46
|
export declare const ARRAY_VALUE = "zh-array-value";
|
|
36
47
|
export declare const BINARY_VALUE = "zh-binary-value";
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -3,18 +3,26 @@ import * as React from "react";
|
|
|
3
3
|
import { Command, Option } from "commander";
|
|
4
4
|
import { render } from "ink-render-string";
|
|
5
5
|
import HelpInfo from "./components/help-info.js";
|
|
6
|
+
import LatestVersionInfo from "./components/latest-version-info.js";
|
|
6
7
|
import { analyzeCommand } from "./commands/analyze.js";
|
|
7
8
|
import { authCommand } from "./commands/auth.js";
|
|
8
9
|
import { monitorRepoCommand } from "./commands/monitor-repo.js";
|
|
9
10
|
import { trackPackageCommand } from "./commands/track-package.js";
|
|
10
11
|
import logger, { setFileStream } from "./common/logging.js";
|
|
11
12
|
const program = new Command();
|
|
12
|
-
const
|
|
13
|
+
export const CURRENT_VERSION = "3.0.1";
|
|
14
|
+
async function getLatestPackageVersion() {
|
|
15
|
+
const response = await fetch("https://registry.npmjs.org/@zeroheight/adoption-cli/latest");
|
|
16
|
+
const json = await response.json();
|
|
17
|
+
return json.version;
|
|
18
|
+
}
|
|
19
|
+
const { output: helpOutput, cleanup: helpCleanup } = render(React.createElement(HelpInfo, null));
|
|
20
|
+
const { output: latestVersionOutput, cleanup: latestVersionCleanup } = render(React.createElement(LatestVersionInfo, { latestVersion: await getLatestPackageVersion() }));
|
|
13
21
|
program
|
|
14
22
|
.name("zh-adoption")
|
|
15
23
|
.description("CLI for measuring design system usage usage in your products")
|
|
16
|
-
.version(
|
|
17
|
-
.addHelpText("before",
|
|
24
|
+
.version(CURRENT_VERSION)
|
|
25
|
+
.addHelpText("before", helpOutput)
|
|
18
26
|
.option("--log-file <path>", "Path to write logs to, if not provided logs only error logs will be written to stderr")
|
|
19
27
|
.addOption(new Option("--log-level <level>", "The lowest level of logs to display")
|
|
20
28
|
.default("error")
|
|
@@ -23,7 +31,7 @@ program
|
|
|
23
31
|
.addCommand(authCommand())
|
|
24
32
|
.addCommand(monitorRepoCommand())
|
|
25
33
|
.addCommand(trackPackageCommand())
|
|
26
|
-
.hook("preAction", (thisCommand, actionCommand) => {
|
|
34
|
+
.hook("preAction", async (thisCommand, actionCommand) => {
|
|
27
35
|
logger.level = thisCommand.opts()["logLevel"];
|
|
28
36
|
if (thisCommand.opts()["logFile"]) {
|
|
29
37
|
setFileStream(thisCommand.opts()["logFile"]);
|
|
@@ -35,8 +43,13 @@ program
|
|
|
35
43
|
globalOpts: thisCommand.opts(),
|
|
36
44
|
},
|
|
37
45
|
}, "Running command");
|
|
46
|
+
const latestVersion = await getLatestPackageVersion();
|
|
47
|
+
if (latestVersion !== CURRENT_VERSION) {
|
|
48
|
+
console.log(latestVersionOutput);
|
|
49
|
+
}
|
|
38
50
|
});
|
|
39
|
-
|
|
51
|
+
helpCleanup();
|
|
52
|
+
latestVersionCleanup();
|
|
40
53
|
// Only start parsing if run as CLI, don't start parsing during testing
|
|
41
54
|
if (process.env["NODE_ENV"] !== "test") {
|
|
42
55
|
program.parse();
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { RenderOptions } from "ink";
|
|
2
2
|
import { Command } from "commander";
|
|
3
|
-
import { RawUsage } from "../ast/analyze.js";
|
|
3
|
+
import { RawColorUsage, RawUsage } from "../ast/analyze.js";
|
|
4
4
|
interface AnalyzeOptions {
|
|
5
5
|
extensions: string;
|
|
6
6
|
ignore: string[];
|
|
7
|
-
dryRun: boolean;
|
|
8
7
|
repoName?: string;
|
|
9
8
|
interactive: boolean;
|
|
9
|
+
componentUsage: boolean;
|
|
10
|
+
colorUsage: boolean;
|
|
10
11
|
}
|
|
11
|
-
export type
|
|
12
|
+
export type RawComponentUsageMap = Map<string, RawUsage[]>;
|
|
13
|
+
export type RawColorUsageMap = Map<string, RawColorUsage>;
|
|
12
14
|
export declare function analyzeAction(options: AnalyzeOptions, renderOptions?: RenderOptions): Promise<void>;
|
|
13
15
|
export declare function analyzeCommand(): Command;
|
|
14
16
|
export {};
|
package/dist/commands/analyze.js
CHANGED
|
@@ -2,16 +2,16 @@ import * as React from "react";
|
|
|
2
2
|
import { render } from "ink";
|
|
3
3
|
import { Command, Option } from "commander";
|
|
4
4
|
import yn from "yn";
|
|
5
|
-
import { analyzeFiles, parseGlobList } from "./analyze.utils.js";
|
|
5
|
+
import { analyzeFiles, analyzeRawColorUsage, parseGlobList, } from "./analyze.utils.js";
|
|
6
6
|
import Analyze from "../components/analyze/analyze.js";
|
|
7
7
|
import NonInteractiveAnalyze from "../components/analyze/non-interactive-analyze.js";
|
|
8
8
|
import logger, { setStdErrStream } from "../common/logging.js";
|
|
9
9
|
export async function analyzeAction(options, renderOptions) {
|
|
10
10
|
if (options.interactive) {
|
|
11
|
-
render(React.createElement(Analyze, {
|
|
11
|
+
render(React.createElement(Analyze, { onAnalyzeFiles: () => analyzeFiles(options.extensions, options.ignore), onAnalyzeColorUsage: () => analyzeRawColorUsage(options.extensions, options.ignore), repoName: options.repoName }), renderOptions);
|
|
12
12
|
}
|
|
13
13
|
else {
|
|
14
|
-
render(React.createElement(NonInteractiveAnalyze, { repoName: options.repoName, onAnalyzeFiles: () => analyzeFiles(options.extensions, options.ignore) }));
|
|
14
|
+
render(React.createElement(NonInteractiveAnalyze, { repoName: options.repoName, onAnalyzeFiles: () => analyzeFiles(options.extensions, options.ignore), onAnalyzeColorUsage: () => analyzeRawColorUsage(options.extensions, options.ignore), shouldAnalyzeComponentUsage: options.componentUsage, shouldAnalyzeTokenUsage: options.colorUsage }));
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
export function analyzeCommand() {
|
|
@@ -26,11 +26,16 @@ export function analyzeCommand() {
|
|
|
26
26
|
.addOption(new Option("-i, --ignore [patterns]", "files to ignore when searching for components, use multiple times to add more than one glob pattern")
|
|
27
27
|
.default(["**/*.{test,spec}.*", "**/*.d.ts"], "*.test.*, *.spec.* and *.d.ts files")
|
|
28
28
|
.argParser(parseGlobList))
|
|
29
|
-
.addOption(new Option("-d, --dry-run", "don't push results to zeroheight").default(false))
|
|
30
29
|
.addOption(new Option("-r, --repo-name <string>", "name of the repository"))
|
|
31
30
|
.addOption(new Option("-in, --interactive [boolean]", "disable to skip input (useful when running in CI)")
|
|
32
31
|
.default(true)
|
|
33
32
|
.argParser((value) => yn(value)))
|
|
33
|
+
.addOption(new Option("-cu, --component-usage [boolean]", "gather data about component and prop usage")
|
|
34
|
+
.default(true)
|
|
35
|
+
.argParser((value) => yn(value)))
|
|
36
|
+
.addOption(new Option("-cou, --color-usage [boolean]", "gather data about color usage")
|
|
37
|
+
.default(true)
|
|
38
|
+
.argParser((value) => yn(value)))
|
|
34
39
|
.action(async (options) => {
|
|
35
40
|
if (!options.interactive) {
|
|
36
41
|
setStdErrStream();
|
|
@@ -1,13 +1,6 @@
|
|
|
1
1
|
import type { Option } from "commander";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
export interface ComponentUsageRecord {
|
|
5
|
-
name: string;
|
|
6
|
-
files: string[];
|
|
7
|
-
count: number;
|
|
8
|
-
package: string;
|
|
9
|
-
props: ComponentProps;
|
|
10
|
-
}
|
|
2
|
+
import { RawColorUsageMap, RawComponentUsageMap } from "./analyze.js";
|
|
3
|
+
import { RawColorUsage } from "../ast/analyze.js";
|
|
11
4
|
/**
|
|
12
5
|
* Get a list of files matching extensions, skips hidden files and node_modules
|
|
13
6
|
* @param base starting directory
|
|
@@ -19,9 +12,15 @@ export interface ComponentUsageRecord {
|
|
|
19
12
|
export declare function findFiles(base: string, extensions: string, ignorePattern: string[]): Promise<string[]>;
|
|
20
13
|
export declare function analyzeFiles(extensions: string, ignorePattern: string[]): Promise<{
|
|
21
14
|
errorFile: string | null;
|
|
22
|
-
usage:
|
|
15
|
+
usage: RawComponentUsageMap;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function calculateNumberOfComponents(usageResult: RawComponentUsageMap): number;
|
|
18
|
+
export declare function calculateNumberOfColors(usageResult: RawColorUsageMap): number;
|
|
19
|
+
export declare function countColorOccurrences(fileContent: string): RawColorUsage;
|
|
20
|
+
export declare function analyzeRawColorUsage(extensions: string, ignorePattern: string[]): Promise<{
|
|
21
|
+
errorFile: string | null;
|
|
22
|
+
usage: RawColorUsageMap;
|
|
23
23
|
}>;
|
|
24
|
-
export declare function calculateNumberOfComponents(usageResult: RawUsageMap): number;
|
|
25
24
|
/**
|
|
26
25
|
* Parse the ignore array and format correctly
|
|
27
26
|
*/
|
|
@@ -101,6 +101,93 @@ export function calculateNumberOfComponents(usageResult) {
|
|
|
101
101
|
}
|
|
102
102
|
return 0;
|
|
103
103
|
}
|
|
104
|
+
export function calculateNumberOfColors(usageResult) {
|
|
105
|
+
if (!usageResult)
|
|
106
|
+
return 0;
|
|
107
|
+
const colors = Array.from(usageResult.entries()).flatMap((cols) => cols[1]);
|
|
108
|
+
const colorCount = colors.reduce((prev, next) => {
|
|
109
|
+
return prev + next.totalCount;
|
|
110
|
+
}, 0);
|
|
111
|
+
return colorCount;
|
|
112
|
+
}
|
|
113
|
+
export function countColorOccurrences(fileContent) {
|
|
114
|
+
// Matches #FFF, #FFFA, #FFFFFF or #FFFFFFA
|
|
115
|
+
const hexColorRegex = /#([a-fA-F0-9]{6}|[a-fA-F0-9]{8})\b|#([a-fA-F0-9]{3})\b/gi;
|
|
116
|
+
const hexMatches = Array.from(fileContent.match(hexColorRegex) ?? []);
|
|
117
|
+
// Matches rgb(255, 255, 255) or rgba(255, 255, 255) (with or without commas)
|
|
118
|
+
const rgbColorRegex = /rgba?\(\s*(\d{1,3})\s*[,\s]\s*(\d{1,3})\s*[,\s]\s*(\d{1,3})(?:\s*[,\s]\s*(\d*\.?\d+))?\s*\)/g;
|
|
119
|
+
const rgbMatches = Array.from(fileContent.match(rgbColorRegex) ?? []);
|
|
120
|
+
// Matches hsl and hsla values
|
|
121
|
+
const hslaColorRegex = /hsl(?:a)?\(\s*(?:(?:none|from\s+[a-zA-Z#0-9()]+)?\s*(?:[a-z\d+\-*/%()\s]+)\s*|(\d+(?:deg|grad|rad|turn)?)\s*(\d+%?)\s*(\d+%?)\s*(?:\/\s*(\d+%?|0?\.\d+))?|(\d+(?:deg|grad|rad|turn)?)\s*,\s*(\d+%)\s*,\s*(\d+%)\s*(?:,\s*(\d+(\.\d+)?%?))?)\)/g;
|
|
122
|
+
const hslaMatches = Array.from(fileContent.match(hslaColorRegex) ?? []);
|
|
123
|
+
// Matches oklab and oklch values (with or without commas and opacity)
|
|
124
|
+
const oklabColorRegex = /okl(?:ab|ch)\(\s*(-?\d*\.?\d+)\s*[,\s]\s*(-?\d*\.?\d+)\s*[,\s]\s*(-?\d*\.?\d+)(?:\s*[,\s]\s*(\d*\.?\d+))?\s*\)/g;
|
|
125
|
+
const oklabMatches = Array.from(fileContent.match(oklabColorRegex) ?? []);
|
|
126
|
+
// Matches hwb values (with or without commas and opacity)
|
|
127
|
+
const hwbColorRegex = /hwb\(\s*(\d*\.?\d+)(?:deg|turn|rad|grad)?\s*[,\s]\s*(\d*\.?\d+%)\s*[,\s]\s*(\d*\.?\d+%)(?:\s*[,\s]\s*(\d*\.?\d+))?\s*\)/g;
|
|
128
|
+
const hwbMatches = Array.from(fileContent.match(hwbColorRegex) ?? []);
|
|
129
|
+
// Matches lab values (with or without commas and opacity)
|
|
130
|
+
const labColorRegex = /(?<!ok)lab\(\s*(-?\d*\.?\d+%)\s*[,\s]\s*(-?\d*\.?\d+)\s*[,\s]\s*(-?\d*\.?\d+)(?:\s*[,\s]\s*(\d*\.?\d+))?\s*\)/g;
|
|
131
|
+
const labMatches = Array.from(fileContent.match(labColorRegex) ?? []);
|
|
132
|
+
// Matches lch values (with or without commas and opacity)
|
|
133
|
+
const lchColorRegex = /(?<!ok)lch\(\s*(-?\d*\.?\d+%)\s*[,\s]\s*(-?\d*\.?\d+)\s*[,\s]\s*(-?\d*\.?\d+)(?:\s*[,\s]\s*(\d*\.?\d+))?\s*\)/g;
|
|
134
|
+
const lchMatches = Array.from(fileContent.match(lchColorRegex) ?? []);
|
|
135
|
+
// Matches color space values
|
|
136
|
+
const colorSpaceColorRegex = /color\(\s*([\w-]+)\s+(?:[-+]?\d*\.?\d+(?:e[-+]?\d+)?%?\s*){3,4}\)/g;
|
|
137
|
+
const colorSpaceMatches = Array.from(fileContent.match(colorSpaceColorRegex) ?? []);
|
|
138
|
+
return {
|
|
139
|
+
hex: hexMatches,
|
|
140
|
+
rgb: rgbMatches,
|
|
141
|
+
hsla: hslaMatches,
|
|
142
|
+
oklab: oklabMatches,
|
|
143
|
+
hwb: hwbMatches,
|
|
144
|
+
lab: labMatches,
|
|
145
|
+
lch: lchMatches,
|
|
146
|
+
colorSpace: colorSpaceMatches,
|
|
147
|
+
totalCount: hexMatches.length +
|
|
148
|
+
rgbMatches.length +
|
|
149
|
+
hslaMatches.length +
|
|
150
|
+
hwbMatches.length +
|
|
151
|
+
labMatches.length +
|
|
152
|
+
lchMatches.length +
|
|
153
|
+
colorSpaceMatches.length +
|
|
154
|
+
oklabMatches.length,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
export async function analyzeRawColorUsage(extensions, ignorePattern) {
|
|
158
|
+
const files = await findFiles(process.cwd(), extensions, ignorePattern);
|
|
159
|
+
if (files.length === 0) {
|
|
160
|
+
throw new Error("Can't find any relevant files");
|
|
161
|
+
}
|
|
162
|
+
const parseErrors = [];
|
|
163
|
+
const usageMap = new Map();
|
|
164
|
+
for (const file of files) {
|
|
165
|
+
try {
|
|
166
|
+
const fileContents = await readFile(file, "utf-8");
|
|
167
|
+
const colorUsage = countColorOccurrences(fileContents);
|
|
168
|
+
if (colorUsage.totalCount > 0) {
|
|
169
|
+
const relativePath = file.slice(process.cwd().length);
|
|
170
|
+
usageMap.set(relativePath, colorUsage);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
catch (e) {
|
|
174
|
+
logger.error({ file, error: e }, "Error parsing file");
|
|
175
|
+
parseErrors.push(`Can't parse file ${file}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const errorFile = `/tmp/zh-adoption-analyze-errors-${Date.now()}`;
|
|
179
|
+
if (parseErrors.length > 0) {
|
|
180
|
+
const file = fs.createWriteStream(errorFile);
|
|
181
|
+
parseErrors.forEach((err) => {
|
|
182
|
+
file.write(err + "\n");
|
|
183
|
+
});
|
|
184
|
+
file.end();
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
errorFile: parseErrors.length > 0 ? errorFile : null,
|
|
188
|
+
usage: usageMap,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
104
191
|
/**
|
|
105
192
|
* Parse the ignore array and format correctly
|
|
106
193
|
*/
|
|
@@ -32,7 +32,8 @@ export async function getPackageInfo(allowedPackages) {
|
|
|
32
32
|
return {
|
|
33
33
|
name: parsedPackage.name,
|
|
34
34
|
path: `.${file.split(base).pop()}`,
|
|
35
|
-
|
|
35
|
+
// Default to 0.0.0 as we don't have a version so it might be a workspaces project/monorepo
|
|
36
|
+
version: parsedPackage.version ?? "0.0.0",
|
|
36
37
|
exports: parsedPackage.exports,
|
|
37
38
|
};
|
|
38
39
|
}));
|
package/dist/common/api.d.ts
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { ComponentProps } from "../ast/analyze.js";
|
|
2
|
-
import {
|
|
2
|
+
import { RawColorUsageMap, RawComponentUsageMap } from "../commands/analyze.js";
|
|
3
3
|
import { Credentials } from "./config.js";
|
|
4
|
+
export interface ComponentUsageRecord {
|
|
5
|
+
name: string;
|
|
6
|
+
files: string[];
|
|
7
|
+
count: number;
|
|
8
|
+
package: string;
|
|
9
|
+
props: ComponentProps;
|
|
10
|
+
}
|
|
11
|
+
export interface TokenLiteralUsageRecord {
|
|
12
|
+
value: string;
|
|
13
|
+
files: string[];
|
|
14
|
+
/** Only support color values right now */
|
|
15
|
+
type: "color";
|
|
16
|
+
count: number;
|
|
17
|
+
}
|
|
4
18
|
export declare enum ResponseStatus {
|
|
5
19
|
Success = "success",
|
|
6
20
|
Error = "error",
|
|
@@ -73,7 +87,11 @@ interface ComponentUsageDetailsSuccess {
|
|
|
73
87
|
repo_name: string;
|
|
74
88
|
};
|
|
75
89
|
}
|
|
76
|
-
export declare function
|
|
90
|
+
export declare function submitComponentUsageData(usage: RawComponentUsageMap, repoName: string, credentials: Credentials): Promise<APIResponse<ComponentUsageDetailsSuccess>>;
|
|
91
|
+
interface TokenLiteralUsageDetailsSuccess {
|
|
92
|
+
token_literal_usage: {};
|
|
93
|
+
}
|
|
94
|
+
export declare function submitTokenLiteralUsageData(usage: RawColorUsageMap, repoName: string, credentials: Credentials): Promise<APIResponse<TokenLiteralUsageDetailsSuccess>>;
|
|
77
95
|
interface RepoNamesSuccess {
|
|
78
96
|
repo_names: string[];
|
|
79
97
|
}
|
package/dist/common/api.js
CHANGED
|
@@ -31,12 +31,20 @@ export async function submitMonitoredRepoDetails(name, version, lockfilePath, pa
|
|
|
31
31
|
version,
|
|
32
32
|
}, credentials);
|
|
33
33
|
}
|
|
34
|
-
export async function
|
|
34
|
+
export async function submitComponentUsageData(usage, repoName, credentials) {
|
|
35
35
|
return post("/component_usages", {
|
|
36
36
|
repo_name: repoName,
|
|
37
37
|
usage: transformUsageByName(usage),
|
|
38
38
|
}, credentials);
|
|
39
39
|
}
|
|
40
|
+
export async function submitTokenLiteralUsageData(usage, repoName, credentials) {
|
|
41
|
+
return post("/token_literal_usages", {
|
|
42
|
+
token_literal_usage: {
|
|
43
|
+
repo_name: repoName,
|
|
44
|
+
usage: transformTokenLiteralUsageByName(usage),
|
|
45
|
+
},
|
|
46
|
+
}, credentials);
|
|
47
|
+
}
|
|
40
48
|
export async function getExistingRepoNames(credentials) {
|
|
41
49
|
return get("/component_usages/repo_names", credentials);
|
|
42
50
|
}
|
|
@@ -141,3 +149,21 @@ function transformUsageByName(usage) {
|
|
|
141
149
|
}
|
|
142
150
|
return Array.from(transformedUsageMap.values());
|
|
143
151
|
}
|
|
152
|
+
function transformTokenLiteralUsageByName(usage) {
|
|
153
|
+
const transformedUsageMap = new Map();
|
|
154
|
+
for (const [file, { hex, rgb, hsla }] of usage) {
|
|
155
|
+
// These values shouldn't collide as they're all different notations and types
|
|
156
|
+
const values = [...hex, ...rgb, ...hsla];
|
|
157
|
+
for (const colorValue of values) {
|
|
158
|
+
const currentValue = transformedUsageMap.get(colorValue);
|
|
159
|
+
const newFileList = [...(currentValue?.files ?? []), file];
|
|
160
|
+
transformedUsageMap.set(colorValue, {
|
|
161
|
+
value: colorValue,
|
|
162
|
+
type: "color",
|
|
163
|
+
files: newFileList,
|
|
164
|
+
count: (currentValue?.count ?? 0) + 1,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return Array.from(transformedUsageMap.values());
|
|
169
|
+
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { RawColorUsageMap, RawComponentUsageMap } from "../../commands/analyze.js";
|
|
3
3
|
export interface AnalyzeProps {
|
|
4
4
|
onAnalyzeFiles: () => Promise<{
|
|
5
5
|
errorFile: string | null;
|
|
6
|
-
usage:
|
|
6
|
+
usage: RawComponentUsageMap;
|
|
7
|
+
}>;
|
|
8
|
+
onAnalyzeColorUsage: () => Promise<{
|
|
9
|
+
errorFile: string | null;
|
|
10
|
+
usage: RawColorUsageMap;
|
|
7
11
|
}>;
|
|
8
|
-
dryRun: boolean;
|
|
9
12
|
repoName?: string;
|
|
10
13
|
}
|
|
11
|
-
export default function Analyze({ onAnalyzeFiles,
|
|
14
|
+
export default function Analyze({ onAnalyzeFiles, onAnalyzeColorUsage, repoName, }: AnalyzeProps): React.JSX.Element;
|
|
@@ -8,48 +8,116 @@ import NoCredentialsOnboarding from "../auth/no-credentials-onboarding.js";
|
|
|
8
8
|
import RepoNamePrompt from "../repo-name-prompt.js";
|
|
9
9
|
import UsageTable from "../usage-table.js";
|
|
10
10
|
import { configPath, writeConfig, readConfig, } from "../../common/config.js";
|
|
11
|
-
import { ResponseStatus, getAuthDetails, getZeroheightURL,
|
|
12
|
-
import { calculateNumberOfComponents } from "../../commands/analyze.utils.js";
|
|
11
|
+
import { ResponseStatus, getAuthDetails, getZeroheightURL, submitComponentUsageData, submitTokenLiteralUsageData, } from "../../common/api.js";
|
|
12
|
+
import { calculateNumberOfColors, calculateNumberOfComponents, } from "../../commands/analyze.utils.js";
|
|
13
13
|
import { ApiError } from "../../common/errors.js";
|
|
14
|
-
|
|
14
|
+
import ColorUsageTable from "../color-usage-table.js";
|
|
15
|
+
var AnalyzeStage;
|
|
16
|
+
(function (AnalyzeStage) {
|
|
17
|
+
AnalyzeStage[AnalyzeStage["initial"] = 0] = "initial";
|
|
18
|
+
AnalyzeStage[AnalyzeStage["finished"] = 1] = "finished";
|
|
19
|
+
AnalyzeStage[AnalyzeStage["fetchCredentials"] = 2] = "fetchCredentials";
|
|
20
|
+
AnalyzeStage[AnalyzeStage["fetchRepoNames"] = 3] = "fetchRepoNames";
|
|
21
|
+
AnalyzeStage[AnalyzeStage["cuScanningFiles"] = 4] = "cuScanningFiles";
|
|
22
|
+
AnalyzeStage[AnalyzeStage["cuScanComplete"] = 5] = "cuScanComplete";
|
|
23
|
+
AnalyzeStage[AnalyzeStage["cuShowMoreDetails"] = 6] = "cuShowMoreDetails";
|
|
24
|
+
AnalyzeStage[AnalyzeStage["cuSendDataCheck"] = 7] = "cuSendDataCheck";
|
|
25
|
+
AnalyzeStage[AnalyzeStage["cuAuthSetUp"] = 8] = "cuAuthSetUp";
|
|
26
|
+
AnalyzeStage[AnalyzeStage["cuRepoSelection"] = 9] = "cuRepoSelection";
|
|
27
|
+
AnalyzeStage[AnalyzeStage["cuSendingData"] = 10] = "cuSendingData";
|
|
28
|
+
AnalyzeStage[AnalyzeStage["cuSendSuccess"] = 11] = "cuSendSuccess";
|
|
29
|
+
AnalyzeStage[AnalyzeStage["tuConfirmRun"] = 12] = "tuConfirmRun";
|
|
30
|
+
AnalyzeStage[AnalyzeStage["tuScanningFiles"] = 13] = "tuScanningFiles";
|
|
31
|
+
AnalyzeStage[AnalyzeStage["tuScanComplete"] = 14] = "tuScanComplete";
|
|
32
|
+
AnalyzeStage[AnalyzeStage["tuShowMoreDetails"] = 15] = "tuShowMoreDetails";
|
|
33
|
+
AnalyzeStage[AnalyzeStage["tuSendDataCheck"] = 16] = "tuSendDataCheck";
|
|
34
|
+
AnalyzeStage[AnalyzeStage["tuAuthSetUp"] = 17] = "tuAuthSetUp";
|
|
35
|
+
AnalyzeStage[AnalyzeStage["tuRepoSelection"] = 18] = "tuRepoSelection";
|
|
36
|
+
AnalyzeStage[AnalyzeStage["tuSendingData"] = 19] = "tuSendingData";
|
|
37
|
+
AnalyzeStage[AnalyzeStage["tuSendSuccess"] = 20] = "tuSendSuccess";
|
|
38
|
+
})(AnalyzeStage || (AnalyzeStage = {}));
|
|
39
|
+
export default function Analyze({ onAnalyzeFiles, onAnalyzeColorUsage, repoName, }) {
|
|
15
40
|
const { exit } = useApp();
|
|
16
|
-
const [
|
|
17
|
-
|
|
41
|
+
const [currentStage, setCurrentStage] = React.useState(AnalyzeStage.initial);
|
|
42
|
+
// Result states
|
|
43
|
+
const [componentUsageResult, setComponentUsageResult] = React.useState(new Map());
|
|
44
|
+
const [colorUsageResult, setColorUsageResult] = React.useState(new Map());
|
|
18
45
|
const [errorList, setErrorList] = React.useState([]);
|
|
19
46
|
const [errorFileLocation, setErrorFileLocation] = React.useState(null);
|
|
47
|
+
// Set up states
|
|
20
48
|
const [credentials, setCredentials] = React.useState(null);
|
|
21
|
-
const [resourceURL, setResourceURL] = React.useState(
|
|
49
|
+
const [resourceURL, setResourceURL] = React.useState(getZeroheightURL());
|
|
22
50
|
const [repo, setRepo] = React.useState(repoName);
|
|
23
|
-
|
|
24
|
-
const [
|
|
51
|
+
// Text input states
|
|
52
|
+
const [continueToTokenUsage, setContinueToTokenUsage] = React.useState("");
|
|
53
|
+
const [runComponentUsage, setRunComponentUsage] = React.useState("");
|
|
54
|
+
const [runTokenUsage, setRunTokenUsage] = React.useState("");
|
|
25
55
|
const [showMoreResults, setShowMoreResults] = React.useState("");
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
// Component usage
|
|
57
|
+
function handleStartComponentUsage(shouldScan) {
|
|
58
|
+
if (shouldScan) {
|
|
59
|
+
setCurrentStage(AnalyzeStage.cuScanningFiles);
|
|
60
|
+
analyzeComponentUsage();
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
setCurrentStage(AnalyzeStage.tuConfirmRun);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
async function analyzeComponentUsage() {
|
|
67
|
+
try {
|
|
68
|
+
const { errorFile, usage } = await onAnalyzeFiles();
|
|
69
|
+
setErrorFileLocation(errorFile);
|
|
70
|
+
setComponentUsageResult(usage);
|
|
71
|
+
setCurrentStage(AnalyzeStage.cuScanComplete);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
setErrorList((s) => [...s, e]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function handleShowMore(showMore) {
|
|
78
|
+
if (showMore) {
|
|
79
|
+
setCurrentStage(AnalyzeStage.cuShowMoreDetails);
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
setCurrentStage(AnalyzeStage.cuSendDataCheck);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function handleSendCuData(shouldSend) {
|
|
86
|
+
if (shouldSend) {
|
|
87
|
+
const creds = await fetchCredentials();
|
|
88
|
+
if (!creds) {
|
|
89
|
+
setCurrentStage(AnalyzeStage.cuAuthSetUp);
|
|
90
|
+
return;
|
|
35
91
|
}
|
|
36
|
-
if (
|
|
92
|
+
if (!repo) {
|
|
93
|
+
setCurrentStage(AnalyzeStage.cuRepoSelection);
|
|
37
94
|
return;
|
|
38
|
-
try {
|
|
39
|
-
const config = await readConfig();
|
|
40
|
-
if (config) {
|
|
41
|
-
setCredentials({ token: config.token, client: config.client });
|
|
42
|
-
}
|
|
43
95
|
}
|
|
44
|
-
|
|
45
|
-
|
|
96
|
+
setCurrentStage(AnalyzeStage.cuSendingData);
|
|
97
|
+
sendCuData(componentUsageResult, repo, creds);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
setCurrentStage(AnalyzeStage.tuConfirmRun);
|
|
101
|
+
exit();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function fetchCredentials() {
|
|
105
|
+
try {
|
|
106
|
+
const config = await readConfig();
|
|
107
|
+
if (config) {
|
|
108
|
+
setCredentials({ token: config.token, client: config.client });
|
|
109
|
+
return { token: config.token, client: config.client };
|
|
46
110
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
catch (e) {
|
|
114
|
+
setErrorList((s) => [...s, "Failed to load credentials"]);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function sendCuData(result, repoName, credentials) {
|
|
51
119
|
try {
|
|
52
|
-
await
|
|
120
|
+
await submitComponentUsageData(result, repoName, credentials);
|
|
53
121
|
const authDetailsResponse = await getAuthDetails(credentials);
|
|
54
122
|
if (authDetailsResponse.status !== ResponseStatus.Success) {
|
|
55
123
|
throw new Error("Failed to get credentials");
|
|
@@ -60,6 +128,7 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
|
|
|
60
128
|
? `/project/${projectId}/adoption/`
|
|
61
129
|
: "/adoption/";
|
|
62
130
|
setResourceURL(resourceURL);
|
|
131
|
+
setCurrentStage(AnalyzeStage.cuSendSuccess);
|
|
63
132
|
}
|
|
64
133
|
catch (e) {
|
|
65
134
|
let errorMessage = "Failed to send data to zeroheight";
|
|
@@ -68,23 +137,6 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
|
|
|
68
137
|
}
|
|
69
138
|
setErrorList((s) => [...s, errorMessage]);
|
|
70
139
|
}
|
|
71
|
-
finally {
|
|
72
|
-
setIsSendingData(false);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
async function handleContinue(shouldContinue) {
|
|
76
|
-
if (shouldContinue) {
|
|
77
|
-
if (!dryRun && credentials && usageResult && !repo) {
|
|
78
|
-
setPromptRepo(true);
|
|
79
|
-
setRepo("");
|
|
80
|
-
}
|
|
81
|
-
else if (!dryRun && credentials && usageResult && repo) {
|
|
82
|
-
sendData(usageResult, repo, credentials);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
exit();
|
|
87
|
-
}
|
|
88
140
|
}
|
|
89
141
|
function handleOnRepoNameSelected(repoName) {
|
|
90
142
|
setRepo(repoName);
|
|
@@ -93,14 +145,13 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
|
|
|
93
145
|
[process.cwd()]: repoName,
|
|
94
146
|
},
|
|
95
147
|
});
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (showMore) {
|
|
100
|
-
setShowMoreResults("true");
|
|
148
|
+
if (currentStage === AnalyzeStage.cuRepoSelection) {
|
|
149
|
+
handleSendCuData(true);
|
|
150
|
+
setCurrentStage(AnalyzeStage.cuSendingData);
|
|
101
151
|
}
|
|
102
152
|
else {
|
|
103
|
-
|
|
153
|
+
handleSendTuData(true);
|
|
154
|
+
setCurrentStage(AnalyzeStage.tuSendingData);
|
|
104
155
|
}
|
|
105
156
|
}
|
|
106
157
|
async function handleCheckCredentials(newClient, newToken) {
|
|
@@ -112,6 +163,86 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
|
|
|
112
163
|
return exit();
|
|
113
164
|
}
|
|
114
165
|
}
|
|
166
|
+
// Token usage
|
|
167
|
+
function startTokenUsageFlow(shouldContinue) {
|
|
168
|
+
if (shouldContinue) {
|
|
169
|
+
setCurrentStage(AnalyzeStage.tuConfirmRun);
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
setCurrentStage(AnalyzeStage.finished);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function handleStartTokenUsage(shouldScan) {
|
|
176
|
+
if (shouldScan) {
|
|
177
|
+
setCurrentStage(AnalyzeStage.tuScanningFiles);
|
|
178
|
+
analyzeTokenUsage();
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
setCurrentStage(AnalyzeStage.finished);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
async function analyzeTokenUsage() {
|
|
185
|
+
try {
|
|
186
|
+
const { errorFile, usage } = await onAnalyzeColorUsage();
|
|
187
|
+
setErrorFileLocation(errorFile);
|
|
188
|
+
setColorUsageResult(usage);
|
|
189
|
+
setCurrentStage(AnalyzeStage.tuScanComplete);
|
|
190
|
+
}
|
|
191
|
+
catch (e) {
|
|
192
|
+
setErrorList((s) => [...s, e]);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function handleShowMoreTU(showMore) {
|
|
196
|
+
if (showMore) {
|
|
197
|
+
setCurrentStage(AnalyzeStage.tuShowMoreDetails);
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
setCurrentStage(AnalyzeStage.tuSendDataCheck);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async function handleSendTuData(shouldSend) {
|
|
204
|
+
if (shouldSend) {
|
|
205
|
+
const creds = await fetchCredentials();
|
|
206
|
+
if (!creds) {
|
|
207
|
+
setCurrentStage(AnalyzeStage.tuAuthSetUp);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (!repo) {
|
|
211
|
+
setCurrentStage(AnalyzeStage.tuRepoSelection);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
setCurrentStage(AnalyzeStage.tuSendingData);
|
|
215
|
+
sendTuData(colorUsageResult, repo, creds);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
setCurrentStage(AnalyzeStage.finished);
|
|
219
|
+
exit();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function sendTuData(result, repoName, credentials) {
|
|
223
|
+
try {
|
|
224
|
+
await submitTokenLiteralUsageData(result, repoName, credentials);
|
|
225
|
+
const authDetailsResponse = await getAuthDetails(credentials);
|
|
226
|
+
if (authDetailsResponse.status !== ResponseStatus.Success) {
|
|
227
|
+
throw new Error("Failed to get credentials");
|
|
228
|
+
}
|
|
229
|
+
const projectId = authDetailsResponse.data.project?.id;
|
|
230
|
+
const resourceURL = getZeroheightURL();
|
|
231
|
+
resourceURL.pathname = projectId
|
|
232
|
+
? `/project/${projectId}/adoption/`
|
|
233
|
+
: "/adoption/";
|
|
234
|
+
setResourceURL(resourceURL);
|
|
235
|
+
setCurrentStage(AnalyzeStage.tuSendSuccess);
|
|
236
|
+
}
|
|
237
|
+
catch (e) {
|
|
238
|
+
let errorMessage = "Failed to send data to zeroheight";
|
|
239
|
+
if (e instanceof ApiError) {
|
|
240
|
+
errorMessage = e.message;
|
|
241
|
+
}
|
|
242
|
+
setErrorList((s) => [...s, errorMessage]);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// UI flow
|
|
115
246
|
if (errorList.length > 0) {
|
|
116
247
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
117
248
|
React.createElement(Text, { color: "red" }, "Error:"),
|
|
@@ -119,7 +250,69 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
|
|
|
119
250
|
"- ",
|
|
120
251
|
e))))));
|
|
121
252
|
}
|
|
122
|
-
if (
|
|
253
|
+
if (currentStage === AnalyzeStage.initial) {
|
|
254
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
255
|
+
React.createElement(Text, null,
|
|
256
|
+
"\uD83D\uDC4B Welcome to the ",
|
|
257
|
+
React.createElement(Text, { color: "#f63e7c" }, "zeroheight"),
|
|
258
|
+
" analyze tool"),
|
|
259
|
+
React.createElement(Newline, null),
|
|
260
|
+
React.createElement(Box, { flexDirection: "column" },
|
|
261
|
+
React.createElement(Text, null,
|
|
262
|
+
"\u2753 Would you like to us to scan the files in this project to understand how components are being used (React only)?",
|
|
263
|
+
" ",
|
|
264
|
+
React.createElement(Text, { dimColor: true }, "(Y/n)")),
|
|
265
|
+
React.createElement(ConfirmInput, { isChecked: true, value: runComponentUsage, onChange: setRunComponentUsage, onSubmit: handleStartComponentUsage }))));
|
|
266
|
+
}
|
|
267
|
+
if (currentStage === AnalyzeStage.cuScanningFiles) {
|
|
268
|
+
return (React.createElement(Text, null,
|
|
269
|
+
React.createElement(Text, { color: "green" },
|
|
270
|
+
React.createElement(Spinner, { type: "dots" })),
|
|
271
|
+
" Scanning files for component usage..."));
|
|
272
|
+
}
|
|
273
|
+
if (currentStage === AnalyzeStage.cuScanComplete) {
|
|
274
|
+
if (componentUsageResult?.size > 0) {
|
|
275
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
276
|
+
React.createElement(Text, { color: "green" },
|
|
277
|
+
calculateNumberOfComponents(componentUsageResult),
|
|
278
|
+
" components found"),
|
|
279
|
+
errorFileLocation && (React.createElement(React.Fragment, null,
|
|
280
|
+
React.createElement(Newline, null),
|
|
281
|
+
React.createElement(Box, null,
|
|
282
|
+
React.createElement(Text, { color: "red" }, "Error:"),
|
|
283
|
+
React.createElement(Text, null,
|
|
284
|
+
" ",
|
|
285
|
+
"Some files could not be parsed. Errors can be found at",
|
|
286
|
+
" ",
|
|
287
|
+
React.createElement(Link, { url: errorFileLocation }, errorFileLocation))))),
|
|
288
|
+
React.createElement(Box, null,
|
|
289
|
+
React.createElement(Text, null,
|
|
290
|
+
"Show more details? ",
|
|
291
|
+
React.createElement(Text, { dimColor: true }, "(y/N)")),
|
|
292
|
+
React.createElement(ConfirmInput, { isChecked: false, value: showMoreResults, onChange: setShowMoreResults, onSubmit: handleShowMore }))));
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
296
|
+
React.createElement(Box, null,
|
|
297
|
+
React.createElement(Text, null, "No files found, try a different directory or change the file extensions you want to include.")),
|
|
298
|
+
React.createElement(Box, null,
|
|
299
|
+
React.createElement(Text, null,
|
|
300
|
+
"Continue to token usage analysis? ",
|
|
301
|
+
React.createElement(Text, { dimColor: true }, "(Y/n)")),
|
|
302
|
+
React.createElement(ConfirmInput, { isChecked: true, value: continueToTokenUsage, onChange: setContinueToTokenUsage, onSubmit: startTokenUsageFlow }))));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (currentStage === AnalyzeStage.cuShowMoreDetails) {
|
|
306
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
307
|
+
React.createElement(UsageTable, { usage: componentUsageResult }),
|
|
308
|
+
React.createElement(Newline, null),
|
|
309
|
+
React.createElement(ContinuePrompt, { onContinue: handleSendCuData })));
|
|
310
|
+
}
|
|
311
|
+
if (currentStage === AnalyzeStage.cuSendDataCheck) {
|
|
312
|
+
return React.createElement(ContinuePrompt, { onContinue: handleSendCuData });
|
|
313
|
+
}
|
|
314
|
+
if (currentStage === AnalyzeStage.cuAuthSetUp ||
|
|
315
|
+
currentStage === AnalyzeStage.tuAuthSetUp) {
|
|
123
316
|
return (React.createElement(NoCredentialsOnboarding, { configPath: configPath(), onCheckCredentials: handleCheckCredentials, onSaveCredentials: async (persist, newClient, newToken) => {
|
|
124
317
|
const newCredentials = { token: newToken, client: newClient };
|
|
125
318
|
setCredentials(newCredentials);
|
|
@@ -129,83 +322,108 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
|
|
|
129
322
|
token: newToken,
|
|
130
323
|
});
|
|
131
324
|
}
|
|
325
|
+
if (currentStage === AnalyzeStage.cuAuthSetUp) {
|
|
326
|
+
handleSendCuData(true);
|
|
327
|
+
setCurrentStage(AnalyzeStage.cuSendingData);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
handleSendTuData(true);
|
|
331
|
+
setCurrentStage(AnalyzeStage.tuSendingData);
|
|
332
|
+
}
|
|
132
333
|
} }));
|
|
133
334
|
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return (React.createElement(
|
|
137
|
-
React.createElement(Text, { color: "green" },
|
|
138
|
-
React.createElement(Spinner, { type: "dots" })),
|
|
139
|
-
" Scanning files..."));
|
|
140
|
-
}
|
|
141
|
-
// No usage data found
|
|
142
|
-
if (usageResult?.size === 0) {
|
|
143
|
-
return (React.createElement(Text, null, "No files found, try a different directory or change the file extensions you want to include"));
|
|
335
|
+
if (currentStage === AnalyzeStage.cuRepoSelection ||
|
|
336
|
+
currentStage === AnalyzeStage.tuRepoSelection) {
|
|
337
|
+
return (React.createElement(RepoNamePrompt, { credentials: credentials, onRepoNameSelected: handleOnRepoNameSelected }));
|
|
144
338
|
}
|
|
145
|
-
|
|
146
|
-
|
|
339
|
+
if (currentStage === AnalyzeStage.cuSendingData ||
|
|
340
|
+
currentStage === AnalyzeStage.tuSendingData) {
|
|
147
341
|
return (React.createElement(Text, null,
|
|
148
342
|
React.createElement(Text, { color: "green" },
|
|
149
343
|
React.createElement(Spinner, { type: "dots" })),
|
|
150
|
-
"
|
|
344
|
+
" ",
|
|
345
|
+
"Sending usage data to ",
|
|
346
|
+
React.createElement(Text, { color: "#f63e7c" }, "zeroheight"),
|
|
347
|
+
"..."));
|
|
151
348
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
return (React.createElement(React.Fragment, null,
|
|
155
|
-
React.createElement(Newline, null),
|
|
349
|
+
if (currentStage === AnalyzeStage.cuSendSuccess) {
|
|
350
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
156
351
|
React.createElement(Text, null,
|
|
157
|
-
React.createElement(Text, { color: "green" }, "
|
|
352
|
+
React.createElement(Text, { color: "green" }, "Successfully sent:"),
|
|
158
353
|
" View your",
|
|
159
354
|
" ",
|
|
160
|
-
React.createElement(Link, { url: resourceURL.toString() },
|
|
355
|
+
React.createElement(Link, { url: resourceURL.toString() },
|
|
356
|
+
React.createElement(Text, { underline: true, color: "#f63e7c" }, "usage data on zeroheight"))),
|
|
357
|
+
React.createElement(Newline, null),
|
|
358
|
+
React.createElement(Box, null,
|
|
359
|
+
React.createElement(Text, null,
|
|
360
|
+
"\u2753 Would you like to us to scan the files in this project to understand how colors are being used? ",
|
|
361
|
+
React.createElement(Text, { dimColor: true }, "(Y/n)")),
|
|
362
|
+
React.createElement(ConfirmInput, { isChecked: true, value: runTokenUsage, onChange: setRunTokenUsage, onSubmit: handleStartTokenUsage }))));
|
|
161
363
|
}
|
|
162
|
-
if (
|
|
163
|
-
!promptRepo &&
|
|
164
|
-
!(showMoreResults === "true" || showContinue)) {
|
|
364
|
+
if (currentStage === AnalyzeStage.tuConfirmRun) {
|
|
165
365
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
366
|
+
React.createElement(Box, null,
|
|
367
|
+
React.createElement(Text, null,
|
|
368
|
+
"\u2753 Would you like to us to scan the files in this project to understand how colors are being used? ",
|
|
369
|
+
React.createElement(Text, { dimColor: true }, "(Y/n)")),
|
|
370
|
+
React.createElement(ConfirmInput, { isChecked: true, value: runTokenUsage, onChange: setRunTokenUsage, onSubmit: handleStartTokenUsage }))));
|
|
371
|
+
}
|
|
372
|
+
if (currentStage === AnalyzeStage.tuScanningFiles) {
|
|
373
|
+
return (React.createElement(Text, null,
|
|
166
374
|
React.createElement(Text, { color: "green" },
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
375
|
+
React.createElement(Spinner, { type: "dots" })),
|
|
376
|
+
" Scanning files for color usage..."));
|
|
377
|
+
}
|
|
378
|
+
if (currentStage === AnalyzeStage.tuScanComplete) {
|
|
379
|
+
if (colorUsageResult?.size > 0) {
|
|
380
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
381
|
+
React.createElement(Text, { color: "green" },
|
|
382
|
+
calculateNumberOfColors(colorUsageResult),
|
|
383
|
+
" instatances of raw color usage found"),
|
|
384
|
+
errorFileLocation && (React.createElement(React.Fragment, null,
|
|
385
|
+
React.createElement(Newline, null),
|
|
386
|
+
React.createElement(Box, null,
|
|
387
|
+
React.createElement(Text, { color: "red" }, "Error:"),
|
|
388
|
+
React.createElement(Text, null,
|
|
389
|
+
" ",
|
|
390
|
+
"Some files could not be parsed. Errors can be found at",
|
|
391
|
+
" ",
|
|
392
|
+
React.createElement(Link, { url: errorFileLocation }, errorFileLocation))))),
|
|
171
393
|
React.createElement(Box, null,
|
|
172
|
-
React.createElement(Text, { color: "red" }, "Error:"),
|
|
173
394
|
React.createElement(Text, null,
|
|
174
|
-
" ",
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
React.createElement(ConfirmInput, { isChecked: false, value: showMoreResults, onChange: setShowMoreResults, onSubmit: handleShowMore }))));
|
|
395
|
+
"Show more details? ",
|
|
396
|
+
React.createElement(Text, { dimColor: true }, "(y/N)")),
|
|
397
|
+
React.createElement(ConfirmInput, { isChecked: false, value: showMoreResults, onChange: setShowMoreResults, onSubmit: handleShowMoreTU }))));
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
401
|
+
React.createElement(Text, null, "No files found, try a different directory or change the file extensions you want to include.")));
|
|
402
|
+
}
|
|
183
403
|
}
|
|
184
|
-
if (
|
|
404
|
+
if (currentStage === AnalyzeStage.tuShowMoreDetails) {
|
|
185
405
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
186
|
-
React.createElement(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
React.createElement(Text, null,
|
|
190
|
-
"Nothing sent to zeroheight, if you want to track component usage data then remove the ",
|
|
191
|
-
React.createElement(Text, { italic: true }, "--dry-run"),
|
|
192
|
-
" option."),
|
|
193
|
-
React.createElement(Newline, null))),
|
|
194
|
-
!dryRun && React.createElement(ContinuePrompt, { onContinue: handleContinue })));
|
|
406
|
+
React.createElement(ColorUsageTable, { usage: colorUsageResult }),
|
|
407
|
+
React.createElement(Newline, null),
|
|
408
|
+
React.createElement(ContinuePrompt, { onContinue: handleSendTuData })));
|
|
195
409
|
}
|
|
196
|
-
if (
|
|
410
|
+
if (currentStage === AnalyzeStage.tuSendDataCheck) {
|
|
411
|
+
return React.createElement(ContinuePrompt, { onContinue: handleSendTuData });
|
|
412
|
+
}
|
|
413
|
+
if (currentStage === AnalyzeStage.tuSendSuccess) {
|
|
197
414
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
198
|
-
|
|
199
|
-
React.createElement(
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
"
|
|
204
|
-
|
|
205
|
-
|
|
415
|
+
React.createElement(Text, null,
|
|
416
|
+
React.createElement(Text, { color: "green" }, "Successfully sent:"),
|
|
417
|
+
" View your",
|
|
418
|
+
" ",
|
|
419
|
+
React.createElement(Link, { url: resourceURL.toString() },
|
|
420
|
+
React.createElement(Text, { underline: true, color: "#f63e7c" }, "usage data on zeroheight"))),
|
|
421
|
+
React.createElement(Newline, null),
|
|
422
|
+
React.createElement(Text, null, "\uD83D\uDC4B See you next time!")));
|
|
206
423
|
}
|
|
207
|
-
if (
|
|
208
|
-
return (React.createElement(
|
|
424
|
+
if (currentStage === AnalyzeStage.finished) {
|
|
425
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
426
|
+
React.createElement(Text, null, "\uD83D\uDC4B See you next time!")));
|
|
209
427
|
}
|
|
210
|
-
return React.createElement(Text, null, "
|
|
428
|
+
return React.createElement(Text, null, "Analzye complete");
|
|
211
429
|
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { RawColorUsageMap, RawComponentUsageMap } from "../../commands/analyze.js";
|
|
3
3
|
export interface NonInteractiveAnalyzeProps {
|
|
4
4
|
onAnalyzeFiles: () => Promise<{
|
|
5
5
|
errorFile: string | null;
|
|
6
|
-
usage:
|
|
6
|
+
usage: RawComponentUsageMap;
|
|
7
|
+
}>;
|
|
8
|
+
onAnalyzeColorUsage: () => Promise<{
|
|
9
|
+
errorFile: string | null;
|
|
10
|
+
usage: RawColorUsageMap;
|
|
7
11
|
}>;
|
|
8
12
|
repoName?: string;
|
|
13
|
+
shouldAnalyzeComponentUsage: boolean;
|
|
14
|
+
shouldAnalyzeTokenUsage: boolean;
|
|
9
15
|
}
|
|
10
|
-
export default function NonInteractiveAnalyze({ repoName, onAnalyzeFiles, }: NonInteractiveAnalyzeProps): React.JSX.Element;
|
|
16
|
+
export default function NonInteractiveAnalyze({ repoName, onAnalyzeFiles, onAnalyzeColorUsage, shouldAnalyzeComponentUsage, shouldAnalyzeTokenUsage, }: NonInteractiveAnalyzeProps): React.JSX.Element;
|
|
@@ -2,18 +2,19 @@ import React from "react";
|
|
|
2
2
|
import { Box, Newline, Text } from "ink";
|
|
3
3
|
import Spinner from "ink-spinner";
|
|
4
4
|
import Link from "ink-link";
|
|
5
|
-
import { calculateNumberOfComponents } from "../../commands/analyze.utils.js";
|
|
5
|
+
import { calculateNumberOfColors, calculateNumberOfComponents, } from "../../commands/analyze.utils.js";
|
|
6
6
|
import { readConfig } from "../../common/config.js";
|
|
7
|
-
import {
|
|
7
|
+
import { submitComponentUsageData, submitTokenLiteralUsageData, } from "../../common/api.js";
|
|
8
8
|
import { ApiError } from "../../common/errors.js";
|
|
9
|
-
export default function NonInteractiveAnalyze({ repoName, onAnalyzeFiles, }) {
|
|
9
|
+
export default function NonInteractiveAnalyze({ repoName, onAnalyzeFiles, onAnalyzeColorUsage, shouldAnalyzeComponentUsage, shouldAnalyzeTokenUsage, }) {
|
|
10
10
|
const [isSendingData, setIsSendingData] = React.useState(false);
|
|
11
11
|
const [isAnalyzingFiles, setIsAnalyzingFiles] = React.useState(false);
|
|
12
12
|
const [isFetchingCredentials, setIsFetchingCredentials] = React.useState(false);
|
|
13
13
|
const [errorFileLocation, setErrorFileLocation] = React.useState(null);
|
|
14
14
|
const [errorList, setErrorList] = React.useState([]);
|
|
15
15
|
const [credentials, setCredentials] = React.useState(null);
|
|
16
|
-
const [
|
|
16
|
+
const [componentUsage, setComponentUsage] = React.useState(null);
|
|
17
|
+
const [colorUsage, setColorUsage] = React.useState(null);
|
|
17
18
|
async function loadCredentials() {
|
|
18
19
|
setIsFetchingCredentials(true);
|
|
19
20
|
try {
|
|
@@ -31,13 +32,13 @@ export default function NonInteractiveAnalyze({ repoName, onAnalyzeFiles, }) {
|
|
|
31
32
|
}
|
|
32
33
|
return null;
|
|
33
34
|
}
|
|
34
|
-
async function
|
|
35
|
+
async function sendComponentUsageData(result, repoName, credentials) {
|
|
35
36
|
setIsSendingData(true);
|
|
36
37
|
try {
|
|
37
|
-
await
|
|
38
|
+
await submitComponentUsageData(result, repoName, credentials);
|
|
38
39
|
}
|
|
39
40
|
catch (e) {
|
|
40
|
-
let errorMessage = "Failed to send data to zeroheight";
|
|
41
|
+
let errorMessage = "Failed to send component usage data to zeroheight";
|
|
41
42
|
if (e instanceof ApiError) {
|
|
42
43
|
errorMessage = e.message;
|
|
43
44
|
}
|
|
@@ -47,10 +48,36 @@ export default function NonInteractiveAnalyze({ repoName, onAnalyzeFiles, }) {
|
|
|
47
48
|
setIsSendingData(false);
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
|
-
async function
|
|
51
|
+
async function sendTokenUsageData(result, repoName, credentials) {
|
|
52
|
+
setIsSendingData(true);
|
|
53
|
+
try {
|
|
54
|
+
await submitTokenLiteralUsageData(result, repoName, credentials);
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
let errorMessage = "Failed to send color usage data to zeroheight";
|
|
58
|
+
if (e instanceof ApiError) {
|
|
59
|
+
errorMessage = e.message;
|
|
60
|
+
}
|
|
61
|
+
setErrorList((s) => [...s, errorMessage]);
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
setIsSendingData(false);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async function analyzeComponentUsage() {
|
|
51
68
|
setIsAnalyzingFiles(true);
|
|
52
69
|
const { errorFile, usage } = await onAnalyzeFiles();
|
|
53
|
-
|
|
70
|
+
setComponentUsage(usage);
|
|
71
|
+
if (errorFile) {
|
|
72
|
+
setErrorFileLocation(errorFile);
|
|
73
|
+
}
|
|
74
|
+
setIsAnalyzingFiles(false);
|
|
75
|
+
return usage;
|
|
76
|
+
}
|
|
77
|
+
async function analyzeTokenUsage() {
|
|
78
|
+
setIsAnalyzingFiles(true);
|
|
79
|
+
const { errorFile, usage } = await onAnalyzeColorUsage();
|
|
80
|
+
setColorUsage(usage);
|
|
54
81
|
if (errorFile) {
|
|
55
82
|
setErrorFileLocation(errorFile);
|
|
56
83
|
}
|
|
@@ -62,18 +89,25 @@ export default function NonInteractiveAnalyze({ repoName, onAnalyzeFiles, }) {
|
|
|
62
89
|
const credentials = await loadCredentials();
|
|
63
90
|
if (!credentials)
|
|
64
91
|
return;
|
|
65
|
-
const usage = await anaylzeUsage();
|
|
66
92
|
if (!repoName)
|
|
67
93
|
return;
|
|
68
|
-
|
|
94
|
+
if (shouldAnalyzeComponentUsage) {
|
|
95
|
+
const componentUsageMap = await analyzeComponentUsage();
|
|
96
|
+
await sendComponentUsageData(componentUsageMap, repoName, credentials);
|
|
97
|
+
}
|
|
98
|
+
if (shouldAnalyzeTokenUsage) {
|
|
99
|
+
const colorUsageMap = await analyzeTokenUsage();
|
|
100
|
+
await sendTokenUsageData(colorUsageMap, repoName, credentials);
|
|
101
|
+
}
|
|
69
102
|
})();
|
|
70
103
|
}, []);
|
|
71
104
|
if (isAnalyzingFiles || isFetchingCredentials || isSendingData) {
|
|
72
105
|
return (React.createElement(Text, null,
|
|
73
106
|
React.createElement(Spinner, { type: "dots" }),
|
|
74
|
-
|
|
107
|
+
" ",
|
|
108
|
+
isAnalyzingFiles && React.createElement(Text, null, "Analyzing files"),
|
|
75
109
|
isFetchingCredentials && React.createElement(Text, null, "Loading credentials"),
|
|
76
|
-
isSendingData && React.createElement(Text, null, "Sending data to
|
|
110
|
+
isSendingData && React.createElement(Text, null, "Sending data to zeroheight")));
|
|
77
111
|
}
|
|
78
112
|
if (errorList.length > 0) {
|
|
79
113
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
@@ -115,12 +149,16 @@ export default function NonInteractiveAnalyze({ repoName, onAnalyzeFiles, }) {
|
|
|
115
149
|
" ",
|
|
116
150
|
React.createElement(Text, { italic: true }, "--interactive false")));
|
|
117
151
|
}
|
|
118
|
-
if (!
|
|
152
|
+
if (!componentUsage && !colorUsage) {
|
|
119
153
|
return React.createElement(Text, null, "No usage could be found.");
|
|
120
154
|
}
|
|
121
|
-
return (React.createElement(React.Fragment, null,
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
155
|
+
return (React.createElement(React.Fragment, null,
|
|
156
|
+
componentUsage && (React.createElement(Text, { color: "green" },
|
|
157
|
+
calculateNumberOfComponents(componentUsage),
|
|
158
|
+
" components found and sent to ",
|
|
159
|
+
React.createElement(Text, { color: "#f63e7c" }, "zeroheight"))),
|
|
160
|
+
colorUsage && (React.createElement(Text, { color: "green" },
|
|
161
|
+
calculateNumberOfColors(colorUsage),
|
|
162
|
+
" usages of raw colors found and sent to ",
|
|
163
|
+
React.createElement(Text, { color: "#f63e7c" }, "zeroheight")))));
|
|
126
164
|
}
|
|
@@ -22,7 +22,9 @@ export default function NoCredentialsOnboarding({ onCheckCredentials, onSaveCred
|
|
|
22
22
|
React.createElement(Text, null,
|
|
23
23
|
"If you need a new auth token, generate them from the",
|
|
24
24
|
" ",
|
|
25
|
-
React.createElement(Link, { url: "https://zeroheight.com/settings/team/developers" },
|
|
25
|
+
React.createElement(Link, { url: "https://zeroheight.com/settings/team/developers" },
|
|
26
|
+
React.createElement(Text, { underline: true, color: "#f63e7c" }, "Developer Settings"))),
|
|
27
|
+
React.createElement(Newline, null),
|
|
26
28
|
field === "client" && (React.createElement(Box, null,
|
|
27
29
|
React.createElement(Text, null, "Client ID: "),
|
|
28
30
|
React.createElement(TextInput, { onChange: setClient, value: client, onSubmit: (v) => {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
const colorTypeNameMap = {
|
|
4
|
+
hex: "HEX",
|
|
5
|
+
rgb: "RGB",
|
|
6
|
+
hsla: "HSL",
|
|
7
|
+
oklab: "Oklab",
|
|
8
|
+
hwb: "HWB",
|
|
9
|
+
lab: "LAB",
|
|
10
|
+
lch: "LCH",
|
|
11
|
+
colorSpace: "Color space",
|
|
12
|
+
totalCount: "Total usage",
|
|
13
|
+
};
|
|
14
|
+
export default function ColorUsageTable({ usage }) {
|
|
15
|
+
const rows = Array.from(usage.entries());
|
|
16
|
+
return (React.createElement(Box, { flexDirection: "column" }, rows.map(([filepath, rawColorUsage]) => {
|
|
17
|
+
const rowValues = Object.entries(rawColorUsage);
|
|
18
|
+
return (React.createElement(Box, { key: filepath, flexDirection: "column" },
|
|
19
|
+
React.createElement(Text, { color: "green" }, filepath),
|
|
20
|
+
React.createElement(Box, { flexDirection: "column", marginLeft: 2 },
|
|
21
|
+
React.createElement(Box, { flexDirection: "column", marginLeft: 2 }, rowValues.map(([type, values]) => {
|
|
22
|
+
if (type === "totalCount")
|
|
23
|
+
return null;
|
|
24
|
+
const count = values.length;
|
|
25
|
+
if (count === 0)
|
|
26
|
+
return null;
|
|
27
|
+
const name = colorTypeNameMap[type];
|
|
28
|
+
return (React.createElement(Box, { gap: 2, key: `${type}-${count}` },
|
|
29
|
+
React.createElement(Text, { bold: true },
|
|
30
|
+
name,
|
|
31
|
+
": "),
|
|
32
|
+
React.createElement(Text, null, count)));
|
|
33
|
+
})),
|
|
34
|
+
React.createElement(Text, { bold: true },
|
|
35
|
+
"Total usage: ",
|
|
36
|
+
rawColorUsage.totalCount))));
|
|
37
|
+
})));
|
|
38
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
interface LatestVersionInfoProps {
|
|
3
|
+
latestVersion: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Rich help banner with info about the latest version
|
|
7
|
+
*/
|
|
8
|
+
export default function LatestVersionInfo({ latestVersion, }: LatestVersionInfoProps): React.JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import Link from "ink-link";
|
|
4
|
+
import { CURRENT_VERSION } from "../cli.js";
|
|
5
|
+
/**
|
|
6
|
+
* Rich help banner with info about the latest version
|
|
7
|
+
*/
|
|
8
|
+
export default function LatestVersionInfo({ latestVersion, }) {
|
|
9
|
+
const isMajorVersionOut = Number(CURRENT_VERSION.split(".")[0]) < Number(latestVersion.split(".")[0]);
|
|
10
|
+
return (React.createElement(React.Fragment, null,
|
|
11
|
+
React.createElement(Box, { borderStyle: "double", paddingLeft: 1, paddingRight: 1, marginBottom: 1, flexDirection: "column" },
|
|
12
|
+
React.createElement(Box, { justifyContent: "center", marginBottom: 1 },
|
|
13
|
+
React.createElement(Text, null, "\uD83D\uDC4B It seems you're not using the latest version of this package")),
|
|
14
|
+
React.createElement(Box, { flexDirection: "column", alignItems: "center", marginBottom: 1 },
|
|
15
|
+
React.createElement(Box, null,
|
|
16
|
+
React.createElement(Text, { color: isMajorVersionOut ? "redBright" : "blueBright", bold: true }, CURRENT_VERSION),
|
|
17
|
+
React.createElement(Text, { bold: true }, ` -> `),
|
|
18
|
+
React.createElement(Text, { color: "green", bold: true }, latestVersion)),
|
|
19
|
+
React.createElement(Box, null,
|
|
20
|
+
React.createElement(Text, null, "Update using: "),
|
|
21
|
+
React.createElement(Text, { bold: true }, "npm i @zeroheight/adoption-cli"))),
|
|
22
|
+
React.createElement(Box, { flexDirection: "column", alignItems: "center" },
|
|
23
|
+
React.createElement(Text, null,
|
|
24
|
+
"For more information on the latest version, check",
|
|
25
|
+
" ",
|
|
26
|
+
React.createElement(Link, { url: "https://www.npmjs.com/package/@zeroheight/adoption-cli" }, "here"))))));
|
|
27
|
+
}
|
|
@@ -5,7 +5,10 @@ export default function ContinuePrompt({ onContinue }) {
|
|
|
5
5
|
const [shouldContinueText, setShouldContinueText] = React.useState("");
|
|
6
6
|
return (React.createElement(Box, null,
|
|
7
7
|
React.createElement(Text, null,
|
|
8
|
-
"
|
|
8
|
+
"Send this data to ",
|
|
9
|
+
React.createElement(Text, { color: "#f63e7c" }, "zeroheight"),
|
|
10
|
+
"?",
|
|
11
|
+
" ",
|
|
9
12
|
React.createElement(Text, { dimColor: true }, "(Y/n):")),
|
|
10
13
|
React.createElement(ConfirmInput, { isChecked: true, value: shouldContinueText, onChange: setShouldContinueText, onSubmit: (answer) => {
|
|
11
14
|
onContinue(answer);
|