@cdxgen/cdxgen 13.3.0 → 13.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -6
- package/bin/cdxgen.js +90 -26
- package/bin/evinse.js +18 -1
- package/bin/repl.js +86 -0
- package/bin/sign.js +45 -15
- package/bin/tracebom.js +5 -1
- package/bin/validate.js +84 -18
- package/bin/verify.js +155 -86
- package/data/helpers/init.gradle +46 -21
- package/data/remediations.json +61 -0
- package/data/rules/dependency-sources.yaml +67 -0
- package/data/rules/scala-evidence.yaml +115 -0
- package/lib/audit/index.js +38 -6
- package/lib/cli/index.js +113 -54
- package/lib/cli/jsBom.js +50 -29
- package/lib/cli/jvmBom.js +665 -138
- package/lib/cli/managedBom.js +320 -200
- package/lib/cli/nativeBom.js +139 -30
- package/lib/core/activity.js +12 -0
- package/lib/core/env.js +17 -9
- package/lib/core/fs.js +3 -2
- package/lib/core/httpClient.js +159 -24
- package/lib/core/pluginRun.js +184 -0
- package/lib/core/processTree.js +100 -0
- package/lib/core/runState.js +39 -0
- package/lib/core/toml.js +72 -0
- package/lib/ecosystems/asarutils.js +4 -2
- package/lib/ecosystems/bunutils.js +5 -7
- package/lib/ecosystems/cargoCache.js +231 -8
- package/lib/ecosystems/cmakeResolver.js +10 -21
- package/lib/ecosystems/cppBuildContext.js +631 -0
- package/lib/ecosystems/cppEvidence.js +337 -22
- package/lib/ecosystems/cppIncludeResolver.js +294 -0
- package/lib/ecosystems/denoutils.js +93 -66
- package/lib/ecosystems/ecosystems.js +1967 -788
- package/lib/ecosystems/golem.js +28 -12
- package/lib/ecosystems/gradleutils.js +314 -30
- package/lib/ecosystems/jsEvidence.js +85 -24
- package/lib/ecosystems/kosi.js +21 -4
- package/lib/ecosystems/npmutils.js +64 -0
- package/lib/ecosystems/parsers-dotnet.js +567 -151
- package/lib/ecosystems/parsers-elm.js +46 -28
- package/lib/ecosystems/parsers-gleam.js +3 -4
- package/lib/ecosystems/parsers-go.js +324 -61
- package/lib/ecosystems/parsers-js.js +92 -26
- package/lib/ecosystems/parsers-julia.js +2 -3
- package/lib/ecosystems/parsers-jvm.js +204 -12
- package/lib/ecosystems/parsers-misc.js +182 -55
- package/lib/ecosystems/parsers-python.js +141 -10
- package/lib/ecosystems/parsers-rust.js +4 -4
- package/lib/ecosystems/parsers-terraform.js +1573 -19
- package/lib/ecosystems/pythonutils.js +102 -6
- package/lib/ecosystems/remote/huggingface.js +3 -0
- package/lib/ecosystems/rubyutils.js +67 -17
- package/lib/ecosystems/rusi.js +22 -9
- package/lib/ecosystems/sbtutils.js +243 -187
- package/lib/ecosystems/scalacliutils.js +342 -0
- package/lib/ecosystems/scalasem.js +2618 -0
- package/lib/ecosystems/terraformRegistry.js +288 -0
- package/lib/ecosystems/utils.js +2 -0
- package/lib/ecosystems/vendoredCode.js +156 -0
- package/lib/evinser/evinser.js +578 -69
- package/lib/helpers/agenticToolutils.js +2 -2
- package/lib/helpers/bomSigner.js +576 -128
- package/lib/inventory/aiCollector.js +4 -1
- package/lib/inventory/analyzer.js +110 -127
- package/lib/inventory/atomUtils.js +171 -48
- package/lib/inventory/auditCategories.js +7 -0
- package/lib/inventory/buildToolRateLimit.js +144 -0
- package/lib/inventory/cacheCommands.js +2 -1
- package/lib/inventory/cmakeBuildDirs.js +254 -0
- package/lib/inventory/deps.js +501 -124
- package/lib/inventory/dosai.js +248 -44
- package/lib/inventory/dosaiParsers.js +310 -15
- package/lib/inventory/dynamic.js +52 -6
- package/lib/inventory/evidenceUtils.js +154 -109
- package/lib/inventory/fetchBatch.js +574 -38
- package/lib/inventory/fetchDiskCache.js +597 -0
- package/lib/inventory/forgeEnricher.js +14 -5
- package/lib/inventory/jvmLocalRepos.js +917 -0
- package/lib/inventory/mcpConfigParser.js +2 -3
- package/lib/inventory/parentSelection.js +146 -0
- package/lib/inventory/rustFormulationParser.js +3 -4
- package/lib/inventory/scalaCoords.js +153 -0
- package/lib/inventory/source.js +8 -8
- package/lib/inventory/spdx.js +101 -0
- package/lib/inventory/traceRunner.js +88 -7
- package/lib/inventory/tracedCrypto.js +294 -0
- package/lib/managers/docker.js +33 -32
- package/lib/managers/oci.js +24 -2
- package/lib/parsers/cmakeCache.js +69 -0
- package/lib/parsers/cmakeLists.js +16 -2
- package/lib/parsers/cmakePresets.js +400 -0
- package/lib/parsers/compileCommands.js +557 -0
- package/lib/parsers/strictJson.js +322 -0
- package/lib/parsers/terraformConfig.js +1073 -0
- package/lib/parsers/terraformSource.js +652 -0
- package/lib/server/server.js +23 -0
- package/lib/stages/postgen/introspection/markers.js +1 -1
- package/lib/stages/postgen/postgen.js +15 -1
- package/lib/stages/postgen/specVersionCompat.js +32 -0
- package/lib/validator/index.js +25 -10
- package/lib/validator/reporters/json.js +1 -0
- package/package.json +21 -21
- package/types/lib/audit/index.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts +24 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts +7 -4
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +3 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/activity.d.ts.map +1 -1
- package/types/lib/core/env.d.ts +9 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/fs.d.ts.map +1 -1
- package/types/lib/core/httpClient.d.ts +13 -8
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/pluginRun.d.ts +97 -0
- package/types/lib/core/pluginRun.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +36 -0
- package/types/lib/core/processTree.d.ts.map +1 -0
- package/types/lib/core/runState.d.ts +29 -0
- package/types/lib/core/runState.d.ts.map +1 -0
- package/types/lib/core/toml.d.ts +20 -0
- package/types/lib/core/toml.d.ts.map +1 -0
- package/types/lib/ecosystems/asarutils.d.ts.map +1 -1
- package/types/lib/ecosystems/bunutils.d.ts.map +1 -1
- package/types/lib/ecosystems/cargoCache.d.ts +12 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -1
- package/types/lib/ecosystems/cmakeResolver.d.ts +5 -3
- package/types/lib/ecosystems/cmakeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/cppBuildContext.d.ts +55 -0
- package/types/lib/ecosystems/cppBuildContext.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +18 -2
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +92 -0
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -0
- package/types/lib/ecosystems/denoutils.d.ts +9 -3
- package/types/lib/ecosystems/denoutils.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +62 -13
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/golem.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +50 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/jsEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts.map +1 -1
- package/types/lib/ecosystems/npmutils.d.ts +15 -0
- package/types/lib/ecosystems/npmutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-dotnet.d.ts +118 -4
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-gleam.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-go.d.ts +44 -2
- package/types/lib/ecosystems/parsers-go.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +6 -1
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts +46 -1
- package/types/lib/ecosystems/parsers-jvm.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +3 -1
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +53 -3
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-terraform.d.ts +38 -21
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +27 -2
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/remote/huggingface.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +21 -1
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/ecosystems/sbtutils.d.ts +88 -2
- package/types/lib/ecosystems/sbtutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts +56 -0
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -0
- package/types/lib/ecosystems/scalasem.d.ts +311 -0
- package/types/lib/ecosystems/scalasem.d.ts.map +1 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts +9 -0
- package/types/lib/ecosystems/terraformRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/utils.d.ts +2 -2
- package/types/lib/ecosystems/utils.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +32 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +75 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/bomSigner.d.ts +80 -4
- package/types/lib/helpers/bomSigner.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +82 -10
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +3 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/buildToolRateLimit.d.ts +38 -0
- package/types/lib/inventory/buildToolRateLimit.d.ts.map +1 -0
- package/types/lib/inventory/cacheCommands.d.ts +2 -1
- package/types/lib/inventory/cacheCommands.d.ts.map +1 -1
- package/types/lib/inventory/cmakeBuildDirs.d.ts +58 -0
- package/types/lib/inventory/cmakeBuildDirs.d.ts.map +1 -0
- package/types/lib/inventory/deps.d.ts +59 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +57 -8
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dosaiParsers.d.ts +53 -7
- package/types/lib/inventory/dosaiParsers.d.ts.map +1 -1
- package/types/lib/inventory/dynamic.d.ts +10 -0
- package/types/lib/inventory/dynamic.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts +19 -4
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +83 -14
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchDiskCache.d.ts +169 -0
- package/types/lib/inventory/fetchDiskCache.d.ts.map +1 -0
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmLocalRepos.d.ts +178 -0
- package/types/lib/inventory/jvmLocalRepos.d.ts.map +1 -0
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/parentSelection.d.ts +34 -0
- package/types/lib/inventory/parentSelection.d.ts.map +1 -0
- package/types/lib/inventory/rustFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/scalaCoords.d.ts +80 -0
- package/types/lib/inventory/scalaCoords.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/spdx.d.ts +9 -0
- package/types/lib/inventory/spdx.d.ts.map +1 -1
- package/types/lib/inventory/traceRunner.d.ts +12 -0
- package/types/lib/inventory/traceRunner.d.ts.map +1 -1
- package/types/lib/inventory/tracedCrypto.d.ts +42 -0
- package/types/lib/inventory/tracedCrypto.d.ts.map +1 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/managers/oci.d.ts +3 -1
- package/types/lib/managers/oci.d.ts.map +1 -1
- package/types/lib/parsers/cmakeCache.d.ts +16 -0
- package/types/lib/parsers/cmakeCache.d.ts.map +1 -1
- package/types/lib/parsers/cmakeLists.d.ts +2 -1
- package/types/lib/parsers/cmakeLists.d.ts.map +1 -1
- package/types/lib/parsers/cmakePresets.d.ts +78 -0
- package/types/lib/parsers/cmakePresets.d.ts.map +1 -0
- package/types/lib/parsers/compileCommands.d.ts +115 -0
- package/types/lib/parsers/compileCommands.d.ts.map +1 -0
- package/types/lib/parsers/strictJson.d.ts +46 -0
- package/types/lib/parsers/strictJson.d.ts.map +1 -0
- package/types/lib/parsers/terraformConfig.d.ts +70 -0
- package/types/lib/parsers/terraformConfig.d.ts.map +1 -0
- package/types/lib/parsers/terraformSource.d.ts +66 -0
- package/types/lib/parsers/terraformSource.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
- package/types/lib/stages/postgen/specVersionCompat.d.ts.map +1 -1
- package/types/lib/validator/index.d.ts +8 -0
- package/types/lib/validator/index.d.ts.map +1 -1
- package/types/lib/validator/reporters/json.d.ts.map +1 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +0 -462
|
@@ -41,6 +41,252 @@ export function applyNugetVersion(pkg, version) {
|
|
|
41
41
|
return applyPurl(pkg, nugetPurl(pkg.name, concrete));
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Compare two NuGet versions: up to four numeric release parts (missing parts
|
|
46
|
+
* read as 0), then the prerelease label, with a release sorting after its own
|
|
47
|
+
* prereleases. Build metadata after `+` is ignored, as NuGet ignores it.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} a Version
|
|
50
|
+
* @param {string} b Version
|
|
51
|
+
* @returns {number} Negative, zero or positive, like a sort comparator
|
|
52
|
+
*/
|
|
53
|
+
export function compareNugetVersions(a, b) {
|
|
54
|
+
const split = (version) => {
|
|
55
|
+
const withoutMetadata = `${version ?? ""}`.trim().split("+")[0];
|
|
56
|
+
const dash = withoutMetadata.indexOf("-");
|
|
57
|
+
const release =
|
|
58
|
+
dash >= 0 ? withoutMetadata.slice(0, dash) : withoutMetadata;
|
|
59
|
+
const prerelease = dash >= 0 ? withoutMetadata.slice(dash + 1) : "";
|
|
60
|
+
const parts = release.split(".").map((part) => Number.parseInt(part, 10));
|
|
61
|
+
return { parts, prerelease };
|
|
62
|
+
};
|
|
63
|
+
const left = split(a);
|
|
64
|
+
const right = split(b);
|
|
65
|
+
for (let index = 0; index < 4; index++) {
|
|
66
|
+
const l = Number.isNaN(left.parts[index]) ? 0 : (left.parts[index] ?? 0);
|
|
67
|
+
const r = Number.isNaN(right.parts[index]) ? 0 : (right.parts[index] ?? 0);
|
|
68
|
+
if (l !== r) {
|
|
69
|
+
return l < r ? -1 : 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (left.prerelease === right.prerelease) {
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
if (!left.prerelease) {
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
if (!right.prerelease) {
|
|
79
|
+
return -1;
|
|
80
|
+
}
|
|
81
|
+
const leftLabels = left.prerelease.toLowerCase().split(".");
|
|
82
|
+
const rightLabels = right.prerelease.toLowerCase().split(".");
|
|
83
|
+
for (
|
|
84
|
+
let index = 0;
|
|
85
|
+
index < Math.max(leftLabels.length, rightLabels.length);
|
|
86
|
+
index++
|
|
87
|
+
) {
|
|
88
|
+
const l = leftLabels[index];
|
|
89
|
+
const r = rightLabels[index];
|
|
90
|
+
if (l === undefined || r === undefined) {
|
|
91
|
+
return l === undefined ? -1 : 1;
|
|
92
|
+
}
|
|
93
|
+
if (l === r) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const lNumeric = /^\d+$/.test(l);
|
|
97
|
+
const rNumeric = /^\d+$/.test(r);
|
|
98
|
+
if (lNumeric && rNumeric) {
|
|
99
|
+
return Number(l) < Number(r) ? -1 : 1;
|
|
100
|
+
}
|
|
101
|
+
if (lNumeric !== rNumeric) {
|
|
102
|
+
return lNumeric ? -1 : 1;
|
|
103
|
+
}
|
|
104
|
+
return l < r ? -1 : 1;
|
|
105
|
+
}
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Whether a NuGet version range allows a version. A bare version is a minimum
|
|
111
|
+
* (`1.0` means at least 1.0); brackets are inclusive and parentheses exclusive
|
|
112
|
+
* (`[1.0]`, `[1.0,2.0)`, `(,2.0]`). An empty or unparseable range allows
|
|
113
|
+
* anything.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} range Range as a nuspec or lock file states it
|
|
116
|
+
* @param {string} version Version to test
|
|
117
|
+
* @returns {boolean} True when the range allows the version
|
|
118
|
+
*/
|
|
119
|
+
export function nugetRangeAllows(range, version) {
|
|
120
|
+
const text = `${range ?? ""}`.trim();
|
|
121
|
+
if (!text || !version) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
const opening = text[0];
|
|
125
|
+
if (opening !== "[" && opening !== "(") {
|
|
126
|
+
return compareNugetVersions(version, text) >= 0;
|
|
127
|
+
}
|
|
128
|
+
const closing = text[text.length - 1];
|
|
129
|
+
if (closing !== "]" && closing !== ")") {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
const inner = text.slice(1, -1);
|
|
133
|
+
const comma = inner.indexOf(",");
|
|
134
|
+
if (comma < 0) {
|
|
135
|
+
return compareNugetVersions(version, inner.trim()) === 0;
|
|
136
|
+
}
|
|
137
|
+
const minimum = inner.slice(0, comma).trim();
|
|
138
|
+
const maximum = inner.slice(comma + 1).trim();
|
|
139
|
+
if (minimum) {
|
|
140
|
+
const order = compareNugetVersions(version, minimum);
|
|
141
|
+
if (order < 0 || (order === 0 && opening === "(")) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (maximum) {
|
|
146
|
+
const order = compareNugetVersions(version, maximum);
|
|
147
|
+
if (order > 0 || (order === 0 && closing === ")")) {
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Choose the installed version a declared dependency resolves to: the lowest
|
|
156
|
+
* installed version the range allows, as NuGet picks the lowest applicable
|
|
157
|
+
* version; else the only installed version, which is what binding redirects
|
|
158
|
+
* unify a dependency onto. Undefined when nothing installed can be named.
|
|
159
|
+
*
|
|
160
|
+
* @param {string} range Declared range
|
|
161
|
+
* @param {string[]} installedVersions Versions installed for the package
|
|
162
|
+
* @returns {string|undefined} The chosen version
|
|
163
|
+
*/
|
|
164
|
+
export function resolveInstalledNugetVersion(range, installedVersions = []) {
|
|
165
|
+
const allowed = installedVersions
|
|
166
|
+
.filter((version) => nugetRangeAllows(range, version))
|
|
167
|
+
.sort(compareNugetVersions);
|
|
168
|
+
if (allowed.length) {
|
|
169
|
+
return allowed[0];
|
|
170
|
+
}
|
|
171
|
+
return installedVersions.length === 1 ? installedVersions[0] : undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* The project directory a .NET manifest belongs to. Restore output records its
|
|
176
|
+
* project file; otherwise project.assets.json sits in the project's `obj/`,
|
|
177
|
+
* and lock files, packages.config and project files sit in the project
|
|
178
|
+
* directory itself.
|
|
179
|
+
*
|
|
180
|
+
* @param {string} manifestFile Manifest path
|
|
181
|
+
* @param {string} [projectPath] Project file the manifest names, when it does
|
|
182
|
+
* @returns {string} Project directory
|
|
183
|
+
*/
|
|
184
|
+
export function dotnetManifestProjectDir(manifestFile, projectPath) {
|
|
185
|
+
if (projectPath) {
|
|
186
|
+
const projectDir = dirname(resolve(projectPath));
|
|
187
|
+
if (safeExistsSync(projectDir)) {
|
|
188
|
+
return projectDir;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const dir = dirname(resolve(manifestFile));
|
|
192
|
+
return basename(dir) === "obj" ? dirname(dir) : dir;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Index the MSBuild properties that props files define, by the directory each
|
|
197
|
+
* file sits in and across the whole scan, so the candidates for every project
|
|
198
|
+
* come from one read of each file.
|
|
199
|
+
*
|
|
200
|
+
* @param {string[]} propsFiles Props files of the scan
|
|
201
|
+
* @returns {{byDir: Object, treeWide: Object}} Property values per directory and
|
|
202
|
+
* across the scan, each a map of property name to values
|
|
203
|
+
*/
|
|
204
|
+
export function indexPropsProperties(propsFiles = []) {
|
|
205
|
+
const byDir = {};
|
|
206
|
+
const treeWide = {};
|
|
207
|
+
for (const propsFile of propsFiles) {
|
|
208
|
+
if (!propsFile) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const nodes = getPropertyGroupTextNodes([propsFile]);
|
|
212
|
+
const dir = dirname(resolve(propsFile));
|
|
213
|
+
byDir[dir] ??= {};
|
|
214
|
+
for (const [key, values] of Object.entries(nodes)) {
|
|
215
|
+
byDir[dir][key] ??= [];
|
|
216
|
+
treeWide[key] ??= [];
|
|
217
|
+
for (const value of values) {
|
|
218
|
+
if (!byDir[dir][key].includes(value)) {
|
|
219
|
+
byDir[dir][key].push(value);
|
|
220
|
+
}
|
|
221
|
+
if (!treeWide[key].includes(value)) {
|
|
222
|
+
treeWide[key].push(value);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
return { byDir, treeWide };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* The candidate values of MSBuild properties that can apply to a project file,
|
|
232
|
+
* from the props files beside it and above it: the nearest directory that
|
|
233
|
+
* defines a property wins, as the nearest Directory.Build.props is the one
|
|
234
|
+
* MSBuild imports. A property no such directory defines falls back to the
|
|
235
|
+
* values the whole scan's props files give it, which a caller only trusts when
|
|
236
|
+
* there is exactly one.
|
|
237
|
+
*
|
|
238
|
+
* @param {string} projFile Project file
|
|
239
|
+
* @param {{byDir: Object, treeWide: Object}} propsIndex Index built by {@link indexPropsProperties}
|
|
240
|
+
* @returns {Object} Map of property name to candidate values
|
|
241
|
+
*/
|
|
242
|
+
export function projectPropertyCandidates(projFile, propsIndex = {}) {
|
|
243
|
+
const candidates = { ...(propsIndex.treeWide || {}) };
|
|
244
|
+
const settled = new Set();
|
|
245
|
+
let dir = dirname(resolve(projFile));
|
|
246
|
+
for (;;) {
|
|
247
|
+
for (const [key, values] of Object.entries(propsIndex.byDir?.[dir] || {})) {
|
|
248
|
+
if (!settled.has(key)) {
|
|
249
|
+
candidates[key] = values;
|
|
250
|
+
settled.add(key);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const parent = dirname(dir);
|
|
254
|
+
if (parent === dir) {
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
dir = parent;
|
|
258
|
+
}
|
|
259
|
+
return candidates;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* The version of each package that every manifest of a scan agrees on, keyed by
|
|
264
|
+
* lowercased package id. A package that two manifests give different versions
|
|
265
|
+
* is left out: without knowing which project a declaration belongs to, picking
|
|
266
|
+
* one of them would be a guess.
|
|
267
|
+
*
|
|
268
|
+
* @param {Object[]} pkgList Components collected from the scan's manifests
|
|
269
|
+
* @returns {Object} Map of lowercased package id to version
|
|
270
|
+
*/
|
|
271
|
+
export function agreedNugetVersions(pkgList = []) {
|
|
272
|
+
const versions = {};
|
|
273
|
+
for (const p of pkgList) {
|
|
274
|
+
if (!p?.name || !p.version || ["application", "project"].includes(p.type)) {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
const key = p.name.toLowerCase();
|
|
278
|
+
versions[key] ??= new Set();
|
|
279
|
+
versions[key].add(p.version);
|
|
280
|
+
}
|
|
281
|
+
const agreed = {};
|
|
282
|
+
for (const [key, values] of Object.entries(versions)) {
|
|
283
|
+
if (values.size === 1) {
|
|
284
|
+
agreed[key] = [...values][0];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return agreed;
|
|
288
|
+
}
|
|
289
|
+
|
|
44
290
|
/**
|
|
45
291
|
* Method to parse .nupkg files
|
|
46
292
|
*
|
|
@@ -70,9 +316,16 @@ export function parseNuspecData(nupkgFile, nuspecData) {
|
|
|
70
316
|
const pkg = { group: "" };
|
|
71
317
|
let npkg;
|
|
72
318
|
const dependenciesMap = {};
|
|
319
|
+
// The version range each dependency accepts, per depending package, so a
|
|
320
|
+
// caller holding the installed packages can point an edge at one of them.
|
|
321
|
+
const dependencyRanges = {};
|
|
73
322
|
const addedMap = {};
|
|
74
323
|
try {
|
|
75
|
-
|
|
324
|
+
// A nuspec written on Windows often starts with a UTF-8 byte order mark,
|
|
325
|
+
// which the XML parser rejects as text before the declaration.
|
|
326
|
+
const data =
|
|
327
|
+
nuspecData.charCodeAt(0) === 0xfeff ? nuspecData.slice(1) : nuspecData;
|
|
328
|
+
npkg = xml2js(data, {
|
|
76
329
|
compact: true,
|
|
77
330
|
alwaysArray: false,
|
|
78
331
|
spaces: 4,
|
|
@@ -94,6 +347,7 @@ export function parseNuspecData(nupkgFile, nuspecData) {
|
|
|
94
347
|
return {
|
|
95
348
|
pkgList,
|
|
96
349
|
dependenciesMap,
|
|
350
|
+
dependencyRanges,
|
|
97
351
|
};
|
|
98
352
|
}
|
|
99
353
|
const m = npkg.metadata;
|
|
@@ -101,7 +355,12 @@ export function parseNuspecData(nupkgFile, nuspecData) {
|
|
|
101
355
|
pkg.version = m.version._;
|
|
102
356
|
pkg.description = m.description._;
|
|
103
357
|
applyPurl(pkg, nugetPurl(pkg.name, pkg.version));
|
|
104
|
-
|
|
358
|
+
// A licence expression is the machine-readable licence NuGet records; the
|
|
359
|
+
// licence URL is the older, often unresolvable form, so the expression wins.
|
|
360
|
+
if (m.license?.$?.type === "expression" && m.license?._?.trim()) {
|
|
361
|
+
pkg.license = findLicenseId(m.license._.trim()) || m.license._.trim();
|
|
362
|
+
}
|
|
363
|
+
if (!pkg.license && m.licenseUrl) {
|
|
105
364
|
pkg.license = findLicenseId(m.licenseUrl._);
|
|
106
365
|
}
|
|
107
366
|
if (m.authors) {
|
|
@@ -128,18 +387,19 @@ export function parseNuspecData(nupkgFile, nuspecData) {
|
|
|
128
387
|
};
|
|
129
388
|
pkg.scope = "required";
|
|
130
389
|
pkgList.push(pkg);
|
|
390
|
+
const ranges = {};
|
|
131
391
|
if (m?.dependencies?.dependency) {
|
|
132
392
|
const dependsOn = [];
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
} else {
|
|
139
|
-
const d = m.dependencies.dependency.$;
|
|
393
|
+
const declared = Array.isArray(m.dependencies.dependency)
|
|
394
|
+
? m.dependencies.dependency
|
|
395
|
+
: [m.dependencies.dependency];
|
|
396
|
+
for (const adep of declared) {
|
|
397
|
+
const d = adep.$;
|
|
140
398
|
dependsOn.push(d.id);
|
|
399
|
+
ranges[d.id] ??= d.version;
|
|
141
400
|
}
|
|
142
401
|
dependenciesMap[pkg["bom-ref"]] = dependsOn;
|
|
402
|
+
dependencyRanges[pkg["bom-ref"]] = ranges;
|
|
143
403
|
} else if (m?.dependencies?.group) {
|
|
144
404
|
let dependencyGroups;
|
|
145
405
|
if (Array.isArray(m.dependencies.group)) {
|
|
@@ -168,6 +428,7 @@ export function parseNuspecData(nupkgFile, nuspecData) {
|
|
|
168
428
|
continue;
|
|
169
429
|
}
|
|
170
430
|
groupPkg.name = agroupdep.id;
|
|
431
|
+
ranges[agroupdep.id] ??= agroupdep.version;
|
|
171
432
|
let declaredRange;
|
|
172
433
|
if (agroupdep?.version) {
|
|
173
434
|
let versionStr = agroupdep.version;
|
|
@@ -225,11 +486,13 @@ export function parseNuspecData(nupkgFile, nuspecData) {
|
|
|
225
486
|
} // for
|
|
226
487
|
} // group dependency block
|
|
227
488
|
dependenciesMap[pkg["bom-ref"]] = dependsOn;
|
|
489
|
+
dependencyRanges[pkg["bom-ref"]] = ranges;
|
|
228
490
|
} // for
|
|
229
491
|
}
|
|
230
492
|
return {
|
|
231
493
|
pkgList,
|
|
232
494
|
dependenciesMap,
|
|
495
|
+
dependencyRanges,
|
|
233
496
|
};
|
|
234
497
|
}
|
|
235
498
|
|
|
@@ -289,7 +552,7 @@ export function parseCsPkgData(pkgData, pkgFile, pkgNameVersions = {}) {
|
|
|
289
552
|
// Backfill from a version already resolved by a more precise manifest.
|
|
290
553
|
// Concrete versions are never overridden - a disagreement means both
|
|
291
554
|
// versions must be tracked.
|
|
292
|
-
pkg.version = pkgNameVersions
|
|
555
|
+
pkg.version = lookupNugetVersion(pkgNameVersions, pkg.name);
|
|
293
556
|
}
|
|
294
557
|
applyNugetVersion(pkg, pkg.version);
|
|
295
558
|
if (pkgFile) {
|
|
@@ -481,16 +744,115 @@ function propertyGroupText(propertyGroup, key) {
|
|
|
481
744
|
return text.trim();
|
|
482
745
|
}
|
|
483
746
|
|
|
747
|
+
/**
|
|
748
|
+
* Look up a package version in a name - version map. NuGet package ids are
|
|
749
|
+
* case-insensitive, so a map may be keyed by the id as written or lowercased.
|
|
750
|
+
*
|
|
751
|
+
* @param {Object} versions Map of package id to version
|
|
752
|
+
* @param {string} name Package id
|
|
753
|
+
* @returns {string|undefined} The version, or undefined when the map has none
|
|
754
|
+
*/
|
|
755
|
+
export function lookupNugetVersion(versions, name) {
|
|
756
|
+
if (!versions || !name) {
|
|
757
|
+
return undefined;
|
|
758
|
+
}
|
|
759
|
+
return versions[name] ?? versions[name.toLowerCase()];
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Collect the MSBuild properties a project file defines itself, from every
|
|
764
|
+
* PropertyGroup, keyed by property name. A later definition wins, as it does
|
|
765
|
+
* when MSBuild evaluates the file.
|
|
766
|
+
*
|
|
767
|
+
* @param {Object} project Project element parsed by xml2js in compact mode
|
|
768
|
+
* @returns {Object} Map of property name to text value
|
|
769
|
+
*/
|
|
770
|
+
function projectOwnProperties(project) {
|
|
771
|
+
const properties = {};
|
|
772
|
+
for (const propertyGroup of project?.PropertyGroup || []) {
|
|
773
|
+
for (const key of Object.keys(propertyGroup)) {
|
|
774
|
+
if (key === "$" || key === "value") {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
const text = propertyGroupText(propertyGroup, key);
|
|
778
|
+
if (text !== undefined) {
|
|
779
|
+
properties[key] = text;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return properties;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Evaluate MSBuild properties of one project with a single `dotnet msbuild`
|
|
788
|
+
* call. Asking for every property at once costs one evaluation per project,
|
|
789
|
+
* where one call per package reference evaluated the same project over and
|
|
790
|
+
* over.
|
|
791
|
+
*
|
|
792
|
+
* @param {string} projFile Project file
|
|
793
|
+
* @param {string[]} names Property names
|
|
794
|
+
* @returns {Object} Map of property name to evaluated value, without empty values
|
|
795
|
+
*/
|
|
796
|
+
function evaluateMsbuildProperties(projFile, names) {
|
|
797
|
+
const values = {};
|
|
798
|
+
if (!projFile || !names.length) {
|
|
799
|
+
return values;
|
|
800
|
+
}
|
|
801
|
+
const result = safeSpawnSync(
|
|
802
|
+
DOTNET_CMD,
|
|
803
|
+
[
|
|
804
|
+
"msbuild",
|
|
805
|
+
projFile,
|
|
806
|
+
"-nologo",
|
|
807
|
+
...names.map((name) => `-getProperty:${name}`),
|
|
808
|
+
],
|
|
809
|
+
{ shell: isWin },
|
|
810
|
+
);
|
|
811
|
+
if (result.status !== 0 || result.error || !result.stdout?.trim()) {
|
|
812
|
+
return values;
|
|
813
|
+
}
|
|
814
|
+
const stdout = result.stdout.trim();
|
|
815
|
+
if (names.length === 1) {
|
|
816
|
+
values[names[0]] = stdout;
|
|
817
|
+
return values;
|
|
818
|
+
}
|
|
819
|
+
// Several properties come back as one JSON document.
|
|
820
|
+
try {
|
|
821
|
+
const evaluated = JSON.parse(stdout)?.Properties || {};
|
|
822
|
+
for (const name of names) {
|
|
823
|
+
if (typeof evaluated[name] === "string" && evaluated[name].trim()) {
|
|
824
|
+
values[name] = evaluated[name].trim();
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
} catch (_e) {
|
|
828
|
+
// Unexpected output: treat every property as unresolved.
|
|
829
|
+
}
|
|
830
|
+
return values;
|
|
831
|
+
}
|
|
832
|
+
|
|
484
833
|
/**
|
|
485
834
|
* Method to parse .csproj like xml files
|
|
486
835
|
*
|
|
836
|
+
* A version the project file does not state is taken, in order, from the
|
|
837
|
+
* versions this project restored, from central package management, and last
|
|
838
|
+
* from a version the whole scan agrees on. Two projects of one tree routinely
|
|
839
|
+
* restore different versions of a package, so a version taken from another
|
|
840
|
+
* project would describe a package this project does not use.
|
|
841
|
+
*
|
|
487
842
|
* @param {String} csProjData Raw data
|
|
488
843
|
* @param {String} projFile File name
|
|
489
|
-
* @param {Object} pkgNameVersions
|
|
844
|
+
* @param {Object} pkgNameVersions Versions this project restored (its project.assets.json,
|
|
845
|
+
* packages.lock.json or packages.config), keyed by package id or lowercased id
|
|
490
846
|
* @param {Boolean} msbuildInstalled Whether msbuild is available to resolve properties
|
|
491
|
-
* @param {Object} pkgVersionLabelCandidates Candidate values for msbuild version properties
|
|
847
|
+
* @param {Object} pkgVersionLabelCandidates Candidate values for msbuild version properties,
|
|
848
|
+
* from the props files that can apply to this project
|
|
492
849
|
* @param {Object} centralVersions Versions declared centrally in Directory.Packages.props,
|
|
493
850
|
* keyed by lowercased package id. See {@link getCentralPackageVersions}.
|
|
851
|
+
* @param {Object} fallbackVersions Versions every project of the scan agrees on, keyed by
|
|
852
|
+
* lowercased package id, used only when nothing closer states one
|
|
853
|
+
* @param {Object} restoredNames Package ids as this project's restore output spells them,
|
|
854
|
+
* keyed by lowercased id. NuGet ids are case-insensitive, so a reference spelled
|
|
855
|
+
* differently names the same package and takes the restored spelling.
|
|
494
856
|
*
|
|
495
857
|
* @returns {Object} Containing parent component, package, and dependencies
|
|
496
858
|
*/
|
|
@@ -501,6 +863,8 @@ export function parseCsProjData(
|
|
|
501
863
|
msbuildInstalled = false,
|
|
502
864
|
pkgVersionLabelCandidates = {},
|
|
503
865
|
centralVersions = {},
|
|
866
|
+
fallbackVersions = {},
|
|
867
|
+
restoredNames = {},
|
|
504
868
|
) {
|
|
505
869
|
const pkgList = [];
|
|
506
870
|
const parentComponent = { type: "application", properties: [] };
|
|
@@ -529,6 +893,39 @@ export function parseCsProjData(
|
|
|
529
893
|
return pkgList;
|
|
530
894
|
}
|
|
531
895
|
const project = projects[0];
|
|
896
|
+
const restoredName = (name) => restoredNames[name.toLowerCase()] || name;
|
|
897
|
+
const resolvedVersion = (name) =>
|
|
898
|
+
lookupNugetVersion(pkgNameVersions, name) ||
|
|
899
|
+
lookupNugetVersion(centralVersions, name) ||
|
|
900
|
+
lookupNugetVersion(fallbackVersions, name);
|
|
901
|
+
// `$(Name)` version labels: the project's own properties, then the props
|
|
902
|
+
// files that can apply to it, then (only for what neither settles) one
|
|
903
|
+
// msbuild evaluation of the project.
|
|
904
|
+
const ownProperties = projectOwnProperties(project);
|
|
905
|
+
const labelOf = (version) => version?.match(/^\$\((.*)\)$/)?.[1];
|
|
906
|
+
const staticLabelValue = (label) =>
|
|
907
|
+
ownProperties[label] ??
|
|
908
|
+
(pkgVersionLabelCandidates[label]?.length === 1
|
|
909
|
+
? pkgVersionLabelCandidates[label][0]
|
|
910
|
+
: undefined);
|
|
911
|
+
let msbuildValues = {};
|
|
912
|
+
if (msbuildInstalled) {
|
|
913
|
+
const unresolved = new Set();
|
|
914
|
+
for (const item of project.ItemGroup || []) {
|
|
915
|
+
for (const reference of item.PackageReference || []) {
|
|
916
|
+
const label = labelOf(
|
|
917
|
+
reference.$?.VersionOverride ||
|
|
918
|
+
reference.VersionOverride?.[0]._?.[0] ||
|
|
919
|
+
reference.$?.Version ||
|
|
920
|
+
reference.Version?.[0]._?.[0],
|
|
921
|
+
);
|
|
922
|
+
if (label && staticLabelValue(label) === undefined) {
|
|
923
|
+
unresolved.add(label);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
msbuildValues = evaluateMsbuildProperties(projFile, [...unresolved].sort());
|
|
928
|
+
}
|
|
532
929
|
// SDK-style projects declare their version through the MSBuild properties
|
|
533
930
|
// that `dotnet pack` reads: PackageVersion, which defaults to Version, which
|
|
534
931
|
// in turn defaults to VersionPrefix plus an optional `-VersionSuffix`. A
|
|
@@ -726,53 +1123,30 @@ export function parseCsProjData(
|
|
|
726
1123
|
if (!pref.Include || pref.Include.includes(".csproj")) {
|
|
727
1124
|
continue;
|
|
728
1125
|
}
|
|
729
|
-
pkg.name = pref.Include;
|
|
1126
|
+
pkg.name = restoredName(pref.Include);
|
|
730
1127
|
// Under central package management a PackageReference carries no Version at
|
|
731
1128
|
// all, and VersionOverride is the documented way for a project to opt out of
|
|
732
|
-
// the central version, so it wins over everything else.
|
|
733
|
-
// is
|
|
734
|
-
//
|
|
735
|
-
// more precise than a central declaration that may be a floating range.
|
|
1129
|
+
// the central version, so it wins over everything else. Otherwise the version
|
|
1130
|
+
// this project restored is more precise than a central declaration that may
|
|
1131
|
+
// be a floating range.
|
|
736
1132
|
pkg.version =
|
|
737
1133
|
pref.VersionOverride ||
|
|
738
1134
|
item.PackageReference[j].VersionOverride?.[0]._?.[0] ||
|
|
739
1135
|
pref.Version ||
|
|
740
1136
|
item.PackageReference[j].Version?.[0]._?.[0] ||
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
if (
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
);
|
|
755
|
-
if (
|
|
756
|
-
result.status === 0 &&
|
|
757
|
-
!result.error &&
|
|
758
|
-
result.stdout.trim()
|
|
759
|
-
) {
|
|
760
|
-
pkg.version = result.stdout.trim();
|
|
761
|
-
version_resolved = true;
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
if (!version_resolved) {
|
|
765
|
-
if (
|
|
766
|
-
pkgVersionLabelCandidates[version_label_match]?.length === 1
|
|
767
|
-
) {
|
|
768
|
-
// Prioritizing correctness over completeness: reject resolved package version label if it has more than one possible values.
|
|
769
|
-
pkg.version = pkgVersionLabelCandidates[version_label_match][0];
|
|
770
|
-
} else if (DEBUG_MODE) {
|
|
771
|
-
console.log(
|
|
772
|
-
`Could not resolve package version label ${version_label_match} for ${pkg.name}`,
|
|
773
|
-
);
|
|
774
|
-
}
|
|
775
|
-
}
|
|
1137
|
+
resolvedVersion(pkg.name);
|
|
1138
|
+
const versionLabel = labelOf(pkg.version);
|
|
1139
|
+
if (versionLabel) {
|
|
1140
|
+
// Prioritizing correctness over completeness: a label with more than one
|
|
1141
|
+
// candidate value across the applicable props files stays unresolved.
|
|
1142
|
+
const labelValue =
|
|
1143
|
+
msbuildValues[versionLabel] ?? staticLabelValue(versionLabel);
|
|
1144
|
+
if (labelValue !== undefined) {
|
|
1145
|
+
pkg.version = labelValue;
|
|
1146
|
+
} else if (DEBUG_MODE) {
|
|
1147
|
+
console.log(
|
|
1148
|
+
`Could not resolve package version label ${versionLabel} for ${pkg.name}`,
|
|
1149
|
+
);
|
|
776
1150
|
}
|
|
777
1151
|
}
|
|
778
1152
|
// A `Version` attribute holds whatever the project declares, which may
|
|
@@ -789,9 +1163,7 @@ export function parseCsProjData(
|
|
|
789
1163
|
pkg.version = exactPin[1];
|
|
790
1164
|
} else if (!concreteVersion(pkg.version)) {
|
|
791
1165
|
declaredRange = pkg.version;
|
|
792
|
-
pkg.version =
|
|
793
|
-
pkgNameVersions[pkg.name] ||
|
|
794
|
-
centralVersions[pkg.name.toLowerCase()];
|
|
1166
|
+
pkg.version = resolvedVersion(pkg.name);
|
|
795
1167
|
}
|
|
796
1168
|
}
|
|
797
1169
|
// A version that could not be resolved from any source must not be
|
|
@@ -866,21 +1238,20 @@ export function parseCsProjData(
|
|
|
866
1238
|
}
|
|
867
1239
|
}
|
|
868
1240
|
}
|
|
1241
|
+
pkg.name = restoredName(pkg.name);
|
|
869
1242
|
let declaredAssemblyVersion;
|
|
870
1243
|
if (incParts.length > 1 && incParts[1].includes("Version")) {
|
|
871
1244
|
const declared = incParts[1].replace("Version=", "").trim();
|
|
872
|
-
// An assembly reference carries the version inline,
|
|
873
|
-
//
|
|
874
|
-
//
|
|
875
|
-
//
|
|
876
|
-
//
|
|
1245
|
+
// An assembly reference carries the assembly's version inline, which is
|
|
1246
|
+
// not the version of the package that ships it (Newtonsoft.Json 13.0.3
|
|
1247
|
+
// ships assembly 13.0.0.0), and can be an MSBuild property such as
|
|
1248
|
+
// `Version=$(TargetFSharpCoreVersion)` that only a build evaluates. A
|
|
1249
|
+
// concrete one is kept as the assembly version; a property as a
|
|
1250
|
+
// declaration.
|
|
877
1251
|
if (declared && !concreteVersion(declared)) {
|
|
878
1252
|
declaredAssemblyVersion = declared;
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
centralVersions[pkg.name.toLowerCase()];
|
|
882
|
-
} else {
|
|
883
|
-
assemblyVersion = declared || pkgNameVersions[pkg.name];
|
|
1253
|
+
} else if (declared) {
|
|
1254
|
+
assemblyVersion = declared;
|
|
884
1255
|
}
|
|
885
1256
|
}
|
|
886
1257
|
if (declaredAssemblyVersion) {
|
|
@@ -889,7 +1260,11 @@ export function parseCsProjData(
|
|
|
889
1260
|
value: declaredAssemblyVersion,
|
|
890
1261
|
});
|
|
891
1262
|
}
|
|
892
|
-
|
|
1263
|
+
// The package version comes from the HintPath's package folder, or from
|
|
1264
|
+
// what this project restored; never from the assembly version, and not
|
|
1265
|
+
// from a central pin, which applies to PackageReference items only.
|
|
1266
|
+
const version =
|
|
1267
|
+
hintVersion ?? lookupNugetVersion(pkgNameVersions, pkg.name);
|
|
893
1268
|
// An assembly reference that states no version anywhere - no HintPath to
|
|
894
1269
|
// read one from and no `Version=` on the Include - leaves the component
|
|
895
1270
|
// versionless in both fields. The registry's latest release is not a
|
|
@@ -917,7 +1292,7 @@ export function parseCsProjData(
|
|
|
917
1292
|
value: assemblyName,
|
|
918
1293
|
});
|
|
919
1294
|
}
|
|
920
|
-
if (
|
|
1295
|
+
if (assemblyVersion) {
|
|
921
1296
|
pkg.properties.push({
|
|
922
1297
|
name: "cdx:dotnet:assembly_version",
|
|
923
1298
|
value: assemblyVersion,
|
|
@@ -966,14 +1341,21 @@ export function parseCsProjData(
|
|
|
966
1341
|
}
|
|
967
1342
|
if (sdkVersion) {
|
|
968
1343
|
parentComponent.version = sdkVersion;
|
|
1344
|
+
} else if (
|
|
1345
|
+
!parentComponent.version &&
|
|
1346
|
+
!Object.keys(sdkVersionProps).length &&
|
|
1347
|
+
(project.$?.Sdk || project.Sdk)
|
|
1348
|
+
) {
|
|
1349
|
+
// An SDK-style project that sets no version builds as 1.0.0, the Version
|
|
1350
|
+
// its project.assets.json records too, so both describe one component. A
|
|
1351
|
+
// version set from a build-time property is unknown, not 1.0.0.
|
|
1352
|
+
parentComponent.version = "1.0.0";
|
|
969
1353
|
}
|
|
970
1354
|
// The purl is built once every PropertyGroup has been read, so a version
|
|
971
|
-
// declared after OutputType is not lost.
|
|
1355
|
+
// declared after OutputType is not lost. A project that states no version
|
|
1356
|
+
// gets a versionless purl: "latest" is not a version.
|
|
972
1357
|
if (parentComponent.name) {
|
|
973
|
-
|
|
974
|
-
parentComponent,
|
|
975
|
-
nugetPurl(parentComponent.name, parentComponent.version || "latest"),
|
|
976
|
-
);
|
|
1358
|
+
applyNugetVersion(parentComponent, parentComponent.version);
|
|
977
1359
|
}
|
|
978
1360
|
let dependencies = [];
|
|
979
1361
|
if (parentComponent?.["bom-ref"]) {
|
|
@@ -991,6 +1373,41 @@ export function parseCsProjData(
|
|
|
991
1373
|
};
|
|
992
1374
|
}
|
|
993
1375
|
|
|
1376
|
+
// The package folders a consuming project compiles against or loads at run
|
|
1377
|
+
// time. A DLL elsewhere in the package (build/, buildTransitive/, tools/,
|
|
1378
|
+
// analyzers/) is a build-time helper of the package itself, such as the
|
|
1379
|
+
// System.Formats.Asn1.dll coverlet.collector carries for its test-platform
|
|
1380
|
+
// collector: the project's code never calls it, so its name must not credit
|
|
1381
|
+
// the package with the project's framework calls (issue 4441).
|
|
1382
|
+
const DOTNET_REFERENCED_ASSET_FOLDERS = new Set(["lib", "ref", "runtimes"]);
|
|
1383
|
+
|
|
1384
|
+
/**
|
|
1385
|
+
* Whether a file of a NuGet package is an assembly a consuming project can
|
|
1386
|
+
* reference: a .dll, .exe, or .so under lib/, ref/, or runtimes/.
|
|
1387
|
+
*
|
|
1388
|
+
* @param {string} packageFile Path of the file inside the package, as project.assets.json lists it
|
|
1389
|
+
* @returns {boolean} true for a referenced assembly
|
|
1390
|
+
*/
|
|
1391
|
+
export function isReferencedDotnetPackageFile(packageFile) {
|
|
1392
|
+
if (typeof packageFile !== "string") {
|
|
1393
|
+
return false;
|
|
1394
|
+
}
|
|
1395
|
+
const lowerFile = packageFile.toLowerCase();
|
|
1396
|
+
if (
|
|
1397
|
+
!lowerFile.endsWith(".dll") &&
|
|
1398
|
+
!lowerFile.endsWith(".exe") &&
|
|
1399
|
+
!lowerFile.endsWith(".so")
|
|
1400
|
+
) {
|
|
1401
|
+
return false;
|
|
1402
|
+
}
|
|
1403
|
+
const normalized = lowerFile.replaceAll("\\", "/");
|
|
1404
|
+
const slashIndex = normalized.indexOf("/");
|
|
1405
|
+
return (
|
|
1406
|
+
slashIndex > 0 &&
|
|
1407
|
+
DOTNET_REFERENCED_ASSET_FOLDERS.has(normalized.slice(0, slashIndex))
|
|
1408
|
+
);
|
|
1409
|
+
}
|
|
1410
|
+
|
|
994
1411
|
/**
|
|
995
1412
|
* Parse a .NET project.assets.json file and return the package list and dependency tree.
|
|
996
1413
|
*
|
|
@@ -1000,7 +1417,8 @@ export function parseCsProjData(
|
|
|
1000
1417
|
*
|
|
1001
1418
|
* @param {string} csProjData Raw JSON string of the project.assets.json file
|
|
1002
1419
|
* @param {string} assetsJsonFile Path to the project.assets.json file, used for evidence properties
|
|
1003
|
-
* @returns {{ pkgList: Object[], dependenciesList: Object[] }}
|
|
1420
|
+
* @returns {{ pkgList: Object[], dependenciesList: Object[], projectPath: (string|undefined) }}
|
|
1421
|
+
* `projectPath` is the project file the restore output belongs to, as restore recorded it
|
|
1004
1422
|
*/
|
|
1005
1423
|
export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
|
|
1006
1424
|
// extract name, operator, version from .NET package representation
|
|
@@ -1040,22 +1458,18 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
|
|
|
1040
1458
|
return { pkgList, dependenciesList };
|
|
1041
1459
|
}
|
|
1042
1460
|
csProjData = JSON.parse(csProjData);
|
|
1043
|
-
|
|
1461
|
+
const projectPath = csProjData.project?.restore?.projectPath;
|
|
1462
|
+
let rootRef;
|
|
1044
1463
|
if (csProjData.project?.restore?.projectName) {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
namespace: "" || null,
|
|
1048
|
-
name: csProjData.project?.restore?.projectName,
|
|
1049
|
-
version: csProjData.project.version || "latest" || null,
|
|
1050
|
-
});
|
|
1464
|
+
// The project's own version as restore evaluated it; "latest" is not a
|
|
1465
|
+
// version, so a project without one gets a versionless purl.
|
|
1051
1466
|
rootPkg = {
|
|
1052
1467
|
group: "",
|
|
1053
1468
|
name: csProjData.project.restore.projectName,
|
|
1054
|
-
version: csProjData.project.version || "latest",
|
|
1055
1469
|
type: "application",
|
|
1056
|
-
purl: purlString,
|
|
1057
|
-
"bom-ref": decodeURIComponent(purlString),
|
|
1058
1470
|
};
|
|
1471
|
+
applyNugetVersion(rootPkg, csProjData.project.version);
|
|
1472
|
+
rootRef = rootPkg["bom-ref"];
|
|
1059
1473
|
pkgList.push(rootPkg);
|
|
1060
1474
|
}
|
|
1061
1475
|
const rootPkgDeps = new Set();
|
|
@@ -1069,53 +1483,42 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
|
|
|
1069
1483
|
if (nameOperatorVersion == null) {
|
|
1070
1484
|
continue;
|
|
1071
1485
|
}
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
//
|
|
1075
|
-
//
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
break;
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
if (!matchFound) {
|
|
1094
|
-
if (DEBUG_MODE) {
|
|
1095
|
-
console.log(
|
|
1096
|
-
"Unable to match",
|
|
1097
|
-
dependencyName,
|
|
1098
|
-
"with a target name. The dependency tree will be imprecise.",
|
|
1099
|
-
);
|
|
1100
|
-
}
|
|
1101
|
-
continue;
|
|
1486
|
+
// A direct dependency is declared as a constraint (`Moq >= 4.17.6`,
|
|
1487
|
+
// `X >= 7.0.0-*`, `[4.0.0, 5.0.0)`), and restore installs one version of
|
|
1488
|
+
// each package per target: the edge points at that installed version,
|
|
1489
|
+
// whatever the constraint's lower bound says.
|
|
1490
|
+
const resolvedTarget = Object.keys(
|
|
1491
|
+
csProjData.targets[frameworkTarget] || {},
|
|
1492
|
+
).find(
|
|
1493
|
+
(targetKey) =>
|
|
1494
|
+
targetKey.split("/")[0].toLowerCase() ===
|
|
1495
|
+
nameOperatorVersion.name.toLowerCase(),
|
|
1496
|
+
);
|
|
1497
|
+
if (!resolvedTarget) {
|
|
1498
|
+
if (DEBUG_MODE) {
|
|
1499
|
+
console.log(
|
|
1500
|
+
"Unable to match",
|
|
1501
|
+
dependencyName,
|
|
1502
|
+
"with a target name. The dependency tree will be imprecise.",
|
|
1503
|
+
);
|
|
1102
1504
|
}
|
|
1505
|
+
continue;
|
|
1103
1506
|
}
|
|
1104
|
-
|
|
1507
|
+
const [nameToUse, resolvedVersion] = resolvedTarget.split("/");
|
|
1105
1508
|
const dpurl = decodeURIComponent(
|
|
1106
1509
|
build({
|
|
1107
1510
|
type: "nuget",
|
|
1108
1511
|
namespace: "" || null,
|
|
1109
1512
|
name: nameToUse,
|
|
1110
|
-
version:
|
|
1513
|
+
version: resolvedVersion || null,
|
|
1111
1514
|
}),
|
|
1112
1515
|
);
|
|
1113
1516
|
rootPkgDeps.add(dpurl);
|
|
1114
1517
|
}
|
|
1115
1518
|
}
|
|
1116
|
-
if (
|
|
1519
|
+
if (rootRef && rootPkgDeps.size) {
|
|
1117
1520
|
dependenciesList.push({
|
|
1118
|
-
ref:
|
|
1521
|
+
ref: rootRef,
|
|
1119
1522
|
dependsOn: Array.from(rootPkgDeps).sort(),
|
|
1120
1523
|
});
|
|
1121
1524
|
}
|
|
@@ -1154,12 +1557,8 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
|
|
|
1154
1557
|
if (lib[rootDep].files && Array.isArray(lib[rootDep].files)) {
|
|
1155
1558
|
const dllFiles = new Set();
|
|
1156
1559
|
lib[rootDep].files.forEach((f) => {
|
|
1157
|
-
if (
|
|
1158
|
-
f.
|
|
1159
|
-
f.endsWith(".exe") ||
|
|
1160
|
-
f.endsWith(".so")
|
|
1161
|
-
) {
|
|
1162
|
-
dllFiles.add(basename(f));
|
|
1560
|
+
if (isReferencedDotnetPackageFile(f)) {
|
|
1561
|
+
dllFiles.add(basename(f.replaceAll("\\", "/")));
|
|
1163
1562
|
}
|
|
1164
1563
|
});
|
|
1165
1564
|
pkg.properties = [
|
|
@@ -1246,6 +1645,7 @@ export function parseCsProjAssetsData(csProjData, assetsJsonFile) {
|
|
|
1246
1645
|
return {
|
|
1247
1646
|
pkgList,
|
|
1248
1647
|
dependenciesList,
|
|
1648
|
+
projectPath,
|
|
1249
1649
|
};
|
|
1250
1650
|
}
|
|
1251
1651
|
|
|
@@ -1321,39 +1721,55 @@ export function parseCsPkgLockData(csLockData, pkgLockFile) {
|
|
|
1321
1721
|
}
|
|
1322
1722
|
const dependsOn = new Set();
|
|
1323
1723
|
if (libData.dependencies) {
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
(assetData.dependencies[aversionNoRuntime]?.[adep.toLowerCase()] &&
|
|
1342
|
-
assetData.dependencies[aversionNoRuntime][adep.toLowerCase()]
|
|
1343
|
-
.type === "Project")
|
|
1344
|
-
) {
|
|
1345
|
-
adepResolvedVersion = undefined;
|
|
1346
|
-
adep = adep.toLowerCase();
|
|
1347
|
-
} else if (DEBUG_MODE) {
|
|
1348
|
-
console.warn(
|
|
1349
|
-
`Unable to find the resolved version for ${adep} ${aversion}. Using ${adepResolvedVersion} which may be incorrect.`,
|
|
1724
|
+
const aversionNoRuntime = aversion.split("/")[0];
|
|
1725
|
+
// The entry for a dependency: in this target, the target without its
|
|
1726
|
+
// runtime identifier, or a runtime-specific target of the same
|
|
1727
|
+
// framework. Package ids are case-insensitive, and the lock file writes
|
|
1728
|
+
// a dependency the way the depending package's nuspec spells it. See
|
|
1729
|
+
// #930 and #937.
|
|
1730
|
+
const sections = [
|
|
1731
|
+
assetData.dependencies[aversion],
|
|
1732
|
+
assetData.dependencies[aversionNoRuntime],
|
|
1733
|
+
...Object.keys(assetData.dependencies)
|
|
1734
|
+
.filter((key) => key.startsWith(`${aversionNoRuntime}/`))
|
|
1735
|
+
.map((key) => assetData.dependencies[key]),
|
|
1736
|
+
].filter(Boolean);
|
|
1737
|
+
const entryFor = (name) => {
|
|
1738
|
+
for (const section of sections) {
|
|
1739
|
+
const key = Object.keys(section).find(
|
|
1740
|
+
(candidate) => candidate.toLowerCase() === name.toLowerCase(),
|
|
1350
1741
|
);
|
|
1742
|
+
if (key) {
|
|
1743
|
+
return { name: key, entry: section[key] };
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
return undefined;
|
|
1747
|
+
};
|
|
1748
|
+
for (const adep of Object.keys(libData.dependencies)) {
|
|
1749
|
+
const found = entryFor(adep);
|
|
1750
|
+
let depName;
|
|
1751
|
+
let depVersion;
|
|
1752
|
+
if (found?.entry?.type === "Project") {
|
|
1753
|
+
depName = found.name;
|
|
1754
|
+
} else if (found?.entry?.resolved) {
|
|
1755
|
+
depName = found.name;
|
|
1756
|
+
depVersion = found.entry.resolved;
|
|
1757
|
+
} else {
|
|
1758
|
+
// The dependency value is the declared range (`[6.0.0, )`), which
|
|
1759
|
+
// names no package version: without a resolved entry there is no
|
|
1760
|
+
// component to point at.
|
|
1761
|
+
if (DEBUG_MODE) {
|
|
1762
|
+
console.warn(
|
|
1763
|
+
`Unable to find the resolved version for ${adep} ${aversion}. The dependency tree will be imprecise.`,
|
|
1764
|
+
);
|
|
1765
|
+
}
|
|
1766
|
+
continue;
|
|
1351
1767
|
}
|
|
1352
1768
|
const adpurl = build({
|
|
1353
1769
|
type: "nuget",
|
|
1354
1770
|
namespace: "" || null,
|
|
1355
|
-
name:
|
|
1356
|
-
version:
|
|
1771
|
+
name: depName,
|
|
1772
|
+
version: depVersion || null,
|
|
1357
1773
|
});
|
|
1358
1774
|
dependsOn.add(decodeURIComponent(adpurl));
|
|
1359
1775
|
}
|