@trustify-da/trustify-da-javascript-client 0.3.0-ea.61444d4 → 0.3.0-ea.62b88e5

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 (59) hide show
  1. package/README.md +22 -8
  2. package/dist/package.json +6 -5
  3. package/dist/src/analysis.d.ts +40 -19
  4. package/dist/src/analysis.js +41 -5
  5. package/dist/src/cli.js +95 -17
  6. package/dist/src/config.d.ts +120 -0
  7. package/dist/src/config.js +260 -0
  8. package/dist/src/cyclone_dx_sbom.d.ts +13 -0
  9. package/dist/src/cyclone_dx_sbom.js +39 -1
  10. package/dist/src/index.d.ts +23 -23
  11. package/dist/src/index.js +23 -39
  12. package/dist/src/license/index.d.ts +2 -2
  13. package/dist/src/license/index.js +9 -6
  14. package/dist/src/license/licenses_api.d.ts +2 -2
  15. package/dist/src/license/licenses_api.js +1 -1
  16. package/dist/src/package_version.d.ts +8 -0
  17. package/dist/src/package_version.js +31 -0
  18. package/dist/src/providers/base_java.d.ts +53 -0
  19. package/dist/src/providers/base_java.js +64 -17
  20. package/dist/src/providers/base_javascript.d.ts +54 -0
  21. package/dist/src/providers/base_javascript.js +106 -4
  22. package/dist/src/providers/golang_gomodules.js +6 -5
  23. package/dist/src/providers/java_gradle.d.ts +48 -0
  24. package/dist/src/providers/java_gradle.js +204 -20
  25. package/dist/src/providers/java_maven.d.ts +28 -8
  26. package/dist/src/providers/java_maven.js +104 -10
  27. package/dist/src/providers/javascript_bun.d.ts +12 -0
  28. package/dist/src/providers/javascript_bun.js +42 -1
  29. package/dist/src/providers/javascript_npm.d.ts +13 -0
  30. package/dist/src/providers/javascript_npm.js +40 -1
  31. package/dist/src/providers/javascript_pnpm.d.ts +13 -0
  32. package/dist/src/providers/javascript_pnpm.js +47 -1
  33. package/dist/src/providers/javascript_yarn.d.ts +12 -0
  34. package/dist/src/providers/javascript_yarn.js +75 -2
  35. package/dist/src/providers/manifest.js +6 -3
  36. package/dist/src/providers/processors/yarn_berry_processor.d.ts +5 -1
  37. package/dist/src/providers/processors/yarn_berry_processor.js +3 -2
  38. package/dist/src/providers/processors/yarn_classic_processor.d.ts +5 -1
  39. package/dist/src/providers/processors/yarn_classic_processor.js +8 -6
  40. package/dist/src/providers/python_uv.js +6 -0
  41. package/dist/src/providers/requirements_parser.js +1 -1
  42. package/dist/src/providers/rust_cargo.js +34 -52
  43. package/dist/src/remediate.d.ts +90 -4
  44. package/dist/src/remediate.js +153 -58
  45. package/dist/src/remediation.d.ts +54 -30
  46. package/dist/src/remediation.js +92 -53
  47. package/dist/src/remediation_report.d.ts +3 -20
  48. package/dist/src/remediation_report.js +37 -16
  49. package/dist/src/sbom.d.ts +11 -0
  50. package/dist/src/sbom.js +10 -0
  51. package/dist/src/tools.d.ts +9 -11
  52. package/dist/src/tools.js +34 -13
  53. package/dist/src/updaters/maven_updater.d.ts +40 -8
  54. package/dist/src/updaters/maven_updater.js +26 -3
  55. package/dist/src/updaters/toml_updater.d.ts +42 -13
  56. package/dist/src/updaters/toml_updater.js +36 -4
  57. package/dist/src/workspace.d.ts +2 -1
  58. package/dist/src/workspace.js +2 -1
  59. package/package.json +7 -6
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
  <h3>Usage</h3>
8
8
  <p>
9
9
 
10
- <strong>Prerequisites:</strong> The <code>TRUSTIFY_DA_BACKEND_URL</code> environment variable must be set to the URL of the Trustify Dependency Analytics backend service. You can set it as an environment variable or pass it in the options object (see <a href="#customization">Customization</a> section).
10
+ <strong>Prerequisites:</strong> Supply the Trustify Dependency Analytics backend URL through <code>options.TRUSTIFY_DA_BACKEND_URL</code> or the <code>TRUSTIFY_DA_BACKEND_URL</code> environment variable (see <a href="#customization">Customization</a>). Programmatic options take precedence over environment variables.
11
11
 
