@zeroheight/adoption-cli 0.4.4 → 1.2.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.
Files changed (35) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +49 -10
  3. package/dist/cli.js +6 -4
  4. package/dist/commands/monitor-repo.d.ts +9 -0
  5. package/dist/commands/monitor-repo.js +36 -0
  6. package/dist/commands/monitor-repo.utils.d.ts +31 -0
  7. package/dist/commands/monitor-repo.utils.js +81 -0
  8. package/dist/commands/track-package.d.ts +5 -1
  9. package/dist/commands/track-package.js +15 -5
  10. package/dist/commands/track-package.utils.d.ts +8 -14
  11. package/dist/commands/track-package.utils.js +24 -15
  12. package/dist/common/api.d.ts +30 -2
  13. package/dist/common/api.js +13 -1
  14. package/dist/common/types/package-file.d.ts +8 -0
  15. package/dist/common/types/package-file.js +1 -0
  16. package/dist/components/analyze/analyze.js +16 -3
  17. package/dist/components/auth/auth.js +16 -2
  18. package/dist/components/auth/no-credentials-onboarding.d.ts +2 -1
  19. package/dist/components/auth/no-credentials-onboarding.js +13 -3
  20. package/dist/components/monitor-repo/monitor-repo.d.ts +2 -0
  21. package/dist/components/monitor-repo/monitor-repo.js +9 -0
  22. package/dist/components/monitor-repo/non-interactive-monitor-repo.d.ts +6 -0
  23. package/dist/components/monitor-repo/non-interactive-monitor-repo.js +112 -0
  24. package/dist/components/track-package/non-interactive-track-package.d.ts +2 -0
  25. package/dist/components/track-package/non-interactive-track-package.js +113 -0
  26. package/dist/components/track-package/track-package.js +119 -11
  27. package/dist/lockfile-parsers/lock-parser.d.ts +9 -0
  28. package/dist/lockfile-parsers/lock-parser.js +5 -0
  29. package/dist/lockfile-parsers/npm-lock-parser.d.ts +6 -0
  30. package/dist/lockfile-parsers/npm-lock-parser.js +54 -0
  31. package/dist/lockfile-parsers/pnpm-lock-parser.d.ts +6 -0
  32. package/dist/lockfile-parsers/pnpm-lock-parser.js +45 -0
  33. package/dist/lockfile-parsers/yarn-lock-parser.d.ts +6 -0
  34. package/dist/lockfile-parsers/yarn-lock-parser.js +70 -0
  35. package/package.json +7 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Release notes
2
2
 
3
+ ## [1.2.0](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/1.2.0) - 17th October 2024
4
+ - Support scoped packages in Yarn lockfiles
5
+
6
+ ## [1.1.0](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/1.1.0) - 17th October 2024
7
+ - Validate authentication credentials on set up
8
+
9
+ ## [1.0.0](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/1.0.0) - 16th October 2024
10
+
11
+ New! Package adoption can now be tracked from the CLI
12
+
13
+ - Keep track of the latest version of your packages
14
+ - Understand which version of packages your repositories are using
15
+
16
+ ## [0.4.5](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.4.5) - 23rd September 2024
17
+
18
+ - Fix URL generated when using a token associated with a workspaces account
19
+
3
20
  ## [0.4.4](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.4.4) - 17th September 2024
4
21
 
5
22
  - Bug fixes
@@ -36,32 +53,42 @@
36
53
  - Stop the flow breaking due to parsing errors
37
54
 
38
55
  ## [0.2.12](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.2.12) - 24th July 2024
56
+
39
57
  - Update release notes
40
58
 
41
59
  ## [0.2.11](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.2.11) - 24th July 2024
60
+
42
61
  - Add accessible release notes
43
62
 
44
63
  ## [0.2.10](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.2.10) - 15th July 2024
64
+
45
65
  - Add ability to reset authentication credentials
46
66
 
47
67
  ## [0.2.9](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.2.9) - 20th June 2024
68
+
48
69
  - Update documentation links
