@cyclonedx/cdxgen 10.0.6 → 10.1.0

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/README.md CHANGED
@@ -350,6 +350,7 @@ cdxgen can retain the dependency tree under the `dependencies` attribute for a s
350
350
  - .NET (packages.lock.json, project.assets.json, paket.lock)
351
351
  - Go (go.mod)
352
352
  - PHP (composer.lock)
353
+ - Ruby (Gemfile.lock)
353
354
 
354
355
  ## Environment variables
355
356
 
package/bin/cdxgen.js CHANGED
@@ -404,7 +404,15 @@ const checkPermissions = (filePath) => {
404
404
  fs.writeFileSync(jsonFile, bomNSData.bomJson);
405
405
  jsonPayload = bomNSData.bomJson;
406
406
  } else {
407
- jsonPayload = JSON.stringify(bomNSData.bomJson, null, 2);
407
+ jsonPayload = JSON.stringify(
408
+ bomNSData.bomJson,
409
+ null,
410
+ options.deep ||
411
+ ["os", "docker", "universal"].includes(options.projectType) ||
412
+ process.env.CI
413
+ ? null
414
+ : 2
415
+ );
408
416
  fs.writeFileSync(jsonFile, jsonPayload);
409
417
  }
410
418
  if (
@@ -499,7 +507,7 @@ const checkPermissions = (filePath) => {
499
507
  bomJsonUnsignedObj.signature = signatureBlock;
500
508
  fs.writeFileSync(
501
509
  jsonFile,
502
- JSON.stringify(bomJsonUnsignedObj, null, 2)
510
+ JSON.stringify(bomJsonUnsignedObj, null, null)
503
511
  );
504
512
  if (publicKeyFile) {
505
513
  // Verifying this signature
package/bin/repl.js CHANGED
@@ -284,7 +284,7 @@ cdxgenRepl.defineCommand("save", {
284
284
  if (!saveToFile) {
285
285
  saveToFile = "bom.json";
286
286
  }
287
- fs.writeFileSync(saveToFile, JSON.stringify(sbom, null, 2));
287
+ fs.writeFileSync(saveToFile, JSON.stringify(sbom, null, null));
288
288
  console.log(`BOM saved successfully to ${saveToFile}`);
289
289
  } else {
290
290
  console.log(
package/docker.js CHANGED
@@ -1194,7 +1194,7 @@ export const getCredsFromHelper = (exeSuffix, serverAddress) => {
1194
1194
  export const addSkippedSrcFiles = (skippedImageSrcs, components) => {
1195
1195
  for (const skippedImage of skippedImageSrcs) {
1196
1196
  for (const co of components) {
1197
- let srcFileValues = [];
1197
+ const srcFileValues = [];
1198
1198
  let srcImageValue;
1199
1199
  co.properties.forEach(function (property) {
1200
1200
  if (property.name === "oci:SrcImage") {
package/evinser.js CHANGED
@@ -132,7 +132,7 @@ export const catalogMavenDeps = async (
132
132
  namespaces: jarNSMapping[purl].namespaces
133
133
  },
134
134
  null,
135
- 2
135
+ null
136
136
  )
137
137
  }
138
138
  });
@@ -165,7 +165,7 @@ export const catalogGradleDeps = async (dirPath, purlsJars, Namespaces) => {
165
165
  namespaces: jarNSMapping[purl].namespaces
166
166
  },
167
167
  null,
168
- 2
168
+ null
169
169
  )
170
170
  }
171
171
  });
@@ -1066,7 +1066,7 @@ export const createEvinseFile = (sliceArtefacts, options) => {
1066
1066
  // Set the current timestamp to indicate this is newer
1067
1067
  bomJson.metadata.timestamp = new Date().toISOString();
1068
1068
  delete bomJson.signature;
1069
- fs.writeFileSync(evinseOutFile, JSON.stringify(bomJson, null, 2));
1069
+ fs.writeFileSync(evinseOutFile, JSON.stringify(bomJson, null, null));
1070
1070
  if (occEvidencePresent || csEvidencePresent || servicesPresent) {
1071
1071
  console.log(evinseOutFile, "created successfully.");
1072
1072
  } else {
package/index.js CHANGED
@@ -1452,7 +1452,10 @@ export const createJavaBom = async (path, options) => {
1452
1452
  }
1453
1453
  // Should we attempt to resolve class names
1454
1454
  if (options.resolveClass || options.deep) {
1455
- jarNSMapping = await collectJarNS(GRADLE_CACHE_DIR);
1455
+ const tmpjarNSMapping = await collectJarNS(GRADLE_CACHE_DIR);
1456
+ if (tmpjarNSMapping && Object.keys(tmpjarNSMapping).length) {
1457
+ jarNSMapping = { ...jarNSMapping, ...tmpjarNSMapping };
1458
+ }
1456
1459
  }
1457
1460
  pkgList = await getMvnMetadata(pkgList, jarNSMapping);
1458
1461
  return buildBomNSData(options, pkgList, "maven", {
@@ -1749,7 +1752,10 @@ export const createJavaBom = async (path, options) => {
1749
1752
  }
1750
1753
  // Should we attempt to resolve class names
1751
1754
  if (options.resolveClass || options.deep) {
1752
- jarNSMapping = await collectJarNS(SBT_CACHE_DIR);
1755
+ const tmpjarNSMapping = await collectJarNS(SBT_CACHE_DIR);
1756
+ if (tmpjarNSMapping && Object.keys(tmpjarNSMapping).length) {
1757
+ jarNSMapping = { ...jarNSMapping, ...tmpjarNSMapping };
1758
+ }
1753
1759
  }
1754
1760
  pkgList = await getMvnMetadata(pkgList, jarNSMapping);
1755
1761
  return buildBomNSData(options, pkgList, "maven", {
@@ -4145,10 +4151,13 @@ export const createRubyBom = async (path, options) => {
4145
4151
  );
4146
4152
  let gemLockFiles = getAllFiles(
4147
4153
  path,
4148
- (options.multiProject ? "**/" : "") + "Gemfile.lock",
4154
+ (options.multiProject ? "**/" : "") + "Gemfile*.lock",
4149
4155
  options
4150
4156
  );
4151
4157
  let pkgList = [];
4158
+ let dependencies = [];
4159
+ let rootList = [];
4160
+ const parentComponent = createDefaultParentComponent(path, "gem", options);
4152
4161
  const gemFileMode = gemFiles.length;
4153
4162
  const gemLockMode = gemLockFiles.length;
4154
4163
  if (gemFileMode && !gemLockMode && options.installDeps) {
@@ -4170,7 +4179,7 @@ export const createRubyBom = async (path, options) => {
4170
4179
  }
4171
4180
  gemLockFiles = getAllFiles(
4172
4181
  path,
4173
- (options.multiProject ? "**/" : "") + "Gemfile.lock",
4182
+ (options.multiProject ? "**/" : "") + "Gemfile*.lock",
4174
4183
  options
4175
4184
  );
4176
4185
  if (gemLockFiles.length) {
@@ -4179,17 +4188,41 @@ export const createRubyBom = async (path, options) => {
4179
4188
  console.log(`Parsing ${f}`);
4180
4189
  }
4181
4190
  const gemLockData = readFileSync(f, { encoding: "utf-8" });
4182
- const dlist = await parseGemfileLockData(gemLockData);
4183
- if (dlist && dlist.length) {
4184
- pkgList = pkgList.concat(dlist);
4191
+ const retMap = await parseGemfileLockData(gemLockData, f);
4192
+ if (retMap.pkgList && retMap.pkgList.length) {
4193
+ pkgList = pkgList.concat(retMap.pkgList);
4194
+ pkgList = trimComponents(pkgList);
4195
+ }
4196
+ if (retMap.dependenciesList && retMap.dependenciesList.length) {
4197
+ dependencies = mergeDependencies(
4198
+ dependencies,
4199
+ retMap.dependenciesList,
4200
+ parentComponent
4201
+ );
4202
+ }
4203
+ if (retMap.rootList && retMap.rootList.length) {
4204
+ rootList = rootList.concat(retMap.rootList);
4185
4205
  }
4186
4206
  }
4187
- return buildBomNSData(options, pkgList, "gem", {
4188
- src: path,
4189
- filename: gemLockFiles.join(", ")
4190
- });
4191
4207
  }
4192
- return {};
4208
+ if (rootList.length) {
4209
+ dependencies = mergeDependencies(
4210
+ dependencies,
4211
+ [
4212
+ {
4213
+ ref: parentComponent["bom-ref"],
4214
+ dependsOn: rootList
4215
+ }
4216
+ ],
4217
+ parentComponent
4218
+ );
4219
+ }
4220
+ return buildBomNSData(options, pkgList, "gem", {
4221
+ src: path,
4222
+ dependencies,
4223
+ parentComponent,
4224
+ filename: gemLockFiles.join(", ")
4225
+ });
4193
4226
  };
4194
4227
 
4195
4228
  /**
@@ -4674,7 +4707,11 @@ export const createMultiXBom = async (pathList, options) => {
4674
4707
  );
4675
4708
  }
4676
4709
  components = components.concat(bomData.bomJson.components);
4677
- dependencies = dependencies.concat(bomData.bomJson.dependencies);
4710
+ dependencies = mergeDependencies(
4711
+ dependencies,
4712
+ bomData.bomJson.dependencies,
4713
+ bomData.parentComponent
4714
+ );
4678
4715
  if (
4679
4716
  bomData.parentComponent &&
4680
4717
  Object.keys(bomData.parentComponent).length
@@ -5039,7 +5076,7 @@ export const createXBom = async (path, options) => {
5039
5076
  );
5040
5077
  const gemLockFiles = getAllFiles(
5041
5078
  path,
5042
- (options.multiProject ? "**/" : "") + "Gemfile.lock",
5079
+ (options.multiProject ? "**/" : "") + "Gemfile*.lock",
5043
5080
  options
5044
5081
  );
5045
5082
  if (gemFiles.length || gemLockFiles.length) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "10.0.6",
3
+ "version": "10.1.0",
4
4
  "description": "Creates CycloneDX Software Bill of Materials (SBOM) from source or container image",
5
5
  "homepage": "http://github.com/cyclonedx/cdxgen",
6
6
  "author": "Prabhu Subramanian <prabhu@appthreat.com>",
package/server.js CHANGED
@@ -152,7 +152,7 @@ const start = (options) => {
152
152
  ) {
153
153
  res.write(bomNSData.bomJson);
154
154
  } else {
155
- res.write(JSON.stringify(bomNSData.bomJson, null, 2));
155
+ res.write(JSON.stringify(bomNSData.bomJson, null, null));
156
156
  }
157
157
  }
158
158
  if (reqOptions.serverUrl && reqOptions.apiKey) {
package/utils.js CHANGED
@@ -544,7 +544,7 @@ export const parsePkgJson = async (pkgJsonFile, simple = false) => {
544
544
  const pkgData = JSON.parse(readFileSync(pkgJsonFile, "utf8"));
545
545
  const pkgIdentifier = parsePackageJsonName(pkgData.name);
546
546
  const name = pkgIdentifier.fullName || pkgData.name;
547
- if (!name && !pkgJsonFile.includes("node_modules")) {
547
+ if (DEBUG_MODE && !name && !pkgJsonFile.includes("node_modules")) {
548
548
  console.log(
549
549
  `${pkgJsonFile} doesn't contain the package name. Consider using the 'npm init' command to create a valid package.json file for this project.`
550
550
  );
@@ -4034,16 +4034,28 @@ export const parseGoVersionData = async function (buildInfoData) {
4034
4034
  * @param {*} pkgList List of packages with metadata
4035
4035
  */
4036
4036
  export const getRubyGemsMetadata = async function (pkgList) {
4037
- const RUBYGEMS_URL = "https://rubygems.org/api/v1/versions/";
4037
+ const RUBYGEMS_V2_URL =
4038
+ process.env.RUBYGEMS_V2_URL || "https://rubygems.org/api/v2/rubygems/";
4039
+ const RUBYGEMS_V1_URL =
4040
+ process.env.RUBYGEMS_V1_URL || "https://rubygems.org/api/v1/gems/";
4038
4041
  const rdepList = [];
4042
+ const apiOptions = {
4043
+ responseType: "json"
4044
+ };
4045
+ if (process.env.GEM_HOST_API_KEY) {
4046
+ apiOptions.headers = {
4047
+ Authorization: process.env.GEM_HOST_API_KEY
4048
+ };
4049
+ }
4039
4050
  for (const p of pkgList) {
4040
4051
  try {
4041
4052
  if (DEBUG_MODE) {
4042
4053
  console.log(`Querying rubygems.org for ${p.name}`);
4043
4054
  }
4044
- const res = await cdxgenAgent.get(RUBYGEMS_URL + p.name + ".json", {
4045
- responseType: "json"
4046
- });
4055
+ const fullUrl = p.version
4056
+ ? `${RUBYGEMS_V2_URL}${p.name}/versions/${p.version}.json`
4057
+ : `${RUBYGEMS_V1_URL}${p.name}.json`;
4058
+ const res = await cdxgenAgent.get(fullUrl, apiOptions);
4047
4059
  let body = res.body;
4048
4060
  if (body && body.length) {
4049
4061
  body = body[0];
@@ -4055,14 +4067,54 @@ export const getRubyGemsMetadata = async function (pkgList) {
4055
4067
  if (body.metadata) {
4056
4068
  if (body.metadata.source_code_uri) {
4057
4069
  p.repository = { url: body.metadata.source_code_uri };
4070
+ if (
4071
+ body.homepage_uri &&
4072
+ body.homepage_uri !== body.metadata.source_code_uri
4073
+ ) {
4074
+ p.homepage = { url: body.homepage_uri };
4075
+ }
4058
4076
  }
4059
4077
  if (body.metadata.bug_tracker_uri) {
4060
- p.homepage = { url: body.metadata.bug_tracker_uri };
4078
+ p.bugs = { url: body.metadata.bug_tracker_uri };
4061
4079
  }
4062
4080
  }
4063
4081
  if (body.sha) {
4064
4082
  p._integrity = "sha256-" + body.sha;
4065
4083
  }
4084
+ if (body.authors) {
4085
+ p.author = body.authors;
4086
+ }
4087
+ // Track the platform such as java
4088
+ if (body.platform && body.platform !== "ruby") {
4089
+ p.properties.push({
4090
+ name: "cdx:gem:platform",
4091
+ value: body.platform
4092
+ });
4093
+ }
4094
+ if (body.ruby_version) {
4095
+ p.properties.push({
4096
+ name: "cdx:gem:rubyVersionSpecifiers",
4097
+ value: body.ruby_version
4098
+ });
4099
+ }
4100
+ if (body.gem_uri) {
4101
+ p.properties.push({
4102
+ name: "cdx:gem:gemUri",
4103
+ value: body.gem_uri
4104
+ });
4105
+ }
4106
+ if (body.yanked) {
4107
+ p.properties.push({
4108
+ name: "cdx:gem:yanked",
4109
+ value: "" + body.yanked
4110
+ });
4111
+ }
4112
+ if (body.prerelease) {
4113
+ p.properties.push({
4114
+ name: "cdx:gem:prerelease",
4115
+ value: "" + body.prerelease
4116
+ });
4117
+ }
4066
4118
  // Use the latest version if none specified
4067
4119
  if (!p.version) {
4068
4120
  p.version = body.number;
@@ -4119,15 +4171,22 @@ export const parseGemspecData = async function (gemspecData) {
4119
4171
  /**
4120
4172
  * Method to parse Gemfile.lock
4121
4173
  *
4122
- * @param {*} gemLockData Gemfile.lock data
4174
+ * @param {object} gemLockData Gemfile.lock data
4175
+ * @param {string} lockFile Lock file
4123
4176
  */
4124
- export const parseGemfileLockData = async function (gemLockData) {
4125
- const pkgList = [];
4177
+ export const parseGemfileLockData = async (gemLockData, lockFile) => {
4178
+ let pkgList = [];
4126
4179
  const pkgnames = {};
4180
+ const dependenciesList = [];
4181
+ const dependenciesMap = {};
4182
+ const pkgVersionMap = {};
4183
+ const rootList = [];
4127
4184
  if (!gemLockData) {
4128
4185
  return pkgList;
4129
4186
  }
4130
4187
  let specsFound = false;
4188
+ // We need two passes to identify components and resolve dependencies
4189
+ // In the first pass, we capture package name and version
4131
4190
  gemLockData.split("\n").forEach((l) => {
4132
4191
  l = l.trim();
4133
4192
  l = l.replace("\r", "");
@@ -4138,35 +4197,106 @@ export const parseGemfileLockData = async function (gemLockData) {
4138
4197
  if (name === "remote:") {
4139
4198
  return;
4140
4199
  }
4200
+ let version = tmpA[1];
4201
+ // We only allow bracket characters ()
4202
+ if (version.search(/[,><~ ]/) < 0) {
4203
+ version = version.replace(/[=()]/g, "");
4204
+ pkgVersionMap[name] = version;
4205
+ }
4206
+ }
4207
+ }
4208
+ if (l === "specs:") {
4209
+ specsFound = true;
4210
+ }
4211
+ if (l === l.toUpperCase()) {
4212
+ specsFound = false;
4213
+ }
4214
+ });
4215
+ specsFound = false;
4216
+ let lastParent = undefined;
4217
+ // In the second pass, we use the space in the prefix to figure out the dependency tree
4218
+ gemLockData.split("\n").forEach((l) => {
4219
+ l = l.replace("\r", "");
4220
+ if (specsFound) {
4221
+ const tmpA = l.split(" (");
4222
+ if (tmpA && tmpA.length == 2) {
4223
+ const nameWithPrefix = tmpA[0];
4224
+ const name = tmpA[0].trim();
4225
+ if (name === "remote:") {
4226
+ return;
4227
+ }
4228
+ const level = nameWithPrefix.replace(name, "").split(" ").length % 2;
4229
+ const purlString = new PackageURL(
4230
+ "gem",
4231
+ "",
4232
+ name,
4233
+ pkgVersionMap[name],
4234
+ null,
4235
+ null
4236
+ ).toString();
4237
+ const bomRef = decodeURIComponent(purlString);
4238
+ if (level === 1) {
4239
+ lastParent = bomRef;
4240
+ rootList.push(bomRef);
4241
+ }
4242
+ const apkg = {
4243
+ name,
4244
+ version: pkgVersionMap[name],
4245
+ purl: purlString,
4246
+ "bom-ref": bomRef,
4247
+ properties: [
4248
+ {
4249
+ name: "SrcFile",
4250
+ value: lockFile
4251
+ }
4252
+ ],
4253
+ evidence: {
4254
+ identity: {
4255
+ field: "purl",
4256
+ confidence: 0.8,
4257
+ methods: [
4258
+ {
4259
+ technique: "manifest-analysis",
4260
+ confidence: 0.8,
4261
+ value: lockFile
4262
+ }
4263
+ ]
4264
+ }
4265
+ }
4266
+ };
4267
+ if (lastParent && lastParent !== bomRef) {
4268
+ if (!dependenciesMap[lastParent]) {
4269
+ dependenciesMap[lastParent] = new Set();
4270
+ }
4271
+ dependenciesMap[lastParent].add(bomRef);
4272
+ }
4273
+ if (!dependenciesMap[bomRef]) {
4274
+ dependenciesMap[bomRef] = new Set();
4275
+ }
4141
4276
  if (!pkgnames[name]) {
4142
- let version = tmpA[1].split(", ")[0];
4143
- version = version.replace(/[(>=<)~ ]/g, "");
4144
- pkgList.push({
4145
- name,
4146
- version
4147
- });
4277
+ pkgList.push(apkg);
4148
4278
  pkgnames[name] = true;
4149
4279
  }
4150
4280
  }
4151
4281
  }
4152
- if (l === "specs:") {
4282
+ if (l.trim() === "specs:") {
4153
4283
  specsFound = true;
4154
4284
  }
4155
- if (
4156
- l === "PLATFORMS" ||
4157
- l === "DEPENDENCIES" ||
4158
- l === "RUBY VERSION" ||
4159
- l === "BUNDLED WITH" ||
4160
- l === "PATH"
4161
- ) {
4285
+ if (l.trim() == l.trim().toUpperCase()) {
4162
4286
  specsFound = false;
4163
4287
  }
4164
4288
  });
4289
+ for (const k of Object.keys(dependenciesMap)) {
4290
+ dependenciesList.push({
4291
+ ref: k,
4292
+ dependsOn: Array.from(dependenciesMap[k])
4293
+ });
4294
+ }
4165
4295
  if (FETCH_LICENSE) {
4166
- return await getRubyGemsMetadata(pkgList);
4167
- } else {
4168
- return pkgList;
4296
+ pkgList = await getRubyGemsMetadata(pkgList);
4297
+ return { pkgList, dependenciesList };
4169
4298
  }
4299
+ return { pkgList, dependenciesList, rootList };
4170
4300
  };
4171
4301
 
4172
4302
  /**
@@ -6728,7 +6858,13 @@ export const collectJarNS = async (jarPath, pomPathMap = {}) => {
6728
6858
  let purl = undefined;
6729
6859
  // In some cases, the pom name might be slightly different to the jar name
6730
6860
  if (!existsSync(pomname)) {
6731
- const pomSearch = getAllFiles(dirname(jf), "*.pom");
6861
+ let searchDir = dirname(jf);
6862
+ // in case of gradle, there would be hash directory that is different for jar vs pom
6863
+ // so we need to start search from a level up
6864
+ if (searchDir.includes(join(".gradle", "caches"))) {
6865
+ searchDir = join(searchDir, "..");
6866
+ }
6867
+ const pomSearch = getAllFiles(searchDir, "**/*.pom");
6732
6868
  if (pomSearch && pomSearch.length === 1) {
6733
6869
  pomname = pomSearch[0];
6734
6870
  }
@@ -6805,12 +6941,16 @@ export const collectJarNS = async (jarPath, pomPathMap = {}) => {
6805
6941
  tmpDirParts.pop();
6806
6942
  // Retrieve the version
6807
6943
  const jarVersion = tmpDirParts.pop();
6944
+ const pkgName = jarFileName.replace(`-${jarVersion}`, "");
6808
6945
  // The result would form the group name
6809
- const jarGroupName = tmpDirParts.join(".").replace(/^\./, "");
6946
+ let jarGroupName = tmpDirParts.join(".").replace(/^\./, "");
6947
+ if (jarGroupName.includes(pkgName)) {
6948
+ jarGroupName = jarGroupName.replace("." + pkgName, "");
6949
+ }
6810
6950
  const purlObj = new PackageURL(
6811
6951
  "maven",
6812
6952
  jarGroupName,
6813
- jarFileName.replace(`-${jarVersion}`, ""),
6953
+ pkgName,
6814
6954
  jarVersion,
6815
6955
  { type: "jar" },
6816
6956
  null
package/utils.test.js CHANGED
@@ -2581,14 +2581,44 @@ test("parseComposerLock", () => {
2581
2581
  });
2582
2582
 
2583
2583
  test("parseGemfileLockData", async () => {
2584
- const deps = await parseGemfileLockData(
2585
- readFileSync("./test/data/Gemfile.lock", { encoding: "utf-8" })
2584
+ let retMap = await parseGemfileLockData(
2585
+ readFileSync("./test/data/Gemfile.lock", { encoding: "utf-8" }),
2586
+ "./test/data/Gemfile.lock"
2586
2587
  );
2587
- expect(deps.length).toEqual(140);
2588
- expect(deps[0]).toEqual({
2588
+ expect(retMap.pkgList.length).toEqual(141);
2589
+ expect(retMap.dependenciesList.length).toEqual(141);
2590
+ expect(retMap.pkgList[0]).toEqual({
2589
2591
  name: "actioncable",
2590
- version: "6.0.0"
2592
+ version: "6.0.0",
2593
+ purl: "pkg:gem/actioncable@6.0.0",
2594
+ "bom-ref": "pkg:gem/actioncable@6.0.0",
2595
+ properties: [{ name: "SrcFile", value: "./test/data/Gemfile.lock" }],
2596
+ evidence: {
2597
+ identity: {
2598
+ field: "purl",
2599
+ confidence: 0.8,
2600
+ methods: [
2601
+ {
2602
+ technique: "manifest-analysis",
2603
+ confidence: 0.8,
2604
+ value: "./test/data/Gemfile.lock"
2605
+ }
2606
+ ]
2607
+ }
2608
+ }
2591
2609
  });
2610
+ retMap = await parseGemfileLockData(
2611
+ readFileSync("./test/data/Gemfile1.lock", { encoding: "utf-8" }),
2612
+ "./test/data/Gemfile1.lock"
2613
+ );
2614
+ expect(retMap.pkgList.length).toEqual(36);
2615
+ expect(retMap.dependenciesList.length).toEqual(36);
2616
+ retMap = await parseGemfileLockData(
2617
+ readFileSync("./test/data/Gemfile2.lock", { encoding: "utf-8" }),
2618
+ "./test/data/Gemfile2.lock"
2619
+ );
2620
+ expect(retMap.pkgList.length).toEqual(90);
2621
+ expect(retMap.dependenciesList.length).toEqual(90);
2592
2622
  });
2593
2623
 
2594
2624
  test("parseGemspecData", async () => {