PSModule.FX.psm1

[Cmdletbinding()]
param()

$scriptName = $MyInvocation.MyCommand.Name
Write-Verbose "[$scriptName] Importing subcomponents"

#region - Data import
Write-Verbose "[$scriptName] - [data] - Processing folder"
$dataFolder = (Join-Path $PSScriptRoot 'data')
Write-Verbose "[$scriptName] - [data] - [$dataFolder]"
Get-ChildItem -Path "$dataFolder" -Recurse -Force -Include '*.psd1' -ErrorAction SilentlyContinue | ForEach-Object {
    Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Importing"
    New-Variable -Name $_.BaseName -Value (Import-PowerShellDataFile -Path $_.FullName) -Force
    Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Done"
}

Write-Verbose "[$scriptName] - [data] - Done"
#endregion - Data import

#region - From /private
Write-Verbose "[$scriptName] - [/private] - Processing folder"

#region - From /private/Build
Write-Verbose "[$scriptName] - [/private/Build] - Processing folder"

#region - From /private/Build/Add-ContentFromItem.ps1
Write-Verbose "[$scriptName] - [/private/Build/Add-ContentFromItem.ps1] - Importing"

function Add-ContentFromItem {
    param(
        [string] $Path,
        [string] $RootModuleFilePath,
        [string] $RootPath
    )
    $relativeFolderPath = $Path.Replace($RootPath, '').TrimStart($pathSeparator)

    Add-Content -Path $RootModuleFilePath -Value @"
#region - From $relativeFolderPath
Write-Verbose "[`$scriptName] - [$relativeFolderPath] - Processing folder"

"@


    $subFolders = $Path | Get-ChildItem -Directory -Force | Sort-Object -Property Name
    foreach ($subFolder in $subFolders) {
        Add-ContentFromItem -Path $subFolder.FullName -RootModuleFilePath $RootModuleFilePath -RootPath $RootPath
    }

    $files = $Path | Get-ChildItem -File -Force -Filter '*.ps1' | Sort-Object -Property FullName
    foreach ($file in $files) {
        $relativeFilePath = $file.FullName.Replace($RootPath, '').TrimStart($pathSeparator)
        Add-Content -Path $RootModuleFilePath -Value @"
#region - From $relativeFilePath
Write-Verbose "[`$scriptName] - [$relativeFilePath] - Importing"

"@

        Get-Content -Path $file.FullName | Add-Content -Path $RootModuleFilePath
        Add-Content -Path $RootModuleFilePath -Value @"

Write-Verbose "[`$scriptName] - [$relativeFilePath] - Done"
#endregion - From $relativeFilePath
"@

    }
    Add-Content -Path $RootModuleFilePath -Value @"

Write-Verbose "[`$scriptName] - [$relativeFolderPath] - Done"
#endregion - From $relativeFolderPath

"@

}

Write-Verbose "[$scriptName] - [/private/Build/Add-ContentFromItem.ps1] - Done"
#endregion - From /private/Build/Add-ContentFromItem.ps1
#region - From /private/Build/Build-PSModuleBase.ps1
Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleBase.ps1] - Importing"

function Build-PSModuleBase {
    [CmdletBinding()]
    param(
        # Path to the folder where the module source code is located.
        [Parameter(Mandatory)]
        [string] $SourceFolderPath,

        # Path to the folder where the built modules are outputted.
        [Parameter(Mandatory)]
        [string] $OutputFolderPath
    )

    $moduleName = Split-Path -Path $SourceFolderPath -Leaf
    Write-Output "::group::[$moduleName] - Build base"

    $deletePaths = @(
        'init',
        'private',
        'public',
        "$moduleName.psd1",
        "$moduleName.psm1"
    )
    Write-Verbose "Copying files from [$SourceFolderPath] to [$OutputFolderPath]"
    Copy-Item -Path "$SourceFolderPath" -Destination $OutputFolderPath -Recurse -Force -Verbose
    Write-Verbose "Deleting files from [$OutputFolderPath] that are not needed"
    Get-ChildItem -Path $OutputFolderPath -Recurse -Force | Where-Object { $_.Name -in $deletePaths } | Remove-Item -Force -Recurse -Verbose
    Write-Output '::endgroup::'

    Write-Output "::group::[$moduleName] - Build base - Result"
    (Get-ChildItem -Path $OutputFolderPath -Recurse -Force).FullName | Sort-Object
    Write-Output '::endgroup::'
}

Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleBase.ps1] - Done"
#endregion - From /private/Build/Build-PSModuleBase.ps1
#region - From /private/Build/Build-PSModuleDocumentation.ps1
Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleDocumentation.ps1] - Importing"

function Build-PSModuleDocumentation {
    [CmdletBinding()]
    param(
        # Path to the folder where the module source code is located.
        [Parameter(Mandatory)]
        [string] $SourceFolderPath,

        # Path to the folder where the built modules are outputted.
        [Parameter(Mandatory)]
        [string] $OutputFolderPath
    )

    $moduleName = Split-Path -Path $SourceFolderPath -Leaf
    Write-Output "::group::[$moduleName] - Build documentation"

    $manifestFile = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -As FileInfo -Verbose:$false
    Resolve-PSModuleDependencies -ManifestFilePath $manifestFile

    Write-Verbose "[$moduleName] - Importing module"
    Import-Module $moduleName

    Write-Verbose "[$moduleName] - List loaded modules"
    $availableModules = Get-Module -ListAvailable -Refresh -Verbose:$false
    $availableModules | Select-Object Name, Version, Path | Sort-Object Name | Format-Table -AutoSize

    if ($moduleName -notin $availableModules.Name) {
        throw "[$moduleName] - Module not found"
    }

    New-MarkdownHelp -Module $moduleName -OutputFolder $OutputFolderPath -Force -Verbose
    Write-Output '::endgroup::'

    Write-Output "::group::[$moduleName] - Build documentation - Result"
    Get-ChildItem -Path $OutputFolderPath -Recurse -Force -Include '*.md' | ForEach-Object {
        Write-Output "::debug::[$moduleName] - [$_] - [$(Get-FileHash -Path $_.FullName -Algorithm SHA256)]"
    }
    Write-Output '::endgroup::'
}

Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleDocumentation.ps1] - Done"
#endregion - From /private/Build/Build-PSModuleDocumentation.ps1
#region - From /private/Build/Build-PSModuleManifest.ps1
Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleManifest.ps1] - Importing"

