@trustify-da/trustify-da-javascript-client 0.3.0-ea.a109536 → 0.3.0-ea.a27acdd

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 (97) hide show
  1. package/README.md +367 -13
  2. package/dist/package.json +44 -24
  3. package/dist/src/analysis.d.ts +28 -11
  4. package/dist/src/analysis.js +99 -79
  5. package/dist/src/batch_opts.d.ts +24 -0
  6. package/dist/src/batch_opts.js +35 -0
  7. package/dist/src/cli.js +346 -9
  8. package/dist/src/cyclone_dx_sbom.d.ts +30 -2
  9. package/dist/src/cyclone_dx_sbom.js +95 -9
  10. package/dist/src/index.d.ts +145 -7
  11. package/dist/src/index.js +362 -27
  12. package/dist/src/license/index.d.ts +28 -0
  13. package/dist/src/license/index.js +103 -0
  14. package/dist/src/license/license_utils.d.ts +40 -0
  15. package/dist/src/license/license_utils.js +137 -0
  16. package/dist/src/license/licenses_api.d.ts +34 -0
  17. package/dist/src/license/licenses_api.js +98 -0
  18. package/dist/src/license/project_license.d.ts +20 -0
  19. package/dist/src/license/project_license.js +62 -0
  20. package/dist/src/oci_image/images.d.ts +1 -1
  21. package/dist/src/oci_image/images.js +15 -1
  22. package/dist/src/oci_image/utils.d.ts +5 -5
  23. package/dist/src/oci_image/utils.js +11 -2
  24. package/dist/src/package_version.d.ts +8 -0
  25. package/dist/src/package_version.js +31 -0
  26. package/dist/src/provider.d.ts +18 -5
  27. package/dist/src/provider.js +33 -5
  28. package/dist/src/providers/base_java.d.ts +67 -12
  29. package/dist/src/providers/base_java.js +82 -61
  30. package/dist/src/providers/base_javascript.d.ts +99 -12
  31. package/dist/src/providers/base_javascript.js +243 -27
  32. package/dist/src/providers/base_pyproject.d.ts +158 -0
  33. package/dist/src/providers/base_pyproject.js +322 -0
  34. package/dist/src/providers/containerfile_parser.d.ts +5 -0
  35. package/dist/src/providers/containerfile_parser.js +20 -0
  36. package/dist/src/providers/golang_gomodules.d.ts +30 -13
  37. package/dist/src/providers/golang_gomodules.js +218 -122
  38. package/dist/src/providers/gomod_parser.d.ts +4 -0
  39. package/dist/src/providers/gomod_parser.js +16 -0
  40. package/dist/src/providers/java_gradle.d.ts +25 -0
  41. package/dist/src/providers/java_gradle.js +128 -4
  42. package/dist/src/providers/java_gradle_groovy.d.ts +1 -1
  43. package/dist/src/providers/java_gradle_kotlin.d.ts +1 -1
  44. package/dist/src/providers/java_maven.d.ts +45 -5
  45. package/dist/src/providers/java_maven.js +291 -10
  46. package/dist/src/providers/javascript_bun.d.ts +22 -0
  47. package/dist/src/providers/javascript_bun.js +141 -0
  48. package/dist/src/providers/javascript_npm.d.ts +19 -0
  49. package/dist/src/providers/javascript_npm.js +61 -1
  50. package/dist/src/providers/javascript_pnpm.d.ts +14 -1
  51. package/dist/src/providers/javascript_pnpm.js +55 -5
  52. package/dist/src/providers/javascript_yarn.d.ts +12 -0
  53. package/dist/src/providers/javascript_yarn.js +75 -2
  54. package/dist/src/providers/manifest.d.ts +2 -0
  55. package/dist/src/providers/manifest.js +22 -4
  56. package/dist/src/providers/marker_evaluator.d.ts +14 -0
  57. package/dist/src/providers/marker_evaluator.js +191 -0
  58. package/dist/src/providers/oci_dockerfile.d.ts +51 -0
  59. package/dist/src/providers/oci_dockerfile.js +177 -0
  60. package/dist/src/providers/processors/yarn_berry_processor.d.ts +6 -2
  61. package/dist/src/providers/processors/yarn_berry_processor.js +91 -7
  62. package/dist/src/providers/processors/yarn_classic_processor.d.ts +6 -2
  63. package/dist/src/providers/processors/yarn_classic_processor.js +8 -6
  64. package/dist/src/providers/python_controller.d.ts +10 -3
  65. package/dist/src/providers/python_controller.js +122 -63
  66. package/dist/src/providers/python_pip.d.ts +16 -4
  67. package/dist/src/providers/python_pip.js +51 -58
  68. package/dist/src/providers/python_pip_pyproject.d.ts +61 -0
  69. package/dist/src/providers/python_pip_pyproject.js +146 -0
  70. package/dist/src/providers/python_poetry.d.ts +98 -0
  71. package/dist/src/providers/python_poetry.js +286 -0
  72. package/dist/src/providers/python_uv.d.ts +46 -0
  73. package/dist/src/providers/python_uv.js +269 -0
  74. package/dist/src/providers/requirements_parser.d.ts +6 -0
  75. package/dist/src/providers/requirements_parser.js +24 -0
  76. package/dist/src/providers/rust_cargo.d.ts +56 -0
  77. package/dist/src/providers/rust_cargo.js +682 -0
  78. package/dist/src/providers/tree-sitter-containerfile.wasm +0 -0
  79. package/dist/src/providers/tree-sitter-gomod.wasm +0 -0
  80. package/dist/src/providers/tree-sitter-requirements.wasm +0 -0
  81. package/dist/src/remediate.d.ts +21 -0
  82. package/dist/src/remediate.js +149 -0
  83. package/dist/src/remediation.d.ts +57 -0
  84. package/dist/src/remediation.js +436 -0
  85. package/dist/src/remediation_report.d.ts +40 -0
  86. package/dist/src/remediation_report.js +159 -0
  87. package/dist/src/sbom.d.ts +28 -2
  88. package/dist/src/sbom.js +26 -4
  89. package/dist/src/tools.d.ts +44 -0
  90. package/dist/src/tools.js +137 -1
  91. package/dist/src/updaters/maven_updater.d.ts +26 -0
  92. package/dist/src/updaters/maven_updater.js +322 -0
  93. package/dist/src/updaters/toml_updater.d.ts +33 -0
  94. package/dist/src/updaters/toml_updater.js +244 -0
  95. package/dist/src/workspace.d.ts +70 -0
  96. package/dist/src/workspace.js +256 -0
  97. package/package.json +45 -25