49
70
 
50
71
  ## [0.2.8](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.2.8) - 19th June 2024
72
+
51
73
  - Bug fixes
52
74
 
53
75
  ## [0.2.7](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.2.7) - 19th June 2024
76
+
54
77
  - Bug fixes
55
78
 
56
79
  ## [0.2.6](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.2.6) - 19th June 2024
80
+
57
81
  - Bug fixes
58
82
 
59
83
  ## [0.2.5](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.2.5) - 14th June 2024
84
+
60
85
  - Add more support for TypeScript files
61
86
  - Allow `--ignore` to be passed as an option
62
87
 
63
88
  ## [0.2.0](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.2.0) - 10th June 2024
89
+
64
90
  - Add ability to define name of repository being analyzed
65
91
 
66
92
  ## [0.1.1](https://www.npmjs.com/package/@zeroheight/adoption-cli/v/0.1.1) - 3rd June 2024
93
+
67
94
  - Initial package set up
package/README.md CHANGED
@@ -12,6 +12,29 @@ npm i @zeroheight/adoption-cli
12
12
 
13
13
  ## Usage
14
14
 
15
+ ### Authentication
16
+
17
+ #### Interactive mode
18
+
19
+ When running a command, you will be prompted to authenticate. This will save the Client ID and Access Token to your local machine.
20
+
21
+ Alternatively, you can authenticate by running the following command:
22
+
23
+ ```
24
+ zh-adoption auth
25
+ ```
26
+
27
+ #### Non-interactive mode
28
+
29
+ When running a command with the `--interactive false` flag, you will need to provide the Client ID and Access Token as environment variables.
30
+
31
+ ```bash
32
+ export ZEROHEIGHT_CLIENT_ID="your-client-id"
33
+ export ZEROHEIGHT_ACCESS_TOKEN="your-access-token"
34
+ ```
35
+
36
+ ---
37
+
15
38
  ### Component usage analysis
16
39
 
17
40
  In the repository in which you wish to analyze the component usage, run the following command:
@@ -54,28 +77,44 @@ Pass in `false` to disable the interactive mode e.g. when running in a CI enviro
54
77
  zh-adoption analyze --interactive false -r "My Repo"
55
78
  ```
56
79
 
80
+ ---
81
+
82
+ ### Monitor repo
57
83
 
84
+ In the repository in which you wish to monitor the package versions being used, run the following command:
58
85
 
59
- To send adoption data to your [zeroheight](https://zeroheight.com/) account you will need to authenticate using a Client ID and Access Token.
86
+ ```bash
87
+ zh-adoption monitor-repo
88
+ ```
60
89
 
61
- ### Authentication
62
- #### Interactive mode
90
+ #### Options
63
91
 
64
- When running the `analyze` command, you will be prompted to authenticate. This will save the Client ID and Access Token to your local machine.
92
+ `-d` / `--dir`
65
93
 
66
- Alternatively, you can authenticate by running the following command:
94
+ Provide a path to the specific folder to find package.json and lock file. This option can be passed through multiple times. If not included all folders with package.json and lockfile will be uploaded to zeroheight.
67
95
 
96
+ ```bash
97
+ zh-adoption monitor-repo -d ./webApp
68
98
  ```
69
- zh-adoption auth
99
+
100
+ ---
101
+
102
+ ### Track package
103
+
104
+ In the repository containing the packages you wish to monitor the version of, run the following command:
105
+
106
+ ```bash
107
+ zh-adoption track-package
70
108
  ```
71
109
 
72
- #### Non-interactive mode
110
+ #### Options
73
111
 
74
- When running the `analyze` command with the `--interactive false` flag, you will need to provide the Client ID and Access Token as environment variables.
112
+ `-in` / `--interactive`
113
+
114
+ Pass in `false` to disable the interactive mode e.g. when running in a CI environment.
75
115
 
76
116
  ```bash
