enosix.SfdxUtil.psm1

#Region '.\Private\checkResponse.ps1' 0
function checkResponse {
    process {
        Write-Verbose ($_ | ConvertTo-Json -Depth 10)
        if (0 -eq $_.status) {
            # Workaround for oddity that result is that sometimes an array with a single element
            if ($_.result -is [array]) {
                $_.result = $_.result[0]
            }
            return $_
        }
        throwResponse "Failure Response" $_
    }
}
#EndRegion '.\Private\checkResponse.ps1' 14
#Region '.\Private\throwResponse.ps1' 0
function throwResponse ($message, $response) {
    throw "${message}: $(${response}|ConvertTo-Json -Depth 10)"
}
#EndRegion '.\Private\throwResponse.ps1' 4
#Region '.\Public\00-sfdx.ps1' 0
# sfdx wrapper function
$sfdxCmd = Get-Command sfdx | Where-Object { $_.CommandType -eq 'Application' }

function sfdx {
  [CmdletBinding(SupportsShouldProcess = $true)]
  param(
    [Parameter(Mandatory = $false,
            ValueFromRemainingArguments = $true,
            ValueFromPipeline = $true,
            ValueFromPipelineByPropertyName = $true,
            HelpMessage = "arguments for sfdx")]
    [String[]]
    $argsForSfdx
  )

  if ($PSCmdlet.ShouldProcess("$sfdxCmd $argsForSfdx")) {
    Write-Verbose "Running: $sfdxCmd $argsForSfdx"
    $result = & $sfdxCmd @argsForSfdx
    Write-Verbose "Finished: $sfdxCmd $argsForSfdx"
    if ($LASTEXITCODE) {
      Write-Verbose "result: $result"
      throw "[sfdx exit code] $LASTEXITCODE"
    }
    return $result
  }
}
#EndRegion '.\Public\00-sfdx.ps1' 27
#Region '.\Public\Build-OctopusPackage.ps1' 0
function Build-OctopusPackage
{
    [CmdletBinding(PositionalBinding = $false, SupportsShouldProcess = $true)]
    param (
        [Parameter(ValueFromPipeline = $false)]
        [Alias("u")]
        [string]$githubUrl,

        [Parameter(ValueFromPipeline = $false)]
        [Alias("b")]
        [string]$pkgBranch,

        [Parameter(ValueFromPipeline = $false)]
        [Alias("s")]
        [Switch]$skipCodeCoverage,

        [Parameter(Mandatory = $true, ValueFromPipeline = $false)]
        [Alias("p")]
        [string[]] $packages,

        [Parameter(Mandatory = $true, ValueFromRemainingArguments)]
        [string[]] $items
    )
    
    rm -rf "./out"
    mkdir "./out"

    if (Test-Path "./bin/deployment/Deploy.ps1" -Type Leaf)
    {
        cp "./bin/deployment/Deploy.ps1" "./out"
    }

    if (Test-Path "./bin/deployment/DeployFailed.ps1" -Type Leaf)
    {
        cp "./bin/deployment/DeployFailed.ps1" "./out"
    }

    if (Test-Path "./bin/deployment/PreDeploy.ps1" -Type Leaf)
    {
        cp "./bin/deployment/PreDeploy.ps1" "./out"
    }

    if (Test-Path "./bin/deployment/PostDeploy.ps1" -Type Leaf)
    {
        cp "./bin/deployment/PostDeploy.ps1" "./out"
    }

    if ($items -and $items.count -gt 0)
    {
        Copy-Item -Path $items -Destination "./out" -Recurse
    }

    $work = Get-Location

    Set-Location "./out/"

    $packageDetail = ConvertTo-Json @($packages | ForEach-Object {
        $report = sfdx force:package:version:report --package $_  --verbose --json | ConvertFrom-Json
        if (!$skipCodeCoverage -and !$report.result.HasPassedCodeCoverageCheck)
        {
            throw "Code Coverage Failed: " + ($report.result.CodeCoverage | ConvertTo-Json)
        }

        return $report.result | Select-Object -Property @{ l = "Id"; e = { $_.SubscriberPackageVersionId } }, Package2Id, Tag, Branch, AncestorId, Version, AncestorVersion
    })

    Write-Output @"
{
    "GitHub": "$githubUrl",
    "Branch": "$pkgBranch",
    "Packages": $packageDetail
}
"@
 > "./out/package.json"

    Set-Location $work
}
#EndRegion '.\Public\Build-OctopusPackage.ps1' 77
#Region '.\Public\Build-SFPackage.ps1' 0
function Build-SFPackage
{
    [CmdletBinding(PositionalBinding = $false, SupportsShouldProcess = $true)]
    param (
        [Parameter(Mandatory = $true, Position = 0)]
        [string] $path,

        [Alias("t")]
        [string]$pkgTag,

        [Alias("b")]
        [string]$pkgBranch,

        [Alias("v")]
        [string]$pkgVersionName,

        [Alias("r")]
        [string]$reqId,

        [Parameter(Mandatory = $true)]
        [Alias("p")]
        [string]$pkgPath,

        [Alias("w")]
        [int] $wait
    )

    $path = Resolve-Path -Path $path
    $work = Get-Location
    Set-Location "$path"
    $result = $null

    if (-Not$pkgVersionName)
    {
        $pkgVersionName = $pkgBranch
    }

    if (-Not$pkgTag)
    {
        $pkgTag = $pkgVersionName
    }

    if (-Not$reqId)
    {
        $sgpId = Get-2GPId "$path/sfdx-project.json" -p $pkgPath
        Write-Output "::set-output name=sgp_id::$sgpId"

        $reqId = Create-Package $sgpId -t $pkgTag -b $pkgBranch -v $pkgVersionName
    }

    if ($wait)
    {
        $waitUntil = $((Get-Date).AddMinutes($wait) ).Ticks
        while ($null -eq $result -and ((Get-Date).Ticks -lt $waitUntil))
        {
            try
            {
                $result = Get-SubscriberPackageVersionId $reqId
            }
            catch [System.Threading.Tasks.TaskCanceledException]
            {
                $reqId = Create-Package $sgpId -t $pkgTag -b $pkgBranch -v $pkgVersionName
            }
            Start-Sleep -Seconds 20
        }
        if (-not$result)
        {
            throw "Timeout"
        }

        $version = Get-PackageVersionId $result
        Write-Output "::set-output name=package_version::$version"
        Write-Output "::set-output name=package_id::$result"
    }
    Write-Output "::set-output name=request_id::$reqId"

    Set-Location $work

}
#EndRegion '.\Public\Build-SFPackage.ps1' 80
#Region '.\Public\Create-Package.ps1' 0
function Create-Package
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$pkgId,

        [Alias("t")]
        [string]$pkgTag = "develop",

        [Alias("b")]
        [string]$pkgBranch,

        [Alias("v")]
        [string]$pkgVersionName
    )

    $retry = $true
    while ($retry) {
        $createRequest = sfdx force:package:version:create --installationkeybypass --codecoverage --package $pkgId --tag $pkgTag --branch $pkgBranch --versionname $pkgVersionName --json | ConvertFrom-Json -AsHashtable | checkResponse
        $retry = $false

        # Package has only "try again" errors?
        if (("Error" -eq $createRequest.result.Status)) {
            if ($createRequest.result.Error) {
                $nonTryAgainErrors = $createRequest.result.Error | Where-Object { $_ -notlike "*try again*" }
                if ($nonTryAgainErrors) {
                    throwResponse "Has unrecoverable errors" $createRequest
                }
                else {
                    # Try Again
                    $retry = $true
                }
            }
            else {
                throwResponse "Error with no detail" $createRequest
            }
        }
    }

    $createRequest.result.Id
}
#EndRegion '.\Public\Create-Package.ps1' 43
#Region '.\Public\Create-ScratchOrg.ps1' 0
function Create-ScratchOrg {
    [CmdletBinding()]
    param(
        [string]$file,

        [Alias("a")]
        [string]$alias,


        [Alias("d")]
        [int]$duration = 7
    )

    if ($alias){
        sfdx force:org:create --definitionfile $file --setdefaultusername --setalias $alias --durationdays $duration
    }else{
        sfdx force:org:create --definitionfile $file --setdefaultusername --durationdays $duration
    }

}
#EndRegion '.\Public\Create-ScratchOrg.ps1' 21
#Region '.\Public\Delete-ScratchOrg.ps1' 0
function Delete-ScratchOrg {
    [CmdletBinding()]
    param( $alias )

    sfdx force:org:delete --noprompt --targetusername $alias
}
#EndRegion '.\Public\Delete-ScratchOrg.ps1' 7
#Region '.\Public\Get-2GPId.ps1' 0
function Get-2GPId {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$projFile,

        [Alias("p")]
        [string]$pkgPath
    )

    $pkgList = ( Get-Content -Path $projFile | ConvertFrom-Json ).packageDirectories

    if ($pkgPath) {
        $pkgList = $pkgList | Where-Object { $_.path -eq $pkgPath }
    }
    $pkg = $pkgList | Select-Object -Last 1

    if (-Not $pkg) {
        throw "ERROR: Unable to find package with path `"$pkgPath`" in `"$projFile`"."
    }
    if (-Not $pkg.package) {
        throw "ERROR: No 2gp ID for package with path `"$pkgPath`" in `"$projFile`"."
    }

    $pkg.package
}
#EndRegion '.\Public\Get-2GPId.ps1' 27
#Region '.\Public\Get-PackageVersionId.ps1' 0
function Get-PackageVersionId
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$pkgId
    )

    $request =  sfdx force:package:version:report --package $pkgId --json | ConvertFrom-Json

    if ($request.result.Version)
    {
        return $request.result.Version
    }

    return $null
}
#EndRegion '.\Public\Get-PackageVersionId.ps1' 18
#Region '.\Public\Get-SubscriberPackageVersionId.ps1' 0
function Get-SubscriberPackageVersionId {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$pkgId,

        [Alias("t")]
        [string]$pkgTag,

        [Alias("b")]
        [string]$pkgBranch
    )

    $pkgList = (sfdx force:package:version:list --json --verbose --packages $pkgId --orderby CreatedDate | ConvertFrom-Json).result

    if ($pkgTag) {
        $pkgList = $pkgList | Where-Object { $_.Tag -eq $pkgTag }
    }

    if ($pkgBranch) {
        $pkgList = $pkgList | Where-Object { $_.Branch -eq $pkgBranch }
    }

    $pkg = $($pkgList | Select-Object -Last 1)

    if ($pkg) {
        return $pkg.SubscriberPackageVersionId
    }else{
        return $null
    }
}
#EndRegion '.\Public\Get-SubscriberPackageVersionId.ps1' 32
#Region '.\Public\Get-SubscriberPackageVersionIdFromRequest.ps1' 0
function Get-SubscriberPackageVersionIdFromRequest
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$requestId
    )

    $request = sfdx force:package:version:create:report --packagecreaterequestid $requestId --json | ConvertFrom-Json -AsHashtable | checkResponse

    if ($request.result.SubscriberPackageVersionId) {
        return $request.result.SubscriberPackageVersionId
    }

    # Package has only "try again" errors?
    if (("Error" -eq $request.result.Status)) {
        if ($request.result.Error) {
            $nonTryAgainErrors = $request.result.Error | Where-Object { $_ -notlike "*try again*" -and $_ -notlike "*unexpected error*" -and $_ -notlike "*Internal Salesforce Error*" }
            if ($nonTryAgainErrors) {
                throwResponse "Has unrecoverable errors" $request
            }
            else{
                throw [System.Threading.Tasks.TaskCanceledException]::New('Has recoverable errors. Please try again.')
            }
        }
        else {
            throwResponse "Error with no detail" $request
        }
    }
    #TODO: Log this
    #$errors = sfdx force:data:soql:query -u devhub -t -q ("SELECT Message FROM Package2VersionCreateRequestError WHERE ParentRequest.Id in ({0}{1}{0})" -f [char]39, $createRequest.result.Id)

    return $null
}
#EndRegion '.\Public\Get-SubscriberPackageVersionIdFromRequest.ps1' 35
#Region '.\Public\Grant-PermSet.ps1' 0
function Grant-PermSet {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$permSet
    )

    sfdx force:user:permset:assign --permsetname $permSet
}
#EndRegion '.\Public\Grant-PermSet.ps1' 10
#Region '.\Public\Import-DataPlan.ps1' 0
function Import-DataPlan {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$file
    )

    sfdx force:data:tree:import --plan $file
}
#EndRegion '.\Public\Import-DataPlan.ps1' 10
#Region '.\Public\Import-Record.ps1' 0
function Import-Record {
    [CmdletBinding()]
    param(
        [Alias("k")]
        [string]$key,

        [Alias("v")]
        [string]$value
    )

    sfdx force:data:record:create --sobjecttype $key --values $value
}
#EndRegion '.\Public\Import-Record.ps1' 13
#Region '.\Public\Import-Records.ps1' 0
function Import-Records {
    [CmdletBinding()]
    param( $file )

    $records = [ordered]@{}
    (Get-Content $file | ConvertFrom-Json).psobject.properties | ForEach-Object { $records[$_.Name] = $_.Value }

    #$records | Write-Output

    foreach ($recordType in $records.Keys) {
        #$recordType | Write-Output
        $entries = $records[$recordType]

        foreach ($record in $entries) {

            $values = @{}
            $record.psobject.properties | ForEach-Object { $values[$_.Name] = $_.Value }


            $valuesString = [system.String]::Join(" ", ($values.GetEnumerator()  | % {
                If ($_.Value.GetType() -eq [String]) {
                    "$($_.Name)='$($_.Value)'"
                }
                elseif ($_.Value.GetType() -eq [bool]) {
                    "$($_.Name)=$($_.Value.ToString().ToLowerInvariant())"
                }
                Else {
                    "$($_.Name)=$($_.Value)"
                }
            }) )

            #$valuesString | Write-Output

            sfdx force:data:record:create --sobjecttype $recordType --values "`"$valuesString`""
        }

    }
}
#EndRegion '.\Public\Import-Records.ps1' 39
#Region '.\Public\Install-FromJson.ps1' 0
function Install-FromJson {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$path,

        [Alias("s")]
        [string[]] $skip ,

        [Alias("w")]
        [int]$wait = 30
    )

    $pkg = Get-Content $path | ConvertFrom-Json -AsHashtable;

    foreach($item in $pkg.Packages) {
        if (!$skip -or (!$skip.Contains($item.Package2Id) -and !$skip.Contains($item.Id))) {
            Install-Package $item.Id -w $wait
        }
    }

    Write-Output (Get-Content $path)
}
#EndRegion '.\Public\Install-FromJson.ps1' 24
#Region '.\Public\Install-Package.ps1' 0
function Install-Package {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$package,

        [Alias("w")]
        [int]$wait = 30
    )

    sfdx force:package:install --package $package --wait $wait --publishwait $wait  --json | ConvertFrom-Json -outVariable results

    Write-Output $results
}
#EndRegion '.\Public\Install-Package.ps1' 15
#Region '.\Public\Invoke-Step.ps1' 0
function Invoke-Step (
        [Parameter(Mandatory = $true)]
        [string]$stepName,
        
        [Parameter(Mandatory = $true)]
        [scriptblock]$action
) {
    $stepsFile = ".completed-steps.json"
    $steps = @()
    if (Test-Path $stepsFile  -PathType Leaf) {
        $steps = Get-Content -Raw -Path $stepsFile | ConvertFrom-Json -NoEnumerate
    }

    if ($steps.Contains($stepName)) {
        Write-Debug "Skipping step $stepName"
    } else {
        Write-Debug "Running step $stepName"

        try {
            $result = & $action
        } catch {
            throw $_.Exception
        }

        $steps += $stepName;
    }

    $steps | ConvertTo-Json -AsArray | Set-Content -Path $stepsFile 
    return $result;
}
#EndRegion '.\Public\Invoke-Step.ps1' 31
#Region '.\Public\Push-Source.ps1' 0
function Push-Source {
    sfdx force:source:push
}
#EndRegion '.\Public\Push-Source.ps1' 4
#Region '.\Public\Rebuild-ScratchOrg.ps1' 0
function Rebuild-ScratchOrg
{
    [CmdletBinding(PositionalBinding = $false, SupportsShouldProcess = $true)]
    param (
    # Flag -a, -setalias set an sfdx alias for for the created scratch org
        [Parameter(Mandatory = $false,
                ValueFromPipeline = $true,
                ValueFromPipelineByPropertyName = $true,
                HelpMessage = "set an sfdx alias for for the created scratch org")]
        [Alias("a")]
        [string]
        $SetAlias,

        [Parameter(Mandatory = $false,
                ValueFromPipeline = $true,
                ValueFromPipelineByPropertyName = $true,
                HelpMessage = "set sfdx wait time in minutes")]
        [Alias("w")]
        [int]
        $Wait = 60,

        [Parameter(Mandatory = $false,
                ValueFromPipeline = $true,
                ValueFromPipelineByPropertyName = $true,
                HelpMessage = "path to rebuild-scratch-org configuration file - defaults to ./rebuild-scratch-org.json or {project-root}/config/rebuild-scratch-org-default.json")]
        [Alias("f")]
        [string]
        $RebuildScratchOrgConfigFile,

    # Flag -o, -open opens the scratch org when its complete
        [Parameter(Mandatory = $false,
                ValueFromPipeline = $true,
                ValueFromPipelineByPropertyName = $true,
                HelpMessage = "open the scratch org when its complete")]
        [Alias("o")]
        [switch]
        $Open,

        [Parameter(Mandatory = $false,
                ValueFromPipeline = $true,
                ValueFromPipelineByPropertyName = $true,
                HelpMessage = "open the scratch org when its complete to this navigation URL path")]
        [Alias("p")]
        [string]
        $OpenUrlPath,

        [Parameter(Mandatory = $false,
                ValueFromPipeline = $true,
                ValueFromPipelineByPropertyName = $true,
                HelpMessage = "duration of the scratch org (in days) (default:7, min:1, max:30)")]
        [Alias("d")]
        [String]
        $DurationDays,

        [Parameter(Mandatory = $false,
                ValueFromPipeline = $true,
                ValueFromPipelineByPropertyName = $true,
                HelpMessage = "username or alias for the dev hub org; overrides default dev hub org")]
        [Alias("v")]
        [String]
        $TargetDevhubUsername,

        [Parameter(Mandatory = $false,
                ValueFromPipeline = $true,
                ValueFromPipelineByPropertyName = $true,
                HelpMessage = "request release=Preview scratch org")]
        [Alias("r")]
        [switch]
        $PreviewReleaseScratchOrg,

        [Parameter(Mandatory = $false,
                ValueFromRemainingArguments = $true,
                ValueFromPipeline = $true,
                ValueFromPipelineByPropertyName = $true,
                HelpMessage = "Package Ids to install (04t)")]
        [String[]]
        $PackageIds
    )

    # "strict mode" best practice
    Set-StrictMode -Version Latest
    $ErrorActionPreference = "Stop"
    $VerbosePreference = "Continue"

    # Load configuration
    if (-Not$RebuildScratchOrgConfigFile)
    {
        if (Test-Path "./rebuild-scratch-org.json")
        {
            $RebuildScratchOrgConfigFile = "./rebuild-scratch-org.json"
        }
        else
        {
            $RebuildScratchOrgConfigFile = "$PSScriptRoot/../config/rebuild-scratch-org-default.json"
        }
    }

    $Config = @{ }
    Write-Verbose "Attempting to read rebuild-scratch-org configuration: $RebuildScratchOrgConfigFile"
    if (Test-Path $RebuildScratchOrgConfigFile)
    {
        (Get-Content $RebuildScratchOrgConfigFile | ConvertFrom-Json).psobject.properties | ForEach-Object { $Config[$_.Name] = $_.Value }
        $ConfigFolderPath = Split-Path -Parent (Resolve-Path $RebuildScratchOrgConfigFile)
    }
    else
    {
        Write-Verbose "Unable to read $RebuildScratchOrgConfigFile"
    }

    $ScratchOrgDefinitionFile = "$PSScriptRoot/../config/project-scratch-def.json"
    if ( $Config.ContainsKey("ScratchOrgDefinitionFile"))
    {
        $ScratchOrgDefinitionFile = Join-Path -Resolve $ConfigFolderPath $Config.ScratchOrgDefinitionFile
    }
    $ScratchOrgDefinitionFile = (Resolve-Path $ScratchOrgDefinitionFile).Path

    $DataRecordCreate = @{ }
    if ( $Config.ContainsKey("DataRecordCreate"))
    {
        $Config.DataRecordCreate.psobject.properties | ForEach-Object { $DataRecordCreate[$_.Name] = $_.Value }
    }

    $PermSets = @()
    if ( $Config.ContainsKey("PermSets"))
    {
        $PermSets = $Config.PermSets
    }

    $DataPlans = @()
    if ( $Config.ContainsKey("DataPlans"))
    {
        $DataPlans = $Config.DataPlans | ForEach-Object {
            Join-Path -Resolve $ConfigFolderPath $_
        }
    }

    $AnonApexFiles = @()
    if ( $Config.ContainsKey("AnonApexFiles"))
    {
        $AnonApexFiles = $Config.AnonApexFiles | ForEach-Object {
            Join-Path -Resolve $ConfigFolderPath $_
        }
    }

    $MdapiDeployBeforeSfdxSource = @()
    if ( $Config.ContainsKey("MdapiDeployBeforeSfdxSource"))
    {
        $MdapiDeployBeforeSfdxSource = $Config.MdapiDeployBeforeSfdxSource
    }

    $MdapiDeployAfterSfdxSource = @()
    if ( $Config.ContainsKey("MdapiDeployAfterSfdxSource"))
    {
        $MdapiDeployAfterSfdxSource = $Config.MdapiDeployAfterSfdxSource
    }

    # Only obtain PackageIds from config file when none are specified on the command line
    if (-Not$PackageIds)
    {
        if ( $Config.ContainsKey("PackageIds"))
        {
            $PackageIds = $Config.PackageIds
        }
    }

    # Validate params
    if ($PackageIds)
    {
        $InvalidPackageIds = $PackageIds | ForEach-Object { @($_ -split ':')[0] } | Where-Object { -not($_.StartsWith('04t') -and (@(15, 18) -contains $_.Length)) }
        if ($InvalidPackageIds)
        {
            throw "ERROR: Unrecognized Package Ids [$InvalidPackageIds] must begin with 04t and have a length of 15 or 18 characters."
        }
    }

    # sfdx wrapper function
    $sfdxCmd = Get-Command sfdx | Where-Object { $_.CommandType -eq 'Application' }
    function sfdx
    {
        [CmdletBinding(SupportsShouldProcess = $true)]
        param(
            [Parameter(Mandatory = $false,
                    ValueFromRemainingArguments = $true,
                    ValueFromPipeline = $true,
                    ValueFromPipelineByPropertyName = $true,
                    HelpMessage = "arguments for sfdx")]
            [String[]]
            $argsForSfdx
        )
        if ( $PSCmdlet.ShouldProcess("$sfdxCmd $argsForSfdx"))
        {
            Write-Verbose "Running: $sfdxCmd $argsForSfdx"
            & $sfdxCmd @argsForSfdx
            Write-Verbose "Finished: $sfdxCmd $argsForSfdx"
            if ($LASTEXITCODE)
            {
                throw "[sfdx exit code] $LASTEXITCODE"
            }
        }
    }

    # Spin up fresh scratch org
    if ($ScratchOrgDefinitionFile)
    {
        $sfdxArgs = @('--wait', $Wait, '--definitionfile', $ScratchOrgDefinitionFile, '--setdefaultusername')
        if ($SetAlias)
        {
            $sfdxArgs += @("description=$SetAlias", '--setalias', $SetAlias)
        }
        if ($DurationDays)
        {
            $sfdxArgs += @('--durationdays', $DurationDays)
        }
        if ($TargetDevhubUsername)
        {
            $sfdxArgs += @('--targetdevhubusername', $TargetDevhubUsername)
        }
        if ($PreviewReleaseScratchOrg)
        {
            $sfdxArgs += @('release=Preview')
        }
        sfdx force:org:create @sfdxArgs
    }

    # Install packages
    if ($PackageIds)
    {
        $PackageIds | ForEach-Object {
            $id, $key = $_ -split ':'
            $sfdxArgs = @('--noprompt', '--publishwait', $Wait, '--wait', $Wait, '--package', $id)
            if ($key)
            {
                $key = $key -join ':'
                $sfdxArgs += @('--installationkey', $key)
            }
            sfdx force:package:install @sfdxArgs
        }

        # "sfdx force:package:installed:list" caused build flakiness with non-overridable 2-minute
        # timeout with message: "ERROR: Your query request was running for too long."
        # Might be able to re-enable if it gains support for a "--wait" parameter
        ## Display installed package versions
        #sfdx force:package:installed:list

    }

    # Push mdapi source
    if ($MdapiDeployBeforeSfdxSource)
    {
        $MdapiDeployBeforeSfdxSource | ForEach-Object {
            sfdx force:mdapi:deploy --wait $Wait --deploydir $_
        }
    }

    # Push sfdx source
    sfdx force:source:push --forceoverwrite

    # Push mdapi source
    if ($MdapiDeployAfterSfdxSource)
    {
        $MdapiDeployAfterSfdxSource | ForEach-Object {
            sfdx force:mdapi:deploy --wait $Wait --deploydir $_
        }
    }

    # Configure logging and connection settings
    if ($DataRecordCreate)
    {
        $DataRecordCreate.Keys | Sort-Object | ForEach-Object {
            sfdx force:data:record:create --sobjecttype $_ --values $DataRecordCreate[$_]
        }
    }

    # Assign permission sets per your development / testing needs
    if ($PermSets)
    {
        $PermSets | ForEach-Object {
            sfdx force:user:permset:assign --permsetname $_
        }
    }

    # Load sample data (Accounts, Wearables Products, a Wearables Price Book, and Pricebook Entries)
    # that corresponds to the `enosix_sap_*` SAP connections.
    if ($DataPlans)
    {
        $DataPlans | ForEach-Object {
            sfdx force:data:tree:import --plan $_
        }
    }
    if ($AnonApexFiles)
    {
        $AnonApexFiles | ForEach-Object {
            sfdx force:apex:execute --apexcodefile $_
        }
    }

    if ($OpenUrlPath)
    {
        sfdx force:org:open --path $OpenUrlPath
    }
    elseif ($Open)
    {
        sfdx force:org:open
    }

    Write-Verbose "$SetAlias Scratch org spin cycle complete!"
}
#EndRegion '.\Public\Rebuild-ScratchOrg.ps1' 308
#Region '.\Public\Run-Tests.ps1' 0
function Run-Tests {
    [CmdletBinding()]
    param( [bool]$throwIfFailed = $false )

    sfdx force:apex:test:run --wait "$WAIT" --resultformat json | ConvertFrom-Json -outVariable testResults

    If ($testResults.result.summary.failing -gt 0 -And $throwIfFailed) {
        throw "Failed tests: $($testResults.result.summary.failing)"
    }

    $testResults
}
#EndRegion '.\Public\Run-Tests.ps1' 13
#Region '.\Public\Sort-CustomLabels.ps1' 0
function Sort-CustomLabels
{
    param (
        $customLabelsPath
    )

    # "strict mode" best practice
    Set-StrictMode -Version Latest
    $ErrorActionPreference = "Stop"

    # Set a default value for $customLabelsPath if it was not passed in as parameter on the command line
    if (-Not$customLabelsPath)
    {
        $projectRoot = (Resolve-Path -LiteralPath "$PSScriptRoot/..").Path
        $customLabelsPath = "$projectRoot/ensxapp/force-app/main/default/labels/CustomLabels.labels-meta.xml"
    }
    if (-Not(Test-Path $customLabelsPath))
    {
        throw "CustomLabels file does not exist: $customLabelsPath"
    }

    # Load CustomLabels xml, uniquely sort nodes by fullName, and save
    $xmlDoc = [xml](Get-Content $customLabelsPath)
    $xmlDoc.CustomLabels.labels | ForEach-Object {
        $null = $xmlDoc.CustomLabels.RemoveChild($_)
        Write-Output $_
    } | Sort-Object -Unique { $_.fullName } | ForEach-Object {
        $null = $xmlDoc.CustomLabels.AppendChild($_)
    }
    $xmlDoc.Save($customLabelsPath)
    "Sorting complete: $customLabelsPath"
}
#EndRegion '.\Public\Sort-CustomLabels.ps1' 33
#Region '.\Public\Update-Package.ps1' 0
function Update-Package
{
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$pkgId,

        [Alias("t")]
        [string]$pkgTag = "develop",

        [Alias("b")]
        [string]$pkgBranch,

        [Alias("v")]
        [string]$pkgVersionName
    )

    sfdx force:package:version:update --installationkeybypass --package $pkgId --tag $pkgTag --branch $pkgBranch --versionname $pkgVersionName --json | ConvertFrom-Json
}
#EndRegion '.\Public\Update-Package.ps1' 20