Skip to content

Auto Release opencode Package #22807

Auto Release opencode Package

Auto Release opencode Package #22807

Workflow file for this run

name: Auto Release opencode Package
on:
schedule:
- cron: '*/15 * * * *'
workflow_dispatch:
inputs:
force:
description: 'Force release even if version matches'
required: false
type: boolean
default: false
dry_run:
description: 'Dry run mode - validate but do not publish'
required: false
type: boolean
default: false
skip_published_check:
description: 'Skip the "already published" check (use if CDN is desynced)'
required: false
type: boolean
default: false
concurrency:
group: opencode-auto-release
cancel-in-progress: false
permissions:
contents: write
actions: write
jobs:
auto-release:
name: Check, Build & Release
runs-on: windows-latest
timeout-minutes: 25
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Cache Chocolatey Dependencies
uses: actions/cache@v4
with:
path: |
C:\ProgramData\chocolatey\bin
C:\ProgramData\chocolatey\lib\ripgrep
C:\ProgramData\chocolatey\lib\fzf
C:\ProgramData\chocolatey\lib\unzip
${{ runner.temp }}/opencode-cache
key: ${{ runner.os }}-choco-deps-v3
restore-keys: |
${{ runner.os }}-choco-deps-
- name: Check for new release
id: check
shell: pwsh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Get current version from nuspec
[xml]$nuspec = Get-Content 'opencode.nuspec'
$currentVersion = $nuspec.package.metadata.version
# Get latest release from GitHub
$headers = @{
'User-Agent' = 'Chocolatey-Updater'
'Accept' = 'application/vnd.github.v3+json'
'Authorization' = "Bearer $env:GITHUB_TOKEN"
}
$maxRetries = 3
$release = $null
for ($i = 1; $i -le $maxRetries; $i++) {
try {
$release = Invoke-RestMethod -Uri 'https://api.github.com/repos/anomalyco/opencode/releases/latest' -Headers $headers -TimeoutSec 30
break
} catch {
if ($_.Exception.Response.StatusCode -eq 403) {
Write-Host "::warning::GitHub API rate limit hit, backing off..."
Start-Sleep -Seconds (30 * $i)
}
if ($i -eq $maxRetries) {
Write-Host "::error::Failed to fetch latest release after $maxRetries attempts: $_"
exit 1
}
Write-Host "::warning::API call attempt $i failed, retrying..."
Start-Sleep -Seconds (5 * $i)
}
}
$latestVersion = $release.tag_name.TrimStart('v')
# Check for Windows x64 asset
$asset = $release.assets | Where-Object { $_.name -eq 'opencode-windows-x64.zip' }
if (-not $asset) {
Write-Host "::notice::No Windows x64 asset available for $latestVersion"
"needs_release=false" >> $env:GITHUB_OUTPUT
exit 0
}
# Determine if release is needed
$forceRelease = '${{ inputs.force }}' -eq 'true'
$needsRelease = ($currentVersion -ne $latestVersion) -or $forceRelease
"needs_release=$($needsRelease.ToString().ToLower())" >> $env:GITHUB_OUTPUT
"current_version=$currentVersion" >> $env:GITHUB_OUTPUT
"latest_version=$latestVersion" >> $env:GITHUB_OUTPUT
"download_url=$($asset.browser_download_url)" >> $env:GITHUB_OUTPUT
"dry_run=${{ inputs.dry_run }}" >> $env:GITHUB_OUTPUT
Write-Host "Current: $currentVersion, Latest: $latestVersion, Needs Release: $needsRelease"
if ('${{ inputs.dry_run }}' -eq 'true') {
Write-Host "::notice::Running in DRY RUN mode"
}
- name: Cache opencode download
if: steps.check.outputs.needs_release == 'true'
uses: actions/cache@v4
id: cache-opencode
with:
path: ${{ runner.temp }}/opencode-cache/opencode-${{ steps.check.outputs.latest_version }}.zip
key: opencode-download-${{ steps.check.outputs.latest_version }}
- name: Download and update package
if: steps.check.outputs.needs_release == 'true'
shell: pwsh
run: |
$version = "${{ steps.check.outputs.latest_version }}"
$downloadUrl = "${{ steps.check.outputs.download_url }}"
$cacheDir = "${{ runner.temp }}/opencode-cache"
$cachedFile = "$cacheDir/opencode-$version.zip"
$tempFile = "$env:TEMP\opencode-$version.zip"
# Create cache directory
if (-not (Test-Path $cacheDir)) {
New-Item -ItemType Directory -Path $cacheDir -Force | Out-Null
}
# Use cached version or download fresh
if ('${{ steps.cache-opencode.outputs.cache-hit }}' -eq 'true' -and (Test-Path $cachedFile)) {
Write-Host "✓ Using cached opencode v$version"
Copy-Item $cachedFile $tempFile
} else {
Write-Host "Downloading opencode v$version..."
$maxRetries = 3
for ($i = 1; $i -le $maxRetries; $i++) {
try {
Invoke-WebRequest -Uri $downloadUrl -OutFile $tempFile -UserAgent 'Chocolatey-Updater' -TimeoutSec 300
break
} catch {
if ($i -eq $maxRetries) {
throw "Failed to download after $maxRetries attempts: $_"
}
Write-Host "::warning::Download attempt $i failed, retrying..."
Start-Sleep -Seconds 5
}
}
Copy-Item $tempFile $cachedFile
}
# Validate downloaded file
if (-not (Test-Path $tempFile) -or (Get-Item $tempFile).Length -eq 0) {
throw "Downloaded file is missing or empty"
}
# Validate zip integrity
try {
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [System.IO.Compression.ZipFile]::OpenRead($tempFile)
$zip.Dispose()
} catch {
throw "Downloaded file is not a valid zip: $_"
}
# Calculate checksum
Write-Host "Calculating checksum..."
$checksum = (Get-FileHash -Path $tempFile -Algorithm SHA256).Hash
Remove-Item $tempFile -Force
# Update nuspec
[xml]$nuspec = Get-Content 'opencode.nuspec'
$nuspec.package.metadata.version = $version
$nuspec.package.metadata.releaseNotes = "https://github.com/anomalyco/opencode/releases/tag/v$version"
$nuspec.Save('opencode.nuspec')
# Update install script checksum
$installScript = Get-Content 'tools\chocolateyinstall.ps1' -Raw
$installScript = $installScript -replace "checksum64\s*=\s*'([A-F0-9]{64}|PLACEHOLDER_CHECKSUM)'", "checksum64 = '$checksum'"
Set-Content -Path 'tools\chocolateyinstall.ps1' -Value $installScript -NoNewline
Write-Host "✓ Package updated to v$version (checksum: $($checksum.Substring(0,16))...)"
- name: Setup Chocolatey
if: steps.check.outputs.needs_release == 'true'
shell: pwsh
run: |
if (Get-Command choco -ErrorAction SilentlyContinue) {
Write-Host "✓ Chocolatey already available"
} else {
Write-Host "Installing Chocolatey..."
Set-ExecutionPolicy Bypass -Scope Process -Force
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072
iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
Write-Host "✓ Chocolatey installed"
}
choco feature enable -n allowGlobalConfirmation
if ($LASTEXITCODE -ne 0) { throw "Failed to enable allowGlobalConfirmation" }
- name: Install dependencies
if: steps.check.outputs.needs_release == 'true'
shell: pwsh
run: |
@('ripgrep', 'fzf', 'unzip') | ForEach-Object {
$libPath = "C:\ProgramData\chocolatey\lib\$_"
if (Test-Path $libPath) {
Write-Host "✓ $_ available (cached)"
} else {
Write-Host "Installing $_..."
choco install $_ -y --limit-output
if ($LASTEXITCODE -ne 0) { throw "Failed to install $_" }
Write-Host "✓ $_ installed"
}
}
- name: Build package
if: steps.check.outputs.needs_release == 'true'
shell: pwsh
run: |
Write-Host "Building package..."
$output = choco pack 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host "::error::Package build failed:"
Write-Host $output
throw "choco pack failed with exit code $LASTEXITCODE"
}
$packageFile = "opencode.${{ steps.check.outputs.latest_version }}.nupkg"
if (-not (Test-Path $packageFile)) {
throw "Expected package file not found: $packageFile"
}
Write-Host "✓ Package built: $packageFile"
- name: Test package installation
if: steps.check.outputs.needs_release == 'true'
shell: pwsh
run: |
Write-Host "Testing package installation..."
$output = choco install opencode -y -s . --force 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host "::error::Package installation failed:"
Write-Host $output
throw "choco install failed with exit code $LASTEXITCODE"
}
# Verify installed version
$installedVersion = opencode --version 2>&1
if ($installedVersion -match '(\d+\.\d+\.\d+)') {
$extractedVersion = $matches[1]
$expectedVersion = '${{ steps.check.outputs.latest_version }}'
if ($extractedVersion -ne $expectedVersion) {
throw "Version mismatch: expected $expectedVersion, got $extractedVersion"
}
Write-Host "✓ Package test passed (v$extractedVersion)"
} else {
throw "Could not extract version from: $installedVersion"
}
- name: Check if version already published
if: steps.check.outputs.needs_release == 'true' && inputs.skip_published_check != true
id: check_published
shell: pwsh
run: |
$version = "${{ steps.check.outputs.latest_version }}"
# Actually try to download the package, not just check metadata
# The OData API can show a version exists before the CDN has the file
Write-Host "Verifying if v$version is actually downloadable from Chocolatey CDN..."
try {
# This URL returns the actual nupkg file - if it 404s, the package isn't really there
$testUrl = "https://community.chocolatey.org/api/v2/package/opencode/$version"
$response = Invoke-WebRequest -Uri $testUrl -Method GET -TimeoutSec 30 -MaximumRedirection 5
# If we get here without error, the package is actually downloadable
if ($response.StatusCode -eq 200 -and $response.Content.Length -gt 1000) {
Write-Host "✓ Version $version is confirmed downloadable (size: $($response.Content.Length) bytes)"
"already_published=true" >> $env:GITHUB_OUTPUT
"publish_reason=Version $version already exists and is downloadable" >> $env:GITHUB_OUTPUT
} else {
Write-Host "::warning::Got response but content seems invalid (status: $($response.StatusCode), size: $($response.Content.Length))"
"already_published=false" >> $env:GITHUB_OUTPUT
}
} catch {
$statusCode = $_.Exception.Response.StatusCode.value__
if ($statusCode -eq 404) {
Write-Host "Package v$version not found on CDN (404) - needs publishing"
} else {
Write-Host "::warning::CDN check failed (status: $statusCode): $($_.Exception.Message)"
Write-Host "::warning::Will attempt to publish anyway"
}
"already_published=false" >> $env:GITHUB_OUTPUT
}
- name: Commit and tag
if: steps.check.outputs.needs_release == 'true' && inputs.dry_run != true
shell: pwsh
run: |
$version = "${{ steps.check.outputs.latest_version }}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
# Check if files changed
git diff --quiet HEAD -- opencode.nuspec tools/chocolateyinstall.ps1
$hasChanges = $LASTEXITCODE -ne 0
if ($hasChanges) {
Write-Host "Committing changes..."
git add opencode.nuspec tools/chocolateyinstall.ps1
git commit -m "chore: update opencode to v$version"
# Handle existing tag
$tagExists = git tag -l "v$version"
if ($tagExists) {
Write-Host "::warning::Tag v$version already exists, recreating..."
git tag -d "v$version"
git push origin ":refs/tags/v$version" 2>&1 | Out-Null
}
git tag -a "v$version" -m "Release v$version"
# Push with retry
$maxRetries = 3
for ($i = 1; $i -le $maxRetries; $i++) {
$pushOutput = git push origin main 2>&1
if ($LASTEXITCODE -eq 0) {
git push origin "v$version"
Write-Host "✓ Changes pushed to repository"
break
}
if ($i -eq $maxRetries) {
Write-Host "::error::Failed to push after $maxRetries attempts: $pushOutput"
exit 1
}
Write-Host "::warning::Push failed, rebasing and retrying..."
git pull --rebase origin main
Start-Sleep -Seconds 2
}
} else {
Write-Host "::notice::No changes detected in package files"
}
- name: Create GitHub Release
if: steps.check.outputs.needs_release == 'true' && inputs.dry_run != true
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.check.outputs.latest_version }}
name: v${{ steps.check.outputs.latest_version }}
body: |
**Full Changelog**: https://github.com/itsrainingmani/opencode-chocolatey/compare/v${{ steps.check.outputs.current_version }}...v${{ steps.check.outputs.latest_version }}
files: opencode.${{ steps.check.outputs.latest_version }}.nupkg
draft: false
prerelease: false
- name: Publish to Chocolatey
if: |
steps.check.outputs.needs_release == 'true' &&
inputs.dry_run != true &&
(steps.check_published.outputs.already_published != 'true' || inputs.skip_published_check == true)
id: publish
shell: pwsh
env:
CHOCO_API_KEY: ${{ secrets.CHOCO_API_KEY }}
run: |
if (-not $env:CHOCO_API_KEY) {
Write-Host "::error::CHOCO_API_KEY secret not configured"
exit 1
}
$version = "${{ steps.check.outputs.latest_version }}"
$packageFile = "opencode.$version.nupkg"
if (-not (Test-Path $packageFile)) {
throw "Package file not found: $packageFile"
}
# Set API key
Write-Host "Setting Chocolatey API key..."
$output = choco apikey -k $env:CHOCO_API_KEY -s https://push.chocolatey.org/ 2>&1
if ($LASTEXITCODE -ne 0) {
Write-Host "::error::Failed to set API key: $output"
throw "choco apikey failed"
}
# Push with retry
Write-Host "Publishing $packageFile to Chocolatey..."
$maxRetries = 3
$lastError = $null
for ($i = 1; $i -le $maxRetries; $i++) {
Write-Host "Push attempt $i of $maxRetries..."
$output = choco push $packageFile -s https://push.chocolatey.org/ --timeout=600 2>&1
if ($LASTEXITCODE -eq 0) {
Write-Host "✓ Successfully published opencode v$version to Chocolatey"
"published=true" >> $env:GITHUB_OUTPUT
exit 0
}
$lastError = $output
$outputStr = $output | Out-String
# Check for "already exists" which is actually success
if ($outputStr -match "already exists|409|conflict") {
Write-Host "::notice::Package v$version already exists on Chocolatey (this is OK)"
"published=true" >> $env:GITHUB_OUTPUT
"already_existed=true" >> $env:GITHUB_OUTPUT
exit 0
}
Write-Host "::warning::Push attempt $i failed (exit code: $LASTEXITCODE):"
Write-Host $output
if ($i -lt $maxRetries) {
Write-Host "Retrying in 10 seconds..."
Start-Sleep -Seconds 10
}
}
Write-Host "::error::Failed to publish after $maxRetries attempts"
Write-Host "Last error: $lastError"
throw "choco push failed after $maxRetries attempts"
- name: Verify publication
if: steps.publish.outputs.published == 'true' && steps.publish.outputs.already_existed != 'true'
shell: pwsh
run: |
$version = "${{ steps.check.outputs.latest_version }}"
Write-Host "Waiting for CDN propagation (60 seconds)..."
Start-Sleep -Seconds 60
# Verify the package is actually downloadable
$maxRetries = 3
for ($i = 1; $i -le $maxRetries; $i++) {
try {
$testUrl = "https://community.chocolatey.org/api/v2/package/opencode/$version"
$response = Invoke-WebRequest -Uri $testUrl -Method GET -TimeoutSec 30
if ($response.StatusCode -eq 200 -and $response.Content.Length -gt 1000) {
Write-Host "✓ Verified: v$version is now downloadable from Chocolatey CDN"
exit 0
}
} catch {
Write-Host "::warning::Verification attempt $i failed: $_"
}
if ($i -lt $maxRetries) {
Write-Host "Waiting 30 more seconds for CDN propagation..."
Start-Sleep -Seconds 30
}
}
Write-Host "::warning::Could not verify package availability - CDN may still be propagating"
Write-Host "::warning::Package should be available within 30 minutes"
- name: Cleanup on failure
if: failure()
shell: pwsh
run: |
Get-ChildItem -Path . -Filter "*.nupkg" | Remove-Item -Force -ErrorAction SilentlyContinue
Write-Host "Cleaned up build artifacts"
- name: Discord Notification
if: steps.check.outputs.needs_release == 'true' && inputs.dry_run != true && success()
shell: pwsh
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }}
run: |
if (-not $env:DISCORD_WEBHOOK_URL) {
Write-Host "::notice::Discord webhook not configured, skipping notification"
exit 0
}
$version = "${{ steps.check.outputs.latest_version }}"
$payload = @{
content = "🎉 **OpenCode Chocolatey Package Released!**"
embeds = @(
@{
title = "New Release: v$version"
description = "Successfully published OpenCode v$version to Chocolatey"
color = 65280
fields = @(
@{ name = "Version"; value = "v$version"; inline = $true }
@{ name = "Package"; value = "[View on Chocolatey](https://community.chocolatey.org/packages/opencode/$version)"; inline = $true }
@{ name = "Changelog"; value = "[View Release](https://github.com/anomalyco/opencode/releases/tag/v$version)"; inline = $true }
)
timestamp = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
}
)
} | ConvertTo-Json -Depth 10
try {
Invoke-RestMethod -Uri $env:DISCORD_WEBHOOK_URL -Method Post -ContentType "application/json" -Body $payload -TimeoutSec 30 | Out-Null
Write-Host "✓ Discord notification sent"
} catch {
Write-Host "::warning::Failed to send Discord notification: $_"
}
- name: Summary
if: always()
shell: pwsh
run: |
$needsRelease = "${{ steps.check.outputs.needs_release }}"
$currentVersion = "${{ steps.check.outputs.current_version }}"
$latestVersion = "${{ steps.check.outputs.latest_version }}"
$dryRun = "${{ inputs.dry_run }}"
$skipCheck = "${{ inputs.skip_published_check }}"
$alreadyPublished = "${{ steps.check_published.outputs.already_published }}"
$published = "${{ steps.publish.outputs.published }}"
$summary = "## Auto-Release Summary`n`n"
$summary += "| Property | Value |`n"
$summary += "|----------|-------|`n"
$summary += "| Current Version | $currentVersion |`n"
$summary += "| Latest Version | $latestVersion |`n"
$summary += "| Needs Release | $needsRelease |`n"
$summary += "| Dry Run | $dryRun |`n"
$summary += "| Skip Published Check | $skipCheck |`n"
$summary += "| Already Published | $alreadyPublished |`n"
$summary += "| Published This Run | $published |`n"
$summary += "| Status | ${{ job.status }} |"
if ($needsRelease -eq 'true') {
if ($dryRun -eq 'true') {
$summary += "`n`n✅ **DRY RUN**: Would have released opencode v$latestVersion"
} elseif ('${{ job.status }}' -eq 'success') {
if ($published -eq 'true') {
$summary += "`n`n✅ **Successfully published** opencode v$latestVersion to Chocolatey"
} elseif ($alreadyPublished -eq 'true') {
$summary += "`n`nℹ️ **Skipped publish**: v$latestVersion already available on Chocolatey"
}
} else {
$summary += "`n`n❌ **Failed** to release opencode v$latestVersion"
}
} else {
$summary += "`n`nℹ️ **No release needed** - already at latest version"
}
$summary | Out-File -FilePath $env:GITHUB_STEP_SUMMARY -Append
# Console output
if ($needsRelease -eq 'true' -and '${{ job.status }}' -eq 'success' -and $dryRun -ne 'true') {
Write-Host "::notice::✅ Auto-released opencode v$latestVersion"
} elseif ($needsRelease -eq 'true' -and '${{ job.status }}' -ne 'success') {
Write-Host "::error::❌ Failed to auto-release opencode v$latestVersion"
}