@trustify-da/trustify-da-javascript-client 0.3.0-ea.e12bc82 → 0.3.0-ea.ff266a3

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 (43) hide show
  1. package/README.md +13 -1
  2. package/dist/package.json +12 -8
  3. package/dist/src/analysis.d.ts +5 -5
  4. package/dist/src/analysis.js +21 -76
  5. package/dist/src/cli.js +72 -6
  6. package/dist/src/cyclone_dx_sbom.d.ts +3 -2
  7. package/dist/src/cyclone_dx_sbom.js +16 -4
  8. package/dist/src/index.d.ts +65 -11
  9. package/dist/src/index.js +5 -3
  10. package/dist/src/license/compatibility.d.ts +18 -0
  11. package/dist/src/license/compatibility.js +45 -0
  12. package/dist/src/license/index.d.ts +28 -0
  13. package/dist/src/license/index.js +100 -0
  14. package/dist/src/license/licenses_api.d.ts +34 -0
  15. package/dist/src/license/licenses_api.js +91 -0
  16. package/dist/src/license/project_license.d.ts +25 -0
  17. package/dist/src/license/project_license.js +139 -0
  18. package/dist/src/oci_image/images.d.ts +4 -5
  19. package/dist/src/oci_image/utils.d.ts +4 -4
  20. package/dist/src/provider.d.ts +12 -3
  21. package/dist/src/provider.js +16 -1
  22. package/dist/src/providers/base_java.d.ts +3 -5
  23. package/dist/src/providers/base_javascript.d.ts +10 -4
  24. package/dist/src/providers/base_javascript.js +28 -4
  25. package/dist/src/providers/golang_gomodules.d.ts +11 -4
  26. package/dist/src/providers/golang_gomodules.js +12 -4
  27. package/dist/src/providers/java_gradle.d.ts +9 -3
  28. package/dist/src/providers/java_gradle.js +11 -2
  29. package/dist/src/providers/java_gradle_groovy.d.ts +1 -1
  30. package/dist/src/providers/java_gradle_kotlin.d.ts +1 -1
  31. package/dist/src/providers/java_maven.d.ts +12 -5
  32. package/dist/src/providers/java_maven.js +32 -5
  33. package/dist/src/providers/python_controller.d.ts +5 -2
  34. package/dist/src/providers/python_controller.js +56 -58
  35. package/dist/src/providers/python_pip.d.ts +11 -4
  36. package/dist/src/providers/python_pip.js +45 -53
  37. package/dist/src/providers/requirements_parser.d.ts +6 -0
  38. package/dist/src/providers/requirements_parser.js +23 -0
  39. package/dist/src/sbom.d.ts +3 -1
  40. package/dist/src/sbom.js +3 -2
  41. package/dist/src/tools.d.ts +22 -6
  42. package/dist/src/tools.js +56 -1
  43. package/package.json +13 -9
package/README.md CHANGED
@@ -83,12 +83,13 @@ Use as CLI Script
83
83
  ```shell
84
84
  $ npx @trustify-da/trustify-da-javascript-client help
85
85
 
86
- Usage: trustify-da-javascript-client {component|stack|image|validate-token}
86
+ Usage: trustify-da-javascript-client {component|stack|image|validate-token|license}
87
87
 
88
88
  Commands:
89
89
  trustify-da-javascript-client stack </path/to/manifest> [--html|--summary] produce stack report for manifest path
90
90
  trustify-da-javascript-client component <path/to/manifest> [--summary] produce component report for a manifest type and content
91
91
  trustify-da-javascript-client image <image-refs..> [--html|--summary] produce image analysis report for OCI image references
92
+ trustify-da-javascript-client license </path/to/manifest> display project license information from manifest and LICENSE file in JSON format
92
93
 
93
94
  Options:
94
95
  --help Show help [boolean]
@@ -123,6 +124,9 @@ $ npx @trustify-da/trustify-da-javascript-client image docker.io/library/node:18
123
124
 
124
125
  # specify architecture using ^^ notation (e.g., httpd:2.4.49^^amd64)
125
126
  $ npx @trustify-da/trustify-da-javascript-client image httpd:2.4.49^^amd64
127
+
128
+ # get project license information
129
+ $ npx @trustify-da/trustify-da-javascript-client license /path/to/package.json
126
130
  ```
127
131
  </li>
128
132
 
@@ -161,6 +165,9 @@ $ trustify-da-javascript-client image docker.io/library/node:18 docker.io/librar
161
165
 
162
166
  # specify architecture using ^^ notation (e.g., httpd:2.4.49^^amd64)
163
167
  $ trustify-da-javascript-client image httpd:2.4.49^^amd64
