@cyclonedx/cdxgen 8.2.4 → 8.3.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.
Files changed (4) hide show
  1. package/index.js +47 -11
  2. package/package.json +1 -1
  3. package/utils.js +45 -20
  4. package/utils.test.js +17 -3
package/index.js CHANGED
@@ -988,9 +988,13 @@ const createJavaBom = async (path, options) => {
988
988
  if (pomFiles && pomFiles.length) {
989
989
  const cdxMavenPlugin =
990
990
  process.env.CDX_MAVEN_PLUGIN ||
991
- "org.cyclonedx:cyclonedx-maven-plugin:2.7.6";
991
+ "org.cyclonedx:cyclonedx-maven-plugin:2.7.7";
992
992
  const cdxMavenGoal = process.env.CDX_MAVEN_GOAL || "makeAggregateBom";
993
- let mvnArgs = [`${cdxMavenPlugin}:${cdxMavenGoal}`, "-DoutputName=bom"];
993
+ let mvnArgs = [
994
+ `${cdxMavenPlugin}:${cdxMavenGoal}`,
995
+ "-DoutputName=bom",
996
+ "-DincludeTestScope=true"
997
+ ];
994
998
  // By using quiet mode we can reduce the maxBuffer used and avoid crashes
995
999
  if (!DEBUG_MODE) {
996
1000
  mvnArgs.push("-q");
@@ -1152,8 +1156,14 @@ const createJavaBom = async (path, options) => {
1152
1156
  let gradleCmd = utils.getGradleCommand(path, null);
1153
1157
  const multiProjectMode = process.env.GRADLE_MULTI_PROJECT_MODE || "";
1154
1158
  // Support for multi-project applications
1155
- if (["true", "1"].includes(multiProjectMode)) {
1156
- console.log("Executing", gradleCmd, "projects in", path);
1159
+ // Let's experiment with defaulting to multi-project mode when multiple gradle files gets detected
1160
+ if (
1161
+ ["true", "1"].includes(multiProjectMode) ||
1162
+ (gradleFiles.length > 1 && !["false", "0"].includes(multiProjectMode))
1163
+ ) {
1164
+ if (DEBUG_MODE) {
1165
+ console.log("Executing", gradleCmd, "projects in", path);
1166
+ }
1157
1167
  const result = spawnSync(
1158
1168
  gradleCmd,
1159
1169
  ["projects", "-q", "--console", "plain"],
@@ -1171,14 +1181,20 @@ const createJavaBom = async (path, options) => {
1171
1181
  const stdout = result.stdout;
1172
1182
  if (stdout) {
1173
1183
  const cmdOutput = Buffer.from(stdout).toString();
1174
- const allProjects = utils.parseGradleProjects(cmdOutput);
1184
+ const retMap = utils.parseGradleProjects(cmdOutput);
1185
+ const allProjects = retMap.projects || [];
1186
+ let rootProject = retMap.rootProject;
1175
1187
  if (!allProjects) {
1176
1188
  console.log(
1177
1189
  "No projects found. Is this a gradle multi-project application?"
1178
1190
  );
1179
1191
  options.failOnError && process.exit(1);
1180
1192
  } else {
1181
- console.log("Found", allProjects.length, "gradle sub-projects");
1193
+ console.log(
1194
+ "Found",
1195
+ allProjects.length,
1196
+ "gradle sub-projects. This might take a while ..."
1197
+ );
1182
1198
  for (let sp of allProjects) {
1183
1199
  let gradleDepArgs = [
1184
1200
  sp + ":dependencies",
@@ -1212,7 +1228,7 @@ const createJavaBom = async (path, options) => {
1212
1228
  const sstdout = sresult.stdout;
1213
1229
  if (sstdout) {
1214
1230
  const cmdOutput = Buffer.from(sstdout).toString();
1215
- const parsedList = utils.parseGradleDep(cmdOutput);
1231
+ const parsedList = utils.parseGradleDep(cmdOutput, rootProject);
1216
1232
  const dlist = parsedList.pkgList;
1217
1233
  parentComponent = dlist.splice(0, 1)[0];
1218
1234
  if (
@@ -1245,7 +1261,7 @@ const createJavaBom = async (path, options) => {
1245
1261
  console.log(
1246
1262
  "Obtained",
1247
1263
  pkgList.length,
1248
- "from this gradle multi-project"
1264
+ "from this gradle multi-project. De-duping this list ..."
1249
1265
  );
1250
1266
  } else {
1251
1267
  console.log(
@@ -1286,8 +1302,29 @@ const createJavaBom = async (path, options) => {
1286
1302
  });
1287
1303
  if (result.status !== 0 || result.error) {
1288
1304
  if (result.stderr) {
1305
+ const cmdError = Buffer.from(result.stderr).toString();
1306
+ if (
1307
+ cmdError.includes(
1308
+ "is not part of the build defined by settings file"
1309
+ ) ||
1310
+ cmdError.includes(
1311
+ "was not found in any of the following sources"
1312
+ )
1313
+ ) {
1314
+ console.log(
1315
+ "This is a multi-project gradle application. Set the environment variable GRADLE_MULTI_PROJECT_MODE to true to improve SBoM accuracy."
1316
+ );
1317
+ }
1318
+ if (DEBUG_MODE) {
1319
+ console.log("-----------------------");
1320
+ }
1289
1321
  console.error(result.stdout, result.stderr);
1322
+ if (DEBUG_MODE) {
1323
+ console.log("-----------------------");
1324
+ }
1325
+ options.failOnError && process.exit(1);
1290
1326
  }
1327
+
1291
1328
  if (DEBUG_MODE || !result.stderr || options.failOnError) {
1292
1329
  console.log(
1293
1330
  "1. Check if the correct version of java and gradle are installed and available in PATH. For example, some project might require Java 11 with gradle 7.\n cdxgen container image bundles Java 17 with gradle 8 which might be incompatible."
@@ -1315,9 +1352,6 @@ const createJavaBom = async (path, options) => {
1315
1352
  );
1316
1353
  options.failOnError && process.exit(1);
1317
1354
  }
1318
- } else {
1319
- console.log("Gradle unexpectedly didn't produce any output");
1320
- options.failOnError && process.exit(1);
1321
1355
  }
1322
1356
  }
1323
1357
  }
@@ -4308,6 +4342,7 @@ const createBom = async (path, options) => {
4308
4342
  case "mvn":
4309
4343
  case "maven":
4310
4344
  case "sbt":
4345
+ options.multiProject = true;
4311
4346
  return await createJavaBom(path, options);
4312
4347
  case "jar":
4313
4348
  options.multiProject = true;
@@ -4334,6 +4369,7 @@ const createBom = async (path, options) => {
4334
4369
  case "typescript":
4335
4370
  case "ts":
4336
4371
  case "tsx":
4372
+ options.multiProject = true;
4337
4373
  return await createNodejsBom(path, options);
4338
4374
  case "python":
4339
4375
  case "py":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyclonedx/cdxgen",
3
- "version": "8.2.4",
3
+ "version": "8.3.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/utils.js CHANGED
@@ -239,6 +239,7 @@ const getNpmMetadata = async function (pkgList) {
239
239
  exports.getNpmMetadata = getNpmMetadata;
240
240
 
241
241
  const _getDepPkgList = async function (
242
+ pkgLockFile,
242
243
  pkgList,
243
244
  dependenciesList,
244
245
  depKeys,
@@ -256,7 +257,13 @@ const _getDepPkgList = async function (
256
257
  name,
257
258
  version,
258
259
  _integrity: pkg.dependencies[name].integrity,
259
- scope
260
+ scope,
261
+ properties: [
262
+ {
263
+ name: "SrcFile",
264
+ value: pkgLockFile
265
+ }
266
+ ]
260
267
  };
261
268
  pkgList.push(apkg);
262
269
  if (pkg.dependencies[name].dependencies) {
@@ -286,6 +293,7 @@ const _getDepPkgList = async function (
286
293
  depKeys[purlString] = true;
287
294
  }
288
295
  await _getDepPkgList(
296
+ pkgLockFile,
289
297
  pkgList,
290
298
  dependenciesList,
291
299
  depKeys,
@@ -431,6 +439,7 @@ const parsePkgLock = async (pkgLockFile) => {
431
439
  });
432
440
  }
433
441
  pkgList = await _getDepPkgList(
442
+ pkgLockFile,
434
443
  pkgList,
435
444
  dependenciesList,
436
445
  depKeys,
@@ -1140,15 +1149,21 @@ exports.parseMavenTree = parseMavenTree;
1140
1149
  /**
1141
1150
  * Parse gradle dependencies output
1142
1151
  * @param {string} rawOutput Raw string output
1152
+ * @param {string} rootProjectName Root project name
1153
+ * @param {string} rootProjectVersion Root project version
1143
1154
  */
1144
- const parseGradleDep = function (rawOutput) {
1155
+ const parseGradleDep = function (
1156
+ rawOutput,
1157
+ rootProjectName = "root",
1158
+ rootProjectVersion = "latest"
1159
+ ) {
1145
1160
  if (typeof rawOutput === "string") {
1146
1161
  let match = "";
1147
1162
  // To render dependency tree we need a root project
1148
1163
  const rootProject = {
1149
1164
  group: "",
1150
- name: "root",
1151
- version: "latest",
1165
+ name: rootProjectName,
1166
+ version: rootProjectVersion,
1152
1167
  type: "maven",
1153
1168
  qualifiers: { type: "jar" }
1154
1169
  };
@@ -1156,7 +1171,7 @@ const parseGradleDep = function (rawOutput) {
1156
1171
  const dependenciesList = [];
1157
1172
  const keys_cache = {};
1158
1173
  let last_level = 0;
1159
- let last_purl = "pkg:maven/root@latest?type=jar";
1174
+ let last_purl = `pkg:maven/${rootProjectName}@${rootProjectVersion}?type=jar`;
1160
1175
  const level_trees = {};
1161
1176
  level_trees[last_purl] = [];
1162
1177
  let stack = [last_purl];
@@ -1317,31 +1332,41 @@ exports.parseLeinMap = parseLeinMap;
1317
1332
 
1318
1333
  /**
1319
1334
  * Parse gradle projects output
1335
+ * FIXME: The method needs to be enhanced to capture project dependency tree. See issue #249
1336
+ *
1320
1337
  * @param {string} rawOutput Raw string output
1321
1338
  */
1322
1339
  const parseGradleProjects = function (rawOutput) {
1340
+ let rootProject = "root";
1341
+ const projects = new Set();
1323
1342
  if (typeof rawOutput === "string") {
1324
- const projects = [];
1325
1343
  const tmpA = rawOutput.split("\n");
1326
1344
  tmpA.forEach((l) => {
1327
- if (l.startsWith("+--- Project") || l.startsWith("\\--- Project")) {
1328
- let projName = l
1329
- .replace("+--- Project ", "")
1330
- .replace("\\--- Project ", "")
1331
- .split(" ")[0];
1332
- projName = projName.replace(/'/g, "");
1333
- if (
1334
- !projName.startsWith(":test") &&
1335
- !projName.startsWith(":docs") &&
1336
- !projName.startsWith(":qa")
1337
- ) {
1338
- projects.push(projName);
1345
+ if (l.startsWith("Root project ")) {
1346
+ rootProject = l
1347
+ .split("Root project ")[1]
1348
+ .split(" ")[0]
1349
+ .replace(/'/g, "");
1350
+ } else if (l.includes("--- Project")) {
1351
+ const tmpB = l.split("Project ");
1352
+ if (tmpB && tmpB.length > 1) {
1353
+ let projName = tmpB[1].split(" ")[0].replace(/'/g, "");
1354
+ // Include all projects including test projects
1355
+ if (projName.startsWith(":")) {
1356
+ projects.add(projName);
1357
+ }
1339
1358
  }
1340
1359
  }
1341
1360
  });
1342
- return projects;
1361
+ return {
1362
+ rootProject,
1363
+ projects: Array.from(projects)
1364
+ };
1343
1365
  }
1344
- return [];
1366
+ return {
1367
+ rootProject,
1368
+ projects: []
1369
+ };
1345
1370
  };
1346
1371
  exports.parseGradleProjects = parseGradleProjects;
1347
1372
 
package/utils.test.js CHANGED
@@ -208,11 +208,25 @@ test("parse gradle dependencies", () => {
208
208
  });
209
209
 
210
210
  test("parse gradle projects", () => {
211
- expect(utils.parseGradleProjects(null)).toEqual([]);
212
- let proj_list = utils.parseGradleProjects(
211
+ expect(utils.parseGradleProjects(null)).toEqual({
212
+ projects: [],
213
+ rootProject: "root"
214
+ });
215
+ let retMap = utils.parseGradleProjects(
213
216
  fs.readFileSync("./test/data/gradle-projects.out", { encoding: "utf-8" })
214
217
  );
215
- expect(proj_list.length).toEqual(9);
218
+ expect(retMap.rootProject).toEqual("elasticsearch");
219
+ expect(retMap.projects.length).toEqual(368);
220
+ retMap = utils.parseGradleProjects(
221
+ fs.readFileSync("./test/data/gradle-projects1.out", { encoding: "utf-8" })
222
+ );
223
+ expect(retMap.rootProject).toEqual("elasticsearch");
224
+ expect(retMap.projects.length).toEqual(403);
225
+ retMap = utils.parseGradleProjects(
226
+ fs.readFileSync("./test/data/gradle-projects2.out", { encoding: "utf-8" })
227
+ );
228
+ expect(retMap.rootProject).toEqual("fineract");
229
+ expect(retMap.projects.length).toEqual(22);
216
230
  });
217
231
 
218
232
  test("parse maven tree", () => {