@herodevs/cli 2.0.0-beta.1 → 2.0.0-beta.3

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/README.md CHANGED
@@ -10,6 +10,14 @@ The HeroDevs CLI
10
10
  * [@herodevs/cli](#herodevscli)
11
11
  <!-- tocstop -->
12
12
 
13
+ ## Installation Instructions
14
+
15
+ 1. Install node v20 or higher: [Download Node](https://nodejs.org/en/download)
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>`
19
+ 1. Refer to the [Commands](#commands) section for a list of commands
20
+
13
21
  ## TERMS
14
22
 
15
23
  Use of this CLI is governed by the [HeroDevs End of Life Dataset Terms of Service and Data Policy](https://docs.herodevs.com/legal/end-of-life-dataset-terms).
@@ -30,7 +38,7 @@ $ npm install -g @herodevs/cli
30
38
  $ hd COMMAND
31
39
  running command...
32
40
  $ hd (--version)
33
- @herodevs/cli/2.0.0-beta.1 linux-x64 node-v22.15.0
41
+ @herodevs/cli/2.0.0-beta.3 linux-x64 node-v22.16.0
34
42
  $ hd --help [COMMAND]
35
43
  USAGE
36
44
  $ hd COMMAND
@@ -95,7 +103,7 @@ EXAMPLES
95
103
  $ hd report committers --csv
96
104
  ```
97
105
 
98
- _See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.1/src/commands/report/committers.ts)_
106
+ _See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.3/src/commands/report/committers.ts)_
99
107
 
100
108
  ## `hd report purls`
101
109
 
@@ -129,7 +137,7 @@ EXAMPLES
129
137
  $ hd report purls --save --csv
130
138
  ```
131
139
 
132
- _See code: [src/commands/report/purls.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.1/src/commands/report/purls.ts)_
140
+ _See code: [src/commands/report/purls.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.3/src/commands/report/purls.ts)_
133
141
 
134
142
  ## `hd scan eol`
135
143
 
@@ -163,7 +171,7 @@ EXAMPLES
163
171
  $ hd scan eol -a --dir=./my-project
164
172
  ```
165
173
 
166
- _See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.1/src/commands/scan/eol.ts)_
174
+ _See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.3/src/commands/scan/eol.ts)_
167
175
 
168
176
  ## `hd scan sbom`
169
177
 
@@ -191,7 +199,7 @@ EXAMPLES
191
199
  $ hd scan sbom --file=path/to/sbom.json
192
200
  ```
193
201
 
194
- _See code: [src/commands/scan/sbom.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.1/src/commands/scan/sbom.ts)_
202
+ _See code: [src/commands/scan/sbom.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.3/src/commands/scan/sbom.ts)_
195
203
 
196
204
  ## `hd update [CHANNEL]`
197
205
 
@@ -229,5 +237,5 @@ EXAMPLES
229
237
  $ hd update --available
230
238
  ```
231
239
 
232
- _See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.6.39/src/commands/update.ts)_
240
+ _See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.6.42/src/commands/update.ts)_
233
241
  <!-- commandsstop -->
package/bin/dev.js CHANGED
@@ -1,8 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- process.env.GRAPHQL_HOST = 'https://api.dev.nes.herodevs.com';
4
- process.env.EOL_REPORT_URL = 'https://eol-report-card.stage.apps.herodevs.io/reports';
5
-
6
3
  import main from './main.js';
7
4
 
