@trustify-da/trustify-da-javascript-client 0.3.0-ea.2ea1d77 → 0.3.0-ea.328523e

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 (92) hide show
  1. package/README.md +272 -19
  2. package/dist/package.json +33 -28
  3. package/dist/src/analysis.d.ts +52 -22
  4. package/dist/src/analysis.js +77 -22
  5. package/dist/src/cli.js +238 -7
  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 +27 -1
  9. package/dist/src/cyclone_dx_sbom.js +82 -9
  10. package/dist/src/index.d.ts +97 -22
  11. package/dist/src/index.js +115 -42
  12. package/dist/src/license/index.d.ts +4 -4
  13. package/dist/src/license/index.js +13 -7
  14. package/dist/src/license/license_utils.js +4 -1
  15. package/dist/src/license/licenses_api.d.ts +3 -3
  16. package/dist/src/license/licenses_api.js +1 -1
  17. package/dist/src/oci_image/images.d.ts +1 -1
  18. package/dist/src/oci_image/images.js +15 -1
  19. package/dist/src/oci_image/utils.d.ts +5 -5
  20. package/dist/src/oci_image/utils.js +11 -2
  21. package/dist/src/package_version.d.ts +8 -0
  22. package/dist/src/package_version.js +31 -0
  23. package/dist/src/provider.d.ts +4 -3
  24. package/dist/src/provider.js +12 -2
  25. package/dist/src/providers/base_java.d.ts +67 -12
  26. package/dist/src/providers/base_java.js +82 -61
  27. package/dist/src/providers/base_javascript.d.ts +90 -15
  28. package/dist/src/providers/base_javascript.js +206 -24
  29. package/dist/src/providers/base_pyproject.d.ts +158 -0
  30. package/dist/src/providers/base_pyproject.js +322 -0
  31. package/dist/src/providers/containerfile_parser.d.ts +5 -0
  32. package/dist/src/providers/containerfile_parser.js +20 -0
  33. package/dist/src/providers/golang_gomodules.d.ts +23 -13
  34. package/dist/src/providers/golang_gomodules.js +208 -120
  35. package/dist/src/providers/gomod_parser.d.ts +4 -0
  36. package/dist/src/providers/gomod_parser.js +16 -0
  37. package/dist/src/providers/java_gradle.d.ts +67 -0
  38. package/dist/src/providers/java_gradle.js +320 -22
  39. package/dist/src/providers/java_gradle_groovy.d.ts +1 -1
  40. package/dist/src/providers/java_gradle_kotlin.d.ts +1 -1
  41. package/dist/src/providers/java_maven.d.ts +37 -9
  42. package/dist/src/providers/java_maven.js +269 -11
  43. package/dist/src/providers/javascript_bun.d.ts +22 -0
  44. package/dist/src/providers/javascript_bun.js +141 -0
  45. package/dist/src/providers/javascript_npm.d.ts +19 -0
  46. package/dist/src/providers/javascript_npm.js +61 -1
  47. package/dist/src/providers/javascript_pnpm.d.ts +13 -0
  48. package/dist/src/providers/javascript_pnpm.js +53 -3
  49. package/dist/src/providers/javascript_yarn.d.ts +12 -0
  50. package/dist/src/providers/javascript_yarn.js +75 -2
  51. package/dist/src/providers/manifest.d.ts +2 -0
  52. package/dist/src/providers/manifest.js +28 -7
  53. package/dist/src/providers/marker_evaluator.d.ts +14 -0
  54. package/dist/src/providers/marker_evaluator.js +191 -0
  55. package/dist/src/providers/oci_dockerfile.d.ts +51 -0
  56. package/dist/src/providers/oci_dockerfile.js +177 -0
  57. package/dist/src/providers/processors/yarn_berry_processor.d.ts +6 -2
  58. package/dist/src/providers/processors/yarn_berry_processor.js +91 -7
  59. package/dist/src/providers/processors/yarn_classic_processor.d.ts +6 -2
  60. package/dist/src/providers/processors/yarn_classic_processor.js +8 -6
  61. package/dist/src/providers/python_controller.d.ts +5 -1
  62. package/dist/src/providers/python_controller.js +69 -8
  63. package/dist/src/providers/python_pip.d.ts +5 -0
  64. package/dist/src/providers/python_pip.js +6 -6
  65. package/dist/src/providers/python_pip_pyproject.d.ts +61 -0
  66. package/dist/src/providers/python_pip_pyproject.js +146 -0
  67. package/dist/src/providers/python_poetry.d.ts +98 -0
  68. package/dist/src/providers/python_poetry.js +286 -0
  69. package/dist/src/providers/python_uv.d.ts +46 -0
  70. package/dist/src/providers/python_uv.js +275 -0
  71. package/dist/src/providers/requirements_parser.js +1 -1
  72. package/dist/src/providers/rust_cargo.d.ts +6 -2
  73. package/dist/src/providers/rust_cargo.js +101 -33
  74. package/dist/src/providers/tree-sitter-containerfile.wasm +0 -0
  75. package/dist/src/providers/tree-sitter-gomod.wasm +0 -0
  76. package/dist/src/remediate.d.ts +107 -0
  77. package/dist/src/remediate.js +244 -0
  78. package/dist/src/remediation.d.ts +81 -0
  79. package/dist/src/remediation.js +475 -0
  80. package/dist/src/remediation_report.d.ts +23 -0
  81. package/dist/src/remediation_report.js +180 -0
  82. package/dist/src/sbom.d.ts +25 -1
  83. package/dist/src/sbom.js +23 -2
  84. package/dist/src/tools.d.ts +37 -13
  85. package/dist/src/tools.js +92 -13
  86. package/dist/src/updaters/maven_updater.d.ts +58 -0
  87. package/dist/src/updaters/maven_updater.js +345 -0
  88. package/dist/src/updaters/toml_updater.d.ts +62 -0
  89. package/dist/src/updaters/toml_updater.js +276 -0
  90. package/dist/src/workspace.d.ts +11 -1
  91. package/dist/src/workspace.js +3 -2
  92. package/package.json +34 -29