function Build-PSModuleManifest {
    [CmdletBinding()]
    param(
        # Path to the folder where the module source code is located.
        [Parameter(Mandatory)]
        [string] $SourceFolderPath,

        # Path to the folder where the built modules are outputted.
        [Parameter(Mandatory)]
        [string] $OutputFolderPath
    )

    $moduleName = Split-Path -Path $SourceFolderPath -Leaf
    Write-Output "::group::[$moduleName] - Build manifest file"
    Write-Verbose "[$moduleName] - Finding manifest file"

    $manifestFile = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -As FileInfo
    $manifestFileName = $manifestFile.Name

    $manifest = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -As Hashtable

    $manifest.RootModule = Get-PSModuleRootModule -SourceFolderPath $SourceFolderPath
    $manifest.Author = $manifest.Keys -contains 'Author' ? -not [string]::IsNullOrEmpty($manifest.Author) ? $manifest.Author : $env:GITHUB_REPOSITORY_OWNER : $env:GITHUB_REPOSITORY_OWNER
    Write-Verbose "[$moduleName] - [Author] - [$($manifest.Author)]"

    $manifest.CompanyName = $manifest.Keys -contains 'CompanyName' ? -not [string]::IsNullOrEmpty($manifest.CompanyName) ? $manifest.CompanyName : $env:GITHUB_REPOSITORY_OWNER : $env:GITHUB_REPOSITORY_OWNER
    Write-Verbose "[$moduleName] - [CompanyName] - [$($manifest.CompanyName)]"

    $year = Get-Date -Format 'yyyy'
    $copyRightOwner = $manifest.CompanyName -eq $manifest.Author ? $manifest.Author : "$($manifest.Author) | $($manifest.CompanyName)"
    $copyRight = "(c) $year $copyRightOwner. All rights reserved."
    $manifest.CopyRight = $manifest.Keys -contains 'CopyRight' ? -not [string]::IsNullOrEmpty($manifest.CopyRight) ? $manifest.CopyRight : $copyRight : $copyRight
    Write-Verbose "[$moduleName] - [CopyRight] - [$($manifest.CopyRight)]"

    $manifest.Description = $manifest.Keys -contains 'Description' ? -not [string]::IsNullOrEmpty($manifest.Description) ? $manifest.Description : 'Unknown' : 'Unknown'
    Write-Verbose "[$moduleName] - [Description] - [$($manifest.Description)]"

    $manifest.PowerShellHostName = $manifest.Keys -contains 'PowerShellHostName' ? -not [string]::IsNullOrEmpty($manifest.PowerShellHostName) ? $manifest.PowerShellHostName : $null : $null
    Write-Verbose "[$moduleName] - [PowerShellHostName] - [$($manifest.PowerShellHostName)]"

    $manifest.PowerShellHostVersion = $manifest.Keys -contains 'PowerShellHostVersion' ? -not [string]::IsNullOrEmpty($manifest.PowerShellHostVersion) ? $manifest.PowerShellHostVersion : $null : $null
    Write-Verbose "[$moduleName] - [PowerShellHostVersion] - [$($manifest.PowerShellHostVersion)]"

    $manifest.DotNetFrameworkVersion = $manifest.Keys -contains 'DotNetFrameworkVersion' ? -not [string]::IsNullOrEmpty($manifest.DotNetFrameworkVersion) ? $manifest.DotNetFrameworkVersion : $null : $null
    Write-Verbose "[$moduleName] - [DotNetFrameworkVersion] - [$($manifest.DotNetFrameworkVersion)]"

    $manifest.ClrVersion = $manifest.Keys -contains 'ClrVersion' ? -not [string]::IsNullOrEmpty($manifest.ClrVersion) ? $manifest.ClrVersion : $null : $null
    Write-Verbose "[$moduleName] - [ClrVersion] - [$($manifest.ClrVersion)]"

    $manifest.ProcessorArchitecture = $manifest.Keys -contains 'ProcessorArchitecture' ? -not [string]::IsNullOrEmpty($manifest.ProcessorArchitecture) ? $manifest.ProcessorArchitecture : 'None' : 'None'
    Write-Verbose "[$moduleName] - [ProcessorArchitecture] - [$($manifest.ProcessorArchitecture)]"

    #Get the path separator for the current OS
    $pathSeparator = [System.IO.Path]::DirectorySeparatorChar

    Write-Verbose "[$moduleName] - [FileList]"
    $files = $SourceFolderPath | Get-ChildItem -File -ErrorAction SilentlyContinue | Where-Object -Property Name -NotLike '*.ps1'
    $files += $SourceFolderPath | Get-ChildItem -Directory | Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue
    $files = $files | Select-Object -ExpandProperty FullName | ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) }
    $fileList = $files | Where-Object { $_ -notLike 'public*' -and $_ -notLike 'private*' -and $_ -notLike 'classes*' }
    $manifest.FileList = $fileList.count -eq 0 ? @() : @($fileList)
    $manifest.FileList | ForEach-Object { Write-Verbose "[$moduleName] - [FileList] - [$_]" }

    Write-Verbose "[$moduleName] - [RequiredAssemblies]"
    $requiredAssembliesFolderPath = Join-Path $SourceFolderPath 'assemblies'
    $requiredAssemblies = Get-ChildItem -Path $RequiredAssembliesFolderPath -Recurse -File -ErrorAction SilentlyContinue -Filter '*.dll' |
        Select-Object -ExpandProperty FullName |
        ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) }
    $manifest.RequiredAssemblies = $requiredAssemblies.count -eq 0 ? @() : @($requiredAssemblies)
    $manifest.RequiredAssemblies | ForEach-Object { Write-Verbose "[$moduleName] - [RequiredAssemblies] - [$_]" }

    Write-Verbose "[$moduleName] - [NestedModules]"
    $nestedModulesFolderPath = Join-Path $SourceFolderPath 'modules'
    $nestedModules = Get-ChildItem -Path $nestedModulesFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.psm1', '*.ps1' |
        Select-Object -ExpandProperty FullName |
        ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) }
    $manifest.NestedModules = $nestedModules.count -eq 0 ? @() : @($nestedModules)
    $manifest.NestedModules | ForEach-Object { Write-Verbose "[$moduleName] - [NestedModules] - [$_]" }

    Write-Verbose "[$moduleName] - [ScriptsToProcess]"
    $allScriptsToProcess = @('scripts', 'classes') | ForEach-Object {
        Write-Verbose "[$moduleName] - [ScriptsToProcess] - Processing [$_]"
        $scriptsFolderPath = Join-Path $SourceFolderPath $_
        $scriptsToProcess = Get-ChildItem -Path $scriptsFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.ps1' |
            Select-Object -ExpandProperty FullName |
            ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) }
            $scriptsToProcess
        }
        $manifest.ScriptsToProcess = $allScriptsToProcess.count -eq 0 ? @() : @($allScriptsToProcess)
        $manifest.ScriptsToProcess | ForEach-Object { Write-Verbose "[$moduleName] - [ScriptsToProcess] - [$_]" }

        Write-Verbose "[$moduleName] - [TypesToProcess]"
        $typesToProcess = Get-ChildItem -Path $SourceFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.Types.ps1xml' |
            Select-Object -ExpandProperty FullName |
            ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) }
    $manifest.TypesToProcess = $typesToProcess.count -eq 0 ? @() : @($typesToProcess)
    $manifest.TypesToProcess | ForEach-Object { Write-Verbose "[$moduleName] - [TypesToProcess] - [$_]" }

    Write-Verbose "[$moduleName] - [FormatsToProcess]"
    $formatsToProcess = Get-ChildItem -Path $SourceFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.Format.ps1xml' |
        Select-Object -ExpandProperty FullName |
        ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) }
    $manifest.FormatsToProcess = $formatsToProcess.count -eq 0 ? @() : @($formatsToProcess)
    $manifest.FormatsToProcess | ForEach-Object { Write-Verbose "[$moduleName] - [FormatsToProcess] - [$_]" }

    Write-Verbose "[$moduleName] - [DscResourcesToExport]"
    $dscResourcesToExportFolderPath = Join-Path $SourceFolderPath 'dscResources'
    $dscResourcesToExport = Get-ChildItem -Path $dscResourcesToExportFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.psm1' |
        Select-Object -ExpandProperty FullName |
        ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) }
    $manifest.DscResourcesToExport = $dscResourcesToExport.count -eq 0 ? @() : @($dscResourcesToExport)
    $manifest.DscResourcesToExport | ForEach-Object { Write-Verbose "[$moduleName] - [DscResourcesToExport] - [$_]" }

    $manifest.FunctionsToExport = Get-PSModuleFunctionsToExport -SourceFolderPath $SourceFolderPath
    $manifest.CmdletsToExport = Get-PSModuleCmdletsToExport -SourceFolderPath $SourceFolderPath
    $manifest.AliasesToExport = Get-PSModuleAliasesToExport -SourceFolderPath $SourceFolderPath
    $manifest.VariablesToExport = Get-PSModuleVariablesToExport -SourceFolderPath $SourceFolderPath

    Write-Verbose "[$moduleName] - [ModuleList]"
    $moduleList = Get-ChildItem -Path $SourceFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.psm1' -Exclude "$moduleName.psm1" |
        Select-Object -ExpandProperty FullName |
        ForEach-Object { $_.Replace($SourceFolderPath, '').TrimStart($pathSeparator) }
    $manifest.ModuleList = $moduleList.count -eq 0 ? @() : @($moduleList)
    $manifest.ModuleList | ForEach-Object { Write-Verbose "[$moduleName] - [ModuleList] - [$_]" }

    Write-Verbose "[$moduleName] - Gather dependencies from files"

    $capturedModules = @()
    $capturedVersions = @()
    $capturedPSEdition = @()

    $files = $SourceFolderPath | Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue
    Write-Verbose "[$moduleName] - Processing [$($files.Count)] files"
    foreach ($file in $files) {
        $relativePath = $file.FullName.Replace($SourceFolderPath, '').TrimStart($pathSeparator)
        Write-Verbose "[$moduleName] - [$relativePath]"

        if ($file.extension -in '.psm1', '.ps1') {
            $fileContent = Get-Content -Path $file

            switch -Regex ($fileContent) {
                # RequiredModules -> REQUIRES -Modules <Module-Name> | <Hashtable>, @() if not provided
                '^#Requires -Modules (.+)$' {
                    # Add captured module name to array
                    $capturedMatches = $matches[1].Split(',').trim()
                    $capturedMatches | ForEach-Object {
                        Write-Verbose "[$moduleName] - [$relativePath] - [REQUIRED -Modules] - [$_]"
                        $hashtable = '\@\s*\{[^\}]*\}'
                        if ($_ -match $hashtable) {
                            $modules = ConvertTo-Hashtable -InputString $_
                            Write-Verbose "[$moduleName] - [$relativePath] - [REQUIRED -Modules] - [$_] - Hashtable"
                            $modules.Keys | ForEach-Object {
                                Write-Verbose "$($modules[$_])]"
                            }
                            $capturedModules += $modules
                        } else {
                            Write-Verbose "[$moduleName] - [$relativePath] - [REQUIRED -Modules] - [$_] - String"
                            $capturedModules += $_
                        }
                    }
                }
                # PowerShellVersion -> REQUIRES -Version <N>[.<n>], $null if not provided
                '^#Requires -Version (.+)$' {
                    Write-Verbose "[$moduleName] - [$relativePath] - [REQUIRED -Version] - [$($matches[1])]"
                    # Add captured module name to array
                    $capturedVersions += $matches[1]
                }
                #CompatiblePSEditions -> REQUIRES -PSEdition <PSEdition-Name>, $null if not provided
                '^#Requires -PSEdition (.+)$' {
                    Write-Verbose "[$moduleName] - [$relativePath] - [REQUIRED -PSEdition] - [$($matches[1])]"
                    # Add captured module name to array
                    $capturedPSEdition += $matches[1]
                }
            }
        }
    }

    Write-Verbose "[$moduleName] - [RequiredModules]"
    $capturedModules = $capturedModules
    $manifest.RequiredModules = $capturedModules
    $manifest.RequiredModules | ForEach-Object { Write-Verbose "[$moduleName] - [RequiredModules] - [$_]" }

    Write-Verbose "[$moduleName] - [RequiredModulesUnique]"
    $manifest.RequiredModules = $manifest.RequiredModules | Sort-Object -Unique
    $manifest.RequiredModules | ForEach-Object { Write-Verbose "[$moduleName] - [RequiredModulesUnique] - [$_]" }

    Write-Verbose "[$moduleName] - [PowerShellVersion]"
    $capturedVersions = $capturedVersions | Sort-Object -Unique -Descending
    $manifest.PowerShellVersion = $capturedVersions.count -eq 0 ? [version]'7.0' : [version]($capturedVersions | Select-Object -First 1)
    Write-Verbose "[$moduleName] - [PowerShellVersion] - [$($manifest.PowerShellVersion)]"

    Write-Verbose "[$moduleName] - [CompatiblePSEditions]"
    $capturedPSEdition = $capturedPSEdition | Sort-Object -Unique
    if ($capturedPSEdition.count -eq 2) {
        throw 'The module is requires both Desktop and Core editions.'
    }
    $manifest.CompatiblePSEditions = $capturedPSEdition.count -eq 0 ? @('Core', 'Desktop') : @($capturedPSEdition)
    $manifest.CompatiblePSEditions | ForEach-Object { Write-Verbose "[$moduleName] - [CompatiblePSEditions] - [$_]" }

    Write-Verbose "[$moduleName] - [PrivateData]"
    $privateData = $manifest.Keys -contains 'PrivateData' ? $null -ne $manifest.PrivateData ? $manifest.PrivateData : @{} : @{}
    if ($manifest.Keys -contains 'PrivateData') {
        $manifest.Remove('PrivateData')
    }

    Write-Verbose "[$moduleName] - [HelpInfoURI]"
    $manifest.HelpInfoURI = $privateData.Keys -contains 'HelpInfoURI' ? $null -ne $privateData.HelpInfoURI ? $privateData.HelpInfoURI : '' : ''
    Write-Verbose "[$moduleName] - [HelpInfoURI] - [$($manifest.HelpInfoURI)]"
    if ([string]::IsNullOrEmpty($manifest.HelpInfoURI)) {
        $manifest.Remove('HelpInfoURI')
    }

    Write-Verbose "[$moduleName] - [DefaultCommandPrefix]"
    $manifest.DefaultCommandPrefix = $privateData.Keys -contains 'DefaultCommandPrefix' ? $null -ne $privateData.DefaultCommandPrefix ? $privateData.DefaultCommandPrefix : '' : ''
    Write-Verbose "[$moduleName] - [DefaultCommandPrefix] - [$($manifest.DefaultCommandPrefix)]"

    $PSData = $privateData.Keys -contains 'PSData' ? $null -ne $privateData.PSData ? $privateData.PSData : @{} : @{}

    Write-Verbose "[$moduleName] - [Tags]"
    $manifest.Tags = $PSData.Keys -contains 'Tags' ? $null -ne $PSData.Tags ? $PSData.Tags : @() : @()
    # Add tags for compatability mode. https://docs.microsoft.com/en-us/powershell/scripting/developer/module/how-to-write-a-powershell-module-manifest?view=powershell-7.1#compatibility-tags
    if ($manifest.CompatiblePSEditions -contains 'Desktop') {
        if ($manifest.Tags -notcontains 'PSEdition_Desktop') {
            $manifest.Tags += 'PSEdition_Desktop'
        }
    }
    if ($manifest.CompatiblePSEditions -contains 'Core') {
        if ($manifest.Tags -notcontains 'PSEdition_Core') {
            $manifest.Tags += 'PSEdition_Core'
        }
    }
    $manifest.Tags | ForEach-Object { Write-Verbose "[$moduleName] - [Tags] - [$_]" }

    if ($PSData.Tags -contains 'PSEdition_Core' -and $manifest.PowerShellVersion -lt '6.0') {
        throw "[$moduleName] - [Tags] - Cannot be PSEdition = 'Core' and PowerShellVersion < 6.0"
    }

    Write-Verbose "[$moduleName] - [LicenseUri]"
    $manifest.LicenseUri = $PSData.Keys -contains 'LicenseUri' ? $null -ne $PSData.LicenseUri ? $PSData.LicenseUri : '' : ''
    Write-Verbose "[$moduleName] - [LicenseUri] - [$($manifest.LicenseUri)]"
    if ([string]::IsNullOrEmpty($manifest.LicenseUri)) {
        $manifest.Remove('LicenseUri')
    }

    Write-Verbose "[$moduleName] - [ProjectUri]"
    $manifest.ProjectUri = $PSData.Keys -contains 'ProjectUri' ? $null -ne $PSData.ProjectUri ? $PSData.ProjectUri : '' : ''
    Write-Verbose "[$moduleName] - [ProjectUri] - [$($manifest.ProjectUri)]"
    if ([string]::IsNullOrEmpty($manifest.ProjectUri)) {
        $manifest.Remove('ProjectUri')
    }

    Write-Verbose "[$moduleName] - [IconUri]"
    $manifest.IconUri = $PSData.Keys -contains 'IconUri' ? $null -ne $PSData.IconUri ? $PSData.IconUri : '' : ''
    Write-Verbose "[$moduleName] - [IconUri] - [$($manifest.IconUri)]"
    if ([string]::IsNullOrEmpty($manifest.IconUri)) {
        $manifest.Remove('IconUri')
    }

    Write-Verbose "[$moduleName] - [ReleaseNotes]"
    $manifest.ReleaseNotes = $PSData.Keys -contains 'ReleaseNotes' ? $null -ne $PSData.ReleaseNotes ? $PSData.ReleaseNotes : '' : ''
    Write-Verbose "[$moduleName] - [ReleaseNotes] - [$($manifest.ReleaseNotes)]"
    if ([string]::IsNullOrEmpty($manifest.ReleaseNotes)) {
        $manifest.Remove('ReleaseNotes')
    }

    Write-Verbose "[$moduleName] - [PreRelease]"
    $manifest.PreRelease = $PSData.Keys -contains 'PreRelease' ? $null -ne $PSData.PreRelease ? $PSData.PreRelease : '' : ''
    Write-Verbose "[$moduleName] - [PreRelease] - [$($manifest.PreRelease)]"
    if ([string]::IsNullOrEmpty($manifest.PreRelease)) {
        $manifest.Remove('PreRelease')
    }

    Write-Verbose "[$moduleName] - [RequireLicenseAcceptance]"
    $manifest.RequireLicenseAcceptance = $PSData.Keys -contains 'RequireLicenseAcceptance' ? $null -ne $PSData.RequireLicenseAcceptance ? $PSData.RequireLicenseAcceptance : $false : $false
    Write-Verbose "[$moduleName] - [RequireLicenseAcceptance] - [$($manifest.RequireLicenseAcceptance)]"
    if ($manifest.RequireLicenseAcceptance -eq $false) {
        $manifest.Remove('RequireLicenseAcceptance')
    }

    Write-Verbose "[$moduleName] - [ExternalModuleDependencies]"
    $manifest.ExternalModuleDependencies = $PSData.Keys -contains 'ExternalModuleDependencies' ? $null -ne $PSData.ExternalModuleDependencies ? $PSData.ExternalModuleDependencies : @() : @()
    if (($manifest.ExternalModuleDependencies).count -eq 0) {
        $manifest.Remove('ExternalModuleDependencies')
    } else {
        $manifest.ExternalModuleDependencies | ForEach-Object { Write-Verbose "[$moduleName] - [ExternalModuleDependencies] - [$_]" }
    }
    <#
        PSEdition_Desktop: Packages that are compatible with Windows PowerShell
        PSEdition_Core: Packages that are compatible with PowerShell 6 and higher
        Windows: Packages that are compatible with the Windows Operating System
        Linux: Packages that are compatible with Linux Operating Systems
        MacOS: Packages that are compatible with the Mac Operating System
        https://learn.microsoft.com/en-us/powershell/gallery/concepts/package-manifest-affecting-ui?view=powershellget-2.x#tag-details
    #>


    Write-Verbose 'Creating new manifest file in outputs folder'
    $outputManifestPath = (Join-Path -Path $OutputFolderPath $moduleName $manifestFileName)
    Write-Verbose "OutputManifestPath - [$outputManifestPath]"
    New-ModuleManifest -Path $outputManifestPath @manifest

    Write-Verbose 'Invoke-Formatter on manifest file'
    $manifestContent = Get-Content -Path $outputManifestPath -Raw
    Invoke-Formatter -ScriptDefinition $manifestContent -Verbose |
        Out-File -FilePath $outputManifestPath -Encoding utf8 -Force

    Write-Output "::group::[$moduleName] - Build manifest file - Result"
    Show-FileContent -Path $outputManifestPath
    Write-Output '::endgroup::'
}

Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleManifest.ps1] - Done"
#endregion - From /private/Build/Build-PSModuleManifest.ps1
#region - From /private/Build/Build-PSModuleRootModule.ps1
Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleRootModule.ps1] - Importing"

