@cyclonedx/cdxgen 12.1.4 → 12.1.5
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/bin/cdxgen.js +12 -0
- package/bin/repl.js +2 -2
- package/lib/cli/index.js +158 -69
- package/lib/evinser/evinser.js +3 -4
- package/lib/evinser/swiftsem.js +1 -1
- package/lib/helpers/caxa.js +1 -1
- package/lib/helpers/display.js +6 -10
- package/lib/helpers/envcontext.js +5 -5
- package/lib/helpers/pythonutils.js +296 -0
- package/lib/helpers/pythonutils.poku.js +469 -0
- package/lib/helpers/utils.js +263 -96
- package/lib/helpers/utils.poku.js +84 -1
- package/lib/managers/piptree.js +1 -1
- package/lib/parsers/npmrc.js +88 -0
- package/lib/parsers/npmrc.poku.js +492 -0
- package/lib/server/openapi.yaml +0 -9
- package/lib/server/server.js +18 -5
- package/lib/stages/pregen/env-audit.js +34 -0
- package/lib/stages/pregen/env-audit.poku.js +290 -0
- package/lib/third-party/arborist/lib/deepest-nesting-target.js +1 -1
- package/lib/third-party/arborist/lib/node.js +3 -3
- package/lib/third-party/arborist/lib/shrinkwrap.js +1 -1
- package/lib/third-party/arborist/lib/tree-check.js +1 -1
- package/package.json +3 -3
- package/types/lib/cli/index.d.ts.map +1 -1
- package/types/lib/evinser/evinser.d.ts.map +1 -1
- package/types/lib/helpers/display.d.ts.map +1 -1
- package/types/lib/helpers/pythonutils.d.ts +9 -0
- package/types/lib/helpers/pythonutils.d.ts.map +1 -0
- package/types/lib/helpers/utils.d.ts.map +1 -1
- package/types/lib/parsers/npmrc.d.ts +23 -0
- package/types/lib/parsers/npmrc.d.ts.map +1 -0
- package/types/lib/server/server.d.ts.map +1 -1
- package/types/lib/stages/pregen/env-audit.d.ts +2 -0
- package/types/lib/stages/pregen/env-audit.d.ts.map +1 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, sep } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { thoughtLog } from "./logger.js";
|
|
5
|
+
import { DEBUG_MODE, PYTHON_CMD, safeExistsSync } from "./utils.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Universal virtual environment metadata detector
|
|
9
|
+
* @param {Object} env - Environment variables (defaults to process.env)
|
|
10
|
+
* @param {string} [explicitPath] - Optional explicit venv path to inspect
|
|
11
|
+
* @returns {Object} Structured environment metadata
|
|
12
|
+
*/
|
|
13
|
+
export function getVenvMetadata(env = process.env, explicitPath = null) {
|
|
14
|
+
const result = {
|
|
15
|
+
type: "system", // 'uv' | 'venv' | 'conda' | 'miniconda' | 'pyenv' | 'poetry' | 'pipenv' | 'virtualenv' | 'pixi' | 'bazel' | 'rye' | 'hatch' | 'pdm' | 'system' | 'unknown'
|
|
16
|
+
path: null,
|
|
17
|
+
isActive: false,
|
|
18
|
+
pythonExecutable: null,
|
|
19
|
+
pythonVersion: "unknown",
|
|
20
|
+
pythonImplementation: null,
|
|
21
|
+
toolVersion: null,
|
|
22
|
+
uv: null,
|
|
23
|
+
conda: null,
|
|
24
|
+
pyenv: null,
|
|
25
|
+
poetry: null,
|
|
26
|
+
pipenv: null,
|
|
27
|
+
pixi: null,
|
|
28
|
+
};
|
|
29
|
+
let venvPath = explicitPath;
|
|
30
|
+
if (!venvPath) {
|
|
31
|
+
if (env.VIRTUAL_ENV) {
|
|
32
|
+
venvPath = env.VIRTUAL_ENV;
|
|
33
|
+
result.isActive = true;
|
|
34
|
+
} else if (env.CONDA_PREFIX) {
|
|
35
|
+
venvPath = env.CONDA_PREFIX;
|
|
36
|
+
result.isActive = true;
|
|
37
|
+
} else if (env.PIXI_PROJECT_ROOT && env.PIXI_ENVIRONMENT_NAME) {
|
|
38
|
+
venvPath = join(
|
|
39
|
+
env.PIXI_PROJECT_ROOT,
|
|
40
|
+
".pixi",
|
|
41
|
+
"envs",
|
|
42
|
+
env.PIXI_ENVIRONMENT_NAME,
|
|
43
|
+
);
|
|
44
|
+
result.isActive = true;
|
|
45
|
+
} else if (env.CONDA_PYTHON_EXE && safeExistsSync(env.CONDA_PYTHON_EXE)) {
|
|
46
|
+
result.pythonExecutable = env.CONDA_PYTHON_EXE;
|
|
47
|
+
result.type = "conda";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!venvPath) {
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
result.path = venvPath;
|
|
54
|
+
const isWin = process.platform === "win32";
|
|
55
|
+
const binDir = isWin ? "Scripts" : "bin";
|
|
56
|
+
const exeNames = isWin
|
|
57
|
+
? ["python.exe", "python3.exe"]
|
|
58
|
+
: ["python", "python3"];
|
|
59
|
+
if (!isWin) {
|
|
60
|
+
for (let minor = 16; minor >= 6; minor--) {
|
|
61
|
+
exeNames.push(`python3.${minor}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const exe of exeNames) {
|
|
65
|
+
const candidate = join(venvPath, binDir, exe);
|
|
66
|
+
if (safeExistsSync(candidate)) {
|
|
67
|
+
result.pythonExecutable = candidate;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (!result.pythonExecutable && isWin) {
|
|
72
|
+
const rootExe = join(venvPath, "python.exe");
|
|
73
|
+
if (safeExistsSync(rootExe)) {
|
|
74
|
+
result.pythonExecutable = rootExe;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (
|
|
78
|
+
env.BUILD_WORKSPACE_DIRECTORY ||
|
|
79
|
+
venvPath.includes("bazel-out") ||
|
|
80
|
+
venvPath.includes(".runfiles")
|
|
81
|
+
) {
|
|
82
|
+
result.type = "bazel";
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
const isLocalVenv = basename(venvPath) === ".venv";
|
|
86
|
+
const projectRoot = isLocalVenv ? dirname(venvPath) : null;
|
|
87
|
+
const pyvenvCfgPath = join(venvPath, "pyvenv.cfg");
|
|
88
|
+
if (safeExistsSync(pyvenvCfgPath)) {
|
|
89
|
+
const cfg = _parsePyvenvCfg(pyvenvCfgPath);
|
|
90
|
+
result.pythonVersion = cfg.version_info || "unknown";
|
|
91
|
+
result.pythonImplementation = cfg.implementation || null;
|
|
92
|
+
if (cfg.uv) {
|
|
93
|
+
result.type = "uv";
|
|
94
|
+
result.toolVersion = cfg.uv;
|
|
95
|
+
result.uv = { version: cfg.uv, home: cfg.home };
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
if (
|
|
99
|
+
env.POETRY_ACTIVE === "1" ||
|
|
100
|
+
venvPath.includes(`pypoetry${sep}virtualenvs`) ||
|
|
101
|
+
(projectRoot && safeExistsSync(join(projectRoot, "poetry.lock")))
|
|
102
|
+
) {
|
|
103
|
+
result.type = "poetry";
|
|
104
|
+
if (projectRoot) result.poetry = { projectRoot };
|
|
105
|
+
const lockFile = projectRoot ? join(projectRoot, "poetry.lock") : null;
|
|
106
|
+
if (lockFile && safeExistsSync(lockFile)) {
|
|
107
|
+
const poetryVersion = _extractPoetryVersion(lockFile);
|
|
108
|
+
if (poetryVersion) result.toolVersion = poetryVersion;
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
if (
|
|
113
|
+
env.PIPENV_ACTIVE === "1" ||
|
|
114
|
+
venvPath.includes(`.virtualenvs${sep}`) ||
|
|
115
|
+
(projectRoot && safeExistsSync(join(projectRoot, "Pipfile")))
|
|
116
|
+
) {
|
|
117
|
+
result.type = "pipenv";
|
|
118
|
+
if (projectRoot) result.pipenv = { projectRoot };
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
if (
|
|
122
|
+
env.RYE_ACTIVE === "1" ||
|
|
123
|
+
(projectRoot &&
|
|
124
|
+
safeExistsSync(join(projectRoot, "requirements.lock")) &&
|
|
125
|
+
safeExistsSync(join(projectRoot, ".rye")))
|
|
126
|
+
) {
|
|
127
|
+
result.type = "rye";
|
|
128
|
+
return result;
|
|
129
|
+
}
|
|
130
|
+
if (env.HATCH_ENV_ACTIVE || venvPath.includes(`hatch${sep}env`)) {
|
|
131
|
+
result.type = "hatch";
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
if (
|
|
135
|
+
env.PDM_ACTIVE === "1" ||
|
|
136
|
+
(projectRoot && safeExistsSync(join(projectRoot, "pdm.lock")))
|
|
137
|
+
) {
|
|
138
|
+
result.type = "pdm";
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
if (cfg.virtualenv) {
|
|
142
|
+
result.type = "virtualenv";
|
|
143
|
+
result.toolVersion = cfg.virtualenv;
|
|
144
|
+
} else {
|
|
145
|
+
result.type = "venv";
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const condaMetaDir = join(venvPath, "conda-meta");
|
|
151
|
+
if (safeExistsSync(condaMetaDir)) {
|
|
152
|
+
if (env.PIXI_PROJECT_ROOT || venvPath.includes(`.pixi${sep}envs`)) {
|
|
153
|
+
result.type = "pixi";
|
|
154
|
+
result.pixi = {
|
|
155
|
+
projectRoot:
|
|
156
|
+
env.PIXI_PROJECT_ROOT || dirname(dirname(dirname(venvPath))),
|
|
157
|
+
};
|
|
158
|
+
} else {
|
|
159
|
+
result.type =
|
|
160
|
+
env.CONDA_PREFIX?.includes("miniconda") ||
|
|
161
|
+
env.CONDA_PREFIX?.includes("mini")
|
|
162
|
+
? "miniconda"
|
|
163
|
+
: "conda";
|
|
164
|
+
result.conda = {
|
|
165
|
+
name: env.CONDA_DEFAULT_ENV || basename(venvPath),
|
|
166
|
+
prefix: venvPath,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
if (env.CONDA_VERSION) {
|
|
170
|
+
result.toolVersion = env.CONDA_VERSION;
|
|
171
|
+
} else {
|
|
172
|
+
const historyPath = join(condaMetaDir, "history");
|
|
173
|
+
if (safeExistsSync(historyPath)) {
|
|
174
|
+
const condaVersion = _extractCondaVersion(historyPath);
|
|
175
|
+
if (condaVersion) result.toolVersion = condaVersion;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const pythonPkgs = _findCondaPythonPackage(condaMetaDir);
|
|
179
|
+
if (pythonPkgs?.version) {
|
|
180
|
+
result.pythonVersion = pythonPkgs.version;
|
|
181
|
+
}
|
|
182
|
+
return result;
|
|
183
|
+
}
|
|
184
|
+
if (env.PYENV_ROOT && venvPath.startsWith(env.PYENV_ROOT)) {
|
|
185
|
+
result.type = "pyenv";
|
|
186
|
+
const versionsDir = join(env.PYENV_ROOT, "versions");
|
|
187
|
+
if (venvPath.startsWith(`${versionsDir}${sep}`)) {
|
|
188
|
+
const version = basename(venvPath);
|
|
189
|
+
result.pyenv = { version, path: venvPath };
|
|
190
|
+
result.pythonVersion = version;
|
|
191
|
+
}
|
|
192
|
+
return result;
|
|
193
|
+
}
|
|
194
|
+
result.type = "unknown";
|
|
195
|
+
return result;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Parse pyvenv.cfg file into key-value object
|
|
200
|
+
*/
|
|
201
|
+
function _parsePyvenvCfg(filePath) {
|
|
202
|
+
const result = {};
|
|
203
|
+
try {
|
|
204
|
+
const content = readFileSync(filePath, "utf-8");
|
|
205
|
+
for (const line of content.split(/\r?\n/)) {
|
|
206
|
+
const trimmed = line.trim();
|
|
207
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
208
|
+
const match = trimmed.match(/^([^=]+?)\s*=\s*(.+)$/);
|
|
209
|
+
if (match) {
|
|
210
|
+
const [, key, value] = match;
|
|
211
|
+
result[key.trim()] = value.trim();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} catch (_err) {
|
|
215
|
+
// Return empty on error
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Extract poetry version from poetry.lock file
|
|
222
|
+
*/
|
|
223
|
+
function _extractPoetryVersion(lockPath) {
|
|
224
|
+
try {
|
|
225
|
+
const content = readFileSync(lockPath, "utf-8");
|
|
226
|
+
const match = content.match(/^\s*poetry-version\s*=\s*"([^"]+)"/m);
|
|
227
|
+
return match ? match[1] : null;
|
|
228
|
+
} catch (_err) {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Extract conda version from conda-meta/history
|
|
235
|
+
*/
|
|
236
|
+
function _extractCondaVersion(historyPath) {
|
|
237
|
+
try {
|
|
238
|
+
const content = readFileSync(historyPath, "utf-8");
|
|
239
|
+
const match = content.match(/conda version:\s*(\S+)/i);
|
|
240
|
+
return match ? match[1] : null;
|
|
241
|
+
} catch (_err) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Find python package info in conda-meta directory
|
|
248
|
+
*/
|
|
249
|
+
function _findCondaPythonPackage(condaMetaDir) {
|
|
250
|
+
try {
|
|
251
|
+
const files = readdirSync(condaMetaDir);
|
|
252
|
+
const pythonFile = files.find(
|
|
253
|
+
(f) => f.startsWith("python-") && f.endsWith(".json"),
|
|
254
|
+
);
|
|
255
|
+
if (!pythonFile) return null;
|
|
256
|
+
|
|
257
|
+
const pkgInfo = JSON.parse(
|
|
258
|
+
readFileSync(join(condaMetaDir, pythonFile), "utf-8"),
|
|
259
|
+
);
|
|
260
|
+
return {
|
|
261
|
+
version: pkgInfo?.version,
|
|
262
|
+
build: pkgInfo?.build,
|
|
263
|
+
};
|
|
264
|
+
} catch (_err) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function get_python_command_from_env(env) {
|
|
270
|
+
const fallbackCmd = PYTHON_CMD;
|
|
271
|
+
const meta = getVenvMetadata(env);
|
|
272
|
+
const pyVersionTxt =
|
|
273
|
+
meta.pythonVersion && meta.pythonVersion !== "unknown"
|
|
274
|
+
? ` version ${meta.pythonVersion}`
|
|
275
|
+
: "";
|
|
276
|
+
if (meta.type === "system") {
|
|
277
|
+
thoughtLog(
|
|
278
|
+
`I'm operating with system-managed python${pyVersionTxt}. I should be careful with the virtualenv creation and dependency tree construction.`,
|
|
279
|
+
);
|
|
280
|
+
} else if (meta.type === "unknown") {
|
|
281
|
+
thoughtLog(
|
|
282
|
+
`I'm operating with an unmanaged python${pyVersionTxt}. Let's check if pip and virtualenv packages are available.`,
|
|
283
|
+
);
|
|
284
|
+
} else {
|
|
285
|
+
thoughtLog(`Looks like python${pyVersionTxt} is managed by ${meta.type}.`);
|
|
286
|
+
}
|
|
287
|
+
if (meta?.pythonExecutable) {
|
|
288
|
+
if (DEBUG_MODE) {
|
|
289
|
+
console.log(
|
|
290
|
+
`Found python${pyVersionTxt} at ${meta.pythonExecutable} managed by ${meta.type}.`,
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
return meta.pythonExecutable;
|
|
294
|
+
}
|
|
295
|
+
return fallbackCmd;
|
|
296
|
+
}
|