diff --git a/src/providers/base_javascript.js b/src/providers/base_javascript.js index 9a767c10..50eb38c5 100644 --- a/src/providers/base_javascript.js +++ b/src/providers/base_javascript.js @@ -2,7 +2,7 @@ import fs from 'node:fs' import os from "node:os"; import path from 'node:path' -import { getCustomPath, invokeCommand, toPurl, toPurlFromString } from "../tools.js"; +import { getCustom, getCustomPath, invokeCommand, toPurl, toPurlFromString } from "../tools.js"; import Sbom from '../sbom.js' import Manifest from './manifest.js'; @@ -314,10 +314,56 @@ export default class Base_javascript { if(!opts.cwd) { opts.cwd = path.dirname(this.#manifest.manifestPath); } - return invokeCommand(this.#cmd, args, opts); + + // Add version manager paths for JavaScript package managers + if (process.platform !== 'win32') { + const versionManagerPaths = []; + + // Add fnm path if available + const fnmDir = getCustom('FNM_DIR', null, opts); + if (fnmDir) { + versionManagerPaths.push(`${fnmDir}/current/bin`); + } + + // Add nvm path if available + const nvmDir = getCustom('NVM_DIR', null, opts); + if (nvmDir) { + versionManagerPaths.push(`${nvmDir}/current/bin`); + } + + // Add local node_modules/.bin path + const localBinPath = path.join(opts.cwd, 'node_modules', '.bin'); + if (fs.existsSync(localBinPath)) { + versionManagerPaths.push(localBinPath); + } + + if (versionManagerPaths.length > 0) { + opts = { + ...opts, + env: { + ...opts.env, + PATH: `${versionManagerPaths.join(path.delimiter)}${path.delimiter}${process.env.PATH}` + } + }; + } + } + + // Try to find the command in the following order: + // 1. Custom path from environment/opts (via getCustomPath) + // 2. Local node_modules/.bin + // 3. Global installation + let cmd = this.#cmd; + if (!fs.existsSync(cmd)) { + const localCmd = path.join(opts.cwd, 'node_modules', '.bin', this._cmdName()); + if (fs.existsSync(localCmd)) { + cmd = localCmd; + } + } + + return invokeCommand(cmd, args, opts); } catch (error) { if (error.code === 'ENOENT') { - throw new Error(`${this.#cmd} is not accessible.`); + throw new Error(`${this.#cmd} is not accessible. Please ensure it is installed via npm, corepack, or your version manager.`); } if (error.code === 'EACCES') { throw new Error(`Permission denied when executing ${this.#cmd}. Please check file permissions.`); diff --git a/src/tools.js b/src/tools.js index 207223f7..e6a77bb6 100644 --- a/src/tools.js +++ b/src/tools.js @@ -1,5 +1,4 @@ import { EOL } from "os"; -import os from 'os'; import { execFileSync } from "child_process"; import { PackageURL } from "packageurl-js"; @@ -75,7 +74,7 @@ export function environmentVariableIsPopulated(envVariableName) { } /** - * + * Utility function for handling spaces in paths on Windows * @param {string} path - path to be checked if contains spaces * @return {string} a path with all spaces escaped or manipulated so it will be able to be part * of commands that will be invoked without errors in os' shell. @@ -83,16 +82,8 @@ export function environmentVariableIsPopulated(envVariableName) { export function handleSpacesInPath(path) { let transformedPath = path // if operating system is windows - if (os.platform() === "win32") { - if(hasSpaces(path)) { - transformedPath = `"${path}"` - } - } - // linux, darwin.. - else { - if(hasSpaces(path)) { - transformedPath = path.replaceAll(" ", "\\ ") - } + if(hasSpaces(path)) { + transformedPath = `"${path}"` } return transformedPath } @@ -165,5 +156,20 @@ export function invokeCommand(bin, args, opts={}) { bin = handleSpacesInPath(bin) } + opts = { + ...opts, + env: { + ...process.env, + PATH: process.env.PATH + } + }; + + + // Add maxBuffer option to handle large outputs + opts = { + ...opts, + maxBuffer: 10 * 1024 * 1024 // 10MB buffer + }; + return execFileSync(bin, args, {...{stdio: 'pipe', encoding: 'utf-8'}, ...opts}) } diff --git a/test/tools.test.js b/test/tools.test.js index b6088d57..119131c6 100644 --- a/test/tools.test.js +++ b/test/tools.test.js @@ -68,7 +68,6 @@ suite('testing the various tools and utility functions', () => { test('Windows Path with spaces', async () => { const tools = await mockToolsPartial("win32") - let path = "c:\\users\\john doe\\pom.xml" let expectedPath = "\"c:\\users\\john doe\\pom.xml\"" let actualPath = tools.handleSpacesInPath(path) @@ -83,22 +82,6 @@ suite('testing the various tools and utility functions', () => { expect(actualPath).to.equal(expectedPath) }) - test('Linux Path with spaces', async () => { - const tools = await mockToolsPartial("linux") - let path = "/usr/john doe/pom.xml" - let expectedPath = "/usr/john\\ doe/pom.xml" - let actualPath = tools.handleSpacesInPath(path) - expect(actualPath).to.equal(expectedPath) - }) - - test('Linux Path with no spaces', async () => { - const tools = await mockToolsPartial("linux") - let path = "/usr/john/pom.xml" - let expectedPath = "/usr/john/pom.xml" - let actualPath = tools.handleSpacesInPath(path) - expect(actualPath).to.equal(expectedPath) - }) - })