@herodevs/cli 2.0.0-beta.4 → 2.0.0-beta.6

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 (55) hide show
  1. package/README.md +143 -109
  2. package/dist/api/gql-operations.d.ts +2 -0
  3. package/dist/api/gql-operations.js +36 -0
  4. package/dist/api/nes.client.d.ts +12 -0
  5. package/dist/api/nes.client.js +71 -0
  6. package/dist/commands/scan/eol.d.ts +12 -12
  7. package/dist/commands/scan/eol.js +163 -104
  8. package/dist/config/constants.d.ts +8 -3
  9. package/dist/config/constants.js +18 -3
  10. package/dist/hooks/finally.d.ts +3 -0
  11. package/dist/hooks/finally.js +14 -0
  12. package/dist/hooks/prerun.js +12 -0
  13. package/dist/service/analytics.svc.d.ts +28 -0
  14. package/dist/service/analytics.svc.js +112 -0
  15. package/dist/service/{eol/cdx.svc.d.ts → cdx.svc.d.ts} +8 -16
  16. package/dist/service/{eol/cdx.svc.js → cdx.svc.js} +17 -7
  17. package/dist/service/display.svc.d.ts +22 -0
  18. package/dist/service/display.svc.js +72 -0
  19. package/dist/service/file.svc.d.ts +20 -0
  20. package/dist/service/file.svc.js +71 -0
  21. package/dist/service/log.svc.d.ts +1 -0
  22. package/dist/service/log.svc.js +9 -0
  23. package/dist/service/{eol/sbom.worker.js → sbom.worker.js} +1 -1
  24. package/package.json +23 -16
  25. package/dist/api/client.d.ts +0 -12
  26. package/dist/api/client.js +0 -43
  27. package/dist/api/nes/nes.client.d.ts +0 -24
  28. package/dist/api/nes/nes.client.js +0 -127
  29. package/dist/api/queries/nes/sbom.d.ts +0 -3
  30. package/dist/api/queries/nes/sbom.js +0 -39
  31. package/dist/api/queries/nes/telemetry.d.ts +0 -2
  32. package/dist/api/queries/nes/telemetry.js +0 -24
  33. package/dist/api/types/hd-cli.types.d.ts +0 -30
  34. package/dist/api/types/hd-cli.types.js +0 -10
  35. package/dist/api/types/nes.types.d.ts +0 -58
  36. package/dist/api/types/nes.types.js +0 -1
  37. package/dist/commands/report/committers.d.ts +0 -23
  38. package/dist/commands/report/committers.js +0 -147
  39. package/dist/commands/report/purls.d.ts +0 -15
  40. package/dist/commands/report/purls.js +0 -85
  41. package/dist/commands/scan/sbom.d.ts +0 -21
  42. package/dist/commands/scan/sbom.js +0 -164
  43. package/dist/service/committers.svc.d.ts +0 -70
  44. package/dist/service/committers.svc.js +0 -196
  45. package/dist/service/eol/eol.svc.d.ts +0 -12
  46. package/dist/service/eol/eol.svc.js +0 -25
  47. package/dist/service/error.svc.d.ts +0 -8
  48. package/dist/service/error.svc.js +0 -28
  49. package/dist/service/nes/nes.svc.d.ts +0 -5
  50. package/dist/service/nes/nes.svc.js +0 -36
  51. package/dist/service/purls.svc.d.ts +0 -23
  52. package/dist/service/purls.svc.js +0 -99
  53. package/dist/ui/shared.ui.d.ts +0 -4
  54. package/dist/ui/shared.ui.js +0 -14
  55. /package/dist/service/{eol/sbom.worker.d.ts → sbom.worker.d.ts} +0 -0
package/README.md CHANGED
@@ -14,8 +14,8 @@ The HeroDevs CLI
14
14
 
