@trustify-da/trustify-da-javascript-client 0.3.0-ea.e645720 → 0.3.0-ea.ec6824b
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/dist/package.json +19 -19
- package/dist/src/analysis.d.ts +7 -7
- package/dist/src/cyclone_dx_sbom.js +11 -3
- package/dist/src/index.d.ts +9 -9
- package/dist/src/index.js +5 -2
- package/dist/src/license/index.d.ts +3 -3
- package/dist/src/license/index.js +4 -1
- package/dist/src/license/license_utils.js +4 -1
- package/dist/src/license/licenses_api.d.ts +2 -2
- package/dist/src/oci_image/images.d.ts +1 -1
- package/dist/src/oci_image/utils.d.ts +5 -5
- package/dist/src/provider.d.ts +4 -3
- package/dist/src/provider.js +3 -1
- package/dist/src/providers/base_java.d.ts +14 -3
- package/dist/src/providers/base_java.js +16 -6
- package/dist/src/providers/base_javascript.d.ts +20 -9
- package/dist/src/providers/base_javascript.js +10 -3
- package/dist/src/providers/base_pyproject.d.ts +10 -5
- package/dist/src/providers/base_pyproject.js +7 -0
- package/dist/src/providers/golang_gomodules.d.ts +2 -1
- package/dist/src/providers/golang_gomodules.js +1 -1
- package/dist/src/providers/java_gradle_groovy.d.ts +1 -1
- package/dist/src/providers/java_gradle_kotlin.d.ts +1 -1
- package/dist/src/providers/java_maven.d.ts +2 -2
- package/dist/src/providers/java_maven.js +72 -0
- package/dist/src/providers/javascript_bun.d.ts +10 -0
- package/dist/src/providers/javascript_bun.js +100 -0
- package/dist/src/providers/javascript_npm.d.ts +6 -1
- package/dist/src/providers/processors/yarn_berry_processor.d.ts +2 -2
- package/dist/src/providers/processors/yarn_classic_processor.d.ts +2 -2
- package/dist/src/providers/python_controller.js +57 -3
- package/dist/src/providers/python_pip.d.ts +1 -0
- package/dist/src/providers/python_pip.js +1 -1
- package/dist/src/providers/python_pip_pyproject.js +3 -1
- package/dist/src/providers/python_poetry.d.ts +31 -8
- package/dist/src/providers/python_poetry.js +62 -14
- package/dist/src/providers/python_uv.d.ts +5 -14
- package/dist/src/providers/python_uv.js +44 -2
- package/dist/src/providers/rust_cargo.d.ts +6 -2
- package/dist/src/providers/rust_cargo.js +31 -4
- package/dist/src/tools.d.ts +2 -2
- package/package.json +20 -20
|
@@ -13,7 +13,7 @@ export default class Base_pyproject {
|
|
|
13
13
|
* @param {Object} [opts={}]
|
|
14
14
|
* @returns {boolean}
|
|
15
15
|
*/
|
|
16
|
-
validateLockFile(manifestDir: string, opts?:
|
|
16
|
+
validateLockFile(manifestDir: string, opts?: Object): boolean;
|
|
17
17
|
/**
|
|
18
18
|
* Walk up from manifestDir to find the directory containing the lock file.
|
|
19
19
|
* Follows the same pattern as Base_javascript._findLockFileDir().
|
|
@@ -22,7 +22,7 @@ export default class Base_pyproject {
|
|
|
22
22
|
* @returns {string|null}
|
|
23
23
|
* @protected
|
|
24
24
|
*/
|
|
25
|
-
protected _findLockFileDir(manifestDir: string, opts?:
|
|
25
|
+
protected _findLockFileDir(manifestDir: string, opts?: Object): string | null;
|
|
26
26
|
/**
|
|
27
27
|
* Detect workspace root boundaries.
|
|
28
28
|
* Currently only uv has native workspace support ([tool.uv.workspace] in pyproject.toml).
|
|
@@ -44,13 +44,13 @@ export default class Base_pyproject {
|
|
|
44
44
|
* @param {Object} [opts={}]
|
|
45
45
|
* @returns {Promise<Provided>}
|
|
46
46
|
*/
|
|
47
|
-
provideStack(manifest: string, opts?:
|
|
47
|
+
provideStack(manifest: string, opts?: Object): Promise<Provided>;
|
|
48
48
|
/**
|
|
49
49
|
* @param {string} manifest - path to pyproject.toml
|
|
50
50
|
* @param {Object} [opts={}]
|
|
51
51
|
* @returns {Promise<Provided>}
|
|
52
52
|
*/
|
|
53
|
-
provideComponent(manifest: string, opts?:
|
|
53
|
+
provideComponent(manifest: string, opts?: Object): Promise<Provided>;
|
|
54
54
|
/**
|
|
55
55
|
* @returns {string}
|
|
56
56
|
* @protected
|
|
@@ -61,6 +61,11 @@ export default class Base_pyproject {
|
|
|
61
61
|
* @protected
|
|
62
62
|
*/
|
|
63
63
|
protected _cmdName(): string;
|
|
64
|
+
/**
|
|
65
|
+
* Returns the package manager name (e.g. pip, poetry, uv)
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
packageManagerName(): string;
|
|
64
69
|
/**
|
|
65
70
|
* Resolve dependencies using the tool-specific command and parser.
|
|
66
71
|
*
|
|
@@ -72,7 +77,7 @@ export default class Base_pyproject {
|
|
|
72
77
|
* @returns {Promise<DependencyData>}
|
|
73
78
|
* @protected
|
|
74
79
|
*/
|
|
75
|
-
protected _getDependencyData(manifestDir: string, workspaceDir: string, parsed: object, opts:
|
|
80
|
+
protected _getDependencyData(manifestDir: string, workspaceDir: string, parsed: object, opts: Object): Promise<DependencyData>;
|
|
76
81
|
/**
|
|
77
82
|
* Canonicalize a Python package name per PEP 503.
|
|
78
83
|
* @param {string} name
|
|
@@ -145,6 +145,13 @@ export default class Base_pyproject {
|
|
|
145
145
|
_cmdName() {
|
|
146
146
|
throw new TypeError('_cmdName must be implemented');
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* Returns the package manager name (e.g. pip, poetry, uv)
|
|
150
|
+
* @returns {string}
|
|
151
|
+
*/
|
|
152
|
+
packageManagerName() {
|
|
153
|
+
return this._cmdName();
|
|
154
|
+
}
|
|
148
155
|
/**
|
|
149
156
|
* Resolve dependencies using the tool-specific command and parser.
|
|
150
157
|
*
|
|
@@ -13,9 +13,10 @@ declare namespace _default {
|
|
|
13
13
|
export { provideComponent };
|
|
14
14
|
export { provideStack };
|
|
15
15
|
export { readLicenseFromManifest };
|
|
16
|
+
export function packageManagerName(): string;
|
|
16
17
|
}
|
|
17
18
|
export default _default;
|
|
18
|
-
export type Provided =
|
|
19
|
+
export type Provided = any;
|
|
19
20
|
export type Package = {
|
|
20
21
|
name: string;
|
|
21
22
|
version: string;
|
|
@@ -6,7 +6,7 @@ import Sbom from '../sbom.js';
|
|
|
6
6
|
import { getCustom, getCustomPath, invokeCommand } from "../tools.js";
|
|
7
7
|
import { filterManifestPathsByDiscoveryIgnore, resolveWorkspaceDiscoveryIgnore } from '../workspace.js';
|
|
8
8
|
import { getParser, getRequireQuery } from './gomod_parser.js';
|
|
9
|
-
export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest };
|
|
9
|
+
export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest, packageManagerName() { return 'go'; } };
|
|
10
10
|
/** @typedef {import('../provider').Provider} */
|
|
11
11
|
/** @typedef {import('../provider').Provided} Provided */
|
|
12
12
|
/** @typedef {{name: string, version: string}} Package */
|
|
@@ -3,5 +3,5 @@ export default class Java_gradle_groovy extends Java_gradle {
|
|
|
3
3
|
_parseAliasForLibsNotation(alias: any): any;
|
|
4
4
|
_extractDepToBeIgnored(dep: any): any;
|
|
5
5
|
}
|
|
6
|
-
export type Provided =
|
|
6
|
+
export type Provided = any;
|
|
7
7
|
import Java_gradle from './java_gradle.js';
|
|
@@ -7,5 +7,5 @@ export default class Java_gradle_kotlin extends Java_gradle {
|
|
|
7
7
|
_parseAliasForLibsNotation(alias: any): any;
|
|
8
8
|
_extractDepToBeIgnored(dep: any): string | null;
|
|
9
9
|
}
|
|
10
|
-
export type Provided =
|
|
10
|
+
export type Provided = any;
|
|
11
11
|
import Java_gradle from './java_gradle.js';
|
|
@@ -51,8 +51,8 @@ export default class Java_maven extends Base_java {
|
|
|
51
51
|
createSbomFileFromTextFormat(textGraphList: string, ignoredDeps: [string], opts: any, manifestPath: string): string;
|
|
52
52
|
#private;
|
|
53
53
|
}
|
|
54
|
-
export type Java_maven =
|
|
55
|
-
export type Provided =
|
|
54
|
+
export type Java_maven = any;
|
|
55
|
+
export type Provided = any;
|
|
56
56
|
export type Package = {
|
|
57
57
|
name: string;
|
|
58
58
|
version: string;
|
|
@@ -181,6 +181,7 @@ export default class Java_maven extends Base_java {
|
|
|
181
181
|
/** @type [Dependency] */
|
|
182
182
|
let dependencies = this.#getDependencies(tmpEffectivePom)
|
|
183
183
|
.filter(d => !this.#dependencyIn(d, ignored));
|
|
184
|
+
dependencies = this.#resolveVersionRanges(dependencies, manifestPath, opts);
|
|
184
185
|
let sbom = new Sbom();
|
|
185
186
|
let rootDependency = this.#getRootFromPom(tmpEffectivePom, manifestPath);
|
|
186
187
|
let purlRoot = this.toPurl(rootDependency.groupId, rootDependency.artifactId, rootDependency.version);
|
|
@@ -289,6 +290,77 @@ export default class Java_maven extends Base_java {
|
|
|
289
290
|
#dependencyIn(dep, deps) {
|
|
290
291
|
return deps.filter(d => dep.artifactId === d.artifactId && dep.groupId === d.groupId && dep.scope === d.scope).length > 0;
|
|
291
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Returns true if the given version string is a Maven version range
|
|
295
|
+
* (starts with '[' or '(').
|
|
296
|
+
* @param {string} version
|
|
297
|
+
* @returns {boolean}
|
|
298
|
+
* @private
|
|
299
|
+
*/
|
|
300
|
+
#isVersionRange(version) {
|
|
301
|
+
return typeof version === 'string' && (version.startsWith('[') || version.startsWith('('));
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Resolves Maven version ranges in the given dependency list by running
|
|
305
|
+
* maven-dependency-plugin:tree and reading the concrete versions it selects.
|
|
306
|
+
* If no dependency uses a version range, returns the list unchanged.
|
|
307
|
+
* @param {Dependency[]} dependencies
|
|
308
|
+
* @param {string} manifestPath
|
|
309
|
+
* @param {object} opts
|
|
310
|
+
* @returns {Dependency[]}
|
|
311
|
+
* @private
|
|
312
|
+
*/
|
|
313
|
+
#resolveVersionRanges(dependencies, manifestPath, opts = {}) {
|
|
314
|
+
// short-circuit if no dependency has a version range
|
|
315
|
+
if (!dependencies.some(dep => this.#isVersionRange(dep.version))) {
|
|
316
|
+
return dependencies;
|
|
317
|
+
}
|
|
318
|
+
const mvn = this.selectToolBinary(manifestPath, opts);
|
|
319
|
+
const mvnArgs = JSON.parse(getCustom('TRUSTIFY_DA_MVN_ARGS', '[]', opts));
|
|
320
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'trustify_da_range_'));
|
|
321
|
+
const tmpDepTree = path.join(tmpDir, 'mvn_deptree_ranges.txt');
|
|
322
|
+
try {
|
|
323
|
+
this._invokeCommand(mvn, [
|
|
324
|
+
'-q',
|
|
325
|
+
'org.apache.maven.plugins:maven-dependency-plugin:3.6.0:tree',
|
|
326
|
+
'-Dscope=compile',
|
|
327
|
+
'-DoutputType=text',
|
|
328
|
+
`-DoutputFile=${tmpDepTree}`,
|
|
329
|
+
...mvnArgs
|
|
330
|
+
], { cwd: path.dirname(manifestPath) });
|
|
331
|
+
const content = fs.readFileSync(tmpDepTree);
|
|
332
|
+
const lines = content.toString().split(EOL).filter(l => l.trim() !== '');
|
|
333
|
+
// Build a map of groupId:artifactId -> resolved version from depth-1 entries
|
|
334
|
+
/** @type {Map<string, string>} */
|
|
335
|
+
const resolvedVersions = new Map();
|
|
336
|
+
for (const line of lines) {
|
|
337
|
+
if (this._getDepth(line) === 1) {
|
|
338
|
+
const purl = this.parseDep(line);
|
|
339
|
+
resolvedVersions.set(`${purl.namespace}:${purl.name}`, purl.version);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
// Replace version ranges with resolved concrete versions
|
|
343
|
+
return dependencies.map(dep => {
|
|
344
|
+
if (this.#isVersionRange(dep.version)) {
|
|
345
|
+
const key = `${dep.groupId}:${dep.artifactId}`;
|
|
346
|
+
const resolved = resolvedVersions.get(key);
|
|
347
|
+
if (resolved) {
|
|
348
|
+
return { ...dep, version: resolved };
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return dep;
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
|
|
356
|
+
console.error("Failed to resolve Maven version ranges: " + error.message);
|
|
357
|
+
}
|
|
358
|
+
return dependencies;
|
|
359
|
+
}
|
|
360
|
+
finally {
|
|
361
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
292
364
|
}
|
|
293
365
|
const DEFAULT_MAVEN_DISCOVERY_IGNORE = [
|
|
294
366
|
'**/target/**',
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default class Javascript_bun extends Base_javascript {
|
|
2
|
+
_listCmdArgs(): void;
|
|
3
|
+
_buildDependencyTree(includeTransitive: any, opts?: {}): {
|
|
4
|
+
name: any;
|
|
5
|
+
version: any;
|
|
6
|
+
dependencies: {};
|
|
7
|
+
};
|
|
8
|
+
#private;
|
|
9
|
+
}
|
|
10
|
+
import Base_javascript from './base_javascript.js';
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parse } from 'jsonc-parser';
|
|
4
|
+
import Base_javascript from './base_javascript.js';
|
|
5
|
+
export default class Javascript_bun extends Base_javascript {
|
|
6
|
+
_lockFileName() {
|
|
7
|
+
return "bun.lock";
|
|
8
|
+
}
|
|
9
|
+
_cmdName() {
|
|
10
|
+
return "bun";
|
|
11
|
+
}
|
|
12
|
+
_listCmdArgs() {
|
|
13
|
+
throw new Error("not supported by Bun");
|
|
14
|
+
}
|
|
15
|
+
_updateLockFileCmdArgs() {
|
|
16
|
+
return ['install', '--lockfile-only'];
|
|
17
|
+
}
|
|
18
|
+
_buildDependencyTree(includeTransitive, opts = {}) {
|
|
19
|
+
this._version();
|
|
20
|
+
const manifestDir = path.dirname(this._getManifest().manifestPath);
|
|
21
|
+
const lockDir = this._findLockFileDir(manifestDir, opts) || manifestDir;
|
|
22
|
+
this._createLockFile(lockDir);
|
|
23
|
+
const lockContent = fs.readFileSync(path.join(lockDir, 'bun.lock'), 'utf-8');
|
|
24
|
+
const lockData = parse(lockContent);
|
|
25
|
+
const packages = lockData.packages || {};
|
|
26
|
+
const memberName = this._getManifest().name;
|
|
27
|
+
const workspaceEntry = this.#findWorkspaceEntry(lockData, lockDir, manifestDir, memberName);
|
|
28
|
+
const directDeps = workspaceEntry?.dependencies || {};
|
|
29
|
+
const tree = { name: memberName, version: this._getManifest().version, dependencies: {} };
|
|
30
|
+
const visited = new Set();
|
|
31
|
+
for (const depName of Object.keys(directDeps)) {
|
|
32
|
+
const resolved = this.#resolvePackage(depName, '', packages);
|
|
33
|
+
if (resolved) {
|
|
34
|
+
tree.dependencies[depName] = this.#buildNode(depName, resolved, packages, includeTransitive, visited);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return tree;
|
|
38
|
+
}
|
|
39
|
+
#findWorkspaceEntry(lockData, lockDir, manifestDir, memberName) {
|
|
40
|
+
const workspaces = lockData.workspaces || {};
|
|
41
|
+
const relPath = path.relative(lockDir, path.resolve(manifestDir));
|
|
42
|
+
if (!relPath || relPath === '.') {
|
|
43
|
+
return workspaces[''] || {};
|
|
44
|
+
}
|
|
45
|
+
const normalised = relPath.split(path.sep).join('/');
|
|
46
|
+
if (workspaces[normalised]) {
|
|
47
|
+
return workspaces[normalised];
|
|
48
|
+
}
|
|
49
|
+
for (const [wsPath, entry] of Object.entries(workspaces)) {
|
|
50
|
+
if (entry.name === memberName && wsPath !== '') {
|
|
51
|
+
return entry;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return workspaces[''] || {};
|
|
55
|
+
}
|
|
56
|
+
#resolvePackage(depName, parentKey, packages) {
|
|
57
|
+
if (parentKey) {
|
|
58
|
+
const scopedKey = `${parentKey}/${depName}`;
|
|
59
|
+
if (packages[scopedKey]) {
|
|
60
|
+
return packages[scopedKey];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return packages[depName] || null;
|
|
64
|
+
}
|
|
65
|
+
#buildNode(depName, resolved, packages, includeTransitive, visited) {
|
|
66
|
+
const resolvedId = Array.isArray(resolved) ? resolved[0] : resolved;
|
|
67
|
+
const version = this.#extractVersion(resolvedId);
|
|
68
|
+
const node = { version };
|
|
69
|
+
if (!includeTransitive) {
|
|
70
|
+
return node;
|
|
71
|
+
}
|
|
72
|
+
const metadata = Array.isArray(resolved) ? (resolved[2] || {}) : {};
|
|
73
|
+
const subDeps = metadata.dependencies || {};
|
|
74
|
+
if (Object.keys(subDeps).length > 0) {
|
|
75
|
+
const visitKey = `${depName}@${version}`;
|
|
76
|
+
if (visited.has(visitKey)) {
|
|
77
|
+
return node;
|
|
78
|
+
}
|
|
79
|
+
visited.add(visitKey);
|
|
80
|
+
node.dependencies = {};
|
|
81
|
+
for (const subName of Object.keys(subDeps)) {
|
|
82
|
+
const subResolved = this.#resolvePackage(subName, depName, packages);
|
|
83
|
+
if (subResolved) {
|
|
84
|
+
node.dependencies[subName] = this.#buildNode(subName, subResolved, packages, true, visited);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return node;
|
|
89
|
+
}
|
|
90
|
+
#extractVersion(resolvedId) {
|
|
91
|
+
if (typeof resolvedId !== 'string') {
|
|
92
|
+
return '0.0.0';
|
|
93
|
+
}
|
|
94
|
+
const atIdx = resolvedId.lastIndexOf('@');
|
|
95
|
+
if (atIdx > 0) {
|
|
96
|
+
return resolvedId.substring(atIdx + 1);
|
|
97
|
+
}
|
|
98
|
+
return '0.0.0';
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export default class Javascript_npm extends Base_javascript {
|
|
2
2
|
_listCmdArgs(includeTransitive: any): string[];
|
|
3
|
-
_buildDependencyTree(includeTransitive: any, opts?: {}):
|
|
3
|
+
_buildDependencyTree(includeTransitive: any, opts?: {}): Object | {
|
|
4
|
+
name: any;
|
|
5
|
+
version: any;
|
|
6
|
+
dependencies: any;
|
|
7
|
+
optionalDependencies: any;
|
|
8
|
+
};
|
|
4
9
|
}
|
|
5
10
|
import Base_javascript from './base_javascript.js';
|
|
@@ -29,13 +29,13 @@ export default class Yarn_berry_processor extends Yarn_processor {
|
|
|
29
29
|
* @param {Object} depTree - The dependency tree object
|
|
30
30
|
* @returns {Map<string, PackageURL>} Map of dependency names to their PackageURL objects
|
|
31
31
|
*/
|
|
32
|
-
getRootDependencies(depTree:
|
|
32
|
+
getRootDependencies(depTree: Object): Map<string, PackageURL>;
|
|
33
33
|
/**
|
|
34
34
|
* Adds dependencies to the SBOM
|
|
35
35
|
* @param {Sbom} sbom - The SBOM object to add dependencies to
|
|
36
36
|
* @param {Object} depTree - The dependency tree object
|
|
37
37
|
*/
|
|
38
|
-
addDependenciesToSbom(sbom: Sbom, depTree:
|
|
38
|
+
addDependenciesToSbom(sbom: Sbom, depTree: Object): void;
|
|
39
39
|
#private;
|
|
40
40
|
}
|
|
41
41
|
import Yarn_processor from "./yarn_processor.js";
|
|
@@ -25,13 +25,13 @@ export default class Yarn_classic_processor extends Yarn_processor {
|
|
|
25
25
|
* @param {Object} depTree - The dependency tree object
|
|
26
26
|
* @returns {Map<string, PackageURL>} Map of dependency names to their PackageURL objects
|
|
27
27
|
*/
|
|
28
|
-
getRootDependencies(depTree:
|
|
28
|
+
getRootDependencies(depTree: Object): Map<string, PackageURL>;
|
|
29
29
|
/**
|
|
30
30
|
* Adds dependencies to the SBOM
|
|
31
31
|
* @param {Sbom} sbom - The SBOM object to add dependencies to
|
|
32
32
|
* @param {Object} depTree - The dependency tree object
|
|
33
33
|
*/
|
|
34
|
-
addDependenciesToSbom(sbom: Sbom, depTree:
|
|
34
|
+
addDependenciesToSbom(sbom: Sbom, depTree: Object): void;
|
|
35
35
|
#private;
|
|
36
36
|
}
|
|
37
37
|
import Yarn_processor from "./yarn_processor.js";
|
|
@@ -19,6 +19,53 @@ function getPipShowOutput(depNames) {
|
|
|
19
19
|
throw new Error('fail invoking \'pip show\' to fetch metadata for all installed packages in environment', { cause: error });
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Get pip inspect output from env var override or by invoking pip inspect.
|
|
24
|
+
* Returns null if pip inspect is unavailable (pip < 22.2) or if freeze/show
|
|
25
|
+
* env vars are set without an inspect override (to avoid inconsistent data).
|
|
26
|
+
* @return {string|null} pip inspect JSON output, or null on failure
|
|
27
|
+
*/
|
|
28
|
+
function getPipInspectOutput() {
|
|
29
|
+
if (environmentVariableIsPopulated("TRUSTIFY_DA_PIP_INSPECT")) {
|
|
30
|
+
return new Buffer.from(process.env["TRUSTIFY_DA_PIP_INSPECT"], 'base64').toString('ascii');
|
|
31
|
+
}
|
|
32
|
+
if (environmentVariableIsPopulated("TRUSTIFY_DA_PIP_FREEZE") || environmentVariableIsPopulated("TRUSTIFY_DA_PIP_SHOW")) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return invokeCommand(this.pathToPipBin, ['inspect']).toString();
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
console.warn('pip inspect is not available (requires pip 22.2+), SBOM will be generated without hashes');
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Parse pip inspect JSON output and build a hash lookup map.
|
|
45
|
+
* @param {string|null} inspectOutput - raw pip inspect JSON string
|
|
46
|
+
* @return {Map<string, Array<{alg: string, content: string}>>} map of lowercase package name to hashes
|
|
47
|
+
*/
|
|
48
|
+
function parsePipInspectHashes(inspectOutput) {
|
|
49
|
+
if (!inspectOutput) {
|
|
50
|
+
return new Map();
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
let inspectData = JSON.parse(inspectOutput);
|
|
54
|
+
let hashMap = new Map();
|
|
55
|
+
for (let pkg of (inspectData.installed || [])) {
|
|
56
|
+
let name = pkg.metadata?.name;
|
|
57
|
+
let sha256 = pkg.download_info?.archive_info?.hashes?.sha256;
|
|
58
|
+
if (name && sha256) {
|
|
59
|
+
hashMap.set(name.toLowerCase(), [{ alg: "SHA-256", content: sha256 }]);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return hashMap;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
console.warn('Failed to parse pip inspect output, SBOM will be generated without hashes');
|
|
66
|
+
return new Map();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
22
69
|
/** @typedef {{name: string, version: string, dependencies: DependencyEntry[], hashes?: Array<{alg: string, content: string}>}} DependencyEntry */
|
|
23
70
|
export default class Python_controller {
|
|
24
71
|
pythonEnvDir;
|
|
@@ -225,6 +272,8 @@ export default class Python_controller {
|
|
|
225
272
|
CachedEnvironmentDeps[packageName.replace("_", "-")] = pipDepTreeEntryForCache;
|
|
226
273
|
});
|
|
227
274
|
}
|
|
275
|
+
const inspectOutput = getPipInspectOutput.call(this);
|
|
276
|
+
const hashMap = parsePipInspectHashes(inspectOutput);
|
|
228
277
|
parsedRequirements.forEach(({ name: depName, version: manifestVersion, hasMarker }) => {
|
|
229
278
|
if (hasMarker && CachedEnvironmentDeps[depName.toLowerCase()] === undefined) {
|
|
230
279
|
return;
|
|
@@ -247,7 +296,7 @@ export default class Python_controller {
|
|
|
247
296
|
}
|
|
248
297
|
let path = [];
|
|
249
298
|
path.push(depName.toLowerCase());
|
|
250
|
-
bringAllDependencies(dependencies, depName, CachedEnvironmentDeps, includeTransitive, path, usePipDepTree);
|
|
299
|
+
bringAllDependencies(dependencies, depName, CachedEnvironmentDeps, includeTransitive, path, usePipDepTree, hashMap);
|
|
251
300
|
});
|
|
252
301
|
dependencies.sort((dep1, dep2) => {
|
|
253
302
|
const DEP1 = dep1.name.toLowerCase();
|
|
@@ -327,8 +376,9 @@ function getDepsList(record) {
|
|
|
327
376
|
* @param includeTransitive
|
|
328
377
|
* @param usePipDepTree
|
|
329
378
|
* @param {[string]}path array representing the path of the current branch in dependency tree, starting with a root dependency - that is - a given dependency in requirements.txt
|
|
379
|
+
* @param {Map<string, Array<{alg: string, content: string}>>} hashMap - map of lowercase package name to hashes
|
|
330
380
|
*/
|
|
331
|
-
function bringAllDependencies(dependencies, dependencyName, cachedEnvironmentDeps, includeTransitive, path, usePipDepTree) {
|
|
381
|
+
function bringAllDependencies(dependencies, dependencyName, cachedEnvironmentDeps, includeTransitive, path, usePipDepTree, hashMap) {
|
|
332
382
|
if (dependencyName?.trim() === "") {
|
|
333
383
|
return;
|
|
334
384
|
}
|
|
@@ -350,7 +400,11 @@ function bringAllDependencies(dependencies, dependencyName, cachedEnvironmentDep
|
|
|
350
400
|
directDeps = record.dependencies;
|
|
351
401
|
}
|
|
352
402
|
let targetDeps = [];
|
|
403
|
+
let hashes = hashMap?.get(depName.toLowerCase());
|
|
353
404
|
let entry = { "name": depName, "version": version, "dependencies": [] };
|
|
405
|
+
if (hashes) {
|
|
406
|
+
entry.hashes = hashes;
|
|
407
|
+
}
|
|
354
408
|
dependencies.push(entry);
|
|
355
409
|
directDeps.forEach((dep) => {
|
|
356
410
|
let depArray = [];
|
|
@@ -360,7 +414,7 @@ function bringAllDependencies(dependencies, dependencyName, cachedEnvironmentDep
|
|
|
360
414
|
depArray.push(dep.toLowerCase());
|
|
361
415
|
if (includeTransitive) {
|
|
362
416
|
// send to recurrsion the array of all deps in path + the current dependency name which is not on the path.
|
|
363
|
-
bringAllDependencies(targetDeps, dep, cachedEnvironmentDeps, includeTransitive, path.concat(depArray), usePipDepTree);
|
|
417
|
+
bringAllDependencies(targetDeps, dep, cachedEnvironmentDeps, includeTransitive, path.concat(depArray), usePipDepTree, hashMap);
|
|
364
418
|
}
|
|
365
419
|
}
|
|
366
420
|
// sort ra
|
|
@@ -5,7 +5,7 @@ import Sbom from '../sbom.js';
|
|
|
5
5
|
import { environmentVariableIsPopulated, getCustom, getCustomPath, invokeCommand } from "../tools.js";
|
|
6
6
|
import Python_controller from './python_controller.js';
|
|
7
7
|
import { getParser, getIgnoreQuery, getPinnedVersionQuery } from './requirements_parser.js';
|
|
8
|
-
export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest };
|
|
8
|
+
export default { isSupported, validateLockFile, provideComponent, provideStack, readLicenseFromManifest, packageManagerName() { return 'pip'; } };
|
|
9
9
|
/** @typedef {{name: string, version: string, dependencies: DependencyEntry[], hashes?: Array<{alg: string, content: string}>}} DependencyEntry */
|
|
10
10
|
/**
|
|
11
11
|
* @type {string} ecosystem for python-pip is 'pip'
|
|
@@ -76,7 +76,9 @@ export default class Python_pip_pyproject extends Base_pyproject {
|
|
|
76
76
|
let name = pkg.metadata.name;
|
|
77
77
|
let version = pkg.metadata.version;
|
|
78
78
|
let key = this._canonicalize(name);
|
|
79
|
-
|
|
79
|
+
let sha256 = pkg.download_info?.archive_info?.hashes?.sha256;
|
|
80
|
+
let hashes = sha256 ? [{ alg: "SHA-256", content: sha256 }] : undefined;
|
|
81
|
+
graph.set(key, { name, version, children: [], hashes });
|
|
80
82
|
}
|
|
81
83
|
for (let pkg of nonRootPackages) {
|
|
82
84
|
let key = this._canonicalize(pkg.metadata.name);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export default class Python_poetry extends Base_pyproject {
|
|
2
|
+
_verifyPoetryAccessible(poetryBin: any): void;
|
|
2
3
|
/**
|
|
3
4
|
* @param {string} manifestDir
|
|
4
5
|
* @param {string} _workspaceDir - unused (poetry has no workspace support)
|
|
@@ -6,7 +7,7 @@ export default class Python_poetry extends Base_pyproject {
|
|
|
6
7
|
* @param {Object} opts
|
|
7
8
|
* @returns {Promise<{directDeps: string[], graph: Map<string, {name: string, version: string, children: string[]}>}>}
|
|
8
9
|
*/
|
|
9
|
-
_getDependencyData(manifestDir: string, _workspaceDir: string, parsed: object, opts:
|
|
10
|
+
_getDependencyData(manifestDir: string, _workspaceDir: string, parsed: object, opts: Object): Promise<{
|
|
10
11
|
directDeps: string[];
|
|
11
12
|
graph: Map<string, {
|
|
12
13
|
name: string;
|
|
@@ -18,17 +19,17 @@ export default class Python_poetry extends Base_pyproject {
|
|
|
18
19
|
* Get poetry show --tree output.
|
|
19
20
|
* @param {string} manifestDir
|
|
20
21
|
* @param {boolean} hasDevGroup
|
|
21
|
-
* @param {
|
|
22
|
+
* @param {string} poetryBin
|
|
22
23
|
* @returns {string}
|
|
23
24
|
*/
|
|
24
|
-
_getPoetryShowTreeOutput(manifestDir: string, hasDevGroup: boolean,
|
|
25
|
+
_getPoetryShowTreeOutput(manifestDir: string, hasDevGroup: boolean, poetryBin: string): string;
|
|
25
26
|
/**
|
|
26
27
|
* Get poetry show --all output (flat list with resolved versions).
|
|
27
28
|
* @param {string} manifestDir
|
|
28
|
-
* @param {
|
|
29
|
+
* @param {string} poetryBin
|
|
29
30
|
* @returns {string}
|
|
30
31
|
*/
|
|
31
|
-
_getPoetryShowAllOutput(manifestDir: string,
|
|
32
|
+
_getPoetryShowAllOutput(manifestDir: string, poetryBin: string): string;
|
|
32
33
|
/**
|
|
33
34
|
* Parse poetry show --all output into a version map.
|
|
34
35
|
* Lines look like: "name (!) 1.2.3 Description text..."
|
|
@@ -46,29 +47,51 @@ export default class Python_poetry extends Base_pyproject {
|
|
|
46
47
|
* → transitiveMarkers['click']['colorama'] = "sys_platform == 'win32'"
|
|
47
48
|
* @param {string|null} lockDir
|
|
48
49
|
* @param {object} parsed - parsed pyproject.toml
|
|
49
|
-
* @returns {{directMarkers: Map<string, string>, transitiveMarkers: Map<string, Map<string, string>>}}
|
|
50
|
+
* @returns {{directMarkers: Map<string, string>, transitiveMarkers: Map<string, Map<string, string>>, hashMap: Map<string, Array<{alg: string, content: string}>>}}
|
|
50
51
|
*/
|
|
51
52
|
_extractMarkerData(lockDir: string | null, parsed: object): {
|
|
52
53
|
directMarkers: Map<string, string>;
|
|
53
54
|
transitiveMarkers: Map<string, Map<string, string>>;
|
|
55
|
+
hashMap: Map<string, Array<{
|
|
56
|
+
alg: string;
|
|
57
|
+
content: string;
|
|
58
|
+
}>>;
|
|
54
59
|
};
|
|
60
|
+
/**
|
|
61
|
+
* Extract a SHA-256 hex digest from poetry.lock files array.
|
|
62
|
+
* Prefers the sdist (.tar.gz) hash; falls back to the first wheel hash.
|
|
63
|
+
* @param {Array<{file: string, hash: string}>} [files]
|
|
64
|
+
* @returns {string|null}
|
|
65
|
+
*/
|
|
66
|
+
_extractSha256FromFiles(files?: Array<{
|
|
67
|
+
file: string;
|
|
68
|
+
hash: string;
|
|
69
|
+
}>): string | null;
|
|
55
70
|
/**
|
|
56
71
|
* Parse poetry show --tree output into a dependency graph structure.
|
|
57
72
|
*
|
|
58
73
|
* @param {string} treeOutput
|
|
59
74
|
* @param {Map<string, string>} versionMap - canonical name -> resolved version
|
|
60
|
-
* @param {{directMarkers: Map<string, string>, transitiveMarkers: Map<string, Map<string, string>>}} markerData
|
|
61
|
-
* @returns {{directDeps: string[], graph: Map<string, {name: string, version: string, children: string[]}>}}
|
|
75
|
+
* @param {{directMarkers: Map<string, string>, transitiveMarkers: Map<string, Map<string, string>>, hashMap: Map<string, Array<{alg: string, content: string}>>}} markerData
|
|
76
|
+
* @returns {{directDeps: string[], graph: Map<string, {name: string, version: string, children: string[], hashes?: Array<{alg: string, content: string}>}>}}
|
|
62
77
|
*/
|
|
63
78
|
_parsePoetryTree(treeOutput: string, versionMap: Map<string, string>, markerData: {
|
|
64
79
|
directMarkers: Map<string, string>;
|
|
65
80
|
transitiveMarkers: Map<string, Map<string, string>>;
|
|
81
|
+
hashMap: Map<string, Array<{
|
|
82
|
+
alg: string;
|
|
83
|
+
content: string;
|
|
84
|
+
}>>;
|
|
66
85
|
}): {
|
|
67
86
|
directDeps: string[];
|
|
68
87
|
graph: Map<string, {
|
|
69
88
|
name: string;
|
|
70
89
|
version: string;
|
|
71
90
|
children: string[];
|
|
91
|
+
hashes?: Array<{
|
|
92
|
+
alg: string;
|
|
93
|
+
content: string;
|
|
94
|
+
}>;
|
|
72
95
|
}>;
|
|
73
96
|
};
|
|
74
97
|
}
|