diff --git a/package-lock.json b/package-lock.json index 42751388..1b975f18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -795,6 +795,7 @@ "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.16.0", "@typescript-eslint/types": "8.16.0", @@ -1480,6 +1481,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1523,6 +1525,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -1713,6 +1716,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001580", "electron-to-chromium": "^1.4.648", @@ -2423,6 +2427,7 @@ "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -5176,6 +5181,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5301,6 +5307,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, + "peer": true, "dependencies": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -5347,6 +5354,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, + "peer": true, "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -6144,6 +6152,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.16.0.tgz", "integrity": "sha512-D7DbgGFtsqIPIFMPJwCad9Gfi/hC0PWErRRHFnaCWoEDYi5tQUDiJCTmGUbBiLzjqAck4KcXt9Ayj0CNlIrF+w==", "dev": true, + "peer": true, "requires": { "@typescript-eslint/scope-manager": "8.16.0", "@typescript-eslint/types": "8.16.0", @@ -6639,7 +6648,8 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true + "dev": true, + "peer": true }, "acorn-import-attributes": { "version": "1.9.5", @@ -6669,6 +6679,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -6801,6 +6812,7 @@ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", "dev": true, + "peer": true, "requires": { "caniuse-lite": "^1.0.30001580", "electron-to-chromium": "^1.4.648", @@ -7295,6 +7307,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.37.0.tgz", "integrity": "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig==", "dev": true, + "peer": true, "requires": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -9280,7 +9293,8 @@ "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", - "dev": true + "dev": true, + "peer": true }, "uc.micro": { "version": "1.0.6", @@ -9366,6 +9380,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, + "peer": true, "requires": { "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", @@ -9397,6 +9412,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, + "peer": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", diff --git a/src/managers/poetry/poetryUtils.ts b/src/managers/poetry/poetryUtils.ts index 6a8abddd..a19699dc 100644 --- a/src/managers/poetry/poetryUtils.ts +++ b/src/managers/poetry/poetryUtils.ts @@ -17,6 +17,19 @@ import { } from '../common/nativePythonFinder'; import { getShellActivationCommands, shortVersion, sortEnvironments } from '../common/utils'; +/** + * Checks if the POETRY_VIRTUALENVS_IN_PROJECT environment variable is set to a truthy value. + * When true, Poetry creates virtualenvs in the project's `.venv` directory. + * Mirrors the PET server logic in `pet-poetry/src/env_variables.rs`. + */ +export function isPoetryVirtualenvsInProject(): boolean { + const value = process.env.POETRY_VIRTUALENVS_IN_PROJECT; + if (value === undefined) { + return false; + } + return value === '1' || value.toLowerCase() === 'true'; +} + async function findPoetry(): Promise { try { return await which('poetry'); @@ -251,19 +264,28 @@ async function nativeToPythonEnv( // Determine if the environment is in Poetry's global virtualenvs directory let isGlobalPoetryEnv = false; - const virtualenvsPath = poetryVirtualenvsPath; // Use the cached value if available - if (virtualenvsPath) { - const normalizedVirtualenvsPath = path.normalize(virtualenvsPath); - isGlobalPoetryEnv = normalizedPrefix.startsWith(normalizedVirtualenvsPath); + + // If POETRY_VIRTUALENVS_IN_PROJECT is set, environments are created in-project (.venv) + // and should not be classified as global + if (isPoetryVirtualenvsInProject() && info.project) { + isGlobalPoetryEnv = false; } else { - // Fall back to checking the default location if we haven't cached the path yet - const homeDir = getUserHomeDir(); - if (homeDir) { - const defaultPath = path.normalize(path.join(homeDir, '.cache', 'pypoetry', 'virtualenvs')); - isGlobalPoetryEnv = normalizedPrefix.startsWith(defaultPath); - - // Try to get the actual path asynchronously for next time - getPoetryVirtualenvsPath(_poetry).catch((e) => traceError(`Error getting Poetry virtualenvs path: ${e}`)); + const virtualenvsPath = poetryVirtualenvsPath; // Use the cached value if available + if (virtualenvsPath) { + const normalizedVirtualenvsPath = path.normalize(virtualenvsPath); + isGlobalPoetryEnv = normalizedPrefix.startsWith(normalizedVirtualenvsPath); + } else { + // Fall back to checking the default location if we haven't cached the path yet + const homeDir = getUserHomeDir(); + if (homeDir) { + const defaultPath = path.normalize(path.join(homeDir, '.cache', 'pypoetry', 'virtualenvs')); + isGlobalPoetryEnv = normalizedPrefix.startsWith(defaultPath); + + // Try to get the actual path asynchronously for next time + getPoetryVirtualenvsPath(_poetry).catch((e) => + traceError(`Error getting Poetry virtualenvs path: ${e}`), + ); + } } } diff --git a/src/test/managers/poetry/poetryUtils.unit.test.ts b/src/test/managers/poetry/poetryUtils.unit.test.ts new file mode 100644 index 00000000..08607784 --- /dev/null +++ b/src/test/managers/poetry/poetryUtils.unit.test.ts @@ -0,0 +1,63 @@ +import assert from 'node:assert'; +import { isPoetryVirtualenvsInProject } from '../../../managers/poetry/poetryUtils'; + +suite('isPoetryVirtualenvsInProject', () => { + let originalEnv: string | undefined; + + setup(() => { + originalEnv = process.env.POETRY_VIRTUALENVS_IN_PROJECT; + }); + + teardown(() => { + if (originalEnv === undefined) { + delete process.env.POETRY_VIRTUALENVS_IN_PROJECT; + } else { + process.env.POETRY_VIRTUALENVS_IN_PROJECT = originalEnv; + } + }); + + test('should return false when env var is not set', () => { + delete process.env.POETRY_VIRTUALENVS_IN_PROJECT; + assert.strictEqual(isPoetryVirtualenvsInProject(), false); + }); + + test('should return true when env var is "true"', () => { + process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'true'; + assert.strictEqual(isPoetryVirtualenvsInProject(), true); + }); + + test('should return true when env var is "True" (case insensitive)', () => { + process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'True'; + assert.strictEqual(isPoetryVirtualenvsInProject(), true); + }); + + test('should return true when env var is "TRUE" (case insensitive)', () => { + process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'TRUE'; + assert.strictEqual(isPoetryVirtualenvsInProject(), true); + }); + + test('should return true when env var is "1"', () => { + process.env.POETRY_VIRTUALENVS_IN_PROJECT = '1'; + assert.strictEqual(isPoetryVirtualenvsInProject(), true); + }); + + test('should return false when env var is "false"', () => { + process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'false'; + assert.strictEqual(isPoetryVirtualenvsInProject(), false); + }); + + test('should return false when env var is "0"', () => { + process.env.POETRY_VIRTUALENVS_IN_PROJECT = '0'; + assert.strictEqual(isPoetryVirtualenvsInProject(), false); + }); + + test('should return false when env var is empty string', () => { + process.env.POETRY_VIRTUALENVS_IN_PROJECT = ''; + assert.strictEqual(isPoetryVirtualenvsInProject(), false); + }); + + test('should return false when env var is arbitrary string', () => { + process.env.POETRY_VIRTUALENVS_IN_PROJECT = 'yes'; + assert.strictEqual(isPoetryVirtualenvsInProject(), false); + }); +});