GitHub.psm1

#region - From classes/Data/App.ps1
$script:App = [pscustomobject]@{
    GitHubApp = [pscustomobject]@{
        ClientID = 'Iv1.f26b61bc99e69405'         # $script:App.GitHubApp.ClientID
    }
    OAuthApp  = [pscustomobject]@{
        ClientID = '7204ae9b0580f2cb8288'         # $script:App.OAuthApp.ClientID
    }
}
#endregion - From classes/Data/App.ps1

#region - From classes/Data/Config.ps1
$script:ConfigTemplate = [pscustomobject]@{
    App  = [pscustomobject]@{
        API      = [pscustomobject]@{
            BaseURI = 'https://api.github.com' # $script:ConfigTemplate.App.API.BaseURI
            Version = '2022-11-28'             # $script:ConfigTemplate.App.API.Version
        }
        Defaults = [pscustomobject]@{}
    }
    User = [pscustomobject]@{
        Auth     = [pscustomobject]@{
            AccessToken  = [pscustomobject]@{
                Value          = ''  # $script:ConfigTemplate.User.Auth.AccessToken.Value
                ExpirationDate = [datetime]::MinValue  # $script:ConfigTemplate.User.Auth.AccessToken.ExpirationDate
            }
            ClientID     = ''        # $script:ConfigTemplate.User.Auth.ClientID
            Mode         = ''        # $script:ConfigTemplate.User.Auth.Mode
            RefreshToken = [pscustomobject]@{
                Value          = ''  # $script:ConfigTemplate.User.Auth.RefreshToken.Value
                ExpirationDate = [datetime]::MinValue  # $script:ConfigTemplate.User.Auth.RefreshToken.ExpirationDate
            }
            Scope        = ''               # $script:ConfigTemplate.User.Auth.Scope
        }
        Defaults = [pscustomobject]@{
            Owner = ''               # $script:ConfigTemplate.User.Defaults.Owner
            Repo  = ''               # $script:ConfigTemplate.User.Defaults.Repo
        }
    }
}
$script:Config = $script:ConfigTemplate
#endregion - From classes/Data/Config.ps1

#region - From classes/Data/SecretVault.ps1
$script:SecretVault = [pscustomobject]@{
    Name = 'GitHub'                           # $script:SecretVault.Name
    Type = 'Microsoft.PowerShell.SecretStore' # $script:SecretVault.Type
}
$script:Secret = [pscustomobject]@{
    Name = 'Config'                           # $script:Secret.Name
}
#endregion - From classes/Data/SecretVault.ps1

#region - From private/Config/Initialize-SecretVault.ps1
#Requires -Version 7.0
#Requires -Modules Microsoft.PowerShell.SecretManagement
#Requires -Modules Microsoft.PowerShell.SecretStore

function Initialize-SecretVault {
    <#
    .SYNOPSIS
    Initialize a secret vault.

    .DESCRIPTION
    Initialize a secret vault. If the vault does not exist, it will be created.

    .EXAMPLE
    Initialize-SecretVault -Name 'SecretStore' -Type 'Microsoft.PowerShell.SecretStore'

    Initializes a secret vault named 'SecretStore' using the 'Microsoft.PowerShell.SecretStore' module.

    .NOTES
    For more information aobut secret vaults, see https://learn.microsoft.com/en-us/powershell/utility-modules/secretmanagement/overview?view=ps-modules
    #>

    [OutputType([void])]
    [CmdletBinding()]
    param (
        # The name of the secret vault.
        [Parameter()]
        [string] $Name,

        # The type of the secret vault.
        [Parameter()]
        [Alias('ModuleName')]
        [string] $Type
    )

    $secretVault = Get-SecretVault | Where-Object { $_.ModuleName -eq $Type }
    $secretVaultExists = $secretVault.count -ne 0
    Write-Verbose "A $Name exists: $secretVaultExists"
    if (-not $secretVaultExists) {
        Write-Verbose "Registering [$Name]"

        switch ($Type) {
            'Microsoft.PowerShell.SecretStore' {
                $vaultParameters = @{
                    Authentication  = 'None'
                    PasswordTimeout = -1
                    Interaction     = 'None'
                    Scope           = 'CurrentUser'
                    WarningAction   = 'SilentlyContinue'
                    Confirm         = $false
                    Force           = $true
                }
                Reset-SecretStore @vaultParameters
            }
        }
    }

    $secretStore = Get-SecretVault | Where-Object { $_.Name -eq $Name }
    $secretStoreExists = $secretStore.count -ne 0
    if (-not $secretStoreExists) {
        $secretVault = @{
            Name         = $Name
            ModuleName   = $Type
            DefaultVault = $true
            Description  = 'SecretStore'
        }
        Register-SecretVault @secretVault
    }
}
#endregion - From private/Config/Initialize-SecretVault.ps1

