v0.5.0: 3.6x faster startup — native CPU monitor, parallel init, lazy… #10
Workflow file for this run
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 & Distribute | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: release-${{ github.ref_name }} | |
| cancel-in-progress: false | |
| env: | |
| CARGO_TERM_COLOR: always | |
| ZIP_NAME: pstop-windows-x86_64.zip | |
| jobs: | |
| # ── Build release binaries ────────────────────────────────────────── | |
| build: | |
| runs-on: windows-latest | |
| outputs: | |
| sha256: ${{ steps.checksum.outputs.sha256 }} | |
| version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Cache cargo registry & build | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| target | |
| key: ${{ runner.os }}-cargo-release-${{ hashFiles('**/Cargo.lock') }} | |
| - name: Extract and verify version from tag | |
| id: version | |
| shell: pwsh | |
| run: | | |
| $tagVer = "${{ github.ref_name }}".TrimStart('v') | |
| echo "version=$tagVer" >> $env:GITHUB_OUTPUT | |
| # Verify Cargo.toml version matches the git tag | |
| $cargoVer = (Select-String -Path Cargo.toml -Pattern '^version\s*=\s*"([^"]+)"').Matches[0].Groups[1].Value | |
| if ($cargoVer -ne $tagVer) { | |
| throw "Version mismatch: Cargo.toml has '$cargoVer' but tag is 'v$tagVer'" | |
| } | |
| Write-Host "Version verified: $tagVer" | |
| - name: Build release binaries | |
| run: cargo build --release | |
| - name: Run tests | |
| run: cargo test --release | |
| - name: Verify binaries exist | |
| shell: pwsh | |
| run: | | |
| if (-not (Test-Path target/release/pstop.exe)) { throw "pstop.exe not found" } | |
| if (-not (Test-Path target/release/htop.exe)) { throw "htop.exe not found" } | |
| Write-Host "pstop.exe size: $((Get-Item target/release/pstop.exe).Length) bytes" | |
| Write-Host "htop.exe size: $((Get-Item target/release/htop.exe).Length) bytes" | |
| - name: Create release archive | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Path staging -Force | |
| Copy-Item target/release/pstop.exe staging/ | |
| Copy-Item target/release/htop.exe staging/ | |
| Copy-Item README.md staging/ | |
| Copy-Item LICENSE staging/ | |
| Compress-Archive -Path staging/* -DestinationPath $env:ZIP_NAME | |
| - name: Compute SHA256 checksum | |
| id: checksum | |
| shell: pwsh | |
| run: | | |
| $hash = (Get-FileHash $env:ZIP_NAME -Algorithm SHA256).Hash.ToLower() | |
| echo "sha256=$hash" >> $env:GITHUB_OUTPUT | |
| # Also write a checksum file for the release | |
| "$hash $env:ZIP_NAME" | Set-Content "$env:ZIP_NAME.sha256" | |
| Write-Host "SHA256: $hash" | |
| - name: Upload build artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release-binaries | |
| path: | | |
| pstop-windows-x86_64.zip | |
| pstop-windows-x86_64.zip.sha256 | |
| retention-days: 5 | |
| # ── Create GitHub Release ─────────────────────────────────────────── | |
| github-release: | |
| needs: build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release-binaries | |
| - name: Create GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| name: "pstop v${{ needs.build.outputs.version }}" | |
| body: | | |
| ## pstop v${{ needs.build.outputs.version }} | |
| **htop for Windows** — beautiful, fast TUI system monitor. | |
| ### Install | |
| ``` | |
| cargo install pstop | |
| ``` | |
| ``` | |
| choco install pstop | |
| ``` | |
| ``` | |
| winget install marlocarlo.pstop | |
| ``` | |
| ### SHA256 (zip) | |
| ``` | |
| ${{ needs.build.outputs.sha256 }} | |
| ``` | |
| files: | | |
| pstop-windows-x86_64.zip | |
| pstop-windows-x86_64.zip.sha256 | |
| draft: false | |
| prerelease: false | |
| # ── Publish to crates.io ──────────────────────────────────────────── | |
| publish-crates: | |
| needs: build | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Check if version already published | |
| id: check | |
| shell: pwsh | |
| run: | | |
| $version = "${{ needs.build.outputs.version }}" | |
| $output = cargo search pstop --limit 1 2>&1 | Out-String | |
| if ($output -match "pstop = `"$version`"") { | |
| echo "skip=true" >> $env:GITHUB_OUTPUT | |
| Write-Host "Version $version already on crates.io — skipping" | |
| } else { | |
| echo "skip=false" >> $env:GITHUB_OUTPUT | |
| } | |
| - name: Publish to crates.io | |
| if: steps.check.outputs.skip != 'true' | |
| shell: pwsh | |
| env: | |
| CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} | |
| run: | | |
| $output = cargo publish --allow-dirty 2>&1 | Out-String | |
| Write-Host $output | |
| if ($LASTEXITCODE -ne 0) { | |
| if ($output -match 'already exists') { | |
| Write-Host "Version already published to crates.io — treating as success" | |
| exit 0 | |
| } | |
| throw "cargo publish failed with exit code $LASTEXITCODE" | |
| } | |
| # ── Publish to Chocolatey ─────────────────────────────────────────── | |
| publish-choco: | |
| needs: [build, github-release] | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release-binaries | |
| - name: Verify artifact integrity | |
| id: verify | |
| shell: pwsh | |
| run: | | |
| $expected = "${{ needs.build.outputs.sha256 }}" | |
| $actual = (Get-FileHash $env:ZIP_NAME -Algorithm SHA256).Hash.ToLower() | |
| if ($actual -ne $expected) { | |
| throw "Checksum mismatch! Expected: $expected Got: $actual" | |
| } | |
| Write-Host "Artifact checksum verified: $actual" | |
| echo "sha256=$actual" >> $env:GITHUB_OUTPUT | |
| - name: Prepare Chocolatey package | |
| shell: pwsh | |
| run: | | |
| $version = "${{ needs.build.outputs.version }}" | |
| $url = "https://github.com/${{ github.repository }}/releases/download/v${version}/$env:ZIP_NAME" | |
| $checksum = "${{ steps.verify.outputs.sha256 }}" | |
| # Update nuspec version | |
| $nuspec = Get-Content choco/pstop.nuspec -Raw | |
| $nuspec = $nuspec -replace '<version>[^<]+</version>', "<version>$version</version>" | |
| Set-Content choco/pstop.nuspec $nuspec | |
| # Update install script with download URL and checksum | |
| $install = Get-Content choco/tools/chocolateyinstall.ps1 -Raw | |
| $install = $install -replace '__DOWNLOAD_URL__', $url | |
| $install = $install -replace '__CHECKSUM__', $checksum | |
| Set-Content choco/tools/chocolateyinstall.ps1 $install | |
| # Generate VERIFICATION.txt (required by Chocolatey moderation for | |
| # packages that download binaries from an external URL) | |
| @" | |
| VERIFICATION | |
| Verification is intended to assist the Chocolatey moderators and community | |
| in verifying that this package's contents are trustworthy. | |
| This package downloads pstop from the official GitHub Releases page: | |
| $url | |
| The SHA256 checksum embedded in chocolateyinstall.ps1 is: | |
| $checksum | |
| You can verify this yourself by downloading the zip and running: | |
| (Get-FileHash pstop-windows-x86_64.zip -Algorithm SHA256).Hash | |
| The source code is available at: | |
| https://github.com/marlocarlo/pstop | |
| The binaries are built from source using 'cargo build --release' via the | |
| GitHub Actions workflow at: | |
| https://github.com/marlocarlo/pstop/actions | |
| License: MIT | |
| https://github.com/marlocarlo/pstop/blob/master/LICENSE | |
| "@ -replace '(?m)^ ', '' | Set-Content "choco/tools/VERIFICATION.txt" | |
| Write-Host "=== nuspec ===" | |
| Get-Content choco/pstop.nuspec | |
| Write-Host "`n=== install script ===" | |
| Get-Content choco/tools/chocolateyinstall.ps1 | |
| Write-Host "`n=== VERIFICATION.txt ===" | |
| Get-Content choco/tools/VERIFICATION.txt | |
| - name: Pack Chocolatey package | |
| shell: pwsh | |
| run: | | |
| cd choco | |
| choco pack pstop.nuspec | |
| # Verify the nupkg was created | |
| $version = "${{ needs.build.outputs.version }}" | |
| $nupkg = "pstop.${version}.nupkg" | |
| if (-not (Test-Path $nupkg)) { throw "Failed to create $nupkg" } | |
| Write-Host "Created: $nupkg ($((Get-Item $nupkg).Length) bytes)" | |
| - name: Push to Chocolatey | |
| shell: pwsh | |
| env: | |
| CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }} | |
| run: | | |
| $ErrorActionPreference = 'Continue' | |
| $version = "${{ needs.build.outputs.version }}" | |
| cd choco | |
| $output = choco push "pstop.${version}.nupkg" --source https://push.chocolatey.org/ --api-key $env:CHOCO_API_KEY 2>&1 | Out-String | |
| Write-Host $output | |
| if ($LASTEXITCODE -ne 0) { | |
| if ($output -match 'already exists' -or $output -match 'submitted state' -or $output -match '409' -or $output -match '403') { | |
| Write-Host "::warning::Chocolatey push blocked (package may already exist or be in moderation) — treating as success" | |
| exit 0 | |
| } | |
| throw "choco push failed with exit code $LASTEXITCODE`: $output" | |
| } | |
| # ── Publish to WinGet ─────────────────────────────────────────────── | |
| publish-winget: | |
| needs: [build, github-release] | |
| runs-on: windows-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install wingetcreate | |
| shell: pwsh | |
| run: | | |
| Invoke-WebRequest -Uri "https://aka.ms/wingetcreate/latest" -OutFile wingetcreate.exe | |
| Write-Host "wingetcreate downloaded" | |
| - name: Download and verify release asset | |
| id: asset | |
| shell: pwsh | |
| run: | | |
| $version = "${{ needs.build.outputs.version }}" | |
| $url = "https://github.com/${{ github.repository }}/releases/download/v${version}/$env:ZIP_NAME" | |
| Write-Host "Downloading: $url" | |
| Invoke-WebRequest -Uri $url -OutFile release.zip -MaximumRetryCount 3 | |
| $sha256 = (Get-FileHash release.zip -Algorithm SHA256).Hash.ToUpper() | |
| $expected = "${{ needs.build.outputs.sha256 }}".ToUpper() | |
| if ($sha256 -ne $expected) { | |
| throw "SHA256 mismatch! Expected: $expected Got: $sha256" | |
| } | |
| Write-Host "SHA256 verified: $sha256" | |
| echo "sha256=$sha256" >> $env:GITHUB_OUTPUT | |
| echo "url=$url" >> $env:GITHUB_OUTPUT | |
| - name: Check for existing open PRs | |
| id: check_pr | |
| shell: pwsh | |
| env: | |
| GH_TOKEN: ${{ secrets.WINGET_GH_PAT }} | |
| run: | | |
| $version = "${{ needs.build.outputs.version }}" | |
| $searchQuery = "repo:microsoft/winget-pkgs is:pr is:open marlocarlo.pstop $version in:title" | |
| $result = gh api "search/issues?q=$([uri]::EscapeDataString($searchQuery))&per_page=5" 2>&1 | ConvertFrom-Json | |
| if ($result.total_count -gt 0) { | |
| echo "skip=true" >> $env:GITHUB_OUTPUT | |
| Write-Host "Open PR already exists for marlocarlo.pstop $version — skipping" | |
| } else { | |
| echo "skip=false" >> $env:GITHUB_OUTPUT | |
| } | |
| - name: Submit to WinGet | |
| if: steps.check_pr.outputs.skip != 'true' | |
| shell: pwsh | |
| env: | |
| WINGET_GH_PAT: ${{ secrets.WINGET_GH_PAT }} | |
| run: | | |
| $version = "${{ needs.build.outputs.version }}" | |
| $url = "${{ steps.asset.outputs.url }}" | |
| Write-Host "Submitting wingetcreate update for marlocarlo.pstop v${version}..." | |
| .\wingetcreate.exe update marlocarlo.pstop ` | |
| --urls $url ` | |
| --version $version ` | |
| --submit ` | |
| --token $env:WINGET_GH_PAT 2>&1 | |
| if ($LASTEXITCODE -eq 0) { | |
| Write-Host "Successfully submitted WinGet update via wingetcreate" | |
| exit 0 | |
| } | |
| Write-Host "wingetcreate update failed — falling back to manual manifest submission..." | |
| $sha256 = "${{ steps.asset.outputs.sha256 }}" | |
| $manifestDir = "manifests" | |
| New-Item -ItemType Directory -Path $manifestDir -Force | |
| # --- Version manifest --- | |
| @" | |
| # yaml-language-server: `$schema=https://aka.ms/winget-manifest.version.1.10.0.schema.json | |
| PackageIdentifier: marlocarlo.pstop | |
| PackageVersion: $version | |
| DefaultLocale: en-US | |
| ManifestType: version | |
| ManifestVersion: 1.10.0 | |
| "@ -replace '(?m)^ ', '' | Set-Content "$manifestDir/marlocarlo.pstop.yaml" -NoNewline | |
| # --- Installer manifest --- | |
| @" | |
| # yaml-language-server: `$schema=https://aka.ms/winget-manifest.installer.1.10.0.schema.json | |
| PackageIdentifier: marlocarlo.pstop | |
| PackageVersion: $version | |
| MinimumOSVersion: 10.0.17763.0 | |
| InstallerType: zip | |
| NestedInstallerType: portable | |
| NestedInstallerFiles: | |
| - RelativeFilePath: pstop.exe | |
| PortableCommandAlias: pstop | |
| - RelativeFilePath: htop.exe | |
| PortableCommandAlias: htop | |
| Installers: | |
| - Architecture: x64 | |
| InstallerUrl: $url | |
| InstallerSha256: $sha256 | |
| UpgradeBehavior: uninstallPrevious | |
| ManifestType: installer | |
| ManifestVersion: 1.10.0 | |
| "@ -replace '(?m)^ ', '' | Set-Content "$manifestDir/marlocarlo.pstop.installer.yaml" -NoNewline | |
| # --- Default locale manifest --- | |
| @" | |
| # yaml-language-server: `$schema=https://aka.ms/winget-manifest.defaultLocale.1.10.0.schema.json | |
| PackageIdentifier: marlocarlo.pstop | |
| PackageVersion: $version | |
| PackageLocale: en-US | |
| Publisher: marlocarlo | |
| PublisherUrl: https://github.com/marlocarlo | |
| PublisherSupportUrl: https://github.com/marlocarlo/pstop/issues | |
| PackageName: pstop | |
| PackageUrl: https://github.com/marlocarlo/pstop | |
| License: MIT | |
| LicenseUrl: https://github.com/marlocarlo/pstop/blob/master/LICENSE | |
| ShortDescription: htop for Windows — beautiful, fast TUI system monitor | |
| Description: |- | |
| pstop is a full drop-in replacement for htop on Windows. Beautiful TUI system monitor | |
| with per-core CPU bars, memory/swap/network monitoring, tree view, process management, | |
| search/filter, 7 color schemes, and full mouse support. Installs both pstop and htop commands. | |
| Tags: | |
| - cli | |
| - htop | |
| - monitor | |
| - process-viewer | |
| - rust | |
| - system-monitor | |
| - terminal | |
| - tui | |
| - windows | |
| ManifestType: defaultLocale | |
| ManifestVersion: 1.10.0 | |
| "@ -replace '(?m)^ ', '' | Set-Content "$manifestDir/marlocarlo.pstop.locale.en-US.yaml" -NoNewline | |
| Write-Host "=== Generated manifests ===" | |
| Get-ChildItem $manifestDir -Filter *.yaml | ForEach-Object { | |
| Write-Host "`n--- $($_.Name) ---" | |
| Get-Content $_.FullName | |
| } | |
| # Validate manifests with winget if available | |
| $wingetAvailable = Get-Command winget -ErrorAction SilentlyContinue | |
| if ($wingetAvailable) { | |
| Write-Host "`nValidating manifests..." | |
| winget validate --manifest $manifestDir | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "winget validate failed — manifest does not conform to schema" | |
| } | |
| Write-Host "Validation passed" | |
| } | |
| # Submit via wingetcreate | |
| Write-Host "`nSubmitting manifest to winget-pkgs..." | |
| .\wingetcreate.exe submit ` | |
| --token $env:WINGET_GH_PAT ` | |
| --prtitle "Update: marlocarlo.pstop version $version" ` | |
| $manifestDir | |
| if ($LASTEXITCODE -ne 0) { | |
| throw "wingetcreate submit failed" | |
| } | |
| Write-Host "Successfully submitted WinGet manifest" |