uscheduler/utils/Release-ToGitHub/Release-ToGitHub.ps1
2026-02-08 22:21:55 +01:00

696 lines
25 KiB
PowerShell

<#
.SYNOPSIS
Automated GitHub release script for MaksIT.UScheduler.
.DESCRIPTION
Creates a GitHub release by performing the following steps:
Pre-flight checks:
- Detects current branch (main or dev)
- On main: requires clean working directory; on dev: uncommitted changes allowed
- Reads version from .csproj (source of truth)
- On main: requires matching tag (vX.Y.Z format)
- Ensures version consistency with CHANGELOG.md
- Confirms GitHub CLI authentication via GH_TOKEN (main branch only)
Test execution:
- Runs all unit tests via Run-Tests.ps1
- Aborts release if any tests fail
- Displays coverage summary (line, branch, method)
Build and release:
- Publishes the .NET project in Release configuration
- Copies Scripts folder into the release
- Creates a versioned ZIP archive
- Extracts release notes from CHANGELOG.md
- Pushes tag to remote if not already present (main branch only)
- Creates (or recreates) the GitHub release with assets (main branch only)
Branch-based behavior (configurable in scriptsettings.json):
- On dev branch: Local build only, no tag required, uncommitted changes allowed
- On release branch: Full GitHub release, tag required, clean working directory required
- On other branches: Blocked
.NOTES
File: Release-ToGitHub.ps1
Author: Maksym Sadovnychyy (MAKS-IT)
Requires: dotnet, git, gh (GitHub CLI - required on main branch only)
Configuration is loaded from scriptsettings.json in the same directory.
Set the GitHub token in an environment variable specified by github.tokenEnvVar.
.EXAMPLE
.\Release-ToGitHub.ps1
Runs the release process using settings from scriptsettings.json.
On dev branch: creates local build (no tag needed).
On main branch: publishes to GitHub (tag required).
.EXAMPLE
# Recommended workflow:
# 1. On dev branch: Update version in .csproj and CHANGELOG.md
# 2. Commit changes
# 3. Run: .\Release-ToGitHub.ps1
# (creates local build for testing - no tag needed)
# 4. Test the build
# 5. Merge to main: git checkout main && git merge dev
# 6. Create tag: git tag v1.0.1
# 7. Run: .\Release-ToGitHub.ps1
# (publishes to GitHub)
#>
# No parameters - behavior is controlled by current branch (configured in scriptsettings.json):
# - dev branch -> Local build only (no tag required, uncommitted changes allowed)
# - release branch -> Full release to GitHub (tag required, clean working directory)
# Load settings from scriptsettings.json
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$settingsPath = Join-Path $scriptDir "scriptsettings.json"
if (-not (Test-Path $settingsPath)) {
Write-Error "Settings file not found: $settingsPath"
exit 1
}
$settings = Get-Content $settingsPath -Raw | ConvertFrom-Json
# Import TestRunner module
$modulePath = Join-Path (Split-Path $scriptDir -Parent) "TestRunner.psm1"
if (-not (Test-Path $modulePath)) {
Write-Error "TestRunner module not found at: $modulePath"
exit 1
}
Import-Module $modulePath -Force
# Set GH_TOKEN from custom environment variable for GitHub CLI authentication
$tokenEnvVar = $settings.github.tokenEnvVar
$env:GH_TOKEN = [System.Environment]::GetEnvironmentVariable($tokenEnvVar)
# Paths from settings (resolve relative to script directory)
$csprojPaths = @()
if ($settings.paths.csprojPath -is [System.Collections.IEnumerable] -and -not ($settings.paths.csprojPath -is [string])) {
foreach ($path in $settings.paths.csprojPath) {
$csprojPaths += [System.IO.Path]::GetFullPath((Join-Path $scriptDir $path))
}
}
else {
$csprojPaths += [System.IO.Path]::GetFullPath((Join-Path $scriptDir $settings.paths.csprojPath))
}
$stagingDir = [System.IO.Path]::GetFullPath((Join-Path $scriptDir $settings.paths.stagingDir))
$releaseDir = [System.IO.Path]::GetFullPath((Join-Path $scriptDir $settings.paths.releaseDir))
$changelogPath = [System.IO.Path]::GetFullPath((Join-Path $scriptDir $settings.paths.changelogPath))
$scriptsPath = [System.IO.Path]::GetFullPath((Join-Path $scriptDir $settings.paths.scriptsPath))
$testProjectPath = [System.IO.Path]::GetFullPath((Join-Path $scriptDir $settings.paths.testProject))
# Release naming patterns
$zipNamePattern = $settings.release.zipNamePattern
$releaseTitlePattern = $settings.release.releaseTitlePattern
# Branch configuration
$releaseBranch = $settings.branches.release
$devBranch = $settings.branches.dev
# Project configuration (avoid hardcoding project names)
$projectsSettings = $settings.projects
$scheduleManagerCsprojEndsWith = $projectsSettings.scheduleManagerCsprojEndsWith
$uschedulerCsprojEndsWith = $projectsSettings.uschedulerCsprojEndsWith
$scheduleManagerAppSettingsFile = $projectsSettings.scheduleManagerAppSettingsFile
$uschedulerAppSettingsFile = $projectsSettings.uschedulerAppSettingsFile
$scheduleManagerServiceBinPath = $projectsSettings.scheduleManagerServiceBinPath
$uschedulerLogDir = $projectsSettings.uschedulerLogDir
$scriptsRelativeToExe = $projectsSettings.scriptsRelativeToExe
# Helper: ensure required commands exist
function Assert-Command {
param([string]$cmd)
if (-not (Get-Command $cmd -ErrorAction SilentlyContinue)) {
Write-Error "Required command '$cmd' is missing. Aborting."
exit 1
}
}
# Helper: extract a csproj property (first match)
function Get-CsprojPropertyValue {
param(
[Parameter(Mandatory=$true)][xml]$csproj,
[Parameter(Mandatory=$true)][string]$propertyName
)
$propNode = $csproj.Project.PropertyGroup |
Where-Object { $_.$propertyName } |
Select-Object -First 1
if ($propNode) {
return $propNode.$propertyName
}
return $null
}
# Helper: resolve output assembly name for published exe
function Resolve-ProjectExeName {
param(
[Parameter(Mandatory=$true)][string]$projPath
)
[xml]$csproj = Get-Content $projPath
$assemblyName = Get-CsprojPropertyValue -csproj $csproj -propertyName "AssemblyName"
if ($assemblyName) {
return $assemblyName
}
return [System.IO.Path]::GetFileNameWithoutExtension($projPath)
}
# Helper: find csproj by configured suffix
function Find-CsprojByEndsWith {
param(
[Parameter(Mandatory=$true)][string[]]$paths,
[Parameter(Mandatory=$true)][string]$endsWith
)
if (-not $endsWith) {
return $null
}
return $paths | Where-Object { $_ -like "*$endsWith" } | Select-Object -First 1
}
Assert-Command dotnet
Assert-Command git
# gh command check deferred until after branch detection (only needed on main branch)
Write-Host ""
Write-Host "==================================================" -ForegroundColor Cyan
Write-Host "RELEASE BUILD" -ForegroundColor Cyan
Write-Host "==================================================" -ForegroundColor Cyan
Write-Host ""
# ==============================================================================
# PRE-FLIGHT CHECKS
# ==============================================================================
# 1. Detect current branch and determine release mode
Write-Host "Detecting current branch..." -ForegroundColor Gray
$currentBranch = git rev-parse --abbrev-ref HEAD 2>$null
if ($LASTEXITCODE -ne 0 -or -not $currentBranch) {
Write-Error "Could not determine current branch."
exit 1
}
$currentBranch = $currentBranch.Trim()
Write-Host " Branch: $currentBranch" -ForegroundColor Green
$isDevBranch = $currentBranch -eq $devBranch
$isReleaseBranch = $currentBranch -eq $releaseBranch
if (-not $isDevBranch -and -not $isReleaseBranch) {
Write-Error "Releases can only be created from '$releaseBranch' or '$devBranch' branches. Current branch: $currentBranch"
exit 1
}
if ($isDevBranch) {
Write-Host " Dev branch ($devBranch) - local build only (no GitHub release)." -ForegroundColor Yellow
}
else {
Write-Host " Release branch ($releaseBranch) - will publish to GitHub." -ForegroundColor Cyan
Assert-Command gh
}
# 2. Check for uncommitted changes (required on main, allowed on dev)
$gitStatus = git status --porcelain 2>$null
if ($gitStatus) {
if ($isReleaseBranch) {
Write-Error "Working directory has uncommitted changes. Commit or stash them before releasing."
Write-Host ""
Write-Host "Uncommitted files:" -ForegroundColor Yellow
$gitStatus | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow }
exit 1
}
else {
Write-Host " Uncommitted changes detected (allowed on dev branch)." -ForegroundColor Yellow
}
} else {
Write-Host " Working directory is clean." -ForegroundColor Green
}
# 3. Get version from csproj (source of truth)
Write-Host "Reading version(s) from csproj(s)..." -ForegroundColor Gray
$projectVersions = @{}
foreach ($projPath in $csprojPaths) {
if (-not (Test-Path $projPath)) {
Write-Error "Csproj not found at: $projPath"
exit 1
}
[xml]$csproj = Get-Content $projPath
$version = Get-CsprojPropertyValue -csproj $csproj -propertyName "Version"
if (-not $version) {
Write-Error "Version not found in $projPath"
exit 1
}
$projectVersions[$projPath] = $version
Write-Host " $([System.IO.Path]::GetFileName($projPath)): $version" -ForegroundColor Green
}
# Use the first project's version as the main version for tag/release
$version = $projectVersions[$csprojPaths[0]]
# 4. Handle tag based on branch
if ($isReleaseBranch) {
# Main branch: tag is required and must match version
Write-Host "Checking for tag on current commit..." -ForegroundColor Gray
$tag = git describe --tags --exact-match HEAD 2>$null
if ($LASTEXITCODE -ne 0 -or -not $tag) {
Write-Error "No tag found on current commit. Create a tag: git tag v$version"
exit 1
}
$tag = $tag.Trim()
if ($tag -notmatch '^v(\d+\.\d+\.\d+)$') {
Write-Error "Tag '$tag' does not match expected format 'vX.Y.Z' (e.g., v$version)."
exit 1
}
$tagVersion = $Matches[1]
if ($tagVersion -ne $version) {
Write-Error "Tag version ($tagVersion) does not match csproj version ($version)."
Write-Host " Either update the tag or the csproj version." -ForegroundColor Yellow
exit 1
}
Write-Host " Tag found: $tag (matches csproj)" -ForegroundColor Green
}
else {
# Dev branch: no tag required, use version from csproj
$tag = "v$version"
Write-Host " Using version from csproj (no tag required on dev)." -ForegroundColor Gray
}
# 5. Verify CHANGELOG.md has matching version entry
Write-Host "Verifying CHANGELOG.md..." -ForegroundColor Gray
if (-not (Test-Path $changelogPath)) {
Write-Error "CHANGELOG.md not found at: $changelogPath"
exit 1
}
$changelog = Get-Content $changelogPath -Raw
if ($changelog -notmatch '##\s+v(\d+\.\d+\.\d+)') {
Write-Error "No version entry found in CHANGELOG.md"
exit 1
}
$changelogVersion = $Matches[1]
if ($changelogVersion -ne $version) {
Write-Error "Csproj version ($version) does not match latest CHANGELOG.md version ($changelogVersion)."
Write-Host " Update CHANGELOG.md or the csproj version." -ForegroundColor Yellow
exit 1
}
Write-Host " CHANGELOG.md version matches: v$changelogVersion" -ForegroundColor Green
# 6. Check GitHub authentication (skip for local-only builds)
if (-not $isDevBranch) {
Write-Host "Checking GitHub authentication..." -ForegroundColor Gray
if (-not $env:GH_TOKEN) {
Write-Error "GH_TOKEN environment variable is not set. Set $tokenEnvVar and rerun."
exit 1
}
$authTest = gh api user 2>$null
if ($LASTEXITCODE -ne 0 -or -not $authTest) {
Write-Error "GitHub CLI authentication failed. GH_TOKEN may be invalid or missing repo scope."
exit 1
}
Write-Host " GitHub CLI authenticated." -ForegroundColor Green
}
else {
Write-Host "Skipping GitHub authentication (local-only mode)." -ForegroundColor Gray
}
Write-Host ""
Write-Host "All pre-flight checks passed!" -ForegroundColor Green
Write-Host ""
# ==============================================================================
# RUN TESTS
# ==============================================================================
Write-Host "Running tests..." -ForegroundColor Cyan
# Run tests using TestRunner module
$testResult = Invoke-TestsWithCoverage -TestProjectPath $testProjectPath -Silent
if (-not $testResult.Success) {
Write-Error "Tests failed. Release aborted."
Write-Host " Error: $($testResult.Error)" -ForegroundColor Red
exit 1
}
Write-Host " All tests passed!" -ForegroundColor Green
Write-Host " Line Coverage: $($testResult.LineRate)%" -ForegroundColor Gray
Write-Host " Branch Coverage: $($testResult.BranchRate)%" -ForegroundColor Gray
Write-Host " Method Coverage: $($testResult.MethodRate)%" -ForegroundColor Gray
Write-Host ""
# ==============================================================================
# BUILD AND RELEASE
# ==============================================================================
# 7. Prepare staging directory
Write-Host "Preparing staging directory..." -ForegroundColor Cyan
if (Test-Path $stagingDir) {
Remove-Item $stagingDir -Recurse -Force
}
New-Item -ItemType Directory -Path $stagingDir | Out-Null
$binDir = Join-Path $stagingDir "bin"
# 8. Publish the project to staging/bin
Write-Host "Publishing projects to bin folder..." -ForegroundColor Cyan
$publishSuccess = $true
$publishedProjects = @()
foreach ($projPath in $csprojPaths) {
$projName = [System.IO.Path]::GetFileNameWithoutExtension($projPath)
$projBinDir = Join-Path $binDir $projName
dotnet publish $projPath -c Release -o $projBinDir
if ($LASTEXITCODE -ne 0) {
Write-Error "dotnet publish failed for $projName."
$publishSuccess = $false
}
else {
$exeBaseName = Resolve-ProjectExeName -projPath $projPath
$publishedProjects += [PSCustomObject]@{
ProjPath = $projPath
ProjName = $projName
BinDir = $projBinDir
ExeBaseName = $exeBaseName
}
Write-Host " Published $projName successfully to: $projBinDir" -ForegroundColor Green
}
}
if (-not $publishSuccess) {
exit 1
}
# 9. Copy Scripts folder to staging
Write-Host "Copying Scripts folder..." -ForegroundColor Cyan
if (-not (Test-Path $scriptsPath)) {
Write-Error "Scripts folder not found at: $scriptsPath"
exit 1
}
$scriptsDestination = Join-Path $stagingDir "Scripts"
Copy-Item -Path $scriptsPath -Destination $scriptsDestination -Recurse
Write-Host " Scripts copied to: $scriptsDestination" -ForegroundColor Green
Write-Host "Updating ScheduleManager appsettings with UScheduler path..." -ForegroundColor Cyan
# 10. Update appsettings.json with scripts in disabled state
# Dynamically locate ScheduleManager appsettings based on settings.projects.scheduleManagerCsprojEndsWith
$scheduleManagerCsprojPath = Find-CsprojByEndsWith -paths $csprojPaths -endsWith $scheduleManagerCsprojEndsWith
if ($scheduleManagerCsprojPath) {
$scheduleManagerProjName = [System.IO.Path]::GetFileNameWithoutExtension($scheduleManagerCsprojPath)
$scheduleManagerBinDir = Join-Path $binDir $scheduleManagerProjName
$scheduleManagerAppSettingsPath = Join-Path $scheduleManagerBinDir $scheduleManagerAppSettingsFile
if (Test-Path $scheduleManagerAppSettingsPath) {
$smAppSettings = Get-Content $scheduleManagerAppSettingsPath -Raw | ConvertFrom-Json
if ($smAppSettings.USchedulerSettings) {
$smAppSettings.USchedulerSettings.ServiceBinPath = $scheduleManagerServiceBinPath
$jsonOutput = $smAppSettings | ConvertTo-Json -Depth 10
Set-Content -Path $scheduleManagerAppSettingsPath -Value $jsonOutput -Encoding UTF8
Write-Host " Updated ServiceBinPath in ScheduleManager appsettings" -ForegroundColor Green
}
else {
Write-Host " Warning: USchedulerSettings section not found in ScheduleManager appsettings" -ForegroundColor Yellow
}
}
else {
Write-Host " Warning: $scheduleManagerAppSettingsFile not found in $scheduleManagerProjName bin folder" -ForegroundColor Yellow
}
}
else {
Write-Host " Warning: ScheduleManager csproj not found in csprojPaths array" -ForegroundColor Yellow
}
Write-Host "Updating UScheduler appsettings with new LogDir bundled scripts paths..." -ForegroundColor Cyan
# Resolve UScheduler csproj by configured suffix (avoid hardcoded ScheduleManager exclusion)
$uschedulerCsprojPath = Find-CsprojByEndsWith -paths $csprojPaths -endsWith $uschedulerCsprojEndsWith
if ($uschedulerCsprojPath) {
$uschedulerProjName = [System.IO.Path]::GetFileNameWithoutExtension($uschedulerCsprojPath)
$uschedulerBinDir = Join-Path $binDir $uschedulerProjName
$appSettingsPath = Join-Path $uschedulerBinDir $uschedulerAppSettingsFile
if (Test-Path $appSettingsPath) {
$appSettings = Get-Content $appSettingsPath -Raw | ConvertFrom-Json
# Update LogDir for release
if ($appSettings.Configuration) {
$appSettings.Configuration.LogDir = $uschedulerLogDir
Write-Host " Updated LogDir in UScheduler appsettings" -ForegroundColor Green
}
else {
Write-Host " Warning: Configuration section not found in UScheduler appsettings" -ForegroundColor Yellow
}
# Find all .ps1 files in Scripts folder (exclude utility scripts in subfolders named "Utilities")
$psScripts = Get-ChildItem -Path $scriptsDestination -Filter "*.ps1" -Recurse |
Where-Object { $_.Directory.Name -ne "Utilities" } |
ForEach-Object {
$relativePath = $_.FullName.Substring($scriptsDestination.Length + 1).Replace('/', '\')
$scriptPath = "$scriptsRelativeToExe\$relativePath"
[PSCustomObject]@{
Path = $scriptPath
IsSigned = $false
Disabled = $true
}
}
# Add scripts to Powershell configuration
if ($psScripts) {
if (-not $appSettings.Configuration) {
$appSettings | Add-Member -MemberType NoteProperty -Name "Configuration" -Value ([PSCustomObject]@{})
}
$appSettings.Configuration.Powershell = @($psScripts)
$jsonOutput = $appSettings | ConvertTo-Json -Depth 10
Set-Content -Path $appSettingsPath -Value $jsonOutput -Encoding UTF8
Write-Host " Added $($psScripts.Count) PowerShell script(s) to appsettings (disabled)" -ForegroundColor Green
$psScripts | ForEach-Object { Write-Host " - $($_.Path)" -ForegroundColor Gray }
}
}
else {
Write-Host " Warning: $uschedulerAppSettingsFile not found in $uschedulerProjName bin folder" -ForegroundColor Yellow
}
}
else {
Write-Host " Warning: UScheduler csproj not found in csprojPaths array" -ForegroundColor Yellow
}
# 11. Create launcher batch file (if enabled)
if ($settings.launcher -and $settings.launcher.enabled) {
Write-Host "Creating launcher batch file..." -ForegroundColor Cyan
$launcherFileName = $settings.launcher.fileName
$targetProject = $settings.launcher.targetProject
# Determine which project to launch
$targetCsprojPath = $null
$targetExeName = $null
$targetProjName = $null
if ($targetProject -eq "scheduleManager") {
$targetCsprojPath = Find-CsprojByEndsWith -paths $csprojPaths -endsWith $scheduleManagerCsprojEndsWith
}
elseif ($targetProject -eq "uscheduler") {
$targetCsprojPath = Find-CsprojByEndsWith -paths $csprojPaths -endsWith $uschedulerCsprojEndsWith
}
else {
Write-Host " Warning: Unknown targetProject '$targetProject' in launcher settings" -ForegroundColor Yellow
}
if ($targetCsprojPath) {
$targetProjName = [System.IO.Path]::GetFileNameWithoutExtension($targetCsprojPath)
$targetExeName = Resolve-ProjectExeName -projPath $targetCsprojPath
$batPath = Join-Path $stagingDir $launcherFileName
$exePath = "%~dp0bin\$targetProjName\$targetExeName.exe"
$batContent = @"
@echo off
start "" "$exePath"
"@
Set-Content -Path $batPath -Value $batContent -Encoding ASCII
Write-Host " Created launcher: $launcherFileName -> $exePath" -ForegroundColor Green
}
else {
Write-Host " Warning: Could not find target project for launcher" -ForegroundColor Yellow
}
}
else {
Write-Host "Skipping launcher batch file creation (disabled in settings)." -ForegroundColor Gray
}
Write-Host ""
# 12. Prepare release directory
if (!(Test-Path $releaseDir)) {
New-Item -ItemType Directory -Path $releaseDir | Out-Null
}
# 13. Create zip file
$zipName = $zipNamePattern -replace '\{version\}', $version
$zipPath = Join-Path $releaseDir $zipName
if (Test-Path $zipPath) {
Remove-Item $zipPath -Force
}
Write-Host "Creating archive $zipName..." -ForegroundColor Cyan
Compress-Archive -Path "$stagingDir\*" -DestinationPath $zipPath -Force
if (-not (Test-Path $zipPath)) {
Write-Error "Failed to create archive $zipPath"
exit 1
}
Write-Host " Archive created: $zipPath" -ForegroundColor Green
# 14. Extract release notes from CHANGELOG.md
Write-Host "Extracting release notes..." -ForegroundColor Cyan
$pattern = "(?ms)^##\s+v$([regex]::Escape($version))\b.*?(?=^##\s+v\d+\.\d+\.\d+|\Z)"
$match = [regex]::Match($changelog, $pattern)
if (-not $match.Success) {
Write-Error "Changelog entry for version $version not found."
exit 1
}
$releaseNotes = $match.Value.Trim()
Write-Host " Release notes extracted." -ForegroundColor Green
# 15. Get repository info
$remoteUrl = git config --get remote.origin.url
if ($LASTEXITCODE -ne 0 -or -not $remoteUrl) {
Write-Error "Could not determine git remote origin URL."
exit 1
}
if ($remoteUrl -match "[:/](?<owner>[^/]+)/(?<repo>[^/.]+)(\.git)?$") {
$owner = $matches['owner']
$repoName = $matches['repo']
$repo = "$owner/$repoName"
} else {
Write-Error "Could not parse GitHub repo from remote URL: $remoteUrl"
exit 1
}
$releaseName = $releaseTitlePattern -replace '\{version\}', $version
Write-Host ""
Write-Host "Release Summary:" -ForegroundColor Cyan
Write-Host " Repository: $repo" -ForegroundColor White
Write-Host " Tag: $tag" -ForegroundColor White
Write-Host " Title: $releaseName" -ForegroundColor White
Write-Host ""
# 16. Check if tag is pushed to remote (skip on dev branch)
if (-not $isDevBranch) {
Write-Host "Verifying tag is pushed to remote..." -ForegroundColor Cyan
$remoteTag = git ls-remote --tags origin $tag 2>$null
if (-not $remoteTag) {
Write-Host " Tag $tag not found on remote. Pushing..." -ForegroundColor Yellow
git push origin $tag
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to push tag $tag to remote."
exit 1
}
Write-Host " Tag pushed successfully." -ForegroundColor Green
}
else {
Write-Host " Tag exists on remote." -ForegroundColor Green
}
# 17. Create or update GitHub release
Write-Host "Creating GitHub release..." -ForegroundColor Cyan
# Check if release already exists
gh release view $tag --repo $repo 2>$null
if ($LASTEXITCODE -eq 0) {
Write-Host " Release $tag already exists. Deleting..." -ForegroundColor Yellow
gh release delete $tag --repo $repo --yes
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to delete existing release $tag."
exit 1
}
}
# Create new release using existing tag
$ghArgs = @(
"release", "create", $tag, $zipPath
"--repo", $repo
"--title", $releaseName
"--notes", $releaseNotes
)
& gh @ghArgs
if ($LASTEXITCODE -ne 0) {
Write-Error "Failed to create GitHub release for tag $tag."
exit 1
}
Write-Host " GitHub release created successfully." -ForegroundColor Green
}
else {
Write-Host "Skipping GitHub release (dev branch)." -ForegroundColor Yellow
}
# 18. Cleanup
if (Test-Path $stagingDir) {
Remove-Item $stagingDir -Recurse -Force
Write-Host " Cleaned up staging directory." -ForegroundColor Gray
}
Write-Host ""
Write-Host "==================================================" -ForegroundColor Green
if ($isDevBranch) {
Write-Host "DEV BUILD COMPLETE" -ForegroundColor Green
}
else {
Write-Host "RELEASE COMPLETE" -ForegroundColor Green
}
Write-Host "==================================================" -ForegroundColor Green
Write-Host ""
if (-not $isDevBranch) {
Write-Host "Release URL: https://github.com/$repo/releases/tag/$tag" -ForegroundColor Cyan
}
Write-Host "Artifacts location: $releaseDir" -ForegroundColor Gray
if ($isDevBranch) {
Write-Host ""
Write-Host "To publish to GitHub, merge to main, tag, and run the script again:" -ForegroundColor Yellow
Write-Host " git checkout main" -ForegroundColor Yellow
Write-Host " git merge dev" -ForegroundColor Yellow
Write-Host " git tag v$version" -ForegroundColor Yellow
Write-Host " .\Release-ToGitHub.ps1" -ForegroundColor Yellow
}
Write-Host ""