function Build-PSModuleRootModule {
    [CmdletBinding()]
    param(
        # Path to the folder where the module source code is located.
        [Parameter(Mandatory)]
        [string] $SourceFolderPath,

        # Path to the folder where the built modules are outputted.
        [Parameter(Mandatory)]
        [string] $OutputFolderPath
    )

    $moduleName = Split-Path -Path $SourceFolderPath -Leaf
    Write-Output "::group::[$moduleName] - Build root module"

    # RE-create the moduleName.psm1 file
    # concat all the files, and add Export-ModuleMembers at the end with modules.
    $moduleOutputfolder = Join-Path -Path $OutputFolderPath -ChildPath $moduleName
    $rootModuleFile = New-Item -Path $moduleOutputfolder -Name "$moduleName.psm1" -Force

    # Add content to the root module file in the following order:
    # 1. Load data files from Data folder
    # 2. Init
    # 3. Private
    # 4. Public
    # 5 *.ps1 on module root
    # 6. Export-ModuleMember

    Add-Content -Path $rootModuleFile.FullName -Value @'
[Cmdletbinding()]
param()

$scriptName = $MyInvocation.MyCommand.Name
Write-Verbose "[$scriptName] Importing subcomponents"

#region - Data import
Write-Verbose "[$scriptName] - [data] - Processing folder"
$dataFolder = (Join-Path $PSScriptRoot 'data')
Write-Verbose "[$scriptName] - [data] - [$dataFolder]"
Get-ChildItem -Path "$dataFolder" -Recurse -Force -Include '*.psd1' -ErrorAction SilentlyContinue | ForEach-Object {
    Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Importing"
    New-Variable -Name $_.BaseName -Value (Import-PowerShellDataFile -Path $_.FullName) -Force
    Write-Verbose "[$scriptName] - [data] - [$($_.Name)] - Done"
}

Write-Verbose "[$scriptName] - [data] - Done"
#endregion - Data import

'@


    $folderProcessingOrder = @(
        'init',
        'private',
        'public'
    )


    $subFolders = Get-ChildItem -Path $SourceFolderPath -Directory -Force | Where-Object -Property Name -In $folderProcessingOrder
    foreach ($subFolder in $subFolders) {
        Add-ContentFromItem -Path $subFolder.FullName -RootModuleFilePath $rootModuleFile.FullName -RootPath $SourceFolderPath
    }

    $files = $SourceFolderPath | Get-ChildItem -File -Force -Filter '*.ps1'
    foreach ($file in $files) {
        $relativePath = $file.FullName.Replace($SourceFolderPath, '').TrimStart($pathSeparator)
        Add-Content -Path $rootModuleFile.FullName -Value @"
#region - From $relativePath
Write-Verbose "[`$scriptName] - [$relativePath] - Importing"

"@

        Get-Content -Path $file.FullName | Add-Content -Path $rootModuleFile.FullName

        Add-Content -Path $rootModuleFile.FullName -Value @"
Write-Verbose "[`$scriptName] - [$relativePath] - Done"
#endregion - From $relativePath

"@

        $file | Remove-Item -Force
    }

    $functionsToExport = Get-PSModuleFunctionsToExport -SourceFolderPath $SourceFolderPath
    $functionsToExport = $($functionsToExport -join "','")

    $cmdletsToExport = Get-PSModuleCmdletsToExport -SourceFolderPath $SourceFolderPath
    $cmdletsToExport = $($cmdletsToExport -join "','")

    $variablesToExport = Get-PSModuleVariablesToExport -SourceFolderPath $SourceFolderPath
    $variablesToExport = $($variablesToExport -join "','")

    $aliasesToExport = Get-PSModuleAliasesToExport -SourceFolderPath $SourceFolderPath
    $aliasesToExport = $($aliasesToExport -join "','")

    Add-Content -Path $rootModuleFile -Value "Export-ModuleMember -Function '$functionsToExport' -Cmdlet '$cmdletsToExport' -Variable '$variablesToExport' -Alias '$aliasesToExport'"
    Write-Output '::endgroup::'

    Write-Output "::group::[$moduleName] - Build root module - Result"
    Show-FileContent -Path $rootModuleFile
    Write-Output '::endgroup::'
}

