@trustify-da/trustify-da-javascript-client 0.2.4-ea-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +482 -0
  3. package/config/config.properties +1 -0
  4. package/dist/package.json +110 -0
  5. package/dist/src/analysis.d.ts +43 -0
  6. package/dist/src/analysis.js +252 -0
  7. package/dist/src/cli.d.ts +2 -0
  8. package/dist/src/cli.js +102 -0
  9. package/dist/src/cyclone_dx_sbom.d.ts +77 -0
  10. package/dist/src/cyclone_dx_sbom.js +244 -0
  11. package/dist/src/index.d.ts +82 -0
  12. package/dist/src/index.js +194 -0
  13. package/dist/src/oci_image/images.d.ts +99 -0
  14. package/dist/src/oci_image/images.js +263 -0
  15. package/dist/src/oci_image/platform.d.ts +59 -0
  16. package/dist/src/oci_image/platform.js +138 -0
  17. package/dist/src/oci_image/utils.d.ts +42 -0
  18. package/dist/src/oci_image/utils.js +496 -0
  19. package/dist/src/provider.d.ts +29 -0
  20. package/dist/src/provider.js +47 -0
  21. package/dist/src/providers/base_java.d.ts +85 -0
  22. package/dist/src/providers/base_java.js +191 -0
  23. package/dist/src/providers/base_javascript.d.ts +127 -0
  24. package/dist/src/providers/base_javascript.js +350 -0
  25. package/dist/src/providers/golang_gomodules.d.ts +42 -0
  26. package/dist/src/providers/golang_gomodules.js +403 -0
  27. package/dist/src/providers/java_gradle.d.ts +35 -0
  28. package/dist/src/providers/java_gradle.js +399 -0
  29. package/dist/src/providers/java_gradle_groovy.d.ts +7 -0
  30. package/dist/src/providers/java_gradle_groovy.js +19 -0
  31. package/dist/src/providers/java_gradle_kotlin.d.ts +11 -0
  32. package/dist/src/providers/java_gradle_kotlin.js +23 -0
  33. package/dist/src/providers/java_maven.d.ts +52 -0
  34. package/dist/src/providers/java_maven.js +263 -0
  35. package/dist/src/providers/javascript_npm.d.ts +4 -0
  36. package/dist/src/providers/javascript_npm.js +15 -0
  37. package/dist/src/providers/javascript_pnpm.d.ts +5 -0
  38. package/dist/src/providers/javascript_pnpm.js +22 -0
  39. package/dist/src/providers/javascript_yarn.d.ts +11 -0
  40. package/dist/src/providers/javascript_yarn.js +39 -0
  41. package/dist/src/providers/manifest.d.ts +11 -0
  42. package/dist/src/providers/manifest.js +48 -0
  43. package/dist/src/providers/processors/yarn_berry_processor.d.ts +41 -0
  44. package/dist/src/providers/processors/yarn_berry_processor.js +130 -0
  45. package/dist/src/providers/processors/yarn_classic_processor.d.ts +37 -0
  46. package/dist/src/providers/processors/yarn_classic_processor.js +109 -0
  47. package/dist/src/providers/processors/yarn_processor.d.ts +9 -0
  48. package/dist/src/providers/processors/yarn_processor.js +20 -0
  49. package/dist/src/providers/python_controller.d.ts +31 -0
  50. package/dist/src/providers/python_controller.js +406 -0
  51. package/dist/src/providers/python_pip.d.ts +35 -0
  52. package/dist/src/providers/python_pip.js +227 -0
  53. package/dist/src/sbom.d.ts +59 -0
  54. package/dist/src/sbom.js +84 -0
  55. package/dist/src/tools.d.ts +74 -0
  56. package/dist/src/tools.js +159 -0
  57. package/package.json +110 -0
