@cdxgen/cdxgen 13.5.0 → 13.6.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 +6 -6
- package/bin/cdxgen.js +4 -0
- package/bin/evinse.js +3 -0
- package/bin/validate.js +6 -0
- package/data/license-aliases.json +24 -2
- package/data/remediations.json +124 -0
- package/data/rules/ai-bom.yaml +1 -1
- package/data/rules/brotli-decoder.yaml +107 -0
- package/data/rules/vscode-extensions.yaml +75 -0
- package/data/spdx-license-list.json +121 -7
- package/data/spdx-licenses.json +69 -0
- package/data/spdx.schema.json +845 -2
- package/lib/cli/bomAssembly.js +92 -3
- package/lib/cli/index.js +108 -1
- package/lib/cli/jsBom.js +153 -64
- package/lib/cli/jvmBom.js +237 -46
- package/lib/cli/managedBom.js +142 -16
- package/lib/cli/nativeBom.js +42 -5
- package/lib/core/boundedFile.js +344 -0
- package/lib/core/brotliProbe.js +182 -0
- package/lib/core/env.js +8 -0
- package/lib/core/fs.js +12 -0
- package/lib/core/httpClient.js +98 -1
- package/lib/core/lockPaths.js +38 -0
- package/lib/core/paths.js +24 -1
- package/lib/core/processTree.js +5 -0
- package/lib/core/toolRegistry.js +170 -0
- package/lib/ecosystems/cppEvidence.js +263 -106
- package/lib/ecosystems/cppIncludeResolver.js +16 -5
- package/lib/ecosystems/ecosystems.js +25 -10
- package/lib/ecosystems/gemHostProbe.js +301 -0
- package/lib/ecosystems/gradleutils.js +828 -224
- package/lib/ecosystems/parsers-bazel.js +107 -6
- package/lib/ecosystems/parsers-js.js +505 -22
- package/lib/ecosystems/parsers-misc.js +209 -20
- package/lib/ecosystems/parsers-python.js +89 -2
- package/lib/ecosystems/pythonutils.js +128 -2
- package/lib/ecosystems/rubyutils.js +538 -31
- package/lib/ecosystems/scalacliutils.js +2 -0
- package/lib/ecosystems/scalasem.js +796 -151
- package/lib/ecosystems/vendoredCode.js +237 -3
- package/lib/ecosystems/venvProbe.js +360 -0
- package/lib/evinser/evinser.js +346 -43
- package/lib/helpers/agenticToolutils.js +413 -93
- package/lib/helpers/vsixutils.js +1574 -315
- package/lib/inventory/atomUtils.js +77 -0
- package/lib/inventory/brotliDecoder.js +142 -0
- package/lib/inventory/buildToolRateLimit.js +1 -1
- package/lib/inventory/cbomutils.js +56 -7
- package/lib/inventory/deps.js +35 -0
- package/lib/inventory/dosai.js +139 -5
- package/lib/inventory/dotnetRuntime.js +217 -0
- package/lib/inventory/envcontext.js +307 -13
- package/lib/inventory/evidenceUtils.js +11 -0
- package/lib/inventory/fetchBatch.js +1 -1
- package/lib/inventory/formulationParsers.js +31 -3
- package/lib/inventory/gtfobins.js +13 -0
- package/lib/inventory/hostRuntimeProbe.js +686 -0
- package/lib/inventory/jvmLocalRepos.js +9 -2
- package/lib/inventory/jvmToolEnv.js +29 -0
- package/lib/inventory/licenseEnhancer.js +12 -0
- package/lib/inventory/mavenCentralMirror.js +534 -0
- package/lib/inventory/nodeBuildInfo.js +218 -0
- package/lib/inventory/nodeHostProbe.js +202 -0
- package/lib/inventory/osPackageResolver.js +182 -0
- package/lib/inventory/phpBuildInfo.js +10 -59
- package/lib/inventory/phpHostProbe.js +32 -488
- package/lib/inventory/rubyBuildInfo.js +972 -0
- package/lib/inventory/rubyHostProbe.js +218 -0
- package/lib/inventory/scalaCoords.js +4 -3
- package/lib/inventory/scalaDedupe.js +440 -0
- package/lib/inventory/source.js +49 -2
- package/lib/managers/binary.js +337 -3
- package/lib/managers/docker.js +79 -0
- package/lib/parsers/largeJson.js +28 -11
- package/lib/parsers/vscodeExtensionId.js +183 -0
- package/lib/server/server.js +12 -1
- package/lib/stages/postgen/ruleEngine.js +13 -5
- package/lib/stages/pregen/envAudit.js +1 -0
- package/lib/stages/pregen/pregen.js +114 -57
- package/lib/validator/bomValidator.js +6 -1
- package/lib/validator/generated/validate-1.6.mjs +1 -1
- package/lib/validator/generated/validate-1.7.mjs +1 -1
- package/lib/validator/generated/validate-2.0.mjs +1 -1
- package/lib/validator/index.js +1 -1
- package/package.json +3 -2
- package/types/lib/cli/bomAssembly.d.ts +13 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +1 -17
- package/types/lib/cli/jsBom.d.ts.map +1 -1
- package/types/lib/cli/jvmBom.d.ts.map +1 -1
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts.map +1 -1
- package/types/lib/core/boundedFile.d.ts +70 -0
- package/types/lib/core/boundedFile.d.ts.map +1 -0
- package/types/lib/core/brotliProbe.d.ts +37 -0
- package/types/lib/core/brotliProbe.d.ts.map +1 -0
- package/types/lib/core/env.d.ts +4 -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 +24 -0
- package/types/lib/core/httpClient.d.ts.map +1 -1
- package/types/lib/core/lockPaths.d.ts +19 -0
- package/types/lib/core/lockPaths.d.ts.map +1 -0
- package/types/lib/core/processTree.d.ts +2 -0
- package/types/lib/core/processTree.d.ts.map +1 -1
- package/types/lib/core/toolRegistry.d.ts +36 -0
- package/types/lib/core/toolRegistry.d.ts.map +1 -0
- package/types/lib/ecosystems/cppEvidence.d.ts +40 -13
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts +2 -1
- package/types/lib/ecosystems/cppIncludeResolver.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gemHostProbe.d.ts +28 -0
- package/types/lib/ecosystems/gemHostProbe.d.ts.map +1 -0
- package/types/lib/ecosystems/gradleutils.d.ts +47 -13
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-bazel.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-js.d.ts +17 -20
- package/types/lib/ecosystems/parsers-js.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-misc.d.ts +19 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-python.d.ts +10 -0
- package/types/lib/ecosystems/parsers-python.d.ts.map +1 -1
- package/types/lib/ecosystems/pythonutils.d.ts +20 -2
- package/types/lib/ecosystems/pythonutils.d.ts.map +1 -1
- package/types/lib/ecosystems/rubyutils.d.ts +144 -12
- package/types/lib/ecosystems/rubyutils.d.ts.map +1 -1
- package/types/lib/ecosystems/scalacliutils.d.ts.map +1 -1
- package/types/lib/ecosystems/vendoredCode.d.ts +26 -0
- package/types/lib/ecosystems/vendoredCode.d.ts.map +1 -1
- package/types/lib/ecosystems/venvProbe.d.ts +40 -0
- package/types/lib/ecosystems/venvProbe.d.ts.map +1 -0
- package/types/lib/evinser/evinser.d.ts +63 -8
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/inventory/atomUtils.d.ts +19 -0
- package/types/lib/inventory/atomUtils.d.ts.map +1 -1
- package/types/lib/inventory/brotliDecoder.d.ts +58 -0
- package/types/lib/inventory/brotliDecoder.d.ts.map +1 -0
- package/types/lib/inventory/cbomutils.d.ts +17 -0
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/deps.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +17 -1
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/dotnetRuntime.d.ts +44 -0
- package/types/lib/inventory/dotnetRuntime.d.ts.map +1 -0
- package/types/lib/inventory/envcontext.d.ts +52 -6
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/formulationParsers.d.ts.map +1 -1
- package/types/lib/inventory/gtfobins.d.ts.map +1 -1
- package/types/lib/inventory/hostRuntimeProbe.d.ts +44 -0
- package/types/lib/inventory/hostRuntimeProbe.d.ts.map +1 -0
- package/types/lib/inventory/jvmToolEnv.d.ts +12 -0
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/licenseEnhancer.d.ts.map +1 -1
- package/types/lib/inventory/mavenCentralMirror.d.ts +170 -0
- package/types/lib/inventory/mavenCentralMirror.d.ts.map +1 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts +95 -0
- package/types/lib/inventory/nodeBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/nodeHostProbe.d.ts +15 -0
- package/types/lib/inventory/nodeHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/osPackageResolver.d.ts +38 -0
- package/types/lib/inventory/osPackageResolver.d.ts.map +1 -1
- package/types/lib/inventory/phpBuildInfo.d.ts.map +1 -1
- package/types/lib/inventory/phpHostProbe.d.ts.map +1 -1
- package/types/lib/inventory/rubyBuildInfo.d.ts +271 -0
- package/types/lib/inventory/rubyBuildInfo.d.ts.map +1 -0
- package/types/lib/inventory/rubyHostProbe.d.ts +47 -0
- package/types/lib/inventory/rubyHostProbe.d.ts.map +1 -0
- package/types/lib/inventory/source.d.ts +10 -0
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/managers/binary.d.ts.map +1 -1
- package/types/lib/managers/docker.d.ts +12 -0
- package/types/lib/managers/docker.d.ts.map +1 -1
- package/types/lib/parsers/largeJson.d.ts +4 -3
- package/types/lib/parsers/largeJson.d.ts.map +1 -1
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/pregen/envAudit.d.ts.map +1 -1
- package/types/lib/stages/pregen/pregen.d.ts +12 -8
- package/types/lib/stages/pregen/pregen.d.ts.map +1 -1
package/lib/parsers/largeJson.js
CHANGED
|
@@ -54,9 +54,20 @@ function applySpec(value, spec) {
|
|
|
54
54
|
return value;
|
|
55
55
|
}
|
|
56
56
|
if (Array.isArray(value)) {
|
|
57
|
+
let elements = value;
|
|
58
|
+
if (spec.map) {
|
|
59
|
+
const mapped = [];
|
|
60
|
+
for (const element of elements) {
|
|
61
|
+
const transformed = spec.map(element);
|
|
62
|
+
if (transformed !== undefined) {
|
|
63
|
+
mapped.push(transformed);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
elements = mapped;
|
|
67
|
+
}
|
|
57
68
|
return spec.filter
|
|
58
|
-
?
|
|
59
|
-
:
|
|
69
|
+
? elements.filter((element) => spec.filter(element))
|
|
70
|
+
: elements;
|
|
60
71
|
}
|
|
61
72
|
if (!spec.members) {
|
|
62
73
|
return value;
|
|
@@ -294,9 +305,10 @@ function scanObjectMembers(cursor, stopAt = undefined) {
|
|
|
294
305
|
}
|
|
295
306
|
}
|
|
296
307
|
|
|
297
|
-
// Decode the array at the cursor in runs of whole elements,
|
|
298
|
-
// `
|
|
299
|
-
|
|
308
|
+
// Decode the array at the cursor in runs of whole elements, mapping each one
|
|
309
|
+
// through `spec.map` (an undefined result drops it) and keeping those that
|
|
310
|
+
// `spec.filter` accepts.
|
|
311
|
+
function readArrayElements(cursor, spec, batchBytes) {
|
|
300
312
|
if (cursor.nextToken() !== OPEN_BRACKET) {
|
|
301
313
|
cursor.fail("Expected an array");
|
|
302
314
|
}
|
|
@@ -310,8 +322,12 @@ function readArrayElements(cursor, filter, batchBytes) {
|
|
|
310
322
|
for (const element of JSON.parse(
|
|
311
323
|
`[${cursor.endCapture(cursor.pos - 1)}]`,
|
|
312
324
|
)) {
|
|
313
|
-
|
|
314
|
-
|
|
325
|
+
const mapped = spec?.map ? spec.map(element) : element;
|
|
326
|
+
if (mapped === undefined) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
if (!spec?.filter || spec.filter(mapped)) {
|
|
330
|
+
elements.push(mapped);
|
|
315
331
|
}
|
|
316
332
|
}
|
|
317
333
|
};
|
|
@@ -367,7 +383,7 @@ function decodeRange(fd, start, end, spec, options) {
|
|
|
367
383
|
);
|
|
368
384
|
}
|
|
369
385
|
} else if (first === OPEN_BRACKET) {
|
|
370
|
-
value = readArrayElements(cursor, spec
|
|
386
|
+
value = readArrayElements(cursor, spec, options.batchBytes);
|
|
371
387
|
} else {
|
|
372
388
|
// A string or number this large has no smaller parts; JSON.parse reports
|
|
373
389
|
// it the way it would for the whole file.
|
|
@@ -449,9 +465,10 @@ export function readJsonMember(filePath, name, options = {}) {
|
|
|
449
465
|
* more than a bounded run of it in one string.
|
|
450
466
|
*
|
|
451
467
|
* `spec` narrows what is kept. `{ members: { name: spec } }` keeps only the
|
|
452
|
-
* named members of an object, each narrowed by its own spec. `{
|
|
453
|
-
*
|
|
454
|
-
* keeps the
|
|
468
|
+
* named members of an object, each narrowed by its own spec. `{ map }`
|
|
469
|
+
* transforms each element of an array, an undefined result dropping it, and
|
|
470
|
+
* `{ filter }` keeps the mapped elements that `filter(element)` accepts.
|
|
471
|
+
* Anything else keeps the whole value.
|
|
455
472
|
*
|
|
456
473
|
* @param {string} filePath JSON file
|
|
457
474
|
* @param {{start: number, end: number}} range Byte range of the value, such as one returned by indexJsonObject
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// The one place that reads the identity of an installed VS Code-family
|
|
2
|
+
// extension from the name of its folder.
|
|
3
|
+
//
|
|
4
|
+
// An editor installs an extension into `<publisher>.<name>-<version>`, and a
|
|
5
|
+
// platform-specific build into `<publisher>.<name>-<version>-<platform>`, for
|
|
6
|
+
// example `anthropic.claude-code-2.1.313-darwin-arm64`. The extension scan and
|
|
7
|
+
// the osquery `vscode_extensions` rows both name the extension from this, so an
|
|
8
|
+
// extension found by `cdxgen -t vscode-extension` and by `obom` is the same
|
|
9
|
+
// component with the same purl.
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Target platforms a VS Code-family marketplace publishes an extension for.
|
|
13
|
+
* `universal` and `web` builds are the same artefact on every platform and are
|
|
14
|
+
* not part of the extension's identity.
|
|
15
|
+
*/
|
|
16
|
+
export const VSCODE_TARGET_PLATFORMS = new Set([
|
|
17
|
+
"win32-x64",
|
|
18
|
+
"win32-arm64",
|
|
19
|
+
"win32-ia32",
|
|
20
|
+
"linux-x64",
|
|
21
|
+
"linux-arm64",
|
|
22
|
+
"linux-armhf",
|
|
23
|
+
"alpine-x64",
|
|
24
|
+
"alpine-arm64",
|
|
25
|
+
"darwin-x64",
|
|
26
|
+
"darwin-arm64",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
// Folder suffixes for builds that run on every platform.
|
|
30
|
+
const NEUTRAL_PLATFORMS = ["universal", "web"];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Normalize a target platform value from a manifest, an `extensions.json`
|
|
34
|
+
* record, or a folder name. Values that do not name a specific platform, such
|
|
35
|
+
* as `universal`, `web`, `undefined`, or an empty string, return an empty
|
|
36
|
+
* string.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} value Raw target platform
|
|
39
|
+
* @returns {string} A value from VSCODE_TARGET_PLATFORMS, or an empty string
|
|
40
|
+
*/
|
|
41
|
+
export function normalizeVscodeTargetPlatform(value) {
|
|
42
|
+
const platform = `${value || ""}`.trim().toLowerCase();
|
|
43
|
+
return VSCODE_TARGET_PLATFORMS.has(platform) ? platform : "";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Split an installed extension folder name into its publisher, name, version
|
|
48
|
+
* and target platform.
|
|
49
|
+
*
|
|
50
|
+
* @param {string} dirName Folder name, such as `ms-python.python-2025.4.0` or
|
|
51
|
+
* `anthropic.claude-code-2.1.313-darwin-arm64`
|
|
52
|
+
* @returns {{publisher: string, name: string, version: string, platform: string}|undefined}
|
|
53
|
+
* The parts, or undefined when the name does not follow the pattern
|
|
54
|
+
*/
|
|
55
|
+
export function parseVscodeExtensionDirName(dirName) {
|
|
56
|
+
let value = `${dirName || ""}`.trim();
|
|
57
|
+
while (value.endsWith("/") || value.endsWith("\\")) {
|
|
58
|
+
value = value.slice(0, -1);
|
|
59
|
+
}
|
|
60
|
+
const dotIdx = value.indexOf(".");
|
|
61
|
+
if (dotIdx < 1) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const publisher = value.substring(0, dotIdx);
|
|
65
|
+
let rest = value.substring(dotIdx + 1);
|
|
66
|
+
let platform = "";
|
|
67
|
+
for (const candidate of [...VSCODE_TARGET_PLATFORMS, ...NEUTRAL_PLATFORMS]) {
|
|
68
|
+
if (rest.toLowerCase().endsWith(`-${candidate}`)) {
|
|
69
|
+
platform = normalizeVscodeTargetPlatform(candidate);
|
|
70
|
+
rest = rest.substring(0, rest.length - candidate.length - 1);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Marketplaces require semver versions, so the version starts at the first
|
|
75
|
+
// hyphen whose remainder is a whole semver: `python-3.11-tools-1.0.0` is the
|
|
76
|
+
// name `python-3.11-tools` at 1.0.0, and `1.0.0-beta-2` stays one version.
|
|
77
|
+
// A folder whose version is not semver falls back to the first hyphen
|
|
78
|
+
// followed by digits and a dot.
|
|
79
|
+
let versionStart = -1;
|
|
80
|
+
let fallbackStart = -1;
|
|
81
|
+
for (let i = 1; i < rest.length - 2; i++) {
|
|
82
|
+
if (rest[i] !== "-" || !isDigit(rest[i + 1])) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (isSemver(rest.substring(i + 1))) {
|
|
86
|
+
versionStart = i;
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
let j = i + 1;
|
|
90
|
+
while (j < rest.length && isDigit(rest[j])) {
|
|
91
|
+
j++;
|
|
92
|
+
}
|
|
93
|
+
if (fallbackStart < 0 && rest[j] === ".") {
|
|
94
|
+
fallbackStart = i;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (versionStart < 0) {
|
|
98
|
+
versionStart = fallbackStart;
|
|
99
|
+
}
|
|
100
|
+
if (versionStart < 1) {
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
const name = rest.substring(0, versionStart);
|
|
104
|
+
const version = rest.substring(versionStart + 1);
|
|
105
|
+
if (!name || !version) {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
return { publisher, name, version, platform };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isDigit(character) {
|
|
112
|
+
return character >= "0" && character <= "9";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function isIdentifierList(text, numericNoLeadingZero) {
|
|
116
|
+
if (!text) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return text.split(".").every((part) => {
|
|
120
|
+
if (!part.length) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
for (const character of part) {
|
|
124
|
+
const ok =
|
|
125
|
+
isDigit(character) ||
|
|
126
|
+
character === "-" ||
|
|
127
|
+
(character >= "a" && character <= "z") ||
|
|
128
|
+
(character >= "A" && character <= "Z");
|
|
129
|
+
if (!ok) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (numericNoLeadingZero && [...part].every(isDigit)) {
|
|
134
|
+
return part === "0" || part[0] !== "0";
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check a string is a semantic version: three numeric parts, then an optional
|
|
142
|
+
* `-prerelease` and `+build`.
|
|
143
|
+
*
|
|
144
|
+
* @param {string} text Candidate version
|
|
145
|
+
* @returns {boolean} true for a semver
|
|
146
|
+
*/
|
|
147
|
+
export function isSemver(text) {
|
|
148
|
+
const plus = text.indexOf("+");
|
|
149
|
+
const core = plus >= 0 ? text.substring(0, plus) : text;
|
|
150
|
+
if (plus >= 0 && !isIdentifierList(text.substring(plus + 1), false)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const dash = core.indexOf("-");
|
|
154
|
+
const numbers = dash >= 0 ? core.substring(0, dash) : core;
|
|
155
|
+
if (dash >= 0 && !isIdentifierList(core.substring(dash + 1), true)) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
const parts = numbers.split(".");
|
|
159
|
+
return (
|
|
160
|
+
parts.length === 3 &&
|
|
161
|
+
parts.every(
|
|
162
|
+
(part) =>
|
|
163
|
+
part.length > 0 &&
|
|
164
|
+
[...part].every(isDigit) &&
|
|
165
|
+
(part === "0" || part[0] !== "0"),
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Read the target platform from the path of an installed extension folder.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} extensionPath Path to the extension folder, with either
|
|
174
|
+
* separator
|
|
175
|
+
* @returns {string} The target platform, or an empty string
|
|
176
|
+
*/
|
|
177
|
+
export function vscodeExtensionPlatformFromPath(extensionPath) {
|
|
178
|
+
const segments = `${extensionPath || ""}`
|
|
179
|
+
.split(/[/\\]/u)
|
|
180
|
+
.filter((segment) => segment.length);
|
|
181
|
+
const dirName = segments[segments.length - 1];
|
|
182
|
+
return parseVscodeExtensionDirName(dirName)?.platform || "";
|
|
183
|
+
}
|
package/lib/server/server.js
CHANGED
|
@@ -659,7 +659,18 @@ const start = (options) => {
|
|
|
659
659
|
);
|
|
660
660
|
}
|
|
661
661
|
}
|
|
662
|
-
|
|
662
|
+
try {
|
|
663
|
+
cloneDir = gitClone(sourcePath, gitRef);
|
|
664
|
+
} catch (_err) {
|
|
665
|
+
console.log("Unable to clone repository. See the server log.");
|
|
666
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
667
|
+
return res.end(
|
|
668
|
+
JSON.stringify({
|
|
669
|
+
error: "Unable to clone repository",
|
|
670
|
+
details: "Cloning the repository failed. See the server log.",
|
|
671
|
+
}),
|
|
672
|
+
);
|
|
673
|
+
}
|
|
663
674
|
srcDir = cloneDir;
|
|
664
675
|
if (purlResolution?.type === "npm") {
|
|
665
676
|
const cloneRootDir = cloneDir;
|
|
@@ -672,16 +672,21 @@ async function interpolateTemplate(template, context, options = {}) {
|
|
|
672
672
|
return template;
|
|
673
673
|
}
|
|
674
674
|
const templateRegex = /\{\{\s*([^}]+)\s*}}/g;
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
675
|
+
// The template is rebuilt from its own slices in one pass, so a value taken
|
|
676
|
+
// from the BOM is never scanned for {{ }} again and never read as a
|
|
677
|
+
// String.replace pattern such as `$&`.
|
|
678
|
+
let result = "";
|
|
679
|
+
let cursor = 0;
|
|
680
|
+
for (const match of template.matchAll(templateRegex)) {
|
|
678
681
|
const [fullMatch, expr] = match;
|
|
682
|
+
let replacement = fullMatch;
|
|
679
683
|
try {
|
|
680
684
|
const expression = jsonata(expr.trim());
|
|
681
685
|
registerCdxHelpers(expression, options);
|
|
682
686
|
const value = await safeEvaluate(expression, context);
|
|
683
|
-
|
|
684
|
-
|
|
687
|
+
if (value !== undefined) {
|
|
688
|
+
replacement = String(value);
|
|
689
|
+
}
|
|
685
690
|
} catch (err) {
|
|
686
691
|
if (DEBUG_MODE) {
|
|
687
692
|
console.warn(
|
|
@@ -690,7 +695,10 @@ async function interpolateTemplate(template, context, options = {}) {
|
|
|
690
695
|
);
|
|
691
696
|
}
|
|
692
697
|
}
|
|
698
|
+
result += template.slice(cursor, match.index) + replacement;
|
|
699
|
+
cursor = match.index + fullMatch.length;
|
|
693
700
|
}
|
|
701
|
+
result += template.slice(cursor);
|
|
694
702
|
return result;
|
|
695
703
|
}
|
|
696
704
|
|
|
@@ -29,6 +29,8 @@ import {
|
|
|
29
29
|
} from "../../core/fs.js";
|
|
30
30
|
import { isMac, isWin } from "../../core/paths.js";
|
|
31
31
|
import { isProjectToolingAllowed } from "../../core/projectTooling.js";
|
|
32
|
+
import { recordToolVersion } from "../../core/toolRegistry.js";
|
|
33
|
+
import { collectProjectRubyDeclarations } from "../../ecosystems/rubyutils.js";
|
|
32
34
|
import {
|
|
33
35
|
dosaiDotnetRequirement,
|
|
34
36
|
dosaiDotnetRequirementAdvice,
|
|
@@ -45,6 +47,7 @@ import {
|
|
|
45
47
|
installSdkmanTool,
|
|
46
48
|
isNvmAvailable,
|
|
47
49
|
isRbenvAvailable,
|
|
50
|
+
isRubyVersionActive,
|
|
48
51
|
isSdkmanAvailable,
|
|
49
52
|
isSdkmanToolAvailable,
|
|
50
53
|
performBundleInstall,
|
|
@@ -65,6 +68,7 @@ import {
|
|
|
65
68
|
readSbtBuildPropertiesVersion,
|
|
66
69
|
readToolVersionEnvHint,
|
|
67
70
|
readToolVersionOverride,
|
|
71
|
+
resolveGradleVersionForJava,
|
|
68
72
|
resolvePartialVersion,
|
|
69
73
|
} from "../../inventory/jvmToolEnv.js";
|
|
70
74
|
import { resolvePluginBinary } from "../../inventory/plugins.js";
|
|
@@ -316,6 +320,27 @@ const JVM_TOOL_DEFAULT_PREFIXES = {
|
|
|
316
320
|
maven: "3.9",
|
|
317
321
|
};
|
|
318
322
|
|
|
323
|
+
/**
|
|
324
|
+
* JDK major a versioned java project type names, such as 27 for `java27`.
|
|
325
|
+
*
|
|
326
|
+
* @param {string} projectType Project type
|
|
327
|
+
* @returns {number|undefined} The major, or undefined for any other type
|
|
328
|
+
*/
|
|
329
|
+
export function javaMajorFromProjectType(projectType) {
|
|
330
|
+
if (typeof projectType !== "string" || !projectType.startsWith("java")) {
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
const major = Number.parseInt(projectType.slice(4), 10);
|
|
334
|
+
return Number.isInteger(major) && `java${major}` === projectType
|
|
335
|
+
? major
|
|
336
|
+
: undefined;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Command each provisioned sdkman candidate is invoked as. */
|
|
340
|
+
const JVM_TOOL_COMMANDS = {
|
|
341
|
+
maven: "mvn",
|
|
342
|
+
};
|
|
343
|
+
|
|
319
344
|
/** sdkman candidates cdxgen can provision for JVM builds. */
|
|
320
345
|
const JVM_BUILD_TOOLS = ["maven", "gradle", "sbt", "scala"];
|
|
321
346
|
|
|
@@ -400,10 +425,26 @@ export function prepareJvmBuildEnv(filePath, options) {
|
|
|
400
425
|
return;
|
|
401
426
|
}
|
|
402
427
|
// Resolve partial versions (e.g. maven3.9 -> 3.9.16) before touching the JDK
|
|
403
|
-
// so the compatibility matrix sees the real versions.
|
|
428
|
+
// so the compatibility matrix sees the real versions. The Gradle default
|
|
429
|
+
// install is constrained by the JDK the project declared or asked for, so a
|
|
430
|
+
// Java 27 project never gets a Gradle that cannot run on it.
|
|
431
|
+
// A versioned java type counts even without an sdkman alias: java27 has
|
|
432
|
+
// none until the broker serves a Java 27 build, but the project still asked
|
|
433
|
+
// for that JDK.
|
|
434
|
+
const explicitJavaMajor = Math.max(
|
|
435
|
+
0,
|
|
436
|
+
...(options.projectType || []).map((pt) =>
|
|
437
|
+
SDKMAN_JAVA_TOOL_ALIASES[pt]
|
|
438
|
+
? extractJavaMajor(SDKMAN_JAVA_TOOL_ALIASES[pt]) || 0
|
|
439
|
+
: javaMajorFromProjectType(pt) || 0,
|
|
440
|
+
),
|
|
441
|
+
);
|
|
442
|
+
const gradleJavaMajor =
|
|
443
|
+
Math.max(detected.javaVersionHint || 0, explicitJavaMajor || 0) ||
|
|
444
|
+
undefined;
|
|
404
445
|
const resolvedPins = [];
|
|
405
446
|
for (const pin of installPins) {
|
|
406
|
-
const resolved = resolveJvmToolPinVersion(pin);
|
|
447
|
+
const resolved = resolveJvmToolPinVersion(pin, gradleJavaMajor);
|
|
407
448
|
if (resolved) {
|
|
408
449
|
resolvedPins.push(resolved);
|
|
409
450
|
}
|
|
@@ -422,6 +463,11 @@ export function prepareJvmBuildEnv(filePath, options) {
|
|
|
422
463
|
const installed = provisionJvmTool(pin, options);
|
|
423
464
|
if (installed) {
|
|
424
465
|
setJvmToolCommandEnv(pin);
|
|
466
|
+
if (pin.version) {
|
|
467
|
+
// The resolved version is already known, so metadata.tools can carry
|
|
468
|
+
// it without spawning another probe, once the tool runs (issue #598).
|
|
469
|
+
recordToolVersion(JVM_TOOL_COMMANDS[pin.tool] || pin.tool, pin.version);
|
|
470
|
+
}
|
|
425
471
|
if (pin.source === "cli") {
|
|
426
472
|
pinnedToolSelected = true;
|
|
427
473
|
}
|
|
@@ -731,11 +777,14 @@ function recordDetectedJvmPins(installPins, wrapperVersions) {
|
|
|
731
777
|
* and to pick defaults.
|
|
732
778
|
*
|
|
733
779
|
* @param {Object} pin `{ tool, version?, source }`
|
|
780
|
+
* @param {Number|undefined} javaMajor JDK major the project declared or
|
|
781
|
+
* configured, when known. Constrains the default Gradle install so a
|
|
782
|
+
* project on a new JDK never resolves a Gradle that cannot run on it.
|
|
734
783
|
*
|
|
735
784
|
* @returns {Object|undefined} Pin with an exact version, or undefined when no
|
|
736
785
|
* version could be determined.
|
|
737
786
|
*/
|
|
738
|
-
export function resolveJvmToolPinVersion(pin) {
|
|
787
|
+
export function resolveJvmToolPinVersion(pin, javaMajor) {
|
|
739
788
|
if (pin.version && isSdkmanToolAvailable(pin.tool, pin.version)) {
|
|
740
789
|
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_RESOLVED, {
|
|
741
790
|
ecosystem: "java",
|
|
@@ -766,20 +815,30 @@ export function resolveJvmToolPinVersion(pin) {
|
|
|
766
815
|
return undefined;
|
|
767
816
|
}
|
|
768
817
|
const prefix = pin.version || JVM_TOOL_DEFAULT_PREFIXES[pin.tool] || "";
|
|
769
|
-
const resolved =
|
|
818
|
+
const resolved =
|
|
819
|
+
pin.tool === "gradle" && !pin.version
|
|
820
|
+
? resolveGradleVersionForJava(versionList, javaMajor)
|
|
821
|
+
: resolvePartialVersion(versionList, prefix);
|
|
770
822
|
if (!resolved) {
|
|
771
823
|
if (pin.version) {
|
|
772
824
|
return pin;
|
|
773
825
|
}
|
|
826
|
+
const gradleMinimum =
|
|
827
|
+
pin.tool === "gradle"
|
|
828
|
+
? minimumGradleVersionForJava(javaMajor)
|
|
829
|
+
: undefined;
|
|
830
|
+
const missingDetail = gradleMinimum
|
|
831
|
+
? `No stable gradle version at or above ${gradleMinimum}, the first that runs on Java ${javaMajor}, is listed by sdkman.`
|
|
832
|
+
: `No stable ${pin.tool} version matched the default prefix '${prefix}'.`;
|
|
774
833
|
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISSING, {
|
|
775
834
|
ecosystem: "java",
|
|
776
835
|
tool: pin.tool,
|
|
777
|
-
wanted: prefix,
|
|
836
|
+
wanted: gradleMinimum ? `>=${gradleMinimum}` : prefix,
|
|
778
837
|
source: "sdkman",
|
|
779
|
-
detail:
|
|
838
|
+
detail: missingDetail,
|
|
780
839
|
});
|
|
781
840
|
console.log(
|
|
782
|
-
|
|
841
|
+
`${missingDetail} Pass an exact version identifier via -t ${pin.tool}<version>.`,
|
|
783
842
|
);
|
|
784
843
|
return undefined;
|
|
785
844
|
}
|
|
@@ -1582,6 +1641,17 @@ export function prepareSwiftEnv(filePath, options) {
|
|
|
1582
1641
|
* @param {String} filePath Path
|
|
1583
1642
|
* @param {Object} options CLI Options
|
|
1584
1643
|
*/
|
|
1644
|
+
// The order pregen reads the Ruby to install in.
|
|
1645
|
+
const PREGEN_RUBY_SOURCE_ORDER = [
|
|
1646
|
+
".ruby-version",
|
|
1647
|
+
".tool-versions",
|
|
1648
|
+
"mise.toml",
|
|
1649
|
+
".mise.toml",
|
|
1650
|
+
".config/mise.toml",
|
|
1651
|
+
"Gemfile.lock",
|
|
1652
|
+
"Gemfile",
|
|
1653
|
+
];
|
|
1654
|
+
|
|
1585
1655
|
export function prepareRubyEnv(filePath, options) {
|
|
1586
1656
|
// Skip preparation early
|
|
1587
1657
|
if (
|
|
@@ -1627,53 +1697,39 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
1627
1697
|
readEnvironmentVariable("GEM_HOME") ||
|
|
1628
1698
|
safeMkdtempSync(join(getTmpDir(), "cdxgen-gem-home-"));
|
|
1629
1699
|
process.env.CDXGEN_GEM_HOME = cdxgenGemHome;
|
|
1630
|
-
//
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1700
|
+
// The Ruby to install is the one developers select: .ruby-version, then the
|
|
1701
|
+
// asdf and mise pins, then the Ruby the lockfile was resolved with, then the
|
|
1702
|
+
// Gemfile directive. Only an exact MRI version can be installed with rbenv.
|
|
1703
|
+
const declarations = collectProjectRubyDeclarations(filePath);
|
|
1704
|
+
for (const declaration of declarations) {
|
|
1705
|
+
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_EXPECTED, {
|
|
1706
|
+
ecosystem: "ruby",
|
|
1707
|
+
tool: declaration.engine === "ruby" ? "ruby" : declaration.engine,
|
|
1708
|
+
wanted: declaration.version || declaration.requirement,
|
|
1709
|
+
source: declaration.source,
|
|
1710
|
+
path: declaration.path,
|
|
1711
|
+
});
|
|
1712
|
+
}
|
|
1713
|
+
const installable = PREGEN_RUBY_SOURCE_ORDER.map((source) =>
|
|
1714
|
+
declarations.find(
|
|
1715
|
+
(declaration) => declaration.source === source && declaration.version,
|
|
1716
|
+
),
|
|
1717
|
+
).find(Boolean);
|
|
1718
|
+
if (installable && installable.engine !== "ruby") {
|
|
1719
|
+
console.log(
|
|
1720
|
+
`This project selects ${installable.engine} ${installable.version} (${installable.source}). cdxgen installs only MRI Ruby with rbenv; build the project with ${installable.engine} first and then invoke cdxgen with the arguments "--lifecycle pre-build".`,
|
|
1721
|
+
);
|
|
1722
|
+
} else if (installable?.prerelease) {
|
|
1723
|
+
console.log(
|
|
1724
|
+
`This project was locked with a pre-release of Ruby ${installable.version}, whose tag the lockfile does not record. Install it before invoking cdxgen.`,
|
|
1725
|
+
);
|
|
1726
|
+
} else if (installable) {
|
|
1727
|
+
rubyVersionNeeded = installable.version;
|
|
1728
|
+
}
|
|
1729
|
+
if (safeExistsSync(join(filePath, "Gemfile.lock"))) {
|
|
1648
1730
|
const gemlockData = readFileSync(join(filePath, "Gemfile.lock"), {
|
|
1649
1731
|
encoding: "utf-8",
|
|
1650
1732
|
});
|
|
1651
|
-
let rubyVersionMarker = false;
|
|
1652
|
-
for (let l of gemlockData.split("\n")) {
|
|
1653
|
-
l = l.replaceAll("\r", "").trim();
|
|
1654
|
-
if (l.includes("RUBY VERSION")) {
|
|
1655
|
-
rubyVersionMarker = true;
|
|
1656
|
-
}
|
|
1657
|
-
if (rubyVersionMarker && l.includes("ruby ")) {
|
|
1658
|
-
const possibleVersion = l
|
|
1659
|
-
.split("ruby ")
|
|
1660
|
-
.pop()
|
|
1661
|
-
.split("p")[0]
|
|
1662
|
-
.split("d")[0];
|
|
1663
|
-
if (/^\d+/.test(possibleVersion)) {
|
|
1664
|
-
rubyVersionNeeded = possibleVersion;
|
|
1665
|
-
break;
|
|
1666
|
-
}
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
if (rubyVersionNeeded) {
|
|
1670
|
-
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_EXPECTED, {
|
|
1671
|
-
ecosystem: "ruby",
|
|
1672
|
-
tool: "ruby",
|
|
1673
|
-
wanted: rubyVersionNeeded,
|
|
1674
|
-
source: "Gemfile.lock",
|
|
1675
|
-
});
|
|
1676
|
-
}
|
|
1677
1733
|
const bundlerVersion = parseGemfileLockBundlerVersion(gemlockData);
|
|
1678
1734
|
if (bundlerVersion) {
|
|
1679
1735
|
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_EXPECTED, {
|
|
@@ -1683,7 +1739,8 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
1683
1739
|
source: "Gemfile.lock",
|
|
1684
1740
|
});
|
|
1685
1741
|
}
|
|
1686
|
-
}
|
|
1742
|
+
}
|
|
1743
|
+
if (!installable) {
|
|
1687
1744
|
// Is the user invoking with a ruby with custom version type. Eg: -t ruby2.5.4
|
|
1688
1745
|
let projectTypes = options.projectType;
|
|
1689
1746
|
if (
|
|
@@ -1716,13 +1773,13 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
1716
1773
|
const existingRuby = collectRubyInfo(filePath);
|
|
1717
1774
|
if (
|
|
1718
1775
|
rubyVersionNeeded &&
|
|
1719
|
-
existingRuby
|
|
1776
|
+
isRubyVersionActive(existingRuby, rubyVersionNeeded)
|
|
1720
1777
|
) {
|
|
1721
1778
|
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_RESOLVED, {
|
|
1722
1779
|
ecosystem: "ruby",
|
|
1723
1780
|
tool: "ruby",
|
|
1724
1781
|
wanted: rubyVersionNeeded,
|
|
1725
|
-
found: existingRuby.version,
|
|
1782
|
+
found: existingRuby.description || existingRuby.version,
|
|
1726
1783
|
source: "env",
|
|
1727
1784
|
detail: `The active interpreter is the required Ruby ${rubyVersionNeeded}.`,
|
|
1728
1785
|
});
|
|
@@ -1754,15 +1811,15 @@ export function prepareRubyEnv(filePath, options) {
|
|
|
1754
1811
|
});
|
|
1755
1812
|
} else if (
|
|
1756
1813
|
rubyVersionNeeded &&
|
|
1757
|
-
!existingRuby
|
|
1814
|
+
!isRubyVersionActive(existingRuby, rubyVersionNeeded)
|
|
1758
1815
|
) {
|
|
1759
1816
|
recordLedgerEvent(LEDGER_EVENT_KINDS.TOOL_MISMATCH, {
|
|
1760
1817
|
ecosystem: "ruby",
|
|
1761
1818
|
tool: "ruby",
|
|
1762
1819
|
wanted: rubyVersionNeeded,
|
|
1763
|
-
found: existingRuby.version,
|
|
1820
|
+
found: existingRuby.description || existingRuby.version,
|
|
1764
1821
|
source: "env",
|
|
1765
|
-
detail: `Ruby ${rubyVersionNeeded} is required and the active interpreter reports ${existingRuby.version}.`,
|
|
1822
|
+
detail: `Ruby ${rubyVersionNeeded} is required and the active interpreter reports ${existingRuby.description || existingRuby.version}.`,
|
|
1766
1823
|
});
|
|
1767
1824
|
}
|
|
1768
1825
|
if (rubyVersionNeeded && !rbenvPresent) {
|
|
@@ -263,10 +263,12 @@ const getSpdxExportSchemaValidator = () => {
|
|
|
263
263
|
* Validate the generated bom using jsonschema
|
|
264
264
|
*
|
|
265
265
|
* @param {object} bomJson content
|
|
266
|
+
* @param {object} [options] Validation options
|
|
267
|
+
* @param {boolean} [options.deep] Run the deep validators after the schema check. Defaults to true.
|
|
266
268
|
*
|
|
267
269
|
* @returns {Promise<Boolean>} true if the BOM is valid. false otherwise.
|
|
268
270
|
*/
|
|
269
|
-
export const validateBom = async (bomJson) => {
|
|
271
|
+
export const validateBom = async (bomJson, options = {}) => {
|
|
270
272
|
if (!bomJson) {
|
|
271
273
|
return true;
|
|
272
274
|
}
|
|
@@ -292,6 +294,9 @@ export const validateBom = async (bomJson) => {
|
|
|
292
294
|
console.log(validate.errors);
|
|
293
295
|
return false;
|
|
294
296
|
}
|
|
297
|
+
if (options.deep === false) {
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
295
300
|
// Deep validation tests
|
|
296
301
|
return (
|
|
297
302
|
validateMetadata(bomJson) &&
|