Write-Verbose "[$scriptName] - [/private/Build/Build-PSModuleRootModule.ps1] - Done"
#endregion - From /private/Build/Build-PSModuleRootModule.ps1
#region - From /private/Build/Get-PSModuleAliasesToExport.ps1
Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleAliasesToExport.ps1] - Importing"

function Get-PSModuleAliasesToExport {
    [CmdletBinding()]
    param(
        # Path to the folder where the module source code is located.
        [Parameter(Mandatory)]
        [string] $SourceFolderPath
    )

    $moduleName = Split-Path -Path $SourceFolderPath -Leaf
    $manifestPropertyName = 'AliasesToExport'

    $manifest = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -Verbose:$false

    Write-Verbose "[$moduleName] - [$manifestPropertyName]"
    $aliasesToExport = ($manifest.AliasesToExport).count -eq 0 ? '' : @($manifest.AliasesToExport)
    $aliasesToExport | ForEach-Object { Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$_]" }
    $aliasesToExport
}

Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleAliasesToExport.ps1] - Done"
#endregion - From /private/Build/Get-PSModuleAliasesToExport.ps1
#region - From /private/Build/Get-PSModuleCmdletsToExport.ps1
Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleCmdletsToExport.ps1] - Importing"

function Get-PSModuleCmdletsToExport {
    [CmdletBinding()]
    param(
        # Path to the folder where the module source code is located.
        [Parameter(Mandatory)]
        [string] $SourceFolderPath
    )

    $moduleName = Split-Path -Path $SourceFolderPath -Leaf
    $manifestPropertyName = 'CmdletsToExport'

    $manifest = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -Verbose:$false

    Write-Verbose "[$moduleName] - [$manifestPropertyName]"
    $cmdletsToExport = ($manifest.CmdletsToExport).count -eq 0 ? '' : @($manifest.CmdletsToExport)
    $cmdletsToExport | ForEach-Object { Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$_]" }
    $cmdletsToExport
}

Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleCmdletsToExport.ps1] - Done"
#endregion - From /private/Build/Get-PSModuleCmdletsToExport.ps1
#region - From /private/Build/Get-PSModuleFolders.ps1
Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleFolders.ps1] - Importing"

function Get-PSModuleFolders {
    <#
        .SYNOPSIS
        Get all folders where the content of the folder is a module file or manifest file.

        .DESCRIPTION
        Get all folders where the content of the folder is a module file or manifest file.
        Search is recursive.

        .EXAMPLE
        Get-PSModuleFolders -Path 'src'

        Get all folders where the content of the folder is a module file or manifest file.
    #>

    [CmdletBinding()]
    param(
        # Path to the folder where the modules are located.
        [Parameter()]
        [ValidateNotNullOrEmpty()]
        [string] $Path = 'src'
    )

    $moduleFolders = Get-ChildItem -Path $Path -Directory -Recurse -ErrorAction SilentlyContinue | Where-Object {
        Get-ChildItem -Path $_.FullName -File -ErrorAction SilentlyContinue | Where-Object {
            $_.Name -match '.*\.psm1|.*\.psd1'
        }
    }
    return $moduleFolders
}

Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleFolders.ps1] - Done"
#endregion - From /private/Build/Get-PSModuleFolders.ps1
#region - From /private/Build/Get-PSModuleFunctionsToExport.ps1
Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleFunctionsToExport.ps1] - Importing"

function Get-PSModuleFunctionsToExport {
    [CmdletBinding()]
    param(
        # Path to the folder where the module source code is located.
        [Parameter(Mandatory)]
        [string] $SourceFolderPath
    )

    $moduleName = Split-Path -Path $SourceFolderPath -Leaf
    $manifestPropertyName = 'FunctionsToExport'

    Write-Verbose "[$moduleName] - [$manifestPropertyName]"
    Write-Verbose "[$moduleName] - [$manifestPropertyName] - Checking path for functions and filters"

    $publicFolderPath = Join-Path $SourceFolderPath 'public'
    Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$publicFolderPath]"
    $functionsToExport = Get-ChildItem -Path $publicFolderPath -Recurse -File -ErrorAction SilentlyContinue -Include '*.ps1' | ForEach-Object {
        $fileContent = Get-Content -Path $_.FullName -Raw
        $containsFunction = ($fileContent -match 'function ') -or ($fileContent -match 'filter ')
        Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$($_.BaseName)] - [$containsFunction]"
        $containsFunction ? $_.BaseName : $null
    }
    $functionsToExport = $functionsToExport.count -eq 0 ? @() : @($functionsToExport)

    $functionsToExport
}

Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleFunctionsToExport.ps1] - Done"
#endregion - From /private/Build/Get-PSModuleFunctionsToExport.ps1
#region - From /private/Build/Get-PSModuleManifest.ps1
Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleManifest.ps1] - Importing"

function Get-PSModuleManifest {
    <#
        .SYNOPSIS
        Get the module manifest.

        .DESCRIPTION
        Get the module manifest as a hashtable.

        .EXAMPLE
        Get-PSModuleManifest -SourceFolderPath 'src/PSModule.FX'
    #>

    [OutputType([string], [System.IO.FileInfo], [System.Collections.Hashtable])]
    [CmdletBinding()]
    param(
        # Path to the folder where the module source code is located.
        [Parameter(Mandatory)]
        [string] $SourceFolderPath,

        # The format of the output
        [Parameter()]
        [ValidateSet('FileName', 'FilePath', 'FileInfo', 'Content', 'Hashtable')]
        [string] $As = 'Hashtable'
    )

    $moduleName = Split-Path -Path $SourceFolderPath -Leaf
    $manifestPropertyName = 'ManifestFile'

    Write-Verbose "[$moduleName] - [$manifestPropertyName]"
    $manifestFileName = "$moduleName.psd1"
    Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$manifestFileName]"
    Write-Verbose "[$moduleName] - [$manifestPropertyName] - Checking path for manifest file"

    $manifestFilePath = Join-Path -Path $SourceFolderPath $manifestFileName
    if (-not (Test-Path -Path $manifestFilePath)) {
        Write-Warning "[$moduleName] - [$manifestPropertyName] - 🟥 No manifest file found"
        return $null
    }
    Write-Verbose "[$moduleName] - [$manifestPropertyName] - 🟩 Found manifest file"

    switch ($As) {
        'FileName' {
            return $manifestFileName
        }
        'FilePath' {
            return $manifestFilePath
        }
        'FileInfo' {
            return Get-Item -Path $manifestFilePath
        }
        'Content' {
            return Get-Content -Path $manifestFilePath
        }
        'Hashtable' {
            return Import-PowerShellDataFile -Path $manifestFilePath
        }
    }
}

Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleManifest.ps1] - Done"
#endregion - From /private/Build/Get-PSModuleManifest.ps1
#region - From /private/Build/Get-PSModuleRootModule.ps1
Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleRootModule.ps1] - Importing"

function Get-PSModuleRootModule {
    [CmdletBinding()]
    param(
        # Path to the folder where the module source code is located.
        [Parameter(Mandatory)]
        [string] $SourceFolderPath
    )

    $moduleName = Split-Path -Path $SourceFolderPath -Leaf
    $manifestPropertyName = 'RootModule'

    Write-Verbose "[$moduleName] - [$manifestPropertyName] - Find root module"
    $manifest = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -Verbose:$false

    $rootModule = $(Get-ChildItem -Path $SourceFolderPath -File | Where-Object { $_.BaseName -like $_.Directory.BaseName -and ($_.Extension -in '.psm1', '.ps1', '.dll', '.cdxml', '.xaml') } | Select-Object -First 1 -ExpandProperty Name )
    if (-not $rootModule) {
        Write-Verbose "[$moduleName] - [$manifestPropertyName] - No RootModule found"
    }

    Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$RootModule]"

    $moduleType = switch -Regex ($RootModule) {
        '\.(ps1|psm1)$' { 'Script' }
        '\.dll$' { 'Binary' }
        '\.cdxml$' { 'CIM' }
        '\.xaml$' { 'Workflow' }
        default { 'Manifest' }
    }
    Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$moduleType]"

    $supportedModuleTypes = @('Script', 'Manifest')
    if ($moduleType -notin $supportedModuleTypes) {
        Write-Warning "[$moduleName] - [$manifestPropertyName] - [$moduleType] - Module type not supported"
    }

    $rootModule = [string]::IsNullOrEmpty($manifest.RootModule) ? $rootModule : @($manifest.RootModule)
    $rootModule
}

Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleRootModule.ps1] - Done"
#endregion - From /private/Build/Get-PSModuleRootModule.ps1
#region - From /private/Build/Get-PSModuleVariablesToExport.ps1
Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleVariablesToExport.ps1] - Importing"

function Get-PSModuleVariablesToExport {
    [CmdletBinding()]
    param(
        # Path to the folder where the module source code is located.
        [Parameter(Mandatory)]
        [string] $SourceFolderPath
    )

    $moduleName = Split-Path -Path $SourceFolderPath -Leaf
    $manifestPropertyName = 'VariablesToExport'

    $manifest = Get-PSModuleManifest -SourceFolderPath $SourceFolderPath -Verbose:$false

    Write-Verbose "[$moduleName] - [$manifestPropertyName]"
    $variablesToExport = ($manifest.VariablesToExport).count -eq 0 ? @() : @($manifest.VariablesToExport)
    $variablesToExport | ForEach-Object { Write-Verbose "[$moduleName] - [$manifestPropertyName] - [$_]" }
    $variablesToExport
}

Write-Verbose "[$scriptName] - [/private/Build/Get-PSModuleVariablesToExport.ps1] - Done"
#endregion - From /private/Build/Get-PSModuleVariablesToExport.ps1
#region - From /private/Build/Resolve-PSModuleDependencies.ps1
Write-Verbose "[$scriptName] - [/private/Build/Resolve-PSModuleDependencies.ps1] - Importing"