168
+
169
+ # get project license information
170
+ $ trustify-da-javascript-client license /path/to/package.json
164
171
  ```
165
172
  </li>
166
173
  </ul>
@@ -372,6 +379,11 @@ const options = {
372
379
  The proxy URL should be in the format: `http://host:port` or `https://host:port`. The API will automatically use the appropriate protocol (HTTP or HTTPS) based on the proxy URL provided.
373
380
  </p>
374
381
 
382
+ <h4>License resolution and dependency license compliance</h4>
383
+ <p>
384
+ The client can resolve the <strong>project license</strong> from the manifest (e.g. <code>package.json</code> <code>license</code>, <code>pom.xml</code> <code>&lt;licenses&gt;</code>) and from a <code>LICENSE</code> or <code>LICENSE.md</code> file in the project, and report when they differ. For <strong>component analysis</strong>, you can optionally run a license check: the client fetches dependency licenses from the backend (by purl) and reports dependencies whose licenses are incompatible with the project license. See <a href="docs/license-resolution-and-compliance.md">License resolution and compliance</a> for design and behavior. To disable the check on component analysis, set <code>TRUSTIFY_DA_LICENSE_CHECK=false</code> or pass <code>licenseCheck: false</code> in the options.
385
+ </p>
386
+
375
387
  <h4>Customizing Executables</h4>
376
388
  <p>
377
389
  This project uses each ecosystem's executable for creating dependency trees. These executables are expected to be
package/dist/package.json CHANGED
@@ -45,29 +45,33 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@babel/core": "^7.23.2",
48
- "@cyclonedx/cyclonedx-library": "~1.13.3",
48
+ "@cyclonedx/cyclonedx-library": "^6.13.0",
49
+ "eslint-import-resolver-typescript": "^4.4.4",
49
50
  "fast-toml": "^0.5.4",
50
- "fast-xml-parser": "^4.5.3",
51
+ "fast-xml-parser": "^5.3.4",
51
52
  "help": "^3.0.2",
52
53
  "https-proxy-agent": "^7.0.6",
53
- "node-fetch": "^2.7.0",
54
- "packageurl-js": "^1.0.2",
55
- "yargs": "^17.7.2"
54
+ "node-fetch": "^3.3.2",
55
+ "packageurl-js": "~1.0.2",
56
+ "tree-sitter-requirements": "github:Strum355/tree-sitter-requirements#d0261ee76b84253997fe70d7d397e78c006c3801",
57
+ "web-tree-sitter": "^0.26.6",
58
+ "yargs": "^18.0.0"
56
59
  },