#region - From private/Auth/DeviceFlow/Check-GitHubAccessToken.ps1
function Check-GitHubAccessToken {

    [DateTime]$accessTokenExirationDate = $script:ConfigTemplate.User.Auth.AccessToken.ExpirationDate
    $accessTokenValid = $accessTokenExirationDate -gt (Get-Date)

    if (-not $accessTokenValid) {
        Write-Warning 'Your access token has expired. Refreshing it...'
        Connect-GitHubAccount -Refresh
    }
    $TimeSpan = New-TimeSpan -Start (Get-Date) -End $accessTokenExirationDate
    Write-Host "Your access token will expire in $($TimeSpan.Days)-$($TimeSpan.Hours):$($TimeSpan.Minutes):$($TimeSpan.Seconds)."
}
#endregion - From private/Auth/DeviceFlow/Check-GitHubAccessToken.ps1

#region - From private/Auth/DeviceFlow/Invoke-GitHubDeviceFlowLogin.ps1
function Invoke-GitHubDeviceFlowLogin {
    <#
        .SYNOPSIS
        Starts the GitHub Device Flow login process.

        .DESCRIPTION
        Starts the GitHub Device Flow login process. This will prompt the user to visit a URL and enter a code.

        .EXAMPLE
        Invoke-GitHubDeviceFlowLogin

        This will start the GitHub Device Flow login process.
        The user gets prompted to visit a URL and enter a code.

        .NOTES
        For more info about the Device Flow visit:
        https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/building-a-cli-with-a-github-app
        https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow
    #>

    [OutputType([void])]
    [CmdletBinding()]
    param(
        # The Client ID of the GitHub App.
        [Parameter(Mandatory)]
        [string] $ClientID,

        # The scope of the access token, when using OAuth authentication.
        # Provide the list of scopes as space-separated values.
        # For more information on scopes visit:
        # https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps
        [Parameter()]
        [string] $Scope,

        # The refresh token to use for re-authentication.
        [Parameter()]
        [switch] $RefreshToken
    )

    do {
        if ($RefreshToken) {
            $tokenResponse = Wait-GitHubAccessToken -ClientID $ClientID -RefreshToken $RefreshToken
        } else {
            $deviceCodeResponse = Request-GitHubDeviceCode -ClientID $ClientID -Scope $Scope

            $deviceCode = $deviceCodeResponse.device_code
            $interval = $deviceCodeResponse.interval
            $userCode = $deviceCodeResponse.user_code
            $verificationUri = $deviceCodeResponse.verification_uri

            Write-Host '! ' -ForegroundColor DarkYellow -NoNewline
            Write-Host "We added the code to your clipboard: [$userCode]"
            $userCode | Set-Clipboard
            Read-Host 'Press Enter to open github.com in your browser...'
            Start-Process $verificationUri

            $tokenResponse = Wait-GitHubAccessToken -DeviceCode $deviceCode -ClientID $ClientID -Interval $interval
        }
    } while ($tokenResponse.error)
    $tokenResponse
}
#endregion - From private/Auth/DeviceFlow/Invoke-GitHubDeviceFlowLogin.ps1