@@ -13,16 +13,29 @@ export function selectTrustifyDABackend(opts?: {
13
13
  TRUSTIFY_DA_DEBUG?: string | undefined;
14
14
  TRUSTIFY_DA_BACKEND_URL?: string | undefined;
15
15
  }): string;
16
+ /**
17
+ * Generate a CycloneDX SBOM from a manifest file. No backend HTTP request is made.
18
+ *
19
+ * @param {string} manifestPath - path to the manifest file (e.g. pom.xml, package.json)
20
+ * @param {Options} [opts={}] - optional options (e.g. workspace dir, tool paths)
21
+ * @returns {Promise<object>} parsed CycloneDX SBOM JSON object
22
+ * @throws {Error} if the manifest is unsupported or SBOM generation fails
23
+ */
24
+ export function generateSbom(manifestPath: string, opts?: Options): Promise<object>;
16
25
  export { parseImageRef } from "./oci_image/utils.js";
17
26
  export { ImageRef } from "./oci_image/images.js";
27
+ export { extractRemediations } from "./remediation.js";
18
28
  declare namespace _default {
19
29
  export { componentAnalysis };
20
30
  export { stackAnalysis };
31
+ export { stackAnalysisBatch };
21
32
  export { imageAnalysis };
22
33
  export { validateToken };
34
+ export { generateSbom };
23
35
  }
24
36
  export default _default;