function Resolve-PSModuleDependencies {
    <#
    .SYNOPSIS
    Resolve dependencies for a module based on the manifest file.

    .DESCRIPTION
    Resolve dependencies for a module based on the manifest file, following PSModuleInfo structure

    .EXAMPLE
    Resolve-PSModuleDependencies -Path 'C:\MyModule\MyModule.psd1'

    Installs all modules defined in the manifest file, following PSModuleInfo structure.

    .NOTES
    Should later be adapted to support both pre-reqs, and dependencies.
    Should later be adapted to take 4 parameters sets: specific version ("requiredVersion" | "GUID"), latest version ModuleVersion, and latest version within a range MinimumVersion - MaximumVersion.
    #>

    [CmdletBinding()]
    param(
        # The path to the manifest file.
        [Parameter(Mandatory)]
        [string] $ManifestFilePath
    )

    Write-Verbose "[$moduleName] - Resolving dependencies"

    $moduleName = $ManifestFilePath | Get-Item | Select-Object -ExpandProperty BaseName
    $manifest = Import-PowerShellDataFile -Path $ManifestFilePath
    Write-Verbose "[$moduleName] - Reading [$ManifestFilePath]"
    Write-Verbose "[$moduleName] - Found [$($manifest.RequiredModules.Count)] modules to install"

    foreach ($requiredModule in $manifest.RequiredModules) {
        $installParams = @{}

        if ($requiredModule -is [string]) {
            $installParams.Name = $requiredModule
        } else {
            $installParams.Name = $requiredModule.ModuleName
            $installParams.MinimumVersion = $requiredModule.ModuleVersion
            $installParams.RequiredVersion = $requiredModule.RequiredVersion
            $installParams.MaximumVersion = $requiredModule.MaximumVersion
        }
        $installParams.Verbose = $false
        $installParams.Force = $true

        Write-Verbose "[$moduleName] - [$($installParams.Name)] - Installing module"
        $VerbosePreferenceOriginal = $VerbosePreference
        $VerbosePreference = 'SilentlyContinue'
        Install-Module @installParams
        $VerbosePreference = $VerbosePreferenceOriginal
        Write-Verbose "[$moduleName] - [$($installParams.Name)] - Importing module"
        $VerbosePreferenceOriginal = $VerbosePreference
        $VerbosePreference = 'SilentlyContinue'
        Import-Module @installParams
        $VerbosePreference = $VerbosePreferenceOriginal
        Write-Verbose "[$moduleName] - [$($installParams.Name)] - Done"
    }
    Write-Verbose "[$moduleName] - Resolving dependencies - Done"
}

Write-Verbose "[$scriptName] - [/private/Build/Resolve-PSModuleDependencies.ps1] - Done"
#endregion - From /private/Build/Resolve-PSModuleDependencies.ps1

Write-Verbose "[$scriptName] - [/private/Build] - Done"
#endregion - From /private/Build

#region - From /private/Publish
Write-Verbose "[$scriptName] - [/private/Publish] - Processing folder"

#region - From /private/Publish/Get-ModulesToPublish.ps1
Write-Verbose "[$scriptName] - [/private/Publish/Get-ModulesToPublish.ps1] - Importing"

#region Helper functions

function Get-ModifiedFileList {
    <#
    .SYNOPSIS
    Get modified files between previous and current commit depending on if you are running on main/master or a custom branch.

    .EXAMPLE
    Get-ModifiedFileList

        Directory: C:\Repo\Azure\ResourceModules\utilities\pipelines\resourcePublish

    Mode LastWriteTime Length Name
    ---- ------------- ------ ----
    la--- 08.12.2021 15:50 7133 Script.ps1

    Get modified files between previous and current commit depending on if you are running on main/master or a custom branch.
    #>

    $CurrentBranch = Get-GitBranchName
    if (($CurrentBranch -eq 'main') -or ($CurrentBranch -eq 'master')) {
        Write-Verbose 'Gathering modified files from the pull request' -Verbose
        $Diff = git diff --name-only --diff-filter=AM HEAD^ HEAD
    } else {
        Write-Verbose 'Gathering modified files between current branch and main' -Verbose
        $Diff = git diff --name-only --diff-filter=AM origin/main
        if ($Diff.count -eq 0) {
            Write-Verbose 'Gathering modified files between current branch and master' -Verbose
            $Diff = git diff --name-only --diff-filter=AM origin/master
        }
    }
    $ModifiedFiles = $Diff | Get-Item -Force

    return $ModifiedFiles
}

function Get-GitBranchName {
    <#
    .SYNOPSIS
    Get the name of the current checked out branch.

    .DESCRIPTION
    Get the name of the current checked out branch. If git cannot find it, best effort based on environment variables is used.

    .EXAMPLE
    Get-CurrentBranch

    feature-branch-1

    Get the name of the current checked out branch.

    #>

    [CmdletBinding()]
    param ()

    # Get branch name from Git
    $BranchName = git branch --show-current

    # If git could not get name, try GitHub variable
    if ([string]::IsNullOrEmpty($BranchName) -and (Test-Path env:GITHUB_REF_NAME)) {
        $BranchName = $env:GITHUB_REF_NAME
    }

    # If git could not get name, try Azure DevOps variable
    if ([string]::IsNullOrEmpty($BranchName) -and (Test-Path env:BUILD_SOURCEBRANCHNAME)) {
        $BranchName = $env:BUILD_SOURCEBRANCHNAME
    }

    return $BranchName
}

function Find-TemplateFile {
    <#
    .SYNOPSIS
    Find the closest main.bicep/json file to the current directory/file.

    .DESCRIPTION
    This function will search the current directory and all parent directories for a main.bicep/json file.

    .PARAMETER Path
    Mandatory. Path to the folder/file that should be searched

    .EXAMPLE
    Find-TemplateFile -Path "C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service\table\.bicep\nested_roleAssignments.bicep"

        Directory: C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service\table

    Mode LastWriteTime Length Name
    ---- ------------- ------ ----
    la--- 05.12.2021 22:45 1230 main.bicep

    Gets the closest main.bicep/json file to the current directory.
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string] $Path
    )

    $FolderPath = Split-Path $Path -Parent
    $FolderName = Split-Path $Path -Leaf
    if ($FolderName -eq 'modules') {
        return $null
    }

    #Prioritizing the bicep file
    $TemplateFilePath = Join-Path $FolderPath 'main.bicep'
    if (-not (Test-Path $TemplateFilePath)) {
        $TemplateFilePath = Join-Path $FolderPath 'main.json'
    }

    if (-not (Test-Path $TemplateFilePath)) {
        return Find-TemplateFile -Path $FolderPath
    }

    return $TemplateFilePath | Get-Item
}

function Get-TemplateFileToPublish {
    <#
    .SYNOPSIS
    Find the closest main.bicep/json file to the changed files in the module folder structure.

    .DESCRIPTION
    Find the closest main.bicep/json file to the changed files in the module folder structure.

    .PARAMETER ModuleFolderPath
    Mandatory. Path to the main/parent module folder.

    .EXAMPLE
    Get-TemplateFileToPublish -ModuleFolderPath "C:\Repos\Azure\ResourceModules\modules\storage\storage-account\"

    C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service\table\main.bicep

    Gets the closest main.bicep/json file to the changed files in the module folder structure.
    Assuming there is a changed file in 'storage\storage-account\table-service\table'
    the function would return the main.bicep file in the same folder.

    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string] $ModuleFolderPath
    )
    $ModuleFolderRelPath = $ModuleFolderPath.Split('/modules/')[-1]
    $ModifiedFiles = Get-ModifiedFileList -Verbose
    Write-Verbose "Looking for modified files under: [$ModuleFolderRelPath]" -Verbose
    $ModifiedModuleFiles = $ModifiedFiles | Where-Object { $_.FullName -like "*$ModuleFolderPath*" }

    $TemplateFilesToPublish = $ModifiedModuleFiles | ForEach-Object {
        Find-TemplateFile -Path $_.FullName -Verbose
    } | Sort-Object -Property FullName -Unique -Descending

    if ($TemplateFilesToPublish.Count -eq 0) {
        Write-Verbose 'No template file found in the modified module.' -Verbose
    }

    Write-Verbose ('Modified modules found: [{0}]' -f $TemplateFilesToPublish.count) -Verbose
    $TemplateFilesToPublish | ForEach-Object {
        $RelPath = ($_.FullName).Split('/modules/')[-1]
        $RelPath = $RelPath.Split('/main.')[0]
        Write-Verbose " - [$RelPath]" -Verbose
    }

    return $TemplateFilesToPublish
}

function Get-ParentModuleTemplateFile {
    <#
    .SYNOPSIS
    Gets the parent main.bicep/json file(s) to the changed files in the module folder structure.

    .DESCRIPTION
    Gets the parent main.bicep/json file(s) to the changed files in the module folder structure.

    .PARAMETER TemplateFilePath
    Mandatory. Path to a main.bicep/json file.

    .PARAMETER Recurse
    Optional. If true, the function will recurse up the folder structure to find the closest main.bicep/json file.

    .EXAMPLE
    Get-ParentModuleTemplateFile -TemplateFilePath 'C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service\table\main.bicep' -Recurse

        Directory: C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service

    Mode LastWriteTime Length Name
    ---- ------------- ------ ----
    la--- 05.12.2021 22:45 1427 main.bicep

        Directory: C:\Repos\Azure\ResourceModules\modules\storage\storage-account

    Mode LastWriteTime Length Name
    ---- ------------- ------ ----
    la--- 02.12.2021 13:19 10768 main.bicep

    Gets the parent main.bicep/json file(s) to the changed files in the module folder structure.

    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string] $TemplateFilePath,

        [Parameter(Mandatory = $false)]
        [switch] $Recurse
    )

    $ModuleFolderPath = Split-Path $TemplateFilePath -Parent
    $ParentFolderPath = Split-Path $ModuleFolderPath -Parent

    #Prioritizing the bicep file
    $ParentTemplateFilePath = Join-Path $ParentFolderPath 'main.bicep'
    if (-not (Test-Path $TemplateFilePath)) {
        $ParentTemplateFilePath = Join-Path $ParentFolderPath 'main.json'
    }

    if (-not (Test-Path -Path $ParentTemplateFilePath)) {
        return
    }

    $ParentTemplateFilesToPublish = [System.Collections.ArrayList]@()
    $ParentTemplateFilesToPublish += $ParentTemplateFilePath | Get-Item

    if ($Recurse) {
        $ParentTemplateFilesToPublish += Get-ParentModuleTemplateFile $ParentTemplateFilePath -Recurse
    }

    return $ParentTemplateFilesToPublish
}