#region - From private/Auth/DeviceFlow/Request-GitHubAccessToken.ps1
function Request-GitHubAccessToken {
    <#
        .SYNOPSIS
        Request a GitHub token using the Device Flow.

        .DESCRIPTION
        Request a GitHub token using the Device Flow.
        This will poll the GitHub API until the user has entered the code.

        .EXAMPLE
        Request-GitHubAccessToken -DeviceCode $deviceCode -ClientID $ClientID

        This will poll the GitHub API until the user has entered the code.

        .NOTES
        For more info about the Device Flow visit:
        https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/building-a-cli-with-a-github-app
    #>

    [OutputType([PSCustomObject])]
    [CmdletBinding(DefaultParameterSetName = 'DeviceFlow')]
    param(
        # The Client ID of the GitHub App.
        [Parameter(Mandatory)]
        [string] $ClientID,

        # The 'device_code' used to request the access token.
        [Parameter(
            Mandatory,
            ParameterSetName = 'DeviceFlow'
        )]
        [string] $DeviceCode,

        # The refresh token used create a new access token.
        [Parameter(
            Mandatory,
            ParameterSetName = 'RefreshToken'
        )]
        [string] $RefreshToken
    )

    $body = @{
        'client_id' = $ClientID
    }

    if ($PSBoundParameters.ContainsKey('RefreshToken')) {
        $body += @{
            'refresh_token' = $RefreshToken
            'grant_type'    = 'refresh_token'
        }
    }

    if ($PSBoundParameters.ContainsKey('DeviceCode')) {
        $body += @{
            'device_code' = $DeviceCode
            'grant_type'  = 'urn:ietf:params:oauth:grant-type:device_code'
        }
    }

    $RESTParams = @{
        Uri     = 'https://github.com/login/oauth/access_token'
        Method  = 'POST'
        Body    = $body
        Headers = @{ 'Accept' = 'application/json' }
    }

    try {
        Write-Verbose ($RESTParams.GetEnumerator() | Out-String)

        $tokenResponse = Invoke-RestMethod @RESTParams -Verbose:$false
        return $tokenResponse
    } catch {
        Write-Error $_
        throw $_
    }
}
#endregion - From private/Auth/DeviceFlow/Request-GitHubAccessToken.ps1

#region - From private/Auth/DeviceFlow/Request-GitHubDeviceCode.ps1
function Request-GitHubDeviceCode {
    <#
        .SYNOPSIS
        Request a GitHub Device Code.

        .DESCRIPTION
        Request a GitHub Device Code.

        .EXAMPLE
        Request-GitHubDeviceCode -ClientID $ClientID -Mode $Mode

        This will request a GitHub Device Code.

        .NOTES
        For more info about the Device Flow visit:
        https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/building-a-cli-with-a-github-app
    #>

    [OutputType([PSCustomObject])]
    [CmdletBinding()]
    param(
        # The Client ID of the GitHub App.
        [Parameter(Mandatory)]
        [string] $ClientID,

        # The scope of the access token, when using OAuth authentication.
        # Provide the list of scopes as space-separated values.
        # For more information on scopes visit:
        # https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps
        [Parameter()]
        [string] $Scope = 'gist, read:org, repo, workflow'
    )

    $headers = @{
        Accept = 'application/json'
    }

    $body = @{
        client_id = $ClientID
        scope     = $Scope
    }

    $RESTParams = @{
        Uri     = 'https://github.com/login/device/code'
        Method  = 'POST'
        Body    = $body
        Headers = $headers
    }

    try {
        Write-Verbose ($RESTParams.GetEnumerator() | Out-String)

        $deviceCodeResponse = Invoke-RestMethod @RESTParams -Verbose:$false
        return $deviceCodeResponse
    } catch {
        Write-Error $_
        throw $_
    }
}

#endregion - From private/Auth/DeviceFlow/Request-GitHubDeviceCode.ps1

#region - From private/Auth/DeviceFlow/Wait-GitHubAccessToken.ps1

