Publish-PackageToGithub.ps1

<#
    Build and publish a PowerShell module to a GitHub organization NuGet feed (no repo linkage, no auto-registration), with explicit module name.
 
    Usage:./
        $env:GITHUB_PAT = '<PAT with write:packages>' # add repo scope if pushing from a private repo
        .\Publish-PackageToGithub.ps1 -Org '<github-org>' -ModuleName '<github-module>' -Path '.' -Username '<github-username>'
 
    Parameters:
        -Org GitHub organization that owns the NuGet feed.
        -ModuleName Module name/manifest basename (expects <ModuleName>.psd1 in -Path).
        -Username GitHub username used for authentication; PAT goes in $env:GITHUB_PAT or -Token.
        -Path Path to the module root (default: current directory).
        -Token GitHub PAT; defaults to $env:GITHUB_PAT (needs write:packages; add repo if private).
 
    Notes:
        - Requires a pre-registered PSRepository whose SourceLocation matches https://nuget.pkg.github.com/<Org>/index.json.
        - The module manifest file must exist as <ModuleName>.psd1 in -Path, with a valid ModuleVersion.
        - Each publish requires a unique ModuleVersion (GitHub Packages rejects duplicate versions).
#>

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

    [Parameter(Mandatory = $true)]
    [string]$ModuleName,

    [Parameter(Mandatory = $true)]
    [string]$Username,

    [string]$Path = ".",

    [string]$Token = $env:GITHUB_PAT
)

function Fail([string]$Message) {
    Write-Host $Message -ForegroundColor Red
    exit 1
}

Set-StrictMode -Version Latest
$ErrorActionPreference = "Stop"

if ([string]::IsNullOrWhiteSpace($Token)) {
    Fail "GitHub PAT is required. Set -Token or \$env:GITHUB_PAT (needs write:packages; add repo if private)."
}

$resolvedPath = Resolve-Path -Path $Path
$manifestPath = Join-Path $resolvedPath "$ModuleName.psd1"

if (-not (Test-Path $manifestPath)) {
    Fail "Module manifest not found at $manifestPath. Ensure -ModuleName matches the psd1 filename."
}

$manifest = Import-PowerShellDataFile -Path $manifestPath
$moduleVersion = $manifest.ModuleVersion
if (-not $moduleVersion) {
    Fail "ModuleVersion is missing in $manifestPath."
}

$source = "https://nuget.pkg.github.com/$Org/index.json"
$secure = ConvertTo-SecureString $Token -AsPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($Username, $secure)

# Require an existing PSRepository pointing to this source; do not register automatically.
$existingRepo = Get-PSRepository -ErrorAction SilentlyContinue | Where-Object { $_.SourceLocation -eq $source }
if (-not $existingRepo) {
    Fail "No PSRepository found for $source. Please register it manually (Register-PSRepository -Name 'GitHubPackages-$Org' -SourceLocation $source -PublishLocation $source ...)."
}
$repoName = $existingRepo[0].Name
Write-Verbose "Using existing PSRepository '$repoName' for $source."

# Guard against pushing a version that already exists (preflight instead of failing late).
try {
    $existingVersion = Find-Module -Name $ModuleName -Repository $repoName -RequiredVersion $moduleVersion -Credential $credential -ErrorAction Stop
} catch {
    $existingVersion = $null
}
if ($existingVersion) {
    Fail "$ModuleName $moduleVersion already exists in '$repoName'. Bump ModuleVersion in the manifest before publishing."
}

Write-Host "Publishing $ModuleName version $moduleVersion to GitHub Packages org feed '$Org' via repository '$repoName'..." -ForegroundColor Cyan

try {
    Publish-Module `
        -Path $resolvedPath `
        -Repository $repoName `
        -NuGetApiKey $Token `
        -ErrorAction Stop
} catch {
    $msg = $_.Exception.Message
    if ($msg -match 'already exists' -or $msg -match 'duplicate' -or $msg -match 'conflict') {
        Fail "$ModuleName $moduleVersion already exists. Bump ModuleVersion in the manifest and retry."
    } elseif ($msg -match '401' -or $msg -match 'unauthorized') {
        Fail "Unauthorized. Verify PAT has write:packages (and repo if private) and username/owner are correct."
    } else {
        $fqid = $_.FullyQualifiedErrorId
        if ($fqid) {
            Fail $fqid
        } else {
            Fail $msg
        }
    }
}

Write-Host "Publish complete: $ModuleName $moduleVersion pushed to https://nuget.pkg.github.com/$Org" -ForegroundColor Green