2026-08-03 12:13:39 +08:00
|
|
|
|
#!/usr/bin/env pwsh
|
|
|
|
|
|
|
2026-08-03 18:55:32 +08:00
|
|
|
|
# cmbuyer Windows 统一安装、验证与启动入口。
|
|
|
|
|
|
# 这个入口只处理开发环境和离线门禁,不连接设备,也不触发采购、下单或付款动作。
|
2026-08-03 12:13:39 +08:00
|
|
|
|
|
|
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
|
|
Set-Location -Path $PSScriptRoot
|
|
|
|
|
|
|
2026-08-03 18:55:32 +08:00
|
|
|
|
# Go 工具链固定用本机版本,避免自动下载造成不可复现的环境漂移。
|
2026-08-03 12:13:39 +08:00
|
|
|
|
$env:GOTOOLCHAIN = "local"
|
|
|
|
|
|
|
2026-08-03 18:55:32 +08:00
|
|
|
|
$AdminDir = Join-Path $PSScriptRoot "admin"
|
2026-08-03 16:27:05 +08:00
|
|
|
|
$ClientDir = Join-Path $PSScriptRoot "client"
|
2026-08-03 18:55:32 +08:00
|
|
|
|
$ClientVenv = Join-Path $ClientDir ".venv"
|
|
|
|
|
|
$ClientPython = Join-Path $ClientVenv "Scripts\python.exe"
|
|
|
|
|
|
|
|
|
|
|
|
function Fail([string]$Message) {
|
|
|
|
|
|
throw $Message
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function Require-Directory([string]$Path, [string]$Name) {
|
|
|
|
|
|
if (-not (Test-Path -LiteralPath $Path -PathType Container)) {
|
|
|
|
|
|
Fail "$Name 目录不存在:$Path。请先完成对应初始化任务。"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function Require-File([string]$Path, [string]$Name) {
|
|
|
|
|
|
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
|
|
|
|
|
|
Fail "$Name 不存在:$Path。请先完成对应初始化任务。"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function Require-Command([string]$Name, [string]$Hint) {
|
|
|
|
|
|
if (-not (Get-Command $Name -ErrorAction SilentlyContinue)) {
|
|
|
|
|
|
Fail "缺少 $Name。$Hint"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function Invoke-Step([string]$Description, [scriptblock]$Action) {
|
|
|
|
|
|
Write-Host "==> $Description"
|
|
|
|
|
|
& $Action
|
|
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
|
|
|
|
Fail "$Description 失败,退出码:$LASTEXITCODE"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function Confirm-Python311OrNewer([string]$PythonExecutable, [string]$Context) {
|
|
|
|
|
|
& $PythonExecutable -c "import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)"
|
|
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
|
|
|
|
Fail "$Context 必须使用 Python 3.11+。不会自动覆盖既有虚拟环境;请手工删除 client/.venv 后重试。"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function Select-PythonLauncherSelector() {
|
|
|
|
|
|
$launcherLines = @(& py -0p)
|
|
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
|
|
|
|
Fail "无法从 Python Launcher 枚举已安装版本。请安装 Python 3.11+ 并确认 py -0p 可运行。"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$minimumVersion = [version]::new(3, 11)
|
|
|
|
|
|
$candidates = @()
|
|
|
|
|
|
foreach ($line in $launcherLines) {
|
|
|
|
|
|
$selectorMatch = [regex]::Match([string]$line, '^\s*-V:(?<selector>\S+)(?:\s+\*)?\s+(?<path>.+?)\s*$')
|
|
|
|
|
|
if (-not $selectorMatch.Success) {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$selector = $selectorMatch.Groups['selector'].Value
|
|
|
|
|
|
$pythonPath = $selectorMatch.Groups['path'].Value
|
|
|
|
|
|
$versionMatch = [regex]::Match($selector, '(?<major>\d+)\.(?<minor>\d+)(?:\.(?<patch>\d+))?')
|
|
|
|
|
|
if (-not $versionMatch.Success) {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$patch = if ($versionMatch.Groups['patch'].Success) { [int]$versionMatch.Groups['patch'].Value } else { 0 }
|
|
|
|
|
|
$version = [version]::new(
|
|
|
|
|
|
[int]$versionMatch.Groups['major'].Value,
|
|
|
|
|
|
[int]$versionMatch.Groups['minor'].Value,
|
|
|
|
|
|
$patch
|
|
|
|
|
|
)
|
|
|
|
|
|
if ($version -ge $minimumVersion) {
|
|
|
|
|
|
$candidates += [PSCustomObject]@{ Selector = $selector; Version = $version; Path = $pythonPath }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ($candidates.Count -eq 0) {
|
|
|
|
|
|
Fail "Python Launcher 未发现 Python 3.11+。当前默认 python 不会作为回退;请安装合规版本后重试。"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$selected = $candidates | Sort-Object @{ Expression = 'Version'; Descending = $true }, @{ Expression = 'Selector'; Descending = $false } | Select-Object -First 1
|
|
|
|
|
|
Write-Host "==> 选择 Python Launcher 解释器:-V:$($selected.Selector)($($selected.Version))"
|
|
|
|
|
|
return $selected
|
|
|
|
|
|
}
|
2026-08-03 12:13:39 +08:00
|
|
|
|
|
|
|
|
|
|
Write-Host "==> 当前目录: $($PWD.Path)"
|
2026-08-03 18:55:32 +08:00
|
|
|
|
Require-Directory $AdminDir "采购服务 admin"
|
|
|
|
|
|
Require-File (Join-Path $AdminDir "go.mod") "采购服务哨兵 admin/go.mod"
|
|
|
|
|
|
Require-Directory $ClientDir "采购工具 client"
|
|
|
|
|
|
Require-File (Join-Path $ClientDir "requirements.txt") "采购工具哨兵 client/requirements.txt"
|
|
|
|
|
|
Require-File (Join-Path $ClientDir "pyproject.toml") "采购工具打包配置 client/pyproject.toml"
|
|
|
|
|
|
Require-Command "go" "请安装 Go 1.23+ 并重新打开 PowerShell。"
|
|
|
|
|
|
if (Test-Path -LiteralPath $ClientVenv) {
|
|
|
|
|
|
if (-not (Test-Path -LiteralPath $ClientVenv -PathType Container)) {
|
|
|
|
|
|
Fail "client/.venv 不是目录。不会覆盖该路径,请人工处理后重试。"
|
2026-08-03 12:13:39 +08:00
|
|
|
|
}
|
2026-08-03 18:55:32 +08:00
|
|
|
|
Require-File $ClientPython "既有 client/.venv Python"
|
|
|
|
|
|
Confirm-Python311OrNewer $ClientPython "既有 client/.venv"
|
2026-08-03 12:13:39 +08:00
|
|
|
|
} else {
|
2026-08-03 18:55:32 +08:00
|
|
|
|
# 默认 python 在本机可能仍指向 3.10;仅在需要新建 venv 时,从 Launcher 中确定性选择最高的合规版本。
|
|
|
|
|
|
Require-Command "py" "请安装 Python Launcher 和 Python 3.11+。"
|
|
|
|
|
|
$PythonRuntime = Select-PythonLauncherSelector
|
|
|
|
|
|
Require-File $PythonRuntime.Path "Python Launcher 选择的解释器"
|
|
|
|
|
|
Invoke-Step "验证所选 Python Launcher 解释器" { & $PythonRuntime.Path -c "import sys; print(sys.version); raise SystemExit(0 if sys.version_info >= (3, 11) else 1)" }
|
|
|
|
|
|
Invoke-Step "创建所选 Python 3.11+ 采购工具虚拟环境" { & $PythonRuntime.Path -m venv $ClientVenv }
|
|
|
|
|
|
Require-File $ClientPython "新建 client/.venv Python"
|
|
|
|
|
|
Confirm-Python311OrNewer $ClientPython "新建 client/.venv"
|
2026-08-03 12:13:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 18:55:32 +08:00
|
|
|
|
# 后续所有采购工具检查与仓库 validator 均使用同一个合规 venv 解释器,避免混用宿主 Python。
|
|
|
|
|
|
Invoke-Step "验证采购工具虚拟环境 Python" { & $ClientPython -c "import sys; print(sys.version); raise SystemExit(0 if sys.version_info >= (3, 11) else 1)" }
|
|
|
|
|
|
|
|
|
|
|
|
Push-Location $AdminDir
|
|
|
|
|
|
try {
|
|
|
|
|
|
Invoke-Step "采购服务:admin/ 同步依赖" { go mod download }
|
|
|
|
|
|
Invoke-Step "采购服务:admin/ 测试" { go test ./... }
|
|
|
|
|
|
Invoke-Step "采购服务:admin/ 静态检查" { go vet ./... }
|
|
|
|
|
|
Invoke-Step "采购服务:admin/ 构建" { go build ./... }
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
Pop-Location
|
2026-08-03 12:13:39 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-03 18:55:32 +08:00
|
|
|
|
Push-Location $ClientDir
|
|
|
|
|
|
try {
|
|
|
|
|
|
Invoke-Step "采购工具:以 editable 方式安装" { & $ClientPython -m pip install -e . }
|
|
|
|
|
|
Invoke-Step "采购工具:验证已安装包" { & $ClientPython -c "import cmbuyer_client; print(cmbuyer_client.__version__)" }
|
|
|
|
|
|
Invoke-Step "采购工具:测试" { & $ClientPython -m unittest discover -s tests -t . }
|
|
|
|
|
|
Invoke-Step "采购工具:语法检查" { & $ClientPython -m compileall -q src tests scripts }
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
Pop-Location
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Invoke-Step "校验 agent 上下文清单" { & $ClientPython scripts/validate_agent_context.py }
|
2026-08-03 12:13:39 +08:00
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
2026-08-03 18:55:32 +08:00
|
|
|
|
Write-Host "==> 基线验证通过。启动命令:"
|
|
|
|
|
|
Write-Host " 采购服务:cd admin; go run ./cmd/server"
|
|
|
|
|
|
Write-Host " 采购工具:cd client; .\.venv\Scripts\python.exe -m cmbuyer_client"
|
2026-08-03 12:13:39 +08:00
|
|
|
|
Write-Host ""
|
2026-08-03 18:55:32 +08:00
|
|
|
|
Write-Host "真机连接与设备验收只能由人工完成;本入口不执行任何采购、下单或付款动作。"
|