@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.
Files changed (55) hide show
  1. package/README.md +142 -108
  2. package/dist/api/gql-operations.d.ts +2 -0
  3. package/dist/api/gql-operations.js +36 -0
  4. package/dist/api/nes.client.d.ts +12 -0
  5. package/dist/api/nes.client.js +71 -0
  6. package/dist/commands/scan/eol.d.ts +12 -12
  7. package/dist/commands/scan/eol.js +163 -104
  8. package/dist/config/constants.d.ts +8 -3
  9. package/dist/config/constants.js +18 -3
  10. package/dist/hooks/finally.d.ts +3 -0
  11. package/dist/hooks/finally.js +14 -0
  12. package/dist/hooks/prerun.js +12 -0
  13. package/dist/service/analytics.svc.d.ts +28 -0
  14. package/dist/service/analytics.svc.js +112 -0
  15. package/dist/service/{eol/cdx.svc.d.ts → cdx.svc.d.ts} +8 -16
  16. package/dist/service/{eol/cdx.svc.js → cdx.svc.js} +17 -7
  17. package/dist/service/display.svc.d.ts +22 -0
  18. package/dist/service/display.svc.js +72 -0
  19. package/dist/service/file.svc.d.ts +20 -0
  20. package/dist/service/file.svc.js +71 -0
  21. package/dist/service/log.svc.d.ts +1 -0
  22. package/dist/service/log.svc.js +9 -0
  23. package/dist/service/{eol/sbom.worker.js → sbom.worker.js} +1 -1
  24. package/package.json +22 -15
  25. package/dist/api/client.d.ts +0 -12
  26. package/dist/api/client.js +0 -43
  27. package/dist/api/nes/nes.client.d.ts +0 -24
  28. package/dist/api/nes/nes.client.js +0 -127
  29. package/dist/api/queries/nes/sbom.d.ts +0 -3
  30. package/dist/api/queries/nes/sbom.js +0 -39
  31. package/dist/api/queries/nes/telemetry.d.ts +0 -2
  32. package/dist/api/queries/nes/telemetry.js +0 -24
  33. package/dist/api/types/hd-cli.types.d.ts +0 -30
  34. package/dist/api/types/hd-cli.types.js +0 -10
  35. package/dist/api/types/nes.types.d.ts +0 -58
  36. package/dist/api/types/nes.types.js +0 -1
  37. package/dist/commands/report/committers.d.ts +0 -23
  38. package/dist/commands/report/committers.js +0 -147
  39. package/dist/commands/report/purls.d.ts +0 -15
  40. package/dist/commands/report/purls.js +0 -85
  41. package/dist/commands/scan/sbom.d.ts +0 -21
  42. package/dist/commands/scan/sbom.js +0 -164
  43. package/dist/service/committers.svc.d.ts +0 -70
  44. package/dist/service/committers.svc.js +0 -196
  45. package/dist/service/eol/eol.svc.d.ts +0 -12
  46. package/dist/service/eol/eol.svc.js +0 -25
  47. package/dist/service/error.svc.d.ts +0 -8
  48. package/dist/service/error.svc.js +0 -28
  49. package/dist/service/nes/nes.svc.d.ts +0 -5
  50. package/dist/service/nes/nes.svc.js +0 -36
  51. package/dist/service/purls.svc.d.ts +0 -23
  52. package/dist/service/purls.svc.js +0 -99
  53. package/dist/ui/shared.ui.d.ts +0 -4
  54. package/dist/ui/shared.ui.js +0 -14
  55. /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
- }
@@ -1,4 +0,0 @@
1
- import type { ComponentStatus } from '../api/types/nes.types.ts';
2
- export declare const STATUS_COLORS: Record<ComponentStatus, string>;
3
- export declare const INDICATORS: Record<ComponentStatus, string>;
4
- export declare const SCAN_ID_KEY = "eol-scan-v1-";
@@ -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-';