function Wait-GitHubAccessToken {
    <#
        .SYNOPSIS
        Waits for the GitHub Device Flow to complete.

        .DESCRIPTION
        Waits for the GitHub Device Flow to complete.
        This will poll the GitHub API until the user has entered the code.

        .EXAMPLE
        Wait-GitHubAccessToken -DeviceCode $deviceCode -ClientID $ClientID -Interval $interval

        This will poll the GitHub API until the user has entered the code.

        .EXAMPLE
        Wait-GitHubAccessToken -Refresh -ClientID $ClientID

        .NOTES
        For more info about the Device Flow visit:
        https://docs.github.com/en/apps/creating-github-apps/writing-code-for-a-github-app/building-a-cli-with-a-github-app
    #>

    [OutputType([PSCustomObject])]
    [CmdletBinding(DefaultParameterSetName = 'DeviceFlow')]
    param(
        # The Client ID of the GitHub App.
        [Parameter(Mandatory)]
        [string] $ClientID,

        # The device code used to request the access token.
        [Parameter(
            Mandatory,
            ParameterSetName = 'DeviceFlow'
        )]
        [string] $DeviceCode,

        # The refresh token used to request a new access token.
        [Parameter(
            Mandatory,
            ParameterSetName = 'RefreshToken'
        )]
        [string] $RefreshToken,

        # The interval to wait between polling for the token.
        [Parameter()]
        [int] $Interval = 5

    )

    do {
        if ($Refresh) {
            $response = Request-GitHubAccessToken -ClientID $ClientID -RefreshToken $RefreshToken
        } else {
            $response = Request-GitHubAccessToken -ClientID $ClientID -DeviceCode $DeviceCode
        }
        if ($response.error) {
            switch ($response.error) {
                'authorization_pending' {
                    # The user has not yet entered the code.
                    # Wait, then poll again.
                    Write-Verbose $response.error_description
                    Start-Sleep -Seconds $interval
                    continue
                }
                'slow_down' {
                    # The app polled too fast.
                    # Wait for the interval plus 5 seconds, then poll again.
                    Write-Verbose $response.error_description
                    Start-Sleep -Seconds ($interval + 5)
                    continue
                }
                'expired_token' {
                    # The 'device_code' expired, and the process needs to restart.
                    Write-Error $response.error_description
                    exit 1
                }
                'unsupported_grant_type' {
                    # The 'grant_type' is not supported.
                    Write-Error $response.error_description
                    exit 1
                }
                'incorrect_client_credentials' {
                    # The 'client_id' is not valid.
                    Write-Error $response.error_description
                    exit 1
                }
                'incorrect_device_code' {
                    # The 'device_code' is not valid.
                    Write-Error $response.error_description
                    exit 2
                }
                'access_denied' {
                    # The user cancelled the process. Stop polling.
                    Write-Error $response.error_description
                    exit 1
                }
                'device_flow_disabled' {
                    # The GitHub App does not support the Device Flow.
                    Write-Error $response.error_description
                    exit 1
                }
                default {
                    # The response contains an access token. Stop polling.
                    Write-Error 'Unknown error:'
                    Write-Error $response.error
                    Write-Error $response.error_description
                    Write-Error $response.error_uri
                    break
                }
            }
        }
    } until ($response.access_token)
    $response
}
#endregion - From private/Auth/DeviceFlow/Wait-GitHubAccessToken.ps1

#region - From public/loader.ps1
Initialize-SecretVault -Name $script:SecretVault.Name -Type $script:SecretVault.Type
Restore-GitHubConfig

if (-not [string]::IsNullOrEmpty($env:GH_TOKEN)) {
    Write-Verbose 'Logging on using GH_TOKEN'
    Connect-GitHubAccount -AccessToken $env:GH_TOKEN
}
if (-not [string]::IsNullOrEmpty($env:GITHUB_TOKEN)) {
    Write-Verbose 'Logging on using GITHUB_TOKEN'
    Connect-GitHubAccount -AccessToken $env:GITHUB_TOKEN
}
#endregion - From public/loader.ps1

#region - From public/Config/Get-GitHubConfig.ps1
function Get-GitHubConfig {
    <#
        .SYNOPSIS
        Get the current GitHub configuration.

        .DESCRIPTION
        Get the current GitHub configuration.
        If the Refresh switch is used, the configuration will be refreshed from the configuration file.

        .EXAMPLE
        Get-GitHubConfig

        Returns the current GitHub configuration.

        .EXAMPLE
        Get-GitHubConfig -Refresh

        Refreshes the current GitHub configuration from the configuration store beofre returning it.
    #>

    [Alias('Get-GHConfig')]
    [OutputType([PSCustomObject])]
    [CmdletBinding()]
    param (
        # Refresh the configuration from the configuration store before returning it.
        [Parameter()]
        [switch] $Refresh
    )

    if ($Refresh) {
        Restore-GitHubConfig
    }

    $script:Config
}
#endregion - From public/Config/Get-GitHubConfig.ps1

