param( [string]$RepositoryRoot = "", [string]$ThothctlPath = "" ) $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest if ([string]::IsNullOrWhiteSpace($RepositoryRoot)) { $RepositoryRoot = (& git rev-parse --show-toplevel).Trim() if ($LASTEXITCODE -ne 0) { throw "git could not resolve the repository root" } } $RepositoryRoot = [System.IO.Path]::GetFullPath($RepositoryRoot) $tracked = @(& git -C $RepositoryRoot ls-files) if ($LASTEXITCODE -ne 0) { throw "git ls-files failed" } $scriptRelativePath = "scripts/test-windows-clone-contract.ps1" $eolAttribute = (& git -C $RepositoryRoot check-attr eol -- $scriptRelativePath).Trim() if ($LASTEXITCODE -ne 0 -or -not $eolAttribute.EndsWith("eol: crlf", [System.StringComparison]::OrdinalIgnoreCase)) { throw "the Windows contract script must have the repository eol=crlf attribute" } $scriptBytes = [System.IO.File]::ReadAllBytes((Join-Path $RepositoryRoot $scriptRelativePath)) if (-not ($scriptBytes -contains [byte]0x0D)) { throw "the Windows contract script was not checked out with CRLF bytes" } $offenders = [System.Collections.Generic.List[string]]::new() foreach ($relativePath in $tracked) { $name = [System.IO.Path]::GetFileName($relativePath) $mustBeLf = $relativePath.EndsWith(".sh", [System.StringComparison]::OrdinalIgnoreCase) -or $relativePath.EndsWith(".yml", [System.StringComparison]::OrdinalIgnoreCase) -or $relativePath.EndsWith(".yaml", [System.StringComparison]::OrdinalIgnoreCase) -or $name.Equals("Dockerfile", [System.StringComparison]::OrdinalIgnoreCase) -or $name.StartsWith("Dockerfile.", [System.StringComparison]::OrdinalIgnoreCase) -or $name.EndsWith(".Dockerfile", [System.StringComparison]::OrdinalIgnoreCase) if (-not $mustBeLf) { continue } $absolutePath = Join-Path $RepositoryRoot $relativePath $bytes = [System.IO.File]::ReadAllBytes($absolutePath) if ($bytes -contains [byte]0x0D) { $offenders.Add($relativePath) } } if ($offenders.Count -ne 0) { throw "CR byte 0x0D found in tracked LF contract files: $($offenders -join ', ')" } $temporaryRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("thothii-windows-contract-" + [guid]::NewGuid().ToString("N")) $savedEnvironment = @{ THT_WORKSPACE_GIT_REMOTE = $env:THT_WORKSPACE_GIT_REMOTE PI_AUTH_FILE = $env:PI_AUTH_FILE THT_SECRETS_FILE = $env:THT_SECRETS_FILE } try { [System.IO.Directory]::CreateDirectory($temporaryRoot) | Out-Null $piAuth = Join-Path $temporaryRoot "pi-auth.json" $secrets = Join-Path $temporaryRoot "thothii.secrets" [System.IO.File]::WriteAllText($piAuth, "{}`n", [System.Text.UTF8Encoding]::new($false)) [System.IO.File]::WriteAllText($secrets, "THT_MODEL_API_KEY=windows-contract`n", [System.Text.UTF8Encoding]::new($false)) $env:THT_WORKSPACE_GIT_REMOTE = "https://git.example.invalid/platform/thoth-workspaces.git" $env:PI_AUTH_FILE = $piAuth $env:THT_SECRETS_FILE = $secrets $composeFiles = @( "--project-directory", $RepositoryRoot, "-f", (Join-Path $RepositoryRoot "compose.yaml"), "-f", (Join-Path $RepositoryRoot "deploy/compose.local.yaml") ) $services = @(& docker compose @composeFiles config --services) if ($LASTEXITCODE -ne 0) { throw "Docker Compose could not render the Windows clone" } if ((($services | Sort-Object) -join ",") -ne "core,frontend") { throw "rendered Windows stack must contain exactly core and frontend" } & docker compose @composeFiles config --quiet if ($LASTEXITCODE -ne 0) { throw "Docker Compose rejected the Windows clone" } if ([string]::IsNullOrWhiteSpace($ThothctlPath)) { $ThothctlPath = Join-Path $RepositoryRoot "dist/thothctl/thothctl-windows-amd64.exe" } $ThothctlPath = [System.IO.Path]::GetFullPath($ThothctlPath) if (-not [System.IO.File]::Exists($ThothctlPath)) { throw "Windows thothctl binary is missing: $ThothctlPath" } & $ThothctlPath --help | Out-Null if ($LASTEXITCODE -ne 0) { throw "Windows thothctl invocation failed" } } finally { foreach ($name in $savedEnvironment.Keys) { [System.Environment]::SetEnvironmentVariable($name, $savedEnvironment[$name], "Process") } if ([System.IO.Directory]::Exists($temporaryRoot)) { Remove-Item -LiteralPath $temporaryRoot -Recurse -Force } } Write-Output "Windows clone, LF-byte, Compose render, and thothctl invocation contracts passed."