@zeroheight/adoption-cli 0.1.2 → 0.2.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/dist/ast/analyze.js +6 -0
- package/dist/commands/analyze.d.ts +1 -0
- package/dist/commands/analyze.js +3 -2
- package/dist/commands/analyze.utils.d.ts +0 -6
- package/dist/commands/analyze.utils.js +0 -55
- package/dist/commands/auth.js +8 -2
- package/dist/common/api.d.ts +5 -0
- package/dist/common/api.js +67 -0
- package/dist/common/config.d.ts +5 -2
- package/dist/common/config.js +15 -6
- package/dist/components/analyze/analyze.d.ts +2 -1
- package/dist/components/analyze/analyze.js +33 -7
- package/dist/components/repo-name-prompt.d.ts +8 -0
- package/dist/components/repo-name-prompt.js +94 -0
- package/package.json +2 -1
package/dist/ast/analyze.js
CHANGED
|
@@ -27,6 +27,8 @@ export function analyze(ast) {
|
|
|
27
27
|
JSXElement(node, state, _nodePath) {
|
|
28
28
|
const el = node.openingElement;
|
|
29
29
|
const elName = el.name.name;
|
|
30
|
+
if (!elName)
|
|
31
|
+
return;
|
|
30
32
|
// Ignore html tags e.g. div, h1, span
|
|
31
33
|
if (elName[0] === elName[0]?.toLocaleLowerCase())
|
|
32
34
|
return;
|
|
@@ -69,6 +71,10 @@ export function analyze(ast) {
|
|
|
69
71
|
TSAsExpression() { },
|
|
70
72
|
TSDeclareFunction() { },
|
|
71
73
|
TSTypeAliasDeclaration() { },
|
|
74
|
+
TSNonNullExpression() { },
|
|
75
|
+
TSEnumDeclaration() { },
|
|
76
|
+
TSParameterProperty() { },
|
|
77
|
+
TSImportEqualsDeclaration() { },
|
|
72
78
|
};
|
|
73
79
|
try {
|
|
74
80
|
walk.ancestor(ast, visitors, base, visitorState);
|
|
@@ -5,6 +5,7 @@ interface AnalyzeOptions {
|
|
|
5
5
|
extensions: string;
|
|
6
6
|
ignore: string;
|
|
7
7
|
dryRun: boolean;
|
|
8
|
+
repoName?: string;
|
|
8
9
|
}
|
|
9
10
|
export type RawUsageMap = Map<string, RawUsage[]>;
|
|
10
11
|
export declare function analyzeAction(options: AnalyzeOptions, renderOptions?: RenderOptions): Promise<void>;
|
package/dist/commands/analyze.js
CHANGED
|
@@ -4,7 +4,7 @@ import { Command, Option } from "commander";
|
|
|
4
4
|
import Analyze from "../components/analyze/analyze.js";
|
|
5
5
|
import { analyzeFiles } from "./analyze.utils.js";
|
|
6
6
|
export async function analyzeAction(options, renderOptions) {
|
|
7
|
-
render(React.createElement(Analyze, { onAnalyzeFiles: () => analyzeFiles(options.extensions, options.ignore), dryRun: options.dryRun }), renderOptions);
|
|
7
|
+
render(React.createElement(Analyze, { onAnalyzeFiles: () => analyzeFiles(options.extensions, options.ignore), dryRun: options.dryRun, repoName: options.repoName }), renderOptions);
|
|
8
8
|
}
|
|
9
9
|
export function analyzeCommand() {
|
|
10
10
|
const command = new Command();
|
|
@@ -12,8 +12,9 @@ export function analyzeCommand() {
|
|
|
12
12
|
.command("analyze")
|
|
13
13
|
.description("Analyze your codebase to determine component usage metrics")
|
|
14
14
|
.addOption(new Option("-e, --extensions [ext]", "file extensions to include when searching for components").default("**/*.{js,jsx,ts,tsx}", "glob pattern to determine file extensions"))
|
|
15
|
-
.addOption(new Option("-i, --ignore", "files to ignore when searching for components").default("**/*.{test,spec}.*", "glob pattern to determine ignored files"))
|
|
15
|
+
.addOption(new Option("-i, --ignore [ext]", "files to ignore when searching for components").default("**/*.{test,spec}.*", "glob pattern to determine ignored files"))
|
|
16
16
|
.addOption(new Option("-d, --dry-run", "don't push results to zeroheight").default(false))
|
|
17
|
+
.addOption(new Option("-r, --repo-name <string>", "name of the repository"))
|
|
17
18
|
.action(async (options) => {
|
|
18
19
|
try {
|
|
19
20
|
await analyzeAction(options);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { RawUsageMap } from "./analyze.js";
|
|
2
|
-
import { Credentials } from "../common/config.js";
|
|
3
2
|
export interface ComponentUsageRecord {
|
|
4
3
|
name: string;
|
|
5
4
|
files: string[];
|
|
@@ -15,11 +14,6 @@ export interface ComponentUsageRecord {
|
|
|
15
14
|
* @returns list of file paths
|
|
16
15
|
*/
|
|
17
16
|
export declare function findFiles(base: string, extensions: string, ignorePattern: string): Promise<string[]>;
|
|
18
|
-
export declare function getZeroheightURL(): URL;
|
|
19
|
-
/**
|
|
20
|
-
* Post usage data to zeroheight to store
|
|
21
|
-
*/
|
|
22
|
-
export declare function submitUsageData(usage: RawUsageMap, credentials: Credentials): Promise<any>;
|
|
23
17
|
export declare function analyzeFiles(extensions: string, ignorePattern: string): Promise<{
|
|
24
18
|
errors: string[];
|
|
25
19
|
usage: RawUsageMap;
|
|
@@ -46,61 +46,6 @@ async function getGitIgnore(base) {
|
|
|
46
46
|
return "";
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
export function getZeroheightURL() {
|
|
50
|
-
if (process.env["NODE_ENV"] === "dev") {
|
|
51
|
-
return new URL("https://zeroheight.dev");
|
|
52
|
-
}
|
|
53
|
-
else {
|
|
54
|
-
return new URL("https://zeroheight.com");
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Post usage data to zeroheight to store
|
|
59
|
-
*/
|
|
60
|
-
export async function submitUsageData(usage, credentials) {
|
|
61
|
-
const baseURL = getZeroheightURL();
|
|
62
|
-
baseURL.pathname = "/open_api/v1/component_usages";
|
|
63
|
-
if (process.env["NODE_ENV"] === "dev") {
|
|
64
|
-
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";
|
|
65
|
-
}
|
|
66
|
-
const response = await fetch(baseURL, {
|
|
67
|
-
method: "POST",
|
|
68
|
-
headers: {
|
|
69
|
-
"Content-Type": "application/json",
|
|
70
|
-
"X-API-CLIENT": credentials.client,
|
|
71
|
-
"X-API-KEY": credentials.token,
|
|
72
|
-
},
|
|
73
|
-
body: JSON.stringify({
|
|
74
|
-
component_usage: {
|
|
75
|
-
usage: transformUsageByName(usage),
|
|
76
|
-
},
|
|
77
|
-
}),
|
|
78
|
-
});
|
|
79
|
-
if (response.status === 401) {
|
|
80
|
-
throw new Error("Unauthorized");
|
|
81
|
-
}
|
|
82
|
-
return await response.json();
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Transform usage map grouped by file into usage map grouped by component
|
|
86
|
-
*/
|
|
87
|
-
function transformUsageByName(usage) {
|
|
88
|
-
const transformedUsageMap = new Map();
|
|
89
|
-
for (const [file, rawUsage] of usage) {
|
|
90
|
-
for (const { count, package: packageName, name } of rawUsage) {
|
|
91
|
-
const key = `package:${packageName} name:${name}`;
|
|
92
|
-
const currentValue = transformedUsageMap.get(key);
|
|
93
|
-
const newFileList = [...(currentValue?.files ?? []), file]; // Add file to list
|
|
94
|
-
transformedUsageMap.set(key, {
|
|
95
|
-
name: currentValue?.name ?? name,
|
|
96
|
-
files: Array.from(new Set(newFileList)), // Ensure unique file paths
|
|
97
|
-
count: count + (currentValue?.count ?? 0),
|
|
98
|
-
package: packageName,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
return Array.from(transformedUsageMap.values());
|
|
103
|
-
}
|
|
104
49
|
export async function analyzeFiles(extensions, ignorePattern) {
|
|
105
50
|
const files = await findFiles(process.cwd(), extensions, ignorePattern);
|
|
106
51
|
if (files.length === 0) {
|
package/dist/commands/auth.js
CHANGED
|
@@ -11,7 +11,10 @@ export async function authAction(options, renderOptions) {
|
|
|
11
11
|
const { waitUntilExit } = render(React.createElement(CredentialsAlreadyExists, { existingConfig: existingConfig, token: options.token, client: options.client, configPath: configPath(), onOverwriteCredentials: async () => {
|
|
12
12
|
const newClient = options.client ?? existingConfig.client;
|
|
13
13
|
const newToken = options.token ?? existingConfig.token;
|
|
14
|
-
await writeConfig(
|
|
14
|
+
await writeConfig({
|
|
15
|
+
client: newClient,
|
|
16
|
+
token: newToken,
|
|
17
|
+
});
|
|
15
18
|
render(React.createElement(Box, { flexDirection: "column" },
|
|
16
19
|
React.createElement(Text, { color: "green" }, "Updated credentials"),
|
|
17
20
|
React.createElement(CredentialsPreview, { client: newClient, token: newToken })));
|
|
@@ -21,7 +24,10 @@ export async function authAction(options, renderOptions) {
|
|
|
21
24
|
else {
|
|
22
25
|
const { waitUntilExit, rerender } = render(React.createElement(NoCredentialsOnboarding, { configPath: configPath(), onSaveCredentials: async (persist, newClient, newToken) => {
|
|
23
26
|
if (persist) {
|
|
24
|
-
await writeConfig(
|
|
27
|
+
await writeConfig({
|
|
28
|
+
client: newClient,
|
|
29
|
+
token: newToken,
|
|
30
|
+
});
|
|
25
31
|
rerender(React.createElement(Box, { flexDirection: "column" },
|
|
26
32
|
React.createElement(Text, { color: "green" }, "Updated credentials"),
|
|
27
33
|
React.createElement(CredentialsPreview, { client: newClient, token: newToken })));
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { RawUsageMap } from "../commands/analyze.js";
|
|
2
|
+
import { Credentials } from "./config.js";
|
|
3
|
+
export declare function getZeroheightURL(): URL;
|
|
4
|
+
export declare function submitUsageData(usage: RawUsageMap, repoName: string, credentials: Credentials): Promise<any>;
|
|
5
|
+
export declare function getExistingRepoNames(credentials: Credentials): Promise<any>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export function getZeroheightURL() {
|
|
2
|
+
if (process.env["NODE_ENV"] === "dev") {
|
|
3
|
+
return new URL("https://zeroheight.dev");
|
|
4
|
+
}
|
|
5
|
+
else {
|
|
6
|
+
return new URL("https://zeroheight.com");
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export async function submitUsageData(usage, repoName, credentials) {
|
|
10
|
+
return post("/open_api/v1/component_usages", {
|
|
11
|
+
repo_name: repoName,
|
|
12
|
+
usage: transformUsageByName(usage),
|
|
13
|
+
}, credentials);
|
|
14
|
+
}
|
|
15
|
+
export function getExistingRepoNames(credentials) {
|
|
16
|
+
return get("/open_api/v1/component_usages/repo_names", credentials);
|
|
17
|
+
}
|
|
18
|
+
async function get(path, credentials) {
|
|
19
|
+
return request(path, credentials, {
|
|
20
|
+
method: "GET",
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async function post(path, body, credentials) {
|
|
24
|
+
return request(path, credentials, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
body: JSON.stringify(body),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
async function request(path, credentials, init) {
|
|
30
|
+
const url = getZeroheightURL();
|
|
31
|
+
url.pathname = path;
|
|
32
|
+
if (process.env["NODE_ENV"] === "dev") {
|
|
33
|
+
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = "0";
|
|
34
|
+
}
|
|
35
|
+
const response = await fetch(url, {
|
|
36
|
+
...init,
|
|
37
|
+
headers: {
|
|
38
|
+
"X-API-CLIENT": credentials.client,
|
|
39
|
+
"X-API-KEY": credentials.token,
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
if (response.status === 401) {
|
|
44
|
+
throw new Error("Unauthorized");
|
|
45
|
+
}
|
|
46
|
+
return await response.json();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Transform usage map grouped by file into usage map grouped by component
|
|
50
|
+
*/
|
|
51
|
+
function transformUsageByName(usage) {
|
|
52
|
+
const transformedUsageMap = new Map();
|
|
53
|
+
for (const [file, rawUsage] of usage) {
|
|
54
|
+
for (const { count, package: packageName, name } of rawUsage) {
|
|
55
|
+
const key = `package:${packageName} name:${name}`;
|
|
56
|
+
const currentValue = transformedUsageMap.get(key);
|
|
57
|
+
const newFileList = [...(currentValue?.files ?? []), file]; // Add file to list
|
|
58
|
+
transformedUsageMap.set(key, {
|
|
59
|
+
name: currentValue?.name ?? name,
|
|
60
|
+
files: Array.from(new Set(newFileList)), // Ensure unique file paths
|
|
61
|
+
count: count + (currentValue?.count ?? 0),
|
|
62
|
+
package: packageName,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return Array.from(transformedUsageMap.values());
|
|
67
|
+
}
|
package/dist/common/config.d.ts
CHANGED
|
@@ -2,10 +2,13 @@ export interface Credentials {
|
|
|
2
2
|
token: string;
|
|
3
3
|
client: string;
|
|
4
4
|
}
|
|
5
|
+
export interface Config extends Credentials {
|
|
6
|
+
repoNames: Record<string, string>;
|
|
7
|
+
}
|
|
5
8
|
export declare function configPath(): string;
|
|
6
9
|
/**
|
|
7
10
|
* Read the credentials from the user's home directory
|
|
8
11
|
* @returns access token and client id or null
|
|
9
12
|
*/
|
|
10
|
-
export declare function readConfig(): Promise<
|
|
11
|
-
export declare function writeConfig(
|
|
13
|
+
export declare function readConfig(): Promise<Config | null>;
|
|
14
|
+
export declare function writeConfig(newValues: Partial<Config>): Promise<void>;
|
package/dist/common/config.js
CHANGED
|
@@ -11,22 +11,31 @@ export function configPath() {
|
|
|
11
11
|
export async function readConfig() {
|
|
12
12
|
try {
|
|
13
13
|
const rawData = await readFile(configPath(), "utf8");
|
|
14
|
-
const { token, client } = JSON.parse(rawData);
|
|
15
|
-
return { token, client };
|
|
14
|
+
const { token, client, repoNames = {} } = JSON.parse(rawData);
|
|
15
|
+
return { token, client, repoNames };
|
|
16
16
|
}
|
|
17
17
|
catch (e) {
|
|
18
18
|
const client = process.env["ZEROHEIGHT_CLIENT_ID"];
|
|
19
19
|
const token = process.env["ZEROHEIGHT_ACCESS_TOKEN"];
|
|
20
20
|
if (client && token) {
|
|
21
|
-
return { token, client };
|
|
21
|
+
return { token, client, repoNames: {} };
|
|
22
22
|
}
|
|
23
23
|
return null;
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
export async function writeConfig(
|
|
26
|
+
export async function writeConfig(newValues) {
|
|
27
|
+
const config = (await readConfig()) ??
|
|
28
|
+
{
|
|
29
|
+
repoNames: {},
|
|
30
|
+
};
|
|
31
|
+
// TODO: if we have a more complex config, we should use a deep merge here
|
|
27
32
|
const payload = JSON.stringify({
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
...config,
|
|
34
|
+
...newValues,
|
|
35
|
+
repoNames: {
|
|
36
|
+
...config.repoNames,
|
|
37
|
+
...(newValues.repoNames ?? {}),
|
|
38
|
+
},
|
|
30
39
|
});
|
|
31
40
|
return writeFile(configPath(), payload);
|
|
32
41
|
}
|
|
@@ -6,5 +6,6 @@ export interface AnalyzeProps {
|
|
|
6
6
|
usage: RawUsageMap;
|
|
7
7
|
}>;
|
|
8
8
|
dryRun: boolean;
|
|
9
|
+
repoName?: string;
|
|
9
10
|
}
|
|
10
|
-
export default function Analyze({ onAnalyzeFiles, dryRun }: AnalyzeProps): React.JSX.Element;
|
|
11
|
+
export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }: AnalyzeProps): React.JSX.Element;
|
|
@@ -4,16 +4,19 @@ import { Box, Newline, Text, useApp } from "ink";
|
|
|
4
4
|
import { configPath, writeConfig, readConfig, } from "../../common/config.js";
|
|
5
5
|
import UsageTable from "../usage-table.js";
|
|
6
6
|
import ContinuePrompt from "../ui/continue-prompt.js";
|
|
7
|
-
import { getZeroheightURL, submitUsageData, } from "../../commands/analyze.utils.js";
|
|
8
7
|
import Link from "ink-link";
|
|
9
8
|
import NoCredentialsOnboarding from "../auth/no-credentials-onboarding.js";
|
|
10
|
-
|
|
9
|
+
import { getZeroheightURL, submitUsageData } from "../../common/api.js";
|
|
10
|
+
import RepoNamePrompt from "../repo-name-prompt.js";
|
|
11
|
+
export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
|
|
11
12
|
const { exit } = useApp();
|
|
12
13
|
const [usageResult, setUsageResult] = React.useState(null);
|
|
13
14
|
const [isSendingData, setIsSendingData] = React.useState(false);
|
|
14
15
|
const [errorList, setErrorList] = React.useState([]);
|
|
15
16
|
const [credentials, setCredentials] = React.useState(null);
|
|
16
17
|
const [resourceURL, setResourceURL] = React.useState(null);
|
|
18
|
+
const [repo, setRepo] = React.useState(repoName);
|
|
19
|
+
const [promptRepo, setPromptRepo] = React.useState(repoName === undefined);
|
|
17
20
|
React.useEffect(() => {
|
|
18
21
|
onAnalyzeFiles()
|
|
19
22
|
.then(({ errors, usage }) => {
|
|
@@ -22,15 +25,23 @@ export default function Analyze({ onAnalyzeFiles, dryRun }) {
|
|
|
22
25
|
})
|
|
23
26
|
.catch((e) => setErrorList((s) => [...s, e]));
|
|
24
27
|
if (!dryRun) {
|
|
25
|
-
readConfig().then((
|
|
28
|
+
readConfig().then((config) => {
|
|
29
|
+
if (config) {
|
|
30
|
+
setCredentials({ token: config.token, client: config.client });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
26
33
|
}
|
|
27
34
|
}, []);
|
|
28
35
|
async function handleContinue(shouldContinue) {
|
|
29
36
|
if (shouldContinue) {
|
|
30
|
-
if (!dryRun && credentials && usageResult) {
|
|
37
|
+
if (!dryRun && credentials && usageResult && !repo) {
|
|
38
|
+
setPromptRepo(true);
|
|
39
|
+
setRepo("");
|
|
40
|
+
}
|
|
41
|
+
else if (!dryRun && credentials && usageResult && repo) {
|
|
31
42
|
setIsSendingData(true);
|
|
32
43
|
try {
|
|
33
|
-
await submitUsageData(usageResult, credentials);
|
|
44
|
+
await submitUsageData(usageResult, repo, credentials);
|
|
34
45
|
const resourceURL = getZeroheightURL();
|
|
35
46
|
resourceURL.pathname = "/adoption/";
|
|
36
47
|
setResourceURL(resourceURL);
|
|
@@ -47,6 +58,15 @@ export default function Analyze({ onAnalyzeFiles, dryRun }) {
|
|
|
47
58
|
exit();
|
|
48
59
|
}
|
|
49
60
|
}
|
|
61
|
+
function handleOnRepoNameSelected(repoName) {
|
|
62
|
+
setRepo(repoName);
|
|
63
|
+
writeConfig({
|
|
64
|
+
repoNames: {
|
|
65
|
+
[process.cwd()]: repoName,
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
setPromptRepo(false);
|
|
69
|
+
}
|
|
50
70
|
if (errorList.length > 0) {
|
|
51
71
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
52
72
|
React.createElement(Text, { color: "red" }, "Error:"),
|
|
@@ -59,7 +79,10 @@ export default function Analyze({ onAnalyzeFiles, dryRun }) {
|
|
|
59
79
|
const newCredentials = { token: newToken, client: newClient };
|
|
60
80
|
setCredentials(newCredentials);
|
|
61
81
|
if (persist) {
|
|
62
|
-
await writeConfig(
|
|
82
|
+
await writeConfig({
|
|
83
|
+
client: newClient,
|
|
84
|
+
token: newToken,
|
|
85
|
+
});
|
|
63
86
|
}
|
|
64
87
|
} }));
|
|
65
88
|
}
|
|
@@ -91,7 +114,7 @@ export default function Analyze({ onAnalyzeFiles, dryRun }) {
|
|
|
91
114
|
" ",
|
|
92
115
|
React.createElement(Link, { url: resourceURL.toString() }, "usage data on zeroheight"))));
|
|
93
116
|
}
|
|
94
|
-
if (usageResult) {
|
|
117
|
+
if (usageResult && !promptRepo) {
|
|
95
118
|
return (React.createElement(Box, { flexDirection: "column" },
|
|
96
119
|
React.createElement(UsageTable, { usage: usageResult }),
|
|
97
120
|
dryRun && (React.createElement(React.Fragment, null,
|
|
@@ -103,5 +126,8 @@ export default function Analyze({ onAnalyzeFiles, dryRun }) {
|
|
|
103
126
|
React.createElement(Newline, null))),
|
|
104
127
|
!dryRun && React.createElement(ContinuePrompt, { onContinue: handleContinue })));
|
|
105
128
|
}
|
|
129
|
+
if (promptRepo) {
|
|
130
|
+
return (React.createElement(RepoNamePrompt, { credentials: credentials, onRepoNameSelected: handleOnRepoNameSelected }));
|
|
131
|
+
}
|
|
106
132
|
return React.createElement(Text, null, "NOPE");
|
|
107
133
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Credentials } from "../common/config.js";
|
|
3
|
+
interface RepoNamePromptProps {
|
|
4
|
+
credentials: Credentials | null;
|
|
5
|
+
onRepoNameSelected: (repoName: string) => void;
|
|
6
|
+
}
|
|
7
|
+
export default function RepoNamePrompt({ credentials, onRepoNameSelected, }: RepoNamePromptProps): React.JSX.Element | null;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import Spinner from "ink-spinner";
|
|
4
|
+
import SelectInput from "ink-select-input";
|
|
5
|
+
import { readConfig } from "../common/config.js";
|
|
6
|
+
import { getExistingRepoNames } from "../common/api.js";
|
|
7
|
+
import TextInput from "ink-text-input";
|
|
8
|
+
var RepoNamePromptState;
|
|
9
|
+
(function (RepoNamePromptState) {
|
|
10
|
+
RepoNamePromptState["LOADING"] = "LOADING";
|
|
11
|
+
RepoNamePromptState["ERROR"] = "ERROR";
|
|
12
|
+
RepoNamePromptState["SELECT_REPO_NAME"] = "SELECT_REPO_NAME";
|
|
13
|
+
RepoNamePromptState["ENTER_REPO_NAME"] = "ENTER_REPO_NAME";
|
|
14
|
+
})(RepoNamePromptState || (RepoNamePromptState = {}));
|
|
15
|
+
export default function RepoNamePrompt({ credentials, onRepoNameSelected, }) {
|
|
16
|
+
const [state, setState] = React.useState(RepoNamePromptState.LOADING);
|
|
17
|
+
const [repoNames, setRepoNames] = React.useState([]);
|
|
18
|
+
const [repoName, setRepoName] = React.useState("");
|
|
19
|
+
async function loadRepoNames(credentials) {
|
|
20
|
+
try {
|
|
21
|
+
const names = await getExistingRepoNames(credentials);
|
|
22
|
+
setRepoNames(names);
|
|
23
|
+
if (names.length === 0) {
|
|
24
|
+
setState(RepoNamePromptState.ENTER_REPO_NAME);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
setState(RepoNamePromptState.SELECT_REPO_NAME);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
console.log('e', e);
|
|
32
|
+
setState(RepoNamePromptState.ERROR);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
React.useEffect(() => {
|
|
36
|
+
if (!credentials) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
setState(RepoNamePromptState.LOADING);
|
|
40
|
+
readConfig().then((config) => {
|
|
41
|
+
const repoName = config?.repoNames?.[process.cwd()];
|
|
42
|
+
if (repoName) {
|
|
43
|
+
onRepoNameSelected(repoName);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
loadRepoNames(credentials);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}, [credentials]);
|
|
50
|
+
function onSelect(item) {
|
|
51
|
+
if (item.value === "enter-repository-name") {
|
|
52
|
+
setState(RepoNamePromptState.ENTER_REPO_NAME);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
onRepoNameSelected(item.value);
|
|
56
|
+
}
|
|
57
|
+
if (state === RepoNamePromptState.LOADING) {
|
|
58
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
59
|
+
React.createElement(Text, null,
|
|
60
|
+
React.createElement(Text, { color: "cyan" },
|
|
61
|
+
React.createElement(Spinner, { type: "dots" })),
|
|
62
|
+
" ",
|
|
63
|
+
"Fetching repository names...")));
|
|
64
|
+
}
|
|
65
|
+
if (state === RepoNamePromptState.ERROR) {
|
|
66
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
67
|
+
React.createElement(Text, { color: "red" }, "Error:"),
|
|
68
|
+
React.createElement(Box, { flexDirection: "column", marginLeft: 1 },
|
|
69
|
+
React.createElement(Text, null, "- Failed to fetch repository names"))));
|
|
70
|
+
}
|
|
71
|
+
if (state === RepoNamePromptState.SELECT_REPO_NAME) {
|
|
72
|
+
const selectItems = [
|
|
73
|
+
{ label: "Enter repository name", value: "enter-repository-name" },
|
|
74
|
+
...repoNames.map((name) => ({
|
|
75
|
+
label: name,
|
|
76
|
+
value: name,
|
|
77
|
+
})),
|
|
78
|
+
];
|
|
79
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
80
|
+
React.createElement(Text, null, "Select the repository name"),
|
|
81
|
+
React.createElement(SelectInput, { items: selectItems, onSelect: onSelect })));
|
|
82
|
+
}
|
|
83
|
+
if (state === RepoNamePromptState.ENTER_REPO_NAME) {
|
|
84
|
+
return (React.createElement(Box, null,
|
|
85
|
+
React.createElement(Text, null, "Repository name:"),
|
|
86
|
+
React.createElement(TextInput, { value: repoName, onChange: setRepoName, onSubmit: (value) => {
|
|
87
|
+
if (value.trim().length === 0) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
onRepoNameSelected(value);
|
|
91
|
+
} })));
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeroheight/adoption-cli",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"main": "dist/cli.js",
|
|
6
6
|
"bin": {
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"glob": "^10.3.15",
|
|
46
46
|
"ink-link": "^3.0.0",
|
|
47
47
|
"ink-render-string": "^1.0.0",
|
|
48
|
+
"ink-select-input": "^5.0.0",
|
|
48
49
|
"ink-spinner": "^5.0.0",
|
|
49
50
|
"ink-testing-library": "git+ssh://git@github.com/vadimdemedes/ink-testing-library.git#f44b077e9a05a1d615bab41c72906726d34ea085",
|
|
50
51
|
"ink-text-input": "^5.0.1",
|