@herodevs/cli 2.0.0-beta.4 → 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 -108
- 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 -12
- package/dist/commands/scan/eol.js +163 -104
- package/dist/config/constants.d.ts +8 -3
- package/dist/config/constants.js +18 -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} +1 -1
- package/package.json +22 -15
- 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 -39
- 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 -58
- 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 -147
- package/dist/commands/report/purls.d.ts +0 -15
- package/dist/commands/report/purls.js +0 -85
- package/dist/commands/scan/sbom.d.ts +0 -21
- package/dist/commands/scan/sbom.js +0 -164
- 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 -12
- package/dist/service/eol/eol.svc.js +0 -25
- 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 -36
- package/dist/service/purls.svc.d.ts +0 -23
- package/dist/service/purls.svc.js +0 -99
- package/dist/ui/shared.ui.d.ts +0 -4
- package/dist/ui/shared.ui.js +0 -14
- /package/dist/service/{eol/sbom.worker.d.ts → sbom.worker.d.ts} +0 -0
|
@@ -1,196 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Parses git log output into structured data
|
|
3
|
-
* @param output - Git log command output
|
|
4
|
-
* @returns Parsed commit entries
|
|
5
|
-
*/
|
|
6
|
-
export function parseGitLogOutput(output) {
|
|
7
|
-
return output
|
|
8
|
-
.split('\n')
|
|
9
|
-
.filter(Boolean)
|
|
10
|
-
.map((line) => {
|
|
11
|
-
// Remove surrounding double quotes if present (e.g. "March|John Doe" → March|John Doe)
|
|
12
|
-
const [month, author] = line.replace(/^"(.*)"$/, '$1').split('|');
|
|
13
|
-
return { month, author };
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Groups commit data by month
|
|
18
|
-
* @param entries - Commit entries
|
|
19
|
-
* @returns Object with months as keys and author commit counts as values
|
|
20
|
-
*/
|
|
21
|
-
export function groupCommitsByMonth(entries) {
|
|
22
|
-
const result = {};
|
|
23
|
-
// Group commits by month
|
|
24
|
-
const commitsByMonth = entries.reduce((acc, entry) => {
|
|
25
|
-
const monthKey = entry.month;
|
|
26
|
-
if (!acc[monthKey]) {
|
|
27
|
-
acc[monthKey] = [];
|
|
28
|
-
}
|
|
29
|
-
acc[monthKey].push(entry);
|
|
30
|
-
return acc;
|
|
31
|
-
}, {});
|
|
32
|
-
// Process each month
|
|
33
|
-
for (const [month, commits] of Object.entries(commitsByMonth)) {
|
|
34
|
-
if (!commits) {
|
|
35
|
-
result[month] = {};
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
// Count commits per author for this month
|
|
39
|
-
const commitsByAuthor = commits.reduce((acc, entry) => {
|
|
40
|
-
const authorKey = entry.author;
|
|
41
|
-
if (!acc[authorKey]) {
|
|
42
|
-
acc[authorKey] = [];
|
|
43
|
-
}
|
|
44
|
-
acc[authorKey].push(entry);
|
|
45
|
-
return acc;
|
|
46
|
-
}, {});
|
|
47
|
-
const authorCounts = {};
|
|
48
|
-
for (const [author, authorCommits] of Object.entries(commitsByAuthor)) {
|
|
49
|
-
authorCounts[author] = authorCommits?.length ?? 0;
|
|
50
|
-
}
|
|
51
|
-
result[month] = authorCounts;
|
|
52
|
-
}
|
|
53
|
-
return result;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Calculates overall commit statistics by author
|
|
57
|
-
* @param entries - Commit entries
|
|
58
|
-
* @returns Object with authors as keys and total commit counts as values
|
|
59
|
-
*/
|
|
60
|
-
export function calculateOverallStats(entries) {
|
|
61
|
-
const commitsByAuthor = entries.reduce((acc, entry) => {
|
|
62
|
-
const authorKey = entry.author;
|
|
63
|
-
if (!acc[authorKey]) {
|
|
64
|
-
acc[authorKey] = [];
|
|
65
|
-
}
|
|
66
|
-
acc[authorKey].push(entry);
|
|
67
|
-
return acc;
|
|
68
|
-
}, {});
|
|
69
|
-
const result = {};
|
|
70
|
-
// Count commits for each author
|
|
71
|
-
for (const author in commitsByAuthor) {
|
|
72
|
-
result[author] = commitsByAuthor[author]?.length ?? 0;
|
|
73
|
-
}
|
|
74
|
-
return result;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Formats monthly report sections
|
|
78
|
-
* @param monthlyData - Grouped commit data by month
|
|
79
|
-
* @returns Formatted monthly report sections
|
|
80
|
-
*/
|
|
81
|
-
export function formatMonthlyReport(monthlyData) {
|
|
82
|
-
const sortedMonths = Object.keys(monthlyData).sort();
|
|
83
|
-
let report = '';
|
|
84
|
-
for (const month of sortedMonths) {
|
|
85
|
-
report += `\n## ${month}\n`;
|
|
86
|
-
const authors = Object.entries(monthlyData[month]).sort((a, b) => b[1] - a[1]);
|
|
87
|
-
for (const [author, count] of authors) {
|
|
88
|
-
report += `${count.toString().padStart(6)} ${author}\n`;
|
|
89
|
-
}
|
|
90
|
-
const monthTotal = authors.reduce((sum, [_, count]) => sum + count, 0);
|
|
91
|
-
report += `${monthTotal.toString().padStart(6)} TOTAL\n`;
|
|
92
|
-
}
|
|
93
|
-
return report;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Formats overall statistics section
|
|
97
|
-
* @param overallStats - Overall commit counts by author
|
|
98
|
-
* @param grandTotal - Total number of commits
|
|
99
|
-
* @returns Formatted overall statistics section
|
|
100
|
-
*/
|
|
101
|
-
export function formatOverallStats(overallStats, grandTotal) {
|
|
102
|
-
let report = '\n## Overall Statistics\n';
|
|
103
|
-
const sortedStats = Object.entries(overallStats).sort((a, b) => b[1] - a[1]);
|
|
104
|
-
for (const [author, count] of sortedStats) {
|
|
105
|
-
report += `${count.toString().padStart(6)} ${author}\n`;
|
|
106
|
-
}
|
|
107
|
-
report += `${grandTotal.toString().padStart(6)} GRAND TOTAL\n`;
|
|
108
|
-
return report;
|
|
109
|
-
}
|
|
110
|
-
/**
|
|
111
|
-
* Formats the report data as CSV
|
|
112
|
-
* @param data - The structured report data
|
|
113
|
-
*/
|
|
114
|
-
export function formatAsCsv(data) {
|
|
115
|
-
// First prepare all author names (for columns)
|
|
116
|
-
const allAuthors = new Set();
|
|
117
|
-
// Collect all unique author names
|
|
118
|
-
for (const monthData of Object.values(data.monthly)) {
|
|
119
|
-
for (const author of Object.keys(monthData)) {
|
|
120
|
-
if (author !== 'total')
|
|
121
|
-
allAuthors.add(author);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
const authors = Array.from(allAuthors).sort();
|
|
125
|
-
// Create CSV header
|
|
126
|
-
let csv = `Month,${authors.join(',')},Total\n`;
|
|
127
|
-
// Add monthly data rows
|
|
128
|
-
const sortedMonths = Object.keys(data.monthly).sort();
|
|
129
|
-
for (const month of sortedMonths) {
|
|
130
|
-
csv += month;
|
|
131
|
-
// Add data for each author
|
|
132
|
-
for (const author of authors) {
|
|
133
|
-
const count = data.monthly[month][author] || 0;
|
|
134
|
-
csv += `,${count}`;
|
|
135
|
-
}
|
|
136
|
-
// Add monthly total
|
|
137
|
-
csv += `,${`${data.monthly[month].total}\n`}`;
|
|
138
|
-
}
|
|
139
|
-
// Add overall totals row
|
|
140
|
-
csv += 'Overall';
|
|
141
|
-
for (const author of authors) {
|
|
142
|
-
const count = data.overall[author] || 0;
|
|
143
|
-
csv += `,${count}`;
|
|
144
|
-
}
|
|
145
|
-
csv += `,${data.overall.total}\n`;
|
|
146
|
-
return csv;
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* Formats the report data as text
|
|
150
|
-
* @param data - The structured report data
|
|
151
|
-
*/
|
|
152
|
-
export function formatAsText(data) {
|
|
153
|
-
let report = 'Monthly Commit Report\n';
|
|
154
|
-
// Monthly sections
|
|
155
|
-
const sortedMonths = Object.keys(data.monthly).sort();
|
|
156
|
-
for (const month of sortedMonths) {
|
|
157
|
-
report += `\n## ${month}\n`;
|
|
158
|
-
const authors = Object.entries(data.monthly[month])
|
|
159
|
-
.filter(([author]) => author !== 'total')
|
|
160
|
-
.sort((a, b) => b[1] - a[1]);
|
|
161
|
-
for (const [author, count] of authors) {
|
|
162
|
-
report += `${count.toString().padStart(6)} ${author}\n`;
|
|
163
|
-
}
|
|
164
|
-
report += `${data.monthly[month].total.toString().padStart(6)} TOTAL\n`;
|
|
165
|
-
}
|
|
166
|
-
// Overall statistics
|
|
167
|
-
report += '\n## Overall Statistics\n';
|
|
168
|
-
const sortedEntries = Object.entries(data.overall)
|
|
169
|
-
.filter(([author]) => author !== 'total')
|
|
170
|
-
.sort((a, b) => b[1] - a[1]);
|
|
171
|
-
for (const [author, count] of sortedEntries) {
|
|
172
|
-
report += `${count.toString().padStart(6)} ${author}\n`;
|
|
173
|
-
}
|
|
174
|
-
report += `${data.overall.total.toString().padStart(6)} GRAND TOTAL\n`;
|
|
175
|
-
return report;
|
|
176
|
-
}
|
|
177
|
-
/**
|
|
178
|
-
* Format output based on user preference
|
|
179
|
-
* @param output
|
|
180
|
-
* @param reportData
|
|
181
|
-
* @returns
|
|
182
|
-
*/
|
|
183
|
-
export function formatOutputBasedOnFlag(output, reportData) {
|
|
184
|
-
let formattedOutput;
|
|
185
|
-
switch (output) {
|
|
186
|
-
case 'json':
|
|
187
|
-
formattedOutput = JSON.stringify(reportData, null, 2);
|
|
188
|
-
break;
|
|
189
|
-
case 'csv':
|
|
190
|
-
formattedOutput = formatAsCsv(reportData);
|
|
191
|
-
break;
|
|
192
|
-
default:
|
|
193
|
-
formattedOutput = formatAsText(reportData);
|
|
194
|
-
}
|
|
195
|
-
return formattedOutput;
|
|
196
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { type Sbom } from './cdx.svc.ts';
|
|
2
|
-
export interface CdxGenOptions {
|
|
3
|
-
projectType?: string[];
|
|
4
|
-
}
|
|
5
|
-
export interface ScanOptions {
|
|
6
|
-
cdxgen?: CdxGenOptions;
|
|
7
|
-
}
|
|
8
|
-
export type CdxCreator = (dir: string, opts: CdxGenOptions) => Promise<{
|
|
9
|
-
bomJson: Sbom;
|
|
10
|
-
}>;
|
|
11
|
-
export declare function createSbom(directory: string, opts?: ScanOptions): Promise<any>;
|
|
12
|
-
export declare function validateIsCycloneDxSbom(sbom: unknown): asserts sbom is Sbom;
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { debugLogger } from "../../service/log.svc.js";
|
|
2
|
-
import { createBomFromDir } from "./cdx.svc.js";
|
|
3
|
-
export async function createSbom(directory, opts = {}) {
|
|
4
|
-
const sbom = await createBomFromDir(directory, opts.cdxgen || {});
|
|
5
|
-
if (!sbom)
|
|
6
|
-
throw new Error('SBOM not generated');
|
|
7
|
-
debugLogger('SBOM generated');
|
|
8
|
-
return sbom;
|
|
9
|
-
}
|
|
10
|
-
export function validateIsCycloneDxSbom(sbom) {
|
|
11
|
-
if (!sbom || typeof sbom !== 'object') {
|
|
12
|
-
throw new Error('SBOM must be an object');
|
|
13
|
-
}
|
|
14
|
-
const s = sbom;
|
|
15
|
-
// Basic CycloneDX validation
|
|
16
|
-
if (!('bomFormat' in s) || s.bomFormat !== 'CycloneDX') {
|
|
17
|
-
throw new Error('Invalid SBOM format: must be CycloneDX');
|
|
18
|
-
}
|
|
19
|
-
if (!('specVersion' in s) || typeof s.specVersion !== 'string') {
|
|
20
|
-
throw new Error('Invalid SBOM: missing specVersion');
|
|
21
|
-
}
|
|
22
|
-
if (!('components' in s) || !Array.isArray(s.components)) {
|
|
23
|
-
throw new Error('Invalid SBOM: missing or invalid components array');
|
|
24
|
-
}
|
|
25
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export declare const isError: (error: unknown) => error is Error;
|
|
2
|
-
export declare const isErrnoException: (error: unknown) => error is NodeJS.ErrnoException;
|
|
3
|
-
export declare const isApolloError: (error: unknown) => error is ApolloError;
|
|
4
|
-
export declare const getErrorMessage: (error: unknown) => string;
|
|
5
|
-
export declare class ApolloError extends Error {
|
|
6
|
-
readonly originalError?: unknown;
|
|
7
|
-
constructor(message: string, original?: unknown);
|
|
8
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export const isError = (error) => {
|
|
2
|
-
return error instanceof Error;
|
|
3
|
-
};
|
|
4
|
-
export const isErrnoException = (error) => {
|
|
5
|
-
return isError(error) && 'code' in error;
|
|
6
|
-
};
|
|
7
|
-
export const isApolloError = (error) => {
|
|
8
|
-
return error instanceof ApolloError;
|
|
9
|
-
};
|
|
10
|
-
export const getErrorMessage = (error) => {
|
|
11
|
-
if (isError(error)) {
|
|
12
|
-
return error.message;
|
|
13
|
-
}
|
|
14
|
-
return 'Unknown error';
|
|
15
|
-
};
|
|
16
|
-
export class ApolloError extends Error {
|
|
17
|
-
originalError;
|
|
18
|
-
constructor(message, original) {
|
|
19
|
-
if (isError(original)) {
|
|
20
|
-
super(`${message}: ${original.message}`);
|
|
21
|
-
}
|
|
22
|
-
else {
|
|
23
|
-
super(`${message}: ${String(original)}`);
|
|
24
|
-
}
|
|
25
|
-
this.name = 'ApolloError';
|
|
26
|
-
this.originalError = original;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import type { NesApolloClient } from '../../api/nes/nes.client.ts';
|
|
2
|
-
import type { ScanInputOptions, ScanResult } from '../../api/types/hd-cli.types.ts';
|
|
3
|
-
import type { InsightsEolScanResult } from '../../api/types/nes.types.ts';
|
|
4
|
-
export declare const buildScanResult: (scan: InsightsEolScanResult) => ScanResult;
|
|
5
|
-
export declare const SbomScanner: (client: NesApolloClient) => (purls: string[], options: ScanInputOptions) => Promise<InsightsEolScanResult>;
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { M_SCAN } from "../../api/queries/nes/sbom.js";
|
|
2
|
-
import { debugLogger } from "../log.svc.js";
|
|
3
|
-
export const buildScanResult = (scan) => {
|
|
4
|
-
const components = new Map();
|
|
5
|
-
for (const c of scan.components) {
|
|
6
|
-
const status = c.info.status;
|
|
7
|
-
components.set(c.purl, {
|
|
8
|
-
info: {
|
|
9
|
-
...c.info,
|
|
10
|
-
nesAvailable: c.remediation !== null,
|
|
11
|
-
status: status === 'SUPPORTED' ? 'EOL_UPCOMING' : status,
|
|
12
|
-
},
|
|
13
|
-
purl: c.purl,
|
|
14
|
-
});
|
|
15
|
-
}
|
|
16
|
-
return {
|
|
17
|
-
components,
|
|
18
|
-
message: scan.message,
|
|
19
|
-
success: true,
|
|
20
|
-
warnings: scan.warnings || [],
|
|
21
|
-
scanId: scan.scanId,
|
|
22
|
-
createdOn: scan.createdOn,
|
|
23
|
-
};
|
|
24
|
-
};
|
|
25
|
-
export const SbomScanner = (client) => async (purls, options) => {
|
|
26
|
-
const { type, page, totalPages, scanId } = options;
|
|
27
|
-
const input = { components: purls, type, page, totalPages, scanId };
|
|
28
|
-
const res = await client.mutate(M_SCAN.gql, { input });
|
|
29
|
-
const scan = res.data?.insights?.scan?.eol;
|
|
30
|
-
if (!scan?.success) {
|
|
31
|
-
debugLogger('failed scan %o', scan || {});
|
|
32
|
-
debugLogger('scan failed');
|
|
33
|
-
throw new Error('Failed to provide scan: ');
|
|
34
|
-
}
|
|
35
|
-
return scan;
|
|
36
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import type { Sbom } from './eol/cdx.svc.ts';
|
|
2
|
-
/**
|
|
3
|
-
* Formats a value for CSV output by wrapping it in quotes if it contains commas.
|
|
4
|
-
* This ensures that values containing commas aren't split into multiple columns
|
|
5
|
-
* when the CSV is opened in a spreadsheet application.
|
|
6
|
-
*/
|
|
7
|
-
export declare function formatCsvValue(value: string): string;
|
|
8
|
-
/**
|
|
9
|
-
* Converts an array of PURLs into either CSV or JSON format.
|
|
10
|
-
* For CSV output, adds a header row with "purl" and formats values to preserve commas.
|
|
11
|
-
* For JSON output, returns a properly indented JSON string.
|
|
12
|
-
*/
|
|
13
|
-
export declare function getPurlOutput(purls: string[], output: string): string;
|
|
14
|
-
/**
|
|
15
|
-
* Extract all PURLs from a CycloneDX SBOM, including components and dependencies
|
|
16
|
-
*/
|
|
17
|
-
export declare function extractPurls(sbom: Sbom): string[];
|
|
18
|
-
/**
|
|
19
|
-
* Parse a purls file in either JSON or text format, including the format of
|
|
20
|
-
* herodevs.purls.json - { purls: [ 'pkg:npm/express@4.18.2', 'pkg:npm/react@18.3.1' ] }
|
|
21
|
-
* or a text file with one purl per line.
|
|
22
|
-
*/
|
|
23
|
-
export declare function parsePurlsFile(purlsFileString: string): string[];
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Formats a value for CSV output by wrapping it in quotes if it contains commas.
|
|
3
|
-
* This ensures that values containing commas aren't split into multiple columns
|
|
4
|
-
* when the CSV is opened in a spreadsheet application.
|
|
5
|
-
*/
|
|
6
|
-
export function formatCsvValue(value) {
|
|
7
|
-
// If the value contains a comma, wrap it in quotes to preserve it as a single cell
|
|
8
|
-
return value.includes(',') ? `"${value}"` : value;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Converts an array of PURLs into either CSV or JSON format.
|
|
12
|
-
* For CSV output, adds a header row with "purl" and formats values to preserve commas.
|
|
13
|
-
* For JSON output, returns a properly indented JSON string.
|
|
14
|
-
*/
|
|
15
|
-
export function getPurlOutput(purls, output) {
|
|
16
|
-
switch (output) {
|
|
17
|
-
case 'csv':
|
|
18
|
-
return ['purl', ...purls].map(formatCsvValue).join('\n');
|
|
19
|
-
default:
|
|
20
|
-
return JSON.stringify({ purls }, null, 2);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Extract PURLs from components recursively
|
|
25
|
-
*/
|
|
26
|
-
function extractPurlsFromComponents(components, purlSet) {
|
|
27
|
-
for (const component of components) {
|
|
28
|
-
if (component.purl) {
|
|
29
|
-
purlSet.add(component.purl);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Extract PURLs from dependencies
|
|
35
|
-
*/
|
|
36
|
-
function extractPurlsFromDependencies(dependencies, purlSet) {
|
|
37
|
-
for (const dependency of dependencies) {
|
|
38
|
-
if (dependency.ref) {
|
|
39
|
-
purlSet.add(dependency.ref);
|
|
40
|
-
}
|
|
41
|
-
if (dependency.dependsOn) {
|
|
42
|
-
for (const dep of dependency.dependsOn) {
|
|
43
|
-
purlSet.add(dep);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
/**
|
|
49
|
-
* Extract all PURLs from a CycloneDX SBOM, including components and dependencies
|
|
50
|
-
*/
|
|
51
|
-
export function extractPurls(sbom) {
|
|
52
|
-
const purlSet = new Set();
|
|
53
|
-
// Extract from direct components
|
|
54
|
-
if (sbom.components) {
|
|
55
|
-
extractPurlsFromComponents(sbom.components, purlSet);
|
|
56
|
-
}
|
|
57
|
-
// Extract from dependencies
|
|
58
|
-
if (sbom.dependencies) {
|
|
59
|
-
extractPurlsFromDependencies(sbom.dependencies, purlSet);
|
|
60
|
-
}
|
|
61
|
-
return Array.from(purlSet);
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Parse a purls file in either JSON or text format, including the format of
|
|
65
|
-
* herodevs.purls.json - { purls: [ 'pkg:npm/express@4.18.2', 'pkg:npm/react@18.3.1' ] }
|
|
66
|
-
* or a text file with one purl per line.
|
|
67
|
-
*/
|
|
68
|
-
export function parsePurlsFile(purlsFileString) {
|
|
69
|
-
// Handle empty string
|
|
70
|
-
if (!purlsFileString.trim()) {
|
|
71
|
-
return [];
|
|
72
|
-
}
|
|
73
|
-
try {
|
|
74
|
-
// Try parsing as JSON first
|
|
75
|
-
const parsed = JSON.parse(purlsFileString);
|
|
76
|
-
if (parsed && Array.isArray(parsed.purls)) {
|
|
77
|
-
return parsed.purls;
|
|
78
|
-
}
|
|
79
|
-
if (Array.isArray(parsed)) {
|
|
80
|
-
return parsed;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
// If not JSON, try parsing as text file
|
|
85
|
-
const lines = purlsFileString
|
|
86
|
-
.split('\n')
|
|
87
|
-
.map((line) => line.trim())
|
|
88
|
-
.filter((line) => line.length > 0 && line.startsWith('pkg:'));
|
|
89
|
-
// Handle single purl case (no newlines)
|
|
90
|
-
if (lines.length === 0 && purlsFileString.trim().startsWith('pkg:')) {
|
|
91
|
-
return [purlsFileString.trim()];
|
|
92
|
-
}
|
|
93
|
-
// Return any valid purls found
|
|
94
|
-
if (lines.length > 0) {
|
|
95
|
-
return lines;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
throw new Error('Invalid purls file: must be either JSON with purls array or text file with one purl per line');
|
|
99
|
-
}
|
package/dist/ui/shared.ui.d.ts
DELETED
package/dist/ui/shared.ui.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { ux } from '@oclif/core';
|
|
2
|
-
export const STATUS_COLORS = {
|
|
3
|
-
EOL: 'red',
|
|
4
|
-
UNKNOWN: 'default',
|
|
5
|
-
OK: 'green',
|
|
6
|
-
EOL_UPCOMING: 'yellow',
|
|
7
|
-
};
|
|
8
|
-
export const INDICATORS = {
|
|
9
|
-
EOL: ux.colorize(STATUS_COLORS.EOL, '✗'),
|
|
10
|
-
UNKNOWN: ux.colorize(STATUS_COLORS.UNKNOWN, '•'),
|
|
11
|
-
OK: ux.colorize(STATUS_COLORS.OK, '✔'),
|
|
12
|
-
EOL_UPCOMING: ux.colorize(STATUS_COLORS.EOL_UPCOMING, '⚡'),
|
|
13
|
-
};
|
|
14
|
-
export const SCAN_ID_KEY = 'eol-scan-v1-';
|
|
File without changes
|