stitch.psm1

using namespace Markdig
using namespace Markdig.Syntax
using namespace System.Management.Automation.Language
using namespace System.Diagnostics.CodeAnalysis
using namespace System.Collections.Specialized
#Region '.\enum\ModuleFlag.ps1' -1

[Flags()]
enum ModuleFlag {
    None        = 0x00
    HasManifest = 0x01
    HasModule   = 0x02
}
#EndRegion '.\enum\ModuleFlag.ps1' 7
#Region '.\private\Changelog\Format-ChangelogEntry.ps1' -1


function Format-ChangelogEntry {
    <#
    .SYNOPSIS
        Format the entry text by replacing tokens from the config file with their values
    .DESCRIPTION
        Format-ChangelogEntry uses the Format.Entry line from the config file to format the Entry line in the
        Changelog.
        The following fields are available in an Entry:
        | Field | Pattern |
        |-------------|---------------|
        | Description | `{desc}` |
        | Type | `{type}` |
        | Scope | `{scope}` |
        | Title | `{title}` |
        | Sha | `{sha}` |
        | Author | `{author}` |
        | Email | `{email}` |
        | Footer | `{ft.<name>}` |
    .EXAMPLE
        $Entry | Format-ChangelogEntry
    #>

    [CmdletBinding()]
    param(
        # Information about the Entry (commit) object
        [Parameter(
            ValueFromPipeline
        )]
        [object]$EntryInfo
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"

        $DEFAULT_FORMAT = '- {sha} {desc} ({author})'
        $DEFAULT_BREAKING_FORMAT = '- {sha} **breaking change** {desc} ({author})'

        $config = Get-ChangelogConfig

        $descriptionPattern = '\{desc\}'
        $typePattern = '\{type\}'
        $scopePattern = '\{scope\}'
        $titlePattern = '\{title\}'
        $shaPattern = '\{sha\}'
        $authorPattern = '\{author\}'
        $emailPattern = '\{email\}'
        $footerPattern = '\{ft\.(\w+)\}'

    } process {

        if ($EntryInfo.IsBreakingChange) {
            $formatOptions = $config.Format.BreakingChange ?? $DEFAULT_BREAKING_FORMAT
        } else {
            $formatOptions = $config.Format.Entry ?? $DEFAULT_FORMAT
        }

        $format = $formatOptions -replace $descriptionPattern , $EntryInfo.Description
        $format = $format -replace $typePattern , $EntryInfo.Type
        $format = $format -replace $scopePattern , $EntryInfo.Scope
        $format = $format -replace $titlePattern , $EntryInfo.Title
        $format = $format -replace $shaPattern , $EntryInfo.ShortSha
        $format = $format -replace $authorPattern , $EntryInfo.Author.Name
        $format = $format -replace $emailPattern , $EntryInfo.Author.Email


        if ($format -match $footerPattern) {
            if ($matches.Count -gt 0) {
                if ($EntryInfo.Footers[$Matches.1]) {
                    $format = $format -replace "\{ft\.$($Matches.1)\}", ($EntryInfo.Footers[$Matches.1] -join ', ')
                }
            }
        }
    } end {
        $format
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Changelog\Format-ChangelogEntry.ps1' 77
#Region '.\private\Changelog\Format-ChangelogFooter.ps1' -1



function Format-ChangelogFooter {
    <#
    .SYNOPSIS
        Format the footer in the Changelog
    .EXAMPLE
        Format-ChangelogFooter
    #>

    [OutputType([string])]
    [CmdletBinding()]
    param(
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $DEFAULT_FORMAT = ''
        $config = Get-ChangelogConfig

        if (-not([string]::IsNullorEmpty($config.Footer))) {
            $formatOptions = $config.Footer
        } else {
            $formatOptions = $DEFAULT_FORMAT
        }
    } process {
        #! There are no replacements in the footer yet
        $format = $formatOptions
    } end {
        $format
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Changelog\Format-ChangelogFooter.ps1' 32
#Region '.\private\Changelog\Format-ChangelogGroup.ps1' -1


function Format-ChangelogGroup {
    <#
    .SYNOPSIS
        Format the heading of a group of changelog entries
    .EXAMPLE
        $group | Format-ChangelogGroup
    #>

    [OutputType([string])]
    [CmdletBinding()]
    param(
        # A table of information about a changelog group
        [Parameter(
            ValueFromPipeline
        )]
        [object]$GroupInfo
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"

        $DEFAULT_FORMAT = '### {name}'

        $config = Get-ChangelogConfig

        $formatOptions = ($config.Format.Group ?? $DEFAULT_FORMAT)
        $namePattern = '\{name\}'
    }
    process {
        Write-Debug "Format was '$formatOptions'"
        Write-Debug "GroupInfo is $($GroupInfo | ConvertTo-Psd)"
        if (-not ([string]::IsNullorEmpty($GroupInfo.DisplayName))) {
            Write-Debug " - DisplayName is $($GroupInfo.DisplayName)"
            $format = $formatOptions -replace $namePattern, $GroupInfo.DisplayName
        } elseif (-not ([string]::IsNullorEmpty($GroupInfo.Name))) {
            $format = $formatOptions -replace $namePattern, $GroupInfo.Name
            Write-Debug " - Name is $($GroupInfo.Name)"
        }
    }
    end {
        Write-Debug "Format is '$format'"
        $format
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Changelog\Format-ChangelogGroup.ps1' 45
#Region '.\private\Changelog\Format-ChangelogHeader.ps1' -1



function Format-ChangelogHeader {
    <#
    .SYNOPSIS
        Format the header in the Changelog
    .EXAMPLE
        Format-ChangelogHeader
    #>

    [OutputType([string])]
    [CmdletBinding()]
    param(
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $DEFAULT_FORMAT = ''
        $config = Get-ChangelogConfig

        if (-not([string]::IsNullorEmpty($config.Header))) {
            $formatOptions  = $config.Header
        } else {
            $formatOptions = $DEFAULT_FORMAT
        }
    }
    process {
        #! There are no replacements in the header yet
        $format = $formatOptions
    }
    end {
        $format
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Changelog\Format-ChangelogHeader.ps1' 34
#Region '.\private\Changelog\Format-ChangelogRelease.ps1' -1


function Format-ChangelogRelease {
    <#
    .SYNOPSIS
        Format the heading for a release in the changelog by replacing tokens form the config file with thier values
    .DESCRIPTION
        Format-ChangelogRelease uses the Format.Release line from the config file to format the Release heading in
        the Changelog.
        The following fields are available in a Release:
 
        | Field | Pattern |
        |-------------------|--------------------------|
        | Name | `{name}` |
        | Date | `{date}` |
        | Date with Format | `{date yyyy-MM-dd}` |
 
    >EXAMPLE
        $release | Format-ChangelogRelease
 
    #>

    [CmdletBinding()]
    param(
        # A table of information about a release
        [Parameter(
            ValueFromPipeline
        )]
        [object]$ReleaseInfo
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"

        $DEFAULT_FORMAT = '## [{name}] - {date yyyy-MM-dd}'

        $config = Get-ChangelogConfig

        $formatOptions = $config.Format.Release ?? $DEFAULT_FORMAT
        $namePattern = '\{name\}'
        $datePattern = '\{date\}'
        $dateFormatPattern = '\{date (?<df>.*?)\}'

    } process {

        Write-Debug " Items: $($ReleaseInfo.Keys)"
        $format = $formatOptions -replace $namePattern, $ReleaseInfo.Name

        # date
        if (-not([string]::IsNullorEmpty($ReleaseInfo.Timestamp))) {
            if ($format -match $dateFormatPattern) {
                if ($ReleaseInfo.Timestamp -is [System.DateTimeOffset]) {
                    $dateText = (Get-Date $ReleaseInfo.Timestamp.UtcDateTime -Format $dateFormat)
                } else {
                    $dateText = (Get-Date $ReleaseInfo.Timestamp -Format $dateFormat)
                }

                $dateField = $Matches.0 # we want to replace the whole field so store that
                $dateFormat = $Matches.df # the format of the datetime object

                $format = $format -replace $dateField , $dateText
            } else {
                $format = $format -replace $datePattern, $ReleaseInfo.Timestamp
            }
        }
    } end {
        $format
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Changelog\Format-ChangelogRelease.ps1' 68
#Region '.\private\Changelog\Format-HeadingText.ps1' -1


function Format-HeadingText {
    <#
    .SYNOPSIS
        If the given Heading Block is a LinkInline, recreate the markdown link text, if not return the headings
        content
    .EXAMPLE
        $heading | Format-HeadingText
    .EXAMPLE
        $heading | Format-HeadingText -NoLink
    #>

    [CmdletBinding()]
    param(
        [Parameter(
            ValueFromPipeline
        )]
        [Markdig.Syntax.HeadingBlock]$Heading,

        # Return the text only without link markup
        [Parameter(
        )]
        [switch]$NoLink
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $headingText = ''
    }
    process {
        $child = $Heading.Inline.FirstChild
        while ($null -ne $child) {
            if ($child -is [Markdig.Syntax.Inlines.LinkInline]) {
                if ($NoLink) {
                    $headingText = $child.FirstChild.Content.ToString()
                }else {
                    Write-Debug ' - creating link text'
                    $headingText += ( -join ('[', $child.FirstChild.Content.ToString(), ']'))
                    Write-Debug " - $headingText"
                    $headingText += ( -join ('(', $child.Url, ')' ))
                    Write-Debug " - $headingText"
                }
            } else {
                $headingText += $child.Content.ToString()
            }
            $child = $child.NextSibling
        }
    }
    end {
        $headingText
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }

}
#EndRegion '.\private\Changelog\Format-HeadingText.ps1' 53
#Region '.\private\Changelog\Resolve-ChangelogGroup.ps1' -1


function Resolve-ChangelogGroup {
    <#
    .SYNOPSIS
        Given a git commit and a configuration identify what group the commit should be in
    .EXAMPLE
        Get-GitCommit | ConvertFrom-ConventionalCommit | Resolve-ChangelogGroup
    #>

    [CmdletBinding()]
    param(
        # A conventional commit object
        [Parameter(
            ValueFromPipeline
        )]
        [PSTypeName('Git.ConventionalCommitInfo')][Object[]]$Commit
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $config = Get-ChangelogConfig
    }
    process {
        Write-Debug "Processing Commit : $($Commit.Title)"
        foreach ($key in $config.Groups.Keys) {
            $group = $config.Groups[$key]
            $display = $group.DisplayName ?? $key
            $group['Name'] = $key
            Write-Debug "Checking group $key"
            switch ($group.Keys) {
                'Type' {
                    if (($null -ne $Commit.Type) -and
                        ($group.Type.Count -gt 0)) {
                        Write-Debug " - Has Type entries"
                        foreach ($type in $group.Type) {
                            Write-Debug " - Checking for a match with $type"
                            if ($Commit.Type -match $type) {
                                return $group
                            }
                        }
                    }
                    continue
                }
                'Title' {
                    if (($null -ne $Commit.Title) -and
                        ($group.Title.Count -gt 0)) {
                        Write-Debug " - Has Title entries"
                        foreach ($title in $group.Title) {
                            Write-Debug " - Checking for a match with $title"
                            if ($Commit.Title -match $title) {
                                return $group
                            }
                        }
                    }
                    continue
                }
                'Scope' {
                    if (($null -ne $Commit.Scope) -and
                        ($group.Scope.Count -gt 0)) {
                        Write-Debug " - Has Scope entries"
                        foreach ($scope in $group.Scope) {
                            Write-Debug " - Checking for a match with $scope"
                            if ($Commit.Scope -match $scope) {
                                return $group
                            }
                        }
                    }
                    continue
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Changelog\Resolve-ChangelogGroup.ps1' 75
#Region '.\private\FeatureFlags\Disable-FeatureFlag.ps1' -1

function Disable-FeatureFlag {
    [CmdletBinding()]
    param(
        # The name of the feature flag to disable
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [string[]]$Name,

        # The description of the feature flag
        [Parameter(
        )]
        [string]$Description
    )
    begin {
        #TODO: I'm relying on BuildInfo, because I don't see a scenario right now where we would use this without it
    }
    process {
        if ($null -ne $BuildInfo) {
            if ($BuildInfo.Keys -contains 'Flags') {
                if ($BuildInfo.Flags.ContainsKey($Name)) {
                    $BuildInfo.Flags[$Name].Enabled = $true
                } else {
                    $BuildInfo.Flags[$Name] = @{
                        Enabled = $true
                        Description = $Description ?? "Missing description"
                    }
                }
            }
        }
    }
    end {
    }
}
#EndRegion '.\private\FeatureFlags\Disable-FeatureFlag.ps1' 38
#Region '.\private\FeatureFlags\Enable-FeatureFlag.ps1' -1

function Enable-FeatureFlag {
    [CmdletBinding()]
    param(
        # The name of the feature flag to enable
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [string[]]$Name,

        # The description of the feature flag
        [Parameter(
        )]
        [string]$Description
    )
    begin {
        #TODO: I'm relying on BuildInfo, because I don't see a scenario right now where we would use this without it
    }
    process {
        if ($null -ne $BuildInfo) {
            if ($BuildInfo.Keys -contains 'Flags') {
                if ($BuildInfo.Flags.ContainsKey($Name)) {
                    $BuildInfo.Flags[$Name].Enabled = $true
                } else {
                    $BuildInfo.Flags[$Name] = @{
                        Enabled = $true
                        Description = $Description ?? "Missing description"
                    }
                }
            }
        }
    }
    end {
    }
}
#EndRegion '.\private\FeatureFlags\Enable-FeatureFlag.ps1' 38
#Region '.\private\InvokeBuild\Invoke-TaskNameCompletion.ps1' -1


function Invoke-TaskNameCompletion {
    <#
    .SYNOPSIS
        Complete the given task name
    .NOTES
        The Parameter that uses this function must be named 'Name'
    #>

    param(
        $commandName,
        $parameterName,
        $wordToComplete,
        $commandAst,
        $fakeBoundParameters
    )
    Write-Debug "Command $commandName parameter $parameterName with '$wordToComplete'"
    $possibleValues = (Invoke-Build ? | Select-Object -ExpandProperty Name)

    if ($fakeBoundParameters.ContainsKey('Name')) {
        $possibleValues | Where-Object {
            $_ -like "$wordToComplete*"
        }
    } else {
        $possibleValues | ForEach-Object { $_ }
    }
}
#EndRegion '.\private\InvokeBuild\Invoke-TaskNameCompletion.ps1' 27
#Region '.\private\Markdown\Add-MarkdownElement.ps1' -1

function Add-MarkdownElement {
    [CmdletBinding()]
    param(
        # Markdown element(s) to add to the document
        [Parameter(
            Position = 0
        )]
        [Object]$Element,

        # The document to add the element to
        [Parameter(
            Position = 1,
            ValueFromPipeline
        )]
        [ref]$Document,

        # Index to insert the Elements to, append to end if not specified
        [Parameter(
            Position = 2
        )]
        [int]$Index,

        # Return the updated document to the pipeline
        [Parameter(
        )]
        [switch]$PassThru
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
    } end {
        if ($PSBoundParameters.ContainsKey('Index')) {
            $Document.Value.Insert($Index, $Element)
        } else {
            $Document.Value.Add($Element)
        }

# if ($PassThru) { $Document.Value | Write-Output -NoEnumerate }
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Markdown\Add-MarkdownElement.ps1' 43
#Region '.\private\Markdown\Get-MarkdownDescendant.ps1' -1


#using namespace Markdig
#using namespace Markdig.Syntax
function Get-MarkdownDescendant {
    [CmdletBinding()]
    param(
        [Parameter(
            ValueFromPipeline
        )]
        [MarkdownObject]$InputObject,

        # The type of element to return
        [Parameter(
            Position = 0
        )]
        [string]$TypeName
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if ($PSBoundParameters.ContainsKey('TypeName')) {

            #Check Type
            $type = $TypeName -as [Type]
            if (-not $type) {
                throw "Type: '$TypeName' not found"
            }
            $methodDescendants = [MarkdownObjectExtensions].GetMethod('Descendants', 1, [MarkdownObject])
            $mdExtensionsType = [MarkdownObjectExtensions]
            $method = $methodDescendants.MakeGenericMethod($Type)
            $method.Invoke($mdExtensionsType, @(, $InputObject)) | ForEach-Object { $PSCmdlet.WriteObject($_, $false) }
        } else {
            [MarkdownObjectExtensions]::Descendants($InputObject)
        }

    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Markdown\Get-MarkdownDescendant.ps1' 42
#Region '.\private\Markdown\Get-MarkdownElement.ps1' -1

function Get-MarkdownElement {
    [CmdletBinding()]
    param(
        [Parameter(
            ValueFromPipeline
        )]
        [Markdig.Syntax.MarkdownObject]$InputObject,

        # The type of element to return
        [Parameter(
            Position = 0
        )]
        [string]$TypeName
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {

    } end {
        #Check Type
        if ($TypeName -notmatch '^Markdig\.Syntax') {
            $TypeName = 'Markdig.Syntax.' + $TypeName
        }

        $type = $TypeName -as [Type]
        if (-not $type) {
            throw "Type: '$TypeName' not found"
        }
        Write-Verbose "Looking for a $type"
        foreach ($token in $InputObject) {
            if ($token -is $type) {
                $token | Write-Output
            }
        }
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Markdown\Get-MarkdownElement.ps1' 39
#Region '.\private\Markdown\Get-MarkdownFrontMatter.ps1' -1

function Get-MarkdownFrontMatter {
    [CmdletBinding()]
    param(
        [Parameter(
            ValueFromPipeline
        )]
        [Markdig.Syntax.MarkdownDocument]$InputObject
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        Get-MarkdownElement -InputObject $InputObject -TypeName 'Markdig.Extensions.Yaml.YamlFrontMatterBlock'
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }

}
#EndRegion '.\private\Markdown\Get-MarkdownFrontMatter.ps1' 20
#Region '.\private\Markdown\Get-MarkdownHeading.ps1' -1

function Get-MarkdownHeading {
    [CmdletBinding()]
    param(
        [Parameter(
            ValueFromPipeline
        )]
        [Markdig.Syntax.MarkdownObject[]]$InputObject
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if ($PSItem -is [Markdig.Syntax.HeadingBlock]) {
            $PSItem | Write-Output
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }

}
#EndRegion '.\private\Markdown\Get-MarkdownHeading.ps1' 22
#Region '.\private\Markdown\Import-Markdown.ps1' -1


function Import-Markdown {
    [CmdletBinding()]
    [OutputType([Markdig.Syntax.MarkdownDocument])]
    param(
        # A markdown file to be converted
        [Parameter(
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path,

        # Additional extensions to add
        # Note advanced and yaml already added
        [Parameter(
        )]
        [string[]]$Extension,

        # Enable track trivia
        [Parameter(
        )]
        [switch]$TrackTrivia
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        try {
            $content = Get-Content $Path -Raw
            $builder = New-Object Markdig.MarkdownPipelineBuilder

            $builder = [Markdig.MarkdownExtensions]::Configure($builder, 'advanced+yaml')
            $builder.PreciseSourceLocation = $true
            if ($TrackTrivia) {
                $builder = [Markdig.MarkdownExtensions]::EnableTrackTrivia($builder)
            }
            [Markdig.Syntax.MarkdownDocument]$document = [Markdig.Parsers.MarkdownParser]::Parse(
                $content ,
                $builder.Build()
            )

            $PSCmdlet.WriteObject($document, $false)
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Markdown\Import-Markdown.ps1' 53
#Region '.\private\Markdown\New-MarkdownElement.ps1' -1

function New-MarkdownElement {
    [CmdletBinding(
        ConfirmImpact = 'Low'
    )]
    param(
        # Text to parse into Markdown Element(s)
        [Parameter(
            ValueFromPipeline
        )]
        [string[]]$InputObject
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $collect = @()
    } process {
        $collect += $InputObject
    } end {
        [Markdig.Markdown]::Parse(
            ($collect -join [System.Environment]::NewLine) ,
            $true
        ) | Write-Output -NoEnumerate
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Markdown\New-MarkdownElement.ps1' 25
#Region '.\private\Markdown\Write-MarkdownDocument.ps1' -1

function Write-MarkdownDocument {
    [CmdletBinding()]
    param(
        [Parameter(
            ValueFromPipeline
        )]
        [Markdig.Syntax.MarkdownObject]$InputObject
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $sw = [System.IO.StringWriter]::new()
        $rr = [Markdig.Renderers.Roundtrip.RoundtripRenderer]::new($sw)

        $rr.Write($InputObject)
        $sw.ToString() | Write-Output
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Markdown\Write-MarkdownDocument.ps1' 23
#Region '.\private\SourceInfo\Get-SourceItemInfo.ps1' -1


#using namespace System.Management.Automation.Language

function Get-SourceItemInfo {
    [CmdletBinding()]
    param(
        # The directory to look in for source files
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [ValidateNotNullOrEmpty()]
        [string[]]$Path,

        # The root directory of the source item, using the convention of a
        # source folder with one or more module folders in it.
        # Should be the Module's Source folder of your project
        [Parameter(
            Position = 0
        )]
        [string]$Root,

        # Path to the source type map
        [Parameter(
        )]
        [string]$TypeMap

    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        @(
            @{
                Option      = 'Constant'
                Name        = 'POWERSHELL_FILETYPES'
                Value       = @( '.ps1', '.psm1' )
                Description = 'Files that are Parsable into an AST'
            }
            @{
                Option      = 'Constant'
                Name        = 'DATA_FILETYPES'
                Value       = @( '.psd1' )
                Description = 'PowerShell Data files'
            }
        ) | ForEach-Object { New-Variable @_ }

        try {
            if ($PSBoundParameters.ContainsKey('TypeMap')) {
                $map = Get-SourceTypeMap -Path $TypeMap
            } else {
                # try to load defaults
                $map = Get-SourceTypeMap
            }
        } catch {
            #TODO: It would be better to have a minimal source map to fall back to
            throw "Could not find map for source types`n$_"
        }
    }
    process {
        :path foreach ($p in $Path) {
            Write-Debug "Processing $p"

            #-------------------------------------------------------------------------------
            #region Load file

            try {
                $fileItem = Get-Item $p -ErrorAction Stop
                $itemProperties = $fileItem.psobject.Properties | Select-Object -ExpandProperty Name

            } catch {
                Write-Error "Could not read $p`n$_"
                continue path
            }

            #endregion Load file
            #-------------------------------------------------------------------------------
            #-------------------------------------------------------------------------------
            #region Create sourceItem object
            $sourceObject = @{
                PSTypeName  = 'Stitch.SourceItemInfo'
                Path        = $fileItem.FullName
                BaseName    = $fileItem.BaseName
                FileName    = $fileItem.Name
                Name        = $fileItem.BaseName
                FileType    = ''
                Ast         = ''
                Tokens      = @()
                ParseErrors = @()
                Directory   = ''
                Module      = ''
                Type        = ''
                Component   = ''
                Visibility  = ''
                Verb        = ''
                Noun        = ''
            }

            #endregion Create sourceItem object
            #-------------------------------------------------------------------------------


            #-------------------------------------------------------------------------------
            #region File types
            # Any pattern listed in the FileTypes key will be considered a SourceItem

            $fileTypes = $map.FileTypes
            :filemap foreach ($fileMap in $fileTypes.GetEnumerator()) {
                $pattern = $fileMap.Key
                $properties = $fileMap.Value
                if ($fileItem.Name -match $pattern) {
                    foreach ($property in $properties.GetEnumerator()) {
                        $sourceObject[$property.Key] = $property.Value
                    }
                }
            }

            $sourceObject.FileType = $fileType ?? 'Source File'
            #endregion File types
            #-------------------------------------------------------------------------------

            #-------------------------------------------------------------------------------
            #region Parse file

            # The filetypes listed in POWERSHELL_FILETYPES are the extensions that are able to
            # be parsed into an AST
            #TODO: Move these into a key in sourcetype config
            if ($POWERSHELL_FILETYPES -contains $fileItem.Extension) {
                try {
                    Write-Debug ' - Parsing powershell'
                    $tokens = @()
                    $parseErrors = @()
                    $ast = [Parser]::ParseFile($fileItem.FullName, [ref]$tokens, [ref]$parseErrors)
                    if ($null -ne $ast) {
                        $sourceObject.Ast = $ast
                        $sourceObject.Tokens = $tokens
                        $sourceObject.ParseErrors = $parseErrors
                    }

                } catch {
                    Write-Warning "Could not parse source item $($fileItem.FullName)`n$_"
                }
                # The filetypes listed in DATA_FILETYPES are able to be imported into the 'Data' field
                # currently, only psd1 files are listed
            } elseif ($DATA_FILETYPES -contains $fileItem.Extension) {
                switch -Regex ($fileItem.Extension) {
                    '^\.psd1$' {
                        try {
                            $sourceObject['Data'] = Import-Psd $fileItem.FullName -Unsafe
                        } catch {
                            Write-Warning "Could not import data from $($fileItem.FullName)`n$_"
                        }
                    }
                }
            }

            #endregion Parse file
            #-------------------------------------------------------------------------------

            #-------------------------------------------------------------------------------
            #region Root directory

            if ([string]::IsNullorEmpty($Root)) {
                Write-Debug ' - No Root path given. Attempting to resolve from project root'
                $projectRoot = Resolve-ProjectRoot
                if ($null -eq $projectRoot) {
                    Write-Verbose ' - Could not resolve the Project Root'
                    $possibleBuildRoot = $PSCmdlet.GetVariableValue('BuildRoot')
                    if ($null -ne $possibleBuildRoot) {
                        Write-Debug ' - Using BuildRoot as root'
                        $projectRoot = $possibleBuildRoot
                    } else {
                        Write-Debug ' - Using current location as root'
                        $projectRoot = Get-Location
                    }
                    Write-Verbose " - Project root is : $projectRoot"
                }
                $relativeToProject = [System.IO.Path]::GetRelativePath($projectRoot, $fileItem.FullName)
                $projectPathParts = $relativeToProject -split [regex]::Escape([System.IO.Path]::DirectorySeparatorChar)
                $rootName = $projectPathParts[0]
                Write-Debug " - Guessing $rootName is the Source directory"
                $Root = (Join-Path $projectRoot $rootName)
                Write-Verbose " - Setting Root to $Root"

            }
            if ([string]::IsNullorEmpty($Root)) {
                throw 'Could not determine the Root directory for SourceItems'
            }

            #endregion Root directory
            #-------------------------------------------------------------------------------

            #-------------------------------------------------------------------------------
            #region Match path
            Write-Debug "Getting relative path from root '$Root'"
            $adjustedPath = [System.IO.Path]::GetRelativePath($Root, $fileItem.FullName)
            Write-Debug " - '$($fileItem.FullName)' adjusted path is '$adjustedPath'"
            $sourceObject['ProjectPath'] = $adjustedPath
            $pathItems = [System.Collections.ArrayList]@(
                $adjustedPath -split [regex]::Escape([System.IO.Path]::DirectorySeparatorChar)
                )
                #! The first item should be the module, second is the directory
                $sourceObject['Directory'] = $pathItems[1]
            Write-Debug "Path Items for ${adjustedPath}: $($pathItems -join ', ')"

            Write-Debug "Matching 'Path' settings in Source Types Configuration"


            #! levels is an Array of hashes
            $levels = $map.Path
            :level foreach ($level in $levels) {
                #-------------------------------------------------------------------------------
                #region depth check

                $pathItemIndex = $levels.IndexOf($level)
                #! There are more levels configured than there are level in this sourceItem.
                #! break out of the level loop
                if ($pathItemsIndex -ge $pathItems.Count) {
                    Write-Debug " - Index is $pathItemsIndex. No more path items"
                    break level
                }
                #endregion depth check
                #-------------------------------------------------------------------------------

                #-------------------------------------------------------------------------------
                #region Path level

                # pathField is the current component of the path when it was split, and level is the hashtable
                # from the sourcetype map config
                $pathField = $pathItems[$pathItemIndex]

                # The user has the option of setting the pathField to a property of the sourceObject by
                # adding an entry in the $levels Array as a string
                # or using a regex to set values by adding a hashtable with the regex as the key and a hashtable of
                # sourceObject property => value
                if ($level -is [String]) {
                    Write-Debug " - level $pathItemIndex is $level"
                    $sourceObject[$level] = $pathField
                    Write-Debug " - $level => $pathField"
                    continue level
                } elseif ($level -is [hashtable]) {
                    Write-Debug " - level $pathItemIndex is a hashtable"
                    foreach ($levelMap in $level.GetEnumerator()) {
                        $pattern = $levelMap.Key
                        $properties = $levelMap.Value
                        Write-Debug " - testing if $pathField matches $pattern"
                        if ($pathField -match $pattern) {
                            # Save the matches so we can use them when we match on the values below
                            $pathFieldMatches = $Matches
                            foreach ($property in $properties.GetEnumerator()) {
                                #! if the value has `{<num>}` then use that match group as the value
                                if ($property.Value -match '\{(\d+)\}') {
                                    $matchNumber = [int]$Matches.1
                                    Write-Debug " - Match number: $($property.Key) => $($pathFieldMatches[$matchNumber])"
                                    if (-not ([string]::IsNullorEmpty($pathFieldMatches[$matchNumber]))) {
                                        $sourceObject[$property.Key] = $pathFieldMatches[$matchNumber]
                                    }
                                    #! if the value has `{<word>}` then use that match group as the value
                                } elseif ($property.Value -match '\{(\w+)\}') {
                                    $matchWord = $Matches.1
                                    if (-not ([string]::IsNullorEmpty($pathFieldMatches[$matchWord]))) {
                                        Write-Debug " - Match word: $($property.Key) => $($pathFieldMatches[$matchWord])"
                                        $sourceObject[$property.Key] = $pathFieldMatches[$matchWord]
                                    }
                                } else {
                                    $sourceObject[$property.Key] = $property.Value
                                }
                            }
                        }
                    }
                    continue level
                }
            }
            #endregion Path level
            #------------------------------------------------------------------------------- }

            #endregion Match path
            #-------------------------------------------------------------------------------


            $mapProperties = $map.Keys

            foreach ($mapProperty in $mapProperties) {
                # if the key maps to a property of the fileItem
                if ($itemProperties -contains $mapProperty) {
                    # The fileItem field we are going to compare against
                    $field = $mapProperty

                    Write-Debug "Matching $field settings in sourcetypes"
                    foreach ($fieldMap in $map[$field].GetEnumerator()) {
                        $pattern = $fieldMap.Key
                        $properties = $fieldMap.Value
                        if ($fileItem.($field) -match $pattern) {
                            Write-Debug " - $($fileItem.($field)) matches $pattern"
                            #! Store these matches in $fieldMatches so that we don't lose them when we do
                            #! additional matches below
                            $fieldMatches = $Matches
                            foreach ($matchMap in $properties.GetEnumerator()) {
                                Write-Debug " - $field map $($matchMap.Key) => $($matchMap.Value)"
                                #! if the value has `{<num>}` then use that match group as the value
                                if ($matchMap.Value -match '\{(\d+)\}') {
                                    $matchNumber = [int]$Matches.1
                                    Write-Debug " - Match number: $($matchMap.Key) => $($fieldMatches[$matchNumber])"
                                    if (-not ([string]::IsNullorEmpty($fieldMatches[$matchNumber]))) {
                                        $sourceObject[$matchMap.Key] = $fieldMatches[$matchNumber]
                                    }
                                    #! if the value has `{<word>}` then use that match group as the value
                                } elseif ($matchMap.Value -match '\{(\w+)\}') {
                                    $matchWord = $Matches.1
                                    if (-not ([string]::IsNullorEmpty($fieldMatches[$matchWord]))) {
                                        Write-Debug " - Match word: $($matchMap.Key) => $($fieldMatches[$matchWord])"
                                        $sourceObject[$matchMap.Key] = $fieldMatches[$matchWord]
                                    }
                                } else {
                                    Write-Debug " - $($matchMap.Key) => $($matchMap.Value)"
                                    $sourceObject[$matchMap.Key] = $matchMap.Value
                                }
                            }
                        }
                    }
                }
            }
            #! special case: Manifest file
            if ($fileItem.Extension -like '.psd1') {
                #! this is why this one is special. A GUID field means that it is probably a manifest
                #TODO: Add the ability to "lookup" a field from a definition in the map config
                if ($sourceObject.Data.ContainsKey('GUID')) {
                    $sourceObject['FileType'] = 'PowerShell Module Manifest'
                    $sourceObject['Type'] = 'manifest'
                    $sourceObject['Visibility'] = 'public'
                }
            }

            $sourceInfo = [PSCustomObject]$sourceObject


            $sourceInfo = $sourceInfo | Add-Member -MemberType ScriptMethod -Name ToString -Value {
                Get-Content $this.Path } -Force -PassThru

            $sourceInfo | Write-Output
        } # end foreach
    } # end process block
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\SourceInfo\Get-SourceItemInfo.ps1' 347
#Region '.\private\SourceInfo\Get-TestItemInfo.ps1' -1


function Get-TestItemInfo {
    [CmdletBinding()]
    param(
        # The directory to look in for source files
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [ValidateNotNullOrEmpty()]
        [string[]]$Path,

        # The root directory to use for test properties
        [Parameter(
        )]
        [string]$Root
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        foreach ($p in $Path) {
            Write-Debug "Processing $p"
            #-------------------------------------------------------------------------------
            #region File selection

            $fileItem = Get-Item $p -ErrorAction Stop

            if ($fileItem.Extension -notlike '.ps1') {
                Write-Verbose "Not adding $($fileItem.Name) because it is not a .ps1 file"
                continue
            } else {
                Write-Debug "$($fileItem.Name) is a test item"
            }
            #endregion File selection
            #-------------------------------------------------------------------------------

            #-------------------------------------------------------------------------------
            #region Object creation
            $pesterConfig = New-PesterConfiguration
            $pesterConfig.Run.Path = $p.FullName
            $pesterConfig.Run.SkipRun = $true
            $pesterConfig.Run.PassThru = $true
            $pesterConfig.Output.Verbosity = 'None' # Quiet
            try {
                $testResult = Invoke-Pester -Configuration $pesterConfig
                Write-Debug "Root is $Root"
            } catch {
                throw "Could not load test item $Path`n$_ "
            }
            $testInfo = @{
                PSTypeName = 'Stitch.TestItemInfo'
                Tests = $testResult.Tests
                Path = $p.FullName
            }

            [PSCustomObject]$testInfo | Write-Output
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }

}
#EndRegion '.\private\SourceInfo\Get-TestItemInfo.ps1' 67
#Region '.\private\Template\Get-StitchTemplateMetadata.ps1' -1

function Get-StitchTemplateMetadata {
    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $content = ($template | Get-Content -Raw)
        $null = $content -match '(?sm)---(.*?)---'
        if ($Matches.Count -gt 0) {
            Write-Debug " - YAML header info found $($Matches.1)"
            $Matches.1 | ConvertFrom-Yaml | Write-Output
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Template\Get-StitchTemplateMetadata.ps1' 28
#Region '.\private\Template\Invoke-StitchTemplate.ps1' -1

function Invoke-StitchTemplate {
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'Medium'
    )]
    param(

        # Specifies a path to the template source
        [Parameter(
            Mandatory,
            Position = 1,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Source,

        # The directory to place the new file in
        [Parameter()]
        [string]$Destination,

        # The name of target file
        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [string]$Name,

        # The target path to write the template output to
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [string]$Target,

        # Binding data to be given to the template
        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [hashtable]$Data,

        # Overwrite the Target with the output
        [Parameter(
        )]
        [switch]$Force,

        # Return the path to the generated file
        [Parameter(
        )]
        [switch]$PassThru
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {

        if (-not ([string]::IsNullorEmpty($Source))) {
            if (-not (Test-Path $Source)) {
                throw "Template file $Source not found"
            }

            try {
                if ([string]::IsNullorEmpty($Data)) {
                    $Data = @{}
                }

                $Data['Name'] = $Name
                # Templates can use this to import/include other templates
                $Data['TemplatePath'] = $Source | Split-Path -Parent


                $templateOptions = @{
                    Path = $Source
                }


                $templateOptions['Binding'] = $Data
                $templateOptions['Safe'] = $true

                Write-Debug "Converting template $Name with options"
                Write-Debug "Output of template to $Target"
                foreach ($option in $templateOptions.Keys) {
                    Write-Debug " - $option => $($templateOptions[$option])"
                }
                if (-not ([string]::IsNullorEmpty($templateOptions.Binding))) {
                    Write-Debug " - Bindings:"
                    foreach ($key in $templateOptions.Binding.Keys) {
                        Write-Debug " - $key => $($templateOptions.Binding[$key])"
                    }
                }

                $verboseFile = [System.IO.Path]::GetTempFileName()
                <#
                EPS builds the templates using StringBuilder, and then "executes" them in a separate powershell
                instance. Because of that, some errors and exceptions dont show up, you just get no output. To
                get the actual error, you need to see what the error of the scriptblock is. It looks like there is
                an update on the [github repo](https://github.com/straightdave/eps) but it is not the released
                version
 
                ! So to confirm that the template functions correctly, check for content first
                #>

                $content = Invoke-EpsTemplate @templateOptions -Verbose 4>$verboseFile
                #! Check this here and use it after we are out of the try block
                $contentExists = (-not([string]::IsNullorEmpty($content)))
            } catch {
                $PSCmdlet.ThrowTerminatingError($_)
            }

            if ($contentExists) {
                $overwrite = $false
                if (Test-Path $Target) {
                    if (-not ($Force)) {
                        $writeErrorSplat = @{
                            Message = "$Target already exists. Use -Force to overwrite"
                            Category = 'ResourceExists'
                            CategoryTargetName = $Target
                        }

                        Write-Error @writeErrorSplat
                    } else {
                        $overwrite = $true
                    }
                }

                if ($overwrite) {
                    Write-Debug "It does exist"
                    $operation = 'Overwrite file'
                } else {
                    Write-Debug 'It does not exist yet'
                    $operation = 'Write file'
                }

                if ($PSCmdlet.ShouldProcess($Target, $operation)) {
                    try {
                        $targetDir = $Target | Split-Path -Parent
                        if (-not (Test-Path $targetDir)) {
                            mkdir $targetDir -Force
                        }
                        $content | Set-Content $Target
                        if ($PassThru) {
                            $Target | Write-Output
                        }
                    } catch {
                        throw "Could not write template content to $Target`n$_"
                    }
                }
            } else {
                #-------------------------------------------------------------------------------
                #region Get template error
                Write-Debug "No content. Getting inner error"
                $verboseOutput = [System.Collections.ArrayList]@(Get-Content $verboseFile)
                #! Replace the first and last lines with braces to make it a scriptblock so we can execute the inner content
                $null = $verboseOutput.RemoveAt(0)
                $null = $verboseOutput.RemoveAt($verboseOutput.Count - 1)

                $null = $verboseOutput.Insert( 0 , 'try {')
                $verboseOutput += @(
                    '} catch {',
                    'throw $_',
                    '}'
                )
                $stringBuilderScript = [scriptblock]::Create(($verboseOutput | Out-String))
                try {
                    Invoke-Command -ScriptBlock $stringBuilderScript
                } catch {
                    throw $_
                }
                #endregion Get template error
                #-------------------------------------------------------------------------------
            }
        } else {
            throw "No Source given to process"
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\private\Template\Invoke-StitchTemplate.ps1' 179
#Region '.\public\Changelog\Add-ChangelogEntry.ps1' -1


function Add-ChangelogEntry {
    <#
    .SYNOPSIS
        Add an entry to the changelog
    #>

    [CmdletBinding()]
    param(
        # The commit to add
        [Parameter(
            Position = 1,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [PSTypeName('Git.ConventionalCommitInfo')][Object]$Commit,

        # Specifies a path to the changelog file
        [Parameter(
            Position = 0
        )]
        [Alias('PSPath')]
        [string]$Path,

        # The release to add the entry to
        [Parameter(
            Position = 2
        )]
        [string]$Release = 'unreleased'
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        enum DocumentState {
            NONE = 0
            RELEASE = 1
            GROUP = 2
        }
    }
    process {
        $group = $Commit | Resolve-ChangelogGroup
        Write-Debug "Commit $($Commit.MessageShort) resolves to group $($group.DisplayName)"

        if (Test-Path $Path) {
            Write-Debug "Now parsing $Path"
            [Markdig.Syntax.MarkdownDocument]$doc = $Path | Import-Markdown -TrackTrivia
            $state = [DocumentState]::NONE
            $tokenCount = 0
            foreach ($token in $doc) {
                Write-Debug "--- $state : Line $($token.Line) $($token.GetType()) Index $($doc.IndexOf($token))"
                switch ($token.GetType()) {
                    'Markdig.Syntax.HeadingBlock' {
                        switch ($token.Level) {
                            2 {
                                Write-Debug " - Is a level 2 heading"
                                $text = $token | Format-HeadingText -NoLink
                                if ($text -match [regex]::Escape($Release)) {
                                    Write-Debug " - *** Heading '$text' matches $Release ***"
                                    $state = [DocumentState]::RELEASE
                                } else {
                                    Write-Debug " - $text did not match"
                                }
                                continue
                            }
                            3 {
                                Write-Debug " - Is a level 3 heading"
                                if ($state -eq [DocumentState]::RELEASE) {
                                    $text = $token | Format-HeadingText -NoLink
                                    if ($text -like $group.DisplayName) {
                                        Write-Debug " - *** Heading '$text' matches group ***"
                                        $state = [DocumentState]::GROUP
                                    }
                                } else {
                                    Write-Debug " - Not in release"
                                }
                                continue
                            }
                            Default {}
                        }
                        continue
                    }
                    'Markdig.Syntax.ListBlock' {
                        if ($state -eq [DocumentState]::GROUP) {
                            Write-Debug "Listblock while GROUP is set"
                            $text = $Commit | Format-ChangelogEntry
                            Write-Debug "Wanting to add '$text' to the list"
                            Write-Debug "$($token.Count) items in the list"
                            # $conversion = $text | ConvertFrom-Markdown | Select-Object -ExpandProperty Tokens |
                            # Select-Object -First 1
                            $text = "$([System.Environment]::NewLine)$text"
                            $entry = [Markdig.Markdown]::Parse($text, $true)


                            Write-Debug "The entry we want to add is a $($entry.GetType()) at $tokenCount"
                            try {
                                $doc.Insert($doc.IndexOf($token), $entry)

                            }
                            catch {
                                $PSCmdlet.ThrowTerminatingError($_)
                            } finally {
                                $state = [DocumentState]::NONE
                            }
                        }
                        continue
                    }
                    'Markdig.Syntax.LinkReferenceDefinitionGroup' {
                        $doc.RemoveAt($doc.IndexOf($token))
                    }
                }
                $tokenCount++
            }
        }
        $doc | Write-MarkdownDocument | Out-File $Path
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Changelog\Add-ChangelogEntry.ps1' 118
#Region '.\public\Changelog\ConvertFrom-Changelog.ps1' -1


function ConvertFrom-Changelog {
    <#
    .SYNOPSIS
        Convert a Changelog file into a PSObject
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
            Mandatory,
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [ValidateNotNullOrEmpty()]
        [string[]]$Path,

        # Optionally return a hashtable instead of an object
        [Parameter(
        )]
        [switch]$AsHashTable
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $changelogObject = @{
            Releases = [System.Collections.ArrayList]@()
        }
    }
    process {
        foreach ($file in $Path) {
            if (Test-Path $file) {
                try {
                    Write-Debug "Importing markdown document $file"
                    $doc = Get-Item $file | Import-Markdown
                } catch {
                    throw "Error parsing markdown`n$_"
                }
            } else {
                throw "$file is not a valid path"
            }
            Write-Debug "Parsing tokens in $file"
            foreach ($token in $doc) {
                switch ($token) {
                    { $_ -is [Markdig.Syntax.HeadingBlock] } {
                        $text = $token | Format-HeadingText
                        switch ($token.Level) {
                            <#
                if this is a level 2 heading then it is a new release
                every token after this one should be added to the release
                and the group should be added to the changelog after it has been completely
                filled out
                #>

                            2 {
                                Write-Debug "at Line $($token.Line) Found new release heading '$text'"
                                if ($null -ne $thisRelease) {
                                    Write-Debug ' - Adding previous group to changelog'
                                    if ($AsHashTable) {
                                        $null = $changelogObject.Releases.Add($thisRelease)
                                    } else {
                                        $null = $changelogObject.Releases.Add([PSCustomObject]$thisRelease)
                                    }

                                    Remove-Variable release, group -ErrorAction SilentlyContinue
                                }
                                $thisRelease = @{
                                    Groups   = [System.Collections.ArrayList]@()
                                }
                                if (-not($AsHashTable)) {
                                    $thisRelease['PSTypeName'] = 'Changelog.Release'
                                }

                                # unreleased
                                if ($text -match '^\[?unreleased\]? - (.*)?') {
                                    Write-Debug '- matches unreleased'
                                    $thisRelease['Version'] = 'unreleased'
                                    $thisRelease['Name'] = 'unreleased'
                                    $thisRelease['Type'] = 'Unreleased'
                                    if ($null -ne $Matches.1) {
                                        $thisRelease['Timestamp'] = (Get-Date $Matches.1)
                                    } else {
                                        $thisRelease['Timestamp'] = (Get-Date -Format 'yyyy-MM-dd')
                                    }
                                    # version, link and date
                                    # [1.0.1](https://github.com/user/repo/compare/vprev..vcur) 1986-02-25
                                } elseif ($text -match '^\[(?<ver>[0-9\.]+)\]\((?<link>[^\)]+)\)\s*-?\s*(?<dt>\d\d\d\d-\d\d-\d\d)?') {
                                    Write-Debug '- matches version,link and date'
                                    if ($null -ne $Matches.ver) {
                                        $thisRelease['Type'] = 'Release'
                                        $thisRelease['Version'] = $Matches.ver
                                        $thisRelease['Name'] = $Matches.ver
                                        if ($null -ne $Matches.dt) {
                                            $thisRelease['Link'] = $Matches.link
                                        }
                                        if ($null -ne $Matches.dt) {
                                            $thisRelease['Timestamp'] = $Matches.dt
                                        }
                                    }
                                    # version and date
                                    # [1.0.1] 1986-02-25
                                } elseif ($text -match '^\[(?<ver>[0-9\.]+)\]\s*-?\s*(?<dt>\d\d\d\d-\d\d-\d\d)?') {
                                    Write-Debug '- matches version and date'
                                    if ($null -ne $Matches.ver) {
                                        $thisRelease['Type'] = 'Release'
                                        $thisRelease['Version'] = $Matches.ver
                                        $thisRelease['Name'] = $Matches.ver
                                        if ($null -ne $Matches.dt) {
                                            $thisRelease['Timestamp'] = $Matches.dt
                                        }
                                    }
                                }
                            }
                            3 {
                                if ($null -ne $group) {
                                    if ($AsHashTable) {
                                        $null = $thisRelease.Groups.Add($group)
                                    } else {
                                        $null = $thisRelease.Groups.Add([PSCustomObject]$group)
                                    }
                                    $group.Clear()
                                }
                                $group = @{
                                    Entries = [System.Collections.ArrayList]@()
                                }
                                $group['DisplayName'] = $text
                                $group['Name'] = $text
                                if (-not($AsHashTable)) {
                                    $group['PSTypeName'] = 'Changelog.Group'
                                }
                            }
                        }
                    }
                    { $_ -is [Markdig.Syntax.ListItemBlock] } {
                        Write-Debug " - list item block at line $($token.Line) column $($token.Column)"
                        # token is a collection of ListItems
                        foreach ($listItem in $token) {
                            Write-Debug " - list item at line $($listItem.Line) column $($listItem.Column)"
                            $text = $listItem.Inline.Content.ToString()
                            $null = $group.Entries.Add(
                                @{
                                    Title = $text
                                    Description = $text
                                }
                            )
                        }
                        continue
                    }
                }
            }
        }
        Write-Debug ' - adding last release to changelog'
        if ($AsHashTable) {
            $null = $changelogObject.Releases.Add($thisRelease)
        } else {
            $null = $changelogObject.Releases.Add([PSCustomObject]$thisRelease)
        }
    }
    end {
        if ($AsHashTable) {
            $changelogObject | Write-Output
        } else {
            $changelogObject['PSTypeName'] = 'ChangelogInfo'
            [PSCustomObject]$changelogObject | Write-Output
        }
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Changelog\ConvertFrom-Changelog.ps1' 169
#Region '.\public\Changelog\ConvertTo-Changelog.ps1' -1


function ConvertTo-Changelog {
    <#
    .SYNOPSIS
        Convert Git-History to a Changelog
    #>

    [CmdletBinding()]
    param(
        # A git history table to be converted
        [Parameter(
            ValueFromPipeline
        )]
        [hashtable]$History
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $config = Get-ChangelogConfig
    }
    process {

        Format-ChangelogHeader
        [System.Environment]::NewLine

        foreach ($releaseName in (($History.GetEnumerator() | Sort-Object { $_.Value.Timestamp } -Descending | Select-Object -ExpandProperty Name))) {
            $release = $History[$releaseName]
            $release | Format-ChangelogRelease
            [System.Environment]::NewLine

            foreach ($groupName in ($release.Groups.GetEnumerator() | Sort-Object { $_.Value.Sort } | Select-Object -ExpandProperty Name)) {
                if ($groupName -like 'omit') { continue }
                $group = $release.Groups[$groupName]
                $group | Format-ChangelogGroup
                [System.Environment]::NewLine

                foreach ($entry in $group.Entries) {
                    $entry | Format-ChangelogEntry
                }
                 [System.Environment]::NewLine
            }
        }

        [System.Environment]::NewLine

        Format-ChangelogFooter
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Changelog\ConvertTo-Changelog.ps1' 50
#Region '.\public\Changelog\Export-ReleaseNotes.ps1' -1


#using namespace System.Diagnostics.CodeAnalysis


function Export-ReleaseNotes {
    [SuppressMessage('PSUseSingularNouns', '', Justification = 'ReleaseNotes is a single document' )]
    [CmdletBinding()]
    param(
        # Specifies a path to the Changelog.md file
        [Parameter(
            Position = 2,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path = 'CHANGELOG.md',

        # The path to the destination file. Outputs to pipeline if not specified
        [Parameter(
            Position = 0
        )]
        [string]$Destination,

        # The release version to create a release from
        [Parameter(
        )]
        [string]$Release
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $changelogData = $null
        function outputItem {
            param(
                [Parameter(
                    Position = 1,
                    ValueFromPipeline
                )]
                [string]$Item,

                [Parameter(
                    Position = 0
                )]
                [bool]$toFile
            )
            if ($toFile) {
                $Destination | Add-Content $Item
            } else {
                $Item | Write-Output
            }
        }
    }
    process {
        $writeToFile = $PSBoundParameters.ContainsKey('Destination')

        if (-not ([string]::IsNullorEmpty($Path))) {
            if (Test-Path $Path) {
                Write-Debug "Converting Changelog : $Path"
                $dpref = $DebugPreference
                $DebugPreference = 'SilentlyContinue'
                $changelogData = ($Path | ConvertFrom-Changelog)
                $DebugPreference = $dpref
                if ($null -ne $changelogData) {
                    Write-Debug "There are $($changelogData.Releases.Count) release sections"
                    :section foreach ($section in $changelogData.Releases ) {
                        Write-Debug "$($section.Type) Section: Version = $($section.Version) Timestamp = $($section.Timestamp)"
                        if ($section.Type -like 'Unreleased') {
                            continue section
                        }
                        if (-not ([string]::IsNullorEmpty($Release))) {
                            if ( [semver]::new($section.Version) -gt [semver]::new($Release)) {
                                continue section
                            }
                        }
                        #! we can use our Format to assemble the Timestamp, version, etc
                        #! the other items should already be in the format we want
                        $section | Format-ChangelogRelease | outputItem $writeToFile
                        foreach ($group in $section.Groups) {
                            #! no need to reformat it
                            $group | Format-ChangelogGroup | outputItem $writeToFile
                            foreach ($entry in $group.Entries) {
                                $entry | Format-ChangelogEntry | outputItem $writeToFile
                            }
                        }
                    }
                }
            }
        } else {
            throw "$Path is not a valid Path"
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Changelog\Export-ReleaseNotes.ps1' 95
#Region '.\public\Changelog\Get-ChangelogConfig.ps1' -1


function Get-ChangelogConfig {
    <#
    .SYNOPSIS
        Look for a psd1 configuration file in the local folder, the path specified, or the module folder
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path
    )
    begin {
        $defaultConfigFile = '.changelog.config.psd1'
    }
    process {
        if (-not($PSBoundParameters.ContainsKey('Path'))) {
            #! if not specified, look in the local directory for the config file
            $Path = Get-Location
        }

        Write-Debug "Path is set as $Path"
        if (Test-Path $Path) {
            $pathItem = Get-Item $Path
            if ($pathItem.PSIsContainer) {
                Write-Debug "Path is a directory. Looking for $defaultConfigFile"
                $possiblePath = (Join-Path $pathItem $defaultConfigFile)
                # look for the file in the directory
                if (Test-Path $possiblePath) {
                    Write-Debug " - Found"
                    $configFile = Get-Item $possiblePath
                }
            } else {
                $configFile = $pathItem
            }

        } else {
            $configFile = Get-Item (Join-Path $ExecutionContext.SessionState.Module.ModuleBase $defaultConfigFile)
        }
        Write-Verbose "Loading configuration from $($configFile.FullName)"
        $config = Import-PowerShellDataFile $configFile.FullName

    }
    end {
        $config
    }
}
#EndRegion '.\public\Changelog\Get-ChangelogConfig.ps1' 53
#Region '.\public\Changelog\Set-ChangelogRelease.ps1' -1


function Set-ChangelogRelease {
    <#
    .SYNOPSIS
        Create a new release section in the Changelog based on the changes in 'Unreleased' and creates a new blank
        'Unreleased' section
    #>

    [Alias('Update-Changelog')]
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'medium'
    )]
    param(
        # Specifies a path to the changelog file
        [Parameter(
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path,

        # The unreleased section will be moved to this version
        [Parameter(
        )]
        [string]$Release,

        # The date of the release
        [Parameter(
        )]
        [datetime]$releaseDate,

        # Skip checking the current git tag information
        [Parameter(
        )]
        [switch]$SkipGitTag
    )
    begin {
        Write-Verbose "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $config = Get-ChangelogConfig
        if (-not($SkipGitTag)) {
            if ($PSBoundParameters.ContainsKey('Release')) {
                $tag = Get-GitTag -Name $Release -ErrorAction SilentlyContinue
            } else {
                $tag = Get-GitTag | Where-Object {
                    $_.Name -match $config.TagPattern
                } |  Select-Object -First 1 -ErrorAction SilentlyContinue
            }

            if ($null -ne $tag) {
                $releaseDate = $tag.Target.Author.When.UtcDateTime
                if ($null -ne $Release) {
                    $null = $tag.FriendlyName -match $config.TagPattern
                    if ($Matches.Count -gt 0) {
                        $Release = $Matches.1
                    }
                }
            } else {
                $PSCmdlet.WriteError("Could not find tag $Release")
            }
        }
        if ([string]::IsNullorEmpty($Release)) {
            throw "No Release version could be found"
        }

        if ([string]::IsNullorEmpty($ReleaseDate)) {
            $PSCmdlet.WriteError("No release date was found for release $Release")
        }
    }
    process {
        Write-Verbose "`n$('-' * 80)`n-- Process start $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        if (Test-Path $Path) {
            Write-Verbose "Setting up temp document"
            $tempFile = [System.IO.Path]::GetTempFileName()
            Get-Content $Path -Raw | Set-Content $tempFile

            Write-Verbose "Now parsing document"
            [Markdig.Syntax.MarkdownDocument]$doc = $tempFile | Import-Markdown -TrackTrivia

            $currentVersionHeading = $doc | Get-MarkdownHeading | Where-Object {
                ($_ | Format-HeadingText -NoLink) -match $config.CurrentVersion
            }
            Write-Verbose "Found $($currentVersionHeading.Count) current version headings"

            if ($null -ne $currentVersionHeading) {
                $afterCurrentHeading = ($doc.IndexOf($currentVersionHeading) + 1)
                $releaseData = @{
                    Name = $Release
                    TimeStamp = $releaseDate
                }
                $newHeading = [Markdig.Markdown]::Parse(
                    ($releaseData | Format-ChangelogRelease),
                    $true
                )
                if ($null -ne $newHeading) {
                    $newText =$newHeading | Format-HeadingText -NoLink
                    Write-Verbose "New Heading is $newText"
                    [ref]$doc | Add-MarkdownElement $newHeading -Index $afterCurrentHeading
                    [ref]$doc | Add-MarkdownElement ([Markdig.Syntax.BlankLineBlock]::new()) -Index $afterCurrentHeading
                }
            }

            $linkRefs = $doc | Get-MarkdownElement LinkReferenceDefinitionGroup | Select-Object -First 1
            if ($null -ne $linkRefs) {
                $doc.RemoveAt($doc.IndexOf($linkRefs))
            }
            try {
                $doc | Write-MarkdownDocument | Out-File $tempFile
                #! -Force required to overwrite our file
                $tempFile | Move-Item -Destination $Path -Force
            } catch {
                $PSCmdlet.ThrowTerminatingError($_)
            }
        }
        Write-Verbose "`n$('-' * 80)`n-- Process end $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    end {
        Write-Verbose "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Changelog\Set-ChangelogRelease.ps1' 120
#Region '.\public\Configuration\Convert-ConfigurationFile.ps1' -1


function Convert-ConfigurationFile {
    <#
    .SYNOPSIS
        Convert a configuration file into a powershell hashtable. Can be psd1, yaml, or json
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more configuration files.
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        Write-Debug "Getting ready to convert configuration file $Path"
        if (Test-Path $Path) {
            Write-Debug ' - File exists'
            $pathItem = Get-Item $Path
            if ($pathItem.PSISContainer) {
                Get-ChildItem -Path $Path -Recurse | Convert-ConfigurationFile
            } else {
                switch -Regex ($pathItem.Extension) {
                    '\.psd1' {
                        #! Note we use the 'Unsafe' parameter so we can have scriptblocks and
                        #! variables in our psd
                        Write-Debug ' - Importing PSD'
                        $configOptions = (Import-Psd -Path $pathItem -Unsafe) | Write-Output
                    }
                    '\.y(a)?ml' {
                        Write-Debug ' - Importing YAML'
                        $configOptions = (Get-Content $pathItem | ConvertFrom-Yaml -Ordered) | Write-Output
                    }
                    '\.json(c)?' {
                        Write-Debug ' - Importing JSON'
                        $configOptions = (Get-Content $pathItem | ConvertFrom-Json -Depth 16) | Write-Output
                    }
                    default {
                        Write-Warning "Could not determine the type for $($pathItem.FullName)"
                    }
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Configuration\Convert-ConfigurationFile.ps1' 55
#Region '.\public\Configuration\Get-BuildConfiguration.ps1' -1


#using namespace System.Collections.Specialized
function Get-BuildConfiguration {
    <#
    .SYNOPSIS
        Gather information about the project for use in tasks
    .DESCRIPTION
        `Get-BuildConfiguration` collects information about paths, source items, versions and modules that it finds
        in -Path. Configuration information can be added/updated using configuration files.
    .EXAMPLE
        Get-BuildConfiguration . -ConfigurationFiles ./.build/config
        gci .build\config | Get-BuildConfiguration .
    #>

    [OutputType([System.Collections.Specialized.OrderedDictionary])]
    [CmdletBinding()]
    param(
        # Specifies a path to the folder to build the configuration for
        [Parameter(
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [string]$Path = (Get-Location),

        # Path to the build configuration file
        [Parameter(
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$ConfigurationFiles,

        # Default Source directory
        [Parameter(
        )]
        [string]$Source,

        # Default Tests directory
        [Parameter(
        )]
        [string]$Tests,

        # Default Staging directory
        [Parameter(
        )]
        [string]$Staging,

        # Default Artifact directory
        [Parameter(
        )]
        [string]$Artifact,

        # Default Docs directory
        [Parameter(
        )]
        [string]$Docs
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"

        # The info table holds all of the gathered project information, which will ultimately be returned to the
        # caller
        $info = [ordered]@{
            Project = @{}
        }

        #-------------------------------------------------------------------------------
        #region Set defaults

        <#
         !used throughout to set "project locations"
         which is why we don't just add it directly to $info
        #>

        $defaultLocations = @{
            Source   = "source"
            Tests    = 'tests'
            Staging  = 'stage'
            Artifact = 'out'
            Docs     = 'docs'
        }

        # Add them as top level keys
        $defaultLocations.Keys | ForEach-Object { $info[$_] = '' }

        #endregion Set defaults
        #-------------------------------------------------------------------------------

        #-------------------------------------------------------------------------------
        #region Normalize paths

        Write-Debug ( @(
                "Paths used to build configuration:",
                "Path : $Path",
                "Source : $Source",
                "Staging : $Staging",
                "Artifact : $Artifact",
                "Tests : $Tests",
                "Docs : $Docs") -join "`n")

        $possibleRoot = $PSCmdlet.GetVariableValue('BuildRoot')
        if ($null -eq $possibleRoot) {
            Write-Debug "`$BuildRoot not found, using current location"
            $possibleRoot = (Get-Location)
        }

        foreach ($location in $defaultLocations.Keys) {
            Write-Debug "Setting the $location path"
            <#
                     The paths to the individual locations are vital to the correct operation of
                     the build.
                     Each variable is checked to see if it exists as a parameter, and then in the
                      caller scope (set via the script that called this function).
                      Finally, we test to see if the "default" is true, and add it
                    #>


            if ($PSBoundParameters.ContainsKey($location)) {
                $possibleLocation = $PSBoundParameters[$location]
            } elseif ($PSCmdlet.GetVariableValue($location)) {
                $possibleLocation = $PSCmdlet.GetVariableValue($location)
            } else {
                $possibleLocation = $defaultLocations[$location]
            }

            if ($null -ne $possibleLocation) {
                if (-not([System.IO.Path]::IsPathFullyQualified($possibleLocation))) {
                    $possibleLocation = (Join-Path $possibleRoot $possibleLocation)
                }

                if (-not(Test-Path $possibleLocation)) {
                    Write-Warning "$possibleLocation set as `$$location, but path does not exist"
                }
                #? Not sure what the right action is here. I could fail the function
                #? because I can't find the path... for now, I will just leave the
                #? unresolved string there because it must have been for a reason?
                Write-Debug " - $possibleLocation"
                $info[$location] = $possibleLocation
            }
        }
        #endregion Normalize paths
        #-------------------------------------------------------------------------------

        #-------------------------------------------------------------------------------
        #region Feature flags

        $flags = Get-FeatureFlag
        if ($null -ne $flags) {
            $info['Flags'] = $flags
        } else {
            Write-Debug "No feature flags were found"
        }

        #endregion Feature flags
        #-------------------------------------------------------------------------------

        #-------------------------------------------------------------------------------
        #region Version info
        $versionInfo = Get-ProjectVersionInfo

        if ($null -ne $versionInfo) {
            Write-Debug "Setting 'Version' key with version info"
            $info.Project['Version'] = $versionInfo
        } else {
            Write-Debug 'No version information found in project'
        }
        #endregion Version info
        #-------------------------------------------------------------------------------
    }
    process {
        #-------------------------------------------------------------------------------
        #region Configuration files

        foreach ($f in $ConfigurationFiles) {
            Write-Debug "Merging $f into BuildInfo"
            if (Test-Path $f) {
                $f | Merge-BuildConfiguration -Object ([ref]$info)
            }
        }

        #endregion Configuration files
        #-------------------------------------------------------------------------------
    }
    end {
        try {
            Write-Debug 'Resolving project root'
            $resolveRootOptions = @{
                Path     = $Path
            }
            $root = (Get-Item (Resolve-ProjectRoot @resolveRootOptions -ErrorAction SilentlyContinue))

        } catch {
            Write-Warning "Could not find Project Root`n$_"
        }

        if ($null -ne $root) {
            Write-Debug ' - root found:'
            Write-Debug " - Path is : $($root.FullName)"
            Write-Debug " - Name is : $($root.BaseName)"

            $info['Project'] = @{
                Path = $root.FullName
                Name = $root.BaseName
            }
        } else {
            Write-Debug " - Project root was not found. 'Path' and 'Name' will be empty"
            $info['Project'] = @{
                Path = ''
                Name = ''
            }
        }

        $info['Modules'] = @{}

        Write-Debug " Loading modules from $($info.Source)"
        foreach ($item in (Get-ModuleItem $info.Source)) {
            Write-Debug " Adding $($item.Name) to the collection"
            #! Get the names of the paths to process from failsafe_defaults, but the
            #! values come from the info table
            Write-Debug " - Adding field 'Paths' to module $($item.Name)"
            $item | Add-Member -NotePropertyName Paths -NotePropertyValue ($defaultLocations.Keys)
            foreach ($location in $defaultLocations.Keys) {
                $moduleLocation = (Join-Path $info[$location] $item.Name)
                Write-Debug " - Adding $location Path : $moduleLocation"
                $item | Add-Member -NotePropertyName $location -NotePropertyValue $moduleLocation
            }
            $info.Modules[$item.Name] = $item
        }

        <#------------------------------------------------------------------
              Now, configure the directories for each module. If a module is
              a Nested Module of another, then the staging folder should be:
              $Staging/RootModuleName/NestedModuleName
            ------------------------------------------------------------------#>


        Write-Debug "$('-' * 80)`n --- Getting NestedModules"
        foreach ($key in $info.Modules.Keys) {
            $currentModule = $info.Modules[$key]
            if ($null -ne $currentModule.NestedModules) {
                foreach ($nest in $currentModule.NestedModules) {
                    if ($nest -is [string]) {
                        $nestedModule = $nest
                    } elseif ($nest -is [hashtable]) {
                        $nestedModule = $nest.ModuleName
                    }
                    Write-Debug " Nested module: $nestedModule"
                    $found = ''
                    switch -Regex ($nestedModule) {
                        # path\to\ModuleName.psm1
                        # path/to/ModuleName.psm1
                        '[\\/]?(?<fname>)\.psm1$' {
                            Write-Debug " - Found path to module file $($Matches.fname)"
                            $found = $Matches.fname
                            continue
                        }
                        # path\to\ModuleName
                        # path/to/ModuleName
                        '(\w+[\\/])*(?<lword>\w+)$' {
                            Write-Debug " - Found path to directory $($Matches.lword)"
                            $found = $Matches.lword
                            continue
                        }
                        Default {
                            Write-Debug ' - Does not match a pattern'
                            $found = $nestedModule
                        }
                    }
                    if ($info.Modules.Keys -contains $found) {
                        Write-Debug " Adding $($currentModule.Name) as parent of $found"
                        $info.Modules[$found] | Add-Member -NotePropertyName 'Parent' -NotePropertyValue $currentModule.Name
                    } else {
                        Write-Debug " $found not found in project's modules`n$($info.Modules.Keys -join "`n - ") "
                    }
                }
            }
        }
        Write-Debug "Completed building configuration settings"
        $info
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Configuration\Get-BuildConfiguration.ps1' 279
#Region '.\public\Configuration\Get-BuildRunBook.ps1' -1


function Get-BuildRunBook {
    <#
    .SYNOPSIS
        Return the runbooks in the given directory
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,

        # Optionally recurse into children
        [Parameter(
        )]
        [switch]$Recurse,

        # Optional runbook filter
        [Parameter(
        )]
        [string]$Filter = '*runbook.ps1'
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"

    }
    process {
        Write-Debug "Looking for runbooks in $($Path.FullName)'"
        $options = @{
            Path = $Path
            Recurse = $Recurse
            Filter = $Filter
        }
        Get-ChildItem @options
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Configuration\Get-BuildRunBook.ps1' 45
#Region '.\public\Configuration\Get-TaskConfiguration.ps1' -1



function Get-TaskConfiguration {
    <#
    .SYNOPSIS
        Get the configuration file for the given task if it exists. First looks in the local user's stitch
        directory, and then the local build configuration directory
    .DESCRIPTION
        Look for the given task's configuration in `<buildconfig>/config/tasks`
    #>

    [CmdletBinding()]
    param(
        # The task object
        [Parameter(
            Position = 1,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [string]$Name,

        [Parameter(
            Position = 0
        )]
        [string]$TaskConfigPath
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $taskConfigOptions = @{
            Filter  = '*.config.psd1'
            Recurse = $true
        }
        $taskConfigPathOptions = @{
            ChildPath           = 'config'
            AdditionalChildPath = 'tasks'
        }
        #! Because we are looking in both the user's stitch directory and the current
        #! project's stitch directory, create an empty array to hold all the files
        $taskConfigFiles = [System.Collections.ArrayList]::new()
    }
    process {
        #-------------------------------------------------------------------------------
        #region User stitch directory
        $userStitchDirectory = Find-LocalUserStitchDirectory

        if ($null -ne $userStitchDirectory) {
            $possibleUserTaskConfigDirectory = (Join-Path -Path $userStitchDirectory @taskConfigPathOptions)
            if (Test-Path $possibleUserTaskConfigDirectory) {
                Write-Debug "User task configuration directory found at $possibleUserTaskConfigDirectory"
                $userTaskConfigDirectory = $possibleUserTaskConfigDirectory
                Get-ChildItem -Path $userTaskConfigDirectory @taskConfigOptions | Merge-FileCollection ([ref]$taskConfigFiles)
            }
            Remove-Variable possibleUserTaskConfigDirectory -ErrorAction SilentlyContinue
        } else {
            Write-Verbose "No stitch directory found for in user's home"
        }
        #endregion User stitch directory
        #-------------------------------------------------------------------------------

        #-------------------------------------------------------------------------------
        #region Find path
        if (-not($PSBoundParameters.ContainsKey('TaskConfigPath'))) {
            Write-Debug 'No TaskConfigPath given. Looking for BuildConfigPath'
            $possibleBuildConfigPath = Find-BuildConfigurationDirectory
            if (-not ([string]::IsNullorEmpty($possibleBuildConfigPath))) {
                Write-Debug "found BuildConfigPath at $possibleBuildConfigPath"
                $BuildConfigPath = $possibleBuildConfigPath
                $TaskConfigPath = (Join-Path -Path $BuildConfigPath @taskConfigPathOptions)
                Remove-Variable possibleBuildConfigPath -ErrorAction SilentlyContinue

            }
        }
        #endregion Find path
        #-------------------------------------------------------------------------------


        if (Test-Path $TaskConfigPath) {
            Write-Debug "Looking for task config files in $TaskConfigPath"

            Get-ChildItem -Path $TaskConfigPath @taskConfigOptions | Merge-FileCollection ([ref]$taskConfigFiles)
            Write-Debug " - Found $($taskConfigFiles.Count) config files"
        }

        if ($taskConfigFiles.Count -gt 0) {
            foreach ($taskConfigFile in $taskConfigFiles) {
                if ((-not ($PSBoundParameters.ContainsKey('Name'))) -or
                    ($TaskConfigFile.BaseName -like "$Name.config")) {
                    try {
                        #TODO: Use the Convert-ConfigurationFile to support any kind of config file, not just psd
                        $config = Import-Psd -Path $taskConfigFile -Unsafe
                        if ($null -eq $config) { $config = @{} }
                            $config['TaskName'] = ($TaskConfigFile.BaseName -replace '\.config$', '')
                            $config['ConfigPath'] = $TaskConfigFile.FullName
                    } catch {
                        $PSCmdlet.ThrowTerminatingError($_)
                    }
                    #TODO: I'm not sure we should return the config object here, unless we change the name to Import
                    $config | Write-Output
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Configuration\Get-TaskConfiguration.ps1' 106
#Region '.\public\Configuration\Merge-BuildConfiguration.ps1' -1

function Merge-BuildConfiguration {
    [CmdletBinding()]
    param(
        # The object to merge the configuration into (by reference)
        [Parameter(
            Mandatory,
            Position = 0
        )]
        [ref]$Object,

        # The top level key in which to add the given table
        [Parameter(
            Position = 1
            )]
            [string]$Key,

            # Specifies a path to one or more configuration files
            [Parameter(
            Position = 2,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
            )]
            [Alias('PSPath')]
            [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        foreach ($file in $Path) {
            $options = Convert-ConfigurationFile $Path

            if ($null -ne $options) {
                if ($PSBoundParameters.ContainsKey('Key')) {
                    $Object.Value.$Key | Update-Object -UpdateObject $options
                } else {
                    $Object.Value | Update-Object -UpdateObject $options
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Configuration\Merge-BuildConfiguration.ps1' 46
#Region '.\public\Configuration\Select-BuildRunBook.ps1' -1


function Select-BuildRunBook {
    <#
    .SYNOPSIS
        Locate the runbook for the given BuildProfile
    .DESCRIPTION
        Select-BuildRunBook locates the runbook associated with the BuildProfile. If no BuildProfile is given,
        Select-BuildRunBook will use default names to search for
    .EXAMPLE
        $ProfilePath | Select-BuildRunBook 'default'
        $ProfilePath | Select-BuildRunBook 'site'
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
            Position = 1,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
            )]
            [Alias('PSPath')]
            [string[]]$Path,

        # The build profile to select the runbook for
        [Parameter(
            Position = 0
        )]
        [string]$BuildProfile
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $defaultProfileNames = @(
            'default',
            'build'
        )
        $defaultRunbookSuffix = "runbook.ps1"
    }
    process {
        if (-not ($PSBoundParameters.ContainsKey('Path'))) {
            if (-not ([string]::IsNullorEmpty($PSCmdlet.GetVariableValue('ProfileRoot')))) {
                $Path = $PSCmdlet.GetVariableValue('ProfileRoot')
            } else {
                $Path = (Get-Location).Path
            }
        }

        if (-not ($PSBoundParameters.ContainsKey('BuildProfile'))) {
            $searches = $defaultProfileNames
        } else {
            $searches = $BuildProfile
        }

        foreach ($p in $Path) {
            if (Test-Path $p) {
                foreach ($searchFor in $searches) {
                    Write-Debug "Looking in $p for $searchFor runbook"
                    <#
                    First, look for the buildprofile.runbook.ps1 in the given directory
                    #>

                    $options = @{
                        Path = $p
                        Filter = "$searchFor.$defaultRunbookSuffix"
                    }
                    $possibleRunbook = Get-ChildItem @options | Select-Object -First 1

                    if ($null -eq $possibleRunbook) {
                        Write-Debug " - No runbook found in $p matching $($options.Filter)"
                        $null = $options.Clear()
                        $options = @{
                            Path = (Join-Path $p $searchFor)
                            Filter = "*$defaultRunbookSuffix"
                        }
                        Write-Debug "Looking in $($options.Path) using $($options.Filter)"
                        if (Test-Path $options.Path) {
                            $possibleRunbook = Get-ChildItem @options | Select-Object -First 1
                        }
                    }

                    if ($null -ne $possibleRunbook) {
                        $possibleRunbook | Write-Output
                    }
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }

}
#EndRegion '.\public\Configuration\Select-BuildRunBook.ps1' 91
#Region '.\public\Content\Checkpoint-Directory.ps1' -1


function Checkpoint-Directory {
    <#
    .SYNOPSIS
        Output the relative path and the MD5 hash value of each file in the given Path
    .DESCRIPTION
 
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingBrokenHashAlgorithms',
        '',
        Justification = 'We are only using MD5 to verify the file has not changed')]
    [OutputType('File.Checksum')]
    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if (Test-Path $Path) {
            foreach ($file in (Get-ChildItem -Path $Path -File -Recurse)) {
                $relative = [System.IO.Path]::GetRelativePath((Resolve-Path $Path), $file.FullName)
                $checksum = $file | Checkpoint-File

                [PSCustomObject]@{
                    PSTypeName = 'File.Checksum'
                    Path       = $relative
                    Hash       = $checksum
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Content\Checkpoint-Directory.ps1' 45
#Region '.\public\Content\Checkpoint-File.ps1' -1


function Checkpoint-File {
    <#
    .SYNOPSIS
        Create a hash of the given file
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingBrokenHashAlgorithms',
        '',
        Justification = 'We are only using MD5 to verify the file has not changed')]
    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $hashingAlgorithm = 'MD5'
    }
    process {
        foreach ($file in $Path) {
            $checksum = $file
            | Get-FileHash -Algorithm $hashingAlgorithm
            | Select-Object -ExpandProperty Hash

            [PSCustomObject]@{
                PSTypeName = 'File.Checksum'
                TimeStamp  = Get-Date
                Hash       = $checksum
            }
        }

    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Content\Checkpoint-File.ps1' 43
#Region '.\public\Content\Checkpoint-String.ps1' -1


function Checkpoint-String {
    <#
    .SYNOPSIS
        Hash the given string using the MD5 algorithm
    #>

    [OutputType('System.String')]
    [CmdletBinding()]
    param(
        # The string to hash
        [Parameter(
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [string]$InputObject
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
        $utf8 = new-object -TypeName System.Text.UTF8Encoding
    }
    process {
        $hash = [System.BitConverter]::ToString($md5.ComputeHash($utf8.GetBytes($InputObject)))
        $hash -replace '-', ''
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Content\Checkpoint-String.ps1' 30
#Region '.\public\Content\Convert-LineEnding.ps1' -1


function Convert-LineEnding {
    <#
    .SYNOPSIS
        Convert the line endings in the given file to "Windows" (CRLF) or "Unix" (LF)
    .DESCRIPTION
        `Convert-LineEnding` will convert all of the line endings in the given file to the type specified. If
        'Windows' or 'CRLF' is given, all line endings will be '\r\n' and if 'Unix' or 'LF' is given all line
        endings will be '\n'
 
        'Unix' (LF) is the default
    .EXAMPLE
        Get-ChildItem . -Filter "*.txt" | Convert-LineEnding -LF
 
        Convert all txt files in the current directory to '\n'
    .NOTES
        WARNING! this can corrupt a binary file.
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'Unix'
    )]
    param(
        # The file to be converted
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,

        # Convert line endings to 'Unix' (LF)
        [Parameter(
            ParameterSetName = 'Unix',
            Position = 1
        )]
        [switch]$LF,

        # Convert line endings to 'Windows' (CRLF)
        [Parameter(
            ParameterSetName = 'Windows',
            Position = 1
        )]
        [switch]$CRLF
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        foreach ($file in $Path) {
            if ($CRLF) {
                Write-Verbose " Converting line endings in $($file.Name) to 'CRLF'"
            ((Get-Content $file) -join "`r`n") | Set-Content -NoNewline -Path $file
            } elseif ($LF) {
                Write-Verbose " Converting line endings in $($file.Name) to 'LF'"
            ((Get-Content $file) -join "`n") | Set-Content -NoNewline -Path $file
            } else {
                Write-Error "No EOL format specified. Please use '-LF' or '-CRLF'"
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}

#EndRegion '.\public\Content\Convert-LineEnding.ps1' 67
#Region '.\public\Content\Find-ParseToken.ps1' -1


function Find-ParseToken {
    <#
    .SYNOPSIS
        Return an array of tokens that match the given pattern
    #>

    [OutputType([System.Array])]
    [CmdletBinding()]
    param(
        # The token to find, as a regex
        [Parameter(
            Position = 0
        )]
        [string]$Pattern,

        # The type of token to look in
        [Parameter(
            Position = 1
        )]
        [System.Management.Automation.PSTokenType]$Type,

        # Specifies a path to one or more locations.
        [Parameter(
        Position = 2,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $options = $PSBoundParameters
        $null = $options.Remove('Pattern')
        try {
            $tokens = Get-ParseToken @options
        }
        catch {
            throw "Could not parse $Path`n$_"
        }

        if ($null -ne $tokens) {
            Write-Debug " - Looking for $Pattern in $($tokens.Count) tokens"
            foreach ($token in $tokens) {
                Write-Debug " - Checking $($token.Content)"
                if ($token.Content -Match $Pattern) {
                    $token | Write-Output

                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Content\Find-ParseToken.ps1' 59
#Region '.\public\Content\Format-File.ps1' -1


function Format-File {
    <#
    .SYNOPSIS
        Run PSSA formatter on the given files
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
            Position = 1,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,

        # Path to the code format settings
        [Parameter(
            Position = 0
        )]
        [object]$Settings = 'CodeFormatting.psd1'
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {

        if (-not($PSBoundParameters.ContainsKey('Path'))) {
            if ($null -ne $psEditor) {
                $currentFile = $psEditor.GetEditorContext().CurrentFile.Path
                if (Test-Path $currentFile) {
                    Write-Debug "Formatting current VSCode file '$currentFile'"
                    $Path += $currentFile
                }
            }
        }
        foreach ($file in $Path) {
            if (Test-Path $file) {
                $content = Get-Content $file -Raw
                $options = @{
                    ScriptDefinition = $content
                    Settings         = $Settings
                }
                try {
                    Invoke-Formatter @options | Set-Content $file
                } catch {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Content\Format-File.ps1' 57
#Region '.\public\Content\Get-ParseToken.ps1' -1


function Get-ParseToken {
    <#
    .SYNOPSIS
        Return an array of Tokens from parsing a file
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,

        # The type of token to return
        [Parameter(
        )]
        [System.Management.Automation.PSTokenType]$Type
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if (Test-Path $Path) {
            $errors = @()
            $content = Get-Item $Path | Get-Content -Raw
            $parsedText = [System.Management.Automation.PSParser]::Tokenize($content, [ref]$errors)

            if ($errors.Count) {
                throw "There were errors parsing $($Path.FullName). $($errors -join "`n")"
            }
            foreach ($token in $parsedText) {
                if ((-not($PSBoundParameters.ContainsKey('Type'))) -or
                    ($token.Type -like $Type)) {
                        $token | Write-Output
                }
            }

        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }

}
#EndRegion '.\public\Content\Get-ParseToken.ps1' 49
#Region '.\public\Content\Invoke-ReplaceToken.ps1' -1


function Invoke-ReplaceToken {
    <#
    .SYNOPSIS
        Replace a given string 'Token' with another string in a given file.
    #>

    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'medium'
    )]
    param(

        # File(s) to replace tokens in
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath', 'Path')]
        [string]$In,

        # The token to replace, written as a regular-expression
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [string]$Token,

        # The value to replace the token with
        [Parameter(
            Position = 1,
            Mandatory
        )]
        [Alias('Value')]
        [string]$With,


        # The destination file to write the new content to
        # If destination is a directory, `Invoke-ReplaceToken` will put the content in a file named the same as
        # the input, but in the given directory
        [Parameter(
            Position = 2
            )]
            [Alias('Out')]
            [string]$Destination
    )
    begin {
    }
    process {
        try {
            $content = Get-Content $In -Raw
            if ($content | Select-String -Pattern $Token) {
                Write-Debug "Token $Token found, replacing with $With"
                $newContent = ($content -replace [regex]::Escape($Token), $With)

                if ($PSBoundParameters.ContainsKey('Destination')) {
                    $destObject = Get-Item $Destination
                    if ($destObject -is [System.IO.FileInfo]) {
                        $destFile = $Destination
                    } elseif ($destObject -is [System.IO.DirectoryInfo]) {
                        $destFile = (Join-Path $Destination ((Get-Item $file).Name))
                    } else {
                        throw "$Destination should be a file or directory"
                    }
                } else {
                    $newContent | Write-Output
                }
                if ($PSCmdlet.ShouldProcess($destFile, "Replace $Token with $With")) {
                    Write-Verbose "Writing output to $destFile"
                    $newContent | Set-Content $destFile -Encoding utf8NoBOM
                }
            } else {
                #! This is a little rude, but I have to find a way to let the user know that nothing changed,
                #! and I don't want to send anything out to the console in case it is being directed somewhere
                #TODO: Consider using Write-Warning
                $save_verbose = $VerbosePreference
                $VerbosePreference = 'Continue'
                Write-Verbose "$Token not found in $In"
                $VerbosePreference = $save_verbose
            }
        } catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    end {
    }
}
#EndRegion '.\public\Content\Invoke-ReplaceToken.ps1' 88
#Region '.\public\Content\Measure-File.ps1' -1


function Measure-File {
    <#
    .SYNOPSIS
        Run PSSA analyzer on the given files
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,

        # Path to the code format settings
        [Parameter(
        )]
        [object]$Settings = 'PSScriptAnalyzerSettings.psd1',

        # Optionally apply fixes
        [Parameter(
        )]
        [switch]$Fix
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if (-not($PSBoundParameters.ContainsKey('Path'))) {
            if ($null -ne $psEditor) {
                $currentFile = $psEditor.GetEditorContext().CurrentFile.Path
                if (Test-Path $currentFile) {
                    Write-Debug "Formatting current VSCode file"
                    $Path += $currentFile
                }
            }
        }
        foreach ($file in $Path) {
            if (Test-Path $file) {
                $options = @{
                    Path     = $file
                    Settings = $Settings
                    Fix      = $Fix
                }
                try {
                    Invoke-ScriptAnalyzer @options
                } catch {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Content\Measure-File.ps1' 60
#Region '.\public\Content\Merge-SourceItem.ps1' -1

#using namespace System.Management.Automation.Language
function Merge-SourceItem {
    [CmdletBinding()]
    param(
        # The SourceItems to be merged
        [Parameter(
            ValueFromPipeline
        )]
        [PSTypeName('Stitch.SourceItemInfo')][object[]]$SourceItem,

        # File to merge the SourceItem into
        [Parameter(
            Position = 0
        )]
        [string]$Path,

        # Optionally wrap the given source items in `#section/endsection` tags
        [Parameter(
        )]
        [string]$AsSection
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $pre = '#region {0} {1}'
        $post = '#endregion {0} {1}'
        $root = Resolve-ProjectRoot

        $sb = New-Object System.Text.StringBuilder

        if ($PSBoundParameters.ContainsKey('AsSection')) {
            $null = $sb.AppendJoin('', @('#', ('=' * 79))).AppendLine()
            $null = $sb.AppendFormat( '#region {0}', $AsSection).AppendLine()
        }

        #-------------------------------------------------------------------------------
        #region Setup
        $sourceInfoUsingStatements = [System.Collections.ArrayList]@()

        $sourceInfoRequires = [System.Collections.ArrayList]@()
        #endregion Setup
        #-------------------------------------------------------------------------------


    }
    process {
        Write-Debug "Processing SourceItem $($PSItem.Name)"

        #-------------------------------------------------------------------------------
        #region Parse SourceItem
        #-------------------------------------------------------------------------------
        #region Content
        Write-Debug "Parsing SourceItem $($PSItem.Name)"
        #! The first NamedBlock in the AST *should* be the enum, class or function
        $predicate = { param($a) $a -is [NamedBlockAst] }
        $ast = $PSItem.Ast
        if ($null -eq $ast) { throw "Could not parse $($PSItem.Name)" }
        $nb = $ast.Find($predicate, $false)

        $start = $nb.Extent.StartLineNumber
        $end = $nb.Extent.EndLineNumber
        Write-Debug " - First NamedBlock found starting on line $start ending on line $end"

        $relativePath = $PSItem.Path -replace [regex]::Escape($root) , ''
        #! remove the leading '\' if it's there
        if ($relativePath.SubString(0,1) -like '\') {
            $relativePath = $relativePath.Substring(1,($relativePath.Length - 1))
        }

        Write-Debug " - Setting relative path to $relativePath"
        #endregion Content
        #-------------------------------------------------------------------------------

        #-------------------------------------------------------------------------------
        #region Using statements
        if ($ast.UsingStatements.Count -gt 0) {
            Write-Debug ' - Storing using statements'
            $sourceInfoUsingStatements += $ast.UsingStatements
        }

        #endregion Using statements
        #-------------------------------------------------------------------------------

        #-------------------------------------------------------------------------------
        #region Requires statements

        if ($ast.ScriptRequirements.Count -gt 0) {
            Write-Debug ' - Storing Requires statements'
            $sourceInfoRequires += $ast.ScriptRequirements
        }
        #endregion Requires statements
        #-------------------------------------------------------------------------------

        #endregion Parse SourceItem
        #-------------------------------------------------------------------------------

        #-------------------------------------------------------------------------------
        #region SourceItem content
        Write-Debug " - Merging $($PSItem.Name) contents"
        $null = $sb.AppendFormat( $pre, $relativePath, $start ).AppendLine()
        $null = $sb.Append( $nb.Extent.Text).AppendLine()
        $null = $sb.AppendFormat( $post, $relativePath, $end).AppendLine()

        #endregion SourceItem content
        #-------------------------------------------------------------------------------
    }
    end {
        #-------------------------------------------------------------------------------
        #region Update module content

        #-------------------------------------------------------------------------------
        #region Add sourceItem
        if ($PSBoundParameters.ContainsKey('AsSection')) {
            $null = $sb.AppendFormat( '#endregion {0}', $AsSection).AppendLine()
            $null = $sb.AppendJoin('', @('#', ('=' * 79))).AppendLine()
        }

        Write-Debug "Writing new content to $Path"
        $sb.ToString() | Add-Content $Path
        $null = $sb.Clear()
        #endregion Add sourceItem
        #-------------------------------------------------------------------------------
        #-------------------------------------------------------------------------------
        #region Parse module
        Write-Debug "$Path exists. Parsing contents"
        $moduleText = Get-Content $Path
        $module = [Parser]::ParseInput($moduleText, [ref]$null, [ref]$null)
        $content = $moduleText
        #endregion Parse module
        #-------------------------------------------------------------------------------

        #-------------------------------------------------------------------------------
        #region Requires statements
        Write-Debug ' - Parsing Requires statements'
        $combinedRequires = $module.ScriptRequirements + $sourceInfoRequires

        if (-not([string]::IsNullorEmpty($combinedRequires.ScriptRequirements.RequiredApplicationId))) {
            $s = "#Requires -ShellId $($combinedRequires.ScriptRequirements.RequiredApplicationId)"
            $content = ($content) -replace [regex]::Escape($s), ''
            $null = $sb.AppendLine($s)
            Remove-Variable s
        }
        if (-not([string]::IsNullorEmpty($combinedRequires.ScriptRequirements.RequiredPSVersion))) {
            $s = "#Requires -Version $($combinedRequires.ScriptRequirements.RequiredPSVersion)"
            $content = ($content) -replace [regex]::Escape($s), ''
            $null = $sb.AppendLine($s)
            Remove-Variable s
        }
        foreach ($rm in $combinedRequires.ScriptRequirements.RequiredModules) {
            $s = "#Requires -Modules $($rm.ToString())"
            $content = ($content) -replace [regex]::Escape($s), ''
            $null = $sb.AppendLine($s)
            Remove-Variable s
        }
        foreach ($ra in $combinedRequires.ScriptRequirements.RequiredAssemblies) {
            $s = "#Requires -Assembly $ra"
            $content = ($content) -replace [regex]::Escape($s), ''
            $null = $sb.AppendLine($s)
            Remove-Variable s
        }
        foreach ($re in $combinedRequires.ScriptRequirements.RequiredPSEditions) {
            $s = "#Requires -PSEdition $re"
            $content = ($content) -replace [regex]::Escape($s), ''
            $null = $sb.AppendLine($s)
            Remove-Variable s
        }
        foreach ($rp in $combinedRequires.ScriptRequirements.RequiresPSSnapIns) {
            $s = "#Requires -PSnapIn $($rp.Name)"
            if (-not([string]::IsNullorEmpty($rp.Version))) {
                $s += " -Version $(rp.Version)"
            }
            $content = ($content) -replace [regex]::Escape($s), ''
            $null = $sb.AppendLine($s)
            Remove-Variable s
        }

        if ($combinedRequires.ScriptRequirements.IsElevationRequired) {
            $s = '#Requires -RunAsAdministrator'
            $content = ($content) -replace [regex]::Escape($s), ''
            $null = $sb.AppendLine($s)
            Remove-Variable s
        }
        #endregion Requires statements
        #-------------------------------------------------------------------------------

        #-------------------------------------------------------------------------------
        #region Using statements
        $combinedUsingStatements = $module.UsingStatements + $sourceInfoUsingStatements

        if ($combinedUsingStatements.Count -gt 0) {
            Write-Debug " - Parsing using statements in $Path"
            Write-Debug "There are $($combinedUsingStatements.Count) using statements"
            Write-Debug "$($combinedUsingStatements | Select-Object Name, UsingStatementKind | Out-String)"
            foreach ($kind in [UsingStatementKind].GetEnumValues()) {
                Write-Debug " - Checking for using $kind statements"
                $statements = $combinedUsingStatements | Where-Object UsingStatementKind -Like $kind

                if ($statements.Count -gt 0) {
                    Write-Debug " - $($statements.Count) statements found"
                    $added = @()
                    foreach ($statement in $statements) {
                        $s = $statement.Extent.Text
                        Write-Debug " - Statement Text: '$s'"

                        if ($added -contains $s) {
                            Write-Debug " - already processed"
                        } else {
                            Write-Debug " - Looking for '$s' in content"
                            # first, remove the line from the original content
                            if ($content -match "^$([regex]::Escape($s))`$") {
                                Write-Debug " - found '$s' in content"
                                $content = ($content) -replace "^$([regex]::Escape($s))`$", ''
                            }
                            $null = $sb.AppendLine($s)
                            $added += $s
                        }
                    }
                }
            }
        } else {
            Write-Debug 'No using statements in module or sourceInfo'
        }
        #endregion Using statements
        #-------------------------------------------------------------------------------

        #-------------------------------------------------------------------------------
        #region Content
        Write-Debug "Writing content back to $Path"
        $null = $sb.AppendJoin("`n", $content)
        $sb.ToString() | Set-Content $Path
        #endregion Content
        #-------------------------------------------------------------------------------

        #endregion Update module content
        #-------------------------------------------------------------------------------

    }
}
#EndRegion '.\public\Content\Merge-SourceItem.ps1' 238
#Region '.\public\Content\Save-Checkpoint.ps1' -1


function Save-Checkpoint {
    <#
    .SYNOPSIS
        Store the relative path and MD5 sum of file(s) in path to the file in CSV format
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations to compute the checksum (MD5 hashes) for
        [Parameter(
        Position = 1,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path,

        # The file that will contain the checksums (CSV format)
        [Parameter(
            Position = 0
        )]
        [string]$ChecksumFile = ".checksum.csv",

        # Force the overwrite of an existing Checksum file
        [Parameter(
        )]
        [switch]$Force

    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if (Test-Path $ChecksumFile) {
            if ($Force) {
                Clear-Content $ChecksumFile
            } else {
                throw "$ChecksumFile already exists. Use -Force to overwrite"
            }
        }
        $path | Checkpoint-Directory | EXport-Csv $ChecksumFile
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Content\Save-Checkpoint.ps1' 47
#Region '.\public\Content\Test-Checkpoint.ps1' -1


function Test-Checkpoint {
    <#
    .SYNOPSIS
        Compares the checkpoint of a file to its current hash
    .DESCRIPTION
        Compare the MD5 hash to the HASH given. Returns true if they are equal, false if not
    .EXAMPLE
        $file | Test-Checkpoint "C72CD5EBFDC6D41E2A9F539AA94F2E8A"
    #>

    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingBrokenHashAlgorithms',
        '',
        Justification = 'We are only using MD5 to verify the file has not changed')]

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 1,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,

        # The md5 hash to compare to
        [Parameter(
            Position = 0,
            Mandatory
        )]
        [string]$Hash
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $item = Get-Item $Path

        if ($item.PSIsContainer) {
            throw "Can only compare files not directories"
        }

        $currentHash = $Path | Get-FileHash -Algorithm MD5 | Select-Object -ExpandProperty Hash

        #! return true or false based on if the hash has changed
        ($Hash -eq $currentHash) | Write-Output
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Content\Test-Checkpoint.ps1' 52
#Region '.\public\Content\Test-ContentChanged.ps1' -1


function Test-ContentChanged {
    <#
    .SYNOPSIS
        Compare the given path to a list of MD5 hashes to determine if files have changed
    .DESCRIPTION
        For each file in the given path, compare the current MD5 hash with the one stored in Checksum. If they are
        the same, return $false, otherwise return $true (one or more files have changed)
 
    #>

    [OutputType('bool')]
    [CmdletBinding(
        DefaultParameterSetName = 'File'
    )]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
            Position = 1,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,

        [Parameter(
            ParameterSetName = 'Array',
            Position = 0
        )]
        [PSTypeName('File.Checksum')][Object[]]$Checksums,

        # The file that contains the checksums (CSV format)
        [Parameter(
            ParameterSetName = 'File',
            Position = 0
        )]
        [string]$ChecksumFile
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $changedFiles = [System.Collections.ArrayList]::new()
    }
    process {
        if (-not ($PSBoundParameters.ContainsKey('ChecksumFile'))) {
            if (-not ($Checksums.Count -gt 0)) {
                throw "Checksums required. Either provide an array of 'File.Checksum' items, or a path to the checksum file"
            }
        } else {
            if (Test-Path $ChecksumFile) {
                $Checksums = (Import-Csv $ChecksumFile)
            } else {
                throw "$ChecksumFile is not a valid path"
            }
        }
        $ChecksumList = [System.Collections.ArrayList]::new($Checksums)

        $currentFiles = Get-ChildItem $Path -File -Recurse

        foreach ($file in $currentFiles) {
            $relative = [System.Io.Path]::GetRelativePath((Resolve-Path $Path), $file)

            $listItem = $ChecksumList | Where-Object Path -Like $relative

            if ($null -ne $listItem) {
                if ($file | Test-Checkpoint $listItem.Hash) {
                    [void]$ChecksumList.Remove($listItem)
                } else {
                    Write-Verbose "$relative has changed"
                    [void]$changedFiles.Add($file.FullName)
                }
            } else {
                Write-Verbose "$relative was added"
                [void]$changedFiles.Add($file.FullName)
            }
        }
    }
    end {
        #! if there are any files left in the list, then it was deleted. Output $true because content changed
        if ($ChecksumList.Count -gt 0) {
            $true | Write-Output
        } else {
            #! If any files were changed, output $true, otherwise $false
            ($changedFiles.Count -gt 0) | Write-Output
        }
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Content\Test-ContentChanged.ps1' 87
#Region '.\public\Content\Test-WindowsLineEnding.ps1' -1


function Test-WindowsLineEnding {
    <#
    .SYNOPSIS
        Test for "Windows Line Endings" (CRLF) in the given file
    .DESCRIPTION
        `Test-WindowsLineEnding` returns true if the file contains CRLF endings, and false if not
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [ValidateNotNullOrEmpty()]
        [string]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if (Test-Path $Path) {
            (Get-Content $Path -Raw) -match '\r\n$'
        } else {
            Write-Error "$Path could not be found"
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Content\Test-WindowsLineEnding.ps1' 35
#Region '.\public\FeatureFlags\Get-FeatureFlag.ps1' -1


function Get-FeatureFlag {
    <#
    .SYNOPSIS
        Retrieve feature flags for the stitch module
    #>

    [CmdletBinding()]
    param(
        # The name of the feature flag to test
        [Parameter(
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [string]$Name
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $featureFlagFile = (Join-Path (Get-ModulePath) 'feature.flags.config.psd1')
    }
    process {
        if ($null -ne $BuildInfo.Flags) {
            Write-Debug "Found the buildinfo table and it has Flags set"
            $featureFlags = $BuildInfo.Flags
        } elseif ($null -ne $featureFlagFile) {
            if (Test-Path $featureFlagFile) {
                $featureFlags = Import-Psd $featureFlagFile -Unsafe
            }
        }

        if ($null -ne $featureFlags) {
            switch ($featureFlags) {
                ($_ -is [System.Collections.Hashtable]) {
                    foreach ($key in $featureFlags.Keys) {
                        $flag =  $featureFlags[$key]
                        $flag['PSTypeName'] = 'Stitch.FeatureFlag'
                        $flag['Name'] = $key

                        if ((-not ($PSBoundParameters.ContainsKey('Name'))) -or
                        ($flag.Name -like $Name)) {
                            [PSCustomObject]$flag | Write-Output
                        }
                        continue
                    }
                }
                default {
                    foreach ($flag in $featureFlags.PSobject.properties) {
                        Write-Debug "Name is $($flag.Name)"
                        if ((-not ($PSBoundParameters.ContainsKey('Name'))) -or
                        ($flag.Name -like $Name)) {
                            $flag | Write-Output
                        }
                    }
                }
            }
        } else {
            Write-Information "No feature flag data was found"
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\FeatureFlags\Get-FeatureFlag.ps1' 63
#Region '.\public\FeatureFlags\Test-FeatureFlag.ps1' -1

function Test-FeatureFlag {
    <#
    .SYNOPSIS
        Test if a feature flag is enabled
    #>

    [OutputType([bool])]
    [CmdletBinding()]
    param(
        # The name of the feature flag to test
        [Parameter(
            Mandatory
        )]
        [ValidateNotNullOrEmpty()]
        [string]$Name
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $flag = Get-FeatureFlag -Name $Name

        if ([string]::IsNullorEmpty($flag)) {
            $false
        } else {
            $flag.Enabled
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\FeatureFlags\Test-FeatureFlag.ps1' 32
#Region '.\public\Git\Add-GitFile.ps1' -1


function Add-GitFile {
    <#
    .EXAMPLE
        Get-ChildItem *.md | function Add-GitFile
    .EXAMPLE
        Get-GitStatus | function Add-GitFile
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'asPath'
    )]
    param(
        # Accept a statusentry
        [Parameter(
            ParameterSetName = 'asEntry',
            ValueFromPipeline
        )]
        [LibGit2Sharp.RepositoryStatus[]]$Entry,

        # Paths to files to add
        [Parameter(
            Position = 0,
            ParameterSetName = 'asPath',
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,


        # Add All items
        [Parameter(
        )]
        [switch]$All,

        # The repository root
        [Parameter(
        )]
        [string]$RepoRoot,

        # Return objects to the pipeline
        [Parameter(
        )]
        [switch]$PassThru
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if ($PSBoundParameters.ContainsKey('Entry')) {
            $PSBoundParameters['Path'] = @()
            Write-Debug ' processing entry'
            foreach ($e in $Entry) {
                Write-Debug " - adding $($e.FilePath)"
                $PSBoundParameters['Path'] += $e.FilePath
            }
        }
        foreach ($file in $Path) {
            Add-GitItem (Resolve-Path $file -Relative)
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Git\Add-GitFile.ps1' 66
#Region '.\public\Git\Checkpoint-GitWorkingDirectory.ps1' -1


function Checkpoint-GitWorkingDirectory {
    <#
    .SYNOPSIS
        Save all changes (including untracked) and push to upstream
    #>

    [CmdletBinding()]
    param(
        # Message to use for the checkpoint commit.
        # Defaults to:
        # `[checkpoint] Creating checkpoint before continuing <date>`
        [Parameter(
        )]
        [string]$Message
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if (-not ($PSBoundParameters.ContainsKey('Message'))) {
            $Message = "[checkpoint] Creating checkpoint before continuing $(Get-Date -Format FileDateTimeUniversal)"
        }

        Write-Verbose 'Staging all changes'
        Add-GitItem -All
        Write-Verbose 'Commiting changes'
        Save-GitCommit -Message $Message
        Write-Verbose 'Pushing changes upstream'
        if (-not(Get-GitBranch -Current | Select-Object -ExpandProperty IsTracking)) {
            Get-GitBranch -Current | Send-GitBranch -SetUpstream
        } else {
            Get-GitBranch -Current | Send-GitBranch
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Git\Checkpoint-GitWorkingDirectory.ps1' 39
#Region '.\public\Git\Clear-MergedGitBranch.ps1' -1


function Clear-MergedGitBranch {
    <#
    .SYNOPSIS
        Prune remote branches and local branches with no tracking branch
    #>

    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'High'
    )]
    param(
        # Only clear remote branches
        [Parameter(
            ParameterSetName = 'Remote'
        )]
        [switch]$RemoteOnly,
        # Only clear remote branches
        [Parameter(
            ParameterSetName = 'Local'
        )]
        [switch]$LocalOnly
    )
    Write-Verbose "Pruning remote first"
    if (-not ($LocalOnly)) {
        if ($PSCmdlet.ShouldProcess("Remote origin", "Prune")) {
            #TODO: Find a "PowerGit way" to do this part
            git remote prune origin
        }
    }
    if (-not ($RemoteOnly)) {
        $branches = Get-GitBranch | Where-Object { $_.IsTracking -and $_.TrackedBranch.IsGone }
        if ($null -ne $branches) {
            Write-Verbose "Removing $($branches.Count) local branches"
            foreach ($branch in $branches) {
                if ($PSCmdlet.ShouldProcess($branch.FriendlyName, "Remove branch")) {
                    Remove-GitBranch
                }
            }
        }
    }
}
#EndRegion '.\public\Git\Clear-MergedGitBranch.ps1' 42
#Region '.\public\Git\ConvertFrom-ConventionalCommit.ps1' -1


function ConvertFrom-ConventionalCommit {
    <#
    .SYNOPSIS
        Convert a git commit message (such as from PowerGit\Get-GitCommit) into an object on the pipeline
    .DESCRIPTION
        A git commit message is technically unstructured text. However, a long standing convention is to structure
        the message should be a single line title, followed by a blank line and then any amount of text in the body.
        Conventional Commits provide additional structure by adding "metadata" to the title:
 
        -
        | |<------ title ----------------------| <- 50 char or less
        | <type>[optional scope]: <description>
        message
        | [optional body] <- 72 char or less
        |
        | [optional footer(s)] <- 72 char or less
        -
        Recommended types are:
        - build
        - chore
        - ci
        - docs
        - feat
        - fix
        - perf
        - refactor
        - revert
        - style
        - test
 
    #>

    [CmdletBinding()]
    param(
        # The commit message to parse
        [Parameter(
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [string]$Message,

        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [object]$Sha,

        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [object]$Author,

        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [object]$Committer

    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        enum Section {
            NONE = 0
            HEAD = 1
            BODY = 2
            FOOT = 3
        }
    }
    process {
        # This will restart for each message on the pipeline
        # Messages (at least the ones from PowerGit objects) are multiline strings
        $section = [Section]::NONE
        $title = $type = $scope = ''
        $body = [System.Collections.ArrayList]@()
        $footers = @{}
        $breakingChange = $false
        $conforming = $false
        $lineNum = 1
        foreach ($line in ($Message -split '\n')) {
            try {
                Write-Debug "Parsing line #$lineNum : '$line'"
                switch -Regex ($line) {
                    '^#+' {
                        Write-Debug ' - Comment line'
                        continue
                    }
                    #! This may match the head, but also may match a specific kind of footer
                    #! too. So we check the line number and go from there
                    @'
(?x) # Matches either a conventional title <type>(<scope>)!: <description>
                  # or a footer of like <type>: <description>
^(?<t>\w+) # Header must start with a type word
(\((?<s>\w+)\))? # Optionally a scope in '()'
(?<b>!)? # Optionally a ! to denote a breaking change
:\s+ # Mandatory colon and a space
(?<d>.+)$ # Everything else is the description
'@
              {
                        Write-Debug ' - Head line'
                        # Parse as a heading only if we are on line one!
                        if ($lineNum -eq 1) {
                            $title = $line
                            $type = $Matches.t
                            $scope = $Matches.s ?? ''
                            $desc = $Matches.d
                            $section = [Section]::HEAD
                            $breakingChange = ($Matches.b -eq '!')
                            $conforming = $true
                        } else {
                            Write-Debug ' - Footer'
                            # There could be multiple entries of the same type of footer
                            # such as:
                            # closes #9
                            # closes #7
                            if ($footers.ContainsKey($Matches.t)) {
                                $footers[$Matches.t] += $Matches.d
                            } else {
                                $footers[$Matches.t] = @($Matches.d)
                            }
                            $section = [Section]::FOOT
                        }
                        continue
                    }
                    @'
(?x) # Matches a git-trailer style footer <type>: <description> or <type> #<description>
^\s*
(?<t>[a-zA-Z0-9-]+)
(:\s|\s\#)
(?<v>.*)$
'@
              {
                        Write-Debug ' - Footer'
                        # There could be multiple entries of the same type of footer
                        # such as:
                        # closes #9
                        # closes #7
                        if ($footers.ContainsKey($Matches.t)) {
                            $footers[$Matches.t] += $Matches.d
                        } else {
                            $footers[$Matches.t] = @($Matches.d)
                        }
                        $section = [Section]::FOOT
                        continue
                    }
                    @'
(?x) # Matches either BREAKING CHANGE: <description> or BREAKING-CHANGE: <description>
^\s*
(?<t>BREAKING[- ]CHANGE)
:\s
(?<v>.*)$
'@
              {
                        Write-Debug ' - Breaking change footer'
                        $footers[$Matches.t] = $Matches.v
                        $breakingChange = $true
                    }
                    '^\s*$' {
                        # might be the end of a section, or it might be in the middle of the body
                        if ($section -eq [Section]::HEAD) {
                            # this is our "one blank line convention"
                            # so the next line should be the start of the body
                            $section = [Section]::BODY
                        }
                        continue
                    }
                    Default {
                        #! if the first line is not in the proper format, it will
                        #! end up here: We can add it as the title, but none of
                        #! the conventional commit specs will be filled
                        if ($lineNum -eq 1) {
                            Write-Verbose " '$line' does not seem to be a conventional commit"
                            $title = $line
                            $desc = $line
                            $conforming = $false
                        } else {
                            # if it matched nothing else, it should be in the body
                            Write-Debug ' - Default match, adding to the body text'
                            $body += $line
                        }
                        continue
                    }
                }
            } catch {
                throw "At $lineNum : '$line'`n$_"
            }
            $lineNum++
        }

        [PSCustomObject]@{
            PSTypeName       = 'Git.ConventionalCommitInfo'
            Message          = $Message
            IsConventional   = $conforming
            IsBreakingChange = $breakingChange
            Title            = $title
            Type             = $type
            Scope            = $scope
            Description      = $desc
            Body             = $body
            Footers          = $footers
            Sha              = $Sha
            ShortSha         = $Sha.Substring(0, 7)
            Author           = $Author
            Committer        = $Committer

        } | Write-Output
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Git\ConvertFrom-ConventionalCommit.ps1' 206
#Region '.\public\Git\Get-GitFile.ps1' -1

function Get-GitFile {
    <#
    .SYNOPSIS
        Return a list of the files listed in git status
    #>

    [OutputType([System.IO.FileInfo])]
    [CmdletBinding()]
    param(
        # The type of files to return
        [Parameter(
        )]
        [ValidateSet(
            'Added', 'Ignored', 'Missing', 'Modified', 'Removed', 'Staged',
            'Unaltered', 'Untracked',
        'RenamedInIndex', 'RenamedInWorkDir', 'ChangedInIndex', 'ChangedInWorkDir')]
        [AllowNull()]
        [string]$Type
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"

    }
    process {
        if ($PSBoundParameters.ContainsKey('Type')) {
            $status = Get-GitStatus | Select-Object -ExpandProperty $Type
        } else {
            $status = Get-GitStatus
        }

        $status | Select-Object -ExpandProperty FilePath | ForEach-Object {
                Get-Item (Resolve-Path $_) | Write-Output
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}

#EndRegion '.\public\Git\Get-GitFile.ps1' 39
#Region '.\public\Git\Get-GitHistory.ps1' -1


function Get-GitHistory {
    [CmdletBinding()]
    param()

    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $config = Get-ChangelogConfig

        $currentVersion = $config.CurrentVersion ?? 'unreleased'

        $releases = @{
            $currentVersion = @{
                Name = $currentVersion
                Timestamp = (Get-Date)
                Groups = @{}
            }
        }
    }
    process {

        foreach ($commit in Get-GitCommit) {
            #-------------------------------------------------------------------------------
            #region Convert commit message
            Write-Debug "Converting $($commit.MessageShort)"
            try {
                $commitObject = $commit | ConvertFrom-ConventionalCommit
            }
            catch {
                $exception = [Exception]::new("Could not convert commit $($commit.MessageShort)`n$($_.PSMessageDetails)")
                $errorRecord = [System.Management.Automation.ErrorRecord]::new(
                    $exception,
                    $_.FullyQualifiedErrorId,
                    $_.CategoryInfo,
                $commit
                )
                $PSCmdlet.ThrowTerminatingError($errorRecord)
            }
            #endregion Convert commit message
            #-------------------------------------------------------------------------------



            if ($null -ne $commit.Refs) {
                foreach ($ref in $commit.Refs) {
                    $name = $ref.CanonicalName -replace '^refs\/', ''
                    if ($name -match '^tags\/(?<tag>.*)$') {
                        Write-Debug ' - is a tag'
                        $commitObject | Add-Member -NotePropertyName Tag -NotePropertyValue $Matches.tag
                        if ($commitObject.Tag -match $config.TagPattern) {
                            # Add a version to the releases
                            $currentVersion = $Matches.1
                            $releases[$currentVersion] = @{
                                Name = $currentVersion
                                Timestamp = (Get-Date '1970-01-01') # set it as the epoch, but update below
                                Groups    = @{}
                            }
                            if ($null -ne $commitObject.Author.When.UtcDateTime) {
                                $releases[$currentVersion].Timestamp = $commitObject.Author.When.UtcDateTime
                            }
                        }
                    }
                }
            }

            #-------------------------------------------------------------------------------
            #region Add to group

            $group = $commitObject | Resolve-ChangelogGroup

            if ($null -eq $group) {
                Write-Debug "no group information found for $($commitObject.MessageShort)"
                $group = @{
                    Name = 'Other'
                    DisplayName = 'Other'
                    Sort = 99999
                }
            }
                if (-not($releases[$currentVersion].Groups.ContainsKey($group.Name))) {
                    $releases[$currentVersion].Groups[$group.Name] = @{
                        DisplayName = $group.DisplayName
                        Sort = $group.Sort
                        Entries = @()
                    }
                }
                $releases[$currentVersion].Groups[$group.Name].Entries += $commitObject

            #endregion Add to group
            #-------------------------------------------------------------------------------
        }
    }
    end {

        $releases | Write-Output
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }

}
#EndRegion '.\public\Git\Get-GitHistory.ps1' 99
#Region '.\public\Git\Get-GitHubDefaultBranch.ps1' -1

function Get-GitHubDefaultBranch {
    <#
    .SYNOPSIS
        Returns the default branch of the given github repository
    #>

    [CmdletBinding()]
    param(
        # The repository to find the default brach in
        [Parameter(
        )]
        [string]$RepositoryName
    )

    if ($PSBoundParameters.Key -notcontains 'RepositoryName') {
        $RepositoryName = Get-GitRepository | Select-Object -ExpandProperty RepositoryName
    }

    Get-GitHubRepository -RepositoryName $RepositoryName | Select-Object -ExpandProperty DefaultBranch
}
#EndRegion '.\public\Git\Get-GitHubDefaultBranch.ps1' 20
#Region '.\public\Git\Get-GitMergedBranch.ps1' -1

function Get-GitMergedBranch {
    <#
    .SYNOPSIS
        Return a list of branches that have been merged into the given branch (or default branch if none specified)
    #>

    [CmdletBinding()]
    param(
        # The branch to use for the "base" (the branch the returned branches are merged into)
        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [string]$FriendlyName = (Get-GitHubDefaultBranch)
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {

        $defaultTip = Get-GitBranch -Name $FriendlyName |
            Foreach-Object {$_.Tip.Sha }

        Get-GitBranch | Where-Object {
            ($_.FriendlyName -ne $FriendlyName) -and ($_.Commits |
                    Select-Object -ExpandProperty Sha) -contains $defaultTip
            } | Write-Output
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Git\Get-GitMergedBranch.ps1' 31
#Region '.\public\Git\Get-GitModifiedFile.ps1' -1

function Get-GitModifiedFile {
    <#
    .SYNOPSIS
        Return a list of the files modified in the current repository
    #>

    [CmdletBinding()]
    param()
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        Get-GitFile -Type Modified
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Git\Get-GitModifiedFile.ps1' 18
#Region '.\public\Git\Get-GitRemoteTrackingBranch.ps1' -1

function Get-GitRemoteTrackingBranch {
    Get-GitBranch | Select-Object -ExpandProperty TrackedBranch
}
#EndRegion '.\public\Git\Get-GitRemoteTrackingBranch.ps1' 4
#Region '.\public\Git\Get-GitStagedFile.ps1' -1


function Get-GitStagedFile {
    <#
    .SYNOPSIS
        Return a list of the files modified in the current repository
    #>

    [CmdletBinding()]
    param()
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        Get-GitFile -Type Staged
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Git\Get-GitStagedFile.ps1' 19
#Region '.\public\Git\Get-GitUntrackedFile.ps1' -1


function Get-GitUntrackedFile {
    <#
    .SYNOPSIS
        Return a list of the files untracked in the current repository
    #>

    [CmdletBinding()]
    param()
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        Get-GitFile -Type Untracked
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Git\Get-GitUntrackedFile.ps1' 19
#Region '.\public\Git\Join-PullRequest.ps1' -1

function Join-PullRequest {
    <#
    .SYNOPSIS
        Merge the current branch's pull request, then pull them into '$DefaultBranch' (usually 'main' or 'master')
    .DESCRIPTION
        Ensuring the current branch is up-to-date on the remote, and that it has a pull-request,
        this function will then:
        1. Merge the current pull request
        1. Switch to the `$DefaultBranch` branch
        1. Pull the latest changes
    #>

    param(
        # The name of the repository. Uses the current repository if not specified
        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [string]$RepositoryName,

        # By default the remote and local branches are deleted if successfully merged. Add -DontDelete to
        # keep the branches
        [Parameter()]
        [switch]$DontDelete,


        # The default branch. usually 'main' or 'master'
        [Parameter(
        )]
        [string]$DefaultBranch

    )
    if (-not($PSBoundParameters.ContainsKey('RepositoryName'))) {
        $PSBoundParameters['RepositoryName'] = (Get-GitRepository | Select-ExpandProperty RepositoryName)
    }

    $status = Get-GitStatus
    if ($status.IsDirty) {
        throw "Changes exist in working directory.`nCommit or stash them first"
    } else {
        if (-not ($PSBoundParameters.ContainsKey('DefaultBranch'))) {
            $DefaultBranch = Get-GitHubDefaultBranch
        }

        if ([string]::IsNullorEmpty($DefaultBranch)) {
            throw "Could not determine default branch. Use -DefaultBranch parameter to specify"
        }

        $branch = Get-GitBranch -Current
        if ($null -ne $branch) {
            #-------------------------------------------------------------------------------
            #region Merge PullRequest
            Write-Debug "Getting Pull Request for branch $($branch.FriendlyName)"
            $pr = $branch | Get-GitHubPullRequest
            if ($null -ne $pr) {
                Write-Verbose "Merging Pull Request # $($pr.number)"
                try {
                    if ($DontDelete) {
                        $pr | Merge-GitHubPullRequest
                        Write-Verbose ' - (remote branch not deleted)'
                    } else {
                        $pr | Merge-GitHubPullRequest -DeleteBranch
                        Write-Verbose ' - (remote branch deleted)'
                    }
                } catch {
                    throw "Could not merge Pull Request`n$_"
                }

                #endregion Merge PullRequest
                #-------------------------------------------------------------------------------

                #-------------------------------------------------------------------------------
                #region Pull changes
                try {
                    Write-Verbose "Switching to branch '$DefaultBranch'"
                    Set-GitHead $DefaultBranch
                }
                catch {
                    throw "Could not switch to branch $DefaultBranch`n$_"
                }

                try {
                    Write-Verbose 'Pulling changes from remote'
                    Receive-GitBranch
                    Write-Verbose "Successfully merged pr #$($pr.number) and updated project"
                }
                catch {
                    throw "Could not update $DefaultBranch`n$_"
                }
                #endregion Pull changes
                #-------------------------------------------------------------------------------

                try {
                    Remove-GitBranch $branch
                }
                catch {
                    throw "Could not delete local branch $($branch.FriendlyName)"
                }
            } else {
                throw "Couldn't find a Pull Request for $($branch.FriendlyName)"
            }
        } else {
            throw "Couldn't get the current branch"
        }
    }
}
#EndRegion '.\public\Git\Join-PullRequest.ps1' 105
#Region '.\public\Git\Start-GitBranch.ps1' -1


function Start-GitBranch {
    param(
        [string]$Name
    )
    New-GitBranch $Name | Set-GitHead
}
#EndRegion '.\public\Git\Start-GitBranch.ps1' 8
#Region '.\public\Git\Sync-GitRepository.ps1' -1


function Sync-GitRepository {
    <#
    .SYNOPSIS
        Update the working directory of the current branch
    .DESCRIPTION
        This is equivelant to `git pull --rebase
    #>

    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'Medium'
    )]
    param()
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {

        $br = Get-GitBranch -Current
        if ($br.IsTracking) {
            $remote = $br.TrackedBranch
            if ($PSCmdlet.ShouldProcess($br.FriendlyName, "Update")) {
                $br | Send-GitBranch origin
                Start-GitRebase -Upstream $remote.FriendlyName -Branch $br.FriendlyName
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Git\Sync-GitRepository.ps1' 32
#Region '.\public\Git\Undo-GitCommit.ps1' -1


function Undo-GitCommit {
    <#
    .SYNOPSIS
        Reset the branch to before the previous commit
    .DESCRIPTION
        There are three types of reset:
        but keep all the changes in the working directory
        Without This is equivelant to `git reset HEAD~1 --mixed
    #>

    [CmdletBinding()]
    param(
        # Hard reset
        [Parameter(
            ParameterSetName = 'Hard'
        )]
        [switch]$Hard,

        # Soft reset
        [Parameter(
            ParameterSetName = 'Soft'
        )]
        [switch]$Soft



    )
    #! The default mode is mixed, it does not have a parameter
    Reset-GitHead -Revision 'HEAD~1' @PSBoundParameters
}
#EndRegion '.\public\Git\Undo-GitCommit.ps1' 31
#Region '.\public\Git\Update-GitRepository.ps1' -1


function Update-GitRepository {
    <#
    .SYNOPSIS
        Update the working directory of the current branch
    .DESCRIPTION
        This is equivelant to `git pull --rebase
    #>

    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'Medium'
    )]
    param()
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {

        $br = Get-GitBranch -Current
        if ($br.IsTracking) {
            $remote = $br.TrackedBranch
            if ($PSCmdlet.ShouldProcess($br.FriendlyName, "Update")) {
                Start-GitRebase -Upstream $remote.FriendlyName -Branch $br.FriendlyName
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Git\Update-GitRepository.ps1' 31
#Region '.\public\InvokeBuild\Get-BuildProperty.ps1' -1


function Get-BuildProperty {
    <#
    .SYNOPSIS
        Return the variable specified using defined variables, environment variables and parameters
    #>

    [CmdletBinding()]
    param(
        # The name of the property
        [Parameter(
            Mandatory,
            Position = 0
        )]
        [string]$Name,

        # The default value if one is not found
        [Parameter(
            Position = 1
        )]
        $Value
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if ($null -ne $PSCmdlet.GetVariableValue($Name)) {
            return $PSCmdlet.GetVariableValue($Name)
        } elseif ($null -ne [Environment]::GetEnvironmentVariable($Name)) {
            return [Environment]::GetEnvironmentVariable($Name)
        } elseif ($null -ne $Value) {
            return $Value
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\InvokeBuild\Get-BuildProperty.ps1' 38
#Region '.\public\InvokeBuild\Get-BuildTask.ps1' -1


function Get-BuildTask {
    [CmdletBinding()]
    param(
        # The name of the task to get
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ArgumentCompleter({ Invoke-TaskNameCompletion @args })]
        [string]$Name
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if (Test-InInvokeBuild) {
            Write-Debug 'Running under Invoke-Build'
            $allData = $PSCmdlet.GetVariableValue('*')
            if ($null -ne $allData) {
                Write-Debug 'Found the star variable'
                Write-Debug "$($allData.All | Show-ObjectProperties | Out-String)"
                $taskData = $allData.All
            } else {
                throw 'Could not retrieve task from Invoke-Build'
            }
        } else {
            $taskData = Invoke-Build ??
        }

        if ($null -ne $taskData) {
            $descriptions = Invoke-Build ?
            foreach ($key in $taskData.Keys) {
                $task = $taskData[$key]
                if ($null -ne $task) {
                    if ($null -eq $task.Synopsis) {
                        $synopsis = (
                            $descriptions
                            | Where-Object -Property Name -Like $key
                            | Select-Object -ExpandProperty Synopsis
                        ) ?? 'No Synopsis'
                        $task | Add-Member -NotePropertyName Synopsis -NotePropertyValue $synopsis
                    }
                    if ($null -eq $task.IsPhase) {

                        #! if the task was written as 'phase <name>' then the InvocationName
                        #! can be used to find it. Add a property 'IsPhase' for easier sorting
                        $task | Add-Member -NotePropertyName IsPhase -NotePropertyValue (
                            ( $task.InvocationInfo.InvocationName -like 'phase' ) ? $true : $false
                        )
                    }
                    if ($null -eq $task.Path) {
                        $task | Add-Member -NotePropertyName Path -NotePropertyValue (
                            Get-Item $task.InvocationInfo.ScriptName
                        )
                    }
                    if ($null -eq $task.Line) {
                        $task | Add-Member -NotePropertyName Line -NotePropertyValue $task.InvocationInfo.ScriptLineNumber
                        $task.PSObject.TypeNames.Insert(0, 'InvokeBuild.TaskInfo')
                    }

                    if ((-not ($PSBoundParameters.ContainsKey('Name'))) -or
                        ($key -like $Name)) {
                        $task | Write-Output
                    }
                } else {
                    throw "No task with name $key"
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\InvokeBuild\Get-BuildTask.ps1' 77
#Region '.\public\InvokeBuild\Get-MetadataComment.ps1' -1


function Get-MetadataComment {
    <#
    .SYNOPSIS
        Return the metadata stored in a special comment within the task file
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $dataPattern = '(?sm)---(?<data>.*?)---'
    }
    process {
        foreach ($file in $Path) {
            if ($file | Test-Path) {
                try {
                    $fileItem = Get-Item $file
                    $helpInfo = Get-Help -Name $fileItem.FullName -Full
                    if ($null -ne $helpInfo) {
                        if ($null -ne $helpInfo.alertSet) {
                            if (-not ([string]::IsNullorEmpty($helpInfo.alertSet.alert.Text))) {
                                $noteInfo = $helpInfo.alertSet.alert.Text
                                if ($noteInfo -match $dataPattern) {
                                    if ($Matches.data) {
                                        $dataText = $Matches.data
                                        $sb = [scriptblock]::Create($dataText)
                                        & $sb
                                    }
                                }
                            }
                        }
                    }
                }
                catch {
                    throw "Could not get path $file`n$_"
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\InvokeBuild\Get-MetadataComment.ps1' 53
#Region '.\public\InvokeBuild\Get-TaskHelp.ps1' -1


function Get-TaskHelp {
    <#
    .SYNOPSIS
        Retrieve the comment based help for the given task
    .NOTES
        If the given task's file does not have help info, it won't be very helpful...
    #>

    [CmdletBinding()]
    param(
        # The name of the task to get the help documentation for
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ArgumentCompleter({ Invoke-TaskNameCompletion @args})]
        [string[]]$Name,

        # The InvocationInfo of a task
        [Parameter(
            ValueFromPipelineByPropertyName
        )]
        [System.Management.Automation.InvocationInfo]$InvocationInfo
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if ($PSBoundParameters.ContainsKey('InvocationInfo')) {
            Get-Help $InvocationInfo.ScriptName -Full
        } elseif ($PSBoundParameters.ContainsKey('Name')) {
            foreach ($taskName in $Name) {
                $task = Get-BuildTask -Name $taskName
                if ($null -ne $task) {
                    Get-Help $task.InvocationInfo.ScriptName -Full
                }
            }
        } else {
            throw "No task given"
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\InvokeBuild\Get-TaskHelp.ps1' 47
#Region '.\public\InvokeBuild\Test-InInvokeBuild.ps1' -1

function Test-InInvokeBuild {
    [CmdletBinding()]
    param(
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $invokeBuildPattern = 'Invoke-Build.ps1'
    }
    process {
        $callStack = Get-PSCallStack
        $inInvokeBuild = $false
        for ($i = 1; $i -lt $callStack.Length; $i++) {
            $caller = $callStack[$i]
            Write-Debug "This caller is $($caller.Command)"
            if ($caller.Command -match $invokeBuildPattern) {
                $inInvokeBuild = $true
                break
            }
        }
        $inInvokeBuild
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\InvokeBuild\Test-InInvokeBuild.ps1' 26
#Region '.\public\Manifest\ConvertFrom-CommentedProperty.ps1' -1

function ConvertFrom-CommentedProperty {
    <#
    .SYNOPSIS
        Uncomment the given Manifest Item
    .DESCRIPTION
        In a typical manifest, unused properties are listed, but commented out with a '#'
        like `# ReleaseNotes = ''`
        Update-Metadata, Import-Psd and similar functions need to have these fields available.
        `ConvertFrom-CommentedProperty` will remove the '#' from the line so that those functions can use the given
        property
    .EXAMPLE
        $manifest | ConvertFrom-CommentedProperty 'ReleaseNotes'
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,

        # The item to uncomment
        [Parameter(
            Position = 0
        )]
        [Alias('PropertyName')]
        [string]$Property
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if ($PSBoundParameters.ContainsKey('Path')) {
            if (Test-Path $Path) {
                $commentToken = $Path | Find-ParseToken -Type Comment -Pattern "^\s*#\s*$Property\s+=.*$" | Select-Object -First 1
                if ($null -ne $commentToken) {
                    $replacementIndent = (' ' * ($commentToken.StartColumn - 1))
                    $newContent = $commentToken.Content -replace '#\s*', $replacementIndent
                    $fileContent = @(Get-Content $Path)
                    $fileContent[$commentToken.StartLine - 1] = $newContent
                    $fileContent | Set-Content $Path

                } else {
                    # if we did not find the comment, signal that it was not successful
                    Write-Warning "$Property comment not found"
                }
            } else {
                throw "$Path is not a valid path"
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }

}
#EndRegion '.\public\Manifest\ConvertFrom-CommentedProperty.ps1' 59
#Region '.\public\Manifest\Get-ModuleExtension.ps1' -1


function Get-ModuleExtension {
    <#
    .SYNOPSIS
        Find modules with the `Extension` key in the manifest
    .NOTES
        This function was pulled from the Plaster Source at commit #d048667
    #>

    [CmdletBinding()]
    param(
        [string]
        $ModuleName,

        [Version]
        $ModuleVersion,

        [Switch]
        $ListAvailable
    )

    #Only get the latest version of each module
    $modules = Get-Module -ListAvailable
    if (!$ListAvailable) {
        $modules = $modules |
        Group-Object Name |
        Foreach-Object {
            $_.group |
            Sort-Object Version |
            Select-Object -Last 1
        }
    }

    Write-Verbose "`nFound $($modules.Length) installed modules to scan for extensions."

    function ParseVersion($versionString) {
        $parsedVersion = $null

        if ($versionString) {
            # We're targeting Semantic Versioning 2.0 so make sure the version has
            # at least 3 components (X.X.X). This logic ensures that the "patch"
            # (third) component has been specified.
            $versionParts = $versionString.Split('.');
            if ($versionParts.Length -lt 3) {
                $versionString = "$versionString.0"
            }

            if ($PSVersionTable.PSEdition -eq "Core") {
                $parsedVersion = New-Object -TypeName "System.Management.Automation.SemanticVersion" -ArgumentList $versionString
            } else {
                $parsedVersion = New-Object -TypeName "System.Version" -ArgumentList $versionString
            }
        }

        return $parsedVersion
    }

    foreach ($module in $modules) {
        if ($module.PrivateData -and
            $module.PrivateData.PSData -and
            $module.PrivateData.PSData.Extensions) {

            Write-Verbose "Found module with extensions: $($module.Name)"

            foreach ($extension in $module.PrivateData.PSData.Extensions) {

                Write-Verbose "Comparing against module extension: $($extension.Module)"

                $minimumVersion = ParseVersion $extension.MinimumVersion
                $maximumVersion = ParseVersion $extension.MaximumVersion

                if (($extension.Module -eq $ModuleName) -and
                    (!$minimumVersion -or $ModuleVersion -ge $minimumVersion) -and
                    (!$maximumVersion -or $ModuleVersion -le $maximumVersion)) {
                    # Return a new object with the extension information
                    [PSCustomObject]@{
                        Module         = $module
                        MinimumVersion = $minimumVersion
                        MaximumVersion = $maximumVersion
                        Details        = $extension.Details
                    }
                }
            }
        }
    }
}
#EndRegion '.\public\Manifest\Get-ModuleExtension.ps1' 86
#Region '.\public\Manifest\Test-CommentedProperty.ps1' -1

function Test-CommentedProperty {
    <#
    .SYNOPSIS
        Test if the given property is commented in the given manifest
    .EXAMPLE
        $manifest | Test-CommentedProperty 'ReleaseNotes'
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,

        # The item to uncomment
        [Parameter(
            Position = 0
        )]
        [Alias('PropertyName')]
        [string]$Property

    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if ($PSBoundParameters.ContainsKey('Path')) {
            if (Test-Path $Path) {
                $commentToken = $Path | Find-ParseToken -Type Comment -Pattern "^\s*#\s*$Property\s+=.*$" | Select-Object -First 1
                $null -ne $commentToken | Write-Output
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Manifest\Test-CommentedProperty.ps1' 41
#Region '.\public\Manifest\Update-ManifestField.ps1' -1


function Update-ManifestField {
    <#
    .SYNOPSIS
        Set the Value of the given PropertyName, even if it is commented out in the manifest given in Path.
    .EXAMPLE
        Get-ChildItem foo.psd1 -Recurse | Update-ManifestField 'ModuleVersion' '0.9.0'
 
        Sets ModuleVersion to '0.9.0' in foo.psd1
    #>

    [CmdletBinding(
        SupportsShouldProcess
    )]
    param(
        # Specifies a path to a manifest file
        [Parameter(
        Position = 2,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path,

        # Field in the Manifest to update
        [Parameter(
            Mandatory,
            Position = 0
        )]
        [string]$PropertyName,

        # List of strings to add to the field
        [Parameter(
            Mandatory,
            Position = 1
        )]
        [string[]]$Value
    )

    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        try {
            Write-Debug "Loading manifest $Path"
            $manifestItem = Get-Item $Path
            $manifestObject = Import-Psd $manifestItem.FullName
        }
        catch {
            throw "Cannot load $($Path)`n$_"
        }

        $options = $PSBoundParameters
        $null = $options.Remove('Name')

        #! if we don't do this, then every field gets written as an array. That doesn't work well for ! many of the
        #! fields like ModuleVersion, etc.
        if ($options.Value.Count -eq 1) {
            $options.Value = [string]$options.Value[0]
        }

        if ($manifestObject.ContainsKey($PropertyName)) {
            #-------------------------------------------------------------------------------
            #region Field exists
            Write-Debug " - Manifest has a $PropertyName field. Updating"
            try {
                if ($PSCmdlet.ShouldProcess($Path, "Update $PropertyName to $Value")) {
                    Update-Metadata @options
                }
            }
            catch {
                throw "Cannot update $PropertyName in $Path`n$_"
            }
            #endregion Field exists
            #-------------------------------------------------------------------------------
        } else {
            #-------------------------------------------------------------------------------
            #region Commented
            Write-Debug "Manifest does not have $PropertyName field. Looking for it in comments"
            $fieldToken = $manifestItem | Find-ParseToken $PropertyName Comment
            if ($null -ne $fieldToken) {
                Write-Debug " - Found comment"
                try {
                    if ($PSCmdlet.ShouldProcess($Path, "Update $PropertyName to $Value")) {
                        $manifestItem | ConvertFrom-CommentedProperty -Property $PropertyName
                        Update-Metadata @options
                    }
                }
                catch {
                    throw "Cannot update $PropertyName in $Path`n$_"
                }

                #endregion Commented
                #-------------------------------------------------------------------------------
            } else {
                #-------------------------------------------------------------------------------
                #region Field missing
                #! Update-ModuleManifest is not really the best option for editing the psd1, because
                #! it does a poor job of formatting "proper" arrays, and it doesn't deal with "non-standard"
                #! fields very well. However, if the field is missing from the file, it is better to use
                #! Update-ModuleManifest than to clobber the comments and formatting ...

                Write-Debug "Could not find $PropertyName in Manifest. Calling Update-ModuleManifest"
                $null = $options.Clear()
                $options = @{
                    Path = $Path
                    $PropertyName = $Value
                }
                try {
                    if ($PSCmdlet.ShouldProcess($Path, "Update $PropertyName to $Value")) {
                        Update-ModuleManifest @options
                    }
                } catch {
                    throw "Cannot update $PropertyName in $Path`n$_"
                }
            #endregion Field missing
            #-------------------------------------------------------------------------------

            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Manifest\Update-ManifestField.ps1' 125
#Region '.\public\Notification\Invoke-BuildNotification.ps1' -1


function Invoke-BuildNotification {
    <#
    .SYNOPSIS
        Display a Toast notification for a completed build
    .EXAMPLE
        Invoke-BuildNotification -LogFile .\out\logs\build-20230525T2051223032Z.log -Status Passed
    #>

    [CmdletBinding()]
    param(
        # The text to add to the notification
        [Parameter(
        )]
        [string]$Text,

        # Build status
        [Parameter(
        )]
        [ValidateSet('Passed', 'Failed', 'Unknown')]
        [string]$Status,

        # Path to the log file
        [Parameter(
        )]
        [string]$LogFile
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $appImage = (Join-Path (Get-ModulePath) "spool-of-thread_1f9f5.png")
    }
    process {

        if (-not ($PSBoundParameters.ContainsKey('Text'))) {
            $Text = "Build Complete"
        }

        if ($PSBoundParameters.ContainsKey('Status')) {
            if ($Status -like 'Passed') {
                $Text = "`u{2705} $Text"
            } elseif ($Status -like 'Failed') {
                $Text = "`u{1f6a8} $Text"
            }
        } else {
            $Text = "`u{2754} $Text"
        }

        $toastOptions = @{
            Text = $Text
            AppLogo = $appImage
        }

        if ($PSBoundParameters.ContainsKey('LogFile')) {
            if (Test-Path $LogFile) {
                $logItem = Get-Item $LogFile
                $btnOptions = @{
                    Content = "Build Log"
                    ActivationType = 'Protocol'
                    Arguments = $logItem.FullName
                }

                $logButton = New-BTButton @btnOptions
                $toastOptions.Text = @($Text, "View the log file")
                $toastOptions.Button = $logButton
            }
        }


        New-BurntToastNotification @toastOptions
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Notification\Invoke-BuildNotification.ps1' 74
#Region '.\public\Path\Confirm-Path.ps1' -1


function Confirm-Path {
    <#
    .SYNOPSIS
        Tests if the directory exists and if it does not, creates it.
    #>

    [OutputType([bool])]
    [CmdletBinding()]
    param(
        # The path to confirm
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,

        # The type of item to confirm
        [Parameter(
        )]
        [ValidateSet('Directory', 'File', 'SymbolicLink', 'Junction', 'HardLink')]
        [string]$ItemType = 'Directory'
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if (Test-Path $Path) {
            Write-Debug "Path exists"
            $true
        } else {
            try {
                Write-Debug "Checking if the directory exists"
                $directory = $Path | Split-Path -Parent
                if (Test-Path $directory) {
                    Write-Debug " - The directory $directory exists"
                } else {
                    $null = New-Item $directory -Force -ItemType Directory
                }
                Write-Debug "Creating $ItemType $Path"
                $null = New-Item -Path $Path -ItemType $ItemType -Force
                Write-Debug "Now confirming $Path exists"
                if (Test-Path $Path) {
                    $true
                } else {
                    $false
                }
            }
            catch {
                throw "There was an error confirming $Path`n$_"
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Confirm-Path.ps1' 59
#Region '.\public\Path\Find-BuildConfigurationDirectory.ps1' -1

function Find-BuildConfigurationDirectory {
    <#
    .SYNOPSIS
        Find the directory that contains the build configuration for the given profile
    #>

    [Alias('Resolve-BuildConfigurationDirectory')]
    [CmdletBinding()]
    param(
        # The BuildProfile to use
        [Parameter(
        )]
        [string]$BuildProfile
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $Options = @{
            Option = 'Constant'
            Name = 'DEFAULT_BUILD_PROFILE'
            Value = 'default'
            Description = 'The default build profile'
        }

        New-Variable @Options
    }
    process {
        if (-not ($PSBoundParameters.ContainsKey('BuildProfile'))) {
            $possibleBuildProfile = $PSCmdlet.GetVariableValue('BuildProfile')
            if ($null -ne $possibleBuildProfile) {
                $BuildProfile = $possibleBuildProfile
            }
        }

        if ([string]::IsNullorEmpty($BuildProfile)) {
            $BuildProfile = $DEFAULT_BUILD_PROFILE
        }

        $possibleProfileRoot = Find-BuildProfileRootDirectory

        if ($null -ne $possibleProfileRoot) {
            $possibleBuildProfilePath = (Join-Path -Path $possibleProfileRoot -ChildPath $BuildProfile)
            if (Test-Path $possibleBuildProfilePath) {
                Get-Item $possibleBuildProfilePath | Write-Output
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Find-BuildConfigurationDirectory.ps1' 50
#Region '.\public\Path\Find-BuildConfigurationRootDirectory.ps1' -1


function Find-BuildConfigurationRootDirectory {
    <#
    .SYNOPSIS
        Find the build configuration root directory for this project
    .EXAMPLE
        Find-BuildConfigurationRootDirectory -Path $BuildRoot
    .EXAMPLE
        $BuildRoot | Find-BuildConfigurationRootDirectory
    .NOTES
        `Find-BuildConfigurationRootDirectory` looks in the current directory of the caller if no Path is given
    #>

    [Alias('Resolve-BuildConfigurationRootDirectory')]
    [OutputType([System.IO.DirectoryInfo])]
    [CmdletBinding()]
    param(
        # Specifies a path to a location to look for the build configuration root
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"

        #TODO: A good example of what would be in the module's (PoshCode) Configuration if we used it
        $possibleRoots = @( '.build', '.stitch' )
        $configurationRootDirectory = $null
    }
    process {
        if (-not($PSBoundParameters.ContainsKey('Path'))) {
            $Path = Get-Location
        }
        #! if this function is called within a build script, then BuildConfigRoot should be set already
        $possibleBuildConfigRoot = $PSCmdlet.GetVariableValue('BuildConfigRoot')

        if (-not ([string]::IsNullOrEmpty($possibleBuildConfigRoot))) {
            Write-Debug "Found `$BuildConfigRoot => $possibleBuildConfigRoot"
            $configurationRootDirectory = $possibleBuildConfigRoot
        } else {
            :path foreach ($possibleRootPath in $Path) {
                Write-Debug "Looking for a build configuration directory in $possibleRootPath"
                :root foreach ($possibleRoot in $possibleRoots) {
                    Write-Debug " - Looking for $possibleRoot directory"
                    $possiblePath = (Join-Path $possibleRootPath $possibleRoot)
                    if (Test-Path $possiblePath) {
                        $possiblePathItem = (Get-Item $possiblePath)
                        if ($possiblePathItem.PSIsContainer) {
                            $configurationRootDirectory = $possiblePathItem
                        } else {
                            $configurationRootDirectory = (Get-Item ($possiblePathItem | Split-Path -Parent))
                        }
                        Write-Debug " - Found build configuration root directory '$configurationRootDirectory'"
                        break path
                    }
                }
            }
        }
    }
    end {
        $configurationRootDirectory | Write-Output
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Find-BuildConfigurationRootDirectory.ps1' 68
#Region '.\public\Path\Find-BuildProfileRootDirectory.ps1' -1


function Find-BuildProfileRootDirectory {
    <#
    .SYNOPSIS
        Find the directory that has the profiles defined
    #>

    [Alias('Resolve-BuildProfileRootDirectory')]
    [CmdletBinding()]
    param(
        # Specifies a path to a location that contains Build Profiles (This should be BuildConfigPath)
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [AllowEmptyString()]
        [AllowNull()]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"

        $possibleProfileDirectories = @( 'profiles', 'profile', 'runbooks' )
        $profileDirectory = $null
    }
    process {
        if ([string]::IsNullorEmpty($Path)) {
            $possibleBuildConfigRoot = Find-BuildConfigurationRootDirectory

            if ([string]::IsNullorEmpty($possibleBuildConfigRoot)) {
                $Path += Get-Location
            } else {
                $Path += $possibleBuildConfigRoot
            }
        }

        #! First, loop through each configuration root directory
        :root foreach ($possibleRootPath in $Path) {
            #! Then, loop through each of the default names for a profile directory
            :profile foreach ($possibleProfileDirectory in $possibleProfileDirectories) {
                $possibleProfilePath = (Join-Path $possibleRootPath $possibleProfileDirectory)

                if (Test-Path $possibleProfilePath) {
                    $possiblePathItem = (Get-Item $possibleProfilePath)
                    if ($possiblePathItem.PSIsContainer) {
                        $profileDirectory = $possibleProfilePath
                    } else {
                        $profileDirectory = $possibleProfilePath | Split-Path -Parent
                    }
                    Write-Debug "Found profile root directory '$profileDirectory'"
                    break root
                }
            }
        }
    }
    end {
        $profileDirectory
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Find-BuildProfileRootDirectory.ps1' 62
#Region '.\public\Path\Find-BuildRunBook.ps1' -1


function Find-BuildRunBook {
    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $possibleRunbookFilters = @(
            "*runbook.ps1"
        )
    }
    process {
        :path foreach ($location in $Path) {
            :filter foreach ($possibleRunbookFilter in $possibleRunbookFilters) {
                $options = @{
                    Path = $location
                    Recurse = $true
                    Filter = $possibleRunbookFilter
                    File = $true
                }
                Get-Childitem @options
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Find-BuildRunBook.ps1' 37
#Region '.\public\Path\Find-InvokeBuildScript.ps1' -1

function Find-InvokeBuildScript {
    <#
    .SYNOPSIS
        Find all "build script" files. These are files that contain tasks to be executed by Invoke-Build
    .LINK
        Find-InvokeBuildTaskFile
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations to look for build scripts.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $buildScriptPattern = "*.build.ps1"
    }
    process {
        foreach ($location in $Path) {
            if (Test-Path $location) {
                $options = @{
                    Path = $location
                    Recurse = $true
                    Filter = $buildScriptPattern
                }
                Get-ChildItem @options
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Find-InvokeBuildScript.ps1' 39
#Region '.\public\Path\Find-InvokeBuildTaskFile.ps1' -1


function Find-InvokeBuildTaskFile {
    <#
    .SYNOPSIS
        Find all "task type" files. These are files that contain "extensions" to the task types. They define a
        function that creates tasks.
    .LINK
        Find-InvokeBuildScript
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations to look for task files.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $taskFilePattern = "*.task.ps1"
    }
    process {
        foreach ($location in $Path) {
            if (Test-Path $location) {
                $options = @{
                    Path = $location
                    Recurse = $true
                    Filter = $taskFilePattern
                }
                Get-ChildItem @options
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Find-InvokeBuildTaskFile.ps1' 41
#Region '.\public\Path\Find-LocalUserStitchDirectory.ps1' -1


function Find-LocalUserStitchDirectory {
    <#
    .SYNOPSIS
        Find the directory in the users home directory that contains stitch configuration items
    #>

    [Alias('Resolve-LocalUserStitchDirectory')]
    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations to look for the users local stitch directory
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $possibleRootDirectories = @(
            $env:USERPROFILE,
            $env:HOME,
            $env:LOCALAPPDATA,
            $env:APPDATA
        )

        $possibleStitchDirectories = @(
            '.stitch'
        )

        $userStitchDirectory = $null
    }
    process {
        if (-not($PSBoundParameters.ContainsKey('Path'))) {
            $Path = $possibleRootDirectories
        }
        #! We only need to search the 'possibleRootDirectories' if a Path was not given
        :root foreach ($possibleRootDirectory in $Path) {
            :stitch foreach ($possibleStitchDirectory in $possibleStitchDirectories) {
                if  ((-not ([string]::IsNullorEmpty($possibleRootDirectory))) -and
                     (-not ([string]::IsNullorEmpty($possibleStitchDirectory)))) {

                    $possiblePath = (Join-Path $possibleRootDirectory $possibleStitchDirectory)
                        if (Test-Path $possiblePath) {
                            $possiblePathItem = (Get-Item $possiblePath)
                            if ($possiblePathItem.PSIsContainer) {
                                $userStitchDirectory = $possiblePath
                            } else {
                                $userStitchDirectory = $possiblePath | Split-Path -Parent
                            }
                        Write-Debug "Local user stitch directory found at $userStitchDirectory"
                        break root
                    }
                }
            }
        }
    }
    end {
        $userStitchDirectory
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Find-LocalUserStitchDirectory.ps1' 64
#Region '.\public\Path\Find-ModuleManifest.ps1' -1


function Find-ModuleManifest {
    <#
    .SYNOPSIS
        Find all module manifests in the given directory.
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $possibleManifests = Get-ChildItem @PSBoundParameters -Recurse -Filter "*.psd1"

        foreach ($possibleManifest in $possibleManifests) {
            try {
                $module = $possibleManifest | Import-Psd -Unsafe
            } catch {
                Write-Debug "$possibleManifest could not be imported"
                continue
            }
            if ($null -ne $module) {
                Write-Debug "Checking if $($possibleManifest.Name) is a manifest"
                if (
                    ($module.Keys -contains 'ModuleVersion') -and
                    ($module.Keys -contains 'GUID') -and
                    ($module.Keys -contains 'PrivateData')
                ) {
                    $possibleManifest | Write-Output
                } else {
                    Write-Debug "- Not a module manifest file"
                }
            } else {
                continue
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Find-ModuleManifest.ps1' 51
#Region '.\public\Path\Find-StitchConfigurationFile.ps1' -1


function Find-StitchConfigurationFile {
    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $possibleConfigFileFilters = @(
            'stitch.config.ps1',
            '.config.ps1'
        )
    }
    process {
        :path foreach ($location in $Path) {
            Write-Debug "Looking in $location"
            :filter foreach ($possibleConfigFileFilter in $possibleConfigFileFilters) {
                $options = @{
                    Path = $location
                    Recurse = $true
                    Filter = $possibleConfigFileFilter
                    File = $true
                }
                $result = Get-Childitem @options | Select-Object -First 1
                if ($null -ne $result) {
                    $result | Write-Output
                    continue path
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Find-StitchConfigurationFile.ps1' 43
#Region '.\public\Path\Find-TestDirectory.ps1' -1


function Find-TestDirectory {
    <#
    .SYNOPSIS
        Find the directory where tests are stored
    #>

    [CmdletBinding()]
    param(
        # Test file pattern
        [Parameter(
        )]
        [string]$TestsPattern = '*.Tests.ps1'
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $root = Resolve-ProjectRoot
        Write-Debug "Looking for test directory in $root"

        $testFiles = Find-TestFile $root -TestsPattern $TestsPattern
        $foundDirectories = [System.Collections.ArrayList]::new()

        if ($testFiles.Count -gt 0) {
            :testfile foreach ($testFile in $testFiles) {
                $relativePath = [System.IO.Path]::GetRelativePath($root, $testFile.FullName)
                $parts = $relativePath -split [regex]::Escape([System.IO.Path]::DirectorySeparatorChar)
                Write-Debug "$($testFile.FullName) is $($parts.Count) levels below root"
                :parts switch ($parts.Count) {
                    0 {
                        throw "The path to $($testFile.FullName) is invalid"
                    }
                    default {
                        $possibleTestPath = (Join-Path $root $parts[0])
                        if ($possibleTestPath -notin $foundDirectories) {
                            [void]$foundDirectories.Add($possibleTestPath)
                            continue testfile
                        }
                    }
                }
            }
        }
    }
    end {
        $foundDirectories | Foreach-Object { Get-Item $_ | Write-Output }
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Find-TestDirectory.ps1' 49
#Region '.\public\Path\Find-TestFile.ps1' -1


function Find-TestFile {
    <#
    .SYNOPSIS
        Find files that contain tests
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,

        # Test file pattern
        [Parameter(
        )]
        [string]$TestsPattern = '*.Tests.ps1'
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $possibleTestFiles = Get-ChildItem -Path $Path -Recurse -File -Filter $TestsPattern

        foreach ($possibleTestFile in $possibleTestFiles) {
            Write-Debug "Checking $possibleTestFile for Pester tests"
            if ($possibleTestFile | Select-String '\s*Describe') {
                Write-Debug "- Has the 'Describe' keyword"
                $possibleTestFile | Write-Output
            } else {
                continue
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Find-TestFile.ps1' 43
#Region '.\public\Path\Get-ModulePath.ps1' -1


function Get-ModulePath {
    [CmdletBinding()]
    param(
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $callStack = Get-PSCallStack
        $caller = $callStack[1]

        $caller.InvocationInfo.MyCommand.Module.ModuleBase
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Get-ModulePath.ps1' 19
#Region '.\public\Path\Merge-FileCollection.ps1' -1

function Merge-FileCollection {
    <#
    .SYNOPSIS
        Merge an array of files into an existing collection, overwritting any that have the same basename
    .NOTES
        The collection is passed in by reference. This is so that the collection is updated without having to
        reapply the result.
    .EXAMPLE
        $updates | Merge-FileCollection [ref]$allFiles
    .EXAMPLE
        Get-ChildItem -Path . -Filter *.ps1 | Merge-FileCollection [ref]$allScripts
    #>

    [CmdletBinding()]
    param(
        # The collection of files to merge the updates into
        [Parameter(
            Mandatory,
            Position = 0
        )]
        [AllowEmptyCollection()]
        [ref]$Collection,

        # The additional files to update the collection with
        [Parameter(
            Mandatory,
            Position = 1,
            ValueFromPipeline
        )]
        [Array]$UpdateFiles

    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        foreach ($currentUpdateFile in $UpdateFiles) {
            <#
             if this file name exists in the Collection array, we remove it from the collection
             and add this file, otherwise just add the file
            #>

            $baseNames = $Collection.Value | Select-Object -ExpandProperty BaseName

            if ($baseNames -contains $currentUpdateFile.BaseName ) {
                $previousTaskFile = $Collection.Value | Where-Object {
                    $_.BaseName -like $currentUpdateFile.BaseName
                }
                if ($null -ne $previousTaskFile) {
                    Write-Verbose "Overriding $($currentUpdateFile.BaseName)"
                    $index = $Collection.Value.IndexOf( $previousTaskFile )
                    $Collection.Value[$index] = $currentUpdateFile
                }
            } else {
                [void]$Collection.Value.Add($currentUpdateFile)
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Merge-FileCollection.ps1' 61
#Region '.\public\Path\Resolve-ProjectRoot.ps1' -1


function Resolve-ProjectRoot {
    <#
    .SYNOPSIS
        Find the root of the current project
    .DESCRIPTION
        Resolve-ProjectRoot will recurse directories toward the root folder looking for a directory that passes
        `Test-ProjectRoot`, unless `$BuildRoot` is already set
 
    .LINK
        Test-ProjectRoot
    #>

    [CmdletBinding()]
    param(
        # Optionally set the starting path to search from
        [Parameter(
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path = (Get-Location).ToString(),

        # Optionally limit the number of levels to seach
        [Parameter()]
        [int]$Depth = 8
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $level = 1
        $originalLocation = $Path | Get-Item
        $currentLocation = $originalLocation
        $driveRoot = $currentLocation.Root


        Write-Debug "Current location: $($currentLocation.FullName)"
        Write-Debug "Current root: $($driveRoot.FullName)"
    }
    process {
        $rootReached = $false
        if ($null -ne $BuildRoot) {
            Write-Debug 'BuildRoot is set, using that'
            $BuildRoot | Write-Output
            break
        }

        :location do {
            if ($null -ne $currentLocation) {
                Write-Debug "Level $level : Testing directory $($currentLocation.FullName)"
                if ($currentLocation.FullName | Test-ProjectRoot) {
                    $rootReached = $true
                    Write-Debug "- Project Root found : $($currentLocation.FullName)"
                    $currentLocation.FullName | Write-Output
                    break location
                } elseif ($level -eq $Depth) {
                    $rootReached = $true
                    throw "- Could not find project root in $Depth levels"
                    break location
                } elseif ($currentLocation -like $driveRoot) {
                    $rootReached = $true
                    throw "- $driveRoot reached looking for project root"
                    break location
                } else {
                    Write-Debug "- $($currentLocation.Name) is not the project root"
                }
            } else {
                Write-Debug "- Reached the root of the drive"
                $rootReached = $true
            }
            Write-Debug 'Setting current location to Parent'
            $currentLocation = $currentLocation.Parent
            $level++
        } until ($rootReached)
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Resolve-ProjectRoot.ps1' 78
#Region '.\public\Path\Resolve-SourceDirectory.ps1' -1


function Resolve-SourceDirectory {
    <#
    .SYNOPSIS
        Resolve the directory that contains the project's source files
    #>

    [CmdletBinding()]
    param(
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $ignoredDirectories = @('.build', '.stitch')
        $maximumNestedLevel = 4
    }
    process {
        $root = Resolve-ProjectRoot
        $sourceDirectory = $null
        Write-Debug "Looking for source directory in $root"

        $manifests = Find-ModuleManifest $root

        if ($manifests.Count -gt 0) {
            :manifest foreach ($manifest in $manifests) {
                $relativePath = [System.IO.Path]::GetRelativePath($root, $manifest.FullName)
                $parts = $relativePath -split [regex]::Escape([System.IO.Path]::DirectorySeparatorChar)
                Write-Debug "$($manifest.FullName) is $($parts.Count) levels below root"
                :parts switch ($parts.Count) {
                    0 {
                        throw "The path to $($manifest.FullName) is invalid"
                    }
                    default {
                        if ($parts[0] -notin $ignoredDirectories) {
                            if ($parts.Count -lt $maximumNestedLevel ) {
                                $possibleSourceDirectory = Get-Item (Join-Path $root $parts[0])
                                if ($null -eq $sourceDirectory) {
                                    $sourceDirectory = $possibleSourceDirectory
                                } else {
                                    if ($possibleSourceDirectory.FullName -eq $sourceDirectory.FullName) {
                                        Write-Debug "$($possibleSourceDirectory.Name) already set"
                                    }
                                }
                            } else {
                                Write-Debug "$($manifest.Name) is nested below maximum levels: $($parts.Count)"
                            }
                        } else {
                            Write-Debug "$($parts[0]) is ignored"
                        }
                        continue manifest
                    }
                }
            }
        } else {
            throw "No manifests found in project '$root'"
        }
        $sourceDirectory
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Resolve-SourceDirectory.ps1' 61
#Region '.\public\Path\Test-PathIsIn.ps1' -1


function Test-PathIsIn {
    <#
    .SYNOPSIS
        Confirm if the given path is within the other
    .DESCRIPTION
        `Test-PathIsIn` checks if the given path (-Path) is a subdirectory of the other (-Parent)
    .EXAMPLE
        Test-PathIsIn "C:\Windows" -Path "C:\Windows\System32\"
 
    .EXAMPLE
        "C:\Windows\System32" | Test-PathIsIn "C:\Windows"
    #>

    [OutputType([System.Boolean])]
    [CmdletBinding()]
    param(
        # The path to test (the subdirectory)
        [Parameter(
            Mandatory,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path,

        # The path to test (the subdirectory)
        [Parameter(
            Position = 0
        )]
        [string]$Parent,

        # Compare paths using case sensitivity
        [Parameter(
        )]
        [switch]$CaseSensitive
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        try {
            Write-Debug "Resolving given Path $Path"
            $childItem = Get-Item (Resolve-Path $Path)

            Write-Debug "Resolving given Parent $Parent"
            $parentItem = Get-Item (Resolve-Path $Parent)

            if ($CaseSensitive) {
                Write-Debug "Matching case-sensitive"
                $parentPath = $parentItem.FullName
                $childPath = $childItem.FullName
            } else {
                Write-Debug "Matching"
                $parentPath = $parentItem.FullName.ToLowerInvariant()
                $childPath = $childItem.FullName.ToLowerInvariant()
            }

            Write-Verbose "Testing if '$childPath' is in '$parentPath'"

            # early test using string comparison
            #! note: will return a false positive for directories with partial match like
            #! c:\windows\system , c:\windows\system32
            Write-Debug "Does '$childPath' start with '$parentPath'"
            if (-not($childPath.StartsWith($parentPath))) {
                Write-Debug " - Yes. Return False"
                return $false
            } else {
                $childRoot = $childItem.Root
                $parentRoot = $parentItem.Root
                Write-Debug " - Yes. Checking path roots '$childRoot' and '$parentRoot'"

                # they /should/ be equal if we made it here
                if ($parentRoot -notlike $childRoot) {
                    return $false
                }

                $childPathParts = $childPath -split [regex]::Escape([IO.Path]::DirectorySeparatorChar)
                $depth = $childPathParts.Count
                $currentPath = $childItem
                $parentFound = $false
                :depth foreach ($level in 1..($depth - 1)) {
                    $currentPath = $currentPath.Parent
                    Write-Debug "Testing if $currentPath equals $($parentItem.FullName)"
                    if ($currentPath -like $parentItem.FullName) {
                        Write-Debug " - Parent found"
                        $parentFound = $true
                        break depth
                    }
                }
                if ($parentFound) {
                    Write-Debug " - Parent found. Return True"
                    return $true
                }
                Write-Debug " - Parent not found. Return False"
                return $false

            }
        } catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }


}
#EndRegion '.\public\Path\Test-PathIsIn.ps1' 108
#Region '.\public\Path\Test-ProjectRoot.ps1' -1


function Test-ProjectRoot {
    <#
    .SYNOPSIS
        Test if the given directory is the root directory of a project
    .DESCRIPTION
        `Test-ProjectRoot` looks for the build configuration file and directory
        `.build.ps1` and either `.stitch\` or `.build`
    .EXAMPLE
        Test-ProjectRoot
 
        Without a -Path, tests the current directory for default project directories
    .EXAMPLE
        $projectPath | Test-ProjectRoot
    .NOTES
        Defaults are:
        - Source : .\source
        - Staging : .\stage
        - Tests : .\tests
        - Artifact : .\out
        - Docs : .\docs
 
    #>

    [CmdletBinding()]
    param(
        # Optionally give a path to start in
        [Parameter(
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ValidateScript(
            {
                if (-not($_ | Test-Path)) {
                    throw "$_ does not exist"
                }
                return $true
            }
        )]
        [Alias('PSPath')]
        [string]$Path = (Get-Location).ToString()
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $possibleRoots = @('.build', '.stitch')
    }
    process {
        $possibleBuildConfigRoot = $Path | Find-BuildConfigurationRootDirectory
        if ($null -ne $possibleBuildConfigRoot) {
            Write-Debug "Found build config root directory '$possibleBuildConfigRoot'"
            if ($possibleBuildConfigRoot.Name -in $possibleRoots) {
                Write-Debug "$($possibleBuildConfigRoot.Name) is a valid root"
                if (Get-ChildItem -Path $Path -Filter '.build.ps1') {
                    Write-Debug "Found build script"
                    $true | Write-Output
                } else {
                    Write-Debug "Did not find build script"
                    $false | Write-Output
                }
            } else {
                $false | Write-Output
            }
        } else {
            $false | Write-Output
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Path\Test-ProjectRoot.ps1' 70
#Region '.\public\Project\Get-GitDescribeInfo.ps1' -1


function Get-GitDescribeInfo {
    <#
    .SYNOPSIS
        Return the version information found in `git describe`
    .DESCRIPTION
        `git describe` will print out the version information in the form of:
        <tag>-<commits since>-<short sha>
    #>

    [CmdletBinding()]
    param(
        # Only use annotated tags (--tags is used by default)
        [Parameter(
        )]
        [switch]$AnnotatedOnly
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $describePattern = (@(
                '^v?(?<majr>\d+)\.',
                '(?<minr>\d+)\.',
                '(?<ptch>\d+)',
                '(?<rmdr>.*)$'
            ) -join '')

        $versionInfo = @{
            PSTypeName                = 'Stitch.VersionInfo'
            Full                      = ''
            Major                     = 0
            Minor                     = 0
            Patch                     = 0
            CommitsSinceVersionSource = 0
            ShortSha                  = ''
            PreReleaseTag             = ''
            MajorMinorPatch           = ''
            SemVer                    = ''
        }
    }
    process {
        $gitCommand = (Get-Command 'git.exe' -ErrorAction SilentlyContinue)

        if ($null -ne $gitCommand) {
            $arguments = [System.Collections.ArrayList]@('describe')

            if (-not($AnnotatedOnly)) { [void]$arguments.Add('--tags') }

            [void]$arguments.Add('--long')
            Write-Debug "calling git with arguments $($arguments -join ' ')"
            $result = & $gitCommand $arguments

            if ($result.Length -gt 0) {
                $versionInfo.Full = $result
                if ($result -match $describePattern) {
                    $versionInfo.Major = ($Matches.majr ?? 0)
                    $versionInfo.Minor = ($Matches.minr ?? 0)
                    $versionInfo.Patch = ($Matches.ptch ?? 0)

                    $versionInfo.MajorMinorPatch = ('{0}.{1}.{2}' -f $Matches.majr, $Matches.minr, $Matches.ptch)

                    $parts = [System.Collections.ArrayList]@($Matches.rmdr.Split('-'))

                    switch ($parts.Count) {
                        0 { Write-Debug "Did not find any parts in $($Matches.rmdr)" }
                        1 {
                            $versionInfo.ShortSha = $parts[0]
                            continue
                        }
                        2 {
                            $versionInfo.CommitsSinceVersionSource  = $parts[0]
                            $versionInfo.ShortSha = $parts[1]
                            continue
                        }
                        default {
                            $versionInfo.ShortSha = $parts[-1]
                            [void]$parts.Remove($parts[-1])
                            $versionInfo.CommitsSinceVersionSource = $parts[-1]
                            [void]$parts.Remove($parts[-1])
                            $versionInfo.PreReleaseTag   = ($parts -join '-')
                        }
                    }

                    $versionInfo.SemVer = ( @(
                        $versionInfo.MajorMinorPatch,
                        $versionInfo.PreReleaseTag) -join '')
                    [PSCustomObject]$versionInfo | Write-Output
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Project\Get-GitDescribeInfo.ps1' 94
#Region '.\public\Project\Get-GitVersionInfo.ps1' -1


function Get-GitVersionInfo {
    <#
    .SYNOPSIS
        Return the output of gitversion dotnet tool as an object
    #>

    [CmdletBinding()]
    param(
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        Write-Debug ' - Checking for gitversion utility'
        $gitverCmd = Get-Command dotnet-gitversion.exe -ErrorAction SilentlyContinue
        if ($null -ne $gitverCmd) {
            Write-Verbose 'Using gitversion for version info'
            $gitVersionCommandInfo = & $gitverCmd @('-?')

            Write-Debug ' - gitversion found. Getting version info'
            $gitVersionCommandInfo | Write-Debug
            $gitVersionOutput = & $gitverCmd @( '-output', 'json')
            if ([string]::IsNullorEmpty($gitVersionOutput)) {
                Write-Warning 'No output from gitversion'
            } else {
                Write-Debug "Version info: $gitVersionOutput"
                try {
                    $gitVersionOutput | ConvertFrom-Json | Write-Output
                } catch {
                    throw "Could not parse json:`n$gitVersionOutput`n$_"
                }
            }

        }
        Write-Debug ' - gitversion not found'
        Write-Information "GitVersion is not installed.`nsee <https://gitversion.net/docs/usage/cli/installation> for details"
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Project\Get-GitVersionInfo.ps1' 42
#Region '.\public\Project\Get-ProjectPath.ps1' -1

function Get-ProjectPath {
    <#
    .SYNOPSIS
        Retrieve the paths to the major project components. (Source, Tests, Docs, Artifacts, Staging)
    #>

    [CmdletBinding()]
    param(
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)'"
        $stitchPathFiles = @(
            '.stitch.config.psd1',
            '.stitch.psd1',
            'stitch.config.psd1'
            )
    }
    process {
        $root = Resolve-ProjectRoot
        if ($null -ne $root) {
            $possibleBuildRoot = $PSCmdlet.GetVariableValue('BuildRoot')
            if (-not ([string]::IsNullorEmpty($possibleBuildRoot))) {
                $root = $possibleBuildRoot
            } else {
                $root = Get-Location
            }
        }
        Write-Verbose "Looking for path config file in $root"
        $pathConfigFiles = (Get-ChildItem -Path "$root/*.psd1" -Include $stitchPathFiles)
        if ($pathConfigFiles.Count -gt 0) {
            Write-Debug ('Found ' + ($pathConfigFiles.Name -join "`n"))
            $pathConfigFile = $pathConfigFiles[0]
        }

        if ($null -ne $pathConfigFile) {
            Write-Verbose " - found $pathConfigFile"
            try {
                $config = Import-Psd $pathConfigFile
                $resolved = @{}
                foreach ($key in $config.Keys) {
                    $resolved[$key] = (Resolve-Path $config[$key])
                }
            } catch {
                $PSCmdlet.ThrowTerminatingError($_)
            }
            [PSCustomObject]$resolved | Write-Output
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Project\Get-ProjectPath.ps1' 52
#Region '.\public\Project\Get-ProjectVersionInfo.ps1' -1


function Get-ProjectVersionInfo {
    <#
    .SYNOPSIS
        Return a collection of Version Information about the project
    .DESCRIPTION
        gitversion dotnet tool
        git describe
        version.(psd1|json|yaml)
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'gitdescribe'
    )]
    param(
        # Use git describe instead of gitversion
        [Parameter(
            ParameterSetName = 'gitdescribe'
        )]
        [switch]$UseGitDescribe,

        # Use the information in version.(psd1|json|yml)
        [Parameter(
            ParameterSetName = 'versionfile'
        )]
        [switch]$UseVersionFile
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        Write-Debug 'Checking for version information'
        try {
            if ($UseVersionFile) {
                Get-VersionFileInfo
            } else {
                $cmd = Get-Command 'gitversion' -ErrorAction SilentlyContinue

                if (($null -ne $cmd) -and (-not ($UseGitDescribe))) {
                    Get-GitVersionInfo
                } else {
                    Get-GitDescribeInfo
                }
            }
        } catch {
            $message       = "Could not get version information for the project"
            $exceptionText = ( @($message, $_.ToString()) -join "`n")
            $thisException = [Exception]::new($exceptionText)
            $eRecord       = New-Object System.Management.Automation.ErrorRecord -ArgumentList (
                $thisException,
                $null,  # errorId
                $_.CategoryInfo.Category, # errorCategory
                $null  # targetObject
            )
            $PSCmdlet.ThrowTerminatingError( $eRecord )
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Project\Get-ProjectVersionInfo.ps1' 61
#Region '.\public\Project\Get-VersionFileInfo.ps1' -1


function Get-VersionFileInfo {
    <#
    .SYNOPSIS
        Return version info stored in a file in the project
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if (-not ($PSBoundParameters.ContainsKey('Path'))) {
            $Path = Resolve-ProjectRoot
        }
        Write-Debug ' - Looking for version.* file'
        $found = Get-ChildItem -Path $Path -Filter 'version.*' -Recurse |
            Sort-Object LastWriteTime |
                Select-Object -Last 1

        if ($null -ne $found) {
            Write-Verbose "Using $found for version info"
            Write-Debug " - Found $($found.FullName)"
            switch -Regex ($found.extension) {
                'psd1' { $versionInfo = Import-Psd $found }
                'json' { $versionInfo = (Get-Content $found | ConvertFrom-Json) }
                'y(a)?ml' { $versionInfo = (Get-Content $found | ConvertFrom-Yaml) }
                Default { Write-Information "$($found.Name) found but no converter for $($found.extension) is set" }
            }
            $versionInfo['PSTypeName'] = 'Stitch.VersionInfo'
            [PSCustomObject]$versionInfo | Write-Output
        } else {
            throw "Could not find version file in $Path"
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Project\Get-VersionFileInfo.ps1' 49
#Region '.\public\Project\Initialize-StitchProject.ps1' -1

#using namespace System.Diagnostics.CodeAnalysis


function Initialize-StitchProject {

    [Alias('Institchilize')]
    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'high'
    )]
    [SuppressMessage('PSAvoidUsingWriteHost', '', Justification='Output of write operation should not be redirected')]
    param(
        # The directory to initialize the build tool in.
        # Defaults to the current directory.
        [Parameter(
        )]
        [string]$Destination,

        # Overwrite existing files
        [Parameter(
        )]
        [switch]$Force,

        # Do not output any status to the console
        [Parameter(
        )]
        [switch]$Quiet
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"

    }
    process {
        if ([string]::IsNullorEmpty($Destination)) {
            Write-Debug "Setting Destination to current directory"
            $Destination = (Get-Location).Path
        }
        $possibleBuildConfigRoot = $Destination | Find-BuildConfigurationRootDirectory
        if (-not ([string]::IsNullorEmpty($possibleBuildConfigRoot))) {
            $buildConfigDir = $possibleBuildConfigRoot
        } else {
            $buildConfigDefaultDir = '.build'
        }
        #-------------------------------------------------------------------------------
        #region Gather info

        if (-not($Quiet)) {
            Write-StitchLogo -Size 'large'
        }

        New-StitchPathConfigurationFile -Force:$Force

        if (-not ([string]::IsNullorEmpty($buildConfigDir))) {
            "Found your build configuration directory '$(Resolve-Path $buildConfigDir -Relative)'"
        } else {
            $prompt = ( -join @(
                'What is the name of your build configuration directory? ',
                $PSStyle.Foreground.BrightBlack,
                " ( $buildConfigDefaultDir )",
                $PSStyle.Reset
                )
                )

            $ans = Read-Host $prompt
            if ([string]::IsNullorEmpty($ans)) {
                $ans = $buildConfigDefaultDir
            }
            $buildConfigDir = (Join-Path $Destination $ans)
        }

        #endregion Gather info
        #-------------------------------------------------------------------------------

        #-------------------------------------------------------------------------------
        #region Create directories
        Write-Debug "Create directories if they do not exist"
        Write-Debug " - Looking for $buildConfigDir"
        if (-not(Test-Path $buildConfigDir)) {
            try {
                '{0} does not exist. {1}Creating{2}' -f $buildConfigDir, $PSStyle.Foreground.Green, $PSStyle.Reset
                $null = mkdir $buildConfigDir -Force
            } catch {
                throw "Could not create Build config directory $BuildConfigDir`n$_"
            }
        }
        $profileRoot = $buildConfigDir | Find-BuildProfileRootDirectory
        if ($null -eq $profileRoot) {
            $profileRoot = (Join-Path $buildConfigDir 'profiles')
            try {
                '{0} does not exist. {1}Creating{2}' -f $profileRoot, $PSStyle.Foreground.Green, $PSStyle.Reset
                $null = mkdir $profileRoot -Force
            } catch {
                throw "Could not create build profile directory $profileRoot`n$_"
            }
        }
        if (-not (Test-Path (Join-Path $profileRoot 'default'))) {
            '{0} does not exist. {1}Creating{2}' -f 'default profile', $PSStyle.Foreground.Green, $PSStyle.Reset
        }
        $profileRoot | New-StitchBuildProfile -Name 'default' -Force:$Force
        Get-ChildItem (Join-Path $profileRoot 'default') -Filter "*.ps1" | Foreach-Object {
            $_ | Format-File 'CodeFormattingOTBS'
        }

        if (-not (Test-Path (Join-Path $Destination '.build.ps1'))) {
            '{0} does not exist. {1}Creating{2}' -f 'build runner', $PSStyle.Foreground.Green, $PSStyle.Reset
        }
        $Destination | New-StitchBuildRunner -Force:$Force
        Get-ChildItem $Destination -Filter ".build.ps1" | Format-File 'CodeFormattingOTBS'

        #endregion Create directories
        #-------------------------------------------------------------------------------
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Project\Initialize-StitchProject.ps1' 117
#Region '.\public\Project\New-StitchBuildProfile.ps1' -1

#using namespace System.Diagnostics.CodeAnalysis


function New-StitchBuildProfile {
    [SuppressMessage('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'File creation methods have their own ShouldProcess')]
    [CmdletBinding()]
    param(
        # The name of the profile to create
        [Parameter(
            Mandatory,
            Position = 0
        )]
        [string]$Name,

        # Profile path in the build config path
        [Parameter(
            Position = 1,
            ValueFromPipeline
        )]
        [string]$ProfileRoot,

        # Overwrite the profile if it exists
        [Parameter(
        )]
        [switch]$Force

    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if (-not ($PSBoundParameters.ContainsKey('ProfileRoot'))) {
            $possibleProfileRoot = Find-BuildProfileRootDirectory
            if ($null -ne $possibleProfileRoot) {
                $ProfileRoot = $possibleProfileRoot
                Remove-Variable $possibleProfileRoot -ErrorAction SilentlyContinue
            } else {
                throw 'Could not find the build profile root directory. Use -ProfileRoot'
            }
        }
        $newProfileDirectory = (Join-Path $ProfileRoot $Name)
        if ((Test-Path $newProfileDirectory) -and
            (-not ($Force))) {
            throw "Profile '$Name' already exists at $newProfileDirectory. Use -Force to Overwrite"
        } else {
            try {
                Write-Debug 'Creating directory'
                $null = mkdir $newProfileDirectory -Force
                Write-Debug 'Creating runbook'
                $newProfileDirectory | New-StitchRunBook -Force:$Force
                Write-Debug 'Creating configuration file'
                $newProfileDirectory | New-StitchConfigurationFile -Force:$Force
            } catch {
                throw "Could not create new build profile '$Name' in '$newProfileDirectory'`n$_"
            }
            #TODO: if we fail to create a file, should we remove the folder in a finally block?
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Project\New-StitchBuildProfile.ps1' 64
#Region '.\public\Project\New-StitchBuildRunner.ps1' -1

function New-StitchBuildRunner {
    <#
    .SYNOPSIS
        Create the main stitch build script
    .EXAMPLE
        New-StitchBuildRunner $BuildRoot
 
        Creates the file $BuildRoot\.build.ps1
    #>

    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'Low'
    )]
    param(
        # Specifies a path to the folder where the runbook should be created
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path,

        # The name of the main build script.
        [Parameter(
        )]
        [string]$Name,

        # Overwrite the file if it exists
        [Parameter(
        )]
        [switch]$Force
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $template = Get-StitchTemplate -Type 'install' -Name '.build.ps1'


        if ($null -ne $template) {
            $template.Destination = $Path
            if ($PSBoundParameters.ContainsKey('Name')) {
                $template.Name = $Name
            }

            if (Test-Path $template.Target) {
                if ($Force) {
                    if ($PSCmdlet.ShouldProcess($template.Target, 'Overwrite file')) {
                        $template | Invoke-StitchTemplate -Force
                    }
                } else {
                    throw "$($template.Target) already exists. Use -Force to overwrite"
                }
            } else {
                $template | Invoke-StitchTemplate
            }
        } else {
            throw 'Could not find the stitch build script file template'
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Project\New-StitchBuildRunner.ps1' 66
#Region '.\public\Project\New-StitchConfigurationFile.ps1' -1

function New-StitchConfigurationFile {
    <#
    .SYNOPSIS
        Create a configuration in the folder specified in Path.
    .EXAMPLE
        New-StitchConfigurationFile $BuildRoot\.stitch\profiles\site
 
        Creates the file $BuildRoot\.stitch\profiles\site\stitch.config.ps1
    #>

    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'Low'
    )]
    param(
        # Specifies a path to the folder where the runbook should be created
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path,

        # The name of the configuration file.
        [Parameter(
        )]
        [string]$Name,

        # Overwrite the file if it exists
        [Parameter(
        )]
        [switch]$Force
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $template = Get-StitchTemplate -Type 'install' -Name '.config.ps1'


        if ($null -ne $template) {
            $template.Destination = $Path
            if ($PSBoundParameters.ContainsKey('Name')) {
                $template.Name = $Name
            } else {
                $template.Name = 'stitch.config.ps1'
            }
            if (Test-Path $template.Target) {
                if ($Force) {
                    if ($PSCmdlet.ShouldProcess($template.Target, 'Overwrite file')) {
                        $template | Invoke-StitchTemplate -Force
                    }
                } else {
                    throw "$($template.Target) already exists. Use -Force to overwrite"
                }
            } else {
                $template | Invoke-StitchTemplate
            }
        } else {
            throw 'Could not find the stitch configuration file template'
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Project\New-StitchConfigurationFile.ps1' 67
#Region '.\public\Project\New-StitchConfigurationPath.ps1' -1

function New-StitchConfigurationPath {
    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 1,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path,

        # The name of the directory. Supports '.build' or '.stitch'
        [Parameter(
        )]
        [ValidateSet('.build', '.stitch')]
        [string]$Name = '.build'
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if (-not ($PSBoundParameters.ContainsKey('Path'))) {
            $Path = Get-Location
        }
        $buildConfigDir = (Join-Path $Path $Name)
        Write-Debug 'Create directories if they do not exist'
        Write-Debug " - Looking for $buildConfigDir"
        if (-not(Test-Path $buildConfigDir)) {
            try {
                '{0} does not exist. {1}Creating{2}' -f $buildConfigDir, $PSStyle.Foreground.Green, $PSStyle.Reset
                $null = mkdir $buildConfigDir -Force
            } catch {
                throw "Could not create Build config directory $BuildConfigDir`n$_"
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }

}
#EndRegion '.\public\Project\New-StitchConfigurationPath.ps1' 43
#Region '.\public\Project\New-StitchPathConfigurationFile.ps1' -1


function New-StitchPathConfigurationFile {
    [CmdletBinding(
        SupportsShouldProcess
    )]
    param(
        # Default Source directory
        [Parameter(
        )]
        [string]$Source,

        # Default Tests directory
        [Parameter(
        )]
        [string]$Tests,

        # Default Staging directory
        [Parameter(
        )]
        [string]$Staging,

        # Default Artifact directory
        [Parameter(
        )]
        [string]$Artifact,

        # Default Docs directory
        [Parameter(
        )]
        [string]$Docs,

        # Do not validate paths
        [Parameter(
        )]
        [switch]$DontValidate,

        # Overwrite an existing file
        [Parameter(
        )]
        [switch]$Force
    )
begin {
    Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    $defaultPathConfigFile = (Join-Path (Get-Location) '.stitch.config.psd1')
    $locations = @{
        Source = @{}
        Tests = @{}
        Staging = @{}
        Artifacts = @{}
        Docs = @{}
    }
}
process {
    foreach ($location in $locations.Keys) {
        if (-not ($PSBoundParameters.ContainsKey($location))) {
            $pathIsSet = $false
            do {
                $ans = Read-Host "The directory where this project's $location is stored "
                if (-not ($DontValidate)) {
                    $possiblePath = (Join-Path (Get-Location) $ans)
                    if (-not (Test-Path $possiblePath)) {
                        $confirmAnswer = Read-Host "$possiblePath does not exist. Use anyway?"
                        if (([string]::IsNullorEmpty($confirmAnswer)) -or
                            ($confirmAnswer -match '^[yY]')) {
                                $PSBoundParameters[$location] = $ans
                                $pathIsSet = $true # break out of loop for this location
                        }
                    } else {
                        $pathIsSet = $true
                    }
                } else {
                    $pathIsSet = $true
                }
            } while (-not ($pathIsSet))
        }
    }

    $pathSettings = $PSBoundParameters

    foreach ($unusedParameter in @('DontValidate', 'Force')) {
        if ($pathSettings.ContainsKey($unusedParameter)) {
            $null = $pathSettings.Remove($unusedParameter)
        }
    }

    if (Test-Path $defaultPathConfigFile) {
        if ($Force) {
            if ($PSCmdlet.ShouldProcess("$defaultPathConfigFile", "Overwrite existing file")) {
                $pathSettings | Export-Psd -Path $defaultPathConfigFile
            }
        }
    }
}
end {
    Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
}
}
#EndRegion '.\public\Project\New-StitchPathConfigurationFile.ps1' 98
#Region '.\public\Project\New-StitchRunBook.ps1' -1

function New-StitchRunBook {
    <#
    .SYNOPSIS
        Create a runbook in the folder specified in Path.
    .EXAMPLE
        New-StitchRunBook $BuildRoot\.stitch\profiles\site
 
        Creates the file $BuildRoot\.stitch\profiles\site\runbook.ps1
    #>

    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'Low'
    )]
    param(
        # Specifies a path to the folder where the runbook should be created
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path,

        # The name of the runbook. Not needed if using profiles
        [Parameter(
        )]
        [string]$Name,

        # Overwrite the file if it exists
        [Parameter(
        )]
        [switch]$Force
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $template = Get-StitchTemplate -Type 'install' -Name 'runbook.ps1'

        $template.Destination = $Path

        if ($null -ne $template) {
            if ($PSBoundParameters.ContainsKey('Name')) {
                $template.Name = $Name
            }
            if (Test-Path $template.Target) {
                if ($Force) {
                    if ($PSCmdlet.ShouldProcess($template.Target, "Overwrite file")) {
                        $template | Invoke-StitchTemplate -Force
                    }
                } else {
                    throw "$($template.Target) already exists. Use -Force to overwrite"
                }
            } else {
                $template | Invoke-StitchTemplate
            }
        } else {
            throw "Could not find the runbook template"
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Project\New-StitchRunBook.ps1' 65
#Region '.\public\Project\Resolve-ProjectName.ps1' -1


function Resolve-ProjectName {
    [CmdletBinding()]
    param(
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $config = Get-BuildConfiguration
        if ([string]::IsNullorEmpty($config.Project.Name)) {
            Write-Debug "Project name not set in configuration`n trying to resolve project root"
            $root = (Resolve-ProjectRoot).BaseName
        } else {
            Write-Debug "Project Name found in configuration"
            $root = $config.Project.Name
        }
    }
    end {
        $root
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Project\Resolve-ProjectName.ps1' 24
#Region '.\public\Project\Test-ProjectPath.ps1' -1


function Test-ProjectPath {
    [CmdletBinding()]
    param(

    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {

    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Project\Test-ProjectPath.ps1' 17
#Region '.\public\Project\Write-StitchLogo.ps1' -1


function Write-StitchLogo {
    [CmdletBinding()]
    param(
        # Small or large logo
        [Parameter(
        )]
        [ValidateSet('small', 'large')]
        [string]$Size = 'large',

        # Do not print the logo in color
        [Parameter(
        )]
        [switch]$NoColor
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $stitchEmoji = "`u{1f9f5}"

        $stitchLogoSmall = @'
:2: ___ _ _ _ _
:2: / __|| |_ (_)| |_ __ | |_
:2: \__ \| _|| || _|/ _|| \
:2: |___/ \__||_| \__|\__||_||_|
'@



        $stitchLogoLarge = @'
:1:-=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=:0:
:1:=- ________________ =-:0:
:1:-= (________________) xxxxxx xx xxxx xx xx -=:0:
:1:=-:0: :2:(______ ) :1: x x x x x x x x x x =-:0:
:1:-=:0: :2:( _____ ) :1: x xxxx x xxxxx xxxx x xxxxx xxxx x xxx -=:0:
:1:=-:0: :2:( ____ ) :1: x x x x xxxx x x x x x x =-:0:
:1:-=:0: :2:( ____) :1: xxxx x x xxx x x x xxx x xxx x x -=:0:
:1:=-:0: _:2:(____________):1:_ x x x x x x x x x x x x x x =-:0:
:1:-= (________________) xxxxxxx xxxxxx xxxx xxxxxx xxxxxx xxxx xxxx -=:0:
:1:=- =-:0:
:1:-=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=--=-=:0:
'@


    }
    process {
        if ($Size -like 'small') {
            $logoSource = $stitchLogoSmall
        } else {
            $logoSource = $stitchLogoLarge
        }
        if (-not($NoColor)) {
            $colors = @(
                $PSStyle.Reset,
                $PSStyle.Foreground.FromRgb('#b1a986'),
                $PSStyle.Foreground.FromRgb('#0679d0')
            )
        } else {
            $colors = @(
                '',
                '',
                ''
            )
        }
        $logoOutput = $logoSource
         for ($c = 0; $c -lt $colors.Length; $c++) {
            $logoOutput = $logoOutput -replace ":$($c.ToString()):", $colors[$c]
         }
    }
    end {
        $logoOutput
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Project\Write-StitchLogo.ps1' 72
#Region '.\public\SourceInfo\Find-TodoItem.ps1' -1


function Find-TodoItem {
    <#
    .SYNOPSIS
        Find all comments in the code base that have the 'TODO' keyword
    .DESCRIPTION
        Show a list of all "TODO comments" in the code base starting at the directory specified in Path
    .EXAMPLE
        Find-TodoItem $BuildRoot
    #>

    [OutputType('Stitch.SourceItem.Todo')]
    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $todoPattern = '^(\s*)(#)?\s*TODO(:)?\s+(.*)$'
    }
    process {
        #TODO: To refine this we could parse the file and use the comment tokens to give to Select-String
        $results = Get-ChildItem $Path -Recurse | Select-String -Pattern $todoPattern -CaseSensitive -AllMatches

         foreach ($result in $results) {
            [PSCustomObject]@{
                PSTypeName = 'Stitch.SourceItem.Todo'
                Text = $result.Matches[0].Groups[4].Value
                Position = (-join ($result.Path, ':', $result.LineNumber))
                File = (Get-Item $result.Path)
                Line = $result.LineNumber
            } | Write-Output
        }
 #>
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\SourceInfo\Find-TodoItem.ps1' 46
#Region '.\public\SourceInfo\Get-ModuleItem.ps1' -1


function Get-ModuleItem {
    <#
    .SYNOPSIS
        Retrieve the modules in the given path
    .DESCRIPTION
        Get-ModuleItem returns an object representing the information about the modules in the directory given in
        Path. It returns information from the manifest such as version number, etc. as well as SourceItemInfo
        objects for all of the source items found in it's subdirectories
    .EXAMPLE
        Get-ModuleItem .\source
    .LINK
        Get-SourceItem
    #>

    [OutputType('Stitch.ModuleItemInfo')]
    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations containing Module Source
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [ValidateNotNullOrEmpty()]
        [string[]]$Path,

        # Optionally return a hashtable instead of an object
        [Parameter(
        )]
        [switch]$AsHashTable
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if (-not ($PSBoundParameters.ContainsKey('Path'))) {
            $Path = Resolve-SourceDirectory
        }
        foreach ($p in $Path) {
            Write-Debug " Looking for module source in '$p'"
            try {
                $pathItem = Get-Item $p -ErrorAction Stop
                if (-not($pathItem.PSIsContainer)) {
                    Write-Verbose "$p is not a Directory, skipping"
                    continue
                }
                foreach ($modulePath in ($pathItem | Get-ChildItem -Directory)) {
                    $info = @{}
                    [ModuleFlag]$flags = [ModuleFlag]::None

                    $name = $modulePath.Name
                    Write-Debug " Module name is $name"
                    $info['Name'] = $name
                    $info['ModuleName'] = $name

                    $manifestFile = (Join-Path $modulePath "$name.psd1")
                    if (Test-Path $manifestFile) {
                        $manifestObject = Import-Psd $manifestFile
                        if (($manifestObject.Keys -contains 'PrivateData') -and
                            ($manifestObject.Keys -contains 'GUID')) {

                                [ModuleFlag]$flags = [ModuleFlag]::HasManifest
                                Write-Debug " Found $name.psd1 testing Manifest"
                                $info['ManifestFile'] = "$name.psd1"
                                $info['Path'] = $manifestFile
                            }
                    }

                    $sourceInfo = Get-SourceItem $modulePath.Parent | Where-Object Module -like $name
                    if ($null -ne $sourceInfo) {
                        [ModuleFlag]$flags += [ModuleFlag]::HasModule
                    }

                    if ($flags.hasFlag([ModuleFlag]::HasManifest)) {
                        Write-Verbose "Manifest found in $($modulePath.BaseName)"
                        foreach ($key in $manifestObject.Keys) {
                            if ($key -notlike 'PrivateData') {
                                $info[$key] = $manifestObject[$key]
                            }
                        }
                        foreach ($key in $manifestObject.PrivateData.PSData.Keys) {
                            $info[$key] = $manifestObject.PrivateData.PSData[$key]
                        }
                    }
                    if ($flags.hasFlag([ModuleFlag]::HasModule)) {
                        $info['SourceDirectories'] = $sourceInfo |
                            Where-Object { @('function', 'class', 'enum') -contains $_.Type } |
                                Select-Object -ExpandProperty Directory | Sort-Object -Unique
                        $info['SourceInfo'] = $sourceInfo
                        if ($info.Keys -notcontains 'RootModule') {
                            $info['ModuleFile'] = "$name.psm1"
                        } else {
                            $info['ModuleFile'] = $info.RootModule
                        }
                        Write-Verbose "Module source found in $($modulePath.BaseName)"
                    }
                    if ($AsHashTable) {
                        $info | Write-Output
                    } else {
                        $info['PSTypeName'] = 'Stitch.ModuleItemInfo'
                        [PSCustomObject]$info | Write-Output
                    }
                }
            } catch {
                $PSCmdlet.ThrowTerminatingError($_)
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\SourceInfo\Get-ModuleItem.ps1' 114
#Region '.\public\SourceInfo\Get-SourceItem.ps1' -1


function Get-SourceItem {
    <#
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,

        # Path to the source type map
        [Parameter(
        )]
        [string]$TypeMap
    )

    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }

    process {
        if (-not($PSBoundParameters.ContainsKey('Path'))) {
            Write-Debug "No path specified. Using default source folder"
            #TODO: Yikes! hard-coded source path
            $Path = (Join-Path (Resolve-ProjectRoot) 'source')
            Write-Debug "Source path root: $Path"
        }
        foreach ($p in $Path) {
            $sourceRoot = $p
            try {
                $item = Get-Item $p -ErrorAction Stop
                if ($item.PSIsContainer) {
                    Get-ChildItem $item.FullName -Recurse -File
                    | Get-SourceItemInfo -Root $sourceRoot
                    | Write-Output
                    continue
                } else {
                        $item
                        | Get-SourceItemInfo -Root $sourceRoot
                        | Write-Output
                    continue
                }
            } catch {
                Write-Warning "$p is not a valid path`n$_"
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\SourceInfo\Get-SourceItem.ps1' 57
#Region '.\public\SourceInfo\Get-SourceTypeMap.ps1' -1


function Get-SourceTypeMap {
    <#
    .SYNOPSIS
        Retrieve the table that maps source items to the appropriate Visibility and Type
    .LINK
        Get-SourceItemInfo
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to the source type map file.
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path

    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        #TODO: Another item that would be good to add to the PoshCode Configuration
        New-Variable -Name DEFAULT_FILE_NAME -Value 'sourcetypes.config.psd1' -Option Constant
    }
    process {
        if (-not ($PSBoundParameters.ContainsKey('Path'))) {
            Write-Debug "No Path given. Looking for Map file in Build Configuration Directory"
            $possibleBuildConfigPath = Find-BuildConfigurationDirectory

            if ($null -ne $possibleBuildConfigPath) {
                $possibleMapFile = (Join-Path $possibleBuildConfigPath $DEFAULT_FILE_NAME)
            }

            if ($null -eq $possibleMapFile) {
                Write-Debug "Not found. Looking for Map file in Build Configuration Root Directory"
                $possibleBuildConfigPath = Find-BuildConfigurationRootDirectory

                if ($null -ne $possibleBuildConfigPath) {
                    $possibleMapFile = (Join-Path $possibleBuildConfigPath $DEFAULT_FILE_NAME)
                }
            }
        } else {
            if (Test-Path $Path) {
                $pathItem = Get-Item $Path
                if ($pathItem.PSIsContainer) {
                    $possibleMapFile = (Join-Path $Path $DEFAULT_FILE_NAME)
                } else {
                    $possibleMapFile = $Path
                }
            }
        }

        if (Test-Path $possibleMapFile) {
            Write-Verbose "Source Type Map file was found at $possibleMapFile"
            try {
                $config = Import-Psd $possibleMapFile -Unsafe
                if ($null -ne $config) {
                    $config['TypeMapFilePath'] = $possibleMapFile
                    $config | Write-Output
                }
            } catch {
                $PSCmdlet.ThrowTerminatingError($_)
            }
        } else {
            Write-Error "No $DEFAULT_FILE_NAME could be found"
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\SourceInfo\Get-SourceTypeMap.ps1' 73
#Region '.\public\SourceInfo\Get-TestItem.ps1' -1

function Get-TestItem {
    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path
    )

    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }

    process {
        if (-not($PSBoundParameters.ContainsKey('Path'))) {
            Write-Debug "No path specified. Looking for `$Tests"
            $testsVariable = $PSCmdlet.GetVariableValue('Tests')
            if ($null -ne $testsVariable) {
                Write-Debug " - found `$Tests: $testsVariable"
            } else {
                Write-Debug 'Checking for default tests folder'
                $possiblePath = (Join-Path (Resolve-ProjectRoot) 'tests')
                if ($null -ne $possiblePath) {
                    if (Test-Path $possiblePath) {
                        $Path = $possiblePath
                    }
                }
            }
            if ($null -eq $Path) {
                throw 'Could not resolve a Path to tests'
            } else {
                Write-Debug "Path is $Path"
            }
        }

        foreach ($p in $Path) {
            try {
                $item = Get-Item $p -ErrorAction Stop
            } catch {
                Write-Warning "$p is not a valid path`n$_"
                continue
            }
            if ($item.PSIsContainer) {
                try {
                    Get-ChildItem $item.FullName -Recurse:$Recurse -File |
                        Get-TestItemInfo -Root $item.FullName | Write-Output
                }
                catch {
                    Write-Warning "$_"
                }
                continue
            } else {
                if ($item.Extension -eq '.ps1') {
                    try {
                        $item | Get-TestItemtInfo | Write-Output
                    }
                    catch {
                        Write-Warning "$_"
                    }
                    continue
                }
                continue
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\SourceInfo\Get-TestItem.ps1' 74
#Region '.\public\SourceInfo\New-FunctionItem.ps1' -1


function New-FunctionItem {
    <#
    .SYNOPSIS
        Create a new function source item in the given module's source folder with the give visibility
    .EXAMPLE
        $module | New-FunctionItem Get-FooItem public
    .EXAMPLE
        New-FunctionItem Get-FooItem Foo public
    #>

    [CmdletBinding()]
    param(
        # The name of the Function to create
        [Parameter(
            Mandatory,
            Position = 0
        )]
        [string]$Name,

        # The name of the module to create the function for
        [Parameter(
            Mandatory,
            Position = 1,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('ModuleName')]
        [string]$Module,

        # Visibility of the function ('public' for exported commands, 'private' for internal commands)
        # defaults to 'public'
        [Parameter(
            Position = 2
        )]
        [ValidateSet('public', 'private')]
        [string]$Visibility = 'public',

        # Code to be added to the begin block of the function
        [Parameter(
        )]
        [string]$Begin,

        # Code to be added to the process block of the function
        [Parameter(
        )]
        [string]$Process,

        # Code to be added to the End block of the function
        [Parameter(
        )]
        [string]$End,

        # Optionally provide a component folder
        [Parameter(
        )]
        [string]$Component,

        # Overwrite an existing file
        [Parameter(
        )]
        [switch]$Force,

        # Return the path to the generated file
        [Parameter(
        )]
        [switch]$PassThru
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $projectPaths = Get-ProjectPath
        if ($null -ne $projectPaths) {
            if (-not ([string]::IsNullorEmpty($projectPaths.Source))) {
                if ($PSBoundParameters.ContainsKey('Module')) {
                    $modulePath = (Join-Path $projectPaths.Source $Module)
                }

                $filePath = (Join-Path -Path $modulePath -ChildPath $Visibility)

                if ($PSBoundParameters.ContainsKey('Component')) {
                    $filePath = (Join-Path $filePath $Component)
                    if (-not(Confirm-Path $filePath -ItemType Directory)) {
                        throw "Could not create source directory $filePath"
                    }
                }
                Write-Debug " - filePath is $filePath"

                $options = @{
                    Type     = 'function'
                    Name     = $Name
                    Destination = $filePath
                    Data     = @{ 'Name' = $Name }
                    Force    = $Force
                    PassThru = $PassThru
                }

                if ($PSBoundParameters.ContainsKey('Begin')) {
                    $options.Data['Begin'] = $Begin
                }
                if ($PSBoundParameters.ContainsKey('Process')) {
                    $options.Data['Process'] = $Process
                }
                if ($PSBoundParameters.ContainsKey('End')) {
                    $options.Data['End'] = $End
                }
                try {
                    New-SourceItem @options
                } catch {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
            } else {
                throw 'Could not resolve Source directory'
            }
        } else {
            throw 'Could not get project path information'
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\SourceInfo\New-FunctionItem.ps1' 123
#Region '.\public\SourceInfo\New-SourceComponent.ps1' -1


function New-SourceComponent {
    <#
    .SYNOPSIS
        Add a new Component folder to the module's source
    #>

    [CmdletBinding()]
    param(
        # The name of the component to add
        [Parameter(
            Position = 0
        )]
        [string]$Name,

        # The name of the module to add the component to
        [Parameter(
            Position = 1,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [string]$Module,

        # Only add the component to the public functions
        [Parameter(
            ParameterSetName = 'public'
            )]
            [switch]$PublicOnly,

            # Only add the component to the private functions
            [Parameter(
            ParameterSetName = 'private'
        )]
        [switch]$PrivateOnly
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $possibleSourceFolder = $PSCmdlet.GetVariableValue('Source')
        if ($null -eq $possibleSourceFolder) {
            $projectSourcePath = Get-ProjectPath | Select-Object -ExpandProperty Source
            Write-Debug "Project path value for Source: $projectSourcePath"
        } else {
            Write-Debug "Source path set from `$Source variable: $Source"
            $projectSourcePath = $possibleSourceFolder
        }
        $moduleDirectory = (Join-Path $projectSourcePath $Module)
        Write-Debug "Module directory is $moduleDirectory"
        if ($null -ne $moduleDirectory) {
            if (-not ($PublicOnly)) {
                $privateDirectory = (Join-Path $moduleDirectory 'private')
                if (Test-Path $privateDirectory) {
                    $null = (Join-Path $privateDirectory $Name) | Confirm-Path -ItemType Directory
                } else {
                    throw "Could not find $privateDirectory"
                }
            }
            if (-not ($PrivateOnly)) {
                $publicDirectory = (Join-Path $moduleDirectory 'public')
                if (Test-Path $publicDirectory) {
                    $null = (Join-Path $publicDirectory $Name) | Confirm-Path -ItemType Directory
                } else {
                    throw "Could not find $publicDirectory"
                }
            }
        } else {
            throw "Module source not found : $moduleDirectory"
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\SourceInfo\New-SourceComponent.ps1' 74
#Region '.\public\SourceInfo\New-SourceItem.ps1' -1


function New-SourceItem {
    <#
    .SYNOPSIS
        Create a new source item using templates
    .DESCRIPTION
        `New-SourceItem
    #>

    [CmdletBinding(
        SupportsShouldProcess,
        ConfirmImpact = 'Low'
    )]
    param(
        # The type of file to create
        [Parameter(
            Position = 0
        )]
        [string]$Type,

        # The file name
        [Parameter(
            Position = 1
        )]
        [string]$Name,

        # The data to pass into the template binding
        [Parameter(
            Position = 2
        )]
        [hashtable]$Data,

        # The directory to place the new file in
        [Parameter()]
        [string]$Destination,

        # Overwrite an existing file
        [Parameter(
        )]
        [switch]$Force,

        # Return the path to the generated file
        [Parameter(
        )]
        [switch]$PassThru
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"

    }
    process {

        $template = Get-StitchTemplate -Type 'new' -Name $Type


        if ($null -ne $template) {
            if ($PSBoundParameters.ContainsKey('Name')) {
                $template.Name = $Name
            }
            if (-not ([string]::IsNullorEmpty($template.Extension))) {
                $template.Name = ( -join ($template.Name, $template.Extension))
            }

            if ($PSBoundParameters.ContainsKey('Destination')) {
                $template.Destination = $Destination
            }

            if ($PSBoundParameters.ContainsKey('Data')) {
                Write-Debug "Processing template Data"
                if (-not ([string]::IsNullorEmpty($template.Data))) {
                    Write-Debug " - Updating Data"
                    $template.Data = ($template.Data | Update-Object $Data)
                } else {
                    Write-Debug " - Setting Data"
                    $template.Data = $Data
                }
            }
            Write-Debug "Invoking template"
            $template | Invoke-StitchTemplate -Force:$Force -PassThru:$PassThru
        } else {
            throw "Could not find a 'new' template for type: $Type"
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }


} #close New-Sourceitem
#EndRegion '.\public\SourceInfo\New-SourceItem.ps1' 89
#Region '.\public\SourceInfo\New-TestItem.ps1' -1

function New-TestItem {
    <#
    .SYNOPSIS
        Create a test item from a source item using the test template
    #>

    [CmdletBinding()]
    param(
        # The SourceItemInfo object to create the test from
        [Parameter(
            ValueFromPipeline
        )]
        [PSTypeName('Stitch.SourceItemInfo')]
        [Object[]]$SourceItem,

        # Overwrite an existing file
        [Parameter(
        )]
        [switch]$Force,

        # Return the path to the generated file
        [Parameter(
        )]
        [switch]$PassThru
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $projectPaths = Get-ProjectPath
        if ($null -ne $projectPaths) {
            if (-not ([string]::IsNullorEmpty($projectPaths.Source))) {
                $relativePath = [System.IO.Path]::GetRelativePath(($projectPaths.Source), $SourceItem.Path)
                Write-Debug "Relative Source path is $relativePath"
                $filePath = $relativePath -replace [regex]::Escape($SourceItem.FileName) , ''
                Write-Debug " - filePath is $filePath"
                $testName = "$filePath$([System.IO.Path]::DirectorySeparatorChar)$($SourceItem.BaseName).Tests.ps1"

                Write-Debug "Setting template Name to $testName"
                $options = @{
                    Type     = 'test'
                    Name     = $testName
                    Data     = @{ s = $SourceItem }
                    Force    = $Force
                    PassThru = $PassThru
                }
                try {
                    New-SourceItem @options
                } catch {
                    $PSCmdlet.ThrowTerminatingError($_)
                }
            } else {
                throw 'Could not resolve Source directory'
            }
        } else {
            throw 'Could not get project path information'
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\SourceInfo\New-TestItem.ps1' 62
#Region '.\public\SourceInfo\Rename-SourceItem.ps1' -1


function Rename-SourceItem {
    <#
    .SYNOPSIS
        Rename the file and the function, enum or class in the file
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 1,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string[]]$Path,

        # The New name of the function
        [Parameter(
        )]
        [string]$NewName,

        # Return the new file object
        [Parameter(
        )]
        [switch]$PassThru
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $predicates = @{
            function = {
                param($ast)
                $ast -is [System.Management.Automation.Language.FunctionDefinitionAst]
            }
            class = {
                param($ast)
                (($ast -is [System.Management.Automation.Language.TypeDefinitionAst]) -and
                ($ast.Type -like 'Class'))
            }
            enum = {
                param($ast)
                (($ast -is [System.Management.Automation.Language.TypeDefinitionAst]) -and
                ($ast.Type -like 'Enum'))
            }
        }

    }
    process {
        :file foreach ($file in $Path) {
            if (Test-Path $file) {
                $fileItem = Get-Item $file
                try {
                    $ast = [Parser]::ParseFile($fileItem.FullName, [ref]$null, [ref]$null)
                }
                catch {
                    throw "Could not parse source item $($fileItem.FullName)`n$_"
                }
                # try to find the type of SourceInfo this is
                $typeWasFound = $false
                :type foreach ($type in $predicates.GetEnumerator()) {
                    $innerAst = $ast.Find($type.Value, $false)
                    if ($null -ne $innerAst) {
                        $typeWasFound = $true
                        $oldName = $innerAst.Name
                        Write-Verbose "Found $($type.Name)"
                        break type
                    }
                }
                #! replace all occurances of the old name in the file
                $newExtent = $ast.Extent.Text -replace [regex]::Escape($oldName), $NewName
                try {
                    $newExtent | Set-Content -Path $fileItem.FullName
                    Write-Debug "Updating content in $($fileItem.Name)"
                }
                catch {
                    throw "Could not write content to $($fileItem.FullName)`n$_"
                }

                $baseDirectory = $fileItem | Split-Path -Parent
                $originalExtension = $fileItem.Extension # pretty sure this will always be .ps1, but ...
                $NewPath = (Join-Path $baseDirectory "$NewName$originalExtension")
                try {
                    Move-Item $fileItem.FullName -Destination $NewPath
                    Write-Debug "Renaming file to $NewPath"
                }
                catch {
                    throw "Could not rename $($fileItem.Name)"
                }
                if ($PassThru) {
                    Get-Item $NewPath
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\SourceInfo\Rename-SourceItem.ps1' 99
#Region '.\public\Template\Get-StitchTemplate.ps1' -1


function Get-StitchTemplate {
    [CmdletBinding()]
    param(
        # The type of template to retrieve
        [Parameter(
        )]
        [string]$Type,

        # The name of the template to retrieve
        [Parameter(
        )]
        [string]$Name
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $templatePath = (Join-Path (Get-ModulePath) 'templates')
    }
    process {
        $templateTypes = @{}
        Get-ChildItem $templatePath -Directory | ForEach-Object {
            Write-Debug "Found template file '$($_.Name)' Adding as $"
            $templateTypes.Add($_.BaseName, $_.FullName)
        }
        foreach ($templateType in $templateTypes.GetEnumerator()) {
            $templates = Get-ChildItem $templateType.Value -Filter '*.eps1' -File
            foreach ($template in $templates) {
                $templateObject = [PSCustomObject]@{
                    PSTypeName  = 'Stitch.TemplateInfo'
                    Type        = $templateType.Name
                    Source      = $template.FullName
                    Destination = ''
                    Name        = $template.BaseName -replace '_', '.'
                    Description = ''
                    Data        = @{}
                }

                $metaData = Get-StitchTemplateMetadata
                if ($null -ne $metaData) {
                    $null = $templateObject | Update-Object -UpdateObject $metaData
                }

                #-------------------------------------------------------------------------------
                #region Set Target

                #! Making this a ScriptProperty means that when Destination or Name are updated
                #! this value will be updated to reflect
                if ([string]::IsNullorEmpty($templateObject.Target)) {
                    $templateObject | Add-Member ScriptProperty -Name Target -Value {
                        if ([string]::IsNullorEmpty($this.Destination)) {
                            $this.Destination = (Get-Location)
                        }
                        (Join-Path ($ExecutionContext.InvokeCommand.ExpandString($this.Destination)) $this.Name)
                    }
                }
                #endregion Set Name
                #-------------------------------------------------------------------------------


                #-------------------------------------------------------------------------------
                #region Set destination

                if ([string]::IsNullorEmpty($templateObject.Target)) {
                    #TODO: I don't think this is right. We should not be using 'path' for anything
                    if ($null -ne $templateObject.path) {
                        $templateObject.Destination = "$($templateObject.path)/$($templateObject.Target)"
                    }
                }

                #endregion Set destination
                #-------------------------------------------------------------------------------

                #-------------------------------------------------------------------------------
                #region Binding data
                if (-not ([string]::IsNullorEmpty($templateObject.bind))) {
                    $pathOptions = @{
                        Path = (Split-Path $template.FullName -Parent)
                        ChildPath = ($ExecutionContext.InvokeCommand.ExpandString($templateObject.bind))
                    }
                    $possibleDataFile = (Join-Path @pathOptions)
                    Write-Debug "Template has a bind parameter $possibleDataFile"
                    if (Test-Path $possibleDataFile) {
                        try {
                            $templateData = Import-Psd $possibleDataFile -Unsafe
                            $templateObject.Data = $templateObject.Data | Update-Object $templateData
                        }
                        catch {
                            throw "An error occurred updating $($templateObject.Name) template data`n$_"
                        }
                    }
                }
                #endregion Binding data
                #-------------------------------------------------------------------------------


                #-------------------------------------------------------------------------------
                #region Set display properties
                $defaultDisplaySet = 'Type', 'Name', 'Destination'
                $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet', [string[]]$defaultDisplaySet)
                $PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
                $templateObject | Add-Member MemberSet PSStandardMembers $PSStandardMembers

                #endregion Set display properties
                #-------------------------------------------------------------------------------

                #TODO: There is probably a better way to do this
                # if no parameters are set
                if ((-not ($PSBoundParameters.ContainsKey('Type'))) -and
                    (-not ($PSBoundParameters.ContainsKey('Name')))) {
                        $templateObject | Write-Output
                # if both are set and they match the object
                } elseif (($PSBoundParameters.ContainsKey('Type')) -and
                          ($PSBoundParameters.ContainsKey('Name'))) {
                    if (($templateObject.Type -like $Type) -and
                        ($templateObject.Name -like $Name)) {
                            $templateObject | Write-Output
                    }
                # if Type is set and it matches the object
                } elseif ($PSBoundParameters.ContainsKey('Type')) {
                    if ($templateObject.Type -like $Type) {
                        $templateObject | Write-Output
                    }
                # if Name is set and it matches the object
                } elseif ($PSBoundParameters.ContainsKey('Name')) {
                    if ($templateObject.Name -like $Name) {
                        $templateObject | Write-Output
                    }
                }
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Template\Get-StitchTemplate.ps1' 136
#Region '.\public\Tests\ConvertFrom-NUnit.ps1' -1


function ConvertFrom-NUnit {
    <#
    .SYNOPSIS
        Convert data in NUnit XML format into a test result object
    #>

    [CmdletBinding(
        DefaultParameterSetName = 'asXml'
    )]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
            ParameterSetName = 'asFile',
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path,

        # The xml content in NUnit format
        [Parameter(
            ParameterSetName = 'asXml',
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [xml]$Xml

    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if ($PSCmdlet.ParameterSetName -like 'asFile') {
            [xml]$Xml = Get-Content $Path
        }

        # --------------------------------------------------------------------------------
        # #region Confirm format

        if ($null -eq $Xml.'test-results') {
            throw 'Content does not contain Test Results'
        }
        $testResults = $Xml.'test-results'
        $environment = $testResults.environment
        if ($null -eq $environment) {
            throw 'No environment information found in result'
        }


        #! Pester puts the entire result output into the 'test-results' node,
        #! then all of the tests are under the 'test-suite'.results
        #! finally, each file is added as a 'test-suite' nodes
        $resultsNode = $Xml.'test-results'.'test-suite'.results
        if ($null -eq $resultsNode) {
            throw 'No results node found in content'
        }
        $fileNodes = $resultsNode.SelectNodes('test-suite')
        if ($null -eq $fileNodes) {
            throw 'No test suites found within result'
        }

        # #endregion Confirm format
        # --------------------------------------------------------------------------------

        # --------------------------------------------------------------------------------
        # #region Environment info

        $runId = New-Guid

        $testRunDirectory = $environment.cwd
        $user = (@($environment.'user-domain' , $environment.user) -join '\')
        $machine = $environment.'machine-name'
        $TimeStamp = (Get-Date (@(
                    $testResults.date,
                    $testResults.time
                ) -join ' '))

        # #endregion Environment info
        # --------------------------------------------------------------------------------


        foreach ($fileNode in $fileNodes) {
            $fullPath = $fileNode.name
            $relativePath = [System.IO.Path]::GetRelativePath( $testRunDirectory, $fullPath)
            $fileResult = $fileNode.result

            $testCases = $fileNode.SelectNodes('//test-case')

            foreach ($testCase in $testCases) {
                $testInfo = @{
                    RunId = $runId
                    Timestamp = $TimeStamp
                    File = $relativePath
                    Name = $testCase.description
                    TestPath = $testCase.name
                    Executed = $testCase.executed
                    Result = $testCase.result
                    Time = $testCase.time
                }
                [PSCustomObject]$testInfo
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Tests\ConvertFrom-NUnit.ps1' 110
#Region '.\public\Tests\ConvertFrom-PesterTestResult.ps1' -1


function ConvertFrom-PesterTestResult {
    <#
    .SYNOPSIS
        Convert a Pester Test Result object to a Stitch.TestResultInfo
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
            ParameterSetName = 'asPath',
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path,

        # CliXml content
        [Parameter(
            ParameterSetName = 'asXml',
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [xml]$Xml,

        # The output of Invoke-Pester -PassThru
        [Parameter(
            ParameterSetName = 'asObject',
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [Pester.Run]$Results

    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if ($PSBoundParameters.ContainsKey('Path')) {
            if ($Path | Test-Path) {
                $testResult = Import-Clixml $Path
            } else {
                throw "$Path is not a valid path"
            }
        } elseif ($PSBoundParameters.ContainsKey('Xml')) {
            try {
                $testResult = [System.Management.Automation.PSSerializer]::Deserialize( $Xml )
            } catch {
                throw "Could not import XML content`n$_"
            }
        } elseif ($PSBoundParameters.ContainsKey('Results')) {
            $testResult = $Results
        } else {
            throw "No content was given to convert"
        }


        if ($null -eq $testResult) {
            throw 'No containers found in test result'
        }
        if ($null -eq $testResult.Containers) {
            throw 'No containers found in test result'
        }
        if ($null -eq $testResult.Tests) {
            throw 'No tests found in test result'
        }

        <#------------------------------------------------------------------
          All checks completed, we should have a usable object now
        ------------------------------------------------------------------#>

        $files = $testResult.Containers

        foreach ($test in $testResult.Tests) {
            $currentFile = files
            | Where-Object {
                $_.Block -contains "[+] $($test.Path[0])"
            }
            if ($null -ne $currentFile) {
                if ($currentFile | Test-Path) {
                    $checkpoint = Checkpoint-File $currentFile
                }

            }
        }

    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Tests\ConvertFrom-PesterTestResult.ps1' 94
#Region '.\public\Tests\Initialize-TestDatabase.ps1' -1


function Initialize-TestDatabase {
    <#
    .SYNOPSIS
        Create a new database for storing Pester test data
    #>

    [CmdletBinding()]
    param(
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {

    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Tests\Initialize-TestDatabase.ps1' 20
#Region '.\public\Tests\New-TestDataDirectory.ps1' -1


function New-TestDataDirectory {
    <#
    .SYNOPSIS
        Create a standard directory for test data
    #>

    [CmdletBinding()]
    param(
        # Specifies a path to one or more locations.
        [Parameter(
        Position = 0,
        ValueFromPipeline,
        ValueFromPipelineByPropertyName
        )]
        [Alias('PSPath')]
        [string]$Path,

        # Return the new data directory
        [Parameter(
        )]
        [switch]$PassThru
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        $directory = $Path | Split-Path
        $newName = $Path | Split-Path -LeafBase
        $newName = $newName -replace 'Tests$', 'Data'

        $dataDirectory = (Join-Path $directory $newName)
        if (-not($dataDirectory | Test-Path)) {
            $dir = mkdir $dataDirectory -Force
            if ($PassThru) {
                $dir
            }
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Tests\New-TestDataDirectory.ps1' 43
#Region '.\public\Tests\Save-TestResult.ps1' -1


function Save-TestResult {
    <#
    .SYNOPSIS
        Save the Pester test results to the database
    #>

    [CmdletBinding()]
    param(
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {

        #TODO(Tests): Write the test info to the database
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\Tests\Save-TestResult.ps1' 21
#Region '.\public\VSCode\Get-CurrentEditorFile.ps1' -1

function Get-CurrentEditorFile {
    [CmdletBinding()]
    param(
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
    process {
        if ($null -ne $psEditor) {
            $psEditor.GetEditorContext().CurrentFile
        }
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\VSCode\Get-CurrentEditorFile.ps1' 17
#Region '.\public\VSCode\Get-VSCodeSetting.ps1' -1

function Get-VSCodeSetting {
    [CmdletBinding()]
    param(
        # The name of the setting to return
        [Parameter(
            Position = 0
        )]
        [string]$Name,

        # Treat the Name as a regular expression
        [Parameter(
        )]
        [switch]$Regex
    )
    begin {
        Write-Debug "`n$('-' * 80)`n-- Begin $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
        $settingsFile = "$env:APPDATA\Code\User\settings.json"
    }
    process {
        if (Test-Path $settingsFile) {
            Write-Debug "Loading the settings file"
            $settings = Get-Content $settingsFile | ConvertFrom-Json -Depth 16 -AsHashtable
        }

        if ($PSBoundParameters.ContainsKey('Name')) {
            if ($Regex) {
                Write-Debug "Looking for settings that match $Name"
                $matchedKeys = $settings.Keys | Where-Object { $_ -match $Name }
            } else {
                Write-Debug "Looking for settings that are like $Name"
                $matchedKeys = $settings.Keys | Where-Object { $_ -like $Name }
            }
            if ($matchedKeys.Count -gt 0) {
                Write-Debug "Found $($matchedKeys.Count) settings"
                $settingsSubSet = @{}
                foreach ($matchedKey in $matchedKeys) {
                    $settingsSubSet[$matchedKey] = $settings[$matchedKey]
                }
                Write-Debug "Creating settings subset"
                $settings = $settingsSubSet
            }
        }

        $settings['PSTypeName'] = 'VSCode.SettingsInfo'
        [PSCustomObject]$settings | Write-Output
    }
    end {
        Write-Debug "`n$('-' * 80)`n-- End $($MyInvocation.MyCommand.Name)`n$('-' * 80)"
    }
}
#EndRegion '.\public\VSCode\Get-VSCodeSetting.ps1' 51
#Region '.\suffix.ps1' -1

Set-Alias -Name Import-BuildScript -Value "$PSScriptRoot\Import-BuildScript.ps1"
Set-Alias -Name Import-TaskFile -Value "$PSScriptRoot\Import-TaskFile.ps1"
#EndRegion '.\suffix.ps1' 3