@herodevs/cli 1.6.0-beta.0 → 2.0.0-beta.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/README.md +11 -7
- package/bin/dev.js +1 -7
- package/dist/api/nes/nes.client.js +4 -3
- package/dist/api/types/hd-cli.types.d.ts +1 -0
- package/dist/commands/scan/eol.d.ts +1 -0
- package/dist/commands/scan/eol.js +12 -1
- package/dist/config/constants.d.ts +9 -0
- package/dist/config/constants.js +9 -0
- package/dist/hooks/npm-update-notifier.d.ts +2 -0
- package/dist/hooks/npm-update-notifier.js +26 -6
- package/dist/service/nes/nes.svc.js +1 -0
- package/dist/ui/eol.ui.js +32 -11
- package/dist/ui/shared.ui.d.ts +1 -0
- package/dist/ui/shared.ui.js +1 -0
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -10,6 +10,10 @@ The HeroDevs CLI
|
|
|
10
10
|
* [@herodevs/cli](#herodevscli)
|
|
11
11
|
<!-- tocstop -->
|
|
12
12
|
|
|
13
|
+
## TERMS
|
|
14
|
+
|
|
15
|
+
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).
|
|
16
|
+
|
|
13
17
|
## Scanning Behavior
|
|
14
18
|
|
|
15
19
|
The CLI's scanning commands (`hd scan eol` and `hd scan sbom`) are designed to be non-invasive:
|
|
@@ -26,7 +30,7 @@ $ npm install -g @herodevs/cli
|
|
|
26
30
|
$ hd COMMAND
|
|
27
31
|
running command...
|
|
28
32
|
$ hd (--version)
|
|
29
|
-
@herodevs/cli/
|
|
33
|
+
@herodevs/cli/2.0.0-beta.1 linux-x64 node-v22.15.0
|
|
30
34
|
$ hd --help [COMMAND]
|
|
31
35
|
USAGE
|
|
32
36
|
$ hd COMMAND
|
|
@@ -60,7 +64,7 @@ DESCRIPTION
|
|
|
60
64
|
Display help for hd.
|
|
61
65
|
```
|
|
62
66
|
|
|
63
|
-
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.
|
|
67
|
+
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.28/src/commands/help.ts)_
|
|
64
68
|
|
|
65
69
|
## `hd report committers`
|
|
66
70
|
|
|
@@ -91,7 +95,7 @@ EXAMPLES
|
|
|
91
95
|
$ hd report committers --csv
|
|
92
96
|
```
|
|
93
97
|
|
|
94
|
-
_See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/
|
|
98
|
+
_See code: [src/commands/report/committers.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.1/src/commands/report/committers.ts)_
|
|
95
99
|
|
|
96
100
|
## `hd report purls`
|
|
97
101
|
|
|
@@ -125,7 +129,7 @@ EXAMPLES
|
|
|
125
129
|
$ hd report purls --save --csv
|
|
126
130
|
```
|
|
127
131
|
|
|
128
|
-
_See code: [src/commands/report/purls.ts](https://github.com/herodevs/cli/blob/
|
|
132
|
+
_See code: [src/commands/report/purls.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.1/src/commands/report/purls.ts)_
|
|
129
133
|
|
|
130
134
|
## `hd scan eol`
|
|
131
135
|
|
|
@@ -159,7 +163,7 @@ EXAMPLES
|
|
|
159
163
|
$ hd scan eol -a --dir=./my-project
|
|
160
164
|
```
|
|
161
165
|
|
|
162
|
-
_See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/
|
|
166
|
+
_See code: [src/commands/scan/eol.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.1/src/commands/scan/eol.ts)_
|
|
163
167
|
|
|
164
168
|
## `hd scan sbom`
|
|
165
169
|
|
|
@@ -187,7 +191,7 @@ EXAMPLES
|
|
|
187
191
|
$ hd scan sbom --file=path/to/sbom.json
|
|
188
192
|
```
|
|
189
193
|
|
|
190
|
-
_See code: [src/commands/scan/sbom.ts](https://github.com/herodevs/cli/blob/
|
|
194
|
+
_See code: [src/commands/scan/sbom.ts](https://github.com/herodevs/cli/blob/v2.0.0-beta.1/src/commands/scan/sbom.ts)_
|
|
191
195
|
|
|
192
196
|
## `hd update [CHANNEL]`
|
|
193
197
|
|
|
@@ -225,5 +229,5 @@ EXAMPLES
|
|
|
225
229
|
$ hd update --available
|
|
226
230
|
```
|
|
227
231
|
|
|
228
|
-
_See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.6.
|
|
232
|
+
_See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.6.39/src/commands/update.ts)_
|
|
229
233
|
<!-- commandsstop -->
|
package/bin/dev.js
CHANGED
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// Localhost
|
|
4
|
-
// process.env.GRAPHQL_HOST = 'http://localhost:3000';
|
|
5
|
-
|
|
6
|
-
// Dev
|
|
7
3
|
process.env.GRAPHQL_HOST = 'https://api.dev.nes.herodevs.com';
|
|
8
|
-
|
|
9
|
-
// Prod
|
|
10
|
-
// process.env.GRAPHQL_HOST = 'https://api.nes.herodevs.com';
|
|
4
|
+
process.env.EOL_REPORT_URL = 'https://eol-report-card.stage.apps.herodevs.io/reports';
|
|
11
5
|
|
|
12
6
|
import main from './main.js';
|
|
13
7
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ApolloClient } from "../../api/client.js";
|
|
2
|
+
import { config } from "../../config/constants.js";
|
|
2
3
|
import { debugLogger } from "../../service/log.svc.js";
|
|
3
4
|
import { SbomScanner, buildScanResult } from "../../service/nes/nes.svc.js";
|
|
4
5
|
import { DEFAULT_SCAN_BATCH_SIZE, DEFAULT_SCAN_INPUT_OPTIONS, } from "../types/hd-cli.types.js";
|
|
@@ -21,9 +22,8 @@ export class NesApolloClient {
|
|
|
21
22
|
* Submit a scan for a list of purls after they've been batched by batchSubmitPurls
|
|
22
23
|
*/
|
|
23
24
|
function submitScan(purls, options) {
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const path = process.env.GRAPHQL_PATH || '/graphql';
|
|
25
|
+
const host = config.graphqlHost;
|
|
26
|
+
const path = config.graphqlPath;
|
|
27
27
|
const url = host + path;
|
|
28
28
|
const client = new NesApolloClient(url);
|
|
29
29
|
return client.scan.purls(purls, options);
|
|
@@ -38,6 +38,7 @@ export const batchSubmitPurls = async (purls, options = DEFAULT_SCAN_INPUT_OPTIO
|
|
|
38
38
|
message: 'No batches to process',
|
|
39
39
|
success: true,
|
|
40
40
|
warnings: [],
|
|
41
|
+
scanId: undefined,
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
const results = await processBatches(batches, options);
|
|
@@ -2,10 +2,11 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { Command, Flags, ux } from '@oclif/core';
|
|
4
4
|
import { batchSubmitPurls } from "../../api/nes/nes.client.js";
|
|
5
|
+
import { config } from "../../config/constants.js";
|
|
5
6
|
import { getErrorMessage, isErrnoException } from "../../service/error.svc.js";
|
|
6
7
|
import { extractPurls, parsePurlsFile } from "../../service/purls.svc.js";
|
|
7
8
|
import { createStatusDisplay, createTableForStatus, groupComponentsByStatus } from "../../ui/eol.ui.js";
|
|
8
|
-
import { INDICATORS, STATUS_COLORS } from "../../ui/shared.ui.js";
|
|
9
|
+
import { INDICATORS, SCAN_ID_KEY, STATUS_COLORS } from "../../ui/shared.ui.js";
|
|
9
10
|
import ScanSbom from "./sbom.js";
|
|
10
11
|
export default class ScanEol extends Command {
|
|
11
12
|
static description = 'Scan a given sbom for EOL data';
|
|
@@ -61,6 +62,9 @@ export default class ScanEol extends Command {
|
|
|
61
62
|
else {
|
|
62
63
|
this.displayResults(scan, flags.all);
|
|
63
64
|
}
|
|
65
|
+
if (scan.scanId) {
|
|
66
|
+
this.printWebReportUrl(scan.scanId);
|
|
67
|
+
}
|
|
64
68
|
}
|
|
65
69
|
return { components };
|
|
66
70
|
}
|
|
@@ -82,6 +86,13 @@ export default class ScanEol extends Command {
|
|
|
82
86
|
this.error(`Failed to read purls file. ${getErrorMessage(error)}`);
|
|
83
87
|
}
|
|
84
88
|
}
|
|
89
|
+
printWebReportUrl(scanId) {
|
|
90
|
+
this.logLine();
|
|
91
|
+
const id = scanId.split(SCAN_ID_KEY)[1];
|
|
92
|
+
const reportCardUrl = config.eolReportUrl;
|
|
93
|
+
const url = ux.colorize('blue', `${reportCardUrl}/${id}`);
|
|
94
|
+
this.log(`🌐 View your free EOL report at: ${ux.colorize('blue', url)}`);
|
|
95
|
+
}
|
|
85
96
|
async scanSbom(sbom) {
|
|
86
97
|
let scan;
|
|
87
98
|
let purls;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const EOL_REPORT_URL = "https://eol-report-card.apps.herodevs.com/reports";
|
|
2
|
+
export declare const GRAPHQL_HOST = "https://api.nes.herodevs.com";
|
|
3
|
+
export declare const GRAPHQL_PATH = "/graphql";
|
|
4
|
+
export declare const config: {
|
|
5
|
+
eolReportUrl: string;
|
|
6
|
+
graphqlHost: string;
|
|
7
|
+
graphqlPath: string;
|
|
8
|
+
showVulnCount: boolean;
|
|
9
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const EOL_REPORT_URL = 'https://eol-report-card.apps.herodevs.com/reports';
|
|
2
|
+
export const GRAPHQL_HOST = 'https://api.nes.herodevs.com';
|
|
3
|
+
export const GRAPHQL_PATH = '/graphql';
|
|
4
|
+
export const config = {
|
|
5
|
+
eolReportUrl: process.env.EOL_REPORT_URL || EOL_REPORT_URL,
|
|
6
|
+
graphqlHost: process.env.GRAPHQL_HOST || GRAPHQL_HOST,
|
|
7
|
+
graphqlPath: process.env.GRAPHQL_PATH || GRAPHQL_PATH,
|
|
8
|
+
showVulnCount: false,
|
|
9
|
+
};
|
|
@@ -2,6 +2,8 @@ import type { Hook } from '@oclif/core';
|
|
|
2
2
|
import { type UpdateInfo } from 'update-notifier';
|
|
3
3
|
declare const updateNotifierHook: Hook.Init;
|
|
4
4
|
export default updateNotifierHook;
|
|
5
|
+
type DistTag = 'latest' | 'beta' | 'alpha' | 'next';
|
|
6
|
+
export declare function getDistTag(version: string): DistTag;
|
|
5
7
|
export declare function handleUpdate(update: UpdateInfo, currentVersion: string): {
|
|
6
8
|
message: string;
|
|
7
9
|
defer: boolean;
|
|
@@ -1,23 +1,43 @@
|
|
|
1
1
|
import updateNotifier, {} from 'update-notifier';
|
|
2
2
|
import pkg from '../../package.json' with { type: 'json' };
|
|
3
|
+
import { debugLogger } from "../service/log.svc.js";
|
|
3
4
|
const updateNotifierHook = async (options) => {
|
|
5
|
+
debugLogger('pkg.version', pkg.version);
|
|
6
|
+
const distTag = getDistTag(pkg.version);
|
|
7
|
+
debugLogger('distTag', distTag);
|
|
8
|
+
const ONE_DAY_MS = 1000 * 60 * 60 * 24;
|
|
9
|
+
// If we're on the latest dist-tag, check for updates every time
|
|
10
|
+
const updateCheckInterval = distTag === 'latest' ? 0 : ONE_DAY_MS;
|
|
11
|
+
debugLogger('updateCheckInterval', updateCheckInterval);
|
|
4
12
|
const notifier = updateNotifier({
|
|
5
13
|
pkg,
|
|
6
|
-
|
|
14
|
+
distTag,
|
|
15
|
+
updateCheckInterval,
|
|
16
|
+
shouldNotifyInNpmScript: true,
|
|
7
17
|
});
|
|
18
|
+
debugLogger('updateNotifierHook', { notifier });
|
|
8
19
|
if (notifier.update) {
|
|
9
20
|
const notification = handleUpdate(notifier.update, pkg.version);
|
|
21
|
+
debugLogger('notification', notification);
|
|
10
22
|
if (notification) {
|
|
11
23
|
notifier.notify(notification);
|
|
12
24
|
}
|
|
13
25
|
}
|
|
14
26
|
};
|
|
15
27
|
export default updateNotifierHook;
|
|
28
|
+
export function getDistTag(version) {
|
|
29
|
+
if (version.includes('-beta'))
|
|
30
|
+
return 'beta';
|
|
31
|
+
if (version.includes('-alpha'))
|
|
32
|
+
return 'alpha';
|
|
33
|
+
if (version.includes('-next'))
|
|
34
|
+
return 'next';
|
|
35
|
+
return 'latest';
|
|
36
|
+
}
|
|
16
37
|
export function handleUpdate(update, currentVersion) {
|
|
17
|
-
const isPreV1 = currentVersion.startsWith('0.');
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const isNext = currentVersion.includes('-next') || update.latest.includes('-next');
|
|
38
|
+
const isPreV1 = currentVersion.startsWith('0.') || update.latest.startsWith('0.');
|
|
39
|
+
const currentDistTag = getDistTag(currentVersion);
|
|
40
|
+
const updateDistTag = getDistTag(update.latest);
|
|
21
41
|
let message = `Update available! v${currentVersion} → v${update.latest}`;
|
|
22
42
|
/**
|
|
23
43
|
* Show breaking changes warning for:
|
|
@@ -28,7 +48,7 @@ export function handleUpdate(update, currentVersion) {
|
|
|
28
48
|
* [1]https://semver.org/#spec-item-4
|
|
29
49
|
* [2]https://antfu.me/posts/epoch-semver#leading-zero-major-versioning
|
|
30
50
|
*/
|
|
31
|
-
if (isPreV1 ||
|
|
51
|
+
if (isPreV1 || currentDistTag !== 'latest' || updateDistTag !== 'latest' || update.type === 'major') {
|
|
32
52
|
message += '\nThis update may contain breaking changes.';
|
|
33
53
|
}
|
|
34
54
|
// For all other updates (minor, patch), they should be non-breaking
|
package/dist/ui/eol.ui.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ux } from '@oclif/core';
|
|
2
2
|
import { makeTable } from '@oclif/table';
|
|
3
3
|
import { PackageURL } from 'packageurl-js';
|
|
4
|
+
import { config } from "../config/constants.js";
|
|
4
5
|
import { resolvePurlPackageName } from "../service/eol/eol.svc.js";
|
|
5
6
|
import { parseMomentToSimpleDate } from "./date.ui.js";
|
|
6
7
|
import { INDICATORS, MAX_PURL_LENGTH, MAX_TABLE_COLUMN_WIDTH, STATUS_COLORS } from "./shared.ui.js";
|
|
@@ -19,10 +20,10 @@ function getDaysEolString(daysEol) {
|
|
|
19
20
|
if (daysEol === null) {
|
|
20
21
|
return '';
|
|
21
22
|
}
|
|
22
|
-
if (daysEol
|
|
23
|
-
return `${Math.abs(daysEol)} days from now`;
|
|
23
|
+
if (daysEol <= 0) {
|
|
24
|
+
return `${Math.abs(daysEol) + 1} days from now`;
|
|
24
25
|
}
|
|
25
|
-
if (daysEol
|
|
26
|
+
if (daysEol > 0) {
|
|
26
27
|
return 'today';
|
|
27
28
|
}
|
|
28
29
|
return `${daysEol} days ago`;
|
|
@@ -32,13 +33,11 @@ function formatDetailedComponent(purl, info) {
|
|
|
32
33
|
const simpleComponent = formatSimpleComponent(purl, status);
|
|
33
34
|
const eolAtString = parseMomentToSimpleDate(eolAt);
|
|
34
35
|
const daysEolString = getDaysEolString(daysEol);
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
` ⮑
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.filter(Boolean)
|
|
41
|
-
.join('\n');
|
|
36
|
+
const eolString = [`${simpleComponent}`, ` ⮑ EOL Date: ${eolAtString} (${daysEolString})`];
|
|
37
|
+
if (config.showVulnCount) {
|
|
38
|
+
eolString.push(` ⮑ # of Vulns: ${vulnCount ?? ''}`);
|
|
39
|
+
}
|
|
40
|
+
const output = eolString.filter(Boolean).join('\n');
|
|
42
41
|
return output;
|
|
43
42
|
}
|
|
44
43
|
export function createStatusDisplay(components, all) {
|
|
@@ -65,6 +64,19 @@ export function createStatusDisplay(components, all) {
|
|
|
65
64
|
export function createTableForStatus(grouped, status) {
|
|
66
65
|
const data = grouped[status].map((component) => convertComponentToTableRow(component));
|
|
67
66
|
if (status === 'EOL' || status === 'SUPPORTED') {
|
|
67
|
+
if (config.showVulnCount) {
|
|
68
|
+
return makeTable({
|
|
69
|
+
data,
|
|
70
|
+
columns: [
|
|
71
|
+
{ key: 'name', name: 'NAME', width: MAX_TABLE_COLUMN_WIDTH },
|
|
72
|
+
{ key: 'version', name: 'VERSION', width: 10 },
|
|
73
|
+
{ key: 'eol', name: 'EOL', width: 12 },
|
|
74
|
+
{ key: 'daysEol', name: 'DAYS EOL', width: 10 },
|
|
75
|
+
{ key: 'type', name: 'TYPE', width: 12 },
|
|
76
|
+
{ key: 'vulnCount', name: '# OF VULNS', width: 12 },
|
|
77
|
+
],
|
|
78
|
+
});
|
|
79
|
+
}
|
|
68
80
|
return makeTable({
|
|
69
81
|
data,
|
|
70
82
|
columns: [
|
|
@@ -73,6 +85,16 @@ export function createTableForStatus(grouped, status) {
|
|
|
73
85
|
{ key: 'eol', name: 'EOL', width: 12 },
|
|
74
86
|
{ key: 'daysEol', name: 'DAYS EOL', width: 10 },
|
|
75
87
|
{ key: 'type', name: 'TYPE', width: 12 },
|
|
88
|
+
],
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (config.showVulnCount) {
|
|
92
|
+
return makeTable({
|
|
93
|
+
data,
|
|
94
|
+
columns: [
|
|
95
|
+
{ key: 'name', name: 'NAME', width: MAX_TABLE_COLUMN_WIDTH },
|
|
96
|
+
{ key: 'version', name: 'VERSION', width: 10 },
|
|
97
|
+
{ key: 'type', name: 'TYPE', width: 12 },
|
|
76
98
|
{ key: 'vulnCount', name: '# OF VULNS', width: 12 },
|
|
77
99
|
],
|
|
78
100
|
});
|
|
@@ -83,7 +105,6 @@ export function createTableForStatus(grouped, status) {
|
|
|
83
105
|
{ key: 'name', name: 'NAME', width: MAX_TABLE_COLUMN_WIDTH },
|
|
84
106
|
{ key: 'version', name: 'VERSION', width: 10 },
|
|
85
107
|
{ key: 'type', name: 'TYPE', width: 12 },
|
|
86
|
-
{ key: 'vulnCount', name: '# OF VULNS', width: 12 },
|
|
87
108
|
],
|
|
88
109
|
});
|
|
89
110
|
}
|
package/dist/ui/shared.ui.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export declare const STATUS_COLORS: Record<ComponentStatus, string>;
|
|
|
3
3
|
export declare const INDICATORS: Record<ComponentStatus, string>;
|
|
4
4
|
export declare const MAX_PURL_LENGTH = 60;
|
|
5
5
|
export declare const MAX_TABLE_COLUMN_WIDTH = 30;
|
|
6
|
+
export declare const SCAN_ID_KEY = "eol-scan-v1-";
|
package/dist/ui/shared.ui.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herodevs/cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0-beta.1",
|
|
4
4
|
"author": "HeroDevs, Inc",
|
|
5
5
|
"bin": {
|
|
6
6
|
"hd": "./bin/run.js"
|
|
@@ -37,19 +37,19 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@apollo/client": "^3.13.8",
|
|
40
|
-
"@cyclonedx/cdxgen": "^11.2.
|
|
40
|
+
"@cyclonedx/cdxgen": "^11.2.7",
|
|
41
41
|
"@oclif/core": "^4",
|
|
42
42
|
"@oclif/plugin-help": "^6",
|
|
43
43
|
"@oclif/plugin-update": "^4",
|
|
44
44
|
"@oclif/table": "^0.4.7",
|
|
45
|
-
"graphql": "^16.
|
|
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
50
|
"@biomejs/biome": "^1.8.3",
|
|
51
51
|
"@oclif/test": "^4",
|
|
52
|
-
"@types/inquirer": "^9.0.
|
|
52
|
+
"@types/inquirer": "^9.0.8",
|
|
53
53
|
"@types/node": "^22",
|
|
54
54
|
"@types/sinon": "^17.0.4",
|
|
55
55
|
"@types/update-notifier": "^6.0.8",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"shx": "^0.4.0",
|
|
59
59
|
"sinon": "^20.0.0",
|
|
60
60
|
"ts-node": "^10",
|
|
61
|
-
"tsx": "^4.19.
|
|
61
|
+
"tsx": "^4.19.4",
|
|
62
62
|
"typescript": "^5.8.3"
|
|
63
63
|
},
|
|
64
64
|
"engines": {
|