77
- export ZEROHEIGHT_CLIENT_ID="your-client-id"
78
- export ZEROHEIGHT_ACCESS_TOKEN="your-access-token"
117
+ zh-adoption track-package --interactive false
79
118
  ```
80
119
 
81
120
  ---
package/dist/cli.js CHANGED
@@ -5,17 +5,19 @@ import { render } from "ink-render-string";
5
5
  import HelpInfo from "./components/help-info.js";
6
6
  import { analyzeCommand } from "./commands/analyze.js";
7
7
  import { authCommand } from "./commands/auth.js";
8
- // import { trackPackageCommand } from "./commands/track-package.js";
8
+ import { monitorRepoCommand } from "./commands/monitor-repo.js";
9
+ import { trackPackageCommand } from "./commands/track-package.js";
9
10
  const program = new Command();
10
11
  const { output, cleanup } = render(React.createElement(HelpInfo, null));
11
12
  program
12
13
  .name("zh-adoption")
13
14
  .description("CLI for measuring design system usage usage in your products")
14
- .version("0.4.4")
15
+ .version("1.2.0")
15
16
  .addHelpText("before", output)
16
17
  .addCommand(analyzeCommand())
17
- .addCommand(authCommand());
18
- // .addCommand(trackPackageCommand());
18
+ .addCommand(authCommand())
19
+ .addCommand(monitorRepoCommand())
20
+ .addCommand(trackPackageCommand());
19
21
  cleanup();
20
22
  // Only start parsing if run as CLI, don't start parsing during testing
21
23
  if (process.env["NODE_ENV"] !== "test") {
@@ -0,0 +1,9 @@
1
+ import { Command } from "commander";
2
+ import { RenderOptions } from "ink";
3
+ interface MonitorRepoOptions {
4
+ interactive: boolean;
5
+ dir?: string[];
6
+ }
7
+ export declare function monitorRepoAction(options: MonitorRepoOptions, _renderOptions?: RenderOptions): Promise<void>;
8
+ export declare function monitorRepoCommand(): Command;
9
+ export {};
@@ -0,0 +1,36 @@
1
+ import * as React from "react";
2
+ import { Command, Option } from "commander";
3
+ import { render } from "ink";
4
+ // import yn from "yn";
5
+ import NonInteractiveMonitorRepo from "../components/monitor-repo/non-interactive-monitor-repo.js";
6
+ export async function monitorRepoAction(options, _renderOptions) {
7
+ // if (options.interactive) {
8
+ // render(<MonitorRepo />, renderOptions);
9
+ // } else {
10
+ render(React.createElement(NonInteractiveMonitorRepo, { packageDirs: options.dir }));
11
+ // }
12
+ }
13
+ export function monitorRepoCommand() {
14
+ const command = new Command();
15
+ return (command
16
+ .command("monitor-repo")
17
+ .description("Monitor package usage in a repository")
18
+ // .addOption(
19
+ // new Option(
20
+ // "-in, --interactive [boolean]",
21
+ // "disable to skip manual input (useful when running in CI)"
22
+ // )
23
+ // .default(true)
24
+ // .argParser((value) => yn(value))
25
+ // )
26
+ .addOption(new Option("-d, --dir <path...>", "use package directory to find package.json and lockfile"))
27
+ .action(async (options) => {
28
+ try {
29
+ await monitorRepoAction(options);
30
+ }
31
+ catch (e) {
32
+ console.error(e);
33
+ process.exitCode = 1;
34
+ }
35
+ }));
36
+ }
@@ -0,0 +1,31 @@
1
+ import NPMLockParser from "../lockfile-parsers/npm-lock-parser.js";
2
+ import PNPMLockParser from "../lockfile-parsers/pnpm-lock-parser.js";
3
+ import YarnLockParser from "../lockfile-parsers/yarn-lock-parser.js";
4
+ /**
5
+ * Group a list of paths by the directory
6
+ */
7
+ export declare function groupByBasename(paths: string[]): Record<string, string[]>;
8
+ /**
9
+ * Retrieve name and version from a package.json filepath
10
+ */
11
+ export declare function getPackageMeta(packageFile: string): Promise<{
12
+ name: string;
13
+ version: string;
14
+ }>;
15
+ export declare function parseLockfile(lockfilePath: string): Promise<NPMLockParser | PNPMLockParser | YarnLockParser>;
16
+ /**
17
+ * Find the lock file based on known lock file names
18
+ * @param subpath - provide to search inside subpath instead of working directory
19
+ */
20
+ export declare function findLockfiles(subpath?: string): Promise<string[]>;
21
+ /**
22
+ * Get the lockfile parser so name, version and packages can be extracted from the lockfile
23
+ *
24
+ * @param filename the lockfile name
25
+ * @returns the parser to use or null if unrecognized
26
+ */
27
+ export declare function getLockParserForFile(filename: string): typeof NPMLockParser | typeof PNPMLockParser | typeof YarnLockParser | null;
28
+ /**
29
+ * Get the type of lockfile given the name
30
+ */
31
+ export declare function getParserTypeFromFileName(filename: string): "yarn" | "npm" | "pnpm" | null;
@@ -0,0 +1,81 @@
1
+ import { readFile } from "fs/promises";
2
+ import { basename, dirname, join as joinPath } from "path";
3
+ import { findFiles } from "./analyze.utils.js";
4
+ import NPMLockParser from "../lockfile-parsers/npm-lock-parser.js";
5
+ import PNPMLockParser from "../lockfile-parsers/pnpm-lock-parser.js";
6
+ import YarnLockParser from "../lockfile-parsers/yarn-lock-parser.js";
7
+ /**
8
+ * Group a list of paths by the directory
9
+ */
10
+ export function groupByBasename(paths) {
11
+ return paths
12
+ .map((p) => {
13
+ return { base: dirname(p), filename: basename(p) };
14
+ })
15
+ .reduce((acc, pathObj) => {
16
+ acc[pathObj.base] = [...(acc[pathObj.base] ?? []), pathObj.filename];
17
+ return acc;
18
+ }, {});
19
+ }
20
+ /**
21
+ * Retrieve name and version from a package.json filepath
22
+ */
23
+ export async function getPackageMeta(packageFile) {
24
+ const packageContents = await readFile(packageFile, "utf-8");
25
+ const packageData = JSON.parse(packageContents);
26
+ return { name: packageData.name, version: packageData.version };
27
+ }
28
+ export async function parseLockfile(lockfilePath) {
29
+ const lockfileName = basename(lockfilePath);
30
+ const ParserClass = getLockParserForFile(lockfileName);
31
+ if (!ParserClass) {
32
+ throw new Error(`Can\'t find parser for ${lockfileName}`);
33
+ }
34
+ const fileContents = await readFile(lockfilePath, "utf-8");
35
+ return new ParserClass(fileContents);
36
+ }
37
+ /**
38
+ * Find the lock file based on known lock file names
39
+ * @param subpath - provide to search inside subpath instead of working directory
40
+ */
41
+ export async function findLockfiles(subpath) {
42
+ const workingDir = joinPath(process.cwd(), subpath ?? "");
43
+ return findFiles(workingDir, "**/**/{package-lock.json,shrinkwrap.yaml,pnpm-lock.yaml,yarn.lock}", "");
44
+ }
45
+ /**
46
+ * Get the lockfile parser so name, version and packages can be extracted from the lockfile
47
+ *
48
+ * @param filename the lockfile name
49
+ * @returns the parser to use or null if unrecognized
50
+ */
51
+ export function getLockParserForFile(filename) {
52
+ switch (filename) {
53
+ case "yarn.lock":
54
+ return YarnLockParser;
55
+ case "package-lock.json":
56
+ return NPMLockParser;
57
+ case "pnpm-lock.yaml":
58
+ return PNPMLockParser;
59
+ case "shrinkwrap.yaml":
60
+ return PNPMLockParser;
61
+ default:
62
+ return null;
63
+ }
64
+ }
65
+ /**
66
+ * Get the type of lockfile given the name
67
+ */
68
+ export function getParserTypeFromFileName(filename) {
69
+ switch (filename) {
70
+ case "yarn.lock":
71
+ return "yarn";
72
+ case "package-lock.json":
73
+ return "npm";
74
+ case "pnpm-lock.yaml":
75
+ return "pnpm";
76
+ case "shrinkwrap.yaml":
77
+ return "pnpm";
78
+ default:
79
+ return null;
80
+ }
81
+ }
@@ -1,4 +1,8 @@
1
1
  import { Command } from "commander";
