@trustify-da/trustify-da-javascript-client 0.3.0-ea.6549d2a → 0.3.0-ea.7281b26
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.
- package/README.md +179 -11
- package/dist/package.json +12 -3
- package/dist/src/analysis.d.ts +16 -0
- package/dist/src/analysis.js +53 -4
- package/dist/src/batch_opts.d.ts +24 -0
- package/dist/src/batch_opts.js +35 -0
- package/dist/src/cli.js +171 -4
- package/dist/src/cyclone_dx_sbom.d.ts +7 -0
- package/dist/src/cyclone_dx_sbom.js +16 -1
- package/dist/src/index.d.ts +75 -2
- package/dist/src/index.js +284 -5
- package/dist/src/license/index.d.ts +2 -2
- package/dist/src/license/index.js +4 -4
- package/dist/src/license/license_utils.d.ts +40 -0
- package/dist/src/license/license_utils.js +134 -0
- package/dist/src/license/licenses_api.js +9 -2
- package/dist/src/license/project_license.d.ts +1 -6
- package/dist/src/license/project_license.js +4 -81
- package/dist/src/oci_image/utils.js +11 -2
- package/dist/src/provider.d.ts +6 -3
- package/dist/src/provider.js +12 -5
- package/dist/src/providers/base_javascript.d.ts +19 -3
- package/dist/src/providers/base_javascript.js +106 -23
- package/dist/src/providers/base_pyproject.d.ts +170 -0
- package/dist/src/providers/base_pyproject.js +338 -0
- package/dist/src/providers/golang_gomodules.d.ts +12 -12
- package/dist/src/providers/golang_gomodules.js +102 -112
- package/dist/src/providers/gomod_parser.d.ts +4 -0
- package/dist/src/providers/gomod_parser.js +16 -0
- package/dist/src/providers/java_gradle.js +2 -1
- package/dist/src/providers/java_maven.d.ts +1 -1
- package/dist/src/providers/java_maven.js +10 -9
- package/dist/src/providers/javascript_pnpm.d.ts +1 -1
- package/dist/src/providers/javascript_pnpm.js +2 -2
- package/dist/src/providers/manifest.d.ts +2 -0
- package/dist/src/providers/manifest.js +22 -4
- package/dist/src/providers/processors/yarn_berry_processor.js +82 -3
- package/dist/src/providers/python_pip.js +3 -2
- package/dist/src/providers/python_poetry.d.ts +42 -0
- package/dist/src/providers/python_poetry.js +169 -0
- package/dist/src/providers/python_uv.d.ts +27 -0
- package/dist/src/providers/python_uv.js +146 -0
- package/dist/src/providers/requirements_parser.js +4 -3
- package/dist/src/providers/rust_cargo.d.ts +52 -0
- package/dist/src/providers/rust_cargo.js +614 -0
- package/dist/src/providers/tree-sitter-gomod.wasm +0 -0
- package/dist/src/providers/tree-sitter-requirements.wasm +0 -0
- package/dist/src/sbom.d.ts +7 -0
- package/dist/src/sbom.js +9 -0
- package/dist/src/workspace.d.ts +61 -0
- package/dist/src/workspace.js +256 -0
- package/package.json +13 -4
- package/dist/src/license/compatibility.d.ts +0 -18
- package/dist/src/license/compatibility.js +0 -45
|
@@ -1,9 +1,10 @@
|
|
|
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
|
+
import { getParser, getRequireQuery } from './gomod_parser.js';
|
|
7
8
|
export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest };
|
|
8
9
|
/** @typedef {import('../provider').Provider} */
|
|
9
10
|
/** @typedef {import('../provider').Provided} Provided */
|
|
@@ -16,46 +17,46 @@ export default { isSupported, validateLockFile, provideComponent, provideStack,
|
|
|
16
17
|
const ecosystem = 'golang';
|
|
17
18
|
const defaultMainModuleVersion = "v0.0.0";
|
|
18
19
|
/**
|
|
19
|
-
* @param {string} manifestName
|
|
20
|
-
* @returns {boolean}
|
|
20
|
+
* @param {string} manifestName the subject manifest name-type
|
|
21
|
+
* @returns {boolean} return true if `pom.xml` is the manifest name-type
|
|
21
22
|
*/
|
|
22
23
|
function isSupported(manifestName) {
|
|
23
24
|
return 'go.mod' === manifestName;
|
|
24
25
|
}
|
|
25
26
|
/**
|
|
26
27
|
* Go modules have no standard license field in go.mod
|
|
27
|
-
* @param {string} manifestPath
|
|
28
|
+
* @param {string} manifestPath path to go.mod
|
|
28
29
|
* @returns {string|null}
|
|
29
30
|
*/
|
|
30
31
|
// eslint-disable-next-line no-unused-vars
|
|
31
|
-
function readLicenseFromManifest(manifestPath) { return
|
|
32
|
+
function readLicenseFromManifest(manifestPath) { return readLicenseFile(manifestPath); }
|
|
32
33
|
/**
|
|
33
|
-
* @param {string} manifestDir
|
|
34
|
+
* @param {string} manifestDir the directory where the manifest lies
|
|
34
35
|
*/
|
|
35
36
|
function validateLockFile() { return true; }
|
|
36
37
|
/**
|
|
37
38
|
* Provide content and content type for maven-maven stack analysis.
|
|
38
|
-
* @param {string} manifest
|
|
39
|
-
* @param {{}} [opts={}]
|
|
40
|
-
* @returns {Provided}
|
|
39
|
+
* @param {string} manifest the manifest path or name
|
|
40
|
+
* @param {{}} [opts={}] optional various options to pass along the application
|
|
41
|
+
* @returns {Promise<Provided>}
|
|
41
42
|
*/
|
|
42
|
-
function provideStack(manifest, opts = {}) {
|
|
43
|
+
async function provideStack(manifest, opts = {}) {
|
|
43
44
|
return {
|
|
44
45
|
ecosystem,
|
|
45
|
-
content: getSBOM(manifest, opts, true),
|
|
46
|
+
content: await getSBOM(manifest, opts, true),
|
|
46
47
|
contentType: 'application/vnd.cyclonedx+json'
|
|
47
48
|
};
|
|
48
49
|
}
|
|
49
50
|
/**
|
|
50
51
|
* Provide content and content type for maven-maven component analysis.
|
|
51
|
-
* @param {string} manifest
|
|
52
|
-
* @param {{}} [opts={}]
|
|
53
|
-
* @returns {Provided}
|
|
52
|
+
* @param {string} manifest path to go.mod for component report
|
|
53
|
+
* @param {{}} [opts={}] optional various options to pass along the application
|
|
54
|
+
* @returns {Promise<Provided>}
|
|
54
55
|
*/
|
|
55
|
-
function provideComponent(manifest, opts = {}) {
|
|
56
|
+
async function provideComponent(manifest, opts = {}) {
|
|
56
57
|
return {
|
|
57
58
|
ecosystem,
|
|
58
|
-
content: getSBOM(manifest, opts, false),
|
|
59
|
+
content: await getSBOM(manifest, opts, false),
|
|
59
60
|
contentType: 'application/vnd.cyclonedx+json'
|
|
60
61
|
};
|
|
61
62
|
}
|
|
@@ -76,50 +77,52 @@ function getChildVertexFromEdge(edge) {
|
|
|
76
77
|
return edge.split(" ")[1];
|
|
77
78
|
}
|
|
78
79
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
80
|
+
* Check whether a require_spec has a valid exhortignore marker.
|
|
81
|
+
* For direct dependencies: `//exhortignore` or `// exhortignore`
|
|
82
|
+
* For indirect dependencies: `// indirect; exhortignore` (semicolon-separated)
|
|
83
|
+
* @param {import('web-tree-sitter').SyntaxNode} specNode
|
|
84
|
+
* @return {boolean}
|
|
82
85
|
*/
|
|
83
|
-
function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
function hasExhortIgnore(specNode) {
|
|
87
|
+
// Ideally this would be the following tree-sitter query instead, but for some
|
|
88
|
+
// reason it throws an error here but not in the playground.
|
|
89
|
+
// (require_spec) ((module_path) @path (version) (comment) @comment (#match? @comment "^//.*exhortignore"))
|
|
90
|
+
// QueryError: Bad pattern structure at offset 53: '(comment) @comment (#match? @comment "^//.*exhortignore")) @spec'...
|
|
91
|
+
let comments = specNode.children.filter(c => c.type === 'comment');
|
|
92
|
+
for (let comment of comments) {
|
|
93
|
+
let text = comment.text;
|
|
94
|
+
if (/^\/\/\s*indirect;\s*exhortignore/.test(text)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
if (/^\/\/\s*exhortignore/.test(text)) {
|
|
98
|
+
return true;
|
|
95
99
|
}
|
|
96
100
|
}
|
|
97
|
-
return
|
|
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();
|
|
101
|
+
return false;
|
|
109
102
|
}
|
|
110
103
|
/**
|
|
111
104
|
*
|
|
112
|
-
* @param {string
|
|
113
|
-
* @
|
|
105
|
+
* @param {string} manifestContent go.mod file contents
|
|
106
|
+
* @param {import('web-tree-sitter').Parser} parser
|
|
107
|
+
* @param {import('web-tree-sitter').Query} requireQuery
|
|
108
|
+
* @return {PackageURL[]} list of ignored dependencies
|
|
114
109
|
*/
|
|
115
|
-
function getIgnoredDeps(
|
|
116
|
-
let
|
|
117
|
-
|
|
118
|
-
|
|
110
|
+
function getIgnoredDeps(manifestContent, parser, requireQuery) {
|
|
111
|
+
let tree = parser.parse(manifestContent);
|
|
112
|
+
return requireQuery.matches(tree.rootNode)
|
|
113
|
+
.filter(match => {
|
|
114
|
+
let specNode = match.captures.find(c => c.name === 'spec').node;
|
|
115
|
+
return hasExhortIgnore(specNode);
|
|
116
|
+
})
|
|
117
|
+
.map(match => {
|
|
118
|
+
let name = match.captures.find(c => c.name === 'name').node.text;
|
|
119
|
+
let version = match.captures.find(c => c.name === 'version').node.text;
|
|
120
|
+
return toPurl(`${name} ${version}`, /[ ]{1,3}/);
|
|
121
|
+
});
|
|
119
122
|
}
|
|
120
123
|
/**
|
|
121
124
|
*
|
|
122
|
-
* @param {[
|
|
125
|
+
* @param {PackageURL[]} allIgnoredDeps list of purls of all dependencies that should be ignored
|
|
123
126
|
* @param {PackageURL} purl object to be checked if needed to be ignored
|
|
124
127
|
* @return {boolean}
|
|
125
128
|
*/
|
|
@@ -138,59 +141,29 @@ function enforceRemovingIgnoredDepsInCaseOfAutomaticVersionUpdate(ignoredDeps, s
|
|
|
138
141
|
}
|
|
139
142
|
/**
|
|
140
143
|
*
|
|
141
|
-
* @param {
|
|
142
|
-
* @param {
|
|
143
|
-
* @
|
|
144
|
+
* @param {string} manifestContent go.mod file contents
|
|
145
|
+
* @param {import('web-tree-sitter').Parser} parser
|
|
146
|
+
* @param {import('web-tree-sitter').Query} requireQuery
|
|
147
|
+
* @return {string[]} all dependencies from go.mod file as "name version" strings
|
|
144
148
|
*/
|
|
145
|
-
function collectAllDepsFromManifest(
|
|
146
|
-
let
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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;
|
|
149
|
+
function collectAllDepsFromManifest(manifestContent, parser, requireQuery) {
|
|
150
|
+
let tree = parser.parse(manifestContent);
|
|
151
|
+
return requireQuery.matches(tree.rootNode).map(match => {
|
|
152
|
+
let name = match.captures.find(c => c.name === 'name').node.text;
|
|
153
|
+
let version = match.captures.find(c => c.name === 'version').node.text;
|
|
154
|
+
return `${name} ${version}`;
|
|
155
|
+
});
|
|
181
156
|
}
|
|
182
157
|
/**
|
|
183
158
|
*
|
|
184
159
|
* @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{[
|
|
186
|
-
* @param {string
|
|
160
|
+
* @param {string[]} goModGraphOutputRows the goModGraphOutputRows from go mod graph' output
|
|
161
|
+
* @param {string} manifestContent go.mod file contents
|
|
187
162
|
* @private
|
|
188
163
|
*/
|
|
189
|
-
function performManifestVersionsCheck(rootElementName, goModGraphOutputRows,
|
|
190
|
-
let goMod = fs.readFileSync(manifest).toString().trim();
|
|
191
|
-
let lines = goMod.split(getLineSeparatorGolang());
|
|
164
|
+
function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, manifestContent, parser, requireQuery) {
|
|
192
165
|
let comparisonLines = goModGraphOutputRows.filter((line) => line.startsWith(rootElementName)).map((line) => getChildVertexFromEdge(line));
|
|
193
|
-
let manifestDeps = collectAllDepsFromManifest(
|
|
166
|
+
let manifestDeps = collectAllDepsFromManifest(manifestContent, parser, requireQuery);
|
|
194
167
|
try {
|
|
195
168
|
comparisonLines.forEach((dependency) => {
|
|
196
169
|
let parts = dependency.split("@");
|
|
@@ -202,7 +175,7 @@ function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, man
|
|
|
202
175
|
let currentVersion = components[1];
|
|
203
176
|
if (currentDepName === depName) {
|
|
204
177
|
if (currentVersion !== version) {
|
|
205
|
-
throw new Error(`
|
|
178
|
+
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
179
|
}
|
|
207
180
|
}
|
|
208
181
|
});
|
|
@@ -218,10 +191,10 @@ function performManifestVersionsCheck(rootElementName, goModGraphOutputRows, man
|
|
|
218
191
|
* @param {string} manifest - path for go.mod
|
|
219
192
|
* @param {{}} [opts={}] - optional various options to pass along the application
|
|
220
193
|
* @param {boolean} includeTransitive - whether the sbom should contain transitive dependencies of the main module or not.
|
|
221
|
-
* @returns {string} the SBOM json content
|
|
194
|
+
* @returns {Promise<string>} the SBOM json content
|
|
222
195
|
* @private
|
|
223
196
|
*/
|
|
224
|
-
function getSBOM(manifest, opts = {}, includeTransitive) {
|
|
197
|
+
async function getSBOM(manifest, opts = {}, includeTransitive) {
|
|
225
198
|
// get custom goBin path
|
|
226
199
|
let goBin = getCustomPath('go', opts);
|
|
227
200
|
// verify goBin is accessible
|
|
@@ -247,14 +220,25 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
|
|
|
247
220
|
catch (error) {
|
|
248
221
|
throw new Error('failed to determine root module name', { cause: error });
|
|
249
222
|
}
|
|
250
|
-
let
|
|
223
|
+
let manifestContent = fs.readFileSync(manifest).toString();
|
|
224
|
+
let [parser, requireQuery] = await Promise.all([getParser(), getRequireQuery()]);
|
|
225
|
+
let ignoredDeps = getIgnoredDeps(manifestContent, parser, requireQuery);
|
|
251
226
|
let allIgnoredDeps = ignoredDeps.map((dep) => dep.toString());
|
|
252
227
|
let sbom = new Sbom();
|
|
253
228
|
let rows = goGraphOutput.split(getLineSeparatorGolang()).filter(line => !line.includes(' go@'));
|
|
254
|
-
let root =
|
|
229
|
+
let root = goModEditOutput['Module']['Path'];
|
|
230
|
+
// Build set of direct dependency paths from go mod edit -json
|
|
231
|
+
let directDepPaths = new Set();
|
|
232
|
+
if (goModEditOutput['Require']) {
|
|
233
|
+
goModEditOutput['Require'].forEach(req => {
|
|
234
|
+
if (!req['Indirect']) {
|
|
235
|
+
directDepPaths.add(req['Path']);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
}
|
|
255
239
|
let matchManifestVersions = getCustom("MATCH_MANIFEST_VERSIONS", "false", opts);
|
|
256
240
|
if (matchManifestVersions === "true") {
|
|
257
|
-
performManifestVersionsCheck(root, rows,
|
|
241
|
+
performManifestVersionsCheck(root, rows, manifestContent, parser, requireQuery);
|
|
258
242
|
}
|
|
259
243
|
const mainModule = toPurl(root, "@");
|
|
260
244
|
const license = readLicenseFromManifest(manifest);
|
|
@@ -272,7 +256,11 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
|
|
|
272
256
|
currentParent = getParentVertexFromEdge(row);
|
|
273
257
|
source = toPurl(currentParent, "@");
|
|
274
258
|
}
|
|
275
|
-
let
|
|
259
|
+
let child = getChildVertexFromEdge(row);
|
|
260
|
+
let target = toPurl(child, "@");
|
|
261
|
+
if (getParentVertexFromEdge(row) === root && !directDepPaths.has(getPackageName(child))) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
276
264
|
sbom.addDependency(source, target);
|
|
277
265
|
});
|
|
278
266
|
// at the end, filter out all ignored dependencies including versions.
|
|
@@ -282,10 +270,12 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
|
|
|
282
270
|
else {
|
|
283
271
|
let directDependencies = rows.filter(row => row.startsWith(root));
|
|
284
272
|
directDependencies.forEach(pair => {
|
|
285
|
-
let
|
|
286
|
-
let
|
|
287
|
-
if (dependencyNotIgnored(ignoredDeps,
|
|
288
|
-
|
|
273
|
+
let child = getChildVertexFromEdge(pair);
|
|
274
|
+
let target = toPurl(child, "@");
|
|
275
|
+
if (dependencyNotIgnored(ignoredDeps, target)) {
|
|
276
|
+
if (directDepPaths.has(getPackageName(child))) {
|
|
277
|
+
sbom.addDependency(mainModule, target);
|
|
278
|
+
}
|
|
289
279
|
}
|
|
290
280
|
});
|
|
291
281
|
enforceRemovingIgnoredDepsInCaseOfAutomaticVersionUpdate(ignoredDeps, sbom);
|
|
@@ -295,7 +285,7 @@ function getSBOM(manifest, opts = {}, includeTransitive) {
|
|
|
295
285
|
/**
|
|
296
286
|
* Utility function for creating Purl String
|
|
297
287
|
|
|
298
|
-
* @param {string
|
|
288
|
+
* @param {string} dependency the name of the artifact, can include a namespace(group) or not - namespace/artifactName.
|
|
299
289
|
* @param {RegExp} delimiter delimiter between name of dependency and version
|
|
300
290
|
* @private
|
|
301
291
|
* @returns {PackageURL|null} PackageUrl Object ready to be used in SBOM
|
|
@@ -320,12 +310,12 @@ function toPurl(dependency, delimiter) {
|
|
|
320
310
|
}
|
|
321
311
|
return pkg;
|
|
322
312
|
}
|
|
323
|
-
/** This function gets rows from go mod graph
|
|
313
|
+
/** This function gets rows from go mod graph, and go.mod graph, and selecting for all
|
|
324
314
|
* packages the has more than one minor the final versions as selected by golang MVS algorithm.
|
|
325
|
-
* @param {[
|
|
315
|
+
* @param {string[]} rows all the rows from go modules dependency tree
|
|
326
316
|
* @param {string} manifestPath the path of the go.mod file
|
|
327
317
|
* @param {string} path to go binary
|
|
328
|
-
* @return {[
|
|
318
|
+
* @return {string[]} rows that contains final versions.
|
|
329
319
|
*/
|
|
330
320
|
function getFinalPackagesVersionsForModule(rows, manifestPath, goBin) {
|
|
331
321
|
let manifestDir = path.dirname(manifestPath);
|
|
@@ -380,7 +370,7 @@ function getFinalPackagesVersionsForModule(rows, manifestPath, goBin) {
|
|
|
380
370
|
/**
|
|
381
371
|
*
|
|
382
372
|
* @param {string} fullPackage - full package with its name and version
|
|
383
|
-
* @return
|
|
373
|
+
* @return {string} package name only
|
|
384
374
|
* @private
|
|
385
375
|
*/
|
|
386
376
|
function getPackageName(fullPackage) {
|
|
@@ -398,7 +388,7 @@ function isSpecialGoModule(moduleName) {
|
|
|
398
388
|
/**
|
|
399
389
|
*
|
|
400
390
|
* @param {string} fullPackage - full package with its name and version
|
|
401
|
-
* @return
|
|
391
|
+
* @return {string|undefined} package version only
|
|
402
392
|
* @private
|
|
403
393
|
*/
|
|
404
394
|
function getVersionOfPackage(fullPackage) {
|
|
@@ -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
|
+
}
|
|
@@ -2,6 +2,7 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { EOL } from 'os';
|
|
4
4
|
import TOML from 'fast-toml';
|
|
5
|
+
import { readLicenseFile } from '../license/license_utils.js';
|
|
5
6
|
import Sbom from '../sbom.js';
|
|
6
7
|
import Base_java, { ecosystem_gradle } from "./base_java.js";
|
|
7
8
|
/** @typedef {import('../provider.js').Provider} */
|
|
@@ -55,7 +56,7 @@ export default class Java_gradle extends Base_java {
|
|
|
55
56
|
* @returns {null}
|
|
56
57
|
*/
|
|
57
58
|
// eslint-disable-next-line no-unused-vars
|
|
58
|
-
readLicenseFromManifest(manifestPath) { return
|
|
59
|
+
readLicenseFromManifest(manifestPath) { return readLicenseFile(manifestPath); }
|
|
59
60
|
/**
|
|
60
61
|
* Provide content and content type for stack analysis.
|
|
61
62
|
* @param {string} manifest - the manifest path or name
|
|
@@ -28,7 +28,7 @@ export default class Java_maven extends Base_java {
|
|
|
28
28
|
*/
|
|
29
29
|
provideComponent(manifest: string, opts?: {}): Provided;
|
|
30
30
|
/**
|
|
31
|
-
* Read license from pom.xml manifest
|
|
31
|
+
* Read license from pom.xml manifest, with fallback to LICENSE file
|
|
32
32
|
* @param {string} manifestPath - path to pom.xml
|
|
33
33
|
* @returns {string|null}
|
|
34
34
|
*/
|
|
@@ -3,6 +3,7 @@ import os from 'node:os';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { EOL } from 'os';
|
|
5
5
|
import { XMLParser } from 'fast-xml-parser';
|
|
6
|
+
import { getLicense } from '../license/license_utils.js';
|
|
6
7
|
import Sbom from '../sbom.js';
|
|
7
8
|
import { getCustom } from '../tools.js';
|
|
8
9
|
import Base_java, { ecosystem_maven } from "./base_java.js";
|
|
@@ -52,28 +53,28 @@ export default class Java_maven extends Base_java {
|
|
|
52
53
|
};
|
|
53
54
|
}
|
|
54
55
|
/**
|
|
55
|
-
* Read license from pom.xml manifest
|
|
56
|
+
* Read license from pom.xml manifest, with fallback to LICENSE file
|
|
56
57
|
* @param {string} manifestPath - path to pom.xml
|
|
57
58
|
* @returns {string|null}
|
|
58
59
|
*/
|
|
59
60
|
readLicenseFromManifest(manifestPath) {
|
|
61
|
+
let fromPom = null;
|
|
60
62
|
try {
|
|
61
63
|
const xml = fs.readFileSync(manifestPath, 'utf-8');
|
|
62
64
|
const parser = new XMLParser({ ignoreAttributes: false });
|
|
63
65
|
const obj = parser.parse(xml);
|
|
64
66
|
const project = obj?.project;
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
+
if (project?.licenses?.license) {
|
|
68
|
+
const license = Array.isArray(project.licenses.license)
|
|
69
|
+
? project.licenses.license[0]
|
|
70
|
+
: project.licenses.license;
|
|
71
|
+
fromPom = (license?.name && license.name.trim()) || null;
|
|
67
72
|
}
|
|
68
|
-
const license = Array.isArray(project.licenses.license)
|
|
69
|
-
? project.licenses.license[0]
|
|
70
|
-
: project.licenses.license;
|
|
71
|
-
const name = (license?.name && license.name.trim()) || null;
|
|
72
|
-
return name || null;
|
|
73
73
|
}
|
|
74
74
|
catch {
|
|
75
|
-
|
|
75
|
+
// leave fromPom as null
|
|
76
76
|
}
|
|
77
|
+
return getLicense(fromPom, manifestPath);
|
|
77
78
|
}
|
|
78
79
|
/**
|
|
79
80
|
* Create a Dot Graph dependency tree for a manifest path.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export default class Javascript_pnpm extends Base_javascript {
|
|
2
2
|
_listCmdArgs(includeTransitive: any): string[];
|
|
3
|
-
_buildDependencyTree(includeTransitive: any,
|
|
3
|
+
_buildDependencyTree(includeTransitive: any, opts?: {}): any;
|
|
4
4
|
}
|
|
5
5
|
import Base_javascript from './base_javascript.js';
|
|
@@ -12,8 +12,8 @@ export default class Javascript_pnpm extends Base_javascript {
|
|
|
12
12
|
_updateLockFileCmdArgs() {
|
|
13
13
|
return ['install', '--frozen-lockfile'];
|
|
14
14
|
}
|
|
15
|
-
_buildDependencyTree(includeTransitive,
|
|
16
|
-
const tree = super._buildDependencyTree(includeTransitive,
|
|
15
|
+
_buildDependencyTree(includeTransitive, opts = {}) {
|
|
16
|
+
const tree = super._buildDependencyTree(includeTransitive, opts);
|
|
17
17
|
if (Array.isArray(tree) && tree.length > 0) {
|
|
18
18
|
return tree[0];
|
|
19
19
|
}
|
|
@@ -9,6 +9,8 @@ export default class Manifest {
|
|
|
9
9
|
this.manifestPath = manifestPath;
|
|
10
10
|
const content = this.loadManifest();
|
|
11
11
|
this.dependencies = this.loadDependencies(content);
|
|
12
|
+
this.peerDependencies = content.peerDependencies || {};
|
|
13
|
+
this.optionalDependencies = content.optionalDependencies || {};
|
|
12
14
|
this.name = content.name;
|
|
13
15
|
this.version = content.version || DEFAULT_VERSION;
|
|
14
16
|
this.ignored = this.loadIgnored(content);
|
|
@@ -27,11 +29,27 @@ export default class Manifest {
|
|
|
27
29
|
}
|
|
28
30
|
loadDependencies(content) {
|
|
29
31
|
let deps = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
const depSources = [
|
|
33
|
+
content.dependencies,
|
|
34
|
+
content.peerDependencies,
|
|
35
|
+
content.optionalDependencies,
|
|
36
|
+
];
|
|
37
|
+
for (const source of depSources) {
|
|
38
|
+
if (source) {
|
|
39
|
+
for (let dep in source) {
|
|
40
|
+
if (!deps.includes(dep)) {
|
|
41
|
+
deps.push(dep);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
32
45
|
}
|
|
33
|
-
|
|
34
|
-
|
|
46
|
+
// bundledDependencies is an array of package names (subset of dependencies)
|
|
47
|
+
if (Array.isArray(content.bundledDependencies)) {
|
|
48
|
+
for (const dep of content.bundledDependencies) {
|
|
49
|
+
if (!deps.includes(dep)) {
|
|
50
|
+
deps.push(dep);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
35
53
|
}
|
|
36
54
|
return deps;
|
|
37
55
|
}
|
|
@@ -48,13 +48,15 @@ export default class Yarn_berry_processor extends Yarn_processor {
|
|
|
48
48
|
if (!depTree) {
|
|
49
49
|
return new Map();
|
|
50
50
|
}
|
|
51
|
-
return new Map(depTree.filter(dep => !this.#isRoot(dep.value))
|
|
51
|
+
return new Map(depTree.filter(dep => !this.#isRoot(dep.value))
|
|
52
|
+
.map(dep => {
|
|
52
53
|
const depName = dep.value;
|
|
53
54
|
const idx = depName.lastIndexOf('@');
|
|
54
55
|
const name = depName.substring(0, idx);
|
|
55
56
|
const version = dep.children.Version;
|
|
56
57
|
return [name, toPurl(purlType, name, version)];
|
|
57
|
-
})
|
|
58
|
+
})
|
|
59
|
+
.filter(([name]) => this._manifest.dependencies.includes(name)));
|
|
58
60
|
}
|
|
59
61
|
/**
|
|
60
62
|
* Checks if a dependency is the root package
|
|
@@ -77,14 +79,58 @@ export default class Yarn_berry_processor extends Yarn_processor {
|
|
|
77
79
|
if (!depTree) {
|
|
78
80
|
return;
|
|
79
81
|
}
|
|
82
|
+
// Build index of nodes by their value for quick lookup
|
|
83
|
+
const nodeIndex = new Map();
|
|
84
|
+
depTree.forEach(n => nodeIndex.set(n.value, n));
|
|
85
|
+
// Determine the set of node values reachable from root via production deps
|
|
86
|
+
const prodDeps = new Set(this._manifest.dependencies);
|
|
87
|
+
const reachable = new Set();
|
|
88
|
+
const queue = [];
|
|
89
|
+
// Seed with root's production dependencies
|
|
90
|
+
const rootNode = depTree.find(n => this.#isRoot(n.value));
|
|
91
|
+
if (rootNode?.children?.Dependencies) {
|
|
92
|
+
for (const d of rootNode.children.Dependencies) {
|
|
93
|
+
const to = this.#purlFromLocator(d.locator);
|
|
94
|
+
if (to) {
|
|
95
|
+
const fullName = to.namespace ? `${to.namespace}/${to.name}` : to.name;
|
|
96
|
+
if (prodDeps.has(fullName)) {
|
|
97
|
+
queue.push(d.locator);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// BFS to find all transitively reachable packages
|
|
103
|
+
while (queue.length > 0) {
|
|
104
|
+
const locator = queue.shift();
|
|
105
|
+
if (reachable.has(locator)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
reachable.add(locator);
|
|
109
|
+
const node = nodeIndex.get(this.#nodeValueFromLocator(locator));
|
|
110
|
+
if (node?.children?.Dependencies) {
|
|
111
|
+
for (const d of node.children.Dependencies) {
|
|
112
|
+
if (!reachable.has(d.locator)) {
|
|
113
|
+
queue.push(d.locator);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Only emit edges for root and reachable nodes
|
|
80
119
|
depTree.forEach(n => {
|
|
81
120
|
const depName = n.value;
|
|
82
|
-
const
|
|
121
|
+
const isRoot = this.#isRoot(depName);
|
|
122
|
+
if (!isRoot && !this.#isReachableNode(depName, reachable)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const from = isRoot ? toPurlFromString(sbom.getRoot().purl) : this.#purlFromNode(depName, n);
|
|
83
126
|
const deps = n.children?.Dependencies;
|
|
84
127
|
if (!deps) {
|
|
85
128
|
return;
|
|
86
129
|
}
|
|
87
130
|
deps.forEach(d => {
|
|
131
|
+
if (!reachable.has(d.locator)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
88
134
|
const to = this.#purlFromLocator(d.locator);
|
|
89
135
|
if (to) {
|
|
90
136
|
sbom.addDependency(from, to);
|
|
@@ -92,6 +138,39 @@ export default class Yarn_berry_processor extends Yarn_processor {
|
|
|
92
138
|
});
|
|
93
139
|
});
|
|
94
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Converts a locator to the node value format used in yarn info output
|
|
143
|
+
* @param {string} locator - e.g. "express@npm:4.17.1"
|
|
144
|
+
* @returns {string} The node value, same as locator for non-virtual
|
|
145
|
+
* @private
|
|
146
|
+
*/
|
|
147
|
+
#nodeValueFromLocator(locator) {
|
|
148
|
+
// Virtual locators: "@scope/name@virtual:hash#npm:version" → "@scope/name@npm:version"
|
|
149
|
+
const virtualMatch = Yarn_berry_processor.VIRTUAL_LOCATOR_PATTERN.exec(locator);
|
|
150
|
+
if (virtualMatch) {
|
|
151
|
+
return `${virtualMatch[1]}@npm:${virtualMatch[2]}`;
|
|
152
|
+
}
|
|
153
|
+
return locator;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Checks if a node is in the reachable set by matching its value against reachable locators
|
|
157
|
+
* @param {string} depName - The node value (e.g. "express@npm:4.17.1")
|
|
158
|
+
* @param {Set<string>} reachable - Set of reachable locators
|
|
159
|
+
* @returns {boolean}
|
|
160
|
+
* @private
|
|
161
|
+
*/
|
|
162
|
+
#isReachableNode(depName, reachable) {
|
|
163
|
+
if (reachable.has(depName)) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
// Check if any reachable locator resolves to this node value
|
|
167
|
+
for (const locator of reachable) {
|
|
168
|
+
if (this.#nodeValueFromLocator(locator) === depName) {
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
95
174
|
/**
|
|
96
175
|
* Creates a PackageURL from a dependency locator
|
|
97
176
|
* @param {string} locator - The dependency locator
|