@cyclonedx/cdxgen 12.1.4 → 12.2.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 +47 -39
- package/bin/cdxgen.js +181 -90
- package/bin/evinse.js +4 -4
- package/bin/repl.js +3 -3
- package/bin/sign.js +102 -0
- package/bin/validate.js +233 -0
- package/bin/verify.js +69 -28
- package/data/queries.json +1 -1
- package/data/rules/ci-permissions.yaml +186 -0
- package/data/rules/dependency-sources.yaml +123 -0
- package/data/rules/package-integrity.yaml +135 -0
- package/data/rules/vscode-extensions.yaml +228 -0
- package/lib/cli/index.js +484 -440
- package/lib/evinser/db.js +137 -0
- package/lib/{helpers → evinser}/db.poku.js +2 -6
- package/lib/evinser/evinser.js +5 -18
- package/lib/evinser/swiftsem.js +1 -1
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/bomSigner.poku.js +156 -0
- package/lib/helpers/caxa.js +1 -1
- package/lib/helpers/ciParsers/azurePipelines.js +295 -0
- package/lib/helpers/ciParsers/azurePipelines.poku.js +253 -0
- package/lib/helpers/ciParsers/circleCi.js +286 -0
- package/lib/helpers/ciParsers/circleCi.poku.js +230 -0
- package/lib/helpers/ciParsers/common.js +24 -0
- package/lib/helpers/ciParsers/githubActions.js +636 -0
- package/lib/helpers/ciParsers/githubActions.poku.js +802 -0
- package/lib/helpers/ciParsers/gitlabCi.js +213 -0
- package/lib/helpers/ciParsers/gitlabCi.poku.js +247 -0
- package/lib/helpers/ciParsers/jenkins.js +181 -0
- package/lib/helpers/ciParsers/jenkins.poku.js +197 -0
- package/lib/helpers/depsUtils.js +203 -0
- package/lib/helpers/depsUtils.poku.js +150 -0
- package/lib/helpers/display.js +429 -14
- package/lib/helpers/envcontext.js +23 -8
- package/lib/helpers/formulationParsers.js +351 -0
- package/lib/helpers/logger.js +14 -0
- package/lib/helpers/protobom.js +9 -9
- package/lib/helpers/pythonutils.js +305 -0
- package/lib/helpers/pythonutils.poku.js +469 -0
- package/lib/helpers/utils.js +970 -528
- package/lib/helpers/utils.poku.js +139 -256
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/versutils.poku.js +315 -0
- package/lib/helpers/vsixutils.js +1061 -0
- package/lib/helpers/vsixutils.poku.js +2247 -0
- package/lib/managers/binary.js +19 -19
- package/lib/managers/docker.js +108 -1
- package/lib/managers/oci.js +10 -0
- package/lib/managers/piptree.js +4 -10
- package/lib/parsers/npmrc.js +92 -0
- package/lib/parsers/npmrc.poku.js +528 -0
- package/lib/server/openapi.yaml +1 -10
- package/lib/server/server.js +58 -16
- package/lib/server/server.poku.js +123 -144
- package/lib/stages/postgen/annotator.js +1 -1
- package/lib/stages/postgen/auditBom.js +197 -0
- package/lib/stages/postgen/auditBom.poku.js +378 -0
- package/lib/stages/postgen/postgen.js +54 -1
- package/lib/stages/postgen/postgen.poku.js +90 -1
- package/lib/stages/postgen/ruleEngine.js +369 -0
- package/lib/stages/pregen/envAudit.js +299 -0
- package/lib/stages/pregen/envAudit.poku.js +572 -0
- package/lib/stages/pregen/pregen.js +12 -8
- package/lib/third-party/arborist/lib/deepest-nesting-target.js +1 -1
- package/lib/third-party/arborist/lib/node.js +3 -3
- package/lib/third-party/arborist/lib/shrinkwrap.js +1 -1
- package/lib/third-party/arborist/lib/tree-check.js +1 -1
- package/lib/{helpers/validator.js → validator/bomValidator.js} +107 -47
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceEngine.poku.js +168 -0
- package/lib/validator/complianceRules.js +1610 -0
- package/lib/validator/complianceRules.poku.js +328 -0
- package/lib/validator/index.js +222 -0
- package/lib/validator/index.poku.js +144 -0
- package/lib/validator/reporters/annotations.js +121 -0
- package/lib/validator/reporters/console.js +149 -0
- package/lib/validator/reporters/index.js +41 -0
- package/lib/validator/reporters/json.js +37 -0
- package/lib/validator/reporters/sarif.js +184 -0
- package/lib/validator/reporters.poku.js +150 -0
- package/package.json +8 -8
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/validate.d.ts +3 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/helpers/utils.d.ts +0 -1
- package/types/lib/cli/index.d.ts +49 -52
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/db.d.ts +34 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +63 -16
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +27 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts +17 -0
- package/types/lib/helpers/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/common.d.ts +11 -0
- package/types/lib/helpers/ciParsers/common.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts +34 -0
- package/types/lib/helpers/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts +17 -0
- package/types/lib/helpers/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts +17 -0
- package/types/lib/helpers/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/helpers/depsUtils.d.ts +21 -0
- package/types/lib/helpers/depsUtils.d.ts.map +1 -0
- package/types/lib/helpers/display.d.ts +111 -11
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/envcontext.d.ts +19 -7
- package/types/lib/helpers/envcontext.d.ts.map +1 -1
- package/types/lib/helpers/formulationParsers.d.ts +50 -0
- package/types/lib/helpers/formulationParsers.d.ts.map +1 -0
- package/types/lib/helpers/logger.d.ts +15 -1
- package/types/lib/helpers/logger.d.ts.map +1 -1
- package/types/lib/helpers/protobom.d.ts +2 -2
- package/types/lib/helpers/pythonutils.d.ts +18 -0
- package/types/lib/helpers/pythonutils.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts +532 -128
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/helpers/versutils.d.ts +8 -0
- package/types/lib/helpers/versutils.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +130 -0
- package/types/lib/helpers/vsixutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +12 -31
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +11 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/managers/piptree.d.ts.map +1 -1
- package/types/lib/parsers/npmrc.d.ts +26 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -0
- package/types/lib/server/server.d.ts +21 -2
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/auditBom.d.ts +20 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +8 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts +18 -0
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +8 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/{helpers/validator.d.ts → validator/bomValidator.d.ts} +1 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +66 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +70 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +70 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +31 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +30 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +21 -0
- package/types/lib/validator/reporters/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/json.d.ts +11 -0
- package/types/lib/validator/reporters/json.d.ts.map +1 -0
- package/types/lib/validator/reporters/sarif.d.ts +16 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/lib/helpers/db.js +0 -162
- package/types/helpers/db.d.ts +0 -35
- package/types/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/db.d.ts +0 -35
- package/types/lib/helpers/db.d.ts.map +0 -1
- package/types/lib/helpers/validator.d.ts.map +0 -1
- package/types/managers/binary.d.ts +0 -37
- package/types/managers/binary.d.ts.map +0 -1
- package/types/managers/docker.d.ts +0 -56
- package/types/managers/docker.d.ts.map +0 -1
- package/types/managers/oci.d.ts +0 -2
- package/types/managers/oci.d.ts.map +0 -1
- package/types/managers/piptree.d.ts +0 -2
- package/types/managers/piptree.d.ts.map +0 -1
- package/types/server/server.d.ts +0 -34
- package/types/server/server.d.ts.map +0 -1
- package/types/stages/postgen/annotator.d.ts +0 -27
- package/types/stages/postgen/annotator.d.ts.map +0 -1
- package/types/stages/postgen/postgen.d.ts +0 -51
- package/types/stages/postgen/postgen.d.ts.map +0 -1
- package/types/stages/pregen/pregen.d.ts +0 -59
- package/types/stages/pregen/pregen.d.ts.map +0 -1
package/lib/cli/index.js
CHANGED
|
@@ -2,9 +2,9 @@ import { Buffer } from "node:buffer";
|
|
|
2
2
|
import {
|
|
3
3
|
accessSync,
|
|
4
4
|
constants,
|
|
5
|
-
existsSync,
|
|
6
5
|
lstatSync,
|
|
7
6
|
mkdtempSync,
|
|
7
|
+
readdirSync,
|
|
8
8
|
readFileSync,
|
|
9
9
|
rmSync,
|
|
10
10
|
statSync,
|
|
@@ -25,14 +25,8 @@ import { parse as loadYaml } from "yaml";
|
|
|
25
25
|
|
|
26
26
|
import { findJSImportsExports } from "../helpers/analyzer.js";
|
|
27
27
|
import { parseCaxaMetadata } from "../helpers/caxa.js";
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
collectEnvInfo,
|
|
31
|
-
getBranch,
|
|
32
|
-
getOriginUrl,
|
|
33
|
-
gitTreeHashes,
|
|
34
|
-
listFiles,
|
|
35
|
-
} from "../helpers/envcontext.js";
|
|
28
|
+
import { mergeDependencies, trimComponents } from "../helpers/depsUtils.js";
|
|
29
|
+
import { GIT_COMMAND } from "../helpers/envcontext.js";
|
|
36
30
|
import { thoughtLog } from "../helpers/logger.js";
|
|
37
31
|
import {
|
|
38
32
|
addEvidenceForDotnet,
|
|
@@ -175,6 +169,14 @@ import {
|
|
|
175
169
|
shouldFetchLicense,
|
|
176
170
|
splitOutputByGradleProjects,
|
|
177
171
|
} from "../helpers/utils.js";
|
|
172
|
+
import {
|
|
173
|
+
cleanupTempDir,
|
|
174
|
+
collectInstalledExtensions,
|
|
175
|
+
discoverIdeExtensionDirs,
|
|
176
|
+
extractVsixToTempDir,
|
|
177
|
+
parseVsixFile,
|
|
178
|
+
VSCODE_EXTENSION_PURL_TYPE,
|
|
179
|
+
} from "../helpers/vsixutils.js";
|
|
178
180
|
import {
|
|
179
181
|
executeOsQuery,
|
|
180
182
|
getBinaryBom,
|
|
@@ -188,6 +190,7 @@ import {
|
|
|
188
190
|
getPkgPathList,
|
|
189
191
|
parseImageName,
|
|
190
192
|
} from "../managers/docker.js";
|
|
193
|
+
import { DEFAULT_NPMRC_BLOCKLIST, parseNpmrc } from "../parsers/npmrc.js";
|
|
191
194
|
|
|
192
195
|
const dirName = dirNameStr;
|
|
193
196
|
|
|
@@ -436,160 +439,6 @@ const addLifecyclesSection = (options) => {
|
|
|
436
439
|
return lifecycles;
|
|
437
440
|
};
|
|
438
441
|
|
|
439
|
-
/**
|
|
440
|
-
* Method to generate the formulation section based on git metadata
|
|
441
|
-
*
|
|
442
|
-
* @param {Object} options
|
|
443
|
-
* @param {Object} context Context
|
|
444
|
-
* @returns {Array} formulation array
|
|
445
|
-
*/
|
|
446
|
-
const addFormulationSection = (options, context) => {
|
|
447
|
-
const formulation = [];
|
|
448
|
-
const provides = [];
|
|
449
|
-
const gitBranch = getBranch();
|
|
450
|
-
const originUrl = getOriginUrl();
|
|
451
|
-
const gitFiles = listFiles();
|
|
452
|
-
const treeHashes = gitTreeHashes();
|
|
453
|
-
let parentOmniborId;
|
|
454
|
-
let treeOmniborId;
|
|
455
|
-
let components = [];
|
|
456
|
-
const aformulation = {};
|
|
457
|
-
// Reuse any existing formulation components
|
|
458
|
-
// See: PR #1172
|
|
459
|
-
if (context?.formulationList?.length) {
|
|
460
|
-
components = components.concat(trimComponents(context.formulationList));
|
|
461
|
-
}
|
|
462
|
-
if (options.specVersion >= 1.6 && Object.keys(treeHashes).length === 2) {
|
|
463
|
-
parentOmniborId = `gitoid:blob:sha1:${treeHashes.parent}`;
|
|
464
|
-
treeOmniborId = `gitoid:blob:sha1:${treeHashes.tree}`;
|
|
465
|
-
components.push({
|
|
466
|
-
type: "file",
|
|
467
|
-
name: "git-parent",
|
|
468
|
-
description: "Git Parent Node.",
|
|
469
|
-
"bom-ref": parentOmniborId,
|
|
470
|
-
omniborId: [parentOmniborId],
|
|
471
|
-
swhid: [`swh:1:rev:${treeHashes.parent}`],
|
|
472
|
-
});
|
|
473
|
-
components.push({
|
|
474
|
-
type: "file",
|
|
475
|
-
name: "git-tree",
|
|
476
|
-
description: "Git Tree Node.",
|
|
477
|
-
"bom-ref": treeOmniborId,
|
|
478
|
-
omniborId: [treeOmniborId],
|
|
479
|
-
swhid: [`swh:1:rev:${treeHashes.tree}`],
|
|
480
|
-
});
|
|
481
|
-
provides.push({
|
|
482
|
-
ref: parentOmniborId,
|
|
483
|
-
provides: [treeOmniborId],
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
|
-
// Collect git related components
|
|
487
|
-
if (gitBranch && gitFiles) {
|
|
488
|
-
const gitFileComponents = gitFiles.map((f) =>
|
|
489
|
-
options.specVersion >= 1.6
|
|
490
|
-
? {
|
|
491
|
-
type: "file",
|
|
492
|
-
name: f.name,
|
|
493
|
-
version: f.hash,
|
|
494
|
-
omniborId: [f.omniborId],
|
|
495
|
-
swhid: [f.swhid],
|
|
496
|
-
}
|
|
497
|
-
: {
|
|
498
|
-
type: "file",
|
|
499
|
-
name: f.name,
|
|
500
|
-
version: f.hash,
|
|
501
|
-
},
|
|
502
|
-
);
|
|
503
|
-
components = components.concat(gitFileComponents);
|
|
504
|
-
// Complete the Artifact Dependency Graph
|
|
505
|
-
if (options.specVersion >= 1.6 && treeOmniborId) {
|
|
506
|
-
provides.push({
|
|
507
|
-
ref: treeOmniborId,
|
|
508
|
-
provides: gitFiles.map((f) => f.ref),
|
|
509
|
-
});
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
// Collect build environment details
|
|
513
|
-
const infoComponents = collectEnvInfo(options.path);
|
|
514
|
-
if (infoComponents?.length) {
|
|
515
|
-
components = components.concat(infoComponents);
|
|
516
|
-
}
|
|
517
|
-
// Should we include the OS crypto libraries
|
|
518
|
-
if (options.includeCrypto) {
|
|
519
|
-
const cryptoLibs = collectOSCryptoLibs(options);
|
|
520
|
-
if (cryptoLibs?.length) {
|
|
521
|
-
components = components.concat(cryptoLibs);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
aformulation["bom-ref"] = uuidv4();
|
|
525
|
-
aformulation.components = trimComponents(components);
|
|
526
|
-
let environmentVars = gitBranch?.length
|
|
527
|
-
? [{ name: "GIT_BRANCH", value: gitBranch }]
|
|
528
|
-
: [];
|
|
529
|
-
const envPrefixes = [
|
|
530
|
-
"GIT",
|
|
531
|
-
"ANDROID",
|
|
532
|
-
"DENO",
|
|
533
|
-
"DOTNET",
|
|
534
|
-
"JAVA_",
|
|
535
|
-
"SDKMAN",
|
|
536
|
-
"CARGO",
|
|
537
|
-
"CONDA",
|
|
538
|
-
"RUST",
|
|
539
|
-
"GEM_",
|
|
540
|
-
"SCALA_",
|
|
541
|
-
"MAVEN_",
|
|
542
|
-
"GRADLE_",
|
|
543
|
-
"NODE_",
|
|
544
|
-
];
|
|
545
|
-
const envBlocklist = [
|
|
546
|
-
"key",
|
|
547
|
-
"token",
|
|
548
|
-
"pass",
|
|
549
|
-
"secret",
|
|
550
|
-
"user",
|
|
551
|
-
"email",
|
|
552
|
-
"auth",
|
|
553
|
-
"session",
|
|
554
|
-
"proxy",
|
|
555
|
-
"cred",
|
|
556
|
-
];
|
|
557
|
-
|
|
558
|
-
for (const aevar of Object.keys(process.env)) {
|
|
559
|
-
const lower = aevar.toLowerCase();
|
|
560
|
-
const value = process.env[aevar] ?? "";
|
|
561
|
-
if (
|
|
562
|
-
envPrefixes.some((p) => aevar.startsWith(p)) &&
|
|
563
|
-
!envBlocklist.some((b) => lower.includes(b)) &&
|
|
564
|
-
!envBlocklist.some((b) => value.includes(b)) &&
|
|
565
|
-
value.length
|
|
566
|
-
) {
|
|
567
|
-
environmentVars.push({
|
|
568
|
-
name: aevar,
|
|
569
|
-
value,
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
if (!environmentVars.length) {
|
|
574
|
-
environmentVars = undefined;
|
|
575
|
-
}
|
|
576
|
-
let sourceInput;
|
|
577
|
-
if (environmentVars) {
|
|
578
|
-
sourceInput = { environmentVars };
|
|
579
|
-
}
|
|
580
|
-
const sourceWorkflow = {
|
|
581
|
-
"bom-ref": uuidv4(),
|
|
582
|
-
uid: uuidv4(),
|
|
583
|
-
taskTypes: originUrl ? ["build", "clone"] : ["build"],
|
|
584
|
-
};
|
|
585
|
-
if (sourceInput) {
|
|
586
|
-
sourceWorkflow.inputs = [sourceInput];
|
|
587
|
-
}
|
|
588
|
-
aformulation.workflows = [sourceWorkflow];
|
|
589
|
-
formulation.push(aformulation);
|
|
590
|
-
return { formulation, provides };
|
|
591
|
-
};
|
|
592
|
-
|
|
593
442
|
/**
|
|
594
443
|
* Function to create metadata block
|
|
595
444
|
*
|
|
@@ -998,6 +847,7 @@ function addExternalReferences(opkg) {
|
|
|
998
847
|
* @param {Object} allImports All imports
|
|
999
848
|
* @param {Object} pkg Package object
|
|
1000
849
|
* @param {string} ptype Package type
|
|
850
|
+
* @returns {Object[]} Array of component objects
|
|
1001
851
|
*/
|
|
1002
852
|
export function listComponents(options, allImports, pkg, ptype = "npm") {
|
|
1003
853
|
const compMap = {};
|
|
@@ -1065,6 +915,15 @@ function addComponent(
|
|
|
1065
915
|
purl = undefined;
|
|
1066
916
|
purlString = undefined;
|
|
1067
917
|
}
|
|
918
|
+
// Some applications like github workflow steps and commands do not have purl
|
|
919
|
+
if (
|
|
920
|
+
pkg.purl === undefined &&
|
|
921
|
+
!pkg?.["bom-ref"]?.startsWith("pkg:") &&
|
|
922
|
+
pkg?.type === "application"
|
|
923
|
+
) {
|
|
924
|
+
purl = undefined;
|
|
925
|
+
purlString = undefined;
|
|
926
|
+
}
|
|
1068
927
|
const description = pkg.description || undefined;
|
|
1069
928
|
let compScope = pkg.scope;
|
|
1070
929
|
if (allImports) {
|
|
@@ -1104,7 +963,12 @@ function addComponent(
|
|
|
1104
963
|
component.data = pkg.data || undefined;
|
|
1105
964
|
}
|
|
1106
965
|
component["type"] = determinePackageType(pkg);
|
|
1107
|
-
|
|
966
|
+
if (purlString) {
|
|
967
|
+
component["bom-ref"] = decodeURIComponent(purlString);
|
|
968
|
+
} else if (pkg["bom-ref"]) {
|
|
969
|
+
component["bom-ref"] = pkg["bom-ref"];
|
|
970
|
+
}
|
|
971
|
+
|
|
1108
972
|
if (
|
|
1109
973
|
component.externalReferences === undefined ||
|
|
1110
974
|
component.externalReferences.length === 0
|
|
@@ -1134,6 +998,18 @@ function addComponent(
|
|
|
1134
998
|
}
|
|
1135
999
|
delete component.authors;
|
|
1136
1000
|
}
|
|
1001
|
+
// Downgrade from 1.7
|
|
1002
|
+
if (options.specVersion < 1.7) {
|
|
1003
|
+
if (component.isExternal) {
|
|
1004
|
+
delete component.isExternal;
|
|
1005
|
+
}
|
|
1006
|
+
if (component.versionRange) {
|
|
1007
|
+
console.warn(
|
|
1008
|
+
`Version Range is not supported in ${options.specVersion} specifications. Please run cdxgen with --spec-version 1.7`,
|
|
1009
|
+
);
|
|
1010
|
+
delete component.versionRange;
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1137
1013
|
// Retain any tags
|
|
1138
1014
|
if (
|
|
1139
1015
|
options.specVersion >= 1.6 &&
|
|
@@ -1153,12 +1029,10 @@ function addComponent(
|
|
|
1153
1029
|
if (pkg.components) {
|
|
1154
1030
|
component.components = pkg.components;
|
|
1155
1031
|
}
|
|
1032
|
+
const compMapKey = component.purl || component["bom-ref"];
|
|
1156
1033
|
// Issue: 1353. We need to keep merging the properties
|
|
1157
|
-
if (compMap[
|
|
1158
|
-
const mergedComponents = trimComponents([
|
|
1159
|
-
compMap[component.purl],
|
|
1160
|
-
component,
|
|
1161
|
-
]);
|
|
1034
|
+
if (compMap[compMapKey]) {
|
|
1035
|
+
const mergedComponents = trimComponents([compMap[compMapKey], component]);
|
|
1162
1036
|
if (mergedComponents?.length === 1) {
|
|
1163
1037
|
component = mergedComponents[0];
|
|
1164
1038
|
}
|
|
@@ -1194,7 +1068,7 @@ function addComponent(
|
|
|
1194
1068
|
component.evidence.identity = pkg.evidence.identity[0];
|
|
1195
1069
|
}
|
|
1196
1070
|
}
|
|
1197
|
-
compMap[
|
|
1071
|
+
compMap[compMapKey] = component;
|
|
1198
1072
|
}
|
|
1199
1073
|
if (pkg.dependencies) {
|
|
1200
1074
|
Object.keys(pkg.dependencies)
|
|
@@ -1385,17 +1259,15 @@ const buildBomNSData = (options, pkgInfo, ptype, context) => {
|
|
|
1385
1259
|
components,
|
|
1386
1260
|
dependencies,
|
|
1387
1261
|
};
|
|
1388
|
-
const formulationData =
|
|
1389
|
-
options.includeFormulation && options.specVersion >= 1.5
|
|
1390
|
-
? addFormulationSection(options, context)
|
|
1391
|
-
: undefined;
|
|
1392
|
-
if (formulationData) {
|
|
1393
|
-
jsonTpl.formulation = formulationData.formulation;
|
|
1394
|
-
}
|
|
1395
1262
|
bomNSData.bomJson = jsonTpl;
|
|
1396
1263
|
bomNSData.nsMapping = nsMapping;
|
|
1397
1264
|
bomNSData.dependencies = dependencies;
|
|
1398
1265
|
bomNSData.parentComponent = parentComponent;
|
|
1266
|
+
// Carry language-specific formulation data (e.g. Pixi) so that
|
|
1267
|
+
// postProcess can merge it when building the final formulation section.
|
|
1268
|
+
if (context?.formulationList?.length) {
|
|
1269
|
+
bomNSData.formulationList = context.formulationList;
|
|
1270
|
+
}
|
|
1399
1271
|
}
|
|
1400
1272
|
return bomNSData;
|
|
1401
1273
|
};
|
|
@@ -1487,6 +1359,7 @@ export async function createJarBom(path, options) {
|
|
|
1487
1359
|
*
|
|
1488
1360
|
* @param {string} path to the project
|
|
1489
1361
|
* @param {Object} options Parse options from the cli
|
|
1362
|
+
* @returns {Object|undefined} BOM object
|
|
1490
1363
|
*/
|
|
1491
1364
|
export function createAndroidBom(path, options) {
|
|
1492
1365
|
return createBinaryBom(path, options);
|
|
@@ -1497,6 +1370,7 @@ export function createAndroidBom(path, options) {
|
|
|
1497
1370
|
*
|
|
1498
1371
|
* @param {string} path to the project
|
|
1499
1372
|
* @param {Object} options Parse options from the cli
|
|
1373
|
+
* @returns {Object|undefined} BOM object
|
|
1500
1374
|
*/
|
|
1501
1375
|
export function createBinaryBom(path, options) {
|
|
1502
1376
|
const tempDir = mkdtempSync(join(getTmpDir(), "blint-tmp-"));
|
|
@@ -1520,6 +1394,7 @@ export function createBinaryBom(path, options) {
|
|
|
1520
1394
|
*
|
|
1521
1395
|
* @param {string} path to the project
|
|
1522
1396
|
* @param {Object} options Parse options from the cli
|
|
1397
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
1523
1398
|
*/
|
|
1524
1399
|
export async function createJavaBom(path, options) {
|
|
1525
1400
|
let jarNSMapping = {};
|
|
@@ -2767,6 +2642,7 @@ export async function createJavaBom(path, options) {
|
|
|
2767
2642
|
*
|
|
2768
2643
|
* @param {string} path to the project
|
|
2769
2644
|
* @param {Object} options Parse options from the cli
|
|
2645
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
2770
2646
|
*/
|
|
2771
2647
|
export async function createNodejsBom(path, options) {
|
|
2772
2648
|
let pkgList = [];
|
|
@@ -2879,6 +2755,7 @@ export async function createNodejsBom(path, options) {
|
|
|
2879
2755
|
);
|
|
2880
2756
|
const npmInstallCount =
|
|
2881
2757
|
Number.parseInt(process.env.NPM_INSTALL_COUNT, 10) || 2;
|
|
2758
|
+
let anyInstallSuccess = false;
|
|
2882
2759
|
// Automatic npm install logic.
|
|
2883
2760
|
// Only perform npm install for smaller projects (< 2 package.json) without the correct number of lock files
|
|
2884
2761
|
if (
|
|
@@ -2889,7 +2766,6 @@ export async function createNodejsBom(path, options) {
|
|
|
2889
2766
|
pkgJsonFiles?.length <= npmInstallCount &&
|
|
2890
2767
|
options.installDeps
|
|
2891
2768
|
) {
|
|
2892
|
-
let anyInstallSuccess = false;
|
|
2893
2769
|
for (const apkgJson of pkgJsonFiles) {
|
|
2894
2770
|
let pkgMgr = "npm";
|
|
2895
2771
|
const supPkgMgrs = ["npm", "yarn", "yarnpkg", "pnpm", "pnpx"];
|
|
@@ -2938,17 +2814,50 @@ export async function createNodejsBom(path, options) {
|
|
|
2938
2814
|
if (pkgMgr === "pnpm") {
|
|
2939
2815
|
installArgs.push("--ignore-pnpmfile");
|
|
2940
2816
|
}
|
|
2941
|
-
if (pkgMgr === "npm"
|
|
2942
|
-
|
|
2817
|
+
if (pkgMgr === "npm") {
|
|
2818
|
+
for (const c of ["--no-audit", "--no-bin-links"]) {
|
|
2819
|
+
if (!installArgs.includes(c)) {
|
|
2820
|
+
installArgs.push(c);
|
|
2821
|
+
}
|
|
2822
|
+
}
|
|
2823
|
+
installArgs.push(`--git=${GIT_COMMAND}`);
|
|
2943
2824
|
}
|
|
2944
2825
|
}
|
|
2826
|
+
if (
|
|
2827
|
+
pkgMgr === "npm" &&
|
|
2828
|
+
isSecureMode &&
|
|
2829
|
+
!installArgs.join(" ").includes("--allow-git")
|
|
2830
|
+
) {
|
|
2831
|
+
console.log(
|
|
2832
|
+
"Consider passing '--allow-git=none' via the environment variable NPM_INSTALL_ARGS to prevent any git dependencies from being fetched and installed via npm.",
|
|
2833
|
+
);
|
|
2834
|
+
}
|
|
2945
2835
|
const basePath = dirname(apkgJson);
|
|
2836
|
+
let npmrcData;
|
|
2837
|
+
if (safeExistsSync(join(basePath, ".npmrc"))) {
|
|
2838
|
+
thoughtLog(
|
|
2839
|
+
"Wait, there is a .npmrc file here! I'm going to check if it has anything malicious.",
|
|
2840
|
+
);
|
|
2841
|
+
npmrcData = readFileSync(join(basePath, ".npmrc"), "utf-8");
|
|
2842
|
+
const npmrcObj = parseNpmrc(npmrcData);
|
|
2843
|
+
for (const [key, value] of Object.entries(npmrcObj)) {
|
|
2844
|
+
const baseKey = key.replace(/^(?:\/\/[^/]+\/|@[^:]+:)/, "");
|
|
2845
|
+
if (
|
|
2846
|
+
DEFAULT_NPMRC_BLOCKLIST.has(baseKey) ||
|
|
2847
|
+
DEFAULT_NPMRC_BLOCKLIST.has(key)
|
|
2848
|
+
) {
|
|
2849
|
+
console.warn(
|
|
2850
|
+
`\x1b[1;35mSECURE MODE: Dangerous configuration ${key}=${value} detected in .npmrc! Verify if this is a trusted project. Remove this setting or any other problematic configurations to proceed.\x1b[0m`,
|
|
2851
|
+
);
|
|
2852
|
+
process.exit(1);
|
|
2853
|
+
}
|
|
2854
|
+
}
|
|
2855
|
+
}
|
|
2946
2856
|
// juice-shop mode
|
|
2947
2857
|
// Projects such as juice-shop prevent lockfile creations using .npmrc files
|
|
2948
2858
|
// Plus, they might require specific npm install args such as --legacy-peer-deps that could lead to strange node_modules structure
|
|
2949
2859
|
// To keep life simple, let's look for any .npmrc file that has package-lock=false to toggle before npm install
|
|
2950
|
-
if (pkgMgr === "npm"
|
|
2951
|
-
const npmrcData = readFileSync(join(basePath, ".npmrc"));
|
|
2860
|
+
if (pkgMgr === "npm") {
|
|
2952
2861
|
if (
|
|
2953
2862
|
npmrcData?.includes("package-lock=false") &&
|
|
2954
2863
|
!installArgs.includes("--package-lock")
|
|
@@ -2966,6 +2875,9 @@ export async function createNodejsBom(path, options) {
|
|
|
2966
2875
|
`**PACKAGE MANAGER**: Let's run the '${pkgMgr}' command with the arguments '${installArgs.join(" ")}' to generate the needed lock files.`,
|
|
2967
2876
|
);
|
|
2968
2877
|
}
|
|
2878
|
+
console.warn(
|
|
2879
|
+
"\x1b[1;35mNotice: Generating an SBOM without a lockfile is risky and non-deterministic. Consider generating and committing the lockfile to your repository to ensure reproducible builds and SBOMs.\x1b[0m",
|
|
2880
|
+
);
|
|
2969
2881
|
console.log(
|
|
2970
2882
|
`Executing '${pkgMgr} ${installArgs.join(" ")}' in`,
|
|
2971
2883
|
basePath,
|
|
@@ -3165,8 +3077,9 @@ export async function createNodejsBom(path, options) {
|
|
|
3165
3077
|
const basePath = dirname(f);
|
|
3166
3078
|
// Determine the parent component
|
|
3167
3079
|
const packageJsonF = join(basePath, "package.json");
|
|
3168
|
-
const
|
|
3169
|
-
|
|
3080
|
+
const pnpmCjsHooks = join(basePath, ".pnpmfile.cjs");
|
|
3081
|
+
const pnpmMjsHooks = join(basePath, ".pnpmfile.mjs");
|
|
3082
|
+
if (safeExistsSync(pnpmMjsHooks) || safeExistsSync(pnpmCjsHooks)) {
|
|
3170
3083
|
thoughtLog("Wait, this pnpm project uses install hooks.");
|
|
3171
3084
|
}
|
|
3172
3085
|
if (!Object.keys(parentComponent).length) {
|
|
@@ -3239,6 +3152,11 @@ export async function createNodejsBom(path, options) {
|
|
|
3239
3152
|
pkgLockFiles?.length &&
|
|
3240
3153
|
isPackageManagerAllowed("npm", ["pnpm", "yarn"], options)
|
|
3241
3154
|
) {
|
|
3155
|
+
if (anyInstallSuccess) {
|
|
3156
|
+
thoughtLog(
|
|
3157
|
+
`I have ${pkgLockFiles.length} package-lock.json file(s) now after a successful npm install.`,
|
|
3158
|
+
);
|
|
3159
|
+
}
|
|
3242
3160
|
manifestFiles = manifestFiles.concat(pkgLockFiles);
|
|
3243
3161
|
for (const f of pkgLockFiles) {
|
|
3244
3162
|
if (DEBUG_MODE) {
|
|
@@ -3644,6 +3562,7 @@ export async function createNodejsBom(path, options) {
|
|
|
3644
3562
|
*
|
|
3645
3563
|
* @param {String} path
|
|
3646
3564
|
* @param {Object} options
|
|
3565
|
+
* @returns {Object | null} BOM object, or `null` when `pixi.lock` is absent and `options.installDeps` is false
|
|
3647
3566
|
*/
|
|
3648
3567
|
export function createPixiBom(path, options) {
|
|
3649
3568
|
const allImports = {};
|
|
@@ -3722,6 +3641,7 @@ export function createPixiBom(path, options) {
|
|
|
3722
3641
|
*
|
|
3723
3642
|
* @param {string} path to the project
|
|
3724
3643
|
* @param {Object} options Parse options from the cli
|
|
3644
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
3725
3645
|
*/
|
|
3726
3646
|
export async function createPythonBom(path, options) {
|
|
3727
3647
|
let allImports = {};
|
|
@@ -3848,7 +3768,6 @@ export async function createPythonBom(path, options) {
|
|
|
3848
3768
|
console.log(`Parsing ${f}`);
|
|
3849
3769
|
}
|
|
3850
3770
|
let retMap = await parsePyLockData(lockData, f);
|
|
3851
|
-
// Should we exit for workspace errors
|
|
3852
3771
|
if (retMap?.workspaceWarningShown) {
|
|
3853
3772
|
options.failOnError && process.exit(1);
|
|
3854
3773
|
}
|
|
@@ -3856,7 +3775,6 @@ export async function createPythonBom(path, options) {
|
|
|
3856
3775
|
pkgList = pkgList.concat(retMap.pkgList);
|
|
3857
3776
|
pkgList = trimComponents(pkgList);
|
|
3858
3777
|
}
|
|
3859
|
-
// Retain the parent hierarchy
|
|
3860
3778
|
if (retMap?.parentComponent?.components?.length) {
|
|
3861
3779
|
if (!parentComponent.components) {
|
|
3862
3780
|
parentComponent.components = [];
|
|
@@ -3872,36 +3790,81 @@ export async function createPythonBom(path, options) {
|
|
|
3872
3790
|
parentComponent,
|
|
3873
3791
|
);
|
|
3874
3792
|
}
|
|
3875
|
-
// Retrieve the tree using virtualenv in deep mode and as a fallback
|
|
3876
|
-
// This is a slow operation
|
|
3877
3793
|
if ((options.deep || !dependencies.length) && !f.endsWith("uv.lock")) {
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
formulationList = formulationList.concat(retMap.formulationList);
|
|
3884
|
-
}
|
|
3885
|
-
if (retMap.dependenciesList) {
|
|
3886
|
-
dependencies = mergeDependencies(
|
|
3887
|
-
dependencies,
|
|
3888
|
-
retMap.dependenciesList,
|
|
3794
|
+
if (options.installDeps) {
|
|
3795
|
+
retMap = await getPipFrozenTree(
|
|
3796
|
+
basePath,
|
|
3797
|
+
f,
|
|
3798
|
+
tempDir,
|
|
3889
3799
|
parentComponent,
|
|
3890
3800
|
);
|
|
3801
|
+
if (retMap.pkgList?.length) pkgList = pkgList.concat(retMap.pkgList);
|
|
3802
|
+
if (retMap.formulationList?.length)
|
|
3803
|
+
formulationList = formulationList.concat(retMap.formulationList);
|
|
3804
|
+
if (retMap.dependenciesList)
|
|
3805
|
+
dependencies = mergeDependencies(
|
|
3806
|
+
dependencies,
|
|
3807
|
+
retMap.dependenciesList,
|
|
3808
|
+
parentComponent,
|
|
3809
|
+
);
|
|
3810
|
+
if (retMap.rootList) {
|
|
3811
|
+
const parentDependsOn = new Set();
|
|
3812
|
+
for (const p of retMap.rootList)
|
|
3813
|
+
parentDependsOn.add(
|
|
3814
|
+
`pkg:pypi/${p.name.toLowerCase()}@${p.version}`,
|
|
3815
|
+
);
|
|
3816
|
+
dependencies.splice(0, 0, {
|
|
3817
|
+
ref: parentComponent["bom-ref"],
|
|
3818
|
+
dependsOn: [...parentDependsOn].sort(),
|
|
3819
|
+
});
|
|
3820
|
+
}
|
|
3821
|
+
} else {
|
|
3822
|
+
let exportedReqs = "";
|
|
3823
|
+
if (f.endsWith("poetry.lock")) {
|
|
3824
|
+
thoughtLog(
|
|
3825
|
+
"Using poetry export as a safe, static alternative to pip install.",
|
|
3826
|
+
);
|
|
3827
|
+
const expCmd = safeSpawnSync(
|
|
3828
|
+
"poetry",
|
|
3829
|
+
["export", "-f", "requirements.txt"],
|
|
3830
|
+
{ cwd: basePath, shell: false },
|
|
3831
|
+
);
|
|
3832
|
+
if (expCmd.status === 0 && expCmd.stdout)
|
|
3833
|
+
exportedReqs = expCmd.stdout.toString();
|
|
3834
|
+
} else if (f.endsWith("pdm.lock")) {
|
|
3835
|
+
thoughtLog(
|
|
3836
|
+
"Using pdm export as a safe, static alternative to pip install.",
|
|
3837
|
+
);
|
|
3838
|
+
const expCmd = safeSpawnSync(
|
|
3839
|
+
"pdm",
|
|
3840
|
+
["export", "-f", "requirements"],
|
|
3841
|
+
{ cwd: basePath, shell: false },
|
|
3842
|
+
);
|
|
3843
|
+
if (expCmd.status === 0 && expCmd.stdout)
|
|
3844
|
+
exportedReqs = expCmd.stdout.toString();
|
|
3845
|
+
}
|
|
3846
|
+
if (exportedReqs) {
|
|
3847
|
+
const tmpReqFile = join(
|
|
3848
|
+
tempDir,
|
|
3849
|
+
`exported-${basename(basePath)}-reqs.txt`,
|
|
3850
|
+
);
|
|
3851
|
+
writeFileSync(tmpReqFile, exportedReqs);
|
|
3852
|
+
const dlist = await parseReqFile(tmpReqFile, false);
|
|
3853
|
+
if (dlist?.length) {
|
|
3854
|
+
pkgList = pkgList.concat(dlist);
|
|
3855
|
+
const parentDependsOn = new Set();
|
|
3856
|
+
for (const p of dlist)
|
|
3857
|
+
parentDependsOn.add(
|
|
3858
|
+
`pkg:pypi/${p.name.toLowerCase()}@${p.version}`,
|
|
3859
|
+
);
|
|
3860
|
+
dependencies.splice(0, 0, {
|
|
3861
|
+
ref: parentComponent["bom-ref"],
|
|
3862
|
+
dependsOn: [...parentDependsOn].sort(),
|
|
3863
|
+
});
|
|
3864
|
+
}
|
|
3865
|
+
}
|
|
3891
3866
|
}
|
|
3892
3867
|
}
|
|
3893
|
-
if (retMap.rootList) {
|
|
3894
|
-
const parentDependsOn = new Set();
|
|
3895
|
-
// Complete the dependency tree by making parent component depend on the first level
|
|
3896
|
-
for (const p of retMap.rootList) {
|
|
3897
|
-
parentDependsOn.add(`pkg:pypi/${p.name.toLowerCase()}@${p.version}`);
|
|
3898
|
-
}
|
|
3899
|
-
const pdependencies = {
|
|
3900
|
-
ref: parentComponent["bom-ref"],
|
|
3901
|
-
dependsOn: [...parentDependsOn].sort(),
|
|
3902
|
-
};
|
|
3903
|
-
dependencies.splice(0, 0, pdependencies);
|
|
3904
|
-
}
|
|
3905
3868
|
}
|
|
3906
3869
|
options.parentComponent = parentComponent;
|
|
3907
3870
|
} // poetryMode
|
|
@@ -3919,7 +3882,7 @@ export async function createPythonBom(path, options) {
|
|
|
3919
3882
|
for (const wf of whlFiles) {
|
|
3920
3883
|
const mData = await readZipEntry(wf, "METADATA");
|
|
3921
3884
|
if (mData) {
|
|
3922
|
-
const dlist = parseBdistMetadata(
|
|
3885
|
+
const dlist = parseBdistMetadata(join(wf, "METADATA"), mData);
|
|
3923
3886
|
if (dlist?.length) {
|
|
3924
3887
|
pkgList = pkgList.concat(dlist);
|
|
3925
3888
|
}
|
|
@@ -4001,29 +3964,29 @@ export async function createPythonBom(path, options) {
|
|
|
4001
3964
|
for (const f of reqFiles) {
|
|
4002
3965
|
const basePath = dirname(f);
|
|
4003
3966
|
if (options.installDeps) {
|
|
4004
|
-
const
|
|
3967
|
+
const rpkgMap = await getPipFrozenTree(
|
|
4005
3968
|
basePath,
|
|
4006
3969
|
f,
|
|
4007
3970
|
tempDir,
|
|
4008
3971
|
parentComponent,
|
|
4009
3972
|
);
|
|
4010
|
-
if (
|
|
4011
|
-
pkgList = pkgList.concat(
|
|
3973
|
+
if (rpkgMap.pkgList?.length) {
|
|
3974
|
+
pkgList = pkgList.concat(rpkgMap.pkgList);
|
|
4012
3975
|
pkgList = trimComponents(pkgList);
|
|
4013
3976
|
}
|
|
4014
|
-
if (
|
|
4015
|
-
formulationList = formulationList.concat(
|
|
3977
|
+
if (rpkgMap.formulationList?.length) {
|
|
3978
|
+
formulationList = formulationList.concat(rpkgMap.formulationList);
|
|
4016
3979
|
formulationList = trimComponents(formulationList);
|
|
4017
3980
|
}
|
|
4018
|
-
if (
|
|
3981
|
+
if (rpkgMap.dependenciesList) {
|
|
4019
3982
|
dependencies = mergeDependencies(
|
|
4020
3983
|
dependencies,
|
|
4021
|
-
|
|
3984
|
+
rpkgMap.dependenciesList,
|
|
4022
3985
|
parentComponent,
|
|
4023
3986
|
);
|
|
4024
3987
|
}
|
|
4025
3988
|
// Add the root packages from this file to the parent's dependencies
|
|
4026
|
-
for (const p of
|
|
3989
|
+
for (const p of rpkgMap.rootList) {
|
|
4027
3990
|
if (
|
|
4028
3991
|
parentComponent &&
|
|
4029
3992
|
p.name === parentComponent.name &&
|
|
@@ -4046,12 +4009,42 @@ export async function createPythonBom(path, options) {
|
|
|
4046
4009
|
parentComponent,
|
|
4047
4010
|
);
|
|
4048
4011
|
}
|
|
4049
|
-
|
|
4012
|
+
if (pkgMap) {
|
|
4013
|
+
// Complete the dependency tree by making parent component depend on the first level
|
|
4014
|
+
for (const p of pkgMap.rootList) {
|
|
4015
|
+
if (
|
|
4016
|
+
parentComponent &&
|
|
4017
|
+
p.name === parentComponent.name &&
|
|
4018
|
+
(p.version === parentComponent.version || p.version === "latest")
|
|
4019
|
+
) {
|
|
4020
|
+
continue;
|
|
4021
|
+
}
|
|
4022
|
+
parentDependsOn.add(`pkg:pypi/${p.name.toLowerCase()}@${p.version}`);
|
|
4023
|
+
}
|
|
4024
|
+
if (pkgMap?.pkgList?.length) {
|
|
4025
|
+
pkgList = pkgList.concat(pkgMap.pkgList);
|
|
4026
|
+
}
|
|
4027
|
+
if (pkgMap?.formulationList?.length) {
|
|
4028
|
+
formulationList = formulationList.concat(pkgMap.formulationList);
|
|
4029
|
+
}
|
|
4030
|
+
if (pkgMap?.dependenciesList) {
|
|
4031
|
+
dependencies = mergeDependencies(
|
|
4032
|
+
dependencies,
|
|
4033
|
+
pkgMap.dependenciesList,
|
|
4034
|
+
parentComponent,
|
|
4035
|
+
);
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4050
4038
|
// ATOM parsedeps block
|
|
4051
4039
|
// Atom parsedeps slices can be used to identify packages that are not declared in manifests
|
|
4052
4040
|
// Since it is a slow operation, we only use it as a fallback or in deep mode
|
|
4053
4041
|
// This change was made in 10.9.2 release onwards
|
|
4054
4042
|
if (options.deep || !pkgList.length) {
|
|
4043
|
+
if (!pkgList.length) {
|
|
4044
|
+
thoughtLog(
|
|
4045
|
+
"I couldn't find any components yet. Let's try static analysis with atom parsedeps command.",
|
|
4046
|
+
);
|
|
4047
|
+
}
|
|
4055
4048
|
const retMap = await getPyModules(path, pkgList, options);
|
|
4056
4049
|
// We need to patch the existing package list to add ImportedModules for evinse to work
|
|
4057
4050
|
if (retMap.modList?.length) {
|
|
@@ -4100,32 +4093,6 @@ export async function createPythonBom(path, options) {
|
|
|
4100
4093
|
}
|
|
4101
4094
|
}
|
|
4102
4095
|
// ATOM parsedeps block
|
|
4103
|
-
if (pkgMap) {
|
|
4104
|
-
// Complete the dependency tree by making parent component depend on the first level
|
|
4105
|
-
for (const p of pkgMap.rootList) {
|
|
4106
|
-
if (
|
|
4107
|
-
parentComponent &&
|
|
4108
|
-
p.name === parentComponent.name &&
|
|
4109
|
-
(p.version === parentComponent.version || p.version === "latest")
|
|
4110
|
-
) {
|
|
4111
|
-
continue;
|
|
4112
|
-
}
|
|
4113
|
-
parentDependsOn.add(`pkg:pypi/${p.name.toLowerCase()}@${p.version}`);
|
|
4114
|
-
}
|
|
4115
|
-
if (pkgMap?.pkgList?.length) {
|
|
4116
|
-
pkgList = pkgList.concat(pkgMap.pkgList);
|
|
4117
|
-
}
|
|
4118
|
-
if (pkgMap?.formulationList?.length) {
|
|
4119
|
-
formulationList = formulationList.concat(pkgMap.formulationList);
|
|
4120
|
-
}
|
|
4121
|
-
if (pkgMap?.dependenciesList) {
|
|
4122
|
-
dependencies = mergeDependencies(
|
|
4123
|
-
dependencies,
|
|
4124
|
-
pkgMap.dependenciesList,
|
|
4125
|
-
parentComponent,
|
|
4126
|
-
);
|
|
4127
|
-
}
|
|
4128
|
-
}
|
|
4129
4096
|
let parentPresent = false;
|
|
4130
4097
|
for (const d of dependencies) {
|
|
4131
4098
|
if (d.ref === parentComponent["bom-ref"]) {
|
|
@@ -4144,7 +4111,6 @@ export async function createPythonBom(path, options) {
|
|
|
4144
4111
|
}
|
|
4145
4112
|
}
|
|
4146
4113
|
}
|
|
4147
|
-
|
|
4148
4114
|
// Final fallback is to manually parse setup.py if we still
|
|
4149
4115
|
// have an empty list
|
|
4150
4116
|
if (!pkgList.length && setupPyMode) {
|
|
@@ -4217,6 +4183,7 @@ export async function createPythonBom(path, options) {
|
|
|
4217
4183
|
*
|
|
4218
4184
|
* @param {string} path to the project
|
|
4219
4185
|
* @param {Object} options Parse options from the cli
|
|
4186
|
+
* @returns {Promise<Object | undefined>} Promise resolving to a BOM object or `undefined`
|
|
4220
4187
|
*/
|
|
4221
4188
|
export async function createGoBom(path, options) {
|
|
4222
4189
|
let pkgList = [];
|
|
@@ -4643,6 +4610,7 @@ export async function createGoBom(path, options) {
|
|
|
4643
4610
|
*
|
|
4644
4611
|
* @param {string} path to the project
|
|
4645
4612
|
* @param {Object} options Parse options from the cli
|
|
4613
|
+
* @returns {Promise<Object|undefined>} Promise resolving to a BOM object or undefined
|
|
4646
4614
|
*/
|
|
4647
4615
|
export async function createRustBom(path, options) {
|
|
4648
4616
|
let pkgList = [];
|
|
@@ -4782,6 +4750,7 @@ export async function createRustBom(path, options) {
|
|
|
4782
4750
|
*
|
|
4783
4751
|
* @param {string} path to the project
|
|
4784
4752
|
* @param {Object} options Parse options from the cli
|
|
4753
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
4785
4754
|
*/
|
|
4786
4755
|
export async function createDartBom(path, options) {
|
|
4787
4756
|
const pubFiles = getAllFiles(
|
|
@@ -4853,6 +4822,7 @@ export async function createDartBom(path, options) {
|
|
|
4853
4822
|
*
|
|
4854
4823
|
* @param {string} path to the project
|
|
4855
4824
|
* @param {Object} options Parse options from the cli
|
|
4825
|
+
* @returns {Object} BOM object
|
|
4856
4826
|
*/
|
|
4857
4827
|
export function createCppBom(path, options) {
|
|
4858
4828
|
let parentComponent;
|
|
@@ -5064,6 +5034,7 @@ export function createCppBom(path, options) {
|
|
|
5064
5034
|
*
|
|
5065
5035
|
* @param {string} path to the project
|
|
5066
5036
|
* @param {Object} options Parse options from the cli
|
|
5037
|
+
* @returns {Object} BOM object
|
|
5067
5038
|
*/
|
|
5068
5039
|
export function createClojureBom(path, options) {
|
|
5069
5040
|
const ednFiles = getAllFiles(
|
|
@@ -5181,6 +5152,7 @@ export function createClojureBom(path, options) {
|
|
|
5181
5152
|
*
|
|
5182
5153
|
* @param {string} path to the project
|
|
5183
5154
|
* @param {Object} options Parse options from the cli
|
|
5155
|
+
* @returns {Object} BOM object
|
|
5184
5156
|
*/
|
|
5185
5157
|
export function createHaskellBom(path, options) {
|
|
5186
5158
|
const cabalFiles = getAllFiles(
|
|
@@ -5213,6 +5185,7 @@ export function createHaskellBom(path, options) {
|
|
|
5213
5185
|
*
|
|
5214
5186
|
* @param {string} path to the project
|
|
5215
5187
|
* @param {Object} options Parse options from the cli
|
|
5188
|
+
* @returns {Object} BOM object
|
|
5216
5189
|
*/
|
|
5217
5190
|
export function createElixirBom(path, options) {
|
|
5218
5191
|
const mixFiles = getAllFiles(
|
|
@@ -5245,6 +5218,7 @@ export function createElixirBom(path, options) {
|
|
|
5245
5218
|
*
|
|
5246
5219
|
* @param {string} path to the project
|
|
5247
5220
|
* @param {Object} options Parse options from the cli
|
|
5221
|
+
* @returns {Object} BOM object
|
|
5248
5222
|
*/
|
|
5249
5223
|
export function createGitHubBom(path, options) {
|
|
5250
5224
|
const ghactionFiles = getAllFiles(
|
|
@@ -5276,6 +5250,7 @@ export function createGitHubBom(path, options) {
|
|
|
5276
5250
|
*
|
|
5277
5251
|
* @param {string} path to the project
|
|
5278
5252
|
* @param {Object} options Parse options from the cli
|
|
5253
|
+
* @returns {Object} BOM object
|
|
5279
5254
|
*/
|
|
5280
5255
|
export function createCloudBuildBom(path, options) {
|
|
5281
5256
|
const cbFiles = getAllFiles(path, "cloudbuild.yml", options);
|
|
@@ -5304,6 +5279,7 @@ export function createCloudBuildBom(path, options) {
|
|
|
5304
5279
|
*
|
|
5305
5280
|
* @param {string} _path to the project
|
|
5306
5281
|
* @param {Object} options Parse options from the cli
|
|
5282
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
5307
5283
|
*/
|
|
5308
5284
|
export function createOSBom(_path, options) {
|
|
5309
5285
|
console.warn(
|
|
@@ -5362,6 +5338,7 @@ export function createOSBom(_path, options) {
|
|
|
5362
5338
|
*
|
|
5363
5339
|
* @param {string} path to the project
|
|
5364
5340
|
* @param {Object} options Parse options from the cli
|
|
5341
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
5365
5342
|
*/
|
|
5366
5343
|
export async function createJenkinsBom(path, options) {
|
|
5367
5344
|
let pkgList = [];
|
|
@@ -5411,6 +5388,7 @@ export async function createJenkinsBom(path, options) {
|
|
|
5411
5388
|
*
|
|
5412
5389
|
* @param {string} path to the project
|
|
5413
5390
|
* @param {Object} options Parse options from the cli
|
|
5391
|
+
* @returns {Object} BOM object
|
|
5414
5392
|
*/
|
|
5415
5393
|
export function createHelmBom(path, options) {
|
|
5416
5394
|
let pkgList = [];
|
|
@@ -5443,6 +5421,7 @@ export function createHelmBom(path, options) {
|
|
|
5443
5421
|
*
|
|
5444
5422
|
* @param {string} path to the project
|
|
5445
5423
|
* @param {Object} options Parse options from the cli
|
|
5424
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
5446
5425
|
*/
|
|
5447
5426
|
export async function createSwiftBom(path, options) {
|
|
5448
5427
|
const swiftFiles = getAllFiles(
|
|
@@ -5587,6 +5566,7 @@ export async function createSwiftBom(path, options) {
|
|
|
5587
5566
|
*
|
|
5588
5567
|
* @param {string} path to the project
|
|
5589
5568
|
* @param {Object} options Parse options from the cli
|
|
5569
|
+
* @returns {Promise<Object | undefined>} Promise resolving to a BOM object, or `undefined` when no Podfiles are found
|
|
5590
5570
|
*/
|
|
5591
5571
|
export async function createCocoaBom(path, options) {
|
|
5592
5572
|
const cocoaFiles = getAllFiles(
|
|
@@ -5603,7 +5583,7 @@ export async function createCocoaBom(path, options) {
|
|
|
5603
5583
|
for (const podFile of cocoaFiles) {
|
|
5604
5584
|
const projectPath = dirname(podFile);
|
|
5605
5585
|
const lockFile = `${podFile}.lock`;
|
|
5606
|
-
if (!
|
|
5586
|
+
if (!safeExistsSync(lockFile) || options.deep) {
|
|
5607
5587
|
if (options.installDeps) {
|
|
5608
5588
|
executePodCommand(["install"], projectPath, options);
|
|
5609
5589
|
} else {
|
|
@@ -5741,6 +5721,7 @@ export async function createCocoaBom(path, options) {
|
|
|
5741
5721
|
*
|
|
5742
5722
|
* @param {string} path to the project
|
|
5743
5723
|
* @param {Object} options Parse options from the cli
|
|
5724
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
5744
5725
|
*/
|
|
5745
5726
|
export async function createNixBom(path, options) {
|
|
5746
5727
|
let pkgList = [];
|
|
@@ -5863,6 +5844,7 @@ export async function createNixBom(path, options) {
|
|
|
5863
5844
|
*
|
|
5864
5845
|
* @param {string} path to the project
|
|
5865
5846
|
* @param {Object} options Parse options from the cli
|
|
5847
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
5866
5848
|
*/
|
|
5867
5849
|
export async function createCaxaBom(path, options) {
|
|
5868
5850
|
let pkgList = [];
|
|
@@ -5914,6 +5896,7 @@ export async function createCaxaBom(path, options) {
|
|
|
5914
5896
|
*
|
|
5915
5897
|
* @param {string} path to the project
|
|
5916
5898
|
* @param {Object} options Parse options from the cli
|
|
5899
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
5917
5900
|
*/
|
|
5918
5901
|
export async function createContainerSpecLikeBom(path, options) {
|
|
5919
5902
|
let services = [];
|
|
@@ -6242,6 +6225,7 @@ export async function createContainerSpecLikeBom(path, options) {
|
|
|
6242
6225
|
*
|
|
6243
6226
|
* @param {string} path to the project
|
|
6244
6227
|
* @param {Object} options Parse options from the cli
|
|
6228
|
+
* @returns {Object} BOM object
|
|
6245
6229
|
*/
|
|
6246
6230
|
export function createPHPBom(path, options) {
|
|
6247
6231
|
let dependencies = [];
|
|
@@ -6349,7 +6333,7 @@ export function createPHPBom(path, options) {
|
|
|
6349
6333
|
// Track all the modules in a mono-repo
|
|
6350
6334
|
if (!Object.keys(parentComponent).length) {
|
|
6351
6335
|
parentComponent = { ...moduleParent };
|
|
6352
|
-
} else {
|
|
6336
|
+
} else if (moduleParent?.["bom-ref"]) {
|
|
6353
6337
|
parentComponent.components = parentComponent.components || [];
|
|
6354
6338
|
parentComponent.components.push(moduleParent);
|
|
6355
6339
|
}
|
|
@@ -6365,7 +6349,9 @@ export function createPHPBom(path, options) {
|
|
|
6365
6349
|
dependencies.splice(0, 0, {
|
|
6366
6350
|
ref: moduleParent["bom-ref"],
|
|
6367
6351
|
dependsOn: [
|
|
6368
|
-
...new Set(
|
|
6352
|
+
...new Set(
|
|
6353
|
+
retMap.rootList.map((p) => p["bom-ref"]).filter(Boolean),
|
|
6354
|
+
),
|
|
6369
6355
|
].sort(),
|
|
6370
6356
|
});
|
|
6371
6357
|
}
|
|
@@ -6378,9 +6364,9 @@ export function createPHPBom(path, options) {
|
|
|
6378
6364
|
}
|
|
6379
6365
|
// Complete the root dependency tree
|
|
6380
6366
|
if (parentComponent?.components?.length) {
|
|
6381
|
-
const parentDependsOn = parentComponent.components
|
|
6382
|
-
(d) => d["bom-ref"]
|
|
6383
|
-
|
|
6367
|
+
const parentDependsOn = parentComponent.components
|
|
6368
|
+
.map((d) => d["bom-ref"])
|
|
6369
|
+
.filter(Boolean);
|
|
6384
6370
|
dependencies = mergeDependencies(
|
|
6385
6371
|
[{ ref: parentComponent["bom-ref"], dependsOn: parentDependsOn }],
|
|
6386
6372
|
dependencies,
|
|
@@ -6402,6 +6388,7 @@ export function createPHPBom(path, options) {
|
|
|
6402
6388
|
*
|
|
6403
6389
|
* @param {string} path to the project
|
|
6404
6390
|
* @param {Object} options Parse options from the cli
|
|
6391
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
6405
6392
|
*/
|
|
6406
6393
|
export async function createRubyBom(path, options) {
|
|
6407
6394
|
// We can look for gem files within node_modules directory
|
|
@@ -6655,6 +6642,7 @@ export async function createRubyBom(path, options) {
|
|
|
6655
6642
|
*
|
|
6656
6643
|
* @param {string} path to the project
|
|
6657
6644
|
* @param {Object} options Parse options from the cli
|
|
6645
|
+
* @returns {Promise<Object|undefined>} Promise resolving to BOM object
|
|
6658
6646
|
*/
|
|
6659
6647
|
export async function createCsharpBom(path, options) {
|
|
6660
6648
|
let manifestFiles = [];
|
|
@@ -7090,11 +7078,202 @@ export async function createCsharpBom(path, options) {
|
|
|
7090
7078
|
});
|
|
7091
7079
|
}
|
|
7092
7080
|
|
|
7081
|
+
/**
|
|
7082
|
+
* Function to create BOM for VS Code / IDE extensions.
|
|
7083
|
+
* Supports two modes:
|
|
7084
|
+
* 1. Directory scan: Discovers `.vsix` files and installed extension directories
|
|
7085
|
+
* 2. IDE discovery: Automatically finds extensions installed by known IDEs
|
|
7086
|
+
*
|
|
7087
|
+
* @param {string} path to the project or directory to scan
|
|
7088
|
+
* @param {Object} options Parse options from the cli
|
|
7089
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
7090
|
+
*/
|
|
7091
|
+
export async function createVscodeExtensionBom(path, options) {
|
|
7092
|
+
let pkgList = [];
|
|
7093
|
+
let dependencies = [];
|
|
7094
|
+
const tempDirs = [];
|
|
7095
|
+
|
|
7096
|
+
// Mode 1: Scan for .vsix files in the given directory, or treat the input
|
|
7097
|
+
// path as a single .vsix file.
|
|
7098
|
+
let vsixFiles = [];
|
|
7099
|
+
if (path.endsWith(".vsix")) {
|
|
7100
|
+
vsixFiles = [resolve(path)];
|
|
7101
|
+
} else {
|
|
7102
|
+
vsixFiles = getAllFiles(
|
|
7103
|
+
path,
|
|
7104
|
+
`${options.multiProject ? "**/" : ""}*.vsix`,
|
|
7105
|
+
options,
|
|
7106
|
+
);
|
|
7107
|
+
}
|
|
7108
|
+
if (vsixFiles.length) {
|
|
7109
|
+
if (DEBUG_MODE) {
|
|
7110
|
+
console.log(`Found ${vsixFiles.length} .vsix file(s) to parse`);
|
|
7111
|
+
}
|
|
7112
|
+
for (const f of vsixFiles) {
|
|
7113
|
+
if (DEBUG_MODE) {
|
|
7114
|
+
console.log(`Parsing ${f}`);
|
|
7115
|
+
}
|
|
7116
|
+
// Get the extension component metadata
|
|
7117
|
+
const component = await parseVsixFile(f);
|
|
7118
|
+
if (component) {
|
|
7119
|
+
pkgList.push(component);
|
|
7120
|
+
}
|
|
7121
|
+
// Extract the vsix to a temp dir and run deep analysis
|
|
7122
|
+
const extractedDir = await extractVsixToTempDir(f);
|
|
7123
|
+
if (extractedDir) {
|
|
7124
|
+
tempDirs.push(extractedDir);
|
|
7125
|
+
const deepResult = await analyzeExtensionDir(extractedDir, options);
|
|
7126
|
+
if (deepResult.pkgList.length) {
|
|
7127
|
+
pkgList = pkgList.concat(deepResult.pkgList);
|
|
7128
|
+
}
|
|
7129
|
+
if (deepResult.dependencies.length) {
|
|
7130
|
+
dependencies = mergeDependencies(
|
|
7131
|
+
dependencies,
|
|
7132
|
+
deepResult.dependencies,
|
|
7133
|
+
);
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
}
|
|
7137
|
+
}
|
|
7138
|
+
|
|
7139
|
+
// Mode 2: Auto-discover extensions from known IDE locations
|
|
7140
|
+
if (options.deep || options.projectType?.includes("ide-extensions")) {
|
|
7141
|
+
const ideDirs = discoverIdeExtensionDirs();
|
|
7142
|
+
if (ideDirs.length) {
|
|
7143
|
+
if (DEBUG_MODE) {
|
|
7144
|
+
console.log(
|
|
7145
|
+
`Discovered IDE extension directories: ${ideDirs.map((d) => `${d.name}: ${d.dir}`).join(", ")}`,
|
|
7146
|
+
);
|
|
7147
|
+
}
|
|
7148
|
+
const ideExtensions = collectInstalledExtensions(ideDirs);
|
|
7149
|
+
if (ideExtensions.length) {
|
|
7150
|
+
if (DEBUG_MODE) {
|
|
7151
|
+
console.log(
|
|
7152
|
+
`Found ${ideExtensions.length} IDE extension(s) from ${ideDirs.length} IDE location(s)`,
|
|
7153
|
+
);
|
|
7154
|
+
}
|
|
7155
|
+
pkgList = pkgList.concat(ideExtensions);
|
|
7156
|
+
// Deep analysis for IDE extension directories
|
|
7157
|
+
for (const ideDir of ideDirs) {
|
|
7158
|
+
await analyzeInstalledExtensionDirs(
|
|
7159
|
+
ideDir.dir,
|
|
7160
|
+
options,
|
|
7161
|
+
pkgList,
|
|
7162
|
+
dependencies,
|
|
7163
|
+
);
|
|
7164
|
+
}
|
|
7165
|
+
}
|
|
7166
|
+
}
|
|
7167
|
+
}
|
|
7168
|
+
|
|
7169
|
+
// Clean up temp directories from vsix extraction
|
|
7170
|
+
for (const td of tempDirs) {
|
|
7171
|
+
cleanupTempDir(td);
|
|
7172
|
+
}
|
|
7173
|
+
pkgList = trimComponents(pkgList);
|
|
7174
|
+
return buildBomNSData(options, pkgList, VSCODE_EXTENSION_PURL_TYPE, {
|
|
7175
|
+
src: path,
|
|
7176
|
+
filename: vsixFiles.join(", "),
|
|
7177
|
+
nsMapping: {},
|
|
7178
|
+
dependencies,
|
|
7179
|
+
});
|
|
7180
|
+
}
|
|
7181
|
+
|
|
7182
|
+
/**
|
|
7183
|
+
* Analyze an extracted extension directory for bundled dependencies.
|
|
7184
|
+
* Looks for npm lock files, node_modules, package.json files, minified JS,
|
|
7185
|
+
* and runs the babel-based analyzer on the source.
|
|
7186
|
+
*
|
|
7187
|
+
* @param {string} extDir Path to the extracted extension directory
|
|
7188
|
+
* @param {Object} options CLI options
|
|
7189
|
+
* @returns {Promise<{pkgList: Object[], dependencies: Object[]}>}
|
|
7190
|
+
*/
|
|
7191
|
+
async function analyzeExtensionDir(extDir, options) {
|
|
7192
|
+
const pkgList = [];
|
|
7193
|
+
let dependencies = [];
|
|
7194
|
+
// Check if the extension directory contains node.js project artifacts
|
|
7195
|
+
const hasPackageJson = safeExistsSync(join(extDir, "package.json"));
|
|
7196
|
+
const hasNodeModules = safeExistsSync(join(extDir, "node_modules"));
|
|
7197
|
+
const hasLockFile =
|
|
7198
|
+
safeExistsSync(join(extDir, "package-lock.json")) ||
|
|
7199
|
+
safeExistsSync(join(extDir, "yarn.lock")) ||
|
|
7200
|
+
safeExistsSync(join(extDir, "pnpm-lock.yaml"));
|
|
7201
|
+
|
|
7202
|
+
// If there are lock files or node_modules, run the full Node.js BOM generator
|
|
7203
|
+
if (hasPackageJson && (hasLockFile || hasNodeModules)) {
|
|
7204
|
+
if (DEBUG_MODE) {
|
|
7205
|
+
console.log(
|
|
7206
|
+
`Running Node.js BOM analysis on extension directory: ${extDir}`,
|
|
7207
|
+
);
|
|
7208
|
+
}
|
|
7209
|
+
const nodeBomOptions = {
|
|
7210
|
+
...options,
|
|
7211
|
+
path: extDir,
|
|
7212
|
+
multiProject: true,
|
|
7213
|
+
installDeps: false,
|
|
7214
|
+
noBabel: false,
|
|
7215
|
+
projectType: ["js"],
|
|
7216
|
+
};
|
|
7217
|
+
const bomData = await createNodejsBom(extDir, nodeBomOptions);
|
|
7218
|
+
if (bomData?.bomJson?.components?.length) {
|
|
7219
|
+
for (const comp of bomData.bomJson.components) {
|
|
7220
|
+
pkgList.push(comp);
|
|
7221
|
+
}
|
|
7222
|
+
}
|
|
7223
|
+
if (bomData?.bomJson?.dependencies?.length) {
|
|
7224
|
+
dependencies = mergeDependencies(
|
|
7225
|
+
dependencies,
|
|
7226
|
+
bomData.bomJson.dependencies,
|
|
7227
|
+
);
|
|
7228
|
+
}
|
|
7229
|
+
return { pkgList, dependencies };
|
|
7230
|
+
}
|
|
7231
|
+
return { pkgList, dependencies };
|
|
7232
|
+
}
|
|
7233
|
+
|
|
7234
|
+
/**
|
|
7235
|
+
* Run deep analysis on installed extension subdirectories within a parent
|
|
7236
|
+
* extensions directory. Each subdirectory represents an installed extension.
|
|
7237
|
+
*
|
|
7238
|
+
* @param {string} extensionsDir Parent directory containing extension subdirs
|
|
7239
|
+
* @param {Object} options CLI options
|
|
7240
|
+
* @param {Object[]} pkgList Mutable array to push discovered components into
|
|
7241
|
+
* @param {Object[]} dependencies Mutable array to merge dependencies into
|
|
7242
|
+
*/
|
|
7243
|
+
async function analyzeInstalledExtensionDirs(
|
|
7244
|
+
extensionsDir,
|
|
7245
|
+
options,
|
|
7246
|
+
pkgList,
|
|
7247
|
+
dependencies,
|
|
7248
|
+
) {
|
|
7249
|
+
let entries;
|
|
7250
|
+
try {
|
|
7251
|
+
entries = readdirSync(extensionsDir, { withFileTypes: true });
|
|
7252
|
+
} catch (_e) {
|
|
7253
|
+
return;
|
|
7254
|
+
}
|
|
7255
|
+
for (const entry of entries) {
|
|
7256
|
+
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
7257
|
+
continue;
|
|
7258
|
+
}
|
|
7259
|
+
const extDir = join(extensionsDir, entry.name);
|
|
7260
|
+
const deepResult = await analyzeExtensionDir(extDir, options);
|
|
7261
|
+
if (deepResult.pkgList.length) {
|
|
7262
|
+
pkgList.push(...deepResult.pkgList);
|
|
7263
|
+
}
|
|
7264
|
+
if (deepResult.dependencies.length) {
|
|
7265
|
+
const merged = mergeDependencies(dependencies, deepResult.dependencies);
|
|
7266
|
+
dependencies.splice(0, dependencies.length, ...merged);
|
|
7267
|
+
}
|
|
7268
|
+
}
|
|
7269
|
+
}
|
|
7270
|
+
|
|
7093
7271
|
/**
|
|
7094
7272
|
* Function to create bom object for cryptographic certificate files
|
|
7095
7273
|
*
|
|
7096
7274
|
* @param {string} path to the project
|
|
7097
7275
|
* @param {Object} options Parse options from the cli
|
|
7276
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
7098
7277
|
*/
|
|
7099
7278
|
export async function createCryptoCertsBom(path, options) {
|
|
7100
7279
|
const pkgList = [];
|
|
@@ -7131,191 +7310,6 @@ export async function createCryptoCertsBom(path, options) {
|
|
|
7131
7310
|
};
|
|
7132
7311
|
}
|
|
7133
7312
|
|
|
7134
|
-
export function mergeDependencies(
|
|
7135
|
-
dependencies,
|
|
7136
|
-
newDependencies,
|
|
7137
|
-
parentComponent = {},
|
|
7138
|
-
) {
|
|
7139
|
-
if (!parentComponent && DEBUG_MODE) {
|
|
7140
|
-
console.log(
|
|
7141
|
-
"Unable to determine parent component. Dependencies will be flattened.",
|
|
7142
|
-
);
|
|
7143
|
-
}
|
|
7144
|
-
let providesFound = false;
|
|
7145
|
-
const deps_map = {};
|
|
7146
|
-
const provides_map = {};
|
|
7147
|
-
const parentRef = parentComponent?.["bom-ref"]
|
|
7148
|
-
? parentComponent["bom-ref"]
|
|
7149
|
-
: undefined;
|
|
7150
|
-
const combinedDeps = dependencies.concat(newDependencies || []);
|
|
7151
|
-
for (const adep of combinedDeps) {
|
|
7152
|
-
if (!deps_map[adep.ref]) {
|
|
7153
|
-
deps_map[adep.ref] = new Set();
|
|
7154
|
-
}
|
|
7155
|
-
if (!provides_map[adep.ref]) {
|
|
7156
|
-
provides_map[adep.ref] = new Set();
|
|
7157
|
-
}
|
|
7158
|
-
if (adep["dependsOn"]) {
|
|
7159
|
-
for (const eachDepends of adep["dependsOn"]) {
|
|
7160
|
-
if (parentRef && eachDepends) {
|
|
7161
|
-
if (eachDepends.toLowerCase() !== parentRef.toLowerCase()) {
|
|
7162
|
-
deps_map[adep.ref].add(eachDepends);
|
|
7163
|
-
}
|
|
7164
|
-
} else {
|
|
7165
|
-
deps_map[adep.ref].add(eachDepends);
|
|
7166
|
-
}
|
|
7167
|
-
}
|
|
7168
|
-
}
|
|
7169
|
-
if (adep["provides"]) {
|
|
7170
|
-
providesFound = true;
|
|
7171
|
-
for (const eachProvides of adep["provides"]) {
|
|
7172
|
-
if (
|
|
7173
|
-
parentRef &&
|
|
7174
|
-
eachProvides.toLowerCase() !== parentRef.toLowerCase()
|
|
7175
|
-
) {
|
|
7176
|
-
provides_map[adep.ref].add(eachProvides);
|
|
7177
|
-
}
|
|
7178
|
-
}
|
|
7179
|
-
}
|
|
7180
|
-
}
|
|
7181
|
-
const retlist = [];
|
|
7182
|
-
for (const akey of Object.keys(deps_map)) {
|
|
7183
|
-
if (providesFound) {
|
|
7184
|
-
retlist.push({
|
|
7185
|
-
ref: akey,
|
|
7186
|
-
dependsOn: Array.from(deps_map[akey]).sort(),
|
|
7187
|
-
provides: Array.from(provides_map[akey]).sort(),
|
|
7188
|
-
});
|
|
7189
|
-
} else {
|
|
7190
|
-
retlist.push({
|
|
7191
|
-
ref: akey,
|
|
7192
|
-
dependsOn: Array.from(deps_map[akey]).sort(),
|
|
7193
|
-
});
|
|
7194
|
-
}
|
|
7195
|
-
}
|
|
7196
|
-
return retlist;
|
|
7197
|
-
}
|
|
7198
|
-
|
|
7199
|
-
/**
|
|
7200
|
-
* Trim duplicate components by retaining all the properties
|
|
7201
|
-
*
|
|
7202
|
-
* @param {Array} components Components
|
|
7203
|
-
*
|
|
7204
|
-
* @returns {Array} Filtered components
|
|
7205
|
-
*/
|
|
7206
|
-
export function trimComponents(components) {
|
|
7207
|
-
const keyCache = {};
|
|
7208
|
-
const filteredComponents = [];
|
|
7209
|
-
for (const comp of components) {
|
|
7210
|
-
const key = (
|
|
7211
|
-
comp.purl ||
|
|
7212
|
-
comp["bom-ref"] ||
|
|
7213
|
-
comp.name + comp.version
|
|
7214
|
-
).toLowerCase();
|
|
7215
|
-
if (!keyCache[key]) {
|
|
7216
|
-
keyCache[key] = comp;
|
|
7217
|
-
} else {
|
|
7218
|
-
const existingComponent = keyCache[key];
|
|
7219
|
-
// We need to retain any properties that differ
|
|
7220
|
-
if (comp.properties) {
|
|
7221
|
-
if (existingComponent.properties) {
|
|
7222
|
-
for (const newprop of comp.properties) {
|
|
7223
|
-
if (
|
|
7224
|
-
!existingComponent.properties.find(
|
|
7225
|
-
(prop) =>
|
|
7226
|
-
prop.name === newprop.name && prop.value === newprop.value,
|
|
7227
|
-
)
|
|
7228
|
-
) {
|
|
7229
|
-
existingComponent.properties.push(newprop);
|
|
7230
|
-
}
|
|
7231
|
-
}
|
|
7232
|
-
} else {
|
|
7233
|
-
existingComponent.properties = comp.properties;
|
|
7234
|
-
}
|
|
7235
|
-
}
|
|
7236
|
-
// Retain all component.evidence.identity
|
|
7237
|
-
if (comp?.evidence?.identity) {
|
|
7238
|
-
if (!existingComponent.evidence) {
|
|
7239
|
-
existingComponent.evidence = { identity: [] };
|
|
7240
|
-
} else if (!existingComponent?.evidence?.identity) {
|
|
7241
|
-
existingComponent.evidence.identity = [];
|
|
7242
|
-
} else if (
|
|
7243
|
-
existingComponent?.evidence?.identity &&
|
|
7244
|
-
!Array.isArray(existingComponent.evidence.identity)
|
|
7245
|
-
) {
|
|
7246
|
-
existingComponent.evidence.identity = [
|
|
7247
|
-
existingComponent.evidence.identity,
|
|
7248
|
-
];
|
|
7249
|
-
}
|
|
7250
|
-
// comp.evidence.identity can be an array or object
|
|
7251
|
-
// Merge the evidence.identity based on methods or objects
|
|
7252
|
-
const isIdentityArray = Array.isArray(comp.evidence.identity);
|
|
7253
|
-
const identities = isIdentityArray
|
|
7254
|
-
? comp.evidence.identity
|
|
7255
|
-
: [comp.evidence.identity];
|
|
7256
|
-
for (const aident of identities) {
|
|
7257
|
-
let methodBasedMerge = false;
|
|
7258
|
-
if (aident?.methods?.length) {
|
|
7259
|
-
for (const amethod of aident.methods) {
|
|
7260
|
-
for (const existIdent of existingComponent.evidence.identity) {
|
|
7261
|
-
if (existIdent.field === aident.field) {
|
|
7262
|
-
if (!existIdent.methods) {
|
|
7263
|
-
existIdent.methods = [];
|
|
7264
|
-
}
|
|
7265
|
-
let isDup = false;
|
|
7266
|
-
for (const emethod of existIdent.methods) {
|
|
7267
|
-
if (emethod?.value === amethod?.value) {
|
|
7268
|
-
isDup = true;
|
|
7269
|
-
break;
|
|
7270
|
-
}
|
|
7271
|
-
}
|
|
7272
|
-
if (!isDup) {
|
|
7273
|
-
existIdent.methods.push(amethod);
|
|
7274
|
-
}
|
|
7275
|
-
methodBasedMerge = true;
|
|
7276
|
-
}
|
|
7277
|
-
}
|
|
7278
|
-
}
|
|
7279
|
-
}
|
|
7280
|
-
if (!methodBasedMerge && aident.field && aident.confidence) {
|
|
7281
|
-
existingComponent.evidence.identity.push(aident);
|
|
7282
|
-
}
|
|
7283
|
-
}
|
|
7284
|
-
if (!isIdentityArray) {
|
|
7285
|
-
const firstIdentity = existingComponent.evidence.identity[0];
|
|
7286
|
-
let identConfidence = firstIdentity?.confidence;
|
|
7287
|
-
// We need to set the confidence to the max of all confidences
|
|
7288
|
-
if (firstIdentity?.methods?.length > 1) {
|
|
7289
|
-
for (const aidentMethod of firstIdentity.methods) {
|
|
7290
|
-
if (
|
|
7291
|
-
aidentMethod?.confidence &&
|
|
7292
|
-
aidentMethod.confidence > identConfidence
|
|
7293
|
-
) {
|
|
7294
|
-
identConfidence = aidentMethod.confidence;
|
|
7295
|
-
}
|
|
7296
|
-
}
|
|
7297
|
-
}
|
|
7298
|
-
firstIdentity.confidence = identConfidence;
|
|
7299
|
-
existingComponent.evidence = {
|
|
7300
|
-
identity: firstIdentity,
|
|
7301
|
-
};
|
|
7302
|
-
}
|
|
7303
|
-
}
|
|
7304
|
-
// If the component is required in any of the child projects, then make it required
|
|
7305
|
-
if (
|
|
7306
|
-
existingComponent?.scope !== "required" &&
|
|
7307
|
-
comp?.scope === "required"
|
|
7308
|
-
) {
|
|
7309
|
-
existingComponent.scope = "required";
|
|
7310
|
-
}
|
|
7311
|
-
}
|
|
7312
|
-
}
|
|
7313
|
-
for (const akey of Object.keys(keyCache)) {
|
|
7314
|
-
filteredComponents.push(keyCache[akey]);
|
|
7315
|
-
}
|
|
7316
|
-
return filteredComponents;
|
|
7317
|
-
}
|
|
7318
|
-
|
|
7319
7313
|
/**
|
|
7320
7314
|
* Dedupe components
|
|
7321
7315
|
*
|
|
@@ -7372,11 +7366,13 @@ export function dedupeBom(options, components, parentComponent, dependencies) {
|
|
|
7372
7366
|
*
|
|
7373
7367
|
* @param {string[]} pathList list of to the project
|
|
7374
7368
|
* @param {Object} options Parse options from the cli
|
|
7369
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
7375
7370
|
*/
|
|
7376
7371
|
export async function createMultiXBom(pathList, options) {
|
|
7377
7372
|
let components = [];
|
|
7378
7373
|
let dependencies = [];
|
|
7379
7374
|
let bomData;
|
|
7375
|
+
let formulationList = [];
|
|
7380
7376
|
let parentComponent = determineParentComponent(options) || {};
|
|
7381
7377
|
let parentSubComponents = [];
|
|
7382
7378
|
options.createMultiXBom = true;
|
|
@@ -7587,6 +7583,9 @@ export async function createMultiXBom(pathList, options) {
|
|
|
7587
7583
|
parentSubComponents.push(bomData.parentComponent);
|
|
7588
7584
|
}
|
|
7589
7585
|
}
|
|
7586
|
+
if (bomData?.formulationList?.length) {
|
|
7587
|
+
formulationList = formulationList.concat(bomData.formulationList);
|
|
7588
|
+
}
|
|
7590
7589
|
}
|
|
7591
7590
|
if (hasAnyProjectType(["oci", "go"], options)) {
|
|
7592
7591
|
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
@@ -8079,6 +8078,27 @@ export async function createMultiXBom(pathList, options) {
|
|
|
8079
8078
|
}
|
|
8080
8079
|
}
|
|
8081
8080
|
}
|
|
8081
|
+
if (hasAnyProjectType(["vscode-extension"], options)) {
|
|
8082
|
+
bomData = await createVscodeExtensionBom(path, options);
|
|
8083
|
+
if (bomData?.bomJson?.components?.length) {
|
|
8084
|
+
if (DEBUG_MODE) {
|
|
8085
|
+
console.log(
|
|
8086
|
+
`Found ${bomData.bomJson.components.length} VS Code extension(s) at ${path}`,
|
|
8087
|
+
);
|
|
8088
|
+
}
|
|
8089
|
+
components = components.concat(bomData.bomJson.components);
|
|
8090
|
+
dependencies = mergeDependencies(
|
|
8091
|
+
dependencies,
|
|
8092
|
+
bomData.bomJson.dependencies,
|
|
8093
|
+
);
|
|
8094
|
+
if (
|
|
8095
|
+
bomData.parentComponent &&
|
|
8096
|
+
Object.keys(bomData.parentComponent).length
|
|
8097
|
+
) {
|
|
8098
|
+
parentSubComponents.push(bomData.parentComponent);
|
|
8099
|
+
}
|
|
8100
|
+
}
|
|
8101
|
+
}
|
|
8082
8102
|
// Collect any crypto keys
|
|
8083
8103
|
if (options.specVersion >= 1.6 && options.includeCrypto) {
|
|
8084
8104
|
if (!hasAnyProjectType(["oci"], options, false)) {
|
|
@@ -8177,7 +8197,16 @@ export async function createMultiXBom(pathList, options) {
|
|
|
8177
8197
|
}
|
|
8178
8198
|
}
|
|
8179
8199
|
}
|
|
8180
|
-
|
|
8200
|
+
const multiResult = dedupeBom(
|
|
8201
|
+
options,
|
|
8202
|
+
components,
|
|
8203
|
+
parentComponent,
|
|
8204
|
+
dependencies,
|
|
8205
|
+
);
|
|
8206
|
+
if (formulationList.length) {
|
|
8207
|
+
multiResult.formulationList = formulationList;
|
|
8208
|
+
}
|
|
8209
|
+
return multiResult;
|
|
8181
8210
|
}
|
|
8182
8211
|
|
|
8183
8212
|
/**
|
|
@@ -8185,6 +8214,7 @@ export async function createMultiXBom(pathList, options) {
|
|
|
8185
8214
|
*
|
|
8186
8215
|
* @param {string} path to the project
|
|
8187
8216
|
* @param {Object} options Parse options from the cli
|
|
8217
|
+
* @returns {Promise<Object|undefined>} Promise resolving to BOM object, or undefined if path is not readable
|
|
8188
8218
|
*/
|
|
8189
8219
|
export async function createXBom(path, options) {
|
|
8190
8220
|
try {
|
|
@@ -8423,6 +8453,16 @@ export async function createXBom(path, options) {
|
|
|
8423
8453
|
return await createJenkinsBom(path, options);
|
|
8424
8454
|
}
|
|
8425
8455
|
|
|
8456
|
+
// VS Code extensions (.vsix files)
|
|
8457
|
+
const vsixFiles = getAllFiles(
|
|
8458
|
+
path,
|
|
8459
|
+
`${options.multiProject ? "**/" : ""}*.vsix`,
|
|
8460
|
+
options,
|
|
8461
|
+
);
|
|
8462
|
+
if (vsixFiles.length) {
|
|
8463
|
+
return await createVscodeExtensionBom(path, options);
|
|
8464
|
+
}
|
|
8465
|
+
|
|
8426
8466
|
// Helm charts
|
|
8427
8467
|
const chartFiles = getAllFiles(
|
|
8428
8468
|
path,
|
|
@@ -8530,6 +8570,7 @@ export async function createXBom(path, options) {
|
|
|
8530
8570
|
*
|
|
8531
8571
|
* @param {string} path to the project
|
|
8532
8572
|
* @param {Object} options Parse options from the cli
|
|
8573
|
+
* @returns {Promise<Object>} Promise resolving to BOM object
|
|
8533
8574
|
*/
|
|
8534
8575
|
export async function createBom(path, options) {
|
|
8535
8576
|
let { projectType } = options;
|
|
@@ -8774,6 +8815,9 @@ export async function createBom(path, options) {
|
|
|
8774
8815
|
if (PROJECT_TYPE_ALIASES["caxa"].includes(projectType[0])) {
|
|
8775
8816
|
return await createCaxaBom(path, options);
|
|
8776
8817
|
}
|
|
8818
|
+
if (PROJECT_TYPE_ALIASES["vscode-extension"].includes(projectType[0])) {
|
|
8819
|
+
return await createVscodeExtensionBom(path, options);
|
|
8820
|
+
}
|
|
8777
8821
|
switch (projectType[0]) {
|
|
8778
8822
|
case "jar":
|
|
8779
8823
|
return createJarBom(path, options);
|