#Requires -Version 7.0 <# .SYNOPSIS Проверка среды разработки против versions.env (Windows). .DESCRIPTION Точный аналог tools/dev/doctor.sh: сборка релиза скачивает Go, Node и Bun ровно тех версий, что записаны в versions.env, а машина разработчика не проверялась никак — расхождение обнаруживалось только на Debian, внутри release-сборки. Расхождение не гипотетическое: локальный Go 1.25 собирал проект успешно (директива `go` в go.mod — языковой baseline, а не выбор компилятора), а релизный бинарь компилировался на 1.26.7 и наследовал ЕЁ stdlib. То есть проверялся не тот код, который уезжает в production. Скрипт ничего не устанавливает и не меняет. .PARAMETER Quiet Печатать только итог. #> [CmdletBinding()] param( [switch]$Quiet ) $ErrorActionPreference = 'Stop' $rootDir = Resolve-Path (Join-Path $PSScriptRoot '..' '..') Set-Location $rootDir $versionsPath = Join-Path $rootDir 'versions.env' if (-not (Test-Path $versionsPath)) { Write-Error 'versions.env не найден: doctor запускается из репозитория HY2XS' } # versions.env — POSIX shell, но по построению содержит только присваивания # KEY=value и комментарии: это его объявленный контракт (см. шапку файла). $contract = @{} foreach ($line in Get-Content $versionsPath) { $trimmed = $line.Trim() if (-not $trimmed -or $trimmed.StartsWith('#')) { continue } $separator = $trimmed.IndexOf('=') if ($separator -lt 1) { continue } $contract[$trimmed.Substring(0, $separator).Trim()] = $trimmed.Substring($separator + 1).Trim() } $failures = 0 function Say([string]$Text) { if (-not $Quiet) { Write-Host $Text } } # Версия спрашивается у самого бинаря: что записано в PATH — не тот вопрос, # отвечать должен тот инструмент, который реально запустится. function Get-ToolVersion { param([string]$Command, [string[]]$Arguments) $resolved = Get-Command $Command -ErrorAction SilentlyContinue if (-not $resolved) { return 'not found' } try { $output = & $resolved.Source @Arguments 2>$null | Select-Object -First 1 } catch { return 'unknown' } $match = [regex]::Match([string]$output, '\d+\.\d+\.\d+') if (-not $match.Success) { return 'unknown' } return $match.Value } function Test-Contract { param([string]$Label, [string]$Required, [string]$Found, [string]$Hint) if (-not $Found) { $Found = 'unknown' } Say '' Say "${Label}:" Say " required: $Required" Say " found: $Found" if ($Found -eq $Required) { Say ' OK' return } Say " FAIL — $Hint" $script:failures++ } Say 'HY2XS development environment' Say "contract: versions.env (HY2XS $($contract.HY2XS_VERSION), release line $($contract.HY2XS_RELEASE_LINE))" # Go попадает в production-артефакт целиком: его stdlib компилируется в # hy2xs-admin. Несовпадение здесь — не удобство, а разная поверхность атаки у # разработчика и у релиза. Test-Contract 'Go' $contract.GO_VERSION (Get-ToolVersion 'go' @('version')) ` "локальный Go собирает не ту stdlib, что уедет в релиз; поставьте $($contract.GO_VERSION)" # Node живёт только на build-хосте, но собирает код, который уезжает в production. Test-Contract 'Node' $contract.NODE_VERSION (Get-ToolVersion 'node' @('--version')) ` "frontend-ассеты собираются другой версией Node; поставьте $($contract.NODE_VERSION)" Test-Contract 'pnpm' $contract.PNPM_VERSION (Get-ToolVersion 'pnpm' @('--version')) ` "разрешение зависимостей frontend может разойтись с lock-файлом; поставьте $($contract.PNPM_VERSION)" # Bun физически входит в скомпилированный оркестратор (`bun build --compile`), # поэтому его версия — часть артефакта, а не инструмента. Test-Contract 'Bun' $contract.BUN_VERSION (Get-ToolVersion 'bun' @('--version')) ` "runtime Bun входит в бинарь оркестратора; поставьте $($contract.BUN_VERSION)" # go.mod обязан называть компилятор явно: директива `go` этого не делает. $declaredToolchain = (Select-String -Path (Join-Path $rootDir 'apps' 'go.mod') -Pattern '^toolchain\s+(go[0-9][^\s]*)' | Select-Object -First 1).Matches.Groups[1].Value if (-not $declaredToolchain) { $declaredToolchain = 'отсутствует' } Test-Contract 'apps/go.mod toolchain' "go$($contract.GO_VERSION)" $declaredToolchain ` 'директива toolchain разошлась с versions.env; сборка отвергнет это в verify_go_toolchain_contract' Say '' if ($failures -eq 0) { Say 'Итог: среда разработки совпадает с контрактом сборки.' exit 0 } Say "Итог: расхождений — $failures. Локальные проверки идут не по тому toolchain," Say 'которым собирается релиз.' exit 1