@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.
Files changed (38) hide show
  1. package/README.md +16 -309
  2. package/bin/dev.js +6 -5
  3. package/bin/run.js +3 -2
  4. package/dist/api/client.d.ts +0 -2
  5. package/dist/api/client.js +19 -18
  6. package/dist/api/nes/nes.client.d.ts +10 -3
  7. package/dist/api/nes/nes.client.js +89 -2
  8. package/dist/api/queries/nes/sbom.js +5 -0
  9. package/dist/api/types/hd-cli.types.d.ts +29 -0
  10. package/dist/api/types/hd-cli.types.js +10 -0
  11. package/dist/api/types/nes.types.d.ts +39 -22
  12. package/dist/api/types/nes.types.js +1 -1
  13. package/dist/commands/report/committers.js +45 -28
  14. package/dist/commands/report/purls.js +42 -29
  15. package/dist/commands/scan/eol.d.ts +16 -4
  16. package/dist/commands/scan/eol.js +144 -41
  17. package/dist/commands/scan/sbom.d.ts +1 -0
  18. package/dist/commands/scan/sbom.js +53 -32
  19. package/dist/service/committers.svc.js +24 -3
  20. package/dist/service/eol/cdx.svc.d.ts +2 -8
  21. package/dist/service/eol/cdx.svc.js +2 -18
  22. package/dist/service/eol/eol.svc.d.ts +1 -23
  23. package/dist/service/eol/eol.svc.js +0 -61
  24. package/dist/service/error.svc.d.ts +8 -0
  25. package/dist/service/error.svc.js +28 -0
  26. package/dist/service/nes/nes.svc.d.ts +4 -3
  27. package/dist/service/nes/nes.svc.js +5 -4
  28. package/dist/service/purls.svc.d.ts +6 -0
  29. package/dist/service/purls.svc.js +26 -0
  30. package/dist/ui/date.ui.d.ts +1 -0
  31. package/dist/ui/date.ui.js +15 -0
  32. package/dist/ui/eol.ui.d.ts +5 -3
  33. package/dist/ui/eol.ui.js +56 -15
  34. package/dist/ui/shared.us.d.ts +3 -0
  35. package/dist/ui/shared.us.js +13 -0
  36. package/package.json +10 -9
  37. package/dist/service/line.svc.d.ts +0 -24
  38. package/dist/service/line.svc.js +0 -61
@@ -1,35 +1,52 @@
1
- export type ScanInput = {
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: 'SBOM';
4
- } | {
5
- type: 'OTHER';
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: ScanResponseReport;
16
+ eol: InsightsEolScanResult;
11
17
  };
12
18
  };
13
19
  }
14
- export interface ScanResponseReport {
15
- components: ScanResultComponent[];
16
- diagnostics?: Record<string, unknown>;
17
- message: string;
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
- export type ComponentStatus = 'EOL' | 'LTS' | 'OK';
21
- export interface ScanResultComponent {
22
- info: {
23
- eolAt: Date | null;
24
- isEol: boolean;
25
- isUnsafe: boolean;
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 ScanResult {
31
- components: Map<string, ScanResultComponent>;
32
- diagnostics?: Record<string, unknown>;
44
+ export interface ScanWarning {
45
+ purl: string;
33
46
  message: string;
34
- success: boolean;
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
- fs.writeFileSync(path.resolve('nes.committers.json'), JSON.stringify(reportData, null, 2));
48
- this.log('Report written to json');
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
- fs.writeFileSync(path.resolve('nes.committers.csv'), csvOutput);
57
- this.log('Report written to csv');
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(csvOutput);
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
- fs.writeFileSync(path.resolve('nes.committers.txt'), textOutput);
68
- this.log('Report written to txt');
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.message}`);
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
- try {
109
- const logProcess = spawnSync('git', [
110
- 'log',
111
- '--all', // Include committers on all branches in the repo
112
- '--format="%ad|%an"', // Format: date|author
113
- '--date=format:%Y-%m', // Format date as YYYY-MM
114
- `--since="${sinceDate}"`,
115
- ], { encoding: 'utf-8' });
116
- if (logProcess.error) {
117
- throw new Error(`Git command failed: ${logProcess.error.message}`);
118
- }
119
- if (!logProcess.stdout) {
120
- return [];
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
- return parseGitLogOutput(logProcess.stdout);
136
+ this.error(`Git command failed: ${getErrorMessage(logProcess.error)}`);
123
137
  }
124
- catch (error) {
125
- this.error(`Failed to fetch git data: ${error.message}`);
126
- return []; // This line won't execute due to this.error() above
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 SbomScan from "../scan/sbom.js";
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
- const sbomArgs = SbomScan.getSbomArgs(flags);
40
- const sbomCommand = new SbomScan(sbomArgs, this.config);
41
- const sbom = await sbomCommand.run();
42
- if (!sbom) {
43
- throw new Error('SBOM not generated');
44
- }
45
- const purls = await extractPurls(sbom);
46
- this.log('Extracted %d purls from SBOM', purls.length);
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
- catch (error) {
62
- const errorMessage = error && typeof error === 'object' && 'message' in error ? error.message : 'Unknown error';
63
- this.warn(`Failed to save purls: ${errorMessage}`);
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 { ScanResult } from '../../api/types/nes.types.ts';
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<ScanResult | {
13
- components: [];
15
+ run(): Promise<{
16
+ components: InsightsEolScanComponent[];
14
17
  }>;
15
- private checkEolScanDisabled;
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 { prepareRows, scanForEol } from "../../service/eol/eol.svc.js";
3
- import { promptComponentDetails } from "../../ui/eol.ui.js";
4
- import SbomScan from "./sbom.js";
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
- const { dir: _dirFlag, file: _fileFlag } = flags;
31
- // Load the SBOM: Only pass the file, dir, and save flags to SbomScan
32
- const sbomArgs = SbomScan.getSbomArgs(flags);
33
- const sbomCommand = new SbomScan(sbomArgs, this.config);
34
- const sbom = await sbomCommand.run();
35
- if (!sbom) {
36
- throw new Error('SBOM not generated');
37
- }
38
- // Scan the SBOM for EOL information
39
- const { purls, scan } = await scanForEol(sbom);
40
- ux.action.stop('Scan completed');
41
- if (!scan?.components) {
42
- if (_fileFlag) {
43
- throw new Error(`Scan failed to generate for file path: ${_fileFlag}`);
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
- if (_dirFlag) {
46
- throw new Error(`Scan failed to generate for dir: ${_dirFlag}`);
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
- const lines = await prepareRows(purls, scan);
51
- if (lines?.length === 0) {
52
- this.log('No dependencies found');
53
- return { components: [] };
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
- checkEolScanDisabled(override = true) {
60
- // Check if running in beta version or pre v1.0.0
61
- const version = this.config.version;
62
- const [major] = version.split('.').map(Number);
63
- if (version.includes('beta') || major < 1) {
64
- this.log(`VERSION=${version}`);
65
- throw new Error('The EOL scan feature is not available in beta releases. Please wait for the stable release.');
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>;