Payload/scripts/harness/lib/RepoRuntime.psm1
|
#Requires -Version 5.1 Set-StrictMode -Version Latest function Test-RepoGitVersionSupported { param([Parameter(Mandatory = $true)][string]$GitExe) $version = (& $GitExe --version 2>$null) -replace '^git version\s+', '' -replace '\.windows.*$', '' if (-not $version) { return $false } $parts = $version.Split('.') if ($parts.Count -lt 2) { return $false } $major = [int]$parts[0] $minor = [int]$parts[1] return ($major -gt 2) -or ($major -eq 2 -and $minor -ge 32) } function Test-RepoNodeVersionSupported { param([Parameter(Mandatory = $true)][string]$NodeExe) & $NodeExe -e "const [M]=process.versions.node.split('.').map(Number); process.exit(M>=24?0:1)" 2>$null | Out-Null return $LASTEXITCODE -eq 0 } function Resolve-RepoPortableGitExe { if ([string]::IsNullOrWhiteSpace($env:LOCALAPPDATA)) { return $null } $path = Join-Path $env:LOCALAPPDATA 'Programs\PortableGit\cmd\git.exe' if ((Test-Path $path) -and (Test-RepoGitVersionSupported -GitExe $path)) { return $path } return $null } function Initialize-RepoUtf8Console { <# .SYNOPSIS Set the session console encodings to UTF-8. .DESCRIPTION Windows-only chcp.com is skipped when SystemRoot is empty so Linux and Dev Container hosts do not Join-Path a missing env var. #> [CmdletBinding()] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidGlobalVars', '', Justification = 'OutputEncoding preference must be session-scoped so native npm/node pipes decode UTF-8')] param() # Windows PowerShell 5.1 decodes native-command stdout with [Console]::OutputEncoding # (OEM 437 by default). Node/Vitest writes UTF-8 checkmarks (U+2713 = bytes E2 9C 93), # which OEM renders as Gamma-pound-o. UTF-8 + code page 65001 keeps the glyph intact # in the Cursor terminal capture. $utf8 = New-Object System.Text.UTF8Encoding $false [Console]::InputEncoding = $utf8 [Console]::OutputEncoding = $utf8 $global:OutputEncoding = $utf8 if (-not [string]::IsNullOrWhiteSpace($env:SystemRoot)) { $chcp = Join-Path $env:SystemRoot 'System32\chcp.com' if (Test-Path -LiteralPath $chcp) { & $chcp 65001 | Out-Null } } } function Initialize-RepoShellEnvironment { <# .SYNOPSIS Resolve git and Node for harness scripts on the current host. .DESCRIPTION Windows-only PortableGit and fnm fallbacks are skipped when LOCALAPPDATA or APPDATA are empty. #> [CmdletBinding()] param( [string]$LogPrefix = 'repo-runtime' ) Initialize-RepoUtf8Console $gitCmd = Get-Command git -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 $gitExe = if ($gitCmd) { [string]$gitCmd.Source } else { '' } if ($gitCmd -and (Test-RepoGitVersionSupported -GitExe $gitExe)) { Write-Output "[$LogPrefix] Using git: $gitExe ($(git --version))" } else { if ($gitCmd) { Write-Output "[$LogPrefix] PATH git unsupported: $gitExe ($(git --version))" } $portableGit = Resolve-RepoPortableGitExe if ($portableGit) { $gitDir = Split-Path $portableGit -Parent $env:PATH = "$gitDir;$env:PATH" Write-Output "[$LogPrefix] Using PortableGit: $portableGit ($(& $portableGit --version))" } else { Write-Output "[$LogPrefix] Git >=2.32 not found; lint-staged and some hooks may fail." } } $nodeCmd = Get-Command node -CommandType Application -ErrorAction SilentlyContinue | Select-Object -First 1 $nodeExe = if ($nodeCmd) { [string]$nodeCmd.Source } else { '' } if ($nodeCmd -and (Test-RepoNodeVersionSupported -NodeExe $nodeExe)) { Write-Output "[$LogPrefix] Using node: $nodeExe ($(node -v))" return } if ($nodeCmd) { Write-Output "[$LogPrefix] PATH node unsupported: $nodeExe ($(node -v))" } else { Write-Output "[$LogPrefix] Node.js not found in PATH." } $fnmBase = $null if (-not [string]::IsNullOrWhiteSpace($env:APPDATA)) { $fnmBase = Join-Path $env:APPDATA 'fnm\node-versions' } if ([string]::IsNullOrWhiteSpace($fnmBase) -or -not (Test-Path $fnmBase)) { throw 'Node >=24 required (package.json engines). Install via fnm or fix PATH.' } $fallback = Get-ChildItem -Path (Join-Path $fnmBase 'v*\installation\node.exe') -ErrorAction SilentlyContinue | Sort-Object { [version]($_.Directory.Parent.Name.TrimStart('v')) } | Where-Object { Test-RepoNodeVersionSupported -NodeExe $_.FullName } | Select-Object -Last 1 if (-not $fallback) { throw "No supported fnm Node >=24 under $fnmBase" } $nodeDir = $fallback.Directory.FullName $env:PATH = "$nodeDir;$env:PATH" Write-Output "[$LogPrefix] Using fnm node: $($fallback.FullName) ($(& $fallback.FullName -v))" } function Invoke-RepoNpmScript { [CmdletBinding()] param( [Parameter(Mandatory = $true)][string]$RepoRoot, [Parameter(Mandatory = $true)][string]$Name, [string[]]$NpmArguments = @(), [string]$LogPrefix = 'repo-runtime' ) Push-Location $RepoRoot try { Initialize-RepoShellEnvironment -LogPrefix $LogPrefix | Out-Null $pkg = Get-Content 'package.json' -Raw | ConvertFrom-Json if (-not $pkg.scripts.PSObject.Properties[$Name]) { throw "package.json has no script '$Name'" } Write-Output "[$LogPrefix] npm run $Name$(if ($NpmArguments.Count) { " -- $($NpmArguments -join ' ')" })" if ($NpmArguments.Count -gt 0) { & npm run $Name -- @NpmArguments } else { & npm run $Name } if ($LASTEXITCODE -ne 0) { throw "npm run $Name failed with exit code $LASTEXITCODE" } } finally { Pop-Location } } Export-ModuleMember -Function Initialize-RepoUtf8Console, Initialize-RepoShellEnvironment, Invoke-RepoNpmScript, Test-RepoNodeVersionSupported, Test-RepoGitVersionSupported |