@trustify-da/trustify-da-javascript-client 0.3.0-ea.608d6fa → 0.3.0-ea.62b88e5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +326 -21
  2. package/dist/package.json +36 -27
  3. package/dist/src/analysis.d.ts +58 -14
  4. package/dist/src/analysis.js +118 -17
  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 +357 -8
  8. package/dist/src/config.d.ts +120 -0
  9. package/dist/src/config.js +260 -0
  10. package/dist/src/cyclone_dx_sbom.d.ts +27 -1
  11. package/dist/src/cyclone_dx_sbom.js +84 -10
  12. package/dist/src/index.d.ts +151 -16
  13. package/dist/src/index.js +369 -36
  14. package/dist/src/license/index.d.ts +4 -4
  15. package/dist/src/license/index.js +13 -7
  16. package/dist/src/license/license_utils.js +4 -1
  17. package/dist/src/license/licenses_api.d.ts +3 -3
  18. package/dist/src/license/licenses_api.js +1 -1
  19. package/dist/src/oci_image/images.d.ts +1 -1
  20. package/dist/src/oci_image/images.js +15 -1
  21. package/dist/src/oci_image/utils.d.ts +5 -5
  22. package/dist/src/oci_image/utils.js +11 -2
  23. package/dist/src/package_version.d.ts +8 -0
  24. package/dist/src/package_version.js +31 -0
  25. package/dist/src/provider.d.ts +8 -4
  26. package/dist/src/provider.js +16 -5
  27. package/dist/src/providers/base_java.d.ts +67 -12
  28. package/dist/src/providers/base_java.js +82 -61
  29. package/dist/src/providers/base_javascript.d.ts +91 -10
  30. package/dist/src/providers/base_javascript.js +214 -24
  31. package/dist/src/providers/base_pyproject.d.ts +158 -0
  32. package/dist/src/providers/base_pyproject.js +322 -0
  33. package/dist/src/providers/containerfile_parser.d.ts +5 -0
  34. package/dist/src/providers/containerfile_parser.js +20 -0
  35. package/dist/src/providers/golang_gomodules.d.ts +23 -13
  36. package/dist/src/providers/golang_gomodules.js +208 -120
  37. package/dist/src/providers/gomod_parser.d.ts +4 -0
  38. package/dist/src/providers/gomod_parser.js +16 -0
  39. package/dist/src/providers/java_gradle.d.ts +67 -0
  40. package/dist/src/providers/java_gradle.js +320 -22
  41. package/dist/src/providers/java_gradle_groovy.d.ts +1 -1
  42. package/dist/src/providers/java_gradle_kotlin.d.ts +1 -1
  43. package/dist/src/providers/java_maven.d.ts +37 -9
  44. package/dist/src/providers/java_maven.js +269 -11
  45. package/dist/src/providers/javascript_bun.d.ts +22 -0
  46. package/dist/src/providers/javascript_bun.js +141 -0
  47. package/dist/src/providers/javascript_npm.d.ts +19 -0
  48. package/dist/src/providers/javascript_npm.js +61 -1
  49. package/dist/src/providers/javascript_pnpm.d.ts +14 -1
  50. package/dist/src/providers/javascript_pnpm.js +55 -5
  51. package/dist/src/providers/javascript_yarn.d.ts +12 -0
  52. package/dist/src/providers/javascript_yarn.js +75 -2
  53. package/dist/src/providers/manifest.d.ts +2 -0
  54. package/dist/src/providers/manifest.js +28 -7
  55. package/dist/src/providers/marker_evaluator.d.ts +14 -0
  56. package/dist/src/providers/marker_evaluator.js +191 -0
  57. package/dist/src/providers/oci_dockerfile.d.ts +51 -0
  58. package/dist/src/providers/oci_dockerfile.js +177 -0
  59. package/dist/src/providers/processors/yarn_berry_processor.d.ts +6 -2
  60. package/dist/src/providers/processors/yarn_berry_processor.js +91 -7
  61. package/dist/src/providers/processors/yarn_classic_processor.d.ts +6 -2
  62. package/dist/src/providers/processors/yarn_classic_processor.js +8 -6
  63. package/dist/src/providers/python_controller.d.ts +5 -1
  64. package/dist/src/providers/python_controller.js +69 -8
  65. package/dist/src/providers/python_pip.d.ts +5 -0
  66. package/dist/src/providers/python_pip.js +6 -6
  67. package/dist/src/providers/python_pip_pyproject.d.ts +61 -0
  68. package/dist/src/providers/python_pip_pyproject.js +146 -0
  69. package/dist/src/providers/python_poetry.d.ts +98 -0
  70. package/dist/src/providers/python_poetry.js +286 -0
  71. package/dist/src/providers/python_uv.d.ts +46 -0
  72. package/dist/src/providers/python_uv.js +275 -0
  73. package/dist/src/providers/requirements_parser.js +1 -1
  74. package/dist/src/providers/rust_cargo.d.ts +12 -3
  75. package/dist/src/providers/rust_cargo.js +107 -31
  76. package/dist/src/providers/tree-sitter-containerfile.wasm +0 -0
  77. package/dist/src/providers/tree-sitter-gomod.wasm +0 -0
  78. package/dist/src/remediate.d.ts +107 -0
  79. package/dist/src/remediate.js +244 -0
  80. package/dist/src/remediation.d.ts +81 -0
  81. package/dist/src/remediation.js +475 -0
  82. package/dist/src/remediation_report.d.ts +23 -0
  83. package/dist/src/remediation_report.js +180 -0
  84. package/dist/src/sbom.d.ts +25 -1
  85. package/dist/src/sbom.js +23 -2
  86. package/dist/src/tools.d.ts +37 -13
  87. package/dist/src/tools.js +92 -13
  88. package/dist/src/updaters/maven_updater.d.ts +58 -0
  89. package/dist/src/updaters/maven_updater.js +345 -0
  90. package/dist/src/updaters/toml_updater.d.ts +62 -0
  91. package/dist/src/updaters/toml_updater.js +276 -0
  92. package/dist/src/workspace.d.ts +71 -0
  93. package/dist/src/workspace.js +257 -0
  94. package/package.json +37 -28