@@ -1,9 +1,12 @@
1
+ /** Media type for CycloneDX JSON batch payloads (batch-analysis API). */
2
+ export const CYCLONEDX_JSON_MEDIA_TYPE: "application/vnd.cyclonedx+json";
1
3
  declare namespace _default {
2
4
  export { requestComponent };
3
5
  export { requestStack };
4
6
  export { requestStackBatch };
5
7
  export { requestImages };
6
8
  export { validateToken };
9
+ export { appendAnalysisQueryParams };
7
10
  }
8
11
  export default _default;
9
12
  /**
@@ -12,41 +15,62 @@ export default _default;
12
15
  * @param {string} manifest - path for the manifest
13
16
  * @param {string} url - the backend url to send the request to
14
17
  * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
15
- * @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>}
16
19
  */
17
- 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>;
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>;
18
21
  /**
19
- * Send a stack analysis request and get the report as 'text/html' or 'application/json'.
20
- * @param {import('./provider').Provider} provider - the provided data for constructing the request
21
- * @param {string} manifest - path for the manifest
22
- * @param {string} url - the backend url to send the request to
23
- * @param {boolean} [html=false] - true will return 'text/html', false will return 'application/json'
24
- * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
25
- * @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
+ */
30
+ declare function requestStack(provider: any, manifest: string, url: string, html?: true | undefined, opts?: any): Promise<string>;
31
+ /**
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>>}
26
39
  */
27
- 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>;
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>;
28
41
  /**
29
- * Send a batch stack analysis request for multiple manifests (SBOMs keyed by purl).
30
- * @param {Object.<string, object>} sbomByPurl - Map of root purl to CycloneDX SBOM object
31
- * @param {string} url - the backend url
32
- * @param {boolean} [html=false] - true returns HTML, false returns JSON
42
+ * @overload
43
+ * @param {Object.<string, object>} sbomByPurl
44
+ * @param {string} url
45
+ * @param {true} [html=false]
33
46
  * @param {import("index.js").Options} [opts={}]
34
- * @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
47
+ * @returns {Promise<string>}
35
48
  */