@@ -0,0 +1,263 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { EOL } from 'os';
5
+ import { XMLParser } from 'fast-xml-parser';
6
+ import Sbom from '../sbom.js';
7
+ import { getCustom } from '../tools.js';
8
+ import Base_java, { ecosystem_maven } from "./base_java.js";
9
+ /** @typedef {import('../provider').Provider} */
10
+ /** @typedef {import('../provider').Provided} Provided */
11
+ /** @typedef {{name: string, version: string}} Package */
12
+ /** @typedef {{groupId: string, artifactId: string, version: string, scope: string, ignore: boolean}} Dependency */
13
+ export default class Java_maven extends Base_java {
14
+ constructor() {
15
+ super('mvn', 'mvnw' + (process.platform === 'win32' ? '.cmd' : ''));
16
+ }
17
+ /**
18
+ * @param {string} manifestName - the subject manifest name-type
19
+ * @returns {boolean} - return true if `pom.xml` is the manifest name-type
20
+ */
21
+ isSupported(manifestName) {
22
+ return 'pom.xml' === manifestName;
23
+ }
24
+ /**
25
+ * @param {string} manifestDir - the directory where the manifest lies
26
+ */
27
+ validateLockFile() { return true; }
28
+ /**
29
+ * Provide content and content type for maven-maven stack analysis.
30
+ * @param {string} manifest - the manifest path or name
31
+ * @param {{}} [opts={}] - optional various options to pass along the application
32
+ * @returns {Provided}
33
+ */
34
+ provideStack(manifest, opts = {}) {
35
+ return {
36
+ ecosystem: ecosystem_maven,
37
+ content: this.#createSbomStackAnalysis(manifest, opts),
38
+ contentType: 'application/vnd.cyclonedx+json'
39
+ };
40
+ }
41
+ /**
42
+ * Provide content and content type for maven-maven component analysis.
43
+ * @param {string} manifest - path to the manifest file
44
+ * @param {{}} [opts={}] - optional various options to pass along the application
45
+ * @returns {Provided}
46
+ */
47
+ provideComponent(manifest, opts = {}) {
48
+ return {
49
+ ecosystem: ecosystem_maven,
50
+ content: this.#getSbomForComponentAnalysis(manifest, opts),
51
+ contentType: 'application/vnd.cyclonedx+json'
52
+ };
53
+ }
54
+ /**
55
+ * Create a Dot Graph dependency tree for a manifest path.
56
+ * @param {string} manifest - path for pom.xml
57
+ * @param {{}} [opts={}] - optional various options to pass along the application
58
+ * @returns {string} the Dot Graph content
59
+ * @private
60
+ */
61
+ #createSbomStackAnalysis(manifest, opts = {}) {
62
+ const manifestDir = path.dirname(manifest);
63
+ const mvn = this.selectToolBinary(manifest, opts);
64
+ const mvnArgs = JSON.parse(getCustom('TRUSTIFY_DA_MVN_ARGS', '[]', opts));
65
+ if (!Array.isArray(mvnArgs)) {
66
+ throw new Error(`configured maven args is not an array, is a ${typeof mvnArgs}`);
67
+ }
68
+ // clean maven target
69
+ try {
70
+ this._invokeCommand(mvn, ['-q', 'clean', ...mvnArgs], { cwd: manifestDir });
71
+ }
72
+ catch (error) {
73
+ throw new Error(`failed to clean maven target`, { cause: error });
74
+ }
75
+ // create dependency graph in a temp file
76
+ let tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'exhort_'));
77
+ let tmpDepTree = path.join(tmpDir, 'mvn_deptree.txt');
78
+ // build initial command (dot outputType is not available for verbose mode)
79
+ let depTreeCmdArgs = ['-q', 'org.apache.maven.plugins:maven-dependency-plugin:3.6.0:tree',
80
+ '-Dscope=compile', '-Dverbose',
81
+ '-DoutputType=text', `-DoutputFile=${tmpDepTree}`];
82
+ // exclude ignored dependencies, exclude format is groupId:artifactId:scope:version.
83
+ // version and scope are marked as '*' if not specified (we do not use scope yet)
84
+ let ignoredDeps = new Array();
85
+ let ignoredArgs = new Array();
86
+ this.#getDependencies(manifest).forEach(dep => {
87
+ if (dep.ignore) {
88
+ ignoredArgs.push(`${dep['groupId']}:${dep['artifactId']}`);
89
+ //version is not reliable because we're not resolving the effective pom
90
+ ignoredDeps.push(this.toPurl(dep.groupId, dep.artifactId));
91
+ }
92
+ });
93
+ if (ignoredArgs.length > 0) {
94
+ depTreeCmdArgs.push(`-Dexcludes=${ignoredArgs.join(',')}`);
95
+ }
96
+ // execute dependency tree command
97
+ try {
98
+ this._invokeCommand(mvn, [...depTreeCmdArgs, ...mvnArgs], { cwd: manifestDir });
99
+ }
100
+ catch (error) {
101
+ throw new Error(`failed creating maven dependency tree`, { cause: error });
102
+ }
103
+ // read dependency tree from temp file
104
+ let content = fs.readFileSync(tmpDepTree);
105
+ if (process.env["TRUSTIFY_DA_DEBUG"] === "true") {
106
+ console.error("Dependency tree that will be used as input for creating the BOM =>" + EOL + EOL + content.toString());
107
+ }
108
+ let sbom = this.createSbomFileFromTextFormat(content.toString(), ignoredDeps, opts);
109
+ // delete temp file and directory
110
+ fs.rmSync(tmpDir, { recursive: true, force: true });
111
+ // return dependency graph as string
112
+ return sbom;
113
+ }
114
+ /**
115
+ *
116
+ * @param {String} textGraphList Text graph String of the manifest
117
+ * @param {[String]} ignoredDeps List of ignored dependencies to be omitted from sbom
118
+ * @return {String} formatted sbom Json String with all dependencies
119
+ */
120
+ createSbomFileFromTextFormat(textGraphList, ignoredDeps, opts) {
121
+ let lines = textGraphList.split(EOL);
122
+ // get root component
123
+ let root = lines[0];
124
+ let rootPurl = this.parseDep(root);
125
+ let sbom = new Sbom();
126
+ sbom.addRoot(rootPurl);
127
+ this.parseDependencyTree(root, 0, lines.slice(1), sbom);
128
+ return sbom.filterIgnoredDeps(ignoredDeps).getAsJsonString(opts);
129
+ }
130
+ /**
131
+ * Create a dependency list for a manifest content.
132
+ * @param {{}} [opts={}] - optional various options to pass along the application
133
+ * @returns {[Dependency]} the Dot Graph content
134
+ * @private
135
+ */
136
+ #getSbomForComponentAnalysis(manifestPath, opts = {}) {
137
+ const mvn = this.selectToolBinary(manifestPath, opts);
138
+ const mvnArgs = JSON.parse(getCustom('TRUSTIFY_DA_MVN_ARGS', '[]', opts));
139
+ if (!Array.isArray(mvnArgs)) {
140
+ throw new Error(`configured maven args is not an array, is a ${typeof mvnArgs}`);
141
+ }
142
+ const tmpEffectivePom = path.resolve(path.join(path.dirname(manifestPath), 'effective-pom.xml'));
143
+ // create effective pom and save to temp file
144
+ try {
145
+ this._invokeCommand(mvn, ['-q', 'help:effective-pom', `-Doutput=${tmpEffectivePom}`, ...mvnArgs], { cwd: path.dirname(manifestPath) });
146
+ }
147
+ catch (error) {
148
+ throw new Error(`failed creating maven effective pom`, { cause: error });
149
+ }
150
+ // iterate over all dependencies in original pom and collect all ignored ones
151
+ let ignored = this.#getDependencies(manifestPath).filter(d => d.ignore);
152
+ // iterate over all dependencies and create a package for every non-ignored one
153
+ /** @type [Dependency] */
154
+ let dependencies = this.#getDependencies(tmpEffectivePom)
155
+ .filter(d => !this.#dependencyIn(d, ignored));
156
+ let sbom = new Sbom();
157
+ let rootDependency = this.#getRootFromPom(tmpEffectivePom, manifestPath);
158
+ let purlRoot = this.toPurl(rootDependency.groupId, rootDependency.artifactId, rootDependency.version);
159
+ sbom.addRoot(purlRoot);
160
+ dependencies.forEach(dep => {
161
+ let currentPurl = this.toPurl(dep.groupId, dep.artifactId, dep.version);
162
+ sbom.addDependency(purlRoot, currentPurl);
163
+ });
164
+ fs.rmSync(tmpEffectivePom);
165
+ // return dependencies list
166
+ return sbom.getAsJsonString(opts);
167
+ }
168
+ /**
169
+ *
170
+ * @param effectivePomManifest effective pom manifest path
171
+ * @param originalManifest pom.xml manifest path
172
+ * @return {Dependency} returns the root dependency for the pom
173
+ * @private
174
+ */
175
+ #getRootFromPom(effectivePomManifest) {
176
+ let parser = new XMLParser();
177
+ let buf = fs.readFileSync(effectivePomManifest);
178
+ let effectivePomStruct = parser.parse(buf.toString());
179
+ let pomRoot;
180
+ if (effectivePomStruct['project']) {
181
+ pomRoot = effectivePomStruct['project'];
182
+ }
183
+ else { // if there is no project root tag, then it's a multi module/submodules aggregator parent POM
184
+ for (let proj of effectivePomStruct['projects']['project']) {
185
+ // need to choose the aggregate POM and not one of the modules.
186
+ if (proj.packaging && proj.packaging === 'pom') {
187
+ pomRoot = proj;
188
+ }
189
+ }
190
+ }
191
+ /** @type Dependency */
192
+ let rootDependency = {
193
+ groupId: pomRoot['groupId'],
194
+ artifactId: pomRoot['artifactId'],
195
+ version: pomRoot['version'],
196
+ scope: '*',
197
+ ignore: false
198
+ };
199
+ return rootDependency;
200
+ }
201
+ /**
202
+ * Get a list of dependencies with marking of dependencies commented with <!--exhortignore-->.
203
+ * @param {string} manifest - path for pom.xml
204
+ * @returns {[Dependency]} an array of dependencies
205
+ * @private
206
+ */
207
+ #getDependencies(manifest) {
208
+ /** @type [Dependency] */
209
+ let ignored = [];
210
+ // build xml parser with options
211
+ let parser = new XMLParser({
212
+ commentPropName: '#comment',
213
+ isArray: (_, jpath) => 'project.dependencies.dependency' === jpath,
214
+ parseTagValue: false
215
+ });
216
+ // read manifest pom.xml file into buffer
217
+ let buf = fs.readFileSync(manifest);
218
+ // parse manifest pom.xml to json
219
+ let pomJson = parser.parse(buf.toString());
220
+ // iterate over all dependencies and chery pick dependencies with a exhortignore comment
221
+ let pomXml;
222
+ // project without modules
223
+ if (pomJson['project']) {
224
+ if (pomJson['project']['dependencies'] !== undefined) {
225
+ pomXml = pomJson['project']['dependencies']['dependency'];
226
+ }
227
+ else {
228
+ pomXml = [];
229
+ }
230
+ }
231
+ else { // project with modules
232
+ pomXml = pomJson['projects']['project'].filter(project => project.dependencies !== undefined).flatMap(project => project.dependencies.dependency);
233
+ }
234
+ pomXml.forEach(dep => {
235
+ let ignore = false;
236
+ if (dep['#comment'] && dep['#comment'].includes('exhortignore')) { // #comment is an array or a string
237
+ ignore = true;
238
+ }
239
+ if (dep['scope'] !== 'test') {
240
+ ignored.push({
241
+ groupId: dep['groupId'],
242
+ artifactId: dep['artifactId'],
243
+ version: dep['version'] ? dep['version'].toString() : '*',
244
+ scope: '*',
245
+ ignore: ignore
246
+ });
247
+ }
248
+ });
249
+ // return list of dependencies
250
+ return ignored;
251
+ }
252
+ /**
253
+ * Utility function for looking up a dependency in a list of dependencies ignoring the "ignored"
254
+ * field
255
+ * @param dep {Dependency} dependency to look for
256
+ * @param deps {[Dependency]} list of dependencies to look in
257
+ * @returns boolean true if found dep in deps
258
+ * @private
259
+ */
260
+ #dependencyIn(dep, deps) {
261
+ return deps.filter(d => dep.artifactId === d.artifactId && dep.groupId === d.groupId && dep.scope === d.scope).length > 0;
262
+ }
263
+ }
@@ -0,0 +1,4 @@
1
+ export default class Javascript_npm extends Base_javascript {
2
+ _listCmdArgs(includeTransitive: any): string[];
3
+ }
4
+ import Base_javascript from './base_javascript.js';
@@ -0,0 +1,15 @@
1
+ import Base_javascript from './base_javascript.js';
2
+ export default class Javascript_npm extends Base_javascript {
3
+ _lockFileName() {
4
+ return "package-lock.json";
5
+ }
6
+ _cmdName() {
7
+ return "npm";
8
+ }
9
+ _listCmdArgs(includeTransitive) {
10
+ return ['ls', includeTransitive ? '--all' : '--depth=0', '--package-lock-only', '--omit=dev', '--json'];
11
+ }
12
+ _updateLockFileCmdArgs() {
13
+ return ['install', '--package-lock-only'];
14
+ }
15
+ }
@@ -0,0 +1,5 @@
1
+ export default class Javascript_pnpm extends Base_javascript {
2
+ _listCmdArgs(includeTransitive: any): string[];
3
+ _buildDependencyTree(includeTransitive: any, manifest: any): any;
4
+ }
5
+ import Base_javascript from './base_javascript.js';
@@ -0,0 +1,22 @@
1
+ import Base_javascript from './base_javascript.js';
2
+ export default class Javascript_pnpm extends Base_javascript {
3
+ _lockFileName() {
4
+ return "pnpm-lock.yaml";
5
+ }
6
+ _cmdName() {
7
+ return "pnpm";
8
+ }
9
+ _listCmdArgs(includeTransitive) {
10
+ return ['ls', includeTransitive ? '--depth=Infinity' : '--depth=0', '--prod', '--json'];
11
+ }
12
+ _updateLockFileCmdArgs() {
13
+ return ['install', '--frozen-lockfile'];
14
+ }
15
+ _buildDependencyTree(includeTransitive, manifest) {
16
+ const tree = super._buildDependencyTree(includeTransitive, manifest);
17
+ if (Array.isArray(tree) && tree.length > 0) {
18
+ return tree[0];
19
+ }
20
+ return {};
21
+ }
22
+ }
@@ -0,0 +1,11 @@
1
+ export default class Javascript_yarn extends Base_javascript {
2
+ static VERSION_PATTERN: RegExp;
3
+ _listCmdArgs(includeTransitive: any, manifestDir: any): any;
4
+ _updateLockFileCmdArgs(manifestDir: any): any;
5
+ _setUp(manifestPath: any, opts: any): void;
6
+ _getRootDependencies(depTree: any): any;
7
+ _parseDepTreeOutput(output: any): any;
8
+ _addDependenciesToSbom(sbom: any, depTree: any): void;
9
+ #private;
10
+ }
11
+ import Base_javascript from './base_javascript.js';
@@ -0,0 +1,39 @@
1
+ import Base_javascript from './base_javascript.js';
2
+ import Yarn_berry_processor from './processors/yarn_berry_processor.js';
3
+ import Yarn_classic_processor from './processors/yarn_classic_processor.js';
4
+ export default class Javascript_yarn extends Base_javascript {
5
+ static VERSION_PATTERN = /^([0-9]+)\./;
6
+ #processor;
7
+ _lockFileName() {
8
+ return "yarn.lock";
9
+ }
10
+ _cmdName() {
11
+ return "yarn";
12
+ }
13
+ _listCmdArgs(includeTransitive, manifestDir) {
14
+ return this.#processor.listCmdArgs(includeTransitive, manifestDir);
15
+ }
16
+ _updateLockFileCmdArgs(manifestDir) {
17
+ return this.#processor.updateLockFileCmdArgs(manifestDir);
18
+ }
19
+ _setUp(manifestPath, opts) {
20
+ super._setUp(manifestPath, opts);
21
+ const version = this._version() ?? '';
22
+ const matches = Javascript_yarn.VERSION_PATTERN.exec(version);
23
+ if (matches?.length !== 2) {
24
+ throw new Error(`Invalid Yarn version format: ${version}`);
25
+ }
26
+ const isClassic = matches[1] === '1';
27
+ this._setEcosystem(isClassic ? 'yarn-classic' : 'yarn-berry');
28
+ this.#processor = isClassic ? new Yarn_classic_processor(this._getManifest()) : new Yarn_berry_processor(this._getManifest());
29
+ }
30
+ _getRootDependencies(depTree) {
31
+ return this.#processor.getRootDependencies(depTree);
32
+ }
33
+ _parseDepTreeOutput(output) {
34
+ return this.#processor.parseDepTreeOutput(output);
35
+ }
36
+ _addDependenciesToSbom(sbom, depTree) {
37
+ this.#processor.addDependenciesToSbom(sbom, depTree);
38
+ }
39
+ }
@@ -0,0 +1,11 @@
1
+ export default class Manifest {
2
+ constructor(manifestPath: any);
3
+ manifestPath: any;
4
+ dependencies: any[];
5
+ name: any;
6
+ version: any;
7
+ ignored: any[];
8
+ loadManifest(): any;
9
+ loadDependencies(content: any): any[];
10
+ loadIgnored(content: any): any[];
11
+ }
@@ -0,0 +1,48 @@
1
+ import fs from "fs";
2
+ import { toPurl } from "../tools.js";
3
+ const DEFAULT_VERSION = 'v0.0.0';
4
+ export default class Manifest {
5
+ constructor(manifestPath) {
6
+ if (!manifestPath) {
7
+ throw new Error("Missing required manifest path");
8
+ }
9
+ this.manifestPath = manifestPath;
10
+ const content = this.loadManifest();
11
+ this.dependencies = this.loadDependencies(content);
12
+ this.name = content.name;
13
+ this.version = content.version || DEFAULT_VERSION;
14
+ this.ignored = this.loadIgnored(content);
15
+ }
16
+ loadManifest() {
17
+ try {
18
+ let manifest = JSON.parse(fs.readFileSync(this.manifestPath, 'utf-8'));
19
+ return manifest;
20
+ }
21
+ catch (err) {
22
+ if (err.code === 'ENOENT') {
23
+ throw new Error("Missing manifest file: " + this.manifestPath, { cause: err });
24
+ }
25
+ throw new Error("Unable to parse manifest: " + this.manifestPath, { cause: err });
26
+ }
27
+ }
28
+ loadDependencies(content) {
29
+ let deps = [];
30
+ if (!content.dependencies) {
31
+ return deps;
32
+ }
33
+ for (let dep in content.dependencies) {
34
+ deps.push(dep);
35
+ }
36
+ return deps;
37
+ }
38
+ loadIgnored(content) {
39
+ let deps = [];
40
+ if (!content.exhortignore) {
41
+ return deps;
42
+ }
43
+ for (let i = 0; i < content.exhortignore.length; i++) {
44
+ deps.push(toPurl("npm", content.exhortignore[i]));
45
+ }
46
+ return deps;
47
+ }
48
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Processor for Yarn Berry package manager
3
+ * Handles parsing and processing of dependencies for Yarn Berry projects
4
+ */
5
+ export default class Yarn_berry_processor extends Yarn_processor {
6
+ static LOCATOR_PATTERN: RegExp;
7
+ static VIRTUAL_LOCATOR_PATTERN: RegExp;
8
+ /**
9
+ * Returns the command arguments for listing dependencies
10
+ * @param {boolean} includeTransitive - Whether to include transitive dependencies
11
+ * @returns {string[]} Command arguments for listing dependencies
12
+ */
13
+ listCmdArgs(includeTransitive: boolean): string[];
14
+ /**
15
+ * Returns the command arguments for updating the lock file
16
+ * @param {string} - Directory containing the manifest file
17
+ * @returns {string[]} Command arguments for updating the lock file
18
+ */
19
+ updateLockFileCmdArgs(): string[];
20
+ /**
21
+ * Parses the dependency tree output from Yarn Berry
22
+ * Converts multiple JSON objects into a valid JSON array
23
+ * @param {string} output - The raw command output
24
+ * @returns {string} Properly formatted JSON string
25
+ */
26
+ parseDepTreeOutput(output: string): string;
27
+ /**
28
+ * Extracts root dependencies from the dependency tree
29
+ * @param {Object} depTree - The dependency tree object
30
+ * @returns {Map<string, PackageURL>} Map of dependency names to their PackageURL objects
31
+ */
32
+ getRootDependencies(depTree: any): Map<string, PackageURL>;
33
+ /**
34
+ * Adds dependencies to the SBOM
35
+ * @param {Sbom} sbom - The SBOM object to add dependencies to
36
+ * @param {Object} depTree - The dependency tree object
37
+ */
38
+ addDependenciesToSbom(sbom: Sbom, depTree: any): void;
39
+ #private;
40
+ }
41
+ import Yarn_processor from "./yarn_processor.js";
@@ -0,0 +1,130 @@
1
+ import { EOL } from 'os';
2
+ import { toPurl, toPurlFromString } from "../../tools.js";
3
+ import { purlType } from "../base_javascript.js";
4
+ import Yarn_processor from "./yarn_processor.js";
5
+ /**
6
+ * Processor for Yarn Berry package manager
7
+ * Handles parsing and processing of dependencies for Yarn Berry projects
8
+ */
9
+ export default class Yarn_berry_processor extends Yarn_processor {
10
+ static LOCATOR_PATTERN = /^(@?[^@]+(?:\/[^@]+)?)@npm:(.+)$/;
11
+ static VIRTUAL_LOCATOR_PATTERN = /^(@?[^@]+(?:\/[^@]+)?)@virtual:[^#]+#npm:(.+)$/;
12
+ /**
13
+ * Returns the command arguments for listing dependencies
14
+ * @param {boolean} includeTransitive - Whether to include transitive dependencies
15
+ * @returns {string[]} Command arguments for listing dependencies
16
+ */
17
+ listCmdArgs(includeTransitive) {
18
+ return ['info', includeTransitive ? '--recursive' : '--all', '--json'];
19
+ }
20
+ /**
21
+ * Returns the command arguments for updating the lock file
22
+ * @param {string} - Directory containing the manifest file
23
+ * @returns {string[]} Command arguments for updating the lock file
24
+ */
25
+ updateLockFileCmdArgs() {
26
+ return ['install', '--immutable'];
27
+ }
28
+ /**
29
+ * Parses the dependency tree output from Yarn Berry
30
+ * Converts multiple JSON objects into a valid JSON array
31
+ * @param {string} output - The raw command output
32
+ * @returns {string} Properly formatted JSON string
33
+ */
34
+ parseDepTreeOutput(output) {
35
+ // Normalize line endings to EOL regardless of platform
36
+ const normalizedOutput = output.replace(/\r\n|\n/g, EOL);
37
+ const lines = normalizedOutput.split(EOL).filter(line => line.trim());
38
+ // Transform multiline JSON objects into a valid JSON array
39
+ const outputArray = lines.join('').replaceAll('}{', '},{');
40
+ return `[${outputArray}]`;
41
+ }
42
+ /**
43
+ * Extracts root dependencies from the dependency tree
44
+ * @param {Object} depTree - The dependency tree object
45
+ * @returns {Map<string, PackageURL>} Map of dependency names to their PackageURL objects
46
+ */
47
+ getRootDependencies(depTree) {
48
+ if (!depTree) {
49
+ return new Map();
50
+ }
51
+ return new Map(depTree.filter(dep => !this.#isRoot(dep.value)).map(dep => {
52
+ const depName = dep.value;
53
+ const idx = depName.lastIndexOf('@');
54
+ const name = depName.substring(0, idx);
55
+ const version = dep.children.Version;
56
+ return [name, toPurl(purlType, name, version)];
57
+ }));
58
+ }
59
+ /**
60
+ * Checks if a dependency is the root package
61
+ * @param {string} name - Name of the dependency
62
+ * @returns {boolean} True if the dependency is the root package
63
+ * @private
64
+ */
65
+ #isRoot(name) {
66
+ if (!name) {
67
+ return false;
68
+ }
69
+ return name.endsWith("@workspace:.");
70
+ }
71
+ /**
72
+ * Adds dependencies to the SBOM
73
+ * @param {Sbom} sbom - The SBOM object to add dependencies to
74
+ * @param {Object} depTree - The dependency tree object
75
+ */
76
+ addDependenciesToSbom(sbom, depTree) {
77
+ if (!depTree) {
78
+ return;
79
+ }
80
+ depTree.forEach(n => {
81
+ const depName = n.value;
82
+ const from = this.#isRoot(depName) ? toPurlFromString(sbom.getRoot().purl) : this.#purlFromNode(depName, n);
83
+ const deps = n.children?.Dependencies;
84
+ if (!deps) {
85
+ return;
86
+ }
87
+ deps.forEach(d => {
88
+ const to = this.#purlFromLocator(d.locator);
89
+ if (to) {
90
+ sbom.addDependency(from, to);
91
+ }
92
+ });
93
+ });
94
+ }
95
+ /**
96
+ * Creates a PackageURL from a dependency locator
97
+ * @param {string} locator - The dependency locator
98
+ * @returns {PackageURL|undefined} The PackageURL or undefined if not valid
99
+ * @private
100
+ */
101
+ #purlFromLocator(locator) {
102
+ if (!locator) {
103
+ return undefined;
104
+ }
105
+ const matches = Yarn_berry_processor.LOCATOR_PATTERN.exec(locator);
106
+ if (matches) {
107
+ return toPurl(purlType, matches[1], matches[2]);
108
+ }
109
+ const virtualMatches = Yarn_berry_processor.VIRTUAL_LOCATOR_PATTERN.exec(locator);
110
+ if (virtualMatches) {
111
+ return toPurl(purlType, virtualMatches[1], virtualMatches[2]);
112
+ }
113
+ return undefined;
114
+ }
115
+ /**
116
+ * Creates a PackageURL from a dependency node
117
+ * @param {string} depName - The dependency name
118
+ * @param {Object} node - The dependency node object
119
+ * @returns {PackageURL|undefined} The PackageURL or undefined if not valid
120
+ * @private
121
+ */
122
+ #purlFromNode(depName, node) {
123
+ if (!node?.children?.Version) {
124
+ return undefined;
125
+ }
126
+ const name = depName.substring(0, depName.lastIndexOf('@'));
127
+ const version = node.children.Version;
128
+ return toPurl(purlType, name, version);
129
+ }
130
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Processor for Yarn Classic package manager
3
+ * Handles parsing and processing of dependencies for Yarn Classic projects
4
+ */
5
+ export default class Yarn_classic_processor extends Yarn_processor {
6
+ /**
7
+ * Returns the command arguments for listing dependencies
8
+ * @param {boolean} includeTransitive - Whether to include transitive dependencies
9
+ * @returns {string[]} Command arguments for listing dependencies
10
+ */
11
+ listCmdArgs(includeTransitive: boolean): string[];
12
+ /**
13
+ * Returns the command arguments for updating the lock file
14
+ * @returns {string[]} Command arguments for updating the lock file
15
+ */
16
+ updateLockFileCmdArgs(): string[];
17
+ /**
18
+ * Parses the dependency tree output from Yarn Classic
19
+ * @param {string} output - The raw command output
20
+ * @returns {string} Unchanged output as it's already in JSON format
21
+ */
22
+ parseDepTreeOutput(output: string): string;
23
+ /**
24
+ * Extracts root dependencies from the dependency tree
25
+ * @param {Object} depTree - The dependency tree object
26
+ * @returns {Map<string, PackageURL>} Map of dependency names to their PackageURL objects
27
+ */
28
+ getRootDependencies(depTree: any): Map<string, PackageURL>;
29
+ /**
30
+ * Adds dependencies to the SBOM
31
+ * @param {Sbom} sbom - The SBOM object to add dependencies to
32
+ * @param {Object} depTree - The dependency tree object
33
+ */
34
+ addDependenciesToSbom(sbom: Sbom, depTree: any): void;
35
+ #private;
36
+ }
37
+ import Yarn_processor from "./yarn_processor.js";