Skip to the content.

Deletes GitHub Actions artifacts using the gh CLI on Windows.

<#
.SYNOPSIS
    Deletes GitHub Actions artifacts using the gh CLI on Windows.

.DESCRIPTION
    Requires gh CLI installed and authenticated (gh auth login).
    Install gh: winget install --id GitHub.cli

.PARAMETER Repo
    Repository in OWNER/REPO format.

.PARAMETER OlderThanDays
    Only delete artifacts older than N days. 0 (default) deletes all.

.PARAMETER DryRun
    Preview without deleting.

.EXAMPLE
    .\Delete-Artifacts.ps1 -Repo "myorg/myrepo" -DryRun
    .\Delete-Artifacts.ps1 -Repo "myorg/myrepo"
    .\Delete-Artifacts.ps1 -Repo "myorg/myrepo" -OlderThanDays 7
#>

param(
    [Parameter(Mandatory = $true)]
    [string]$Repo,

    [int]$OlderThanDays = 0,

    [switch]$DryRun
)

# Verify gh is installed and authenticated
if (-not (Get-Command gh -ErrorAction SilentlyContinue)) {
    Write-Error "gh CLI not found. Install with: winget install --id GitHub.cli"
    exit 1
}

$authStatus = gh auth status 2>&1
if ($LASTEXITCODE -ne 0) {
    Write-Error "gh CLI not authenticated. Run: gh auth login"
    exit 1
}

Write-Host "Fetching artifacts from $Repo..." -ForegroundColor Cyan

# Compute cutoff date if filtering by age
$cutoff = if ($OlderThanDays -gt 0) {
    (Get-Date).ToUniversalTime().AddDays(-$OlderThanDays)
} else { $null }

if ($cutoff) {
    Write-Host "Will only delete artifacts created before: $($cutoff.ToString('o'))" -ForegroundColor Cyan
}

# Fetch all artifacts (gh handles pagination with --paginate)
try {
    $json = gh api --paginate "repos/$Repo/actions/artifacts" --jq '.artifacts[]' 2>$null
    if ($LASTEXITCODE -ne 0) {
        Write-Error "Failed to fetch artifacts from $Repo. Check repo name and permissions."
        exit 1
    }
    $artifacts = $json | ConvertFrom-Json
}
catch {
    Write-Error "Failed to parse artifacts: $_"
    exit 1
}

if (-not $artifacts -or $artifacts.Count -eq 0) {
    Write-Host "No artifacts found." -ForegroundColor Yellow
    exit 0
}

Write-Host "Found $($artifacts.Count) artifact(s)." -ForegroundColor Cyan
Write-Host ""

$deleted = 0
$skipped = 0
$failed  = 0

foreach ($artifact in $artifacts) {
    $createdAt = [datetime]$artifact.created_at
    $sizeMB    = [math]::Round($artifact.size_in_bytes / 1MB, 2)
    $label     = "[$($artifact.id)] $($artifact.name) ($sizeMB MB, $($createdAt.ToString('yyyy-MM-dd')))"

    if ($cutoff -and $createdAt -gt $cutoff) {
        Write-Host "  SKIP         $label" -ForegroundColor DarkGray
        $skipped++
        continue
    }

    if ($DryRun) {
        Write-Host "  WOULD DELETE $label" -ForegroundColor Yellow
        $deleted++
        continue
    }

    gh api -X DELETE "repos/$Repo/actions/artifacts/$($artifact.id)" --silent 2>$null
    if ($LASTEXITCODE -eq 0) {
        Write-Host "  DELETED      $label" -ForegroundColor Green
        $deleted++
    }
    else {
        Write-Host "  FAILED       $label" -ForegroundColor Red
        $failed++
    }
}

Write-Host ""
Write-Host "Summary:" -ForegroundColor Cyan
Write-Host "  Deleted/Would delete: $deleted"
Write-Host "  Skipped             : $skipped"
Write-Host "  Failed              : $failed"