function Get-GitDistance {
    <#
    .SYNOPSIS
    Get the number of commits following the specified commit.

    .PARAMETER Commit
    Optional. A specified git reference to get commit counts on.

    .EXAMPLE
    Get-GitDistance -Commit origin/main.

    620

    There are currently 620 commits on origin/main. When run as a push on main, this will be the current commit number on the main branch.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [string] $Commit = 'HEAD'

    )

    return [int](git rev-list --count $Commit) + 1
}

function Get-ModuleVersionFromFile {
    <#
    .SYNOPSIS
    Gets the version from the version file from the corresponding main.bicep/json file.

    .DESCRIPTION
    Gets the version file from the corresponding main.bicep/json file.
    The file needs to be in the same folder as the template file itself.

    .PARAMETER TemplateFilePath
    Mandatory. Path to a main.bicep/json file.

    .EXAMPLE
    Get-ModuleVersionFromFile -TemplateFilePath 'C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service\table\main.bicep'

    0.3

    Get the version file from the specified main.bicep file.
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string] $TemplateFilePath
    )

    $ModuleFolder = Split-Path -Path $TemplateFilePath -Parent
    $VersionFilePath = Join-Path $ModuleFolder 'version.json'

    if (-not (Test-Path -Path $VersionFilePath)) {
        throw "No version file found at: [$VersionFilePath]"
    }

    $VersionFileContent = Get-Content $VersionFilePath | ConvertFrom-Json

    return $VersionFileContent.version
}

function Get-NewModuleVersion {
    <#
    .SYNOPSIS
    Generates a new version for the specified module.

    .DESCRIPTION
    Generates a new version for the specified module, based on version.json file and git commit count.
    Major and minor version numbers are gathered from the version.json file.
    Patch version number is calculated based on the git commit count on the branch.

    .PARAMETER TemplateFilePath
    Mandatory. Path to a main.bicep/json file.

    .EXAMPLE
    Get-NewModuleVersion -TemplateFilePath 'C:\Repos\Azure\ResourceModules\modules\storage\storage-account\table-service\table\main.bicep'

    0.3.630

    Generates a new version for the table module.

    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string] $TemplateFilePath
    )

    $Version = Get-ModuleVersionFromFile -TemplateFilePath $TemplateFilePath
    $Patch = Get-GitDistance
    $NewVersion = "$Version.$Patch"

    $BranchName = Get-GitBranchName -Verbose

    if ($BranchName -ne 'main' -and $BranchName -ne 'master') {
        $NewVersion = "$NewVersion-prerelease".ToLower()
    }

    return $NewVersion
}

#endregion

<#
.SYNOPSIS
Generates a hashtable with template file paths to publish with a new version.

.DESCRIPTION
Generates a hashtable with template file paths to publish with a new version.

.PARAMETER TemplateFilePath
Mandatory. Path to a main.bicep/json file.

.PARAMETER PublishLatest
Optional. Publish an absolute latest version.
Note: This version may include breaking changes and is not recommended for production environments

.EXAMPLE
Get-ModulesToPublish -TemplateFilePath 'C:\Repos\Azure\ResourceModules\modules\storage\storage-account\main.bicep'

Name Value
---- -----
TemplateFilePath C:\Repos\Azure\ResourceModules\modules\storage\storage-account\file-service\share\main.bicep
Version 0.3.848-prerelease
TemplateFilePath C:\Repos\Azure\ResourceModules\modules\storage\storage-account\file-service\main.bicep
Version 0.3.848-prerelease
TemplateFilePath C:\Repos\Azure\ResourceModules\modules\storage\storage-account\main.bicep
Version 0.3.848-prerelease

Generates a hashtable with template file paths to publish and their new versions.

#>
#
function Get-ModulesToPublish {

    [CmdletBinding()]
    param (
        [Parameter(Mandatory)]
        [string] $TemplateFilePath,

        [Parameter(Mandatory = $false)]
        [bool] $PublishLatest = $true

    )

    $ModuleFolderPath = Split-Path $TemplateFilePath -Parent
    $TemplateFilesToPublish = Get-TemplateFileToPublish -ModuleFolderPath $ModuleFolderPath | Sort-Object FullName -Descending

    $modulesToPublish = [System.Collections.ArrayList]@()
    foreach ($TemplateFileToPublish in $TemplateFilesToPublish) {
        $ModuleVersion = Get-NewModuleVersion -TemplateFilePath $TemplateFileToPublish.FullName -Verbose

        $modulesToPublish += @{
            Version          = $ModuleVersion
            TemplateFilePath = $TemplateFileToPublish.FullName
        }

        if ($ModuleVersion -notmatch 'prerelease') {

            # Latest Major,Minor
            $modulesToPublish += @{
                Version          = ($ModuleVersion.Split('.')[0..1] -join '.')
                TemplateFilePath = $TemplateFileToPublish.FullName
            }

            # Latest Major
            $modulesToPublish += @{
                Version          = ($ModuleVersion.Split('.')[0])
                TemplateFilePath = $TemplateFileToPublish.FullName
            }

            if ($PublishLatest) {
                # Absolute latest
                $modulesToPublish += @{
                    Version          = 'latest'
                    TemplateFilePath = $TemplateFileToPublish.FullName
                }
            }
        }

        $ParentTemplateFilesToPublish = Get-ParentModuleTemplateFile -TemplateFilePath $TemplateFileToPublish.FullName -Recurse
        foreach ($ParentTemplateFileToPublish in $ParentTemplateFilesToPublish) {
            $ParentModuleVersion = Get-NewModuleVersion -TemplateFilePath $ParentTemplateFileToPublish.FullName

            $modulesToPublish += @{
                Version          = $ParentModuleVersion
                TemplateFilePath = $ParentTemplateFileToPublish.FullName
            }

            if ($ModuleVersion -notmatch 'prerelease') {

                # Latest Major,Minor
                $modulesToPublish += @{
                    Version          = ($ParentModuleVersion.Split('.')[0..1] -join '.')
                    TemplateFilePath = $ParentTemplateFileToPublish.FullName
                }

                # Latest Major
                $modulesToPublish += @{
                    Version          = ($ParentModuleVersion.Split('.')[0])
                    TemplateFilePath = $ParentTemplateFileToPublish.FullName
                }

                if ($PublishLatest) {
                    # Absolute latest
                    $modulesToPublish += @{
                        Version          = 'latest'
                        TemplateFilePath = $ParentTemplateFileToPublish.FullName
                    }
                }
            }
        }
    }

    $modulesToPublish = $modulesToPublish | Sort-Object TemplateFilePath, Version -Descending -Unique

    if ($modulesToPublish.count -gt 0) {
        Write-Verbose 'Publish the following modules:'-Verbose
        $modulesToPublish | ForEach-Object {
            $RelPath = ($_.TemplateFilePath).Split('/modules/')[-1]
            $RelPath = $RelPath.Split('/main.')[0]
            Write-Verbose (' - [{0}] [{1}] ' -f $RelPath, $_.Version) -Verbose
        }
    } else {
        Write-Verbose 'No modules with changes found to publish.'-Verbose
    }

    return $modulesToPublish
}

Write-Verbose "[$scriptName] - [/private/Publish/Get-ModulesToPublish.ps1] - Done"
#endregion - From /private/Publish/Get-ModulesToPublish.ps1

Write-Verbose "[$scriptName] - [/private/Publish] - Done"
#endregion - From /private/Publish

#region - From /private/Test
Write-Verbose "[$scriptName] - [/private/Test] - Processing folder"

#region - From /private/Test/Invoke-PSCustomTests.ps1
Write-Verbose "[$scriptName] - [/private/Test/Invoke-PSCustomTests.ps1] - Importing"

function Invoke-PSCustomTests {
    [CmdletBinding()]
    param(
        # Path to the folder where the built modules are outputted.
        [Parameter(Mandatory)]
        [string] $ModuleFolder,

        # Path to the folder where the built modules are outputted.
        [Parameter(Mandatory)]
        [string] $TestFolderPath
    )

    $containerParams = @{
        Path = $TestFolderPath
        Data = @{
            Path = $ModuleFolder
        }
    }
    Write-Verbose 'ContainerParams:'
    Write-Verbose "$($containerParams | ConvertTo-Json -Depth 5)"


    $pesterParams = @{
        Configuration = @{
            Run        = @{
                Container = New-PesterContainer @containerParams
                PassThru  = $false
            }
            TestResult = @{
                TestSuiteName = 'PSSA'
                OutputPath    = '.\outputs\CustomTest.Results.xml'
                OutputFormat  = 'NUnitXml'
                Enabled       = $true
            }
            Output     = @{
                Verbosity           = 'Detailed'
                StackTraceVerbosity = 'None'
            }
        }
        ErrorAction   = 'Stop'
    }

    Write-Verbose 'PesterParams:'
    Write-Verbose "$($pesterParams | ConvertTo-Json -Depth 5)"

    Invoke-Pester @pesterParams

}

Write-Verbose "[$scriptName] - [/private/Test/Invoke-PSCustomTests.ps1] - Done"
#endregion - From /private/Test/Invoke-PSCustomTests.ps1
#region - From /private/Test/Invoke-PSScriptAnalyserTest.ps1
Write-Verbose "[$scriptName] - [/private/Test/Invoke-PSScriptAnalyserTest.ps1] - Importing"

