@trustify-da/trustify-da-javascript-client 0.3.0-ea.7ed8d8c → 0.3.0-ea.7ee4047
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 +388 -20
- package/dist/package.json +39 -26
- package/dist/src/analysis.d.ts +58 -20
- package/dist/src/analysis.js +134 -78
- package/dist/src/batch_opts.d.ts +24 -0
- package/dist/src/batch_opts.js +35 -0
- package/dist/src/cli.js +427 -12
- package/dist/src/config.d.ts +120 -0
- package/dist/src/config.js +260 -0
- package/dist/src/cyclone_dx_sbom.d.ts +30 -2
- package/dist/src/cyclone_dx_sbom.js +95 -9
- package/dist/src/index.d.ts +154 -16
- package/dist/src/index.js +372 -36
- package/dist/src/license/index.d.ts +28 -0
- package/dist/src/license/index.js +106 -0
- package/dist/src/license/license_utils.d.ts +40 -0
- package/dist/src/license/license_utils.js +137 -0
- package/dist/src/license/licenses_api.d.ts +34 -0
- package/dist/src/license/licenses_api.js +98 -0
- package/dist/src/license/project_license.d.ts +20 -0
- package/dist/src/license/project_license.js +62 -0
- package/dist/src/oci_image/images.d.ts +1 -1
- package/dist/src/oci_image/images.js +15 -1
- package/dist/src/oci_image/utils.d.ts +5 -5
- package/dist/src/oci_image/utils.js +11 -2
- package/dist/src/package_version.d.ts +8 -0
- package/dist/src/package_version.js +31 -0
- package/dist/src/provider.d.ts +16 -3
- package/dist/src/provider.js +33 -5
- package/dist/src/providers/base_java.d.ts +67 -12
- package/dist/src/providers/base_java.js +82 -61
- package/dist/src/providers/base_javascript.d.ts +99 -12
- package/dist/src/providers/base_javascript.js +244 -28
- package/dist/src/providers/base_pyproject.d.ts +158 -0
- package/dist/src/providers/base_pyproject.js +322 -0
- package/dist/src/providers/containerfile_parser.d.ts +5 -0
- package/dist/src/providers/containerfile_parser.js +20 -0
- package/dist/src/providers/golang_gomodules.d.ts +30 -13
- package/dist/src/providers/golang_gomodules.js +219 -122
- package/dist/src/providers/gomod_parser.d.ts +4 -0
- package/dist/src/providers/gomod_parser.js +16 -0
- package/dist/src/providers/java_gradle.d.ts +73 -0
- package/dist/src/providers/java_gradle.js +331 -23
- package/dist/src/providers/java_gradle_groovy.d.ts +1 -1
- package/dist/src/providers/java_gradle_kotlin.d.ts +1 -1
- package/dist/src/providers/java_maven.d.ts +44 -9
- package/dist/src/providers/java_maven.js +299 -13
- package/dist/src/providers/javascript_bun.d.ts +22 -0
- package/dist/src/providers/javascript_bun.js +141 -0
- package/dist/src/providers/javascript_npm.d.ts +19 -0
- package/dist/src/providers/javascript_npm.js +61 -1
- package/dist/src/providers/javascript_pnpm.d.ts +14 -1
- package/dist/src/providers/javascript_pnpm.js +55 -5
- package/dist/src/providers/javascript_yarn.d.ts +12 -0
- package/dist/src/providers/javascript_yarn.js +75 -2
- package/dist/src/providers/manifest.d.ts +2 -0
- package/dist/src/providers/manifest.js +28 -7
- package/dist/src/providers/marker_evaluator.d.ts +14 -0
- package/dist/src/providers/marker_evaluator.js +191 -0
- package/dist/src/providers/oci_dockerfile.d.ts +51 -0
- package/dist/src/providers/oci_dockerfile.js +177 -0
- package/dist/src/providers/processors/yarn_berry_processor.d.ts +6 -2
- package/dist/src/providers/processors/yarn_berry_processor.js +91 -7
- package/dist/src/providers/processors/yarn_classic_processor.d.ts +6 -2
- package/dist/src/providers/processors/yarn_classic_processor.js +8 -6
- package/dist/src/providers/python_controller.d.ts +5 -1
- package/dist/src/providers/python_controller.js +69 -8
- package/dist/src/providers/python_pip.d.ts +12 -0
- package/dist/src/providers/python_pip.js +18 -8
- package/dist/src/providers/python_pip_pyproject.d.ts +61 -0
- package/dist/src/providers/python_pip_pyproject.js +146 -0
- package/dist/src/providers/python_poetry.d.ts +98 -0
- package/dist/src/providers/python_poetry.js +286 -0
- package/dist/src/providers/python_uv.d.ts +46 -0
- package/dist/src/providers/python_uv.js +275 -0
- package/dist/src/providers/requirements_parser.js +6 -9
- package/dist/src/providers/rust_cargo.d.ts +56 -0
- package/dist/src/providers/rust_cargo.js +682 -0
- package/dist/src/providers/tree-sitter-containerfile.wasm +0 -0
- package/dist/src/providers/tree-sitter-gomod.wasm +0 -0
- package/dist/src/providers/tree-sitter-requirements.wasm +0 -0
- package/dist/src/remediate.d.ts +107 -0
- package/dist/src/remediate.js +244 -0
- package/dist/src/remediation.d.ts +81 -0
- package/dist/src/remediation.js +475 -0
- package/dist/src/remediation_report.d.ts +23 -0
- package/dist/src/remediation_report.js +180 -0
- package/dist/src/sbom.d.ts +28 -2
- package/dist/src/sbom.js +26 -4
- package/dist/src/tools.d.ts +53 -11
- package/dist/src/tools.js +147 -13
- package/dist/src/updaters/maven_updater.d.ts +58 -0
- package/dist/src/updaters/maven_updater.js +345 -0
- package/dist/src/updaters/toml_updater.d.ts +62 -0
- package/dist/src/updaters/toml_updater.js +276 -0
- package/dist/src/workspace.d.ts +71 -0
- package/dist/src/workspace.js +257 -0
- package/package.json +40 -27
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { load as yamlLoad } from 'js-yaml';
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {{
|
|
6
|
+
* 'group-by'?: 'dependency' | 'bundle',
|
|
7
|
+
* exclude?: string[],
|
|
8
|
+
* labels?: string[],
|
|
9
|
+
* 'branch-prefix'?: string,
|
|
10
|
+
* [key: string]: unknown
|
|
11
|
+
* }} RemediationConfig
|
|
12
|
+
*/
|
|
13
|
+
/** @typedef {{ critical?: number, high?: number, 'license-conflicts'?: number, [key: string]: unknown }} FailOnConfig */
|
|
14
|
+
/** @typedef {{ 'fail-on'?: FailOnConfig, [key: string]: unknown }} CheckConfig */
|
|
15
|
+
/** @typedef {{ format?: string, targets?: string[], [key: string]: unknown }} SbomConfig */
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {{
|
|
18
|
+
* 'backend-url'?: string,
|
|
19
|
+
* providers?: string[] | string,
|
|
20
|
+
* sources?: string[] | string,
|
|
21
|
+
* remediation?: RemediationConfig,
|
|
22
|
+
* check?: CheckConfig,
|
|
23
|
+
* sbom?: SbomConfig,
|
|
24
|
+
* [key: string]: unknown
|
|
25
|
+
* }} FileConfig
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* @typedef {{
|
|
29
|
+
* backendUrl: string | null,
|
|
30
|
+
* backendUrlSource: 'cli' | 'environment' | 'file' | 'default',
|
|
31
|
+
* providers: string[],
|
|
32
|
+
* sources: string[],
|
|
33
|
+
* groupBy: string,
|
|
34
|
+
* remediation: object,
|
|
35
|
+
* check: object,
|
|
36
|
+
* sbom: object
|
|
37
|
+
* }} ResolvedConfig
|
|
38
|
+
*/
|
|
39
|
+
/** Config file names discovered by walking up the directory tree, in precedence order. */
|
|
40
|
+
export const CONFIG_FILENAMES = ['.trustify-da.yml', '.trustify-da.yaml'];
|
|
41
|
+
function isMapping(value) {
|
|
42
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
const prototype = Object.getPrototypeOf(value);
|
|
46
|
+
return prototype === Object.prototype || prototype == null;
|
|
47
|
+
}
|
|
48
|
+
function assertMapping(value, field, source) {
|
|
49
|
+
if (!isMapping(value)) {
|
|
50
|
+
throw new Error(`Invalid config file ${source}: ${field} must be a mapping`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function assertString(value, field, source) {
|
|
54
|
+
if (typeof value !== 'string') {
|
|
55
|
+
throw new Error(`Invalid config file ${source}: ${field} must be a string`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function assertStringArray(value, field, source) {
|
|
59
|
+
if (!Array.isArray(value) || value.some(item => typeof item !== 'string')) {
|
|
60
|
+
throw new Error(`Invalid config file ${source}: ${field} must be an array of strings`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function assertStringOrArray(value, field, source) {
|
|
64
|
+
if (typeof value !== 'string') {
|
|
65
|
+
assertStringArray(value, field, source);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Validates all fields in the initial configuration schema. Unknown fields are preserved.
|
|
70
|
+
* @param {unknown} config
|
|
71
|
+
* @param {string} [source]
|
|
72
|
+
* @returns {FileConfig}
|
|
73
|
+
*/
|
|
74
|
+
function validateConfig(config, source = '<config>') {
|
|
75
|
+
assertMapping(config, 'root', source);
|
|
76
|
+
if (config['backend-url'] !== undefined) {
|
|
77
|
+
assertString(config['backend-url'], 'backend-url', source);
|
|
78
|
+
}
|
|
79
|
+
if (config.providers !== undefined) {
|
|
80
|
+
assertStringOrArray(config.providers, 'providers', source);
|
|
81
|
+
}
|
|
82
|
+
if (config.sources !== undefined) {
|
|
83
|
+
assertStringOrArray(config.sources, 'sources', source);
|
|
84
|
+
}
|
|
85
|
+
if (config.remediation !== undefined) {
|
|
86
|
+
assertMapping(config.remediation, 'remediation', source);
|
|
87
|
+
const remediation = config.remediation;
|
|
88
|
+
if (remediation['group-by'] !== undefined && !['dependency', 'bundle'].includes(remediation['group-by'])) {
|
|
89
|
+
throw new Error(`Invalid config file ${source}: remediation.group-by must be dependency or bundle`);
|
|
90
|
+
}
|
|
91
|
+
if (remediation.exclude !== undefined) {
|
|
92
|
+
assertStringArray(remediation.exclude, 'remediation.exclude', source);
|
|
93
|
+
}
|
|
94
|
+
if (remediation.labels !== undefined) {
|
|
95
|
+
assertStringArray(remediation.labels, 'remediation.labels', source);
|
|
96
|
+
}
|
|
97
|
+
if (remediation['branch-prefix'] !== undefined) {
|
|
98
|
+
assertString(remediation['branch-prefix'], 'remediation.branch-prefix', source);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (config.check !== undefined) {
|
|
102
|
+
assertMapping(config.check, 'check', source);
|
|
103
|
+
if (config.check['fail-on'] !== undefined) {
|
|
104
|
+
assertMapping(config.check['fail-on'], 'check.fail-on', source);
|
|
105
|
+
for (const field of ['critical', 'high', 'license-conflicts']) {
|
|
106
|
+
const value = config.check['fail-on'][field];
|
|
107
|
+
if (value !== undefined && (typeof value !== 'number' || !Number.isFinite(value))) {
|
|
108
|
+
throw new Error(`Invalid config file ${source}: check.fail-on.${field} must be a finite number`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (config.sbom !== undefined) {
|
|
114
|
+
assertMapping(config.sbom, 'sbom', source);
|
|
115
|
+
if (config.sbom.format !== undefined) {
|
|
116
|
+
assertString(config.sbom.format, 'sbom.format', source);
|
|
117
|
+
}
|
|
118
|
+
if (config.sbom.targets !== undefined) {
|
|
119
|
+
assertStringArray(config.sbom.targets, 'sbom.targets', source);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return config;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Walks up from `startPath` looking for a `.trustify-da.yml` (or `.yaml`) file,
|
|
126
|
+
* similar to how `.eslintrc` discovery works. If `startPath` points at a file,
|
|
127
|
+
* discovery begins in its containing directory. Within a directory, `.yml` takes
|
|
128
|
+
* precedence over `.yaml`.
|
|
129
|
+
* @param {string} startPath - manifest file or directory to start the search from
|
|
130
|
+
* @returns {string | null} absolute path to the config file, or null if none found
|
|
131
|
+
*/
|
|
132
|
+
function findConfigFile(startPath) {
|
|
133
|
+
let dir = path.resolve(startPath || '.');
|
|
134
|
+
try {
|
|
135
|
+
if (fs.statSync(dir).isFile()) {
|
|
136
|
+
dir = path.dirname(dir);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// startPath may not exist yet — walk up from its resolved location anyway
|
|
141
|
+
}
|
|
142
|
+
// Walk up until the filesystem root (where dirname(dir) === dir)
|
|
143
|
+
for (;;) {
|
|
144
|
+
for (const name of CONFIG_FILENAMES) {
|
|
145
|
+
const candidate = path.join(dir, name);
|
|
146
|
+
try {
|
|
147
|
+
if (fs.statSync(candidate).isFile()) {
|
|
148
|
+
return candidate;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Ignore inaccessible or disappearing candidates and continue discovery.
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const parent = path.dirname(dir);
|
|
156
|
+
if (parent === dir) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
dir = parent;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Discovers and parses `.trustify-da.yml` by walking up from `startPath`.
|
|
164
|
+
* A missing config file is not an error — an empty object is returned so callers
|
|
165
|
+
* can fall back to defaults.
|
|
166
|
+
* @param {string} [startPath] - manifest file or directory to start the search from
|
|
167
|
+
* @returns {FileConfig} the parsed config object, or `{}` when no file is found
|
|
168
|
+
* @throws {Error} when the config file exists but contains malformed YAML
|
|
169
|
+
*/
|
|
170
|
+
export function loadConfig(startPath) {
|
|
171
|
+
const configPath = findConfigFile(startPath);
|
|
172
|
+
if (!configPath) {
|
|
173
|
+
return {};
|
|
174
|
+
}
|
|
175
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
176
|
+
let doc;
|
|
177
|
+
try {
|
|
178
|
+
doc = yamlLoad(content);
|
|
179
|
+
}
|
|
180
|
+
catch (err) {
|
|
181
|
+
throw new Error(`Failed to parse config file ${configPath}: ${err.message}`);
|
|
182
|
+
}
|
|
183
|
+
return doc == null ? {} : validateConfig(doc, configPath);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Normalizes a providers/sources value to an array of trimmed strings. Accepts
|
|
187
|
+
* either a YAML array (`[redhat, osv]`) or a comma-separated string (`redhat,osv`).
|
|
188
|
+
* @param {string[] | string | undefined | null} value
|
|
189
|
+
* @returns {string[]}
|
|
190
|
+
*/
|
|
191
|
+
function toArray(value) {
|
|
192
|
+
if (Array.isArray(value)) {
|
|
193
|
+
return value.filter(v => typeof v === 'string' && v.trim()).map(v => v.trim());
|
|
194
|
+
}
|
|
195
|
+
if (typeof value === 'string') {
|
|
196
|
+
return value.split(',').map(v => v.trim()).filter(Boolean);
|
|
197
|
+
}
|
|
198
|
+
return [];
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Returns the first argument that is neither undefined nor null.
|
|
202
|
+
* @param {...unknown} values
|
|
203
|
+
* @returns {unknown}
|
|
204
|
+
*/
|
|
205
|
+
function first(...values) {
|
|
206
|
+
return values.find(v => v != null);
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Merges config sources with precedence: CLI flag > environment variable
|
|
210
|
+
* (`TRUSTIFY_DA_*`) > config file > hardcoded default. Providers and sources are
|
|
211
|
+
* normalized to arrays regardless of whether the source used a YAML array or a
|
|
212
|
+
* comma-separated string.
|
|
213
|
+
* @param {FileConfig} [fileConfig={}] - values parsed from `.trustify-da.yml`
|
|
214
|
+
* @param {{ providers?: string, sources?: string, groupBy?: string, backendUrl?: string }} [cliFlags={}]
|
|
215
|
+
* @param {{ [key: string]: string | undefined }} [envVars={}] - typically `process.env`
|
|
216
|
+
* @returns {ResolvedConfig} the resolved, typed config object
|
|
217
|
+
*/
|
|
218
|
+
export function mergeConfig(fileConfig = {}, cliFlags = {}, envVars = {}) {
|
|
219
|
+
const file = validateConfig(fileConfig || {});
|
|
220
|
+
const cli = cliFlags || {};
|
|
221
|
+
const env = envVars || {};
|
|
222
|
+
let backendUrl = null;
|
|
223
|
+
let backendUrlSource = 'default';
|
|
224
|
+
if (cli.backendUrl != null) {
|
|
225
|
+
backendUrl = cli.backendUrl;
|
|
226
|
+
backendUrlSource = 'cli';
|
|
227
|
+
}
|
|
228
|
+
else if (env.TRUSTIFY_DA_BACKEND_URL != null) {
|
|
229
|
+
backendUrl = env.TRUSTIFY_DA_BACKEND_URL;
|
|
230
|
+
backendUrlSource = 'environment';
|
|
231
|
+
}
|
|
232
|
+
else if (file['backend-url'] != null) {
|
|
233
|
+
backendUrl = file['backend-url'];
|
|
234
|
+
backendUrlSource = 'file';
|
|
235
|
+
}
|
|
236
|
+
const groupBy = first(cli.groupBy, env.TRUSTIFY_DA_GROUP_BY, file.remediation?.['group-by']) ?? 'dependency';
|
|
237
|
+
if (!['dependency', 'bundle'].includes(groupBy)) {
|
|
238
|
+
throw new Error(`Invalid group-by value "${groupBy}". Expected dependency or bundle.`);
|
|
239
|
+
}
|
|
240
|
+
return {
|
|
241
|
+
backendUrl,
|
|
242
|
+
backendUrlSource,
|
|
243
|
+
providers: toArray(first(cli.providers, env.TRUSTIFY_DA_PROVIDERS, file.providers)),
|
|
244
|
+
sources: toArray(first(cli.sources, env.TRUSTIFY_DA_SOURCES, file.sources)),
|
|
245
|
+
groupBy,
|
|
246
|
+
remediation: file.remediation ?? {},
|
|
247
|
+
check: file.check ?? {},
|
|
248
|
+
sbom: file.sbom ?? {},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Loads and merges the project configuration for a target path.
|
|
253
|
+
* @param {string} [startPath] - manifest file or directory to start discovery from
|
|
254
|
+
* @param {{ providers?: string, sources?: string, groupBy?: string, backendUrl?: string }} [cliFlags={}] - explicit options
|
|
255
|
+
* @param {{ [key: string]: string | undefined }} [envVars={}] - typically `process.env`
|
|
256
|
+
* @returns {ResolvedConfig} the resolved, typed config object
|
|
257
|
+
*/
|
|
258
|
+
export function resolveConfig(startPath, cliFlags = {}, envVars = {}) {
|
|
259
|
+
return mergeConfig(loadConfig(startPath), cliFlags, envVars);
|
|
260
|
+
}
|
|
@@ -6,9 +6,10 @@ export default class CycloneDxSbom {
|
|
|
6
6
|
sourceManifestForAuditTrail: any;
|
|
7
7
|
/**
|
|
8
8
|
* @param {PackageURL} root - add main/root component for sbom
|
|
9
|
+
* @param {string|Array} [licenses] - optional license(s) for the root component
|
|
9
10
|
* @return {CycloneDxSbom} the CycloneDxSbom Sbom Object
|
|
10
11
|
*/
|
|
11
|
-
addRoot(root: PackageURL): CycloneDxSbom;
|
|
12
|
+
addRoot(root: PackageURL, licenses?: string | any[]): CycloneDxSbom;
|
|
12
13
|
/**
|
|
13
14
|
* @return {{{"bom-ref": string, name, purl: string, type, version}}} root component of sbom.
|
|
14
15
|
*/
|
|
@@ -17,9 +18,27 @@ export default class CycloneDxSbom {
|
|
|
17
18
|
* Adds a dependency relationship between two components in the SBOM
|
|
18
19
|
* @param {PackageURL} sourceRef - The source component (parent)
|
|
19
20
|
* @param {PackageURL} targetRef - The target component (dependency)
|
|
21
|
+
* @param {string} [scope] - Scope of the dependency
|
|
22
|
+
* @param {Array<{alg: string, content: string}>} [targetHashes] - Optional hashes for the target component
|
|
20
23
|
* @return {CycloneDxSbom} The updated SBOM
|
|
21
24
|
*/
|
|
22
|
-
addDependency(sourceRef: PackageURL, targetRef: PackageURL, scope
|
|
25
|
+
addDependency(sourceRef: PackageURL, targetRef: PackageURL, scope?: string, targetHashes?: Array<{
|
|
26
|
+
alg: string;
|
|
27
|
+
content: string;
|
|
28
|
+
}>): CycloneDxSbom;
|
|
29
|
+
/**
|
|
30
|
+
* Attach hashes to already-added components by matching their PURL. This is a
|
|
31
|
+
* post-processing step so ecosystem-specific hash sources (e.g. Maven reading
|
|
32
|
+
* the local .m2 cache) can enrich the SBOM without threading their concern
|
|
33
|
+
* through the shared dependency-tree parser. Components without a matching
|
|
34
|
+
* entry, or whose hashes are already set, are left untouched.
|
|
35
|
+
* @param {Map<string, Array<{alg: string, content: string}>>} hashMap - PURL→hashes map
|
|
36
|
+
* @return {CycloneDxSbom} the updated SBOM
|
|
37
|
+
*/
|
|
38
|
+
attachHashes(hashMap: Map<string, Array<{
|
|
39
|
+
alg: string;
|
|
40
|
+
content: string;
|
|
41
|
+
}>>): CycloneDxSbom;
|
|
23
42
|
/** @param {{}} opts - various options, settings and configuration of application.
|
|
24
43
|
* @return String CycloneDx Sbom json object in a string format
|
|
25
44
|
*/
|
|
@@ -48,6 +67,8 @@ export default class CycloneDxSbom {
|
|
|
48
67
|
type: any;
|
|
49
68
|
version: any;
|
|
50
69
|
scope: any;
|
|
70
|
+
licenses?: any;
|
|
71
|
+
hashes?: any;
|
|
51
72
|
};
|
|
52
73
|
/**
|
|
53
74
|
* This method gets an array of dependencies to be ignored, and remove all of them from CycloneDx Sbom
|
|
@@ -68,6 +89,13 @@ export default class CycloneDxSbom {
|
|
|
68
89
|
* @return {boolean}
|
|
69
90
|
*/
|
|
70
91
|
checkIfPackageInsideDependsOnList(component: any, name: string): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Checks if any entry in the dependsOn list of sourceRef starts with the given purl prefix.
|
|
94
|
+
* @param {PackageURL} sourceRef - The source component
|
|
95
|
+
* @param {string} purlPrefix - The purl prefix to match (e.g. "pkg:npm/minimist@")
|
|
96
|
+
* @return {boolean}
|
|
97
|
+
*/
|
|
98
|
+
checkDependsOnByPurlPrefix(sourceRef: PackageURL, purlPrefix: string): boolean;
|
|
71
99
|
/** Removes the root component from the sbom
|
|
72
100
|
*/
|
|
73
101
|
removeRootComponent(): void;
|
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { EOL } from "os";
|
|
2
2
|
import { PackageURL } from "packageurl-js";
|
|
3
|
+
import { getPackageVersion } from "./package_version.js";
|
|
4
|
+
/**
|
|
5
|
+
* This client's version, read once at module load, used to populate
|
|
6
|
+
* metadata.tools.components[].version in the generated SBOM.
|
|
7
|
+
* @type {string|undefined}
|
|
8
|
+
*/
|
|
9
|
+
const PACKAGE_VERSION = getPackageVersion();
|
|
3
10
|
/**
|
|
4
11
|
*
|
|
5
12
|
* @param component {PackageURL}
|
|
6
13
|
* @param type type of package - application or library
|
|
7
14
|
* @param scope scope of the component - runtime or compile
|
|
8
|
-
* @
|
|
15
|
+
* @param licenses optional license string or array of licenses for the component
|
|
16
|
+
* @param hashes optional array of hash objects for the component, e.g. [{alg: "SHA-256", content: "..."}]
|
|
17
|
+
* @return {{"bom-ref": string, name, purl: string, type, version, scope, licenses?, hashes?}}
|
|
9
18
|
* @private
|
|
10
19
|
*/
|
|
11
|
-
function getComponent(component, type, scope) {
|
|
20
|
+
function getComponent(component, type, scope, licenses, hashes) {
|
|
12
21
|
let componentObject;
|
|
13
22
|
if (component instanceof PackageURL) {
|
|
14
23
|
if (component.namespace) {
|
|
@@ -36,6 +45,28 @@ function getComponent(component, type, scope) {
|
|
|
36
45
|
else {
|
|
37
46
|
componentObject = component;
|
|
38
47
|
}
|
|
48
|
+
// Add licenses if provided (CycloneDX format). Callers must provide valid SPDX identifiers.
|
|
49
|
+
if (licenses) {
|
|
50
|
+
const licenseArray = Array.isArray(licenses) ? licenses : [licenses];
|
|
51
|
+
const validLicenses = licenseArray
|
|
52
|
+
.map(lic => {
|
|
53
|
+
if (typeof lic === 'string') {
|
|
54
|
+
return { license: { id: lic } };
|
|
55
|
+
}
|
|
56
|
+
if (typeof lic === 'object' && lic !== null && ('license' in lic || 'expression' in lic)) {
|
|
57
|
+
return lic;
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
})
|
|
61
|
+
.filter(Boolean);
|
|
62
|
+
if (validLicenses.length > 0) {
|
|
63
|
+
componentObject.licenses = validLicenses;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Add hashes if provided (CycloneDX 1.4 format).
|
|
67
|
+
if (hashes && hashes.length > 0) {
|
|
68
|
+
componentObject.hashes = hashes;
|
|
69
|
+
}
|
|
39
70
|
return componentObject;
|
|
40
71
|
}
|
|
41
72
|
function createDependency(dependency) {
|
|
@@ -56,11 +87,12 @@ export default class CycloneDxSbom {
|
|
|
56
87
|
}
|
|
57
88
|
/**
|
|
58
89
|
* @param {PackageURL} root - add main/root component for sbom
|
|
90
|
+
* @param {string|Array} [licenses] - optional license(s) for the root component
|
|
59
91
|
* @return {CycloneDxSbom} the CycloneDxSbom Sbom Object
|
|
60
92
|
*/
|
|
61
|
-
addRoot(root) {
|
|
93
|
+
addRoot(root, licenses) {
|
|
62
94
|
this.rootComponent =
|
|
63
|
-
getComponent(root, "application");
|
|
95
|
+
getComponent(root, "application", undefined, licenses);
|
|
64
96
|
this.components.push(this.rootComponent);
|
|
65
97
|
return this;
|
|
66
98
|
}
|
|
@@ -74,16 +106,24 @@ export default class CycloneDxSbom {
|
|
|
74
106
|
* Adds a dependency relationship between two components in the SBOM
|
|
75
107
|
* @param {PackageURL} sourceRef - The source component (parent)
|
|
76
108
|
* @param {PackageURL} targetRef - The target component (dependency)
|
|
109
|
+
* @param {string} [scope] - Scope of the dependency
|
|
110
|
+
* @param {Array<{alg: string, content: string}>} [targetHashes] - Optional hashes for the target component
|
|
77
111
|
* @return {CycloneDxSbom} The updated SBOM
|
|
78
112
|
*/
|
|
79
|
-
addDependency(sourceRef, targetRef, scope) {
|
|
113
|
+
addDependency(sourceRef, targetRef, scope, targetHashes) {
|
|
80
114
|
const sourcePurl = sourceRef.toString();
|
|
81
115
|
const targetPurl = targetRef.toString();
|
|
82
116
|
// Ensure both components exist in the components list
|
|
83
117
|
[sourceRef, targetRef].forEach((ref, index) => {
|
|
84
118
|
const purl = index === 0 ? sourcePurl : targetPurl;
|
|
85
|
-
|
|
86
|
-
|
|
119
|
+
const existingIndex = this.getComponentIndex(purl);
|
|
120
|
+
if (existingIndex < 0) {
|
|
121
|
+
const hashes = index === 1 ? targetHashes : undefined;
|
|
122
|
+
this.components.push(getComponent(ref, "library", scope, undefined, hashes));
|
|
123
|
+
}
|
|
124
|
+
else if (index === 1 && targetHashes && targetHashes.length > 0 && !this.components[existingIndex].hashes) {
|
|
125
|
+
// Update hashes if the component was first seen without them (e.g. as a source)
|
|
126
|
+
this.components[existingIndex].hashes = targetHashes;
|
|
87
127
|
}
|
|
88
128
|
});
|
|
89
129
|
// Ensure source dependency exists
|
|
@@ -102,22 +142,54 @@ export default class CycloneDxSbom {
|
|
|
102
142
|
}
|
|
103
143
|
return this;
|
|
104
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Attach hashes to already-added components by matching their PURL. This is a
|
|
147
|
+
* post-processing step so ecosystem-specific hash sources (e.g. Maven reading
|
|
148
|
+
* the local .m2 cache) can enrich the SBOM without threading their concern
|
|
149
|
+
* through the shared dependency-tree parser. Components without a matching
|
|
150
|
+
* entry, or whose hashes are already set, are left untouched.
|
|
151
|
+
* @param {Map<string, Array<{alg: string, content: string}>>} hashMap - PURL→hashes map
|
|
152
|
+
* @return {CycloneDxSbom} the updated SBOM
|
|
153
|
+
*/
|
|
154
|
+
attachHashes(hashMap) {
|
|
155
|
+
if (!hashMap || hashMap.size === 0) {
|
|
156
|
+
return this;
|
|
157
|
+
}
|
|
158
|
+
for (const component of this.components) {
|
|
159
|
+
if (component.hashes) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const hashes = hashMap.get(component.purl);
|
|
163
|
+
if (hashes && hashes.length > 0) {
|
|
164
|
+
component.hashes = hashes;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return this;
|
|
168
|
+
}
|
|
105
169
|
/** @param {{}} opts - various options, settings and configuration of application.
|
|
106
170
|
* @return String CycloneDx Sbom json object in a string format
|
|
107
171
|
*/
|
|
108
172
|
getAsJsonString(opts) {
|
|
109
173
|
let manifestType = opts["manifest-type"];
|
|
110
174
|
this.setSourceManifest(opts["source-manifest"]);
|
|
175
|
+
const rootPurl = this.rootComponent?.purl;
|
|
111
176
|
this.sbomObject = {
|
|
112
177
|
"bomFormat": "CycloneDX",
|
|
113
|
-
"specVersion": "1.
|
|
178
|
+
"specVersion": "1.6",
|
|
114
179
|
"version": 1,
|
|
115
180
|
"metadata": {
|
|
116
181
|
"timestamp": new Date(),
|
|
117
182
|
"component": this.rootComponent,
|
|
183
|
+
"tools": {
|
|
184
|
+
"components": [{
|
|
185
|
+
"type": "application",
|
|
186
|
+
"name": "trustify-da-javascript-client",
|
|
187
|
+
"version": PACKAGE_VERSION
|
|
188
|
+
}]
|
|
189
|
+
},
|
|
118
190
|
"properties": new Array()
|
|
119
191
|
},
|
|
120
|
-
"components": this.components,
|
|
192
|
+
"components": this.components.filter(c => c.purl !== rootPurl),
|
|
121
193
|
"dependencies": this.dependencies
|
|
122
194
|
};
|
|
123
195
|
if (this.rootComponent === undefined) {
|
|
@@ -229,6 +301,20 @@ export default class CycloneDxSbom {
|
|
|
229
301
|
return false;
|
|
230
302
|
}
|
|
231
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Checks if any entry in the dependsOn list of sourceRef starts with the given purl prefix.
|
|
306
|
+
* @param {PackageURL} sourceRef - The source component
|
|
307
|
+
* @param {string} purlPrefix - The purl prefix to match (e.g. "pkg:npm/minimist@")
|
|
308
|
+
* @return {boolean}
|
|
309
|
+
*/
|
|
310
|
+
checkDependsOnByPurlPrefix(sourceRef, purlPrefix) {
|
|
311
|
+
const sourcePurl = sourceRef.toString();
|
|
312
|
+
const depIndex = this.getDependencyIndex(sourcePurl);
|
|
313
|
+
if (depIndex < 0) {
|
|
314
|
+
return false;
|
|
315
|
+
}
|
|
316
|
+
return this.dependencies[depIndex].dependsOn.some(dep => dep.startsWith(purlPrefix));
|
|
317
|
+
}
|
|
232
318
|
/** Removes the root component from the sbom
|
|
233
319
|
*/
|
|
234
320
|
removeRootComponent() {
|