@trustify-da/trustify-da-javascript-client 0.3.0-ea.ff266a3 → 0.3.0-ea.ff694a0

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 (70) hide show
  1. package/README.md +179 -11
  2. package/dist/package.json +13 -4
  3. package/dist/src/analysis.d.ts +16 -0
  4. package/dist/src/analysis.js +53 -4
  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 +171 -4
  8. package/dist/src/cyclone_dx_sbom.d.ts +14 -1
  9. package/dist/src/cyclone_dx_sbom.js +34 -6
  10. package/dist/src/index.d.ts +134 -2
  11. package/dist/src/index.js +352 -6
  12. package/dist/src/license/index.d.ts +2 -2
  13. package/dist/src/license/index.js +4 -4
  14. package/dist/src/license/license_utils.d.ts +40 -0
  15. package/dist/src/license/license_utils.js +134 -0
  16. package/dist/src/license/licenses_api.js +9 -2
  17. package/dist/src/license/project_license.d.ts +1 -6
  18. package/dist/src/license/project_license.js +4 -81
  19. package/dist/src/oci_image/utils.js +11 -2
  20. package/dist/src/provider.d.ts +7 -3
  21. package/dist/src/provider.js +16 -5
  22. package/dist/src/providers/base_java.d.ts +5 -9
  23. package/dist/src/providers/base_java.js +9 -38
  24. package/dist/src/providers/base_javascript.d.ts +30 -3
  25. package/dist/src/providers/base_javascript.js +115 -25
  26. package/dist/src/providers/base_pyproject.d.ts +158 -0
  27. package/dist/src/providers/base_pyproject.js +322 -0
  28. package/dist/src/providers/golang_gomodules.d.ts +22 -12
  29. package/dist/src/providers/golang_gomodules.js +167 -120
  30. package/dist/src/providers/gomod_parser.d.ts +4 -0
  31. package/dist/src/providers/gomod_parser.js +16 -0
  32. package/dist/src/providers/java_gradle.d.ts +19 -0
  33. package/dist/src/providers/java_gradle.js +118 -3
  34. package/dist/src/providers/java_maven.d.ts +9 -1
  35. package/dist/src/providers/java_maven.js +103 -10
  36. package/dist/src/providers/javascript_bun.d.ts +10 -0
  37. package/dist/src/providers/javascript_bun.js +100 -0
  38. package/dist/src/providers/javascript_npm.d.ts +1 -0
  39. package/dist/src/providers/javascript_npm.js +21 -0
  40. package/dist/src/providers/javascript_pnpm.d.ts +1 -1
  41. package/dist/src/providers/javascript_pnpm.js +8 -4
  42. package/dist/src/providers/manifest.d.ts +2 -0
  43. package/dist/src/providers/manifest.js +22 -4
  44. package/dist/src/providers/marker_evaluator.d.ts +14 -0
  45. package/dist/src/providers/marker_evaluator.js +191 -0
  46. package/dist/src/providers/processors/yarn_berry_processor.js +88 -5
  47. package/dist/src/providers/python_controller.d.ts +5 -1
  48. package/dist/src/providers/python_controller.js +8 -4
  49. package/dist/src/providers/python_pip.d.ts +5 -0
  50. package/dist/src/providers/python_pip.js +8 -7
  51. package/dist/src/providers/python_pip_pyproject.d.ts +61 -0
  52. package/dist/src/providers/python_pip_pyproject.js +146 -0
  53. package/dist/src/providers/python_poetry.d.ts +75 -0
  54. package/dist/src/providers/python_poetry.js +238 -0
  55. package/dist/src/providers/python_uv.d.ts +55 -0
  56. package/dist/src/providers/python_uv.js +227 -0
  57. package/dist/src/providers/requirements_parser.js +4 -3
  58. package/dist/src/providers/rust_cargo.d.ts +53 -0
  59. package/dist/src/providers/rust_cargo.js +614 -0
  60. package/dist/src/providers/tree-sitter-gomod.wasm +0 -0
  61. package/dist/src/providers/tree-sitter-requirements.wasm +0 -0
  62. package/dist/src/sbom.d.ts +14 -1
  63. package/dist/src/sbom.js +13 -2
  64. package/dist/src/tools.d.ts +26 -0
  65. package/dist/src/tools.js +58 -0
  66. package/dist/src/workspace.d.ts +70 -0
  67. package/dist/src/workspace.js +256 -0
  68. package/package.json +14 -5
  69. package/dist/src/license/compatibility.d.ts +0 -18
  70. package/dist/src/license/compatibility.js +0 -45
