diff --git a/deno.json b/deno.json index 569055f7e..8572bde4e 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,6 @@ { "name": "@cyclonedx/cdxgen", - "version": "10.10.3", + "version": "10.10.4", "exports": "./lib/cli/index.js", "compilerOptions": { "allowJs": true, diff --git a/jsr.json b/jsr.json index 884908093..2d615726b 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@cyclonedx/cdxgen", - "version": "10.10.3", + "version": "10.10.4", "exports": "./lib/cli/index.js", "include": ["*.js", "lib/**", "bin/**", "data/**", "types/**"], "exclude": [ diff --git a/lib/helpers/utils.js b/lib/helpers/utils.js index a032e4ed3..203d9e855 100644 --- a/lib/helpers/utils.js +++ b/lib/helpers/utils.js @@ -5517,7 +5517,7 @@ export async function parseGemfileLockData(gemLockData, lockFile) { const dependenciesMap = {}; const pkgVersionMap = {}; const pkgVersionPlatformMap = {}; - const rootList = []; + const pkgNameRef = {}; if (!gemLockData) { return pkgList; } @@ -5577,6 +5577,9 @@ export async function parseGemfileLockData(gemLockData, lockFile) { let lastBranch = undefined; let lastTag = undefined; let lastParentPlatform = undefined; + // Dependencies block would begin with DEPENDENCIES + let dependenciesBlock = false; + const rootList = []; // In the second pass, we use the space in the prefix to figure out the dependency tree gemLockData.split("\n").forEach((l) => { l = l.replace("\r", ""); @@ -5596,6 +5599,11 @@ export async function parseGemfileLockData(gemLockData, lockFile) { lastTag = l.trim().split(" ")[1]; } if (l.trim() === l.trim().toUpperCase()) { + if (l.trim() === "DEPENDENCIES") { + dependenciesBlock = true; + return; + } + dependenciesBlock = false; specsFound = false; lastRemote = undefined; lastRevision = undefined; @@ -5670,7 +5678,6 @@ export async function parseGemfileLockData(gemLockData, lockFile) { const bomRef = decodeURIComponent(purlString); if (level === 1) { lastParent = bomRef; - rootList.push(bomRef); } const properties = [ { @@ -5737,11 +5744,17 @@ export async function parseGemfileLockData(gemLockData, lockFile) { if (!dependenciesMap[bomRef]) { dependenciesMap[bomRef] = new Set(); } + pkgNameRef[name] = bomRef; // Allow duplicate packages if the version number includes platform if (!pkgnames[purlString]) { pkgList.push(apkg); pkgnames[purlString] = true; } + } else if (dependenciesBlock) { + const rootDepName = l.trim().split(" ")[0].replace("!", ""); + if (pkgNameRef[rootDepName]) { + rootList.push(pkgNameRef[rootDepName]); + } } }); for (const k of Object.keys(dependenciesMap)) { @@ -5752,7 +5765,7 @@ export async function parseGemfileLockData(gemLockData, lockFile) { } if (FETCH_LICENSE) { pkgList = await getRubyGemsMetadata(pkgList); - return { pkgList, dependenciesList }; + return { pkgList, dependenciesList, rootList }; } return { pkgList, dependenciesList, rootList }; } diff --git a/lib/helpers/utils.test.js b/lib/helpers/utils.test.js index a8c546cf8..fd450df7f 100644 --- a/lib/helpers/utils.test.js +++ b/lib/helpers/utils.test.js @@ -3863,6 +3863,7 @@ test("parseGemfileLockData", async () => { "./test/data/Gemfile.lock", ); expect(retMap.pkgList.length).toEqual(140); + expect(retMap.rootList.length).toEqual(42); expect(retMap.dependenciesList.length).toEqual(140); expect(retMap.pkgList[0]).toEqual({ name: "actioncable", @@ -3895,30 +3896,35 @@ test("parseGemfileLockData", async () => { "./test/data/Gemfile1.lock", ); expect(retMap.pkgList.length).toEqual(36); + expect(retMap.rootList.length).toEqual(2); expect(retMap.dependenciesList.length).toEqual(36); retMap = await parseGemfileLockData( readFileSync("./test/data/Gemfile2.lock", { encoding: "utf-8" }), "./test/data/Gemfile2.lock", ); expect(retMap.pkgList.length).toEqual(89); + expect(retMap.rootList.length).toEqual(2); expect(retMap.dependenciesList.length).toEqual(89); retMap = await parseGemfileLockData( readFileSync("./test/data/Gemfile4.lock", { encoding: "utf-8" }), "./test/data/Gemfile4.lock", ); expect(retMap.pkgList.length).toEqual(182); + expect(retMap.rootList.length).toEqual(78); expect(retMap.dependenciesList.length).toEqual(182); retMap = await parseGemfileLockData( readFileSync("./test/data/Gemfile5.lock", { encoding: "utf-8" }), "./test/data/Gemfile5.lock", ); expect(retMap.pkgList.length).toEqual(43); + expect(retMap.rootList.length).toEqual(11); expect(retMap.dependenciesList.length).toEqual(43); retMap = await parseGemfileLockData( readFileSync("./test/data/Gemfile6.lock", { encoding: "utf-8" }), "./test/data/Gemfile6.lock", ); expect(retMap.pkgList.length).toEqual(139); + expect(retMap.rootList.length).toEqual(22); expect(retMap.dependenciesList.length).toEqual(139); }); diff --git a/package.json b/package.json index 261feae2e..dcca88326 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cyclonedx/cdxgen", - "version": "10.10.3", + "version": "10.10.4", "description": "Creates CycloneDX Software Bill of Materials (SBOM) from source or container image", "homepage": "http://github.com/cyclonedx/cdxgen", "author": "Prabhu Subramanian ", diff --git a/types/lib/helpers/utils.d.ts b/types/lib/helpers/utils.d.ts index dc4e970fb..b655a2275 100644 --- a/types/lib/helpers/utils.d.ts +++ b/types/lib/helpers/utils.d.ts @@ -584,13 +584,6 @@ export function parseGemspecData(gemspecData: string): Promise; * @param {string} lockFile Lock file */ export function parseGemfileLockData(gemLockData: object, lockFile: string): Promise