@herodevs/cli 2.0.0-beta.3 → 2.0.0-beta.5
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 +142 -110
- package/bin/main.js +2 -2
- 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 +71 -0
- package/dist/commands/scan/eol.d.ts +12 -20
- package/dist/commands/scan/eol.js +162 -148
- package/dist/config/constants.d.ts +9 -3
- package/dist/config/constants.js +19 -3
- package/dist/hooks/finally.d.ts +3 -0
- package/dist/hooks/finally.js +14 -0
- package/dist/hooks/prerun.js +12 -0
- package/dist/service/analytics.svc.d.ts +28 -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 +22 -0
- package/dist/service/display.svc.js +72 -0
- package/dist/service/file.svc.d.ts +20 -0
- package/dist/service/file.svc.js +71 -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/package.json +24 -17
- package/dist/api/client.d.ts +0 -12
- package/dist/api/client.js +0 -43
- package/dist/api/nes/nes.client.d.ts +0 -24
- package/dist/api/nes/nes.client.js +0 -127
- package/dist/api/queries/nes/sbom.d.ts +0 -3
- package/dist/api/queries/nes/sbom.js +0 -36
- 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 -31
- package/dist/api/types/hd-cli.types.js +0 -10
- package/dist/api/types/nes.types.d.ts +0 -54
- 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 -28
- 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/service/{eol/sbom.worker.d.ts → sbom.worker.d.ts} +0 -0
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { type ComponentStatus, type InsightsEolScanComponent, type ScanWarning } from './nes.types.ts';
|
|
2
|
-
export declare const isValidComponentStatus: (status: string) => status is ComponentStatus;
|
|
3
|
-
export interface ScanInputOptions {
|
|
4
|
-
type: 'SBOM' | 'OTHER';
|
|
5
|
-
page: number;
|
|
6
|
-
totalPages: number;
|
|
7
|
-
scanId?: string;
|
|
8
|
-
}
|
|
9
|
-
export declare const DEFAULT_SCAN_BATCH_SIZE = 1000;
|
|
10
|
-
export declare const DEFAULT_SCAN_INPUT_OPTIONS: ScanInputOptions;
|
|
11
|
-
export type ScanResultComponentsMap = Map<string, InsightsEolScanComponent>;
|
|
12
|
-
export type ScanInput = {
|
|
13
|
-
components: string[];
|
|
14
|
-
options: ScanInputOptions;
|
|
15
|
-
};
|
|
16
|
-
export interface ScanResult {
|
|
17
|
-
components: ScanResultComponentsMap;
|
|
18
|
-
createdOn?: string;
|
|
19
|
-
diagnostics?: Record<string, unknown>;
|
|
20
|
-
message: string;
|
|
21
|
-
success: boolean;
|
|
22
|
-
warnings: ScanWarning[];
|
|
23
|
-
scanId: string | undefined;
|
|
24
|
-
}
|
|
25
|
-
export interface ProcessBatchOptions {
|
|
26
|
-
batch: string[];
|
|
27
|
-
index: number;
|
|
28
|
-
totalPages: number;
|
|
29
|
-
scanOptions: ScanInputOptions;
|
|
30
|
-
previousScanId?: string;
|
|
31
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { VALID_STATUSES } from "./nes.types.js";
|
|
2
|
-
export const isValidComponentStatus = (status) => {
|
|
3
|
-
return VALID_STATUSES.includes(status);
|
|
4
|
-
};
|
|
5
|
-
export const DEFAULT_SCAN_BATCH_SIZE = 1000;
|
|
6
|
-
export const DEFAULT_SCAN_INPUT_OPTIONS = {
|
|
7
|
-
type: 'SBOM',
|
|
8
|
-
page: 1,
|
|
9
|
-
totalPages: 1,
|
|
10
|
-
};
|
|
@@ -1,54 +0,0 @@
|
|
|
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 */
|
|
7
|
-
components: string[];
|
|
8
|
-
/** The type of scan being performed (e.g. 'SBOM') */
|
|
9
|
-
type: string;
|
|
10
|
-
page: number;
|
|
11
|
-
totalPages: number;
|
|
12
|
-
}
|
|
13
|
-
export interface ScanResponse {
|
|
14
|
-
insights: {
|
|
15
|
-
scan: {
|
|
16
|
-
eol: InsightsEolScanResult;
|
|
17
|
-
};
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Result of the EOL scan operation
|
|
22
|
-
*/
|
|
23
|
-
export interface InsightsEolScanResult {
|
|
24
|
-
scanId?: string;
|
|
25
|
-
createdOn: string;
|
|
26
|
-
success: boolean;
|
|
27
|
-
message: string;
|
|
28
|
-
components: InsightsEolScanComponent[];
|
|
29
|
-
warnings: ScanWarning[];
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Information about a component's EOL status
|
|
33
|
-
*/
|
|
34
|
-
export interface InsightsEolScanComponentInfo {
|
|
35
|
-
isEol: boolean;
|
|
36
|
-
isUnsafe: boolean;
|
|
37
|
-
eolAt: Date | null;
|
|
38
|
-
status: ComponentStatus;
|
|
39
|
-
daysEol: number | null;
|
|
40
|
-
vulnCount: number | null;
|
|
41
|
-
}
|
|
42
|
-
export interface InsightsEolScanComponent {
|
|
43
|
-
info: InsightsEolScanComponentInfo;
|
|
44
|
-
purl: string;
|
|
45
|
-
}
|
|
46
|
-
export interface ScanWarning {
|
|
47
|
-
purl: string;
|
|
48
|
-
message: string;
|
|
49
|
-
type?: string;
|
|
50
|
-
error?: unknown;
|
|
51
|
-
diagnostics?: Record<string, unknown>;
|
|
52
|
-
}
|
|
53
|
-
export type ComponentStatus = (typeof VALID_STATUSES)[number];
|
|
54
|
-
export declare const VALID_STATUSES: readonly ["UNKNOWN", "OK", "EOL", "SUPPORTED"];
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const VALID_STATUSES = ['UNKNOWN', 'OK', 'EOL', 'SUPPORTED'];
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
|
-
import { type ReportData } from '../../service/committers.svc.ts';
|
|
3
|
-
export default class Committers extends Command {
|
|
4
|
-
static description: string;
|
|
5
|
-
static enableJsonFlag: boolean;
|
|
6
|
-
static examples: string[];
|
|
7
|
-
static flags: {
|
|
8
|
-
months: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
-
csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
-
save: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
-
};
|
|
12
|
-
run(): Promise<ReportData | string>;
|
|
13
|
-
/**
|
|
14
|
-
* Generates structured report data
|
|
15
|
-
* @param entries - parsed git log output for commits
|
|
16
|
-
*/
|
|
17
|
-
private generateReportData;
|
|
18
|
-
/**
|
|
19
|
-
* Fetches git commit data with month and author information
|
|
20
|
-
* @param sinceDate - Date range for git log
|
|
21
|
-
*/
|
|
22
|
-
private fetchGitCommitData;
|
|
23
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { Command, Flags } from '@oclif/core';
|
|
3
|
-
import fs from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { calculateOverallStats, formatAsCsv, formatAsText, groupCommitsByMonth, parseGitLogOutput, } from "../../service/committers.svc.js";
|
|
6
|
-
import { getErrorMessage, isErrnoException } from "../../service/error.svc.js";
|
|
7
|
-
export default class Committers extends Command {
|
|
8
|
-
static description = 'Generate report of committers to a git repository';
|
|
9
|
-
static enableJsonFlag = true;
|
|
10
|
-
static examples = [
|
|
11
|
-
'<%= config.bin %> <%= command.id %>',
|
|
12
|
-
'<%= config.bin %> <%= command.id %> --csv -s',
|
|
13
|
-
'<%= config.bin %> <%= command.id %> --json',
|
|
14
|
-
'<%= config.bin %> <%= command.id %> --csv',
|
|
15
|
-
];
|
|
16
|
-
static flags = {
|
|
17
|
-
months: Flags.integer({
|
|
18
|
-
char: 'm',
|
|
19
|
-
description: 'The number of months of git history to review',
|
|
20
|
-
default: 12,
|
|
21
|
-
}),
|
|
22
|
-
csv: Flags.boolean({
|
|
23
|
-
char: 'c',
|
|
24
|
-
description: 'Output in CSV format',
|
|
25
|
-
default: false,
|
|
26
|
-
}),
|
|
27
|
-
save: Flags.boolean({
|
|
28
|
-
char: 's',
|
|
29
|
-
description: 'Save the committers report as eol.committers.<output>',
|
|
30
|
-
default: false,
|
|
31
|
-
}),
|
|
32
|
-
};
|
|
33
|
-
async run() {
|
|
34
|
-
const { flags } = await this.parse(Committers);
|
|
35
|
-
const { months, csv, save } = flags;
|
|
36
|
-
const isJson = this.jsonEnabled();
|
|
37
|
-
const sinceDate = `${months} months ago`;
|
|
38
|
-
this.log('Starting committers report with flags: %O', flags);
|
|
39
|
-
try {
|
|
40
|
-
// Generate structured report data
|
|
41
|
-
const entries = this.fetchGitCommitData(sinceDate);
|
|
42
|
-
this.log('Fetched %d commit entries', entries.length);
|
|
43
|
-
const reportData = this.generateReportData(entries);
|
|
44
|
-
// Handle different output scenarios
|
|
45
|
-
if (isJson) {
|
|
46
|
-
// JSON mode
|
|
47
|
-
if (save) {
|
|
48
|
-
try {
|
|
49
|
-
fs.writeFileSync(path.resolve('eol.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
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return reportData;
|
|
57
|
-
}
|
|
58
|
-
const textOutput = formatAsText(reportData);
|
|
59
|
-
if (csv) {
|
|
60
|
-
// CSV mode
|
|
61
|
-
const csvOutput = formatAsCsv(reportData);
|
|
62
|
-
if (save) {
|
|
63
|
-
try {
|
|
64
|
-
fs.writeFileSync(path.resolve('eol.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
|
-
}
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
this.log(textOutput);
|
|
73
|
-
}
|
|
74
|
-
return csvOutput;
|
|
75
|
-
}
|
|
76
|
-
if (save) {
|
|
77
|
-
try {
|
|
78
|
-
fs.writeFileSync(path.resolve('eol.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
|
-
}
|
|
84
|
-
}
|
|
85
|
-
else {
|
|
86
|
-
this.log(textOutput);
|
|
87
|
-
}
|
|
88
|
-
return textOutput;
|
|
89
|
-
}
|
|
90
|
-
catch (error) {
|
|
91
|
-
this.error(`Failed to generate report: ${getErrorMessage(error)}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
/**
|
|
95
|
-
* Generates structured report data
|
|
96
|
-
* @param entries - parsed git log output for commits
|
|
97
|
-
*/
|
|
98
|
-
generateReportData(entries) {
|
|
99
|
-
if (entries.length === 0) {
|
|
100
|
-
return { monthly: {}, overall: { total: 0 } };
|
|
101
|
-
}
|
|
102
|
-
const monthlyData = groupCommitsByMonth(entries);
|
|
103
|
-
const overallStats = calculateOverallStats(entries);
|
|
104
|
-
const grandTotal = entries.length;
|
|
105
|
-
// Format into a structured report data object
|
|
106
|
-
const report = {
|
|
107
|
-
monthly: {},
|
|
108
|
-
overall: { ...overallStats, total: grandTotal },
|
|
109
|
-
};
|
|
110
|
-
// Add monthly totals
|
|
111
|
-
for (const [month, authors] of Object.entries(monthlyData)) {
|
|
112
|
-
const monthTotal = Object.values(authors).reduce((sum, count) => sum + count, 0);
|
|
113
|
-
report.monthly[month] = { ...authors, total: monthTotal };
|
|
114
|
-
}
|
|
115
|
-
return report;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Fetches git commit data with month and author information
|
|
119
|
-
* @param sinceDate - Date range for git log
|
|
120
|
-
*/
|
|
121
|
-
fetchGitCommitData(sinceDate) {
|
|
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)}`);
|
|
135
|
-
}
|
|
136
|
-
this.error(`Git command failed: ${getErrorMessage(logProcess.error)}`);
|
|
137
|
-
}
|
|
138
|
-
if (logProcess.status !== 0) {
|
|
139
|
-
this.error(`Git command failed with status ${logProcess.status}: ${logProcess.stderr}`);
|
|
140
|
-
}
|
|
141
|
-
if (!logProcess.stdout) {
|
|
142
|
-
return [];
|
|
143
|
-
}
|
|
144
|
-
return parseGitLogOutput(logProcess.stdout);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
|
-
export default class ReportPurls extends Command {
|
|
3
|
-
static description: string;
|
|
4
|
-
static enableJsonFlag: boolean;
|
|
5
|
-
static examples: string[];
|
|
6
|
-
static flags: {
|
|
7
|
-
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
-
dir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
-
save: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
-
csv: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
-
};
|
|
12
|
-
run(): Promise<{
|
|
13
|
-
purls: string[];
|
|
14
|
-
}>;
|
|
15
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { Command, Flags, ux } from '@oclif/core';
|
|
4
|
-
import { getErrorMessage, isErrnoException } from "../../service/error.svc.js";
|
|
5
|
-
import { extractPurls, getPurlOutput } from "../../service/purls.svc.js";
|
|
6
|
-
import ScanSbom from "../scan/sbom.js";
|
|
7
|
-
export default class ReportPurls extends Command {
|
|
8
|
-
static description = 'Generate a list of purls from a sbom';
|
|
9
|
-
static enableJsonFlag = true;
|
|
10
|
-
static examples = [
|
|
11
|
-
'<%= config.bin %> <%= command.id %> --json -s',
|
|
12
|
-
'<%= config.bin %> <%= command.id %> --dir=./my-project',
|
|
13
|
-
'<%= config.bin %> <%= command.id %> --file=path/to/sbom.json',
|
|
14
|
-
'<%= config.bin %> <%= command.id %> --dir=./my-project --save',
|
|
15
|
-
'<%= config.bin %> <%= command.id %> --save --csv',
|
|
16
|
-
];
|
|
17
|
-
static flags = {
|
|
18
|
-
file: Flags.string({
|
|
19
|
-
char: 'f',
|
|
20
|
-
description: 'The file path of an existing cyclonedx sbom to scan for EOL',
|
|
21
|
-
}),
|
|
22
|
-
dir: Flags.string({
|
|
23
|
-
char: 'd',
|
|
24
|
-
description: 'The directory to scan in order to create a cyclonedx sbom',
|
|
25
|
-
}),
|
|
26
|
-
save: Flags.boolean({
|
|
27
|
-
char: 's',
|
|
28
|
-
default: false,
|
|
29
|
-
description: 'Save the list of purls as eol.purls.<output>',
|
|
30
|
-
}),
|
|
31
|
-
csv: Flags.boolean({
|
|
32
|
-
char: 'c',
|
|
33
|
-
default: false,
|
|
34
|
-
description: 'Save output in CSV format (only applies when using --save)',
|
|
35
|
-
}),
|
|
36
|
-
};
|
|
37
|
-
async run() {
|
|
38
|
-
const { flags } = await this.parse(ReportPurls);
|
|
39
|
-
const { dir: _dirFlag, file: _fileFlag, save, csv } = flags;
|
|
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);
|
|
48
|
-
}
|
|
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(), `eol.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
|
-
}
|
|
76
|
-
}
|
|
77
|
-
// Return wrapped object with metadata
|
|
78
|
-
return { purls };
|
|
79
|
-
}
|
|
80
|
-
catch (error) {
|
|
81
|
-
this.error(`Failed to generate PURLs: ${getErrorMessage(error)}`);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
|
-
import type { Sbom } from '../../service/eol/cdx.svc.ts';
|
|
3
|
-
export default class ScanSbom extends Command {
|
|
4
|
-
static description: string;
|
|
5
|
-
static enableJsonFlag: boolean;
|
|
6
|
-
static examples: string[];
|
|
7
|
-
static flags: {
|
|
8
|
-
file: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
-
dir: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
-
save: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
-
background: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
-
};
|
|
13
|
-
static loadSbom(flags: Record<string, string>, config: Command['config']): Promise<Sbom>;
|
|
14
|
-
static getSbomArgs(flags: Record<string, string>): string[];
|
|
15
|
-
getScanOptions(): {};
|
|
16
|
-
run(): Promise<Sbom | undefined>;
|
|
17
|
-
private _getSbomFromScan;
|
|
18
|
-
private _getSbomInBackground;
|
|
19
|
-
private _getSbomFromFile;
|
|
20
|
-
private _saveSbom;
|
|
21
|
-
}
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process';
|
|
2
|
-
import fs from 'node:fs';
|
|
3
|
-
import { join, resolve } from 'node:path';
|
|
4
|
-
import { Command, Flags, ux } from '@oclif/core';
|
|
5
|
-
import { createSbom, validateIsCycloneDxSbom } from "../../service/eol/eol.svc.js";
|
|
6
|
-
import { getErrorMessage } from "../../service/error.svc.js";
|
|
7
|
-
export default class ScanSbom extends Command {
|
|
8
|
-
static description = 'Scan a SBOM for purls';
|
|
9
|
-
static enableJsonFlag = true;
|
|
10
|
-
static examples = [
|
|
11
|
-
'<%= config.bin %> <%= command.id %> --dir=./my-project',
|
|
12
|
-
'<%= config.bin %> <%= command.id %> --file=path/to/sbom.json',
|
|
13
|
-
];
|
|
14
|
-
static flags = {
|
|
15
|
-
file: Flags.string({
|
|
16
|
-
char: 'f',
|
|
17
|
-
description: 'The file path of an existing cyclonedx sbom to scan for EOL',
|
|
18
|
-
}),
|
|
19
|
-
dir: Flags.string({
|
|
20
|
-
char: 'd',
|
|
21
|
-
description: 'The directory to scan in order to create a cyclonedx sbom',
|
|
22
|
-
}),
|
|
23
|
-
save: Flags.boolean({
|
|
24
|
-
char: 's',
|
|
25
|
-
default: false,
|
|
26
|
-
description: 'Save the generated SBOM as eol.sbom.json in the scanned directory',
|
|
27
|
-
}),
|
|
28
|
-
background: Flags.boolean({
|
|
29
|
-
char: 'b',
|
|
30
|
-
default: false,
|
|
31
|
-
description: 'Run the scan in the background',
|
|
32
|
-
}),
|
|
33
|
-
};
|
|
34
|
-
static async loadSbom(flags, config) {
|
|
35
|
-
const sbomArgs = ScanSbom.getSbomArgs(flags);
|
|
36
|
-
const sbomCommand = new ScanSbom(sbomArgs, config);
|
|
37
|
-
const sbom = await sbomCommand.run();
|
|
38
|
-
if (!sbom) {
|
|
39
|
-
throw new Error('SBOM not generated');
|
|
40
|
-
}
|
|
41
|
-
return sbom;
|
|
42
|
-
}
|
|
43
|
-
static getSbomArgs(flags) {
|
|
44
|
-
const { dir, file, save, json, background } = flags ?? {};
|
|
45
|
-
const sbomArgs = [];
|
|
46
|
-
if (file)
|
|
47
|
-
sbomArgs.push('--file', file);
|
|
48
|
-
if (dir)
|
|
49
|
-
sbomArgs.push('--dir', dir);
|
|
50
|
-
// if (save) sbomArgs.push('--save'); // only save if sbom command is used directly with -s flag
|
|
51
|
-
if (json)
|
|
52
|
-
sbomArgs.push('--json');
|
|
53
|
-
if (background)
|
|
54
|
-
sbomArgs.push('--background');
|
|
55
|
-
return sbomArgs;
|
|
56
|
-
}
|
|
57
|
-
getScanOptions() {
|
|
58
|
-
// intentionally provided for mocking
|
|
59
|
-
return {};
|
|
60
|
-
}
|
|
61
|
-
async run() {
|
|
62
|
-
const { flags } = await this.parse(ScanSbom);
|
|
63
|
-
const { dir, save, file, background } = flags;
|
|
64
|
-
// Validate that exactly one of --file or --dir is provided
|
|
65
|
-
if (file && dir) {
|
|
66
|
-
this.error('Cannot specify both --file and --dir flags. Please use one or the other.');
|
|
67
|
-
}
|
|
68
|
-
let sbom;
|
|
69
|
-
const path = dir || process.cwd();
|
|
70
|
-
if (file) {
|
|
71
|
-
sbom = this._getSbomFromFile(file);
|
|
72
|
-
}
|
|
73
|
-
else if (background) {
|
|
74
|
-
this._getSbomInBackground(path);
|
|
75
|
-
this.log(`The scan is running in the background. The file will be saved at ${path}/eol.sbom.json`);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
79
|
-
sbom = await this._getSbomFromScan(path);
|
|
80
|
-
if (save) {
|
|
81
|
-
this._saveSbom(path, sbom);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return sbom;
|
|
85
|
-
}
|
|
86
|
-
async _getSbomFromScan(_dirFlag) {
|
|
87
|
-
const dir = resolve(_dirFlag);
|
|
88
|
-
try {
|
|
89
|
-
if (!fs.existsSync(dir)) {
|
|
90
|
-
this.error(`Directory not found: ${dir}`);
|
|
91
|
-
}
|
|
92
|
-
const stats = fs.statSync(dir);
|
|
93
|
-
if (!stats.isDirectory()) {
|
|
94
|
-
this.error(`Path is not a directory: ${dir}`);
|
|
95
|
-
}
|
|
96
|
-
ux.action.start(`Scanning ${dir}`);
|
|
97
|
-
const options = this.getScanOptions();
|
|
98
|
-
const sbom = await createSbom(dir, options);
|
|
99
|
-
if (!sbom) {
|
|
100
|
-
this.error(`SBOM failed to generate for dir: ${dir}`);
|
|
101
|
-
}
|
|
102
|
-
return sbom;
|
|
103
|
-
}
|
|
104
|
-
catch (error) {
|
|
105
|
-
this.error(`Failed to scan directory: ${getErrorMessage(error)}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
_getSbomInBackground(path) {
|
|
109
|
-
try {
|
|
110
|
-
const opts = this.getScanOptions();
|
|
111
|
-
const args = [
|
|
112
|
-
JSON.stringify({
|
|
113
|
-
opts,
|
|
114
|
-
path,
|
|
115
|
-
}),
|
|
116
|
-
];
|
|
117
|
-
const workerProcess = spawn('node', [join(import.meta.url, '../../service/eol/sbom.worker.js'), ...args], {
|
|
118
|
-
stdio: 'ignore',
|
|
119
|
-
detached: true,
|
|
120
|
-
env: { ...process.env },
|
|
121
|
-
});
|
|
122
|
-
workerProcess.unref();
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
this.error(`Failed to start background scan: ${getErrorMessage(error)}`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
_getSbomFromFile(_fileFlag) {
|
|
129
|
-
const file = resolve(_fileFlag);
|
|
130
|
-
try {
|
|
131
|
-
if (!fs.existsSync(file)) {
|
|
132
|
-
this.error(`SBOM file not found: ${file}`);
|
|
133
|
-
}
|
|
134
|
-
ux.action.start(`Loading sbom from ${file}`);
|
|
135
|
-
const fileContent = fs.readFileSync(file, {
|
|
136
|
-
encoding: 'utf8',
|
|
137
|
-
flag: 'r',
|
|
138
|
-
});
|
|
139
|
-
const sbom = JSON.parse(fileContent);
|
|
140
|
-
validateIsCycloneDxSbom(sbom);
|
|
141
|
-
return sbom;
|
|
142
|
-
}
|
|
143
|
-
catch (error) {
|
|
144
|
-
this.error(`Failed to read SBOM file: ${getErrorMessage(error)}`);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
_saveSbom(dir, sbom) {
|
|
148
|
-
try {
|
|
149
|
-
const outputPath = join(dir, 'eol.sbom.json');
|
|
150
|
-
fs.writeFileSync(outputPath, JSON.stringify(sbom, null, 2));
|
|
151
|
-
if (!this.jsonEnabled()) {
|
|
152
|
-
this.log(`SBOM saved to ${outputPath}`);
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
this.error(`Failed to save SBOM: ${getErrorMessage(error)}`);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
export interface CommitEntry {
|
|
2
|
-
month: string;
|
|
3
|
-
author: string;
|
|
4
|
-
}
|
|
5
|
-
export interface AuthorCommitCounts {
|
|
6
|
-
[author: string]: number;
|
|
7
|
-
}
|
|
8
|
-
export interface MonthlyData {
|
|
9
|
-
[month: string]: AuthorCommitCounts;
|
|
10
|
-
}
|
|
11
|
-
export interface ReportData {
|
|
12
|
-
monthly: {
|
|
13
|
-
[month: string]: {
|
|
14
|
-
[author: string]: number;
|
|
15
|
-
total: number;
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
overall: {
|
|
19
|
-
[author: string]: number;
|
|
20
|
-
total: number;
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Parses git log output into structured data
|
|
25
|
-
* @param output - Git log command output
|
|
26
|
-
* @returns Parsed commit entries
|
|
27
|
-
*/
|
|
28
|
-
export declare function parseGitLogOutput(output: string): CommitEntry[];
|
|
29
|
-
/**
|
|
30
|
-
* Groups commit data by month
|
|
31
|
-
* @param entries - Commit entries
|
|
32
|
-
* @returns Object with months as keys and author commit counts as values
|
|
33
|
-
*/
|
|
34
|
-
export declare function groupCommitsByMonth(entries: CommitEntry[]): MonthlyData;
|
|
35
|
-
/**
|
|
36
|
-
* Calculates overall commit statistics by author
|
|
37
|
-
* @param entries - Commit entries
|
|
38
|
-
* @returns Object with authors as keys and total commit counts as values
|
|
39
|
-
*/
|
|
40
|
-
export declare function calculateOverallStats(entries: CommitEntry[]): AuthorCommitCounts;
|
|
41
|
-
/**
|
|
42
|
-
* Formats monthly report sections
|
|
43
|
-
* @param monthlyData - Grouped commit data by month
|
|
44
|
-
* @returns Formatted monthly report sections
|
|
45
|
-
*/
|
|
46
|
-
export declare function formatMonthlyReport(monthlyData: MonthlyData): string;
|
|
47
|
-
/**
|
|
48
|
-
* Formats overall statistics section
|
|
49
|
-
* @param overallStats - Overall commit counts by author
|
|
50
|
-
* @param grandTotal - Total number of commits
|
|
51
|
-
* @returns Formatted overall statistics section
|
|
52
|
-
*/
|
|
53
|
-
export declare function formatOverallStats(overallStats: AuthorCommitCounts, grandTotal: number): string;
|
|
54
|
-
/**
|
|
55
|
-
* Formats the report data as CSV
|
|
56
|
-
* @param data - The structured report data
|
|
57
|
-
*/
|
|
58
|
-
export declare function formatAsCsv(data: ReportData): string;
|
|
59
|
-
/**
|
|
60
|
-
* Formats the report data as text
|
|
61
|
-
* @param data - The structured report data
|
|
62
|
-
*/
|
|
63
|
-
export declare function formatAsText(data: ReportData): string;
|
|
64
|
-
/**
|
|
65
|
-
* Format output based on user preference
|
|
66
|
-
* @param output
|
|
67
|
-
* @param reportData
|
|
68
|
-
* @returns
|
|
69
|
-
*/
|
|
70
|
-
export declare function formatOutputBasedOnFlag(output: string, reportData: ReportData): string;
|