@trustify-da/trustify-da-javascript-client 0.3.0-ea.bbe2094 → 0.3.0-ea.bdaadb2

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 (75) hide show
  1. package/README.md +177 -3
  2. package/dist/package.json +29 -22
  3. package/dist/src/analysis.d.ts +14 -7
  4. package/dist/src/analysis.js +29 -13
  5. package/dist/src/cli.js +106 -2
  6. package/dist/src/cyclone_dx_sbom.d.ts +13 -0
  7. package/dist/src/cyclone_dx_sbom.js +50 -4
  8. package/dist/src/index.d.ts +14 -10
  9. package/dist/src/index.js +15 -26
  10. package/dist/src/license/index.d.ts +3 -3
  11. package/dist/src/license/index.js +4 -1
  12. package/dist/src/license/license_utils.js +4 -1
  13. package/dist/src/license/licenses_api.d.ts +2 -2
  14. package/dist/src/oci_image/images.d.ts +1 -1
  15. package/dist/src/oci_image/images.js +15 -1
  16. package/dist/src/oci_image/utils.d.ts +5 -5
  17. package/dist/src/package_version.d.ts +8 -0
  18. package/dist/src/package_version.js +31 -0
  19. package/dist/src/provider.d.ts +4 -3
  20. package/dist/src/provider.js +4 -2
  21. package/dist/src/providers/base_java.d.ts +67 -3
  22. package/dist/src/providers/base_java.js +80 -23
  23. package/dist/src/providers/base_javascript.d.ts +68 -9
  24. package/dist/src/providers/base_javascript.js +112 -3
  25. package/dist/src/providers/base_pyproject.d.ts +10 -5
  26. package/dist/src/providers/base_pyproject.js +7 -0
  27. package/dist/src/providers/containerfile_parser.d.ts +5 -0
  28. package/dist/src/providers/containerfile_parser.js +20 -0
  29. package/dist/src/providers/golang_gomodules.d.ts +2 -1
  30. package/dist/src/providers/golang_gomodules.js +44 -3
  31. package/dist/src/providers/java_gradle.d.ts +48 -0
  32. package/dist/src/providers/java_gradle.js +194 -15
  33. package/dist/src/providers/java_gradle_groovy.d.ts +1 -1
  34. package/dist/src/providers/java_gradle_kotlin.d.ts +1 -1
  35. package/dist/src/providers/java_maven.d.ts +30 -5
  36. package/dist/src/providers/java_maven.js +168 -7
  37. package/dist/src/providers/javascript_bun.d.ts +12 -0
  38. package/dist/src/providers/javascript_bun.js +42 -1
  39. package/dist/src/providers/javascript_npm.d.ts +19 -1
  40. package/dist/src/providers/javascript_npm.js +40 -1
  41. package/dist/src/providers/javascript_pnpm.d.ts +13 -0
  42. package/dist/src/providers/javascript_pnpm.js +47 -1
  43. package/dist/src/providers/javascript_yarn.d.ts +12 -0
  44. package/dist/src/providers/javascript_yarn.js +75 -2
  45. package/dist/src/providers/oci_dockerfile.d.ts +51 -0
  46. package/dist/src/providers/oci_dockerfile.js +177 -0
  47. package/dist/src/providers/processors/yarn_berry_processor.d.ts +6 -2
  48. package/dist/src/providers/processors/yarn_berry_processor.js +3 -2
  49. package/dist/src/providers/processors/yarn_classic_processor.d.ts +6 -2
  50. package/dist/src/providers/processors/yarn_classic_processor.js +8 -6
  51. package/dist/src/providers/python_controller.js +61 -4
  52. package/dist/src/providers/python_pip.d.ts +1 -0
  53. package/dist/src/providers/python_pip.js +1 -1
  54. package/dist/src/providers/python_poetry.d.ts +31 -8
  55. package/dist/src/providers/python_poetry.js +62 -14
  56. package/dist/src/providers/python_uv.d.ts +5 -14
  57. package/dist/src/providers/python_uv.js +44 -2
  58. package/dist/src/providers/rust_cargo.d.ts +6 -2
  59. package/dist/src/providers/rust_cargo.js +101 -33
  60. package/dist/src/providers/tree-sitter-containerfile.wasm +0 -0
  61. package/dist/src/remediate.d.ts +21 -0
  62. package/dist/src/remediate.js +149 -0
  63. package/dist/src/remediation.d.ts +57 -0
  64. package/dist/src/remediation.js +436 -0
  65. package/dist/src/remediation_report.d.ts +40 -0
  66. package/dist/src/remediation_report.js +159 -0
  67. package/dist/src/sbom.d.ts +11 -0
  68. package/dist/src/sbom.js +10 -0
  69. package/dist/src/tools.d.ts +2 -2
  70. package/dist/src/tools.js +24 -1
  71. package/dist/src/updaters/maven_updater.d.ts +26 -0
  72. package/dist/src/updaters/maven_updater.js +322 -0
  73. package/dist/src/updaters/toml_updater.d.ts +33 -0
  74. package/dist/src/updaters/toml_updater.js +244 -0
  75. package/package.json +30 -23
