rust-switcher 1.0.9

Windows keyboard layout switcher and text conversion utility
Documentation
param(
  [Parameter(Mandatory = $false)]
  [string]$Version = ''
)

. "$PSScriptRoot\common.ps1"

Assert-Tool git
Assert-Tool cargo
Assert-Tool gh

$repoRoot = Get-RepoRoot
Push-Location $repoRoot
try {
  Assert-OnBranch 'dev'
  Assert-CleanWorktree

  Invoke-Checked git @('fetch', 'origin', '--tags', '--prune') -Quiet

  $v = if ([string]::IsNullOrWhiteSpace($Version)) {
    Get-CargoPackageVersion 'rust-switcher'
  } else {
    Assert-SemVer $Version
  }

  $tag = "v$v"
  $head = (Invoke-Checked git @('rev-parse', 'HEAD') -Quiet).Output.Trim()

  $tagSha = Get-TagSha $tag
  if (-not $tagSha) {
    throw "Tag '$tag' does not exist. Create it first (recommended: just release $v)."
  }
  if ($tagSha -ne $head) {
    throw "Tag '$tag' points to $tagSha while HEAD is $head. Refusing to upload assets for a mismatched tag."
  }

  Write-Host "`n>> cargo build --release"
  Invoke-Checked cargo @('build', '--release')

  $exe = Join-Path $repoRoot 'target\release\rust-switcher.exe'
  if (-not (Test-Path $exe)) {
    throw "Expected build artifact not found: $exe"
  }

  $assets = @($exe)
  $pdb = Join-Path $repoRoot 'target\release\rust-switcher.pdb'
  if (Test-Path $pdb) {
    $assets += $pdb
  }

  $notesFile = New-TemporaryFile
  try {
    $prev = (Invoke-Checked git @('tag', '--list', 'v*', '--sort=-v:refname') -Quiet).Output.Split("`n") |
      ForEach-Object { $_.Trim() } |
      Where-Object { $_ -and $_ -ne $tag } |
      Select-Object -First 1

    $changes = if ($prev) {
      (Invoke-Checked git @('log', '--oneline', "$prev..$tag") -Quiet).Output.Trim()
    } else {
      (Invoke-Checked git @('log', '--oneline', '-n', '20') -Quiet).Output.Trim()
    }

    $notes = @(
      "Release $tag",
      "",
      "Commit: $head",
      "",
      "Changes:",
      ($changes ?? '(no commits found)')
    ) -join "`n"

    Set-Content -Path $notesFile -Value $notes -Encoding UTF8

    Invoke-Checked gh @('auth', 'status') -Quiet

    $view = Invoke-Checked gh @('release', 'view', $tag) -AllowFailure -Quiet
    if ($view.ExitCode -ne 0) {
      Invoke-Checked gh @('release', 'create', $tag, '--title', $tag, '--notes-file', $notesFile, '--target', 'dev', '--verify-tag')
    } else {
      Invoke-Checked gh @('release', 'edit', $tag, '--title', $tag, '--notes-file', $notesFile, '--target', 'dev', '--verify-tag')
    }

    $uploadArgs = @('release', 'upload', $tag) + $assets + @('--clobber')
    Invoke-Checked gh $uploadArgs

    $url = (Invoke-Checked gh @('release', 'view', $tag, '--json', 'url', '-q', '.url') -Quiet).Output.Trim()
    Write-Host "`nOK: GitHub Release updated: $url"
  }
  finally {
    Remove-Item -Force $notesFile -ErrorAction SilentlyContinue
  }
}
finally {
  Pop-Location
}