File size: 4,376 Bytes
1f21206 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | export type CommandResult = {
ok: boolean
stdout: string
stderr: string
code: number
}
export type CommandRunner = (
cmd: string,
args: string[],
) => Promise<CommandResult>
type PythonCandidate = {
command: string
prefixArgs: string[]
locator: {
command: string
args: string[]
} | null
}
export type PythonRuntimeResolution = {
installed: boolean
version: string | null
path: string | null
command: string | null
prefixArgs: string[]
source: 'custom' | 'system' | 'venv' | null
error: string | null
}
function getPythonCandidates(platform: NodeJS.Platform): PythonCandidate[] {
if (platform === 'win32') {
return [
{
command: 'python3',
prefixArgs: [],
locator: { command: 'where', args: ['python3'] },
},
{
command: 'python',
prefixArgs: [],
locator: { command: 'where', args: ['python'] },
},
{
command: 'py',
prefixArgs: ['-3'],
locator: { command: 'where', args: ['py'] },
},
{
command: 'py',
prefixArgs: [],
locator: { command: 'where', args: ['py'] },
},
]
}
return [
{
command: 'python3',
prefixArgs: [],
locator: { command: 'which', args: ['python3'] },
},
]
}
function extractPythonVersion(output: string): string | null {
const match = output.match(/Python\s+([0-9][^\s]*)/i)
return match?.[1] ?? null
}
export function isPythonVersionAtLeast(
version: string | null,
major: number,
minor: number,
): boolean {
if (!version) return false
const match = version.match(/^(\d+)\.(\d+)/)
if (!match) return false
const currentMajor = Number(match[1])
const currentMinor = Number(match[2])
return currentMajor > major || (currentMajor === major && currentMinor >= minor)
}
function firstOutputLine(output: string): string | null {
const line = output
.split(/\r?\n/)
.map(value => value.trim())
.find(Boolean)
return line ?? null
}
async function locateCandidatePath(
candidate: PythonCandidate,
runCommand: CommandRunner,
): Promise<string | null> {
if (!candidate.locator) return null
const locateResult = await runCommand(candidate.locator.command, candidate.locator.args)
if (!locateResult.ok) return null
return firstOutputLine(locateResult.stdout)
}
export async function detectPythonRuntime(
platform: NodeJS.Platform,
runCommand: CommandRunner,
venvPythonPath?: string,
customPythonPath?: string | null,
): Promise<PythonRuntimeResolution> {
const normalizedCustomPath = customPythonPath?.trim()
if (normalizedCustomPath) {
const customResult = await runCommand(normalizedCustomPath, ['--version'])
if (customResult.ok) {
return {
installed: true,
version: extractPythonVersion(`${customResult.stdout}\n${customResult.stderr}`),
path: normalizedCustomPath,
command: normalizedCustomPath,
prefixArgs: [],
source: 'custom',
error: null,
}
}
return {
installed: false,
version: null,
path: normalizedCustomPath,
command: normalizedCustomPath,
prefixArgs: [],
source: 'custom',
error: customResult.stderr || customResult.stdout || `Failed to run ${normalizedCustomPath}`,
}
}
for (const candidate of getPythonCandidates(platform)) {
const versionResult = await runCommand(candidate.command, [...candidate.prefixArgs, '--version'])
if (!versionResult.ok) continue
return {
installed: true,
version: extractPythonVersion(`${versionResult.stdout}\n${versionResult.stderr}`),
path: await locateCandidatePath(candidate, runCommand),
command: candidate.command,
prefixArgs: candidate.prefixArgs,
source: 'system',
error: null,
}
}
if (venvPythonPath) {
const venvResult = await runCommand(venvPythonPath, ['--version'])
if (venvResult.ok) {
return {
installed: true,
version: extractPythonVersion(`${venvResult.stdout}\n${venvResult.stderr}`),
path: venvPythonPath,
command: venvPythonPath,
prefixArgs: [],
source: 'venv',
error: null,
}
}
}
return {
installed: false,
version: null,
path: null,
command: null,
prefixArgs: [],
source: null,
error: null,
}
}
|