@cdxgen/cdxgen 13.5.0 → 13.7.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 +17 -10
- package/bin/audit.js +7 -4
- package/bin/cdxgen.js +260 -37
- package/bin/convert.js +146 -22
- package/bin/evinse.js +28 -3
- package/bin/repl.js +47 -3
- package/bin/sign.js +211 -34
- package/bin/tracebom.js +18 -6
- package/bin/validate.js +24 -3
- package/bin/verify.js +113 -5
- package/data/behavior-taxonomy.schema.json +1491 -0
- package/data/cyclonedx-2.0-bundled.schema.json +18606 -5528
- package/data/cyclonedx-2.0-cryptography-defs.schema.json +857 -0
- package/data/license-aliases.json +24 -2
- package/data/pqc-migration.json +239 -0
- package/data/remediations.json +176 -0
- package/data/rules/ai-bom.yaml +1 -1
- package/data/rules/brotli-decoder.yaml +107 -0
- package/data/rules/cbom-security.yaml +4 -3
- package/data/rules/package-integrity.yaml +28 -0
- package/data/rules/vscode-extensions.yaml +75 -0
- package/data/spdx-license-list.json +121 -7
- package/data/spdx-licenses.json +69 -0
- package/data/spdx.schema.json +845 -2
- package/lib/audit/index.js +25 -3
- package/lib/cli/bomAssembly.js +172 -4
- package/lib/cli/cliOptions.js +121 -17
- package/lib/cli/index.js +720 -902
- package/lib/cli/jsBom.js +156 -64
- package/lib/cli/jvmBom.js +602 -86
- package/lib/cli/managedBom.js +161 -19
- package/lib/cli/nativeBom.js +52 -5
- package/lib/core/boundedFile.js +344 -0
- package/lib/core/brotliProbe.js +182 -0
- package/lib/core/env.js +34 -0
- package/lib/core/fs.js +138 -3
- package/lib/core/globPatterns.js +218 -0
- package/lib/core/httpClient.js +98 -1
- package/lib/core/lockPaths.js +38 -0
- package/lib/core/paths.js +24 -1
- package/lib/core/processTree.js +100 -8
- package/lib/core/projectConfig.js +332 -25
- package/lib/core/toolRegistry.js +184 -0
- package/lib/ecosystems/cppEvidence.js +263 -106
- package/lib/ecosystems/cppIncludeResolver.js +16 -5
- package/lib/ecosystems/denoutils.js +2 -2
- package/lib/ecosystems/ecosystems.js +25 -10
- package/lib/ecosystems/gemHostProbe.js +301 -0
- package/lib/ecosystems/golem.js +3 -8
- package/lib/ecosystems/gradleutils.js +1909 -229
- package/lib/ecosystems/jsEvidence.js +90 -38
- package/lib/ecosystems/kosi.js +3 -5
- package/lib/ecosystems/npmVersionResolution.js +376 -0
- package/lib/ecosystems/parsers-bazel.js +107 -6
- package/lib/ecosystems/parsers-js.js +505 -22
- package/lib/ecosystems/parsers-misc.js +209 -20
- package/lib/ecosystems/parsers-python.js +178 -30
- package/lib/ecosystems/parsers-rust.js +117 -15
- package/lib/ecosystems/pythonutils.js +128 -2
- package/lib/ecosystems/remote/huggingface.js +22 -0
- package/lib/ecosystems/rubyutils.js +538 -31
- package/lib/ecosystems/rusi.js +2 -4
- package/lib/ecosystems/scalacliutils.js +2 -0
- package/lib/ecosystems/scalasem.js +798 -152
- package/lib/ecosystems/vendoredCode.js +237 -3
- package/lib/ecosystems/venvProbe.js +360 -0
- package/lib/evinser/evinser.js +536 -71
- package/lib/evinser/scalasem.js +5 -1
- package/lib/helpers/agenticToolutils.js +525 -94
- package/lib/helpers/bomSigner.js +43 -9
- package/lib/helpers/jssSigner.js +1276 -0
- package/lib/helpers/vsixutils.js +1574 -315
- package/lib/inventory/agentBlueprint.js +193 -0
- package/lib/inventory/agentToolNames.js +145 -0
- package/lib/inventory/aiCollector.js +153 -7
- package/lib/inventory/aiInventory.js +44 -8
- package/lib/inventory/aiProviders.js +324 -0
- package/lib/inventory/atomUtils.js +77 -0
- package/lib/inventory/bomUtils.js +8 -1
- package/lib/inventory/brotliDecoder.js +142 -0
- package/lib/inventory/buildToolRateLimit.js +1 -1
- package/lib/inventory/cbomutils.js +56 -7
- package/lib/inventory/ciParsers/githubActionsLock.js +821 -0
- package/lib/inventory/citations.js +31 -2
- package/lib/inventory/communityAiConfigParser.js +978 -80
- package/lib/inventory/cyclonedx20.js +152 -0
- package/lib/inventory/cyclonedx20Common.js +286 -0
- package/lib/inventory/cyclonedx20Derived.js +159 -0
- package/lib/inventory/cyclonedx20Parties.js +552 -0
- package/lib/inventory/cyclonedx20Read.js +768 -0
- package/lib/inventory/cyclonedx20ReadCommon.js +374 -0
- package/lib/inventory/cyclonedx20ReadComponents.js +692 -0
- package/lib/inventory/cyclonedx20ReadContext.js +380 -0
- package/lib/inventory/cyclonedx20ReadEvidence.js +466 -0
- package/lib/inventory/cyclonedx20ReadSections.js +599 -0
- package/lib/inventory/cyclonedx20Render.js +766 -0
- package/lib/inventory/cyclonedx20RenderComponents.js +716 -0
- package/lib/inventory/cyclonedx20RenderDefinitions.js +405 -0
- package/lib/inventory/cyclonedx20RenderEvidence.js +726 -0
- package/lib/inventory/cyclonedx20RenderHardware.js +203 -0
- package/lib/inventory/cyclonedx20RenderPatents.js +174 -0
- package/lib/inventory/cyclonedx20RenderRefIds.js +314 -0
- package/lib/inventory/cyclonedx20RenderSections.js +499 -0
- package/lib/inventory/cyclonedx20Tables.js +594 -0
- package/lib/inventory/deps.js +141 -13
- package/lib/inventory/dosai.js +375 -6
- package/lib/inventory/dosaiParsers.js +3 -6
- package/lib/inventory/dotnetRuntime.js +367 -0
- package/lib/inventory/ecosystemFormulationParsers.js +979 -0
- package/lib/inventory/envcontext.js +436 -48
- package/lib/inventory/evidenceUtils.js +13 -1
- package/lib/inventory/fetchBatch.js +1 -1
- package/lib/inventory/formulationParsers.js +124 -14
- package/lib/inventory/gtfobins.js +13 -0
- package/lib/inventory/hbom.js +45 -6
- package/lib/inventory/hbomLoader.js +166 -0
- package/lib/inventory/hostRuntimeProbe.js +686 -0
- package/lib/inventory/jvmLocalRepos.js +9 -2
- package/lib/inventory/jvmToolEnv.js +29 -0
- package/lib/inventory/licenseEnhancer.js +12 -0
- package/lib/inventory/mavenCentralMirror.js +534 -0
- package/lib/inventory/mcpConfigParser.js +5 -0
- package/lib/inventory/nodeBuildInfo.js +218 -0
- package/lib/inventory/nodeHostProbe.js +202 -0
- package/lib/inventory/occurrenceKey.js +90 -0
- package/lib/inventory/osPackageResolver.js +182 -0
- package/lib/inventory/phpBuildInfo.js +10 -59
- package/lib/inventory/phpHostProbe.js +32 -488
- package/lib/inventory/pqcMigration.js +426 -0
- package/lib/inventory/rubyBuildInfo.js +972 -0
- package/lib/inventory/rubyHostProbe.js +218 -0
- package/lib/inventory/rustFormulationParser.js +21 -10
- package/lib/inventory/scalaCoords.js +4 -3
- package/lib/inventory/scalaDedupe.js +440 -0
- package/lib/inventory/source.js +114 -8
- package/lib/managers/binary.js +337 -3
- package/lib/managers/docker.js +79 -0
- package/lib/parsers/huggingfaceManifest.js +9 -1
- package/lib/parsers/huggingfaceUtils.js +18 -0
- package/lib/parsers/kiroPermissions.js +166 -0
- package/lib/parsers/largeJson.js +28 -11
- package/lib/parsers/vscodeExtensionId.js +183 -0
- package/lib/server/server.js +48 -35
- package/lib/stages/postgen/postgen.js +41 -1
- package/lib/stages/postgen/ruleEngine.js +13 -5
- package/lib/stages/postgen/specVersionCompat.js +76 -0
- package/lib/stages/pregen/envAudit.js +1 -0
- package/lib/stages/pregen/pregen.js +114 -57
- package/lib/validator/bomValidator.js +14 -2
- package/lib/validator/complianceEngine.js +30 -3
- package/lib/validator/complianceRules.js +1416 -4
- package/lib/validator/generated/validate-1.6.mjs +1 -1
- package/lib/validator/generated/validate-1.7.mjs +1 -1
- package/lib/validator/generated/validate-2.0.mjs +57554 -13851
- package/lib/validator/index.js +49 -8
- package/lib/validator/reporters/json.js +6 -0
- package/package.json +11 -10
- package/types/bin/repl.d.ts.map +1 -1
- package/types/lib/audit/index.d.ts +6 -2
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts +29 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/cliOptions.d.ts +6 -0
- 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 +3 -18
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +15 -0
- 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/boundedFile.d.ts +100 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -0
- package/types/lib/core/brotliProbe.d.ts +37 -0
- package/types/lib/core/brotliProbe.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +14 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts +4 -0
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/globPatterns.d.ts +70 -0
- package/types/lib/core/globPatterns.d.ts.map +1 -0
- package/types/lib/core/httpClient.d.ts +24 -0
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/lockPaths.d.ts +19 -0
- package/types/lib/core/lockPaths.d.ts.map +1 -0
- package/types/lib/core/paths.d.ts +9 -0
- package/types/lib/core/paths.d.ts.map +1 -1
- package/types/lib/core/processTree.d.ts +17 -4
- package/types/lib/core/processTree.d.ts.map +1 -1
- package/types/lib/core/projectConfig.d.ts +47 -8
- package/types/lib/core/projectConfig.d.ts.map +1 -1
- package/types/lib/core/toolRegistry.d.ts +36 -0
- package/types/lib/core/toolRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
- package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +294 -20
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts +14 -1
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmVersionResolution.d.ts +119 -0
- package/types/lib/ecosystems/npmVersionResolution.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +17 -20
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +19 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +10 -0
- 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 +19 -1
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +144 -12
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalasem.d.ts +58 -16
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
- package/types/lib/ecosystems/venvProbe.d.ts +40 -0
- package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +120 -13
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/evinser/scalasem.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +53 -19
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +20 -3
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/helpers/jssSigner.d.ts +288 -0
- package/types/lib/helpers/jssSigner.d.ts.map +1 -0
- package/types/lib/helpers/vsixutils.d.ts +303 -19
- package/types/lib/helpers/vsixutils.d.ts.map +1 -1
- package/types/lib/inventory/agentBlueprint.d.ts +70 -0
- package/types/lib/inventory/agentBlueprint.d.ts.map +1 -0
- package/types/lib/inventory/agentToolNames.d.ts +51 -0
- package/types/lib/inventory/agentToolNames.d.ts.map +1 -0
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/aiProviders.d.ts +85 -0
- package/types/lib/inventory/aiProviders.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +19 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/bomUtils.d.ts.map +1 -1
- package/types/lib/inventory/brotliDecoder.d.ts +58 -0
- package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
- package/types/lib/inventory/cbomutils.d.ts +17 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/ciParsers/githubActionsLock.d.ts +101 -0
- package/types/lib/inventory/ciParsers/githubActionsLock.d.ts.map +1 -0
- package/types/lib/inventory/citations.d.ts +5 -0
- package/types/lib/inventory/citations.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/cyclonedx20.d.ts +99 -0
- package/types/lib/inventory/cyclonedx20.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Common.d.ts +102 -0
- package/types/lib/inventory/cyclonedx20Common.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Derived.d.ts +74 -0
- package/types/lib/inventory/cyclonedx20Derived.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Parties.d.ts +91 -0
- package/types/lib/inventory/cyclonedx20Parties.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Read.d.ts +51 -0
- package/types/lib/inventory/cyclonedx20Read.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadCommon.d.ts +69 -0
- package/types/lib/inventory/cyclonedx20ReadCommon.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadComponents.d.ts +77 -0
- package/types/lib/inventory/cyclonedx20ReadComponents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadContext.d.ts +118 -0
- package/types/lib/inventory/cyclonedx20ReadContext.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts +40 -0
- package/types/lib/inventory/cyclonedx20ReadEvidence.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20ReadSections.d.ts +45 -0
- package/types/lib/inventory/cyclonedx20ReadSections.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Render.d.ts +53 -0
- package/types/lib/inventory/cyclonedx20Render.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderComponents.d.ts +73 -0
- package/types/lib/inventory/cyclonedx20RenderComponents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts +29 -0
- package/types/lib/inventory/cyclonedx20RenderDefinitions.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts +110 -0
- package/types/lib/inventory/cyclonedx20RenderEvidence.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderHardware.d.ts +42 -0
- package/types/lib/inventory/cyclonedx20RenderHardware.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderPatents.d.ts +26 -0
- package/types/lib/inventory/cyclonedx20RenderPatents.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts +55 -0
- package/types/lib/inventory/cyclonedx20RenderRefIds.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20RenderSections.d.ts +45 -0
- package/types/lib/inventory/cyclonedx20RenderSections.d.ts.map +1 -0
- package/types/lib/inventory/cyclonedx20Tables.d.ts +134 -0
- package/types/lib/inventory/cyclonedx20Tables.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +35 -2
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +36 -1
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +91 -0
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
- package/types/lib/inventory/ecosystemFormulationParsers.d.ts +73 -0
- package/types/lib/inventory/ecosystemFormulationParsers.d.ts.map +1 -0
- package/types/lib/inventory/envcontext.d.ts +74 -12
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/gtfobins.d.ts.map +1 -1
- package/types/lib/inventory/hbom.d.ts +11 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -1
- package/types/lib/inventory/hbomLoader.d.ts +54 -0
- package/types/lib/inventory/hbomLoader.d.ts.map +1 -1
- package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
- package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
- package/types/lib/inventory/jvmLocalRepos.d.ts +5 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
- package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
- package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
- package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/occurrenceKey.d.ts +52 -0
- package/types/lib/inventory/occurrenceKey.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +38 -0
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -1
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -1
- package/types/lib/inventory/pqcMigration.d.ts +84 -0
- package/types/lib/inventory/pqcMigration.d.ts.map +1 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
- package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts +7 -2
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -1
- package/types/lib/inventory/scalaDedupe.d.ts +43 -0
- package/types/lib/inventory/scalaDedupe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +25 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +12 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceManifest.d.ts +10 -1
- package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts +12 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/kiroPermissions.d.ts +62 -0
- package/types/lib/parsers/kiroPermissions.d.ts.map +1 -0
- package/types/lib/parsers/largeJson.d.ts +4 -3
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/parsers/vscodeExtensionId.d.ts +48 -0
- package/types/lib/parsers/vscodeExtensionId.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +7 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/ruleEngine.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts +25 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +12 -8
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
- package/types/lib/validator/bomValidator.d.ts +5 -1
- package/types/lib/validator/bomValidator.d.ts.map +1 -1
- package/types/lib/validator/complianceEngine.d.ts +4 -4
- package/types/lib/validator/complianceEngine.d.ts.map +1 -1
- package/types/lib/validator/complianceRules.d.ts +14 -2
- package/types/lib/validator/complianceRules.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +10 -0
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
|
@@ -0,0 +1,979 @@
|
|
|
1
|
+
import { basename, dirname, isAbsolute, relative, resolve } from "node:path";
|
|
2
|
+
|
|
3
|
+
import { readRegularTextFile } from "../core/boundedFile.js";
|
|
4
|
+
|
|
5
|
+
// Build-input formulation parsers for the ecosystems whose build files cdxgen
|
|
6
|
+
// can read without running a build: make and CMake for C/C++, go.mod for Go,
|
|
7
|
+
// pip requirements and constraints files for Python, and project.assets.json
|
|
8
|
+
// plus .csproj for .NET. Rust already has its own parser
|
|
9
|
+
// (rustFormulationParser); Go binaries are read by the OBOM path through the
|
|
10
|
+
// bounded buildinfo reader, so a source scan records the module file here.
|
|
11
|
+
//
|
|
12
|
+
// Every parser takes `parse(files, options, context)`, where context.rootDir
|
|
13
|
+
// is the scanned project root. Files are read only when they are regular
|
|
14
|
+
// files under that root, and components record their path relative to it.
|
|
15
|
+
|
|
16
|
+
// A build file larger than this is machine generated (or not a build file at
|
|
17
|
+
// all), and reading it would only burn memory, so it is skipped unread.
|
|
18
|
+
export const MAX_READ_BYTES = 2 * 1024 * 1024;
|
|
19
|
+
|
|
20
|
+
// `dotnet restore` output grows with the dependency graph, and tens of
|
|
21
|
+
// megabytes are common in .NET monorepos, so it gets its own, larger bound.
|
|
22
|
+
export const MAX_ASSETS_READ_BYTES = 32 * 1024 * 1024;
|
|
23
|
+
|
|
24
|
+
// No build-file line worth reading is longer than this. Longer lines are
|
|
25
|
+
// skipped before any tokenising, so a crafted file cannot make a parser
|
|
26
|
+
// spend more than linear time on one line.
|
|
27
|
+
const MAX_LINE_LENGTH = 4096;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The path of a build input relative to the scanned project root, with
|
|
31
|
+
* forward slashes, as recorded in bom-refs and `internal:SrcFile`. A file
|
|
32
|
+
* outside the root, or a call without a root, records only its file name,
|
|
33
|
+
* so no path of the scanning machine reaches the BOM.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} filePath file the component describes
|
|
36
|
+
* @param {string} [rootDir] scanned project root
|
|
37
|
+
* @returns {string} project-relative path, or the file name
|
|
38
|
+
*/
|
|
39
|
+
export function formulationSourcePath(filePath, rootDir) {
|
|
40
|
+
if (!filePath) {
|
|
41
|
+
return filePath;
|
|
42
|
+
}
|
|
43
|
+
if (rootDir) {
|
|
44
|
+
const rel = relative(resolve(rootDir), resolve(filePath));
|
|
45
|
+
if (rel && !isAbsolute(rel) && rel.split(/[\\/]/)[0] !== "..") {
|
|
46
|
+
return rel.replaceAll("\\", "/");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return basename(filePath);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Read a build file when it is a regular file within the size bound. A
|
|
54
|
+
* symlink, a FIFO, a file that resolves outside rootDir and an oversize
|
|
55
|
+
* file are not read.
|
|
56
|
+
*
|
|
57
|
+
* @param {string} filePath file to read
|
|
58
|
+
* @param {string} [rootDir] root the file must stay under
|
|
59
|
+
* @param {number} [maxBytes] largest size read, in bytes
|
|
60
|
+
* @returns {string|undefined} file text, or undefined
|
|
61
|
+
*/
|
|
62
|
+
function readBounded(filePath, rootDir, maxBytes = MAX_READ_BYTES) {
|
|
63
|
+
return readRegularTextFile(filePath, maxBytes, rootDir);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Split text into lines, dropping lines longer than MAX_LINE_LENGTH.
|
|
68
|
+
*
|
|
69
|
+
* @param {string} content file text
|
|
70
|
+
* @returns {string[]} lines of reasonable length
|
|
71
|
+
*/
|
|
72
|
+
function boundedLines(content) {
|
|
73
|
+
return content
|
|
74
|
+
.split("\n")
|
|
75
|
+
.map((line) => (line.endsWith("\r") ? line.slice(0, -1) : line))
|
|
76
|
+
.filter((line) => line.length <= MAX_LINE_LENGTH);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Join lines that end in a backslash with the line after them, the way make
|
|
81
|
+
* and pip read their files. A line longer than MAX_LINE_LENGTH contributes no
|
|
82
|
+
* text but still continues when it ends in a backslash, so its neighbours
|
|
83
|
+
* stay where the tool would put them.
|
|
84
|
+
*
|
|
85
|
+
* @param {string} content file text
|
|
86
|
+
* @param {object} rules joining rules
|
|
87
|
+
* @param {boolean} rules.commentLinesContinue true for make, where a comment
|
|
88
|
+
* ending in a backslash continues; false for pip, where a comment line
|
|
89
|
+
* never continues and ends a continuation in progress
|
|
90
|
+
* @returns {string[]} logical lines
|
|
91
|
+
*/
|
|
92
|
+
function joinContinuationLines(content, { commentLinesContinue }) {
|
|
93
|
+
const logicalLines = [];
|
|
94
|
+
let pending;
|
|
95
|
+
for (const rawLine of content.split("\n")) {
|
|
96
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
97
|
+
const overlong = line.length > MAX_LINE_LENGTH;
|
|
98
|
+
const text = overlong ? "" : line;
|
|
99
|
+
const isComment = !commentLinesContinue && text.trimStart().startsWith("#");
|
|
100
|
+
const continues = line.trimEnd().endsWith("\\") && !isComment;
|
|
101
|
+
let piece = text;
|
|
102
|
+
if (continues && !overlong) {
|
|
103
|
+
piece = text.trimEnd().slice(0, -1);
|
|
104
|
+
}
|
|
105
|
+
const joined =
|
|
106
|
+
pending === undefined ? piece : `${pending} ${piece.trimStart()}`;
|
|
107
|
+
if (continues) {
|
|
108
|
+
pending = joined;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
logicalLines.push(joined);
|
|
112
|
+
pending = undefined;
|
|
113
|
+
}
|
|
114
|
+
if (pending !== undefined) {
|
|
115
|
+
logicalLines.push(pending);
|
|
116
|
+
}
|
|
117
|
+
return logicalLines;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Split a line on runs of whitespace.
|
|
122
|
+
*
|
|
123
|
+
* @param {string} line line text
|
|
124
|
+
* @returns {string[]} non-empty tokens
|
|
125
|
+
*/
|
|
126
|
+
function tokens(line) {
|
|
127
|
+
return line.split(/\s+/).filter(Boolean);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function appendProperty(properties, name, value) {
|
|
131
|
+
if (!name || value === undefined || value === null || value === "") {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
properties.push({
|
|
135
|
+
name,
|
|
136
|
+
value: typeof value === "string" ? value : String(value),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function createBuildInputComponent(name, filePath, tool, rootDir) {
|
|
141
|
+
const sourcePath = formulationSourcePath(filePath, rootDir);
|
|
142
|
+
return {
|
|
143
|
+
type: "application",
|
|
144
|
+
name,
|
|
145
|
+
version: "config",
|
|
146
|
+
"bom-ref": `urn:cdxgen:formulation:${tool}:${sourcePath}`,
|
|
147
|
+
properties: [
|
|
148
|
+
{ name: "internal:SrcFile", value: sourcePath },
|
|
149
|
+
{ name: "cdx:build:tool", value: tool },
|
|
150
|
+
],
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const MAX_LISTED = 25;
|
|
155
|
+
|
|
156
|
+
const MAKE_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
157
|
+
const MAKE_INCLUDE_DIRECTIVES = new Set(["include", "-include", "sinclude"]);
|
|
158
|
+
const MAKE_VARIABLE_MODIFIERS = new Set(["export", "override", "private"]);
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Whether a path from a make file is safe to record: relative to the make
|
|
162
|
+
* file and not leaving the repository. Absolute POSIX, Windows drive and UNC
|
|
163
|
+
* paths, home-relative paths and paths with a `..` segment describe the
|
|
164
|
+
* machine or tree around the project, so they are dropped.
|
|
165
|
+
*
|
|
166
|
+
* @param {string} includePath include directive argument or target name
|
|
167
|
+
* @returns {boolean} true when the path can be recorded
|
|
168
|
+
*/
|
|
169
|
+
function isRecordableIncludePath(includePath) {
|
|
170
|
+
if (
|
|
171
|
+
!includePath ||
|
|
172
|
+
includePath.startsWith("/") ||
|
|
173
|
+
includePath.startsWith("\\") ||
|
|
174
|
+
includePath.startsWith("~")
|
|
175
|
+
) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
if (
|
|
179
|
+
includePath.length >= 2 &&
|
|
180
|
+
includePath[1] === ":" &&
|
|
181
|
+
/^[A-Za-z]$/.test(includePath[0])
|
|
182
|
+
) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
return !includePath.split(/[\\/]/).includes("..");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Whether the text before a rule colon is a target name worth recording: one
|
|
190
|
+
* token with no whitespace or quotes, not an option-like word, not a special
|
|
191
|
+
* (`.PHONY`), variable-reference or pattern target, and a path that passes
|
|
192
|
+
* the same filter as include directives.
|
|
193
|
+
*
|
|
194
|
+
* @param {string} name text before the rule colon
|
|
195
|
+
* @returns {boolean} true when the target can be recorded
|
|
196
|
+
*/
|
|
197
|
+
function isRecordableMakeTarget(name) {
|
|
198
|
+
if (!name || /[\s"'`]/.test(name)) {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
if (".#$-".includes(name[0]) || name.includes("%")) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
return isRecordableIncludePath(name);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* The variable name a `define` line opens, without a trailing assignment
|
|
209
|
+
* operator.
|
|
210
|
+
*
|
|
211
|
+
* @param {string[]} words tokens after the `define` keyword
|
|
212
|
+
* @returns {string|undefined} the variable name
|
|
213
|
+
*/
|
|
214
|
+
function defineVariableName(words) {
|
|
215
|
+
let name = words[0] || "";
|
|
216
|
+
while (name && "?+!:=".includes(name[name.length - 1])) {
|
|
217
|
+
name = name.slice(0, -1);
|
|
218
|
+
}
|
|
219
|
+
return MAKE_IDENTIFIER.test(name) ? name : undefined;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Collect target names, include directives and variable names from a make
|
|
224
|
+
* file. Values are deliberately not recorded: flags can carry tokens or
|
|
225
|
+
* machine-specific paths, and a name is enough for audit triage. Continuation
|
|
226
|
+
* lines are joined first and `define ... endef` bodies are skipped, so text
|
|
227
|
+
* from inside a value is never read as a target. Lines are tokenised with
|
|
228
|
+
* indexOf, so the work stays linear in the line length.
|
|
229
|
+
*
|
|
230
|
+
* @param {string} content make file text
|
|
231
|
+
* @returns {{ targets: string[], includes: string[], variables: string[] }}
|
|
232
|
+
*/
|
|
233
|
+
function parseMakeContent(content) {
|
|
234
|
+
const targets = [];
|
|
235
|
+
const includes = [];
|
|
236
|
+
const variables = [];
|
|
237
|
+
let defineDepth = 0;
|
|
238
|
+
for (const line of joinContinuationLines(content, {
|
|
239
|
+
commentLinesContinue: true,
|
|
240
|
+
})) {
|
|
241
|
+
const trimmed = line.trim();
|
|
242
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
const words = tokens(trimmed);
|
|
246
|
+
let start = 0;
|
|
247
|
+
while (
|
|
248
|
+
start < words.length - 1 &&
|
|
249
|
+
MAKE_VARIABLE_MODIFIERS.has(words[start])
|
|
250
|
+
) {
|
|
251
|
+
start += 1;
|
|
252
|
+
}
|
|
253
|
+
const directive = words[start];
|
|
254
|
+
if (defineDepth > 0) {
|
|
255
|
+
if (directive === "endef") {
|
|
256
|
+
defineDepth -= 1;
|
|
257
|
+
} else if (directive === "define") {
|
|
258
|
+
defineDepth += 1;
|
|
259
|
+
}
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
// Recipe lines cannot declare targets or variables.
|
|
263
|
+
if (line.startsWith("\t")) {
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (directive === "define") {
|
|
267
|
+
defineDepth = 1;
|
|
268
|
+
const name = defineVariableName(words.slice(start + 1));
|
|
269
|
+
if (name) {
|
|
270
|
+
variables.push(name);
|
|
271
|
+
}
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (directive === "endef") {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (MAKE_INCLUDE_DIRECTIVES.has(words[0])) {
|
|
278
|
+
includes.push(...words.slice(1).filter(isRecordableIncludePath));
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
// export, override and private qualify the assignment that follows.
|
|
282
|
+
const body = start ? words.slice(start).join(" ") : trimmed;
|
|
283
|
+
const eq = body.indexOf("=");
|
|
284
|
+
const colon = body.indexOf(":");
|
|
285
|
+
// An assignment: `=`, `?=`, `+=`, `!=`, `:=`, `::=` or `:::=` before any
|
|
286
|
+
// rule colon.
|
|
287
|
+
const colonIsOperator =
|
|
288
|
+
colon !== -1 &&
|
|
289
|
+
eq > colon &&
|
|
290
|
+
body
|
|
291
|
+
.slice(colon, eq)
|
|
292
|
+
.split("")
|
|
293
|
+
.every((ch) => ch === ":");
|
|
294
|
+
if (eq !== -1 && (colon === -1 || eq < colon || colonIsOperator)) {
|
|
295
|
+
let nameEnd = colonIsOperator ? colon : eq;
|
|
296
|
+
while (nameEnd > 0 && "?+!".includes(body[nameEnd - 1])) {
|
|
297
|
+
nameEnd -= 1;
|
|
298
|
+
}
|
|
299
|
+
const name = body.slice(0, nameEnd).trim();
|
|
300
|
+
if (MAKE_IDENTIFIER.test(name)) {
|
|
301
|
+
variables.push(name);
|
|
302
|
+
}
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (start || colon <= 0) {
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
// A Windows drive path such as C:\out\app.exe names the machine.
|
|
309
|
+
if (
|
|
310
|
+
colon === 1 &&
|
|
311
|
+
/^[A-Za-z]$/.test(body[0]) &&
|
|
312
|
+
(body[2] === "\\" || body[2] === "/")
|
|
313
|
+
) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
const name = body.slice(0, colon).trim();
|
|
317
|
+
if (isRecordableMakeTarget(name)) {
|
|
318
|
+
targets.push(name);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return { targets, includes, variables };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function parseMakeFile(filePath, rootDir) {
|
|
325
|
+
const content = readBounded(filePath, rootDir);
|
|
326
|
+
if (!content) {
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
const { targets, includes, variables } = parseMakeContent(content);
|
|
330
|
+
const component = createBuildInputComponent(
|
|
331
|
+
basename(dirname(filePath)) || basename(filePath),
|
|
332
|
+
filePath,
|
|
333
|
+
"make",
|
|
334
|
+
rootDir,
|
|
335
|
+
);
|
|
336
|
+
const properties = component.properties;
|
|
337
|
+
appendProperty(properties, "cdx:make:targetCount", targets.length);
|
|
338
|
+
appendProperty(
|
|
339
|
+
properties,
|
|
340
|
+
"cdx:make:targets",
|
|
341
|
+
targets.length ? targets.slice(0, MAX_LISTED).join(", ") : undefined,
|
|
342
|
+
);
|
|
343
|
+
appendProperty(
|
|
344
|
+
properties,
|
|
345
|
+
"cdx:make:includes",
|
|
346
|
+
includes.length ? includes.slice(0, MAX_LISTED).join(", ") : undefined,
|
|
347
|
+
);
|
|
348
|
+
appendProperty(
|
|
349
|
+
properties,
|
|
350
|
+
"cdx:make:variables",
|
|
351
|
+
variables.length ? variables.slice(0, MAX_LISTED).join(", ") : undefined,
|
|
352
|
+
);
|
|
353
|
+
appendProperty(
|
|
354
|
+
properties,
|
|
355
|
+
"cdx:make:usesDocker",
|
|
356
|
+
targets.some((t) => /docker/i.test(t)) ? "true" : undefined,
|
|
357
|
+
);
|
|
358
|
+
return component;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Remove CMake comments: `#` line comments and `#[[ ... ]]` bracket
|
|
363
|
+
* comments, outside quoted and bracket arguments. The text is scanned once
|
|
364
|
+
* with indexOf, so the work stays linear in the file size.
|
|
365
|
+
*
|
|
366
|
+
* @param {string} content CMakeLists.txt text
|
|
367
|
+
* @returns {string} the text without comments
|
|
368
|
+
*/
|
|
369
|
+
function stripCMakeComments(content) {
|
|
370
|
+
const kept = [];
|
|
371
|
+
let segmentStart = 0;
|
|
372
|
+
let index = 0;
|
|
373
|
+
let inQuote = false;
|
|
374
|
+
// A bracket opener is `[`, any number of `=`, then `[`; it returns the
|
|
375
|
+
// matching closer, or undefined.
|
|
376
|
+
const bracketCloser = (at) => {
|
|
377
|
+
let cursor = at + 1;
|
|
378
|
+
while (content[cursor] === "=") {
|
|
379
|
+
cursor += 1;
|
|
380
|
+
}
|
|
381
|
+
return content[cursor] === "["
|
|
382
|
+
? `]${"=".repeat(cursor - at - 1)}]`
|
|
383
|
+
: undefined;
|
|
384
|
+
};
|
|
385
|
+
while (index < content.length) {
|
|
386
|
+
const ch = content[index];
|
|
387
|
+
if (ch === "\\") {
|
|
388
|
+
index += 2;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (inQuote) {
|
|
392
|
+
if (ch === '"') {
|
|
393
|
+
inQuote = false;
|
|
394
|
+
}
|
|
395
|
+
index += 1;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (ch === '"') {
|
|
399
|
+
inQuote = true;
|
|
400
|
+
index += 1;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (ch === "[") {
|
|
404
|
+
const closer = bracketCloser(index);
|
|
405
|
+
if (closer) {
|
|
406
|
+
const end = content.indexOf(closer, index);
|
|
407
|
+
index = end === -1 ? content.length : end + closer.length;
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
index += 1;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
if (ch === "#") {
|
|
414
|
+
kept.push(content.slice(segmentStart, index));
|
|
415
|
+
const closer =
|
|
416
|
+
content[index + 1] === "[" ? bracketCloser(index + 1) : undefined;
|
|
417
|
+
let end;
|
|
418
|
+
if (closer) {
|
|
419
|
+
end = content.indexOf(closer, index);
|
|
420
|
+
end = end === -1 ? content.length : end + closer.length;
|
|
421
|
+
} else {
|
|
422
|
+
end = content.indexOf("\n", index);
|
|
423
|
+
end = end === -1 ? content.length : end;
|
|
424
|
+
}
|
|
425
|
+
index = end;
|
|
426
|
+
segmentStart = end;
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
index += 1;
|
|
430
|
+
}
|
|
431
|
+
kept.push(content.slice(segmentStart));
|
|
432
|
+
return kept.join("");
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Blank the inside of CMake quoted arguments, keeping the text length, so a
|
|
437
|
+
* command name or parenthesis inside a string is not read as code. Indexes
|
|
438
|
+
* into the result point at the same characters of the input.
|
|
439
|
+
*
|
|
440
|
+
* @param {string} content CMakeLists.txt text without comments
|
|
441
|
+
* @returns {string} the text with quoted argument contents replaced by spaces
|
|
442
|
+
*/
|
|
443
|
+
function maskCMakeStrings(content) {
|
|
444
|
+
const kept = [];
|
|
445
|
+
let segmentStart = 0;
|
|
446
|
+
let index = 0;
|
|
447
|
+
while (index < content.length) {
|
|
448
|
+
const ch = content[index];
|
|
449
|
+
if (ch === "\\") {
|
|
450
|
+
index += 2;
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
if (ch !== '"') {
|
|
454
|
+
index += 1;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
let end = index + 1;
|
|
458
|
+
while (end < content.length && content[end] !== '"') {
|
|
459
|
+
end += content[end] === "\\" ? 2 : 1;
|
|
460
|
+
}
|
|
461
|
+
end = Math.min(end, content.length);
|
|
462
|
+
kept.push(content.slice(segmentStart, index + 1));
|
|
463
|
+
kept.push(" ".repeat(end - index - 1));
|
|
464
|
+
segmentStart = end;
|
|
465
|
+
index = end + 1;
|
|
466
|
+
}
|
|
467
|
+
kept.push(content.slice(segmentStart));
|
|
468
|
+
return kept.join("");
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const CMAKE_PROJECT_KEYWORDS = new Set([
|
|
472
|
+
"DESCRIPTION",
|
|
473
|
+
"HOMEPAGE_URL",
|
|
474
|
+
"LANGUAGES",
|
|
475
|
+
"VERSION",
|
|
476
|
+
]);
|
|
477
|
+
const CMAKE_LANGUAGES = new Set([
|
|
478
|
+
"ASM",
|
|
479
|
+
"ASM-ATT",
|
|
480
|
+
"ASM_MASM",
|
|
481
|
+
"ASM_NASM",
|
|
482
|
+
"C",
|
|
483
|
+
"CSharp",
|
|
484
|
+
"CUDA",
|
|
485
|
+
"CXX",
|
|
486
|
+
"Fortran",
|
|
487
|
+
"HIP",
|
|
488
|
+
"ISPC",
|
|
489
|
+
"Java",
|
|
490
|
+
"OBJC",
|
|
491
|
+
"OBJCXX",
|
|
492
|
+
"Swift",
|
|
493
|
+
]);
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Read the first `project(<name> ...)` command of a CMake file whose
|
|
497
|
+
* comments were removed. Command names are case-insensitive in CMake. The
|
|
498
|
+
* arguments are cut at the next `)` with indexOf, so a file full of
|
|
499
|
+
* unterminated `project(` calls is still read in linear time. Languages are
|
|
500
|
+
* the arguments after `LANGUAGES`, or, in the short form
|
|
501
|
+
* `project(name C CXX)`, the arguments before the first keyword.
|
|
502
|
+
*
|
|
503
|
+
* @param {string} content CMakeLists.txt text without comments
|
|
504
|
+
* @param {string} masked the same text with quoted contents blanked
|
|
505
|
+
* @returns {{ name: string, languages: string[] }|undefined}
|
|
506
|
+
*/
|
|
507
|
+
function parseCMakeProject(content, masked) {
|
|
508
|
+
const projectCall = /\bproject\s*\(/i.exec(masked);
|
|
509
|
+
if (!projectCall) {
|
|
510
|
+
return undefined;
|
|
511
|
+
}
|
|
512
|
+
const argsStart = projectCall.index + projectCall[0].length;
|
|
513
|
+
const argsEnd = masked.indexOf(")", argsStart);
|
|
514
|
+
if (argsEnd === -1) {
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
const args = tokens(content.slice(argsStart, argsEnd)).map((arg) =>
|
|
518
|
+
arg.length >= 2 && arg.startsWith('"') && arg.endsWith('"')
|
|
519
|
+
? arg.slice(1, -1)
|
|
520
|
+
: arg,
|
|
521
|
+
);
|
|
522
|
+
const name = args[0];
|
|
523
|
+
if (!name || !/^[A-Za-z0-9_.-]+$/.test(name)) {
|
|
524
|
+
return undefined;
|
|
525
|
+
}
|
|
526
|
+
const rest = args.slice(1);
|
|
527
|
+
const languagesAt = rest.indexOf("LANGUAGES");
|
|
528
|
+
const languageArgs = [];
|
|
529
|
+
for (const arg of rest.slice(languagesAt + 1)) {
|
|
530
|
+
if (CMAKE_PROJECT_KEYWORDS.has(arg)) {
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
languageArgs.push(arg);
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
name,
|
|
537
|
+
languages: [...new Set(languageArgs.filter((a) => CMAKE_LANGUAGES.has(a)))],
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function parseCMakeLists(filePath, rootDir) {
|
|
542
|
+
const raw = readBounded(filePath, rootDir);
|
|
543
|
+
if (!raw) {
|
|
544
|
+
return undefined;
|
|
545
|
+
}
|
|
546
|
+
const content = stripCMakeComments(raw);
|
|
547
|
+
const masked = maskCMakeStrings(content);
|
|
548
|
+
const minimum = /\bcmake_minimum_required\s*\(\s*VERSION\s+([0-9.]+)/i.exec(
|
|
549
|
+
masked,
|
|
550
|
+
);
|
|
551
|
+
const project = parseCMakeProject(content, masked);
|
|
552
|
+
const executableCount = (masked.match(/\badd_executable\s*\(/gi) || [])
|
|
553
|
+
.length;
|
|
554
|
+
const libraryCount = (masked.match(/\badd_library\s*\(/gi) || []).length;
|
|
555
|
+
const component = createBuildInputComponent(
|
|
556
|
+
project?.name || basename(dirname(filePath)) || "cmake-project",
|
|
557
|
+
filePath,
|
|
558
|
+
"cmake",
|
|
559
|
+
rootDir,
|
|
560
|
+
);
|
|
561
|
+
const properties = component.properties;
|
|
562
|
+
appendProperty(properties, "cdx:cmake:minimumVersion", minimum?.[1]);
|
|
563
|
+
appendProperty(
|
|
564
|
+
properties,
|
|
565
|
+
"cdx:cmake:languages",
|
|
566
|
+
project?.languages.length ? project.languages.join(", ") : undefined,
|
|
567
|
+
);
|
|
568
|
+
appendProperty(properties, "cdx:cmake:executableCount", executableCount);
|
|
569
|
+
appendProperty(properties, "cdx:cmake:libraryCount", libraryCount);
|
|
570
|
+
return component;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Parser for make and CMake build inputs (C/C++ and friends).
|
|
575
|
+
*
|
|
576
|
+
* @type {{id: string, patterns: string[], parse(files: string[], options?: Object, context?: {rootDir?: string}): {components: Object[]}}}
|
|
577
|
+
*/
|
|
578
|
+
export const makefileFormulationParser = {
|
|
579
|
+
id: "make-build",
|
|
580
|
+
patterns: [
|
|
581
|
+
"**/Makefile",
|
|
582
|
+
"**/makefile",
|
|
583
|
+
"**/GNUmakefile",
|
|
584
|
+
"**/*.mk",
|
|
585
|
+
"**/CMakeLists.txt",
|
|
586
|
+
],
|
|
587
|
+
parse(files, _options, context = {}) {
|
|
588
|
+
const components = [];
|
|
589
|
+
for (const filePath of files || []) {
|
|
590
|
+
const base = basename(filePath).toLowerCase();
|
|
591
|
+
const component = base.startsWith("cmakelists")
|
|
592
|
+
? parseCMakeLists(filePath, context.rootDir)
|
|
593
|
+
: parseMakeFile(filePath, context.rootDir);
|
|
594
|
+
if (component) {
|
|
595
|
+
components.push(component);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
return { components };
|
|
599
|
+
},
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Read the directives of a go.mod file. Requirements are counted from
|
|
604
|
+
* single-line `require` directives and the lines of `require ( ... )`
|
|
605
|
+
* blocks only; `replace`, `exclude` and `retract` lines are not
|
|
606
|
+
* requirements. Lines are split into tokens, with no regex over the file.
|
|
607
|
+
*
|
|
608
|
+
* @param {string} content go.mod text
|
|
609
|
+
* @returns {{ module: string|undefined, goVersion: string|undefined, toolchain: string|undefined, requireCount: number, indirectCount: number }}
|
|
610
|
+
*/
|
|
611
|
+
function parseGoModContent(content) {
|
|
612
|
+
const result = {
|
|
613
|
+
module: undefined,
|
|
614
|
+
goVersion: undefined,
|
|
615
|
+
toolchain: undefined,
|
|
616
|
+
requireCount: 0,
|
|
617
|
+
indirectCount: 0,
|
|
618
|
+
};
|
|
619
|
+
const unquote = (value) =>
|
|
620
|
+
value && value.length >= 2 && value.startsWith('"') && value.endsWith('"')
|
|
621
|
+
? value.slice(1, -1)
|
|
622
|
+
: value;
|
|
623
|
+
const countRequirement = (spec, comment) => {
|
|
624
|
+
const parts = tokens(spec);
|
|
625
|
+
if (parts.length < 2 || !parts[1].startsWith("v")) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
result.requireCount += 1;
|
|
629
|
+
if (comment && tokens(comment.replaceAll(";", " ")).includes("indirect")) {
|
|
630
|
+
result.indirectCount += 1;
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
let block;
|
|
634
|
+
for (const rawLine of boundedLines(content)) {
|
|
635
|
+
const commentStart = rawLine.indexOf("//");
|
|
636
|
+
const code = (
|
|
637
|
+
commentStart === -1 ? rawLine : rawLine.slice(0, commentStart)
|
|
638
|
+
).trim();
|
|
639
|
+
const comment =
|
|
640
|
+
commentStart === -1 ? "" : rawLine.slice(commentStart + 2).trim();
|
|
641
|
+
if (block) {
|
|
642
|
+
if (code === ")") {
|
|
643
|
+
block = undefined;
|
|
644
|
+
} else if (block === "require" && code) {
|
|
645
|
+
countRequirement(code, comment);
|
|
646
|
+
}
|
|
647
|
+
continue;
|
|
648
|
+
}
|
|
649
|
+
if (!code) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
const words = tokens(code);
|
|
653
|
+
const directive = words[0];
|
|
654
|
+
if (words.length === 2 && words[1] === "(") {
|
|
655
|
+
block = directive;
|
|
656
|
+
continue;
|
|
657
|
+
}
|
|
658
|
+
if (words.length === 1 && directive.length > 1 && directive.endsWith("(")) {
|
|
659
|
+
block = directive.slice(0, -1);
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
if (directive === "module" && result.module === undefined) {
|
|
663
|
+
result.module = unquote(words[1]);
|
|
664
|
+
} else if (directive === "go" && result.goVersion === undefined) {
|
|
665
|
+
result.goVersion = words[1];
|
|
666
|
+
} else if (directive === "toolchain" && result.toolchain === undefined) {
|
|
667
|
+
result.toolchain = words[1];
|
|
668
|
+
} else if (directive === "require") {
|
|
669
|
+
countRequirement(words.slice(1).join(" "), comment);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
return result;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function parseGoMod(filePath, rootDir) {
|
|
676
|
+
const content = readBounded(filePath, rootDir);
|
|
677
|
+
if (!content) {
|
|
678
|
+
return undefined;
|
|
679
|
+
}
|
|
680
|
+
const { module, goVersion, toolchain, requireCount, indirectCount } =
|
|
681
|
+
parseGoModContent(content);
|
|
682
|
+
if (!module) {
|
|
683
|
+
return undefined;
|
|
684
|
+
}
|
|
685
|
+
const component = createBuildInputComponent(module, filePath, "go", rootDir);
|
|
686
|
+
const properties = component.properties;
|
|
687
|
+
appendProperty(properties, "cdx:go:module", module);
|
|
688
|
+
appendProperty(properties, "cdx:go:version", goVersion);
|
|
689
|
+
appendProperty(properties, "cdx:go:toolchain", toolchain);
|
|
690
|
+
appendProperty(properties, "cdx:go:requireCount", requireCount);
|
|
691
|
+
appendProperty(properties, "cdx:go:indirectCount", indirectCount);
|
|
692
|
+
return component;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Parser for go.mod build inputs. The compiled binaries are read by the
|
|
697
|
+
* binary/OBOM path through the bounded Go buildinfo reader, not here.
|
|
698
|
+
*
|
|
699
|
+
* @type {{id: string, patterns: string[], parse(files: string[], options?: Object, context?: {rootDir?: string}): {components: Object[]}}}
|
|
700
|
+
*/
|
|
701
|
+
export const goModuleFormulationParser = {
|
|
702
|
+
id: "go-module",
|
|
703
|
+
patterns: ["**/go.mod"],
|
|
704
|
+
parse(files, _options, context = {}) {
|
|
705
|
+
const components = [];
|
|
706
|
+
for (const filePath of files || []) {
|
|
707
|
+
const component = parseGoMod(filePath, context.rootDir);
|
|
708
|
+
if (component) {
|
|
709
|
+
components.push(component);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
return { components };
|
|
713
|
+
},
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
/**
|
|
717
|
+
* Whether a pip requirement is a direct reference rather than a name with a
|
|
718
|
+
* version specifier: `name @ url` (PEP 508), a bare URL, or a local path.
|
|
719
|
+
* Such a requirement is never an exact pin, even when its URL holds `==`.
|
|
720
|
+
*
|
|
721
|
+
* @param {string} requirement requirement text before any option
|
|
722
|
+
* @returns {boolean} true for a direct reference
|
|
723
|
+
*/
|
|
724
|
+
function isPipDirectReference(requirement) {
|
|
725
|
+
const schemeAt = requirement.indexOf("://");
|
|
726
|
+
const at = requirement.indexOf("@");
|
|
727
|
+
if (at !== -1 && (schemeAt === -1 || at < schemeAt)) {
|
|
728
|
+
return true;
|
|
729
|
+
}
|
|
730
|
+
if (schemeAt !== -1) {
|
|
731
|
+
return true;
|
|
732
|
+
}
|
|
733
|
+
return (
|
|
734
|
+
requirement.startsWith(".") ||
|
|
735
|
+
requirement.startsWith("/") ||
|
|
736
|
+
requirement.startsWith("\\") ||
|
|
737
|
+
(requirement[1] === ":" && /^[A-Za-z]$/.test(requirement[0]))
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Count the requirements of a pip requirements or constraints file and how
|
|
743
|
+
* many of them are exact pins (`==` or `===`). Lines ending in a backslash
|
|
744
|
+
* continue on the next line, which is where pip-compile writes the
|
|
745
|
+
* `--hash` options, so hashes are looked for in the joined logical line.
|
|
746
|
+
* Editable installs (`-e`, `--editable`) are requirements but never pins.
|
|
747
|
+
*
|
|
748
|
+
* @param {string} content file text
|
|
749
|
+
* @returns {{ requirementCount: number, pinCount: number, hasHashes: boolean }}
|
|
750
|
+
*/
|
|
751
|
+
function parsePipContent(content) {
|
|
752
|
+
let requirementCount = 0;
|
|
753
|
+
let pinCount = 0;
|
|
754
|
+
let hasHashes = false;
|
|
755
|
+
// As in pip's join_lines, a comment line never continues, even when it
|
|
756
|
+
// ends in a backslash; it ends any continuation in progress instead.
|
|
757
|
+
for (const logicalLine of joinContinuationLines(content, {
|
|
758
|
+
commentLinesContinue: false,
|
|
759
|
+
})) {
|
|
760
|
+
const trimmed = logicalLine.trim();
|
|
761
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
const commentStart = trimmed.indexOf(" #");
|
|
765
|
+
const requirement = (
|
|
766
|
+
commentStart === -1 ? trimmed : trimmed.slice(0, commentStart)
|
|
767
|
+
).trim();
|
|
768
|
+
if (!requirement) {
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
const words = tokens(requirement);
|
|
772
|
+
const isEditable =
|
|
773
|
+
words[0] === "--editable" ||
|
|
774
|
+
words[0].startsWith("--editable=") ||
|
|
775
|
+
(words[0].startsWith("-e") && !words[0].startsWith("--"));
|
|
776
|
+
// Other option lines such as --index-url, -r and -c are not
|
|
777
|
+
// requirements.
|
|
778
|
+
if (requirement.startsWith("-") && !isEditable) {
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
requirementCount += 1;
|
|
782
|
+
if (words.some((word) => word.toLowerCase().startsWith("--hash"))) {
|
|
783
|
+
hasHashes = true;
|
|
784
|
+
}
|
|
785
|
+
if (isEditable) {
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
// The specifier ends at the first option or environment marker.
|
|
789
|
+
const optionStart = words.findIndex((word) => word.startsWith("--"));
|
|
790
|
+
const specifierWords =
|
|
791
|
+
optionStart === -1 ? words : words.slice(0, optionStart);
|
|
792
|
+
if (isPipDirectReference(specifierWords.join(" "))) {
|
|
793
|
+
continue;
|
|
794
|
+
}
|
|
795
|
+
let specifier = specifierWords.join("");
|
|
796
|
+
const markerStart = specifier.indexOf(";");
|
|
797
|
+
if (markerStart !== -1) {
|
|
798
|
+
specifier = specifier.slice(0, markerStart);
|
|
799
|
+
}
|
|
800
|
+
const pinStart = specifier.indexOf("==");
|
|
801
|
+
if (
|
|
802
|
+
pinStart !== -1 &&
|
|
803
|
+
!specifier.includes(",") &&
|
|
804
|
+
!specifier.slice(pinStart).includes("*")
|
|
805
|
+
) {
|
|
806
|
+
pinCount += 1;
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return { requirementCount, pinCount, hasHashes };
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function parsePipRequirements(filePath, rootDir) {
|
|
813
|
+
const content = readBounded(filePath, rootDir);
|
|
814
|
+
if (!content) {
|
|
815
|
+
return undefined;
|
|
816
|
+
}
|
|
817
|
+
const { requirementCount, pinCount, hasHashes } = parsePipContent(content);
|
|
818
|
+
const component = createBuildInputComponent(
|
|
819
|
+
basename(filePath),
|
|
820
|
+
filePath,
|
|
821
|
+
"pip",
|
|
822
|
+
rootDir,
|
|
823
|
+
);
|
|
824
|
+
const properties = component.properties;
|
|
825
|
+
appendProperty(properties, "cdx:pip:requirementCount", requirementCount);
|
|
826
|
+
appendProperty(properties, "cdx:pip:pinCount", pinCount);
|
|
827
|
+
appendProperty(
|
|
828
|
+
properties,
|
|
829
|
+
"cdx:pip:unpinnedCount",
|
|
830
|
+
requirementCount - pinCount,
|
|
831
|
+
);
|
|
832
|
+
appendProperty(
|
|
833
|
+
properties,
|
|
834
|
+
"cdx:pip:hasHashes",
|
|
835
|
+
hasHashes ? "true" : undefined,
|
|
836
|
+
);
|
|
837
|
+
appendProperty(
|
|
838
|
+
properties,
|
|
839
|
+
"cdx:pip:kind",
|
|
840
|
+
/constraints/i.test(basename(filePath)) ? "constraints" : "requirements",
|
|
841
|
+
);
|
|
842
|
+
return component;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
/**
|
|
846
|
+
* Parser for pip requirements and constraints files.
|
|
847
|
+
*
|
|
848
|
+
* @type {{id: string, patterns: string[], parse(files: string[], options?: Object, context?: {rootDir?: string}): {components: Object[]}}}
|
|
849
|
+
*/
|
|
850
|
+
export const pythonRequirementsFormulationParser = {
|
|
851
|
+
id: "python-requirements",
|
|
852
|
+
patterns: [
|
|
853
|
+
"**/requirements*.txt",
|
|
854
|
+
"**/constraints*.txt",
|
|
855
|
+
"**/requirements/*.txt",
|
|
856
|
+
"**/requirements.d/*.txt",
|
|
857
|
+
],
|
|
858
|
+
parse(files, _options, context = {}) {
|
|
859
|
+
const components = [];
|
|
860
|
+
for (const filePath of files || []) {
|
|
861
|
+
const component = parsePipRequirements(filePath, context.rootDir);
|
|
862
|
+
if (component) {
|
|
863
|
+
components.push(component);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return { components };
|
|
867
|
+
},
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
function parseCsProj(filePath, rootDir) {
|
|
871
|
+
const content = readBounded(filePath, rootDir);
|
|
872
|
+
if (!content) {
|
|
873
|
+
return undefined;
|
|
874
|
+
}
|
|
875
|
+
const sdk = /\bSdk\s*=\s*"([^"]+)"/.exec(content);
|
|
876
|
+
const targetFramework =
|
|
877
|
+
/<TargetFrameworks?>([^<]+)<\/TargetFrameworks?>/.exec(content);
|
|
878
|
+
const outputType = /<OutputType>([^<]+)<\/OutputType>/.exec(content);
|
|
879
|
+
const version = /<Version>([^<]+)<\/Version>/.exec(content);
|
|
880
|
+
const packageReferenceCount = (
|
|
881
|
+
content.match(/<PackageReference\s+Include=/g) || []
|
|
882
|
+
).length;
|
|
883
|
+
const component = createBuildInputComponent(
|
|
884
|
+
basename(filePath).replace(/\.csproj$/i, ""),
|
|
885
|
+
filePath,
|
|
886
|
+
"nuget",
|
|
887
|
+
rootDir,
|
|
888
|
+
);
|
|
889
|
+
const properties = component.properties;
|
|
890
|
+
appendProperty(properties, "cdx:msbuild:sdk", sdk?.[1]);
|
|
891
|
+
appendProperty(
|
|
892
|
+
properties,
|
|
893
|
+
"cdx:msbuild:targetFrameworks",
|
|
894
|
+
targetFramework?.[1]?.trim(),
|
|
895
|
+
);
|
|
896
|
+
appendProperty(properties, "cdx:msbuild:outputType", outputType?.[1]?.trim());
|
|
897
|
+
appendProperty(properties, "cdx:msbuild:version", version?.[1]?.trim());
|
|
898
|
+
appendProperty(
|
|
899
|
+
properties,
|
|
900
|
+
"cdx:msbuild:packageReferenceCount",
|
|
901
|
+
packageReferenceCount,
|
|
902
|
+
);
|
|
903
|
+
return component;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function parseProjectAssets(filePath, rootDir) {
|
|
907
|
+
const content = readBounded(filePath, rootDir, MAX_ASSETS_READ_BYTES);
|
|
908
|
+
if (!content) {
|
|
909
|
+
return undefined;
|
|
910
|
+
}
|
|
911
|
+
let data;
|
|
912
|
+
try {
|
|
913
|
+
data = JSON.parse(content);
|
|
914
|
+
} catch {
|
|
915
|
+
return undefined;
|
|
916
|
+
}
|
|
917
|
+
if (!data || typeof data !== "object") {
|
|
918
|
+
return undefined;
|
|
919
|
+
}
|
|
920
|
+
const frameworks = Object.keys(data.project?.frameworks || {});
|
|
921
|
+
const libraryCount = Object.keys(data.libraries || {}).length;
|
|
922
|
+
const targetCount = Object.keys(data.targets || {}).length;
|
|
923
|
+
// The restore project name is an MSBuild project name, never a path.
|
|
924
|
+
const projectName = data.project?.restore?.projectName;
|
|
925
|
+
const component = createBuildInputComponent(
|
|
926
|
+
(typeof projectName === "string" &&
|
|
927
|
+
!/[\\/]/.test(projectName) &&
|
|
928
|
+
projectName) ||
|
|
929
|
+
basename(dirname(filePath)) ||
|
|
930
|
+
"nuget-restore",
|
|
931
|
+
filePath,
|
|
932
|
+
"nuget",
|
|
933
|
+
rootDir,
|
|
934
|
+
);
|
|
935
|
+
const properties = component.properties;
|
|
936
|
+
const assetsVersion =
|
|
937
|
+
typeof data.version === "string" ||
|
|
938
|
+
(typeof data.version === "number" && Number.isFinite(data.version))
|
|
939
|
+
? data.version
|
|
940
|
+
: undefined;
|
|
941
|
+
appendProperty(properties, "cdx:nuget:assetsVersion", assetsVersion);
|
|
942
|
+
appendProperty(
|
|
943
|
+
properties,
|
|
944
|
+
"cdx:nuget:frameworks",
|
|
945
|
+
frameworks.length ? frameworks.slice(0, MAX_LISTED).join(", ") : undefined,
|
|
946
|
+
);
|
|
947
|
+
appendProperty(properties, "cdx:nuget:libraryCount", libraryCount);
|
|
948
|
+
appendProperty(properties, "cdx:nuget:targetCount", targetCount);
|
|
949
|
+
return component;
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* Parser for .NET build inputs: project.assets.json (the file `dotnet
|
|
954
|
+
* restore` writes; some projects call it packages.assets.json) and the
|
|
955
|
+
* SDK-style .csproj project files.
|
|
956
|
+
*
|
|
957
|
+
* @type {{id: string, patterns: string[], parse(files: string[], options?: Object, context?: {rootDir?: string}): {components: Object[]}}}
|
|
958
|
+
*/
|
|
959
|
+
export const dotnetProjectFormulationParser = {
|
|
960
|
+
id: "dotnet-project",
|
|
961
|
+
patterns: [
|
|
962
|
+
"**/project.assets.json",
|
|
963
|
+
"**/packages.assets.json",
|
|
964
|
+
"**/*.csproj",
|
|
965
|
+
],
|
|
966
|
+
parse(files, _options, context = {}) {
|
|
967
|
+
const components = [];
|
|
968
|
+
for (const filePath of files || []) {
|
|
969
|
+
const base = basename(filePath).toLowerCase();
|
|
970
|
+
const component = base.endsWith("assets.json")
|
|
971
|
+
? parseProjectAssets(filePath, context.rootDir)
|
|
972
|
+
: parseCsProj(filePath, context.rootDir);
|
|
973
|
+
if (component) {
|
|
974
|
+
components.push(component);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return { components };
|
|
978
|
+
},
|
|
979
|
+
};
|