package/dist/src/cli.js CHANGED
@@ -4,6 +4,7 @@ import * as path from "path";
4
4
  import yargs from 'yargs';
5
5
  import { hideBin } from 'yargs/helpers';
6
6
  import { getProjectLicense, getLicenseDetails } from './license/index.js';
7
+ import { runRemediation } from './remediate.js';
7
8
  import client, { selectTrustifyDABackend, generateSbom } from './index.js';
8
9
  // command for component analysis take manifest type and content
9
10
  const component = {
@@ -19,11 +20,25 @@ const component = {
19
20
  desc: 'Workspace root directory (for monorepos; lock file is expected here)',
20
21
  type: 'string',
21
22
  normalize: true,
23
+ },
24
+ providers: {
25
+ desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
26
+ type: 'string',
27
+ },
28
+ sources: {
29
+ desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
30
+ type: 'string',
22
31
  }
23
32
  }),
24
33
  handler: async (args) => {
25
34
  let manifestName = args['/path/to/manifest'];
26
35
  const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
36
+ if (args.providers) {
37
+ opts.TRUSTIFY_DA_PROVIDERS = args.providers;
38
+ }
39
+ if (args.sources) {
40
+ opts.TRUSTIFY_DA_SOURCES = args.sources;
41
+ }
27
42
  let res = await client.componentAnalysis(manifestName, opts);
28
43
  console.log(JSON.stringify(res, null, 2));
29
44
  }
@@ -72,6 +87,14 @@ const image = {
72
87
  desc: 'For JSON report, get only the \'summary\'',
73
88
  type: 'boolean',
74
89
  conflicts: 'html'
90
+ },
91
+ providers: {
92
+ desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
93
+ type: 'string',
94
+ },
95
+ sources: {
96
+ desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
97
+ type: 'string',
75
98
  }
76
99
  }),
77
100
  handler: async (args) => {
@@ -81,7 +104,14 @@ const image = {
81
104
  }
82
105
  let html = args['html'];
83
106
  let summary = args['summary'];
84
- let res = await client.imageAnalysis(imageRefs, html);
107
+ const opts = {};
108
+ if (args.providers) {
109
+ opts.TRUSTIFY_DA_PROVIDERS = args.providers;
110
+ }
111
+ if (args.sources) {
112
+ opts.TRUSTIFY_DA_SOURCES = args.sources;
113
+ }
114
+ let res = await client.imageAnalysis(imageRefs, html, opts);
85
115
  if (summary && !html) {
86
116
  let summaries = {};
87
117
  for (let [imageRef, report] of Object.entries(res)) {
@@ -132,6 +162,14 @@ const stack = {
132
162
  desc: 'Workspace root directory (for monorepos; lock file is expected here)',
133
163
  type: 'string',
134
164
  normalize: true,
165
+ },
166
+ providers: {
167
+ desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
168
+ type: 'string',
169
+ },
170
+ sources: {
171
+ desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
172
+ type: 'string',
135
173
  }
136
174
  }),
137
175
  handler: async (args) => {
@@ -139,6 +177,12 @@ const stack = {
139
177
  let html = args['html'];
140
178
  let summary = args['summary'];
141
179
  const opts = args.workspaceDir ? { TRUSTIFY_DA_WORKSPACE_DIR: args.workspaceDir } : {};
180
+ if (args.providers) {
181
+ opts.TRUSTIFY_DA_PROVIDERS = args.providers;
182
+ }
183
+ if (args.sources) {
184
+ opts.TRUSTIFY_DA_SOURCES = args.sources;
185
+ }
142
186
  let theProvidersSummary = new Map();
143
187
  let theProvidersObject = {};
144
188
  let res = await client.stackAnalysis(manifest, html, opts);
@@ -201,6 +245,14 @@ const stackBatch = {
201
245
  desc: 'Stop on first invalid package.json or SBOM error (env: TRUSTIFY_DA_CONTINUE_ON_ERROR=false)',
202
246
  type: 'boolean',
203
247
  default: false,
248
+ },
249
+ providers: {
250
+ desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
251
+ type: 'string',
252
+ },
253
+ sources: {
254
+ desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
255
+ type: 'string',
204
256
  }
205
257
  }),
206
258
  handler: async (args) => {
@@ -221,6 +273,12 @@ const stackBatch = {
221
273
  if (args.failFast) {
222
274
  opts.continueOnError = false;
223
275
  }
276
+ if (args.providers) {
277
+ opts.TRUSTIFY_DA_PROVIDERS = args.providers;
278
+ }
279
+ if (args.sources) {
280
+ opts.TRUSTIFY_DA_SOURCES = args.sources;
281
+ }
224
282
  let res = await client.stackAnalysisBatch(workspaceRoot, html, opts);
225
283
  const batchAnalysis = res && typeof res === 'object' && res != null && 'analysis' in res ? res.analysis : res;
226
284
  if (summary && !html && typeof batchAnalysis === 'object') {
@@ -373,9 +431,54 @@ const sbom = {
373
431
  }
374
432
  }
375
433
  };
434
+ const remediate = {
435
+ command: 'remediate <path>',
436
+ desc: 'Scan and apply vulnerability remediations to manifest files',
437
+ builder: yargs => yargs.positional('path', {
438
+ desc: 'Path to manifest file or directory',
439
+ type: 'string',
440
+ normalize: true,
441
+ }).options({
442
+ 'dry-run': {
443
+ alias: 'd',
444
+ type: 'boolean',
445
+ desc: 'Preview changes without modifying files',
446
+ },
447
+ providers: {
448
+ desc: 'Comma-separated list of vulnerability providers (env: TRUSTIFY_DA_PROVIDERS)',
449
+ type: 'string',
450
+ },
451
+ sources: {
452
+ desc: 'Comma-separated list of vulnerability sources (env: TRUSTIFY_DA_SOURCES)',
453
+ type: 'string',
454
+ },
455
+ 'group-by': {
456
+ type: 'string',
457
+ choices: ['dependency', 'bundle'],
458
+ default: 'dependency',
459
+ desc: 'Report grouping strategy',
460
+ },
461
+ }),
462
+ handler: async (args) => {
463
+ try {
464
+ const result = await runRemediation(args.path, {
465
+ dryRun: args['dry-run'],
466
+ providers: args.providers,
467
+ sources: args.sources,
468
+ groupBy: args['group-by'],
469
+ });
470
+ console.log(result.output);
471
+ process.exit(result.exitCode);
472
+ }
473
+ catch (err) {
474
+ console.error(err.message);
475
+ process.exit(1);
476
+ }
477
+ }
478
+ };
376
479
  // parse and invoke the command
377
480
  yargs(hideBin(process.argv))
378
- .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}`)
481
+ .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}`)
379
482
  .command(stack)