8
5
  try {
@@ -16,6 +16,7 @@ export declare class NesApolloClient implements NesClient {
16
16
  query<T, V extends Record<string, unknown> | undefined>(query: apollo.DocumentNode, variables?: V): Promise<apollo.ApolloQueryResult<T>>;
17
17
  }
18
18
  export declare const batchSubmitPurls: (purls: string[], options?: ScanInputOptions, batchSize?: number) => Promise<ScanResult>;
19
+ export declare const dedupeAndEncodePurls: (purls: string[]) => string[];
19
20
  export declare const createBatches: (items: string[], batchSize: number) => string[][];
20
21
  export declare const processBatch: ({ batch, index, totalPages, scanOptions, previousScanId, }: ProcessBatchOptions) => Promise<InsightsEolScanResult>;
21
22
  export declare const processBatches: (batches: string[][], scanOptions: ScanInputOptions) => Promise<InsightsEolScanResult[]>;
@@ -1,3 +1,4 @@
1
+ import { PackageURL } from 'packageurl-js';
1
2
  import { ApolloClient } from "../../api/client.js";
2
3
  import { config } from "../../config/constants.js";
3
4
  import { debugLogger } from "../../service/log.svc.js";
@@ -25,12 +26,14 @@ function submitScan(purls, options) {
25
26
  const host = config.graphqlHost;
26
27
  const path = config.graphqlPath;
27
28
  const url = host + path;
29
+ debugLogger('Submitting scan to %s', url);
28
30
  const client = new NesApolloClient(url);
29
31
  return client.scan.purls(purls, options);
30
32
  }
31
33
  export const batchSubmitPurls = async (purls, options = DEFAULT_SCAN_INPUT_OPTIONS, batchSize = DEFAULT_SCAN_BATCH_SIZE) => {
32
34
  try {
33
- const batches = createBatches(purls, batchSize);
35
+ const dedupedAndEncodedPurls = dedupeAndEncodePurls(purls);
36
+ const batches = createBatches(dedupedAndEncodedPurls, batchSize);
34
37
  debugLogger('Processing %d batches', batches.length);
35
38
  if (batches.length === 0) {
36
39
  return {
@@ -39,6 +42,7 @@ export const batchSubmitPurls = async (purls, options = DEFAULT_SCAN_INPUT_OPTIO
39
42
  success: true,
40
43
  warnings: [],
41
44
  scanId: undefined,
45
+ createdOn: undefined,
42
46
  };
43
47
  }
44
48
  const results = await processBatches(batches, options);
@@ -49,6 +53,22 @@ export const batchSubmitPurls = async (purls, options = DEFAULT_SCAN_INPUT_OPTIO
49
53
  throw new Error(`Failed to process purls: ${error instanceof Error ? error.message : String(error)}`);
50
54
  }
51
55
  };
56
+ export const dedupeAndEncodePurls = (purls) => {
57
+ const dedupedAndEncodedPurls = new Set();
58
+ for (const purl of purls) {
59
+ try {
60
+ // The PackageURL.fromString method encodes each part of the purl
61
+ const encodedPurl = PackageURL.fromString(purl).toString();
62
+ if (!dedupedAndEncodedPurls.has(encodedPurl)) {
63
+ dedupedAndEncodedPurls.add(encodedPurl);
64
+ }
65
+ }
66
+ catch (error) {
67
+ debugLogger('Error encoding purl: %s', error);
68
+ }
69
+ }
70
+ return Array.from(dedupedAndEncodedPurls);
71
+ };
52
72
  export const createBatches = (items, batchSize) => {
53
73
  const numberOfBatches = Math.ceil(items.length / batchSize);
54
74
  return Array.from({ length: numberOfBatches }, (_, index) => {
@@ -19,6 +19,7 @@ export const M_SCAN = {
19
19
  diagnostics
20
20
  message
21
21
  scanId
22
+ createdOn
22
23
  success
23
24
  warnings {
24
25
  purl
@@ -15,6 +15,7 @@ export type ScanInput = {
15
15
  };
16
16
  export interface ScanResult {
17
17
  components: ScanResultComponentsMap;
18
+ createdOn?: string;
18
19
  diagnostics?: Record<string, unknown>;
19
20
  message: string;
20
21
  success: boolean;
@@ -22,6 +22,7 @@ export interface ScanResponse {
22
22
  */
23
23
  export interface InsightsEolScanResult {
24
24
  scanId?: string;
25
+ createdOn: string;
25
26
  success: boolean;
26
27
  message: string;
27
28
  components: InsightsEolScanComponent[];
@@ -14,6 +14,7 @@ export default class ScanEol extends Command {
14
14
  };
15
15
  run(): Promise<{
16
16
  components: InsightsEolScanComponent[];
17
+ createdOn: string;
17
18
  }>;
18
19
  private getScan;
19
20
  private getPurlsFromFile;
@@ -52,7 +52,7 @@ export default class ScanEol extends Command {
52
52
  ux.action.stop('\nScan completed');
53
53
  const components = this.getFilteredComponents(scan, flags.all);
54
54
  if (flags.save) {
55
- await this.saveReport(components);
55
+ await this.saveReport(components, scan.createdOn);
56
56
  }
57
57
  if (!this.jsonEnabled()) {
58
58
  if (flags.table) {
@@ -66,7 +66,7 @@ export default class ScanEol extends Command {
66
66
  this.printWebReportUrl(scan.scanId);
67
67
  }
68
68
  }
69
- return { components };
69
+ return { components, createdOn: scan.createdOn ?? '' };
70
70
  }
71
71
  async getScan(flags, config) {
72
72
  if (flags.purls) {
@@ -116,26 +116,25 @@ export default class ScanEol extends Command {
116
116
  getFilteredComponents(scan, all) {
117
117
  return Array.from(scan.components.values()).filter((component) => all || ['EOL', 'SUPPORTED'].includes(component.info.status));
118
118
  }
119
- async saveReport(components) {
119
+ async saveReport(components, createdOn) {
120
120
  const { flags } = await this.parse(ScanEol);
121
121
  const reportPath = path.join(flags.dir || process.cwd(), 'eol.report.json');
122
122
  try {
123
- fs.writeFileSync(reportPath, JSON.stringify({ components }, null, 2));
123
+ fs.writeFileSync(reportPath, JSON.stringify({ components, createdOn }, null, 2));
124
124
  this.log('Report saved to eol.report.json');
125
125
  }
126
126
  catch (error) {
127
127
  if (!isErrnoException(error)) {
128
128
  this.error(`Failed to save report: ${getErrorMessage(error)}`);
129
129
  }
130
- switch (error.code) {
131
- case 'EACCES':
132
- this.error('Permission denied. Unable to save report to eol.report.json');
133
- break;
134
- case 'ENOSPC':
135
- this.error('No space left on device. Unable to save report to eol.report.json');
136
- break;
137
- default:
138
- this.error(`Failed to save report: ${getErrorMessage(error)}`);
130
+ if (error.code === 'EACCES') {
131
+ this.error('Permission denied. Unable to save report to eol.report.json');
132
+ }
133
+ else if (error.code === 'ENOSPC') {
134
+ this.error('No space left on device. Unable to save report to eol.report.json');
135
+ }
136
+ else {
137
+ this.error(`Failed to save report: ${getErrorMessage(error)}`);
139
138
  }
140
139
  }
141
140
  }
@@ -5,5 +5,5 @@ export const config = {
5
5
  eolReportUrl: process.env.EOL_REPORT_URL || EOL_REPORT_URL,
6
6
  graphqlHost: process.env.GRAPHQL_HOST || GRAPHQL_HOST,
7
7
  graphqlPath: process.env.GRAPHQL_PATH || GRAPHQL_PATH,
8
- showVulnCount: false,
8
+ showVulnCount: true,
9
9
  };
@@ -11,6 +11,7 @@ export const buildScanResult = (scan) => {
11
11
  success: true,
12
12
  warnings: scan.warnings || [],
13
13
  scanId: scan.scanId,
14
+ createdOn: scan.createdOn,
14
15
  };
15
16
  };
16
17
  export const SbomScanner = (client) => async (purls, options) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herodevs/cli",
3
- "version": "2.0.0-beta.1",
3
+ "version": "2.0.0-beta.3",
4
4
  "author": "HeroDevs, Inc",
5
5
  "bin": {
6
6
  "hd": "./bin/run.js"
@@ -37,27 +37,27 @@
37
37
  ],
38
38
  "dependencies": {
39
39
  "@apollo/client": "^3.13.8",
40
- "@cyclonedx/cdxgen": "^11.2.7",
41
- "@oclif/core": "^4",
42
- "@oclif/plugin-help": "^6",
43
- "@oclif/plugin-update": "^4",
44
- "@oclif/table": "^0.4.7",
40
+ "@cyclonedx/cdxgen": "^11.3.2",
41
+ "@oclif/core": "^4.3.1",
42
+ "@oclif/plugin-help": "^6.2.28",
43
+ "@oclif/plugin-update": "^4.6.42",
44
+ "@oclif/table": "^0.4.8",
45
45
  "graphql": "^16.11.0",
46
46
  "packageurl-js": "^2.0.1",
47
47
  "update-notifier": "^7.3.1"
48
48
  },
49
49
  "devDependencies": {
50
- "@biomejs/biome": "^1.8.3",
51
- "@oclif/test": "^4",
50
+ "@biomejs/biome": "^1.9.4",
51
+ "@oclif/test": "^4.1.13",
52
52
  "@types/inquirer": "^9.0.8",
53
- "@types/node": "^22",
53
+ "@types/node": "^22.15.30",
54
54
  "@types/sinon": "^17.0.4",
55
55
  "@types/update-notifier": "^6.0.8",
56
56
  "globstar": "^1.0.0",
57
- "oclif": "^4",
57
+ "oclif": "^4.18.0",
58
58
  "shx": "^0.4.0",
59
59
  "sinon": "^20.0.0",
60
- "ts-node": "^10",
60
+ "ts-node": "^10.9.2",
61
61
  "tsx": "^4.19.4",
62
62
  "typescript": "^5.8.3"
63
63
  },