@zeroheight/adoption-cli 2.4.2 → 3.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/CHANGELOG.md CHANGED
@@ -1,12 +1,25 @@
1
1
  # Release notes
2
2
 
3
+ ## [3.0.0](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/3.0.0) - 15th March 2025
4
+
5
+ - `analyze` command updated to collect date about color usage
6
+ - For non-interactive command new flags introduced: `--color-usage` and `--component-usage`
7
+ - Improvement to usability of interactive `analyze` command
8
+ - Remove `--dry-run` from interactive `analyze`
9
+ - Additional prompt added if using an outdated version of the CLI tool
10
+ - Allow `track-package` command to use package where version is unspecified
11
+
12
+ ## [2.4.3](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/2.4.3) - 2nd January 2025
13
+
14
+ - `track-package --packages` option can be used to allow tracking specific packages within monorepos in non-interactive mode
15
+
3
16
  ## [2.4.2](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/2.4.2) - 17th December 2024
4
17
 
5
18
  - Fix issue with parsing pnpm lockfiles using version `6.0`
6
19
 
7
20
  ## [2.4.1](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/2.4.1) - 11th December 2024
8
21
 
9
- - The monitor-repo command now supports custom package naming for projects.
22
+ - The `monitor-repo` command now supports custom package naming for projects.
10
23
 
11
24
  ## [2.4.0](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/2.4.0) - 10th December 2024
12
25
 
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
- #### Options
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 searching for components.
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}.*"
@@ -186,6 +196,18 @@ Pass in `false` to disable the interactive mode e.g. when running in a CI enviro
186
196
  zh-adoption track-package --interactive false
