@herodevs/cli 2.0.0-beta.8 → 2.0.0
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 +282 -39
- package/bin/main.js +2 -6
- package/dist/api/apollo.client.d.ts +3 -0
- package/dist/api/apollo.client.js +53 -0
- package/dist/api/ci-token.client.d.ts +27 -0
- package/dist/api/ci-token.client.js +95 -0
- package/dist/api/errors.d.ts +8 -0
- package/dist/api/errors.js +13 -0
- package/dist/api/gql-operations.d.ts +3 -0
- package/dist/api/gql-operations.js +36 -1
- package/dist/api/graphql-errors.d.ts +6 -0
- package/dist/api/graphql-errors.js +22 -0
- package/dist/api/nes.client.d.ts +1 -2
- package/dist/api/nes.client.js +40 -16
- package/dist/api/user-setup.client.d.ts +18 -0
- package/dist/api/user-setup.client.js +92 -0
- package/dist/commands/auth/login.d.ts +14 -0
- package/dist/commands/auth/login.js +225 -0
- package/dist/commands/auth/logout.d.ts +5 -0
- package/dist/commands/auth/logout.js +27 -0
- package/dist/commands/auth/provision-ci-token.d.ts +5 -0
- package/dist/commands/auth/provision-ci-token.js +72 -0
- package/dist/commands/report/committers.d.ts +27 -0
- package/dist/commands/report/committers.js +215 -0
- package/dist/commands/scan/eol.d.ts +7 -0
- package/dist/commands/scan/eol.js +120 -32
- package/dist/commands/tracker/init.d.ts +14 -0
- package/dist/commands/tracker/init.js +84 -0
- package/dist/commands/tracker/run.d.ts +15 -0
- package/dist/commands/tracker/run.js +183 -0
- package/dist/config/constants.d.ts +14 -0
- package/dist/config/constants.js +15 -0
- package/dist/config/tracker.config.d.ts +16 -0
- package/dist/config/tracker.config.js +16 -0
- package/dist/hooks/finally/finally.js +13 -7
- package/dist/hooks/init/01_initialize_amplitude.js +20 -9
- package/dist/service/analytics.svc.d.ts +10 -4
- package/dist/service/analytics.svc.js +180 -18
- package/dist/service/auth-config.svc.d.ts +2 -0
- package/dist/service/auth-config.svc.js +8 -0
- package/dist/service/auth-refresh.svc.d.ts +8 -0
- package/dist/service/auth-refresh.svc.js +45 -0
- package/dist/service/auth-token.svc.d.ts +11 -0
- package/dist/service/auth-token.svc.js +62 -0
- package/dist/service/auth.svc.d.ts +27 -0
- package/dist/service/auth.svc.js +91 -0
- package/dist/service/cdx.svc.d.ts +9 -1
- package/dist/service/cdx.svc.js +17 -12
- package/dist/service/ci-auth.svc.d.ts +6 -0
- package/dist/service/ci-auth.svc.js +32 -0
- package/dist/service/ci-token.svc.d.ts +6 -0
- package/dist/service/ci-token.svc.js +44 -0
- package/dist/service/committers.svc.d.ts +58 -0
- package/dist/service/committers.svc.js +78 -0
- package/dist/service/display.svc.d.ts +8 -0
- package/dist/service/display.svc.js +17 -2
- package/dist/service/encrypted-store.svc.d.ts +5 -0
- package/dist/service/encrypted-store.svc.js +43 -0
- package/dist/service/error.svc.d.ts +8 -0
- package/dist/service/error.svc.js +28 -0
- package/dist/service/file.svc.d.ts +17 -7
- package/dist/service/file.svc.js +80 -36
- package/dist/service/jwt.svc.d.ts +1 -0
- package/dist/service/jwt.svc.js +19 -0
- package/dist/service/tracker.svc.d.ts +58 -0
- package/dist/service/tracker.svc.js +101 -0
- package/dist/types/auth.d.ts +9 -0
- package/dist/utils/open-in-browser.d.ts +1 -0
- package/dist/utils/open-in-browser.js +21 -0
- package/dist/utils/retry.d.ts +11 -0
- package/dist/utils/retry.js +29 -0
- package/dist/utils/strip-typename.d.ts +1 -0
- package/dist/utils/strip-typename.js +16 -0
- package/package.json +40 -22
- package/dist/service/sbom.worker.js +0 -26
- /package/dist/{service/sbom.worker.d.ts → types/auth.js} +0 -0
package/dist/service/file.svc.js
CHANGED
|
@@ -1,10 +1,70 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path, { join, resolve } from 'node:path';
|
|
3
|
-
import { isCdxBom } from '@herodevs/eol-shared';
|
|
3
|
+
import { isCdxBom, isSpdxBom, spdxToCdxBom } from '@herodevs/eol-shared';
|
|
4
4
|
import { filenamePrefix } from "../config/constants.js";
|
|
5
5
|
import { getErrorMessage } from "./log.svc.js";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Computes an absolute output path using either a provided path or the base directory and default name.
|
|
8
|
+
*/
|
|
9
|
+
function resolveOutputPath(baseDir, defaultFilename, customPath) {
|
|
10
|
+
const defaultOutput = resolve(join(baseDir, defaultFilename));
|
|
11
|
+
if (!customPath) {
|
|
12
|
+
return { fileName: defaultFilename, fullPath: defaultOutput };
|
|
13
|
+
}
|
|
14
|
+
const resolvedCustomPath = resolve(customPath);
|
|
15
|
+
let targetPath = resolvedCustomPath;
|
|
16
|
+
const hasTrailingSeparator = /[\\/]$/.test(customPath);
|
|
17
|
+
const customIsDirectory = fs.existsSync(resolvedCustomPath) && fs.statSync(resolvedCustomPath).isDirectory();
|
|
18
|
+
if (hasTrailingSeparator || customIsDirectory) {
|
|
19
|
+
targetPath = join(resolvedCustomPath, defaultFilename);
|
|
20
|
+
}
|
|
21
|
+
return { fileName: path.basename(targetPath), fullPath: targetPath };
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Ensures the output directory for a given path exists, is a directory, and is writable.
|
|
25
|
+
*/
|
|
26
|
+
function ensureOutputDirectory(fullPath, fileName) {
|
|
27
|
+
const targetDir = path.dirname(fullPath);
|
|
28
|
+
if (!fs.existsSync(targetDir)) {
|
|
29
|
+
throw new Error(`Unable to save ${fileName}`);
|
|
30
|
+
}
|
|
31
|
+
const stats = fs.statSync(targetDir);
|
|
32
|
+
if (!stats.isDirectory()) {
|
|
33
|
+
throw new Error(`Unable to save ${fileName}`);
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
fs.accessSync(targetDir, fs.constants.W_OK);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
throw new Error(`Unable to save ${fileName}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Writes JSON to disk after validating directory constraints and formats the payload for readability.
|
|
44
|
+
*/
|
|
45
|
+
function writeJsonFile(fullPath, fileName, payload, failureLabel) {
|
|
46
|
+
ensureOutputDirectory(fullPath, fileName);
|
|
47
|
+
try {
|
|
48
|
+
fs.writeFileSync(fullPath, JSON.stringify(payload, null, 2));
|
|
49
|
+
return fullPath;
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const fileError = error;
|
|
53
|
+
switch (fileError.code) {
|
|
54
|
+
case 'EACCES':
|
|
55
|
+
throw new Error(`Permission denied. Unable to save ${fileName}`);
|
|
56
|
+
case 'ENOSPC':
|
|
57
|
+
throw new Error(`No space left on device. Unable to save ${fileName}`);
|
|
58
|
+
case 'ENOENT':
|
|
59
|
+
case 'ENOTDIR':
|
|
60
|
+
throw new Error(`Unable to save ${fileName}`);
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`Failed to save ${failureLabel}: ${getErrorMessage(error)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Reads an SBOM from a file path and converts it to CycloneDX format
|
|
67
|
+
* Supports both SPDX 2.3 and CycloneDX formats
|
|
8
68
|
*/
|
|
9
69
|
export function readSbomFromFile(filePath) {
|
|
10
70
|
const file = resolve(filePath);
|
|
@@ -13,11 +73,14 @@ export function readSbomFromFile(filePath) {
|
|
|
13
73
|
}
|
|
14
74
|
try {
|
|
15
75
|
const fileContent = fs.readFileSync(file, 'utf8');
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
18
|
-
|
|
76
|
+
const jsonContent = JSON.parse(fileContent);
|
|
77
|
+
if (isSpdxBom(jsonContent)) {
|
|
78
|
+
return spdxToCdxBom(jsonContent);
|
|
79
|
+
}
|
|
80
|
+
if (isCdxBom(jsonContent)) {
|
|
81
|
+
return jsonContent;
|
|
19
82
|
}
|
|
20
|
-
|
|
83
|
+
throw new Error(`Invalid SBOM file format. Expected SPDX 2.3 or CycloneDX format.`);
|
|
21
84
|
}
|
|
22
85
|
catch (error) {
|
|
23
86
|
throw new Error(`Failed to read SBOM file: ${getErrorMessage(error)}`);
|
|
@@ -36,36 +99,17 @@ export function validateDirectory(dirPath) {
|
|
|
36
99
|
throw new Error(`Path is not a directory: ${dir}`);
|
|
37
100
|
}
|
|
38
101
|
}
|
|
102
|
+
const artifactFilenames = {
|
|
103
|
+
sbom: `${filenamePrefix}.sbom.json`,
|
|
104
|
+
sbomTrimmed: `${filenamePrefix}.sbom-trimmed.json`,
|
|
105
|
+
report: `${filenamePrefix}.report.json`,
|
|
106
|
+
};
|
|
39
107
|
/**
|
|
40
|
-
* Saves an SBOM to
|
|
108
|
+
* Saves an SBOM, trimmed SBOM, or report to disk using the correct default filename.
|
|
41
109
|
*/
|
|
42
|
-
export function
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
throw new Error(`Failed to save SBOM: ${getErrorMessage(error)}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* Saves an EOL report to a file in the specified directory
|
|
54
|
-
*/
|
|
55
|
-
export function saveReportToFile(dir, report) {
|
|
56
|
-
const reportPath = path.join(dir, `${filenamePrefix}.report.json`);
|
|
57
|
-
try {
|
|
58
|
-
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
|
59
|
-
return reportPath;
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
const fileError = error;
|
|
63
|
-
if (fileError.code === 'EACCES') {
|
|
64
|
-
throw new Error(`Permission denied. Unable to save report to ${filenamePrefix}.report.json`);
|
|
65
|
-
}
|
|
66
|
-
if (fileError.code === 'ENOSPC') {
|
|
67
|
-
throw new Error(`No space left on device. Unable to save report to ${filenamePrefix}.report.json`);
|
|
68
|
-
}
|
|
69
|
-
throw new Error(`Failed to save report: ${getErrorMessage(error)}`);
|
|
70
|
-
}
|
|
110
|
+
export function saveArtifactToFile(dir, request) {
|
|
111
|
+
const defaultFilename = artifactFilenames[request.kind];
|
|
112
|
+
const customOutputPath = 'outputPath' in request ? request.outputPath : undefined;
|
|
113
|
+
const { fileName, fullPath } = resolveOutputPath(dir, defaultFilename, customOutputPath);
|
|
114
|
+
return writeJsonFile(fullPath, fileName, request.payload, fileName);
|
|
71
115
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function decodeJwtPayload(token: string | undefined): Record<string, unknown> | undefined;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function decodeJwtPayload(token) {
|
|
2
|
+
if (!token) {
|
|
3
|
+
return;
|
|
4
|
+
}
|
|
5
|
+
try {
|
|
6
|
+
const parts = token.split('.');
|
|
7
|
+
if (parts.length < 2 || !parts[1]) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const payload = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'));
|
|
11
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
return payload;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { type TrackerCategoryDefinition, type TrackerConfig } from '../config/tracker.config.ts';
|
|
2
|
+
export type GitLastCommit = {
|
|
3
|
+
hash: string;
|
|
4
|
+
timestamp: string;
|
|
5
|
+
author: string;
|
|
6
|
+
};
|
|
7
|
+
export type FilesStats = {
|
|
8
|
+
total: number;
|
|
9
|
+
source: number;
|
|
10
|
+
comment: number;
|
|
11
|
+
single: number;
|
|
12
|
+
block: number;
|
|
13
|
+
mixed: number;
|
|
14
|
+
empty: number;
|
|
15
|
+
todo: number;
|
|
16
|
+
blockEmpty: number;
|
|
17
|
+
[k: string]: number | string | boolean;
|
|
18
|
+
};
|
|
19
|
+
export type CategoryStatsResult = {
|
|
20
|
+
name: string;
|
|
21
|
+
totals: FilesStats;
|
|
22
|
+
errors: string[];
|
|
23
|
+
fileTypes: string[];
|
|
24
|
+
};
|
|
25
|
+
export type CategorySavedResult = {
|
|
26
|
+
timestamp: string;
|
|
27
|
+
hash: string;
|
|
28
|
+
categories: CategoryStatsResult[];
|
|
29
|
+
};
|
|
30
|
+
export declare const INITIAL_FILES_STATS: FilesStats;
|
|
31
|
+
export declare const getRootDir: (path: string) => string;
|
|
32
|
+
export declare const getConfiguration: (path: string, folderName: string, fileName: string) => TrackerConfig;
|
|
33
|
+
export declare const createTrackerConfig: (rootPath: string, config: TrackerConfig, overwrite?: boolean) => Promise<void>;
|
|
34
|
+
export declare const getFilesFromCategory: (category: TrackerCategoryDefinition, options: {
|
|
35
|
+
rootDir: string;
|
|
36
|
+
ignorePatterns?: string[];
|
|
37
|
+
}) => string[];
|
|
38
|
+
export declare const getFileStats: (path: string, options: {
|
|
39
|
+
rootDir: string;
|
|
40
|
+
}) => {
|
|
41
|
+
source: number;
|
|
42
|
+
total: number;
|
|
43
|
+
comment: number;
|
|
44
|
+
single: number;
|
|
45
|
+
block: number;
|
|
46
|
+
mixed: number;
|
|
47
|
+
empty: number;
|
|
48
|
+
todo: number;
|
|
49
|
+
blockEmpty: number;
|
|
50
|
+
path: string;
|
|
51
|
+
fileType: string;
|
|
52
|
+
error?: undefined;
|
|
53
|
+
} | {
|
|
54
|
+
path: string;
|
|
55
|
+
fileType: string;
|
|
56
|
+
error: boolean;
|
|
57
|
+
};
|
|
58
|
+
export declare const saveResults: (categoriesResult: CategoryStatsResult[], rootDir: string, outputDir: string, git: GitLastCommit) => string;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { writeFile } from 'node:fs/promises';
|
|
3
|
+
import { extname, join, resolve } from 'node:path';
|
|
4
|
+
import { globSync } from 'glob'; // replace with node:fs globSync as soon as v22 is minimum supported version
|
|
5
|
+
import sloc from 'sloc';
|
|
6
|
+
import { DEFAULT_TRACKER_RUN_DATA_FILE } from '../config/constants.js';
|
|
7
|
+
import { TRACKER_ROOT_FILE } from "../config/tracker.config.js";
|
|
8
|
+
export const INITIAL_FILES_STATS = {
|
|
9
|
+
total: 0,
|
|
10
|
+
block: 0,
|
|
11
|
+
blockEmpty: 0,
|
|
12
|
+
comment: 0,
|
|
13
|
+
empty: 0,
|
|
14
|
+
mixed: 0,
|
|
15
|
+
single: 0,
|
|
16
|
+
source: 0,
|
|
17
|
+
todo: 0,
|
|
18
|
+
};
|
|
19
|
+
export const getRootDir = (path) => {
|
|
20
|
+
if (existsSync(join(path, TRACKER_ROOT_FILE))) {
|
|
21
|
+
return path;
|
|
22
|
+
}
|
|
23
|
+
else if (path === join(path, '..')) {
|
|
24
|
+
throw new Error(`Couldn't find root directory for the project`);
|
|
25
|
+
}
|
|
26
|
+
return getRootDir(resolve(join(path, '..')));
|
|
27
|
+
};
|
|
28
|
+
export const getConfiguration = (path, folderName, fileName) => {
|
|
29
|
+
const filePath = join(path, folderName, fileName);
|
|
30
|
+
if (!existsSync(filePath)) {
|
|
31
|
+
throw new Error(`Couldn't find configuration ${fileName} file in ${path}. If you haven't, run tracker init command to create the configuration file. If you have a custom folder and configuration file, use the flags -d (directory) and -f (filename) to specify it`);
|
|
32
|
+
}
|
|
33
|
+
const stringConfiguration = readFileSync(filePath, {
|
|
34
|
+
encoding: 'utf-8',
|
|
35
|
+
});
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(stringConfiguration);
|
|
38
|
+
}
|
|
39
|
+
catch (_err) {
|
|
40
|
+
throw new Error(`A configuration file was found, but it's contents are not valid. Review your configuration file and fix any errors, or run tracker init -o to overwrite the file`);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export const createTrackerConfig = async (rootPath, config, overwrite = false) => {
|
|
44
|
+
const { outputDir } = config;
|
|
45
|
+
const configDir = join(rootPath, outputDir);
|
|
46
|
+
const configFile = join(configDir, config.configFile);
|
|
47
|
+
const doesConfigFileExists = existsSync(configFile);
|
|
48
|
+
if (!existsSync(configDir)) {
|
|
49
|
+
mkdirSync(configDir);
|
|
50
|
+
}
|
|
51
|
+
if (doesConfigFileExists && !overwrite) {
|
|
52
|
+
throw new Error(`Configuration file already exists for this repo. If you want to overwrite it, run the command again with the --overwrite flag`);
|
|
53
|
+
}
|
|
54
|
+
await writeFile(join(configDir, config.configFile), JSON.stringify(config, null, 2));
|
|
55
|
+
};
|
|
56
|
+
export const getFilesFromCategory = (category, options) => {
|
|
57
|
+
const { fileTypes, includes } = category;
|
|
58
|
+
const { rootDir, ignorePatterns } = options;
|
|
59
|
+
// if no includes folder or no specific file type is set, we ignore the category
|
|
60
|
+
if (fileTypes.length === 0 || includes.length === 0) {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
const patterns = includes.flatMap((include) => fileTypes.map((type) => `*${include.replace('./', '')}/**/*.${type}`));
|
|
64
|
+
return globSync(patterns, {
|
|
65
|
+
cwd: rootDir,
|
|
66
|
+
ignore: ignorePatterns?.map((ignore) => `${ignore}`) ?? [],
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
export const getFileStats = (path, options) => {
|
|
70
|
+
const fileType = extname(path).replace(/\./g, '');
|
|
71
|
+
try {
|
|
72
|
+
const stats = sloc(readFileSync(join(options.rootDir, path), 'utf8'), fileType);
|
|
73
|
+
return {
|
|
74
|
+
path,
|
|
75
|
+
fileType,
|
|
76
|
+
...stats,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
catch (_err) {
|
|
80
|
+
return {
|
|
81
|
+
path,
|
|
82
|
+
fileType,
|
|
83
|
+
error: true,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
export const saveResults = (categoriesResult, rootDir, outputDir, git) => {
|
|
88
|
+
const dataResults = [];
|
|
89
|
+
const dataFile = join(rootDir, outputDir, DEFAULT_TRACKER_RUN_DATA_FILE);
|
|
90
|
+
try {
|
|
91
|
+
const savedOutput = readFileSync(dataFile).toString('utf8');
|
|
92
|
+
dataResults.push(...JSON.parse(savedOutput));
|
|
93
|
+
}
|
|
94
|
+
catch (_err) { }
|
|
95
|
+
dataResults.push({
|
|
96
|
+
...git,
|
|
97
|
+
categories: categoriesResult,
|
|
98
|
+
});
|
|
99
|
+
writeFileSync(dataFile, JSON.stringify(dataResults, null, 2));
|
|
100
|
+
return `${dataFile}`;
|
|
101
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function openInBrowser(url: string): Promise<void>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { platform } from 'node:os';
|
|
3
|
+
export function openInBrowser(url) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const escapedUrl = `"${url.replace(/"/g, '\\"')}"`;
|
|
6
|
+
const command = (() => {
|
|
7
|
+
const plat = platform();
|
|
8
|
+
if (plat === 'darwin')
|
|
9
|
+
return `open ${escapedUrl}`; // macOS
|
|
10
|
+
if (plat === 'win32')
|
|
11
|
+
return `start "" ${escapedUrl}`; // Windows
|
|
12
|
+
return `xdg-open ${escapedUrl}`; // Linux
|
|
13
|
+
})();
|
|
14
|
+
exec(command, (err) => {
|
|
15
|
+
if (err)
|
|
16
|
+
reject(new Error(`Failed to open browser: ${err.message}`));
|
|
17
|
+
else
|
|
18
|
+
resolve();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type RetryOptions = {
|
|
2
|
+
attempts: number;
|
|
3
|
+
baseDelayMs: number;
|
|
4
|
+
onRetry?: (info: {
|
|
5
|
+
attempt: number;
|
|
6
|
+
delayMs: number;
|
|
7
|
+
error: unknown;
|
|
8
|
+
}) => void;
|
|
9
|
+
finalErrorMessage?: string;
|
|
10
|
+
};
|
|
11
|
+
export declare function withRetries<T>(operation: string, fn: () => Promise<T>, options: RetryOptions): Promise<T>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { debugLogger } from "../service/log.svc.js";
|
|
2
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3
|
+
export async function withRetries(operation, fn, options) {
|
|
4
|
+
const { attempts, baseDelayMs, onRetry, finalErrorMessage } = options;
|
|
5
|
+
let lastError;
|
|
6
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
7
|
+
try {
|
|
8
|
+
return await fn();
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
lastError = error;
|
|
12
|
+
if (attempt === attempts) {
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
const delayMs = baseDelayMs * attempt;
|
|
16
|
+
if (onRetry) {
|
|
17
|
+
onRetry({ attempt, delayMs, error });
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
debugLogger('Retry (%s) attempt %d/%d after %dms: %o', operation, attempt, attempts, delayMs, error);
|
|
21
|
+
}
|
|
22
|
+
await sleep(delayMs);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const message = finalErrorMessage ??
|
|
26
|
+
(lastError instanceof Error ? lastError.message : null) ??
|
|
27
|
+
'Please contact support@herodevs.com.';
|
|
28
|
+
throw new Error(message);
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function stripTypename<T>(obj: T): T;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function stripTypename(obj) {
|
|
2
|
+
if (Array.isArray(obj)) {
|
|
3
|
+
return obj.map(stripTypename);
|
|
4
|
+
}
|
|
5
|
+
if (obj !== null && typeof obj === 'object') {
|
|
6
|
+
const result = {};
|
|
7
|
+
for (const key of Object.keys(obj)) {
|
|
8
|
+
if (key === '__typename')
|
|
9
|
+
continue;
|
|
10
|
+
const value = obj[key];
|
|
11
|
+
result[key] = stripTypename(value);
|
|
12
|
+
}
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
return obj;
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@herodevs/cli",
|
|
3
|
-
"version": "2.0.0
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"author": "HeroDevs, Inc",
|
|
5
5
|
"bin": {
|
|
6
6
|
"hd": "./bin/run.js"
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"url": "https://github.com/herodevs/cli"
|
|
12
12
|
},
|
|
13
13
|
"homepage": "https://github.com/herodevs/cli",
|
|
14
|
-
"bugs": "https://github.com
|
|
14
|
+
"bugs": "https://github.com/herodevs/cli/issues",
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "shx rm -rf dist && tsc -b",
|
|
17
17
|
"ci": "biome ci",
|
|
@@ -20,17 +20,21 @@
|
|
|
20
20
|
"clean:files": "shx rm -f herodevs.**.csv herodevs.**.json herodevs.**.txt",
|
|
21
21
|
"dev": "npm run build && ./bin/dev.js",
|
|
22
22
|
"dev:debug": "npm run build && DEBUG=oclif:* ./bin/dev.js",
|
|
23
|
+
"dev:auth:local": "OAUTH_CONNECT_URL='http://localhost:6040/realms/herodevs_local/protocol/openid-connect' npm run dev auth login",
|
|
24
|
+
"dev:auth:logout": "npm run dev auth logout",
|
|
23
25
|
"format": "biome format --write",
|
|
24
26
|
"lint": "biome lint --write",
|
|
25
27
|
"postpack": "shx rm -f oclif.manifest.json",
|
|
26
28
|
"prepare": "shx test -d dist || npm run build",
|
|
27
29
|
"prepack": "oclif manifest",
|
|
28
30
|
"pretest": "npm run lint && npm run typecheck",
|
|
29
|
-
"readme": "npm run ci:fix && npm run build &&
|
|
30
|
-
"test": "
|
|
31
|
-
"test:
|
|
31
|
+
"readme": "npm run ci:fix && npm run build && oclif readme && sed -i '' 's|/plugin-help/blob/v|/plugin-help/blob/|; s|/plugin-update/blob/v|/plugin-update/blob/|' README.md",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest watch",
|
|
34
|
+
"test:e2e": "globstar -- node --import tsx --import ./e2e/setup/register-mock-auth.mjs --test \"e2e/**/*.test.ts\"",
|
|
32
35
|
"typecheck": "tsc --noEmit",
|
|
33
|
-
"version": "oclif manifest"
|
|
36
|
+
"version": "oclif manifest",
|
|
37
|
+
"postversion": "node scripts/update-install-script-version.js && git add README.md"
|
|
34
38
|
},
|
|
35
39
|
"keywords": [
|
|
36
40
|
"herodevs",
|
|
@@ -38,34 +42,49 @@
|
|
|
38
42
|
"herodevs cli"
|
|
39
43
|
],
|
|
40
44
|
"dependencies": {
|
|
41
|
-
"@amplitude/analytics-node": "^1.5.
|
|
42
|
-
"@apollo/client": "^
|
|
43
|
-
"@cyclonedx/cdxgen": "
|
|
44
|
-
"@herodevs/eol-shared": "github:herodevs/eol-shared#v0.1.
|
|
45
|
-
"@
|
|
45
|
+
"@amplitude/analytics-node": "^1.5.26",
|
|
46
|
+
"@apollo/client": "^4.0.9",
|
|
47
|
+
"@cyclonedx/cdxgen": "^12.1.1",
|
|
48
|
+
"@herodevs/eol-shared": "github:herodevs/eol-shared#v0.1.18",
|
|
49
|
+
"@inquirer/prompts": "^8.0.2",
|
|
50
|
+
"@oclif/core": "^4.8.0",
|
|
46
51
|
"@oclif/plugin-help": "^6.2.32",
|
|
47
|
-
"@oclif/plugin-update": "^4.7.
|
|
52
|
+
"@oclif/plugin-update": "^4.7.16",
|
|
53
|
+
"@oclif/table": "^0.5.1",
|
|
54
|
+
"cli-progress": "^3.12.0",
|
|
55
|
+
"conf": "^15.1.0",
|
|
56
|
+
"date-fns": "^4.1.0",
|
|
57
|
+
"glob": "^13.0.0",
|
|
48
58
|
"graphql": "^16.11.0",
|
|
49
59
|
"node-machine-id": "^1.1.12",
|
|
50
|
-
"ora": "^
|
|
60
|
+
"ora": "^9.0.0",
|
|
51
61
|
"packageurl-js": "^2.0.1",
|
|
52
|
-
"
|
|
62
|
+
"sloc": "^0.3.2",
|
|
63
|
+
"terminal-link": "^5.0.0",
|
|
53
64
|
"update-notifier": "^7.3.1"
|
|
54
65
|
},
|
|
55
66
|
"devDependencies": {
|
|
56
|
-
"@biomejs/biome": "^2.
|
|
57
|
-
"@oclif/test": "^4.1.
|
|
67
|
+
"@biomejs/biome": "^2.3.8",
|
|
68
|
+
"@oclif/test": "^4.1.15",
|
|
69
|
+
"@types/cli-progress": "^3.11.6",
|
|
70
|
+
"@types/debug": "^4.1.12",
|
|
58
71
|
"@types/inquirer": "^9.0.9",
|
|
59
|
-
"@types/
|
|
60
|
-
"@types/
|
|
72
|
+
"@types/mock-fs": "^4.13.4",
|
|
73
|
+
"@types/node": "^24.10.1",
|
|
74
|
+
"@types/ora": "^3.1.0",
|
|
75
|
+
"@types/sinon": "^21.0.0",
|
|
76
|
+
"@types/sloc": "^0.2.3",
|
|
77
|
+
"@types/terminal-link": "^1.1.0",
|
|
61
78
|
"@types/update-notifier": "^6.0.8",
|
|
62
79
|
"globstar": "^1.0.0",
|
|
63
|
-
"
|
|
80
|
+
"mock-fs": "^5.5.0",
|
|
81
|
+
"oclif": "^4.22.47",
|
|
64
82
|
"shx": "^0.4.0",
|
|
65
83
|
"sinon": "^21.0.0",
|
|
66
84
|
"ts-node": "^10.9.2",
|
|
67
|
-
"tsx": "^4.
|
|
68
|
-
"typescript": "^5.9.
|
|
85
|
+
"tsx": "^4.21.0",
|
|
86
|
+
"typescript": "^5.9.3",
|
|
87
|
+
"vitest": "^4.0.16"
|
|
69
88
|
},
|
|
70
89
|
"engines": {
|
|
71
90
|
"node": ">=20.0.0"
|
|
@@ -83,7 +102,6 @@
|
|
|
83
102
|
"commands": "./dist/commands",
|
|
84
103
|
"plugins": [
|
|
85
104
|
"@oclif/plugin-help",
|
|
86
|
-
"@oclif/plugin-plugins",
|
|
87
105
|
"@oclif/plugin-update"
|
|
88
106
|
],
|
|
89
107
|
"hooks": {
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { writeFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { createBom } from '@cyclonedx/cdxgen';
|
|
4
|
-
import { filenamePrefix } from "../config/constants.js";
|
|
5
|
-
import { SBOM_DEFAULT__OPTIONS } from "./cdx.svc.js";
|
|
6
|
-
process.on('uncaughtException', (err) => {
|
|
7
|
-
console.error('Uncaught exception:', err.message);
|
|
8
|
-
process.exit(1);
|
|
9
|
-
});
|
|
10
|
-
process.on('unhandledRejection', (reason) => {
|
|
11
|
-
console.error('Unhandled rejection:', reason);
|
|
12
|
-
process.exit(1);
|
|
13
|
-
});
|
|
14
|
-
try {
|
|
15
|
-
console.log('Sbom worker started');
|
|
16
|
-
const options = JSON.parse(process.argv[2]);
|
|
17
|
-
const { path, opts } = options;
|
|
18
|
-
const { bomJson } = await createBom(path, { ...SBOM_DEFAULT__OPTIONS, ...opts });
|
|
19
|
-
const outputPath = join(path, `${filenamePrefix}.sbom.json`);
|
|
20
|
-
writeFileSync(outputPath, JSON.stringify(bomJson, null, 2));
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
console.error('Error creating SBOM', error.message);
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
File without changes
|