feat: enhanced error handing when inspecting processes. Added an intenal lookup table (map) of certain error codes that might show up on win-witr. also changed "Process Ancestry" to "Why It Exists" to match the original witr. etc #37
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release Build | |
| on: | |
| pull_request: | |
| types: [closed] | |
| branches: | |
| - main | |
| permissions: | |
| contents: write | |
| actions: write | |
| checks: write | |
| jobs: | |
| prepare: | |
| name: Prepare release (check files, version, notes) | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_release: ${{ steps.check_files.outputs.should_release }} | |
| version: ${{ steps.next_version.outputs.version }} | |
| version_number: ${{ steps.next_version.outputs.version_number }} | |
| notes: ${{ steps.parse_commit.outputs.notes }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Check if source files changed | |
| id: check_files | |
| shell: pwsh | |
| run: | | |
| Write-Output "DEBUG: Starting check_files" | |
| Write-Output "DEBUG: PR Event Data - Base: ${{ github.event.pull_request.base.sha }} - Head: ${{ github.event.pull_request.head.sha }}" | |
| $changedFiles = git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | |
| Write-Output "DEBUG: Changed files found:" | |
| Write-Output $changedFiles | |
| $sourceChanged = $false | |
| foreach ($file in $changedFiles) { | |
| if ($file -match '\.(cpp|h|hpp|c|cc|cxx)$') { | |
| $sourceChanged = $true | |
| Write-Output "DEBUG: Source file changed: $file" | |
| break | |
| } | |
| } | |
| Write-Output "DEBUG: sourceChanged is $sourceChanged" | |
| if ($sourceChanged) { | |
| Write-Output "should_release=true" >> $env:GITHUB_OUTPUT | |
| Write-Output "DEBUG: Setting should_release=true" | |
| } else { | |
| Write-Output "should_release=false" >> $env:GITHUB_OUTPUT | |
| Write-Output "DEBUG: Setting should_release=false" | |
| } | |
| - name: Get latest release tag | |
| if: steps.check_files.outputs.should_release == 'true' | |
| id: get_latest_tag | |
| shell: pwsh | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| Write-Output "DEBUG: Starting get_latest_tag" | |
| $repo = "${{ github.repository }}" | |
| Write-Output "DEBUG: Repo is $repo" | |
| $headers = @{"Authorization" = "token $env:GITHUB_TOKEN"; "Accept" = "application/vnd.github.v3+json"} | |
| # Try GitHub Releases API first (includes prereleases) | |
| try { | |
| Write-Output "DEBUG: Fetching releases from API..." | |
| $releases = Invoke-RestMethod -Uri "https://api.github.com/repos/$repo/releases?per_page=100" -Headers $headers -UseBasicParsing | |
| Write-Output "DEBUG: Fetched $($releases.Count) releases" | |
| } catch { | |
| Write-Output "DEBUG: Failed to fetch releases from API: $_" | |
| $releases = @() | |
| } | |
| $candidates = @() | |
| foreach ($r in $releases) { | |
| Write-Output "DEBUG: Checking release tag: $($r.tag_name)" | |
| if ($r.tag_name -match 'v?(\d+)\.(\d+)\.(\d+)') { | |
| $candidates += [PSCustomObject]@{ Tag = $r.tag_name; Major = [int]$Matches[1]; Minor = [int]$Matches[2]; Patch = [int]$Matches[3] } | |
| } | |
| } | |
| if ($candidates.Count -gt 0) { | |
| Write-Output "DEBUG: Found valid candidates in releases." | |
| $sorted = $candidates | Sort-Object -Property @{Expression={$_.Major};Descending=$true}, @{Expression={$_.Minor};Descending=$true}, @{Expression={$_.Patch};Descending=$true} | |
| $latestTag = $sorted[0].Tag | |
| Write-Output "DEBUG: Selected latest tag from API: $latestTag" | |
| Write-Output "latest_tag=$latestTag" >> $env:GITHUB_OUTPUT | |
| exit 0 | |
| } | |
| # Fallback: ensure tags are fetched and inspect local tags | |
| Write-Output "DEBUG: Fallback to local git tags" | |
| git fetch --tags --prune || true | |
| $local = git tag -l "v[0-9]*.[0-9]*.[0-9]*" | |
| Write-Output "DEBUG: Local tags found: $local" | |
| if ($local) { | |
| $sortedTags = $local | ForEach-Object { | |
| if ($_ -match 'v?(\d+)\.(\d+)\.(\d+)') { | |
| [PSCustomObject]@{ Tag = $_; Major = [int]$Matches[1]; Minor = [int]$Matches[2]; Patch = [int]$Matches[3] } | |
| } | |
| } | Sort-Object -Property @{Expression={$_.Major};Descending=$true}, @{Expression={$_.Minor};Descending=$true}, @{Expression={$_.Patch};Descending=$true} | |
| if ($sortedTags.Count -gt 0) { | |
| $latestTag = $sortedTags[0].Tag | |
| Write-Output "DEBUG: Selected latest tag from local tags: $latestTag" | |
| Write-Output "latest_tag=$latestTag" >> $env:GITHUB_OUTPUT | |
| } else { | |
| Write-Output "DEBUG: No valid semantic version tags found locally. Defaulting to v0.0.0" | |
| Write-Output "latest_tag=v0.0.0" >> $env:GITHUB_OUTPUT | |
| } | |
| } else { | |
| Write-Output "DEBUG: No local tags found. Defaulting to v0.0.0" | |
| Write-Output "latest_tag=v0.0.0" >> $env:GITHUB_OUTPUT | |
| } | |
| - name: Determine next version | |
| if: steps.check_files.outputs.should_release == 'true' | |
| id: next_version | |
| shell: pwsh | |
| run: | | |
| $latestTag = "${{ steps.get_latest_tag.outputs.latest_tag }}" | |
| Write-Output "DEBUG: Starting next_version. Input latestTag: $latestTag" | |
| if ($latestTag -match 'v?(\d+)\.(\d+)\.(\d+)') { | |
| $major = [int]$Matches[1] | |
| $minor = [int]$Matches[2] | |
| $patch = [int]$Matches[3] | |
| $patch = $patch + 1 | |
| $newVersion = "v$major.$minor.$patch" | |
| Write-Output "DEBUG: Calculated new version: $newVersion" | |
| Write-Output "version=$newVersion" >> $env:GITHUB_OUTPUT | |
| Write-Output "version_number=$major.$minor.$patch" >> $env:GITHUB_OUTPUT | |
| } else { | |
| Write-Error "Could not parse version from tag: $latestTag" | |
| exit 1 | |
| } | |
| - name: Parse commit message for release notes | |
| if: steps.check_files.outputs.should_release == 'true' | |
| id: parse_commit | |
| shell: pwsh | |
| run: | | |
| Write-Output "DEBUG: Starting parse_commit" | |
| # Get all commits from the PR (base to head) | |
| $baseSha = "${{ github.event.pull_request.base.sha }}" | |
| $headSha = "${{ github.event.pull_request.head.sha }}" | |
| Write-Output "DEBUG: Base SHA: $baseSha" | |
| Write-Output "DEBUG: Head SHA: $headSha" | |
| # Get all commit messages in the PR | |
| $commits = @(git log --pretty=format:"%h|%s|%b" "$baseSha..$headSha") | |
| Write-Output "DEBUG: Found $($commits.Count) commits in PR" | |
| # Initialize categorized lists | |
| $features = @() | |
| $fixes = @() | |
| $performance = @() | |
| $refactors = @() | |
| $docs = @() | |
| $tests = @() | |
| $chores = @() | |
| $builds = @() | |
| $ci = @() | |
| $style = @() | |
| $reverts = @() | |
| $others = @() | |
| # Parse each commit | |
| foreach ($line in $commits) { | |
| if ([string]::IsNullOrWhiteSpace($line)) { continue } | |
| $parts = $line -split '\|', 3 | |
| if ($parts.Count -lt 2) { continue } | |
| $commitHash = $parts[0] | |
| $subject = $parts[1] | |
| $body = if ($parts.Count -eq 3) { $parts[2] } else { "" } | |
| Write-Output "DEBUG: Processing commit $commitHash : $subject" | |
| # Parse conventional commit format | |
| if ($subject -match '^(fix|feat|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?:\s*(.+)$') { | |
| $type = $Matches[1] | |
| $scope = if ($Matches[2]) { $Matches[2] } else { "" } | |
| $description = $Matches[3] | |
| $entry = "- $description ($commitHash)" | |
| switch ($type) { | |
| 'feat' { $features += $entry } | |
| 'fix' { $fixes += $entry } | |
| 'perf' { $performance += $entry } | |
| 'refactor' { $refactors += $entry } | |
| 'docs' { $docs += $entry } | |
| 'test' { $tests += $entry } | |
| 'chore' { $chores += $entry } | |
| 'build' { $builds += $entry } | |
| 'ci' { $ci += $entry } | |
| 'style' { $style += $entry } | |
| 'revert' { $reverts += $entry } | |
| default { $others += "- $subject ($commitHash)" } | |
| } | |
| } else { | |
| # Non-conventional commit | |
| $others += "- $subject ($commitHash)" | |
| } | |
| } | |
| # Build release notes | |
| $releaseNotes = "## Changes`n`n" | |
| if ($features.Count -gt 0) { | |
| $releaseNotes += "### ✨ Features`n" | |
| $releaseNotes += ($features -join "`n") + "`n`n" | |
| } | |
| if ($fixes.Count -gt 0) { | |
| $releaseNotes += "### 🐛 Fixes`n" | |
| $releaseNotes += ($fixes -join "`n") + "`n`n" | |
| } | |
| if ($performance.Count -gt 0) { | |
| $releaseNotes += "### ⚡ Performance`n" | |
| $releaseNotes += ($performance -join "`n") + "`n`n" | |
| } | |
| if ($refactors.Count -gt 0) { | |
| $releaseNotes += "### ♻️ Refactoring`n" | |
| $releaseNotes += ($refactors -join "`n") + "`n`n" | |
| } | |
| if ($docs.Count -gt 0) { | |
| $releaseNotes += "### 📝 Documentation`n" | |
| $releaseNotes += ($docs -join "`n") + "`n`n" | |
| } | |
| if ($tests.Count -gt 0) { | |
| $releaseNotes += "### ✅ Tests`n" | |
| $releaseNotes += ($tests -join "`n") + "`n`n" | |
| } | |
| if ($builds.Count -gt 0) { | |
| $releaseNotes += "### 📦 Build`n" | |
| $releaseNotes += ($builds -join "`n") + "`n`n" | |
| } | |
| if ($chores.Count -gt 0) { | |
| $releaseNotes += "### 🔧 Chores`n" | |
| $releaseNotes += ($chores -join "`n") + "`n`n" | |
| } | |
| if ($ci.Count -gt 0) { | |
| $releaseNotes += "### 🔄 CI/CD`n" | |
| $releaseNotes += ($ci -join "`n") + "`n`n" | |
| } | |
| if ($style.Count -gt 0) { | |
| $releaseNotes += "### 💄 Style`n" | |
| $releaseNotes += ($style -join "`n") + "`n`n" | |
| } | |
| if ($reverts.Count -gt 0) { | |
| $releaseNotes += "### ⏪ Reverts`n" | |
| $releaseNotes += ($reverts -join "`n") + "`n`n" | |
| } | |
| if ($others.Count -gt 0) { | |
| $releaseNotes += "### 📋 Other Changes`n" | |
| $releaseNotes += ($others -join "`n") + "`n`n" | |
| } | |
| Write-Output "DEBUG: releaseNotes: $releaseNotes" | |
| $releaseNotes += "---`n*Based on commits from $baseSha to $headSha*" | |
| $delimiter = "EOF_$(Get-Random)" | |
| Write-Output "DEBUG: Writing notes to output using delimiter $delimiter" | |
| Write-Output "notes<<$delimiter" >> $env:GITHUB_OUTPUT | |
| Write-Output $releaseNotes >> $env:GITHUB_OUTPUT | |
| Write-Output $delimiter >> $env:GITHUB_OUTPUT | |
| build: | |
| name: Build matrix for multiple Windows architectures | |
| needs: prepare | |
| if: needs.prepare.outputs.should_release == 'true' | |
| runs-on: windows-latest | |
| strategy: | |
| matrix: | |
| arch: [x64, x86, arm64] | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Compile for ${{ matrix.arch }} | |
| shell: cmd | |
| run: | | |
| @echo off | |
| REM Find vcvarsall.bat dynamically | |
| for /f "usebackq tokens=*" %%i in (`"C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do ( | |
| set "VS_PATH=%%i" | |
| ) | |
| if not exist "%VS_PATH%\VC\Auxiliary\Build\vcvarsall.bat" ( | |
| echo Error: vcvarsall.bat not found. | |
| exit /b 1 | |
| ) | |
| REM Map architecture for cross-compilation (host_target) | |
| REM GitHub Actions windows-latest runners are x64, so we need x64_<target> for cross-compilation | |
| set "TARGET_ARCH=${{ matrix.arch }}" | |
| set "VCVARS_ARCH=%TARGET_ARCH%" | |
| if "%TARGET_ARCH%"=="x86" set "VCVARS_ARCH=x64_x86" | |
| if "%TARGET_ARCH%"=="arm64" set "VCVARS_ARCH=x64_arm64" | |
| REM Initialize environment for the target architecture | |
| call "%VS_PATH%\VC\Auxiliary\Build\vcvarsall.bat" %VCVARS_ARCH% | |
| set outName=win-witr-${{ matrix.arch }}.exe | |
| echo Compiling %outName%... | |
| cl /O2 /Ot /GL /std:c++20 /EHsc main.cpp /DUNICODE /D_UNICODE /Fe:%outName% | |
| if errorlevel 1 exit /b 1 | |
| - name: Upload build artifact for ${{ matrix.arch }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: win-witr-${{ matrix.arch }} | |
| path: win-witr-${{ matrix.arch }}.exe | |
| create-release: | |
| name: Create GitHub Release with all artifacts | |
| needs: [prepare, build] | |
| if: needs.prepare.outputs.should_release == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout repository for tagging | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Download all build artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: artifacts | |
| - name: List artifacts | |
| run: ls -R artifacts | |
| - name: Flatten artifact directory structure | |
| run: | | |
| set -e | |
| echo "Flattening artifact directory structure..." | |
| find artifacts -name "*.exe" -type f -exec mv -n {} artifacts/ \; | |
| echo "Removing empty subdirectories..." | |
| find artifacts -mindepth 1 -type d -delete || echo "Warning: Failed to delete some subdirectories in artifacts/ - this may indicate remaining files or permission issues" | |
| echo "Flattened artifacts directory:" | |
| ls -la artifacts/ | |
| - name: Ensure release tag exists and push | |
| if: needs.prepare.outputs.version != '' | |
| shell: bash | |
| run: | | |
| set -e | |
| echo "DEBUG: Ensure Release Tag Exists" | |
| echo "DEBUG: received version: '${{ needs.prepare.outputs.version }}'" | |
| echo "DEBUG: received sha: $(git rev-parse HEAD)" | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| TAG="${{ needs.prepare.outputs.version }}" | |
| echo "Preparing tag: $TAG" | |
| if [ -z "$TAG" ]; then | |
| echo "No tag provided; cannot create release" | |
| exit 1 | |
| fi | |
| if git rev-parse "$TAG" >/dev/null 2>&1; then | |
| echo "DEBUG: Tag $TAG already exists locally" | |
| else | |
| # Explicitly tag the HEAD sha to match the checked out state | |
| HEAD_SHA=$(git rev-parse HEAD) | |
| echo "DEBUG: Creating tag $TAG on $HEAD_SHA" | |
| git tag -a "$TAG" -m "Release $TAG" "$HEAD_SHA" | |
| echo "DEBUG: Created local tag $TAG" | |
| fi | |
| # Push tag (skip errors if it already exists on remote) | |
| echo "DEBUG: Pushing tag $TAG" | |
| git push origin "$TAG" || echo "Tag push failed or already exists on remote" | |
| - name: Create Release using GH CLI | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAG: ${{ needs.prepare.outputs.version }} | |
| NOTES: ${{ needs.prepare.outputs.notes }} | |
| run: | | |
| TITLE="win-witr $TAG" | |
| echo "Checking if release '$TAG' exists..." | |
| if gh release view "$TAG" > /dev/null 2>&1; then | |
| echo "Release '$TAG' found. Deleting to recreate..." | |
| gh release delete "$TAG" -y | |
| else | |
| echo "Release '$TAG' does not exist." | |
| fi | |
| echo "Locating artifacts..." | |
| # Find the files anywhere in the current directory, robust to structure changes | |
| FILE_X64=$(find . -type f -name "win-witr-x64.exe" | head -n 1) | |
| FILE_X86=$(find . -type f -name "win-witr-x86.exe" | head -n 1) | |
| FILE_ARM64=$(find . -type f -name "win-witr-arm64.exe" | head -n 1) | |
| echo "Found x64 artifact: $FILE_X64" | |
| echo "Found x86 artifact: $FILE_X86" | |
| echo "Found arm64 artifact: $FILE_ARM64" | |
| if [[ -z "$FILE_X64" || -z "$FILE_X86" || -z "$FILE_ARM64" ]]; then | |
| echo "Error: Could not locate all expected artifacts." | |
| echo "Searching for any .exe files:" | |
| find . -name "*.exe" | |
| exit 1 | |
| fi | |
| echo "Creating release '$TAG'..." | |
| gh release create "$TAG" \ | |
| "$FILE_X64" \ | |
| "$FILE_X86" \ | |
| "$FILE_ARM64" \ | |
| --title "$TITLE" \ | |
| --notes "$NOTES" \ | |
| --prerelease |