UNPKG

pi-lens

Version:

Real-time code feedback for pi — LSP, linters, formatters, type-checking, structural analysis & booboo

170 lines (167 loc) • 6.25 kB
import { createRequire as __pilensCreateRequire } from "node:module"; const require = __pilensCreateRequire(import.meta.url); import { extractTomlTableSection, hasTomlTable, parseTomlStringArray } from "./chunk-PAK6IDME.js"; import { recordDegradationOnce } from "./chunk-N3YQJI6O.js"; import { UV_WORKSPACE_EXCLUDE_DIALECT, UV_WORKSPACE_MEMBERS_DIALECT, isAtOrAboveHomeDir, matchesWorkspaceMemberPattern, toPosix, walkUpDirs } from "./chunk-Q5U6FMDI.js"; // dist/clients/python-environment.js import { access, readFile } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; var UV_WORKSPACE_TABLE = "tool\\.uv\\.workspace"; async function findUvWorkspace(startDir, homeDir) { const resolvedStart = path.resolve(startDir); let nearestProject; for (const dir of walkUpDirs(startDir)) { if (isAtOrAboveHomeDir(dir, homeDir)) break; let content; try { content = await readFile(path.join(dir, "pyproject.toml"), "utf8"); } catch { continue; } if (!nearestProject) nearestProject = dir; if (hasTomlTable(content, UV_WORKSPACE_TABLE)) { const workspaceTable = extractTomlTableSection(content, UV_WORKSPACE_TABLE); return { root: dir, explicit: true, projectRoot: nearestProject, isStartDir: nearestProject === resolvedStart, members: parseTomlStringArray(workspaceTable, "members"), exclude: parseTomlStringArray(workspaceTable, "exclude") }; } } return nearestProject ? { root: nearestProject, explicit: false, projectRoot: nearestProject, isStartDir: nearestProject === resolvedStart, members: [], exclude: [] } : void 0; } function isUvWorkspaceMember(workspace, projectRoot) { const relative = toPosix(path.relative(workspace.root, projectRoot)); if (relative.length === 0 || relative === ".." || relative.startsWith("../") || path.isAbsolute(relative)) { return false; } const matches = (dialect) => (pattern) => matchesWorkspaceMemberPattern(pattern, relative, dialect, () => recordDegradationOnce({ kind: "workspace-glob-cap", subject: String(pattern.length), reason: "workspace glob matcher exceeded its memo-table cell cap" })); return !workspace.exclude.some(matches(UV_WORKSPACE_EXCLUDE_DIALECT)) && workspace.members.some(matches(UV_WORKSPACE_MEMBERS_DIALECT)); } var AMBIENT_ENVIRONMENT_VARIABLES = { "uv-project-environment": "UV_PROJECT_ENVIRONMENT", "virtual-env": "VIRTUAL_ENV", conda: "CONDA_PREFIX" }; function hasAmbientPythonEnvironment(env = process.env) { return Object.values(AMBIENT_ENVIRONMENT_VARIABLES).some((name) => Boolean(env[name])); } async function detectPythonEnvironment(projectRoot, homeDir = os.homedir(), options = {}) { const root = path.resolve(projectRoot); const uvWorkspace = await findUvWorkspace(root, homeDir); const isProjectRoot = uvWorkspace?.isStartDir === true; const memberWorkspaceRoot = isProjectRoot && uvWorkspace.explicit && isUvWorkspaceMember(uvWorkspace, root) ? uvWorkspace.root : void 0; const uvEnvironmentRoot = isProjectRoot ? memberWorkspaceRoot ?? uvWorkspace.projectRoot : void 0; const uvProjectEnvironment = process.env.UV_PROJECT_ENVIRONMENT; const allowAmbient = options.allowAmbient ?? true; const isWithinProjectRoot = (candidateRoot) => { const relative = path.relative(root, path.resolve(candidateRoot)); return relative !== ".." && !relative.startsWith(`..${path.sep}`) && // #3871 V1: another Windows drive has no relative path, so // `path.relative` returns the absolute target: no `..`, not inside. !path.isAbsolute(relative); }; const candidates = [ ...uvEnvironmentRoot !== void 0 && uvProjectEnvironment ? [ { // `path.resolve` leaves an absolute value untouched and // anchors a relative one at the project's workspace root. root: path.resolve(uvEnvironmentRoot, uvProjectEnvironment), source: "uv-project-environment" } ] : [], ...memberWorkspaceRoot !== void 0 ? [ { root: path.join(memberWorkspaceRoot, ".venv"), source: "uv-workspace" } ] : [], { root: process.env.VIRTUAL_ENV, source: "virtual-env" }, { root: process.env.CONDA_PREFIX, source: "conda" }, { root: path.join(root, ".venv"), source: "project-dot-venv" }, { root: path.join(root, "venv"), source: "project-venv" } ]; for (const candidate of candidates) { if (!candidate.root) continue; if (!allowAmbient && candidate.source in AMBIENT_ENVIRONMENT_VARIABLES && !isWithinProjectRoot(candidate.root)) continue; const binDir = path.join(candidate.root, process.platform === "win32" ? "Scripts" : "bin"); const pythonPath = path.join(binDir, process.platform === "win32" ? "python.exe" : "python"); try { await access(pythonPath); return { root: candidate.root, binDir, pythonPath, source: candidate.source }; } catch { } } return void 0; } async function detectPythonVenv(projectRoot) { return (await detectPythonEnvironment(projectRoot))?.pythonPath; } function augmentPythonEnvironment(baseEnvironment, environment) { if (!environment) return baseEnvironment; const inheritedPath = baseEnvironment.PATH ?? baseEnvironment.Path ?? baseEnvironment.path ?? ""; const augmentedPath = inheritedPath ? `${environment.binDir}${path.delimiter}${inheritedPath}` : environment.binDir; const childEnvironment = { ...baseEnvironment, PATH: augmentedPath, VIRTUAL_ENV: environment.root }; if (process.platform === "win32") childEnvironment.Path = augmentedPath; return childEnvironment; } function pythonEnvironmentToolCandidates(environment, command) { if (!environment) return []; if (process.platform !== "win32") { return [path.join(environment.binDir, command)]; } return [ path.join(environment.binDir, `${command}.exe`), path.join(environment.binDir, `${command}.cmd`), path.join(environment.binDir, command) ]; } export { hasAmbientPythonEnvironment, detectPythonEnvironment, detectPythonVenv, augmentPythonEnvironment, pythonEnvironmentToolCandidates };