Files
jiapuapp/design-pipeline/scripts/python-runtime.mjs
T
2026-07-22 17:31:38 +08:00

75 lines
2.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import fs from 'node:fs'
import path from 'node:path'
import { spawnSync } from 'node:child_process'
function canRunPython(executable) {
const result = spawnSync(executable, ['--version'], {
encoding: 'utf8',
stdio: 'ignore',
})
return !result.error && result.status === 0
}
/**
* 解析设计构建管线唯一可用的 Python 入口。
*
* 顺序必须保持稳定:显式配置 > 项目虚拟环境 > Windows Python Manager >
* 系统命令。这样既尊重调用方选择,也不会把 WindowsApps 的零字节执行别名
* 误判为真实解释器。每个候选都必须实际执行 `--version`,文件存在本身不算可用。
*/
export function resolvePythonExecutable({
pipelineDirectory,
environment = process.env,
platform = process.platform,
pathExists = fs.existsSync,
canRun = canRunPython,
} = {}) {
if (!pipelineDirectory) throw new Error('解析 Python 入口时缺少 design-pipeline 目录')
const configured = environment.PYTHON?.trim()
if (configured) {
if (canRun(configured)) return configured
throw new Error(`PYTHON 指定的解释器不可用:${configured}`)
}
const pathApi = platform === 'win32' ? path.win32 : path.posix
const candidates = []
const virtualEnvironment = platform === 'win32'
? pathApi.join(pipelineDirectory, '.venv', 'Scripts', 'python.exe')
: pathApi.join(pipelineDirectory, '.venv', 'bin', 'python')
if (pathExists(virtualEnvironment)) candidates.push(virtualEnvironment)
if (platform === 'win32' && environment.LOCALAPPDATA) {
const managerPython = pathApi.join(environment.LOCALAPPDATA, 'Python', 'bin', 'python.exe')
if (pathExists(managerPython)) candidates.push(managerPython)
}
candidates.push(platform === 'win32' ? 'python' : 'python3', 'python')
for (const candidate of new Set(candidates)) {
if (canRun(candidate)) return candidate
}
throw new Error(
'未找到可用的 Python。请设置 PYTHON 为真实解释器路径,或在 design-pipeline/.venv 中安装项目虚拟环境。',
)
}
/**
* 通过统一入口执行 Python,保证任何构建和测试都不会向源码目录写入 `.pyc`。
* 调用方只提供业务参数;`-B`、进程错误和退出码转换由这里集中负责。
*/
export function runPythonCommand({ executable, args, cwd, spawn = spawnSync } = {}) {
if (typeof executable !== 'string' || executable.trim() === '') throw new Error('执行 Python 时缺少解释器')
if (!Array.isArray(args)) throw new Error('执行 Python 时 args 必须为数组')
if (typeof cwd !== 'string' || cwd.trim() === '') throw new Error('执行 Python 时缺少工作目录')
const result = spawn(executable, ['-B', ...args], {
cwd,
encoding: 'utf8',
stdio: 'inherit',
})
if (result.error) throw new Error(`无法启动 Python${executable}):${result.error.message}`)
if (result.status !== 0) throw new Error(`Python 命令执行失败,退出码:${result.status ?? 'unknown'}`)
return result
}