57
60
  "devDependencies": {
58
61
  "@babel/core": "^7.23.2",
59
- "@trustify-da/trustify-da-api-model": "^2.0.1",
62
+ "@trustify-da/trustify-da-api-model": "^2.0.7",
60
63
  "@types/node": "^20.17.30",
61
64
  "@types/which": "^3.0.4",
62
65
  "babel-plugin-rewire": "^1.2.0",
63
- "c8": "^8.0.0",
66
+ "c8": "^11.0.0",
64
67
  "chai": "^4.3.7",
65
68
  "eslint": "^8.42.0",
69
+ "eslint-import-resolver-typescript": "^4.4.4",
66
70
  "eslint-plugin-editorconfig": "^4.0.3",
67
71
  "eslint-plugin-import": "^2.29.1",
68
72
  "esmock": "^2.6.2",
69
73
  "mocha": "^10.2.0",
70
- "msw": "^1.3.2",
74
+ "msw": "^2.12.7",
71
75
  "sinon": "^15.1.2",
72
76
  "sinon-chai": "^3.7.0",
73
77
  "typescript": "^5.1.3",
@@ -13,7 +13,7 @@ export default _default;
13
13
  * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
14
14
  * @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
15
15
  */
16
- declare function requestComponent(provider: import('./provider').Provider, manifest: string, url: string, opts?: import("index.js").Options | undefined): Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>;
16
+ declare function requestComponent(provider: import("./provider").Provider, manifest: string, url: string, opts?: import("index.js").Options): Promise<import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport>;
17
17
  /**
18
18
  * Send a stack analysis request and get the report as 'text/html' or 'application/json'.
19
19
  * @param {import('./provider').Provider} provider - the provided data for constructing the request
@@ -23,7 +23,7 @@ declare function requestComponent(provider: import('./provider').Provider, manif
23
23
  * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
24
24
  * @returns {Promise<string|import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
25
25
  */
26
- declare function requestStack(provider: import('./provider').Provider, manifest: string, url: string, html?: boolean | undefined, opts?: import("index.js").Options | undefined): Promise<string | import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>;
26
+ declare function requestStack(provider: import("./provider").Provider, manifest: string, url: string, html?: boolean, opts?: import("index.js").Options): Promise<string | import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport>;
27
27
  /**
28
28
  *
29
29
  * @param {Array<string>} imageRefs
@@ -31,8 +31,8 @@ declare function requestStack(provider: import('./provider').Provider, manifest:
31
31
  * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
32
32
  * @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
33
33
  */
34
- declare function requestImages(imageRefs: Array<string>, url: string, html?: boolean, opts?: import("index.js").Options | undefined): Promise<string | {
35
- [x: string]: import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport;
34
+ declare function requestImages(imageRefs: Array<string>, url: string, html?: boolean, opts?: import("index.js").Options): Promise<string | {
35
+ [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
36
36
  }>;
37
37
  /**
38
38
  *
@@ -40,4 +40,4 @@ declare function requestImages(imageRefs: Array<string>, url: string, html?: boo
40
40
  * @param {import("index.js").Options} [opts={}] - optional various options to pass headers for t he validateToken Request
41
41
  * @return {Promise<number>} return the HTTP status Code of the response from the validate token request.
42
42
  */
43
- declare function validateToken(url: any, opts?: import("index.js").Options | undefined): Promise<number>;
43
+ declare function validateToken(url: any, opts?: import("index.js").Options): Promise<number>;
@@ -1,28 +1,10 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { EOL } from "os";
4
- import { HttpsProxyAgent } from "https-proxy-agent";
4
+ import { runLicenseCheck } from "./license/index.js";
5
5
  import { generateImageSBOM, parseImageRef } from "./oci_image/utils.js";
6
- import { RegexNotToBeLogged, getCustom } from "./tools.js";
6
+ import { addProxyAgent, getCustom, getTokenHeaders, TRUSTIFY_DA_OPERATION_TYPE_HEADER, TRUSTIFY_DA_PACKAGE_MANAGER_HEADER } from "./tools.js";
7
7
  export default { requestComponent, requestStack, requestImages, validateToken };
8
- const rhdaTokenHeader = "rhda-token";
9
- const rhdaTelemetryId = "rhda-telemetry-id";
10
- const rhdaSourceHeader = "rhda-source";
11
- const rhdaOperationTypeHeader = "rhda-operation-type";
12
- const rhdaPackageManagerHeader = "rhda-pkg-manager";
13
- /**
14
- * Adds proxy agent configuration to fetch options if a proxy URL is specified
15
- * @param {RequestInit} options - The base fetch options
16
- * @param {import("index.js").Options} opts - The trustify DA options that may contain proxy configuration
17
- * @returns {RequestInit} The fetch options with proxy agent if applicable
18
- */
19
- function addProxyAgent(options, opts) {
20
- const proxyUrl = getCustom('TRUSTIFY_DA_PROXY_URL', null, opts);
21
- if (proxyUrl) {
22
- options.agent = new HttpsProxyAgent(proxyUrl);
23
- }
24
- return options;
25
- }
26
8
  /**
27
9
  * Send a stack analysis request and get the report as 'text/html' or 'application/json'.
28
10
  * @param {import('./provider').Provider} provider - the provided data for constructing the request
@@ -35,15 +17,15 @@ function addProxyAgent(options, opts) {
35
17
  async function requestStack(provider, manifest, url, html = false, opts = {}) {
36
18
  opts["source-manifest"] = Buffer.from(fs.readFileSync(manifest).toString()).toString('base64');
37
19
  opts["manifest-type"] = path.parse(manifest).base;
38
- let provided = provider.provideStack(manifest, opts); // throws error if content providing failed
20
+ let provided = await provider.provideStack(manifest, opts); // throws error if content providing failed
39
21
  opts["source-manifest"] = "";
40
- opts[rhdaOperationTypeHeader.toUpperCase().replaceAll("-", "_")] = "stack-analysis";
22
+ opts[TRUSTIFY_DA_OPERATION_TYPE_HEADER.toUpperCase().replaceAll("-", "_")] = "stack-analysis";
41
23
  let startTime = new Date();
42
24
  let endTime;
43
25
  if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
44
26
  console.log("Starting time of sending stack analysis request to the dependency analytics server= " + startTime);
45
27
  }
46
- opts[rhdaPackageManagerHeader.toUpperCase().replaceAll("-", "_")] = provided.ecosystem;
28
+ opts[TRUSTIFY_DA_PACKAGE_MANAGER_HEADER.toUpperCase().replaceAll("-", "_")] = provided.ecosystem;
47
29
  const fetchOptions = addProxyAgent({
48
30
  method: 'POST',
49
31
  headers: {
@@ -53,7 +35,7 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) {
53
35
  },
54
36
  body: provided.content
55
37
  }, opts);
56
- const finalUrl = new URL(`${url}/api/v4/analysis`);
38
+ const finalUrl = new URL(`${url}/api/v5/analysis`);
57
39
  if (opts['TRUSTIFY_DA_RECOMMENDATIONS_ENABLED'] === 'false') {
58
40
  finalUrl.searchParams.append('recommend', 'false');
59
41
  }
@@ -94,13 +76,13 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) {
94
76
  */
95
77
  async function requestComponent(provider, manifest, url, opts = {}) {
96
78
  opts["source-manifest"] = Buffer.from(fs.readFileSync(manifest).toString()).toString('base64');
97
- let provided = provider.provideComponent(manifest, opts); // throws error if content providing failed
79
+ let provided = await provider.provideComponent(manifest, opts); // throws error if content providing failed
98
80
  opts["source-manifest"] = "";
99
- opts[rhdaOperationTypeHeader.toUpperCase().replaceAll("-", "_")] = "component-analysis";
81
+ opts[TRUSTIFY_DA_OPERATION_TYPE_HEADER.toUpperCase().replaceAll("-", "_")] = "component-analysis";
100
82
  if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
101
83
  console.log("Starting time of sending component analysis request to Trustify DA backend server= " + new Date());
102
84
  }
103
- opts[rhdaPackageManagerHeader.toUpperCase().replaceAll("-", "_")] = provided.ecosystem;
85
+ opts[TRUSTIFY_DA_PACKAGE_MANAGER_HEADER.toUpperCase().replaceAll("-", "_")] = provided.ecosystem;
104
86
  const fetchOptions = addProxyAgent({
105
87
  method: 'POST',
106
88
  headers: {
@@ -110,7 +92,7 @@ async function requestComponent(provider, manifest, url, opts = {}) {
110
92
  },
111
93
  body: provided.content
112
94
  }, opts);
113
- const finalUrl = new URL(`${url}/api/v4/analysis`);
95
+ const finalUrl = new URL(`${url}/api/v5/analysis`);
114
96
  if (opts['TRUSTIFY_DA_RECOMMENDATIONS_ENABLED'] === 'false') {
115
97
  finalUrl.searchParams.append('recommend', 'false');
116
98
  }
@@ -127,6 +109,15 @@ async function requestComponent(provider, manifest, url, opts = {}) {
127
109
  console.log(JSON.stringify(result, null, 4));
128
110
  console.log("Ending time of sending component analysis request to Trustify DA backend server= " + new Date());
129
111
  }
112
+ const licenseCheckEnabled = getCustom('TRUSTIFY_DA_LICENSE_CHECK', 'true', opts) !== 'false' && opts.licenseCheck !== false;
113
+ if (licenseCheckEnabled) {
114
+ try {
115
+ result.licenseSummary = await runLicenseCheck(provided.content, manifest, url, opts, result);
116
+ }
117
+ catch (licenseErr) {
118
+ result.licenseSummary = { error: licenseErr.message };
119
+ }
120
+ }
130
121
  }
131
122
  else {
132
123
  throw new Error(`Got error response from Trustify DA backend - http return code : ${resp.status}, ex-request-id: ${resp.headers.get("ex-request-id")} error message => ${await resp.text()}`);
@@ -146,7 +137,7 @@ async function requestImages(imageRefs, url, html = false, opts = {}) {
146
137
  const parsedImageRef = parseImageRef(image, opts);
147
138
  imageSboms[parsedImageRef.getPackageURL().toString()] = generateImageSBOM(parsedImageRef, opts);
148
139
  }
149
- const finalUrl = new URL(`${url}/api/v4/batch-analysis`);
140
+ const finalUrl = new URL(`${url}/api/v5/batch-analysis`);
150
141
  if (opts['TRUSTIFY_DA_RECOMMENDATIONS_ENABLED'] === 'false') {
151
142
  finalUrl.searchParams.append('recommend', 'false');
152
143
  }
@@ -195,7 +186,7 @@ async function validateToken(url, opts = {}) {
195
186
  ...getTokenHeaders(opts),
196
187
  }
197
188
  }, opts);
198
- let resp = await fetch(`${url}/api/v4/token`, fetchOptions);
189
+ let resp = await fetch(`${url}/api/v5/token`, fetchOptions);
199
190
  if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
200
191
  let exRequestId = resp.headers.get("ex-request-id");
201
192
  if (exRequestId) {
@@ -204,49 +195,3 @@ async function validateToken(url, opts = {}) {
204
195
  }
205
196
  return resp.status;
206
197
  }
207
- /**
208
- *
209
- * @param {string} headerName - the header name to populate in request
210
- * @param headers
211
- * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
212
- * @private
213
- */
214
- function setRhdaHeader(headerName, headers, opts) {
215
- let rhdaHeaderValue = getCustom(headerName.toUpperCase().replaceAll("-", "_"), null, opts);
216
- if (rhdaHeaderValue) {
217
- headers[headerName] = rhdaHeaderValue;
218
- }
219
- }
220
- /**
221
- * Utility function for fetching vendor tokens
222
- * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
223
- * @returns {{}}
224
- */
225
- function getTokenHeaders(opts = {}) {
226
- let supportedTokens = ['snyk', 'oss-index'];
227
- let headers = {};
228
- supportedTokens.forEach(vendor => {
229
- let token = getCustom(`TRUSTIFY_DA_${vendor.replace("-", "_").toUpperCase()}_TOKEN`, null, opts);
230
- if (token) {
231
- headers[`ex-${vendor}-token`] = token;
232
- }
233
- let user = getCustom(`TRUSTIFY_DA_${vendor.replace("-", "_").toUpperCase()}_USER`, null, opts);
234
- if (user) {
235
- headers[`ex-${vendor}-user`] = user;
236
- }
237
- });
238
- setRhdaHeader(rhdaTokenHeader, headers, opts);
239
- setRhdaHeader(rhdaSourceHeader, headers, opts);
240
- setRhdaHeader(rhdaOperationTypeHeader, headers, opts);
241
- setRhdaHeader(rhdaPackageManagerHeader, headers, opts);
242
- setRhdaHeader(rhdaTelemetryId, headers, opts);
243
- if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
244
- console.log("Headers Values to be sent to Trustify DA backend:" + EOL);
245
- for (const headerKey in headers) {
246
- if (!headerKey.match(RegexNotToBeLogged)) {
247
- console.log(`${headerKey}: ${headers[headerKey]}`);
248
- }
249
- }
250
- }
251
- return headers;
252
- }
package/dist/src/cli.js CHANGED
@@ -2,7 +2,8 @@
2
2
  import * as path from "path";
3
3
  import yargs from 'yargs';
4
4
  import { hideBin } from 'yargs/helpers';
5
- import client from './index.js';
5
+ import { getProjectLicense, getLicenseDetails } from './license/index.js';
6
+ import client, { selectTrustifyDABackend } from './index.js';
6
7
  // command for component analysis take manifest type and content
7
8
  const component = {
8
9
  command: 'component </path/to/manifest>',
@@ -22,9 +23,8 @@ const validateToken = {
22
23
  command: 'validate-token <token-provider> [--token-value thevalue]',
23
24
  desc: 'Validates input token if authentic and authorized',
24
25
  builder: yargs => yargs.positional('token-provider', {
25
- desc: 'the token provider',
26
- type: 'string',
27
- choices: ['snyk', 'oss-index'],
26
+ desc: 'the token provider name',
27
+ type: 'string'
28
28
  }).options({
29
29
  tokenValue: {
30
30
  alias: 'value',
@@ -37,7 +37,7 @@ const validateToken = {
37
37
  let opts = {};
38
38
  if (args['tokenValue'] !== undefined && args['tokenValue'].trim() !== "") {
39
39
  let tokenValue = args['tokenValue'].trim();
40
- opts[`TRUSTIFY_DA_${tokenProvider}_TOKEN`] = tokenValue;
40
+ opts[`TRUSTIFY_DA_PROVIDER_${tokenProvider}_TOKEN`] = tokenValue;
41
41
  }
42
42
  let res = await client.validateToken(opts);
43
43
  console.log(res);
@@ -143,13 +143,79 @@ const stack = {
143
143
  console.log(html ? res : JSON.stringify(!html && summary ? theProvidersObject : res, null, 2));
144
144
  }
145
145
  };
146
+ // command for license checking
147
+ const license = {
148
+ command: 'license </path/to/manifest>',
149
+ desc: 'Display project license information from manifest and LICENSE file in JSON format',
150
+ builder: yargs => yargs.positional('/path/to/manifest', {
151
+ desc: 'manifest path for license analysis',
152
+ type: 'string',
153
+ normalize: true,
154
+ }),
155
+ handler: async (args) => {
156
+ let manifestPath = args['/path/to/manifest'];
157
+ const opts = {}; // CLI options can be extended in the future
158
+ try {
159
+ selectTrustifyDABackend(opts);
160
+ }
161
+ catch (err) {
162
+ console.error(JSON.stringify({ error: err.message }, null, 2));
163
+ process.exit(1);
164
+ }
165
+ let localResult;
166
+ try {
167
+ localResult = getProjectLicense(manifestPath);
168
+ }
169
+ catch (err) {
170
+ console.error(JSON.stringify({ error: `Failed to read manifest: ${err.message}` }, null, 2));
171
+ process.exit(1);
172
+ }
173
+ const errors = [];
174
+ // Build LicenseInfo objects
175
+ const buildLicenseInfo = async (spdxId) => {
176
+ if (!spdxId) {
177
+ return null;
178
+ }
179
+ const licenseInfo = { spdxId };
180
+ try {
181
+ const details = await getLicenseDetails(spdxId, opts);
182
+ if (details) {
183
+ // Check if backend recognized the license as valid
184
+ if (details.category === 'UNKNOWN') {
185
+ errors.push(`"${spdxId}" is not a valid SPDX license identifier. Please use a valid SPDX expression (e.g., "Apache-2.0", "MIT"). See https://spdx.org/licenses/`);
186
+ }
187
+ else {
188
+ Object.assign(licenseInfo, details);
189
+ }
190
+ }
191
+ else {
192
+ errors.push(`No license details found for ${spdxId}`);
193
+ }
194
+ }
195
+ catch (err) {
196
+ errors.push(`Failed to fetch details for ${spdxId}: ${err.message}`);
197
+ }
198
+ return licenseInfo;
199
+ };
200
+ const output = {
201
+ manifestLicense: await buildLicenseInfo(localResult.fromManifest),
202
+ fileLicense: await buildLicenseInfo(localResult.fromFile),
203
+ mismatch: localResult.mismatch
204
+ };
205
+ if (errors.length > 0) {
206
+ output.errors = errors;
207
+ }
208
+ console.log(JSON.stringify(output, null, 2));
209
+ }
210
+ };
146
211
  // parse and invoke the command
147
212
  yargs(hideBin(process.argv))
148
- .usage(`Usage: ${process.argv[0].includes("node") ? path.parse(process.argv[1]).base : path.parse(process.argv[0]).base} {component|stack|image|validate-token}`)
213
+ .usage(`Usage: ${process.argv[0].includes("node") ? path.parse(process.argv[1]).base : path.parse(process.argv[0]).base} {component|stack|image|validate-token|license}`)
149
214
  .command(stack)
150
215
  .command(component)
151
216
  .command(image)
152
217
  .command(validateToken)
218
+ .command(license)
153
219
  .scriptName('')
154
220
  .version(false)
155
221
  .demandCommand(1)
@@ -1,4 +1,3 @@
1
- /// <reference types="packageurl-js/src/package-url" />
2
1
  export default class CycloneDxSbom {
3
2
  sbomObject: any;
4
3
  rootComponent: any;
@@ -7,9 +6,10 @@ export default class CycloneDxSbom {
7
6
  sourceManifestForAuditTrail: any;
8
7
  /**
9
8
  * @param {PackageURL} root - add main/root component for sbom
9
+ * @param {string|Array} [licenses] - optional license(s) for the root component
10
10
  * @return {CycloneDxSbom} the CycloneDxSbom Sbom Object
11
11
  */
12
- addRoot(root: PackageURL): CycloneDxSbom;
12
+ addRoot(root: PackageURL, licenses?: string | any[]): CycloneDxSbom;
13
13
  /**
14
14
  * @return {{{"bom-ref": string, name, purl: string, type, version}}} root component of sbom.
15
15
  */
@@ -49,6 +49,7 @@ export default class CycloneDxSbom {
49
49
  type: any;
50
50
  version: any;
51
51
  scope: any;
52
+ licenses?: any;
52
53
  };
53
54
  /**
54
55
  * This method gets an array of dependencies to be ignored, and remove all of them from CycloneDx Sbom
@@ -5,10 +5,11 @@ import { PackageURL } from "packageurl-js";
5
5
  * @param component {PackageURL}
6
6
  * @param type type of package - application or library
7
7
  * @param scope scope of the component - runtime or compile
8
- * @return {{"bom-ref": string, name, purl: string, type, version, scope}}
8
+ * @param licenses optional license string or array of licenses for the component
9
+ * @return {{"bom-ref": string, name, purl: string, type, version, scope, licenses?}}
9
10
  * @private
10
11
  */
11
- function getComponent(component, type, scope) {
12
+ function getComponent(component, type, scope, licenses) {
12
13
  let componentObject;
13
14
  if (component instanceof PackageURL) {
14
15
  if (component.namespace) {
@@ -36,6 +37,16 @@ function getComponent(component, type, scope) {
36
37
  else {
37
38
  componentObject = component;
38
39
  }
40
+ // Add licenses if provided (CycloneDX format). Callers must provide valid SPDX identifiers.
41
+ if (licenses) {
42
+ const licenseArray = Array.isArray(licenses) ? licenses : [licenses];
43
+ componentObject.licenses = licenseArray.map(lic => {
44
+ if (typeof lic === 'string') {
45
+ return { license: { id: lic } };
46
+ }
47
+ return lic;
48
+ });
49
+ }
39
50
  return componentObject;
40
51
  }
41
52
  function createDependency(dependency) {
@@ -56,11 +67,12 @@ export default class CycloneDxSbom {
56
67
  }
57
68
  /**
58
69
  * @param {PackageURL} root - add main/root component for sbom
70
+ * @param {string|Array} [licenses] - optional license(s) for the root component
59
71
  * @return {CycloneDxSbom} the CycloneDxSbom Sbom Object
60
72
  */
61
- addRoot(root) {
73
+ addRoot(root, licenses) {
62
74
  this.rootComponent =
63
- getComponent(root, "application");
75
+ getComponent(root, "application", undefined, licenses);
64
76
  this.components.push(this.rootComponent);
65
77
  return this;
66
78
  }
@@ -12,7 +12,7 @@
12
12
  export function selectTrustifyDABackend(opts?: {
13
13
  TRUSTIFY_DA_DEBUG?: string | undefined;
14
14
  TRUSTIFY_DA_BACKEND_URL?: string | undefined;
15
- } | undefined): string;
15
+ }): string;
16
16
  export { parseImageRef } from "./oci_image/utils.js";
17
17
  export { ImageRef } from "./oci_image/images.js";
18
18
  declare namespace _default {
@@ -23,7 +23,6 @@ declare namespace _default {
23
23
  }
24
24
  export default _default;
25
25
  export type Options = {
26
- [key: string]: string | undefined;
27
26
  TRUSTIFY_DA_DOCKER_PATH?: string | undefined;
28
27
  TRUSTIFY_DA_GO_MVS_LOGIC_ENABLED?: string | undefined;
29
28
  TRUSTIFY_DA_GO_PATH?: string | undefined;
@@ -48,10 +47,12 @@ export type Options = {
48
47
  TRUSTIFY_DA_SYFT_CONFIG_PATH?: string | undefined;
49
48
  TRUSTIFY_DA_SYFT_PATH?: string | undefined;
50
49
  TRUSTIFY_DA_YARN_PATH?: string | undefined;
50
+ TRUSTIFY_DA_LICENSE_CHECK?: string | undefined;
51
51
  MATCH_MANIFEST_VERSIONS?: string | undefined;
52
- RHDA_SOURCE?: string | undefined;
53
- RHDA_TOKEN?: string | undefined;
54
- RHDA_TELEMETRY_ID?: string | undefined;
52
+ TRUSTIFY_DA_SOURCE?: string | undefined;
53
+ TRUSTIFY_DA_TOKEN?: string | undefined;
54
+ TRUSTIFY_DA_TELEMETRY_ID?: string | undefined;
55
+ [key: string]: string | undefined;
55
56
  };
56
57
  /**
57
58
  * Get component analysis report for a manifest content.
@@ -60,16 +61,68 @@ export type Options = {
60
61
  * @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
61
62
  * @throws {Error} if no matching provider, failed to get create content, or backend request failed
62
63
  */
63
- declare function componentAnalysis(manifest: string, opts?: Options | undefined): Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>;
64
+ declare function componentAnalysis(manifest: string, opts?: Options): Promise<import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport>;
65
+ /**
66
+ * @overload
67
+ * @param {string} manifest
68
+ * @param {true} html
69
+ * @param {Options} [opts={}]
70
+ * @returns {Promise<string>}
71
+ * @throws {Error}
72
+ */
64
73
  declare function stackAnalysis(manifest: string, html: true, opts?: Options | undefined): Promise<string>;
65
- declare function stackAnalysis(manifest: string, html: false, opts?: Options | undefined): Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>;
66
- declare function stackAnalysis(manifest: string, html?: boolean | undefined, opts?: Options | undefined): Promise<string | import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>;
74
+ /**
75
+ * @overload
76
+ * @param {string} manifest
77
+ * @param {false} html
78
+ * @param {Options} [opts={}]
79
+ * @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
80
+ * @throws {Error}
81
+ */
82
+ declare function stackAnalysis(manifest: string, html: false, opts?: Options | undefined): Promise<import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport>;
83
+ /**
84
+ * Get stack analysis report for a manifest file.
85
+ * @overload
86
+ * @param {string} manifest - path for the manifest
87
+ * @param {boolean} [html=false] - true will return a html string, false will return AnalysisReport object.
88
+ * @param {Options} [opts={}] - optional various options to pass along the application
89
+ * @returns {Promise<string|import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
90
+ * @throws {Error} if manifest inaccessible, no matching provider, failed to get create content,
91
+ * or backend request failed
92
+ */
93
+ declare function stackAnalysis(manifest: string, html?: boolean | undefined, opts?: Options | undefined): Promise<string | import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport>;
94
+ /**
95
+ * @overload
96
+ * @param {Array<string>} imageRefs
97
+ * @param {true} html
98
+ * @param {Options} [opts={}]
99
+ * @returns {Promise<string>}
100
+ * @throws {Error}
101
+ */
67
102
  declare function imageAnalysis(imageRefs: Array<string>, html: true, opts?: Options | undefined): Promise<string>;
103
+ /**
104
+ * @overload
105
+ * @param {Array<string>} imageRefs
106
+ * @param {false} html
107
+ * @param {Options} [opts={}]
108
+ * @returns {Promise<Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
109
+ * @throws {Error}
110
+ */
68
111
  declare function imageAnalysis(imageRefs: Array<string>, html: false, opts?: Options | undefined): Promise<{
69
- [x: string]: import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport;
112
+ [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
70
113
  }>;
114
+ /**
115
+ * Get image analysis report for a set of OCI image references.
116
+ * @overload
117
+ * @param {Array<string>} imageRefs - OCI image references
118
+ * @param {boolean} [html=false] - true will return a html string, false will return AnalysisReport
119
+ * @param {Options} [opts={}] - optional various options to pass along the application
120
+ * @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
121
+ * @throws {Error} if manifest inaccessible, no matching provider, failed to get create content,
122
+ * or backend request failed
123
+ */
71
124
  declare function imageAnalysis(imageRefs: Array<string>, html?: boolean | undefined, opts?: Options | undefined): Promise<string | {
72
- [x: string]: import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport;
125
+ [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
73
126
  }>;
74
127
  /**
75
128
  * Validates the Exhort token.
@@ -77,4 +130,5 @@ declare function imageAnalysis(imageRefs: Array<string>, html?: boolean | undefi
77
130
  * @returns {Promise<object>} A promise that resolves with the validation result from the backend.
78
131
  * @throws {Error} if the backend request failed.
79
132
  */
80
- declare function validateToken(opts?: Options | undefined): Promise<object>;
133
+ declare function validateToken(opts?: Options): Promise<object>;
134
+ export { getProjectLicense, findLicenseFilePath, identifyLicenseViaBackend, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
package/dist/src/index.js CHANGED
@@ -8,6 +8,7 @@ import.meta.dirname;
8
8
  import * as url from 'url';
9
9
  export { parseImageRef } from "./oci_image/utils.js";
10
10
  export { ImageRef } from "./oci_image/images.js";
11
+ export { getProjectLicense, findLicenseFilePath, identifyLicenseViaBackend, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
11
12
  export default { componentAnalysis, stackAnalysis, imageAnalysis, validateToken };
12
13
  /**
13
14
  * @typedef {{
@@ -35,10 +36,11 @@ export default { componentAnalysis, stackAnalysis, imageAnalysis, validateToken
35
36
  * TRUSTIFY_DA_SYFT_CONFIG_PATH?: string | undefined,
36
37
  * TRUSTIFY_DA_SYFT_PATH?: string | undefined,
37
38
  * TRUSTIFY_DA_YARN_PATH?: string | undefined,
39
+ * TRUSTIFY_DA_LICENSE_CHECK?: string | undefined,
38
40
  * MATCH_MANIFEST_VERSIONS?: string | undefined,
39
- * RHDA_SOURCE?: string | undefined,
40
- * RHDA_TOKEN?: string | undefined,
41
- * RHDA_TELEMETRY_ID?: string | undefined,
41
+ * TRUSTIFY_DA_SOURCE?: string | undefined,
42
+ * TRUSTIFY_DA_TOKEN?: string | undefined,
43
+ * TRUSTIFY_DA_TELEMETRY_ID?: string | undefined,
42
44
  * [key: string]: string | undefined,
43
45
  * }} Options
44
46
  */
@@ -0,0 +1,18 @@
1
+ /**
2
+ * License compatibility: whether a dependency license is compatible with the project license.
3
+ * Relies on backend-provided license categories.
4
+ *
5
+ * Compatibility is based on restrictiveness hierarchy:
6
+ * PERMISSIVE < WEAK_COPYLEFT < STRONG_COPYLEFT
7
+ *
8
+ * A dependency is compatible if it's equal or less restrictive than the project license.
9
+ * A dependency is incompatible if it's more restrictive than the project license.
10
+ */
11
+ /**
12
+ * Check if a dependency's license is compatible with the project license based on backend categories.
13
+ *
14
+ * @param {string} [projectCategory] - backend category for project license: PERMISSIVE | WEAK_COPYLEFT | STRONG_COPYLEFT | UNKNOWN
15
+ * @param {string} [dependencyCategory] - backend category for dependency license: PERMISSIVE | WEAK_COPYLEFT | STRONG_COPYLEFT | UNKNOWN
16
+ * @returns {'compatible'|'incompatible'|'unknown'}
17
+ */
18
+ export function getCompatibility(projectCategory?: string, dependencyCategory?: string): "compatible" | "incompatible" | "unknown";