Skip to content

v0.5.0: 3.6x faster startup — native CPU monitor, parallel init, lazy… #10

v0.5.0: 3.6x faster startup — native CPU monitor, parallel init, lazy…

v0.5.0: 3.6x faster startup — native CPU monitor, parallel init, lazy… #10

Workflow file for this run

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"