@zeroheight/adoption-cli 0.3.1 → 0.4.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,5 +1,9 @@
1
1
  # Release notes
2
2
 
3
+ ## [0.4.0](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.3.1) - 2nd August 2024
4
+
5
+ - Allow `--interactive false` option for `analyze` command to better sipport CI
6
+
3
7
  ## [0.3.1](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.3.1) - 30th July 2024
4
8
 
5
9
  - Condense analyze output to be easier to parse
package/dist/cli.js CHANGED
@@ -10,7 +10,7 @@ const { output, cleanup } = render(React.createElement(HelpInfo, null));
10
10
  program
11
11
  .name("zh-adoption")
12
12
  .description("CLI for measuring design system usage usage in your products")
13
- .version("0.3.1")
13
+ .version("0.4.0")
14
14
  .addHelpText("before", output)
15
15
  .addCommand(analyzeCommand())
16
16
  .addCommand(authCommand());
@@ -6,6 +6,7 @@ interface AnalyzeOptions {
6
6
  ignore: string;
7
7
  dryRun: boolean;
8
8
  repoName?: string;
9
+ interactive: boolean;
9
10
  }
10
11
  export type RawUsageMap = Map<string, RawUsage[]>;
11
12
  export declare function analyzeAction(options: AnalyzeOptions, renderOptions?: RenderOptions): Promise<void>;
@@ -1,10 +1,17 @@
1
1
  import React from "react";
2
2
  import { render } from "ink";
3
3
  import { Command, Option } from "commander";
4
- import Analyze from "../components/analyze/analyze.js";
4
+ import yn from "yn";
5
5
  import { analyzeFiles } from "./analyze.utils.js";
6
+ import Analyze from "../components/analyze/analyze.js";
7
+ import NonInteractiveAnalyze from "../components/analyze/non-interactive-analyze.js";
6
8
  export async function analyzeAction(options, renderOptions) {
7
- render(React.createElement(Analyze, { onAnalyzeFiles: () => analyzeFiles(options.extensions, options.ignore), dryRun: options.dryRun, repoName: options.repoName }), renderOptions);
9
+ if (options.interactive) {
10
+ render(React.createElement(Analyze, { dryRun: options.dryRun, onAnalyzeFiles: () => analyzeFiles(options.extensions, options.ignore), repoName: options.repoName }), renderOptions);
11
+ }
12
+ else {
13
+ render(React.createElement(NonInteractiveAnalyze, { repoName: options.repoName, onAnalyzeFiles: () => analyzeFiles(options.extensions, options.ignore) }));
14
+ }
8
15
  }