12
12
  <ul>
13
13
  <li>
@@ -275,6 +275,18 @@ $ trustify-da-javascript-client remediate /path/to/pom.xml --dry-run --group-by
275
275
  <li><a href="https://www.rust-lang.org/">Rust</a> - <a href="https://doc.rust-lang.org/cargo/">Cargo</a></li>
276
276
  </ul>
277
277
 
278
+ <h3>Dockerfile Analysis Prerequisites</h3>
279
+ <p>
280
+ OCI image and <code>Dockerfile</code>/<code>Containerfile</code> analysis require the following external tools to be installed and available on your <code>PATH</code>:
281
+ </p>
282
+ <ul>
283
+ <li><a href="https://github.com/anchore/syft">Syft</a> — generates the image SBOM.</li>
284
+ <li><a href="https://github.com/containers/skopeo">skopeo</a> — resolves the image digest and manifest from the registry.</li>
285
+ </ul>
286
+ <p>
287
+ <strong>Dockerfile analysis is not supported on Windows</strong>, because <code>skopeo</code> provides no native Windows binary.
288
+ </p>
289
+
278
290
  <h3>License Detection</h3>
279
291
  <p>
280
292
  The client automatically detects your project's license with intelligent fallback:
@@ -466,13 +478,13 @@ All of the above examples are valid for marking a package to be ignored
466
478
 
467
479
  <h3>Customization</h3>
468
480
  <p>
469
- There are 2 approaches for customizing <em>Trustify Dependency Analytics JavaScript Client</em>. Whether you're using this API as a
481
+ There are two approaches for customizing <em>Trustify Dependency Analytics JavaScript Client</em>. Whether you're using this API as a
470
482
  <em>Global Module</em>, a <em>Remote Script</em>, or an <em>ESM Module</em>, you can use <em>Environment Variables</em>
471
- for various customization.
483
+ for various customization. <em>ESM Module</em> users can also pass programmatic options.
472
484
 
473
- <strong>Note:</strong> The <code>TRUSTIFY_DA_BACKEND_URL</code> environment variable is <strong>mandatory</strong> and must be set to the URL of the Trustify Dependency Analytics backend service. Without this variable, the API will throw an error.
485
+ <strong>Precedence:</strong> Programmatic options override environment variables, which override built-in defaults. A backend URL supplied through either <code>options.TRUSTIFY_DA_BACKEND_URL</code> or <code>TRUSTIFY_DA_BACKEND_URL</code> is mandatory.
474
486
 
475
- However, <em>ESM Module</em> users, can opt for customizing programmatically:
487
+ Example programmatic customization:
476
488
 
477
489
  ```javascript
478
490
  import client from '@trustify-da/trustify-da-javascript-client'
@@ -515,7 +527,7 @@ let imageAnalysisHtml = await client.imageAnalysis(['docker.io/library/node:18']
515
527
  // Specify architecture using ^^ notation (e.g., httpd:2.4.49^^amd64)
516
528
  let imageAnalysisWithArch = await client.imageAnalysis(['httpd:2.4.49^^amd64'], false, options)
517
529
  ```
518
- **_Environment variables takes precedence._**
530
+ **_Programmatic options take precedence over environment variables._**
519
531
  </p>
520
532
 
521
533
  <h4>Monorepo / Workspace Support</h4>
@@ -551,12 +563,14 @@ const options = {
551
563
  }