380
483
  .command(stackBatch)
381
484
  .command(component)
@@ -383,6 +486,7 @@ yargs(hideBin(process.argv))
383
486
  .command(validateToken)
384
487
  .command(license)
385
488
  .command(sbom)
489
+ .command(remediate)
386
490
  .scriptName('')
387
491
  .version(false)
388
492
  .demandCommand(1)
@@ -26,6 +26,19 @@ export default class CycloneDxSbom {
26
26
  alg: string;
27
27
  content: string;
28
28
  }>): CycloneDxSbom;
29
+ /**
30
+ * Attach hashes to already-added components by matching their PURL. This is a
31
+ * post-processing step so ecosystem-specific hash sources (e.g. Maven reading
32
+ * the local .m2 cache) can enrich the SBOM without threading their concern
33
+ * through the shared dependency-tree parser. Components without a matching
34
+ * entry, or whose hashes are already set, are left untouched.
35
+ * @param {Map<string, Array<{alg: string, content: string}>>} hashMap - PURL→hashes map
36
+ * @return {CycloneDxSbom} the updated SBOM
37
+ */
38
+ attachHashes(hashMap: Map<string, Array<{
39
+ alg: string;
40
+ content: string;
41
+ }>>): CycloneDxSbom;
29
42
  /** @param {{}} opts - various options, settings and configuration of application.
30
43
  * @return String CycloneDx Sbom json object in a string format
31
44
  */
@@ -1,5 +1,12 @@
1
1
  import { EOL } from "os";
2
2
  import { PackageURL } from "packageurl-js";