15
15
  1. Install node v20 or higher: [Download Node](https://nodejs.org/en/download)
16
16
  1. Install the CLI using one of the following methods:
17
- - Globally: Refer to the [Usage](#usage) instructions on installing the CLI globally
18
- - Npx:`npx @herodevs/cli@beta <commands>`
17
+ * Globally: Refer to the [Usage](#usage) instructions on installing the CLI globally
18
+ * npx: `npx @herodevs/cli@beta`
19
19
  1. Refer to the [Commands](#commands) section for a list of commands
20
20
 
21
21
  ## TERMS
@@ -24,21 +24,20 @@ Use of this CLI is governed by the [HeroDevs End of Life Dataset Terms of Servic
24
24
 
25
25
  ## Scanning Behavior
26
26
 
27
- The CLI's scanning commands (`hd scan eol` and `hd scan sbom`) are designed to be non-invasive:
27
+ The CLI is designed to be non-invasive:
28
28
 
29
- * They do not install dependencies or modify package manager files (package-lock.json, yarn.lock, etc.)
30
- * They analyze the project in its current state
29
+ * It does not install dependencies or modify package manager files (package-lock.json, yarn.lock, etc.)
30
+ * It analyzes the project in its current state
31
31
  * If you need dependencies installed for accurate scanning, please install them manually before running the scan
32
32
 
33
-
34
33
  ## Usage
35
34
  <!-- usage -->
36
35
  ```sh-session
37
- $ npm install -g @herodevs/cli
36
+ $ npm install -g @herodevs/cli@beta
38
37
  $ hd COMMAND
39
38
  running command...
40
39
  $ hd (--version)
41
- @herodevs/cli/2.0.0-beta.4 linux-x64 node-v22.16.0
40
+ @herodevs/cli/2.0.0-beta.6 darwin-arm64 node-v22.18.0
42
41
  $ hd --help [COMMAND]
43
42
  USAGE
44
43
  $ hd COMMAND
@@ -48,10 +47,7 @@ USAGE
48
47
  ## Commands
49
48
  <!-- commands -->
50
49
  * [`hd help [COMMAND]`](#hd-help-command)
51
- * [`hd report committers`](#hd-report-committers)
52
- * [`hd report purls`](#hd-report-purls)
53
50
  * [`hd scan eol`](#hd-scan-eol)
54
- * [`hd scan sbom`](#hd-scan-sbom)
55
51
  * [`hd update [CHANNEL]`](#hd-update-channel)
56
52
 
57
53
  ## `hd help [COMMAND]`
@@ -72,168 +68,206 @@ DESCRIPTION
72
68
  Display help for hd.
73
69
  ```
74
70
 
75
- _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.29/src/commands/help.ts)_
71
+ _See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.32/src/commands/help.ts)_
76
72
 
77
- ## `hd report committers`
73
+ ## `hd scan eol`
78
74
 
79
- Generate report of committers to a git repository
75
+ Scan a given SBOM for EOL data
80
76
 
81
77
  ```
82
78
  USAGE
83
- $ hd report committers [--json] [-m <value>] [-c] [-s]
79
+ $ hd scan eol [--json] [-f <value> | -d <value>] [-s] [--saveSbom]
84
80
 
85
81
  FLAGS
86
- -c, --csv Output in CSV format
87
- -m, --months=<value> [default: 12] The number of months of git history to review
88
- -s, --save Save the committers report as herodevs.committers.<output>
82
+ -d, --dir=<value> [default: <current directory>] The directory to scan in order to create a cyclonedx SBOM
83
+ -f, --file=<value> The file path of an existing cyclonedx SBOM to scan for EOL
84
+ -s, --save Save the generated report as herodevs.report.json in the scanned directory
85
+ --saveSbom Save the generated SBOM as herodevs.sbom.json in the scanned directory
89
86
 
90
87
  GLOBAL FLAGS
91
88
  --json Format output as json.
92
89
 
93
90
  DESCRIPTION
94
- Generate report of committers to a git repository
91
+ Scan a given SBOM for EOL data
95
92
 
96
93
  EXAMPLES
97
- $ hd report committers
94
+ Default behavior (no command or flags specified)
95
+
96
+ $ hd
97
+
98
+ Equivalent to
99
+
100
+ $ hd scan eol --dir .
98
101
 
99
- $ hd report committers --csv -s
102
+ Skip SBOM generation and specify an existing file
100
103
 
101
- $ hd report committers --json
104
+ $ hd scan eol --file /path/to/sbom.json
102
105
 
103
- $ hd report committers --csv
106
+ Save the report or SBOM to a file
107
+
108
+ $ hd scan eol --save --saveSbom
109
+
110
+ Output the report in JSON format (for APIs, CI, etc.)
111
+
112
+ $ hd scan eol --json
104
113
  ```
105
114
 
106
- _See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.4/src/commands/report/committers.ts)_
115
+ _See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.5/src/commands/scan/eol.ts)_
107
116
 
108
- ## `hd report purls`
117
+ ## `hd update [CHANNEL]`
109
118
 
110
- Generate a list of purls from a sbom
119
+ update the hd CLI
111
120
 
112
121
  ```
113
122
  USAGE
114
- $ hd report purls [--json] [-f <value>] [-d <value>] [-s] [-c]
123
+ $ hd update [CHANNEL] [--force | | [-a | -v <value> | -i]] [-b ]
115
124
 
116
125
  FLAGS
117
- -c, --csv Save output in CSV format (only applies when using --save)
118
- -d, --dir=<value> The directory to scan in order to create a cyclonedx sbom
119
- -f, --file=<value> The file path of an existing cyclonedx sbom to scan for EOL
120
- -s, --save Save the list of purls as herodevs.purls.<output>
121
-
122
- GLOBAL FLAGS
123
- --json Format output as json.
126
+ -a, --available See available versions.
127
+ -b, --verbose Show more details about the available versions.
128
+ -i, --interactive Interactively select version to install. This is ignored if a channel is provided.
129
+ -v, --version=<value> Install a specific version.
130
+ --force Force a re-download of the requested version.
124
131
 
125
132
  DESCRIPTION
126
- Generate a list of purls from a sbom
133
+ update the hd CLI
127
134
 
128
135
  EXAMPLES
129
- $ hd report purls --json -s
130
-
131
- $ hd report purls --dir=./my-project
136
+ Update to the stable channel:
132
137
 
133
- $ hd report purls --file=path/to/sbom.json
138
+ $ hd update stable
134
139
 
135
- $ hd report purls --dir=./my-project --save
140
+ Update to a specific version:
136
141
 
137
- $ hd report purls --save --csv
138
- ```
142
+ $ hd update --version 1.0.0
139
143
 
140
- _See code: [src/commands/report/purls.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.4/src/commands/report/purls.ts)_
144
+ Interactively select version:
141
145
 
142
- ## `hd scan eol`
146
+ $ hd update --interactive
143
147
 
144
- Scan a given sbom for EOL data
148
+ See available versions:
145
149
 
150
+ $ hd update --available
146
151
  ```
147
- USAGE
148
- $ hd scan eol [--json] [-f <value>] [-p <value>] [-d <value>] [-s]
149
-
150
- FLAGS
151
- -d, --dir=<value> The directory to scan in order to create a cyclonedx sbom
152
- -f, --file=<value> The file path of an existing cyclonedx sbom to scan for EOL
153
- -p, --purls=<value> The file path of a list of purls to scan for EOL
154
- -s, --save Save the generated report as herodevs.report.json in the scanned directory
155
152
 
156
- GLOBAL FLAGS
157
- --json Format output as json.
153
+ _See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.7.4/src/commands/update.ts)_
154
+ <!-- commandsstop -->
158
155
 
159
- DESCRIPTION
160
- Scan a given sbom for EOL data
156
+ ## CI/CD Usage
161
157
 
162
- EXAMPLES
163
- $ hd scan eol --dir=./my-project
158
+ You can use `@herodevs/cli` in your CI/CD pipelines to automate EOL scanning.
164
159
 
165
- $ hd scan eol --file=path/to/sbom.json
160
+ ### Using the Docker Image (recommended)
166
161
 
167
- $ hd scan eol --purls=path/to/purls.json
162
+ We provide a Docker image that's pre-configured to run EOL scans. Based on [`cdxgen`](https://github.com/CycloneDX/cdxgen),
163
+ it contains build tools for most project types and will provide best results when generating an SBOM. Use these templates to generate a report and save it to your CI job artifact for analysis and processing after your scan runs.
168
164
 
169
- $ hd scan eol -a --dir=./my-project
170
- ```
165
+ #### GitHub Actions
171
166
 
172
- _See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.4/src/commands/scan/eol.ts)_
167
+ ```yaml
168
+ ## .github/workflows/herodevs-eol-scan.yml
169
+ name: HeroDevs EOL Scan
173
170
 
174
- ## `hd scan sbom`
171
+ on:
172
+ push:
173
+ branches: [ main ]
174
+ pull_request:
175
+ branches: [ main ]
175
176
 
176
- Scan a SBOM for purls
177
+ jobs:
178
+ scan:
179
+ runs-on: ubuntu-latest
180
+ steps:
181
+ - uses: actions/checkout@v4
177
182
 
183
+ - name: Run EOL Scan with Docker
184
+ uses: docker://ghcr.io/herodevs/eol-scan
185
+ with:
186
+ args: "-s"
187
+
188
+ - name: Upload artifact
189
+ uses: actions/upload-artifact@v4
190
+ with:
191
+ name: my-eol-report
192
+ path: herodevs.report.json
178
193
  ```