function Invoke-PSSATest {
    [CmdLetBinding()]
    param(
        # Path to the folder where the built modules are outputted.
        [Parameter(Mandatory)]
        [string] $ModuleFolder
    )

    $modules = Get-Module -ListAvailable
    $PSSAModule = $modules | Where-Object Name -EQ PSScriptAnalyzer | Sort-Object Version -Descending | Select-Object -First 1
    $pesterModule = $modules | Where-Object Name -EQ Pester | Sort-Object Version -Descending | Select-Object -First 1

    Write-Verbose 'Testing with:' -Verbose
    Write-Verbose " PowerShell $($PSVersionTable.PSVersion.ToString())" -Verbose
    Write-Verbose " Pester $($pesterModule.version)" -Verbose
    Write-Verbose " PSScriptAnalyzer $($PSSAModule.version)" -Verbose

    $containerParams = @{
        Path = (Join-Path -Path $PSScriptRoot -ChildPath 'tests' 'PSScriptAnalyser' 'PSScriptAnalyser.Tests.ps1')
        Data = @{
            Path = $ModuleFolder
        }
    }

    Write-Verbose 'ContainerParams:'
    Write-Verbose "$($containerParams | ConvertTo-Json -Depth 5)"


    $pesterParams = @{
        Configuration = @{
            Run        = @{
                Container = New-PesterContainer @containerParams
                PassThru  = $false
            }
            TestResult = @{
                TestSuiteName = 'PSSA'
                OutputPath    = '.\outputs\PSSA.Results.xml'
                OutputFormat  = 'NUnitXml'
                Enabled       = $true
            }
            Output     = @{
                Verbosity           = 'Detailed'
                StackTraceVerbosity = 'None'
            }
        }
        ErrorAction   = 'Stop'
    }
    Write-Verbose 'PesterParams:'
    Write-Verbose "$($pesterParams | ConvertTo-Json -Depth 5)"

    Invoke-Pester @pesterParams

}

Write-Verbose "[$scriptName] - [/private/Test/Invoke-PSScriptAnalyserTest.ps1] - Done"
#endregion - From /private/Test/Invoke-PSScriptAnalyserTest.ps1

Write-Verbose "[$scriptName] - [/private/Test] - Done"
#endregion - From /private/Test

#region - From /private/Utilities
Write-Verbose "[$scriptName] - [/private/Utilities] - Processing folder"

#region - From /private/Utilities/Add-PSModulePath.ps1
Write-Verbose "[$scriptName] - [/private/Utilities/Add-PSModulePath.ps1] - Importing"

function Add-PSModulePath {
    [CmdletBinding()]
    param(
        # Path to the folder where the module source code is located.
        [Parameter(Mandatory)]
        [string] $Path
    )

    if ($IsWindows) {
        $PSModulePathSeparator = ';'
    } else {
        $PSModulePathSeparator = ':'
    }
    $env:PSModulePath += "$PSModulePathSeparator$Path"

    Write-Verbose "PSModulePath:"
    $env:PSModulePath.Split($PSModulePathSeparator) | ForEach-Object {
        Write-Verbose " - [$_]"
    }
}

Write-Verbose "[$scriptName] - [/private/Utilities/Add-PSModulePath.ps1] - Done"
#endregion - From /private/Utilities/Add-PSModulePath.ps1
#region - From /private/Utilities/ConvertTo-Hashtable.ps1
Write-Verbose "[$scriptName] - [/private/Utilities/ConvertTo-Hashtable.ps1] - Importing"

function ConvertTo-Hashtable {
    <#
        .SYNOPSIS
        Converts a string to a hashtable.

        .DESCRIPTION
        Converts a string to a hashtable.

        .EXAMPLE
        ConvertTo-Hashtable -InputString "@{Key1 = 'Value1'; Key2 = 'Value2'}"

        Key Value
        --- -----
        Key1 Value1
        Key2 Value2

        Converts the string to a hashtable.
    #>

    param (
        # The string to convert to a hashtable.
        [Parameter(Mandatory = $true)]
        [string]$InputString
    )

    $outputHashtable = @{}

    # Match pairs of key = value
    $regexPattern = "\s*(\w+)\s*=\s*\'([^\']+)\'"
    $regMatches = [regex]::Matches($InputString, $regexPattern)
    foreach ($match in $regMatches) {
        $key = $match.Groups[1].value
        $value = $match.Groups[2].value

        $outputHashtable[$key] = $value
    }

    return $outputHashtable
}


$InputString = "@{ ModuleName = 'AzureRM.Netcore'; MaximumVersion = '0.12.0' }"

Write-Verbose "[$scriptName] - [/private/Utilities/ConvertTo-Hashtable.ps1] - Done"
#endregion - From /private/Utilities/ConvertTo-Hashtable.ps1
#region - From /private/Utilities/Show-FileContent.ps1
Write-Verbose "[$scriptName] - [/private/Utilities/Show-FileContent.ps1] - Importing"

function Show-FileContent {
    <#
        .SYNOPSIS
        Prints the content of a file with line numbers in front of each line.

        .DESCRIPTION
        Prints the content of a file with line numbers in front of each line.

        .EXAMPLE
        $Path = 'C:\Repos\GitHub\PSModule\Framework\PSModule.FX\src\PSModule.FX\private\Utilities\Show-FileContent.ps1'
        Show-FileContent -Path $Path

        Shows the content of the file with line numbers in front of each line.
    #>

    [CmdletBinding()]
    param (
        # The path to the file to show the content of.
        [Parameter(Mandatory)]
        [string]$Path
    )

    $content = Get-Content -Path $Path
    $lineNumber = 1
    # Foreach line print the line number in front of the line with [ ] around it. The linenumber should dynamically adjust to the number of digits with the length of the file.
    foreach ($line in $content) {
        $lineNumberFormatted = $lineNumber.ToString().PadLeft($content.Count.ToString().Length)
        '[{0}] {1}' -f $lineNumberFormatted, $line
        $lineNumber++
    }
}

Write-Verbose "[$scriptName] - [/private/Utilities/Show-FileContent.ps1] - Done"
#endregion - From /private/Utilities/Show-FileContent.ps1

Write-Verbose "[$scriptName] - [/private/Utilities] - Done"
#endregion - From /private/Utilities

#region - From /private/Invoke-PSModuleBuild.ps1
Write-Verbose "[$scriptName] - [/private/Invoke-PSModuleBuild.ps1] - Importing"

function Invoke-PSModuleBuild {
    #DECISION: The manifest file = name of the folder.
    #DECISION: The basis of the module manifest comes from the defined manifest file.
    #DECISION: Values that are not defined in the module manifest file are generated from reading the module files.
    #DECISION: If no RootModule is defined in the manifest file, we assume a .psm1 file with the same name as the module is on root.
    #DECISION: Currently only Script and Manifest modules are supported.
    #DECISION: The output folder = .\outputs on the root of the repo.
    #DECISION: The module that is build is stored under the output folder in a folder with the same name as the module.
    #DECISION: A new module manifest file is created every time to get a new GUID, so that the specific version of the module can be imported.

    param(
        # Path to the folder where the module source code is located.
        [Parameter(Mandatory)]
        [string] $ModuleFolderPath,

        # Path to the folder where the built modules are outputted.
        [Parameter(Mandatory)]
        [string] $ModulesOutputFolder,

        # Path to the folder where the built module documentation is outputted.
        [Parameter(Mandatory)]
        [string] $DocsOutputFolder
    )

    $moduleName = Split-Path -Path $ModuleFolderPath -Leaf

    Write-Output "::group::[$moduleName]"
    Write-Verbose "ModuleFolderPath - [$ModuleFolderPath]"

    $moduleSourceFolder = Get-Item -Path $ModuleFolderPath

    Build-PSModuleBase -SourceFolderPath $moduleSourceFolder -OutputFolderPath $modulesOutputFolder
    Build-PSModuleRootModule -SourceFolderPath $moduleSourceFolder -OutputFolderPath $modulesOutputFolder
    Build-PSModuleManifest -SourceFolderPath $moduleSourceFolder -OutputFolderPath $modulesOutputFolder
    $moduleOutputFolder = Join-Path $modulesOutputFolder $moduleName
    $docOutputFolder = Join-Path $docsOutputFolder $moduleName
    Build-PSModuleDocumentation -SourceFolderPath $moduleOutputFolder -OutputFolderPath $docOutputFolder

    Write-Verbose "[$moduleName] - Done"
}

Write-Verbose "[$scriptName] - [/private/Invoke-PSModuleBuild.ps1] - Done"
#endregion - From /private/Invoke-PSModuleBuild.ps1
#region - From /private/Invoke-PSModuleTest.ps1
Write-Verbose "[$scriptName] - [/private/Invoke-PSModuleTest.ps1] - Importing"

function Invoke-PSModuleTest {
    param(
        # Path to the folder where the built modules are outputted.
        [Parameter(Mandatory)]
        [string] $ModuleFolderPath
    )

    $moduleName = Split-Path -Path $ModuleFolderPath -Leaf

    Write-Output "::group::[$moduleName]"
    Write-Verbose "ModuleFolderPath - [$ModuleFolderPath]"

    Write-Output "::group::[$moduleName] - Invoke-ScriptAnalyzer"
    $moduleFolder = Get-Item -Path $ModuleFolderPath
    Invoke-PSSATest -ModuleFolder $moduleFolder -Verbose:$false
    Write-Output "::endgroup::"

    Write-Verbose "[$moduleName] - Done"
}

# <#
# Run tests from ".\tests\$moduleName.Tests.ps1"
# # Import the module using Import-Module $moduleManifestFilePath,
# # Do not not just add the outputted module file to the PATH of the runner (current context is enough) $env:PATH += ";.\outputs\$moduleName" as the import-module will actually test that the module is importable.
# #>

# <#
# Run tests from ".\tests\$moduleName.Tests.ps1"
# #>

# <#
# Test-ModuleManifest -Path $Path
# #>

Write-Verbose "[$scriptName] - [/private/Invoke-PSModuleTest.ps1] - Done"
#endregion - From /private/Invoke-PSModuleTest.ps1

Write-Verbose "[$scriptName] - [/private] - Done"
#endregion - From /private

#region - From /public
Write-Verbose "[$scriptName] - [/public] - Processing folder"

#region - From /public/Build-PSModule.ps1
Write-Verbose "[$scriptName] - [/public/Build-PSModule.ps1] - Importing"

#Requires -Modules platyPS, PowerShellGet, PackageManagement