9
16
  export function analyzeCommand() {
10
17
  const command = new Command();
@@ -15,6 +22,9 @@ export function analyzeCommand() {
15
22
  .addOption(new Option("-i, --ignore [ext]", "files to ignore when searching for components").default("**/*.{test,spec}.*", "glob pattern to determine ignored files"))
16
23
  .addOption(new Option("-d, --dry-run", "don't push results to zeroheight").default(false))
17
24
  .addOption(new Option("-r, --repo-name <string>", "name of the repository"))
25
+ .addOption(new Option("-in, --interactive [boolean]", "disable to skip input (useful when running in CI)")
26
+ .default(true)
27
+ .argParser((value) => yn(value)))
18
28
  .action(async (options) => {
19
29
  try {
20
30
  await analyzeAction(options);
@@ -18,3 +18,4 @@ export declare function analyzeFiles(extensions: string, ignorePattern: string):
18
18
  errorFile: string | null;
19
19
  usage: RawUsageMap;
20
20
  }>;
21
+ export declare function calculateNumberOfComponents(usageResult: RawUsageMap): number;
@@ -70,8 +70,23 @@ export async function analyzeFiles(extensions, ignorePattern) {
70
70
  const errorFile = `/tmp/zh-adoption-analyze-errors-${Date.now()}`;
71
71
  if (parseErrors.length > 0) {
72
72
  const file = fs.createWriteStream(errorFile);
73
- parseErrors.forEach((err) => { file.write(err + '\n'); });
73
+ parseErrors.forEach((err) => {
74
+ file.write(err + "\n");
75
+ });
74
76
  file.end();
75
77
  }
76
- return { errorFile: parseErrors.length > 0 ? errorFile : null, usage: usageMap };
78
+ return {
79
+ errorFile: parseErrors.length > 0 ? errorFile : null,
80
+ usage: usageMap,
81
+ };
82
+ }
83
+ export function calculateNumberOfComponents(usageResult) {
84
+ if (usageResult) {
85
+ const components = Array.from(usageResult.entries()).flatMap((comps) => comps[1]);
86
+ const componentCount = components.reduce((prev, next) => {
87
+ return prev + next.count;
88
+ }, 0);
89
+ return componentCount;
90
+ }
91
+ return 0;
77
92
  }
@@ -1,14 +1,15 @@
1
1
  import React from "react";
2
2
  import Spinner from "ink-spinner";
3
3
  import { Box, Newline, Text, useApp } from "ink";
4
- import { configPath, writeConfig, readConfig, } from "../../common/config.js";
5
- import UsageTable from "../usage-table.js";
6
- import ContinuePrompt from "../ui/continue-prompt.js";
7
4
  import Link from "ink-link";
5
+ import ConfirmInput from "../ui/confirm-input.js";
6
+ import ContinuePrompt from "../ui/continue-prompt.js";
8
7
  import NoCredentialsOnboarding from "../auth/no-credentials-onboarding.js";
9
- import { getZeroheightURL, submitUsageData } from "../../common/api.js";
10
8
  import RepoNamePrompt from "../repo-name-prompt.js";
11
- import ConfirmInput from "../ui/confirm-input.js";
9
+ import UsageTable from "../usage-table.js";
10
+ import { configPath, writeConfig, readConfig, } from "../../common/config.js";
11
+ import { getZeroheightURL, submitUsageData } from "../../common/api.js";
12
+ import { calculateNumberOfComponents } from "../../commands/analyze.utils.js";
12
13
  export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
13
14
  const { exit } = useApp();
14
15
  const [usageResult, setUsageResult] = React.useState(null);
@@ -22,20 +23,46 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
22
23
  const [showContinue, setShowContinue] = React.useState(false);
23
24
  const [showMoreResults, setShowMoreResults] = React.useState("");
24
25
  React.useEffect(() => {
25
- onAnalyzeFiles()
26
- .then(({ errorFile, usage }) => {
27
- setErrorFileLocation(errorFile);
28
- setUsageResult(usage);
29
- })
30
- .catch((e) => setErrorList((s) => [...s, e]));
31
- if (!dryRun) {
32
- readConfig().then((config) => {
26
+ (async () => {
27
+ try {
28
+ const { errorFile, usage } = await onAnalyzeFiles();
29
+ setErrorFileLocation(errorFile);
30
+ setUsageResult(usage);
31
+ }
32
+ catch (e) {
33
+ setErrorList((s) => [...s, e]);
34
+ }
35
+ if (dryRun)
36
+ return;
37
+ try {
38
+ const config = await readConfig();
33
39
  if (config) {
34
40
  setCredentials({ token: config.token, client: config.client });
35
41
  }
36
- });
37
- }
42
+ }
43
+ catch (e) {
44
+ setErrorList((s) => [...s, "Failed to load credentials"]);
45
+ }
46
+ })();
38
47
  }, []);
48
+ async function sendData(result, repoName, credentials) {
49
+ setIsSendingData(true);
50
+ try {
51
+ await submitUsageData(result, repoName, credentials);
52
+ const resourceURL = getZeroheightURL();
53
+ resourceURL.pathname = "/adoption/";
54
+ setResourceURL(resourceURL);
55
+ }
56
+ catch (e) {
57
+ const errorMessage = e.message === "Unauthorized"
58
+ ? "Unauthorized. Please reset your authentication by running: zh-adoption auth"
59
+ : "Failed to send data to zeroheight";
60
+ setErrorList((s) => [...s, errorMessage]);
61
+ }
62
+ finally {
63
+ setIsSendingData(false);
64
+ }
65
+ }
39
66
  async function handleContinue(shouldContinue) {
40
67
  if (shouldContinue) {
41
68
  if (!dryRun && credentials && usageResult && !repo) {
@@ -43,22 +70,7 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
43
70
  setRepo("");
44
71
  }
45
72
  else if (!dryRun && credentials && usageResult && repo) {
46
- setIsSendingData(true);
47
- try {
48
- await submitUsageData(usageResult, repo, credentials);
49
- const resourceURL = getZeroheightURL();
50
- resourceURL.pathname = "/adoption/";
51
- setResourceURL(resourceURL);
52
- }
53
- catch (e) {
54
- const errorMessage = e.message === "Unauthorized"
55
- ? "Unauthorized. Please reset your authentication by running: zh-adoption auth"
56
- : "Failed to send data to zeroheight";
57
- setErrorList((s) => [...s, errorMessage]);
58
- }
59
- finally {
60
- setIsSendingData(false);
61
- }
73
+ sendData(usageResult, repo, credentials);
62
74
  }
63
75
  }
64
76
  else {
@@ -76,22 +88,12 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
76
88
  }
77
89
  function handleShowMore(showMore) {
78
90
  if (showMore) {
79
- setShowMoreResults('true');
91
+ setShowMoreResults("true");
80
92
  }
81
93
  else {
82
94
  setShowContinue(true);
83
95
  }
84
96
  }
85
- function calculateNumberOfComponents() {
86
- if (usageResult) {
87
- const components = Array.from(usageResult.entries()).flatMap((comps) => comps[1]);
88
- const componentCount = components.reduce((prev, next) => {
89
- return prev + next.count;
90
- }, 0);
91
- return componentCount;
92
- }
93
- return 0;
94
- }
95
97
  if (errorList.length > 0) {
96
98
  return (React.createElement(Box, { flexDirection: "column" },
97
99
  React.createElement(Text, { color: "red" }, "Error:"),
@@ -99,7 +101,7 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
99
101
  "- ",
100
102
  e))))));
101
103
  }
102
- if (!credentials && !dryRun) {
104
+ if (!promptRepo && !credentials && !dryRun) {
103
105
  return (React.createElement(NoCredentialsOnboarding, { configPath: configPath(), onSaveCredentials: async (persist, newClient, newToken) => {
104
106
  const newCredentials = { token: newToken, client: newClient };
105
107
  setCredentials(newCredentials);
@@ -139,27 +141,29 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
139
141
  " ",
140
142
  React.createElement(Link, { url: resourceURL.toString() }, "usage data on zeroheight"))));
141
143
  }
142
- if (usageResult && !promptRepo && !(showMoreResults === 'true' || showContinue)) {
144
+ if (usageResult &&
145
+ !promptRepo &&
146
+ !(showMoreResults === "true" || showContinue)) {
143
147
  return (React.createElement(Box, { flexDirection: "column" },
144
148
  React.createElement(Text, { color: "green" },
145
- calculateNumberOfComponents(),
149
+ calculateNumberOfComponents(usageResult),
146
150
  " components found"),
147
151
  errorFileLocation && (React.createElement(React.Fragment, null,
148
152
  React.createElement(Newline, null),
149
153
  React.createElement(Box, null,
150
154
  React.createElement(Text, { color: "red" }, "Error:"),
151
155
  React.createElement(Text, null,
152
- ' ',
153
- "Some files could not be parsed. Errors can be found at ",
156
+ " ",
157
+ "Some files could not be parsed. Errors can be found at",
158
+ " ",
154
159
  React.createElement(Link, { url: errorFileLocation }, errorFileLocation))))),
155
160
  React.createElement(Box, null,
156
161
  React.createElement(Text, null,
157
- "Show more details?",
158
- " ",
162
+ "Show more details? ",
159
163
  React.createElement(Text, { dimColor: true }, "(y/N)")),
160
164
  React.createElement(ConfirmInput, { isChecked: false, value: showMoreResults, onChange: setShowMoreResults, onSubmit: handleShowMore }))));
161
165
  }
162
- if (usageResult && !promptRepo && showMoreResults === 'true') {
166
+ if (usageResult && !promptRepo && showMoreResults === "true") {
163
167
  return (React.createElement(Box, { flexDirection: "column" },
164
168
  React.createElement(UsageTable, { usage: usageResult }),
165
169
  dryRun && (React.createElement(React.Fragment, null,
@@ -185,5 +189,5 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
185
189
  if (promptRepo) {
186
190
  return (React.createElement(RepoNamePrompt, { credentials: credentials, onRepoNameSelected: handleOnRepoNameSelected }));
187
191
  }
188
- return React.createElement(Text, null, "NOPE");
192
+ return React.createElement(Text, null, "Done");
189
193
  }
@@ -0,0 +1,10 @@
1
+ import React from "react";
2
+ import { RawUsageMap } from "../../commands/analyze.js";
3
+ export interface NonInteractiveAnalyzeProps {
4
+ onAnalyzeFiles: () => Promise<{
5
+ errorFile: string | null;
6
+ usage: RawUsageMap;
7
+ }>;
8
+ repoName?: string;
9
+ }
10
+ export default function NonInteractiveAnalyze({ repoName, onAnalyzeFiles, }: NonInteractiveAnalyzeProps): React.JSX.Element;
@@ -0,0 +1,124 @@
1
+ import React from "react";
2
+ import { Box, Newline, Text } from "ink";
3
+ import Spinner from "ink-spinner";
4
+ import Link from "ink-link";
5
+ import { calculateNumberOfComponents } from "../../commands/analyze.utils.js";
6
+ import { readConfig } from "../../common/config.js";
7
+ import { submitUsageData } from "../../common/api.js";
8
+ export default function NonInteractiveAnalyze({ repoName, onAnalyzeFiles, }) {
9
+ const [isSendingData, setIsSendingData] = React.useState(false);
10
+ const [isAnalyzingFiles, setIsAnalyzingFiles] = React.useState(false);
11
+ const [isFetchingCredentials, setIsFetchingCredentials] = React.useState(false);
12
+ const [errorFileLocation, setErrorFileLocation] = React.useState(null);
13
+ const [errorList, setErrorList] = React.useState([]);
14
+ const [credentials, setCredentials] = React.useState(null);
15
+ const [usage, setUsage] = React.useState(null);
16
+ async function loadCredentials() {
17
+ setIsFetchingCredentials(true);
18
+ try {
19
+ const config = await readConfig();
20
+ if (config) {
21
+ setCredentials({ token: config.token, client: config.client });
22
+ }
23
+ return config;
24
+ }
25
+ catch (e) {
26
+ setErrorList((s) => [...s, "Failed to load credentials"]);
27
+ }
28
+ finally {
29
+ setIsFetchingCredentials(false);
30
+ }
31
+ return null;
32
+ }
33
+ async function sendData(result, repoName, credentials) {
34
+ setIsSendingData(true);
35
+ try {
36
+ await submitUsageData(result, repoName, credentials);
37
+ }
38
+ catch (e) {
39
+ const errorMessage = e.message === "Unauthorized"
40
+ ? "Unauthorized. Please reset your authentication by running: zh-adoption auth"
41
+ : "Failed to send data to zeroheight";
42
+ setErrorList((s) => [...s, errorMessage]);
43
+ }
44
+ finally {
45
+ setIsSendingData(false);
46
+ }
47
+ }
48
+ async function anaylzeUsage() {
49
+ setIsAnalyzingFiles(true);
50
+ const { errorFile, usage } = await onAnalyzeFiles();
51
+ setUsage(usage);
52
+ if (errorFile) {
53
+ setErrorFileLocation(errorFile);
54
+ }
55
+ setIsAnalyzingFiles(false);
56
+ return usage;
57
+ }
58
+ React.useEffect(() => {
59
+ (async () => {
60
+ const credentials = await loadCredentials();
61
+ if (!credentials)
62
+ return;
63
+ const usage = await anaylzeUsage();
64
+ if (!repoName)
65
+ return;
66
+ await sendData(usage, repoName, credentials);
67
+ })();
68
+ }, []);
69
+ if (isAnalyzingFiles || isFetchingCredentials || isSendingData) {
70
+ return (React.createElement(Text, null,
71
+ React.createElement(Spinner, { type: "dots" }),
72
+ isAnalyzingFiles && React.createElement(Text, null, "Analyzing components"),
73
+ isFetchingCredentials && React.createElement(Text, null, "Loading credentials"),
74
+ isSendingData && React.createElement(Text, null, "Sending data to zerohegiht")));
75
+ }
76
+ if (errorList.length > 0) {
77
+ return (React.createElement(Box, { flexDirection: "column" },
78
+ React.createElement(Text, { color: "red" }, "Error:"),
79
+ React.createElement(Box, { flexDirection: "column", marginLeft: 1 }, errorList.map((e) => (React.createElement(Text, { key: e },
80
+ "- ",
81
+ e))))));
82
+ }
83
+ if (errorFileLocation) {
84
+ return (React.createElement(React.Fragment, null,
85
+ React.createElement(Newline, null),
86
+ React.createElement(Box, null,
87
+ React.createElement(Text, { color: "red" }, "Error:"),
88
+ React.createElement(Text, null,
89
+ " ",
90
+ "Some files could not be parsed. Errors can be found at",
91
+ " ",
92
+ React.createElement(Link, { url: errorFileLocation }, errorFileLocation)))));
93
+ }
94
+ if (!repoName) {
95
+ return (React.createElement(Text, null,
96
+ React.createElement(Text, { bold: true }, "Repo name"),
97
+ " not found, run the ",
98
+ React.createElement(Text, { bold: true }, "analyze"),
99
+ " ",
100
+ "command in interactive mode or provide the",
101
+ " ",
102
+ React.createElement(Text, { italic: true },
103
+ "--repo-name ",
104
+ "<my repo name>"),
105
+ " option"));
106
+ }
107
+ if (!credentials) {
108
+ return (React.createElement(Text, null,
109
+ "Credentials not found, please ensure they are set before running the",
110
+ " ",
111
+ React.createElement(Text, { bold: true }, "analyze"),
112
+ " command with",
113
+ " ",
114
+ React.createElement(Text, { italic: true }, "--interactive false")));
115
+ }
116
+ if (!usage) {
117
+ return React.createElement(Text, null, "No usage could be found.");
118
+ }
119
+ return (React.createElement(React.Fragment, null, usage && (React.createElement(Text, { color: "green" },
120
+ calculateNumberOfComponents(usage),
121
+ " components found and sent to",
122
+ " ",
123
+ React.createElement(Text, { color: "#f63e7c" }, "zeroheight")))));
124
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroheight/adoption-cli",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "license": "ISC",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {