@cdxgen/cdxgen 13.2.0 → 13.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.
- package/README.md +28 -12
- package/bin/cdxgen.js +227 -119
- package/bin/evinse.js +5 -0
- package/bin/sign.js +88 -14
- package/bin/tracebom.js +64 -4
- package/bin/validate.js +29 -7
- package/bin/verify.js +79 -39
- package/data/remediations.json +24 -0
- package/lib/cli/bomAssembly.js +105 -15
- package/lib/cli/cliOptions.js +93 -1
- package/lib/cli/index.js +43 -4
- package/lib/cli/jsBom.js +49 -24
- package/lib/cli/jvmBom.js +46 -12
- package/lib/cli/managedBom.js +112 -41
- package/lib/cli/nativeBom.js +125 -14
- package/lib/core/env.js +5 -6
- package/lib/core/fs.js +174 -6
- package/lib/core/metadataProperties.js +97 -0
- package/lib/core/projectConfig.js +31 -4
- package/lib/ecosystems/bunutils.js +269 -101
- package/lib/ecosystems/caxa.js +434 -2
- package/lib/ecosystems/cmakeResolver.js +66 -0
- package/lib/ecosystems/cppEvidence.js +64 -31
- package/lib/ecosystems/ecosystems.js +19 -8
- package/lib/ecosystems/gradleutils.js +42 -7
- package/lib/ecosystems/jsEvidence.js +19 -20
- package/lib/ecosystems/kosi.js +10 -3
- package/lib/ecosystems/npmutils.js +110 -9
- package/lib/ecosystems/parsers-dotnet.js +51 -10
- package/lib/ecosystems/parsers-go.js +47 -9
- package/lib/ecosystems/parsers-js.js +171 -50
- package/lib/ecosystems/parsers-misc.js +747 -161
- package/lib/ecosystems/parsers-python.js +26 -14
- package/lib/ecosystems/parsers-rust.js +24 -18
- package/lib/ecosystems/rubyutils.js +1426 -166
- package/lib/ecosystems/rusi.js +32 -1
- package/lib/evinser/evinser.js +436 -128
- package/lib/evinser/swiftsem.js +1914 -302
- package/lib/helpers/bomSigner.js +652 -221
- package/lib/inventory/atomUtils.js +568 -31
- package/lib/inventory/cdxrs.js +103 -12
- package/lib/inventory/depsUtils.js +183 -11
- package/lib/inventory/display.js +4 -5
- package/lib/inventory/dosai.js +550 -13
- package/lib/inventory/dynamic.js +39 -1
- package/lib/inventory/evidenceUtils.js +14 -6
- package/lib/inventory/fetchBatch.js +116 -17
- package/lib/inventory/protobom.js +8 -1
- package/lib/inventory/purl.js +152 -3
- package/lib/inventory/sbomDocument.js +2 -70
- package/lib/inventory/source.js +37 -19
- package/lib/inventory/traceRunner.js +210 -21
- package/lib/managers/binary.js +115 -24
- package/lib/managers/docker.js +440 -20
- package/lib/managers/oci.js +39 -1
- package/lib/parsers/cmakeLists.js +452 -0
- package/lib/parsers/largeJson.js +454 -0
- package/lib/parsers/npmrc.js +19 -1
- package/lib/stages/postgen/postgen.js +68 -15
- package/lib/validator/bomValidator.js +6 -16
- package/lib/validator/index.js +48 -10
- package/lib/validator/reporters/console.js +3 -0
- package/lib/validator/reporters/json.js +1 -0
- package/lib/validator/reporters/sarif.js +7 -0
- package/package.json +20 -20
- package/types/lib/cli/bomAssembly.d.ts +2 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +33 -1
- package/types/lib/cli/cliOptions.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +55 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/metadataProperties.d.ts +39 -0
- package/types/lib/core/metadataProperties.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +6 -2
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/ecosystems/bunutils.d.ts +12 -5
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/caxa.d.ts +14 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +23 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +20 -4
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +5 -2
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +13 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +42 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +10 -0
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +10 -4
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +67 -3
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +143 -2
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts +13 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +59 -9
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/swiftsem.d.ts +280 -15
- package/types/lib/evinser/swiftsem.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +106 -13
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +145 -3
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/cdxrs.d.ts +8 -3
- package/types/lib/inventory/cdxrs.d.ts.map +1 -1
- package/types/lib/inventory/depsUtils.d.ts +15 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -1
- package/types/lib/inventory/display.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +64 -1
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/purl.d.ts +71 -2
- package/types/lib/inventory/purl.d.ts.map +1 -1
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +47 -1
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts +30 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +29 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +14 -0
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +76 -0
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +52 -0
- package/types/lib/parsers/largeJson.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +5 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -1
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/console.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -1
package/lib/cli/jvmBom.js
CHANGED
|
@@ -167,14 +167,14 @@ export async function createJarBom(path, options) {
|
|
|
167
167
|
const parentComponent = createDefaultParentComponent(path, "maven", options);
|
|
168
168
|
if (options.useGradleCache) {
|
|
169
169
|
nsMapping = await collectGradleDependencies(
|
|
170
|
-
getGradleCommand(path, null),
|
|
170
|
+
getGradleCommand(path, null, options),
|
|
171
171
|
path,
|
|
172
172
|
false,
|
|
173
173
|
true,
|
|
174
174
|
);
|
|
175
175
|
} else if (options.useMavenCache) {
|
|
176
176
|
nsMapping = await collectMvnDependencies(
|
|
177
|
-
getMavenCommand(path, null),
|
|
177
|
+
getMavenCommand(path, null, options),
|
|
178
178
|
null,
|
|
179
179
|
false,
|
|
180
180
|
true,
|
|
@@ -245,6 +245,8 @@ export function discoverSbtProjectsFromCmd(basePath, sbtCmd, env) {
|
|
|
245
245
|
{
|
|
246
246
|
cwd: basePath,
|
|
247
247
|
shell: true,
|
|
248
|
+
// The quotes around the sbt command are for the shell to strip.
|
|
249
|
+
cdxgenRawShellArgs: true,
|
|
248
250
|
env,
|
|
249
251
|
encoding: "utf-8",
|
|
250
252
|
},
|
|
@@ -453,7 +455,7 @@ export async function createJavaBom(path, options) {
|
|
|
453
455
|
}
|
|
454
456
|
}
|
|
455
457
|
const firstPom = pomFiles.length ? pomFiles[0] : undefined;
|
|
456
|
-
let mavenCmd = getMavenCommand(path, path);
|
|
458
|
+
let mavenCmd = getMavenCommand(path, path, options);
|
|
457
459
|
for (const f of pomFiles) {
|
|
458
460
|
const basePath = dirname(f);
|
|
459
461
|
if (
|
|
@@ -473,7 +475,7 @@ export async function createJavaBom(path, options) {
|
|
|
473
475
|
);
|
|
474
476
|
}
|
|
475
477
|
if (mavenCmd?.endsWith("mvn")) {
|
|
476
|
-
mavenCmd = getMavenCommand(basePath, path);
|
|
478
|
+
mavenCmd = getMavenCommand(basePath, path, options);
|
|
477
479
|
}
|
|
478
480
|
// Should we attempt to resolve class names
|
|
479
481
|
if (options.resolveClass || options.deep) {
|
|
@@ -1041,11 +1043,23 @@ export async function createJavaBom(path, options) {
|
|
|
1041
1043
|
if (resolveGradleDistribution && gradleInitArgs.length) {
|
|
1042
1044
|
gradleInitArgs.push("-PcdxgenResolveDistribution=true");
|
|
1043
1045
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1046
|
+
// `gradle properties` evaluates the project's build scripts, so it is
|
|
1047
|
+
// part of the dependency-tooling surface gated by --no-install-deps
|
|
1048
|
+
// (and the pre-build lifecycle).
|
|
1049
|
+
const gradleToolingAllowed =
|
|
1050
|
+
options.installDeps !== false || options.introspect === true;
|
|
1051
|
+
if (gradleToolingAllowed) {
|
|
1052
|
+
parallelPropTaskOut = executeParallelGradleProperties(
|
|
1053
|
+
gradleRootPath,
|
|
1054
|
+
[null].concat(includedBuilds),
|
|
1055
|
+
gradleInitArgs,
|
|
1056
|
+
options,
|
|
1057
|
+
);
|
|
1058
|
+
} else {
|
|
1059
|
+
console.log(
|
|
1060
|
+
"Skipping 'gradle properties' since dependency installation is disabled. Only statically parsed Gradle metadata will be used.",
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1049
1063
|
if (readEnvironmentVariable("GRADLE_INCLUDED_BUILDS") === undefined) {
|
|
1050
1064
|
const outputLines = parallelPropTaskOut.split("\n");
|
|
1051
1065
|
for (const [_i, line] of outputLines.entries()) {
|
|
@@ -1073,7 +1087,14 @@ export async function createJavaBom(path, options) {
|
|
|
1073
1087
|
}
|
|
1074
1088
|
parallelPropTaskOut = parallelPropTaskOut.concat(
|
|
1075
1089
|
"\n",
|
|
1076
|
-
|
|
1090
|
+
gradleToolingAllowed
|
|
1091
|
+
? executeParallelGradleProperties(
|
|
1092
|
+
gradleRootPath,
|
|
1093
|
+
includedBuilds,
|
|
1094
|
+
[],
|
|
1095
|
+
options,
|
|
1096
|
+
)
|
|
1097
|
+
: "",
|
|
1077
1098
|
);
|
|
1078
1099
|
}
|
|
1079
1100
|
}
|
|
@@ -1114,6 +1135,8 @@ export async function createJavaBom(path, options) {
|
|
|
1114
1135
|
parallelPropTaskOut = executeParallelGradleProperties(
|
|
1115
1136
|
gradleRootPath,
|
|
1116
1137
|
allProjectsStr.filter((module) => !modulesToSkip.includes(module)),
|
|
1138
|
+
[],
|
|
1139
|
+
options,
|
|
1117
1140
|
);
|
|
1118
1141
|
const splitPropTaskOut = splitOutputByGradleProjects(
|
|
1119
1142
|
parallelPropTaskOut,
|
|
@@ -1161,7 +1184,7 @@ export async function createJavaBom(path, options) {
|
|
|
1161
1184
|
)
|
|
1162
1185
|
) {
|
|
1163
1186
|
allProjects.push(parentComponent);
|
|
1164
|
-
const gradleCmd = getGradleCommand(gradleRootPath, null);
|
|
1187
|
+
const gradleCmd = getGradleCommand(gradleRootPath, null, options);
|
|
1165
1188
|
const gradleDepTask = readEnvironmentVariable("GRADLE_DEPENDENCY_TASK")
|
|
1166
1189
|
? readEnvironmentVariable("GRADLE_DEPENDENCY_TASK")
|
|
1167
1190
|
: "dependencies";
|
|
@@ -1639,6 +1662,13 @@ export async function createJavaBom(path, options) {
|
|
|
1639
1662
|
pkgList = pkgList.concat(dlist);
|
|
1640
1663
|
}
|
|
1641
1664
|
}
|
|
1665
|
+
} else if (options.installDeps === false && !options.introspect) {
|
|
1666
|
+
// Executing sbt evaluates the project's build definition (arbitrary
|
|
1667
|
+
// Scala), so it belongs to the dependency-tooling surface disabled
|
|
1668
|
+
// by --no-install-deps and the pre-build lifecycle.
|
|
1669
|
+
console.log(
|
|
1670
|
+
"Skipping sbt dependencyTree since dependency installation is disabled. Only statically parsed sbt metadata will be used.",
|
|
1671
|
+
);
|
|
1642
1672
|
} else {
|
|
1643
1673
|
const SBT_CMD = readEnvironmentVariable("SBT_CMD") || "sbt";
|
|
1644
1674
|
let sbtVersion = determineSbtVersion(path);
|
|
@@ -1754,6 +1784,9 @@ export async function createJavaBom(path, options) {
|
|
|
1754
1784
|
const result = safeSpawnSync(SBT_CMD, sbtArgs, {
|
|
1755
1785
|
cwd: basePath,
|
|
1756
1786
|
shell: true,
|
|
1787
|
+
// The sbt commands below embed their own shell quoting, which the
|
|
1788
|
+
// shell must strip for sbt to see separate commands.
|
|
1789
|
+
cdxgenRawShellArgs: true,
|
|
1757
1790
|
env,
|
|
1758
1791
|
});
|
|
1759
1792
|
if (result.status !== 0 || result.error) {
|
|
@@ -1893,6 +1926,7 @@ export async function createJavaBom(path, options) {
|
|
|
1893
1926
|
|
|
1894
1927
|
if (
|
|
1895
1928
|
millFiles?.length &&
|
|
1929
|
+
(options.installDeps !== false || options.introspect === true) &&
|
|
1896
1930
|
isPackageManagerAllowed(
|
|
1897
1931
|
"mill",
|
|
1898
1932
|
["bazel", "sbt", "gradle", "maven"],
|
|
@@ -1905,7 +1939,7 @@ export async function createJavaBom(path, options) {
|
|
|
1905
1939
|
"maven",
|
|
1906
1940
|
options,
|
|
1907
1941
|
);
|
|
1908
|
-
const millCmd = getMillCommand(millRootPath);
|
|
1942
|
+
const millCmd = getMillCommand(millRootPath, options);
|
|
1909
1943
|
const millCommonArgs = [
|
|
1910
1944
|
"--color",
|
|
1911
1945
|
"false",
|
package/lib/cli/managedBom.js
CHANGED
|
@@ -73,7 +73,10 @@ import {
|
|
|
73
73
|
getPyModules,
|
|
74
74
|
} from "../ecosystems/pythonutils.js";
|
|
75
75
|
import {
|
|
76
|
+
addRubyGemsDistributionUrls,
|
|
77
|
+
addRubyRuntimeComponent,
|
|
76
78
|
collectGemModuleNames,
|
|
79
|
+
describeProjectGem,
|
|
77
80
|
enrichGemsFromLocalCache,
|
|
78
81
|
parseBundleConfig,
|
|
79
82
|
parseGemfileLockData,
|
|
@@ -104,7 +107,7 @@ import {
|
|
|
104
107
|
createDosaiMethodsSlice,
|
|
105
108
|
isDosaiDotnetLanguage,
|
|
106
109
|
normalizeDosaiServiceMap,
|
|
107
|
-
|
|
110
|
+
readDosaiMethodsReport,
|
|
108
111
|
} from "../inventory/dosai.js";
|
|
109
112
|
import { addEvidenceForDotnet } from "../inventory/evidenceUtils.js";
|
|
110
113
|
import { nugetPurl, pypiBomRef } from "../inventory/purl.js";
|
|
@@ -573,8 +576,17 @@ export async function createPythonBom(path, options) {
|
|
|
573
576
|
}
|
|
574
577
|
if (requirementsMode || pipenvMode) {
|
|
575
578
|
if (pipenvMode) {
|
|
576
|
-
//
|
|
577
|
-
|
|
579
|
+
// `pipenv install` builds and installs the project's own dependencies
|
|
580
|
+
// (including `-e .` self-references that execute setup.py), so unlike
|
|
581
|
+
// the lockfile parsing below it must honor --no-install-deps and the
|
|
582
|
+
// pre-build lifecycle.
|
|
583
|
+
if (options.installDeps !== false) {
|
|
584
|
+
safeSpawnSync("pipenv", ["install"], { cwd: path, shell: isWin });
|
|
585
|
+
} else {
|
|
586
|
+
console.log(
|
|
587
|
+
"Skipping 'pipenv install' since dependency installation is disabled.",
|
|
588
|
+
);
|
|
589
|
+
}
|
|
578
590
|
const piplockFile = join(path, "Pipfile.lock");
|
|
579
591
|
if (safeExistsSync(piplockFile)) {
|
|
580
592
|
let dlist;
|
|
@@ -1352,6 +1364,7 @@ export async function createRubyBom(path, options) {
|
|
|
1352
1364
|
let pkgList = [];
|
|
1353
1365
|
let dependencies = [];
|
|
1354
1366
|
let rootList = [];
|
|
1367
|
+
const projectGemRefs = [];
|
|
1355
1368
|
const parentComponent = createDefaultParentComponent(path, "gem", options);
|
|
1356
1369
|
const gemFileMode = gemFiles.length;
|
|
1357
1370
|
const gemLockMode = gemLockFiles.length;
|
|
@@ -1405,10 +1418,18 @@ export async function createRubyBom(path, options) {
|
|
|
1405
1418
|
if (retMap.rootList?.length) {
|
|
1406
1419
|
rootList = rootList.concat(retMap.rootList);
|
|
1407
1420
|
}
|
|
1421
|
+
// `PATH remote: .` is relative to the lockfile, so only the lockfiles
|
|
1422
|
+
// of the scanned directory can serve the gem the root gemspec builds
|
|
1423
|
+
if (
|
|
1424
|
+
retMap.projectGemRefs?.length &&
|
|
1425
|
+
dirname(resolve(f)) === resolve(path)
|
|
1426
|
+
) {
|
|
1427
|
+
projectGemRefs.push(...retMap.projectGemRefs);
|
|
1428
|
+
}
|
|
1408
1429
|
}
|
|
1409
1430
|
}
|
|
1410
1431
|
// Parsing .gemspec files would help us get more metadata such as description, authors, licenses etc
|
|
1411
|
-
let
|
|
1432
|
+
let projectGem;
|
|
1412
1433
|
if (gemspecFiles.length) {
|
|
1413
1434
|
if (!gemLockFiles.length && !hasAnyProjectType(["oci"], options, false)) {
|
|
1414
1435
|
console.log(
|
|
@@ -1420,38 +1441,51 @@ export async function createRubyBom(path, options) {
|
|
|
1420
1441
|
detail: "only gemspec files were available to parse",
|
|
1421
1442
|
});
|
|
1422
1443
|
}
|
|
1444
|
+
// Bundler evaluated the root gemspec to lock it under `PATH remote: .`,
|
|
1445
|
+
// so the locked release stands in for a version computed at runtime. It
|
|
1446
|
+
// is resolved before the merge, so that the gemspec and lockfile
|
|
1447
|
+
// sightings of the gem reconcile into one component.
|
|
1448
|
+
const lockedVersions = Object.create(null);
|
|
1449
|
+
for (const p of pkgList) {
|
|
1450
|
+
if (p.version && projectGemRefs.includes(p["bom-ref"])) {
|
|
1451
|
+
lockedVersions[p.name] = p.version;
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1423
1454
|
for (const f of gemspecFiles) {
|
|
1424
1455
|
const gemspecData = readFileSync(f, { encoding: "utf-8" });
|
|
1425
|
-
const
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
) {
|
|
1434
|
-
rootGemspecComponent = gpkgList[0];
|
|
1435
|
-
}
|
|
1456
|
+
const isRootGemspec = dirname(resolve(f)) === resolve(path);
|
|
1457
|
+
const gpkgList = await parseGemspecData(
|
|
1458
|
+
gemspecData,
|
|
1459
|
+
f,
|
|
1460
|
+
isRootGemspec ? { lockedVersions } : {},
|
|
1461
|
+
);
|
|
1462
|
+
if (!gpkgList.length) {
|
|
1463
|
+
continue;
|
|
1436
1464
|
}
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
) {
|
|
1443
|
-
parentComponent.name = rootGemspecComponent.name;
|
|
1444
|
-
parentComponent.version = rootGemspecComponent.version || "latest";
|
|
1445
|
-
const parentPurl = build({
|
|
1446
|
-
type: "gem",
|
|
1447
|
-
namespace: parentComponent.group || null,
|
|
1448
|
-
name: parentComponent.name,
|
|
1449
|
-
version: parentComponent.version || null,
|
|
1450
|
-
});
|
|
1451
|
-
parentComponent["bom-ref"] = decodeURIComponent(parentPurl);
|
|
1452
|
-
parentComponent["purl"] = parentPurl;
|
|
1465
|
+
// The gemspec in the scanned directory is the gem the project builds
|
|
1466
|
+
if (!projectGem && isRootGemspec && gpkgList[0]?.name) {
|
|
1467
|
+
projectGem = gpkgList[0];
|
|
1468
|
+
}
|
|
1469
|
+
pkgList = trimComponents(pkgList.concat(gpkgList));
|
|
1453
1470
|
}
|
|
1454
1471
|
}
|
|
1472
|
+
// Describe that gem in metadata.component, unless `--project-name` names a
|
|
1473
|
+
// different project or an image is being scanned
|
|
1474
|
+
if (
|
|
1475
|
+
projectGem &&
|
|
1476
|
+
!hasAnyProjectType(["oci"], options, false) &&
|
|
1477
|
+
(!("project-name" in options) || options.projectName === projectGem.name)
|
|
1478
|
+
) {
|
|
1479
|
+
({ pkgList, dependencies, rootList } = describeProjectGem(
|
|
1480
|
+
parentComponent,
|
|
1481
|
+
projectGem,
|
|
1482
|
+
pkgList,
|
|
1483
|
+
dependencies,
|
|
1484
|
+
rootList,
|
|
1485
|
+
projectGemRefs,
|
|
1486
|
+
options,
|
|
1487
|
+
));
|
|
1488
|
+
}
|
|
1455
1489
|
if (rootList.length) {
|
|
1456
1490
|
dependencies = mergeDependencies(
|
|
1457
1491
|
dependencies,
|
|
@@ -1547,6 +1581,21 @@ export async function createRubyBom(path, options) {
|
|
|
1547
1581
|
// lookup, gives us the sha256 of the exact native build, and unlike the
|
|
1548
1582
|
// registry lookups it also works in dry-run mode.
|
|
1549
1583
|
pkgList = await enrichGemsFromLocalCache(pkgList, { gemHome });
|
|
1584
|
+
// The download URL of a gem from the public registry needs no network call
|
|
1585
|
+
pkgList = addRubyGemsDistributionUrls(pkgList);
|
|
1586
|
+
// Gems declare the Ruby they run on; surface it as the runtime they depend
|
|
1587
|
+
// on. The component says the runtime is external, which only CycloneDX 1.7
|
|
1588
|
+
// can express, and a container image ships its own Ruby.
|
|
1589
|
+
if (
|
|
1590
|
+
options.specVersion >= 1.7 &&
|
|
1591
|
+
!hasAnyProjectType(["oci"], options, false)
|
|
1592
|
+
) {
|
|
1593
|
+
({ pkgList, dependencies } = addRubyRuntimeComponent(
|
|
1594
|
+
pkgList,
|
|
1595
|
+
parentComponent,
|
|
1596
|
+
dependencies,
|
|
1597
|
+
));
|
|
1598
|
+
}
|
|
1550
1599
|
return buildBomNSData(options, pkgList, "gem", {
|
|
1551
1600
|
src: path,
|
|
1552
1601
|
dependencies,
|
|
@@ -2006,9 +2055,13 @@ export async function createCsharpBom(path, options) {
|
|
|
2006
2055
|
pkgList = trimComponents(pkgList);
|
|
2007
2056
|
// Perform deep analysis using dosai
|
|
2008
2057
|
if (options.deep) {
|
|
2058
|
+
// resolve (not join) so a fully-qualified --deps-slices-file is used
|
|
2059
|
+
// as-is: join would append it to the scan path, which is never a valid
|
|
2060
|
+
// path on Windows and silently loses the .NET evidence elsewhere
|
|
2061
|
+
// (issue 4394). A relative value still resolves against the scan path.
|
|
2009
2062
|
const slicesFile = resolve(
|
|
2010
2063
|
options.depsSlicesFile
|
|
2011
|
-
?
|
|
2064
|
+
? resolve(path, options.depsSlicesFile)
|
|
2012
2065
|
: join(getTmpDir(), "dosai.json"),
|
|
2013
2066
|
);
|
|
2014
2067
|
// Create the slices file if it doesn't exist
|
|
@@ -2021,14 +2074,24 @@ export async function createCsharpBom(path, options) {
|
|
|
2021
2074
|
resolve(slicesFile),
|
|
2022
2075
|
options,
|
|
2023
2076
|
);
|
|
2024
|
-
if (!sliceResult
|
|
2025
|
-
|
|
2026
|
-
|
|
2077
|
+
if (!sliceResult) {
|
|
2078
|
+
// Visible without --debug: otherwise the components silently lose
|
|
2079
|
+
// the dependency-slice evidence while the scan still exits 0.
|
|
2080
|
+
// --evidence (which implies --deep) runs dosai again during evinse
|
|
2081
|
+
// and can still add occurrences, so this names only what is lost.
|
|
2082
|
+
console.warn(
|
|
2083
|
+
"Slicing with dosai was unsuccessful, so the .NET components get no occurrence, reachability, or services evidence from the dependency slice. Re-run with --debug for the dosai errors.",
|
|
2027
2084
|
);
|
|
2028
2085
|
}
|
|
2086
|
+
} else if (DEBUG_MODE) {
|
|
2087
|
+
console.log(
|
|
2088
|
+
`Reusing the existing dosai methods slice "${slicesFile}" without checking whether it honours the current --exclude patterns. Delete the file to force a fresh run.`,
|
|
2089
|
+
);
|
|
2029
2090
|
}
|
|
2030
2091
|
pkgList = addEvidenceForDotnet(pkgList, slicesFile);
|
|
2031
|
-
const methodsSlice =
|
|
2092
|
+
const methodsSlice = readDosaiMethodsReport(slicesFile, {
|
|
2093
|
+
sections: ["Metadata", "Services", "ApiEndpoints"],
|
|
2094
|
+
});
|
|
2032
2095
|
// Provider-backed Services[] first so bom-refs, trust zones, and data
|
|
2033
2096
|
// classifications survive; ApiEndpoints derivation fills in the rest.
|
|
2034
2097
|
const servicesMap = collectDosaiServiceComponents(methodsSlice, {});
|
|
@@ -2036,12 +2099,20 @@ export async function createCsharpBom(path, options) {
|
|
|
2036
2099
|
services = normalizeDosaiServiceMap(servicesMap);
|
|
2037
2100
|
}
|
|
2038
2101
|
}
|
|
2039
|
-
// Parent dependency tree
|
|
2102
|
+
// Parent dependency tree. A single project file already contributed an
|
|
2103
|
+
// entry for the same parent, so the lock file roots are merged into it
|
|
2104
|
+
// rather than added as a second entry for the same ref.
|
|
2040
2105
|
if (parentDependsOn.size && parentComponent?.["bom-ref"]) {
|
|
2041
|
-
dependencies
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2106
|
+
dependencies = mergeDependencies(
|
|
2107
|
+
[
|
|
2108
|
+
{
|
|
2109
|
+
ref: parentComponent["bom-ref"],
|
|
2110
|
+
dependsOn: Array.from(parentDependsOn).sort(),
|
|
2111
|
+
},
|
|
2112
|
+
],
|
|
2113
|
+
dependencies,
|
|
2114
|
+
parentComponent,
|
|
2115
|
+
);
|
|
2045
2116
|
}
|
|
2046
2117
|
if (shouldFetchLicense()) {
|
|
2047
2118
|
const retMap = await getNugetMetadata(pkgList, dependencies);
|
package/lib/cli/nativeBom.js
CHANGED
|
@@ -31,7 +31,9 @@ import { dirNameStr, isMac } from "../core/paths.js";
|
|
|
31
31
|
import {
|
|
32
32
|
buildDependentPurl,
|
|
33
33
|
collapseCmakeVersions,
|
|
34
|
+
preferFetchedCmakeDependencies,
|
|
34
35
|
resolveCmakeContext,
|
|
36
|
+
withoutScrapedFetchDependency,
|
|
35
37
|
} from "../ecosystems/cmakeResolver.js";
|
|
36
38
|
import { getCppModules } from "../ecosystems/cppEvidence.js";
|
|
37
39
|
import {
|
|
@@ -48,6 +50,7 @@ import {
|
|
|
48
50
|
} from "../ecosystems/parsers-elm.js";
|
|
49
51
|
import { parseGleamProject } from "../ecosystems/parsers-gleam.js";
|
|
50
52
|
import {
|
|
53
|
+
isGoModWhyNotNeeded,
|
|
51
54
|
parseGoListDep,
|
|
52
55
|
parseGoModData,
|
|
53
56
|
parseGoModGraph,
|
|
@@ -69,6 +72,7 @@ import {
|
|
|
69
72
|
} from "../ecosystems/parsers-lua.js";
|
|
70
73
|
import {
|
|
71
74
|
buildObjectForCocoaPod,
|
|
75
|
+
classifySwiftDependencyScopes,
|
|
72
76
|
executeAlpmList,
|
|
73
77
|
executeApkList,
|
|
74
78
|
executeDpkgList,
|
|
@@ -196,7 +200,6 @@ export function getCargoCacheDir() {
|
|
|
196
200
|
export async function createGoBom(path, options) {
|
|
197
201
|
let pkgList = [];
|
|
198
202
|
let dependencies = [];
|
|
199
|
-
const allImports = {};
|
|
200
203
|
let parentComponent = createDefaultParentComponent(path, "golang", options);
|
|
201
204
|
// Is this a binary file
|
|
202
205
|
let maybeBinary;
|
|
@@ -236,6 +239,8 @@ export async function createGoBom(path, options) {
|
|
|
236
239
|
}
|
|
237
240
|
}
|
|
238
241
|
const doneList = {};
|
|
242
|
+
const requiredModules = new Set();
|
|
243
|
+
const unneededModules = new Set();
|
|
239
244
|
let circuitBreak = false;
|
|
240
245
|
if (DEBUG_MODE) {
|
|
241
246
|
console.log(
|
|
@@ -249,12 +254,12 @@ export async function createGoBom(path, options) {
|
|
|
249
254
|
}
|
|
250
255
|
const pkgFullName = `${apkg.name}`;
|
|
251
256
|
if (apkg.scope === "required") {
|
|
252
|
-
|
|
257
|
+
requiredModules.add(pkgFullName);
|
|
253
258
|
continue;
|
|
254
259
|
}
|
|
255
260
|
if (
|
|
256
261
|
apkg.scope === "optional" ||
|
|
257
|
-
|
|
262
|
+
requiredModules.has(pkgFullName) ||
|
|
258
263
|
doneList[pkgFullName]
|
|
259
264
|
) {
|
|
260
265
|
continue;
|
|
@@ -283,19 +288,40 @@ export async function createGoBom(path, options) {
|
|
|
283
288
|
const mstdout = mresult.stdout;
|
|
284
289
|
if (mstdout) {
|
|
285
290
|
const cmdOutput = Buffer.from(mstdout).toString();
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
291
|
+
if (isGoModWhyNotNeeded(cmdOutput)) {
|
|
292
|
+
unneededModules.add(pkgFullName);
|
|
293
|
+
} else {
|
|
294
|
+
// The last line of the import chain is a package of the module:
|
|
295
|
+
// github.com/golang/protobuf/proto for github.com/golang/protobuf
|
|
296
|
+
const whyPkg = parseGoModWhy(cmdOutput);
|
|
297
|
+
if (
|
|
298
|
+
whyPkg === pkgFullName ||
|
|
299
|
+
whyPkg?.startsWith(`${pkgFullName}/`)
|
|
300
|
+
) {
|
|
301
|
+
requiredModules.add(pkgFullName);
|
|
302
|
+
}
|
|
292
303
|
}
|
|
293
304
|
doneList[pkgFullName] = true;
|
|
294
305
|
}
|
|
295
306
|
}
|
|
296
307
|
}
|
|
308
|
+
// A module go reports as unneeded is optional, but only when every module
|
|
309
|
+
// was checked: once go mod why fails, the answers are incomplete, and a
|
|
310
|
+
// tool failure is not evidence that a module is unused.
|
|
311
|
+
for (const apkg of pkgList) {
|
|
312
|
+
if (apkg.scope) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (requiredModules.has(apkg.name)) {
|
|
316
|
+
apkg.scope = "required";
|
|
317
|
+
} else if (!circuitBreak && unneededModules.has(apkg.name)) {
|
|
318
|
+
apkg.scope = "optional";
|
|
319
|
+
}
|
|
320
|
+
}
|
|
297
321
|
if (DEBUG_MODE) {
|
|
298
|
-
console.log(
|
|
322
|
+
console.log(
|
|
323
|
+
`Required packages: ${requiredModules.size}, not needed: ${circuitBreak ? "unknown" : unneededModules.size}`,
|
|
324
|
+
);
|
|
299
325
|
}
|
|
300
326
|
return buildBomNSData(options, pkgList, "golang", {
|
|
301
327
|
src: path,
|
|
@@ -560,7 +586,6 @@ export async function createGoBom(path, options) {
|
|
|
560
586
|
}
|
|
561
587
|
if (pkgList.length && !shouldManuallyParse) {
|
|
562
588
|
return buildBomNSData(options, pkgList, "golang", {
|
|
563
|
-
allImports,
|
|
564
589
|
dependencies,
|
|
565
590
|
parentComponent,
|
|
566
591
|
src: path,
|
|
@@ -1415,22 +1440,31 @@ export function createCppBom(path, options) {
|
|
|
1415
1440
|
}
|
|
1416
1441
|
if (cmakeLikeFiles.length) {
|
|
1417
1442
|
const beforeCmake = pkgList.length;
|
|
1443
|
+
// The files at the scan root name the project, so they are read before
|
|
1444
|
+
// the files of any sub-project (the sort is stable otherwise). The scan
|
|
1445
|
+
// root may be given as a relative path while the files come back absolute.
|
|
1446
|
+
const scanRoot = resolve(path);
|
|
1447
|
+
const atScanRoot = (f) => resolve(dirname(f)) === scanRoot;
|
|
1448
|
+
cmakeLikeFiles.sort(
|
|
1449
|
+
(a, b) => (atScanRoot(a) ? 0 : 1) - (atScanRoot(b) ? 0 : 1),
|
|
1450
|
+
);
|
|
1418
1451
|
for (const f of cmakeLikeFiles) {
|
|
1419
1452
|
if (DEBUG_MODE) {
|
|
1420
1453
|
console.log(`Parsing ${f}`);
|
|
1421
1454
|
}
|
|
1422
|
-
const basePath = dirname(f);
|
|
1423
1455
|
const retMap = parseCmakeLikeFile(f, "generic");
|
|
1424
1456
|
if (retMap.pkgList?.length) {
|
|
1425
1457
|
pkgList = pkgList.concat(retMap.pkgList);
|
|
1426
1458
|
}
|
|
1427
1459
|
if (
|
|
1428
|
-
|
|
1460
|
+
atScanRoot(f) &&
|
|
1429
1461
|
retMap.parentComponent &&
|
|
1430
1462
|
Object.keys(retMap.parentComponent).length
|
|
1431
1463
|
) {
|
|
1432
1464
|
if (!parentComponent) {
|
|
1433
1465
|
parentComponent = retMap.parentComponent;
|
|
1466
|
+
// a sub-project reusing the root project's name is the root itself
|
|
1467
|
+
addedParentComponentsMap[retMap.parentComponent.name] = true;
|
|
1434
1468
|
} else {
|
|
1435
1469
|
parentComponent.components = parentComponent.components || [];
|
|
1436
1470
|
if (!addedParentComponentsMap[retMap.parentComponent.name]) {
|
|
@@ -1445,6 +1479,7 @@ export function createCppBom(path, options) {
|
|
|
1445
1479
|
) {
|
|
1446
1480
|
retMap.parentComponent.type = "library";
|
|
1447
1481
|
pkgList.push(retMap.parentComponent);
|
|
1482
|
+
addedParentComponentsMap[retMap.parentComponent.name] = true;
|
|
1448
1483
|
}
|
|
1449
1484
|
// Retain the dependency tree from cmake
|
|
1450
1485
|
if (retMap.dependenciesList) {
|
|
@@ -1464,7 +1499,11 @@ export function createCppBom(path, options) {
|
|
|
1464
1499
|
// resolved versions and are left as they are.
|
|
1465
1500
|
pkgList = pkgList
|
|
1466
1501
|
.slice(0, beforeCmake)
|
|
1467
|
-
.concat(
|
|
1502
|
+
.concat(
|
|
1503
|
+
collapseCmakeVersions(
|
|
1504
|
+
preferFetchedCmakeDependencies(pkgList.slice(beforeCmake)),
|
|
1505
|
+
),
|
|
1506
|
+
);
|
|
1468
1507
|
}
|
|
1469
1508
|
// CMake describes how a project is built from source. An image or an OS
|
|
1470
1509
|
// installation carries build output, not a build tree, so resolving CMake
|
|
@@ -1554,6 +1593,7 @@ export function createCppBom(path, options) {
|
|
|
1554
1593
|
{ name: "cdx:cmake:resolvedVia", value: "gitclone-script" },
|
|
1555
1594
|
],
|
|
1556
1595
|
};
|
|
1596
|
+
pkgList = withoutScrapedFetchDependency(pkgList, dep.name);
|
|
1557
1597
|
if (dep.sourceDir) {
|
|
1558
1598
|
depComponent.properties.push({
|
|
1559
1599
|
name: "cdx:cmake:sourceDir",
|
|
@@ -2024,6 +2064,50 @@ export async function createSwiftBom(path, options) {
|
|
|
2024
2064
|
let parentComponent = {};
|
|
2025
2065
|
const completedPath = [];
|
|
2026
2066
|
let packageArgsMessageShown = false;
|
|
2067
|
+
// Packages that only the root package's tests use are scoped optional so
|
|
2068
|
+
// that `--required-only` can narrow the BOM. A package some project (or
|
|
2069
|
+
// some production path) needs stays required.
|
|
2070
|
+
const swiftScopes = {
|
|
2071
|
+
optionalRefs: new Set(),
|
|
2072
|
+
requiredRefs: new Set(),
|
|
2073
|
+
optionalNames: new Set(),
|
|
2074
|
+
requiredNames: new Set(),
|
|
2075
|
+
};
|
|
2076
|
+
const classifiedPaths = new Set();
|
|
2077
|
+
const classifyScopes = (basePath, swiftCommand, treeData) => {
|
|
2078
|
+
if (classifiedPaths.has(basePath)) {
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
classifiedPaths.add(basePath);
|
|
2082
|
+
const dumpArgs =
|
|
2083
|
+
swiftCommand === "xcrun"
|
|
2084
|
+
? ["swift", "package", "dump-package"]
|
|
2085
|
+
: ["package", "dump-package"];
|
|
2086
|
+
const dumpResult = safeSpawnSync(swiftCommand, dumpArgs, {
|
|
2087
|
+
cwd: basePath,
|
|
2088
|
+
});
|
|
2089
|
+
if (!dumpResult?.stdout) {
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
let dumpJson;
|
|
2093
|
+
let treeJson;
|
|
2094
|
+
try {
|
|
2095
|
+
dumpJson = JSON.parse(Buffer.from(dumpResult.stdout).toString());
|
|
2096
|
+
} catch (_e) {
|
|
2097
|
+
return;
|
|
2098
|
+
}
|
|
2099
|
+
try {
|
|
2100
|
+
treeJson = treeData ? JSON.parse(treeData) : undefined;
|
|
2101
|
+
} catch (_e) {
|
|
2102
|
+
treeJson = undefined;
|
|
2103
|
+
}
|
|
2104
|
+
const scopes = classifySwiftDependencyScopes(dumpJson, treeJson);
|
|
2105
|
+
for (const key of Object.keys(swiftScopes)) {
|
|
2106
|
+
for (const value of scopes[key]) {
|
|
2107
|
+
swiftScopes[key].add(value);
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
};
|
|
2027
2111
|
if (pkgResolvedFiles.length) {
|
|
2028
2112
|
for (const f of pkgResolvedFiles) {
|
|
2029
2113
|
if (!parentComponent || !Object.keys(parentComponent).length) {
|
|
@@ -2147,6 +2231,33 @@ export async function createSwiftBom(path, options) {
|
|
|
2147
2231
|
detail: "the swift package command failed",
|
|
2148
2232
|
});
|
|
2149
2233
|
}
|
|
2234
|
+
// Scope classification also works without the dependency graph
|
|
2235
|
+
classifyScopes(basePath, swiftCommand, treeData);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
// Scope packages that only tests use as optional. A package some project or
|
|
2239
|
+
// production path needs keeps its default (required) scope; evidence-based
|
|
2240
|
+
// filtering after generation can still promote an optional package when
|
|
2241
|
+
// the sources are found to use it.
|
|
2242
|
+
for (const apkg of pkgList) {
|
|
2243
|
+
if (apkg?.scope || !apkg?.name) {
|
|
2244
|
+
continue;
|
|
2245
|
+
}
|
|
2246
|
+
const refs = [apkg["bom-ref"], apkg.purl].filter(Boolean);
|
|
2247
|
+
const names = [String(apkg.name).toLowerCase()];
|
|
2248
|
+
for (const aproperty of apkg.properties || []) {
|
|
2249
|
+
if (aproperty?.name === "cdx:swift:packageName" && aproperty.value) {
|
|
2250
|
+
names.push(String(aproperty.value).toLowerCase());
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
const isOptional =
|
|
2254
|
+
refs.some((r) => swiftScopes.optionalRefs.has(r)) ||
|
|
2255
|
+
names.some((n) => swiftScopes.optionalNames.has(n));
|
|
2256
|
+
const isRequired =
|
|
2257
|
+
refs.some((r) => swiftScopes.requiredRefs.has(r)) ||
|
|
2258
|
+
names.some((n) => swiftScopes.requiredNames.has(n));
|
|
2259
|
+
if (isOptional && !isRequired) {
|
|
2260
|
+
apkg.scope = "optional";
|
|
2150
2261
|
}
|
|
2151
2262
|
}
|
|
2152
2263
|
if (shouldFetchLicense()) {
|
package/lib/core/env.js
CHANGED
|
@@ -491,7 +491,7 @@ export const PROJECT_TYPE_ALIASES = {
|
|
|
491
491
|
"visionos",
|
|
492
492
|
],
|
|
493
493
|
binary: ["binary", "blint"],
|
|
494
|
-
oci: ["docker", "oci", "container", "podman", "rootfs", "oci-dir"],
|
|
494
|
+
oci: ["docker", "image", "oci", "container", "podman", "rootfs", "oci-dir"],
|
|
495
495
|
cocoa: ["cocoa", "cocoapods", "objective-c", "swift", "ios"],
|
|
496
496
|
scala: ["scala", "scala3", "sbt", "mill"],
|
|
497
497
|
nix: ["nix", "nixos", "flake"],
|
|
@@ -643,11 +643,10 @@ export function hasAnyProjectType(projectTypes, options, defaultStatus = true) {
|
|
|
643
643
|
const allProjectTypes = [...projectTypes];
|
|
644
644
|
// Convert the project types into base types
|
|
645
645
|
const baseProjectTypes = [];
|
|
646
|
-
// Support for arbitrary versioned ruby type
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
) {
|
|
646
|
+
// Support for arbitrary versioned ruby type: a requested ruby3.4.1 is a
|
|
647
|
+
// ruby project. The test is on the requested types; testing the queried
|
|
648
|
+
// ones made every scan a ruby scan whenever "ruby" was asked about.
|
|
649
|
+
if (options.projectType?.some((p) => p.startsWith("ruby"))) {
|
|
651
650
|
baseProjectTypes.push("ruby");
|
|
652
651
|
}
|
|
653
652
|
// Versioned JVM build tool types such as maven3.9.9 or gradle8.14 are java
|