@herodevs/cli 2.0.0-beta.1 → 2.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +201 -110
  2. package/bin/dev.js +1 -4
  3. package/bin/main.js +3 -3
  4. package/bin/run.js +1 -1
  5. package/dist/api/gql-operations.d.ts +2 -0
  6. package/dist/api/gql-operations.js +36 -0
  7. package/dist/api/nes.client.d.ts +12 -0
  8. package/dist/api/nes.client.js +84 -0
  9. package/dist/commands/scan/eol.d.ts +18 -19
  10. package/dist/commands/scan/eol.js +214 -142
  11. package/dist/config/constants.d.ts +9 -3
  12. package/dist/config/constants.js +19 -3
  13. package/dist/hooks/finally/finally.d.ts +3 -0
  14. package/dist/hooks/finally/finally.js +18 -0
  15. package/dist/hooks/{npm-update-notifier.js → init/00_npm-update-notifier.js} +3 -3
  16. package/dist/hooks/init/01_initialize_amplitude.d.ts +3 -0
  17. package/dist/hooks/init/01_initialize_amplitude.js +15 -0
  18. package/dist/service/analytics.svc.d.ts +27 -0
  19. package/dist/service/analytics.svc.js +112 -0
  20. package/dist/service/{eol/cdx.svc.d.ts → cdx.svc.d.ts} +8 -16
  21. package/dist/service/{eol/cdx.svc.js → cdx.svc.js} +17 -7
  22. package/dist/service/display.svc.d.ts +30 -0
  23. package/dist/service/display.svc.js +87 -0
  24. package/dist/service/file.svc.d.ts +30 -0
  25. package/dist/service/file.svc.js +115 -0
  26. package/dist/service/log.svc.d.ts +1 -0
  27. package/dist/service/log.svc.js +9 -0
  28. package/dist/service/{eol/sbom.worker.js → sbom.worker.js} +2 -1
  29. package/dist/utils/strip-typename.d.ts +1 -0
  30. package/dist/utils/strip-typename.js +15 -0
  31. package/package.json +33 -22
  32. package/dist/api/client.d.ts +0 -12
  33. package/dist/api/client.js +0 -43
  34. package/dist/api/nes/nes.client.d.ts +0 -23
  35. package/dist/api/nes/nes.client.js +0 -107
  36. package/dist/api/queries/nes/sbom.d.ts +0 -3
  37. package/dist/api/queries/nes/sbom.js +0 -35
  38. package/dist/api/queries/nes/telemetry.d.ts +0 -2
  39. package/dist/api/queries/nes/telemetry.js +0 -24
  40. package/dist/api/types/hd-cli.types.d.ts +0 -30
  41. package/dist/api/types/hd-cli.types.js +0 -10
  42. package/dist/api/types/nes.types.d.ts +0 -53
  43. package/dist/api/types/nes.types.js +0 -1
  44. package/dist/commands/report/committers.d.ts +0 -23
  45. package/dist/commands/report/committers.js +0 -146
  46. package/dist/commands/report/purls.d.ts +0 -15
  47. package/dist/commands/report/purls.js +0 -84
  48. package/dist/commands/scan/sbom.d.ts +0 -21
  49. package/dist/commands/scan/sbom.js +0 -159
  50. package/dist/service/committers.svc.d.ts +0 -70
  51. package/dist/service/committers.svc.js +0 -196
  52. package/dist/service/eol/eol.svc.d.ts +0 -14
  53. package/dist/service/eol/eol.svc.js +0 -49
  54. package/dist/service/error.svc.d.ts +0 -8
  55. package/dist/service/error.svc.js +0 -28
  56. package/dist/service/nes/nes.svc.d.ts +0 -5
  57. package/dist/service/nes/nes.svc.js +0 -27
  58. package/dist/service/purls.svc.d.ts +0 -23
  59. package/dist/service/purls.svc.js +0 -99
  60. package/dist/ui/date.ui.d.ts +0 -1
  61. package/dist/ui/date.ui.js +0 -15
  62. package/dist/ui/eol.ui.d.ts +0 -15
  63. package/dist/ui/eol.ui.js +0 -134
  64. package/dist/ui/shared.ui.d.ts +0 -6
  65. package/dist/ui/shared.ui.js +0 -16
  66. /package/dist/hooks/{npm-update-notifier.d.ts → init/00_npm-update-notifier.d.ts} +0 -0
  67. /package/dist/hooks/{prerun.d.ts → prerun/prerun.d.ts} +0 -0
  68. /package/dist/hooks/{prerun.js → prerun/prerun.js} +0 -0
  69. /package/dist/service/{eol/sbom.worker.d.ts → sbom.worker.d.ts} +0 -0
