58 lines
2.0 KiB
TypeScript
58 lines
2.0 KiB
TypeScript
import { statSync } from 'node:fs'
|
|
import path from 'node:path'
|
|
|
|
export interface SourcePythonOptions {
|
|
/** Explicit developer interpreter (`HERMES_DESKTOP_PYTHON`), honoured as-is. */
|
|
override?: string
|
|
isWindows?: boolean
|
|
fileExists?: (candidate: string) => boolean
|
|
}
|
|
|
|
/** A checkout's own interpreter paths, in preference order. */
|
|
function interpreterPaths(isWindows: boolean): string[] {
|
|
return isWindows
|
|
? [path.join('.venv', 'Scripts', 'python.exe'), path.join('venv', 'Scripts', 'python.exe')]
|
|
: [path.join('.venv', 'bin', 'python'), path.join('venv', 'bin', 'python')]
|
|
}
|
|
|
|
function defaultFileExists(candidate: string): boolean {
|
|
try {
|
|
return statSync(candidate).isFile()
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The interpreter that belongs to a source checkout: an explicit override, else
|
|
* the checkout's own virtualenv, else null.
|
|
*
|
|
* Never a PATH Python. A system interpreter can import a checkout while lacking
|
|
* its selected dependencies (`updater/checkout-source.ts`), so falling back
|
|
* silently turns "this root has no runtime" into an update probe or a backend
|
|
* running under an interpreter nothing selected — and a failed read becomes a
|
|
* wrong answer instead of a refusal. PM deletes the in-tree `venv`/`.venv` once
|
|
* a generation is committed, which makes null the ordinary answer for a managed
|
|
* install: its callers resolve the installation launcher instead
|
|
* (`resolveSourceInstallationBackend`, `readSourceUpdate`) or refuse
|
|
* (`preflightStateDb`), and the backend ladder moves on to its next rung.
|
|
*/
|
|
export function resolveSourcePython(
|
|
root: string,
|
|
{ override, isWindows = process.platform === 'win32', fileExists = defaultFileExists }: SourcePythonOptions = {}
|
|
): string | null {
|
|
if (override && fileExists(override)) {
|
|
return override
|
|
}
|
|
|
|
for (const relative of interpreterPaths(isWindows)) {
|
|
const candidate: string = path.join(root, relative)
|
|
|
|
if (fileExists(candidate)) {
|
|
return candidate
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|