@cdxgen/cdxgen 13.1.0 → 13.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/bin/cdxgen.js +16 -3
- package/bin/evinse.js +2 -0
- package/data/crypto-oid.json +88 -12
- package/data/remediations.json +23 -0
- package/data/rules/ai-bom.yaml +78 -0
- package/data/rules/cargo-rust.yaml +132 -0
- package/data/rules/mcp-servers.yaml +122 -0
- package/lib/audit/targets.js +6 -1
- package/lib/cli/bomAssembly.js +12 -0
- package/lib/cli/index.js +192 -1
- package/lib/cli/jsBom.js +59 -2
- package/lib/cli/jvmBom.js +22 -0
- package/lib/cli/managedBom.js +152 -0
- package/lib/cli/nativeBom.js +798 -1
- package/lib/core/activity.js +120 -2
- package/lib/core/env.js +39 -1
- package/lib/core/userAgent.js +151 -0
- package/lib/ecosystems/cargoCache.js +364 -0
- package/lib/ecosystems/caxa.js +13 -5
- package/lib/ecosystems/cppEvidence.js +40 -2
- package/lib/ecosystems/ecosystems.js +241 -91
- package/lib/ecosystems/golem.js +11 -1
- package/lib/ecosystems/gradleutils.js +143 -2
- package/lib/ecosystems/kosi.js +1256 -0
- package/lib/ecosystems/parsers-crystal.js +179 -0
- package/lib/ecosystems/parsers-dotnet.js +69 -39
- package/lib/ecosystems/parsers-elm.js +816 -0
- package/lib/ecosystems/parsers-gleam.js +1 -1
- package/lib/ecosystems/parsers-julia.js +268 -0
- package/lib/ecosystems/parsers-lua.js +218 -0
- package/lib/ecosystems/parsers-misc.js +172 -0
- package/lib/ecosystems/parsers-nim.js +221 -0
- package/lib/ecosystems/parsers-opam.js +180 -0
- package/lib/ecosystems/parsers-perl.js +277 -0
- package/lib/ecosystems/parsers-python.js +1 -1
- package/lib/ecosystems/parsers-r.js +148 -0
- package/lib/ecosystems/parsers-rebar.js +411 -0
- package/lib/ecosystems/parsers-rust.js +932 -17
- package/lib/ecosystems/parsers-spack.js +204 -0
- package/lib/ecosystems/parsers-stack.js +264 -0
- package/lib/ecosystems/parsers-terraform.js +194 -0
- package/lib/ecosystems/rusi.js +126 -6
- package/lib/evinser/evinser.js +111 -1
- package/lib/helpers/agenticToolutils.js +925 -0
- package/lib/inventory/agentFormulationParser.js +57 -4
- package/lib/inventory/aiCollector.js +504 -10
- package/lib/inventory/aiInventory.js +12 -0
- package/lib/inventory/analyzer.js +488 -30
- package/lib/inventory/astWorker.js +6 -0
- package/lib/inventory/auditCategories.js +8 -0
- package/lib/inventory/cbomutils.js +30 -0
- package/lib/inventory/cdxrs.js +7 -2
- package/lib/inventory/communityAiConfigParser.js +215 -2
- package/lib/inventory/dosai.js +77 -0
- package/lib/inventory/envcontext.js +9 -6
- package/lib/inventory/evidenceUtils.js +13 -0
- package/lib/inventory/fetchBatch.js +74 -2
- package/lib/inventory/fetchRate.js +74 -11
- package/lib/inventory/forgeEnricher.js +3 -2
- package/lib/inventory/jvmToolEnv.js +6 -5
- package/lib/inventory/mcpConfigParser.js +166 -2
- package/lib/inventory/mcpDiscovery.js +11 -0
- package/lib/inventory/mcpServerManifest.js +389 -0
- package/lib/inventory/mcpbBundle.js +419 -0
- package/lib/inventory/plugins.js +3 -0
- package/lib/inventory/protobom.js +54 -88
- package/lib/inventory/protobomLoader.js +4 -0
- package/lib/inventory/rustFormulationParser.js +1 -1
- package/lib/inventory/source.js +10 -4
- package/lib/inventory/stylePackageRefs.js +41 -0
- package/lib/inventory/svelteUtils.js +524 -0
- package/lib/parsers/huggingfaceUtils.js +15 -7
- package/lib/parsers/onnx.js +294 -0
- package/lib/parsers/safetensors.js +114 -0
- package/lib/stages/postgen/introspection/markers.js +29 -6
- package/lib/stages/postgen/postgen.js +169 -1
- package/lib/third-party/arborist/snapshots/calc-dep-flags.test.cjs +462 -0
- package/package.json +25 -25
- package/types/lib/audit/targets.d.ts.map +1 -1
- package/types/lib/cli/bomAssembly.d.ts.map +1 -1
- package/types/lib/cli/index.d.ts +2 -2
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/cli/jsBom.d.ts +23 -0
- 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 +33 -0
- package/types/lib/cli/managedBom.d.ts.map +1 -1
- package/types/lib/cli/nativeBom.d.ts +88 -8
- 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 +21 -0
- package/types/lib/core/env.d.ts.map +1 -1
- package/types/lib/core/userAgent.d.ts +56 -0
- package/types/lib/core/userAgent.d.ts.map +1 -0
- package/types/lib/ecosystems/cargoCache.d.ts +75 -0
- package/types/lib/ecosystems/cargoCache.d.ts.map +1 -0
- package/types/lib/ecosystems/caxa.d.ts.map +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts +1 -1
- package/types/lib/ecosystems/cppEvidence.d.ts.map +1 -1
- package/types/lib/ecosystems/ecosystems.d.ts +15 -1
- package/types/lib/ecosystems/ecosystems.d.ts.map +1 -1
- package/types/lib/ecosystems/gradleutils.d.ts +20 -0
- package/types/lib/ecosystems/gradleutils.d.ts.map +1 -1
- package/types/lib/ecosystems/kosi.d.ts +73 -0
- package/types/lib/ecosystems/kosi.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts +27 -0
- package/types/lib/ecosystems/parsers-crystal.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts +19 -0
- package/types/lib/ecosystems/parsers-dotnet.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-elm.d.ts +79 -0
- package/types/lib/ecosystems/parsers-elm.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-julia.d.ts +57 -0
- package/types/lib/ecosystems/parsers-julia.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-lua.d.ts +50 -0
- package/types/lib/ecosystems/parsers-lua.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-misc.d.ts +23 -0
- package/types/lib/ecosystems/parsers-misc.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-nim.d.ts +43 -0
- package/types/lib/ecosystems/parsers-nim.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-opam.d.ts +31 -0
- package/types/lib/ecosystems/parsers-opam.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-perl.d.ts +36 -0
- package/types/lib/ecosystems/parsers-perl.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-r.d.ts +35 -0
- package/types/lib/ecosystems/parsers-r.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts +42 -0
- package/types/lib/ecosystems/parsers-rebar.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-rust.d.ts +199 -0
- package/types/lib/ecosystems/parsers-rust.d.ts.map +1 -1
- package/types/lib/ecosystems/parsers-spack.d.ts +33 -0
- package/types/lib/ecosystems/parsers-spack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-stack.d.ts +35 -0
- package/types/lib/ecosystems/parsers-stack.d.ts.map +1 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts +34 -0
- package/types/lib/ecosystems/parsers-terraform.d.ts.map +1 -0
- package/types/lib/ecosystems/pythonutils.d.ts +1 -1
- package/types/lib/ecosystems/rusi.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts +29 -0
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/agenticToolutils.d.ts +87 -0
- package/types/lib/helpers/agenticToolutils.d.ts.map +1 -0
- package/types/lib/inventory/agentFormulationParser.d.ts.map +1 -1
- package/types/lib/inventory/aiCollector.d.ts.map +1 -1
- package/types/lib/inventory/aiInventory.d.ts.map +1 -1
- package/types/lib/inventory/analyzer.d.ts.map +1 -1
- package/types/lib/inventory/auditCategories.d.ts +4 -0
- package/types/lib/inventory/auditCategories.d.ts.map +1 -1
- package/types/lib/inventory/cbomutils.d.ts.map +1 -1
- package/types/lib/inventory/communityAiConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/dosai.d.ts +23 -0
- package/types/lib/inventory/dosai.d.ts.map +1 -1
- package/types/lib/inventory/envcontext.d.ts.map +1 -1
- package/types/lib/inventory/evidenceUtils.d.ts.map +1 -1
- package/types/lib/inventory/fetchBatch.d.ts +34 -0
- package/types/lib/inventory/fetchBatch.d.ts.map +1 -1
- package/types/lib/inventory/fetchRate.d.ts +9 -6
- package/types/lib/inventory/fetchRate.d.ts.map +1 -1
- package/types/lib/inventory/forgeEnricher.d.ts.map +1 -1
- package/types/lib/inventory/jvmToolEnv.d.ts +5 -4
- package/types/lib/inventory/jvmToolEnv.d.ts.map +1 -1
- package/types/lib/inventory/mcpConfigParser.d.ts.map +1 -1
- package/types/lib/inventory/mcpDiscovery.d.ts.map +1 -1
- package/types/lib/inventory/mcpServerManifest.d.ts +19 -0
- package/types/lib/inventory/mcpServerManifest.d.ts.map +1 -0
- package/types/lib/inventory/mcpbBundle.d.ts +19 -0
- package/types/lib/inventory/mcpbBundle.d.ts.map +1 -0
- package/types/lib/inventory/plugins.d.ts.map +1 -1
- package/types/lib/inventory/protobom.d.ts +18 -13
- package/types/lib/inventory/protobom.d.ts.map +1 -1
- package/types/lib/inventory/protobomLoader.d.ts.map +1 -1
- package/types/lib/inventory/source.d.ts.map +1 -1
- package/types/lib/inventory/stylePackageRefs.d.ts +17 -0
- package/types/lib/inventory/stylePackageRefs.d.ts.map +1 -0
- package/types/lib/inventory/svelteUtils.d.ts +124 -0
- package/types/lib/inventory/svelteUtils.d.ts.map +1 -0
- package/types/lib/parsers/huggingfaceUtils.d.ts +8 -1
- package/types/lib/parsers/huggingfaceUtils.d.ts.map +1 -1
- package/types/lib/parsers/onnx.d.ts +28 -0
- package/types/lib/parsers/onnx.d.ts.map +1 -0
- package/types/lib/parsers/safetensors.d.ts +22 -0
- package/types/lib/parsers/safetensors.d.ts.map +1 -0
- package/types/lib/stages/postgen/introspection/markers.d.ts +3 -2
- package/types/lib/stages/postgen/introspection/markers.d.ts.map +1 -1
- package/types/lib/stages/postgen/postgen.d.ts +33 -0
- package/types/lib/stages/postgen/postgen.d.ts.map +1 -1
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import * as toml from "smol-toml";
|
|
4
|
+
|
|
5
|
+
import { tryBuildPurl } from "../inventory/purl.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Julia project parser.
|
|
9
|
+
*
|
|
10
|
+
* Two files are consulted:
|
|
11
|
+
* - `Project.toml` — the manifest holding the project name, UUID, version,
|
|
12
|
+
* the names (and UUIDs) of its direct dependencies, and version
|
|
13
|
+
* compatibility constraints.
|
|
14
|
+
* - `Manifest.toml` — the lock file written by Pkg on resolve, pinning every
|
|
15
|
+
* package in the environment with its UUID, version, content hash, and the
|
|
16
|
+
* names of the packages it depends on.
|
|
17
|
+
*
|
|
18
|
+
* Packages are identified with the registered `julia` purl type, which carries
|
|
19
|
+
* the package UUID as a required qualifier (`pkg:julia/JSON@0.21.4?uuid=…`).
|
|
20
|
+
* The UUID is what Julia itself uses to identify packages, so keeping it in
|
|
21
|
+
* the purl lets consumers match components across differently named mirrors.
|
|
22
|
+
*
|
|
23
|
+
* Julia standard libraries ship with the language itself and carry no version
|
|
24
|
+
* in the manifest; they are emitted without a version and marked as standard
|
|
25
|
+
* libraries so they can be told apart from registry packages.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Parse a Julia environment from its `Project.toml` and optional
|
|
30
|
+
* `Manifest.toml`.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} [projectTomlFile] Path to `Project.toml`, if present
|
|
33
|
+
* @param {string} [manifestTomlFile] Path to `Manifest.toml`, if present
|
|
34
|
+
* @returns {{ pkgList: object[], dependencies: object[], parentComponent: object, rootInputs?: string[] }}
|
|
35
|
+
*/
|
|
36
|
+
export function parseJuliaProject(projectTomlFile, manifestTomlFile) {
|
|
37
|
+
const project = projectTomlFile ? readTomlFile(projectTomlFile) : undefined;
|
|
38
|
+
|
|
39
|
+
const parentComponent = {};
|
|
40
|
+
const directNames = new Set(
|
|
41
|
+
project?.deps && typeof project.deps === "object"
|
|
42
|
+
? Object.keys(project.deps)
|
|
43
|
+
: [],
|
|
44
|
+
);
|
|
45
|
+
if (project?.name) {
|
|
46
|
+
const properties = [{ name: "internal:SrcFile", value: projectTomlFile }];
|
|
47
|
+
if (typeof project.uuid === "string" && project.uuid) {
|
|
48
|
+
properties.push({ name: "cdx:julia:uuid", value: project.uuid });
|
|
49
|
+
}
|
|
50
|
+
parentComponent.type = "application";
|
|
51
|
+
parentComponent.name = project.name;
|
|
52
|
+
if (typeof project.version === "string" && project.version) {
|
|
53
|
+
parentComponent.version = project.version;
|
|
54
|
+
}
|
|
55
|
+
parentComponent.description = `Julia project: ${project.name}`;
|
|
56
|
+
parentComponent.properties = properties;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
let manifest;
|
|
60
|
+
if (manifestTomlFile) {
|
|
61
|
+
manifest = readTomlFile(manifestTomlFile);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Manifest packages live under `[[deps.<Name>]]` array-of-table entries,
|
|
65
|
+
// which smol-toml surfaces as a plain object keyed by package name.
|
|
66
|
+
const manifestDeps = manifest?.deps;
|
|
67
|
+
if (!manifestDeps || typeof manifestDeps !== "object") {
|
|
68
|
+
// Lockless fallback: emit the declared direct dependencies without
|
|
69
|
+
// resolved versions, using the UUIDs from Project.toml.
|
|
70
|
+
const pkgList = [...directNames].map((name) =>
|
|
71
|
+
juliaPackage(name, undefined, project?.deps?.[name], {
|
|
72
|
+
direct: true,
|
|
73
|
+
stdlib: JULIA_STDLIBS.has(name),
|
|
74
|
+
srcFile: projectTomlFile,
|
|
75
|
+
}),
|
|
76
|
+
);
|
|
77
|
+
return {
|
|
78
|
+
pkgList,
|
|
79
|
+
dependencies: [],
|
|
80
|
+
parentComponent,
|
|
81
|
+
rootInputs: pkgList.map((p) => p["bom-ref"]),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const pkgList = [];
|
|
86
|
+
const edges = [];
|
|
87
|
+
const refByName = new Map();
|
|
88
|
+
for (const [name, raw] of Object.entries(manifestDeps)) {
|
|
89
|
+
// `[[deps.<Name>]]` is an array of tables; a name repeats only when two
|
|
90
|
+
// sources of the same package are pinned, and the first entry is the one
|
|
91
|
+
// the environment resolves first.
|
|
92
|
+
const entry = Array.isArray(raw) ? raw[0] : raw;
|
|
93
|
+
const uuid =
|
|
94
|
+
typeof entry?.uuid === "string" && entry.uuid ? entry.uuid : undefined;
|
|
95
|
+
const registryVersion =
|
|
96
|
+
typeof entry?.version === "string" && entry.version
|
|
97
|
+
? entry.version
|
|
98
|
+
: undefined;
|
|
99
|
+
// Entries without a registry version are pinned by content hash (git or
|
|
100
|
+
// local path sources); the hash is the most precise version available.
|
|
101
|
+
const gitTreeSha1 =
|
|
102
|
+
typeof entry?.["git-tree-sha1"] === "string" && entry["git-tree-sha1"]
|
|
103
|
+
? entry["git-tree-sha1"]
|
|
104
|
+
: undefined;
|
|
105
|
+
const version = registryVersion || gitTreeSha1;
|
|
106
|
+
const pkg = juliaPackage(name, version, uuid, {
|
|
107
|
+
direct: directNames.has(name),
|
|
108
|
+
stdlib: !registryVersion && JULIA_STDLIBS.has(name),
|
|
109
|
+
srcFile: manifestTomlFile,
|
|
110
|
+
});
|
|
111
|
+
pkgList.push(pkg);
|
|
112
|
+
refByName.set(name, pkg["bom-ref"]);
|
|
113
|
+
}
|
|
114
|
+
for (const [name, raw] of Object.entries(manifestDeps)) {
|
|
115
|
+
const ref = refByName.get(name);
|
|
116
|
+
if (!ref) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const entry = Array.isArray(raw) ? raw[0] : raw;
|
|
120
|
+
const dependsOn = (Array.isArray(entry?.deps) ? entry.deps : [])
|
|
121
|
+
.map((depName) => refByName.get(depName))
|
|
122
|
+
.filter(Boolean);
|
|
123
|
+
edges.push({ ref, dependsOn: [...new Set(dependsOn)].sort() });
|
|
124
|
+
}
|
|
125
|
+
const rootInputs = [...directNames]
|
|
126
|
+
.map((name) => refByName.get(name))
|
|
127
|
+
.filter(Boolean);
|
|
128
|
+
return { pkgList, dependencies: edges, parentComponent, rootInputs };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Tell whether a `Project.toml` declares a Julia environment.
|
|
133
|
+
*
|
|
134
|
+
* Cloud Native Buildpacks and Gleam use the same file names, so the caller
|
|
135
|
+
* needs a content check before dispatching. A Julia project always carries a
|
|
136
|
+
* package `uuid`, a `[deps]` table, or a `[compat]` table.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} filePath Path to the candidate `Project.toml`
|
|
139
|
+
* @returns {boolean} true when the file is a Julia project
|
|
140
|
+
*/
|
|
141
|
+
export function isJuliaProjectFile(filePath) {
|
|
142
|
+
const project = readTomlFile(filePath);
|
|
143
|
+
return Boolean(
|
|
144
|
+
project &&
|
|
145
|
+
(typeof project.uuid === "string" ||
|
|
146
|
+
(project.deps && typeof project.deps === "object") ||
|
|
147
|
+
(project.compat && typeof project.compat === "object")),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Tell whether a `Manifest.toml` is a Julia lock file.
|
|
153
|
+
*
|
|
154
|
+
* Gleam writes a lowercase `manifest.toml` that case-insensitive filesystems
|
|
155
|
+
* surface under the same glob; a Julia manifest is recognised by its
|
|
156
|
+
* `manifest_format` marker or its `[[deps.<Name>]]` tables.
|
|
157
|
+
*
|
|
158
|
+
* @param {string} filePath Path to the candidate `Manifest.toml`
|
|
159
|
+
* @returns {boolean} true when the file is a Julia manifest
|
|
160
|
+
*/
|
|
161
|
+
export function isJuliaManifestFile(filePath) {
|
|
162
|
+
const manifest = readTomlFile(filePath);
|
|
163
|
+
return Boolean(
|
|
164
|
+
manifest &&
|
|
165
|
+
(typeof manifest.manifest_format === "string" ||
|
|
166
|
+
(manifest.deps && typeof manifest.deps === "object")),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Build a component-like package record for a Julia dependency.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} name Package name as Julia writes it
|
|
174
|
+
* @param {string|undefined} version Resolved version, if known
|
|
175
|
+
* @param {string|undefined} uuid Package UUID, if known
|
|
176
|
+
* @param {object} opts Extra context (`direct`, `stdlib`, `srcFile`)
|
|
177
|
+
* @returns {object} Package record
|
|
178
|
+
*/
|
|
179
|
+
function juliaPackage(name, version, uuid, opts) {
|
|
180
|
+
// The julia purl type requires the uuid qualifier; when it is unknown the
|
|
181
|
+
// purl is dropped entirely rather than emitted without its identity anchor.
|
|
182
|
+
const purl =
|
|
183
|
+
name && uuid
|
|
184
|
+
? tryBuildPurl({
|
|
185
|
+
type: "julia",
|
|
186
|
+
name,
|
|
187
|
+
version: version || undefined,
|
|
188
|
+
qualifiers: { uuid },
|
|
189
|
+
})
|
|
190
|
+
: null;
|
|
191
|
+
const properties = [
|
|
192
|
+
{ name: "internal:SrcFile", value: opts.srcFile },
|
|
193
|
+
{
|
|
194
|
+
name: "cdx:julia:dependency",
|
|
195
|
+
value: opts.direct ? "direct" : "transitive",
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
if (uuid) {
|
|
199
|
+
properties.push({ name: "cdx:julia:uuid", value: uuid });
|
|
200
|
+
}
|
|
201
|
+
if (opts.stdlib) {
|
|
202
|
+
properties.push({ name: "cdx:julia:stdlib", value: "true" });
|
|
203
|
+
}
|
|
204
|
+
const pkg = {
|
|
205
|
+
name,
|
|
206
|
+
...(version ? { version } : {}),
|
|
207
|
+
type: "library",
|
|
208
|
+
scope: "required",
|
|
209
|
+
properties,
|
|
210
|
+
};
|
|
211
|
+
if (purl) {
|
|
212
|
+
pkg.purl = purl;
|
|
213
|
+
pkg["bom-ref"] = decodeURIComponent(purl);
|
|
214
|
+
} else {
|
|
215
|
+
pkg["bom-ref"] = `library:${name}:${version || ""}`;
|
|
216
|
+
}
|
|
217
|
+
return pkg;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Julia standard libraries ship with the language itself. They carry no
|
|
222
|
+
* version in the manifest, so they are recognised by name against this list.
|
|
223
|
+
*
|
|
224
|
+
* @type {Set<string>}
|
|
225
|
+
*/
|
|
226
|
+
const JULIA_STDLIBS = new Set([
|
|
227
|
+
"Base",
|
|
228
|
+
"CRC32c",
|
|
229
|
+
"Dates",
|
|
230
|
+
"DelimitedFiles",
|
|
231
|
+
"Distributed",
|
|
232
|
+
"FileWatching",
|
|
233
|
+
"Future",
|
|
234
|
+
"LibGit2",
|
|
235
|
+
"Libdl",
|
|
236
|
+
"LinearAlgebra",
|
|
237
|
+
"Logging",
|
|
238
|
+
"Markdown",
|
|
239
|
+
"Mmap",
|
|
240
|
+
"Printf",
|
|
241
|
+
"Profile",
|
|
242
|
+
"Random",
|
|
243
|
+
"REPL",
|
|
244
|
+
"Serialization",
|
|
245
|
+
"SharedArrays",
|
|
246
|
+
"Sockets",
|
|
247
|
+
"SparseArrays",
|
|
248
|
+
"Statistics",
|
|
249
|
+
"TOML",
|
|
250
|
+
"Test",
|
|
251
|
+
"UUIDs",
|
|
252
|
+
"Unicode",
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Read and parse a TOML file, warning instead of throwing on invalid input.
|
|
257
|
+
*
|
|
258
|
+
* @param {string} filePath File to read
|
|
259
|
+
* @returns {object|undefined} Parsed value
|
|
260
|
+
*/
|
|
261
|
+
function readTomlFile(filePath) {
|
|
262
|
+
try {
|
|
263
|
+
return toml.parse(readFileSync(filePath, "utf-8"));
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.warn(`Failed to parse ${filePath}: ${error.message}`);
|
|
266
|
+
return undefined;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { tryBuildPurl } from "../inventory/purl.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lua parser for rockspecs and `luarocks.lock`.
|
|
7
|
+
*
|
|
8
|
+
* A rockspec is a Lua source file whose top level is a sequence of
|
|
9
|
+
* assignments describing one package: its name, version, source, and a
|
|
10
|
+
* `dependencies` table of constraint strings (`"lua >= 5.1"`). LuaRocks 3
|
|
11
|
+
* can also persist the resolved set to `luarocks.lock`. Both are read with a
|
|
12
|
+
* small line scanner over the shapes these files actually use, rather than a
|
|
13
|
+
* Lua interpreter.
|
|
14
|
+
*
|
|
15
|
+
* Rocks are identified with the registered `luarocks` purl type
|
|
16
|
+
* (`pkg:luarocks/<name>@<version>`). Lua versions carry a rock revision
|
|
17
|
+
* suffix (`5.4.6-1`); the full rock version is kept verbatim because the
|
|
18
|
+
* revision distinguishes distinct builds.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Parse a rockspec manifest.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} rockspecFile Path to the `.rockspec` file
|
|
25
|
+
* @returns {{ pkgList: object[], parentComponent: object }}
|
|
26
|
+
*/
|
|
27
|
+
export function parseRockspecFile(rockspecFile) {
|
|
28
|
+
const fields = readLuaAssignments(rockspecFile);
|
|
29
|
+
const name = fields.get("package");
|
|
30
|
+
const version = fields.get("version");
|
|
31
|
+
const declaredDeps = fields.get("dependencies");
|
|
32
|
+
|
|
33
|
+
const parentComponent = {};
|
|
34
|
+
if (name) {
|
|
35
|
+
parentComponent.type = "application";
|
|
36
|
+
parentComponent.name = name;
|
|
37
|
+
if (version) {
|
|
38
|
+
parentComponent.version = version;
|
|
39
|
+
}
|
|
40
|
+
parentComponent.description = `Lua rockspec: ${name}`;
|
|
41
|
+
parentComponent.properties = [
|
|
42
|
+
{ name: "internal:SrcFile", value: rockspecFile },
|
|
43
|
+
];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const directNames = new Set(
|
|
47
|
+
Array.isArray(declaredDeps) ? declaredDeps.map((dep) => dep.name) : [],
|
|
48
|
+
);
|
|
49
|
+
const pkgList = [...directNames].map((depName) =>
|
|
50
|
+
luaPackage(depName, undefined, { direct: true, srcFile: rockspecFile }),
|
|
51
|
+
);
|
|
52
|
+
return { pkgList, parentComponent };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parse a `luarocks.lock` file.
|
|
57
|
+
*
|
|
58
|
+
* @param {string} lockFile Path to `luarocks.lock`
|
|
59
|
+
* @param {string} [rockspecFile] Path to the rockspec, to mark direct deps
|
|
60
|
+
* @returns {{ pkgList: object[], parentComponent: object }}
|
|
61
|
+
*/
|
|
62
|
+
export function parseLuaRocksLock(lockFile, rockspecFile) {
|
|
63
|
+
const fields = readLuaAssignments(lockFile);
|
|
64
|
+
const locked = fields.get("dependencies");
|
|
65
|
+
if (!Array.isArray(locked)) {
|
|
66
|
+
return { pkgList: [], parentComponent: {} };
|
|
67
|
+
}
|
|
68
|
+
const directNames = new Set();
|
|
69
|
+
if (rockspecFile) {
|
|
70
|
+
const declared = readLuaAssignments(rockspecFile).get("dependencies");
|
|
71
|
+
if (Array.isArray(declared)) {
|
|
72
|
+
for (const dep of declared) {
|
|
73
|
+
if (typeof dep?.name === "string") {
|
|
74
|
+
directNames.add(dep.name);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const pkgList = locked
|
|
80
|
+
.filter((dep) => typeof dep?.name === "string" && dep.name)
|
|
81
|
+
.map((dep) =>
|
|
82
|
+
luaPackage(dep.name, dep.version, {
|
|
83
|
+
// Without a rockspec there is no declared set to compare against, so
|
|
84
|
+
// the whole closure is reported direct rather than guessing.
|
|
85
|
+
direct: rockspecFile ? directNames.has(dep.name) : true,
|
|
86
|
+
srcFile: lockFile,
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
return { pkgList, parentComponent: {} };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Build a component-like package record for a Lua rock.
|
|
94
|
+
*
|
|
95
|
+
* @param {string} name Rock name
|
|
96
|
+
* @param {string|undefined} version Rock version including revision suffix
|
|
97
|
+
* @param {object} opts Extra context (`direct`, `srcFile`)
|
|
98
|
+
* @returns {object} Package record
|
|
99
|
+
*/
|
|
100
|
+
function luaPackage(name, version, opts) {
|
|
101
|
+
const purl = tryBuildPurl({
|
|
102
|
+
type: "luarocks",
|
|
103
|
+
name,
|
|
104
|
+
version: version || undefined,
|
|
105
|
+
});
|
|
106
|
+
const properties = [
|
|
107
|
+
{ name: "internal:SrcFile", value: opts.srcFile },
|
|
108
|
+
{
|
|
109
|
+
name: "cdx:luarocks:dependency",
|
|
110
|
+
value: opts.direct ? "direct" : "transitive",
|
|
111
|
+
},
|
|
112
|
+
];
|
|
113
|
+
const pkg = {
|
|
114
|
+
name,
|
|
115
|
+
...(version ? { version } : {}),
|
|
116
|
+
type: "library",
|
|
117
|
+
scope: "required",
|
|
118
|
+
properties,
|
|
119
|
+
};
|
|
120
|
+
if (purl) {
|
|
121
|
+
pkg.purl = purl;
|
|
122
|
+
pkg["bom-ref"] = decodeURIComponent(purl);
|
|
123
|
+
} else {
|
|
124
|
+
pkg["bom-ref"] = `library:${name}:${version || ""}`;
|
|
125
|
+
}
|
|
126
|
+
return pkg;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Read the top-level assignments of a Lua configuration file.
|
|
131
|
+
*
|
|
132
|
+
* The files cdxgen consumes use a constrained surface: `key = value` lines
|
|
133
|
+
* where the value is a quoted string, a table of strings or
|
|
134
|
+
* `["key"] = "value"` entries, or a short list expression. Values are
|
|
135
|
+
* normalised to strings, arrays of `{name, constraint}` records, or arrays
|
|
136
|
+
* of `{key, value}` records for mixed tables.
|
|
137
|
+
*
|
|
138
|
+
* @param {string} filePath File to read
|
|
139
|
+
* @returns {Map<string, *>} Parsed top-level fields
|
|
140
|
+
*/
|
|
141
|
+
export function readLuaAssignments(filePath) {
|
|
142
|
+
const fields = new Map();
|
|
143
|
+
let text;
|
|
144
|
+
try {
|
|
145
|
+
text = readFileSync(filePath, "utf-8");
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.warn(`Failed to read ${filePath}: ${error.message}`);
|
|
148
|
+
return fields;
|
|
149
|
+
}
|
|
150
|
+
let currentTable;
|
|
151
|
+
let depth = 0;
|
|
152
|
+
for (const rawLine of text.split("\n")) {
|
|
153
|
+
const line = rawLine.trim();
|
|
154
|
+
if (!line || line.startsWith("--") || line.startsWith("#")) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (depth === 0) {
|
|
158
|
+
const assignment = line.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/u);
|
|
159
|
+
if (!assignment) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
const [, key, rest] = assignment;
|
|
163
|
+
const stringValue = rest.match(/^"([^"]*)"/u);
|
|
164
|
+
if (stringValue) {
|
|
165
|
+
fields.set(key, stringValue[1]);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (rest.startsWith("{")) {
|
|
169
|
+
currentTable = [];
|
|
170
|
+
fields.set(key, currentTable);
|
|
171
|
+
depth = countBraces(rest, 0);
|
|
172
|
+
}
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
// Inside a table: collect string items, `key = "value"` pairs, and the
|
|
176
|
+
// bracketed `["key"] = "value"` form LuaRocks uses for names that are not
|
|
177
|
+
// valid Lua identifiers.
|
|
178
|
+
depth = countBraces(line, depth);
|
|
179
|
+
const keyed = line.match(
|
|
180
|
+
/^(?:\[\s*"([^"]+)"\s*\]|([A-Za-z_][A-Za-z0-9_]*))\s*=\s*"([^"]*)"/u,
|
|
181
|
+
);
|
|
182
|
+
if (keyed && currentTable) {
|
|
183
|
+
currentTable.push({ name: keyed[1] ?? keyed[2], version: keyed[3] });
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const item = line.match(/^"([^"]+)"/u);
|
|
187
|
+
if (item && currentTable) {
|
|
188
|
+
const tokens = item[1].split(/\s+/u);
|
|
189
|
+
currentTable.push({
|
|
190
|
+
name: tokens[0],
|
|
191
|
+
constraint: tokens.slice(1).join(" "),
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (depth <= 0) {
|
|
195
|
+
depth = 0;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return fields;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Track table nesting depth for one line.
|
|
203
|
+
*
|
|
204
|
+
* @param {string} line Line to scan
|
|
205
|
+
* @param {number} depth Depth before the line
|
|
206
|
+
* @returns {number} Depth after the line
|
|
207
|
+
*/
|
|
208
|
+
function countBraces(line, depth) {
|
|
209
|
+
let next = depth;
|
|
210
|
+
for (const char of line) {
|
|
211
|
+
if (char === "{") {
|
|
212
|
+
next += 1;
|
|
213
|
+
} else if (char === "}") {
|
|
214
|
+
next -= 1;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return next;
|
|
218
|
+
}
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
mapConanPkgRefToPurlStringAndNameAndVersion,
|
|
42
42
|
nixGenericPurl,
|
|
43
43
|
purlFromUrlString,
|
|
44
|
+
tryBuildPurl,
|
|
44
45
|
} from "../inventory/purl.js";
|
|
45
46
|
import { getLicenses, spdxLicenses } from "../inventory/spdx.js";
|
|
46
47
|
import { getDartMetadata } from "./ecosystems.js";
|
|
@@ -3203,3 +3204,174 @@ async function fullScanCocoaPod(dependency, component, options) {
|
|
|
3203
3204
|
component.externalReferences = externalRefs;
|
|
3204
3205
|
}
|
|
3205
3206
|
}
|
|
3207
|
+
|
|
3208
|
+
/**
|
|
3209
|
+
* Parse an xmake `xmake-requires.lock` file.
|
|
3210
|
+
*
|
|
3211
|
+
* The lock is a Lua table, written when the `package.requires_lock` policy is
|
|
3212
|
+
* enabled. Its top level holds a `__meta__` entry plus one section per build
|
|
3213
|
+
* configuration, keyed `platform|arch`; each section maps a requirement key
|
|
3214
|
+
* (`"zlib#31fecfc4"`, or `"sol2 v3.2.1#72267bd5"` when the requirement
|
|
3215
|
+
* carries a version) to a table with the resolved `version` and the
|
|
3216
|
+
* `repo` revision the package definition came from.
|
|
3217
|
+
*
|
|
3218
|
+
* The same package appears once per configuration, so entries are collapsed
|
|
3219
|
+
* by name and version, and the configurations a package was resolved for are
|
|
3220
|
+
* kept as a property. A requirement key without a `version` field names a
|
|
3221
|
+
* system package xmake did not resolve; it is inventoried without a version.
|
|
3222
|
+
*
|
|
3223
|
+
* No `xmake` purl type is registered, so packages are identified as generic
|
|
3224
|
+
* carrying a `cdx:purl:proposedType=xmake` property, following the
|
|
3225
|
+
* convention used for nix and zig.
|
|
3226
|
+
*
|
|
3227
|
+
* @param {string} lockFile Path to `xmake-requires.lock`
|
|
3228
|
+
* @returns {Object[]} Package records for the pinned requirements
|
|
3229
|
+
*/
|
|
3230
|
+
export function parseXmakeRequiresLock(lockFile) {
|
|
3231
|
+
let text;
|
|
3232
|
+
try {
|
|
3233
|
+
text = readFileSync(lockFile, "utf-8");
|
|
3234
|
+
} catch (error) {
|
|
3235
|
+
console.warn(`Failed to read ${lockFile}: ${error.message}`);
|
|
3236
|
+
return [];
|
|
3237
|
+
}
|
|
3238
|
+
const byKey = new Map();
|
|
3239
|
+
// The outermost `{` is the file's own table, so a configuration section
|
|
3240
|
+
// opens at depth 1 and a requirement inside it at depth 2. Each is dropped
|
|
3241
|
+
// again once the brace depth falls back to where it opened.
|
|
3242
|
+
const CONFIGURATION_DEPTH = 1;
|
|
3243
|
+
const REQUIREMENT_DEPTH = 2;
|
|
3244
|
+
let configuration;
|
|
3245
|
+
let requirement;
|
|
3246
|
+
let depth = 0;
|
|
3247
|
+
for (const rawLine of text.split("\n")) {
|
|
3248
|
+
const line = rawLine.trim();
|
|
3249
|
+
if (!line || line.startsWith("--")) {
|
|
3250
|
+
continue;
|
|
3251
|
+
}
|
|
3252
|
+
// `["macosx|arm64"] = {` opens a configuration; `["zlib#hash"] = {` opens
|
|
3253
|
+
// a requirement inside one.
|
|
3254
|
+
const opener = line.match(/^\[\s*"([^"]+)"\s*\]\s*=\s*\{/u);
|
|
3255
|
+
if (opener && depth === CONFIGURATION_DEPTH) {
|
|
3256
|
+
// `__meta__` is normally written unquoted, but a lock that quotes it
|
|
3257
|
+
// must not become a configuration section either.
|
|
3258
|
+
configuration = opener[1] === "__meta__" ? undefined : opener[1];
|
|
3259
|
+
} else if (opener && depth === REQUIREMENT_DEPTH && configuration) {
|
|
3260
|
+
requirement = xmakeRequirementName(opener[1]);
|
|
3261
|
+
const key = `${configuration}|${requirement}`;
|
|
3262
|
+
if (requirement && !byKey.has(key)) {
|
|
3263
|
+
byKey.set(key, { name: requirement, configuration });
|
|
3264
|
+
}
|
|
3265
|
+
} else if (!opener && configuration && requirement) {
|
|
3266
|
+
const version = line.match(/^version\s*=\s*"([^"]*)"/u);
|
|
3267
|
+
if (version) {
|
|
3268
|
+
const record = byKey.get(`${configuration}|${requirement}`);
|
|
3269
|
+
if (record) {
|
|
3270
|
+
record.version = version[1];
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
depth = Math.max(0, depth + countXmakeBraces(line));
|
|
3275
|
+
if (depth <= REQUIREMENT_DEPTH) {
|
|
3276
|
+
requirement = undefined;
|
|
3277
|
+
}
|
|
3278
|
+
if (depth <= CONFIGURATION_DEPTH) {
|
|
3279
|
+
configuration = undefined;
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
// Collapse the per-configuration entries into one component per resolved
|
|
3284
|
+
// coordinate, listing the configurations it was resolved for.
|
|
3285
|
+
const merged = new Map();
|
|
3286
|
+
for (const record of byKey.values()) {
|
|
3287
|
+
const key = `${record.name}@${record.version || ""}`;
|
|
3288
|
+
const existing = merged.get(key);
|
|
3289
|
+
if (existing) {
|
|
3290
|
+
existing.configurations.add(record.configuration);
|
|
3291
|
+
continue;
|
|
3292
|
+
}
|
|
3293
|
+
merged.set(key, {
|
|
3294
|
+
name: record.name,
|
|
3295
|
+
version: record.version,
|
|
3296
|
+
configurations: new Set([record.configuration]),
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
|
|
3300
|
+
const pkgList = [];
|
|
3301
|
+
for (const record of merged.values()) {
|
|
3302
|
+
// A malformed coordinate in an untrusted lock costs one entry, not the
|
|
3303
|
+
// whole scan.
|
|
3304
|
+
const purl = tryBuildPurl({
|
|
3305
|
+
type: "generic",
|
|
3306
|
+
namespace: "xmake",
|
|
3307
|
+
name: record.name,
|
|
3308
|
+
version: record.version || null,
|
|
3309
|
+
});
|
|
3310
|
+
if (!purl) {
|
|
3311
|
+
continue;
|
|
3312
|
+
}
|
|
3313
|
+
pkgList.push({
|
|
3314
|
+
name: record.name,
|
|
3315
|
+
...(record.version ? { version: record.version } : {}),
|
|
3316
|
+
type: "library",
|
|
3317
|
+
scope: "required",
|
|
3318
|
+
purl,
|
|
3319
|
+
"bom-ref": decodeURIComponent(purl),
|
|
3320
|
+
properties: [
|
|
3321
|
+
{ name: "internal:SrcFile", value: lockFile },
|
|
3322
|
+
{ name: "cdx:purl:proposedType", value: "xmake" },
|
|
3323
|
+
{
|
|
3324
|
+
name: "cdx:xmake:configurations",
|
|
3325
|
+
value: [...record.configurations].sort().join(", "),
|
|
3326
|
+
},
|
|
3327
|
+
],
|
|
3328
|
+
evidence: {
|
|
3329
|
+
identity: {
|
|
3330
|
+
field: "purl",
|
|
3331
|
+
confidence: 0.5,
|
|
3332
|
+
methods: [
|
|
3333
|
+
{
|
|
3334
|
+
technique: "source-code-analysis",
|
|
3335
|
+
confidence: 0.5,
|
|
3336
|
+
value: `Filename ${lockFile}`,
|
|
3337
|
+
},
|
|
3338
|
+
],
|
|
3339
|
+
},
|
|
3340
|
+
},
|
|
3341
|
+
});
|
|
3342
|
+
}
|
|
3343
|
+
return pkgList;
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
/**
|
|
3347
|
+
* Reduce an xmake requirement key to the package name.
|
|
3348
|
+
*
|
|
3349
|
+
* Keys look like `zlib#31fecfc4` or `sol2 v3.2.1#72267bd5`: the trailing
|
|
3350
|
+
* `#<hash>` identifies the resolved configuration and the optional second
|
|
3351
|
+
* token is the requested version range, neither of which is the name.
|
|
3352
|
+
*
|
|
3353
|
+
* @param {string} key Requirement key as written in the lock
|
|
3354
|
+
* @returns {string|undefined} Package name
|
|
3355
|
+
*/
|
|
3356
|
+
function xmakeRequirementName(key) {
|
|
3357
|
+
const name = key.split("#")[0].trim().split(/\s+/u)[0];
|
|
3358
|
+
return name || undefined;
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
/**
|
|
3362
|
+
* Net brace change contributed by one line of a Lua table.
|
|
3363
|
+
*
|
|
3364
|
+
* @param {string} line Line to scan
|
|
3365
|
+
* @returns {number} Opens minus closes
|
|
3366
|
+
*/
|
|
3367
|
+
function countXmakeBraces(line) {
|
|
3368
|
+
let delta = 0;
|
|
3369
|
+
for (const char of line) {
|
|
3370
|
+
if (char === "{") {
|
|
3371
|
+
delta += 1;
|
|
3372
|
+
} else if (char === "}") {
|
|
3373
|
+
delta -= 1;
|
|
3374
|
+
}
|
|
3375
|
+
}
|
|
3376
|
+
return delta;
|
|
3377
|
+
}
|