@cdxgen/cdxgen 0.0.1 → 13.0.1
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 +921 -87
- package/bin/audit.js +239 -0
- package/bin/cdxgen.js +2335 -0
- package/bin/convert.js +220 -0
- package/bin/evinse.js +353 -0
- package/bin/hbom.js +504 -0
- package/bin/repl.js +2224 -0
- package/bin/sign.js +195 -0
- package/bin/tracebom.js +303 -0
- package/bin/validate.js +268 -0
- package/bin/verify.js +189 -0
- package/data/README.md +307 -0
- package/data/bom-1.4.schema.json +1605 -0
- package/data/bom-1.5.schema.json +3660 -0
- package/data/bom-1.6.schema.json +5511 -0
- package/data/bom-1.7.schema.json +6700 -0
- package/data/cbomosdb-queries.json +68 -0
- package/data/component-tags.json +339 -0
- package/data/container-knowledge-index.json +125 -0
- package/data/cosdb-queries.json +17 -0
- package/data/crypto-oid.json +1998 -0
- package/data/cryptography-defs.json +3793 -0
- package/data/cryptography-defs.schema.json +576 -0
- package/data/cyclonedx-2.0-bundled.schema.json +7182 -0
- package/data/frameworks-list.json +219 -0
- package/data/glibc-stdlib.json +117 -0
- package/data/gtfobins-index.json +6296 -0
- package/data/helpers/init.gradle +71 -0
- package/data/jsf-0.82.schema.json +211 -0
- package/data/known-licenses.json +82 -0
- package/data/lic-mapping.json +338 -0
- package/data/license-aliases.json +6624 -0
- package/data/license-db.json +39059 -0
- package/data/license-deprecations.json +302 -0
- package/data/lolbas-index.json +150 -0
- package/data/predictive-audit-allowlist.json +11 -0
- package/data/pypi-pkg-aliases.json +1199 -0
- package/data/python-stdlib.json +308 -0
- package/data/queries-darwin.json +193 -0
- package/data/queries-win.json +257 -0
- package/data/queries.json +268 -0
- package/data/ruby-known-modules.json +281 -0
- package/data/rules/ai-agent-governance.yaml +323 -0
- package/data/rules/ai-bom.yaml +268 -0
- package/data/rules/ai-oversight.yaml +186 -0
- package/data/rules/ai-provenance.yaml +92 -0
- package/data/rules/asar-archives.yaml +150 -0
- package/data/rules/cbom-security.yaml +165 -0
- package/data/rules/chrome-extensions.yaml +248 -0
- package/data/rules/ci-permissions.yaml +802 -0
- package/data/rules/container-risk.yaml +353 -0
- package/data/rules/dependency-sources.yaml +236 -0
- package/data/rules/golem-go.yaml +229 -0
- package/data/rules/hbom-compliance.yaml +325 -0
- package/data/rules/hbom-performance.yaml +307 -0
- package/data/rules/hbom-security.yaml +248 -0
- package/data/rules/host-topology.yaml +165 -0
- package/data/rules/mcp-servers.yaml +388 -0
- package/data/rules/obom-runtime.yaml +1776 -0
- package/data/rules/package-integrity.yaml +364 -0
- package/data/rules/rootfs-hardening.yaml +179 -0
- package/data/rules/vscode-extensions.yaml +237 -0
- package/data/spdx-export.schema.json +6794 -0
- package/data/spdx-license-list.json +7449 -0
- package/data/spdx-licenses.json +759 -0
- package/data/spdx.schema.json +819 -0
- package/data/templates/README.md +3 -0
- package/data/templates/asvs-4.0.3.cdx.json +2943 -0
- package/data/templates/asvs-5.0.cdx.json +3046 -0
- package/data/templates/bsimm-v13.cdx.json +1008 -0
- package/data/templates/masvs-2.0.0.cdx.json +225 -0
- package/data/templates/nist-ssdf-1.1.cdx.json +828 -0
- package/data/templates/pcissc-secure-slc-1.1.cdx.json +1027 -0
- package/data/templates/scvs-1.0.0.cdx.json +813 -0
- package/data/templates/ssaf-DRAFT-2023-11.cdx.json +127 -0
- package/data/vendor-alias.json +41967 -0
- package/data/wrapdb-releases.json +4227 -0
- package/index.cjs +15 -0
- package/lib/audit/index.js +2800 -0
- package/lib/audit/progress.js +114 -0
- package/lib/audit/reporters.js +937 -0
- package/lib/audit/scoring.js +560 -0
- package/lib/audit/targets.js +798 -0
- package/lib/cli/bomAssembly.js +1882 -0
- package/lib/cli/cliOptions.js +543 -0
- package/lib/cli/index.js +2759 -0
- package/lib/cli/jsBom.js +2087 -0
- package/lib/cli/jvmBom.js +1858 -0
- package/lib/cli/managedBom.js +2083 -0
- package/lib/cli/nativeBom.js +2125 -0
- package/lib/core/activity.js +1087 -0
- package/lib/core/env.js +719 -0
- package/lib/core/fs.js +1058 -0
- package/lib/core/httpClient.js +730 -0
- package/lib/core/logger.js +280 -0
- package/lib/core/parallel.js +323 -0
- package/lib/core/paths.js +70 -0
- package/lib/core/projectConfig.js +122 -0
- package/lib/core/propertySanitizer.js +174 -0
- package/lib/core/spdx.js +5 -0
- package/lib/core/state.js +59 -0
- package/lib/core/text.js +207 -0
- package/lib/core/ui.js +920 -0
- package/lib/core/urls.js +84 -0
- package/lib/ecosystems/asarutils.js +1636 -0
- package/lib/ecosystems/bunutils.js +402 -0
- package/lib/ecosystems/caxa.js +77 -0
- package/lib/ecosystems/chromextutils.js +1174 -0
- package/lib/ecosystems/cmakeResolver.js +587 -0
- package/lib/ecosystems/cppEvidence.js +305 -0
- package/lib/ecosystems/denoutils.js +1094 -0
- package/lib/ecosystems/dotnetutils.js +132 -0
- package/lib/ecosystems/ecosystems.js +2772 -0
- package/lib/ecosystems/golem.js +1807 -0
- package/lib/ecosystems/gradleutils.js +1048 -0
- package/lib/ecosystems/jsEvidence.js +317 -0
- package/lib/ecosystems/npmutils.js +586 -0
- package/lib/ecosystems/parsers-bazel.js +875 -0
- package/lib/ecosystems/parsers-dotnet.js +1430 -0
- package/lib/ecosystems/parsers-gleam.js +302 -0
- package/lib/ecosystems/parsers-go.js +818 -0
- package/lib/ecosystems/parsers-js.js +4222 -0
- package/lib/ecosystems/parsers-jvm.js +948 -0
- package/lib/ecosystems/parsers-misc.js +3191 -0
- package/lib/ecosystems/parsers-python.js +2070 -0
- package/lib/ecosystems/parsers-rust.js +1294 -0
- package/lib/ecosystems/parsers-zig.js +861 -0
- package/lib/ecosystems/pep770.js +341 -0
- package/lib/ecosystems/pylockutils.js +427 -0
- package/lib/ecosystems/pythonutils.js +1281 -0
- package/lib/ecosystems/registryProvenance.js +1003 -0
- package/lib/ecosystems/remote/huggingface.js +1343 -0
- package/lib/ecosystems/rubyutils.js +1629 -0
- package/lib/ecosystems/rusi.js +820 -0
- package/lib/ecosystems/sbtutils.js +972 -0
- package/lib/ecosystems/tea.js +607 -0
- package/lib/ecosystems/utils.js +583 -0
- package/lib/ecosystems/zigResolver.js +410 -0
- package/lib/evinser/db.js +145 -0
- package/lib/evinser/evinser.js +2362 -0
- package/lib/evinser/scalasem.js +65 -0
- package/lib/evinser/swiftsem.js +767 -0
- package/lib/helpers/annotationFormatter.js +84 -0
- package/lib/helpers/bomSigner.js +312 -0
- package/lib/helpers/exportUtils.js +160 -0
- package/lib/helpers/remote/dependency-track.js +117 -0
- package/lib/helpers/versutils.js +202 -0
- package/lib/helpers/vsixutils.js +1069 -0
- package/lib/inventory/agentFormulationParser.js +322 -0
- package/lib/inventory/aiCollector.js +2140 -0
- package/lib/inventory/aiInventory.js +414 -0
- package/lib/inventory/aiModelVariants.js +121 -0
- package/lib/inventory/aiOversightCollector.js +661 -0
- package/lib/inventory/aiProvenanceCollector.js +822 -0
- package/lib/inventory/analyzer.js +6089 -0
- package/lib/inventory/analyzerScope.js +807 -0
- package/lib/inventory/astWorker.js +20 -0
- package/lib/inventory/atomUtils.js +1092 -0
- package/lib/inventory/auditCategories.js +192 -0
- package/lib/inventory/bomUtils.js +369 -0
- package/lib/inventory/cacheCommands.js +182 -0
- package/lib/inventory/cacheDir.js +104 -0
- package/lib/inventory/cbomutils.js +590 -0
- package/lib/inventory/cdxrs.js +434 -0
- package/lib/inventory/ciParsers/azurePipelines.js +295 -0
- package/lib/inventory/ciParsers/circleCi.js +286 -0
- package/lib/inventory/ciParsers/common.js +24 -0
- package/lib/inventory/ciParsers/githubActions.js +2434 -0
- package/lib/inventory/ciParsers/gitlabCi.js +213 -0
- package/lib/inventory/ciParsers/jenkins.js +180 -0
- package/lib/inventory/citations.js +234 -0
- package/lib/inventory/communityAiConfigParser.js +756 -0
- package/lib/inventory/containerRisk.js +204 -0
- package/lib/inventory/cryptoAlgorithmFamily.js +258 -0
- package/lib/inventory/deps.js +807 -0
- package/lib/inventory/depsUtils.js +588 -0
- package/lib/inventory/display.js +2128 -0
- package/lib/inventory/dosai.js +601 -0
- package/lib/inventory/dosaiParsers.js +154 -0
- package/lib/inventory/dynamic.js +145 -0
- package/lib/inventory/envcontext.js +1793 -0
- package/lib/inventory/evidenceUtils.js +774 -0
- package/lib/inventory/fetchBatch.js +779 -0
- package/lib/inventory/fetchRate.js +255 -0
- package/lib/inventory/forgeEnricher.js +300 -0
- package/lib/inventory/formulationParsers.js +422 -0
- package/lib/inventory/gtfobins.js +339 -0
- package/lib/inventory/hbom.js +719 -0
- package/lib/inventory/hbomAnalysis.js +310 -0
- package/lib/inventory/hbomLoader.js +35 -0
- package/lib/inventory/hostTopology.js +835 -0
- package/lib/inventory/inventoryStats.js +100 -0
- package/lib/inventory/jsonLike.js +121 -0
- package/lib/inventory/licenseEnhancer.js +918 -0
- package/lib/inventory/lolbas.js +285 -0
- package/lib/inventory/mcp.js +253 -0
- package/lib/inventory/mcpConfigParser.js +683 -0
- package/lib/inventory/mcpDiscovery.js +112 -0
- package/lib/inventory/mcpPinning.js +209 -0
- package/lib/inventory/osPackageResolver.js +493 -0
- package/lib/inventory/osinfo.js +383 -0
- package/lib/inventory/osqueryTransform.js +274 -0
- package/lib/inventory/plugins.js +466 -0
- package/lib/inventory/protobom.js +231 -0
- package/lib/inventory/protobomLoader.js +43 -0
- package/lib/inventory/provenanceUtils.js +224 -0
- package/lib/inventory/purl.js +749 -0
- package/lib/inventory/rustFormulationParser.js +339 -0
- package/lib/inventory/sbomDocument.js +340 -0
- package/lib/inventory/source.js +1488 -0
- package/lib/inventory/spdx.js +361 -0
- package/lib/inventory/spdxExpression.js +512 -0
- package/lib/inventory/spdxUtils.js +107 -0
- package/lib/inventory/table.js +267 -0
- package/lib/inventory/traceRunner.js +439 -0
- package/lib/inventory/unicodeScan.js +147 -0
- package/lib/managers/binary.js +3459 -0
- package/lib/managers/containerutils.js +125 -0
- package/lib/managers/docker.js +2145 -0
- package/lib/managers/dockerConnection.js +162 -0
- package/lib/managers/oci.js +704 -0
- package/lib/managers/piptree.js +267 -0
- package/lib/parsers/cmakeCache.js +199 -0
- package/lib/parsers/gguf.js +492 -0
- package/lib/parsers/gitAiNotes.js +233 -0
- package/lib/parsers/gitmodules.js +239 -0
- package/lib/parsers/htmlExtract.js +610 -0
- package/lib/parsers/huggingfaceManifest.js +551 -0
- package/lib/parsers/huggingfaceUtils.js +506 -0
- package/lib/parsers/iri.js +618 -0
- package/lib/parsers/npmrc.js +99 -0
- package/lib/parsers/ollama.js +101 -0
- package/lib/parsers/xml.js +841 -0
- package/lib/server/openapi.yaml +433 -0
- package/lib/server/server.js +616 -0
- package/lib/stages/postgen/annotator.js +977 -0
- package/lib/stages/postgen/auditBom.js +468 -0
- package/lib/stages/postgen/postgen.js +1239 -0
- package/lib/stages/postgen/ruleEngine.js +731 -0
- package/lib/stages/postgen/sortBom.js +195 -0
- package/lib/stages/postgen/spdxConverter.js +796 -0
- package/lib/stages/postgen/specVersionCompat.js +703 -0
- package/lib/stages/pregen/envAudit.js +388 -0
- package/lib/stages/pregen/pregen.js +637 -0
- package/lib/third-party/README.md +9 -0
- package/lib/third-party/arborist/CHANGELOG.md +1109 -0
- package/lib/third-party/arborist/LICENSE.md +20 -0
- package/lib/third-party/arborist/README.md +315 -0
- package/lib/third-party/arborist/lib/arborist/index.js +258 -0
- package/lib/third-party/arborist/lib/arborist/load-actual.js +605 -0
- package/lib/third-party/arborist/lib/arborist/load-virtual.js +300 -0
- package/lib/third-party/arborist/lib/bin-links.js +120 -0
- package/lib/third-party/arborist/lib/calc-dep-flags.js +130 -0
- package/lib/third-party/arborist/lib/case-insensitive-map.js +51 -0
- package/lib/third-party/arborist/lib/consistent-resolve.js +42 -0
- package/lib/third-party/arborist/lib/debug.js +50 -0
- package/lib/third-party/arborist/lib/dep-valid.js +190 -0
- package/lib/third-party/arborist/lib/edge.js +417 -0
- package/lib/third-party/arborist/lib/from-path.js +36 -0
- package/lib/third-party/arborist/lib/gather-dep-set.js +43 -0
- package/lib/third-party/arborist/lib/index.js +22 -0
- package/lib/third-party/arborist/lib/inventory.js +138 -0
- package/lib/third-party/arborist/lib/link.js +169 -0
- package/lib/third-party/arborist/lib/node.js +1673 -0
- package/lib/third-party/arborist/lib/npm-extension.js +45 -0
- package/lib/third-party/arborist/lib/override-resolves.js +11 -0
- package/lib/third-party/arborist/lib/override-set.js +319 -0
- package/lib/third-party/arborist/lib/package-extensions.js +289 -0
- package/lib/third-party/arborist/lib/printable.js +210 -0
- package/lib/third-party/arborist/lib/proc-log.js +30 -0
- package/lib/third-party/arborist/lib/realpath.js +100 -0
- package/lib/third-party/arborist/lib/relpath.js +4 -0
- package/lib/third-party/arborist/lib/shrinkwrap.js +1291 -0
- package/lib/third-party/arborist/lib/spec-from-lock.js +34 -0
- package/lib/third-party/arborist/lib/tree-check.js +165 -0
- package/lib/third-party/arborist/lib/validate-npm-package-name.js +107 -0
- package/lib/third-party/arborist/lib/version-from-tgz.js +51 -0
- package/lib/third-party/arborist/lib/yarn-lock.js +397 -0
- package/lib/validator/bomValidator.js +985 -0
- package/lib/validator/complianceEngine.js +241 -0
- package/lib/validator/complianceRules.js +1680 -0
- package/lib/validator/generated/runtime.mjs +17 -0
- package/lib/validator/generated/validate-1.6.mjs +21258 -0
- package/lib/validator/generated/validate-1.7.mjs +24803 -0
- package/lib/validator/generated/validate-2.0.mjs +22931 -0
- package/lib/validator/index.js +246 -0
- package/lib/validator/reporters/annotations.js +142 -0
- package/lib/validator/reporters/console.js +160 -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/package.json +209 -26
- package/types/bin/audit.d.ts +3 -0
- package/types/bin/audit.d.ts.map +1 -0
- package/types/bin/cdxgen.d.ts +3 -0
- package/types/bin/cdxgen.d.ts.map +1 -0
- package/types/bin/convert.d.ts +3 -0
- package/types/bin/convert.d.ts.map +1 -0
- package/types/bin/evinse.d.ts +3 -0
- package/types/bin/evinse.d.ts.map +1 -0
- package/types/bin/hbom.d.ts +3 -0
- package/types/bin/hbom.d.ts.map +1 -0
- package/types/bin/repl.d.ts +3 -0
- package/types/bin/repl.d.ts.map +1 -0
- package/types/bin/sign.d.ts +3 -0
- package/types/bin/sign.d.ts.map +1 -0
- package/types/bin/tracebom.d.ts +3 -0
- package/types/bin/tracebom.d.ts.map +1 -0
- package/types/bin/validate.d.ts +14 -0
- package/types/bin/validate.d.ts.map +1 -0
- package/types/bin/verify.d.ts +3 -0
- package/types/bin/verify.d.ts.map +1 -0
- package/types/lib/audit/index.d.ts +153 -0
- package/types/lib/audit/index.d.ts.map +1 -0
- package/types/lib/audit/progress.d.ts +31 -0
- package/types/lib/audit/progress.d.ts.map +1 -0
- package/types/lib/audit/reporters.d.ts +62 -0
- package/types/lib/audit/reporters.d.ts.map +1 -0
- package/types/lib/audit/scoring.d.ts +36 -0
- package/types/lib/audit/scoring.d.ts.map +1 -0
- package/types/lib/audit/targets.d.ts +77 -0
- package/types/lib/audit/targets.d.ts.map +1 -0
- package/types/lib/cli/bomAssembly.d.ts +329 -0
- package/types/lib/cli/bomAssembly.d.ts.map +1 -0
- package/types/lib/cli/cliOptions.d.ts +156 -0
- package/types/lib/cli/cliOptions.d.ts.map +1 -0
- package/types/lib/cli/index.d.ts +107 -0
- package/types/lib/cli/index.d.ts.map +1 -0
- package/types/lib/cli/jsBom.d.ts +94 -0
- package/types/lib/cli/jsBom.d.ts.map +1 -0
- package/types/lib/cli/jvmBom.d.ts +54 -0
- package/types/lib/cli/jvmBom.d.ts.map +1 -0
- package/types/lib/cli/managedBom.d.ts +105 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -0
- package/types/lib/cli/nativeBom.d.ts +143 -0
- package/types/lib/cli/nativeBom.d.ts.map +1 -0
- package/types/lib/core/activity.d.ts +178 -0
- package/types/lib/core/activity.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +192 -0
- package/types/lib/core/env.d.ts.map +1 -0
- package/types/lib/core/fs.d.ts +158 -0
- package/types/lib/core/fs.d.ts.map +1 -0
- package/types/lib/core/httpClient.d.ts +149 -0
- package/types/lib/core/httpClient.d.ts.map +1 -0
- package/types/lib/core/logger.d.ts +50 -0
- package/types/lib/core/logger.d.ts.map +1 -0
- package/types/lib/core/parallel.d.ts +95 -0
- package/types/lib/core/parallel.d.ts.map +1 -0
- package/types/lib/core/paths.d.ts +36 -0
- package/types/lib/core/paths.d.ts.map +1 -0
- package/types/lib/core/projectConfig.d.ts +23 -0
- package/types/lib/core/projectConfig.d.ts.map +1 -0
- package/types/lib/core/propertySanitizer.d.ts +23 -0
- package/types/lib/core/propertySanitizer.d.ts.map +1 -0
- package/types/lib/core/spdx.d.ts +5 -0
- package/types/lib/core/spdx.d.ts.map +1 -0
- package/types/lib/core/state.d.ts +20 -0
- package/types/lib/core/state.d.ts.map +1 -0
- package/types/lib/core/text.d.ts +78 -0
- package/types/lib/core/text.d.ts.map +1 -0
- package/types/lib/core/ui.d.ts +87 -0
- package/types/lib/core/ui.d.ts.map +1 -0
- package/types/lib/core/urls.d.ts +43 -0
- package/types/lib/core/urls.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.d.ts +71 -0
- package/types/lib/ecosystems/asarutils.d.ts.map +1 -0
- package/types/lib/ecosystems/bunutils.d.ts +38 -0
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts +8 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -0
- package/types/lib/ecosystems/chromextutils.d.ts +97 -0
- package/types/lib/ecosystems/chromextutils.d.ts.map +1 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts +176 -0
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +35 -0
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +76 -0
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -0
- package/types/lib/ecosystems/dotnetutils.d.ts +45 -0
- package/types/lib/ecosystems/dotnetutils.d.ts.map +1 -0
- package/types/lib/ecosystems/ecosystems.d.ts +279 -0
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -0
- package/types/lib/ecosystems/golem.d.ts +60 -0
- package/types/lib/ecosystems/golem.d.ts.map +1 -0
- package/types/lib/ecosystems/gradleutils.d.ts +139 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -0
- package/types/lib/ecosystems/jsEvidence.d.ts +17 -0
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -0
- package/types/lib/ecosystems/npmutils.d.ts +183 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-bazel.d.ts +36 -0
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +116 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-gleam.d.ts +33 -0
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-go.d.ts +92 -0
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-js.d.ts +242 -0
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-jvm.d.ts +100 -0
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +450 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-python.d.ts +145 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +56 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-zig.d.ts +83 -0
- package/types/lib/ecosystems/parsers-zig.d.ts.map +1 -0
- package/types/lib/ecosystems/pep770.d.ts +38 -0
- package/types/lib/ecosystems/pep770.d.ts.map +1 -0
- package/types/lib/ecosystems/pylockutils.d.ts +68 -0
- package/types/lib/ecosystems/pylockutils.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +70 -0
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -0
- package/types/lib/ecosystems/registryProvenance.d.ts +26 -0
- package/types/lib/ecosystems/registryProvenance.d.ts.map +1 -0
- package/types/lib/ecosystems/remote/huggingface.d.ts +31 -0
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -0
- package/types/lib/ecosystems/rubyutils.d.ts +182 -0
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -0
- package/types/lib/ecosystems/rusi.d.ts +40 -0
- package/types/lib/ecosystems/rusi.d.ts.map +1 -0
- package/types/lib/ecosystems/sbtutils.d.ts +179 -0
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -0
- package/types/lib/ecosystems/tea.d.ts +180 -0
- package/types/lib/ecosystems/tea.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +41 -0
- package/types/lib/ecosystems/utils.d.ts.map +1 -0
- package/types/lib/ecosystems/zigResolver.d.ts +70 -0
- package/types/lib/ecosystems/zigResolver.d.ts.map +1 -0
- package/types/lib/evinser/db.d.ts +17 -0
- package/types/lib/evinser/db.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +354 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -0
- package/types/lib/evinser/scalasem.d.ts +15 -0
- package/types/lib/evinser/scalasem.d.ts.map +1 -0
- package/types/lib/evinser/swiftsem.d.ts +103 -0
- package/types/lib/evinser/swiftsem.d.ts.map +1 -0
- package/types/lib/helpers/annotationFormatter.d.ts +23 -0
- package/types/lib/helpers/annotationFormatter.d.ts.map +1 -0
- package/types/lib/helpers/bomSigner.d.ts +28 -0
- package/types/lib/helpers/bomSigner.d.ts.map +1 -0
- package/types/lib/helpers/exportUtils.d.ts +52 -0
- package/types/lib/helpers/exportUtils.d.ts.map +1 -0
- package/types/lib/helpers/remote/dependency-track.d.ts +23 -0
- package/types/lib/helpers/remote/dependency-track.d.ts.map +1 -0
- 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/inventory/agentFormulationParser.d.ts +21 -0
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -0
- package/types/lib/inventory/aiCollector.d.ts +61 -0
- package/types/lib/inventory/aiCollector.d.ts.map +1 -0
- package/types/lib/inventory/aiInventory.d.ts +103 -0
- package/types/lib/inventory/aiInventory.d.ts.map +1 -0
- package/types/lib/inventory/aiModelVariants.d.ts +31 -0
- package/types/lib/inventory/aiModelVariants.d.ts.map +1 -0
- package/types/lib/inventory/aiOversightCollector.d.ts +28 -0
- package/types/lib/inventory/aiOversightCollector.d.ts.map +1 -0
- package/types/lib/inventory/aiProvenanceCollector.d.ts +13 -0
- package/types/lib/inventory/aiProvenanceCollector.d.ts.map +1 -0
- package/types/lib/inventory/analyzer.d.ts +157 -0
- package/types/lib/inventory/analyzer.d.ts.map +1 -0
- package/types/lib/inventory/analyzerScope.d.ts +106 -0
- package/types/lib/inventory/analyzerScope.d.ts.map +1 -0
- package/types/lib/inventory/astWorker.d.ts +9 -0
- package/types/lib/inventory/astWorker.d.ts.map +1 -0
- package/types/lib/inventory/atomUtils.d.ts +159 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -0
- package/types/lib/inventory/auditCategories.d.ts +80 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -0
- package/types/lib/inventory/bomUtils.d.ts +145 -0
- package/types/lib/inventory/bomUtils.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +28 -0
- package/types/lib/inventory/cacheCommands.d.ts.map +1 -0
- package/types/lib/inventory/cacheDir.d.ts +55 -0
- package/types/lib/inventory/cacheDir.d.ts.map +1 -0
- package/types/lib/inventory/cbomutils.d.ts +37 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -0
- package/types/lib/inventory/cdxrs.d.ts +90 -0
- package/types/lib/inventory/cdxrs.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/azurePipelines.d.ts +27 -0
- package/types/lib/inventory/ciParsers/azurePipelines.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/circleCi.d.ts +27 -0
- package/types/lib/inventory/ciParsers/circleCi.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/common.d.ts +11 -0
- package/types/lib/inventory/ciParsers/common.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/githubActions.d.ts +38 -0
- package/types/lib/inventory/ciParsers/githubActions.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/gitlabCi.d.ts +26 -0
- package/types/lib/inventory/ciParsers/gitlabCi.d.ts.map +1 -0
- package/types/lib/inventory/ciParsers/jenkins.d.ts +26 -0
- package/types/lib/inventory/ciParsers/jenkins.d.ts.map +1 -0
- package/types/lib/inventory/citations.d.ts +90 -0
- package/types/lib/inventory/citations.d.ts.map +1 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts +14 -0
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -0
- package/types/lib/inventory/containerRisk.d.ts +20 -0
- package/types/lib/inventory/containerRisk.d.ts.map +1 -0
- package/types/lib/inventory/cryptoAlgorithmFamily.d.ts +37 -0
- package/types/lib/inventory/cryptoAlgorithmFamily.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +140 -0
- package/types/lib/inventory/deps.d.ts.map +1 -0
- package/types/lib/inventory/depsUtils.d.ts +74 -0
- package/types/lib/inventory/depsUtils.d.ts.map +1 -0
- package/types/lib/inventory/display.d.ts +227 -0
- package/types/lib/inventory/display.d.ts.map +1 -0
- package/types/lib/inventory/dosai.d.ts +130 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -0
- package/types/lib/inventory/dosaiParsers.d.ts +59 -0
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -0
- package/types/lib/inventory/dynamic.d.ts +25 -0
- package/types/lib/inventory/dynamic.d.ts.map +1 -0
- package/types/lib/inventory/envcontext.d.ts +377 -0
- package/types/lib/inventory/envcontext.d.ts.map +1 -0
- package/types/lib/inventory/evidenceUtils.d.ts +74 -0
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -0
- package/types/lib/inventory/fetchBatch.d.ts +127 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -0
- package/types/lib/inventory/fetchRate.d.ts +118 -0
- package/types/lib/inventory/fetchRate.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts +31 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -0
- package/types/lib/inventory/formulationParsers.d.ts +50 -0
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -0
- package/types/lib/inventory/gtfobins.d.ts +27 -0
- package/types/lib/inventory/gtfobins.d.ts.map +1 -0
- package/types/lib/inventory/hbom.d.ts +58 -0
- package/types/lib/inventory/hbom.d.ts.map +1 -0
- package/types/lib/inventory/hbomAnalysis.d.ts +56 -0
- package/types/lib/inventory/hbomAnalysis.d.ts.map +1 -0
- package/types/lib/inventory/hbomLoader.d.ts +7 -0
- package/types/lib/inventory/hbomLoader.d.ts.map +1 -0
- package/types/lib/inventory/hostTopology.d.ts +47 -0
- package/types/lib/inventory/hostTopology.d.ts.map +1 -0
- package/types/lib/inventory/inventoryStats.d.ts +42 -0
- package/types/lib/inventory/inventoryStats.d.ts.map +1 -0
- package/types/lib/inventory/jsonLike.d.ts +23 -0
- package/types/lib/inventory/jsonLike.d.ts.map +1 -0
- package/types/lib/inventory/licenseEnhancer.d.ts +70 -0
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -0
- package/types/lib/inventory/lolbas.d.ts +16 -0
- package/types/lib/inventory/lolbas.d.ts.map +1 -0
- package/types/lib/inventory/mcp.d.ts +29 -0
- package/types/lib/inventory/mcp.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts +20 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -0
- package/types/lib/inventory/mcpDiscovery.d.ts +33 -0
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -0
- package/types/lib/inventory/mcpPinning.d.ts +11 -0
- package/types/lib/inventory/mcpPinning.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +49 -0
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -0
- package/types/lib/inventory/osinfo.d.ts +201 -0
- package/types/lib/inventory/osinfo.d.ts.map +1 -0
- package/types/lib/inventory/osqueryTransform.d.ts +84 -0
- package/types/lib/inventory/osqueryTransform.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts +58 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -0
- package/types/lib/inventory/protobom.d.ts +71 -0
- package/types/lib/inventory/protobom.d.ts.map +1 -0
- package/types/lib/inventory/protobomLoader.d.ts +17 -0
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -0
- package/types/lib/inventory/provenanceUtils.d.ts +98 -0
- package/types/lib/inventory/provenanceUtils.d.ts.map +1 -0
- package/types/lib/inventory/purl.d.ts +313 -0
- package/types/lib/inventory/purl.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts +17 -0
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -0
- package/types/lib/inventory/sbomDocument.d.ts +59 -0
- package/types/lib/inventory/sbomDocument.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +163 -0
- package/types/lib/inventory/source.d.ts.map +1 -0
- package/types/lib/inventory/spdx.d.ts +66 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -0
- package/types/lib/inventory/spdxExpression.d.ts +58 -0
- package/types/lib/inventory/spdxExpression.d.ts.map +1 -0
- package/types/lib/inventory/spdxUtils.d.ts +12 -0
- package/types/lib/inventory/spdxUtils.d.ts.map +1 -0
- package/types/lib/inventory/table.d.ts +22 -0
- package/types/lib/inventory/table.d.ts.map +1 -0
- package/types/lib/inventory/traceRunner.d.ts +80 -0
- package/types/lib/inventory/traceRunner.d.ts.map +1 -0
- package/types/lib/inventory/unicodeScan.d.ts +46 -0
- package/types/lib/inventory/unicodeScan.d.ts.map +1 -0
- package/types/lib/managers/binary.d.ts +147 -0
- package/types/lib/managers/binary.d.ts.map +1 -0
- package/types/lib/managers/containerutils.d.ts +30 -0
- package/types/lib/managers/containerutils.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts +157 -0
- package/types/lib/managers/docker.d.ts.map +1 -0
- package/types/lib/managers/dockerConnection.d.ts +42 -0
- package/types/lib/managers/dockerConnection.d.ts.map +1 -0
- package/types/lib/managers/oci.d.ts +21 -0
- package/types/lib/managers/oci.d.ts.map +1 -0
- package/types/lib/managers/piptree.d.ts +13 -0
- package/types/lib/managers/piptree.d.ts.map +1 -0
- package/types/lib/parsers/cmakeCache.d.ts +70 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -0
- package/types/lib/parsers/gguf.d.ts +39 -0
- package/types/lib/parsers/gguf.d.ts.map +1 -0
- package/types/lib/parsers/gitAiNotes.d.ts +15 -0
- package/types/lib/parsers/gitAiNotes.d.ts.map +1 -0
- package/types/lib/parsers/gitmodules.d.ts +88 -0
- package/types/lib/parsers/gitmodules.d.ts.map +1 -0
- package/types/lib/parsers/htmlExtract.d.ts +34 -0
- package/types/lib/parsers/htmlExtract.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceManifest.d.ts +153 -0
- package/types/lib/parsers/huggingfaceManifest.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts +155 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -0
- package/types/lib/parsers/iri.d.ts +68 -0
- package/types/lib/parsers/iri.d.ts.map +1 -0
- package/types/lib/parsers/npmrc.d.ts +33 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -0
- package/types/lib/parsers/ollama.d.ts +22 -0
- package/types/lib/parsers/ollama.d.ts.map +1 -0
- package/types/lib/parsers/xml.d.ts +47 -0
- package/types/lib/parsers/xml.d.ts.map +1 -0
- package/types/lib/server/server.d.ts +51 -0
- package/types/lib/server/server.d.ts.map +1 -0
- package/types/lib/stages/postgen/annotator.d.ts +36 -0
- package/types/lib/stages/postgen/annotator.d.ts.map +1 -0
- package/types/lib/stages/postgen/auditBom.d.ts +92 -0
- package/types/lib/stages/postgen/auditBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/postgen.d.ts +59 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -0
- 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/postgen/sortBom.d.ts +34 -0
- package/types/lib/stages/postgen/sortBom.d.ts.map +1 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts +11 -0
- package/types/lib/stages/postgen/spdxConverter.d.ts.map +1 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts +71 -0
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -0
- package/types/lib/stages/pregen/envAudit.d.ts +14 -0
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -0
- package/types/lib/stages/pregen/pregen.d.ts +59 -0
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/arborist/index.d.ts +47 -0
- package/types/lib/third-party/arborist/lib/arborist/index.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts +4 -0
- package/types/lib/third-party/arborist/lib/arborist/load-actual.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts +4 -0
- package/types/lib/third-party/arborist/lib/arborist/load-virtual.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/bin-links.d.ts +18 -0
- package/types/lib/third-party/arborist/lib/bin-links.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/calc-dep-flags.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/calc-dep-flags.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts +9 -0
- package/types/lib/third-party/arborist/lib/case-insensitive-map.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/consistent-resolve.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/consistent-resolve.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/debug.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/debug.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/dep-valid.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/dep-valid.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/edge.d.ts +46 -0
- package/types/lib/third-party/arborist/lib/edge.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/from-path.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/from-path.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/gather-dep-set.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/index.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/index.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/inventory.d.ts +14 -0
- package/types/lib/third-party/arborist/lib/inventory.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/link.d.ts +18 -0
- package/types/lib/third-party/arborist/lib/link.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/node.d.ts +112 -0
- package/types/lib/third-party/arborist/lib/node.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/npm-extension.d.ts +18 -0
- package/types/lib/third-party/arborist/lib/npm-extension.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/override-resolves.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/override-resolves.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/override-set.d.ts +27 -0
- package/types/lib/third-party/arborist/lib/override-set.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/package-extensions.d.ts +36 -0
- package/types/lib/third-party/arborist/lib/package-extensions.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/printable.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/printable.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/proc-log.d.ts +20 -0
- package/types/lib/third-party/arborist/lib/proc-log.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/realpath.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/realpath.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/relpath.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/relpath.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts +54 -0
- package/types/lib/third-party/arborist/lib/shrinkwrap.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/spec-from-lock.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/spec-from-lock.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/tree-check.d.ts +3 -0
- package/types/lib/third-party/arborist/lib/tree-check.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/validate-npm-package-name.d.ts +13 -0
- package/types/lib/third-party/arborist/lib/validate-npm-package-name.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts +6 -0
- package/types/lib/third-party/arborist/lib/version-from-tgz.d.ts.map +1 -0
- package/types/lib/third-party/arborist/lib/yarn-lock.d.ts +35 -0
- package/types/lib/third-party/arborist/lib/yarn-lock.d.ts.map +1 -0
- package/types/lib/validator/bomValidator.d.ts +40 -0
- package/types/lib/validator/bomValidator.d.ts.map +1 -0
- package/types/lib/validator/complianceEngine.d.ts +72 -0
- package/types/lib/validator/complianceEngine.d.ts.map +1 -0
- package/types/lib/validator/complianceRules.d.ts +61 -0
- package/types/lib/validator/complianceRules.d.ts.map +1 -0
- package/types/lib/validator/index.d.ts +101 -0
- package/types/lib/validator/index.d.ts.map +1 -0
- package/types/lib/validator/reporters/annotations.d.ts +37 -0
- package/types/lib/validator/reporters/annotations.d.ts.map +1 -0
- package/types/lib/validator/reporters/console.d.ts +34 -0
- package/types/lib/validator/reporters/console.d.ts.map +1 -0
- package/types/lib/validator/reporters/index.d.ts +30 -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 +22 -0
- package/types/lib/validator/reporters/sarif.d.ts.map +1 -0
- package/mod.d.ts +0 -7
- package/mod.js +0 -22
|
@@ -0,0 +1,2070 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
4
|
+
import { URL } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { build } from "@cdxgen/cdx-purl";
|
|
7
|
+
import toml from "@iarna/toml";
|
|
8
|
+
import { parse as _load } from "yaml";
|
|
9
|
+
|
|
10
|
+
import { DEBUG_MODE } from "../core/activity.js";
|
|
11
|
+
import { getAllFiles, safeExistsSync, safeSpawnSync } from "../core/fs.js";
|
|
12
|
+
import { PYTHON_STD_MODULES } from "../core/state.js";
|
|
13
|
+
import { tryBuildPurl } from "../inventory/purl.js";
|
|
14
|
+
import {
|
|
15
|
+
createExternalReferenceKey,
|
|
16
|
+
getPyMetadata,
|
|
17
|
+
mergeExternalReferences,
|
|
18
|
+
} from "./ecosystems.js";
|
|
19
|
+
import {
|
|
20
|
+
collectPyLockDependencyRelationships,
|
|
21
|
+
collectPyLockFileComponents,
|
|
22
|
+
collectPyLockPackageProperties,
|
|
23
|
+
collectPyLockTopLevelProperties,
|
|
24
|
+
getPyLockPackages,
|
|
25
|
+
isDefaultPypiRegistry,
|
|
26
|
+
isPyLockObject,
|
|
27
|
+
normalizePyLockRegistry,
|
|
28
|
+
} from "./pylockutils.js";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Method to parse python requires_dist attribute found in pypi setup.py
|
|
32
|
+
*
|
|
33
|
+
* @param {String} dist_string string
|
|
34
|
+
*/
|
|
35
|
+
export function parsePyRequiresDist(dist_string) {
|
|
36
|
+
if (!dist_string) {
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
const tmpA = dist_string.split(" ");
|
|
40
|
+
let name = "";
|
|
41
|
+
let version = "";
|
|
42
|
+
if (!tmpA) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
if (tmpA.length === 1) {
|
|
46
|
+
name = tmpA[0];
|
|
47
|
+
} else if (tmpA.length > 1) {
|
|
48
|
+
name = tmpA[0];
|
|
49
|
+
const tmpVersion = tmpA[1];
|
|
50
|
+
version = tmpVersion.split(",")[0].replace(/[();=&glt><]/g, "");
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
name,
|
|
54
|
+
version,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Method to parse pipfile.lock data
|
|
60
|
+
*
|
|
61
|
+
* @param {Object} lockData JSON data from Pipfile.lock
|
|
62
|
+
*/
|
|
63
|
+
export async function parsePiplockData(lockData) {
|
|
64
|
+
const pkgList = [];
|
|
65
|
+
Object.keys(lockData)
|
|
66
|
+
.filter((i) => i !== "_meta")
|
|
67
|
+
.forEach((k) => {
|
|
68
|
+
const depBlock = lockData[k];
|
|
69
|
+
Object.keys(depBlock).forEach((p) => {
|
|
70
|
+
const pkg = depBlock[p];
|
|
71
|
+
if (Object.hasOwn(pkg, "version")) {
|
|
72
|
+
const versionStr = pkg.version.replace("==", "");
|
|
73
|
+
pkgList.push({ name: p, version: versionStr });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
return await getPyMetadata(pkgList, false);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Append a deduplicated name/value property to a component's properties array.
|
|
82
|
+
*
|
|
83
|
+
* @param {object} component Component to mutate.
|
|
84
|
+
* @param {string} name Property name.
|
|
85
|
+
* @param {string} value Property value.
|
|
86
|
+
* @returns {void}
|
|
87
|
+
*/
|
|
88
|
+
export function addComponentProperty(component, name, value) {
|
|
89
|
+
if (value === undefined || value === null || value === "" || !component) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
component.properties = component.properties || [];
|
|
93
|
+
if (
|
|
94
|
+
component.properties.some(
|
|
95
|
+
(property) => property.name === name && property.value === value,
|
|
96
|
+
)
|
|
97
|
+
) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
component.properties.push({
|
|
101
|
+
name,
|
|
102
|
+
value,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const PYTHON_DIRECT_REFERENCE_PATTERN =
|
|
107
|
+
/^([A-Za-z0-9_.-]+)(?:\[[^\]]+])?\s*@\s*(\S+)$/;
|
|
108
|
+
|
|
109
|
+
function isWindowsAbsolutePath(value) {
|
|
110
|
+
return /^[a-zA-Z]:[\\/]/.test(value) || value.startsWith("\\\\");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function normalizePythonDependencyKey(value) {
|
|
114
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
115
|
+
return undefined;
|
|
116
|
+
}
|
|
117
|
+
return value.trim().toLowerCase().replaceAll("_", "-");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function extractPythonDependencyKey(value) {
|
|
121
|
+
const manifestSource = parsePyProjectDependencySourceString(value);
|
|
122
|
+
if (manifestSource?.name) {
|
|
123
|
+
return normalizePythonDependencyKey(manifestSource.name);
|
|
124
|
+
}
|
|
125
|
+
const packageMatch =
|
|
126
|
+
typeof value === "string"
|
|
127
|
+
? value.trim().match(/^([A-Za-z0-9_.-]+)(?:\[[^\]]+])?/)
|
|
128
|
+
: undefined;
|
|
129
|
+
return normalizePythonDependencyKey(packageMatch?.[1]);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function classifyPythonManifestSourceValue(value) {
|
|
133
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
const normalizedValue = value.trim();
|
|
137
|
+
const lowerValue = normalizedValue.toLowerCase();
|
|
138
|
+
if (
|
|
139
|
+
lowerValue.startsWith("git+") ||
|
|
140
|
+
lowerValue.startsWith("git://") ||
|
|
141
|
+
lowerValue.startsWith("git@") ||
|
|
142
|
+
lowerValue.startsWith("ssh://git@")
|
|
143
|
+
) {
|
|
144
|
+
return {
|
|
145
|
+
type: "git",
|
|
146
|
+
value: normalizedValue,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (
|
|
150
|
+
lowerValue.startsWith("http://") ||
|
|
151
|
+
lowerValue.startsWith("https://") ||
|
|
152
|
+
lowerValue.startsWith("ftp://")
|
|
153
|
+
) {
|
|
154
|
+
return {
|
|
155
|
+
type: "url",
|
|
156
|
+
value: normalizedValue,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (
|
|
160
|
+
lowerValue.startsWith("file:") ||
|
|
161
|
+
normalizedValue.startsWith("./") ||
|
|
162
|
+
normalizedValue.startsWith("../") ||
|
|
163
|
+
normalizedValue.startsWith("/") ||
|
|
164
|
+
isWindowsAbsolutePath(normalizedValue)
|
|
165
|
+
) {
|
|
166
|
+
return {
|
|
167
|
+
type: "path",
|
|
168
|
+
value: normalizedValue,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function applyManifestSourceProperties(
|
|
175
|
+
component,
|
|
176
|
+
propertyPrefix,
|
|
177
|
+
manifestSource,
|
|
178
|
+
) {
|
|
179
|
+
if (!manifestSource?.type || !manifestSource?.value) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
addComponentProperty(
|
|
183
|
+
component,
|
|
184
|
+
`${propertyPrefix}:manifestSourceType`,
|
|
185
|
+
manifestSource.type,
|
|
186
|
+
);
|
|
187
|
+
addComponentProperty(
|
|
188
|
+
component,
|
|
189
|
+
`${propertyPrefix}:manifestSource`,
|
|
190
|
+
manifestSource.value,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function recordPythonDependencySource(
|
|
195
|
+
dependencySourceMap,
|
|
196
|
+
dependencyName,
|
|
197
|
+
sourceType,
|
|
198
|
+
sourceValue,
|
|
199
|
+
) {
|
|
200
|
+
const normalizedKey = normalizePythonDependencyKey(dependencyName);
|
|
201
|
+
if (!normalizedKey || !sourceType || !sourceValue) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
dependencySourceMap[normalizedKey] = {
|
|
205
|
+
type: sourceType,
|
|
206
|
+
value: sourceValue,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function parsePyProjectDependencySourceString(value) {
|
|
211
|
+
if (typeof value !== "string" || !value.includes("@")) {
|
|
212
|
+
return undefined;
|
|
213
|
+
}
|
|
214
|
+
const directReferenceMatch = value
|
|
215
|
+
.trim()
|
|
216
|
+
.match(PYTHON_DIRECT_REFERENCE_PATTERN);
|
|
217
|
+
if (!directReferenceMatch) {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
const manifestSource = classifyPythonManifestSourceValue(
|
|
221
|
+
directReferenceMatch[2],
|
|
222
|
+
);
|
|
223
|
+
if (!manifestSource) {
|
|
224
|
+
return undefined;
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
name: directReferenceMatch[1],
|
|
228
|
+
...manifestSource,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function collectPythonManifestSource(pkg) {
|
|
233
|
+
const sourceCandidates = [
|
|
234
|
+
{ kind: "git", value: pkg?.source?.git },
|
|
235
|
+
{ kind: "git", value: pkg?.vcs?.git },
|
|
236
|
+
{ kind: "url", value: pkg?.vcs?.url },
|
|
237
|
+
{ kind: "url", value: pkg?.source?.url },
|
|
238
|
+
{ kind: "path", value: pkg?.source?.path },
|
|
239
|
+
{ kind: "path", value: pkg?.source?.editable },
|
|
240
|
+
{ kind: "path", value: pkg?.source?.virtual },
|
|
241
|
+
];
|
|
242
|
+
for (const candidate of sourceCandidates) {
|
|
243
|
+
if (typeof candidate.value !== "string" || !candidate.value.trim()) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const normalizedValue = candidate.value.trim();
|
|
247
|
+
if (candidate.kind === "git") {
|
|
248
|
+
return {
|
|
249
|
+
type: "git",
|
|
250
|
+
value: normalizedValue.startsWith("git+")
|
|
251
|
+
? normalizedValue
|
|
252
|
+
: `git+${normalizedValue}`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const manifestSource = classifyPythonManifestSourceValue(normalizedValue);
|
|
256
|
+
if (manifestSource) {
|
|
257
|
+
return manifestSource;
|
|
258
|
+
}
|
|
259
|
+
return {
|
|
260
|
+
type: candidate.kind,
|
|
261
|
+
value: normalizedValue,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
return undefined;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function parsePythonRequirementManifestSource(value) {
|
|
268
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
269
|
+
return undefined;
|
|
270
|
+
}
|
|
271
|
+
const normalizedValue = value.trim();
|
|
272
|
+
const directReferenceMatch = normalizedValue.match(
|
|
273
|
+
PYTHON_DIRECT_REFERENCE_PATTERN,
|
|
274
|
+
);
|
|
275
|
+
if (directReferenceMatch) {
|
|
276
|
+
const manifestSource = classifyPythonManifestSourceValue(
|
|
277
|
+
directReferenceMatch[2],
|
|
278
|
+
);
|
|
279
|
+
if (manifestSource) {
|
|
280
|
+
return {
|
|
281
|
+
name: directReferenceMatch[1],
|
|
282
|
+
...manifestSource,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
const vcsRequirementMatch = normalizedValue.match(
|
|
287
|
+
/^(git\+\S+?)(?:#.*egg=([A-Za-z0-9_.-]+))?$/,
|
|
288
|
+
);
|
|
289
|
+
if (vcsRequirementMatch?.[2]) {
|
|
290
|
+
return {
|
|
291
|
+
name: vcsRequirementMatch[2],
|
|
292
|
+
type: "git",
|
|
293
|
+
value: vcsRequirementMatch[1],
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return undefined;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Method to parse python pyproject.toml file
|
|
301
|
+
*
|
|
302
|
+
* @param {string} tomlFile pyproject.toml file
|
|
303
|
+
* @returns {Object} Object with parent component, root dependencies, and metadata.
|
|
304
|
+
*/
|
|
305
|
+
export function parsePyProjectTomlFile(tomlFile) {
|
|
306
|
+
function handleBlock(pkg, atool) {
|
|
307
|
+
for (const k of ["name", "version", "description", "license"]) {
|
|
308
|
+
// We can copy string values as-is
|
|
309
|
+
if (
|
|
310
|
+
!pkg[k] &&
|
|
311
|
+
atool[k] &&
|
|
312
|
+
(typeof atool[k] === "string" || atool[k] instanceof String)
|
|
313
|
+
) {
|
|
314
|
+
pkg[k] = atool[k];
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (atool.authors) {
|
|
318
|
+
if (Array.isArray(atool.authors) && atool.authors.length > 0) {
|
|
319
|
+
// Multiple author objects
|
|
320
|
+
if (
|
|
321
|
+
Object.keys(atool.authors[0]).length &&
|
|
322
|
+
(atool.authors[0]?.name || atool.authors[0]?.email)
|
|
323
|
+
) {
|
|
324
|
+
pkg.authors = atool.authors;
|
|
325
|
+
} else {
|
|
326
|
+
pkg.author = atool.authors.join(", ");
|
|
327
|
+
}
|
|
328
|
+
} else if (
|
|
329
|
+
typeof atool.authors === "string" ||
|
|
330
|
+
atool.authors instanceof String
|
|
331
|
+
) {
|
|
332
|
+
pkg.author = atool.authors.trim();
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (atool.homepage) {
|
|
336
|
+
pkg.homepage = { url: atool.homepage };
|
|
337
|
+
}
|
|
338
|
+
if (atool.repository) {
|
|
339
|
+
pkg.repository = { url: atool.repository };
|
|
340
|
+
}
|
|
341
|
+
if (atool.keywords && Array.isArray(atool.keywords)) {
|
|
342
|
+
pkg.tags = atool.keywords.sort();
|
|
343
|
+
}
|
|
344
|
+
if (atool["requires-python"]) {
|
|
345
|
+
pkg.properties = [
|
|
346
|
+
{ name: "cdx:pypi:requiresPython", value: atool["requires-python"] },
|
|
347
|
+
];
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let poetryMode = false;
|
|
352
|
+
let uvMode = false;
|
|
353
|
+
let hatchMode = false;
|
|
354
|
+
const workspacePaths = [];
|
|
355
|
+
let tomlData;
|
|
356
|
+
const directDepsKeys = {};
|
|
357
|
+
const groupDepsKeys = {};
|
|
358
|
+
const dependencySourceMap = {};
|
|
359
|
+
try {
|
|
360
|
+
tomlData = toml.parse(readFileSync(tomlFile, { encoding: "utf-8" }));
|
|
361
|
+
} catch (err) {
|
|
362
|
+
console.log(`Error while parsing the pyproject file ${tomlFile}.`, err);
|
|
363
|
+
}
|
|
364
|
+
const pkg = {};
|
|
365
|
+
if (!tomlData) {
|
|
366
|
+
return {};
|
|
367
|
+
}
|
|
368
|
+
if (
|
|
369
|
+
tomlData?.tool?.poetry ||
|
|
370
|
+
tomlData?.["build-system"]?.["build-backend"]?.startsWith("poetry.core")
|
|
371
|
+
) {
|
|
372
|
+
poetryMode = true;
|
|
373
|
+
}
|
|
374
|
+
if (tomlData?.tool?.uv) {
|
|
375
|
+
uvMode = true;
|
|
376
|
+
}
|
|
377
|
+
if (tomlData?.["build-system"]?.["build-backend"]?.startsWith("hatchling.")) {
|
|
378
|
+
hatchMode = true;
|
|
379
|
+
}
|
|
380
|
+
if (
|
|
381
|
+
uvMode &&
|
|
382
|
+
tomlData.tool.uv.workspace &&
|
|
383
|
+
Array.isArray(tomlData.tool.uv.workspace?.members)
|
|
384
|
+
) {
|
|
385
|
+
for (const amember of tomlData.tool.uv.workspace.members) {
|
|
386
|
+
const memberPyProjPaths = amember.endsWith("/*")
|
|
387
|
+
? amember.replace(/\/\*$/, "/**/pyproject.toml")
|
|
388
|
+
: `${amember}/**/pyproject.toml`;
|
|
389
|
+
workspacePaths.push(memberPyProjPaths);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// uv and others
|
|
393
|
+
if (tomlData?.project && Object.keys(tomlData.project).length) {
|
|
394
|
+
handleBlock(pkg, tomlData.project);
|
|
395
|
+
}
|
|
396
|
+
if (tomlData?.tool && Object.keys(tomlData.tool).length) {
|
|
397
|
+
for (const atoolKey of Object.keys(tomlData.tool)) {
|
|
398
|
+
const atool = tomlData.tool[atoolKey];
|
|
399
|
+
handleBlock(pkg, atool);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (pkg.name) {
|
|
403
|
+
pkg.type = "application";
|
|
404
|
+
const ppurl = build({
|
|
405
|
+
type: "pypi",
|
|
406
|
+
namespace: pkg.group || "" || null,
|
|
407
|
+
name: pkg.name,
|
|
408
|
+
version: pkg.version || "latest" || null,
|
|
409
|
+
});
|
|
410
|
+
pkg["bom-ref"] = decodeURIComponent(ppurl);
|
|
411
|
+
pkg["purl"] = ppurl;
|
|
412
|
+
pkg.evidence = {
|
|
413
|
+
identity: {
|
|
414
|
+
field: "purl",
|
|
415
|
+
confidence: 1,
|
|
416
|
+
methods: [
|
|
417
|
+
{
|
|
418
|
+
technique: "manifest-analysis",
|
|
419
|
+
confidence: 1,
|
|
420
|
+
value: tomlFile,
|
|
421
|
+
},
|
|
422
|
+
],
|
|
423
|
+
},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
if (tomlData?.project?.dependencies) {
|
|
427
|
+
for (const adep of tomlData.project.dependencies) {
|
|
428
|
+
const dependencyKey = extractPythonDependencyKey(adep);
|
|
429
|
+
if (dependencyKey) {
|
|
430
|
+
directDepsKeys[dependencyKey] = true;
|
|
431
|
+
}
|
|
432
|
+
const manifestSource = parsePyProjectDependencySourceString(adep);
|
|
433
|
+
if (manifestSource) {
|
|
434
|
+
recordPythonDependencySource(
|
|
435
|
+
dependencySourceMap,
|
|
436
|
+
manifestSource.name,
|
|
437
|
+
manifestSource.type,
|
|
438
|
+
manifestSource.value,
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (tomlData["dependency-groups"]) {
|
|
444
|
+
for (const agroup of Object.keys(tomlData["dependency-groups"])) {
|
|
445
|
+
tomlData["dependency-groups"][agroup].forEach((p) => {
|
|
446
|
+
if (typeof p === "string" || p instanceof String) {
|
|
447
|
+
const pname = p.split(/(==|<=|~=|>=)/)[0].split(" ")[0];
|
|
448
|
+
if (!groupDepsKeys[pname]) {
|
|
449
|
+
groupDepsKeys[pname] = [];
|
|
450
|
+
}
|
|
451
|
+
groupDepsKeys[pname].push(agroup);
|
|
452
|
+
const manifestSource = parsePyProjectDependencySourceString(p);
|
|
453
|
+
if (manifestSource) {
|
|
454
|
+
recordPythonDependencySource(
|
|
455
|
+
dependencySourceMap,
|
|
456
|
+
manifestSource.name,
|
|
457
|
+
manifestSource.type,
|
|
458
|
+
manifestSource.value,
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (tomlData?.tool?.poetry?.dependencies) {
|
|
468
|
+
for (const adep of Object.keys(tomlData?.tool?.poetry?.dependencies)) {
|
|
469
|
+
if (
|
|
470
|
+
![
|
|
471
|
+
"python",
|
|
472
|
+
"py",
|
|
473
|
+
"pytest",
|
|
474
|
+
"pylint",
|
|
475
|
+
"ruff",
|
|
476
|
+
"setuptools",
|
|
477
|
+
"bandit",
|
|
478
|
+
].includes(adep)
|
|
479
|
+
) {
|
|
480
|
+
directDepsKeys[adep] = true;
|
|
481
|
+
const poetryDependency = tomlData.tool.poetry.dependencies[adep];
|
|
482
|
+
if (poetryDependency?.git) {
|
|
483
|
+
recordPythonDependencySource(
|
|
484
|
+
dependencySourceMap,
|
|
485
|
+
adep,
|
|
486
|
+
"git",
|
|
487
|
+
poetryDependency.git,
|
|
488
|
+
);
|
|
489
|
+
} else if (poetryDependency?.url) {
|
|
490
|
+
recordPythonDependencySource(
|
|
491
|
+
dependencySourceMap,
|
|
492
|
+
adep,
|
|
493
|
+
"url",
|
|
494
|
+
poetryDependency.url,
|
|
495
|
+
);
|
|
496
|
+
} else if (poetryDependency?.path) {
|
|
497
|
+
recordPythonDependencySource(
|
|
498
|
+
dependencySourceMap,
|
|
499
|
+
adep,
|
|
500
|
+
"path",
|
|
501
|
+
poetryDependency.path,
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
} // for
|
|
506
|
+
if (tomlData?.tool?.poetry?.group) {
|
|
507
|
+
for (const agroup of Object.keys(tomlData.tool.poetry.group)) {
|
|
508
|
+
for (const adep of Object.keys(
|
|
509
|
+
tomlData.tool.poetry.group[agroup]?.dependencies,
|
|
510
|
+
)) {
|
|
511
|
+
if (!groupDepsKeys[adep]) {
|
|
512
|
+
groupDepsKeys[adep] = [];
|
|
513
|
+
}
|
|
514
|
+
groupDepsKeys[adep].push(agroup);
|
|
515
|
+
const poetryDependency =
|
|
516
|
+
tomlData.tool.poetry.group[agroup]?.dependencies?.[adep];
|
|
517
|
+
if (poetryDependency?.git) {
|
|
518
|
+
recordPythonDependencySource(
|
|
519
|
+
dependencySourceMap,
|
|
520
|
+
adep,
|
|
521
|
+
"git",
|
|
522
|
+
poetryDependency.git,
|
|
523
|
+
);
|
|
524
|
+
} else if (poetryDependency?.url) {
|
|
525
|
+
recordPythonDependencySource(
|
|
526
|
+
dependencySourceMap,
|
|
527
|
+
adep,
|
|
528
|
+
"url",
|
|
529
|
+
poetryDependency.url,
|
|
530
|
+
);
|
|
531
|
+
} else if (poetryDependency?.path) {
|
|
532
|
+
recordPythonDependencySource(
|
|
533
|
+
dependencySourceMap,
|
|
534
|
+
adep,
|
|
535
|
+
"path",
|
|
536
|
+
poetryDependency.path,
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
} // for
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
if (tomlData?.tool?.uv?.sources) {
|
|
544
|
+
for (const adep of Object.keys(tomlData.tool.uv.sources)) {
|
|
545
|
+
const uvSource = Array.isArray(tomlData.tool.uv.sources[adep])
|
|
546
|
+
? tomlData.tool.uv.sources[adep][0]
|
|
547
|
+
: tomlData.tool.uv.sources[adep];
|
|
548
|
+
if (uvSource?.git) {
|
|
549
|
+
recordPythonDependencySource(
|
|
550
|
+
dependencySourceMap,
|
|
551
|
+
adep,
|
|
552
|
+
"git",
|
|
553
|
+
uvSource.git,
|
|
554
|
+
);
|
|
555
|
+
} else if (uvSource?.url) {
|
|
556
|
+
recordPythonDependencySource(
|
|
557
|
+
dependencySourceMap,
|
|
558
|
+
adep,
|
|
559
|
+
"url",
|
|
560
|
+
uvSource.url,
|
|
561
|
+
);
|
|
562
|
+
} else if (uvSource?.path) {
|
|
563
|
+
recordPythonDependencySource(
|
|
564
|
+
dependencySourceMap,
|
|
565
|
+
adep,
|
|
566
|
+
"path",
|
|
567
|
+
uvSource.path,
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
return {
|
|
573
|
+
parentComponent: pkg,
|
|
574
|
+
poetryMode,
|
|
575
|
+
uvMode,
|
|
576
|
+
hatchMode,
|
|
577
|
+
workspacePaths,
|
|
578
|
+
directDepsKeys,
|
|
579
|
+
groupDepsKeys,
|
|
580
|
+
dependencySourceMap,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function collectPythonLockDistributionReferences(pkg) {
|
|
585
|
+
const externalReferences = [];
|
|
586
|
+
const seen = new Set();
|
|
587
|
+
|
|
588
|
+
function addExternalReference(type, url, comment) {
|
|
589
|
+
if (typeof url !== "string" || !url.trim()) {
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
const normalizedUrl = url.trim();
|
|
593
|
+
const reference = {
|
|
594
|
+
type,
|
|
595
|
+
url: normalizedUrl,
|
|
596
|
+
comment,
|
|
597
|
+
};
|
|
598
|
+
const referenceKey = createExternalReferenceKey(reference);
|
|
599
|
+
if (seen.has(referenceKey)) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
seen.add(referenceKey);
|
|
603
|
+
externalReferences.push(reference);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
addExternalReference("distribution", pkg?.archive?.url, "archive");
|
|
607
|
+
addExternalReference("distribution", pkg?.sdist?.url, "sdist");
|
|
608
|
+
if (Array.isArray(pkg?.wheels)) {
|
|
609
|
+
for (const wheel of pkg.wheels) {
|
|
610
|
+
addExternalReference(
|
|
611
|
+
"distribution",
|
|
612
|
+
wheel?.url,
|
|
613
|
+
wheel?.file || wheel?.name || wheel?.filename || "wheel",
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
const vcsSource = [
|
|
618
|
+
{ kind: "url", value: pkg?.vcs?.url },
|
|
619
|
+
{ kind: "git", value: pkg?.vcs?.git },
|
|
620
|
+
{ kind: "git", value: pkg?.source?.git },
|
|
621
|
+
].find(
|
|
622
|
+
(entry) => typeof entry.value === "string" && entry.value.trim().length > 0,
|
|
623
|
+
);
|
|
624
|
+
if (vcsSource) {
|
|
625
|
+
const vcsUrl = vcsSource.value.trim();
|
|
626
|
+
const normalizedVcsUrl =
|
|
627
|
+
vcsSource.kind === "git" && !vcsUrl.startsWith("git+")
|
|
628
|
+
? `git+${vcsUrl}`
|
|
629
|
+
: vcsUrl;
|
|
630
|
+
addExternalReference("vcs", normalizedVcsUrl, "vcs");
|
|
631
|
+
}
|
|
632
|
+
if (pkg?.source?.url) {
|
|
633
|
+
const manifestSource = classifyPythonManifestSourceValue(pkg.source.url);
|
|
634
|
+
addExternalReference(
|
|
635
|
+
manifestSource?.type === "git" ? "vcs" : "distribution",
|
|
636
|
+
pkg.source.url,
|
|
637
|
+
"source",
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
return externalReferences;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function collectPythonLockMetadataFileEntries(lockTomlObj, pkg) {
|
|
644
|
+
if (!lockTomlObj?.metadata?.files || !pkg?.name) {
|
|
645
|
+
return [];
|
|
646
|
+
}
|
|
647
|
+
const expectedKeys = new Set([normalizePythonDependencyKey(pkg.name)]);
|
|
648
|
+
if (pkg.version) {
|
|
649
|
+
expectedKeys.add(
|
|
650
|
+
`${normalizePythonDependencyKey(pkg.name)} ${`${pkg.version}`.trim().toLowerCase()}`,
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
const matchingEntries = [];
|
|
654
|
+
for (const [entryKey, entryValues] of Object.entries(
|
|
655
|
+
lockTomlObj.metadata.files,
|
|
656
|
+
)) {
|
|
657
|
+
if (!Array.isArray(entryValues)) {
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
if (expectedKeys.has(normalizePythonDependencyKey(entryKey))) {
|
|
661
|
+
matchingEntries.push(...entryValues);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
return matchingEntries;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Derive a file name for a file entry of a python lock file.
|
|
669
|
+
*
|
|
670
|
+
* - poetry.lock `[metadata.files]` entries carry a `file` key.
|
|
671
|
+
* - pdm.lock `[metadata.files]` entries carry a `url` key (no `file`).
|
|
672
|
+
* - pylock.toml / uv.lock artifacts can carry an explicit `name`, a local
|
|
673
|
+
* `path`, and/or a `url`.
|
|
674
|
+
*
|
|
675
|
+
* @param {object} fileEntry A single lock-file file entry.
|
|
676
|
+
* @returns {string | undefined} The derived file name, or undefined when none can be derived.
|
|
677
|
+
*/
|
|
678
|
+
export function derivePythonLockMetadataFileName(fileEntry) {
|
|
679
|
+
if (!fileEntry || typeof fileEntry !== "object") {
|
|
680
|
+
return undefined;
|
|
681
|
+
}
|
|
682
|
+
// Explicit filenames (pylock `name`, poetry `file`) win over derived ones.
|
|
683
|
+
for (const key of ["file", "name"]) {
|
|
684
|
+
const value = fileEntry[key];
|
|
685
|
+
if (typeof value === "string" && value.trim()) {
|
|
686
|
+
return basename(value.trim());
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
// Local artifact paths (pylock `path`).
|
|
690
|
+
if (typeof fileEntry.path === "string" && fileEntry.path.trim()) {
|
|
691
|
+
const name = basename(fileEntry.path.trim());
|
|
692
|
+
if (name) {
|
|
693
|
+
return name;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
// Remote artifact URLs (pdm `url`, uv/pylock `url`). Strip the query string
|
|
697
|
+
// and fragment by reading the pathname, and percent-decode the basename.
|
|
698
|
+
if (typeof fileEntry.url === "string" && fileEntry.url.trim()) {
|
|
699
|
+
const rawUrl = fileEntry.url.trim();
|
|
700
|
+
let name;
|
|
701
|
+
try {
|
|
702
|
+
name = basename(new URL(rawUrl).pathname);
|
|
703
|
+
} catch (_err) {
|
|
704
|
+
// Not an absolute URL - treat it as a path-like string.
|
|
705
|
+
name = basename(rawUrl);
|
|
706
|
+
}
|
|
707
|
+
if (name) {
|
|
708
|
+
try {
|
|
709
|
+
name = decodeURIComponent(name);
|
|
710
|
+
} catch (_err) {
|
|
711
|
+
// Ignore malformed URLs and fall through to undefined.
|
|
712
|
+
}
|
|
713
|
+
if (name) {
|
|
714
|
+
return name;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
return undefined;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function collectPythonLockMetadataDistributionReferences(fileEntries) {
|
|
722
|
+
const distributionReferences = [];
|
|
723
|
+
for (const fileEntry of fileEntries || []) {
|
|
724
|
+
if (typeof fileEntry?.url !== "string" || !fileEntry.url.trim()) {
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
727
|
+
distributionReferences.push({
|
|
728
|
+
type: "distribution",
|
|
729
|
+
url: fileEntry.url.trim(),
|
|
730
|
+
comment: fileEntry.file,
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
return distributionReferences;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Method to parse python lock files such as poetry.lock, pdm.lock, uv.lock, and pylock.toml.
|
|
738
|
+
*
|
|
739
|
+
* @param {string} lockData Raw TOML text from poetry.lock, pdm.lock, uv.lock, or pylock.toml
|
|
740
|
+
* @param {string} lockFile Lock file name for evidence
|
|
741
|
+
* @param {string} pyProjectFile pyproject.toml file
|
|
742
|
+
*/
|
|
743
|
+
export async function parsePyLockData(lockData, lockFile, pyProjectFile) {
|
|
744
|
+
let pkgList = [];
|
|
745
|
+
const rootList = [];
|
|
746
|
+
const dependenciesList = [];
|
|
747
|
+
const depsMap = {};
|
|
748
|
+
const existingPkgMap = {};
|
|
749
|
+
const pkgBomRefMap = {};
|
|
750
|
+
let directDepsKeys = {};
|
|
751
|
+
let groupDepsKeys = {};
|
|
752
|
+
let dependencySourceMap = {};
|
|
753
|
+
let parentComponent;
|
|
754
|
+
let workspacePaths;
|
|
755
|
+
let workspaceWarningShown = false;
|
|
756
|
+
let hasWorkspaces = false;
|
|
757
|
+
let pyLockProperties = [];
|
|
758
|
+
// Keep track of any workspace components to be added to the parent component
|
|
759
|
+
const workspaceComponentMap = {};
|
|
760
|
+
const workspaceDependencySourceMap = {};
|
|
761
|
+
const workspacePyProjMap = {};
|
|
762
|
+
const workspaceRefPyProjMap = {};
|
|
763
|
+
const pkgParentMap = {};
|
|
764
|
+
if (!lockData) {
|
|
765
|
+
return { pkgList, dependenciesList };
|
|
766
|
+
}
|
|
767
|
+
if (!pyProjectFile && lockFile) {
|
|
768
|
+
// See if there is a pyproject.toml in the same directory
|
|
769
|
+
pyProjectFile = join(dirname(lockFile), "pyproject.toml");
|
|
770
|
+
}
|
|
771
|
+
if (pyProjectFile && safeExistsSync(pyProjectFile)) {
|
|
772
|
+
if (DEBUG_MODE) {
|
|
773
|
+
console.log(
|
|
774
|
+
`Parsing ${pyProjectFile} for dependencies and groups information.`,
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
const pyProjMap = parsePyProjectTomlFile(pyProjectFile);
|
|
778
|
+
directDepsKeys = pyProjMap.directDepsKeys || {};
|
|
779
|
+
groupDepsKeys = pyProjMap.groupDepsKeys || {};
|
|
780
|
+
dependencySourceMap = pyProjMap.dependencySourceMap || {};
|
|
781
|
+
parentComponent = pyProjMap.parentComponent;
|
|
782
|
+
workspacePaths = pyProjMap.workspacePaths;
|
|
783
|
+
if (workspacePaths?.length) {
|
|
784
|
+
if (!hasWorkspaces) {
|
|
785
|
+
hasWorkspaces = true;
|
|
786
|
+
}
|
|
787
|
+
// Parent component is going to have children
|
|
788
|
+
parentComponent.components = [];
|
|
789
|
+
for (const awpath of workspacePaths) {
|
|
790
|
+
const wpyprojfiles = getAllFiles(dirname(lockFile), awpath);
|
|
791
|
+
if (!wpyprojfiles?.length) {
|
|
792
|
+
if (!workspaceWarningShown) {
|
|
793
|
+
console.log(
|
|
794
|
+
`Unable to collect pyproject.toml files for the workspace pattern ${awpath}. Ensure cdxgen is run from the root directory containing the application source code.`,
|
|
795
|
+
);
|
|
796
|
+
console.log(
|
|
797
|
+
"The dependency tree in the generated SBOM will be flattened and therefore incorrect.",
|
|
798
|
+
);
|
|
799
|
+
workspaceWarningShown = true;
|
|
800
|
+
}
|
|
801
|
+
continue;
|
|
802
|
+
}
|
|
803
|
+
for (const awpyproj of wpyprojfiles) {
|
|
804
|
+
if (DEBUG_MODE) {
|
|
805
|
+
console.log(
|
|
806
|
+
`Parsing workspace ${awpyproj} to improve the dependency tree.`,
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
// Nested workspace is not supported
|
|
810
|
+
const wcompMap = parsePyProjectTomlFile(awpyproj);
|
|
811
|
+
if (wcompMap?.parentComponent) {
|
|
812
|
+
wcompMap.parentComponent.properties =
|
|
813
|
+
wcompMap.parentComponent.properties || [];
|
|
814
|
+
wcompMap.parentComponent.properties.push({
|
|
815
|
+
name: "internal:is_workspace",
|
|
816
|
+
value: "true",
|
|
817
|
+
});
|
|
818
|
+
wcompMap.parentComponent.properties.push({
|
|
819
|
+
name: "internal:SrcFile",
|
|
820
|
+
value: awpyproj,
|
|
821
|
+
});
|
|
822
|
+
wcompMap.parentComponent.properties.push({
|
|
823
|
+
name: "internal:virtual_path",
|
|
824
|
+
value: relative(dirname(lockFile), dirname(awpyproj)),
|
|
825
|
+
});
|
|
826
|
+
workspaceComponentMap[wcompMap.parentComponent.name] =
|
|
827
|
+
wcompMap.parentComponent;
|
|
828
|
+
workspacePyProjMap[wcompMap.parentComponent.name] = awpyproj;
|
|
829
|
+
if (wcompMap.parentComponent["bom-ref"]) {
|
|
830
|
+
workspaceRefPyProjMap[wcompMap.parentComponent["bom-ref"]] =
|
|
831
|
+
awpyproj;
|
|
832
|
+
}
|
|
833
|
+
// uv.lock auto normalizes names containing underscores
|
|
834
|
+
if (wcompMap.parentComponent.name.includes("_")) {
|
|
835
|
+
workspaceComponentMap[
|
|
836
|
+
wcompMap.parentComponent.name.replaceAll("_", "-")
|
|
837
|
+
] = wcompMap.parentComponent;
|
|
838
|
+
workspacePyProjMap[
|
|
839
|
+
wcompMap.parentComponent.name.replaceAll("_", "-")
|
|
840
|
+
] = awpyproj;
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
const wparentComponentRef = wcompMap.parentComponent["bom-ref"];
|
|
844
|
+
if (wcompMap?.dependencySourceMap) {
|
|
845
|
+
Object.assign(
|
|
846
|
+
workspaceDependencySourceMap,
|
|
847
|
+
wcompMap.dependencySourceMap,
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
// Track the parents of workspace direct dependencies
|
|
851
|
+
if (wcompMap?.directDepsKeys) {
|
|
852
|
+
for (const wdd of Object.keys(wcompMap?.directDepsKeys)) {
|
|
853
|
+
if (!pkgParentMap[wdd]) {
|
|
854
|
+
pkgParentMap[wdd] = [];
|
|
855
|
+
}
|
|
856
|
+
pkgParentMap[wdd].push(wparentComponentRef);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
let lockTomlObj;
|
|
864
|
+
try {
|
|
865
|
+
lockTomlObj = toml.parse(lockData);
|
|
866
|
+
} catch (err) {
|
|
867
|
+
if (lockFile) {
|
|
868
|
+
console.log(`Error while parsing the lock file ${lockFile}.`, err);
|
|
869
|
+
} else {
|
|
870
|
+
console.log("Error while parsing the lock data as toml", err);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
// Check for workspaces
|
|
874
|
+
if (lockTomlObj?.manifest?.members) {
|
|
875
|
+
const workspaceMembers = lockTomlObj.manifest.members;
|
|
876
|
+
if (workspaceMembers && !hasWorkspaces) {
|
|
877
|
+
hasWorkspaces = true;
|
|
878
|
+
}
|
|
879
|
+
for (const amember of workspaceMembers) {
|
|
880
|
+
if (amember === parentComponent.name) {
|
|
881
|
+
continue;
|
|
882
|
+
}
|
|
883
|
+
if (workspaceComponentMap[amember]) {
|
|
884
|
+
parentComponent.components.push(workspaceComponentMap[amember]);
|
|
885
|
+
} else {
|
|
886
|
+
if (!workspaceWarningShown) {
|
|
887
|
+
console.log(
|
|
888
|
+
`Unable to identify the metadata for the workspace ${amember}. Check if the path specified in ${workspacePyProjMap[amember] || pyProjectFile} is valid.`,
|
|
889
|
+
);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
const pyLockMode = isPyLockObject(lockTomlObj);
|
|
895
|
+
if (pyLockMode) {
|
|
896
|
+
pyLockProperties = collectPyLockTopLevelProperties(lockTomlObj);
|
|
897
|
+
if (parentComponent) {
|
|
898
|
+
parentComponent.properties = parentComponent.properties || [];
|
|
899
|
+
parentComponent.properties =
|
|
900
|
+
parentComponent.properties.concat(pyLockProperties);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
const packageEntries = getPyLockPackages(lockTomlObj);
|
|
904
|
+
for (const apkg of packageEntries) {
|
|
905
|
+
// This avoids validation errors with uv.lock
|
|
906
|
+
if (parentComponent?.name && parentComponent.name === apkg.name) {
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
const pkg = {
|
|
910
|
+
name: apkg.name,
|
|
911
|
+
version: apkg.version,
|
|
912
|
+
description: apkg.description || "",
|
|
913
|
+
properties: [],
|
|
914
|
+
};
|
|
915
|
+
if (pyProjectFile || workspacePyProjMap[apkg.name]) {
|
|
916
|
+
pkg.properties.push({
|
|
917
|
+
name: "internal:SrcFile",
|
|
918
|
+
value: workspacePyProjMap[apkg.name] || pyProjectFile,
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
const manifestSource =
|
|
922
|
+
dependencySourceMap[normalizePythonDependencyKey(apkg.name)] ||
|
|
923
|
+
workspaceDependencySourceMap[normalizePythonDependencyKey(apkg.name)] ||
|
|
924
|
+
collectPythonManifestSource(apkg);
|
|
925
|
+
applyManifestSourceProperties(pkg, "cdx:pypi", manifestSource);
|
|
926
|
+
if (apkg.optional) {
|
|
927
|
+
pkg.scope = "optional";
|
|
928
|
+
}
|
|
929
|
+
// poetry/pdm/uv use "python-versions", while pylock (PEP 751) uses "requires-python".
|
|
930
|
+
// Prefer the existing lock-family field when both are present.
|
|
931
|
+
const requiresPython = apkg["python-versions"] || apkg["requires-python"];
|
|
932
|
+
if (requiresPython) {
|
|
933
|
+
pkg.properties.push({
|
|
934
|
+
name: "cdx:pypi:requiresPython",
|
|
935
|
+
value: requiresPython,
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
if (apkg.index && !isDefaultPypiRegistry(apkg.index)) {
|
|
939
|
+
pkg.properties.push({
|
|
940
|
+
name: "cdx:pypi:registry",
|
|
941
|
+
value: normalizePyLockRegistry(apkg.index),
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
if (apkg?.source) {
|
|
945
|
+
if (
|
|
946
|
+
apkg.source.registry &&
|
|
947
|
+
!apkg?.source?.registry?.startsWith("https://pypi.org/")
|
|
948
|
+
) {
|
|
949
|
+
pkg.properties.push({
|
|
950
|
+
name: "cdx:pypi:registry",
|
|
951
|
+
value: normalizePyLockRegistry(apkg.source.registry),
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
if (apkg?.source?.virtual) {
|
|
955
|
+
pkg.properties.push({
|
|
956
|
+
name: "internal:virtual_path",
|
|
957
|
+
value: workspacePyProjMap[apkg.name] || apkg.source.virtual,
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
if (apkg?.source?.editable) {
|
|
961
|
+
pkg.properties.push({
|
|
962
|
+
name: "internal:virtual_path",
|
|
963
|
+
value: apkg.source.editable,
|
|
964
|
+
});
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
mergeExternalReferences(pkg, collectPythonLockDistributionReferences(apkg));
|
|
968
|
+
if (pyLockMode) {
|
|
969
|
+
pkg.properties = pkg.properties.concat(
|
|
970
|
+
collectPyLockPackageProperties(apkg),
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
// Is this component a module?
|
|
974
|
+
if (workspaceComponentMap[pkg.name]) {
|
|
975
|
+
pkg.properties.push({
|
|
976
|
+
name: "internal:is_workspace",
|
|
977
|
+
value: "true",
|
|
978
|
+
});
|
|
979
|
+
pkg.type = "application";
|
|
980
|
+
}
|
|
981
|
+
const purlString = build({
|
|
982
|
+
type: "pypi",
|
|
983
|
+
namespace: "" || null,
|
|
984
|
+
name: pkg.name,
|
|
985
|
+
version: pkg.version || null,
|
|
986
|
+
});
|
|
987
|
+
pkg.purl = purlString;
|
|
988
|
+
pkg["bom-ref"] = decodeURIComponent(purlString);
|
|
989
|
+
if (parentComponent && pkg["bom-ref"] === parentComponent["bom-ref"]) {
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
pkg.evidence = {
|
|
993
|
+
identity: {
|
|
994
|
+
field: "purl",
|
|
995
|
+
confidence: 1,
|
|
996
|
+
methods: [
|
|
997
|
+
{
|
|
998
|
+
technique: "manifest-analysis",
|
|
999
|
+
confidence: 1,
|
|
1000
|
+
value: lockFile,
|
|
1001
|
+
},
|
|
1002
|
+
],
|
|
1003
|
+
},
|
|
1004
|
+
};
|
|
1005
|
+
if (groupDepsKeys?.[pkg.name]) {
|
|
1006
|
+
pkg.scope = "optional";
|
|
1007
|
+
pkg.properties = pkg.properties.concat(
|
|
1008
|
+
groupDepsKeys[pkg.name].map((g) => {
|
|
1009
|
+
return { name: "cdx:pyproject:group", value: g };
|
|
1010
|
+
}),
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
// Track the workspace purls that had an explicit dependency on this package
|
|
1014
|
+
if (pkgParentMap[pkg.name]) {
|
|
1015
|
+
for (const workspaceRef of pkgParentMap[pkg.name]) {
|
|
1016
|
+
pkg.properties.push({
|
|
1017
|
+
name: "internal:workspaceRef",
|
|
1018
|
+
value: workspaceRef,
|
|
1019
|
+
});
|
|
1020
|
+
if (workspaceRefPyProjMap[workspaceRef]) {
|
|
1021
|
+
pkg.properties.push({
|
|
1022
|
+
name: "internal:workspaceSrcFile",
|
|
1023
|
+
value: workspaceRefPyProjMap[workspaceRef],
|
|
1024
|
+
});
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
const metadataFileEntries = collectPythonLockMetadataFileEntries(
|
|
1029
|
+
lockTomlObj,
|
|
1030
|
+
pkg,
|
|
1031
|
+
);
|
|
1032
|
+
mergeExternalReferences(
|
|
1033
|
+
pkg,
|
|
1034
|
+
collectPythonLockMetadataDistributionReferences(metadataFileEntries),
|
|
1035
|
+
);
|
|
1036
|
+
if (metadataFileEntries.length) {
|
|
1037
|
+
pkg.components = [];
|
|
1038
|
+
for (const afileObj of metadataFileEntries) {
|
|
1039
|
+
const fileName = derivePythonLockMetadataFileName(afileObj);
|
|
1040
|
+
// Skip entries we cannot name - nameless type:file components are
|
|
1041
|
+
// invalid per the CycloneDX schema and would fail validation.
|
|
1042
|
+
if (!fileName) {
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
const hashParts = afileObj?.hash?.split(":");
|
|
1046
|
+
let hashes;
|
|
1047
|
+
if (hashParts?.length === 2) {
|
|
1048
|
+
const alg = hashParts[0].replace("sha", "SHA-");
|
|
1049
|
+
hashes = [{ alg, content: hashParts[1] }];
|
|
1050
|
+
}
|
|
1051
|
+
pkg.components.push({
|
|
1052
|
+
type: "file",
|
|
1053
|
+
name: fileName,
|
|
1054
|
+
hashes,
|
|
1055
|
+
evidence: {
|
|
1056
|
+
identity: {
|
|
1057
|
+
field: "name",
|
|
1058
|
+
confidence: 1,
|
|
1059
|
+
methods: [
|
|
1060
|
+
{
|
|
1061
|
+
technique: "manifest-analysis",
|
|
1062
|
+
confidence: 1,
|
|
1063
|
+
value: lockFile,
|
|
1064
|
+
},
|
|
1065
|
+
],
|
|
1066
|
+
},
|
|
1067
|
+
},
|
|
1068
|
+
properties: [{ name: "internal:SrcFile", value: lockFile }],
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
// All entries may have been skipped (e.g. none had a derivable name).
|
|
1072
|
+
if (!pkg.components.length) {
|
|
1073
|
+
delete pkg.components;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
if (pyLockMode) {
|
|
1077
|
+
const pylockFileComponents = collectPyLockFileComponents(apkg, lockFile);
|
|
1078
|
+
if (pylockFileComponents.length) {
|
|
1079
|
+
pkg.components = (pkg.components || []).concat(pylockFileComponents);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
const normalizedPkgName = normalizePythonDependencyKey(pkg.name);
|
|
1083
|
+
if (
|
|
1084
|
+
directDepsKeys[normalizedPkgName] ||
|
|
1085
|
+
(hasWorkspaces && !Object.keys(workspaceComponentMap).length)
|
|
1086
|
+
) {
|
|
1087
|
+
rootList.push(pkg);
|
|
1088
|
+
}
|
|
1089
|
+
// This would help the lookup
|
|
1090
|
+
existingPkgMap[pkg.name.toLowerCase()] = pkg["bom-ref"];
|
|
1091
|
+
pkgBomRefMap[pkg["bom-ref"]] = pkg;
|
|
1092
|
+
// Do not repeat workspace components again under components
|
|
1093
|
+
// This will reduce false positives, when a downstream tool attempts to analyze all components
|
|
1094
|
+
if (pkg.type !== "application") {
|
|
1095
|
+
pkgList.push(pkg);
|
|
1096
|
+
}
|
|
1097
|
+
if (!depsMap[pkg["bom-ref"]]) {
|
|
1098
|
+
depsMap[pkg["bom-ref"]] = new Set();
|
|
1099
|
+
}
|
|
1100
|
+
// Track the workspace tree
|
|
1101
|
+
if (pkgParentMap[pkg.name]) {
|
|
1102
|
+
for (const pkgParentRef of pkgParentMap[pkg.name]) {
|
|
1103
|
+
if (!depsMap[pkgParentRef]) {
|
|
1104
|
+
depsMap[pkgParentRef] = new Set();
|
|
1105
|
+
}
|
|
1106
|
+
depsMap[pkgParentRef].add(pkg["bom-ref"]);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
let optionalDependencies = [];
|
|
1110
|
+
let devDependencies = [];
|
|
1111
|
+
const pylockRelationshipDeps = pyLockMode
|
|
1112
|
+
? collectPyLockDependencyRelationships(apkg)
|
|
1113
|
+
: [];
|
|
1114
|
+
if (apkg["dev-dependencies"]) {
|
|
1115
|
+
for (const agroup of Object.keys(apkg["dev-dependencies"])) {
|
|
1116
|
+
devDependencies = devDependencies.concat(
|
|
1117
|
+
apkg["dev-dependencies"][agroup],
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
if (apkg["optional-dependencies"]) {
|
|
1122
|
+
for (const agroup of Object.keys(apkg["optional-dependencies"])) {
|
|
1123
|
+
optionalDependencies = optionalDependencies.concat(
|
|
1124
|
+
apkg["optional-dependencies"][agroup],
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
if (
|
|
1129
|
+
apkg.dependencies ||
|
|
1130
|
+
pylockRelationshipDeps.length ||
|
|
1131
|
+
devDependencies.length ||
|
|
1132
|
+
optionalDependencies.length
|
|
1133
|
+
) {
|
|
1134
|
+
if (Array.isArray(apkg.dependencies)) {
|
|
1135
|
+
// pdm.lock files
|
|
1136
|
+
let allDeps = apkg.dependencies;
|
|
1137
|
+
allDeps = allDeps.concat(devDependencies);
|
|
1138
|
+
allDeps = allDeps.concat(optionalDependencies);
|
|
1139
|
+
for (const apkgDep of allDeps) {
|
|
1140
|
+
// Example: "msgpack>=0.5.2"
|
|
1141
|
+
const nameStr =
|
|
1142
|
+
apkgDep.name || apkgDep.split(/(==|<=|~=|>=)/)[0].split(" ")[0];
|
|
1143
|
+
// Python package names are normalized/case-insensitive; support both forms for lookup.
|
|
1144
|
+
const nameLower = nameStr.toLowerCase();
|
|
1145
|
+
const depPkgRef =
|
|
1146
|
+
existingPkgMap[nameLower] || existingPkgMap[nameStr];
|
|
1147
|
+
depsMap[pkg["bom-ref"]].add(depPkgRef || nameStr);
|
|
1148
|
+
// Propagate the workspace properties to the child components
|
|
1149
|
+
if (depPkgRef && pkgBomRefMap[depPkgRef]) {
|
|
1150
|
+
const dependentPkg = pkgBomRefMap[depPkgRef];
|
|
1151
|
+
dependentPkg.properties = dependentPkg.properties || [];
|
|
1152
|
+
const addedValue = {};
|
|
1153
|
+
// Is the parent a workspace
|
|
1154
|
+
if (workspaceComponentMap[pkg.name]) {
|
|
1155
|
+
dependentPkg.properties.push({
|
|
1156
|
+
name: "internal:workspaceRef",
|
|
1157
|
+
value: pkg["bom-ref"],
|
|
1158
|
+
});
|
|
1159
|
+
dependentPkg.properties.push({
|
|
1160
|
+
name: "internal:workspaceSrcFile",
|
|
1161
|
+
value: workspaceRefPyProjMap[pkg["bom-ref"]],
|
|
1162
|
+
});
|
|
1163
|
+
addedValue[pkg["bom-ref"]] = true;
|
|
1164
|
+
}
|
|
1165
|
+
for (const pprop of pkg.properties) {
|
|
1166
|
+
if (
|
|
1167
|
+
pprop.name.startsWith("internal:workspace") &&
|
|
1168
|
+
!addedValue[pprop.value]
|
|
1169
|
+
) {
|
|
1170
|
+
dependentPkg.properties.push(pprop);
|
|
1171
|
+
addedValue[pprop.value] = true;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
for (const relationship of pylockRelationshipDeps) {
|
|
1177
|
+
const depPkgRef =
|
|
1178
|
+
existingPkgMap[relationship.name.toLowerCase()] ||
|
|
1179
|
+
existingPkgMap[relationship.name];
|
|
1180
|
+
if (depPkgRef) {
|
|
1181
|
+
depsMap[pkg["bom-ref"]].add(depPkgRef);
|
|
1182
|
+
}
|
|
1183
|
+
if (relationship.scope === "optional-extra") {
|
|
1184
|
+
pkg.scope = pkg.scope || "optional";
|
|
1185
|
+
pkg.properties.push({
|
|
1186
|
+
name: "cdx:pyproject:extra",
|
|
1187
|
+
value: relationship.name,
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
if (relationship.scope === "dependency-group") {
|
|
1191
|
+
pkg.properties.push({
|
|
1192
|
+
name: "cdx:pyproject:dependencyGroupMember",
|
|
1193
|
+
value: relationship.name,
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
} else if (apkg.dependencies && Object.keys(apkg.dependencies).length) {
|
|
1198
|
+
for (const apkgDep of Object.keys(apkg.dependencies)) {
|
|
1199
|
+
depsMap[pkg["bom-ref"]].add(existingPkgMap[apkgDep] || apkgDep);
|
|
1200
|
+
}
|
|
1201
|
+
for (const relationship of pylockRelationshipDeps) {
|
|
1202
|
+
const depPkgRef =
|
|
1203
|
+
existingPkgMap[relationship.name.toLowerCase()] ||
|
|
1204
|
+
existingPkgMap[relationship.name];
|
|
1205
|
+
if (depPkgRef) {
|
|
1206
|
+
depsMap[pkg["bom-ref"]].add(depPkgRef);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
} else {
|
|
1210
|
+
for (const relationship of pylockRelationshipDeps) {
|
|
1211
|
+
const depPkgRef =
|
|
1212
|
+
existingPkgMap[relationship.name.toLowerCase()] ||
|
|
1213
|
+
existingPkgMap[relationship.name];
|
|
1214
|
+
if (depPkgRef) {
|
|
1215
|
+
depsMap[pkg["bom-ref"]].add(depPkgRef);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
for (const key of Object.keys(depsMap)) {
|
|
1222
|
+
const dependsOnList = new Set();
|
|
1223
|
+
const parentPkg = pkgBomRefMap[key];
|
|
1224
|
+
for (const adep of Array.from(depsMap[key])) {
|
|
1225
|
+
let depRef;
|
|
1226
|
+
if (adep.startsWith("pkg:")) {
|
|
1227
|
+
depRef = adep;
|
|
1228
|
+
} else if (existingPkgMap[adep]) {
|
|
1229
|
+
depRef = existingPkgMap[adep];
|
|
1230
|
+
} else if (existingPkgMap[adep.toLowerCase()]) {
|
|
1231
|
+
depRef = existingPkgMap[adep.toLowerCase()];
|
|
1232
|
+
} else if (existingPkgMap[`py${adep}`]) {
|
|
1233
|
+
depRef = existingPkgMap[`py${adep}`];
|
|
1234
|
+
} else if (existingPkgMap[adep.replace(/-/g, "_")]) {
|
|
1235
|
+
depRef = existingPkgMap[adep.replace(/-/g, "_")];
|
|
1236
|
+
}
|
|
1237
|
+
if (depRef) {
|
|
1238
|
+
dependsOnList.add(depRef);
|
|
1239
|
+
// We need to propagate the workspace properties from the parent
|
|
1240
|
+
const dependentPkg = pkgBomRefMap[depRef];
|
|
1241
|
+
dependentPkg.properties = dependentPkg.properties || [];
|
|
1242
|
+
const addedValue = {};
|
|
1243
|
+
for (const p of dependentPkg.properties) {
|
|
1244
|
+
if (p.name.startsWith("internal:workspace")) {
|
|
1245
|
+
addedValue[p.value] = true;
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
if (parentPkg?.properties?.length) {
|
|
1249
|
+
for (const pprop of parentPkg.properties) {
|
|
1250
|
+
if (
|
|
1251
|
+
pprop.name.startsWith("internal:workspace") &&
|
|
1252
|
+
!addedValue[pprop.value]
|
|
1253
|
+
) {
|
|
1254
|
+
dependentPkg.properties.push(pprop);
|
|
1255
|
+
addedValue[pprop.value] = true;
|
|
1256
|
+
} else if (pprop.name === "internal:is_workspace") {
|
|
1257
|
+
dependentPkg.properties.push({
|
|
1258
|
+
name: "internal:workspaceRef",
|
|
1259
|
+
value: parentPkg["bom-ref"],
|
|
1260
|
+
});
|
|
1261
|
+
dependentPkg.properties.push({
|
|
1262
|
+
name: "internal:workspaceSrcFile",
|
|
1263
|
+
value: workspaceRefPyProjMap[parentPkg["bom-ref"]],
|
|
1264
|
+
});
|
|
1265
|
+
addedValue[parentPkg["bom-ref"]] = true;
|
|
1266
|
+
addedValue[workspaceRefPyProjMap[parentPkg["bom-ref"]]] = true;
|
|
1267
|
+
const childDeps = depsMap[dependentPkg["bom-ref"]];
|
|
1268
|
+
for (const childRef of childDeps) {
|
|
1269
|
+
if (!childRef.startsWith("pkg:")) {
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
const childPkg = pkgBomRefMap[childRef];
|
|
1273
|
+
if (childPkg) {
|
|
1274
|
+
childPkg.properties = childPkg.properties || [];
|
|
1275
|
+
childPkg.properties.push({
|
|
1276
|
+
name: "internal:workspaceRef",
|
|
1277
|
+
value: parentPkg["bom-ref"],
|
|
1278
|
+
});
|
|
1279
|
+
childPkg.properties.push({
|
|
1280
|
+
name: "internal:workspaceSrcFile",
|
|
1281
|
+
value: workspaceRefPyProjMap[parentPkg["bom-ref"]],
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
dependenciesList.push({
|
|
1291
|
+
ref: key,
|
|
1292
|
+
dependsOn: [...dependsOnList].sort(),
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
pkgList = await getPyMetadata(pkgList, false);
|
|
1296
|
+
return {
|
|
1297
|
+
parentComponent,
|
|
1298
|
+
pkgList,
|
|
1299
|
+
rootList,
|
|
1300
|
+
dependenciesList,
|
|
1301
|
+
pyLockProperties,
|
|
1302
|
+
workspaceWarningShown,
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
/**
|
|
1307
|
+
* Method to parse requirements.txt file. This must be replaced with atom parsedeps.
|
|
1308
|
+
*
|
|
1309
|
+
* @param {String} reqFile Requirements.txt file
|
|
1310
|
+
* @param {Boolean} fetchDepsInfo Fetch dependencies info from pypi
|
|
1311
|
+
*
|
|
1312
|
+
* @returns {Promise[Array<Object>]} List of direct dependencies from the requirements file
|
|
1313
|
+
*/
|
|
1314
|
+
export async function parseReqFile(reqFile, fetchDepsInfo = false) {
|
|
1315
|
+
return await parseReqData(reqFile, null, fetchDepsInfo);
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
const LICENSE_ID_COMMENTS_PATTERN =
|
|
1319
|
+
/^(Apache-2\.0|MIT|ISC|GPL-|LGPL-|BSD-[23]-Clause)/i;
|
|
1320
|
+
|
|
1321
|
+
function parseLicenseComment(comment) {
|
|
1322
|
+
if (!comment) {
|
|
1323
|
+
return undefined;
|
|
1324
|
+
}
|
|
1325
|
+
const licenses = comment
|
|
1326
|
+
.split("/")
|
|
1327
|
+
.map((value) => {
|
|
1328
|
+
const licenseId = value.trim();
|
|
1329
|
+
if (!licenseId.match(LICENSE_ID_COMMENTS_PATTERN)) {
|
|
1330
|
+
return undefined;
|
|
1331
|
+
}
|
|
1332
|
+
return { license: { id: licenseId } };
|
|
1333
|
+
})
|
|
1334
|
+
.filter((value) => value !== undefined);
|
|
1335
|
+
return licenses.length ? licenses : undefined;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* Method to parse requirements.txt file. Must only be used internally.
|
|
1340
|
+
*
|
|
1341
|
+
* @param {String} reqFile Requirements.txt file
|
|
1342
|
+
* @param {Object} reqData Requirements.txt data for internal invocations from setup.py file etc.
|
|
1343
|
+
* @param {Boolean} fetchDepsInfo Fetch dependencies info from pypi
|
|
1344
|
+
* @returns {Promise<Array<Object>>} List of direct dependencies from the requirements file
|
|
1345
|
+
*/
|
|
1346
|
+
async function parseReqData(reqFile, reqData = null, fetchDepsInfo = false) {
|
|
1347
|
+
const pkgList = [];
|
|
1348
|
+
let compScope;
|
|
1349
|
+
if (!reqFile && !reqData) {
|
|
1350
|
+
console.warn(
|
|
1351
|
+
"Either the requirements file or the data needs to be provided for parsing.",
|
|
1352
|
+
);
|
|
1353
|
+
return pkgList;
|
|
1354
|
+
}
|
|
1355
|
+
reqData = reqData || readFileSync(reqFile, { encoding: "utf-8" });
|
|
1356
|
+
const evidence = reqFile
|
|
1357
|
+
? {
|
|
1358
|
+
identity: {
|
|
1359
|
+
field: "purl",
|
|
1360
|
+
confidence: 0.5,
|
|
1361
|
+
methods: [
|
|
1362
|
+
{
|
|
1363
|
+
technique: "manifest-analysis",
|
|
1364
|
+
confidence: 0.5,
|
|
1365
|
+
value: reqFile,
|
|
1366
|
+
},
|
|
1367
|
+
],
|
|
1368
|
+
},
|
|
1369
|
+
}
|
|
1370
|
+
: undefined;
|
|
1371
|
+
const normalizedData = reqData.replace(/\r/g, "").replace(/\\\n/g, " ");
|
|
1372
|
+
const lines = normalizedData.split("\n");
|
|
1373
|
+
for (const line of lines) {
|
|
1374
|
+
let l = line.trim();
|
|
1375
|
+
let editableRequirement = false;
|
|
1376
|
+
if (l.includes("# Basic requirements")) {
|
|
1377
|
+
compScope = "required";
|
|
1378
|
+
} else if (l.includes("added by pip freeze")) {
|
|
1379
|
+
compScope = undefined;
|
|
1380
|
+
}
|
|
1381
|
+
if (l.startsWith("-e ") || l.startsWith("--editable ")) {
|
|
1382
|
+
editableRequirement = true;
|
|
1383
|
+
l = l.replace(/^--editable\s+|^-e\s+/, "").trim();
|
|
1384
|
+
}
|
|
1385
|
+
if (l.startsWith("Skipping line") || l.startsWith("(add")) {
|
|
1386
|
+
continue;
|
|
1387
|
+
}
|
|
1388
|
+
if (!l || l.startsWith("#") || l.startsWith("-")) {
|
|
1389
|
+
continue;
|
|
1390
|
+
}
|
|
1391
|
+
let comment = null;
|
|
1392
|
+
const commentMatch = l.match(/\s+#(.*)$/);
|
|
1393
|
+
if (commentMatch) {
|
|
1394
|
+
comment = commentMatch[1].trim();
|
|
1395
|
+
l = l.substring(0, commentMatch.index).trim();
|
|
1396
|
+
}
|
|
1397
|
+
const properties = reqFile
|
|
1398
|
+
? [
|
|
1399
|
+
{
|
|
1400
|
+
name: "internal:SrcFile",
|
|
1401
|
+
value: reqFile,
|
|
1402
|
+
},
|
|
1403
|
+
]
|
|
1404
|
+
: [];
|
|
1405
|
+
const hashes = [];
|
|
1406
|
+
const hashRegex = /--hash=([a-zA-Z0-9\-]+):([a-fA-F0-9]+)/g;
|
|
1407
|
+
let hashMatch;
|
|
1408
|
+
while ((hashMatch = hashRegex.exec(l)) !== null) {
|
|
1409
|
+
let alg = hashMatch[1].toUpperCase();
|
|
1410
|
+
if (alg === "SHA256") alg = "SHA-256";
|
|
1411
|
+
else if (alg === "SHA384") alg = "SHA-384";
|
|
1412
|
+
else if (alg === "SHA512") alg = "SHA-512";
|
|
1413
|
+
else if (alg === "SHA1") alg = "SHA-1";
|
|
1414
|
+
hashes.push({
|
|
1415
|
+
alg: alg,
|
|
1416
|
+
content: hashMatch[2],
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
// Strip the hash flags and any residual backslashes
|
|
1420
|
+
l = l
|
|
1421
|
+
.replace(/--hash=[a-zA-Z0-9\-]+:[a-fA-F0-9]+/g, "")
|
|
1422
|
+
.replace(/\\/g, "")
|
|
1423
|
+
.trim();
|
|
1424
|
+
// Handle markers
|
|
1425
|
+
let markers = null;
|
|
1426
|
+
let structuredMarkers = null;
|
|
1427
|
+
if (l.includes(";")) {
|
|
1428
|
+
const parts = l.split(";");
|
|
1429
|
+
l = parts[0].trim();
|
|
1430
|
+
markers = parts.slice(1).join(";").trim();
|
|
1431
|
+
structuredMarkers = parseReqEnvMarkers(markers);
|
|
1432
|
+
}
|
|
1433
|
+
const requirementManifestSource = parsePythonRequirementManifestSource(l);
|
|
1434
|
+
if (requirementManifestSource?.name) {
|
|
1435
|
+
const apkg = {
|
|
1436
|
+
name: requirementManifestSource.name,
|
|
1437
|
+
version: null,
|
|
1438
|
+
scope: compScope,
|
|
1439
|
+
evidence,
|
|
1440
|
+
};
|
|
1441
|
+
if (hashes.length > 0) {
|
|
1442
|
+
apkg.hashes = hashes;
|
|
1443
|
+
}
|
|
1444
|
+
const licenses = parseLicenseComment(comment);
|
|
1445
|
+
if (licenses) {
|
|
1446
|
+
apkg.licenses = licenses;
|
|
1447
|
+
}
|
|
1448
|
+
applyManifestSourceProperties(
|
|
1449
|
+
apkg,
|
|
1450
|
+
"cdx:pypi",
|
|
1451
|
+
requirementManifestSource,
|
|
1452
|
+
);
|
|
1453
|
+
if (editableRequirement) {
|
|
1454
|
+
addComponentProperty(apkg, "cdx:pypi:editable", "true");
|
|
1455
|
+
}
|
|
1456
|
+
if (markers) {
|
|
1457
|
+
addComponentProperty(apkg, "cdx:pip:markers", markers);
|
|
1458
|
+
if (structuredMarkers?.length > 0) {
|
|
1459
|
+
addComponentProperty(
|
|
1460
|
+
apkg,
|
|
1461
|
+
"cdx:pip:structuredMarkers",
|
|
1462
|
+
JSON.stringify(structuredMarkers),
|
|
1463
|
+
);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
if (reqFile) {
|
|
1467
|
+
addComponentProperty(apkg, "internal:SrcFile", reqFile);
|
|
1468
|
+
}
|
|
1469
|
+
pkgList.push(apkg);
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// Handle extras (e.g., package[extra1,extra2])
|
|
1474
|
+
let extras = null;
|
|
1475
|
+
const extrasMatch = l.match(/^([a-zA-Z0-9_\-.]+)(\[([^\]]+)])?(.*)$/);
|
|
1476
|
+
if (extrasMatch) {
|
|
1477
|
+
const [, packageName, , extrasStr, versionSpecifiers] = extrasMatch;
|
|
1478
|
+
const name = packageName;
|
|
1479
|
+
if (extrasStr) {
|
|
1480
|
+
extras = extrasStr.split(",").map((e) => e.trim());
|
|
1481
|
+
l = `${name}${versionSpecifiers}`; // Reconstruct without extras for version parsing
|
|
1482
|
+
}
|
|
1483
|
+
if (PYTHON_STD_MODULES.includes(name)) {
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
const versionMatch = versionSpecifiers.match(
|
|
1487
|
+
/(==|!=|<=|>=|<|>|~=)([0-9.a-zA-Z_*\-+]*)/,
|
|
1488
|
+
);
|
|
1489
|
+
let version = null;
|
|
1490
|
+
if (versionMatch) {
|
|
1491
|
+
version = versionMatch[2].replaceAll("*", "0") || null;
|
|
1492
|
+
if (version === "0") {
|
|
1493
|
+
version = null;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
const apkg = {
|
|
1497
|
+
name,
|
|
1498
|
+
version,
|
|
1499
|
+
scope: compScope,
|
|
1500
|
+
evidence,
|
|
1501
|
+
};
|
|
1502
|
+
if (hashes.length > 0) {
|
|
1503
|
+
apkg.hashes = hashes;
|
|
1504
|
+
}
|
|
1505
|
+
const licenses = parseLicenseComment(comment);
|
|
1506
|
+
if (licenses) {
|
|
1507
|
+
apkg.licenses = licenses;
|
|
1508
|
+
}
|
|
1509
|
+
if (extras && extras.length > 0) {
|
|
1510
|
+
properties.push({
|
|
1511
|
+
name: "cdx:pypi:extras",
|
|
1512
|
+
value: extras.join(","),
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
if (versionSpecifiers && !versionSpecifiers.startsWith("==")) {
|
|
1516
|
+
properties.push({
|
|
1517
|
+
name: "cdx:pypi:versionSpecifiers",
|
|
1518
|
+
value: versionSpecifiers.trim(),
|
|
1519
|
+
});
|
|
1520
|
+
}
|
|
1521
|
+
if (markers) {
|
|
1522
|
+
properties.push({
|
|
1523
|
+
name: "cdx:pip:markers",
|
|
1524
|
+
value: markers,
|
|
1525
|
+
});
|
|
1526
|
+
if (structuredMarkers && structuredMarkers.length > 0) {
|
|
1527
|
+
properties.push({
|
|
1528
|
+
name: "cdx:pip:structuredMarkers",
|
|
1529
|
+
value: JSON.stringify(structuredMarkers),
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
if (editableRequirement) {
|
|
1534
|
+
properties.push({
|
|
1535
|
+
name: "cdx:pypi:editable",
|
|
1536
|
+
value: "true",
|
|
1537
|
+
});
|
|
1538
|
+
}
|
|
1539
|
+
if (properties.length) {
|
|
1540
|
+
apkg.properties = properties;
|
|
1541
|
+
}
|
|
1542
|
+
pkgList.push(apkg);
|
|
1543
|
+
} else {
|
|
1544
|
+
const match = l.match(/^([a-zA-Z0-9_\-.]+)(.*)$/);
|
|
1545
|
+
if (!match) {
|
|
1546
|
+
continue;
|
|
1547
|
+
}
|
|
1548
|
+
const [, name, versionSpecifiers] = match;
|
|
1549
|
+
if (PYTHON_STD_MODULES.includes(name)) {
|
|
1550
|
+
continue;
|
|
1551
|
+
}
|
|
1552
|
+
const versionMatch = versionSpecifiers.match(
|
|
1553
|
+
/(==|!=|<=|>=|<|>|~=)([0-9.a-zA-Z_*\-+]*)/,
|
|
1554
|
+
);
|
|
1555
|
+
let version = null;
|
|
1556
|
+
if (versionMatch) {
|
|
1557
|
+
version = versionMatch[2].replaceAll("*", "0") || null;
|
|
1558
|
+
if (version === "0") version = null;
|
|
1559
|
+
}
|
|
1560
|
+
const apkg = {
|
|
1561
|
+
name,
|
|
1562
|
+
version,
|
|
1563
|
+
scope: compScope,
|
|
1564
|
+
evidence,
|
|
1565
|
+
};
|
|
1566
|
+
const licenses = parseLicenseComment(comment);
|
|
1567
|
+
if (licenses) {
|
|
1568
|
+
apkg.licenses = licenses;
|
|
1569
|
+
}
|
|
1570
|
+
if (versionSpecifiers && !versionSpecifiers.startsWith("==")) {
|
|
1571
|
+
properties.push({
|
|
1572
|
+
name: "cdx:pypi:versionSpecifiers",
|
|
1573
|
+
value: versionSpecifiers.trim(),
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
if (markers) {
|
|
1577
|
+
properties.push({
|
|
1578
|
+
name: "cdx:pip:markers",
|
|
1579
|
+
value: markers,
|
|
1580
|
+
});
|
|
1581
|
+
if (structuredMarkers && structuredMarkers.length > 0) {
|
|
1582
|
+
properties.push({
|
|
1583
|
+
name: "cdx:pip:structuredMarkers",
|
|
1584
|
+
value: JSON.stringify(structuredMarkers),
|
|
1585
|
+
});
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
if (editableRequirement) {
|
|
1589
|
+
properties.push({
|
|
1590
|
+
name: "cdx:pypi:editable",
|
|
1591
|
+
value: "true",
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1594
|
+
if (properties.length) {
|
|
1595
|
+
apkg.properties = properties;
|
|
1596
|
+
}
|
|
1597
|
+
pkgList.push(apkg);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1600
|
+
return await getPyMetadata(pkgList, fetchDepsInfo);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
/**
|
|
1604
|
+
* Parse environment markers into structured format
|
|
1605
|
+
*
|
|
1606
|
+
* @param {String} markersStr Raw markers string
|
|
1607
|
+
* @returns {Array<Object>} Structured markers array
|
|
1608
|
+
*/
|
|
1609
|
+
export function parseReqEnvMarkers(markersStr) {
|
|
1610
|
+
if (!markersStr) return [];
|
|
1611
|
+
|
|
1612
|
+
const markers = [];
|
|
1613
|
+
const tokens = markersStr
|
|
1614
|
+
.replace(/\s+/g, " ")
|
|
1615
|
+
.trim()
|
|
1616
|
+
.split(/\s+(and|or)\s+/gi)
|
|
1617
|
+
.filter((token) => token.trim());
|
|
1618
|
+
for (const token of tokens) {
|
|
1619
|
+
if (token.toLowerCase() === "and" || token.toLowerCase() === "or") {
|
|
1620
|
+
markers.push({
|
|
1621
|
+
operator: token.toLowerCase(),
|
|
1622
|
+
});
|
|
1623
|
+
} else {
|
|
1624
|
+
const match = token.match(
|
|
1625
|
+
/([a-zA-Z_]+)\s*(==|!=|<=|>=|<|>)\s*["']?([^"']*)["']?/,
|
|
1626
|
+
);
|
|
1627
|
+
if (match) {
|
|
1628
|
+
markers.push({
|
|
1629
|
+
variable: match[1],
|
|
1630
|
+
operator: match[2],
|
|
1631
|
+
value: match[3],
|
|
1632
|
+
});
|
|
1633
|
+
} else {
|
|
1634
|
+
// Add as raw token if parsing fails
|
|
1635
|
+
markers.push({
|
|
1636
|
+
raw: token,
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
return markers;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
/**
|
|
1645
|
+
* Method to parse setup.py data
|
|
1646
|
+
*
|
|
1647
|
+
* @param {Object} setupPyData Contents of setup.py
|
|
1648
|
+
*/
|
|
1649
|
+
export async function parseSetupPyFile(setupPyData) {
|
|
1650
|
+
let lines = [];
|
|
1651
|
+
let requires_found = false;
|
|
1652
|
+
let should_break = false;
|
|
1653
|
+
setupPyData.split("\n").forEach((l) => {
|
|
1654
|
+
l = l.trim();
|
|
1655
|
+
if (l.includes("install_requires")) {
|
|
1656
|
+
l = l.replace("install_requires=[", "");
|
|
1657
|
+
requires_found = true;
|
|
1658
|
+
}
|
|
1659
|
+
if (l.length && requires_found && !should_break) {
|
|
1660
|
+
if (l.includes("]")) {
|
|
1661
|
+
should_break = true;
|
|
1662
|
+
l = l.replaceAll("],", "").replaceAll("]", "");
|
|
1663
|
+
}
|
|
1664
|
+
let tmpA = l.replace(/['"]/g, "").split(",");
|
|
1665
|
+
tmpA = tmpA.filter((v) => v.length);
|
|
1666
|
+
lines = lines.concat(tmpA);
|
|
1667
|
+
}
|
|
1668
|
+
});
|
|
1669
|
+
return await parseReqData(null, lines.join("\n"), false);
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
/**
|
|
1673
|
+
* Method to create purl using information in pixi.lock file.
|
|
1674
|
+
* According to pixi lock file satisfiability (https://pixi.sh/latest/features/lockfile/#lockfile-satisfiability)
|
|
1675
|
+
*
|
|
1676
|
+
*
|
|
1677
|
+
*
|
|
1678
|
+
* @param {*} packageData
|
|
1679
|
+
* @returns
|
|
1680
|
+
*/
|
|
1681
|
+
function createPurlTemplate(packageData) {
|
|
1682
|
+
// conda defines build/channel/subdir/type as its qualifiers, and the pixi
|
|
1683
|
+
// field is already called subdir, so it maps straight across. pixi records
|
|
1684
|
+
// the build separately while conda versions carry it as a `version-build`
|
|
1685
|
+
// suffix.
|
|
1686
|
+
const version = packageData["build"]
|
|
1687
|
+
? `${packageData["version"]}-${packageData["build"]}`
|
|
1688
|
+
: packageData["version"];
|
|
1689
|
+
return tryBuildPurl({
|
|
1690
|
+
type: packageData["kind"],
|
|
1691
|
+
name: packageData["name"],
|
|
1692
|
+
version,
|
|
1693
|
+
qualifiers: packageData["subdir"]
|
|
1694
|
+
? { subdir: packageData["subdir"] }
|
|
1695
|
+
: undefined,
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
/**
|
|
1700
|
+
* Identifier for a pixi package, used as its `bom-ref` and by every dependency
|
|
1701
|
+
* that references it. Derived from the purl so the graph and the components
|
|
1702
|
+
* agree, with a fallback for a package whose purl cannot be built.
|
|
1703
|
+
*
|
|
1704
|
+
* @param {object} packageData Package entry from a pixi.lock file
|
|
1705
|
+
* @returns {string} bom-ref
|
|
1706
|
+
*/
|
|
1707
|
+
function createPixiBomRef(packageData) {
|
|
1708
|
+
const purl = createPurlTemplate(packageData);
|
|
1709
|
+
if (purl) {
|
|
1710
|
+
return decodeURIComponent(purl);
|
|
1711
|
+
}
|
|
1712
|
+
return `library:${packageData["name"]}:${packageData["version"] || ""}`;
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
/**
|
|
1716
|
+
* Method to parse pixi.lock data
|
|
1717
|
+
*
|
|
1718
|
+
* @param {String} pixiLockFileName pixi.lock file name
|
|
1719
|
+
* @param {String} path File path
|
|
1720
|
+
*/
|
|
1721
|
+
export function parsePixiLockFile(pixiLockFileName, path) {
|
|
1722
|
+
const pixiFileData = readFileSync(pixiLockFileName, { encoding: "utf-8" });
|
|
1723
|
+
const pixiLockData = _load(pixiFileData);
|
|
1724
|
+
const evidenceBasePath = path ?? dirname(pixiLockFileName);
|
|
1725
|
+
|
|
1726
|
+
// this function returns
|
|
1727
|
+
let pkgList;
|
|
1728
|
+
const formulationList = [];
|
|
1729
|
+
const rootList = [];
|
|
1730
|
+
let dependenciesList;
|
|
1731
|
+
// we do not set false because we have assumed that pixi lock is accurate
|
|
1732
|
+
const frozen = true;
|
|
1733
|
+
|
|
1734
|
+
/**
|
|
1735
|
+
* pixiMapper used with a map on pixi packages list.
|
|
1736
|
+
* the pixi list contains the following information e.g.
|
|
1737
|
+
* {kind: conda
|
|
1738
|
+
* name: alsa-lib
|
|
1739
|
+
* version: 1.2.11
|
|
1740
|
+
* build: h31becfc_1
|
|
1741
|
+
* build_number: 1
|
|
1742
|
+
* subdir: linux-aarch64
|
|
1743
|
+
* url: https://conda.anaconda.org/conda-forge/linux-aarch64/alsa-lib-1.2.11-h31becfc_1.conda
|
|
1744
|
+
* sha256: d062bc712dd307714dfdb0f7da095a510c138c5db76321494a516ac127f9e5cf
|
|
1745
|
+
* md5: 76bf292a85a0556cef4f500420cabe6c
|
|
1746
|
+
* depends:
|
|
1747
|
+
* - libgcc-ng >=12
|
|
1748
|
+
* license: LGPL-2.1-or-later
|
|
1749
|
+
* license_family: GPL
|
|
1750
|
+
* size: 584152
|
|
1751
|
+
* timestamp: 1709396718705
|
|
1752
|
+
* }
|
|
1753
|
+
* We create the purl using the following logic:
|
|
1754
|
+
* "purl": "pkg:{kind}/{name}@{version}-{build}?os={os}"
|
|
1755
|
+
* type would be "library" and evidence would be
|
|
1756
|
+
* {
|
|
1757
|
+
* "identity": {
|
|
1758
|
+
* "field": "purl",
|
|
1759
|
+
* "confidence": 1,
|
|
1760
|
+
* "methods": [
|
|
1761
|
+
* {
|
|
1762
|
+
* "technique": "instrumentation",
|
|
1763
|
+
* "confidence": 1,
|
|
1764
|
+
* "value": "pixi.lock"
|
|
1765
|
+
* }
|
|
1766
|
+
* ]
|
|
1767
|
+
* }
|
|
1768
|
+
* }
|
|
1769
|
+
*
|
|
1770
|
+
*/
|
|
1771
|
+
function pixiMapper(packageData) {
|
|
1772
|
+
// return pkgList
|
|
1773
|
+
/** E.g. of what a pkgList element looks like
|
|
1774
|
+
* {
|
|
1775
|
+
* name: "conda-content-trust",
|
|
1776
|
+
* version: "latest",
|
|
1777
|
+
* purl: "pkg:pypi/conda-content-trust@latest",
|
|
1778
|
+
* type: "library",
|
|
1779
|
+
* "bom-ref": "pkg:pypi/conda-content-trust@latest",
|
|
1780
|
+
* scope: "excluded",
|
|
1781
|
+
* evidence: {
|
|
1782
|
+
* identity: {
|
|
1783
|
+
* field: "purl",
|
|
1784
|
+
* confidence: 1,
|
|
1785
|
+
* methods: [
|
|
1786
|
+
* {
|
|
1787
|
+
* technique: "instrumentation",
|
|
1788
|
+
* confidence: 1,
|
|
1789
|
+
* value: "/home/greatsage/miniconda3",
|
|
1790
|
+
* },
|
|
1791
|
+
* ],
|
|
1792
|
+
* },
|
|
1793
|
+
* },
|
|
1794
|
+
* properties: [
|
|
1795
|
+
* {
|
|
1796
|
+
* name: "internal:SrcFile",
|
|
1797
|
+
* value: "/home/greatsage/projects/supplyChain/trials/pythonprojs/fastapi/requirements.txt",
|
|
1798
|
+
* },
|
|
1799
|
+
* ],
|
|
1800
|
+
* }
|
|
1801
|
+
*
|
|
1802
|
+
*/
|
|
1803
|
+
const purlTemplate = createPurlTemplate(packageData);
|
|
1804
|
+
return {
|
|
1805
|
+
name: packageData["name"],
|
|
1806
|
+
version: packageData["version"],
|
|
1807
|
+
...(purlTemplate ? { purl: purlTemplate } : {}),
|
|
1808
|
+
type: "library",
|
|
1809
|
+
"bom-ref": createPixiBomRef(packageData),
|
|
1810
|
+
// "licenses": [
|
|
1811
|
+
// [{
|
|
1812
|
+
// "id": packageData["license"]
|
|
1813
|
+
// }]
|
|
1814
|
+
// ],
|
|
1815
|
+
supplier: {
|
|
1816
|
+
name: packageData["build"],
|
|
1817
|
+
url: packageData["url"],
|
|
1818
|
+
},
|
|
1819
|
+
// "hashes": [
|
|
1820
|
+
// {"md5": packageData["md5"]},
|
|
1821
|
+
// {"sha256": packageData["sha256"]}
|
|
1822
|
+
// ],
|
|
1823
|
+
evidence: {
|
|
1824
|
+
identity: {
|
|
1825
|
+
field: "purl",
|
|
1826
|
+
confidence: 1,
|
|
1827
|
+
methods: [
|
|
1828
|
+
{
|
|
1829
|
+
technique: "instrumentation",
|
|
1830
|
+
confidence: 1,
|
|
1831
|
+
// "value": `${path}/.pixi/envs/default`
|
|
1832
|
+
},
|
|
1833
|
+
],
|
|
1834
|
+
},
|
|
1835
|
+
},
|
|
1836
|
+
properties: [
|
|
1837
|
+
{ name: "cdx:pixi:operating_system", value: packageData["subdir"] },
|
|
1838
|
+
{
|
|
1839
|
+
name: "cdx:pixi:build_number",
|
|
1840
|
+
value: `${packageData["build_number"]}`,
|
|
1841
|
+
},
|
|
1842
|
+
{ name: "cdx:pixi:build", value: `${packageData["build"]}` },
|
|
1843
|
+
],
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function mapAddEvidenceValue(p) {
|
|
1848
|
+
// TODO: get pixi environment variable (PR #1343)
|
|
1849
|
+
p.evidence.identity.methods[0].value = `${evidenceBasePath}/.pixi/envs/default`;
|
|
1850
|
+
return p;
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// create the pkgList
|
|
1854
|
+
pkgList = pixiLockData["packages"].map(pixiMapper);
|
|
1855
|
+
pkgList = pkgList.map(mapAddEvidenceValue);
|
|
1856
|
+
|
|
1857
|
+
// create dependencies
|
|
1858
|
+
const dictionary_packages = pixiLockData["packages"].reduce(
|
|
1859
|
+
(accumulator, currentObject) => {
|
|
1860
|
+
accumulator[currentObject["name"]] = currentObject;
|
|
1861
|
+
return accumulator;
|
|
1862
|
+
},
|
|
1863
|
+
{},
|
|
1864
|
+
);
|
|
1865
|
+
|
|
1866
|
+
dependenciesList = [];
|
|
1867
|
+
for (const package_iter of pixiLockData["packages"]) {
|
|
1868
|
+
const depends = package_iter["depends"];
|
|
1869
|
+
if (!depends) {
|
|
1870
|
+
continue;
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
const purltemplate = createPixiBomRef(package_iter);
|
|
1874
|
+
const subdir = package_iter["subdir"];
|
|
1875
|
+
const dependsOn = new Set();
|
|
1876
|
+
for (const depends_package of depends) {
|
|
1877
|
+
const depends_package_name = depends_package.split(" ");
|
|
1878
|
+
const depends_package_information =
|
|
1879
|
+
dictionary_packages[depends_package_name[0] + subdir];
|
|
1880
|
+
if (!depends_package_information) {
|
|
1881
|
+
continue;
|
|
1882
|
+
}
|
|
1883
|
+
dependsOn.add(createPixiBomRef(depends_package_information));
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
dependenciesList.push({
|
|
1887
|
+
ref: purltemplate,
|
|
1888
|
+
dependsOn: [...dependsOn].sort(),
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
return {
|
|
1893
|
+
pkgList,
|
|
1894
|
+
formulationList,
|
|
1895
|
+
rootList,
|
|
1896
|
+
dependenciesList,
|
|
1897
|
+
frozen,
|
|
1898
|
+
};
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
/**
|
|
1902
|
+
* Method to parse pixi.toml file
|
|
1903
|
+
*
|
|
1904
|
+
* @param {String} pixiToml
|
|
1905
|
+
*/
|
|
1906
|
+
export function parsePixiTomlFile(pixiToml) {
|
|
1907
|
+
const pixiTomlFile = readFileSync(pixiToml, { encoding: "utf-8" });
|
|
1908
|
+
let tomlData;
|
|
1909
|
+
try {
|
|
1910
|
+
tomlData = toml.parse(pixiTomlFile);
|
|
1911
|
+
} catch (err) {
|
|
1912
|
+
console.log(`Error while parsing the pixi file ${pixiToml}.`, err);
|
|
1913
|
+
return {};
|
|
1914
|
+
}
|
|
1915
|
+
const pkg = {};
|
|
1916
|
+
if (!tomlData) {
|
|
1917
|
+
return pkg;
|
|
1918
|
+
}
|
|
1919
|
+
const projectData = tomlData.workspace || tomlData.project || {};
|
|
1920
|
+
pkg.description = projectData.description;
|
|
1921
|
+
pkg.name = projectData.name;
|
|
1922
|
+
pkg.version = projectData.version;
|
|
1923
|
+
// pkg.authors = tomlData['project']['authors'];
|
|
1924
|
+
pkg.homepage = projectData.homepage;
|
|
1925
|
+
pkg.repository = projectData.repository;
|
|
1926
|
+
return pkg;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
/**
|
|
1930
|
+
* Method to run cli command `pixi install`
|
|
1931
|
+
*
|
|
1932
|
+
*
|
|
1933
|
+
*/
|
|
1934
|
+
export function generatePixiLockFile(_path) {
|
|
1935
|
+
const result = safeSpawnSync("pixi", ["install"]);
|
|
1936
|
+
|
|
1937
|
+
if (result.status !== 0) {
|
|
1938
|
+
// Handle errors
|
|
1939
|
+
if (result.error && result.error.code === "ENOENT") {
|
|
1940
|
+
console.error(
|
|
1941
|
+
"Error: pixi command not found. Make sure pixi.js is installed globally.",
|
|
1942
|
+
);
|
|
1943
|
+
} else {
|
|
1944
|
+
console.error(
|
|
1945
|
+
`Error executing pixi install: ${result.error || result.stderr.toString()}`,
|
|
1946
|
+
);
|
|
1947
|
+
}
|
|
1948
|
+
process.exit(1);
|
|
1949
|
+
} else {
|
|
1950
|
+
console.log("Dependencies installed successfully.");
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
/**
|
|
1955
|
+
* Parse a Mojo `mojoproject.toml` manifest.
|
|
1956
|
+
*
|
|
1957
|
+
* Mojo projects are pixi-managed, so conda and PyPI dependencies pulled through
|
|
1958
|
+
* pixi.lock already keep their correct registered types via the pixi path.
|
|
1959
|
+
* Only Mojo's *own* packages — declared in `mojoproject.toml` — need special
|
|
1960
|
+
* handling: `mojo` is not a registered purl type, so each is emitted as
|
|
1961
|
+
* `pkg:generic/...` with a `cdx:purl:proposedType=mojo` property.
|
|
1962
|
+
*
|
|
1963
|
+
* The manifest is TOML. The `[project]` table carries the project's name and
|
|
1964
|
+
* version; `[dependencies]` maps dependency names to version specifiers. A
|
|
1965
|
+
* declared version range (e.g. `==0.1.0`, `>=0.2`) is normalised to its
|
|
1966
|
+
* concrete version when one is present, otherwise the version is omitted.
|
|
1967
|
+
*
|
|
1968
|
+
* @param {string} mojoProjectFile Path to `mojoproject.toml`
|
|
1969
|
+
* @returns {{ pkgList: object[], parentComponent: object }}
|
|
1970
|
+
*/
|
|
1971
|
+
export function parseMojoProject(mojoProjectFile) {
|
|
1972
|
+
let manifest;
|
|
1973
|
+
try {
|
|
1974
|
+
manifest = toml.parse(readFileSync(mojoProjectFile, "utf-8"));
|
|
1975
|
+
} catch (error) {
|
|
1976
|
+
console.warn(`Failed to parse ${mojoProjectFile}: ${error.message}`);
|
|
1977
|
+
return { pkgList: [], parentComponent: {} };
|
|
1978
|
+
}
|
|
1979
|
+
|
|
1980
|
+
const parentComponent = {};
|
|
1981
|
+
const project = manifest.project;
|
|
1982
|
+
if (project && typeof project === "object") {
|
|
1983
|
+
if (project.name) {
|
|
1984
|
+
parentComponent.name = `${project.name}`;
|
|
1985
|
+
parentComponent.type = "application";
|
|
1986
|
+
if (project.version) {
|
|
1987
|
+
parentComponent.version = `${project.version}`;
|
|
1988
|
+
}
|
|
1989
|
+
parentComponent.description =
|
|
1990
|
+
typeof project.description === "string"
|
|
1991
|
+
? project.description
|
|
1992
|
+
: `Mojo project: ${project.name}`;
|
|
1993
|
+
parentComponent.properties = [
|
|
1994
|
+
{ name: "internal:SrcFile", value: mojoProjectFile },
|
|
1995
|
+
];
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
const pkgList = [];
|
|
2000
|
+
const deps = manifest.dependencies;
|
|
2001
|
+
if (deps && typeof deps === "object") {
|
|
2002
|
+
for (const [name, specRaw] of Object.entries(deps)) {
|
|
2003
|
+
const spec =
|
|
2004
|
+
typeof specRaw === "string"
|
|
2005
|
+
? specRaw
|
|
2006
|
+
: specRaw && typeof specRaw === "object"
|
|
2007
|
+
? `${specRaw.version || ""}`
|
|
2008
|
+
: "";
|
|
2009
|
+
const version = normaliseMojoVersion(spec);
|
|
2010
|
+
pkgList.push(mojoPackage(name, version, mojoProjectFile));
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
return { pkgList, parentComponent };
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
/**
|
|
2018
|
+
* Build a component record for a Mojo dependency. Mojo has no registered purl
|
|
2019
|
+
* type, so the package is identified as generic with a proposedType marker.
|
|
2020
|
+
*
|
|
2021
|
+
* @param {string} name Dependency name
|
|
2022
|
+
* @param {string|undefined} version Concrete version, if one was declared
|
|
2023
|
+
* @param {string} srcFile Source manifest path for evidence
|
|
2024
|
+
* @returns {object} Package record
|
|
2025
|
+
*/
|
|
2026
|
+
function mojoPackage(name, version, srcFile) {
|
|
2027
|
+
const purl = tryBuildPurl({
|
|
2028
|
+
type: "generic",
|
|
2029
|
+
name,
|
|
2030
|
+
version: version || undefined,
|
|
2031
|
+
});
|
|
2032
|
+
const pkg = {
|
|
2033
|
+
name,
|
|
2034
|
+
...(version ? { version } : {}),
|
|
2035
|
+
type: "library",
|
|
2036
|
+
scope: "required",
|
|
2037
|
+
properties: [
|
|
2038
|
+
{ name: "internal:SrcFile", value: srcFile },
|
|
2039
|
+
{ name: "cdx:purl:proposedType", value: "mojo" },
|
|
2040
|
+
{ name: "cdx:mojo:dependency", value: "direct" },
|
|
2041
|
+
],
|
|
2042
|
+
};
|
|
2043
|
+
if (purl) {
|
|
2044
|
+
pkg.purl = purl;
|
|
2045
|
+
pkg["bom-ref"] = decodeURIComponent(purl);
|
|
2046
|
+
} else {
|
|
2047
|
+
pkg["bom-ref"] = `library:${name}:${version || ""}`;
|
|
2048
|
+
}
|
|
2049
|
+
return pkg;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
/**
|
|
2053
|
+
* Normalise a Mojo/Python-style version specifier to a concrete version.
|
|
2054
|
+
*
|
|
2055
|
+
* Specifiers like `==0.1.0`, `>=0.2,<1`, or `~=1.2` are reduced to the first
|
|
2056
|
+
* concrete version token; bare version strings pass through. An unresolvable
|
|
2057
|
+
* range yields undefined so the purl omits the version rather than encoding a
|
|
2058
|
+
* meaningless operator.
|
|
2059
|
+
*
|
|
2060
|
+
* @param {string} spec Version specifier
|
|
2061
|
+
* @returns {string|undefined}
|
|
2062
|
+
*/
|
|
2063
|
+
function normaliseMojoVersion(spec) {
|
|
2064
|
+
if (!spec || typeof spec !== "string") return undefined;
|
|
2065
|
+
const trimmed = spec.trim();
|
|
2066
|
+
if (!trimmed) return undefined;
|
|
2067
|
+
// Strip common comparison operators and take the first concrete version.
|
|
2068
|
+
const match = trimmed.match(/(\d+(?:\.\d+)*(?:[+-][A-Za-z0-9.]+)?)/);
|
|
2069
|
+
return match ? match[1] : undefined;
|
|
2070
|
+
}
|