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..aeb86108 100644 --- a/src/managers/poetry/poetryUtils.ts +++ b/src/managers/poetry/poetryUtils.ts @@ -25,6 +25,38 @@ async function findPoetry(): Promise { } } +/** + * Returns the platform-specific default path for Poetry's virtualenvs directory. + * - Linux: ~/.cache/pypoetry/virtualenvs + * - macOS: ~/Library/Caches/pypoetry/virtualenvs + * - Windows: %LOCALAPPDATA%\pypoetry\Cache\virtualenvs (or %APPDATA%\pypoetry\Cache\virtualenvs) + */ +export function getDefaultPoetryVirtualenvsPath(): string | undefined { + if (isWindows()) { + const localAppData = process.env.LOCALAPPDATA; + if (localAppData) { + return path.join(localAppData, 'pypoetry', 'Cache', 'virtualenvs'); + } + const appData = process.env.APPDATA; + if (appData) { + return path.join(appData, 'pypoetry', 'Cache', 'virtualenvs'); + } + return undefined; + } + + const home = getUserHomeDir(); + if (!home) { + return undefined; + } + + if (process.platform === 'darwin') { + return path.join(home, 'Library', 'Caches', 'pypoetry', 'virtualenvs'); + } + + // Linux default + return path.join(home, '.cache', 'pypoetry', 'virtualenvs'); +} + export const POETRY_GLOBAL = 'Global'; export const POETRY_PATH_KEY = `${ENVS_EXTENSION_ID}:poetry:POETRY_PATH`; @@ -182,10 +214,7 @@ export async function getPoetryVirtualenvsPath(poetryExe?: string): Promise { + let isWindowsStub: sinon.SinonStub; + let getUserHomeDirStub: sinon.SinonStub; + let originalPlatform: PropertyDescriptor | undefined; + let originalEnv: NodeJS.ProcessEnv; + + setup(() => { + isWindowsStub = sinon.stub(platformUtils, 'isWindows'); + getUserHomeDirStub = sinon.stub(pathUtils, 'getUserHomeDir'); + originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform'); + originalEnv = { ...process.env }; + }); + + teardown(() => { + sinon.restore(); + if (originalPlatform) { + Object.defineProperty(process, 'platform', originalPlatform); + } + process.env = originalEnv; + }); + + test('should return Linux path on Linux', () => { + isWindowsStub.returns(false); + Object.defineProperty(process, 'platform', { value: 'linux', configurable: true }); + getUserHomeDirStub.returns('/home/testuser'); + + const result = getDefaultPoetryVirtualenvsPath(); + + assert.strictEqual(result, path.join('/home/testuser', '.cache', 'pypoetry', 'virtualenvs')); + }); + + test('should return macOS path on darwin', () => { + isWindowsStub.returns(false); + Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true }); + getUserHomeDirStub.returns('/Users/testuser'); + + const result = getDefaultPoetryVirtualenvsPath(); + + assert.strictEqual(result, path.join('/Users/testuser', 'Library', 'Caches', 'pypoetry', 'virtualenvs')); + }); + + test('should return LOCALAPPDATA path on Windows when LOCALAPPDATA is set', () => { + isWindowsStub.returns(true); + process.env.LOCALAPPDATA = 'C:\\Users\\testuser\\AppData\\Local'; + process.env.APPDATA = 'C:\\Users\\testuser\\AppData\\Roaming'; + + const result = getDefaultPoetryVirtualenvsPath(); + + assert.strictEqual( + result, + path.join('C:\\Users\\testuser\\AppData\\Local', 'pypoetry', 'Cache', 'virtualenvs'), + ); + }); + + test('should fall back to APPDATA on Windows when LOCALAPPDATA is not set', () => { + isWindowsStub.returns(true); + delete process.env.LOCALAPPDATA; + process.env.APPDATA = 'C:\\Users\\testuser\\AppData\\Roaming'; + + const result = getDefaultPoetryVirtualenvsPath(); + + assert.strictEqual( + result, + path.join('C:\\Users\\testuser\\AppData\\Roaming', 'pypoetry', 'Cache', 'virtualenvs'), + ); + }); + + test('should return undefined on Windows when neither LOCALAPPDATA nor APPDATA is set', () => { + isWindowsStub.returns(true); + delete process.env.LOCALAPPDATA; + delete process.env.APPDATA; + + const result = getDefaultPoetryVirtualenvsPath(); + + assert.strictEqual(result, undefined); + }); + + test('should return undefined when home directory is empty string on non-Windows', () => { + isWindowsStub.returns(false); + Object.defineProperty(process, 'platform', { value: 'linux', configurable: true }); + getUserHomeDirStub.returns(''); + + const result = getDefaultPoetryVirtualenvsPath(); + + assert.strictEqual(result, undefined); + }); + + test('should return undefined when home directory is undefined on non-Windows', () => { + isWindowsStub.returns(false); + Object.defineProperty(process, 'platform', { value: 'linux', configurable: true }); + getUserHomeDirStub.returns(undefined); + + const result = getDefaultPoetryVirtualenvsPath(); + + assert.strictEqual(result, undefined); + }); +});