function Build-PSModule {
    <#
        .SYNOPSIS
        Builds a PowerShell module

        .DESCRIPTION
        Builds a PowerShell module

        .EXAMPLE
        Build-PSModule -Path 'src' -OutputPath 'outputs'

        Builds all modules in the 'src' folder and stores them in the 'outputs' folder

        .EXAMPLE
        Build-PSModule -Path 'src' -Name 'PSModule.FX' -OutputPath 'outputs'

        Builds the 'PSModule.FX' module in the 'src' folder and stores it in the 'outputs' folder.

        .NOTES
        #DECISION: Modules are default located under the '.\src' folder which is the root of the repo.
        #DECISION: Module name = the name of the folder under src. Inherited decision from PowerShell team.
        #DECISION: The module manifest file = name of the folder.
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        # Path to the folder where the modules are located.
        [Parameter()]
        [string] $Path = 'src',

        # Name of the module to process.
        [Parameter()]
        [string] $Name = '*',

        # Path to the folder where the built modules are outputted.
        [Parameter()]
        [string] $OutputPath = 'outputs'
    )

    Write-Output "::group::Starting..."

    $modulesOutputFolderPath = Join-Path -Path $OutputPath 'modules'
    Write-Verbose "Creating module output folder [$modulesOutputFolderPath]"
    $modulesOutputFolder = New-Item -Path $modulesOutputFolderPath -ItemType Directory -Force
    Add-PSModulePath -Path $modulesOutputFolder

    $docsOutputFolderPath = Join-Path -Path $OutputPath 'docs'
    Write-Verbose "Creating docs output folder [$docsOutputFolderPath]"
    $docsOutputFolder = New-Item -Path $docsOutputFolderPath -ItemType Directory -Force

    $moduleFolders = Get-PSModuleFolders -Path $Path | Where-Object { $_.Name -like $Name }
    Write-Verbose "Found $($moduleFolders.Count) module(s)"
    $moduleFolders | ForEach-Object {
        Write-Verbose "[$($_.Name)]"
    }

    foreach ($moduleFolder in $moduleFolders) {
        Invoke-PSModuleBuild -ModuleFolderPath $moduleFolder.FullName -ModulesOutputFolder $modulesOutputFolder -DocsOutputFolder $docsOutputFolder
    }
}

Write-Verbose "[$scriptName] - [/public/Build-PSModule.ps1] - Done"
#endregion - From /public/Build-PSModule.ps1
#region - From /public/Publish-PSModule.ps1
Write-Verbose "[$scriptName] - [/public/Publish-PSModule.ps1] - Importing"

#Requires -Modules platyPS

function Publish-PSModule {
    <#
        .SYNOPSIS
        Publishes a module to the PowerShell Gallery and GitHub Pages.

        .DESCRIPTION
        Publishes a module to the PowerShell Gallery and GitHub Pages.

        .EXAMPLE
        Publish-PSModule -Name 'PSModule.FX' -APIKey $env:PSGALLERY_API_KEY
    #>

    [Alias('Release-Module')]
    [CmdletBinding(SupportsShouldProcess)]
    param(
        # Name of the module to process.
        [Parameter()]
        [string] $Name = '*',

        # The API key for the destination repository.
        [Parameter(Mandatory)]
        [string] $APIKey
    )
    $task = New-Object System.Collections.Generic.List[string]
    #region Publish-Module
    $task.Add('Release-Module')
    Write-Output "::group::[$($task -join '] - [')] - Starting..."

    Import-Module PackageManagement, PowerShellGet -Verbose:$false -ErrorAction Stop

    ########################
    # Gather some basic info
    ########################

    $outputPath = Get-Item -Path .\outputs\modules | Select-Object -ExpandProperty FullName
    $env:PSModulePath += ":$SRCPath"
    $env:PSModulePath -Split ':'

    $modulesOutputFolderPath = Join-Path -Path 'outputs' 'modules'
    Write-Verbose "Getting module output folder [$modulesOutputFolderPath]"
    $modulesOutputFolder = Get-Item -Path $modulesOutputFolderPath
    Add-PSModulePath -Path $modulesOutputFolder

    $moduleFolders = Get-ChildItem -Path $modulesOutputFolder -Directory | Where-Object { $_.Name -like $Name }

    foreach ($module in $moduleFolders) {
        $moduleName = $module.Name
        $manifestFilePath = "$module\$moduleName.psd1"
        $task.Add($moduleName)
        Write-Output "::group::[$($task -join '] - [')] - Starting..."

        #region Generate-Version
        $task.Add('Generate-Version')
        Write-Output "::group::[$($task -join '] - [')]"
        Write-Verbose "[$($task -join '] - [')] - Generate version"

        [Version]$newVersion = '0.0.0'

        try {
            $onlineVersion = [Version](Find-Module $moduleName -Verbose:$false).Version
        } catch {
            $onlineVersion = $newVersion
            Write-Warning "Could not find module online. Using [$($onlineVersion.ToString())]"
        }
        Write-Warning "Online: [$($onlineVersion.ToString())]"
        $manifestVersion = [Version](Test-ModuleManifest $manifestFilePath -Verbose:$false).Version
        Write-Warning "Manifest: [$($manifestVersion.ToString())]"

        Write-Verbose "branch is: [$env:GITHUB_REF_NAME]"

        if ($manifestVersion.Major -gt $onlineVersion.Major) {
            $newVersionMajor = $manifestVersion.Major
            $newVersionMinor = 0
            $newVersionBuild = 0
        } else {
            $newVersionMajor = $onlineVersion.Major
            if ($manifestVersion.Minor -gt $onlineVersion.Minor) {
                $newVersionMinor = $manifestVersion.Minor
                $newVersionBuild = 0
            } else {
                $newVersionMinor = $onlineVersion.Minor
                $newVersionBuild = $onlineVersion.Build + 1
            }
        }
        [Version]$newVersion = [version]::new($newVersionMajor, $newVersionMinor, $newVersionBuild)
        Write-Warning "newVersion: [$($newVersion.ToString())]"

        Write-Verbose "[$($task -join '] - [')] - Create draft release with version"
        gh release create $newVersion --title $newVersion --generate-notes --draft --target $env:GITHUB_REF_NAME

        if ($env:GITHUB_REF_NAME -ne 'main') {
            Write-Verbose "[$($task -join '] - [')] - Not on main, but on [$env:GITHUB_REF_NAME]"
            Write-Verbose "[$($task -join '] - [')] - Generate pre-release version"
            $prerelease = $env:GITHUB_REF_NAME -replace '[^a-zA-Z0-9]', ''
            Write-Verbose "[$($task -join '] - [')] - Prerelease is: [$prerelease]"
            if ($newVersion -ge [version]'1.0.0') {
                Write-Verbose "[$($task -join '] - [')] - Version is greater than 1.0.0 -> Update-ModuleManifest with prerelease [$prerelease]"
                Update-ModuleManifest -Path $manifestFilePath -Prerelease $prerelease -ErrorAction Continue
                gh release edit $newVersion -tag "$newVersion-$prerelease" --prerelease
            }
        }

        Write-Verbose "[$($task -join '] - [')] - Bump module version -> module metadata: Update-ModuleMetadata"
        Update-ModuleManifest -Path $manifestFilePath -ModuleVersion $newVersion -ErrorAction Continue

        Write-Output "::group::[$($task -join '] - [')] - Done"
        $task.RemoveAt($task.Count - 1)
        #endregion Generate-Version

        #region Publish-Docs
        $task.Add('Publish-Docs')
        Write-Output "::group::[$($task -join '] - [')]"
        Write-Output "::group::[$($task -join '] - [')] - Do something"

        Write-Verbose "[$($task -join '] - [')] - Publish docs to GitHub Pages"
        Write-Verbose "[$($task -join '] - [')] - Update docs path: Update-ModuleMetadata"
        # What about updateable help? https://learn.microsoft.com/en-us/powershell/scripting/developer/help/supporting-updatable-help?view=powershell-7.3

        Write-Output "::group::[$($task -join '] - [')] - Done"
        $task.RemoveAt($task.Count - 1)
        #endregion Publish-Docs

        #region Publish-ToPSGallery
        $task.Add('Publish-ToPSGallery')
        Write-Output "::group::[$($task -join '] - [')]"
        Write-Output "::group::[$($task -join '] - [')] - Do something"

        Write-Verbose "[$($task -join '] - [')] - Publish module to PowerShell Gallery using [$APIKey]"
        Publish-Module -Path "$module" -NuGetApiKey $APIKey

        Write-Verbose "[$($task -join '] - [')] - Publish GitHub release for [$newVersion]"
        gh release edit $newVersion --draft=false

        Write-Verbose "[$($task -join '] - [')] - Doing something"
        Write-Output "::group::[$($task -join '] - [')] - Done"
        $task.RemoveAt($task.Count - 1)
        #endregion Publish-ToPSGallery

    }

    $task.RemoveAt($task.Count - 1)
    Write-Output "::group::[$($task -join '] - [')] - Stopping..."
    Write-Output '::endgroup::'
    #endregion Publish-Module

}

Write-Verbose "[$scriptName] - [/public/Publish-PSModule.ps1] - Done"
#endregion - From /public/Publish-PSModule.ps1
#region - From /public/Test-PSModule.ps1
Write-Verbose "[$scriptName] - [/public/Test-PSModule.ps1] - Importing"

#Requires -Modules Pester, PSScriptAnalyzer

function Test-PSModule {
    <#
        .SYNOPSIS
        Test a module.

        .DESCRIPTION
        Test a module using Pester and PSScriptAnalyzer.
        Runs both custom tests defined in a module's tests folder and the default tests.

        .EXAMPLE
        Test-PSModule -Name 'PSModule.FX' -Path 'outputs'
    #>

    [CmdletBinding(SupportsShouldProcess)]
    param(
        # Name of the module to process.
        [Parameter()]
        [string] $Name = '*',

        # Path to the folder where the built modules are outputted.
        [Parameter()]
        [string] $Path
    )

    Write-Output '::group::Starting...'

    $moduleFolders = Get-PSModuleFolders -Path $Path | Where-Object { $_.Name -like $Name }
    Write-Verbose "Found $($moduleFolders.Count) module(s)"
    $moduleFolders | ForEach-Object {
        Write-Verbose "[$($_.Name)]"
    }

    foreach ($moduleFolder in $moduleFolders) {
        Invoke-PSModuleTest -ModuleFolderPath $moduleFolder.FullName
    }
}

Write-Verbose "[$scriptName] - [/public/Test-PSModule.ps1] - Done"
#endregion - From /public/Test-PSModule.ps1

Write-Verbose "[$scriptName] - [/public] - Done"
#endregion - From /public

Export-ModuleMember -Function 'Build-PSModule','Publish-PSModule','Test-PSModule' -Cmdlet '' -Variable '' -Alias ''