3
+ import { getPackageVersion } from "./package_version.js";
4
+ /**
5
+ * This client's version, read once at module load, used to populate
6
+ * metadata.tools.components[].version in the generated SBOM.
7
+ * @type {string|undefined}
8
+ */
9
+ const PACKAGE_VERSION = getPackageVersion();
3
10
  /**
4
11
  *
5
12
  * @param component {PackageURL}
@@ -41,12 +48,20 @@ function getComponent(component, type, scope, licenses, hashes) {
41
48
  // Add licenses if provided (CycloneDX format). Callers must provide valid SPDX identifiers.
42
49
  if (licenses) {
43
50
  const licenseArray = Array.isArray(licenses) ? licenses : [licenses];
44
- componentObject.licenses = licenseArray.map(lic => {
51
+ const validLicenses = licenseArray
52
+ .map(lic => {
45
53
  if (typeof lic === 'string') {
46
54
  return { license: { id: lic } };
47
55
  }
48
- return lic;
49
- });
56
+ if (typeof lic === 'object' && lic !== null && ('license' in lic || 'expression' in lic)) {
57
+ return lic;
58
+ }
59
+ return null;
60
+ })
61
+ .filter(Boolean);
62
+ if (validLicenses.length > 0) {
63
+ componentObject.licenses = validLicenses;
64
+ }
50
65
  }
51
66
  // Add hashes if provided (CycloneDX 1.4 format).
52
67
  if (hashes && hashes.length > 0) {
@@ -127,6 +142,30 @@ export default class CycloneDxSbom {
127
142
  }
128
143
  return this;
129
144
  }
145
+ /**
146
+ * Attach hashes to already-added components by matching their PURL. This is a
147
+ * post-processing step so ecosystem-specific hash sources (e.g. Maven reading
148
+ * the local .m2 cache) can enrich the SBOM without threading their concern
149
+ * through the shared dependency-tree parser. Components without a matching
150
+ * entry, or whose hashes are already set, are left untouched.
151
+ * @param {Map<string, Array<{alg: string, content: string}>>} hashMap - PURL→hashes map
152
+ * @return {CycloneDxSbom} the updated SBOM
153
+ */
154
+ attachHashes(hashMap) {
155
+ if (!hashMap || hashMap.size === 0) {
156
+ return this;
157
+ }
158
+ for (const component of this.components) {
159
+ if (component.hashes) {
160
+ continue;
161
+ }
162
+ const hashes = hashMap.get(component.purl);
163
+ if (hashes && hashes.length > 0) {
164
+ component.hashes = hashes;
165
+ }
166
+ }
167
+ return this;
168
+ }
130
169
  /** @param {{}} opts - various options, settings and configuration of application.
131
170
  * @return String CycloneDx Sbom json object in a string format
132
171
  */
