From 8744c859425ac928632f125343fec71a5ee06f84 Mon Sep 17 00:00:00 2001 From: Zaine Date: Fri, 8 May 2026 14:38:51 +0100 Subject: [PATCH] zone 2 --- .gitea/workflows/build.yml | 20 ++++ scripts/gated-analysis.ps1 | 192 +++++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 .gitea/workflows/build.yml create mode 100644 scripts/gated-analysis.ps1 diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..798bd49 --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,20 @@ +name: Zone + +on: + push: + branches: + - main + + schedule: + - cron: "0 0 * * *" + + workflow_dispatch: + +jobs: + build: + runs-on: site-build + + steps: + - name: Check out repo + uses: actions/checkout@v4 + diff --git a/scripts/gated-analysis.ps1 b/scripts/gated-analysis.ps1 new file mode 100644 index 0000000..53199ef --- /dev/null +++ b/scripts/gated-analysis.ps1 @@ -0,0 +1,192 @@ +#!/usr/bin/env pwsh +[CmdletBinding()] +param( + [string]$DiscordWebhookUrl = $env:DISCORD_WEBHOOK_URL, + [string]$Repository = $env:GITHUB_REPOSITORY, + [string]$RefName = $env:GITHUB_REF_NAME, + [string]$Sha = $env:GITHUB_SHA, + [string]$RunUrl = $env:GITHUB_SERVER_URL +) + +$ErrorActionPreference = 'Stop' +$exitCode = 0 +$smellFailures = New-Object System.Collections.Generic.List[string] +$smellWarnings = New-Object System.Collections.Generic.List[string] + +function Add-SmellFailure { + param([string]$Message) + $script:smellFailures.Add($Message) +} + +function Add-SmellWarning { + param([string]$Message) + $script:smellWarnings.Add($Message) +} + +function Get-ProjectFiles { + $excludeDirs = @('.git', 'node_modules', 'dist', 'build', 'coverage') + Get-ChildItem -Path . -File -Recurse -Force | + Where-Object { + $pathParts = $_.FullName.Substring((Get-Location).Path.Length).Split([IO.Path]::DirectorySeparatorChar, [StringSplitOptions]::RemoveEmptyEntries) + -not ($pathParts | Where-Object { $excludeDirs -contains $_ }) + } +} + +function Test-CodeSmells { + Write-Host 'Running gated smell checks...' + + $files = @(Get-ProjectFiles) + $debtMarkerPattern = '\b(T' + 'ODO|F' + 'IXME|H' + 'ACK|X' + 'XX|W' + 'IP)\b' + foreach ($file in $files) { + $relativePath = Resolve-Path -Relative $file.FullName + + if ($file.Length -gt 1MB) { + Add-SmellWarning "$relativePath is larger than 1 MB." + } + + $content = Get-Content -Raw -LiteralPath $file.FullName + + if ($content -match '(?m)^(<<<<<<<|=======|>>>>>>>)') { + Add-SmellFailure "$relativePath contains merge conflict markers." + } + + if ($content -match '(?i)(discord(?:_webhook)?_url|password|api[_-]?key|secret)\s*[:=]\s*["''][^"'']{8,}["'']') { + Add-SmellFailure "$relativePath may contain a committed secret." + } + + $todoMatches = [regex]::Matches($content, "(?i)$debtMarkerPattern") + if ($todoMatches.Count -gt 0) { + Add-SmellWarning "$relativePath contains $($todoMatches.Count) debt marker(s)." + } + } + + if ($smellFailures.Count -gt 0) { + Write-Host 'Smell checks failed:' + $smellFailures | ForEach-Object { Write-Host " - $_" } + return $false + } + + if ($smellWarnings.Count -gt 0) { + Write-Host 'Smell check warnings:' + $smellWarnings | ForEach-Object { Write-Host " - $_" } + } else { + Write-Host 'No smell check warnings found.' + } + + return $true +} + +function Invoke-TestSuite { + Write-Host 'Running test suite...' + $tempRoot = if ($env:RUNNER_TEMP) { $env:RUNNER_TEMP } else { [IO.Path]::GetTempPath() } + $outputFile = Join-Path $tempRoot 'zone-test-output.txt' + + & npm test 2>&1 | Tee-Object -FilePath $outputFile + $testExitCode = $LASTEXITCODE + $output = Get-Content -Raw -LiteralPath $outputFile + + return [pscustomobject]@{ + Passed = ($testExitCode -eq 0) + ExitCode = $testExitCode + Output = $output.Trim() + } +} + +function Get-ShortText { + param( + [string]$Text, + [int]$MaxLength = 1400 + ) + + if ([string]::IsNullOrWhiteSpace($Text)) { + return 'No output captured.' + } + + if ($Text.Length -le $MaxLength) { + return $Text + } + + return $Text.Substring($Text.Length - $MaxLength) +} + +function Send-DiscordReport { + param( + [bool]$SmellsPassed, + [object]$Tests + ) + + if ([string]::IsNullOrWhiteSpace($DiscordWebhookUrl)) { + Write-Host 'DISCORD_WEBHOOK_URL is not set; skipping Discord report.' + return + } + + $status = if ($SmellsPassed -and $Tests.Passed) { 'passed' } else { 'failed' } + $color = if ($status -eq 'passed') { 5763719 } else { 15548997 } + $shaShort = if ($Sha -and $Sha.Length -ge 7) { $Sha.Substring(0, 7) } else { $Sha } + $repoText = if ($Repository) { $Repository } else { 'zone' } + $refText = if ($RefName) { $RefName } else { 'unknown ref' } + $smellStatus = if ($SmellsPassed) { 'passed' } else { 'failed' } + $testStatus = if ($Tests.Passed) { 'passed' } else { "failed (exit $($Tests.ExitCode))" } + $warningText = if ($smellWarnings.Count -gt 0) { ($smellWarnings -join "`n") } else { 'None' } + $failureText = if ($smellFailures.Count -gt 0) { ($smellFailures -join "`n") } else { 'None' } + + $description = @" +Repository: $repoText +Ref: $refText +Commit: $shaShort + +Smell checks: $smellStatus +Tests: $testStatus +"@ + + $fields = @( + @{ + name = 'Smell failures' + value = Get-ShortText -Text $failureText -MaxLength 900 + inline = $false + }, + @{ + name = 'Smell warnings' + value = Get-ShortText -Text $warningText -MaxLength 900 + inline = $false + }, + @{ + name = 'Test output' + value = "``````text`n$(Get-ShortText -Text $Tests.Output -MaxLength 900)`n``````" + inline = $false + } + ) + + $payload = @{ + username = 'Zone CI' + embeds = @( + @{ + title = "Gated analysis $status" + description = $description.Trim() + color = $color + fields = $fields + timestamp = (Get-Date).ToUniversalTime().ToString('o') + } + ) + } + + if ($RunUrl -and $env:GITHUB_RUN_ID) { + $payload.embeds[0].url = "$RunUrl/$repoText/actions/runs/$($env:GITHUB_RUN_ID)" + } + + Invoke-RestMethod -Method Post -Uri $DiscordWebhookUrl -ContentType 'application/json' -Body ($payload | ConvertTo-Json -Depth 8) | Out-Null + Write-Host 'Discord report published.' +} + +$smellsPassed = Test-CodeSmells +if (-not $smellsPassed) { + $exitCode = 1 +} + +$tests = Invoke-TestSuite +if (-not $tests.Passed) { + $exitCode = $tests.ExitCode +} + +Send-DiscordReport -SmellsPassed $smellsPassed -Tests $tests +exit $exitCode