Functions/GenXdev.Coding.PowerShell.Modules/New-PullRequestForGenXdevModuleChanges.ps1

################################################################################
<#
.SYNOPSIS
Creates a pull request for changes made to a GenXdev module.
 
.DESCRIPTION
This function automates the process of creating a pull request for changes made to
a GenXdev module. It handles GitHub authentication, repository forking, and pull
request creation.
 
.PARAMETER ModuleName
The name of the GenXdev module to create a pull request for.
 
.PARAMETER CommitMessage
The commit message to use when committing changes.
 
.PARAMETER PullRequestTitle
The title for the pull request.
 
.PARAMETER PullRequestDescription
The description for the pull request.
 
.EXAMPLE
New-PullRequestForGenXdevModuleChanges -ModuleName "GenXdev.Coding" `
    -CommitMessage "Added new features" `
    -PullRequestTitle "New Features" `
    -PullRequestDescription "Added support for X and Y"
 
.EXAMPLE
prmodule GenXdev.Coding "Bug fixes" "Fixed issues" "Various bug fixes implemented"
#>

function New-PullRequestForGenXdevModuleChanges {

    [CmdletBinding(SupportsShouldProcess)]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")]
    [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "")]

    [Alias("prgenxdevmodule")]

    param(
        ########################################################################
        [Parameter(
            Position = 0,
            Mandatory = $true,
            HelpMessage = "Name of the GenXdev module"
        )]
        [ValidateSet(
            "GenXdev.AI",
            "GenXdev.Coding",
            "GenXdev.Console",
            "GenXdev.Data",
            "GenXdev.FileSystem",
            "GenXdev.Helpers",
            "GenXdev.Queries",
            "GenXdev.Webbrowser",
            "GenXdev.Windows"
        )]
        [string]$ModuleName,
        ########################################################################
        [Parameter(
            Position = 1,
            Mandatory = $false,
            HelpMessage = "Message for the commit"
        )]
        [string]$CommitMessage = "Improvements to GenXdev module",
        ########################################################################
        [Parameter(
            Position = 2,
            Mandatory = $false,
            HelpMessage = "Title for the pull request"
        )]
        [string]$PullRequestTitle = "Module improvements",
        ########################################################################
        [Parameter(
            Position = 3,
            Mandatory = $false,
            HelpMessage = "Description for the pull request"
        )]
        [string]$PullRequestDescription = "These changes improve functionality " +
        "and fix issues I encountered."
        ########################################################################
    )

    begin {
        $w = "store/fileupload"
        Microsoft.PowerShell.Utility\Write-Verbose "Checking for GitHub CLI installation"
        GenXdev.Coding\AssureGithubCLIInstalled
    }


process {
        function Register-GitHubApp {

            # GitHub OAuth application credentials
            $clientId = "Ov23livZivIhWiBMR3Yb"

            # Start device flow authentication
            GenXdev.Webbrowser\Close-Webbrowser -Force
            GenXdev.Webbrowser\Open-Webbrowser -nw -mon 0 -right
            GenXdev.Webbrowser\Select-WebbrowserTab
            GenXdev.Windows\Set-WindowPosition -left

            $deviceCodeRequest = Microsoft.PowerShell.Utility\Invoke-RestMethod -Method Post -Uri "https://github.com/login/device/code" -Body @{
                client_id = $clientId
                scope     = "repo"
            } -Headers @{
                Accept = "application/json"
            }

            GenXdev.Webbrowser\Set-WebbrowserTabLocation (deviceCodeRequest.verification_uri)

            Microsoft.PowerShell.Utility\Write-Host "Please visit: $($deviceCodeRequest.verification_uri)"
            Microsoft.PowerShell.Utility\Write-Host "And enter code: $($deviceCodeRequest.user_code)"
            Microsoft.PowerShell.Utility\Write-Host "Waiting for authentication..."

            # Poll for the token
            $token = $null
            $startTime = Microsoft.PowerShell.Utility\Get-Date
            while (((Microsoft.PowerShell.Utility\Get-Date) - $startTime).TotalSeconds -lt 300) {
                try {
                    $tokenResponse = Microsoft.PowerShell.Utility\Invoke-RestMethod -Method Post -Uri "https://github.com/login/oauth/access_token" -Body @{
                        client_id   = $clientId
                        device_code = $deviceCodeRequest.device_code
                        grant_type  = "urn:ietf:params:oauth:grant-type:device_code"
                    } -Headers @{
                        Accept = "application/json"
                    }

                    if ($tokenResponse.access_token) {
                        $token = $tokenResponse.access_token
                        break
                    }
                }
                catch {
                    # Still waiting for user to authorize
                    Microsoft.PowerShell.Utility\Start-Sleep -Seconds 5
                }
            }

            return $token
        }

        Microsoft.PowerShell.Utility\Write-Verbose "Checking dependencies for $ModuleName"
        GenXdev.Coding\Assert-GenXdevDependencyUsage -BaseModuleName $ModuleName -ErrorAction Stop

        Microsoft.PowerShell.Utility\Write-Verbose "Running unit tests for $ModuleName"
        GenXdev.Coding\Assert-GenXdevUnitTest -ModuleName $ModuleName -ErrorAction Stop

        # get full path to module
        $modulePath = GenXdev.FileSystem\Expand-Path "$PSScriptRoot\..\Modules\$ModuleName\1.160.2025\"

        Microsoft.PowerShell.Utility\Write-Verbose "Checking for module manifest at: $modulePath"
        if (!(Microsoft.PowerShell.Management\Test-Path -Path "$modulePath\$ModuleName.psd1")) {
            Microsoft.PowerShell.Utility\Write-Error "No module manifest found in current directory"
            exit 1
        }

        # load module version information
        $manifestFile = GenXdev.FileSystem\Expand-Path "$modulePath\$ModuleName.psd1"
        $moduleVersion = (Microsoft.PowerShell.Utility\Import-PowerShellDataFile $manifestFile).ModuleVersion

        Microsoft.PowerShell.Utility\Write-Verbose "Processing $ModuleName version $moduleVersion"

        # authenticate with github
        Microsoft.PowerShell.Utility\Write-Verbose "Authenticating with GitHub"
        $token = Register-GitHubApp
        if (!$token) {
            Microsoft.PowerShell.Utility\Write-Error "Authentication failed or timed out"
            exit 1
        }

        # Find the original repository
        $headers = @{
            Authorization = "token $token"
            Accept        = "application/vnd.github.v3+json"
        }

        # Search for the repository in genXdev organization
        $repoSearchResult = Microsoft.PowerShell.Utility\Invoke-RestMethod -Method Get -Uri "https://api.github.com/repos/genXdev/$ModuleName" -Headers $headers -ErrorAction SilentlyContinue

        if (!$repoSearchResult) {
            Microsoft.PowerShell.Utility\Write-Host "Repository not found. Uploading to genXdev.net instead..."

            # Get module path and create zip file
            $modulePath = GenXdev.FileSystem\Expand-Path "$PSScriptRoot\..\Modules\$ModuleName\1.160.2025\"
            $zipPath = [System.IO.Path]::GetTempFileName() + ".zip"
            Microsoft.PowerShell.Archive\Compress-Archive -Path "$modulePath\*" -DestinationPath $zipPath -Force

            try {
                $uploadUrl = ("https://genxdev.net/$w?filename=$([Uri]::EscapeDataString(
                    "$ModuleName.zip"))&session=$([Uri]::EscapeDataString(
                        [Guid]::NewGuid().ToString().ToLowerInvariant()))"
);

                $boundary = [System.Guid]::NewGuid().ToString()
                $fileBin = [System.IO.File]::ReadAllBytes($zipPath)
                $enc = [System.Text.Encoding]::GetEncoding("iso-8859-1")
                $fileEnc = $enc.GetString($fileBin)

                $LF = "`r`n"
                $bodyLines = (
                    "--$boundary",
                    "Content-Disposition: form-data; name=`"file`"; filename=`"$ModuleName.zip`"",
                    "Content-Type: application/x-zip-compressed$LF",
                    $fileEnc,
                    "--$boundary--$LF"
                ) -join $LF

                $result = Microsoft.PowerShell.Utility\Invoke-RestMethod -Uri $uploadUrl `
                    -Method Post `
                    -ContentType "multipart/form-data; boundary=$boundary" `
                    -Body $bodyLines

                Microsoft.PowerShell.Utility\Write-Verbose "Upload result: $($result | Microsoft.PowerShell.Utility\ConvertTo-Json -Compress)"
                Microsoft.PowerShell.Utility\Write-Host "Module successfully uploaded to genXdev.net"
                Microsoft.PowerShell.Utility\Write-Host "Please contact genXdev to inform about the new module suggestion."
                return
            }
            finally {
                Microsoft.PowerShell.Management\Remove-Item $zipPath -Force
            }
        }

        Microsoft.PowerShell.Utility\Write-Host "Found original repository: $($repoSearchResult.html_url)"

        # Create a fork if doesn't exist
        $username = (Microsoft.PowerShell.Utility\Invoke-RestMethod -Method Get -Uri "https://api.github.com/user" -Headers $headers).login

        try {
            Microsoft.PowerShell.Utility\Invoke-RestMethod -Method Get -Uri "https://api.github.com/repos/$username/$ModuleName" -Headers $headers
            Microsoft.PowerShell.Utility\Write-Verbose "Fork already exists"
        }
        catch {
            # Fork doesn't exist, create it
            if ($PSCmdlet.ShouldProcess("$ModuleName", "Create fork")) {
                Microsoft.PowerShell.Utility\Write-Host "Creating fork of $ModuleName..."
                Microsoft.PowerShell.Utility\Invoke-RestMethod -Method Post -Uri "https://api.github.com/repos/genXdev/$ModuleName/forks" -Headers $headers
            }
        }

        # Setup git and push changes
        if ($PSCmdlet.ShouldProcess("$ModuleName", "Push changes to GitHub")) {
            git init
            git config user.name $username
            git remote add origin "https://github.com/$username/$ModuleName.git"
            git remote add upstream "https://github.com/genXdev/$ModuleName.git"

            # Add, commit and push changes
            git add .
            git commit -m $CommitMessage
            git branch -M main
            git push -u origin main -f
        }

        # --- Begin Extension ---
        # Find the commit with the exact message "Release 1.160.2025" and extract its commit hash
        $releaseCommitMsg = "Release 1.160.2025"
        $releaseCommitHash = (git log --grep="^$releaseCommitMsg$" --format="%H" -n 1).Trim()
        if (-not $releaseCommitHash) {
            Microsoft.PowerShell.Utility\Write-Error "No commit found with message '$releaseCommitMsg'"
            exit 1
        }
        # Append the commit hash to the pull request description.
        $PullRequestDescription += "`nCommit hash: $releaseCommitHash"
        # --- End Extension ---

        # Create pull request
        if ($PSCmdlet.ShouldProcess("$ModuleName", "Create pull request")) {
            $prBody = @{
                title = $PullRequestTitle
                body  = $PullRequestDescription
                head  = "$username:main"
                base  = "main"
            }

            $pr = Microsoft.PowerShell.Utility\Invoke-RestMethod -Method Post -Uri "https://api.github.com/repos/genXdev/$ModuleName/pulls" -Body ($prBody | Microsoft.PowerShell.Utility\ConvertTo-Json) -Headers $headers -ContentType "application/json"

            Microsoft.PowerShell.Utility\Write-Host "Pull request created successfully: $($pr.html_url)"
            Microsoft.PowerShell.Utility\Write-Host "Thank you for contributing to GenXdev!"
        }
    }

    end {

    }
}
################################################################################