@herodevs/cli 2.0.0-beta.1 → 2.0.0-beta.11
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 +201 -110
- package/bin/dev.js +1 -4
- package/bin/main.js +3 -3
- package/bin/run.js +1 -1
- package/dist/api/gql-operations.d.ts +2 -0
- package/dist/api/gql-operations.js +36 -0
- package/dist/api/nes.client.d.ts +12 -0
- package/dist/api/nes.client.js +84 -0
- package/dist/commands/scan/eol.d.ts +18 -19
- package/dist/commands/scan/eol.js +214 -142
- package/dist/config/constants.d.ts +9 -3
- package/dist/config/constants.js +19 -3
- package/dist/hooks/finally/finally.d.ts +3 -0
- package/dist/hooks/finally/finally.js +18 -0
- package/dist/hooks/{npm-update-notifier.js → init/00_npm-update-notifier.js} +3 -3
- package/dist/hooks/init/01_initialize_amplitude.d.ts +3 -0
- package/dist/hooks/init/01_initialize_amplitude.js +15 -0
- package/dist/service/analytics.svc.d.ts +27 -0
- package/dist/service/analytics.svc.js +112 -0
- package/dist/service/{eol/cdx.svc.d.ts → cdx.svc.d.ts} +8 -16
- package/dist/service/{eol/cdx.svc.js → cdx.svc.js} +17 -7
- package/dist/service/display.svc.d.ts +30 -0
- package/dist/service/display.svc.js +87 -0
- package/dist/service/file.svc.d.ts +30 -0
- package/dist/service/file.svc.js +115 -0
- package/dist/service/log.svc.d.ts +1 -0
- package/dist/service/log.svc.js +9 -0
- package/dist/service/{eol/sbom.worker.js → sbom.worker.js} +2 -1
- package/dist/utils/strip-typename.d.ts +1 -0
- package/dist/utils/strip-typename.js +15 -0
- package/package.json +33 -22
- package/dist/api/client.d.ts +0 -12
- package/dist/api/client.js +0 -43
- package/dist/api/nes/nes.client.d.ts +0 -23
- package/dist/api/nes/nes.client.js +0 -107
- package/dist/api/queries/nes/sbom.d.ts +0 -3
- package/dist/api/queries/nes/sbom.js +0 -35
- package/dist/api/queries/nes/telemetry.d.ts +0 -2
- package/dist/api/queries/nes/telemetry.js +0 -24
- package/dist/api/types/hd-cli.types.d.ts +0 -30
- package/dist/api/types/hd-cli.types.js +0 -10
- package/dist/api/types/nes.types.d.ts +0 -53
- package/dist/api/types/nes.types.js +0 -1
- package/dist/commands/report/committers.d.ts +0 -23
- package/dist/commands/report/committers.js +0 -146
- package/dist/commands/report/purls.d.ts +0 -15
- package/dist/commands/report/purls.js +0 -84
- package/dist/commands/scan/sbom.d.ts +0 -21
- package/dist/commands/scan/sbom.js +0 -159
- package/dist/service/committers.svc.d.ts +0 -70
- package/dist/service/committers.svc.js +0 -196
- package/dist/service/eol/eol.svc.d.ts +0 -14
- package/dist/service/eol/eol.svc.js +0 -49
- package/dist/service/error.svc.d.ts +0 -8
- package/dist/service/error.svc.js +0 -28
- package/dist/service/nes/nes.svc.d.ts +0 -5
- package/dist/service/nes/nes.svc.js +0 -27
- package/dist/service/purls.svc.d.ts +0 -23
- package/dist/service/purls.svc.js +0 -99
- package/dist/ui/date.ui.d.ts +0 -1
- package/dist/ui/date.ui.js +0 -15
- package/dist/ui/eol.ui.d.ts +0 -15
- package/dist/ui/eol.ui.js +0 -134
- package/dist/ui/shared.ui.d.ts +0 -6
- package/dist/ui/shared.ui.js +0 -16
- /package/dist/hooks/{npm-update-notifier.d.ts → init/00_npm-update-notifier.d.ts} +0 -0
- /package/dist/hooks/{prerun.d.ts → prerun/prerun.d.ts} +0 -0
- /package/dist/hooks/{prerun.js → prerun/prerun.js} +0 -0
- /package/dist/service/{eol/sbom.worker.d.ts → sbom.worker.d.ts} +0 -0
|
@@ -1,199 +1,271 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { config } from "../../config/constants.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
1
|
+
import { trimCdxBom } from '@herodevs/eol-shared';
|
|
2
|
+
import { Command, Flags } from '@oclif/core';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { submitScan } from "../../api/nes.client.js";
|
|
5
|
+
import { config, filenamePrefix } from "../../config/constants.js";
|
|
6
|
+
import { track } from "../../service/analytics.svc.js";
|
|
7
|
+
import { createSbom } from "../../service/cdx.svc.js";
|
|
8
|
+
import { countComponentsByStatus, formatDataPrivacyLink, formatReportSaveHint, formatScanResults, formatWebReportUrl, } from "../../service/display.svc.js";
|
|
9
|
+
import { readSbomFromFile, saveArtifactToFile, validateDirectory } from "../../service/file.svc.js";
|
|
10
|
+
import { getErrorMessage } from "../../service/log.svc.js";
|
|
11
11
|
export default class ScanEol extends Command {
|
|
12
|
-
static description = 'Scan a given
|
|
12
|
+
static description = 'Scan a given SBOM for EOL data';
|
|
13
13
|
static enableJsonFlag = true;
|
|
14
14
|
static examples = [
|
|
15
|
-
'
|
|
16
|
-
'<%= config.bin %> <%= command.id %> --
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
{ description: 'Default behavior (no command or flags specified)', command: '<%= config.bin %>' },
|
|
16
|
+
{ description: 'Equivalent to', command: '<%= config.bin %> <%= command.id %> --dir .' },
|
|
17
|
+
{
|
|
18
|
+
description: 'Skip SBOM generation and specify an existing file',
|
|
19
|
+
command: '<%= config.bin %> <%= command.id %> --file /path/to/sbom.json',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
description: 'Save the report or SBOM to a file',
|
|
23
|
+
command: '<%= config.bin %> <%= command.id %> --save --saveSbom',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
description: 'Output the report in JSON format (for APIs, CI, etc.)',
|
|
27
|
+
command: '<%= config.bin %> <%= command.id %> --json',
|
|
28
|
+
},
|
|
19
29
|
];
|
|
20
30
|
static flags = {
|
|
21
31
|
file: Flags.string({
|
|
22
32
|
char: 'f',
|
|
23
|
-
description: 'The file path of an existing
|
|
24
|
-
|
|
25
|
-
purls: Flags.string({
|
|
26
|
-
char: 'p',
|
|
27
|
-
description: 'The file path of a list of purls to scan for EOL',
|
|
33
|
+
description: 'The file path of an existing SBOM to scan for EOL (supports CycloneDX and SPDX 2.3 formats)',
|
|
34
|
+
exclusive: ['dir'],
|
|
28
35
|
}),
|
|
29
36
|
dir: Flags.string({
|
|
30
37
|
char: 'd',
|
|
31
|
-
|
|
38
|
+
default: process.cwd(),
|
|
39
|
+
defaultHelp: async () => '<current directory>',
|
|
40
|
+
description: 'The directory to scan in order to create a cyclonedx SBOM',
|
|
41
|
+
exclusive: ['file'],
|
|
32
42
|
}),
|
|
33
43
|
save: Flags.boolean({
|
|
34
44
|
char: 's',
|
|
35
45
|
default: false,
|
|
36
|
-
description:
|
|
46
|
+
description: `Save the generated report as ${filenamePrefix}.report.json in the scanned directory`,
|
|
47
|
+
}),
|
|
48
|
+
output: Flags.string({
|
|
49
|
+
char: 'o',
|
|
50
|
+
description: `Save the generated report to a custom path (defaults to ${filenamePrefix}.report.json when not provided)`,
|
|
51
|
+
}),
|
|
52
|
+
saveSbom: Flags.boolean({
|
|
53
|
+
aliases: ['save-sbom'],
|
|
54
|
+
default: false,
|
|
55
|
+
description: `Save the generated SBOM as ${filenamePrefix}.sbom.json in the scanned directory`,
|
|
56
|
+
}),
|
|
57
|
+
sbomOutput: Flags.string({
|
|
58
|
+
aliases: ['sbom-output'],
|
|
59
|
+
description: `Save the generated SBOM to a custom path (defaults to ${filenamePrefix}.sbom.json when not provided)`,
|
|
37
60
|
}),
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
description: 'Show all components (default is EOL and SUPPORTED only)',
|
|
61
|
+
saveTrimmedSbom: Flags.boolean({
|
|
62
|
+
aliases: ['save-trimmed-sbom'],
|
|
41
63
|
default: false,
|
|
64
|
+
description: `Save the trimmed SBOM as ${filenamePrefix}.sbom-trimmed.json in the scanned directory`,
|
|
42
65
|
}),
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
description: 'Display the results in a table',
|
|
66
|
+
hideReportUrl: Flags.boolean({
|
|
67
|
+
aliases: ['hide-report-url'],
|
|
46
68
|
default: false,
|
|
69
|
+
description: 'Hide the generated web report URL for this scan',
|
|
47
70
|
}),
|
|
71
|
+
version: Flags.version(),
|
|
48
72
|
};
|
|
49
73
|
async run() {
|
|
50
74
|
const { flags } = await this.parse(ScanEol);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
track('CLI EOL Scan Started', (context) => ({
|
|
76
|
+
command: context.command,
|
|
77
|
+
command_flags: context.command_flags,
|
|
78
|
+
}));
|
|
79
|
+
const sbomStartTime = performance.now();
|
|
80
|
+
const sbom = await this.loadSbom();
|
|
81
|
+
const sbomEndTime = performance.now();
|
|
82
|
+
if (!flags.file) {
|
|
83
|
+
track('CLI SBOM Generated', (context) => ({
|
|
84
|
+
command: context.command,
|
|
85
|
+
command_flags: context.command_flags,
|
|
86
|
+
sbom_generation_time: (sbomEndTime - sbomStartTime) / 1000,
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
let reportOutputPath = flags.output;
|
|
90
|
+
let sbomOutputPath = flags.sbomOutput;
|
|
91
|
+
if (flags.output && !flags.save) {
|
|
92
|
+
this.warn('--output requires --save to write the report. Run again with --save to create the file.');
|
|
93
|
+
reportOutputPath = undefined;
|
|
94
|
+
}
|
|
95
|
+
if (flags.sbomOutput && !flags.saveSbom) {
|
|
96
|
+
this.warn('--sbomOutput requires --saveSbom to write the SBOM. Run again with --saveSbom to create the file.');
|
|
97
|
+
sbomOutputPath = undefined;
|
|
98
|
+
}
|
|
99
|
+
const shouldSaveSbom = !flags.file && flags.saveSbom;
|
|
100
|
+
if (shouldSaveSbom) {
|
|
101
|
+
const sbomPath = this.saveSbom(flags.dir, sbom, sbomOutputPath);
|
|
102
|
+
this.log(`SBOM saved to ${sbomPath}`);
|
|
103
|
+
track('CLI SBOM Output Saved', (context) => ({
|
|
104
|
+
command: context.command,
|
|
105
|
+
command_flags: context.command_flags,
|
|
106
|
+
sbom_output_path: sbomPath,
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
if (!sbom.components?.length) {
|
|
110
|
+
track('CLI EOL Scan Ended, No Components Found', (context) => ({
|
|
111
|
+
command: context.command,
|
|
112
|
+
command_flags: context.command_flags,
|
|
113
|
+
}));
|
|
114
|
+
this.log('No components found in scan. Report not generated.');
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const scanStartTime = performance.now();
|
|
118
|
+
const scan = await this.scanSbom(sbom);
|
|
119
|
+
const scanEndTime = performance.now();
|
|
120
|
+
const componentCounts = countComponentsByStatus(scan);
|
|
121
|
+
track('CLI EOL Scan Completed', (context) => ({
|
|
122
|
+
command: context.command,
|
|
123
|
+
command_flags: context.command_flags,
|
|
124
|
+
eol_true_count: componentCounts.EOL,
|
|
125
|
+
eol_unknown_count: componentCounts.UNKNOWN,
|
|
126
|
+
nes_available_count: componentCounts.NES_AVAILABLE,
|
|
127
|
+
number_of_packages: componentCounts.TOTAL,
|
|
128
|
+
sbom_created: !flags.file,
|
|
129
|
+
scan_load_time: (scanEndTime - scanStartTime) / 1000,
|
|
130
|
+
scanned_ecosystems: componentCounts.ECOSYSTEMS,
|
|
131
|
+
web_report_link: !flags.hideReportUrl && scan.id ? `${config.eolReportUrl}/${scan.id}` : undefined,
|
|
132
|
+
web_report_hidden: flags.hideReportUrl,
|
|
133
|
+
}));
|
|
134
|
+
const shouldSaveReport = flags.save;
|
|
135
|
+
if (shouldSaveReport) {
|
|
136
|
+
const reportPath = this.saveReport(scan, flags.dir, reportOutputPath);
|
|
137
|
+
this.log(`Report saved to ${reportPath}`);
|
|
138
|
+
track('CLI JSON Scan Output Saved', (context) => ({
|
|
139
|
+
command: context.command,
|
|
140
|
+
command_flags: context.command_flags,
|
|
141
|
+
report_output_path: reportPath,
|
|
142
|
+
}));
|
|
56
143
|
}
|
|
57
144
|
if (!this.jsonEnabled()) {
|
|
58
|
-
|
|
59
|
-
this.log(`${scan.components.size} components scanned`);
|
|
60
|
-
this.displayResultsInTable(scan, flags.all);
|
|
61
|
-
}
|
|
62
|
-
else {
|
|
63
|
-
this.displayResults(scan, flags.all);
|
|
64
|
-
}
|
|
65
|
-
if (scan.scanId) {
|
|
66
|
-
this.printWebReportUrl(scan.scanId);
|
|
67
|
-
}
|
|
145
|
+
this.displayResults(scan, flags.hideReportUrl, Boolean(reportOutputPath || sbomOutputPath));
|
|
68
146
|
}
|
|
69
|
-
return
|
|
147
|
+
return scan;
|
|
70
148
|
}
|
|
71
|
-
async
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
149
|
+
async loadSbom() {
|
|
150
|
+
const { flags } = await this.parse(ScanEol);
|
|
151
|
+
const spinner = ora();
|
|
152
|
+
spinner.start(flags.file ? 'Loading SBOM file' : 'Generating SBOM');
|
|
153
|
+
const sbom = flags.file ? this.getSbomFromFile(flags.file) : await this.getSbomFromScan(flags.dir);
|
|
154
|
+
if (!sbom) {
|
|
155
|
+
spinner.fail(flags.file ? 'Failed to load SBOM file' : 'Failed to generate SBOM');
|
|
156
|
+
throw new Error('SBOM not generated');
|
|
157
|
+
}
|
|
158
|
+
spinner.succeed(flags.file ? 'Loaded SBOM file' : 'Generated SBOM');
|
|
159
|
+
return sbom;
|
|
79
160
|
}
|
|
80
|
-
|
|
161
|
+
async scanSbom(sbom) {
|
|
162
|
+
const { flags } = await this.parse(ScanEol);
|
|
163
|
+
const spinner = ora().start('Trimming SBOM');
|
|
164
|
+
const trimmedSbom = trimCdxBom(sbom);
|
|
165
|
+
spinner.succeed('SBOM trimmed');
|
|
166
|
+
if (flags.saveTrimmedSbom) {
|
|
167
|
+
const trimmedPath = this.saveTrimmedSbom(flags.dir, trimmedSbom);
|
|
168
|
+
this.log(`Trimmed SBOM saved to ${trimmedPath}`);
|
|
169
|
+
track('CLI Trimmed SBOM Output Saved', (context) => ({
|
|
170
|
+
command: context.command,
|
|
171
|
+
command_flags: context.command_flags,
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
spinner.start('Scanning for EOL packages');
|
|
81
175
|
try {
|
|
82
|
-
const
|
|
83
|
-
|
|
176
|
+
const scan = await submitScan({ sbom: trimmedSbom });
|
|
177
|
+
spinner.succeed('Scan completed');
|
|
178
|
+
return scan;
|
|
84
179
|
}
|
|
85
180
|
catch (error) {
|
|
86
|
-
|
|
181
|
+
spinner.fail('Scanning failed');
|
|
182
|
+
const errorMessage = getErrorMessage(error);
|
|
183
|
+
track('CLI EOL Scan Failed', (context) => ({
|
|
184
|
+
command: context.command,
|
|
185
|
+
command_flags: context.command_flags,
|
|
186
|
+
scan_failure_reason: errorMessage,
|
|
187
|
+
}));
|
|
188
|
+
this.error(`Failed to submit scan to NES. ${errorMessage}`);
|
|
87
189
|
}
|
|
88
190
|
}
|
|
89
|
-
|
|
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
|
-
}
|
|
96
|
-
async scanSbom(sbom) {
|
|
97
|
-
let scan;
|
|
98
|
-
let purls;
|
|
191
|
+
saveReport(report, dir, outputPath) {
|
|
99
192
|
try {
|
|
100
|
-
|
|
193
|
+
return saveArtifactToFile(dir, { kind: 'report', payload: report, outputPath });
|
|
101
194
|
}
|
|
102
195
|
catch (error) {
|
|
103
|
-
|
|
196
|
+
const errorMessage = getErrorMessage(error);
|
|
197
|
+
track('CLI Error Encountered', () => ({ error: errorMessage }));
|
|
198
|
+
this.error(errorMessage);
|
|
104
199
|
}
|
|
200
|
+
}
|
|
201
|
+
saveSbom(dir, sbom, outputPath) {
|
|
105
202
|
try {
|
|
106
|
-
|
|
203
|
+
return saveArtifactToFile(dir, { kind: 'sbom', payload: sbom, outputPath });
|
|
107
204
|
}
|
|
108
205
|
catch (error) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
this.warn('No components found in scan');
|
|
206
|
+
const errorMessage = getErrorMessage(error);
|
|
207
|
+
track('CLI Error Encountered', () => ({ error: errorMessage }));
|
|
208
|
+
this.error(errorMessage);
|
|
113
209
|
}
|
|
114
|
-
return scan;
|
|
115
|
-
}
|
|
116
|
-
getFilteredComponents(scan, all) {
|
|
117
|
-
return Array.from(scan.components.values()).filter((component) => all || ['EOL', 'SUPPORTED'].includes(component.info.status));
|
|
118
210
|
}
|
|
119
|
-
|
|
120
|
-
const { flags } = await this.parse(ScanEol);
|
|
121
|
-
const reportPath = path.join(flags.dir || process.cwd(), 'eol.report.json');
|
|
211
|
+
saveTrimmedSbom(dir, sbom) {
|
|
122
212
|
try {
|
|
123
|
-
|
|
124
|
-
this.log('Report saved to eol.report.json');
|
|
213
|
+
return saveArtifactToFile(dir, { kind: 'sbomTrimmed', payload: sbom });
|
|
125
214
|
}
|
|
126
215
|
catch (error) {
|
|
127
|
-
|
|
128
|
-
|
|
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)}`);
|
|
139
|
-
}
|
|
216
|
+
const errorMessage = getErrorMessage(error);
|
|
217
|
+
track('CLI Error Encountered', () => ({ error: errorMessage }));
|
|
218
|
+
this.error(errorMessage);
|
|
140
219
|
}
|
|
141
220
|
}
|
|
142
|
-
displayResults(
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
this.
|
|
146
|
-
return;
|
|
221
|
+
displayResults(report, hideReportUrl, hasCustomOutput) {
|
|
222
|
+
const lines = formatScanResults(report);
|
|
223
|
+
for (const line of lines) {
|
|
224
|
+
this.log(line);
|
|
147
225
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
226
|
+
if (!hideReportUrl && report.id) {
|
|
227
|
+
const lines = formatWebReportUrl(report.id, config.eolReportUrl);
|
|
228
|
+
for (const line of lines) {
|
|
229
|
+
this.log(line);
|
|
230
|
+
}
|
|
153
231
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const statuses = ['SUPPORTED', 'EOL'];
|
|
159
|
-
if (all) {
|
|
160
|
-
statuses.unshift('UNKNOWN', 'OK');
|
|
161
|
-
}
|
|
162
|
-
for (const status of statuses) {
|
|
163
|
-
const components = grouped[status];
|
|
164
|
-
if (components.length > 0) {
|
|
165
|
-
const table = createTableForStatus(grouped, status);
|
|
166
|
-
this.displayTable(table, components.length, status);
|
|
232
|
+
else if (hideReportUrl && !hasCustomOutput) {
|
|
233
|
+
const lines = formatReportSaveHint();
|
|
234
|
+
for (const line of lines) {
|
|
235
|
+
this.log(line);
|
|
167
236
|
}
|
|
168
237
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
this.log(
|
|
238
|
+
const privacyLines = formatDataPrivacyLink();
|
|
239
|
+
for (const line of privacyLines) {
|
|
240
|
+
this.log(line);
|
|
241
|
+
}
|
|
242
|
+
this.log('* Use --json to output the report payload');
|
|
243
|
+
this.log(`* Use --save to save the report to ${filenamePrefix}.report.json`);
|
|
244
|
+
this.log('* Use --help for more commands or options');
|
|
174
245
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
246
|
+
async getSbomFromScan(dirPath) {
|
|
247
|
+
try {
|
|
248
|
+
validateDirectory(dirPath);
|
|
249
|
+
const sbom = await createSbom(dirPath);
|
|
250
|
+
if (!sbom) {
|
|
251
|
+
this.error(`SBOM failed to generate for dir: ${dirPath}`);
|
|
252
|
+
}
|
|
253
|
+
return sbom;
|
|
179
254
|
}
|
|
180
|
-
|
|
181
|
-
|
|
255
|
+
catch (error) {
|
|
256
|
+
const errorMessage = getErrorMessage(error);
|
|
257
|
+
track('CLI Error Encountered', () => ({ error: errorMessage }));
|
|
258
|
+
this.error(`Failed to scan directory: ${errorMessage}`);
|
|
182
259
|
}
|
|
183
260
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
261
|
+
getSbomFromFile(filePath) {
|
|
262
|
+
try {
|
|
263
|
+
return readSbomFromFile(filePath);
|
|
264
|
+
}
|
|
265
|
+
catch (error) {
|
|
266
|
+
const errorMessage = getErrorMessage(error);
|
|
267
|
+
track('CLI Error Encountered', () => ({ error: errorMessage }));
|
|
268
|
+
this.error(errorMessage);
|
|
191
269
|
}
|
|
192
|
-
}
|
|
193
|
-
logLegend() {
|
|
194
|
-
this.log(ux.colorize(STATUS_COLORS.UNKNOWN, `${INDICATORS.UNKNOWN} = No Known Issues`));
|
|
195
|
-
this.log(ux.colorize(STATUS_COLORS.OK, `${INDICATORS.OK} = OK`));
|
|
196
|
-
this.log(ux.colorize(STATUS_COLORS.SUPPORTED, `${INDICATORS.SUPPORTED}= Supported: End-of-Life (EOL) is scheduled`));
|
|
197
|
-
this.log(ux.colorize(STATUS_COLORS.EOL, `${INDICATORS.EOL} = End of Life (EOL)`));
|
|
198
270
|
}
|
|
199
271
|
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
export declare const EOL_REPORT_URL = "https://
|
|
2
|
-
export declare const GRAPHQL_HOST = "https://
|
|
1
|
+
export declare const EOL_REPORT_URL = "https://apps.herodevs.com/eol/reports";
|
|
2
|
+
export declare const GRAPHQL_HOST = "https://gateway.prod.apps.herodevs.io";
|
|
3
3
|
export declare const GRAPHQL_PATH = "/graphql";
|
|
4
|
+
export declare const ANALYTICS_URL = "https://apps.herodevs.com/api/eol/track";
|
|
5
|
+
export declare const CONCURRENT_PAGE_REQUESTS = 3;
|
|
6
|
+
export declare const PAGE_SIZE = 500;
|
|
4
7
|
export declare const config: {
|
|
5
8
|
eolReportUrl: string;
|
|
6
9
|
graphqlHost: string;
|
|
7
10
|
graphqlPath: string;
|
|
8
|
-
|
|
11
|
+
analyticsUrl: string;
|
|
12
|
+
concurrentPageRequests: number;
|
|
13
|
+
pageSize: number;
|
|
9
14
|
};
|
|
15
|
+
export declare const filenamePrefix = "herodevs";
|
package/dist/config/constants.js
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
|
-
export const EOL_REPORT_URL = 'https://
|
|
2
|
-
export const GRAPHQL_HOST = 'https://
|
|
1
|
+
export const EOL_REPORT_URL = 'https://apps.herodevs.com/eol/reports';
|
|
2
|
+
export const GRAPHQL_HOST = 'https://gateway.prod.apps.herodevs.io';
|
|
3
3
|
export const GRAPHQL_PATH = '/graphql';
|
|
4
|
+
export const ANALYTICS_URL = 'https://apps.herodevs.com/api/eol/track';
|
|
5
|
+
export const CONCURRENT_PAGE_REQUESTS = 3;
|
|
6
|
+
export const PAGE_SIZE = 500;
|
|
7
|
+
let concurrentPageRequests = CONCURRENT_PAGE_REQUESTS;
|
|
8
|
+
const parsed = Number.parseInt(process.env.CONCURRENT_PAGE_REQUESTS ?? '0', 10);
|
|
9
|
+
if (parsed > 0) {
|
|
10
|
+
concurrentPageRequests = parsed;
|
|
11
|
+
}
|
|
12
|
+
let pageSize = PAGE_SIZE;
|
|
13
|
+
const parsedPageSize = Number.parseInt(process.env.PAGE_SIZE ?? '0', 10);
|
|
14
|
+
if (parsedPageSize > 0) {
|
|
15
|
+
pageSize = parsedPageSize;
|
|
16
|
+
}
|
|
4
17
|
export const config = {
|
|
5
18
|
eolReportUrl: process.env.EOL_REPORT_URL || EOL_REPORT_URL,
|
|
6
19
|
graphqlHost: process.env.GRAPHQL_HOST || GRAPHQL_HOST,
|
|
7
20
|
graphqlPath: process.env.GRAPHQL_PATH || GRAPHQL_PATH,
|
|
8
|
-
|
|
21
|
+
analyticsUrl: process.env.ANALYTICS_URL || ANALYTICS_URL,
|
|
22
|
+
concurrentPageRequests,
|
|
23
|
+
pageSize,
|
|
9
24
|
};
|
|
25
|
+
export const filenamePrefix = 'herodevs';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import ora, {} from 'ora';
|
|
2
|
+
import { track } from "../../service/analytics.svc.js";
|
|
3
|
+
const hook = async (opts) => {
|
|
4
|
+
const isHelpOrVersionCmd = opts.argv.includes('--help') || opts.argv.includes('--version');
|
|
5
|
+
const hasError = Boolean(opts.error);
|
|
6
|
+
let spinner;
|
|
7
|
+
if (!isHelpOrVersionCmd && !hasError) {
|
|
8
|
+
spinner = ora().start('Cleaning up');
|
|
9
|
+
}
|
|
10
|
+
await track('CLI Session Ended', (context) => ({
|
|
11
|
+
cli_version: context.cli_version,
|
|
12
|
+
ended_at: new Date(),
|
|
13
|
+
})).promise;
|
|
14
|
+
if (!isHelpOrVersionCmd && !hasError) {
|
|
15
|
+
spinner?.stop();
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
export default hook;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import updateNotifier, {} from 'update-notifier';
|
|
2
|
-
import pkg from '
|
|
3
|
-
import { debugLogger } from "
|
|
4
|
-
const updateNotifierHook = async (
|
|
2
|
+
import pkg from '../../../package.json' with { type: 'json' };
|
|
3
|
+
import { debugLogger } from "../../service/log.svc.js";
|
|
4
|
+
const updateNotifierHook = async () => {
|
|
5
5
|
debugLogger('pkg.version', pkg.version);
|
|
6
6
|
const distTag = getDistTag(pkg.version);
|
|
7
7
|
debugLogger('distTag', distTag);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { parseArgs } from 'node:util';
|
|
2
|
+
import { initializeAnalytics, track } from "../../service/analytics.svc.js";
|
|
3
|
+
const hook = async () => {
|
|
4
|
+
const args = parseArgs({ allowPositionals: true, strict: false });
|
|
5
|
+
initializeAnalytics();
|
|
6
|
+
track('CLI Command Submitted', (context) => ({
|
|
7
|
+
command: args.positionals.join(' ').trim(),
|
|
8
|
+
command_flags: Object.entries(args.values).flat().join(' '),
|
|
9
|
+
app_used: context.app_used,
|
|
10
|
+
ci_provider: context.ci_provider,
|
|
11
|
+
cli_version: context.cli_version,
|
|
12
|
+
started_at: context.started_at,
|
|
13
|
+
}));
|
|
14
|
+
};
|
|
15
|
+
export default hook;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Types } from '@amplitude/analytics-node';
|
|
2
|
+
interface AnalyticsContext {
|
|
3
|
+
locale?: string;
|
|
4
|
+
os_platform?: string;
|
|
5
|
+
os_release?: string;
|
|
6
|
+
started_at?: Date;
|
|
7
|
+
ended_at?: Date;
|
|
8
|
+
app_used?: string;
|
|
9
|
+
ci_provider?: string;
|
|
10
|
+
cli_version?: string;
|
|
11
|
+
command?: string;
|
|
12
|
+
command_flags?: string;
|
|
13
|
+
error?: string;
|
|
14
|
+
eol_true_count?: number;
|
|
15
|
+
eol_unknown_count?: number;
|
|
16
|
+
nes_available_count?: number;
|
|
17
|
+
nes_remediation_count?: number;
|
|
18
|
+
number_of_packages?: number;
|
|
19
|
+
sbom_created?: boolean;
|
|
20
|
+
scan_load_time?: number;
|
|
21
|
+
scanned_ecosystems?: string[];
|
|
22
|
+
scan_failure_reason?: string;
|
|
23
|
+
web_report_link?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function initializeAnalytics(): void;
|
|
26
|
+
export declare function track(event: string, getProperties?: (context: AnalyticsContext) => Partial<AnalyticsContext>): Types.AmplitudeReturn<Types.Result>;
|
|
27
|
+
export {};
|