36
49
  declare function requestStackBatch(sbomByPurl: {
37
- [x: string]: any;
38
- }, url: string, html?: boolean, opts?: import("index.js").Options): Promise<string | {
39
- [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
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]
57
+ * @param {import("index.js").Options} [opts={}]
58
+ * @returns {Promise<Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts').AnalysisReport>>}
59
+ */
60
+ declare function requestStackBatch(sbomByPurl: {
61
+ [x: string]: object;
62
+ }, url: string, html?: false | undefined, opts?: any): Promise<{
63
+ [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts").AnalysisReport;
40
64
  }>;
41
65
  /**
42
66
  *
43
67
  * @param {Array<string>} imageRefs
44
68
  * @param {string} url
45
69
  * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
46
- * @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>>}
47
71
  */
48
- declare function requestImages(imageRefs: Array<string>, url: string, html?: boolean, opts?: import("index.js").Options): Promise<string | {
49
- [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
72
+ declare function requestImages(imageRefs: Array<string>, url: string, html?: boolean, opts?: any): Promise<string | {
73
+ [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport.ts").AnalysisReport;
50
74
  }>;
51
75
  /**
52
76
  *
@@ -54,4 +78,10 @@ declare function requestImages(imageRefs: Array<string>, url: string, html?: boo
54
78
  * @param {import("index.js").Options} [opts={}] - optional various options to pass headers for t he validateToken Request
55
79
  * @return {Promise<number>} return the HTTP status Code of the response from the validate token request.
56
80
  */
57
- declare function validateToken(url: any, opts?: import("index.js").Options): Promise<number>;
81
+ declare function validateToken(url: any, opts?: any): Promise<number>;
82
+ /**
83
+ * Append common analysis query parameters (recommend, providers, sources) to a URL.
84
+ * @param {URL} url - the URL object to append query parameters to
85
+ * @param {import("index.js").Options} [opts={}] - options containing parameter overrides
86
+ */
87
+ declare function appendAnalysisQueryParams(url: URL, opts?: any): void;
@@ -4,7 +4,27 @@ import { EOL } from "os";
4
4
  import { runLicenseCheck } from "./license/index.js";
5
5
  import { generateImageSBOM, parseImageRef } from "./oci_image/utils.js";
6
6
  import { addProxyAgent, getCustom, getTokenHeaders, TRUSTIFY_DA_OPERATION_TYPE_HEADER, TRUSTIFY_DA_PACKAGE_MANAGER_HEADER } from "./tools.js";
7
- export default { requestComponent, requestStack, requestStackBatch, requestImages, validateToken };
7
+ /** Media type for CycloneDX JSON batch payloads (batch-analysis API). */
8
+ export const CYCLONEDX_JSON_MEDIA_TYPE = 'application/vnd.cyclonedx+json';
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
+ */
8
28
  /**
9
29
  * Send a stack analysis request and get the report as 'text/html' or 'application/json'.
10
30
  * @param {import('./provider').Provider} provider - the provided data for constructing the request
@@ -12,12 +32,15 @@ export default { requestComponent, requestStack, requestStackBatch, requestImage
12
32
  * @param {string} url - the backend url to send the request to
13
33
  * @param {boolean} [html=false] - true will return 'text/html', false will return 'application/json'
14
34
  * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
15
- * @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>}
16
36
  */
17
37
  async function requestStack(provider, manifest, url, html = false, opts = {}) {
18
38
  opts["source-manifest"] = Buffer.from(fs.readFileSync(manifest).toString()).toString('base64');
19
39
  opts["manifest-type"] = path.parse(manifest).base;
20
40
  let provided = await provider.provideStack(manifest, opts); // throws error if content providing failed
41
+ if (provided.batch) {
42
+ return requestStackBatch(JSON.parse(provided.content), url, html, opts);
43
+ }
21
44
  opts["source-manifest"] = "";
22
45
  opts[TRUSTIFY_DA_OPERATION_TYPE_HEADER.toUpperCase().replaceAll("-", "_")] = "stack-analysis";
23
46
  let startTime = new Date();
@@ -36,9 +59,7 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) {
36
59
  body: provided.content
37
60
  }, opts);
38
61
  const finalUrl = new URL(`${url}/api/v5/analysis`);
39
- if (opts['TRUSTIFY_DA_RECOMMENDATIONS_ENABLED'] === 'false') {
40
- finalUrl.searchParams.append('recommend', 'false');
41
- }
62
+ appendAnalysisQueryParams(finalUrl, opts);
42
63
  let resp = await fetch(finalUrl, fetchOptions);
43
64
  let result;
44
65
  if (resp.status === 200) {
@@ -72,11 +93,14 @@ async function requestStack(provider, manifest, url, html = false, opts = {}) {
72
93
  * @param {string} manifest - path for the manifest
73
94
  * @param {string} url - the backend url to send the request to
74
95
  * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
75
- * @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>}
76
97
  */
77
98
  async function requestComponent(provider, manifest, url, opts = {}) {
78
99
  opts["source-manifest"] = Buffer.from(fs.readFileSync(manifest).toString()).toString('base64');
79
100
  let provided = await provider.provideComponent(manifest, opts); // throws error if content providing failed
101
+ if (provided.batch) {
102
+ return requestStackBatch(JSON.parse(provided.content), url, false, opts);
103
+ }
80
104
  opts["source-manifest"] = "";
81
105
  opts[TRUSTIFY_DA_OPERATION_TYPE_HEADER.toUpperCase().replaceAll("-", "_")] = "component-analysis";
82
106
  if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
@@ -93,9 +117,7 @@ async function requestComponent(provider, manifest, url, opts = {}) {
93
117
  body: provided.content
94
118
  }, opts);
95
119
  const finalUrl = new URL(`${url}/api/v5/analysis`);
96
- if (opts['TRUSTIFY_DA_RECOMMENDATIONS_ENABLED'] === 'false') {
97
- finalUrl.searchParams.append('recommend', 'false');
98
- }
120
+ appendAnalysisQueryParams(finalUrl, opts);
99
121
  let resp = await fetch(finalUrl, fetchOptions);
100
122
  let result;
101
123
  if (resp.status === 200) {
@@ -109,7 +131,9 @@ async function requestComponent(provider, manifest, url, opts = {}) {
109
131
  console.log(JSON.stringify(result, null, 4));
110
132
  console.log("Ending time of sending component analysis request to Trustify DA backend server= " + new Date());
111
133
  }
112
- 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';
113
137
  if (licenseCheckEnabled) {
114
138
  try {
115
139
  result.licenseSummary = await runLicenseCheck(provided.content, manifest, url, opts, result);
@@ -124,24 +148,38 @@ async function requestComponent(provider, manifest, url, opts = {}) {
124
148
  }
125
149
  return Promise.resolve(result);
126
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
+ */
127
167
  /**
128
168
  * Send a batch stack analysis request for multiple manifests (SBOMs keyed by purl).
129
169
  * @param {Object.<string, object>} sbomByPurl - Map of root purl to CycloneDX SBOM object
130
170
  * @param {string} url - the backend url
131
171
  * @param {boolean} [html=false] - true returns HTML, false returns JSON
132
172
  * @param {import("index.js").Options} [opts={}]
133
- * @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>>}
134
174
  */
135
175
  async function requestStackBatch(sbomByPurl, url, html = false, opts = {}) {
136
176
  const finalUrl = new URL(`${url}/api/v5/batch-analysis`);
137
- if (opts['TRUSTIFY_DA_RECOMMENDATIONS_ENABLED'] === 'false') {
138
- finalUrl.searchParams.append('recommend', 'false');
139
- }
177
+ appendAnalysisQueryParams(finalUrl, opts);
140
178
  const fetchOptions = addProxyAgent({
141
179
  method: 'POST',
142
180
  headers: {
143
181
  'Accept': html ? 'text/html' : 'application/json',
144
- 'Content-Type': 'application/json',
182
+ 'Content-Type': CYCLONEDX_JSON_MEDIA_TYPE,
145
183
  ...getTokenHeaders(opts)
146
184
  },
147
185
  body: JSON.stringify(sbomByPurl)
@@ -175,7 +213,7 @@ async function requestStackBatch(sbomByPurl, url, html = false, opts = {}) {
175
213
  * @param {Array<string>} imageRefs
176
214
  * @param {string} url
177
215
  * @param {import("index.js").Options} [opts={}] - optional various options to pass along the application
178
- * @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>>}
179
217
  */
180
218
  async function requestImages(imageRefs, url, html = false, opts = {}) {
181
219
  const imageSboms = {};
@@ -184,18 +222,17 @@ async function requestImages(imageRefs, url, html = false, opts = {}) {
184
222
  imageSboms[parsedImageRef.getPackageURL().toString()] = generateImageSBOM(parsedImageRef, opts);
185
223
  }
186
224
  const finalUrl = new URL(`${url}/api/v5/batch-analysis`);
187
- if (opts['TRUSTIFY_DA_RECOMMENDATIONS_ENABLED'] === 'false') {
188
- finalUrl.searchParams.append('recommend', 'false');
189
- }
190
- const resp = await fetch(finalUrl, {
225
+ appendAnalysisQueryParams(finalUrl, opts);
226
+ const fetchOptions = addProxyAgent({
191
227
  method: 'POST',
192
228
  headers: {
193
229
  'Accept': html ? 'text/html' : 'application/json',
194
- 'Content-Type': 'application/vnd.cyclonedx+json',
230
+ 'Content-Type': CYCLONEDX_JSON_MEDIA_TYPE,
195
231
  ...getTokenHeaders(opts)
196
232
  },
197
233
  body: JSON.stringify(imageSboms),
198
- });
234
+ }, opts);
235
+ const resp = await fetch(finalUrl, fetchOptions);
199
236
  if (resp.status === 200) {
200
237
  let result;
201
238
  if (!html) {
@@ -219,6 +256,24 @@ async function requestImages(imageRefs, url, html = false, opts = {}) {
219
256
  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()}`);
220
257
  }
221
258
  }
259
+ /**
260
+ * Append common analysis query parameters (recommend, providers, sources) to a URL.
261
+ * @param {URL} url - the URL object to append query parameters to
262
+ * @param {import("index.js").Options} [opts={}] - options containing parameter overrides
263
+ */
264
+ function appendAnalysisQueryParams(url, opts = {}) {
265
+ if (getCustom('TRUSTIFY_DA_RECOMMEND', 'true', opts) === 'false') {
266
+ url.searchParams.append('recommend', 'false');
267
+ }
268
+ const providers = getCustom('TRUSTIFY_DA_PROVIDERS', null, opts);
269
+ if (providers) {
270
+ url.searchParams.append('providers', providers);
271
+ }
272
+ const sources = getCustom('TRUSTIFY_DA_SOURCES', null, opts);
273
+ if (sources) {
274
+ url.searchParams.append('sources', sources);
275
+ }
276
+ }
222
277
  /**
223
278
  *
224
279
  * @param url the backend url to send the request to
package/dist/src/cli.js CHANGED
@@ -1,9 +1,47 @@
1
1
  #!/usr/bin/env node
2
+ import fs from 'node:fs';
2
3
  import * as path from "path";
3
4
  import yargs from 'yargs';
4
5
  import { hideBin } from 'yargs/helpers';
6
+ import { resolveConfig } from './config.js';
5
7
  import { getProjectLicense, getLicenseDetails } from './license/index.js';
6
- import client, { selectTrustifyDABackend } from './index.js';
8
+ import { runRemediation } from './remediate.js';
9
+ import { generateReport } from './remediation_report.js';
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
+ }
7
45
  // command for component analysis take manifest type and content
8
46
  const component = {
9
47
  command: 'component </path/to/manifest>',
@@ -18,11 +56,32 @@ const component = {
18
56
  desc: 'Workspace root directory (for monorepos; lock file is expected here)',
19
57
  type: 'string',
20
58
  normalize: true,
59
+ },
60
+ backendUrl: {
61
+ desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
62
+ type: 'string',
63
+ },
64
+ providers: {
65
+ desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
66
+ type: 'string',
67
+ },
68
+ sources: {
69
+ desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
70
+ type: 'string',
21
71
  }
22
- }),
72
+ }).middleware(configMiddleware('/path/to/manifest')),
23
73
  handler: async (args) => {
24
74
  let manifestName = args['/path/to/manifest'];
25
75
  const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
76
+ if (args.backendUrl !== undefined) {
77
+ opts.TRUSTIFY_DA_BACKEND_URL = args.backendUrl;
78
+ }
79
+ if (args.providers !== undefined) {
80
+ opts.TRUSTIFY_DA_PROVIDERS = args.providers;
81
+ }
82
+ if (args.sources !== undefined) {
83
+ opts.TRUSTIFY_DA_SOURCES = args.sources;
84
+ }
26
85
  let res = await client.componentAnalysis(manifestName, opts);
27
86
  console.log(JSON.stringify(res, null, 2));
28
87
  }
@@ -71,8 +130,20 @@ const image = {
71
130
  desc: 'For JSON report, get only the \'summary\'',
72
131
  type: 'boolean',
73
132
  conflicts: 'html'
133
+ },
134
+ backendUrl: {
135
+ desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
136
+ type: 'string',
137
+ },
138
+ providers: {
139
+ desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
140
+ type: 'string',
141
+ },
142
+ sources: {
143
+ desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
144
+ type: 'string',
74
145
  }
75
- }),
146
+ }).middleware(configMiddleware()),
76
147
  handler: async (args) => {
77
148
  let imageRefs = args['image-refs'];
78
149
  if (!Array.isArray(imageRefs)) {
@@ -80,7 +151,17 @@ const image = {
80
151
  }
81
152
  let html = args['html'];
82
153
  let summary = args['summary'];
83
- let res = await client.imageAnalysis(imageRefs, html);
154
+ const opts = {};
155
+ if (args.backendUrl !== undefined) {
156
+ opts.TRUSTIFY_DA_BACKEND_URL = args.backendUrl;
157
+ }
158
+ if (args.providers !== undefined) {
159
+ opts.TRUSTIFY_DA_PROVIDERS = args.providers;
160
+ }
161
+ if (args.sources !== undefined) {
162
+ opts.TRUSTIFY_DA_SOURCES = args.sources;
163
+ }
164
+ let res = await client.imageAnalysis(imageRefs, html, opts);
84
165
  if (summary && !html) {
85
166
  let summaries = {};
86
167
  for (let [imageRef, report] of Object.entries(res)) {
@@ -131,13 +212,34 @@ const stack = {
131
212
  desc: 'Workspace root directory (for monorepos; lock file is expected here)',
132
213
  type: 'string',
133
214
  normalize: true,
215
+ },
216
+ backendUrl: {
217
+ desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
218
+ type: 'string',
219
+ },
220
+ providers: {
221
+ desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
222
+ type: 'string',
223
+ },
224
+ sources: {
225
+ desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
226
+ type: 'string',
134
227
  }
135
- }),
228
+ }).middleware(configMiddleware('/path/to/manifest')),
136
229
  handler: async (args) => {
137
230
  let manifest = args['/path/to/manifest'];
138
231
  let html = args['html'];
139
232
  let summary = args['summary'];
140
233
  const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
234
+ if (args.backendUrl !== undefined) {
235
+ opts.TRUSTIFY_DA_BACKEND_URL = args.backendUrl;
236
+ }
237
+ if (args.providers !== undefined) {
238
+ opts.TRUSTIFY_DA_PROVIDERS = args.providers;
239
+ }
240
+ if (args.sources !== undefined) {
241
+ opts.TRUSTIFY_DA_SOURCES = args.sources;
242
+ }
141
243
  let theProvidersSummary = new Map();
142
244
  let theProvidersObject = {};
143
245
  let res = await client.stackAnalysis(manifest, html, opts);
@@ -200,13 +302,28 @@ const stackBatch = {
200
302
  desc: 'Stop on first invalid package.json or SBOM error (env: TRUSTIFY_DA_CONTINUE_ON_ERROR=false)',
201
303
  type: 'boolean',
202
304
  default: false,
305
+ },
306
+ backendUrl: {
307
+ desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
308
+ type: 'string',
309
+ },
310
+ providers: {
311
+ desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
312
+ type: 'string',
313
+ },
314
+ sources: {
315
+ desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
316
+ type: 'string',
203
317
  }
204
- }),
318
+ }).middleware(configMiddleware('/path/to/workspace-root')),
205
319
  handler: async (args) => {
206
320
  const workspaceRoot = args['/path/to/workspace-root'];
207
321
  const html = args['html'];
208
322
  const summary = args['summary'];
209
323
  const opts = {};
324
+ if (args.backendUrl !== undefined) {
325
+ opts.TRUSTIFY_DA_BACKEND_URL = args.backendUrl;
326
+ }
210
327
  if (args.concurrency != null) {
211
328
  opts.batchConcurrency = args.concurrency;
212
329
  }
@@ -220,6 +337,12 @@ const stackBatch = {
220
337
  if (args.failFast) {
221
338
  opts.continueOnError = false;
222
339
  }
340
+ if (args.providers !== undefined) {
341
+ opts.TRUSTIFY_DA_PROVIDERS = args.providers;
342
+ }
343
+ if (args.sources !== undefined) {
344
+ opts.TRUSTIFY_DA_SOURCES = args.sources;
345
+ }
223
346
  let res = await client.stackAnalysisBatch(workspaceRoot, html, opts);
224
347
  const batchAnalysis = res && typeof res === 'object' && res != null && 'analysis' in res ? res.analysis : res;
225
348
  if (summary && !html && typeof batchAnalysis === 'object') {
@@ -325,15 +448,123 @@ const license = {
325
448
  console.log(JSON.stringify(output, null, 2));
326
449
  }
327
450
  };
451
+ const sbom = {
452
+ command: 'sbom </path/to/manifest> [--output]',
453
+ desc: 'generate a CycloneDX SBOM from a manifest file',
454
+ builder: yargs => yargs.positional('/path/to/manifest', {
455
+ desc: 'manifest path for SBOM generation',
456
+ type: 'string',
457
+ normalize: true,
458
+ }).options({
459
+ output: {
460
+ alias: 'o',
461
+ desc: 'Write SBOM JSON to a file instead of stdout',
462
+ type: 'string',
463
+ normalize: true,
464
+ },
465
+ workspaceDir: {
466
+ alias: 'w',
467
+ desc: 'Workspace root directory (for monorepos; lock file is expected here)',
468
+ type: 'string',
469
+ normalize: true,
470
+ }
471
+ }),
472
+ handler: async (args) => {
473
+ let manifest = args['/path/to/manifest'];
474
+ const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
475
+ let result;
476
+ try {
477
+ result = await generateSbom(manifest, opts);
478
+ }
479
+ catch (err) {
480
+ console.error(JSON.stringify({ error: `Failed to generate SBOM: ${err.message}` }, null, 2));
481
+ process.exit(1);
482
+ }
483
+ const json = JSON.stringify(result, null, 2);
484
+ if (args.output) {
485
+ try {
486
+ fs.writeFileSync(args.output, json);
487
+ }
488
+ catch (err) {
489
+ console.error(JSON.stringify({ error: `Failed to write output file: ${err.message}` }, null, 2));
490
+ process.exit(1);
491
+ }
492
+ }
493
+ else {
494
+ console.log(json);
495
+ }
496
+ }
497
+ };
498
+ const remediate = {
499
+ command: 'remediate <path>',
500
+ desc: 'Scan and apply vulnerability remediations to manifest files',
501
+ builder: yargs => yargs.positional('path', {
502
+ desc: 'Path to manifest file or directory',
503
+ type: 'string',
504
+ normalize: true,
505
+ }).options({
506
+ 'dry-run': {
507
+ alias: 'd',
508
+ type: 'boolean',
509
+ desc: 'Preview changes without modifying files',
510
+ },
511
+ providers: {
512
+ desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
513
+ type: 'string',
514
+ },
515
+ sources: {
516
+ desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
517
+ type: 'string',
518
+ },
519
+ 'group-by': {
520
+ type: 'string',
521
+ choices: ['dependency', 'bundle'],
522
+ desc: 'Report grouping strategy (default: dependency)',
523
+ },
524
+ backendUrl: {
525
+ desc: 'Trustify DA backend URL (env: TRUSTIFY_DA_BACKEND_URL)',
526
+ type: 'string',
527
+ },
528
+ }).middleware(configMiddleware('path', { groupBy: true })),
529
+ handler: async (args) => {
530
+ try {
531
+ const result = await runRemediation(args.path, {
532
+ dryRun: args['dry-run'],
533
+ providers: args.providers,
534
+ sources: args.sources,
535
+ backendUrl: args.backendUrl,
536
+ });
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'] }));
549
+ process.exit(result.exitCode);
550
+ }
551
+ catch (err) {
552
+ console.error(err.message);
553
+ process.exit(1);
554
+ }
555
+ }
556
+ };
328
557
  // parse and invoke the command
329
558
  yargs(hideBin(process.argv))
330
- .usage(`Usage: ${process.argv[0].includes("node") ? path.parse(process.argv[1]).base : path.parse(process.argv[0]).base} {component|stack|stack-batch|image|validate-token|license}`)
559
+ .usage(`Usage: ${process.argv[0].includes("node") ? path.parse(process.argv[1]).base : path.parse(process.argv[0]).base} {component|stack|stack-batch|image|validate-token|license|sbom|remediate}`)
331
560
  .command(stack)
332
561
  .command(stackBatch)
333
562
  .command(component)
334
563
  .command(image)
335
564
  .command(validateToken)
336
565
  .command(license)
566
+ .command(sbom)
567
+ .command(remediate)
337
568
  .scriptName('')
338
569
  .version(false)
339
570
  .demandCommand(1)