@trustify-da/trustify-da-javascript-client 0.2.4-ea.13
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/LICENSE +201 -0
- package/README.md +482 -0
- package/config/config.properties +1 -0
- package/dist/package.json +106 -0
- package/dist/src/analysis.d.ts +43 -0
- package/dist/src/analysis.js +252 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +102 -0
- package/dist/src/cyclone_dx_sbom.d.ts +77 -0
- package/dist/src/cyclone_dx_sbom.js +244 -0
- package/dist/src/index.d.ts +82 -0
- package/dist/src/index.js +194 -0
- package/dist/src/oci_image/images.d.ts +99 -0
- package/dist/src/oci_image/images.js +263 -0
- package/dist/src/oci_image/platform.d.ts +59 -0
- package/dist/src/oci_image/platform.js +138 -0
- package/dist/src/oci_image/utils.d.ts +42 -0
- package/dist/src/oci_image/utils.js +496 -0
- package/dist/src/provider.d.ts +29 -0
- package/dist/src/provider.js +47 -0
- package/dist/src/providers/base_java.d.ts +85 -0
- package/dist/src/providers/base_java.js +191 -0
- package/dist/src/providers/base_javascript.d.ts +127 -0
- package/dist/src/providers/base_javascript.js +350 -0
- package/dist/src/providers/golang_gomodules.d.ts +42 -0
- package/dist/src/providers/golang_gomodules.js +403 -0
- package/dist/src/providers/java_gradle.d.ts +35 -0
- package/dist/src/providers/java_gradle.js +399 -0
- package/dist/src/providers/java_gradle_groovy.d.ts +7 -0
- package/dist/src/providers/java_gradle_groovy.js +19 -0
- package/dist/src/providers/java_gradle_kotlin.d.ts +11 -0
- package/dist/src/providers/java_gradle_kotlin.js +23 -0
- package/dist/src/providers/java_maven.d.ts +52 -0
- package/dist/src/providers/java_maven.js +263 -0
- package/dist/src/providers/javascript_npm.d.ts +4 -0
- package/dist/src/providers/javascript_npm.js +15 -0
- package/dist/src/providers/javascript_pnpm.d.ts +5 -0
- package/dist/src/providers/javascript_pnpm.js +22 -0
- package/dist/src/providers/javascript_yarn.d.ts +11 -0
- package/dist/src/providers/javascript_yarn.js +39 -0
- package/dist/src/providers/manifest.d.ts +11 -0
- package/dist/src/providers/manifest.js +48 -0
- package/dist/src/providers/processors/yarn_berry_processor.d.ts +41 -0
- package/dist/src/providers/processors/yarn_berry_processor.js +130 -0
- package/dist/src/providers/processors/yarn_classic_processor.d.ts +37 -0
- package/dist/src/providers/processors/yarn_classic_processor.js +109 -0
- package/dist/src/providers/processors/yarn_processor.d.ts +9 -0
- package/dist/src/providers/processors/yarn_processor.js +20 -0
- package/dist/src/providers/python_controller.d.ts +31 -0
- package/dist/src/providers/python_controller.js +406 -0
- package/dist/src/providers/python_pip.d.ts +35 -0
- package/dist/src/providers/python_pip.js +227 -0
- package/dist/src/sbom.d.ts +59 -0
- package/dist/src/sbom.js +84 -0
- package/dist/src/tools.d.ts +74 -0
- package/dist/src/tools.js +159 -0
- package/package.json +106 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { toPurl, toPurlFromString } from "../../tools.js";
|
|
2
|
+
import { purlType } from "../base_javascript.js";
|
|
3
|
+
import Yarn_processor from "./yarn_processor.js";
|
|
4
|
+
/**
|
|
5
|
+
* Processor for Yarn Classic package manager
|
|
6
|
+
* Handles parsing and processing of dependencies for Yarn Classic projects
|
|
7
|
+
*/
|
|
8
|
+
export default class Yarn_classic_processor extends Yarn_processor {
|
|
9
|
+
/**
|
|
10
|
+
* Returns the command arguments for listing dependencies
|
|
11
|
+
* @param {boolean} includeTransitive - Whether to include transitive dependencies
|
|
12
|
+
* @returns {string[]} Command arguments for listing dependencies
|
|
13
|
+
*/
|
|
14
|
+
listCmdArgs(includeTransitive) {
|
|
15
|
+
return ['list', includeTransitive ? '--depth=Infinity' : '--depth=0', '--prod', '--frozen-lockfile', '--json'];
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Returns the command arguments for updating the lock file
|
|
19
|
+
* @returns {string[]} Command arguments for updating the lock file
|
|
20
|
+
*/
|
|
21
|
+
updateLockFileCmdArgs() {
|
|
22
|
+
return ['install', '--frozen-lockfile'];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parses the dependency tree output from Yarn Classic
|
|
26
|
+
* @param {string} output - The raw command output
|
|
27
|
+
* @returns {string} Unchanged output as it's already in JSON format
|
|
28
|
+
*/
|
|
29
|
+
parseDepTreeOutput(output) {
|
|
30
|
+
return output;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extracts root dependencies from the dependency tree
|
|
34
|
+
* @param {Object} depTree - The dependency tree object
|
|
35
|
+
* @returns {Map<string, PackageURL>} Map of dependency names to their PackageURL objects
|
|
36
|
+
*/
|
|
37
|
+
getRootDependencies(depTree) {
|
|
38
|
+
if (!depTree?.data?.trees) {
|
|
39
|
+
return new Map();
|
|
40
|
+
}
|
|
41
|
+
return new Map(depTree.data.trees.map(dep => {
|
|
42
|
+
const depName = dep.name;
|
|
43
|
+
const idx = depName.lastIndexOf('@');
|
|
44
|
+
const name = depName.substring(0, idx);
|
|
45
|
+
const version = idx !== -1 ? depName.substring(idx + 1) : '';
|
|
46
|
+
return [name, toPurl(purlType, name, version)];
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Adds dependencies to the SBOM
|
|
51
|
+
* @param {Sbom} sbom - The SBOM object to add dependencies to
|
|
52
|
+
* @param {Object} depTree - The dependency tree object
|
|
53
|
+
*/
|
|
54
|
+
addDependenciesToSbom(sbom, depTree) {
|
|
55
|
+
if (!depTree?.data?.trees) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const rootPurl = toPurlFromString(sbom.getRoot().purl);
|
|
59
|
+
const purls = new Map();
|
|
60
|
+
depTree.data.trees.forEach(n => {
|
|
61
|
+
const dep = new NodeMetaData(n);
|
|
62
|
+
if (this._manifest.dependencies.includes(dep.name)) {
|
|
63
|
+
sbom.addDependency(rootPurl, dep.purl);
|
|
64
|
+
}
|
|
65
|
+
purls.set(dep.name, dep.purl);
|
|
66
|
+
});
|
|
67
|
+
depTree.data.trees.forEach(n => {
|
|
68
|
+
this.#addChildrenToSbom(sbom, n, purls);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Recursively adds child dependencies to the SBOM
|
|
73
|
+
* @param {Sbom} sbom - The SBOM object to add dependencies to
|
|
74
|
+
* @param {Object} node - The current dependency node
|
|
75
|
+
* @param {Map<string, PackageURL>} purls - Map of dependency names to their PackageURL objects
|
|
76
|
+
* @private
|
|
77
|
+
*/
|
|
78
|
+
#addChildrenToSbom(sbom, node, purls) {
|
|
79
|
+
const dep = new NodeMetaData(node);
|
|
80
|
+
const children = node.children ? node.children : [];
|
|
81
|
+
children.forEach(c => {
|
|
82
|
+
const child = new NodeMetaData(c);
|
|
83
|
+
const from = dep.shadow ? purls.get(dep.name) : dep.purl;
|
|
84
|
+
const to = child.shadow ? purls.get(child.name) : child.purl;
|
|
85
|
+
if (from && to) {
|
|
86
|
+
sbom.addDependency(from, to);
|
|
87
|
+
}
|
|
88
|
+
this.#addChildrenToSbom(sbom, c, purls);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Helper class to extract and store metadata from a dependency node
|
|
94
|
+
*/
|
|
95
|
+
class NodeMetaData {
|
|
96
|
+
/**
|
|
97
|
+
* Creates a new NodeMetaData instance
|
|
98
|
+
* @param {Object} node - The dependency node
|
|
99
|
+
*/
|
|
100
|
+
constructor(node) {
|
|
101
|
+
this.nodeName = node.name;
|
|
102
|
+
const idx = this.nodeName.lastIndexOf('@');
|
|
103
|
+
this.name = this.nodeName.substring(0, idx);
|
|
104
|
+
this.version = idx !== -1 ? this.nodeName.substring(idx + 1) : '';
|
|
105
|
+
this.purl = toPurl(purlType, this.name, this.version);
|
|
106
|
+
const shadowNode = node.shadow;
|
|
107
|
+
this.shadow = shadowNode ? shadowNode : false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export default class Yarn_processor {
|
|
2
|
+
constructor(manifest) {
|
|
3
|
+
this._manifest = manifest;
|
|
4
|
+
}
|
|
5
|
+
installCmd() {
|
|
6
|
+
throw new Error('Method "installCmd" must be implemented.');
|
|
7
|
+
}
|
|
8
|
+
listDepsCmd() {
|
|
9
|
+
throw new Error('Method "listDepsCmd" must be implemented.');
|
|
10
|
+
}
|
|
11
|
+
getRootDependencies() {
|
|
12
|
+
throw new Error('Method "getRootDependencies" must be implemented.');
|
|
13
|
+
}
|
|
14
|
+
addDependenciesToSbom() {
|
|
15
|
+
throw new Error('Method "addDependenciesToSbom" must be implemented.');
|
|
16
|
+
}
|
|
17
|
+
parseDepTreeOutput() {
|
|
18
|
+
throw new Error('Method "parseDepTreeOutput" must be implemented.');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/** @typedef {{name: string, version: string, dependencies: DependencyEntry[]}} DependencyEntry */
|
|
2
|
+
export default class Python_controller {
|
|
3
|
+
/**
|
|
4
|
+
* Constructor to create new python controller instance to interact with pip package manager
|
|
5
|
+
* @param {boolean} realEnvironment - whether to use real environment supplied by client or to create virtual environment
|
|
6
|
+
* @param {string} pathToPip - path to pip package manager
|
|
7
|
+
* @param {string} pathToPython - path to python binary
|
|
8
|
+
* @param {string} pathToRequirements
|
|
9
|
+
* @
|
|
10
|
+
*/
|
|
11
|
+
constructor(realEnvironment: boolean, pathToPip: string, pathToPython: string, pathToRequirements: string, options?: {});
|
|
12
|
+
pythonEnvDir: any;
|
|
13
|
+
pathToPipBin: string;
|
|
14
|
+
pathToPythonBin: string;
|
|
15
|
+
realEnvironment: boolean;
|
|
16
|
+
pathToRequirements: string;
|
|
17
|
+
options: {};
|
|
18
|
+
prepareEnvironment(): void;
|
|
19
|
+
/**
|
|
20
|
+
*
|
|
21
|
+
* @param {boolean} includeTransitive - whether to return include in returned object transitive dependencies or not
|
|
22
|
+
* @return {[DependencyEntry]}
|
|
23
|
+
*/
|
|
24
|
+
getDependencies(includeTransitive: boolean): [DependencyEntry];
|
|
25
|
+
#private;
|
|
26
|
+
}
|
|
27
|
+
export type DependencyEntry = {
|
|
28
|
+
name: string;
|
|
29
|
+
version: string;
|
|
30
|
+
dependencies: DependencyEntry[];
|
|
31
|
+
};
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os, { EOL } from "os";
|
|
4
|
+
import { environmentVariableIsPopulated, getCustom, invokeCommand } from "../tools.js";
|
|
5
|
+
function getPipFreezeOutput() {
|
|
6
|
+
try {
|
|
7
|
+
return environmentVariableIsPopulated("TRUSTIFY_DA_PIP_FREEZE") ? new Buffer.from(process.env["TRUSTIFY_DA_PIP_FREEZE"], 'base64').toString('ascii') : invokeCommand(this.pathToPipBin, ['freeze', '--all']).toString();
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
throw new Error('Failed invoking \'pip freeze\' to list all installed packages in environment', { cause: error });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function getPipShowOutput(depNames) {
|
|
14
|
+
try {
|
|
15
|
+
return environmentVariableIsPopulated("TRUSTIFY_DA_PIP_SHOW") ? new Buffer.from(process.env["TRUSTIFY_DA_PIP_SHOW"], 'base64').toString('ascii') : invokeCommand(this.pathToPipBin, ['show', ...depNames]).toString();
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
throw new Error('fail invoking \'pip show\' to fetch metadata for all installed packages in environment', { cause: error });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** @typedef {{name: string, version: string, dependencies: DependencyEntry[]}} DependencyEntry */
|
|
22
|
+
export default class Python_controller {
|
|
23
|
+
pythonEnvDir;
|
|
24
|
+
pathToPipBin;
|
|
25
|
+
pathToPythonBin;
|
|
26
|
+
realEnvironment;
|
|
27
|
+
pathToRequirements;
|
|
28
|
+
options;
|
|
29
|
+
/**
|
|
30
|
+
* Constructor to create new python controller instance to interact with pip package manager
|
|
31
|
+
* @param {boolean} realEnvironment - whether to use real environment supplied by client or to create virtual environment
|
|
32
|
+
* @param {string} pathToPip - path to pip package manager
|
|
33
|
+
* @param {string} pathToPython - path to python binary
|
|
34
|
+
* @param {string} pathToRequirements
|
|
35
|
+
* @
|
|
36
|
+
*/
|
|
37
|
+
constructor(realEnvironment, pathToPip, pathToPython, pathToRequirements, options = {}) {
|
|
38
|
+
this.pathToPythonBin = pathToPython;
|
|
39
|
+
this.pathToPipBin = pathToPip;
|
|
40
|
+
this.realEnvironment = realEnvironment;
|
|
41
|
+
this.prepareEnvironment();
|
|
42
|
+
this.pathToRequirements = pathToRequirements;
|
|
43
|
+
this.options = options;
|
|
44
|
+
}
|
|
45
|
+
prepareEnvironment() {
|
|
46
|
+
if (!this.realEnvironment) {
|
|
47
|
+
this.pythonEnvDir = path.join(path.sep, "tmp", "exhort_env_js");
|
|
48
|
+
try {
|
|
49
|
+
invokeCommand(this.pathToPythonBin, ['-m', 'venv', this.pythonEnvDir]);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
throw new Error('Failed creating virtual python environment', { cause: error });
|
|
53
|
+
}
|
|
54
|
+
if (this.pathToPythonBin.includes("python3")) {
|
|
55
|
+
this.pathToPipBin = path.join(path.sep, this.pythonEnvDir, os.platform() === 'win32' ? "Scripts" : "bin", this.#decideIfWindowsOrLinuxPath("pip3"));
|
|
56
|
+
this.pathToPythonBin = path.join(path.sep, this.pythonEnvDir, os.platform() === 'win32' ? "Scripts" : "bin", this.#decideIfWindowsOrLinuxPath("python3"));
|
|
57
|
+
if (os.platform() === 'win32') {
|
|
58
|
+
let driveLetter = path.parse(process.cwd()).root;
|
|
59
|
+
this.pathToPythonBin = `${driveLetter}${this.pathToPythonBin.substring(1)}`;
|
|
60
|
+
this.pathToPipBin = `${driveLetter}${this.pathToPipBin.substring(1)}`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
this.pathToPipBin = path.join(path.sep, this.pythonEnvDir, os.platform() === 'win32' ? "Scripts" : "bin", this.#decideIfWindowsOrLinuxPath("pip"));
|
|
65
|
+
this.pathToPythonBin = path.join(path.sep, this.pythonEnvDir, os.platform() === 'win32' ? "Scripts" : "bin", this.#decideIfWindowsOrLinuxPath("python"));
|
|
66
|
+
if (os.platform() === 'win32') {
|
|
67
|
+
let driveLetter = path.parse(process.cwd()).root;
|
|
68
|
+
this.pathToPythonBin = `${driveLetter}${this.pathToPythonBin.substring(1)}`;
|
|
69
|
+
this.pathToPipBin = `${driveLetter}${this.pathToPipBin.substring(1)}`;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// upgrade pip version to latest
|
|
73
|
+
try {
|
|
74
|
+
invokeCommand(this.pathToPythonBin, ['-m', 'pip', 'install', '--upgrade', 'pip']);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
throw new Error('Failed upgrading pip version in virtual python environment', { cause: error });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
if (this.pathToPythonBin.startsWith("python")) {
|
|
82
|
+
this.pythonEnvDir = process.cwd();
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
this.pythonEnvDir = path.dirname(this.pathToPythonBin);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
#decideIfWindowsOrLinuxPath(fileName) {
|
|
90
|
+
if (os.platform() === "win32") {
|
|
91
|
+
return fileName + ".exe";
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
return fileName;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
*
|
|
99
|
+
* @param {boolean} includeTransitive - whether to return include in returned object transitive dependencies or not
|
|
100
|
+
* @return {[DependencyEntry]}
|
|
101
|
+
*/
|
|
102
|
+
getDependencies(includeTransitive) {
|
|
103
|
+
let startingTime;
|
|
104
|
+
let endingTime;
|
|
105
|
+
if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
|
|
106
|
+
startingTime = new Date();
|
|
107
|
+
console.log("Starting time to get requirements.txt dependency tree = " + startingTime);
|
|
108
|
+
}
|
|
109
|
+
if (!this.realEnvironment) {
|
|
110
|
+
let installBestEfforts = getCustom("TRUSTIFY_DA_PYTHON_INSTALL_BEST_EFFORTS", "false", this.options);
|
|
111
|
+
if (installBestEfforts === "false") {
|
|
112
|
+
try {
|
|
113
|
+
invokeCommand(this.pathToPipBin, ['install', '-r', this.pathToRequirements]);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
throw new Error('Failed installing requirements.txt manifest in virtual python environment', { cause: error });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// make best efforts to install the requirements.txt on the virtual environment created from the python3 passed in.
|
|
120
|
+
// that means that it will install the packages without referring to the versions, but will let pip choose the version
|
|
121
|
+
// tailored for version of the python environment( and of pip package manager) for each package.
|
|
122
|
+
else {
|
|
123
|
+
let matchManifestVersions = getCustom("MATCH_MANIFEST_VERSIONS", "true", this.options);
|
|
124
|
+
if (matchManifestVersions === "true") {
|
|
125
|
+
throw new Error("Conflicting settings, TRUSTIFY_DA_PYTHON_INSTALL_BEST_EFFORTS=true can only work with MATCH_MANIFEST_VERSIONS=false");
|
|
126
|
+
}
|
|
127
|
+
this.#installingRequirementsOneByOne();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
let dependencies = this.#getDependenciesImpl(includeTransitive);
|
|
131
|
+
this.#cleanEnvironment();
|
|
132
|
+
if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
|
|
133
|
+
endingTime = new Date();
|
|
134
|
+
console.log("Ending time to get requirements.txt dependency tree = " + endingTime);
|
|
135
|
+
let time = (endingTime - startingTime) / 1000;
|
|
136
|
+
console.log("total time to get requirements.txt dependency tree = " + time);
|
|
137
|
+
}
|
|
138
|
+
return dependencies;
|
|
139
|
+
}
|
|
140
|
+
#installingRequirementsOneByOne() {
|
|
141
|
+
let requirementsContent = fs.readFileSync(this.pathToRequirements);
|
|
142
|
+
let requirementsRows = requirementsContent.toString().split(EOL);
|
|
143
|
+
requirementsRows.filter((line) => !line.trim().startsWith("#")).filter((line) => line.trim() !== "").forEach((dependency) => {
|
|
144
|
+
let dependencyName = getDependencyName(dependency);
|
|
145
|
+
try {
|
|
146
|
+
invokeCommand(this.pathToPipBin, ['install', dependencyName]);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
throw new Error(`Failed in best-effort installing ${dependencyName} in virtual python environment`, { cause: error });
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* @private
|
|
155
|
+
*/
|
|
156
|
+
#cleanEnvironment() {
|
|
157
|
+
if (!this.realEnvironment) {
|
|
158
|
+
try {
|
|
159
|
+
invokeCommand(this.pathToPipBin, ['uninstall', '-y', '-r', this.pathToRequirements]);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
throw new Error('Failed uninstalling requirements.txt in virtual python environment', { cause: error });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
#getDependenciesImpl(includeTransitive) {
|
|
167
|
+
let dependencies = new Array();
|
|
168
|
+
let usePipDepTree = getCustom("TRUSTIFY_DA_PIP_USE_DEP_TREE", "false", this.options);
|
|
169
|
+
let freezeOutput;
|
|
170
|
+
let lines;
|
|
171
|
+
let depNames;
|
|
172
|
+
let pipShowOutput;
|
|
173
|
+
let allPipShowDeps;
|
|
174
|
+
let pipDepTreeJsonArrayOutput;
|
|
175
|
+
if (usePipDepTree !== "true") {
|
|
176
|
+
freezeOutput = getPipFreezeOutput.call(this);
|
|
177
|
+
lines = freezeOutput.split(EOL);
|
|
178
|
+
depNames = lines.map(line => getDependencyName(line));
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
pipDepTreeJsonArrayOutput = getDependencyTreeJsonFromPipDepTree(this.pathToPipBin, this.pathToPythonBin);
|
|
182
|
+
}
|
|
183
|
+
if (usePipDepTree !== "true") {
|
|
184
|
+
pipShowOutput = getPipShowOutput.call(this, depNames);
|
|
185
|
+
allPipShowDeps = pipShowOutput.split(EOL + "---" + EOL);
|
|
186
|
+
}
|
|
187
|
+
//debug
|
|
188
|
+
// pipShowOutput = "alternative pip show output goes here for debugging"
|
|
189
|
+
let matchManifestVersions = getCustom("MATCH_MANIFEST_VERSIONS", "true", this.options);
|
|
190
|
+
let linesOfRequirements = fs.readFileSync(this.pathToRequirements).toString().split(EOL).filter((line) => !line.trim().startsWith("#")).map(line => line.trim());
|
|
191
|
+
let CachedEnvironmentDeps = {};
|
|
192
|
+
if (usePipDepTree !== "true") {
|
|
193
|
+
allPipShowDeps.forEach((record) => {
|
|
194
|
+
let dependencyName = getDependencyNameShow(record).toLowerCase();
|
|
195
|
+
CachedEnvironmentDeps[dependencyName] = record;
|
|
196
|
+
CachedEnvironmentDeps[dependencyName.replace("-", "_")] = record;
|
|
197
|
+
CachedEnvironmentDeps[dependencyName.replace("_", "-")] = record;
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
pipDepTreeJsonArrayOutput.forEach(depTreeEntry => {
|
|
202
|
+
let packageName = depTreeEntry["package"]["package_name"].toLowerCase();
|
|
203
|
+
let pipDepTreeEntryForCache = {
|
|
204
|
+
name: packageName,
|
|
205
|
+
version: depTreeEntry["package"]["installed_version"],
|
|
206
|
+
dependencies: depTreeEntry["dependencies"].map(dep => dep["package_name"])
|
|
207
|
+
};
|
|
208
|
+
CachedEnvironmentDeps[packageName] = pipDepTreeEntryForCache;
|
|
209
|
+
CachedEnvironmentDeps[packageName.replace("-", "_")] = pipDepTreeEntryForCache;
|
|
210
|
+
CachedEnvironmentDeps[packageName.replace("_", "-")] = pipDepTreeEntryForCache;
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
linesOfRequirements.forEach((dep) => {
|
|
214
|
+
// if matchManifestVersions setting is turned on , then
|
|
215
|
+
if (matchManifestVersions === "true") {
|
|
216
|
+
let dependencyName;
|
|
217
|
+
let manifestVersion;
|
|
218
|
+
let installedVersion;
|
|
219
|
+
let doubleEqualSignPosition;
|
|
220
|
+
if (dep.includes("==")) {
|
|
221
|
+
doubleEqualSignPosition = dep.indexOf("==");
|
|
222
|
+
manifestVersion = dep.substring(doubleEqualSignPosition + 2).trim();
|
|
223
|
+
if (manifestVersion.includes("#")) {
|
|
224
|
+
let hashCharIndex = manifestVersion.indexOf("#");
|
|
225
|
+
manifestVersion = manifestVersion.substring(0, hashCharIndex);
|
|
226
|
+
}
|
|
227
|
+
dependencyName = getDependencyName(dep);
|
|
228
|
+
// only compare between declared version in manifest to installed version , if the package is installed.
|
|
229
|
+
if (CachedEnvironmentDeps[dependencyName.toLowerCase()] !== undefined) {
|
|
230
|
+
if (usePipDepTree !== "true") {
|
|
231
|
+
installedVersion = getDependencyVersion(CachedEnvironmentDeps[dependencyName.toLowerCase()]);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
installedVersion = CachedEnvironmentDeps[dependencyName.toLowerCase()].version;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
if (installedVersion) {
|
|
238
|
+
if (manifestVersion.trim() !== installedVersion.trim()) {
|
|
239
|
+
throw new Error(`Can't continue with analysis - versions mismatch for dependency name ${dependencyName} (manifest version=${manifestVersion}, installed version=${installedVersion}).If you want to allow version mismatch for analysis between installed and requested packages, set environment variable/setting MATCH_MANIFEST_VERSIONS=false`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
let path = new Array();
|
|
245
|
+
let depName = getDependencyName(dep);
|
|
246
|
+
//array to track a path for each branch in the dependency tree
|
|
247
|
+
path.push(depName.toLowerCase());
|
|
248
|
+
bringAllDependencies(dependencies, depName, CachedEnvironmentDeps, includeTransitive, path, usePipDepTree);
|
|
249
|
+
});
|
|
250
|
+
dependencies.sort((dep1, dep2) => {
|
|
251
|
+
const DEP1 = dep1.name.toLowerCase();
|
|
252
|
+
const DEP2 = dep2.name.toLowerCase();
|
|
253
|
+
if (DEP1 < DEP2) {
|
|
254
|
+
return -1;
|
|
255
|
+
}
|
|
256
|
+
if (DEP1 > DEP2) {
|
|
257
|
+
return 1;
|
|
258
|
+
}
|
|
259
|
+
return 0;
|
|
260
|
+
});
|
|
261
|
+
return dependencies;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
*
|
|
266
|
+
* @param {string} record - a record block from pip show
|
|
267
|
+
* @return {string} the name of the dependency of the pip show record.
|
|
268
|
+
*/
|
|
269
|
+
function getDependencyNameShow(record) {
|
|
270
|
+
let versionKeyIndex = record.indexOf("Name:");
|
|
271
|
+
let versionToken = record.substring(versionKeyIndex + 5);
|
|
272
|
+
let endOfLine = versionToken.indexOf(EOL);
|
|
273
|
+
return versionToken.substring(0, endOfLine).trim();
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
*
|
|
277
|
+
* @param {string} record - a record block from pip show
|
|
278
|
+
* @return {string} the name of the dependency of the pip show record.
|
|
279
|
+
*/
|
|
280
|
+
function getDependencyVersion(record) {
|
|
281
|
+
let versionKeyIndex = record.indexOf("Version:");
|
|
282
|
+
let versionToken = record.substring(versionKeyIndex + 8);
|
|
283
|
+
let endOfLine = versionToken.indexOf(EOL);
|
|
284
|
+
return versionToken.substring(0, endOfLine).trim();
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
*
|
|
288
|
+
* @param depLine the dependency with version/ version requirement as shown in requirements.txt
|
|
289
|
+
* @return {string} the name of dependency
|
|
290
|
+
*/
|
|
291
|
+
function getDependencyName(depLine) {
|
|
292
|
+
const regex = /[^\w\s-_.]/g;
|
|
293
|
+
let endIndex = depLine.search(regex);
|
|
294
|
+
let result = depLine.substring(0, endIndex);
|
|
295
|
+
// In case package in requirements text only contain package name without version
|
|
296
|
+
if (result.trim() === "") {
|
|
297
|
+
const regex = /[\w\s-_.]+/g;
|
|
298
|
+
if (depLine.match(regex)) {
|
|
299
|
+
result = depLine.match(regex)[0];
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
result = depLine;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return result.trim();
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
*
|
|
309
|
+
* @param record - a dependency record block from pip show
|
|
310
|
+
* @return {[string]} array of all direct deps names of that dependency
|
|
311
|
+
*/
|
|
312
|
+
function getDepsList(record) {
|
|
313
|
+
let requiresKeyIndex = record.indexOf("Requires:");
|
|
314
|
+
let requiresToken = record.substring(requiresKeyIndex + 9);
|
|
315
|
+
let endOfLine = requiresToken.indexOf(EOL);
|
|
316
|
+
let listOfDepsString = requiresToken.substring(0, endOfLine);
|
|
317
|
+
let list = listOfDepsString.split(",").filter(line => line.trim() !== "").map(line => line.trim());
|
|
318
|
+
return list;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
*
|
|
322
|
+
* @param {[DependencyEntry]} dependencies
|
|
323
|
+
* @param dependencyName
|
|
324
|
+
* @param cachedEnvironmentDeps
|
|
325
|
+
* @param includeTransitive
|
|
326
|
+
* @param usePipDepTree
|
|
327
|
+
* @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
|
|
328
|
+
*/
|
|
329
|
+
function bringAllDependencies(dependencies, dependencyName, cachedEnvironmentDeps, includeTransitive, path, usePipDepTree) {
|
|
330
|
+
if (dependencyName?.trim() === "") {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
let record = cachedEnvironmentDeps[dependencyName.toLowerCase()];
|
|
334
|
+
if (record == null) {
|
|
335
|
+
throw new Error(`Package ${dependencyName} is not installed in your python environment, either install it (better to install requirements.txt altogether) or set the setting TRUSTIFY_DA_PYTHON_VIRTUAL_ENV=true to automatically install it in virtual environment (please note that this may slow down the analysis)`);
|
|
336
|
+
}
|
|
337
|
+
let depName;
|
|
338
|
+
let version;
|
|
339
|
+
let directDeps;
|
|
340
|
+
if (usePipDepTree !== "true") {
|
|
341
|
+
depName = getDependencyNameShow(record);
|
|
342
|
+
version = getDependencyVersion(record);
|
|
343
|
+
directDeps = getDepsList(record);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
depName = record.name;
|
|
347
|
+
version = record.version;
|
|
348
|
+
directDeps = record.dependencies;
|
|
349
|
+
}
|
|
350
|
+
let targetDeps = new Array();
|
|
351
|
+
let entry = { "name": depName, "version": version, "dependencies": [] };
|
|
352
|
+
dependencies.push(entry);
|
|
353
|
+
directDeps.forEach((dep) => {
|
|
354
|
+
let depArray = new Array();
|
|
355
|
+
// to avoid infinite loop, check if the dependency not already on current path, before going recursively resolving its dependencies.
|
|
356
|
+
if (!path.includes(dep.toLowerCase())) {
|
|
357
|
+
// send to recurrsion the path + the current dep
|
|
358
|
+
depArray.push(dep.toLowerCase());
|
|
359
|
+
if (includeTransitive) {
|
|
360
|
+
// send to recurrsion the array of all deps in path + the current dependency name which is not on the path.
|
|
361
|
+
bringAllDependencies(targetDeps, dep, cachedEnvironmentDeps, includeTransitive, path.concat(depArray), usePipDepTree);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
// sort ra
|
|
365
|
+
targetDeps.sort((dep1, dep2) => {
|
|
366
|
+
const DEP1 = dep1.name.toLowerCase();
|
|
367
|
+
const DEP2 = dep2.name.toLowerCase();
|
|
368
|
+
if (DEP1 < DEP2) {
|
|
369
|
+
return -1;
|
|
370
|
+
}
|
|
371
|
+
if (DEP1 > DEP2) {
|
|
372
|
+
return 1;
|
|
373
|
+
}
|
|
374
|
+
return 0;
|
|
375
|
+
});
|
|
376
|
+
entry["dependencies"] = targetDeps;
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* This function install tiny pipdeptree tool using pip ( if it's not already installed on python environment), and use it to fetch the dependency tree in json format.
|
|
381
|
+
* @param {string }pipPath - the filesystem path location of pip binary
|
|
382
|
+
* @param {string }pythonPath - the filesystem path location of python binary
|
|
383
|
+
* @return {Object[] } json array containing objects with the packages and their dependencies from pipdeptree utility
|
|
384
|
+
* @private
|
|
385
|
+
*/
|
|
386
|
+
function getDependencyTreeJsonFromPipDepTree(pipPath, pythonPath) {
|
|
387
|
+
let dependencyTree;
|
|
388
|
+
try {
|
|
389
|
+
invokeCommand(pipPath, ['install', 'pipdeptree']);
|
|
390
|
+
}
|
|
391
|
+
catch (error) {
|
|
392
|
+
throw new Error(`Failed installing pipdeptree utility`, { cause: error });
|
|
393
|
+
}
|
|
394
|
+
try {
|
|
395
|
+
if (pythonPath.startsWith("python")) {
|
|
396
|
+
dependencyTree = invokeCommand('pipdeptree', ['--json']).toString();
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
dependencyTree = invokeCommand('pipdeptree', ['--json', '--python', pythonPath]).toString();
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
throw new Error(`Failed building dependency tree using pipdeptree tool, stopping analysis`, { cause: error });
|
|
404
|
+
}
|
|
405
|
+
return JSON.parse(dependencyTree);
|
|
406
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
declare namespace _default {
|
|
2
|
+
export { isSupported };
|
|
3
|
+
export { validateLockFile };
|
|
4
|
+
export { provideComponent };
|
|
5
|
+
export { provideStack };
|
|
6
|
+
}
|
|
7
|
+
export default _default;
|
|
8
|
+
export type DependencyEntry = {
|
|
9
|
+
name: string;
|
|
10
|
+
version: string;
|
|
11
|
+
dependencies: DependencyEntry[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} manifestName - the subject manifest name-type
|
|
15
|
+
* @returns {boolean} - return true if `requirements.txt` is the manifest name-type
|
|
16
|
+
*/
|
|
17
|
+
declare function isSupported(manifestName: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* @param {string} manifestDir - the directory where the manifest lies
|
|
20
|
+
*/
|
|
21
|
+
declare function validateLockFile(): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Provide content and content type for python-pip component analysis.
|
|
24
|
+
* @param {string} manifest - path to requirements.txt for component report
|
|
25
|
+
* @param {{}} [opts={}] - optional various options to pass along the application
|
|
26
|
+
* @returns {Provided}
|
|
27
|
+
*/
|
|
28
|
+
declare function provideComponent(manifest: string, opts?: {} | undefined): Provided;
|
|
29
|
+
/**
|
|
30
|
+
* Provide content and content type for python-pip stack analysis.
|
|
31
|
+
* @param {string} manifest - the manifest path or name
|
|
32
|
+
* @param {{}} [opts={}] - optional various options to pass along the application
|
|
33
|
+
* @returns {Provided}
|
|
34
|
+
*/
|
|
35
|
+
declare function provideStack(manifest: string, opts?: {} | undefined): Provided;
|