@@ -1,43 +0,0 @@
1
- import * as apollo from '@apollo/client/core/index.js';
2
- import { ApolloError } from "../service/error.svc.js";
3
- export const createApollo = (url) => new apollo.ApolloClient({
4
- cache: new apollo.InMemoryCache({
5
- addTypename: false,
6
- }),
7
- headers: {
8
- 'User-Agent': `hdcli/${process.env.npm_package_version ?? 'unknown'}`,
9
- },
10
- link: apollo.ApolloLink.from([
11
- new apollo.HttpLink({
12
- uri: url,
13
- }),
14
- ]),
15
- });
16
- export class ApolloClient {
17
- #apollo;
18
- constructor(url) {
19
- this.#apollo = createApollo(url);
20
- }
21
- async mutate(mutation, variables) {
22
- try {
23
- return await this.#apollo.mutate({
24
- mutation,
25
- variables,
26
- });
27
- }
28
- catch (error) {
29
- throw new ApolloError('GraphQL mutation failed', error);
30
- }
31
- }
32
- async query(query, variables) {
33
- try {
34
- return await this.#apollo.query({
35
- query,
36
- variables,
37
- });
38
- }
39
- catch (error) {
40
- throw new ApolloError('GraphQL query failed', error);
41
- }
42
- }
43
- }
@@ -1,23 +0,0 @@
1
- import type * as apollo from '@apollo/client/core/index.js';
2
- import type { InsightsEolScanInput, InsightsEolScanResult } from '../../api/types/nes.types.ts';
3
- import { type ProcessBatchOptions, type ScanInputOptions, type ScanResult } from '../types/hd-cli.types.ts';
4
- export interface NesClient {
5
- scan: {
6
- purls: (purls: string[], options: ScanInputOptions) => Promise<InsightsEolScanResult>;
7
- };
8
- }
9
- export declare class NesApolloClient implements NesClient {
10
- #private;
11
- scan: {
12
- purls: (purls: string[], options: ScanInputOptions) => Promise<InsightsEolScanResult>;
13
- };
14
- constructor(url: string);
15
- mutate<T, V extends Record<string, unknown>>(mutation: apollo.DocumentNode, variables?: V): Promise<apollo.FetchResult<T>>;
16
- query<T, V extends Record<string, unknown> | undefined>(query: apollo.DocumentNode, variables?: V): Promise<apollo.ApolloQueryResult<T>>;
17
- }
18
- export declare const batchSubmitPurls: (purls: string[], options?: ScanInputOptions, batchSize?: number) => Promise<ScanResult>;
19
- export declare const createBatches: (items: string[], batchSize: number) => string[][];
20
- export declare const processBatch: ({ batch, index, totalPages, scanOptions, previousScanId, }: ProcessBatchOptions) => Promise<InsightsEolScanResult>;
21
- export declare const processBatches: (batches: string[][], scanOptions: ScanInputOptions) => Promise<InsightsEolScanResult[]>;
22
- export declare const handleBatchResults: (results: InsightsEolScanResult[]) => ScanResult;
23
- export declare const buildInsightsEolScanInput: (purls: string[], options: ScanInputOptions) => InsightsEolScanInput;
@@ -1,107 +0,0 @@
1
- import { ApolloClient } from "../../api/client.js";
2
- import { config } from "../../config/constants.js";
3
- import { debugLogger } from "../../service/log.svc.js";
4
- import { SbomScanner, buildScanResult } from "../../service/nes/nes.svc.js";
5
- import { DEFAULT_SCAN_BATCH_SIZE, DEFAULT_SCAN_INPUT_OPTIONS, } from "../types/hd-cli.types.js";
6
- export class NesApolloClient {
7
- scan = {
8
- purls: SbomScanner(this),
9
- };
10
- #apollo;
11
- constructor(url) {
12
- this.#apollo = new ApolloClient(url);
13
- }
14
- mutate(mutation, variables) {
15
- return this.#apollo.mutate(mutation, variables);
16
- }
17
- query(query, variables) {
18
- return this.#apollo.query(query, variables);
19
- }
20
- }
21
- /**
22
- * Submit a scan for a list of purls after they've been batched by batchSubmitPurls
23
- */
24
- function submitScan(purls, options) {
25
- const host = config.graphqlHost;
26
- const path = config.graphqlPath;
27
- const url = host + path;
28
- const client = new NesApolloClient(url);
29
- return client.scan.purls(purls, options);
30
- }
31
- export const batchSubmitPurls = async (purls, options = DEFAULT_SCAN_INPUT_OPTIONS, batchSize = DEFAULT_SCAN_BATCH_SIZE) => {
32
- try {
33
- const batches = createBatches(purls, batchSize);
34
- debugLogger('Processing %d batches', batches.length);
35
- if (batches.length === 0) {
36
- return {
37
- components: new Map(),
38
- message: 'No batches to process',
39
- success: true,
40
- warnings: [],
41
- scanId: undefined,
42
- };
43
- }
44
- const results = await processBatches(batches, options);
45
- return handleBatchResults(results);
46
- }
47
- catch (error) {
48
- debugLogger('Fatal error in batchSubmitPurls: %s', error);
49
- throw new Error(`Failed to process purls: ${error instanceof Error ? error.message : String(error)}`);
50
- }
51
- };
52
- export const createBatches = (items, batchSize) => {
53
- const numberOfBatches = Math.ceil(items.length / batchSize);
54
- return Array.from({ length: numberOfBatches }, (_, index) => {
55
- const startIndex = index * batchSize;
56
- const endIndex = startIndex + batchSize;
57
- return items.slice(startIndex, endIndex);
58
- });
59
- };
60
- export const processBatch = async ({ batch, index, totalPages, scanOptions, previousScanId, }) => {
61
- const page = index + 1;
62
- if (page > totalPages) {
63
- throw new Error('Total pages exceeded');
64
- }
65
- debugLogger('Processing batch %d of %d', page, totalPages);
66
- debugLogger('ScanID: %s', previousScanId);
67
- const result = await submitScan(batch, {
68
- ...scanOptions,
69
- page,
70
- totalPages,
71
- scanId: previousScanId,
72
- });
73
- return result;
74
- };
75
- export const processBatches = async (batches, scanOptions) => {
76
- const totalPages = batches.length;
77
- const results = [];
78
- for (const [index, batch] of batches.entries()) {
79
- const previousScanId = results[index - 1]?.scanId;
80
- const result = await processBatch({
81
- batch,
82
- index,
83
- totalPages,
84
- scanOptions,
85
- previousScanId,
86
- });
87
- results.push(result);
88
- }
89
- return results;
90
- };
91
- export const handleBatchResults = (results) => {
92
- if (results.length === 0) {
93
- throw new Error('No results to process');
94
- }
95
- // The API returns placeholders for each batch except the last one.
96
- const finalResult = results[results.length - 1];
97
- return buildScanResult(finalResult);
98
- };
99
- export const buildInsightsEolScanInput = (purls, options) => {
100
- const { type, page, totalPages } = options;
101
- return {
102
- components: purls,
103
- type,
104
- page,
105
- totalPages,
106
- };
107
- };
@@ -1,3 +0,0 @@
1
- export declare const M_SCAN: {
2
- gql: import("graphql/language/ast.js").DocumentNode;
3
- };
@@ -1,35 +0,0 @@
1
- import { gql } from '@apollo/client/core/core.cjs';
2
- export const M_SCAN = {
3
- gql: gql `
4
- mutation EolScan($input: InsightsEolScanInput!) {
5
- insights {
6
- scan {
7
- eol(input: $input) {
8
- components {
9
- purl
10
- info {
11
- isEol
12
- isUnsafe
13
- eolAt
14
- daysEol
15
- status
16
- vulnCount
17
- }
18
- }
19
- diagnostics
20
- message
21
- scanId
22
- success
23
- warnings {
24
- purl
25
- type
26
- message
27
- error
28
- diagnostics
29
- }
30
- }
31
- }
32
- }
33
- }
34
- `,
35
- };
@@ -1,2 +0,0 @@
1
- export declare const TELEMETRY_INITIALIZE_MUTATION: import("graphql/language/ast.js").DocumentNode;
2
- export declare const TELEMETRY_REPORT_MUTATION: import("graphql/language/ast.js").DocumentNode;
@@ -1,24 +0,0 @@
1
- import { gql } from '@apollo/client/core/core.cjs';
2
- export const TELEMETRY_INITIALIZE_MUTATION = gql `
3
- mutation Telemetry($clientName: String!) {
4
- telemetry {
5
- initialize(input: { context: { client: { id: $clientName } } }) {
6
- success
7
- oid
8
- message
9
- }
10
- }
11
- }
12
- `;
13
- export const TELEMETRY_REPORT_MUTATION = gql `
14
- mutation Report($key: String!, $report: JSON!, $metadata: JSON) {
15
- telemetry {
16
- report(input: { key: $key, report: $report, metadata: $metadata }) {
17
- txId
18
- success
19
- message
20
- diagnostics
21
- }
22
- }
23
- }
24
- `;
@@ -1,30 +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
- diagnostics?: Record<string, unknown>;
19
- message: string;
20
- success: boolean;
21
- warnings: ScanWarning[];
22
- scanId: string | undefined;
23
- }
24
- export interface ProcessBatchOptions {
25
- batch: string[];
26
- index: number;
27
- totalPages: number;
28
- scanOptions: ScanInputOptions;
29
- previousScanId?: string;
30
- }
@@ -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,53 +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
- success: boolean;
26
- message: string;
27
- components: InsightsEolScanComponent[];
28
- warnings: ScanWarning[];
29
- }
30
- /**
31
- * Information about a component's EOL status
32
- */
33
- export interface InsightsEolScanComponentInfo {
34
- isEol: boolean;
35
- isUnsafe: boolean;
36
- eolAt: Date | null;
37
- status: ComponentStatus;
38
- daysEol: number | null;
39
- vulnCount: number | null;
40
- }
41
- export interface InsightsEolScanComponent {
42
- info: InsightsEolScanComponentInfo;
43
- purl: string;
44
- }
45
- export interface ScanWarning {
46
- purl: string;
47
- message: string;
48
- type?: string;
49
- error?: unknown;
50
- diagnostics?: Record<string, unknown>;
51
- }
52
- export type ComponentStatus = (typeof VALID_STATUSES)[number];
53
- 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
- }