@@ -1,11 +1,12 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { EOL } from "os";
4
3
  import { PackageURL } from 'packageurl-js';
5
4
  import { readLicenseFile } from '../license/license_utils.js';
6
5
  import Sbom from '../sbom.js';
7
6
  import { getCustom, getCustomPath, invokeCommand } from "../tools.js";
8
- export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest };
7
+ import { filterManifestPathsByDiscoveryIgnore, resolveWorkspaceDiscoveryIgnore } from '../workspace.js';
8
+ import { getParser, getRequireQuery } from './gomod_parser.js';
9
+ export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest, packageManagerName() { return 'go'; } };
9
10
  /** @typedef {import('../provider').Provider} */
10
11
  /** @typedef {import('../provider').Provided} Provided */
11
12
  /** @typedef {{name: string, version: string}} Package */
@@ -17,46 +18,46 @@ export default { isSupported, validateLockFile, provideComponent, provideStack,
17
18
  const ecosystem = 'golang';
18
19
  const defaultMainModuleVersion = "v0.0.0";
19
20
  /**
20
- * @param {string} manifestName - the subject manifest name-type
21
- * @returns {boolean} - return true if `pom.xml` is the manifest name-type
21
+ * @param {string} manifestName the subject manifest name-type
22
+ * @returns {boolean} return true if `pom.xml` is the manifest name-type
22
23
  */
23
24
  function isSupported(manifestName) {
24
25
  return 'go.mod' === manifestName;
25
26
  }
26
27
  /**
27
28
  * Go modules have no standard license field in go.mod
28
- * @param {string} manifestPath - path to go.mod
29
+ * @param {string} manifestPath path to go.mod
29
30
  * @returns {string|null}
30
31
  */
31
32
  // eslint-disable-next-line no-unused-vars
32
33
  function readLicenseFromManifest(manifestPath) { return readLicenseFile(manifestPath); }
33
34
  /**
34
- * @param {string} manifestDir - the directory where the manifest lies
35
+ * @param {string} manifestDir the directory where the manifest lies
35
36
  */
36
37
  function validateLockFile() { return true; }
37
38
  /**
38
39
  * Provide content and content type for maven-maven stack analysis.
39
- * @param {string} manifest - the manifest path or name
40
- * @param {{}} [opts={}] - optional various options to pass along the application
41
- * @returns {Provided}
40
+ * @param {string} manifest the manifest path or name
41
+ * @param {{}} [opts={}] optional various options to pass along the application
42
+ * @returns {Promise<Provided>}
42
43
  */
43
- function provideStack(manifest, opts = {}) {
44
+ async function provideStack(manifest, opts = {}) {
44
45
  return {
45
46
  ecosystem,
46
- content: getSBOM(manifest, opts, true),
47
+ content: await getSBOM(manifest, opts, true),
47
48
  contentType: 'application/vnd.cyclonedx+json'
48
49
  };
49
50
  }
50
51
  /**
51
52
  * Provide content and content type for maven-maven component analysis.
52
- * @param {string} manifest - path to go.mod for component report
53
- * @param {{}} [opts={}] - optional various options to pass along the application
54
- * @returns {Provided}
53
+ * @param {string} manifest path to go.mod for component report
54
+ * @param {{}} [opts={}] optional various options to pass along the application
55
+ * @returns {Promise<Provided>}
55
56
  */
56
- function provideComponent(manifest, opts = {}) {
57
+ async function provideComponent(manifest, opts = {}) {
57
58
  return {
58
59
  ecosystem,
59
- content: getSBOM(manifest, opts, false),
60
+ content: await getSBOM(manifest, opts, false),
60
61
  contentType: 'application/vnd.cyclonedx+json'
61
62
  };
62
63
  }
@@ -77,50 +78,53 @@ function getChildVertexFromEdge(edge) {
77
78
  return edge.split(" ")[1];
78
79
  }
79
80
  /**
80
- *
81
- * @param line one row from go.mod file
82
- * @return {boolean} whether line from go.mod should be considered as ignored or not
81
+ * Check whether a require_spec has a valid ignore marker.
82
+ * Supports both the legacy `exhortignore` marker and the new `trustify-da-ignore` marker.
83
+ * For direct dependencies: `//exhortignore` or `// trustify-da-ignore`
84
+ * For indirect dependencies: `// indirect; exhortignore` or `// indirect; trustify-da-ignore` (semicolon-separated)
85
+ * @param {import('web-tree-sitter').SyntaxNode} specNode
86
+ * @return {boolean}
83
87
  */
84
- function ignoredLine(line) {
85
- let result = false;
86
- if (line.match(".*exhortignore.*")) {
87
- if (line.match(".+//\\s*exhortignore") || line.match(".+//\\sindirect (//)?\\s*exhortignore")) {
88
- let trimmedRow = line.trim();
89
- if (!trimmedRow.startsWith("module ") && !trimmedRow.startsWith("go ") && !trimmedRow.startsWith("require (") && !trimmedRow.startsWith("require(")
90
- && !trimmedRow.startsWith("exclude ") && !trimmedRow.startsWith("replace ") && !trimmedRow.startsWith("retract ") && !trimmedRow.startsWith("use ")
91
- && !trimmedRow.includes("=>")) {
92
- if (trimmedRow.startsWith("require ") || trimmedRow.match("^[a-z.0-9/-]+\\s{1,2}[vV][0-9]\\.[0-9](\\.[0-9]){0,2}.*")) {
93
- result = true;
94
- }
95
- }
88
+ function hasExhortIgnore(specNode) {
89
+ // Ideally this would be the following tree-sitter query instead, but for some
90
+ // reason it throws an error here but not in the playground.
91
+ // (require_spec) ((module_path) @path (version) (comment) @comment (#match? @comment "^//.*exhortignore"))
92
+ // QueryError: Bad pattern structure at offset 53: '(comment) @comment (#match? @comment "^//.*exhortignore")) @spec'...
93
+ let comments = specNode.children.filter(c => c.type === 'comment');
94
+ for (let comment of comments) {
95
+ let text = comment.text;
96
+ if (/^\/\/\s*indirect;\s*(exhortignore|trustify-da-ignore)(?![\w-])/.test(text)) {
97
+ return true;
98
+ }
99
+ if (/^\/\/\s*(exhortignore|trustify-da-ignore)(?![\w-])/.test(text)) {
100
+ return true;
96
101
  }
97
102
  }
98
- return result;
99
- }
100
- /**
101
- * extract package name from go.mod line that contains exhortignore comment.
102
- * @param line a row contains exhortignore as part of a comment
103
- * @return {string} the full package name + group/namespace + version
104
- * @private
105
- */
106
- function extractPackageName(line) {
107
- let trimmedRow = line.trim();
108
- let firstRemarkNotationOccurrence = trimmedRow.indexOf("//");
109
- return trimmedRow.substring(0, firstRemarkNotationOccurrence).trim();
103
+ return false;
110
104
  }
111
105
  /**
112
106
  *
113
- * @param {string } manifest - path to manifest
114
- * @return {[PackageURL]} list of ignored dependencies d
107
+ * @param {string} manifestContent go.mod file contents
108
+ * @param {import('web-tree-sitter').Parser} parser
109
+ * @param {import('web-tree-sitter').Query} requireQuery
110
+ * @return {PackageURL[]} list of ignored dependencies
115
111
  */
116
- function getIgnoredDeps(manifest) {
117
- let goMod = fs.readFileSync(manifest).toString().trim();
118
- let lines = goMod.split(getLineSeparatorGolang());
119
- return lines.filter(line => ignoredLine(line)).map(line => extractPackageName(line)).map(dep => toPurl(dep, /[ ]{1,3}/));
112
+ function getIgnoredDeps(manifestContent, parser, requireQuery) {
113
+ let tree = parser.parse(manifestContent);
114
+ return requireQuery.matches(tree.rootNode)
115
+ .filter(match => {
116
+ let specNode = match.captures.find(c => c.name === 'spec').node;
117
+ return hasExhortIgnore(specNode);
118
+ })
119
+ .map(match => {
120
+ let name = match.captures.find(c => c.name === 'name').node.text;
121
+ let version = match.captures.find(c => c.name === 'version').node.text;
122
+ return toPurl(`${name} ${version}`, /[ ]{1,3}/);
123
+ });
120
124
  }
121
125
  /**
122
126
  *
123
- * @param {[PackageURL]}allIgnoredDeps - list of purls of all dependencies that should be ignored
127
+ * @param {PackageURL[]} allIgnoredDeps list of purls of all dependencies that should be ignored
124
128
  * @param {PackageURL} purl object to be checked if needed to be ignored
125
129
  * @return {boolean}
126
130
  */
@@ -139,59 +143,29 @@ function enforceRemovingIgnoredDepsInCaseOfAutomaticVersionUpdate(ignoredDeps, s
139
143
  }
140
144
  /**
141
145
  *
142
- * @param {[string]} lines - array of lines of go.mod manifest
143
- * @param {string} goMod - content of go.mod manifest
144
- * @return {[string]} all dependencies from go.mod file as array
146
+ * @param {string} manifestContent go.mod file contents
147
+ * @param {import('web-tree-sitter').Parser} parser
148
+ * @param {import('web-tree-sitter').Query} requireQuery
149
+ * @return {string[]} all dependencies from go.mod file as "name version" strings
145
150
  */
146
- function collectAllDepsFromManifest(lines, goMod) {
147
- let result;
148
- // collect all deps that starts with require keyword
149
- result = lines.filter((line) => line.trim().startsWith("require") && !line.includes("(")).map((dep) => dep.substring("require".length).trim());
150
- // collect all deps that are inside `require` blocks
151
- let currentSegmentOfGoMod = goMod;
152
- let requirePositionObject = decideRequireBlockIndex(currentSegmentOfGoMod);
153
- while (requirePositionObject.index > -1) {
154
- let depsInsideRequirementsBlock = currentSegmentOfGoMod.substring(requirePositionObject.index + requirePositionObject.startingOffeset).trim();
155
- let endOfBlockIndex = depsInsideRequirementsBlock.indexOf(")");
156
- let currentIndex = 0;
157
- while (currentIndex < endOfBlockIndex) {
158
- let endOfLinePosition = depsInsideRequirementsBlock.indexOf(EOL, currentIndex);
159
- let dependency = depsInsideRequirementsBlock.substring(currentIndex, endOfLinePosition);
160
- result.push(dependency.trim());
161
- currentIndex = endOfLinePosition + 1;
162
- }
163
- currentSegmentOfGoMod = currentSegmentOfGoMod.substring(endOfBlockIndex + 1).trim();
164
- requirePositionObject = decideRequireBlockIndex(currentSegmentOfGoMod);
165
- }
166
- function decideRequireBlockIndex(goMod) {
167
- let object = {};
168
- let index = goMod.indexOf("require(");
169
- object.startingOffeset = "require(".length;
170
- if (index === -1) {
171
- index = goMod.indexOf("require (");
172
- object.startingOffeset = "require (".length;
173
- if (index === -1) {
174
- index = goMod.indexOf("require (");
175
- object.startingOffeset = "require (".length;
176
- }
177
- }
178
- object.index = index;
179
- return object;
180
- }
181
- return result;
151
+ function collectAllDepsFromManifest(manifestContent, parser, requireQuery) {
152
+ let tree = parser.parse(manifestContent);
153
+ return requireQuery.matches(tree.rootNode).map(match => {
154
+ let name = match.captures.find(c => c.name === 'name').node.text;
155
+ let version = match.captures.find(c => c.name === 'version').node.text;
156
+ return `${name} ${version}`;
157
+ });
182
158
  }
183
159
  /**
184
160
  *
185
161
  * @param {string} rootElementName the rootElementName element of go mod graph, to compare only direct deps from go mod graph against go.mod manifest
186
- * @param{[string]} goModGraphOutputRows the goModGraphOutputRows from go mod graph' output
187
- * @param {string }manifest path to go.mod manifest on file system
162
+ * @param {string[]} goModGraphOutputRows the goModGraphOutputRows from go mod graph' output
163
+ * @param {string} manifestContent go.mod file contents
188
164
  * @private
189
165
  */
190
- function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, manifest) {
191
- let goMod = fs.readFileSync(manifest).toString().trim();
192
- let lines = goMod.split(getLineSeparatorGolang());
166
+ function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, manifestContent, parser, requireQuery) {
193
167
  let comparisonLines = goModGraphOutputRows.filter((line) => line.startsWith(rootElementName)).map((line) => getChildVertexFromEdge(line));
194
- let manifestDeps = collectAllDepsFromManifest(lines, goMod);
168
+ let manifestDeps = collectAllDepsFromManifest(manifestContent, parser, requireQuery);
195
169
  try {
196
170
  comparisonLines.forEach((dependency) => {
197
171
  let parts = dependency.split("@");
@@ -203,7 +177,7 @@ function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, man
203
177
  let currentVersion = components[1];
204
178
  if (currentDepName === depName) {
205
179
  if (currentVersion !== version) {
206
- throw new Error(`versions mismatch for dependency name ${depName}, manifest version=${currentVersion}, installed Version=${version}, if you want to allow version mismatch for analysis between installed and requested packages, set environment variable/setting - MATCH_MANIFEST_VERSIONS=false`);
180
+ throw new Error(`version mismatch for dependency "${depName}", manifest version=${currentVersion}, installed version=${version}, if you want to allow version mismatch for analysis between installed and requested packages, set environment variable/setting MATCH_MANIFEST_VERSIONS=false`);
207
181
  }
208
182
  }
209
183
  });
@@ -214,15 +188,45 @@ function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, man
214
188
  throw error;
215
189
  }
216
190
  }
191
+ /** Parses go.sum to build a hash map of module@version to CycloneDX hashes. */
192
+ function parseGoSumHashes(manifestDir) {
193
+ let goSumPath = path.join(manifestDir, 'go.sum');
194
+ if (!fs.existsSync(goSumPath)) {
195
+ return new Map();
196
+ }
197
+ let hashMap = new Map();
198
+ let content = fs.readFileSync(goSumPath, 'utf-8');
199
+ for (let line of content.split('\n')) {
200
+ line = line.trim();
201
+ if (!line) {
202
+ continue;
203
+ }
204
+ let parts = line.split(/\s+/);
205
+ if (parts.length < 3) {
206
+ continue;
207
+ }
208
+ let [mod, version, hash] = parts;
209
+ if (version.endsWith('/go.mod')) {
210
+ continue;
211
+ }
212
+ if (!hash.startsWith('h1:')) {
213
+ continue;
214
+ }
215
+ let base64Part = hash.slice(3);
216
+ let hexDigest = Buffer.from(base64Part, 'base64').toString('hex');
217
+ hashMap.set(`${mod}@${version}`, [{ alg: "SHA-256", content: hexDigest }]);
218
+ }
219
+ return hashMap;
220
+ }
217
221
  /**
218
222
  * Create SBOM json string for go Module.
219
223
  * @param {string} manifest - path for go.mod
220
224
  * @param {{}} [opts={}] - optional various options to pass along the application
221
225
  * @param {boolean} includeTransitive - whether the sbom should contain transitive dependencies of the main module or not.
222
- * @returns {string} the SBOM json content
226
+ * @returns {Promise<string>} the SBOM json content
223
227
  * @private
224
228
  */
225
- function getSBOM(manifest, opts = {}, includeTransitive) {
229
+ async function getSBOM(manifest, opts = {}, includeTransitive) {
226
230
  // get custom goBin path
227
231
  let goBin = getCustomPath('go', opts);
228
232
  // verify goBin is accessible
@@ -248,15 +252,27 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
248
252
  catch (error) {
249
253
  throw new Error('failed to determine root module name', { cause: error });
250
254
  }
251
- let ignoredDeps = getIgnoredDeps(manifest);
255
+ let manifestContent = fs.readFileSync(manifest).toString();
256
+ let [parser, requireQuery] = await Promise.all([getParser(), getRequireQuery()]);
257
+ let ignoredDeps = getIgnoredDeps(manifestContent, parser, requireQuery);
252
258
  let allIgnoredDeps = ignoredDeps.map((dep) => dep.toString());
253
259
  let sbom = new Sbom();
254
260
  let rows = goGraphOutput.split(getLineSeparatorGolang()).filter(line => !line.includes(' go@'));
255
- let root = getParentVertexFromEdge(goModEditOutput['Module']['Path']);
261
+ let root = goModEditOutput['Module']['Path'];
262
+ // Build set of direct dependency paths from go mod edit -json
263
+ let directDepPaths = new Set();
264
+ if (goModEditOutput['Require']) {
265
+ goModEditOutput['Require'].forEach(req => {
266
+ if (!req['Indirect']) {
267
+ directDepPaths.add(req['Path']);
268
+ }
269
+ });
270
+ }
256
271
  let matchManifestVersions = getCustom("MATCH_MANIFEST_VERSIONS", "false", opts);
257
272
  if (matchManifestVersions === "true") {
258
- performManifestVersionsCheck(root, rows, manifest);
273
+ performManifestVersionsCheck(root, rows, manifestContent, parser, requireQuery);
259
274
  }
275
+ let goSumHashes = parseGoSumHashes(manifestDir);
260
276
  const mainModule = toPurl(root, "@");
261
277
  const license = readLicenseFromManifest(manifest);
262
278
  sbom.addRoot(mainModule, license);
@@ -273,8 +289,12 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
273
289
  currentParent = getParentVertexFromEdge(row);
274
290
  source = toPurl(currentParent, "@");
275
291
  }
276
- let target = toPurl(getChildVertexFromEdge(row), "@");
277
- sbom.addDependency(source, target);
292
+ let child = getChildVertexFromEdge(row);
293
+ let target = toPurl(child, "@");
294
+ if (getParentVertexFromEdge(row) === root && !directDepPaths.has(getPackageName(child))) {
295
+ return;
296
+ }
297
+ sbom.addDependency(source, target, undefined, goSumHashes.get(child));
278
298
  });
279
299
  // at the end, filter out all ignored dependencies including versions.
280
300
  sbom.filterIgnoredDepsIncludingVersion(allIgnoredDeps);
@@ -283,20 +303,32 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
283
303
  else {
284
304
  let directDependencies = rows.filter(row => row.startsWith(root));
285
305
  directDependencies.forEach(pair => {
286
- let dependency = getChildVertexFromEdge(pair);
287
- let depPurl = toPurl(dependency, "@");
288
- if (dependencyNotIgnored(ignoredDeps, depPurl)) {
289
- sbom.addDependency(mainModule, depPurl);
306
+ let child = getChildVertexFromEdge(pair);
307
+ let target = toPurl(child, "@");
308
+ if (dependencyNotIgnored(ignoredDeps, target)) {
309
+ if (directDepPaths.has(getPackageName(child))) {
310
+ sbom.addDependency(mainModule, target, undefined, goSumHashes.get(child));
311
+ }
290
312
  }
291
313
  });
292
314
  enforceRemovingIgnoredDepsInCaseOfAutomaticVersionUpdate(ignoredDeps, sbom);
293
315
  }
316
+ // Backfill hashes for components that were only seen as dependency sources
317
+ for (const comp of sbom.sbomModel.components) {
318
+ if (!comp.hashes && comp.purl) {
319
+ let key = decodeURIComponent(comp.purl.replace('pkg:golang/', ''));
320
+ let hashes = goSumHashes.get(key);
321
+ if (hashes) {
322
+ comp.hashes = hashes;
323
+ }
324
+ }
325
+ }
294
326
  return sbom.getAsJsonString(opts);
295
327
  }
296
328
  /**
297
329
  * Utility function for creating Purl String
298
330
 
299
- * @param {string }dependency the name of the artifact, can include a namespace(group) or not - namespace/artifactName.
331
+ * @param {string} dependency the name of the artifact, can include a namespace(group) or not - namespace/artifactName.
300
332
  * @param {RegExp} delimiter delimiter between name of dependency and version
301
333
  * @private
302
334
  * @returns {PackageURL|null} PackageUrl Object ready to be used in SBOM
@@ -321,12 +353,12 @@ function toPurl(dependency, delimiter) {
321
353
  }
322
354
  return pkg;
323
355
  }
324
- /** This function gets rows from go mod graph , and go.mod graph, and selecting for all
356
+ /** This function gets rows from go mod graph, and go.mod graph, and selecting for all
325
357
  * packages the has more than one minor the final versions as selected by golang MVS algorithm.
326
- * @param {[string]}rows all the rows from go modules dependency tree
358
+ * @param {string[]} rows all the rows from go modules dependency tree
327
359
  * @param {string} manifestPath the path of the go.mod file
328
360
  * @param {string} path to go binary
329
- * @return {[string]} rows that contains final versions.
361
+ * @return {string[]} rows that contains final versions.
330
362
  */
331
363
  function getFinalPackagesVersionsForModule(rows, manifestPath, goBin) {
332
364
  let manifestDir = path.dirname(manifestPath);
@@ -339,13 +371,7 @@ function getFinalPackagesVersionsForModule(rows, manifestPath, goBin) {
339
371
  catch (error) {
340
372
  throw new Error('failed to list all modules', { cause: error });
341
373
  }
342
- let finalVersionModules = new Map();
343
- finalVersionsForAllModules.split(getLineSeparatorGolang()).filter(string => string.trim() !== "")
344
- .filter(string => string.trim().split(" ").length === 2)
345
- .forEach((dependency) => {
346
- let dep = dependency.split(" ");
347
- finalVersionModules[dep[0]] = dep[1];
348
- });
374
+ let finalVersionModules = parseModuleVersions(finalVersionsForAllModules);
349
375
  let finalVersionModulesArray = new Array();
350
376
  rows.filter(string => string.trim() !== "").forEach(module => {
351
377
  let child = getChildVertexFromEdge(module);
@@ -381,7 +407,7 @@ function getFinalPackagesVersionsForModule(rows, manifestPath, goBin) {
381
407
  /**
382
408
  *
383
409
  * @param {string} fullPackage - full package with its name and version
384
- * @return -{string} package name only
410
+ * @return {string} package name only
385
411
  * @private
386
412
  */
387
413
  function getPackageName(fullPackage) {
@@ -399,14 +425,76 @@ function isSpecialGoModule(moduleName) {
399
425
  /**
400
426
  *
401
427
  * @param {string} fullPackage - full package with its name and version
402
- * @return -{string} package version only
428
+ * @return {string|undefined} package version only
403
429
  * @private
404
430
  */
405
431
  function getVersionOfPackage(fullPackage) {
406
432
  let parts = fullPackage.split("@");
407
433
  return parts.length > 1 ? parts[1] : undefined;
408
434
  }
435
+ function parseModuleVersions(goListOutput) {
436
+ let modules = {};
437
+ goListOutput.split(getLineSeparatorGolang()).filter(string => string.trim() !== "")
438
+ .forEach((line) => {
439
+ let parts = line.trim().split(" ");
440
+ if (parts.length === 2) {
441
+ modules[parts[0]] = parts[1];
442
+ }
443
+ else if (parts.length >= 4 && parts[2] === "=>") {
444
+ modules[parts[0]] = parts[parts.length - 1];
445
+ }
446
+ });
447
+ return modules;
448
+ }
409
449
  function getLineSeparatorGolang() {
410
450
  let reg = /\n|\r\n/;
411
451
  return reg;
412
452
  }
453
+ /**
454
+ * Discover all go.mod manifest paths in a Go workspace.
455
+ * Uses `go work edit -json` to get workspace members.
456
+ *
457
+ * @param {string} workspaceRoot - Absolute or relative path to workspace root (must contain go.work)
458
+ * @param {import('../index.js').Options} [opts={}]
459
+ * @returns {Promise<string[]>} Paths to go.mod files (absolute)
460
+ */
461
+ export async function discoverGoWorkspaceModules(workspaceRoot, opts = {}) {
462
+ const root = path.resolve(workspaceRoot);
463
+ const goWork = path.join(root, 'go.work');
464
+ if (!fs.existsSync(goWork)) {
465
+ return [];
466
+ }
467
+ const goBin = getCustomPath('go', opts);
468
+ let output;
469
+ try {
470
+ output = invokeCommand(goBin, ['work', 'edit', '-json', goWork], { cwd: root });
471
+ }
472
+ catch {
473
+ return [];
474
+ }
475
+ let workspace;
476
+ try {
477
+ workspace = JSON.parse(output.toString().trim());
478
+ }
479
+ catch {
480
+ return [];
481
+ }
482
+ const useEntries = workspace.Use || [];
483
+ if (useEntries.length === 0) {
484
+ return [];
485
+ }
486
+ const manifestPaths = [];
487
+ for (const entry of useEntries) {
488
+ const diskPath = entry.DiskPath;
489
+ if (!diskPath) {
490
+ continue;
491
+ }
492
+ const moduleDir = path.resolve(root, diskPath);
493
+ const goMod = path.join(moduleDir, 'go.mod');
494
+ if (fs.existsSync(goMod)) {
495
+ manifestPaths.push(goMod);
496
+ }
497
+ }
498
+ const ignorePatterns = resolveWorkspaceDiscoveryIgnore(opts);
499
+ return filterManifestPathsByDiscoveryIgnore(manifestPaths, root, ignorePatterns);
500
+ }
@@ -0,0 +1,4 @@
1
+ export function getParser(): Promise<Parser>;
2
+ export function getRequireQuery(): Promise<Query>;
3
+ import { Parser } from 'web-tree-sitter';
4
+ import { Query } from 'web-tree-sitter';
@@ -0,0 +1,16 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { Language, Parser, Query } from 'web-tree-sitter';
3
+ const wasmUrl = new URL('./tree-sitter-gomod.wasm', import.meta.url);
4
+ async function init() {
5
+ await Parser.init();
6
+ const wasmBytes = new Uint8Array(await readFile(wasmUrl));
7
+ return await Language.load(wasmBytes);
8
+ }
9
+ export async function getParser() {
10
+ const language = await init();
11
+ return new Parser().setLanguage(language);
12
+ }
13
+ export async function getRequireQuery() {
14
+ const language = await init();
15
+ return new Query(language, '(require_spec (module_path) @name (version) @version) @spec');
16
+ }
@@ -1,3 +1,46 @@
1
+ /**
2
+ * Discover all build.gradle[.kts] manifest paths in a Gradle multi-project build.
3
+ * Uses a custom init script to get structured project listing.
4
+ *
5
+ * @param {string} workspaceRoot - Absolute or relative path to workspace root (must contain settings.gradle[.kts])
6
+ * @param {import('../index.js').Options} [opts={}]
7
+ * @returns {Promise<string[]>} Paths to build.gradle[.kts] files (absolute)
8
+ */
9
+ export function discoverGradleSubprojects(workspaceRoot: string, opts?: import("../index.js").Options): Promise<string[]>;
10
+ /**
11
+ * Parse the structured output from the Gradle init script.
12
+ *
13
+ * @param {string} raw - Raw stdout from gradle
14
+ * @returns {{ path: string, dir: string }[]}
15
+ */
16
+ export function parseGradleInitScriptOutput(raw: string): {
17
+ path: string;
18
+ dir: string;
19
+ }[];
20
+ /**
21
+ * Parse the structured output from the Gradle hash init script.
22
+ * Each recognised line has the form `::DA_HASH::group:name:version::<file-path>`.
23
+ *
24
+ * @param {string} raw - Raw stdout from gradle
25
+ * @returns {{ id: string, file: string }[]} component id (`group:name:version`) and absolute artifact file path
26
+ */
27
+ export function parseGradleHashScriptOutput(raw: string): {
28
+ id: string;
29
+ file: string;
30
+ }[];
31
+ /**
32
+ * Parse a Gradle module component id (`group:name:version`) into its coordinate
33
+ * parts. The caller builds the canonical PURL key from these parts using the same
34
+ * builder {@link parseDep} uses, so the stored key and the lookup key cannot drift.
35
+ *
36
+ * @param {string} id - the Gradle component id
37
+ * @returns {{group: string, name: string, version: string}|null} the parsed coordinate, or null when the id is malformed
38
+ */
39
+ export function parseComponentId(id: string): {
40
+ group: string;
41
+ name: string;
42
+ version: string;
43
+ } | null;
1
44
  /**
2
45
  * This class provides common functionality for Groovy and Kotlin DSL files.
3
46
  */
@@ -35,6 +78,30 @@ export default class Java_gradle extends Base_java {
35
78
  * @returns {Provided}
36
79
  */
37
80
  provideComponent(manifest: string, opts?: {}): Provided;
81
+ /**
82
+ * Compute SHA-256 hashes for the resolved artifacts of a Gradle manifest.
83
+ *
84
+ * Rather than scanning the local Gradle cache, this asks Gradle itself for
85
+ * the resolved artifact files via an init script (mirroring the pattern used
86
+ * by {@link discoverGradleSubprojects}), then hashes each file with the
87
+ * Node.js `crypto` module. The result is keyed by the canonical PURL string
88
+ * built via {@link Base_java#toPurl} — the same builder {@link parseDep} uses
89
+ * for the lookup — so the stored key and the lookup key cannot drift.
90
+ *
91
+ * Degrades gracefully: if Gradle cannot be invoked, the init script fails, or
92
+ * an artifact has no readable file (e.g. BOM/`platform()` dependencies), the
93
+ * hash for that component is omitted rather than throwing. A warning is emitted
94
+ * on every degradation path so incomplete hash coverage is visible even without
95
+ * `TRUSTIFY_DA_DEBUG` (mirroring the pip/cargo providers).
96
+ *
97
+ * @param {string} manifest - path to build.gradle[.kts]
98
+ * @param {{}} [opts={}] - optional various options to pass along the application
99
+ * @returns {Promise<Map<string, Array<{alg: string, content: string}>>>} map of canonical PURL string to CycloneDX hashes
100
+ */
101
+ parseGradleHashes(manifest: string, opts?: {}): Promise<Map<string, Array<{
102
+ alg: string;
103
+ content: string;
104
+ }>>>;
38
105
  #private;
39
106
  }
40
107
  export type Provided = import("../provider.js").Provided;