25
37
  export type Options = {
38
+ TRUSTIFY_DA_CARGO_PATH?: string | undefined;
26
39
  TRUSTIFY_DA_DOCKER_PATH?: string | undefined;
27
40
  TRUSTIFY_DA_GO_MVS_LOGIC_ENABLED?: string | undefined;
28
41
  TRUSTIFY_DA_GO_PATH?: string | undefined;
@@ -41,17 +54,53 @@ export type Options = {
41
54
  TRUSTIFY_DA_PYTHON_PATH?: string | undefined;
42
55
  TRUSTIFY_DA_PYTHON_VIRTUAL_ENV?: string | undefined;
43
56
  TRUSTIFY_DA_PYTHON3_PATH?: string | undefined;
44
- TRUSTIFY_DA_RECOMMENDATIONS_ENABLED?: string | undefined;
57
+ TRUSTIFY_DA_PROVIDERS?: string | undefined;
58
+ TRUSTIFY_DA_RECOMMEND?: string | undefined;
59
+ TRUSTIFY_DA_SOURCES?: string | undefined;
45
60
  TRUSTIFY_DA_SKOPEO_CONFIG_PATH?: string | undefined;
46
61
  TRUSTIFY_DA_SKOPEO_PATH?: string | undefined;
47
62
  TRUSTIFY_DA_SYFT_CONFIG_PATH?: string | undefined;
48
63
  TRUSTIFY_DA_SYFT_PATH?: string | undefined;
49
64
  TRUSTIFY_DA_YARN_PATH?: string | undefined;
65
+ TRUSTIFY_DA_WORKSPACE_DIR?: string | undefined;
66
+ TRUSTIFY_DA_LICENSE_CHECK?: string | undefined;
50
67
  MATCH_MANIFEST_VERSIONS?: string | undefined;
51
68
  TRUSTIFY_DA_SOURCE?: string | undefined;
52
69
  TRUSTIFY_DA_TOKEN?: string | undefined;
53
70
  TRUSTIFY_DA_TELEMETRY_ID?: string | undefined;
54
- [key: string]: string | undefined;
71
+ TRUSTIFY_DA_WORKSPACE_DIR?: string | undefined;
72
+ batchConcurrency?: number | undefined;
73
+ TRUSTIFY_DA_BATCH_CONCURRENCY?: string | undefined;
74
+ workspaceDiscoveryIgnore?: string[] | undefined;
75
+ TRUSTIFY_DA_WORKSPACE_DISCOVERY_IGNORE?: string | undefined;
76
+ continueOnError?: boolean | undefined;
77
+ TRUSTIFY_DA_CONTINUE_ON_ERROR?: string | undefined;
78
+ batchMetadata?: boolean | undefined;
79
+ TRUSTIFY_DA_BATCH_METADATA?: string | undefined;
80
+ TRUSTIFY_DA_UV_PATH?: string | undefined;
81
+ TRUSTIFY_DA_POETRY_PATH?: string | undefined;
82
+ [key: string]: string | number | boolean | string[] | undefined;
83
+ };
84
+ export type BatchAnalysisMetadata = {
85
+ workspaceRoot: string;
86
+ ecosystem: "javascript" | "cargo" | "pyproject" | "unknown";
87
+ total: number;
88
+ successful: number;
89
+ failed: number;
90
+ errors: Array<{
91
+ manifestPath: string;
92
+ phase: "validation" | "sbom";
93
+ reason: string;
94
+ }>;
95
+ };
96
+ export type SbomResult = {
97
+ ok: true;
98
+ purl: string;
99
+ sbom: object;
100
+ } | {
101
+ ok: false;
102
+ manifestPath: string;
103
+ reason: string;
55
104
  };
56
105
  /**
57
106
  * Get component analysis report for a manifest content.
@@ -60,7 +109,7 @@ export type Options = {
60
109
  * @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
61
110
  * @throws {Error} if no matching provider, failed to get create content, or backend request failed
62
111
  */
63
- declare function componentAnalysis(manifest: string, opts?: Options): Promise<import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport>;
112
+ declare function componentAnalysis(manifest: string, opts?: Options): Promise<any>;
64
113
  /**
65
114
  * @overload
66
115
  * @param {string} manifest
@@ -78,7 +127,7 @@ declare function stackAnalysis(manifest: string, html: true, opts?: Options | un
78
127
  * @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
79
128
  * @throws {Error}
80
129
  */
81
- declare function stackAnalysis(manifest: string, html: false, opts?: Options | undefined): Promise<import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport>;
130
+ declare function stackAnalysis(manifest: string, html: false, opts?: Options | undefined): Promise<any>;
82
131
  /**
83
132
  * Get stack analysis report for a manifest file.
84
133
  * @overload
@@ -89,7 +138,82 @@ declare function stackAnalysis(manifest: string, html: false, opts?: Options | u
89
138
  * @throws {Error} if manifest inaccessible, no matching provider, failed to get create content,
90
139
  * or backend request failed
91
140
  */
92
- declare function stackAnalysis(manifest: string, html?: boolean | undefined, opts?: Options | undefined): Promise<string | import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport>;
141
+ declare function stackAnalysis(manifest: string, html?: boolean | undefined, opts?: Options | undefined): Promise<string | any>;
142
+ /**
143
+ * @overload
144
+ * @param {string} workspaceRoot
145
+ * @param {true} html
146
+ * @param {Options & { batchMetadata: true }} opts
147
+ * @returns {Promise<{ analysis: string, metadata: BatchAnalysisMetadata }>}
148
+ * @throws {Error}
149
+ */
150
+ declare function stackAnalysisBatch(workspaceRoot: string, html: true, opts: Options & {
151
+ batchMetadata: true;
152
+ }): Promise<{
153
+ analysis: string;
154
+ metadata: BatchAnalysisMetadata;
155
+ }>;
156
+ /**
157
+ * @overload
158
+ * @param {string} workspaceRoot
159
+ * @param {true} html
160
+ * @param {Options & { batchMetadata?: false }} [opts={}]
161
+ * @returns {Promise<string>}
162
+ * @throws {Error}
163
+ */
164
+ declare function stackAnalysisBatch(workspaceRoot: string, html: true, opts?: (Options & {
165
+ batchMetadata?: false;
166
+ }) | undefined): Promise<string>;
167
+ /**
168
+ * @overload
169
+ * @param {string} workspaceRoot
170
+ * @param {false} html
171
+ * @param {Options & { batchMetadata: true }} opts
172
+ * @returns {Promise<{ analysis: Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>, metadata: BatchAnalysisMetadata }>}
173
+ * @throws {Error}
174
+ */
175
+ declare function stackAnalysisBatch(workspaceRoot: string, html: false, opts: Options & {
176
+ batchMetadata: true;
177
+ }): Promise<{
178
+ analysis: {
179
+ [x: string]: any;
180
+ };
181
+ metadata: BatchAnalysisMetadata;
182
+ }>;
183
+ /**
184
+ * @overload
185
+ * @param {string} workspaceRoot
186
+ * @param {false} html
187
+ * @param {Options & { batchMetadata?: false }} [opts={}]
188
+ * @returns {Promise<Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
189
+ * @throws {Error}
190
+ */
191
+ declare function stackAnalysisBatch(workspaceRoot: string, html: false, opts?: (Options & {
192
+ batchMetadata?: false;
193
+ }) | undefined): Promise<{
194
+ [x: string]: any;
195
+ }>;
196
+ /**
197
+ * Get stack analysis for all workspace packages/crates (batch).
198
+ * Detects ecosystem from workspace root: Cargo (Cargo.toml + Cargo.lock) or JS/TS (package.json + lock file).
199
+ * SBOMs are generated in parallel (see `batchConcurrency`) unless `continueOnError: false` (fail-fast sequential).
200
+ * With `opts.batchMetadata` / `TRUSTIFY_DA_BATCH_METADATA`, returns `{ analysis, metadata }` including validation and SBOM errors.
201
+ *
202
+ * @overload
203
+ * @param {string} workspaceRoot - Path to workspace root (containing lock file and workspace config)
204
+ * @param {boolean} [html=false] - true returns HTML, false returns JSON report
205
+ * @param {Options} [opts={}] - `batchConcurrency`, discovery ignores, `continueOnError` (default true), `batchMetadata` (default false)
206
+ * @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>|{ analysis: string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>, metadata: BatchAnalysisMetadata }>}
207
+ * @throws {Error} if workspace root invalid, no manifests found, no packages pass validation, no SBOMs produced, or backend request failed. When `opts.batchMetadata` is set, `error.batchMetadata` may be set on thrown errors.
208
+ */
209
+ declare function stackAnalysisBatch(workspaceRoot: string, html?: boolean | undefined, opts?: Options | undefined): Promise<string | {
210
+ [x: string]: any;
211
+ } | {
212
+ analysis: string | {
213
+ [x: string]: any;
214
+ };
215
+ metadata: BatchAnalysisMetadata;
216
+ }>;
93
217
  /**
94
218
  * @overload
95
219
  * @param {Array<string>} imageRefs
@@ -108,7 +232,7 @@ declare function imageAnalysis(imageRefs: Array<string>, html: true, opts?: Opti
108
232
  * @throws {Error}
109
233
  */
110
234
  declare function imageAnalysis(imageRefs: Array<string>, html: false, opts?: Options | undefined): Promise<{
111
- [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
235
+ [x: string]: any;
112
236
  }>;
113
237
  /**
114
238
  * Get image analysis report for a set of OCI image references.
@@ -121,7 +245,7 @@ declare function imageAnalysis(imageRefs: Array<string>, html: false, opts?: Opt
121
245
  * or backend request failed
122
246
  */
123
247
  declare function imageAnalysis(imageRefs: Array<string>, html?: boolean | undefined, opts?: Options | undefined): Promise<string | {
124
- [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
248
+ [x: string]: any;
125
249
  }>;
126
250
  /**
127
251
  * Validates the Exhort token.
@@ -130,3 +254,17 @@ declare function imageAnalysis(imageRefs: Array<string>, html?: boolean | undefi
130
254
  * @throws {Error} if the backend request failed.
131
255
  */
132
256
  declare function validateToken(opts?: Options): Promise<object>;
257
+ import { discoverMavenModules } from './providers/java_maven.js';
258
+ import { discoverGradleSubprojects } from './providers/java_gradle.js';
259
+ import { discoverGoWorkspaceModules } from './providers/golang_gomodules.js';
260
+ import { discoverUvWorkspaceMembers } from './providers/python_uv.js';
261
+ import { discoverWorkspacePackages } from './workspace.js';
262
+ import { discoverWorkspaceCrates } from './workspace.js';
263
+ import { validatePackageJson } from './workspace.js';
264
+ import { resolveWorkspaceDiscoveryIgnore } from './workspace.js';
265
+ import { filterManifestPathsByDiscoveryIgnore } from './workspace.js';
266
+ import { resolveContinueOnError } from './batch_opts.js';
267
+ import { resolveBatchMetadata } from './batch_opts.js';
268
+ export { discoverMavenModules, discoverGradleSubprojects, discoverGoWorkspaceModules, discoverUvWorkspaceMembers, discoverWorkspacePackages, discoverWorkspaceCrates, validatePackageJson, resolveWorkspaceDiscoveryIgnore, filterManifestPathsByDiscoveryIgnore, resolveContinueOnError, resolveBatchMetadata };
269
+ export { getProjectLicense, findLicenseFilePath, identifyLicense, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
270
+ export { generateReport, generateDeduplicationKey } from "./remediation_report.js";
package/dist/src/index.js CHANGED
@@ -1,16 +1,27 @@
1
+ import fs from 'node:fs';
1
2
  import path from "node:path";
2
3
  import { EOL } from "os";
3
- import { availableProviders, match } from './provider.js';
4
+ import pLimit from 'p-limit';
4
5
  import analysis from './analysis.js';
5
- import fs from 'node:fs';
6
+ import { resolveBatchMetadata, resolveContinueOnError } from './batch_opts.js';
7
+ import { getPackageVersion } from './package_version.js';
8
+ import { availableProviders, match } from './provider.js';
9
+ import { discoverGoWorkspaceModules } from './providers/golang_gomodules.js';
10
+ import { discoverGradleSubprojects } from './providers/java_gradle.js';
11
+ import { discoverMavenModules } from './providers/java_maven.js';
12
+ import { discoverUvWorkspaceMembers } from './providers/python_uv.js';
6
13
  import { getCustom } from "./tools.js";
7
- import.meta.dirname;
8
- import * as url from 'url';
14
+ import { discoverWorkspaceCrates, discoverWorkspacePackages, filterManifestPathsByDiscoveryIgnore, resolveWorkspaceDiscoveryIgnore, validatePackageJson, } from './workspace.js';
9
15
  export { parseImageRef } from "./oci_image/utils.js";
10
16
  export { ImageRef } from "./oci_image/images.js";
11
- export default { componentAnalysis, stackAnalysis, imageAnalysis, validateToken };
17
+ export { getProjectLicense, findLicenseFilePath, identifyLicense, getLicenseDetails, licensesFromReport, normalizeLicensesResponse, runLicenseCheck, getCompatibility } from "./license/index.js";
18
+ export { extractRemediations } from "./remediation.js";
19
+ export { generateReport, generateDeduplicationKey } from './remediation_report.js';
20
+ export default { componentAnalysis, stackAnalysis, stackAnalysisBatch, imageAnalysis, validateToken, generateSbom };
21
+ export { discoverMavenModules, discoverGradleSubprojects, discoverGoWorkspaceModules, discoverUvWorkspaceMembers, discoverWorkspacePackages, discoverWorkspaceCrates, validatePackageJson, resolveWorkspaceDiscoveryIgnore, filterManifestPathsByDiscoveryIgnore, resolveContinueOnError, resolveBatchMetadata, };
12
22
  /**
13
23
  * @typedef {{
24
+ * TRUSTIFY_DA_CARGO_PATH?: string | undefined,
14
25
  * TRUSTIFY_DA_DOCKER_PATH?: string | undefined,
15
26
  * TRUSTIFY_DA_GO_MVS_LOGIC_ENABLED?: string | undefined,
16
27
  * TRUSTIFY_DA_GO_PATH?: string | undefined,
@@ -29,19 +40,44 @@ export default { componentAnalysis, stackAnalysis, imageAnalysis, validateToken
29
40
  * TRUSTIFY_DA_PYTHON_PATH?: string | undefined,
30
41
  * TRUSTIFY_DA_PYTHON_VIRTUAL_ENV?: string | undefined,
31
42
  * TRUSTIFY_DA_PYTHON3_PATH?: string | undefined,
32
- * TRUSTIFY_DA_RECOMMENDATIONS_ENABLED?: string | undefined,
43
+ * TRUSTIFY_DA_PROVIDERS?: string | undefined,
44
+ * TRUSTIFY_DA_RECOMMEND?: string | undefined,
45
+ * TRUSTIFY_DA_SOURCES?: string | undefined,
33
46
  * TRUSTIFY_DA_SKOPEO_CONFIG_PATH?: string | undefined,
34
47
  * TRUSTIFY_DA_SKOPEO_PATH?: string | undefined,
35
48
  * TRUSTIFY_DA_SYFT_CONFIG_PATH?: string | undefined,
36
49
  * TRUSTIFY_DA_SYFT_PATH?: string | undefined,
37
50
  * TRUSTIFY_DA_YARN_PATH?: string | undefined,
51
+ * TRUSTIFY_DA_WORKSPACE_DIR?: string | undefined,
52
+ * TRUSTIFY_DA_LICENSE_CHECK?: string | undefined,
38
53
  * MATCH_MANIFEST_VERSIONS?: string | undefined,
39
54
  * TRUSTIFY_DA_SOURCE?: string | undefined,
40
55
  * TRUSTIFY_DA_TOKEN?: string | undefined,
41
56
  * TRUSTIFY_DA_TELEMETRY_ID?: string | undefined,
42
- * [key: string]: string | undefined,
57
+ * TRUSTIFY_DA_WORKSPACE_DIR?: string | undefined,
58
+ * batchConcurrency?: number | undefined,
59
+ * TRUSTIFY_DA_BATCH_CONCURRENCY?: string | undefined,
60
+ * workspaceDiscoveryIgnore?: string[] | undefined,
61
+ * TRUSTIFY_DA_WORKSPACE_DISCOVERY_IGNORE?: string | undefined,
62
+ * continueOnError?: boolean | undefined,
63
+ * TRUSTIFY_DA_CONTINUE_ON_ERROR?: string | undefined,
64
+ * batchMetadata?: boolean | undefined,
65
+ * TRUSTIFY_DA_BATCH_METADATA?: string | undefined,
66
+ * TRUSTIFY_DA_UV_PATH?: string | undefined,
67
+ * TRUSTIFY_DA_POETRY_PATH?: string | undefined,
68
+ * [key: string]: string | number | boolean | string[] | undefined,
43
69
  * }} Options
44
70
  */
71
+ /**
72
+ * @typedef {{
73
+ * workspaceRoot: string,
74
+ * ecosystem: 'javascript' | 'cargo' | 'pyproject' | 'unknown',
75
+ * total: number,
76
+ * successful: number,
77
+ * failed: number,
78
+ * errors: Array<{ manifestPath: string, phase: 'validation' | 'sbom', reason: string }>
79
+ * }} BatchAnalysisMetadata
80
+ */
45
81
  /**
46
82
  * Logs messages to the console if the TRUSTIFY_DA_DEBUG environment variable is set to "true".
47
83
  * @param {string} alongsideText - The text to prepend to the log message.
@@ -58,23 +94,7 @@ function logOptionsAndEnvironmentsVariables(alongsideText, valueToBePrinted) {
58
94
  * @private
59
95
  */
60
96
  function readAndPrintVersionFromPackageJson() {
61
- let dirName;
62
- // new ESM way in nodeJS ( since node version 22 ) to bring module directory.
63
- dirName = import.meta.dirname;
64
- // old ESM way in nodeJS ( before node versions 22.00 to bring module directory)
65
- if (!dirName) {
66
- dirName = url.fileURLToPath(new URL('.', import.meta.url));
67
- }
68
- try {
69
- if (__dirname) {
70
- dirName = __dirname;
71
- }
72
- }
73
- catch (e) {
74
- console.log("__dirname is not defined, continue with fileUrlPath");
75
- }
76
- let packageJson = JSON.parse(fs.readFileSync(path.join(dirName, "..", "package.json")).toString());
77
- logOptionsAndEnvironmentsVariables("trustify-da-javascript-client analysis started, version: ", packageJson.version);
97
+ logOptionsAndEnvironmentsVariables("trustify-da-javascript-client analysis started, version: ", getPackageVersion());
78
98
  }
79
99
  /**
80
100
  * This function is used to determine the Trustify DA backend URL.
@@ -127,7 +147,7 @@ export function selectTrustifyDABackend(opts = {}) {
127
147
  async function stackAnalysis(manifest, html = false, opts = {}) {
128
148
  const theUrl = selectTrustifyDABackend(opts);
129
149
  fs.accessSync(manifest, fs.constants.R_OK); // throws error if file unreadable
130
- let provider = match(manifest, availableProviders); // throws error if no matching provider
150
+ let provider = match(manifest, availableProviders, opts); // throws error if no matching provider
131
151
  return await analysis.requestStack(provider, manifest, theUrl, html, opts); // throws error request sending failed
132
152
  }
133
153
  /**
@@ -141,8 +161,10 @@ async function componentAnalysis(manifest, opts = {}) {
141
161
  const theUrl = selectTrustifyDABackend(opts);
142
162
  fs.accessSync(manifest, fs.constants.R_OK);
143
163
  opts["manifest-type"] = path.basename(manifest);
144
- let provider = match(manifest, availableProviders); // throws error if no matching provider
145
- return await analysis.requestComponent(provider, manifest, theUrl, opts); // throws error request sending failed
164
+ let provider = match(manifest, availableProviders, opts); // throws error if no matching provider
165
+ const result = await analysis.requestComponent(provider, manifest, theUrl, opts); // throws error request sending failed
166
+ result.packageManager = provider.packageManagerName();
167
+ return result;
146
168
  }
147
169
  /**
148
170
  * @overload
@@ -174,6 +196,319 @@ async function imageAnalysis(imageRefs, html = false, opts = {}) {
174
196
  const theUrl = selectTrustifyDABackend(opts);
175
197
  return await analysis.requestImages(imageRefs, theUrl, html, opts);
176
198
  }
199
+ /**
200
+ * Max concurrent SBOM generations for batch workspace analysis. Env/opts override default 10.
201
+ * @param {Options} opts
202
+ * @returns {number}
203
+ * @private
204
+ */
205
+ function resolveBatchConcurrency(opts) {
206
+ const fromEnv = getCustom('TRUSTIFY_DA_BATCH_CONCURRENCY', null, opts);
207
+ const raw = opts.batchConcurrency ?? fromEnv ?? '10';
208
+ const n = typeof raw === 'number' ? raw : parseInt(String(raw), 10);
209
+ if (!Number.isFinite(n) || n < 1) {
210
+ return 10;
211
+ }
212
+ return Math.min(256, n);
213
+ }
214
+ /**
215
+ * @param {string} root
216
+ * @param {'javascript' | 'cargo' | 'unknown'} ecosystem
217
+ * @param {number} totalSbomAttempts
218
+ * @param {number} successfulSbomCount
219
+ * @param {Array<{ manifestPath: string, phase: 'validation' | 'sbom', reason: string }>} errors
220
+ * @returns {BatchAnalysisMetadata}
221
+ * @private
222
+ */
223
+ function buildBatchAnalysisMetadata(root, ecosystem, totalSbomAttempts, successfulSbomCount, errors) {
224
+ return {
225
+ workspaceRoot: root,
226
+ ecosystem,
227
+ total: totalSbomAttempts,
228
+ successful: successfulSbomCount,
229
+ failed: errors.length,
230
+ errors: [...errors],
231
+ };
232
+ }
233
+ /**
234
+ * Generate a CycloneDX SBOM from a manifest file. No backend HTTP request is made.
235
+ *
236
+ * @param {string} manifestPath - path to the manifest file (e.g. pom.xml, package.json)
237
+ * @param {Options} [opts={}] - optional options (e.g. workspace dir, tool paths)
238
+ * @returns {Promise<object>} parsed CycloneDX SBOM JSON object
239
+ * @throws {Error} if the manifest is unsupported or SBOM generation fails
240
+ */
241
+ export async function generateSbom(manifestPath, opts = {}) {
242
+ fs.accessSync(manifestPath, fs.constants.R_OK);
243
+ const result = await generateOneSbom(manifestPath, opts);
244
+ if (!result.ok) {
245
+ throw new Error(`Failed to generate SBOM for ${result.manifestPath}: ${result.reason}`);
246
+ }
247
+ return result.sbom;
248
+ }
249
+ /**
250
+ * @typedef {{ ok: true, purl: string, sbom: object } | { ok: false, manifestPath: string, reason: string }} SbomResult
251
+ */
252
+ /**
253
+ * Generate an SBOM for a single manifest, returning a normalized result.
254
+ *
255
+ * @param {string} manifestPath
256
+ * @param {Options} workspaceOpts - opts with `TRUSTIFY_DA_WORKSPACE_DIR` set
257
+ * @returns {Promise<SbomResult>}
258
+ * @private
259
+ */
260
+ async function generateOneSbom(manifestPath, workspaceOpts) {
261
+ const provider = match(manifestPath, availableProviders, workspaceOpts);
262
+ const provided = await provider.provideStack(manifestPath, workspaceOpts);
263
+ const sbom = JSON.parse(provided.content);
264
+ const purl = sbom?.metadata?.component?.purl || sbom?.metadata?.component?.['bom-ref'];
265
+ if (!purl) {
266
+ return { ok: false, manifestPath, reason: 'missing purl in SBOM' };
267
+ }
268
+ return { ok: true, purl, sbom };
269
+ }
270
+ /**
271
+ * Detect the workspace ecosystem and discover manifest paths.
272
+ *
273
+ * @param {string} root - Resolved workspace root
274
+ * @param {Options} opts
275
+ * @returns {Promise<{ ecosystem: 'javascript' | 'cargo' | 'maven' | 'gradle' | 'gomodules' | 'pyproject' | 'unknown', manifestPaths: string[] }>}
276
+ * @private
277
+ */
278
+ async function detectWorkspaceManifests(root, opts) {
279
+ const cargoToml = path.join(root, 'Cargo.toml');
280
+ const cargoLock = path.join(root, 'Cargo.lock');
281
+ const packageJson = path.join(root, 'package.json');
282
+ const pomXml = path.join(root, 'pom.xml');
283
+ if (fs.existsSync(cargoToml) && fs.existsSync(cargoLock)) {
284
+ return { ecosystem: 'cargo', manifestPaths: await discoverWorkspaceCrates(root, opts) };
285
+ }
286
+ if (fs.existsSync(pomXml)) {
287
+ const manifestPaths = await discoverMavenModules(root, opts);
288
+ if (manifestPaths.length > 0) {
289
+ return { ecosystem: 'maven', manifestPaths };
290
+ }
291
+ }
292
+ const hasGradleSettings = fs.existsSync(path.join(root, 'settings.gradle'))
293
+ || fs.existsSync(path.join(root, 'settings.gradle.kts'));
294
+ if (hasGradleSettings) {
295
+ const manifestPaths = await discoverGradleSubprojects(root, opts);
296
+ if (manifestPaths.length > 0) {
297
+ return { ecosystem: 'gradle', manifestPaths };
298
+ }
299
+ }
300
+ if (fs.existsSync(path.join(root, 'go.work'))) {
301
+ const manifestPaths = await discoverGoWorkspaceModules(root, opts);
302
+ if (manifestPaths.length > 0) {
303
+ return { ecosystem: 'gomodules', manifestPaths };
304
+ }
305
+ }
306
+ if (fs.existsSync(path.join(root, 'pyproject.toml')) && fs.existsSync(path.join(root, 'uv.lock'))) {
307
+ const manifestPaths = await discoverUvWorkspaceMembers(root, opts);
308
+ if (manifestPaths.length > 0) {
309
+ return { ecosystem: 'pyproject', manifestPaths };
310
+ }
311
+ }
312
+ const hasJsLock = fs.existsSync(path.join(root, 'bun.lock'))
313
+ || fs.existsSync(path.join(root, 'pnpm-lock.yaml'))
314
+ || fs.existsSync(path.join(root, 'yarn.lock'))
315
+ || fs.existsSync(path.join(root, 'package-lock.json'));
316
+ if (fs.existsSync(packageJson) && hasJsLock) {
317
+ let manifestPaths = await discoverWorkspacePackages(root, opts);
318
+ if (manifestPaths.length === 0) {
319
+ manifestPaths = [packageJson];
320
+ }
321
+ return { ecosystem: 'javascript', manifestPaths };
322
+ }
323
+ return { ecosystem: 'unknown', manifestPaths: [] };
324
+ }
325
+ /**
326
+ * Validate discovered JS package.json manifests, collecting errors.
327
+ *
328
+ * @param {string[]} manifestPaths
329
+ * @param {boolean} continueOnError
330
+ * @param {Array<{ manifestPath: string, phase: 'validation' | 'sbom', reason: string }>} collectedErrors - mutated in place
331
+ * @returns {{ validPaths: string[] }}
332
+ * @throws {Error} on first invalid manifest when `continueOnError` is false
333
+ * @private
334
+ */
335
+ function validateJsManifests(manifestPaths, continueOnError, collectedErrors) {
336
+ const validPaths = [];
337
+ for (const p of manifestPaths) {
338
+ const v = validatePackageJson(p);
339
+ if (v.valid) {
340
+ validPaths.push(p);
341
+ }
342
+ else {
343
+ collectedErrors.push({ manifestPath: p, phase: 'validation', reason: v.error });
344
+ console.warn(`Skipping invalid package.json (${v.error}): ${p}`);
345
+ if (!continueOnError) {
346
+ throw new Error(`Invalid package.json (${v.error}): ${p}`);
347
+ }
348
+ }
349
+ }
350
+ return { validPaths };
351
+ }
352
+ /**
353
+ * Generate SBOMs for all manifests. In fail-fast mode, stops on first error.
354
+ * In continue-on-error mode, runs concurrently and collects failures.
355
+ *
356
+ * @param {string[]} manifestPaths
357
+ * @param {Options} workspaceOpts
358
+ * @param {boolean} continueOnError
359
+ * @param {number} concurrency
360
+ * @param {Array<{ manifestPath: string, phase: 'validation' | 'sbom', reason: string }>} collectedErrors - mutated in place
361
+ * @returns {Promise<Object.<string, object>>} sbomByPurl map
362
+ * @throws {Error} on first SBOM failure when `continueOnError` is false
363
+ * @private
364
+ */
365
+ async function generateSboms(manifestPaths, workspaceOpts, continueOnError, concurrency, collectedErrors) {
366
+ /** @type {SbomResult[]} */
367
+ const results = [];
368
+ if (!continueOnError) {
369
+ for (const manifestPath of manifestPaths) {
370
+ const result = await generateOneSbom(manifestPath, workspaceOpts);
371
+ if (!result.ok) {
372
+ collectedErrors.push({ manifestPath: result.manifestPath, phase: 'sbom', reason: result.reason });
373
+ throw new Error(`${result.manifestPath}: ${result.reason}`);
374
+ }
375
+ results.push(result);
376
+ }
377
+ }
378
+ else {
379
+ const limit = pLimit(concurrency);
380
+ const settled = await Promise.all(manifestPaths.map(manifestPath => limit(async () => {
381
+ try {
382
+ return await generateOneSbom(manifestPath, workspaceOpts);
383
+ }
384
+ catch (err) {
385
+ const msg = err instanceof Error ? err.message : String(err);
386
+ if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
387
+ console.log(`Skipping ${manifestPath}: ${msg}`);
388
+ }
389
+ return { ok: false, manifestPath, reason: msg };
390
+ }
391
+ })));
392
+ for (const r of settled) {
393
+ results.push(r);
394
+ if (!r.ok) {
395
+ collectedErrors.push({ manifestPath: r.manifestPath, phase: 'sbom', reason: r.reason });
396
+ }
397
+ }
398
+ }
399
+ const sbomByPurl = {};
400
+ for (const r of results) {
401
+ if (r.ok) {
402
+ sbomByPurl[r.purl] = r.sbom;
403
+ }
404
+ }
405
+ return sbomByPurl;
406
+ }
407
+ /**
408
+ * Create an Error with optional `batchMetadata` attached.
409
+ * @param {string} message
410
+ * @param {boolean} wantMetadata
411
+ * @param {BatchAnalysisMetadata} [metadata]
412
+ * @returns {Error}
413
+ * @private
414
+ */
415
+ function batchError(message, wantMetadata, metadata) {
416
+ const err = new Error(message);
417
+ if (wantMetadata && metadata) {
418
+ err.batchMetadata = metadata;
419
+ }
420
+ return err;
421
+ }
422
+ /**
423
+ * @overload
424
+ * @param {string} workspaceRoot
425
+ * @param {true} html
426
+ * @param {Options & { batchMetadata: true }} opts
427
+ * @returns {Promise<{ analysis: string, metadata: BatchAnalysisMetadata }>}
428
+ * @throws {Error}
429
+ */
430
+ /**
431
+ * @overload
432
+ * @param {string} workspaceRoot
433
+ * @param {true} html
434
+ * @param {Options & { batchMetadata?: false }} [opts={}]
435
+ * @returns {Promise<string>}
436
+ * @throws {Error}
437
+ */
438
+ /**
439
+ * @overload
440
+ * @param {string} workspaceRoot
441
+ * @param {false} html
442
+ * @param {Options & { batchMetadata: true }} opts
443
+ * @returns {Promise<{ analysis: Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>, metadata: BatchAnalysisMetadata }>}
444
+ * @throws {Error}
445
+ */
446
+ /**
447
+ * @overload
448
+ * @param {string} workspaceRoot
449
+ * @param {false} html
450
+ * @param {Options & { batchMetadata?: false }} [opts={}]
451
+ * @returns {Promise<Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>>}
452
+ * @throws {Error}
453
+ */
454
+ /**
455
+ * Get stack analysis for all workspace packages/crates (batch).
456
+ * Detects ecosystem from workspace root: Cargo (Cargo.toml + Cargo.lock) or JS/TS (package.json + lock file).
457
+ * SBOMs are generated in parallel (see `batchConcurrency`) unless `continueOnError: false` (fail-fast sequential).
458
+ * With `opts.batchMetadata` / `TRUSTIFY_DA_BATCH_METADATA`, returns `{ analysis, metadata }` including validation and SBOM errors.
459
+ *
460
+ * @overload
461
+ * @param {string} workspaceRoot - Path to workspace root (containing lock file and workspace config)
462
+ * @param {boolean} [html=false] - true returns HTML, false returns JSON report
463
+ * @param {Options} [opts={}] - `batchConcurrency`, discovery ignores, `continueOnError` (default true), `batchMetadata` (default false)
464
+ * @returns {Promise<string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>|{ analysis: string|Object.<string, import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>, metadata: BatchAnalysisMetadata }>}
465
+ * @throws {Error} if workspace root invalid, no manifests found, no packages pass validation, no SBOMs produced, or backend request failed. When `opts.batchMetadata` is set, `error.batchMetadata` may be set on thrown errors.
466
+ */
467
+ async function stackAnalysisBatch(workspaceRoot, html = false, opts = {}) {
468
+ const theUrl = selectTrustifyDABackend(opts);
469
+ const root = path.resolve(workspaceRoot);
470
+ fs.accessSync(root, fs.constants.R_OK);
471
+ const continueOnError = resolveContinueOnError(opts);
472
+ const wantMetadata = resolveBatchMetadata(opts);
473
+ /** @type {Array<{ manifestPath: string, phase: 'validation' | 'sbom', reason: string }>} */
474
+ const collectedErrors = [];
475
+ const { ecosystem, manifestPaths: discovered } = await detectWorkspaceManifests(root, opts);
476
+ let manifestPaths = discovered;
477
+ if (ecosystem === 'javascript') {
478
+ try {
479
+ const { validPaths } = validateJsManifests(manifestPaths, continueOnError, collectedErrors);
480
+ manifestPaths = validPaths;
481
+ }
482
+ catch (err) {
483
+ throw batchError(err.message, wantMetadata, buildBatchAnalysisMetadata(root, ecosystem, 0, 0, collectedErrors));
484
+ }
485
+ if (manifestPaths.length === 0 && discovered.length > 0) {
486
+ const detail = collectedErrors.map(e => `${e.manifestPath}: ${e.reason}`).join('; ');
487
+ throw batchError(`No valid packages after validation at ${root}. ${detail}`, wantMetadata, buildBatchAnalysisMetadata(root, ecosystem, 0, 0, collectedErrors));
488
+ }
489
+ }
490
+ if (manifestPaths.length === 0) {
491
+ throw new Error(`No workspace manifests found at ${root}. Ensure a supported workspace root exists (Cargo.toml+Cargo.lock, pom.xml, build.gradle, go.work, pyproject.toml+uv.lock, or package.json+lock file).`);
492
+ }
493
+ const workspaceOpts = { ...opts, TRUSTIFY_DA_WORKSPACE_DIR: root };
494
+ const concurrency = resolveBatchConcurrency(opts);
495
+ let sbomByPurl;
496
+ try {
497
+ sbomByPurl = await generateSboms(manifestPaths, workspaceOpts, continueOnError, concurrency, collectedErrors);
498
+ }
499
+ catch (err) {
500
+ throw batchError(err.message, wantMetadata, buildBatchAnalysisMetadata(root, ecosystem, manifestPaths.length, 0, collectedErrors));
501
+ }
502
+ if (Object.keys(sbomByPurl).length === 0) {
503
+ throw batchError(`No valid SBOMs produced from ${manifestPaths.length} manifest(s) at ${root}`, wantMetadata, buildBatchAnalysisMetadata(root, ecosystem, manifestPaths.length, 0, collectedErrors));
504
+ }
505
+ const analysisResult = await analysis.requestStackBatch(sbomByPurl, theUrl, html, opts);
506
+ const meta = buildBatchAnalysisMetadata(root, ecosystem, manifestPaths.length, Object.keys(sbomByPurl).length, collectedErrors);
507
+ if (wantMetadata) {
508
+ return { analysis: analysisResult, metadata: meta };
509
+ }
510
+ return analysisResult;
511
+ }
177
512
  /**
178
513
  * Validates the Exhort token.
179
514
  * @param {Options} [opts={}] - Optional parameters, potentially including token override.