2
2
  import { RenderOptions } from "ink";
3
- export declare function trackPackageAction(renderOptions?: RenderOptions): Promise<void>;
3
+ interface TrackPackageOptions {
4
+ interactive: boolean;
5
+ }
6
+ export declare function trackPackageAction(options: TrackPackageOptions, renderOptions?: RenderOptions): Promise<void>;
4
7
  export declare function trackPackageCommand(): Command;
8
+ export {};
@@ -1,18 +1,28 @@
1
1
  import * as React from "react";
2
- import { Command } from "commander";
2
+ import { Command, Option } from "commander";
3
3
  import { render } from "ink";
4
+ import yn from "yn";
5
+ import NonInteractiveTrackPackage from "../components/track-package/non-interactive-track-package.js";
4
6
  import TrackPackage from "../components/track-package/track-package.js";
5
- export async function trackPackageAction(renderOptions) {
6
- render(React.createElement(TrackPackage, null), renderOptions);
7
+ export async function trackPackageAction(options, renderOptions) {
8
+ if (options.interactive) {
9
+ render(React.createElement(TrackPackage, null), renderOptions);
10
+ }
11
+ else {
12
+ render(React.createElement(NonInteractiveTrackPackage, null));
13
+ }
7
14
  }
8
15
  export function trackPackageCommand() {
9
16
  const command = new Command();
10
17
  return command
11
18
  .command("track-package")
12
19
  .description("Track the latest version of your package in zeroheight")
13
- .action(async () => {
20
+ .addOption(new Option("-in, --interactive [boolean]", "disable to skip manual input (useful when running in CI)")
21
+ .default(true)
22
+ .argParser((value) => yn(value)))
23
+ .action(async (options) => {
14
24
  try {
15
- await trackPackageAction();
25
+ await trackPackageAction(options);
16
26
  }
17
27
  catch (e) {
18
28
  console.error(e);
@@ -1,16 +1,10 @@
1
+ import { PackageFile } from "../common/types/package-file.js";
2
+ /**
3
+ * Find the package.json file
4
+ * @param subpath - provide to search inside subpath instead of working directory
5
+ */
6
+ export declare function findPackageFiles(subpath?: string): Promise<string[]>;
1
7
  export declare function getPackageInfo(): Promise<{
2
- name: null;
3
- path: null;
4
- version: null;
5
- error: string;
6
- } | {
7
- name: any;
8
- path: string;
9
- version: any;
10
- error: null;
11
- } | {
12
- name: null;
13
- path: string;
14
- version: null;
15
- error: string;
8
+ files: PackageFile[];
9
+ error: string | null;
16
10
  }>;
@@ -1,32 +1,41 @@
1
1
  import { readFile } from "fs/promises";
2
+ import { join as joinPath } from "path";
2
3
  import { findFiles } from "./analyze.utils.js";
4
+ /**
5
+ * Find the package.json file
6
+ * @param subpath - provide to search inside subpath instead of working directory
7
+ */
8
+ export async function findPackageFiles(subpath) {
9
+ const workingDir = joinPath(process.cwd(), subpath ?? "");
10
+ return findFiles(workingDir, "**/**/package.json", "");
11
+ }
3
12
  export async function getPackageInfo() {
4
13
  const base = process.cwd();
5
- const files = await findFiles(base, "**/**/package.json", "");
14
+ const files = await findPackageFiles();
6
15
  if (files.length === 0) {
7
16
  return {
8
- name: null,
9
- path: null,
10
- version: null,
11
- error: "Can't find any package files"
17
+ files: [],
18
+ error: "Can't find any package files",
12
19
  };
13
20
  }
14
- const relativeBase = `.${files[0]?.split(base).pop()}`;
15
21
  try {
16
- const fileContents = await readFile(files[0], "utf-8");
22
+ const packageFiles = await Promise.all(files.map(async (file) => {
23
+ const fileContents = await readFile(file, "utf-8");
24
+ return {
25
+ name: JSON.parse(fileContents).name,
26
+ path: `.${files[0]?.split(base).pop()}`,
27
+ version: JSON.parse(fileContents).version,
28
+ };
29
+ }));
17
30
  return {
18
- name: JSON.parse(fileContents).name,
19
- path: relativeBase,
20
- version: JSON.parse(fileContents).version,
21
- error: null
31
+ files: packageFiles,
32
+ error: null,
22
33
  };
23
34
  }
24
35
  catch {
25
36
  return {
26
- name: null,
27
- path: relativeBase,
28
- version: null,
29
- error: `Can't parse file ${files[0]}`
37
+ files: [],
38
+ error: `Can't parse file ${files[0]}`,
30
39
  };
31
40
  }
32
41
  }
@@ -1,6 +1,34 @@
1
1
  import { RawUsageMap } from "../commands/analyze.js";
2
2
  import { Credentials } from "./config.js";
3
+ /**
4
+ * Open API V2 Response format
5
+ */
6
+ interface APIResponse<T> {
7
+ status: string;
8
+ message: string;
9
+ data: T;
10
+ }
3
11
  export declare function getZeroheightURL(): URL;
4
- export declare function submitPackageDetails(name: string, path: string, version: string, credentials: Credentials): Promise<any>;
12
+ export declare function submitPackageDetails(name: string, path: string, version: string, credentials: Credentials): Promise<APIResponse<any>>;
13
+ export declare function submitMonitoredRepoDetails(name: string, version: string, lockfilePath: string, packages: {
14
+ name: string;
15
+ version: string;
16
+ }[], credentials: Credentials): Promise<APIResponse<{
17
+ errors: string[];
18
+ }>>;
5
19
  export declare function submitUsageData(usage: RawUsageMap, repoName: string, credentials: Credentials): Promise<any>;
6
- export declare function getExistingRepoNames(credentials: Credentials): Promise<any>;
20
+ export declare function getExistingRepoNames(credentials: Credentials): Promise<string[]>;
21
+ interface AuthTokensResponse {
22
+ scopes: string[];
23
+ email: string;
24
+ project: {
25
+ id: number;
26
+ name: string;
27
+ } | null;
28
+ team: {
29
+ id: number;
30
+ name: string;
31
+ };
32
+ }
33
+ export declare function getAuthDetails(credentials: Credentials): Promise<AuthTokensResponse>;
34
+ export {};
@@ -13,6 +13,14 @@ export async function submitPackageDetails(name, path, version, credentials) {
13
13
  latest_version: version,
14
14
  }, credentials);
15
15
  }
16
+ export async function submitMonitoredRepoDetails(name, version, lockfilePath, packages, credentials) {
17
+ return post("/open_api/v2/monitored_repositories", {
18
+ name,
19
+ lock_file_path: lockfilePath,
20
+ packages,
21
+ version,
22
+ }, credentials);
23
+ }
16
24
  export async function submitUsageData(usage, repoName, credentials) {
17
25
  return post("/open_api/v1/component_usages", {
18
26
  repo_name: repoName,
@@ -22,6 +30,10 @@ export async function submitUsageData(usage, repoName, credentials) {
22
30
  export function getExistingRepoNames(credentials) {
23
31
  return get("/open_api/v1/component_usages/repo_names", credentials);
24
32
  }
33
+ export async function getAuthDetails(credentials) {
34
+ const response = await get("/open_api/v2/auth_tokens", credentials);
35
+ return response.data;
36
+ }
25
37
  async function get(path, credentials) {
26
38
  return request(path, credentials, {
27
39
  method: "GET",
@@ -42,7 +54,7 @@ async function request(path, credentials, init) {
42
54
  const response = await fetch(url, {
43
55
  ...init,
44
56
  headers: {
45
- "X-API-CLIENT-NAME": 'cli',
57
+ "X-API-CLIENT-NAME": "cli",
46
58
  "X-API-CLIENT": credentials.client,
47
59
  "X-API-KEY": credentials.token,
48
60
  "Content-Type": "application/json",
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Sparse package.json interface, only containing properties we care about
3
+ */
4
+ export interface PackageFile {
5
+ name: string;
6
+ path: string;
7
+ version: string;
8
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -8,7 +8,7 @@ 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 { getZeroheightURL, submitUsageData } from "../../common/api.js";
11
+ import { getAuthDetails, getZeroheightURL, submitUsageData, } from "../../common/api.js";
12
12
  import { calculateNumberOfComponents } from "../../commands/analyze.utils.js";
13
13
  export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
14
14
  const { exit } = useApp();
@@ -49,8 +49,12 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
49
49
  setIsSendingData(true);
50
50
  try {
51
51
  await submitUsageData(result, repoName, credentials);
52
+ const authDetails = await getAuthDetails(credentials);
53
+ const projectId = authDetails.project?.id;
52
54
  const resourceURL = getZeroheightURL();
53
- resourceURL.pathname = "/adoption/";
55
+ resourceURL.pathname = projectId
56
+ ? `/project/${projectId}/adoption/`
57
+ : "/adoption/";
54
58
  setResourceURL(resourceURL);
55
59
  }
56
60
  catch (e) {
@@ -94,6 +98,15 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
94
98
  setShowContinue(true);
95
99
  }
96
100
  }
101
+ async function handleCheckCredentials(newClient, newToken) {
102
+ try {
103
+ await getAuthDetails({ token: newToken, client: newClient });
104
+ }
105
+ catch {
106
+ setErrorList(["Auth credentials are not valid"]);
107
+ return exit();
108
+ }
109
+ }
97
110
  if (errorList.length > 0) {
98
111
  return (React.createElement(Box, { flexDirection: "column" },
99
112
  React.createElement(Text, { color: "red" }, "Error:"),
@@ -102,7 +115,7 @@ export default function Analyze({ onAnalyzeFiles, dryRun, repoName, }) {
102
115
  e))))));
103
116
  }
104
117
  if (!promptRepo && !credentials && !dryRun) {
105
- return (React.createElement(NoCredentialsOnboarding, { configPath: configPath(), onSaveCredentials: async (persist, newClient, newToken) => {
118
+ return (React.createElement(NoCredentialsOnboarding, { configPath: configPath(), onCheckCredentials: handleCheckCredentials, onSaveCredentials: async (persist, newClient, newToken) => {
106
119
  const newCredentials = { token: newToken, client: newClient };
107
120
  setCredentials(newCredentials);
108
121
  if (persist) {
@@ -1,4 +1,4 @@
1
- import { Box, Text } from "ink";
1
+ import { Box, Text, useApp } from "ink";
2
2
  import React from "react";
3
3
  import ConfirmInput from "../ui/confirm-input.js";
4
4
  import NoCredentialsOnboarding from "./no-credentials-onboarding.js";
@@ -6,6 +6,7 @@ import { configPath, readConfig, writeConfig, } from "../../common/config.js";
6
6
  import CredentialsAlreadyExists from "./credentials-already-exists.js";
7
7
  import CredentialsPreview from "./credentials-preview.js";
8
8
  import Spinner from "ink-spinner";
9
+ import { getAuthDetails } from "../../common/api.js";
9
10
  var Step;
10
11
  (function (Step) {
11
12
  Step[Step["LOADING_CONFIG"] = 0] = "LOADING_CONFIG";
@@ -14,8 +15,10 @@ var Step;
14
15
  Step[Step["DONE_WRITE_FAIL"] = 3] = "DONE_WRITE_FAIL";
15
16
  Step[Step["DONE_WRITE_SUCCESS"] = 4] = "DONE_WRITE_SUCCESS";
16
17
  Step[Step["DONE_NO_CHANGE"] = 5] = "DONE_NO_CHANGE";
18
+ Step[Step["DONE_AUTH_FAIL"] = 6] = "DONE_AUTH_FAIL";
17
19
  })(Step || (Step = {}));
18
20
  export default function Auth({ token, client }) {
21
+ const { exit } = useApp();
19
22
  const [shouldOverwriteAnswer, setShouldOverwriteAnswer] = React.useState("");
20
23
  const [existingConfig, setExistingConfig] = React.useState(null);
21
24
  const [currentStep, setCurrentStep] = React.useState(Step.LOADING_CONFIG);
@@ -64,6 +67,15 @@ export default function Auth({ token, client }) {
64
67
  setCurrentStep(Step.DONE_NO_CHANGE);
65
68
  }
66
69
  }
70
+ async function handleCheckCredentials(newClient, newToken) {
71
+ try {
72
+ await getAuthDetails({ token: newToken, client: newClient });
73
+ }
74
+ catch {
75
+ setCurrentStep(Step.DONE_AUTH_FAIL);
76
+ return exit();
77
+ }
78
+ }
67
79
  async function handleSaveCredentials(persist, newClient, newToken) {
68
80
  if (persist) {
69
81
  await mergeAndWriteConfig(existingConfig, newClient, newToken);
@@ -83,7 +95,7 @@ export default function Auth({ token, client }) {
83
95
  React.createElement(Text, { dimColor: true }, "(y/N)")),
84
96
  React.createElement(ConfirmInput, { isChecked: false, value: shouldOverwriteAnswer, onChange: setShouldOverwriteAnswer, onSubmit: handleSubmitOverwrite }))));
85
97
  case Step.PROMPT_NEW_CREDENTIALS:
86
- return (React.createElement(NoCredentialsOnboarding, { onSaveCredentials: handleSaveCredentials, configPath: configPath() }));
98
+ return (React.createElement(NoCredentialsOnboarding, { onCheckCredentials: handleCheckCredentials, onSaveCredentials: handleSaveCredentials, configPath: configPath() }));
87
99
  case Step.DONE_WRITE_SUCCESS:
88
100
  return (React.createElement(Box, { flexDirection: "column" },
89
101
  React.createElement(Text, { color: "green" }, "Updated credentials"),
@@ -94,6 +106,8 @@ export default function Auth({ token, client }) {
94
106
  existingConfig?.client && existingConfig?.token && (React.createElement(CredentialsPreview, { client: existingConfig.client, token: existingConfig.token }))));
95
107
  case Step.DONE_WRITE_FAIL:
96
108
  return React.createElement(Text, { color: "yellow" }, "Something went wrong, try again.");
109
+ case Step.DONE_AUTH_FAIL:
110
+ return (React.createElement(Text, { color: "red" }, "Unable to authenticate. Please re-run the command to try again."));
97
111
  case Step.LOADING_CONFIG:
98
112
  return (React.createElement(Text, null,
99
113
  React.createElement(Text, { color: "green" },
@@ -1,7 +1,8 @@
1
1
  import * as React from "react";
2
2
  interface NoCredentialsOnboardingProps {
3
+ onCheckCredentials: (client: string, token: string) => Promise<void>;
3
4
  onSaveCredentials: (storeCredentials: boolean, client: string, token: string) => Promise<void>;
4
5
  configPath: string;
5
6
  }
6
- export default function NoCredentialsOnboarding({ onSaveCredentials, configPath, }: NoCredentialsOnboardingProps): React.JSX.Element;
7
+ export default function NoCredentialsOnboarding({ onCheckCredentials, onSaveCredentials, configPath, }: NoCredentialsOnboardingProps): React.JSX.Element;
7
8
  export {};