@@ -1,10 +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';
4
+ import { readLicenseFile } from '../license/license_utils.js';
5
5
  import Sbom from '../sbom.js';
6
6
  import { getCustom, getCustomPath, invokeCommand } from "../tools.js";
7
- 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'; } };
8
10
  /** @typedef {import('../provider').Provider} */
9
11
  /** @typedef {import('../provider').Provided} Provided */
10
12
  /** @typedef {{name: string, version: string}} Package */
@@ -16,46 +18,46 @@ export default { isSupported, validateLockFile, provideComponent, provideStack,
16
18
  const ecosystem = 'golang';
17
19
  const defaultMainModuleVersion = "v0.0.0";
18
20
  /**
19
- * @param {string} manifestName - the subject manifest name-type
20
- * @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
21
23
  */
22
24
  function isSupported(manifestName) {
23
25
  return 'go.mod' === manifestName;
24
26
  }
25
27
  /**
26
28
  * Go modules have no standard license field in go.mod
27
- * @param {string} manifestPath - path to go.mod
29
+ * @param {string} manifestPath path to go.mod
28
30
  * @returns {string|null}
29
31
  */
30
32
  // eslint-disable-next-line no-unused-vars
31
- function readLicenseFromManifest(manifestPath) { return null; }
33
+ function readLicenseFromManifest(manifestPath) { return readLicenseFile(manifestPath); }
32
34
  /**
33
- * @param {string} manifestDir - the directory where the manifest lies
35
+ * @param {string} manifestDir the directory where the manifest lies
34
36
  */
35
37
  function validateLockFile() { return true; }
36
38
  /**
37
39
  * Provide content and content type for maven-maven stack analysis.
38
- * @param {string} manifest - the manifest path or name
39
- * @param {{}} [opts={}] - optional various options to pass along the application
40
- * @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>}
41
43
  */
42
- function provideStack(manifest, opts = {}) {
44
+ async function provideStack(manifest, opts = {}) {
43
45
  return {
44
46
  ecosystem,
45
- content: getSBOM(manifest, opts, true),
47
+ content: await getSBOM(manifest, opts, true),
46
48
  contentType: 'application/vnd.cyclonedx+json'
47
49
  };
48
50
  }
49
51
  /**
50
52
  * Provide content and content type for maven-maven component analysis.
51
- * @param {string} manifest - path to go.mod for component report
52
- * @param {{}} [opts={}] - optional various options to pass along the application
53
- * @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>}
54
56
  */
55
- function provideComponent(manifest, opts = {}) {
57
+ async function provideComponent(manifest, opts = {}) {
56
58
  return {
57
59
  ecosystem,
58
- content: getSBOM(manifest, opts, false),
60
+ content: await getSBOM(manifest, opts, false),
59
61
  contentType: 'application/vnd.cyclonedx+json'
60
62
  };
61
63
  }
@@ -76,50 +78,52 @@ function getChildVertexFromEdge(edge) {
76
78
  return edge.split(" ")[1];
77
79
  }
78
80
  /**
79
- *
80
- * @param line one row from go.mod file
81
- * @return {boolean} whether line from go.mod should be considered as ignored or not
81
+ * Check whether a require_spec has a valid exhortignore marker.
82
+ * For direct dependencies: `//exhortignore` or `// exhortignore`
83
+ * For indirect dependencies: `// indirect; exhortignore` (semicolon-separated)
84
+ * @param {import('web-tree-sitter').SyntaxNode} specNode
85
+ * @return {boolean}
82
86
  */
83
- function ignoredLine(line) {
84
- let result = false;
85
- if (line.match(".*exhortignore.*")) {
86
- if (line.match(".+//\\s*exhortignore") || line.match(".+//\\sindirect (//)?\\s*exhortignore")) {
87
- let trimmedRow = line.trim();
88
- if (!trimmedRow.startsWith("module ") && !trimmedRow.startsWith("go ") && !trimmedRow.startsWith("require (") && !trimmedRow.startsWith("require(")
89
- && !trimmedRow.startsWith("exclude ") && !trimmedRow.startsWith("replace ") && !trimmedRow.startsWith("retract ") && !trimmedRow.startsWith("use ")
90
- && !trimmedRow.includes("=>")) {
91
- if (trimmedRow.startsWith("require ") || trimmedRow.match("^[a-z.0-9/-]+\\s{1,2}[vV][0-9]\\.[0-9](\\.[0-9]){0,2}.*")) {
92
- result = true;
93
- }
94
- }
87
+ function hasExhortIgnore(specNode) {
88
+ // Ideally this would be the following tree-sitter query instead, but for some
89
+ // reason it throws an error here but not in the playground.
90
+ // (require_spec) ((module_path) @path (version) (comment) @comment (#match? @comment "^//.*exhortignore"))
91
+ // QueryError: Bad pattern structure at offset 53: '(comment) @comment (#match? @comment "^//.*exhortignore")) @spec'...
92
+ let comments = specNode.children.filter(c => c.type === 'comment');
93
+ for (let comment of comments) {
94
+ let text = comment.text;
95
+ if (/^\/\/\s*indirect;\s*exhortignore/.test(text)) {
96
+ return true;
97
+ }
98
+ if (/^\/\/\s*exhortignore/.test(text)) {
99
+ return true;
95
100
  }
96
101
  }
97
- return result;
98
- }
99
- /**
100
- * extract package name from go.mod line that contains exhortignore comment.
101
- * @param line a row contains exhortignore as part of a comment
102
- * @return {string} the full package name + group/namespace + version
103
- * @private
104
- */
105
- function extractPackageName(line) {
106
- let trimmedRow = line.trim();
107
- let firstRemarkNotationOccurrence = trimmedRow.indexOf("//");
108
- return trimmedRow.substring(0, firstRemarkNotationOccurrence).trim();
102
+ return false;
109
103
  }
110
104
  /**
111
105
  *
112
- * @param {string } manifest - path to manifest
113
- * @return {[PackageURL]} list of ignored dependencies d
106
+ * @param {string} manifestContent go.mod file contents
107
+ * @param {import('web-tree-sitter').Parser} parser
108
+ * @param {import('web-tree-sitter').Query} requireQuery
109
+ * @return {PackageURL[]} list of ignored dependencies
114
110
  */
115
- function getIgnoredDeps(manifest) {
116
- let goMod = fs.readFileSync(manifest).toString().trim();
117
- let lines = goMod.split(getLineSeparatorGolang());
118
- return lines.filter(line => ignoredLine(line)).map(line => extractPackageName(line)).map(dep => toPurl(dep, /[ ]{1,3}/));
111
+ function getIgnoredDeps(manifestContent, parser, requireQuery) {
112
+ let tree = parser.parse(manifestContent);
113
+ return requireQuery.matches(tree.rootNode)
114
+ .filter(match => {
115
+ let specNode = match.captures.find(c => c.name === 'spec').node;
116
+ return hasExhortIgnore(specNode);
117
+ })
118
+ .map(match => {
119
+ let name = match.captures.find(c => c.name === 'name').node.text;
120
+ let version = match.captures.find(c => c.name === 'version').node.text;
121
+ return toPurl(`${name} ${version}`, /[ ]{1,3}/);
122
+ });
119
123
  }
120
124
  /**
121
125
  *
122
- * @param {[PackageURL]}allIgnoredDeps - list of purls of all dependencies that should be ignored
126
+ * @param {PackageURL[]} allIgnoredDeps list of purls of all dependencies that should be ignored
123
127
  * @param {PackageURL} purl object to be checked if needed to be ignored
124
128
  * @return {boolean}
125
129
  */
@@ -138,59 +142,29 @@ function enforceRemovingIgnoredDepsInCaseOfAutomaticVersionUpdate(ignoredDeps, s
138
142
  }
139
143
  /**
140
144
  *
141
- * @param {[string]} lines - array of lines of go.mod manifest
142
- * @param {string} goMod - content of go.mod manifest
143
- * @return {[string]} all dependencies from go.mod file as array
145
+ * @param {string} manifestContent go.mod file contents
146
+ * @param {import('web-tree-sitter').Parser} parser
147
+ * @param {import('web-tree-sitter').Query} requireQuery
148
+ * @return {string[]} all dependencies from go.mod file as "name version" strings
144
149
  */
145
- function collectAllDepsFromManifest(lines, goMod) {
146
- let result;
147
- // collect all deps that starts with require keyword
148
- result = lines.filter((line) => line.trim().startsWith("require") && !line.includes("(")).map((dep) => dep.substring("require".length).trim());
149
- // collect all deps that are inside `require` blocks
150
- let currentSegmentOfGoMod = goMod;
151
- let requirePositionObject = decideRequireBlockIndex(currentSegmentOfGoMod);
152
- while (requirePositionObject.index > -1) {
153
- let depsInsideRequirementsBlock = currentSegmentOfGoMod.substring(requirePositionObject.index + requirePositionObject.startingOffeset).trim();
154
- let endOfBlockIndex = depsInsideRequirementsBlock.indexOf(")");
155
- let currentIndex = 0;
156
- while (currentIndex < endOfBlockIndex) {
157
- let endOfLinePosition = depsInsideRequirementsBlock.indexOf(EOL, currentIndex);
158
- let dependency = depsInsideRequirementsBlock.substring(currentIndex, endOfLinePosition);
159
- result.push(dependency.trim());
160
- currentIndex = endOfLinePosition + 1;
161
- }
162
- currentSegmentOfGoMod = currentSegmentOfGoMod.substring(endOfBlockIndex + 1).trim();
163
- requirePositionObject = decideRequireBlockIndex(currentSegmentOfGoMod);
164
- }
165
- function decideRequireBlockIndex(goMod) {
166
- let object = {};
167
- let index = goMod.indexOf("require(");
168
- object.startingOffeset = "require(".length;
169
- if (index === -1) {
170
- index = goMod.indexOf("require (");
171
- object.startingOffeset = "require (".length;
172
- if (index === -1) {
173
- index = goMod.indexOf("require (");
174
- object.startingOffeset = "require (".length;
175
- }
176
- }
177
- object.index = index;
178
- return object;
179
- }
180
- return result;
150
+ function collectAllDepsFromManifest(manifestContent, parser, requireQuery) {
151
+ let tree = parser.parse(manifestContent);
152
+ return requireQuery.matches(tree.rootNode).map(match => {
153
+ let name = match.captures.find(c => c.name === 'name').node.text;
154
+ let version = match.captures.find(c => c.name === 'version').node.text;
155
+ return `${name} ${version}`;
156
+ });
181
157
  }
182
158
  /**
183
159
  *
184
160
  * @param {string} rootElementName the rootElementName element of go mod graph, to compare only direct deps from go mod graph against go.mod manifest
185
- * @param{[string]} goModGraphOutputRows the goModGraphOutputRows from go mod graph' output
186
- * @param {string }manifest path to go.mod manifest on file system
161
+ * @param {string[]} goModGraphOutputRows the goModGraphOutputRows from go mod graph' output
162
+ * @param {string} manifestContent go.mod file contents
187
163
  * @private
188
164
  */
189
- function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, manifest) {
190
- let goMod = fs.readFileSync(manifest).toString().trim();
191
- let lines = goMod.split(getLineSeparatorGolang());
165
+ function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, manifestContent, parser, requireQuery) {
192
166
  let comparisonLines = goModGraphOutputRows.filter((line) => line.startsWith(rootElementName)).map((line) => getChildVertexFromEdge(line));
193
- let manifestDeps = collectAllDepsFromManifest(lines, goMod);
167
+ let manifestDeps = collectAllDepsFromManifest(manifestContent, parser, requireQuery);
194
168
  try {
195
169
  comparisonLines.forEach((dependency) => {
196
170
  let parts = dependency.split("@");
@@ -202,7 +176,7 @@ function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, man
202
176
  let currentVersion = components[1];
203
177
  if (currentDepName === depName) {
204
178
  if (currentVersion !== version) {
205
- 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`);
179
+ 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`);
206
180
  }
207
181
  }
208
182
  });
@@ -218,10 +192,10 @@ function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, man
218
192
  * @param {string} manifest - path for go.mod
219
193
  * @param {{}} [opts={}] - optional various options to pass along the application
220
194
  * @param {boolean} includeTransitive - whether the sbom should contain transitive dependencies of the main module or not.
221
- * @returns {string} the SBOM json content
195
+ * @returns {Promise<string>} the SBOM json content
222
196
  * @private
223
197
  */
224
- function getSBOM(manifest, opts = {}, includeTransitive) {
198
+ async function getSBOM(manifest, opts = {}, includeTransitive) {
225
199
  // get custom goBin path
226
200
  let goBin = getCustomPath('go', opts);
227
201
  // verify goBin is accessible
@@ -247,14 +221,25 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
247
221
  catch (error) {
248
222
  throw new Error('failed to determine root module name', { cause: error });
249
223
  }
250
- let ignoredDeps = getIgnoredDeps(manifest);
224
+ let manifestContent = fs.readFileSync(manifest).toString();
225
+ let [parser, requireQuery] = await Promise.all([getParser(), getRequireQuery()]);
226
+ let ignoredDeps = getIgnoredDeps(manifestContent, parser, requireQuery);
251
227
  let allIgnoredDeps = ignoredDeps.map((dep) => dep.toString());
252
228
  let sbom = new Sbom();
253
229
  let rows = goGraphOutput.split(getLineSeparatorGolang()).filter(line => !line.includes(' go@'));
254
- let root = getParentVertexFromEdge(goModEditOutput['Module']['Path']);
230
+ let root = goModEditOutput['Module']['Path'];
231
+ // Build set of direct dependency paths from go mod edit -json
232
+ let directDepPaths = new Set();
233
+ if (goModEditOutput['Require']) {
234
+ goModEditOutput['Require'].forEach(req => {
235
+ if (!req['Indirect']) {
236
+ directDepPaths.add(req['Path']);
237
+ }
238
+ });
239
+ }
255
240
  let matchManifestVersions = getCustom("MATCH_MANIFEST_VERSIONS", "false", opts);
256
241
  if (matchManifestVersions === "true") {
257
- performManifestVersionsCheck(root, rows, manifest);
242
+ performManifestVersionsCheck(root, rows, manifestContent, parser, requireQuery);
258
243
  }
259
244
  const mainModule = toPurl(root, "@");
260
245
  const license = readLicenseFromManifest(manifest);
@@ -272,7 +257,11 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
272
257
  currentParent = getParentVertexFromEdge(row);
273
258
  source = toPurl(currentParent, "@");
274
259
  }
275
- let target = toPurl(getChildVertexFromEdge(row), "@");
260
+ let child = getChildVertexFromEdge(row);
261
+ let target = toPurl(child, "@");
262
+ if (getParentVertexFromEdge(row) === root && !directDepPaths.has(getPackageName(child))) {
263
+ return;
264
+ }
276
265
  sbom.addDependency(source, target);
277
266
  });
278
267
  // at the end, filter out all ignored dependencies including versions.
@@ -282,10 +271,12 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
282
271
  else {
283
272
  let directDependencies = rows.filter(row => row.startsWith(root));
284
273
  directDependencies.forEach(pair => {
285
- let dependency = getChildVertexFromEdge(pair);
286
- let depPurl = toPurl(dependency, "@");
287
- if (dependencyNotIgnored(ignoredDeps, depPurl)) {
288
- sbom.addDependency(mainModule, depPurl);
274
+ let child = getChildVertexFromEdge(pair);
275
+ let target = toPurl(child, "@");
276
+ if (dependencyNotIgnored(ignoredDeps, target)) {
277
+ if (directDepPaths.has(getPackageName(child))) {
278
+ sbom.addDependency(mainModule, target);
279
+ }
289
280
  }
290
281
  });
291
282
  enforceRemovingIgnoredDepsInCaseOfAutomaticVersionUpdate(ignoredDeps, sbom);
@@ -295,7 +286,7 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
295
286
  /**
296
287
  * Utility function for creating Purl String
297
288
 
298
- * @param {string }dependency the name of the artifact, can include a namespace(group) or not - namespace/artifactName.
289
+ * @param {string} dependency the name of the artifact, can include a namespace(group) or not - namespace/artifactName.
299
290
  * @param {RegExp} delimiter delimiter between name of dependency and version
300
291
  * @private
301
292
  * @returns {PackageURL|null} PackageUrl Object ready to be used in SBOM
@@ -320,12 +311,12 @@ function toPurl(dependency, delimiter) {
320
311
  }
321
312
  return pkg;
322
313
  }
323
- /** This function gets rows from go mod graph , and go.mod graph, and selecting for all
314
+ /** This function gets rows from go mod graph, and go.mod graph, and selecting for all
324
315
  * packages the has more than one minor the final versions as selected by golang MVS algorithm.
325
- * @param {[string]}rows all the rows from go modules dependency tree
316
+ * @param {string[]} rows all the rows from go modules dependency tree
326
317
  * @param {string} manifestPath the path of the go.mod file
327
318
  * @param {string} path to go binary
328
- * @return {[string]} rows that contains final versions.
319
+ * @return {string[]} rows that contains final versions.
329
320
  */
330
321
  function getFinalPackagesVersionsForModule(rows, manifestPath, goBin) {
331
322
  let manifestDir = path.dirname(manifestPath);
@@ -338,13 +329,7 @@ function getFinalPackagesVersionsForModule(rows, manifestPath, goBin) {
338
329
  catch (error) {
339
330
  throw new Error('failed to list all modules', { cause: error });
340
331
  }
341
- let finalVersionModules = new Map();
342
- finalVersionsForAllModules.split(getLineSeparatorGolang()).filter(string => string.trim() !== "")
343
- .filter(string => string.trim().split(" ").length === 2)
344
- .forEach((dependency) => {
345
- let dep = dependency.split(" ");
346
- finalVersionModules[dep[0]] = dep[1];
347
- });
332
+ let finalVersionModules = parseModuleVersions(finalVersionsForAllModules);
348
333
  let finalVersionModulesArray = new Array();
349
334
  rows.filter(string => string.trim() !== "").forEach(module => {
350
335
  let child = getChildVertexFromEdge(module);
@@ -380,7 +365,7 @@ function getFinalPackagesVersionsForModule(rows, manifestPath, goBin) {
380
365
  /**
381
366
  *
382
367
  * @param {string} fullPackage - full package with its name and version
383
- * @return -{string} package name only
368
+ * @return {string} package name only
384
369
  * @private
385
370
  */
386
371
  function getPackageName(fullPackage) {
@@ -398,14 +383,76 @@ function isSpecialGoModule(moduleName) {
398
383
  /**
399
384
  *
400
385
  * @param {string} fullPackage - full package with its name and version
401
- * @return -{string} package version only
386
+ * @return {string|undefined} package version only
402
387
  * @private
403
388
  */
404
389
  function getVersionOfPackage(fullPackage) {
405
390
  let parts = fullPackage.split("@");
406
391
  return parts.length > 1 ? parts[1] : undefined;
407
392
  }
393
+ function parseModuleVersions(goListOutput) {
394
+ let modules = {};
395
+ goListOutput.split(getLineSeparatorGolang()).filter(string => string.trim() !== "")
396
+ .forEach((line) => {
397
+ let parts = line.trim().split(" ");
398
+ if (parts.length === 2) {
399
+ modules[parts[0]] = parts[1];
400
+ }
401
+ else if (parts.length >= 4 && parts[2] === "=>") {
402
+ modules[parts[0]] = parts[parts.length - 1];
403
+ }
404
+ });
405
+ return modules;
406
+ }
408
407
  function getLineSeparatorGolang() {
409
408
  let reg = /\n|\r\n/;
410
409
  return reg;
411
410
  }
411
+ /**
412
+ * Discover all go.mod manifest paths in a Go workspace.
413
+ * Uses `go work edit -json` to get workspace members.
414
+ *
415
+ * @param {string} workspaceRoot - Absolute or relative path to workspace root (must contain go.work)
416
+ * @param {import('../index.js').Options} [opts={}]
417
+ * @returns {Promise<string[]>} Paths to go.mod files (absolute)
418
+ */
419
+ export async function discoverGoWorkspaceModules(workspaceRoot, opts = {}) {
420
+ const root = path.resolve(workspaceRoot);
421
+ const goWork = path.join(root, 'go.work');
422
+ if (!fs.existsSync(goWork)) {
423
+ return [];
424
+ }
425
+ const goBin = getCustomPath('go', opts);
426
+ let output;
427
+ try {
428
+ output = invokeCommand(goBin, ['work', 'edit', '-json', goWork], { cwd: root });
429
+ }
430
+ catch {
431
+ return [];
432
+ }
433
+ let workspace;
434
+ try {
435
+ workspace = JSON.parse(output.toString().trim());
436
+ }
437
+ catch {
438
+ return [];
439
+ }
440
+ const useEntries = workspace.Use || [];
441
+ if (useEntries.length === 0) {
442
+ return [];
443
+ }
444
+ const manifestPaths = [];
445
+ for (const entry of useEntries) {
446
+ const diskPath = entry.DiskPath;
447
+ if (!diskPath) {
448
+ continue;
449
+ }
450
+ const moduleDir = path.resolve(root, diskPath);
451
+ const goMod = path.join(moduleDir, 'go.mod');
452
+ if (fs.existsSync(goMod)) {
453
+ manifestPaths.push(goMod);
454
+ }
455
+ }
456
+ const ignorePatterns = resolveWorkspaceDiscoveryIgnore(opts);
457
+ return filterManifestPathsByDiscoveryIgnore(manifestPaths, root, ignorePatterns);
458
+ }
@@ -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,22 @@
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
+ }[];
1
20
  /**
2
21
  * This class provides common functionality for Groovy and Kotlin DSL files.
3
22
  */
@@ -1,8 +1,13 @@
1
+ import crypto from 'node:crypto';
1
2
  import fs from 'node:fs';
3
+ import os from 'node:os';
2
4
  import path from 'node:path';
3
5
  import { EOL } from 'os';
4
- import TOML from 'fast-toml';
6
+ import { parse as parseToml } from 'smol-toml';
7
+ import { readLicenseFile } from '../license/license_utils.js';
5
8
  import Sbom from '../sbom.js';
9
+ import { invokeCommand } from '../tools.js';
10
+ import { filterManifestPathsByDiscoveryIgnore, resolveWorkspaceDiscoveryIgnore } from '../workspace.js';
6
11
  import Base_java, { ecosystem_gradle } from "./base_java.js";
7
12
  /** @typedef {import('../provider.js').Provider} */
8
13
  /** @typedef {import('../provider.js').Provided} Provided */
@@ -55,7 +60,7 @@ export default class Java_gradle extends Base_java {
55
60
  * @returns {null}
56
61
  */
57
62
  // eslint-disable-next-line no-unused-vars
58
- readLicenseFromManifest(manifestPath) { return null; }
63
+ readLicenseFromManifest(manifestPath) { return readLicenseFile(manifestPath); }
59
64
  /**
60
65
  * Provide content and content type for stack analysis.
61
66
  * @param {string} manifest - the manifest path or name
@@ -361,7 +366,7 @@ export default class Java_gradle extends Base_java {
361
366
  // Read and parse the TOML file
362
367
  let pathOfToml = path.join(path.dirname(manifestPath), "gradle", "libs.versions.toml");
363
368
  const tomlString = fs.readFileSync(pathOfToml).toString();
364
- let tomlObject = TOML.parse(tomlString);
369
+ let tomlObject = parseToml(tomlString);
365
370
  let groupPlusArtifactObject = tomlObject.libraries[alias];
366
371
  let parts = groupPlusArtifactObject.module.split(":");
367
372
  let groupId = parts[0];
@@ -406,3 +411,113 @@ export default class Java_gradle extends Base_java {
406
411
  return undefined;
407
412
  }
408
413
  }
414
+ const DEFAULT_GRADLE_DISCOVERY_IGNORE = [
415
+ '**/build/**',
416
+ '**/.gradle/**',
417
+ ];
418
+ /** Gradle init script that emits structured project listing. */
419
+ const GRADLE_INIT_SCRIPT = `allprojects {
420
+ task daListProjects {
421
+ doLast {
422
+ println "::DA_PROJECT::\${project.path}::\${project.projectDir}"
423
+ }
424
+ }
425
+ }
426
+ `;
427
+ /**
428
+ * Discover all build.gradle[.kts] manifest paths in a Gradle multi-project build.
429
+ * Uses a custom init script to get structured project listing.
430
+ *
431
+ * @param {string} workspaceRoot - Absolute or relative path to workspace root (must contain settings.gradle[.kts])
432
+ * @param {import('../index.js').Options} [opts={}]
433
+ * @returns {Promise<string[]>} Paths to build.gradle[.kts] files (absolute)
434
+ */
435
+ export async function discoverGradleSubprojects(workspaceRoot, opts = {}) {
436
+ const root = path.resolve(workspaceRoot);
437
+ const hasSettings = fs.existsSync(path.join(root, 'settings.gradle'))
438
+ || fs.existsSync(path.join(root, 'settings.gradle.kts'));
439
+ if (!hasSettings) {
440
+ return [];
441
+ }
442
+ const manifestPaths = [];
443
+ const rootBuildKts = path.join(root, 'build.gradle.kts');
444
+ const rootBuild = path.join(root, 'build.gradle');
445
+ const rootManifest = fs.existsSync(rootBuildKts) ? rootBuildKts : fs.existsSync(rootBuild) ? rootBuild : null;
446
+ if (rootManifest) {
447
+ manifestPaths.push(rootManifest);
448
+ }
449
+ let gradleBin;
450
+ try {
451
+ gradleBin = new Java_gradle().selectToolBinary(rootManifest || rootBuild, opts);
452
+ }
453
+ catch {
454
+ const ignorePatterns = [...resolveWorkspaceDiscoveryIgnore(opts), ...DEFAULT_GRADLE_DISCOVERY_IGNORE];
455
+ return filterManifestPathsByDiscoveryIgnore(manifestPaths, root, ignorePatterns);
456
+ }
457
+ const initScriptPath = path.join(os.tmpdir(), `da-list-projects-${crypto.randomUUID()}.gradle`);
458
+ try {
459
+ fs.writeFileSync(initScriptPath, GRADLE_INIT_SCRIPT);
460
+ let output;
461
+ try {
462
+ output = invokeCommand(gradleBin, [
463
+ '-q', '--no-daemon',
464
+ '--init-script', initScriptPath,
465
+ 'daListProjects',
466
+ ], { cwd: root });
467
+ }
468
+ catch {
469
+ const ignorePatterns = [...resolveWorkspaceDiscoveryIgnore(opts), ...DEFAULT_GRADLE_DISCOVERY_IGNORE];
470
+ return filterManifestPathsByDiscoveryIgnore(manifestPaths, root, ignorePatterns);
471
+ }
472
+ const projects = parseGradleInitScriptOutput(output.toString());
473
+ for (const proj of projects) {
474
+ if (proj.path === ':') {
475
+ continue;
476
+ }
477
+ const projDir = path.resolve(proj.dir);
478
+ const buildKts = path.join(projDir, 'build.gradle.kts');
479
+ const buildGroovy = path.join(projDir, 'build.gradle');
480
+ if (fs.existsSync(buildKts)) {
481
+ manifestPaths.push(buildKts);
482
+ }
483
+ else if (fs.existsSync(buildGroovy)) {
484
+ manifestPaths.push(buildGroovy);
485
+ }
486
+ }
487
+ }
488
+ finally {
489
+ try {
490
+ fs.unlinkSync(initScriptPath);
491
+ }
492
+ catch { /* ignore */ }
493
+ }
494
+ const ignorePatterns = [...resolveWorkspaceDiscoveryIgnore(opts), ...DEFAULT_GRADLE_DISCOVERY_IGNORE];
495
+ return filterManifestPathsByDiscoveryIgnore(manifestPaths, root, ignorePatterns);
496
+ }
497
+ /**
498
+ * Parse the structured output from the Gradle init script.
499
+ *
500
+ * @param {string} raw - Raw stdout from gradle
501
+ * @returns {{ path: string, dir: string }[]}
502
+ */
503
+ export function parseGradleInitScriptOutput(raw) {
504
+ const projects = [];
505
+ for (const rawLine of raw.split('\n')) {
506
+ const line = rawLine.trimEnd();
507
+ if (!line.startsWith('::DA_PROJECT::')) {
508
+ continue;
509
+ }
510
+ const prefix = '::DA_PROJECT::';
511
+ const remainder = line.substring(prefix.length);
512
+ const lastSep = remainder.lastIndexOf('::');
513
+ if (lastSep < 0) {
514
+ continue;
515
+ }
516
+ const projPath = remainder.substring(0, lastSep);
517
+ const dir = remainder.substring(lastSep + 2);
518
+ if (projPath && dir) {
519
+ projects.push({ path: projPath, dir });
520
+ }
521
+ }
522
+ return projects;
523
+ }
@@ -1,3 +1,11 @@
1
+ /**
2
+ * Discover all pom.xml manifest paths in a Maven multi-module project.
3
+ *
4
+ * @param {string} workspaceRoot - Absolute or relative path to workspace root (must contain pom.xml)
5
+ * @param {object} [opts={}]
6
+ * @returns {Promise<string[]>} Paths to pom.xml files (absolute)
7
+ */
8
+ export function discoverMavenModules(workspaceRoot: string, opts?: object): Promise<string[]>;
1
9
  /** @typedef {import('../provider').Provider} */
2
10
  /** @typedef {import('../provider').Provided} Provided */
3
11
  /** @typedef {{name: string, version: string}} Package */
@@ -28,7 +36,7 @@ export default class Java_maven extends Base_java {
28
36
  */
29
37
  provideComponent(manifest: string, opts?: {}): Provided;
30
38
  /**
31
- * Read license from pom.xml manifest
39
+ * Read license from pom.xml manifest, with fallback to LICENSE file
32
40
  * @param {string} manifestPath - path to pom.xml
33
41
  * @returns {string|null}
34
42
  */