#region - From public/Config/Reset-GitHubConfig.ps1
function Reset-GitHubConfig {
    <#
        .SYNOPSIS
        Reset the GitHub configuration.

        .DESCRIPTION
        Reset the GitHub configuration. Specific scopes can be reset by using the Scope parameter.

        .EXAMPLE
        Reset-GitHubConfig

        Resets the entire GitHub configuration.

        .EXAMPLE
        Reset-GitHubConfig -Scope 'App.API'

        Resets the App.API scope of the GitHub configuration.
    #>

    [Alias('Reset-GHConfig')]
    [OutputType([void])]
    [CmdletBinding()]
    param(
        # Reset the GitHub configuration for a specific scope.
        [Parameter()]
        [ValidateSet('App', 'App.API', 'App.Defaults', 'User', 'User.Auth', 'User.Defaults', 'All')]
        [string] $Scope = 'All'
    )

    switch ($Scope) {
        'App' {
            $script:Config.App = $script:ConfigTemplate.App
        }
        'App.API' {
            $script:Config.App.API = $script:ConfigTemplate.App.API
        }
        'App.Defaults' {
            $script:Config.App.Defaults = $script:ConfigTemplate.App.Defaults
        }
        'User' {
            $script:Config.User = $script:ConfigTemplate.User
        }
        'User.Auth' {
            $script:Config.User.Auth = $script:ConfigTemplate.User.Auth
        }
        'User.Defaults' {
            $script:Config.User.Defaults = $script:ConfigTemplate.User.Defaults
        }
        'All' {
            $script:Config = $script:ConfigTemplateDefaults
        }
    }
    Save-GitHubConfig
}
#endregion - From public/Config/Reset-GitHubConfig.ps1

#region - From public/Config/Restore-GitHubConfig.ps1
#Requires -Version 7.0
#Requires -Modules Microsoft.PowerShell.SecretManagement

function Restore-GitHubConfig {
    <#
        .SYNOPSIS
        Restore the GitHub configuration from the configuration store.

        .DESCRIPTION
        Restore the GitHub configuration from the configuration store.

        .EXAMPLE
        Restore-GitHubConfig

        Restores the GitHub configuration from the configuration store.
    #>

    [Alias('Load-GitHubConfig')]
    [Alias('Load-GHConfig')]
    [Alias('Restore-GHConfig')]
    [OutputType([void])]
    [CmdletBinding()]
    param()

    $vault = Get-SecretVault -Name $script:SecretVault.Name
    $vaultExists = $vault.count -eq 1
    if ($vaultExists) {
        $secretExists = Get-SecretInfo -Name $script:Secret.Name -Vault $script:SecretVault.Name
        if ($secretExists) {
            $script:Config = Get-Secret -Name $script:Secret.Name -AsPlainText -Vault $script:SecretVault.Name | ConvertFrom-Json
        } else {
            Write-Warning "Unable to restore configuration."
            Write-Warning "The secret [$($script:Secret.Name)] does not exist in the vault [$($script:SecretVault.Name)]."
        }
    } else {
        Write-Warning "Unable to restore configuration."
        Write-Warning "The vault [$($script:SecretVault.Name)] does not exist."
    }
}
#endregion - From public/Config/Restore-GitHubConfig.ps1

#region - From public/Config/Save-GitHubConfig.ps1
#Requires -Version 7.0
#Requires -Modules Microsoft.PowerShell.SecretManagement

function Save-GitHubConfig {
    <#
        .SYNOPSIS
        Save the GitHub configuration to the configuration store.

        .DESCRIPTION
        Save the GitHub configuration to the configuration store.

        .EXAMPLE
        Save-GitHubConfig

        Saves the GitHub configuration to the configuration store.
    #>

    [Alias('Save-GHConfig')]
    [OutputType([void])]
    [CmdletBinding()]
    param()

    $config = $script:Config | ConvertTo-Json -Depth 100
    Set-Secret -Name $script:Secret.Name -Secret $config -Vault $script:SecretVault.Name
}
#endregion - From public/Config/Save-GitHubConfig.ps1

