@herodevs/cli 2.0.0-beta.10 → 2.0.0-beta.12
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 +47 -29
- package/dist/api/nes.client.js +10 -1
- package/dist/commands/scan/eol.d.ts +5 -0
- package/dist/commands/scan/eol.js +80 -22
- package/dist/hooks/finally/finally.js +4 -4
- package/dist/service/analytics.svc.d.ts +0 -1
- package/dist/service/cdx.svc.d.ts +9 -1
- package/dist/service/cdx.svc.js +17 -12
- package/dist/service/display.svc.d.ts +8 -0
- package/dist/service/display.svc.js +17 -2
- package/dist/service/file.svc.d.ts +17 -7
- package/dist/service/file.svc.js +80 -36
- package/package.json +10 -11
- package/dist/service/sbom.worker.d.ts +0 -1
- package/dist/service/sbom.worker.js +0 -26
package/README.md
CHANGED
|
@@ -10,6 +10,11 @@ The HeroDevs CLI
|
|
|
10
10
|
* [@herodevs/cli](#herodevscli)
|
|
11
11
|
<!-- tocstop -->
|
|
12
12
|
|
|
13
|
+
### Terms and Data Security
|
|
14
|
+
|
|
15
|
+
- [HeroDevs End of Life Dataset Terms of Service and Data Policy](https://docs.herodevs.com/legal/end-of-life-dataset-terms)
|
|
16
|
+
- [HeroDevs End of Life Dataset Data Privacy and Security](https://docs.herodevs.com/eol-ds/data-privacy-and-security)
|
|
17
|
+
|
|
13
18
|
### Prerequisites
|
|
14
19
|
|
|
15
20
|
- Install node v20 or higher: [Download Node](https://nodejs.org/en/download)
|
|
@@ -38,17 +43,13 @@ npm install -g @herodevs/cli@beta
|
|
|
38
43
|
HeroDevs CLI is available as a binary installation, without requiring `npm`. To do that, you may either download and run the script manually, or use the following cURL or Wget command:
|
|
39
44
|
|
|
40
45
|
```sh
|
|
41
|
-
curl -o- https://raw.githubusercontent.com/herodevs/cli/v2.0.0-beta.
|
|
46
|
+
curl -o- https://raw.githubusercontent.com/herodevs/cli/v2.0.0-beta.12/scripts/install.sh | bash
|
|
42
47
|
```
|
|
43
48
|
|
|
44
49
|
```sh
|
|
45
|
-
wget -qO- https://raw.githubusercontent.com/herodevs/cli/v2.0.0-beta.
|
|
50
|
+
wget -qO- https://raw.githubusercontent.com/herodevs/cli/v2.0.0-beta.12/scripts/install.sh | bash
|
|
46
51
|
```
|
|
47
52
|
|
|
48
|
-
## TERMS
|
|
49
|
-
|
|
50
|
-
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).
|
|
51
|
-
|
|
52
53
|
## Scanning Behavior
|
|
53
54
|
|
|
54
55
|
The CLI is designed to be non-invasive:
|
|
@@ -103,7 +104,7 @@ DESCRIPTION
|
|
|
103
104
|
Display help for hd.
|
|
104
105
|
```
|
|
105
106
|
|
|
106
|
-
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.
|
|
107
|
+
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v6.2.33/src/commands/help.ts)_
|
|
107
108
|
|
|
108
109
|
## `hd scan eol`
|
|
109
110
|
|
|
@@ -111,14 +112,18 @@ Scan a given SBOM for EOL data
|
|
|
111
112
|
|
|
112
113
|
```
|
|
113
114
|
USAGE
|
|
114
|
-
$ hd scan eol [--json] [-f <value> | -d <value>] [-s] [--saveSbom] [--version]
|
|
115
|
+
$ hd scan eol [--json] [-f <value> | -d <value>] [-s] [-o <value>] [--saveSbom] [--sbomOutput <value>] [--saveTrimmedSbom] [--hideReportUrl] [--version]
|
|
115
116
|
|
|
116
117
|
FLAGS
|
|
117
|
-
-d, --dir=<value>
|
|
118
|
-
-f, --file=<value>
|
|
119
|
-
-s, --save
|
|
120
|
-
|
|
121
|
-
--
|
|
118
|
+
-d, --dir=<value> [default: <current directory>] The directory to scan in order to scan for EOL
|
|
119
|
+
-f, --file=<value> The file path of an existing SBOM to scan for EOL (supports CycloneDX and SPDX 2.3 formats)
|
|
120
|
+
-s, --save Save the generated report as herodevs.report.json in the scanned directory
|
|
121
|
+
-o, --output=<value> Save the generated report to a custom path (requires --save, defaults to herodevs.report.json when not provided)
|
|
122
|
+
--hideReportUrl Hide the generated web report URL for this scan
|
|
123
|
+
--saveSbom Save the generated SBOM as herodevs.sbom.json in the scanned directory
|
|
124
|
+
--sbomOutput=<value> Save the generated SBOM to a custom path (requires --saveSbom, defaults to herodevs.sbom.json when not provided)
|
|
125
|
+
--saveTrimmedSbom Save the trimmed SBOM as herodevs.sbom-trimmed.json in the scanned directory
|
|
126
|
+
--version Show CLI version.
|
|
122
127
|
|
|
123
128
|
GLOBAL FLAGS
|
|
124
129
|
--json Format output as json.
|
|
@@ -143,6 +148,10 @@ EXAMPLES
|
|
|
143
148
|
|
|
144
149
|
$ hd scan eol --save --saveSbom
|
|
145
150
|
|
|
151
|
+
Save the report and SBOM to custom paths
|
|
152
|
+
|
|
153
|
+
$ hd scan eol --dir . --save --saveSbom --output ./reports/my-report.json --sbomOutput ./reports/my-sbom.json
|
|
154
|
+
|
|
146
155
|
Output the report in JSON format (for APIs, CI, etc.)
|
|
147
156
|
|
|
148
157
|
$ hd scan eol --json
|
|
@@ -188,7 +197,7 @@ EXAMPLES
|
|
|
188
197
|
$ hd update --available
|
|
189
198
|
```
|
|
190
199
|
|
|
191
|
-
_See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.7.
|
|
200
|
+
_See code: [@oclif/plugin-update](https://github.com/oclif/plugin-update/blob/v4.7.8/src/commands/update.ts)_
|
|
192
201
|
<!-- commandsstop -->
|
|
193
202
|
|
|
194
203
|
## CI/CD Usage
|
|
@@ -200,6 +209,8 @@ You can use `@herodevs/cli` in your CI/CD pipelines to automate EOL scanning.
|
|
|
200
209
|
We provide a Docker image that's pre-configured to run EOL scans. Based on [`cdxgen`](https://github.com/CycloneDX/cdxgen),
|
|
201
210
|
it contains build tools for most project types and will provide best results when generating an SBOM. Use these templates to generate a report and save it to your CI job artifact for analysis and processing after your scan runs.
|
|
202
211
|
|
|
212
|
+
**Note:** There is a potential to run into permission issues writing out the report to your CI runner. Please ensure that your CI runner is setup to have proper read/write permissions for wherever your output files are being written to.
|
|
213
|
+
|
|
203
214
|
#### GitHub Actions
|
|
204
215
|
|
|
205
216
|
```yaml
|
|
@@ -215,19 +226,25 @@ on:
|
|
|
215
226
|
jobs:
|
|
216
227
|
scan:
|
|
217
228
|
runs-on: ubuntu-latest
|
|
229
|
+
environment: demo
|
|
218
230
|
steps:
|
|
219
|
-
-
|
|
231
|
+
- name: Checkout repository
|
|
232
|
+
uses: actions/checkout@v5
|
|
220
233
|
|
|
221
|
-
- name: Run EOL Scan
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
234
|
+
- name: Run EOL Scan
|
|
235
|
+
run: |
|
|
236
|
+
docker run --name eol-scanner \
|
|
237
|
+
-v $GITHUB_WORKSPACE:/app \
|
|
238
|
+
-w /app \
|
|
239
|
+
ghcr.io/herodevs/eol-scan --save --output /tmp/herodevs.report.json
|
|
240
|
+
docker cp eol-scanner:/tmp/herodevs.report.json ${{ runner.temp }}/herodevs.report.json
|
|
241
|
+
docker rm eol-scanner
|
|
242
|
+
|
|
243
|
+
- name: Upload artifact
|
|
244
|
+
uses: actions/upload-artifact@v5
|
|
228
245
|
with:
|
|
229
246
|
name: my-eol-report
|
|
230
|
-
path: herodevs.report.json
|
|
247
|
+
path: ${{ runner.temp }}/herodevs.report.json
|
|
231
248
|
```
|
|
232
249
|
|
|
233
250
|
#### GitLab CI/CD
|
|
@@ -270,18 +287,19 @@ jobs:
|
|
|
270
287
|
scan:
|
|
271
288
|
runs-on: ubuntu-latest
|
|
272
289
|
steps:
|
|
273
|
-
- uses: actions/checkout@
|
|
274
|
-
|
|
290
|
+
- uses: actions/checkout@v5
|
|
291
|
+
|
|
292
|
+
- uses: actions/setup-node@v6
|
|
275
293
|
with:
|
|
276
|
-
node-version: '
|
|
294
|
+
node-version: '24'
|
|
277
295
|
|
|
278
296
|
- run: echo # Prepare environment, install tooling, perform setup, etc.
|
|
279
297
|
|
|
280
298
|
- name: Run EOL Scan
|
|
281
|
-
run: npx @herodevs/cli@beta
|
|
299
|
+
run: npx @herodevs/cli@beta scan eol
|
|
282
300
|
|
|
283
|
-
- name: Upload
|
|
284
|
-
uses: actions/upload-artifact@
|
|
301
|
+
- name: Upload artifact
|
|
302
|
+
uses: actions/upload-artifact@v5
|
|
285
303
|
with:
|
|
286
304
|
name: my-eol-report
|
|
287
305
|
path: herodevs.report.json
|
package/dist/api/nes.client.js
CHANGED
|
@@ -6,7 +6,8 @@ import { createReportMutation, getEolReportQuery } from "./gql-operations.js";
|
|
|
6
6
|
export const createApollo = (uri) => new ApolloClient({
|
|
7
7
|
cache: new InMemoryCache(),
|
|
8
8
|
defaultOptions: {
|
|
9
|
-
query: { fetchPolicy: 'no-cache' },
|
|
9
|
+
query: { fetchPolicy: 'no-cache', errorPolicy: 'all' },
|
|
10
|
+
mutate: { errorPolicy: 'all' },
|
|
10
11
|
},
|
|
11
12
|
link: new HttpLink({
|
|
12
13
|
uri,
|
|
@@ -21,6 +22,10 @@ export const SbomScanner = (client) => {
|
|
|
21
22
|
mutation: createReportMutation,
|
|
22
23
|
variables: { input },
|
|
23
24
|
});
|
|
25
|
+
if (res?.errors?.length) {
|
|
26
|
+
debugLogger('GraphQL errors in createReport: %o', res.errors);
|
|
27
|
+
throw new Error('Failed to create EOL report');
|
|
28
|
+
}
|
|
24
29
|
const result = res.data?.eol?.createReport;
|
|
25
30
|
if (!result?.success || !result.id) {
|
|
26
31
|
debugLogger('failed scan %o', result || {});
|
|
@@ -44,6 +49,10 @@ export const SbomScanner = (client) => {
|
|
|
44
49
|
const batch = pages.slice(i, i + config.concurrentPageRequests);
|
|
45
50
|
const batchResponses = await Promise.all(batch);
|
|
46
51
|
for (const response of batchResponses) {
|
|
52
|
+
if (response?.errors?.length) {
|
|
53
|
+
debugLogger('GraphQL errors in getReport query: %o', response.errors);
|
|
54
|
+
throw new Error('Failed to fetch EOL report');
|
|
55
|
+
}
|
|
47
56
|
const report = response.data.eol.report;
|
|
48
57
|
reportMetadata ??= report;
|
|
49
58
|
components.push(...(report?.components ?? []));
|
|
@@ -11,7 +11,11 @@ export default class ScanEol extends Command {
|
|
|
11
11
|
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
dir: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
13
|
save: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
15
|
saveSbom: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
sbomOutput: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
17
|
+
saveTrimmedSbom: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
18
|
+
hideReportUrl: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
19
|
version: import("@oclif/core/interfaces").BooleanFlag<void>;
|
|
16
20
|
};
|
|
17
21
|
run(): Promise<EolReport | undefined>;
|
|
@@ -19,6 +23,7 @@ export default class ScanEol extends Command {
|
|
|
19
23
|
private scanSbom;
|
|
20
24
|
private saveReport;
|
|
21
25
|
private saveSbom;
|
|
26
|
+
private saveTrimmedSbom;
|
|
22
27
|
private displayResults;
|
|
23
28
|
private getSbomFromScan;
|
|
24
29
|
private getSbomFromFile;
|
|
@@ -5,8 +5,8 @@ import { submitScan } from "../../api/nes.client.js";
|
|
|
5
5
|
import { config, filenamePrefix } from "../../config/constants.js";
|
|
6
6
|
import { track } from "../../service/analytics.svc.js";
|
|
7
7
|
import { createSbom } from "../../service/cdx.svc.js";
|
|
8
|
-
import { countComponentsByStatus, formatScanResults, formatWebReportUrl } from "../../service/display.svc.js";
|
|
9
|
-
import { readSbomFromFile,
|
|
8
|
+
import { countComponentsByStatus, formatDataPrivacyLink, formatReportSaveHint, formatScanResults, formatWebReportUrl, } from "../../service/display.svc.js";
|
|
9
|
+
import { readSbomFromFile, saveArtifactToFile, validateDirectory } from "../../service/file.svc.js";
|
|
10
10
|
import { getErrorMessage } from "../../service/log.svc.js";
|
|
11
11
|
export default class ScanEol extends Command {
|
|
12
12
|
static description = 'Scan a given SBOM for EOL data';
|
|
@@ -30,7 +30,7 @@ export default class ScanEol extends Command {
|
|
|
30
30
|
static flags = {
|
|
31
31
|
file: Flags.string({
|
|
32
32
|
char: 'f',
|
|
33
|
-
description: 'The file path of an existing
|
|
33
|
+
description: 'The file path of an existing SBOM to scan for EOL (supports CycloneDX and SPDX 2.3 formats)',
|
|
34
34
|
exclusive: ['dir'],
|
|
35
35
|
}),
|
|
36
36
|
dir: Flags.string({
|
|
@@ -45,11 +45,29 @@ export default class ScanEol extends Command {
|
|
|
45
45
|
default: false,
|
|
46
46
|
description: `Save the generated report as ${filenamePrefix}.report.json in the scanned directory`,
|
|
47
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
|
+
}),
|
|
48
52
|
saveSbom: Flags.boolean({
|
|
49
53
|
aliases: ['save-sbom'],
|
|
50
54
|
default: false,
|
|
51
55
|
description: `Save the generated SBOM as ${filenamePrefix}.sbom.json in the scanned directory`,
|
|
52
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)`,
|
|
60
|
+
}),
|
|
61
|
+
saveTrimmedSbom: Flags.boolean({
|
|
62
|
+
aliases: ['save-trimmed-sbom'],
|
|
63
|
+
default: false,
|
|
64
|
+
description: `Save the trimmed SBOM as ${filenamePrefix}.sbom-trimmed.json in the scanned directory`,
|
|
65
|
+
}),
|
|
66
|
+
hideReportUrl: Flags.boolean({
|
|
67
|
+
aliases: ['hide-report-url'],
|
|
68
|
+
default: false,
|
|
69
|
+
description: 'Hide the generated web report URL for this scan',
|
|
70
|
+
}),
|
|
53
71
|
version: Flags.version(),
|
|
54
72
|
};
|
|
55
73
|
async run() {
|
|
@@ -57,7 +75,6 @@ export default class ScanEol extends Command {
|
|
|
57
75
|
track('CLI EOL Scan Started', (context) => ({
|
|
58
76
|
command: context.command,
|
|
59
77
|
command_flags: context.command_flags,
|
|
60
|
-
scan_location: flags.dir,
|
|
61
78
|
}));
|
|
62
79
|
const sbomStartTime = performance.now();
|
|
63
80
|
const sbom = await this.loadSbom();
|
|
@@ -66,12 +83,22 @@ export default class ScanEol extends Command {
|
|
|
66
83
|
track('CLI SBOM Generated', (context) => ({
|
|
67
84
|
command: context.command,
|
|
68
85
|
command_flags: context.command_flags,
|
|
69
|
-
scan_location: flags.dir,
|
|
70
86
|
sbom_generation_time: (sbomEndTime - sbomStartTime) / 1000,
|
|
71
87
|
}));
|
|
72
88
|
}
|
|
73
|
-
|
|
74
|
-
|
|
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);
|
|
75
102
|
this.log(`SBOM saved to ${sbomPath}`);
|
|
76
103
|
track('CLI SBOM Output Saved', (context) => ({
|
|
77
104
|
command: context.command,
|
|
@@ -83,7 +110,6 @@ export default class ScanEol extends Command {
|
|
|
83
110
|
track('CLI EOL Scan Ended, No Components Found', (context) => ({
|
|
84
111
|
command: context.command,
|
|
85
112
|
command_flags: context.command_flags,
|
|
86
|
-
scan_location: flags.dir,
|
|
87
113
|
}));
|
|
88
114
|
this.log('No components found in scan. Report not generated.');
|
|
89
115
|
return;
|
|
@@ -100,13 +126,14 @@ export default class ScanEol extends Command {
|
|
|
100
126
|
nes_available_count: componentCounts.NES_AVAILABLE,
|
|
101
127
|
number_of_packages: componentCounts.TOTAL,
|
|
102
128
|
sbom_created: !flags.file,
|
|
103
|
-
scan_location: flags.dir,
|
|
104
129
|
scan_load_time: (scanEndTime - scanStartTime) / 1000,
|
|
105
130
|
scanned_ecosystems: componentCounts.ECOSYSTEMS,
|
|
106
|
-
web_report_link: scan.id ? `${config.eolReportUrl}/${scan.id}` : undefined,
|
|
131
|
+
web_report_link: !flags.hideReportUrl && scan.id ? `${config.eolReportUrl}/${scan.id}` : undefined,
|
|
132
|
+
web_report_hidden: flags.hideReportUrl,
|
|
107
133
|
}));
|
|
108
|
-
|
|
109
|
-
|
|
134
|
+
const shouldSaveReport = flags.save;
|
|
135
|
+
if (shouldSaveReport) {
|
|
136
|
+
const reportPath = this.saveReport(scan, flags.dir, reportOutputPath);
|
|
110
137
|
this.log(`Report saved to ${reportPath}`);
|
|
111
138
|
track('CLI JSON Scan Output Saved', (context) => ({
|
|
112
139
|
command: context.command,
|
|
@@ -115,7 +142,7 @@ export default class ScanEol extends Command {
|
|
|
115
142
|
}));
|
|
116
143
|
}
|
|
117
144
|
if (!this.jsonEnabled()) {
|
|
118
|
-
this.displayResults(scan);
|
|
145
|
+
this.displayResults(scan, flags.hideReportUrl, Boolean(reportOutputPath || sbomOutputPath));
|
|
119
146
|
}
|
|
120
147
|
return scan;
|
|
121
148
|
}
|
|
@@ -132,9 +159,21 @@ export default class ScanEol extends Command {
|
|
|
132
159
|
return sbom;
|
|
133
160
|
}
|
|
134
161
|
async scanSbom(sbom) {
|
|
135
|
-
const
|
|
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');
|
|
136
175
|
try {
|
|
137
|
-
const scan = await submitScan({ sbom:
|
|
176
|
+
const scan = await submitScan({ sbom: trimmedSbom });
|
|
138
177
|
spinner.succeed('Scan completed');
|
|
139
178
|
return scan;
|
|
140
179
|
}
|
|
@@ -144,15 +183,14 @@ export default class ScanEol extends Command {
|
|
|
144
183
|
track('CLI EOL Scan Failed', (context) => ({
|
|
145
184
|
command: context.command,
|
|
146
185
|
command_flags: context.command_flags,
|
|
147
|
-
scan_location: context.scan_location,
|
|
148
186
|
scan_failure_reason: errorMessage,
|
|
149
187
|
}));
|
|
150
188
|
this.error(`Failed to submit scan to NES. ${errorMessage}`);
|
|
151
189
|
}
|
|
152
190
|
}
|
|
153
|
-
saveReport(report, dir) {
|
|
191
|
+
saveReport(report, dir, outputPath) {
|
|
154
192
|
try {
|
|
155
|
-
return
|
|
193
|
+
return saveArtifactToFile(dir, { kind: 'report', payload: report, outputPath });
|
|
156
194
|
}
|
|
157
195
|
catch (error) {
|
|
158
196
|
const errorMessage = getErrorMessage(error);
|
|
@@ -160,9 +198,9 @@ export default class ScanEol extends Command {
|
|
|
160
198
|
this.error(errorMessage);
|
|
161
199
|
}
|
|
162
200
|
}
|
|
163
|
-
saveSbom(dir, sbom) {
|
|
201
|
+
saveSbom(dir, sbom, outputPath) {
|
|
164
202
|
try {
|
|
165
|
-
return
|
|
203
|
+
return saveArtifactToFile(dir, { kind: 'sbom', payload: sbom, outputPath });
|
|
166
204
|
}
|
|
167
205
|
catch (error) {
|
|
168
206
|
const errorMessage = getErrorMessage(error);
|
|
@@ -170,17 +208,37 @@ export default class ScanEol extends Command {
|
|
|
170
208
|
this.error(errorMessage);
|
|
171
209
|
}
|
|
172
210
|
}
|
|
173
|
-
|
|
211
|
+
saveTrimmedSbom(dir, sbom) {
|
|
212
|
+
try {
|
|
213
|
+
return saveArtifactToFile(dir, { kind: 'sbomTrimmed', payload: sbom });
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
const errorMessage = getErrorMessage(error);
|
|
217
|
+
track('CLI Error Encountered', () => ({ error: errorMessage }));
|
|
218
|
+
this.error(errorMessage);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
displayResults(report, hideReportUrl, hasCustomOutput) {
|
|
174
222
|
const lines = formatScanResults(report);
|
|
175
223
|
for (const line of lines) {
|
|
176
224
|
this.log(line);
|
|
177
225
|
}
|
|
178
|
-
if (report.id) {
|
|
226
|
+
if (!hideReportUrl && report.id) {
|
|
179
227
|
const lines = formatWebReportUrl(report.id, config.eolReportUrl);
|
|
180
228
|
for (const line of lines) {
|
|
181
229
|
this.log(line);
|
|
182
230
|
}
|
|
183
231
|
}
|
|
232
|
+
else if (hideReportUrl && !hasCustomOutput) {
|
|
233
|
+
const lines = formatReportSaveHint();
|
|
234
|
+
for (const line of lines) {
|
|
235
|
+
this.log(line);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
const privacyLines = formatDataPrivacyLink();
|
|
239
|
+
for (const line of privacyLines) {
|
|
240
|
+
this.log(line);
|
|
241
|
+
}
|
|
184
242
|
this.log('* Use --json to output the report payload');
|
|
185
243
|
this.log(`* Use --save to save the report to ${filenamePrefix}.report.json`);
|
|
186
244
|
this.log('* Use --help for more commands or options');
|
|
@@ -2,16 +2,16 @@ import ora, {} from 'ora';
|
|
|
2
2
|
import { track } from "../../service/analytics.svc.js";
|
|
3
3
|
const hook = async (opts) => {
|
|
4
4
|
const isHelpOrVersionCmd = opts.argv.includes('--help') || opts.argv.includes('--version');
|
|
5
|
+
const hasError = Boolean(opts.error);
|
|
5
6
|
let spinner;
|
|
6
|
-
if (!isHelpOrVersionCmd) {
|
|
7
|
+
if (!isHelpOrVersionCmd && !hasError) {
|
|
7
8
|
spinner = ora().start('Cleaning up');
|
|
8
9
|
}
|
|
9
|
-
|
|
10
|
+
await track('CLI Session Ended', (context) => ({
|
|
10
11
|
cli_version: context.cli_version,
|
|
11
12
|
ended_at: new Date(),
|
|
12
13
|
})).promise;
|
|
13
|
-
if (!isHelpOrVersionCmd) {
|
|
14
|
-
await event;
|
|
14
|
+
if (!isHelpOrVersionCmd && !hasError) {
|
|
15
15
|
spinner?.stop();
|
|
16
16
|
}
|
|
17
17
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createBom } from '@cyclonedx/cdxgen';
|
|
2
|
+
import { postProcess } from '@cyclonedx/cdxgen/stages/postgen/postgen';
|
|
1
3
|
import type { CdxBom } from '@herodevs/eol-shared';
|
|
2
4
|
export declare const SBOM_DEFAULT__OPTIONS: {
|
|
3
5
|
$0: string;
|
|
@@ -61,4 +63,10 @@ export declare const SBOM_DEFAULT__OPTIONS: {
|
|
|
61
63
|
* Lazy loads cdxgen (for ESM purposes), scans
|
|
62
64
|
* `directory`, and returns the `bomJson` property.
|
|
63
65
|
*/
|
|
64
|
-
|
|
66
|
+
type CreateSbomDependencies = {
|
|
67
|
+
createBom: typeof createBom;
|
|
68
|
+
postProcess: typeof postProcess;
|
|
69
|
+
};
|
|
70
|
+
export declare function createSbomFactory({ createBom: createBomDependency, postProcess: postProcessDependency, }?: Partial<CreateSbomDependencies>): (directory: string) => Promise<CdxBom>;
|
|
71
|
+
export declare const createSbom: (directory: string) => Promise<CdxBom>;
|
|
72
|
+
export {};
|
package/dist/service/cdx.svc.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createBom } from '@cyclonedx/cdxgen';
|
|
2
|
+
import { postProcess } from '@cyclonedx/cdxgen/stages/postgen/postgen';
|
|
2
3
|
import { debugLogger } from "./log.svc.js";
|
|
3
4
|
const author = process.env.npm_package_author ?? 'HeroDevs, Inc.';
|
|
4
5
|
export const SBOM_DEFAULT__OPTIONS = {
|
|
@@ -24,8 +25,8 @@ export const SBOM_DEFAULT__OPTIONS = {
|
|
|
24
25
|
includeFormulation: false,
|
|
25
26
|
'no-install-deps': true,
|
|
26
27
|
noInstallDeps: true,
|
|
27
|
-
'min-confidence': 1,
|
|
28
|
-
minConfidence: 1,
|
|
28
|
+
'min-confidence': 0.1,
|
|
29
|
+
minConfidence: 0.1,
|
|
29
30
|
multiProject: true,
|
|
30
31
|
'no-banner': false,
|
|
31
32
|
noBabel: false,
|
|
@@ -62,14 +63,18 @@ export const SBOM_DEFAULT__OPTIONS = {
|
|
|
62
63
|
usagesSlicesFile: 'usages.slices.json',
|
|
63
64
|
validate: true,
|
|
64
65
|
};
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
66
|
+
export function createSbomFactory({ createBom: createBomDependency = createBom, postProcess: postProcessDependency = postProcess, } = {}) {
|
|
67
|
+
return async function createSbom(directory) {
|
|
68
|
+
const sbom = await createBomDependency(directory, SBOM_DEFAULT__OPTIONS);
|
|
69
|
+
if (!sbom) {
|
|
70
|
+
throw new Error('SBOM not generated');
|
|
71
|
+
}
|
|
72
|
+
const postProcessedSbom = postProcessDependency(sbom, SBOM_DEFAULT__OPTIONS);
|
|
73
|
+
if (!postProcessedSbom) {
|
|
74
|
+
throw new Error('SBOM not generated');
|
|
75
|
+
}
|
|
76
|
+
debugLogger('Successfully generated SBOM');
|
|
77
|
+
return postProcessedSbom.bomJson;
|
|
78
|
+
};
|
|
75
79
|
}
|
|
80
|
+
export const createSbom = createSbomFactory();
|
|
@@ -20,3 +20,11 @@ export declare function formatScanResults(report: EolReport): string[];
|
|
|
20
20
|
* Formats web report URL for console display
|
|
21
21
|
*/
|
|
22
22
|
export declare function formatWebReportUrl(id: string, reportCardUrl: string): string[];
|
|
23
|
+
/**
|
|
24
|
+
* Formats data privacy information link for console display
|
|
25
|
+
*/
|
|
26
|
+
export declare function formatDataPrivacyLink(): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Formats the report save hint for console display when the web report URL is hidden
|
|
29
|
+
*/
|
|
30
|
+
export declare function formatReportSaveHint(): string[];
|
|
@@ -7,6 +7,7 @@ const STATUS_COLORS = {
|
|
|
7
7
|
OK: 'green',
|
|
8
8
|
EOL_UPCOMING: 'yellow',
|
|
9
9
|
};
|
|
10
|
+
const SEPARATOR_WIDTH = 40;
|
|
10
11
|
/**
|
|
11
12
|
* Formats status row text with appropriate color and icon
|
|
12
13
|
*/
|
|
@@ -54,7 +55,7 @@ export function formatScanResults(report) {
|
|
|
54
55
|
}
|
|
55
56
|
return [
|
|
56
57
|
ux.colorize('bold', 'Scan results:'),
|
|
57
|
-
ux.colorize('bold', '-'.repeat(
|
|
58
|
+
ux.colorize('bold', '-'.repeat(SEPARATOR_WIDTH)),
|
|
58
59
|
ux.colorize('bold', `${report.components.length.toLocaleString()} total packages scanned`),
|
|
59
60
|
getStatusRowText.EOL(`${EOL.toLocaleString().padEnd(5)} End-of-Life (EOL)`),
|
|
60
61
|
getStatusRowText.EOL_UPCOMING(`${EOL_UPCOMING.toLocaleString().padEnd(5)} EOL Upcoming`),
|
|
@@ -68,5 +69,19 @@ export function formatScanResults(report) {
|
|
|
68
69
|
*/
|
|
69
70
|
export function formatWebReportUrl(id, reportCardUrl) {
|
|
70
71
|
const url = ux.colorize('blue', terminalLink(new URL(reportCardUrl).hostname, `${reportCardUrl}/${id}`, { fallback: (_, url) => url }));
|
|
71
|
-
return [ux.colorize('bold', '-'.repeat(
|
|
72
|
+
return [ux.colorize('bold', '-'.repeat(SEPARATOR_WIDTH)), `🌐 View your full EOL report at: ${url}\n`];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Formats data privacy information link for console display
|
|
76
|
+
*/
|
|
77
|
+
export function formatDataPrivacyLink() {
|
|
78
|
+
const privacyUrl = 'https://docs.herodevs.com/eol-ds/data-privacy-and-security';
|
|
79
|
+
const link = ux.colorize('blue', terminalLink('Learn more about data privacy', privacyUrl, { fallback: (text, url) => `${text}: ${url}` }));
|
|
80
|
+
return [`🔒 ${link}\n`];
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Formats the report save hint for console display when the web report URL is hidden
|
|
84
|
+
*/
|
|
85
|
+
export function formatReportSaveHint() {
|
|
86
|
+
return [ux.colorize('bold', '-'.repeat(SEPARATOR_WIDTH)), 'To save your detailed JSON report, use the --save flag'];
|
|
72
87
|
}
|
|
@@ -3,18 +3,28 @@ export interface FileError extends Error {
|
|
|
3
3
|
code?: string;
|
|
4
4
|
}
|
|
5
5
|
/**
|
|
6
|
-
* Reads an SBOM from a file path
|
|
6
|
+
* Reads an SBOM from a file path and converts it to CycloneDX format
|
|
7
|
+
* Supports both SPDX 2.3 and CycloneDX formats
|
|
7
8
|
*/
|
|
8
9
|
export declare function readSbomFromFile(filePath: string): CdxBom;
|
|
9
10
|
/**
|
|
10
11
|
* Validates that a directory path exists and is actually a directory
|
|
11
12
|
*/
|
|
12
13
|
export declare function validateDirectory(dirPath: string): void;
|
|
14
|
+
type SaveArtifactRequest = {
|
|
15
|
+
kind: 'sbom';
|
|
16
|
+
payload: CdxBom;
|
|
17
|
+
outputPath?: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'sbomTrimmed';
|
|
20
|
+
payload: CdxBom;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'report';
|
|
23
|
+
payload: EolReport;
|
|
24
|
+
outputPath?: string;
|
|
25
|
+
};
|
|
13
26
|
/**
|
|
14
|
-
* Saves an SBOM to
|
|
27
|
+
* Saves an SBOM, trimmed SBOM, or report to disk using the correct default filename.
|
|
15
28
|
*/
|
|
16
|
-
export declare function
|
|
17
|
-
|
|
18
|
-
* Saves an EOL report to a file in the specified directory
|
|
19
|
-
*/
|
|
20
|
-
export declare function saveReportToFile(dir: string, report: EolReport): string;
|
|
29
|
+
export declare function saveArtifactToFile(dir: string, request: SaveArtifactRequest): string;
|
|
30
|
+
export {};
|
package/dist/service/file.svc.js
CHANGED
|
@@ -1,10 +1,70 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path, { join, resolve } from 'node:path';
|
|
3
|
-
import { isCdxBom } from '@herodevs/eol-shared';
|
|
3
|
+
import { isCdxBom, isSpdxBom, spdxToCdxBom } from '@herodevs/eol-shared';
|
|
4
4
|
import { filenamePrefix } from "../config/constants.js";
|
|
5
5
|
import { getErrorMessage } from "./log.svc.js";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Computes an absolute output path using either a provided path or the base directory and default name.
|
|
8
|
+
*/
|
|
9
|
+
function resolveOutputPath(baseDir, defaultFilename, customPath) {
|
|
10
|
+
const defaultOutput = resolve(join(baseDir, defaultFilename));
|
|
11
|
+
if (!customPath) {
|
|
12
|
+
return { fileName: defaultFilename, fullPath: defaultOutput };
|
|
13
|
+
}
|
|
14
|
+
const resolvedCustomPath = resolve(customPath);
|
|
15
|
+
let targetPath = resolvedCustomPath;
|
|
16
|
+
const hasTrailingSeparator = /[\\/]$/.test(customPath);
|
|
17
|
+
const customIsDirectory = fs.existsSync(resolvedCustomPath) && fs.statSync(resolvedCustomPath).isDirectory();
|
|
18
|
+
if (hasTrailingSeparator || customIsDirectory) {
|
|
19
|
+
targetPath = join(resolvedCustomPath, defaultFilename);
|
|
20
|
+
}
|
|
21
|
+
return { fileName: path.basename(targetPath), fullPath: targetPath };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Ensures the output directory for a given path exists, is a directory, and is writable.
|
|
25
|
+
*/
|
|
26
|
+
function ensureOutputDirectory(fullPath, fileName) {
|
|
27
|
+
const targetDir = path.dirname(fullPath);
|
|
28
|
+
if (!fs.existsSync(targetDir)) {
|
|
29
|
+
throw new Error(`Unable to save ${fileName}`);
|
|
30
|
+
}
|
|
31
|
+
const stats = fs.statSync(targetDir);
|
|
32
|
+
if (!stats.isDirectory()) {
|
|
33
|
+
throw new Error(`Unable to save ${fileName}`);
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
fs.accessSync(targetDir, fs.constants.W_OK);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new Error(`Unable to save ${fileName}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Writes JSON to disk after validating directory constraints and formats the payload for readability.
|
|
44
|
+
*/
|
|
45
|
+
function writeJsonFile(fullPath, fileName, payload, failureLabel) {
|
|
46
|
+
ensureOutputDirectory(fullPath, fileName);
|
|
47
|
+
try {
|
|
48
|
+
fs.writeFileSync(fullPath, JSON.stringify(payload, null, 2));
|
|
49
|
+
return fullPath;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const fileError = error;
|
|
53
|
+
switch (fileError.code) {
|
|
54
|
+
case 'EACCES':
|
|
55
|
+
throw new Error(`Permission denied. Unable to save ${fileName}`);
|
|
56
|
+
case 'ENOSPC':
|
|
57
|
+
throw new Error(`No space left on device. Unable to save ${fileName}`);
|
|
58
|
+
case 'ENOENT':
|
|
59
|
+
case 'ENOTDIR':
|
|
60
|
+
throw new Error(`Unable to save ${fileName}`);
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`Failed to save ${failureLabel}: ${getErrorMessage(error)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Reads an SBOM from a file path and converts it to CycloneDX format
|
|
67
|
+
* Supports both SPDX 2.3 and CycloneDX formats
|
|
8
68
|
*/
|
|
9
69
|
export function readSbomFromFile(filePath) {
|
|
10
70
|
const file = resolve(filePath);
|
|
@@ -13,11 +73,14 @@ export function readSbomFromFile(filePath) {
|
|
|
13
73
|
}
|
|
14
74
|
try {
|
|
15
75
|
const fileContent = fs.readFileSync(file, 'utf8');
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
18
|
-
|
|
76
|
+
const jsonContent = JSON.parse(fileContent);
|
|
77
|
+
if (isSpdxBom(jsonContent)) {
|
|
78
|
+
return spdxToCdxBom(jsonContent);
|
|
79
|
+
}
|
|
80
|
+
if (isCdxBom(jsonContent)) {
|
|
81
|
+
return jsonContent;
|
|
19
82
|
}
|
|
20
|
-
|
|
83
|
+
throw new Error(`Invalid SBOM file format. Expected SPDX 2.3 or CycloneDX format.`);
|
|
21
84
|
}
|
|
22
85
|
catch (error) {
|
|
23
86
|
throw new Error(`Failed to read SBOM file: ${getErrorMessage(error)}`);
|
|
@@ -36,36 +99,17 @@ export function validateDirectory(dirPath) {
|
|
|
36
99
|
throw new Error(`Path is not a directory: ${dir}`);
|
|
37
100
|
}
|
|
38
101
|
}
|
|
102
|
+
const artifactFilenames = {
|
|
103
|
+
sbom: `${filenamePrefix}.sbom.json`,
|
|
104
|
+
sbomTrimmed: `${filenamePrefix}.sbom-trimmed.json`,
|
|
105
|
+
report: `${filenamePrefix}.report.json`,
|
|
106
|
+
};
|
|
39
107
|
/**
|
|
40
|
-
* Saves an SBOM to
|
|
108
|
+
* Saves an SBOM, trimmed SBOM, or report to disk using the correct default filename.
|
|
41
109
|
*/
|
|
42
|
-
export function
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
throw new Error(`Failed to save SBOM: ${getErrorMessage(error)}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Saves an EOL report to a file in the specified directory
|
|
54
|
-
*/
|
|
55
|
-
export function saveReportToFile(dir, report) {
|
|
56
|
-
const reportPath = path.join(dir, `${filenamePrefix}.report.json`);
|
|
57
|
-
try {
|
|
58
|
-
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
|
59
|
-
return reportPath;
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
const fileError = error;
|
|
63
|
-
if (fileError.code === 'EACCES') {
|
|
64
|
-
throw new Error(`Permission denied. Unable to save report to ${filenamePrefix}.report.json`);
|
|
65
|
-
}
|
|
66
|
-
if (fileError.code === 'ENOSPC') {
|
|
67
|
-
throw new Error(`No space left on device. Unable to save report to ${filenamePrefix}.report.json`);
|
|
68
|
-
}
|
|
69
|
-
throw new Error(`Failed to save report: ${getErrorMessage(error)}`);
|
|
70
|
-
}
|
|
110
|
+
export function saveArtifactToFile(dir, request) {
|
|
111
|
+
const defaultFilename = artifactFilenames[request.kind];
|
|
112
|
+
const customOutputPath = 'outputPath' in request ? request.outputPath : undefined;
|
|
113
|
+
const { fileName, fullPath } = resolveOutputPath(dir, defaultFilename, customOutputPath);
|
|
114
|
+
return writeJsonFile(fullPath, fileName, request.payload, fileName);
|
|
71
115
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herodevs/cli",
|
|
3
|
-
"version": "2.0.0-beta.
|
|
3
|
+
"version": "2.0.0-beta.12",
|
|
4
4
|
"author": "HeroDevs, Inc",
|
|
5
5
|
"bin": {
|
|
6
6
|
"hd": "./bin/run.js"
|
|
@@ -39,34 +39,33 @@
|
|
|
39
39
|
"herodevs cli"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@amplitude/analytics-node": "^1.5.
|
|
42
|
+
"@amplitude/analytics-node": "^1.5.20",
|
|
43
43
|
"@apollo/client": "^3.13.8",
|
|
44
|
-
"@cyclonedx/cdxgen": "
|
|
44
|
+
"@cyclonedx/cdxgen": "^11.11.0",
|
|
45
45
|
"@herodevs/eol-shared": "github:herodevs/eol-shared#v0.1.11",
|
|
46
46
|
"@oclif/core": "^4.5.3",
|
|
47
47
|
"@oclif/plugin-help": "^6.2.32",
|
|
48
|
-
"@oclif/plugin-update": "^4.7.
|
|
49
|
-
"graphql": "^16.11.0",
|
|
48
|
+
"@oclif/plugin-update": "^4.7.13",
|
|
50
49
|
"node-machine-id": "^1.1.12",
|
|
51
|
-
"ora": "^
|
|
50
|
+
"ora": "^9.0.0",
|
|
52
51
|
"packageurl-js": "^2.0.1",
|
|
53
|
-
"terminal-link": "^
|
|
52
|
+
"terminal-link": "^5.0.0",
|
|
54
53
|
"update-notifier": "^7.3.1"
|
|
55
54
|
},
|
|
56
55
|
"devDependencies": {
|
|
57
56
|
"@biomejs/biome": "^2.2.2",
|
|
58
57
|
"@oclif/test": "^4.1.13",
|
|
59
58
|
"@types/inquirer": "^9.0.9",
|
|
60
|
-
"@types/node": "^24.
|
|
59
|
+
"@types/node": "^24.9.2",
|
|
61
60
|
"@types/sinon": "^17.0.4",
|
|
62
61
|
"@types/update-notifier": "^6.0.8",
|
|
63
62
|
"globstar": "^1.0.0",
|
|
64
|
-
"oclif": "^4.22.
|
|
63
|
+
"oclif": "^4.22.38",
|
|
65
64
|
"shx": "^0.4.0",
|
|
66
65
|
"sinon": "^21.0.0",
|
|
67
66
|
"ts-node": "^10.9.2",
|
|
68
|
-
"tsx": "^4.20.
|
|
69
|
-
"typescript": "^5.9.
|
|
67
|
+
"tsx": "^4.20.6",
|
|
68
|
+
"typescript": "^5.9.3"
|
|
70
69
|
},
|
|
71
70
|
"engines": {
|
|
72
71
|
"node": ">=20.0.0"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { writeFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { createBom } from '@cyclonedx/cdxgen';
|
|
4
|
-
import { filenamePrefix } from "../config/constants.js";
|
|
5
|
-
import { SBOM_DEFAULT__OPTIONS } from "./cdx.svc.js";
|
|
6
|
-
process.on('uncaughtException', (err) => {
|
|
7
|
-
console.error('Uncaught exception:', err.message);
|
|
8
|
-
process.exit(1);
|
|
9
|
-
});
|
|
10
|
-
process.on('unhandledRejection', (reason) => {
|
|
11
|
-
console.error('Unhandled rejection:', reason);
|
|
12
|
-
process.exit(1);
|
|
13
|
-
});
|
|
14
|
-
try {
|
|
15
|
-
console.log('Sbom worker started');
|
|
16
|
-
const options = JSON.parse(process.argv[2]);
|
|
17
|
-
const { path, opts } = options;
|
|
18
|
-
const { bomJson } = await createBom(path, { ...SBOM_DEFAULT__OPTIONS, ...opts });
|
|
19
|
-
const outputPath = join(path, `${filenamePrefix}.sbom.json`);
|
|
20
|
-
writeFileSync(outputPath, JSON.stringify(bomJson, null, 2));
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
console.error('Error creating SBOM', error.message);
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|