@herodevs/cli 1.1.0-beta.1 → 1.3.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 +16 -309
- package/bin/dev.js +6 -5
- package/bin/run.js +3 -2
- package/dist/api/client.d.ts +0 -2
- package/dist/api/client.js +19 -18
- package/dist/api/nes/nes.client.d.ts +10 -3
- package/dist/api/nes/nes.client.js +89 -2
- package/dist/api/queries/nes/sbom.js +5 -0
- package/dist/api/types/hd-cli.types.d.ts +29 -0
- package/dist/api/types/hd-cli.types.js +10 -0
- package/dist/api/types/nes.types.d.ts +39 -22
- package/dist/api/types/nes.types.js +1 -1
- package/dist/commands/report/committers.js +45 -28
- package/dist/commands/report/purls.js +42 -29
- package/dist/commands/scan/eol.d.ts +16 -4
- package/dist/commands/scan/eol.js +144 -41
- package/dist/commands/scan/sbom.d.ts +1 -0
- package/dist/commands/scan/sbom.js +53 -32
- package/dist/service/committers.svc.js +24 -3
- package/dist/service/eol/cdx.svc.d.ts +2 -8
- package/dist/service/eol/cdx.svc.js +2 -18
- package/dist/service/eol/eol.svc.d.ts +1 -23
- package/dist/service/eol/eol.svc.js +0 -61
- package/dist/service/error.svc.d.ts +8 -0
- package/dist/service/error.svc.js +28 -0
- package/dist/service/nes/nes.svc.d.ts +4 -3
- package/dist/service/nes/nes.svc.js +5 -4
- package/dist/service/purls.svc.d.ts +6 -0
- package/dist/service/purls.svc.js +26 -0
- package/dist/ui/date.ui.d.ts +1 -0
- package/dist/ui/date.ui.js +15 -0
- package/dist/ui/eol.ui.d.ts +5 -3
- package/dist/ui/eol.ui.js +56 -15
- package/dist/ui/shared.us.d.ts +3 -0
- package/dist/ui/shared.us.js +13 -0
- package/package.json +10 -9
- package/dist/service/line.svc.d.ts +0 -24
- package/dist/service/line.svc.js +0 -61
|
@@ -1,35 +1,52 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Input parameters for the EOL scan operation
|
|
3
|
+
*/
|
|
4
|
+
export interface InsightsEolScanInput {
|
|
5
|
+
scanId?: string;
|
|
6
|
+
/** Array of package URLs in purl format to scan */
|
|
2
7
|
components: string[];
|
|
3
|
-
type
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
/** The type of scan being performed (e.g. 'SBOM') */
|
|
9
|
+
type: string;
|
|
10
|
+
page: number;
|
|
11
|
+
totalPages: number;
|
|
12
|
+
}
|
|
7
13
|
export interface ScanResponse {
|
|
8
14
|
insights: {
|
|
9
15
|
scan: {
|
|
10
|
-
eol:
|
|
16
|
+
eol: InsightsEolScanResult;
|
|
11
17
|
};
|
|
12
18
|
};
|
|
13
19
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Result of the EOL scan operation
|
|
22
|
+
*/
|
|
23
|
+
export interface InsightsEolScanResult {
|
|
24
|
+
scanId?: string;
|
|
18
25
|
success: boolean;
|
|
26
|
+
message: string;
|
|
27
|
+
components: InsightsEolScanComponent[];
|
|
28
|
+
warnings: ScanWarning[];
|
|
19
29
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Information about a component's EOL status
|
|
32
|
+
*/
|
|
33
|
+
export interface InsightsEolScanComponentInfo {
|
|
34
|
+
isEol: boolean;
|
|
35
|
+
isUnsafe: boolean;
|
|
36
|
+
eolAt: Date | null;
|
|
37
|
+
status: ComponentStatus;
|
|
38
|
+
daysEol: number | null;
|
|
39
|
+
}
|
|
40
|
+
export interface InsightsEolScanComponent {
|
|
41
|
+
info: InsightsEolScanComponentInfo;
|
|
27
42
|
purl: string;
|
|
28
|
-
status?: ComponentStatus;
|
|
29
43
|
}
|
|
30
|
-
export interface
|
|
31
|
-
|
|
32
|
-
diagnostics?: Record<string, unknown>;
|
|
44
|
+
export interface ScanWarning {
|
|
45
|
+
purl: string;
|
|
33
46
|
message: string;
|
|
34
|
-
|
|
47
|
+
type?: string;
|
|
48
|
+
error?: unknown;
|
|
49
|
+
diagnostics?: Record<string, unknown>;
|
|
35
50
|
}
|
|
51
|
+
export type ComponentStatus = (typeof VALID_STATUSES)[number];
|
|
52
|
+
export declare const VALID_STATUSES: readonly ["UNKNOWN", "OK", "EOL", "LTS"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export
|
|
1
|
+
export const VALID_STATUSES = ['UNKNOWN', 'OK', 'EOL', 'LTS'];
|
|
@@ -3,6 +3,7 @@ import { Command, Flags } from '@oclif/core';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { calculateOverallStats, formatAsCsv, formatAsText, groupCommitsByMonth, parseGitLogOutput, } from "../../service/committers.svc.js";
|
|
6
|
+
import { getErrorMessage, isErrnoException } from "../../service/error.svc.js";
|
|
6
7
|
export default class Committers extends Command {
|
|
7
8
|
static description = 'Generate report of committers to a git repository';
|
|
8
9
|
static enableJsonFlag = true;
|
|
@@ -44,28 +45,42 @@ export default class Committers extends Command {
|
|
|
44
45
|
if (isJson) {
|
|
45
46
|
// JSON mode
|
|
46
47
|
if (save) {
|
|
47
|
-
|
|
48
|
-
|
|
48
|
+
try {
|
|
49
|
+
fs.writeFileSync(path.resolve('nes.committers.json'), JSON.stringify(reportData, null, 2));
|
|
50
|
+
this.log('Report written to json');
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.error(`Failed to save JSON report: ${getErrorMessage(error)}`);
|
|
54
|
+
}
|
|
49
55
|
}
|
|
50
56
|
return reportData;
|
|
51
57
|
}
|
|
58
|
+
const textOutput = formatAsText(reportData);
|
|
52
59
|
if (csv) {
|
|
53
60
|
// CSV mode
|
|
54
61
|
const csvOutput = formatAsCsv(reportData);
|
|
55
62
|
if (save) {
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
try {
|
|
64
|
+
fs.writeFileSync(path.resolve('nes.committers.csv'), csvOutput);
|
|
65
|
+
this.log('Report written to csv');
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
this.error(`Failed to save CSV report: ${getErrorMessage(error)}`);
|
|
69
|
+
}
|
|
58
70
|
}
|
|
59
71
|
else {
|
|
60
|
-
this.log(
|
|
72
|
+
this.log(textOutput);
|
|
61
73
|
}
|
|
62
74
|
return csvOutput;
|
|
63
75
|
}
|
|
64
|
-
// Text mode
|
|
65
|
-
const textOutput = formatAsText(reportData);
|
|
66
76
|
if (save) {
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
try {
|
|
78
|
+
fs.writeFileSync(path.resolve('nes.committers.txt'), textOutput);
|
|
79
|
+
this.log('Report written to txt');
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
this.error(`Failed to save txt report: ${getErrorMessage(error)}`);
|
|
83
|
+
}
|
|
69
84
|
}
|
|
70
85
|
else {
|
|
71
86
|
this.log(textOutput);
|
|
@@ -73,8 +88,7 @@ export default class Committers extends Command {
|
|
|
73
88
|
return textOutput;
|
|
74
89
|
}
|
|
75
90
|
catch (error) {
|
|
76
|
-
this.error(`Failed to generate report: ${error
|
|
77
|
-
throw error;
|
|
91
|
+
this.error(`Failed to generate report: ${getErrorMessage(error)}`);
|
|
78
92
|
}
|
|
79
93
|
}
|
|
80
94
|
/**
|
|
@@ -105,25 +119,28 @@ export default class Committers extends Command {
|
|
|
105
119
|
* @param sinceDate - Date range for git log
|
|
106
120
|
*/
|
|
107
121
|
fetchGitCommitData(sinceDate) {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (logProcess.error) {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
const logProcess = spawnSync('git', [
|
|
123
|
+
'log',
|
|
124
|
+
'--all', // Include committers on all branches in the repo
|
|
125
|
+
'--format="%ad|%an"', // Format: date|author
|
|
126
|
+
'--date=format:%Y-%m', // Format date as YYYY-MM
|
|
127
|
+
`--since="${sinceDate}"`,
|
|
128
|
+
], { encoding: 'utf-8' });
|
|
129
|
+
if (logProcess.error) {
|
|
130
|
+
if (isErrnoException(logProcess.error)) {
|
|
131
|
+
if (logProcess.error.code === 'ENOENT') {
|
|
132
|
+
this.error('Git command not found. Please ensure git is installed and available in your PATH.');
|
|
133
|
+
}
|
|
134
|
+
this.error(`Git command failed: ${getErrorMessage(logProcess.error)}`);
|
|
121
135
|
}
|
|
122
|
-
|
|
136
|
+
this.error(`Git command failed: ${getErrorMessage(logProcess.error)}`);
|
|
123
137
|
}
|
|
124
|
-
|
|
125
|
-
this.error(`
|
|
126
|
-
|
|
138
|
+
if (logProcess.status !== 0) {
|
|
139
|
+
this.error(`Git command failed with status ${logProcess.status}: ${logProcess.stderr}`);
|
|
140
|
+
}
|
|
141
|
+
if (!logProcess.stdout) {
|
|
142
|
+
return [];
|
|
127
143
|
}
|
|
144
|
+
return parseGitLogOutput(logProcess.stdout);
|
|
128
145
|
}
|
|
129
146
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { Command, Flags, ux } from '@oclif/core';
|
|
4
|
+
import { getErrorMessage, isErrnoException } from "../../service/error.svc.js";
|
|
4
5
|
import { extractPurls, getPurlOutput } from "../../service/purls.svc.js";
|
|
5
|
-
import
|
|
6
|
+
import ScanSbom from "../scan/sbom.js";
|
|
6
7
|
export default class ReportPurls extends Command {
|
|
7
8
|
static description = 'Generate a list of purls from a sbom';
|
|
8
9
|
static enableJsonFlag = true;
|
|
@@ -36,36 +37,48 @@ export default class ReportPurls extends Command {
|
|
|
36
37
|
async run() {
|
|
37
38
|
const { flags } = await this.parse(ReportPurls);
|
|
38
39
|
const { dir: _dirFlag, file: _fileFlag, save, csv } = flags;
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
ux.action.stop('Scan completed');
|
|
48
|
-
// Print the purls
|
|
49
|
-
for (const purl of purls) {
|
|
50
|
-
this.log(purl);
|
|
51
|
-
}
|
|
52
|
-
// Save if requested
|
|
53
|
-
if (save) {
|
|
54
|
-
try {
|
|
55
|
-
const outputFile = csv && !this.jsonEnabled() ? 'csv' : 'json';
|
|
56
|
-
const outputPath = path.join(_dirFlag || process.cwd(), `nes.purls.${outputFile}`);
|
|
57
|
-
const purlOutput = getPurlOutput(purls, outputFile);
|
|
58
|
-
fs.writeFileSync(outputPath, purlOutput);
|
|
59
|
-
this.log('Purls saved to %s', outputPath);
|
|
40
|
+
try {
|
|
41
|
+
const sbom = await ScanSbom.loadSbom(flags, this.config);
|
|
42
|
+
const purls = await extractPurls(sbom);
|
|
43
|
+
this.log('Extracted %d purls from SBOM', purls.length);
|
|
44
|
+
ux.action.stop('Scan completed');
|
|
45
|
+
// Print the purls
|
|
46
|
+
for (const purl of purls) {
|
|
47
|
+
this.log(purl);
|
|
60
48
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
49
|
+
// Save if requested
|
|
50
|
+
if (save) {
|
|
51
|
+
try {
|
|
52
|
+
const outputFile = csv && !this.jsonEnabled() ? 'csv' : 'json';
|
|
53
|
+
const outputPath = path.join(_dirFlag || process.cwd(), `nes.purls.${outputFile}`);
|
|
54
|
+
const purlOutput = getPurlOutput(purls, outputFile);
|
|
55
|
+
fs.writeFileSync(outputPath, purlOutput);
|
|
56
|
+
this.log('Purls saved to %s', outputPath);
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (isErrnoException(error)) {
|
|
60
|
+
switch (error.code) {
|
|
61
|
+
case 'EACCES':
|
|
62
|
+
this.error('Permission denied: Cannot write to output file');
|
|
63
|
+
break;
|
|
64
|
+
case 'ENOSPC':
|
|
65
|
+
this.error('No space left on device');
|
|
66
|
+
break;
|
|
67
|
+
case 'EISDIR':
|
|
68
|
+
this.error('Cannot write to output file: Is a directory');
|
|
69
|
+
break;
|
|
70
|
+
default:
|
|
71
|
+
this.error(`Failed to save purls: ${getErrorMessage(error)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
this.error(`Failed to save purls: ${getErrorMessage(error)}`);
|
|
75
|
+
}
|
|
64
76
|
}
|
|
77
|
+
// Return wrapped object with metadata
|
|
78
|
+
return { purls };
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
this.error(`Failed to generate PURLs: ${getErrorMessage(error)}`);
|
|
65
82
|
}
|
|
66
|
-
// Return wrapped object with metadata
|
|
67
|
-
return {
|
|
68
|
-
purls,
|
|
69
|
-
};
|
|
70
83
|
}
|
|
71
84
|
}
|
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
-
import type {
|
|
2
|
+
import type { 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
6
|
static examples: string[];
|
|
7
7
|
static flags: {
|
|
8
8
|
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>;
|
|
9
10
|
dir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
11
|
save: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
getCustomerSupport: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
14
|
};
|
|
12
|
-
run(): Promise<
|
|
13
|
-
components: [];
|
|
15
|
+
run(): Promise<{
|
|
16
|
+
components: InsightsEolScanComponent[];
|
|
14
17
|
}>;
|
|
15
|
-
private
|
|
18
|
+
private getScan;
|
|
19
|
+
private getPurlsFromFile;
|
|
20
|
+
private scanSbom;
|
|
21
|
+
private getFilteredComponents;
|
|
22
|
+
private saveReport;
|
|
23
|
+
private displayResults;
|
|
24
|
+
private displayNoComponentsMessage;
|
|
25
|
+
private logLine;
|
|
26
|
+
private displayStatusSection;
|
|
27
|
+
private logLegend;
|
|
16
28
|
}
|
|
@@ -1,19 +1,32 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
1
3
|
import { Command, Flags, ux } from '@oclif/core';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
4
|
+
import { batchSubmitPurls } from "../../api/nes/nes.client.js";
|
|
5
|
+
import { DEFAULT_SCAN_BATCH_SIZE, DEFAULT_SCAN_INPUT_OPTIONS } from '../../api/types/hd-cli.types.js';
|
|
6
|
+
import { getErrorMessage, isErrnoException } from "../../service/error.svc.js";
|
|
7
|
+
import { extractPurls } from "../../service/purls.svc.js";
|
|
8
|
+
import { parsePurlsFile } from "../../service/purls.svc.js";
|
|
9
|
+
import { createStatusDisplay } from "../../ui/eol.ui.js";
|
|
10
|
+
import { INDICATORS, STATUS_COLORS } from "../../ui/shared.us.js";
|
|
11
|
+
import ScanSbom from "./sbom.js";
|
|
5
12
|
export default class ScanEol extends Command {
|
|
6
13
|
static description = 'Scan a given sbom for EOL data';
|
|
7
14
|
static enableJsonFlag = true;
|
|
8
15
|
static examples = [
|
|
9
16
|
'<%= config.bin %> <%= command.id %> --dir=./my-project',
|
|
10
17
|
'<%= config.bin %> <%= command.id %> --file=path/to/sbom.json',
|
|
18
|
+
'<%= config.bin %> <%= command.id %> --purls=path/to/purls.json',
|
|
19
|
+
'<%= config.bin %> <%= command.id %> -a --dir=./my-project',
|
|
11
20
|
];
|
|
12
21
|
static flags = {
|
|
13
22
|
file: Flags.string({
|
|
14
23
|
char: 'f',
|
|
15
24
|
description: 'The file path of an existing cyclonedx sbom to scan for EOL',
|
|
16
25
|
}),
|
|
26
|
+
purls: Flags.string({
|
|
27
|
+
char: 'p',
|
|
28
|
+
description: 'The file path of a list of purls to scan for EOL',
|
|
29
|
+
}),
|
|
17
30
|
dir: Flags.string({
|
|
18
31
|
char: 'd',
|
|
19
32
|
description: 'The directory to scan in order to create a cyclonedx sbom',
|
|
@@ -23,52 +36,142 @@ export default class ScanEol extends Command {
|
|
|
23
36
|
default: false,
|
|
24
37
|
description: 'Save the generated SBOM as nes.sbom.json in the scanned directory',
|
|
25
38
|
}),
|
|
39
|
+
all: Flags.boolean({
|
|
40
|
+
char: 'a',
|
|
41
|
+
description: 'Show all components (default is EOL and LTS only)',
|
|
42
|
+
default: false,
|
|
43
|
+
}),
|
|
44
|
+
getCustomerSupport: Flags.boolean({
|
|
45
|
+
char: 'c',
|
|
46
|
+
description: 'Get Never-Ending Support for End-of-Life components',
|
|
47
|
+
default: false,
|
|
48
|
+
}),
|
|
26
49
|
};
|
|
27
50
|
async run() {
|
|
28
|
-
this.checkEolScanDisabled();
|
|
29
51
|
const { flags } = await this.parse(ScanEol);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
if (flags.getCustomerSupport) {
|
|
53
|
+
this.log(ux.colorize('yellow', 'Never-Ending Support is on the way. Please stay tuned for this feature.'));
|
|
54
|
+
}
|
|
55
|
+
const scan = await this.getScan(flags, this.config);
|
|
56
|
+
ux.action.stop('\nScan completed');
|
|
57
|
+
const filteredComponents = this.getFilteredComponents(scan, flags.all);
|
|
58
|
+
if (flags.save) {
|
|
59
|
+
await this.saveReport(filteredComponents);
|
|
60
|
+
}
|
|
61
|
+
if (this.jsonEnabled()) {
|
|
62
|
+
return { components: filteredComponents };
|
|
63
|
+
}
|
|
64
|
+
await this.displayResults(scan, flags.all);
|
|
65
|
+
return { components: filteredComponents };
|
|
66
|
+
}
|
|
67
|
+
async getScan(flags, config) {
|
|
68
|
+
if (flags.purls) {
|
|
69
|
+
ux.action.start(`Scanning purls from ${flags.purls}`);
|
|
70
|
+
const purls = this.getPurlsFromFile(flags.purls);
|
|
71
|
+
return batchSubmitPurls(purls, DEFAULT_SCAN_INPUT_OPTIONS, DEFAULT_SCAN_BATCH_SIZE);
|
|
72
|
+
}
|
|
73
|
+
const sbom = await ScanSbom.loadSbom(flags, config);
|
|
74
|
+
const scan = this.scanSbom(sbom, flags);
|
|
75
|
+
return scan;
|
|
76
|
+
}
|
|
77
|
+
getPurlsFromFile(filePath) {
|
|
78
|
+
if (typeof filePath !== 'string') {
|
|
79
|
+
this.error(`Failed to parse file path: ${filePath}`);
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const purlsFileString = fs.readFileSync(filePath, 'utf8');
|
|
83
|
+
return parsePurlsFile(purlsFileString);
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
this.error(`Failed to read purls file. ${getErrorMessage(error)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
async scanSbom(sbom, flags) {
|
|
90
|
+
let scan;
|
|
91
|
+
let purls;
|
|
92
|
+
try {
|
|
93
|
+
purls = await extractPurls(sbom);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
this.error(`Failed to extract purls from sbom. ${getErrorMessage(error)}`);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
scan = await batchSubmitPurls(purls, DEFAULT_SCAN_INPUT_OPTIONS, DEFAULT_SCAN_BATCH_SIZE);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
this.error(`Failed to submit scan to NES from sbom. ${getErrorMessage(error)}`);
|
|
103
|
+
}
|
|
104
|
+
if (scan.components.size === 0) {
|
|
105
|
+
this.warn('No components found in scan');
|
|
106
|
+
}
|
|
107
|
+
return scan;
|
|
108
|
+
}
|
|
109
|
+
getFilteredComponents(scan, all) {
|
|
110
|
+
return Array.from(scan.components.entries())
|
|
111
|
+
.filter(([_, component]) => all || ['EOL', 'LTS'].includes(component.info.status))
|
|
112
|
+
.map(([_, component]) => component);
|
|
113
|
+
}
|
|
114
|
+
async saveReport(components) {
|
|
115
|
+
try {
|
|
116
|
+
const { flags } = await this.parse(ScanEol);
|
|
117
|
+
const reportPath = path.join(flags.dir || process.cwd(), 'nes.eol.json');
|
|
118
|
+
fs.writeFileSync(reportPath, JSON.stringify({ components }, null, 2));
|
|
119
|
+
this.log('Report saved to nes.eol.json');
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (isErrnoException(error)) {
|
|
123
|
+
switch (error.code) {
|
|
124
|
+
case 'EACCES':
|
|
125
|
+
this.error('Permission denied. Unable to save report to nes.eol.json');
|
|
126
|
+
break;
|
|
127
|
+
case 'ENOSPC':
|
|
128
|
+
this.error('No space left on device. Unable to save report to nes.eol.json');
|
|
129
|
+
break;
|
|
130
|
+
default:
|
|
131
|
+
this.error(`Failed to save report: ${getErrorMessage(error)}`);
|
|
132
|
+
}
|
|
44
133
|
}
|
|
45
|
-
|
|
46
|
-
|
|
134
|
+
else {
|
|
135
|
+
this.error(`Failed to save report: ${getErrorMessage(error)}`);
|
|
47
136
|
}
|
|
48
|
-
throw new Error('Scan failed to generate components.');
|
|
49
137
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
138
|
+
}
|
|
139
|
+
async displayResults(scan, all) {
|
|
140
|
+
const { UNKNOWN, OK, LTS, EOL } = createStatusDisplay(scan.components, all);
|
|
141
|
+
if (!UNKNOWN.length && !OK.length && !LTS.length && !EOL.length) {
|
|
142
|
+
this.displayNoComponentsMessage(all);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.log(ux.colorize('bold', 'Here are the results of the scan:'));
|
|
146
|
+
this.logLine();
|
|
147
|
+
// Display sections in order of increasing severity
|
|
148
|
+
for (const components of [UNKNOWN, OK, LTS, EOL]) {
|
|
149
|
+
this.displayStatusSection(components);
|
|
150
|
+
}
|
|
151
|
+
this.logLegend();
|
|
152
|
+
}
|
|
153
|
+
displayNoComponentsMessage(all) {
|
|
154
|
+
if (!all) {
|
|
155
|
+
this.log(ux.colorize('yellow', 'No End-of-Life or Long Term Support components found in scan.'));
|
|
156
|
+
this.log(ux.colorize('yellow', 'Use --all flag to view all components.'));
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
this.log(ux.colorize('yellow', 'No components found in scan.'));
|
|
54
160
|
}
|
|
55
|
-
const r = await promptComponentDetails(lines);
|
|
56
|
-
this.log('What now %o', r);
|
|
57
|
-
return scan;
|
|
58
161
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (
|
|
64
|
-
this.log(
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
// Just in case the beta check fails
|
|
68
|
-
if (override) {
|
|
69
|
-
this.log(`VERSION=${version}`);
|
|
70
|
-
this.log('EOL scan is disabled');
|
|
71
|
-
return { components: [] };
|
|
162
|
+
logLine() {
|
|
163
|
+
this.log(ux.colorize('bold', '-'.repeat(50)));
|
|
164
|
+
}
|
|
165
|
+
displayStatusSection(components) {
|
|
166
|
+
if (components.length > 0) {
|
|
167
|
+
this.log(components.join('\n'));
|
|
168
|
+
this.logLine();
|
|
72
169
|
}
|
|
73
170
|
}
|
|
171
|
+
logLegend() {
|
|
172
|
+
this.log(ux.colorize(`${STATUS_COLORS.UNKNOWN}`, `${INDICATORS.UNKNOWN} = No Known Issues`));
|
|
173
|
+
this.log(ux.colorize(`${STATUS_COLORS.OK}`, `${INDICATORS.OK} = OK`));
|
|
174
|
+
this.log(ux.colorize(`${STATUS_COLORS.LTS}`, `${INDICATORS.LTS}= Long Term Support (LTS)`));
|
|
175
|
+
this.log(ux.colorize(`${STATUS_COLORS.EOL}`, `${INDICATORS.EOL} = End of Life (EOL)`));
|
|
176
|
+
}
|
|
74
177
|
}
|
|
@@ -10,6 +10,7 @@ export default class ScanSbom extends Command {
|
|
|
10
10
|
save: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
11
|
background: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
12
|
};
|
|
13
|
+
static loadSbom(flags: Record<string, string>, config: Command['config']): Promise<Sbom>;
|
|
13
14
|
static getSbomArgs(flags: Record<string, string>): string[];
|
|
14
15
|
getScanOptions(): {};
|
|
15
16
|
run(): Promise<Sbom | undefined>;
|