#region - From public/Config/Set-GitHubConfig.ps1
function Set-GitHubConfig {
    <#
        .SYNOPSIS
        Set the GitHub configuration.

        .DESCRIPTION
        Set the GitHub configuration. Specific scopes can be set by using the parameters.

        .EXAMPLE
        Set-GitHubConfig -APIBaseURI 'https://api.github.com' -APIVersion '2022-11-28'

        Sets the App.API scope of the GitHub configuration.
    #>

    [Alias('Set-GHConfig')]
    [CmdletBinding()]
    param (
        # Set the API Base URI.
        [Parameter()]
        [string] $APIBaseURI,

        # Set the GitHub API Version.
        [Parameter()]
        [string] $APIVersion,

        # Set the default for the Owner parameter.
        [Parameter()]
        [string] $Owner,

        # Set the default for the Repo parameter.
        [Parameter()]
        [string] $Repo
    )

    switch ($PSBoundParameters.Keys) {
        'APIBaseURI' {
            $script:ConfigTemplate.App.API.BaseURI = $APIBaseURI
        }
        'APIVersion' {
            $script:ConfigTemplate.App.API.Version = $APIVersion
        }
        'Owner' {
            $script:ConfigTemplate.User.Defaults.Owner = $Owner
        }
        'Repo' {
            $script:ConfigTemplate.User.Defaults.Repo = $Repo
        }
    }
    Save-GitHubConfig
}
#endregion - From public/Config/Set-GitHubConfig.ps1