179
- USAGE
180
- $ hd scan sbom [--json] [-f <value>] [-d <value>] [-s] [-b]
181
194
 
182
- FLAGS
183
- -b, --background Run the scan in the background
184
- -d, --dir=<value> The directory to scan in order to create a cyclonedx sbom
185
- -f, --file=<value> The file path of an existing cyclonedx sbom to scan for EOL
186
- -s, --save Save the generated SBOM as herodevs.sbom.json in the scanned directory
195
+ #### GitLab CI/CD
196
+
197
+ ```yaml
198
+ eol-scan:
199
+ image:
200
+ name: "ghcr.io/herodevs/eol-scan"
201
+ # Entrypoint or base command must be disabled due
202
+ # to GitLab's execution mechanism and run manually
203
+ entrypoint: [""]
204
+ script: "npx @herodevs/cli@beta scan eol -s"
205
+ artifacts:
206
+ paths:
207
+ - herodevs.report.json
208
+ ```
187
209
 
188
- GLOBAL FLAGS
189
- --json Format output as json.
210
+ ### Using `npx`
190
211
 
191
- DESCRIPTION
192
- Scan a SBOM for purls
212
+ You can use `npx` to run the CLI just like you'd run it locally.
193
213
 
194
- EXAMPLES
195
- $ hd scan sbom --dir=./my-project
214
+ > [!NOTE]
215
+ > The development environment is expected to be ready to run the app. For best results,
216
+ prefer [using the prebuilt image](#using-the-docker-image-recommended), but otherwise, prepare
217
+ all requirements before the scan step.
196
218
 
197
- $ hd scan sbom --file=path/to/sbom.json
198
- ```
219
+ #### GitHub Actions
199
220
 
200
- _See code: [src/commands/scan/sbom.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.4/src/commands/scan/sbom.ts)_
221
+ ```yaml
222
+ ## .github/workflows/herodevs-eol-scan.yml
223
+ name: HeroDevs EOL Scan
201
224
 
202
- ## `hd update [CHANNEL]`
203
-
204
- update the hd CLI
225
+ on:
226
+ push:
227
+ branches: [ main ]
228
+ pull_request:
229
+ branches: [ main ]
205
230
 
206
- ```
207
- USAGE
208
- $ hd update [CHANNEL] [--force | | [-a | -v <value> | -i]] [-b ]
209
-
210
- FLAGS
211
- -a, --available See available versions.
212
- -b, --verbose Show more details about the available versions.
213
- -i, --interactive Interactively select version to install. This is ignored if a channel is provided.
214
- -v, --version=<value> Install a specific version.
215
- --force Force a re-download of the requested version.
231
+ jobs:
232
+ scan:
233
+ runs-on: ubuntu-latest
234
+ steps:
235
+ - uses: actions/checkout@v4
236
+ - uses: actions/setup-node@v4
237
+ with:
238
+ node-version: '20'
216
239
 
217
- DESCRIPTION
218
- update the hd CLI
240
+ - run: echo # Prepare environment, install tooling, perform setup, etc.
219
241
 
220
- EXAMPLES
221
- Update to the stable channel:
242
+ - name: Run EOL Scan
243
+ run: npx @herodevs/cli@beta
222
244
 
223
- $ hd update stable
245
+ - name: Upload artifact
246
+ uses: actions/upload-artifact@v4
247
+ with:
248
+ name: my-eol-report
249
+ path: herodevs.report.json
250
+ ```
224
251
 
225
- Update to a specific version:
252
+ #### GitLab CI/CD
226
253
 
227
- $ hd update --version 1.0.0
254
+ ```yaml
255
+ image: alpine
228
256
 
229
- Interactively select version:
257
+ eol-scan:
258
+ script:
259
+ - echo # Prepare environment, install tooling, perform setup, etc.
260
+ - npx @herodevs/cli@beta scan eol -s
261
+ artifacts:
262
+ paths:
263
+ - herodevs.report.json
264
+ ```
230
265
 
231
- $ hd update --interactive
266
+ ## Local Docker image scans
232
267
 
233
- See available versions:
268
+ The same pre-configured image can be pulled locally to scan in an optimized environment. Mount your code
269
+ to `/app` or a specified working directory to perform the scan:
234
270
 
235
- $ hd update --available
271
+ ```shell
272
+ docker run -v "$PWD":/app ghcr.io/herodevs/eol-scan
236
273
  ```
237
-
238
- _See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.6.45/src/commands/update.ts)_
239
- <!-- commandsstop -->
@@ -0,0 +1,2 @@
1
+ export declare const createReportMutation: import("graphql/language/ast.js").DocumentNode;
2
+ export declare const getEolReportQuery: import("graphql/language/ast.js").DocumentNode;
@@ -0,0 +1,36 @@
1
+ import { gql } from '@apollo/client/core/core.cjs';
2
+ export const createReportMutation = gql `
3
+ mutation createReport($input: CreateEolReportInput) {
4
+ eol {
5
+ createReport(input: $input) {
6
+ success
7
+ id
8
+ totalRecords
9
+ }
10
+ }
11
+ }
12
+ `;
13
+ export const getEolReportQuery = gql `
14
+ query GetEolReport($input: GetEolReportInput) {
15
+ eol {
16
+ report(input: $input) {
17
+ id
18
+ createdOn
19
+ metadata
20
+ components {
21
+ purl
22
+ metadata
23
+ nesRemediation {
24
+ remediations {
25
+ urls {
26
+ main
27
+ }
28
+ }
29
+ }
30
+ }
31
+ page
32
+ totalRecords
33
+ }
34
+ }
35
+ }
36
+ `;
@@ -0,0 +1,12 @@
1
+ import { ApolloClient } from '@apollo/client/core/index.js';
2
+ import type { CreateEolReportInput, EolReport } from '@herodevs/eol-shared';
3
+ export declare const createApollo: (uri: string) => ApolloClient<import("@apollo/client/core/index.js").NormalizedCacheObject>;
4
+ export declare const SbomScanner: (client: ReturnType<typeof createApollo>) => (input: CreateEolReportInput) => Promise<EolReport>;
5
+ export declare class NesClient {
6
+ startScan: ReturnType<typeof SbomScanner>;
7
+ constructor(url: string);
8
+ }
9
+ /**
10
+ * Submit a scan for a list of purls
11
+ */
12
+ export declare function submitScan(input: CreateEolReportInput): Promise<EolReport>;
@@ -0,0 +1,71 @@
1
+ import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client/core/index.js';
2
+ import { config } from "../config/constants.js";
3
+ import { debugLogger } from "../service/log.svc.js";
4
+ import { createReportMutation, getEolReportQuery } from "./gql-operations.js";
5
+ export const createApollo = (uri) => new ApolloClient({
6
+ cache: new InMemoryCache(),
7
+ defaultOptions: {
8
+ query: { fetchPolicy: 'no-cache' },
9
+ },
10
+ link: new HttpLink({
11
+ uri,
12
+ headers: {
13
+ 'User-Agent': `hdcli/${process.env.npm_package_version ?? 'unknown'}`,
14
+ },
15
+ }),
16
+ });
17
+ export const SbomScanner = (client) => {
18
+ return async (input) => {
19
+ const res = await client.mutate({
20
+ mutation: createReportMutation,
21
+ variables: { input },
22
+ });
23
+ const result = res.data?.eol?.createReport;
24
+ if (!result?.success || !result.id) {
25
+ debugLogger('failed scan %o', result || {});
26
+ throw new Error('Failed to create EOL report');
27
+ }
28
+ const totalRecords = result.totalRecords || 0;
29
+ const totalPages = Math.ceil(totalRecords / config.pageSize);
30
+ const pages = Array.from({ length: totalPages }, (_, index) => client.query({
31
+ query: getEolReportQuery,
32
+ variables: {
33
+ input: {
34
+ id: result.id,
35
+ page: index + 1,
36
+ size: config.pageSize,
37
+ },
38
+ },
39
+ }));
40
+ const components = [];
41
+ let reportMetadata = null;
42
+ for (let i = 0; i < pages.length; i += config.concurrentPageRequests) {
43
+ const batch = pages.slice(i, i + config.concurrentPageRequests);
44
+ const batchResponses = await Promise.all(batch);
45
+ for (const response of batchResponses) {
46
+ const report = response.data.eol.report;
47
+ reportMetadata ??= report;
48
+ components.push(...(report?.components ?? []));
49
+ }
50
+ }
51
+ if (!reportMetadata) {
52
+ throw new Error('Failed to fetch EOL report');
53
+ }
54
+ return { ...reportMetadata, components };
55
+ };
56
+ };
57
+ export class NesClient {
58
+ startScan;
59
+ constructor(url) {
60
+ this.startScan = SbomScanner(createApollo(url));
61
+ }
62
+ }
63
+ /**
64
+ * Submit a scan for a list of purls
65
+ */
66
+ export function submitScan(input) {
67
+ const url = config.graphqlHost + config.graphqlPath;
68
+ debugLogger('Submitting scan to %s', url);
69
+ const client = new NesClient(url);
70
+ return client.startScan(input);
71
+ }
@@ -1,24 +1,24 @@
1
+ import type { EolReport } from '@herodevs/eol-shared';
1
2
  import { Command } from '@oclif/core';
2
- import type { ComponentStatus, InsightsEolScanComponent } from '../../api/types/nes.types.ts';
3
3
  export default class ScanEol extends Command {
4
4
  static description: string;
5
5
  static enableJsonFlag: boolean;
6
- static examples: string[];
6
+ static examples: {
7
+ description: string;
8
+ command: string;
9
+ }[];
7
10
  static flags: {
8
11
  file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
- purls: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
- dir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ dir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
13
  save: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
+ saveSbom: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
15
  };
13
- run(): Promise<{
14
- components: InsightsEolScanComponent[];
15
- createdOn: string;
16
- }>;
17
- private getScan;
18
- private getPurlsFromFile;
19
- private printWebReportUrl;
16
+ run(): Promise<EolReport | undefined>;
17
+ private loadSbom;
20
18
  private scanSbom;
21
19
  private saveReport;
20
+ private saveSbom;
22
21
  private displayResults;
22
+ private getSbomFromScan;
23
+ private getSbomFromFile;
23
24
  }
24
- export declare function countComponentsByStatus(components: InsightsEolScanComponent[]): Record<ComponentStatus | 'NES_AVAILABLE', number>;