552
564
  ```
553
565
 
566
+ When both are supplied, <code>options.TRUSTIFY_DA_PROXY_URL</code> takes precedence over the environment variable.
567
+
554
568
  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.
555
569
  </p>
556
570
 
557
571
  <h4>License resolution and dependency license compliance</h4>
558
572
  <p>
559
- 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>, <code>Cargo.toml</code> <code>license</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.
573
+ 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>, <code>Cargo.toml</code> <code>license</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. An explicit <code>licenseCheck</code> option takes precedence over the environment variable.
560
574
  </p>
561
575
 
562
576
  <h4>Customizing Executables</h4>
@@ -664,7 +678,7 @@ can cause a confusion for the user in the client consuming the API and leads to
664
678
 
665
679
  ##### Usage
666
680
 
667
- To eliminate confusion and improve clarity as discussed above, the following setting was introduced - `MATCH_MANIFEST_VERSIONS`, in the form of environment variable/key in opts ( as usual , environment variable takes precedence )
681
+ To eliminate confusion and improve clarity as discussed above, the following setting was introduced - `MATCH_MANIFEST_VERSIONS`, in the form of an environment variable or key in opts. When both are supplied, the value in opts takes precedence.
668
682
  for two ecosystems:
669
683
  - Golang - Go Modules
670
684
  - Python - pip
package/dist/package.json CHANGED
@@ -25,7 +25,9 @@
25
25
  "provenance": true
26
26
  },
27
27
  "type": "module",
28
- "bin": "dist/src/cli.js",
28
+ "bin": {
29
+ "trustify-da": "dist/src/cli.js"
30
+ },
29
31
  "main": "dist/src/index.js",
30
32
  "module": "dist/src/index.js",
31
33
  "types": "dist/src/index.d.ts",
@@ -47,8 +49,7 @@
47
49
  "postcompile": "cp node_modules/tree-sitter-requirements/tree-sitter-requirements.wasm dist/src/providers/tree-sitter-requirements.wasm && cp node_modules/tree-sitter-gomod/tree-sitter-gomod.wasm dist/src/providers/tree-sitter-gomod.wasm && cp node_modules/tree-sitter-containerfile/tree-sitter-containerfile.wasm dist/src/providers/tree-sitter-containerfile.wasm"
48
50
  },
49
51
  "dependencies": {
50
- "@babel/core": "^7.23.2",
51
- "@cyclonedx/cyclonedx-library": "^10.0.0",
52
+ "@yarnpkg/parsers": "^3.1.0",
52
53
  "eslint-import-resolver-typescript": "^4.4.4",
53
54
  "fast-glob": "^3.3.3",
54
55
  "fast-xml-parser": "^5.3.4",
@@ -68,12 +69,12 @@
68
69
  "yargs": "^18.0.0"
69
70
  },
70
71
  "devDependencies": {
71
- "@babel/core": "^7.23.2",
72
+ "@cyclonedx/cyclonedx-library": "^10.2.0",
72
73
  "@eslint/js": "^10.0.0",
73
74
  "@trustify-da/trustify-da-api-model": "^2.0.12",
74
75
  "@types/node": "^25.9.1",
75
76
  "@types/which": "^3.0.4",
76
- "babel-plugin-rewire": "^1.2.0",
77
+ "ajv": "^8.20.0",
77
78
  "c8": "^11.0.0",
78
79
  "chai": "^6.2.2",
79
80
  "eslint": "^10.4.1",
@@ -15,41 +15,62 @@ export default _default;
15
15
  * @param {string} manifest - path for the manifest
16
16
  * @param {string} url - the backend url to send the request to
17
17
  * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
18
- * @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
18
+ * @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>}
19
19
  */
20
- declare function requestComponent(provider: any, manifest: string, url: string, opts?: any): Promise<any>;
20
+ declare function requestComponent(provider: any, manifest: string, url: string, opts?: any): Promise<import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts").AnalysisReport>;
21
21
  /**
22
- * Send a stack analysis request and get the report as 'text/html' or 'application/json'.
23
- * @param {import('./provider').Provider} provider - the provided data for constructing the request
24
- * @param {string} manifest - path for the manifest
25
- * @param {string} url - the backend url to send the request to
26
- * @param {boolean} [html=false] - true will return 'text/html', false will return 'application/json'
27
- * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
28
- * @returns {Promise<string|import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
22
+ * @overload
23
+ * @param {import('./provider').Provider} provider
24
+ * @param {string} manifest
25
+ * @param {string} url
26
+ * @param {true} [html]
27
+ * @param {import("index.js").Options} [opts={}]
28
+ * @returns {Promise<string>}
29
29
  */
30
- declare function requestStack(provider: any, manifest: string, url: string, html?: boolean, opts?: any): Promise<string | any>;
30
+ declare function requestStack(provider: any, manifest: string, url: string, html?: true | undefined, opts?: any): Promise<string>;
31
31
  /**
32
- * Send a batch stack analysis request for multiple manifests (SBOMs keyed by purl).
33
- * @param {Object.<string, object>} sbomByPurl - Map of root purl to CycloneDX SBOM object
34
- * @param {string} url - the backend url
35
- * @param {boolean} [html=false] - true returns HTML, false returns JSON
32
+ * @overload
33
+ * @param {import('./provider').Provider} provider
34
+ * @param {string} manifest
35
+ * @param {string} url
36
+ * @param {false} [html]
37
+ * @param {import("index.js").Options} [opts={}]
38
+ * @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
39
+ */
40
+ declare function requestStack(provider: any, manifest: string, url: string, html?: false | undefined, opts?: any): Promise<import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts").AnalysisReport>;
41
+ /**
42
+ * @overload
43
+ * @param {Object.<string, object>} sbomByPurl
44
+ * @param {string} url
45
+ * @param {true} [html=false]
46
+ * @param {import("index.js").Options} [opts={}]
47
+ * @returns {Promise<string>}
48
+ */
49
+ declare function requestStackBatch(sbomByPurl: {
50
+ [x: string]: object;
51
+ }, url: string, html?: true | undefined, opts?: any): Promise<string>;
52
+ /**
53
+ * @overload
54
+ * @param {Object.<string, object>} sbomByPurl
55
+ * @param {string} url
56
+ * @param {false} [html=false]
36
57
  * @param {import("index.js").Options} [opts={}]
37
- * @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
58
+ * @returns {Promise<Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
38
59
  */
39
60
  declare function requestStackBatch(sbomByPurl: {
40
61
  [x: string]: object;
41
- }, url: string, html?: boolean, opts?: any): Promise<string | {
42
- [x: string]: any;
62
+ }, url: string, html?: false | undefined, opts?: any): Promise<{
63
+ [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts").AnalysisReport;
43
64
  }>;
44
65
  /**
45
66
  *
46
67
  * @param {Array<string>} imageRefs
47
68
  * @param {string} url
48
69
  * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
49
- * @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
70
+ * @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
50
71
  */
51
72
  declare function requestImages(imageRefs: Array<string>, url: string, html?: boolean, opts?: any): Promise<string | {
52
- [x: string]: any;
73
+ [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts").AnalysisReport;
53
74
  }>;
54
75
  /**
55
76
  *
@@ -7,6 +7,24 @@ import { addProxyAgent, getCustom, getTokenHeaders, TRUSTIFY_DA_OPERATION_TYPE_H
7
7
  /** Media type for CycloneDX JSON batch payloads (batch-analysis API). */
8
8
  export const CYCLONEDX_JSON_MEDIA_TYPE = 'application/vnd.cyclonedx+json';
9
9
  export default { requestComponent, requestStack, requestStackBatch, requestImages, validateToken, appendAnalysisQueryParams };
10
+ /**
11
+ * @overload
12
+ * @param {import('./provider').Provider} provider
13
+ * @param {string} manifest
14
+ * @param {string} url
15
+ * @param {true} [html]
16
+ * @param {import("index.js").Options} [opts={}]
17
+ * @returns {Promise<string>}
18
+ */
19
+ /**
20
+ * @overload
21
+ * @param {import('./provider').Provider} provider
22
+ * @param {string} manifest
23
+ * @param {string} url
24
+ * @param {false} [html]
25
+ * @param {import("index.js").Options} [opts={}]
26
+ * @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
27
+ */
10
28
  /**
11
29
  * Send a stack analysis request and get the report as 'text/html' or 'application/json'.
12
30
  * @param {import('./provider').Provider} provider - the provided data for constructing the request
@@ -14,7 +32,7 @@ export default { requestComponent, requestStack, requestStackBatch, requestImage
14
32
  * @param {string} url - the backend url to send the request to
15
33
  * @param {boolean} [html=false] - true will return 'text/html', false will return 'application/json'
16
34
  * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
17
- * @returns {Promise<string|import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
35
+ * @returns {Promise<string|import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>}
18
36
  */
19
37
  async function requestStack(provider, manifest, url, html = false, opts = {}) {
20
38
  opts["source-manifest"] = Buffer.from(fs.readFileSync(manifest).toString()).toString('base64');
@@ -75,7 +93,7 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) {
75
93
  * @param {string} manifest - path for the manifest
76
94
  * @param {string} url - the backend url to send the request to
77
95
  * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
78
- * @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
96
+ * @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>}
79
97
  */
80
98
  async function requestComponent(provider, manifest, url, opts = {}) {
81
99
  opts["source-manifest"] = Buffer.from(fs.readFileSync(manifest).toString()).toString('base64');
@@ -113,7 +131,9 @@ async function requestComponent(provider, manifest, url, opts = {}) {
113
131
  console.log(JSON.stringify(result, null, 4));
114
132
  console.log("Ending time of sending component analysis request to Trustify DA backend server= " + new Date());
115
133
  }
116
- const licenseCheckEnabled = getCustom('TRUSTIFY_DA_LICENSE_CHECK', 'true', opts) !== 'false' && opts.licenseCheck !== false;
134
+ const licenseCheckEnabled = typeof opts.licenseCheck === 'boolean'
135
+ ? opts.licenseCheck
136
+ : getCustom('TRUSTIFY_DA_LICENSE_CHECK', 'true', opts) !== 'false';
117
137
  if (licenseCheckEnabled) {
118
138
  try {
119
139
  result.licenseSummary = await runLicenseCheck(provided.content, manifest, url, opts, result);
@@ -128,13 +148,29 @@ async function requestComponent(provider, manifest, url, opts = {}) {
128
148
  }
129
149
  return Promise.resolve(result);
130
150
  }
151
+ /**
152
+ * @overload
153
+ * @param {Object.<string, object>} sbomByPurl
154
+ * @param {string} url
155
+ * @param {true} [html=false]
156
+ * @param {import("index.js").Options} [opts={}]
157
+ * @returns {Promise<string>}
158
+ */
159
+ /**
160
+ * @overload
161
+ * @param {Object.<string, object>} sbomByPurl
162
+ * @param {string} url
163
+ * @param {false} [html=false]
164
+ * @param {import("index.js").Options} [opts={}]
165
+ * @returns {Promise<Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
166
+ */
131
167
  /**
132
168
  * Send a batch stack analysis request for multiple manifests (SBOMs keyed by purl).
133
169
  * @param {Object.<string, object>} sbomByPurl - Map of root purl to CycloneDX SBOM object
134
170
  * @param {string} url - the backend url
135
171
  * @param {boolean} [html=false] - true returns HTML, false returns JSON
136
172
  * @param {import("index.js").Options} [opts={}]
137
- * @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
173
+ * @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
138
174
  */
139
175
  async function requestStackBatch(sbomByPurl, url, html = false, opts = {}) {
140
176
  const finalUrl = new URL(`${url}/api/v5/batch-analysis`);
@@ -177,7 +213,7 @@ async function requestStackBatch(sbomByPurl, url, html = false, opts = {}) {
177
213
  * @param {Array<string>} imageRefs
178
214
  * @param {string} url
179
215
  * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
180
- * @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
216
+ * @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
181
217
  */
182
218
  async function requestImages(imageRefs, url, html = false, opts = {}) {
183
219
  const imageSboms = {};
package/dist/src/cli.js CHANGED
@@ -3,9 +3,45 @@ import fs from 'node:fs';
3
3
  import * as path from "path";
4
4
  import yargs from 'yargs';
5
5
  import { hideBin } from 'yargs/helpers';
6
+ import { resolveConfig } from './config.js';
6
7
  import { getProjectLicense, getLicenseDetails } from './license/index.js';
7
8
  import { runRemediation } from './remediate.js';
9
+ import { generateReport } from './remediation_report.js';
8
10
  import client, { selectTrustifyDABackend, generateSbom } from './index.js';
11
+ /**
12
+ * Builds a yargs middleware that loads `.trustify-da.yml` (discovered by walking
13
+ * up from the command's target path, or cwd when no path is available) and fills `--providers`, `--sources`, and
14
+ * `--group-by` with config file values when they were not supplied on the CLI or
15
+ * via environment variables. Precedence: CLI flag > env var > config file > default.
16
+ * @param {string} [pathKey] - positional argument holding the target path; defaults to the current working directory
17
+ * @param {{ groupBy?: boolean }} [options={}] - set `groupBy` when the command exposes `--group-by`
18
+ * @returns {(args: object) => object} yargs middleware
19
+ */
20
+ function configMiddleware(pathKey, options = {}) {
21
+ return args => {
22
+ const startPath = pathKey === undefined ? process.cwd() : args[pathKey];
23
+ const envVars = options.groupBy
24
+ ? process.env
25
+ : Object.fromEntries(Object.entries(process.env).filter(([key]) => key !== 'TRUSTIFY_DA_GROUP_BY'));
26
+ const merged = resolveConfig(startPath, { backendUrl: args.backendUrl, providers: args.providers, sources: args.sources, groupBy: args['group-by'] }, envVars);
27
+ if (merged.backendUrlSource === 'file' && process.env.TRUSTIFY_DA_TOKEN) {
28
+ throw new Error('Refusing to send TRUSTIFY_DA_TOKEN to a backend selected by project configuration. Set --backend-url or TRUSTIFY_DA_BACKEND_URL to explicitly trust it.');
29
+ }
30
+ if (args.providers !== undefined || merged.providers.length) {
31
+ args.providers = merged.providers.join(',');
32
+ }
33
+ if (args.sources !== undefined || merged.sources.length) {
34
+ args.sources = merged.sources.join(',');
35
+ }
36
+ if (merged.backendUrl != null) {
37
+ args.backendUrl = merged.backendUrl;
38
+ }
39
+ if (options.groupBy) {
40
+ args['group-by'] = merged.groupBy;
41
+ }
42
+ return args;
43
+ };
44
+ }
9
45
  // command for component analysis take manifest type and content
10
46
  const component = {
11
47
  command: 'component </path/to/manifest>',
@@ -21,6 +57,10 @@ const component = {
21
57
  type: 'string',
22
58
  normalize: true,
23
59
  },
60
+ backendUrl: {
61
+ desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
62
+ type: 'string',
63
+ },
24
64
  providers: {
25
65
  desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
26
66
  type: 'string',
@@ -29,14 +69,17 @@ const component = {
29
69
  desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
30
70
  type: 'string',
31
71
  }
32
- }),
72
+ }).middleware(configMiddleware('/path/to/manifest')),
33
73
  handler: async (args) => {
34
74
  let manifestName = args['/path/to/manifest'];
35
75
  const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
36
- if (args.providers) {
76
+ if (args.backendUrl !== undefined) {
77
+ opts.TRUSTIFY_DA_BACKEND_URL = args.backendUrl;
78
+ }
79
+ if (args.providers !== undefined) {
37
80
  opts.TRUSTIFY_DA_PROVIDERS = args.providers;
38
81
  }
39
- if (args.sources) {
82
+ if (args.sources !== undefined) {
40
83
  opts.TRUSTIFY_DA_SOURCES = args.sources;
41
84
  }
42
85
  let res = await client.componentAnalysis(manifestName, opts);
@@ -88,6 +131,10 @@ const image = {
88
131
  type: 'boolean',
89
132
  conflicts: 'html'
90
133
  },
134
+ backendUrl: {
135
+ desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
136
+ type: 'string',
137
+ },
91
138
  providers: {
92
139
  desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
93
140
  type: 'string',
@@ -96,7 +143,7 @@ const image = {
96
143
  desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
97
144
  type: 'string',
98
145
  }
99
- }),
146
+ }).middleware(configMiddleware()),
100
147
  handler: async (args) => {
101
148
  let imageRefs = args['image-refs'];
102
149
  if (!Array.isArray(imageRefs)) {
@@ -105,10 +152,13 @@ const image = {
105
152
  let html = args['html'];
106
153
  let summary = args['summary'];
107
154
  const opts = {};
108
- if (args.providers) {
155
+ if (args.backendUrl !== undefined) {
156
+ opts.TRUSTIFY_DA_BACKEND_URL = args.backendUrl;
157
+ }
158
+ if (args.providers !== undefined) {
109
159
  opts.TRUSTIFY_DA_PROVIDERS = args.providers;
110
160
  }
111
- if (args.sources) {
161
+ if (args.sources !== undefined) {
112
162
  opts.TRUSTIFY_DA_SOURCES = args.sources;
113
163
  }
114
164
  let res = await client.imageAnalysis(imageRefs, html, opts);
@@ -163,6 +213,10 @@ const stack = {
163
213
  type: 'string',
164
214
  normalize: true,
165
215
  },
216
+ backendUrl: {
217
+ desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
218
+ type: 'string',
219
+ },
166
220
  providers: {
167
221
  desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
168
222
  type: 'string',
@@ -171,16 +225,19 @@ const stack = {
171
225
  desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
172
226
  type: 'string',
173
227
  }
174
- }),
228
+ }).middleware(configMiddleware('/path/to/manifest')),
175
229
  handler: async (args) => {
176
230
  let manifest = args['/path/to/manifest'];
177
231
  let html = args['html'];
178
232
  let summary = args['summary'];
179
233
  const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
180
- if (args.providers) {
234
+ if (args.backendUrl !== undefined) {
235
+ opts.TRUSTIFY_DA_BACKEND_URL = args.backendUrl;
236
+ }
237
+ if (args.providers !== undefined) {
181
238
  opts.TRUSTIFY_DA_PROVIDERS = args.providers;
182
239
  }
183
- if (args.sources) {
240
+ if (args.sources !== undefined) {
184
241
  opts.TRUSTIFY_DA_SOURCES = args.sources;
185
242
  }
186
243
  let theProvidersSummary = new Map();
@@ -246,6 +303,10 @@ const stackBatch = {
246
303
  type: 'boolean',
247
304
  default: false,
248
305
  },
306
+ backendUrl: {
307
+ desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
308
+ type: 'string',
309
+ },
249
310
  providers: {
250
311
  desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
251
312
  type: 'string',
@@ -254,12 +315,15 @@ const stackBatch = {
254
315
  desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
255
316
  type: 'string',
256
317
  }
257
- }),
318
+ }).middleware(configMiddleware('/path/to/workspace-root')),
258
319
  handler: async (args) => {
259
320
  const workspaceRoot = args['/path/to/workspace-root'];
260
321
  const html = args['html'];
261
322
  const summary = args['summary'];
262
323
  const opts = {};
324
+ if (args.backendUrl !== undefined) {
325
+ opts.TRUSTIFY_DA_BACKEND_URL = args.backendUrl;
326
+ }
263
327
  if (args.concurrency != null) {
264
328
  opts.batchConcurrency = args.concurrency;
265
329
  }
@@ -273,10 +337,10 @@ const stackBatch = {
273
337
  if (args.failFast) {
274
338
  opts.continueOnError = false;
275
339
  }
276
- if (args.providers) {
340
+ if (args.providers !== undefined) {
277
341
  opts.TRUSTIFY_DA_PROVIDERS = args.providers;
278
342
  }
279
- if (args.sources) {
343
+ if (args.sources !== undefined) {
280
344
  opts.TRUSTIFY_DA_SOURCES = args.sources;
281
345
  }
282
346
  let res = await client.stackAnalysisBatch(workspaceRoot, html, opts);
@@ -455,19 +519,33 @@ const remediate = {
455
519
  'group-by': {
456
520
  type: 'string',
457
521
  choices: ['dependency', 'bundle'],
458
- default: 'dependency',
459
- desc: 'Report grouping strategy',
522
+ desc: 'Report grouping strategy (default: dependency)',
460
523
  },
461
- }),
524
+ backendUrl: {
525
+ desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
526
+ type: 'string',
527
+ },
528
+ }).middleware(configMiddleware('path', { groupBy: true })),
462
529
  handler: async (args) => {
463
530
  try {
464
531
  const result = await runRemediation(args.path, {
465
532
  dryRun: args['dry-run'],
466
533
  providers: args.providers,
467
534
  sources: args.sources,
468
- groupBy: args['group-by'],
535
+ backendUrl: args.backendUrl,
469
536
  });
470
- console.log(result.output);
537
+ if (result.remediations.length === 0 && result.manifests.length === 0) {
538
+ console.log('No supported manifest files found.');
539
+ process.exit(result.exitCode);
540
+ }
541
+ if (!args['dry-run'] && result.appliedFiles.length > 0) {
542
+ console.log(`Updated ${result.appliedFiles.length} file(s):`);
543
+ for (const file of result.appliedFiles) {
544
+ console.log(` ${file}`);
545
+ }
546
+ console.log('');
547
+ }
548
+ console.log(generateReport(result.remediations, { groupBy: args['group-by'], dryRun: args['dry-run'] }));
471
549
  process.exit(result.exitCode);
472
550
  }
473
551
  catch (err) {
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Discovers and parses `.trustify-da.yml` by walking up from `startPath`.
3
+ * A missing config file is not an error — an empty object is returned so callers
4
+ * can fall back to defaults.
5
+ * @param {string} [startPath] - manifest file or directory to start the search from
6
+ * @returns {FileConfig} the parsed config object, or `{}` when no file is found
7
+ * @throws {Error} when the config file exists but contains malformed YAML
8
+ */
9
+ export function loadConfig(startPath?: string): FileConfig;
10
+ /**
11
+ * Merges config sources with precedence: CLI flag > environment variable
12
+ * (`TRUSTIFY_DA_*`) > config file > hardcoded default. Providers and sources are
13
+ * normalized to arrays regardless of whether the source used a YAML array or a
14
+ * comma-separated string.
15
+ * @param {FileConfig} [fileConfig={}] - values parsed from `.trustify-da.yml`
16
+ * @param {{ providers?: string, sources?: string, groupBy?: string, backendUrl?: string }} [cliFlags={}]
17
+ * @param {{ [key: string]: string | undefined }} [envVars={}] - typically `process.env`
18
+ * @returns {ResolvedConfig} the resolved, typed config object
19
+ */
20
+ export function mergeConfig(fileConfig?: FileConfig, cliFlags?: {
21
+ providers?: string;
22
+ sources?: string;
23
+ groupBy?: string;
24
+ backendUrl?: string;
25
+ }, envVars?: {
26
+ [key: string]: string | undefined;
27
+ }): ResolvedConfig;
28
+ /**
29
+ * Loads and merges the project configuration for a target path.
30
+ * @param {string} [startPath] - manifest file or directory to start discovery from
31
+ * @param {{ providers?: string, sources?: string, groupBy?: string, backendUrl?: string }} [cliFlags={}] - explicit options
32
+ * @param {{ [key: string]: string | undefined }} [envVars={}] - typically `process.env`
33
+ * @returns {ResolvedConfig} the resolved, typed config object
34
+ */
35
+ export function resolveConfig(startPath?: string, cliFlags?: {
36
+ providers?: string;
37
+ sources?: string;
38
+ groupBy?: string;
39
+ backendUrl?: string;
40
+ }, envVars?: {
41
+ [key: string]: string | undefined;
42
+ }): ResolvedConfig;
43
+ /**
44
+ * @typedef {{
45
+ * 'group-by'?: 'dependency' | 'bundle',
46
+ * exclude?: string[],
47
+ * labels?: string[],
48
+ * 'branch-prefix'?: string,
49
+ * [key: string]: unknown
50
+ * }} RemediationConfig
51
+ */
52
+ /** @typedef {{ critical?: number, high?: number, 'license-conflicts'?: number, [key: string]: unknown }} FailOnConfig */
53
+ /** @typedef {{ 'fail-on'?: FailOnConfig, [key: string]: unknown }} CheckConfig */
54
+ /** @typedef {{ format?: string, targets?: string[], [key: string]: unknown }} SbomConfig */
55
+ /**
56
+ * @typedef {{
57
+ * 'backend-url'?: string,
58
+ * providers?: string[] | string,
59
+ * sources?: string[] | string,
60
+ * remediation?: RemediationConfig,
61
+ * check?: CheckConfig,
62
+ * sbom?: SbomConfig,
63
+ * [key: string]: unknown
64
+ * }} FileConfig
65
+ */
66
+ /**
67
+ * @typedef {{
68
+ * backendUrl: string | null,
69
+ * backendUrlSource: 'cli' | 'environment' | 'file' | 'default',
70
+ * providers: string[],
71
+ * sources: string[],
72
+ * groupBy: string,
73
+ * remediation: object,
74
+ * check: object,
75
+ * sbom: object
76
+ * }} ResolvedConfig
77
+ */
78
+ /** Config file names discovered by walking up the directory tree, in precedence order. */
79
+ export const CONFIG_FILENAMES: string[];
80
+ export type RemediationConfig = {
81
+ "group-by"?: "dependency" | "bundle";
82
+ exclude?: string[];
83
+ labels?: string[];
84
+ "branch-prefix"?: string;
85
+ [key: string]: unknown;
86
+ };
87
+ export type FailOnConfig = {
88
+ critical?: number;
89
+ high?: number;
90
+ "license-conflicts"?: number;
91
+ [key: string]: unknown;
92
+ };
93
+ export type CheckConfig = {
94
+ "fail-on"?: FailOnConfig;
95
+ [key: string]: unknown;
96
+ };
97
+ export type SbomConfig = {
98
+ format?: string;
99
+ targets?: string[];
100
+ [key: string]: unknown;
101
+ };
102
+ export type FileConfig = {
103
+ "backend-url"?: string;
104
+ providers?: string[] | string;
105
+ sources?: string[] | string;
106
+ remediation?: RemediationConfig;
107
+ check?: CheckConfig;
108
+ sbom?: SbomConfig;
109
+ [key: string]: unknown;
110
+ };
111
+ export type ResolvedConfig = {
112
+ backendUrl: string | null;
113
+ backendUrlSource: "cli" | "environment" | "file" | "default";
114
+ providers: string[];
115
+ sources: string[];
116
+ groupBy: string;
117
+ remediation: object;
118
+ check: object;
119
+ sbom: object;
120
+ };