@@ -136,11 +175,18 @@ export default class CycloneDxSbom {
136
175
  const rootPurl = this.rootComponent?.purl;
137
176
  this.sbomObject = {
138
177
  "bomFormat": "CycloneDX",
139
- "specVersion": "1.4",
178
+ "specVersion": "1.6",
140
179
  "version": 1,
141
180
  "metadata": {
142
181
  "timestamp": new Date(),
143
182
  "component": this.rootComponent,
183
+ "tools": {
184
+ "components": [{
185
+ "type": "application",
186
+ "name": "trustify-da-javascript-client",
187
+ "version": PACKAGE_VERSION
188
+ }]
189
+ },
144
190
  "properties": new Array()
145
191
  },
146
192
  "components": this.components.filter(c => c.purl !== rootPurl),
@@ -24,6 +24,7 @@ export function selectTrustifyDABackend(opts?: {
24
24
  export function generateSbom(manifestPath: string, opts?: Options): Promise<object>;
25
25
  export { parseImageRef } from "./oci_image/utils.js";
26
26
  export { ImageRef } from "./oci_image/images.js";
27
+ export { extractRemediations } from "./remediation.js";
27
28
  declare namespace _default {
28
29
  export { componentAnalysis };
29
30
  export { stackAnalysis };
@@ -53,7 +54,9 @@ export type Options = {
53
54
  TRUSTIFY_DA_PYTHON_PATH?: string | undefined;
54
55
  TRUSTIFY_DA_PYTHON_VIRTUAL_ENV?: string | undefined;
55
56
  TRUSTIFY_DA_PYTHON3_PATH?: string | undefined;
56
- TRUSTIFY_DA_RECOMMENDATIONS_ENABLED?: string | undefined;
57
+ TRUSTIFY_DA_PROVIDERS?: string | undefined;
58
+ TRUSTIFY_DA_RECOMMEND?: string | undefined;
59
+ TRUSTIFY_DA_SOURCES?: string | undefined;
57
60
  TRUSTIFY_DA_SKOPEO_CONFIG_PATH?: string | undefined;
58
61
  TRUSTIFY_DA_SKOPEO_PATH?: string | undefined;
59
62
  TRUSTIFY_DA_SYFT_CONFIG_PATH?: string | undefined;
@@ -106,7 +109,7 @@ export type SbomResult = {
106
109
  * @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
107
110
  * @throws {Error} if no matching provider, failed to get create content, or backend request failed
108
111
  */
109
- 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>;
110
113
  /**
111
114
  * @overload
112
115
  * @param {string} manifest
@@ -124,7 +127,7 @@ declare function stackAnalysis(manifest: string, html: true, opts?: Options | un
124
127
  * @returns {Promise<import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport>}
125
128
  * @throws {Error}
126
129
  */
127
- 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>;
128
131
  /**
129
132
  * Get stack analysis report for a manifest file.
130
133
  * @overload
@@ -135,7 +138,7 @@ declare function stackAnalysis(manifest: string, html: false, opts?: Options | u
135
138
  * @throws {Error} if manifest inaccessible, no matching provider, failed to get create content,
136
139
  * or backend request failed
137
140
  */
138
- 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>;
139
142
  /**
140
143
  * @overload
141
144
  * @param {string} workspaceRoot
@@ -173,7 +176,7 @@ declare function stackAnalysisBatch(workspaceRoot: string, html: false, opts: Op
173
176
  batchMetadata: true;
174
177
  }): Promise<{
175
178
  analysis: {
176
- [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
179
+ [x: string]: any;
177
180
  };
178
181
  metadata: BatchAnalysisMetadata;
179
182
  }>;
@@ -188,7 +191,7 @@ declare function stackAnalysisBatch(workspaceRoot: string, html: false, opts: Op
188
191
  declare function stackAnalysisBatch(workspaceRoot: string, html: false, opts?: (Options & {
189
192
  batchMetadata?: false;
190
193
  }) | undefined): Promise<{
191
- [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
194
+ [x: string]: any;
192
195
  }>;
193
196
  /**
194
197
  * Get stack analysis for all workspace packages/crates (batch).
@@ -204,10 +207,10 @@ declare function stackAnalysisBatch(workspaceRoot: string, html: false, opts?: (
204
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.
205
208
  */
206
209
  declare function stackAnalysisBatch(workspaceRoot: string, html?: boolean | undefined, opts?: Options | undefined): Promise<string | {
207
- [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
210
+ [x: string]: any;
208
211
  } | {
209
212
  analysis: string | {
210
- [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
213
+ [x: string]: any;
211
214
  };
212
215
  metadata: BatchAnalysisMetadata;
213
216
  }>;
@@ -229,7 +232,7 @@ declare function imageAnalysis(imageRefs: Array<string>, html: true, opts?: Opti
229
232
  * @throws {Error}
230
233
  */
231
234
  declare function imageAnalysis(imageRefs: Array<string>, html: false, opts?: Options | undefined): Promise<{
232
- [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
235
+ [x: string]: any;
233
236
  }>;
234
237
  /**
235
238
  * Get image analysis report for a set of OCI image references.
@@ -242,7 +245,7 @@ declare function imageAnalysis(imageRefs: Array<string>, html: false, opts?: Opt
242
245
  * or backend request failed
243
246
  */
244
247
  declare function imageAnalysis(imageRefs: Array<string>, html?: boolean | undefined, opts?: Options | undefined): Promise<string | {
245
- [x: string]: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport;
248
+ [x: string]: any;
246
249
  }>;
247
250
  /**
248
251
  * Validates the Exhort token.
@@ -264,3 +267,4 @@ import { resolveContinueOnError } from './batch_opts.js';
264
267
  import { resolveBatchMetadata } from './batch_opts.js';
265
268
  export { discoverMavenModules, discoverGradleSubprojects, discoverGoWorkspaceModules, discoverUvWorkspaceMembers, discoverWorkspacePackages, discoverWorkspaceCrates, validatePackageJson, resolveWorkspaceDiscoveryIgnore, filterManifestPathsByDiscoveryIgnore, resolveContinueOnError, resolveBatchMetadata };
266
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,21 +1,22 @@
1
+ import fs from 'node:fs';
1
2
  import path from "node:path";
2
3
  import { EOL } from "os";
3
4
  import pLimit from 'p-limit';
4
- import { availableProviders, match } from './provider.js';
5
5
  import analysis from './analysis.js';
6
- import fs from 'node:fs';
7
- import { getCustom } from "./tools.js";
8
6
  import { resolveBatchMetadata, resolveContinueOnError } from './batch_opts.js';
9
- import { discoverMavenModules } from './providers/java_maven.js';
10
- import { discoverGradleSubprojects } from './providers/java_gradle.js';
7
+ import { getPackageVersion } from './package_version.js';
8
+ import { availableProviders, match } from './provider.js';
11
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
12
  import { discoverUvWorkspaceMembers } from './providers/python_uv.js';
13
+ import { getCustom } from "./tools.js";
13
14
  import { discoverWorkspaceCrates, discoverWorkspacePackages, filterManifestPathsByDiscoveryIgnore, resolveWorkspaceDiscoveryIgnore, validatePackageJson, } from './workspace.js';
14
- import.meta.dirname;
15
- import * as url from 'url';
16
15
  export { parseImageRef } from "./oci_image/utils.js";
17
16
  export { ImageRef } from "./oci_image/images.js";
18
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';
19
20
  export default { componentAnalysis, stackAnalysis, stackAnalysisBatch, imageAnalysis, validateToken, generateSbom };
20
21
  export { discoverMavenModules, discoverGradleSubprojects, discoverGoWorkspaceModules, discoverUvWorkspaceMembers, discoverWorkspacePackages, discoverWorkspaceCrates, validatePackageJson, resolveWorkspaceDiscoveryIgnore, filterManifestPathsByDiscoveryIgnore, resolveContinueOnError, resolveBatchMetadata, };
21
22
  /**
@@ -39,7 +40,9 @@ export { discoverMavenModules, discoverGradleSubprojects, discoverGoWorkspaceMod
39
40
  * TRUSTIFY_DA_PYTHON_PATH?: string | undefined,
40
41
  * TRUSTIFY_DA_PYTHON_VIRTUAL_ENV?: string | undefined,
41
42
  * TRUSTIFY_DA_PYTHON3_PATH?: string | undefined,
42
- * TRUSTIFY_DA_RECOMMENDATIONS_ENABLED?: string | undefined,
43
+ * TRUSTIFY_DA_PROVIDERS?: string | undefined,
44
+ * TRUSTIFY_DA_RECOMMEND?: string | undefined,
45
+ * TRUSTIFY_DA_SOURCES?: string | undefined,
43
46
  * TRUSTIFY_DA_SKOPEO_CONFIG_PATH?: string | undefined,
44
47
  * TRUSTIFY_DA_SKOPEO_PATH?: string | undefined,
45
48
  * TRUSTIFY_DA_SYFT_CONFIG_PATH?: string | undefined,
@@ -91,23 +94,7 @@ function logOptionsAndEnvironmentsVariables(alongsideText, valueToBePrinted) {
91
94
  * @private
92
95
  */
93
96
  function readAndPrintVersionFromPackageJson() {
94
- let dirName;
95
- // new ESM way in nodeJS ( since node version 22 ) to bring module directory.
96
- dirName = import.meta.dirname;
97
- // old ESM way in nodeJS ( before node versions 22.00 to bring module directory)
98
- if (!dirName) {
99
- dirName = url.fileURLToPath(new URL('.', import.meta.url));
100
- }
101
- try {
102
- if (__dirname) {
103
- dirName = __dirname;
104
- }
105
- }
106
- catch (e) {
107
- console.log("__dirname is not defined, continue with fileUrlPath");
108
- }
109
- let packageJson = JSON.parse(fs.readFileSync(path.join(dirName, "..", "package.json")).toString());
110
- logOptionsAndEnvironmentsVariables("trustify-da-javascript-client analysis started, version: ", packageJson.version);
97
+ logOptionsAndEnvironmentsVariables("trustify-da-javascript-client analysis started, version: ", getPackageVersion());
111
98
  }
112
99
  /**
113
100
  * This function is used to determine the Trustify DA backend URL.
@@ -175,7 +162,9 @@ async function componentAnalysis(manifest, opts = {}) {
175
162
  fs.accessSync(manifest, fs.constants.R_OK);
176
163
  opts["manifest-type"] = path.basename(manifest);
177
164
  let provider = match(manifest, availableProviders, opts); // throws error if no matching provider
178
- return await analysis.requestComponent(provider, manifest, theUrl, opts); // throws error request sending failed
165
+ const result = await analysis.requestComponent(provider, manifest, theUrl, opts); // throws error request sending failed
166
+ result.packageManager = provider.packageManagerName();
167
+ return result;
179
168
  }
180
169
  /**
181
170
  * @overload
@@ -9,10 +9,10 @@
9
9
  * @param {import('@trustify-da/trustify-da-api-model/model/v5/AnalysisReport').AnalysisReport} [analysisResult] - analysis result that includes licenses array from backend
10
10
  * @returns {Promise<{ projectLicense: { manifest: Object|null, file: Object|null, mismatch: boolean }, incompatibleDependencies: Array<{ purl: string, licenses: string[], category?: string, reason: string }>, error?: string }>}
11
11
  */
12
- export function runLicenseCheck(sbomContent: string, manifestPath: string, url: string, opts?: import("../index.js").Options, analysisResult?: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport): Promise<{
12
+ export function runLicenseCheck(sbomContent: string, manifestPath: string, url: string, opts?: import("../index.js").Options, analysisResult?: any): Promise<{
13
13
  projectLicense: {
14
- manifest: any | null;
15
- file: any | null;
14
+ manifest: Object | null;
15
+ file: Object | null;
16
16
  mismatch: boolean;
17
17
  };
18
18
  incompatibleDependencies: Array<{
@@ -85,11 +85,14 @@ export async function runLicenseCheck(sbomContent, manifestPath, url, opts = {},
85
85
  continue;
86
86
  const status = getCompatibility(projectCategory, entry.category);
87
87
  if (status === 'incompatible') {
88
+ const reason = entry.category?.toUpperCase() === 'UNKNOWN'
89
+ ? 'License not recognized as a standard SPDX identifier. Manual review recommended to verify compatibility.'
90
+ : 'Dependency license(s) are incompatible with the project license.';
88
91
  incompatibleDependencies.push({
89
92
  purl,
90
93
  licenses: entry.licenses,
91
94
  category: entry.category,
92
- reason: 'Dependency license(s) are incompatible with the project license.'
95
+ reason
93
96
  });
94
97
  }
95
98
  }
@@ -114,9 +114,12 @@ export function getCompatibility(projectCategory, dependencyCategory) {
114
114
  }
115
115
  const proj = projectCategory.toUpperCase();
116
116
  const dep = dependencyCategory.toUpperCase();
117
- if (proj === 'UNKNOWN' || dep === 'UNKNOWN') {
117
+ if (proj === 'UNKNOWN') {
118
118
  return 'unknown';
119
119
  }
120
+ if (dep === 'UNKNOWN') {
121
+ return 'incompatible';
122
+ }
120
123
  const restrictiveness = {
121
124
  'PERMISSIVE': 1,
122
125
  'WEAK_COPYLEFT': 2,
@@ -6,7 +6,7 @@
6
6
  * @param {import('../index.js').Options} [opts={}] - options (proxy, token, TRUSTIFY_DA_BACKEND_URL, etc.)
7
7
  * @returns {Promise<Object|null>} License details or null if not found
8
8
  */
9
- export function getLicenseDetails(spdxId: string, opts?: import("../index.js").Options): Promise<any | null>;
9
+ export function getLicenseDetails(spdxId: string, opts?: import("../index.js").Options): Promise<Object | null>;
10
10
  /**
11
11
  * Normalize the LicensesResponse shape (array of LicenseProviderResult) into a map of purl -> license info.
12
12
  * Each provider result has { status, summary, packages } where packages is { [purl]: { concluded, evidence } }.
@@ -28,7 +28,7 @@ export function normalizeLicensesResponse(data: unknown, purls?: string[]): Map<
28
28
  * @param {string[]} [purls] - optional list of purls to restrict to
29
29
  * @returns {Map<string, { licenses: string[], category?: string }>}
30
30
  */
31
- export function licensesFromReport(analysisReport: import("@trustify-da/trustify-da-api-model/model/v5/AnalysisReport").AnalysisReport, purls?: string[]): Map<string, {
31
+ export function licensesFromReport(analysisReport: any, purls?: string[]): Map<string, {
32
32
  licenses: string[];
33
33
  category?: string;
34
34
  }>;
@@ -78,7 +78,7 @@ export class ImageRef {
78
78
  * @param {string} [platform]
79
79
  * @param {import("index.js").Options} [opts={}]
80
80
  */
81
- constructor(image: string, platform?: string, opts?: import("index.js").Options);
81
+ constructor(image: string, platform?: string, opts?: any);
82
82
  /** @type {Image} */
83
83
  image: Image;
84
84
  /** @type {Platform} */
@@ -242,8 +242,22 @@ export class ImageRef {
242
242
  getPackageURL() {
243
243
  /** @type {Object.<string, string>} */
244
244
  const qualifiers = {};
245
- const repositoryUrl = this.image.getNameWithoutTag();
245
+ let repositoryUrl = this.image.getNameWithoutTag();
246
246
  const simpleName = this.image.getSimpleName();
247
+ // Normalize Docker Hub image references so all forms produce the same PURL:
248
+ // node → docker.io/node
249
+ // docker.io/library/node → docker.io/node
250
+ if (repositoryUrl != null) {
251
+ const lower = repositoryUrl.toLowerCase();
252
+ if (lower === simpleName.toLowerCase()) {
253
+ // Bare name (e.g. "node") — default to docker.io
254
+ repositoryUrl = `docker.io/${simpleName}`;
255
+ }
256
+ else if (lower.startsWith('docker.io/library/')) {
257
+ // Official image with library/ prefix — strip it
258
+ repositoryUrl = `docker.io/${lower.substring('docker.io/library/'.length)}`;
259
+ }
260
+ }
247
261
  if (repositoryUrl != null && repositoryUrl.toLowerCase() !== simpleName.toLowerCase()) {
248
262
  qualifiers[ImageRef.REPOSITORY_QUALIFIER] = repositoryUrl.toLowerCase();
249
263
  }
@@ -4,7 +4,7 @@
4
4
  * @param {import("../index.js").Options} [opts={}] - optional various options to pass along the application
5
5
  * @returns {{}}
6
6
  */
7
- export function generateImageSBOM(imageRef: import("./images").ImageRef, opts?: import("../index.js").Options): {};
7
+ export function generateImageSBOM(imageRef: any, opts?: import("../index.js").Options): {};
8
8
  /**
9
9
  *
10
10
  * @param {string} image
@@ -25,7 +25,7 @@ export function getImagePlatform(opts?: import("../index.js").Options): Platform
25
25
  * @returns {Object.<string, string>} - The image digests
26
26
  * @throws {Error} If the image info is invalid
27
27
  */
28
- export function getImageDigests(imageRef: import("./images").ImageRef, opts?: import("../index.js").Options): {
28
+ export function getImageDigests(imageRef: any, opts?: import("../index.js").Options): {
29
29
  [x: string]: string;
30
30
  };
31
31
  export type SyftImageSource = {
@@ -34,9 +34,9 @@ export type SyftImageSource = {
34
34
  getVariant: () => string;
35
35
  };
36
36
  export type SyftImageSourceType = {
37
- getOs: (arg0: any) => string;
38
- getArch: (arg0: any) => string;
39
- getVariant: (arg0: any) => string;
37
+ getOs: (arg0: Object) => string;
38
+ getArch: (arg0: Object) => string;
39
+ getVariant: (arg0: Object) => string;
40
40
  };
41
41
  import { ImageRef } from './images.js';
42
42
  import { Platform } from './platform.js';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Reads this package's version from package.json, resolving the path relative
3
+ * to this module. Works under ESM (node >= 22 import.meta.dirname, older via
4
+ * import.meta.url) and CommonJS (__dirname). Returns undefined if the file
5
+ * cannot be read, so callers never fail on a missing version.
6
+ * @return {string|undefined} the package version, or undefined
7
+ */
8
+ export function getPackageVersion(): string | undefined;
@@ -0,0 +1,31 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import * as url from "url";
4
+ /**
5
+ * Reads this package's version from package.json, resolving the path relative
6
+ * to this module. Works under ESM (node >= 22 import.meta.dirname, older via
7
+ * import.meta.url) and CommonJS (__dirname). Returns undefined if the file
8
+ * cannot be read, so callers never fail on a missing version.
9
+ * @return {string|undefined} the package version, or undefined
10
+ */
11
+ export function getPackageVersion() {
12
+ try {
13
+ let dirName = import.meta.dirname;
14
+ if (!dirName) {
15
+ dirName = url.fileURLToPath(new URL('.', import.meta.url));
16
+ }
17
+ try {
18
+ if (__dirname) {
19
+ dirName = __dirname;
20
+ }
21
+ }
22
+ catch {
23
+ // __dirname not defined under ESM, continue with fileURLToPath
24
+ }
25
+ const packageJson = JSON.parse(fs.readFileSync(path.join(dirName, "..", "package.json")).toString());
26
+ return packageJson.version;
27
+ }
28
+ catch {
29
+ return undefined;
30
+ }
31
+ }
@@ -21,7 +21,7 @@ export function match(manifest: string, providers: [Provider], opts?: {
21
21
  TRUSTIFY_DA_WORKSPACE_DIR?: string;
22
22
  }): Provider;
23
23
  /** @typedef {{ecosystem: string, contentType: string, content: string}} Provided */
24
- /** @typedef {{isSupported: function(string): boolean, validateLockFile: function(string, Object): void, provideComponent: function(string, {}): Provided | Promise<Provided>, provideStack: function(string, {}): Provided | Promise<Provided>, readLicenseFromManifest: function(string): string | null}} Provider */
24
+ /** @typedef {{isSupported: function(string): boolean, validateLockFile: function(string, Object): void, provideComponent: function(string, {}): Provided | Promise<Provided>, provideStack: function(string, {}): Provided | Promise<Provided>, readLicenseFromManifest: function(string, Object=): string | null, packageManagerName: function(): string}} Provider */
25
25
  /**
26
26
  * MUST include all providers here.
27
27
  * @type {[Provider]}
@@ -34,8 +34,9 @@ export type Provided = {
34
34
  };
35
35
  export type Provider = {
36
36
  isSupported: (arg0: string) => boolean;
37
- validateLockFile: (arg0: string, arg1: any) => void;
37
+ validateLockFile: (arg0: string, arg1: Object) => void;
38
38
  provideComponent: (arg0: string, arg1: {}) => Provided | Promise<Provided>;
39
39
  provideStack: (arg0: string, arg1: {}) => Provided | Promise<Provided>;
40
- readLicenseFromManifest: (arg0: string) => string | null;
40
+ readLicenseFromManifest: (arg0: string, arg1: Object | undefined) => string | null;
41
+ packageManagerName: () => string;
41
42
  };