#region - From public/Auth/Connect-GitHubAccount.ps1
function Connect-GitHubAccount {
    <#
        .SYNOPSIS
        Connects to GitHub using a personal access token or device code login.

        .DESCRIPTION
        Connects to GitHub using a personal access token or device code login.

        For device flow / device code login:
        PowerShell requests device and user verification codes and gets the authorization URL where you will enter the user verification code.
        In GitHub you will be asked to enter a user verification code at https://github.com/login/device.
        PowerShell will keep polling GitHub for the user authentication status. Once you have authorized the device,
        the app will be able to make API calls with a new access token.

        .EXAMPLE
        Connect-GitHubAccount

        Connects to GitHub using a device flow login.

        .EXAMPLE
        Connect-GitHubAccount -AccessToken 'ghp_####'

        Connects to GitHub using a personal access token (PAT).

        .EXAMPLE
        Connect-GitHubAccount -Refresh

        Refreshes the access token.

        .EXAMPLE
        Connect-GitHubAccount -Mode 'OAuthApp' -Scope 'gist read:org repo workflow'

        Connects to GitHub using a device flow login and sets the scope of the access token.

        .NOTES
        https://docs.github.com/en/rest/overview/other-authentication-methods#authenticating-for-saml-sso
    #>

    [Alias('Connect-GHAccount')]
    [Alias('Connect-GitHub')]
    [Alias('Connect-GH')]
    [Alias('Login-GitHubAccount')]
    [Alias('Login-GHAccount')]
    [Alias('Login-GitHub')]
    [Alias('Login-GH')]
    [OutputType([void])]
    [CmdletBinding(DefaultParameterSetName = 'DeviceFlow')]
    param (
        # Choose between authentication methods, either OAuthApp or GitHubApp.
        # For more info about the types of authentication visit:
        # https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/differences-between-github-apps-and-oauth-apps
        [Parameter(ParameterSetName = 'DeviceFlow')]
        [ValidateSet('OAuthApp', 'GitHubApp')]
        [string] $Mode = 'GitHubApp',

        # The scope of the access token, when using OAuth authentication.
        # Provide the list of scopes as space-separated values.
        # For more information on scopes visit:
        # https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps
        [Parameter(ParameterSetName = 'DeviceFlow')]
        [string] $Scope,

        # Refresh the access token.
        [Parameter(
            Mandatory,
            ParameterSetName = 'Refresh'
        )]
        [switch] $Refresh,

        # The personal access token to use for authentication.
        [Parameter(
            Mandatory,
            ParameterSetName = 'PAT'
        )]
        [String] $AccessToken
    )

    $vault = Get-SecretVault | Where-Object -Property ModuleName -EQ $script:SecretVault.Type

    if ($null -eq $vault) {
        Initialize-SecretVault -Name $script:SecretVault.Name -Type $script:SecretVault.Type
        $vault = Get-SecretVault | Where-Object -Property ModuleName -EQ $script:SecretVault.Type
    }

    $clientID = $script:App.$Mode.ClientID

    switch ($PSCmdlet.ParameterSetName) {
        'Refresh' {
            Write-Verbose 'Refreshing access token...'
            $tokenResponse = Invoke-GitHubDeviceFlowLogin -ClientID $clientID -RefreshToken $script:Config.User.Auth.RefreshToken.Value
        }
        'DeviceFlow' {
            Write-Verbose 'Logging in using device flow...'
            if ([string]::IsNullOrEmpty($Scope) -and ($Mode -eq 'OAuthApp')) {
                $Scope = 'gist read:org repo workflow'
            }
            $tokenResponse = Invoke-GitHubDeviceFlowLogin -ClientID $clientID -Scope $Scope
            $script:Config.User.Auth.Mode = $Mode
        }
        'PAT' {
            Write-Verbose 'Logging in using personal access token...'
            Reset-GitHubConfig -Scope 'User.Auth'
            $script:Config.User.Auth.AccessToken.Value = $Token
            $script:Config.User.Auth.Mode = 'PAT'
            Save-GitHubConfig
            Write-Host '✓ ' -ForegroundColor Green -NoNewline
            Write-Host 'Logged in using a personal access token (PAT)!'
            return
        }
    }

    if ($tokenResponse) {
        $script:Config.User.Auth.AccessToken.Value = $tokenResponse.access_token
        $script:Config.User.Auth.AccessToken.ExpirationDate = (Get-Date).AddSeconds($tokenResponse.expires_in)
        $script:Config.User.Auth.RefreshToken.Value = $tokenResponse.refresh_token
        $script:Config.User.Auth.RefreshToken.ExpirationDate = (Get-Date).AddSeconds($tokenResponse.refresh_token_expires_in)
        $script:Config.User.Auth.Scope = $tokenResponse.scope
    }

    Save-GitHubConfig

    Write-Host '✓ ' -ForegroundColor Green -NoNewline
    Write-Host "Logged in to GitHub!"
}
#endregion - From public/Auth/Connect-GitHubAccount.ps1

#region - From public/Auth/Disconnect-GitHubAccount.ps1
function Disconnect-GitHubAccount {
    <#
        .SYNOPSIS
        Disconnects from GitHub and removes the current GitHub configuration.

        .DESCRIPTION
        Disconnects from GitHub and removes the current GitHub configuration.

        .EXAMPLE
        Disconnect-GitHubAccount

        Disconnects from GitHub and removes the current GitHub configuration.
    #>

    [Alias('Disconnect-GHAccount')]
    [Alias('Disconnect-GitHub')]
    [Alias('Disconnect-GH')]
    [Alias('Logout-GitHubAccount')]
    [Alias('Logout-GHAccount')]
    [Alias('Logout-GitHub')]
    [Alias('Logout-GH')]
    [Alias('Logoff-GitHubAccount')]
    [Alias('Logoff-GHAccount')]
    [Alias('Logoff-GitHub')]
    [Alias('Logoff-GH')]
    [OutputType([void])]
    [CmdletBinding()]
    param ()

    Reset-GitHubConfig

    Write-Host "✓ " -ForegroundColor Green -NoNewline
    Write-Host "Logged out of GitHub!"
}
#endregion - From public/Auth/Disconnect-GitHubAccount.ps1

Export-ModuleMember -Function 'Connect-GitHubAccount','Disconnect-GitHubAccount','Get-GitHubConfig','Reset-GitHubConfig','Restore-GitHubConfig','Save-GitHubConfig','Set-GitHubConfig','' -Cmdlet '' -Variable '' -Alias '*'