Skip to content

Release

Release #6

Workflow file for this run

name: Release
on:
workflow_dispatch:
inputs:
version_bump:
description: "Version bump type (use 'none' to re-publish an existing release)"
required: true
type: choice
options:
- patch
- minor
- major
- none
default: patch
publish_to_gallery:
description: "Publish to PowerShell Gallery"
required: true
type: boolean
default: true
permissions:
contents: write
concurrency:
group: release
cancel-in-progress: false
jobs:
release:
runs-on: ubuntu-latest
environment: CopilotCmdletsEnv
env:
# Supported runtimes — used by both the build and staging steps.
# Keep in sync with SupportedRids in src/ModuleState.cs and $runtimes in build.ps1.
RUNTIMES: 'win-x64 osx-arm64'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- name: Determine version
id: version
shell: pwsh
run: |
# Find latest release tag (used to compute next version or identify the version to re-publish)
$tag = git describe --tags --abbrev=0 2>$null
$hasTag = ($LASTEXITCODE -eq 0 -and $tag)
if (-not $hasTag) {
$major = 0; $minor = 0; $patch = 0
} elseif ($tag -match '^v?(\d+)\.(\d+)\.(\d+)$') {
$major = [int]$Matches[1]; $minor = [int]$Matches[2]; $patch = [int]$Matches[3]
} else {
Write-Error "Could not parse version from tag '$tag'"
exit 1
}
$bump = '${{ inputs.version_bump }}'
if ($bump -eq 'none') {
if (-not $hasTag) {
Write-Error "No existing release tag found — cannot re-publish"
exit 1
}
$version = "$major.$minor.$patch"
Write-Host "Re-publishing existing version: $version (tag: $tag)"
"version=$version" >> $env:GITHUB_OUTPUT
"tag=$tag" >> $env:GITHUB_OUTPUT
"is_new=false" >> $env:GITHUB_OUTPUT
} else {
switch ($bump) {
'major' { $newVersion = "$($major + 1).0.0" }
'minor' { $newVersion = "$major.$($minor + 1).0" }
default { $newVersion = "$major.$minor.$($patch + 1)" }
}
Write-Host "Version: $major.$minor.$patch -> $newVersion"
"version=$newVersion" >> $env:GITHUB_OUTPUT
"tag=v$newVersion" >> $env:GITHUB_OUTPUT
"is_new=true" >> $env:GITHUB_OUTPUT
}
- name: Checkout release tag
if: steps.version.outputs.is_new == 'false'
env:
RELEASE_TAG: ${{ steps.version.outputs.tag }}
run: git checkout "$RELEASE_TAG"
- name: Update module manifest version
if: steps.version.outputs.is_new == 'true'
shell: pwsh
run: |
$manifest = Join-Path $env:GITHUB_WORKSPACE 'CopilotCmdlets.psd1'
$content = Get-Content $manifest -Raw
$updated = $content -replace "ModuleVersion\s*=\s*'\d+\.\d+\.\d+'", "ModuleVersion = '${{ steps.version.outputs.version }}'"
Set-Content -Path $manifest -Value $updated -NoNewline
Write-Host "Updated manifest to ${{ steps.version.outputs.version }}"
- name: Run unit tests
run: dotnet test tests/CopilotCmdlets.Tests.csproj --filter "Category=Unit"
- name: Build all platforms
shell: pwsh
run: |
$project = Join-Path $env:GITHUB_WORKSPACE 'src/CopilotCmdlets.csproj'
$outDir = Join-Path $env:GITHUB_WORKSPACE 'out'
$runtimes = $env:RUNTIMES -split ' '
foreach ($rid in $runtimes) {
$ridOut = Join-Path $outDir $rid
Write-Host "Publishing $rid -> $ridOut"
dotnet publish $project -c Release -r $rid --self-contained false -o $ridOut
if ($LASTEXITCODE -ne 0) { throw "dotnet publish failed for $rid" }
}
# RID-neutral gallery build
$galleryDir = Join-Path $outDir 'gallery'
Write-Host "Publishing RID-neutral gallery build -> $galleryDir"
dotnet publish $project -c Release -o $galleryDir
if ($LASTEXITCODE -ne 0) { throw "dotnet publish failed for gallery build" }
- name: Stage module files and create release zips
id: artifacts
shell: pwsh
run: |
$root = $env:GITHUB_WORKSPACE
$outDir = Join-Path $root 'out'
$manifest = Join-Path $root 'CopilotCmdlets.psd1'
$runtimes = $env:RUNTIMES -split ' '
$version = '${{ steps.version.outputs.version }}'
$galleryDir = Join-Path $outDir 'gallery'
# Stage per-RID builds and create zips
$zipPaths = @()
foreach ($rid in $runtimes) {
$ridOut = Join-Path $outDir $rid
$ridStage = Join-Path $ridOut 'CopilotCmdlets'
if (Test-Path $ridStage) { Remove-Item $ridStage -Recurse -Force }
New-Item -ItemType Directory -Force -Path $ridStage | Out-Null
Get-ChildItem -Path $ridOut -Force |
Where-Object { $_.FullName -ne $ridStage } |
Copy-Item -Destination $ridStage -Recurse -Force
Copy-Item $manifest -Destination $ridStage -Force
$zipPath = Join-Path $root "CopilotCmdlets-$version-$rid.zip"
Compress-Archive -Path (Join-Path $ridStage '*') -DestinationPath $zipPath -Force
Write-Host "Created $zipPath"
$zipPaths += $zipPath
}
# Stage gallery build
$galleryStage = Join-Path $galleryDir 'CopilotCmdlets'
if (Test-Path $galleryStage) { Remove-Item $galleryStage -Recurse -Force }
New-Item -ItemType Directory -Force -Path $galleryStage | Out-Null
Get-ChildItem -Path $galleryDir -Force |
Where-Object { $_.FullName -ne $galleryStage } |
Copy-Item -Destination $galleryStage -Recurse -Force
# Merge per-RID native payloads into gallery stage so Install-PSResource
# ships the Copilot CLI for every supported platform in a single package.
$galleryRuntimes = Join-Path $galleryStage 'runtimes'
New-Item -ItemType Directory -Force -Path $galleryRuntimes | Out-Null
foreach ($rid in $runtimes) {
$ridRuntimes = Join-Path $outDir $rid 'runtimes'
if (Test-Path $ridRuntimes) {
Get-ChildItem -Path $ridRuntimes -Force |
Copy-Item -Destination $galleryRuntimes -Recurse -Force
}
}
Copy-Item $manifest -Destination $galleryStage -Force
# Ensure native CLI binaries have execute permission.
# NuGet/zip packaging strips the Unix execute bit; without this,
# Connect-Copilot fails with 'Permission denied' on macOS/Linux.
# Skip Windows RIDs — .exe files don't need a Unix execute bit.
foreach ($rid in ($runtimes | Where-Object { -not $_.StartsWith('win-') })) {
$nativeDir = Join-Path $galleryRuntimes $rid 'native'
if (Test-Path $nativeDir) {
Get-ChildItem -Path $nativeDir -File | ForEach-Object {
chmod +x $_.FullName
}
}
}
# Output zip paths (newline-separated) for the release step
$joined = $zipPaths -join "`n"
# Use a delimiter for multi-line output
$delimiter = "EOF_$(Get-Random)"
"zip_paths<<$delimiter" >> $env:GITHUB_OUTPUT
$joined >> $env:GITHUB_OUTPUT
$delimiter >> $env:GITHUB_OUTPUT
- name: Commit version bump
if: steps.version.outputs.is_new == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add CopilotCmdlets.psd1
git diff --cached --quiet || git commit -m "Release ${{ steps.version.outputs.tag }}"
if ! git tag --list "${{ steps.version.outputs.tag }}" | grep -q .; then
git tag "${{ steps.version.outputs.tag }}"
fi
git push origin HEAD "${{ steps.version.outputs.tag }}"
- name: Create GitHub Release
if: steps.version.outputs.is_new == 'true'
shell: pwsh
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
$tag = '${{ steps.version.outputs.tag }}'
$zipPaths = '${{ steps.artifacts.outputs.zip_paths }}' -split "`n" | Where-Object { $_ }
Write-Host "Creating release $tag with assets:"
$zipPaths | ForEach-Object { Write-Host " $_" }
$existing = gh release view $tag --json tagName 2>$null
if ($existing) {
Write-Host "Release $tag already exists — uploading assets"
gh release upload $tag @zipPaths --clobber
} else {
gh release create $tag @zipPaths --title $tag --notes "Release $tag"
}
- name: Publish to PowerShell Gallery
if: inputs.publish_to_gallery
shell: pwsh
env:
POWERSHELL_GALLERY_API_KEY: ${{ secrets.POWERSHELL_GALLERY_API_KEY }}
run: |
$galleryStage = Join-Path $env:GITHUB_WORKSPACE 'out/gallery/CopilotCmdlets'
Write-Host "Publishing CopilotCmdlets ${{ steps.version.outputs.version }} to PowerShell Gallery"
Publish-Module -Path $galleryStage -NuGetApiKey $env:POWERSHELL_GALLERY_API_KEY
Write-Host "Successfully published to PowerShell Gallery"