Skip to content

Commit 889c79f

Browse files
committed
feat: use custom path for npm/pnpm
Signed-off-by: Ruben Romero Montes <[email protected]>
1 parent 8a7e65d commit 889c79f

File tree

1 file changed

+159
-156
lines changed

1 file changed

+159
-156
lines changed

src/providers/base_javascript.js

Lines changed: 159 additions & 156 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import os from "node:os";
33
import path from 'node:path'
44
import { PackageURL } from 'packageurl-js'
55

6-
import { invokeCommand } from "../tools.js";
6+
import { getCustomPath, invokeCommand } from "../tools.js";
77
import Sbom from '../sbom.js'
88

99
/** @typedef {import('../provider.js').Provider} */
@@ -15,197 +15,200 @@ const defaultVersion = 'v0.0.0'
1515

1616
export default class Base_javascript {
1717

18-
/**
18+
// Resolved cmd to use
19+
#cmd;
20+
21+
/**
1922
* @returns {string} the name of the lock file name for the specific implementation
2023
*/
21-
_lockFileName() {
22-
throw new TypeError("_lockFileName must be implemented");
23-
}
24+
_lockFileName() {
25+
throw new TypeError("_lockFileName must be implemented");
26+
}
2427

25-
/**
28+
/**
2629
* @returns {string} the command name to use for the specific JS package manager
2730
*/
28-
_cmdName() {
29-
throw new TypeError("_cmdName must be implemented");
30-
}
31-
32-
/**
33-
* @returns {Array<string>}
34-
*/
35-
_listCmdArgs() {
36-
throw new TypeError("_listCmdArgs must be implemented");
37-
}
38-
39-
/**
40-
* @returns {Array<string>}
41-
*/
42-
_updateLockFileCmdArgs() {
43-
throw new TypeError("_updateLockFileCmdArgs must be implemented");
44-
}
45-
46-
/**
31+
_cmdName() {
32+
throw new TypeError("_cmdName must be implemented");
33+
}
34+
35+
/**
36+
* @returns {Array<string>}
37+
*/
38+
_listCmdArgs() {
39+
throw new TypeError("_listCmdArgs must be implemented");
40+
}
41+
42+
/**
43+
* @returns {Array<string>}
44+
*/
45+
_updateLockFileCmdArgs() {
46+
throw new TypeError("_updateLockFileCmdArgs must be implemented");
47+
}
48+
49+
/**
4750
* @param {string} manifestName - the subject manifest name-type
4851
* @returns {boolean} - return true if `pom.xml` is the manifest name-type
4952
*/
50-
isSupported(manifestName) {
51-
return 'package.json' === manifestName;
52-
}
53+
isSupported(manifestName) {
54+
return 'package.json' === manifestName;
55+
}
5356

54-
/**
57+
/**
5558
* Checks if a required lock file exists in the same path as the manifest
5659
*
5760
* @param {string} manifestDir - The base directory where the manifest is located
5861
* @returns {boolean} - True if the lock file exists
5962
*/
60-
validateLockFile(manifestDir) {
61-
const lock = path.join(manifestDir, this._lockFileName());
62-
return fs.existsSync(lock);
63-
}
63+
validateLockFile(manifestDir) {
64+
const lock = path.join(manifestDir, this._lockFileName());
65+
return fs.existsSync(lock);
66+
}
6467

65-
/**
68+
/**
6669
* Provide content and content type for maven-maven stack analysis.
6770
* @param {string} manifest - the manifest path or name
6871
* @param {{}} [opts={}] - optional various options to pass along the application
6972
* @returns {Provided}
7073
*/
71-
provideStack(manifest, opts = {}) {
72-
return {
73-
ecosystem,
74-
content: this.#getSBOM(manifest, opts, true),
75-
contentType: 'application/vnd.cyclonedx+json'
76-
}
77-
}
78-
79-
/**
74+
provideStack(manifest, opts = {}) {
75+
return {
76+
ecosystem,
77+
content: this.#getSBOM(manifest, opts, true),
78+
contentType: 'application/vnd.cyclonedx+json'
79+
}
80+
}
81+
82+
/**
8083
* Provide content and content type for maven-maven component analysis.
8184
* @param {string} manifest - path to pom.xml for component report
8285
* @param {{}} [opts={}] - optional various options to pass along the application
8386
* @returns {Provided}
8487
*/
85-
provideComponent(manifest, opts = {}) {
86-
return {
87-
ecosystem,
88-
content: this.#getSBOM(manifest, opts, false),
89-
contentType: 'application/vnd.cyclonedx+json'
90-
}
91-
}
92-
93-
/**
94-
* Utility function for creating Purl String
95-
96-
* @param name the name of the artifact, can include a namespace(group) or not - namespace/artifactName.
97-
* @param version the version of the artifact
98-
* @returns {PackageURL|null} PackageUrl Object ready to be used in SBOM
99-
*/
100-
#toPurl(name, version) {
101-
let parts = name.split("/");
102-
var purlNs, purlName;
103-
if (parts.length === 2) {
104-
purlNs = parts[0];
105-
purlName = parts[1];
106-
} else {
107-
purlName = parts[0];
108-
}
109-
return new PackageURL('npm', purlNs, purlName, version, undefined, undefined);
110-
}
111-
112-
_buildDependencyTree(includeTransitive, manifest) {
113-
this.#version();
114-
let manifestDir = path.dirname(manifest)
115-
this.#createLockFile(manifestDir);
116-
117-
let npmOutput = this.#executeListCmd(includeTransitive, manifestDir);
118-
return JSON.parse(npmOutput);
119-
}
120-
121-
/**
88+
provideComponent(manifest, opts = {}) {
89+
return {
90+
ecosystem,
91+
content: this.#getSBOM(manifest, opts, false),
92+
contentType: 'application/vnd.cyclonedx+json'
93+
}
94+
}
95+
96+
/**
97+
* Utility function for creating Purl String
98+
* @param name the name of the artifact, can include a namespace(group) or not - namespace/artifactName.
99+
* @param version the version of the artifact
100+
* @returns {PackageURL|null} PackageUrl Object ready to be used in SBOM
101+
*/
102+
#toPurl(name, version) {
103+
let parts = name.split("/");
104+
var purlNs, purlName;
105+
if (parts.length === 2) {
106+
purlNs = parts[0];
107+
purlName = parts[1];
108+
} else {
109+
purlName = parts[0];
110+
}
111+
return new PackageURL('npm', purlNs, purlName, version, undefined, undefined);
112+
}
113+
114+
_buildDependencyTree(includeTransitive, manifest) {
115+
this.#version();
116+
let manifestDir = path.dirname(manifest)
117+
this.#createLockFile(manifestDir);
118+
119+
let npmOutput = this.#executeListCmd(includeTransitive, manifestDir);
120+
return JSON.parse(npmOutput);
121+
}
122+
123+
/**
122124
* Create SBOM json string for npm Package.
123125
* @param {string} manifest - path for package.json
124126
* @param {{}} [opts={}] - optional various options to pass along the application
125127
* @returns {string} the SBOM json content
126128
* @private
127129
*/
128-
#getSBOM(manifest, opts = {}, includeTransitive) {
129-
const depsObject = this._buildDependencyTree(includeTransitive, manifest);
130-
let rootName = depsObject["name"]
131-
let rootVersion = depsObject["version"]
132-
if (!rootVersion) {
133-
rootVersion = defaultVersion
134-
}
135-
let mainComponent = this.#toPurl(rootName, rootVersion);
136-
137-
let sbom = new Sbom();
138-
sbom.addRoot(mainComponent)
139-
140-
let dependencies = depsObject["dependencies"] || {};
141-
this.#addAllDependencies(sbom, sbom.getRoot(), dependencies)
142-
let packageJson = fs.readFileSync(manifest).toString()
143-
let packageJsonObject = JSON.parse(packageJson);
144-
if (packageJsonObject.exhortignore !== undefined) {
145-
let ignoredDeps = Array.from(packageJsonObject.exhortignore);
146-
sbom.filterIgnoredDeps(ignoredDeps)
147-
}
148-
return sbom.getAsJsonString(opts)
149-
}
150-
151-
/**
130+
#getSBOM(manifest, opts = {}, includeTransitive) {
131+
this.#cmd = getCustomPath(this._cmdName(), opts);
132+
const depsObject = this._buildDependencyTree(includeTransitive, manifest, opts);
133+
let rootName = depsObject["name"]
134+
let rootVersion = depsObject["version"]
135+
if (!rootVersion) {
136+
rootVersion = defaultVersion
137+
}
138+
let mainComponent = this.#toPurl(rootName, rootVersion);
139+
140+
let sbom = new Sbom();
141+
sbom.addRoot(mainComponent)
142+
143+
let dependencies = depsObject["dependencies"] || {};
144+
this.#addAllDependencies(sbom, sbom.getRoot(), dependencies)
145+
let packageJson = fs.readFileSync(manifest).toString()
146+
let packageJsonObject = JSON.parse(packageJson);
147+
if (packageJsonObject.exhortignore !== undefined) {
148+
let ignoredDeps = Array.from(packageJsonObject.exhortignore);
149+
sbom.filterIgnoredDeps(ignoredDeps)
150+
}
151+
return sbom.getAsJsonString(opts)
152+
}
153+
154+
/**
152155
* This function recursively build the Sbom from the JSON that npm listing returns
153156
* @param sbom this is the sbom object
154157
* @param from this is the current component in bom (Should start with root/main component of SBOM) for which we want to add all its dependencies.
155158
* @param dependencies the current dependency list (initially it's the list of the root component)
156159
* @private
157160
*/
158-
#addAllDependencies(sbom, from, dependencies) {
159-
Object.entries(dependencies)
160-
.filter(entry => entry[1].version !== undefined)
161-
.forEach(entry => {
162-
let [name, artifact] = entry;
163-
let purl = this.#toPurl(name, artifact.version);
164-
sbom.addDependency(from, purl)
165-
let transitiveDeps = artifact.dependencies
166-
if (transitiveDeps !== undefined) {
167-
this.#addAllDependencies(sbom, sbom.purlToComponent(purl), transitiveDeps)
168-
}
169-
});
170-
}
171-
172-
#executeListCmd(includeTransitive, manifestDir) {
173-
const listArgs = this._listCmdArgs(includeTransitive, manifestDir);
174-
try {
175-
return invokeCommand(this._cmdName(), listArgs)
176-
} catch (error) {
177-
throw new Error(`failed to list dependencies via "${this._cmdName()} ${listArgs.join(' ')}" - Error: ${error}`, {cause: error});
178-
}
179-
}
180-
181-
#version() {
182-
try {
183-
invokeCommand(this._cmdName(), ['--version'], {stdio: 'ignore'});
184-
} catch (error) {
185-
if (error.code === 'ENOENT') {
186-
throw new Error(`${this._cmdName()} is not accessible`);
187-
}
188-
throw new Error(`failed to check for package manager binary at ${this._cmdName()}`, {cause: error})
189-
}
190-
}
191-
192-
#createLockFile(manifestDir) {
193-
// in windows os, --prefix flag doesn't work, it behaves really weird , instead of installing the package.json fromm the prefix folder,
194-
// it's installing package.json (placed in current working directory of process) into prefix directory, so
195-
let originalDir = process.cwd()
196-
if (os.platform() === 'win32') {
197-
process.chdir(manifestDir)
198-
}
199-
const args = this._updateLockFileCmdArgs(manifestDir);
200-
try {
201-
invokeCommand(this._cmdName(), args)
202-
} catch (error) {
203-
throw new Error(`failed to create lockfile "${args}" - Error: ${error}`, {cause: error});
204-
} finally {
205-
if (os.platform() === 'win32') {
206-
process.chdir(originalDir)
207-
}
208-
}
209-
}
161+
#addAllDependencies(sbom, from, dependencies) {
162+
Object.entries(dependencies)
163+
.filter(entry => entry[1].version !== undefined)
164+
.forEach(entry => {
165+
let [name, artifact] = entry;
166+
let purl = this.#toPurl(name, artifact.version);
167+
sbom.addDependency(from, purl)
168+
let transitiveDeps = artifact.dependencies
169+
if (transitiveDeps !== undefined) {
170+
this.#addAllDependencies(sbom, sbom.purlToComponent(purl), transitiveDeps)
171+
}
172+
});
173+
}
174+
175+
#executeListCmd(includeTransitive, manifestDir) {
176+
const listArgs = this._listCmdArgs(includeTransitive, manifestDir);
177+
return this.#invokeCommand(listArgs);
178+
}
179+
180+
#version() {
181+
this.#invokeCommand(['--version'], { stdio: 'ignore' });
182+
}
183+
184+
#createLockFile(manifestDir) {
185+
// in windows os, --prefix flag doesn't work, it behaves really weird , instead of installing the package.json fromm the prefix folder,
186+
// it's installing package.json (placed in current working directory of process) into prefix directory, so
187+
let originalDir = process.cwd()
188+
if (os.platform() === 'win32') {
189+
process.chdir(manifestDir)
190+
}
191+
const args = this._updateLockFileCmdArgs(manifestDir);
192+
try {
193+
this.#invokeCommand(args);
194+
} catch (error) {
195+
throw new Error(`failed to create lockfile "${args}"`, { cause: error });
196+
} finally {
197+
if (os.platform() === 'win32') {
198+
process.chdir(originalDir)
199+
}
200+
}
201+
}
202+
203+
#invokeCommand(args, opts = {}) {
204+
try {
205+
return invokeCommand(this.#cmd, args, opts);
206+
} catch (error) {
207+
if (error.code === 'ENOENT') {
208+
throw new Error(`${this.#cmd} is not accessible`);
209+
}
210+
throw new Error(`failed to execute ${this.#cmd} ${args}`, { cause: error })
211+
}
212+
}
210213
}
211214

0 commit comments

Comments
 (0)