187
197
  ```
188
198
 
199
+ `-p` / `--packages`
200
+
201
+ Provide one or multiple package names to match when searching for design system packages. This must be passed when `-in` / `--interactive` is set to `false`.
202
+
203
+ ```bash
204
+ zh-adoption track-package -in false -p package1 -p package2 -p package3
205
+ ```
206
+
207
+ ```bash
208
+ zh-adoption track-package -in false --packages package1,package2,package3
209
+ ```
210
+
189
211
  `--log-level`
190
212
 
191
213
  Provide a severity to output diagnostic messages.
@@ -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
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
  declare const program: Command;
4
+ export declare const CURRENT_VERSION = "3.0.0";
4
5
  export default program;
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 { output, cleanup } = render(React.createElement(HelpInfo, null));
13
+ export const CURRENT_VERSION = "3.0.0";
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("2.4.2")
17
- .addHelpText("before", output)
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
- cleanup();
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
+ tokenUsage: boolean;
10
11
  }
11
- export type RawUsageMap = Map<string, RawUsage[]>;
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 {};
@@ -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, { dryRun: options.dryRun, onAnalyzeFiles: () => analyzeFiles(options.extensions, options.ignore), repoName: options.repoName }), renderOptions);
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.tokenUsage }));
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 { RawUsageMap } from "./analyze.js";
3
- import { ComponentProps } from "../ast/analyze.js";
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: RawUsageMap;
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
  */
@@ -2,6 +2,7 @@ import { Command } from "commander";
2
2
  import { RenderOptions } from "ink";
3
3
  interface TrackPackageOptions {
4
4
  interactive: boolean;
5
+ packages?: string[];
5
6
  }
6
7
  export declare function trackPackageAction(options: TrackPackageOptions, renderOptions?: RenderOptions): Promise<void>;
7
8
  export declare function trackPackageCommand(): Command;
@@ -5,12 +5,13 @@ import yn from "yn";
5
5
  import NonInteractiveTrackPackage from "../components/track-package/non-interactive-track-package.js";
6
6
  import TrackPackage from "../components/track-package/track-package.js";
7
7
  import { setStdErrStream } from "../common/logging.js";
8
+ import { parsePackageList } from "./track-package.utils.js";
8
9
  export async function trackPackageAction(options, renderOptions) {
9
10
  if (options.interactive) {
10
11
  render(React.createElement(TrackPackage, null), renderOptions);
11
12
  }
12
13
  else {
13
- render(React.createElement(NonInteractiveTrackPackage, null));
14
+ render(React.createElement(NonInteractiveTrackPackage, { allowedPackages: options.packages }));
14
15
  }
15
16
  }
16
17
  export function trackPackageCommand() {
@@ -24,6 +25,7 @@ export function trackPackageCommand() {
24
25
  .addOption(new Option("-in, --interactive [boolean]", "disable to skip manual input (useful when running in CI)")
25
26
  .default(true)
26
27
  .argParser((value) => yn(value)))
28
+ .addOption(new Option("-p, --packages [packageNames]", "specify the packages to search for, use multiple times to match more than one package").argParser(parsePackageList))
27
29
  .action(async (options) => {
28
30
  if (!options.interactive) {
29
31
  setStdErrStream();
@@ -4,7 +4,14 @@ import { PackageFile } from "../common/types/package-file.js";
4
4
  * @param subpath - provide to search inside subpath instead of working directory
5
5
  */
6
6
  export declare function findPackageFiles(subpath?: string): Promise<string[]>;
7
- export declare function getPackageInfo(): Promise<{
7
+ /**
8
+ * Get allowed package files from the directory and return
9
+ * their name/package/version/exports
10
+ *
11
+ * @param allowedPackages a list of package names to match on
12
+ * @returns a list of matched packages files and errors
13
+ */
14
+ export declare function getPackageInfo(allowedPackages?: string[]): Promise<{
8
15
  files: PackageFile[];
9
16
  error: string | null;
10
17
  }>;
@@ -12,3 +19,7 @@ export declare function getPackageInfo(): Promise<{
12
19
  * Transform exports object into fully qualified aliases
13
20
  */
14
21
  export declare function getAliasesFromExports(packageName: string, exports?: PackageFile["exports"]): string[];
22
+ /**
23
+ * Parse package list and format correctly
24
+ */
25
+ export declare function parsePackageList(value: string, previous?: string[]): string[];
@@ -9,7 +9,14 @@ export async function findPackageFiles(subpath) {
9
9
  const workingDir = joinPath(process.cwd(), subpath ?? "");
10
10
  return findFiles(workingDir, "**/**/package.json", []);
11
11
  }
12
- export async function getPackageInfo() {
12
+ /**
13
+ * Get allowed package files from the directory and return
14
+ * their name/package/version/exports
15
+ *
16
+ * @param allowedPackages a list of package names to match on
17
+ * @returns a list of matched packages files and errors
18
+ */
19
+ export async function getPackageInfo(allowedPackages) {
13
20
  const base = process.cwd();
14
21
  const files = await findPackageFiles();
15
22
  if (files.length === 0) {
@@ -19,16 +26,26 @@ export async function getPackageInfo() {
19
26
  };
20
27
  }
21
28
  try {
22
- const packageFiles = await Promise.all(files.map(async (file) => {
29
+ let packageFiles = await Promise.all(files.map(async (file) => {
23
30
  const fileContents = await readFile(file, "utf-8");
24
31
  const parsedPackage = JSON.parse(fileContents);
25
32
  return {
26
33
  name: parsedPackage.name,
27
- path: `.${files[0]?.split(base).pop()}`,
28
- version: parsedPackage.version,
34
+ path: `.${file.split(base).pop()}`,
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",
29
37
  exports: parsedPackage.exports,
30
38
  };
31
39
  }));
40
+ if (allowedPackages?.length) {
41
+ packageFiles = packageFiles.filter((pack) => allowedPackages.includes(pack.name));
42
+ if (!packageFiles.length) {
43
+ return {
44
+ files: [],
45
+ error: "Can't find any matching packages",
46
+ };
47
+ }
48
+ }
32
49
  return {
33
50
  files: packageFiles,
34
51
  error: null,
@@ -51,3 +68,15 @@ export function getAliasesFromExports(packageName, exports) {
51
68
  .map((path) => joinPath(packageName, path))
52
69
  .filter((name) => name !== packageName);
53
70
  }
71
+ /**
72
+ * Parse package list and format correctly
73
+ */
74
+ export function parsePackageList(value, previous = []) {
75
+ return [
76
+ ...previous,
77
+ ...value
78
+ .split(",")
79
+ .map((entry) => entry.trim())
80
+ .filter(Boolean),
81
+ ];
82
+ }
@@ -1,6 +1,20 @@
1
1
  import { ComponentProps } from "../ast/analyze.js";
2
- import { RawUsageMap } from "../commands/analyze.js";
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 submitUsageData(usage: RawUsageMap, repoName: string, credentials: Credentials): Promise<APIResponse<ComponentUsageDetailsSuccess>>;
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
  }
@@ -31,12 +31,20 @@ export async function submitMonitoredRepoDetails(name, version, lockfilePath, pa
31
31
  version,
32
32
  }, credentials);
33
33
  }
34
- export async function submitUsageData(usage, repoName, credentials) {
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 { RawUsageMap } from "../../commands/analyze.js";
2
+ import { RawColorUsageMap, RawComponentUsageMap } from "../../commands/analyze.js";
3
3
  export interface AnalyzeProps {
4
4
  onAnalyzeFiles: () => Promise<{
5
5
  errorFile: string | null;
6
- usage: RawUsageMap;
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, dryRun, repoName, }: AnalyzeProps): React.JSX.Element;
14
+ export default function Analyze({ onAnalyzeFiles, onAnalyzeColorUsage, repoName, }: AnalyzeProps): React.JSX.Element;