diff --git a/Actions/.Modules/ReadSettings.psm1 b/Actions/.Modules/ReadSettings.psm1 new file mode 100644 index 000000000..27aba1e1e --- /dev/null +++ b/Actions/.Modules/ReadSettings.psm1 @@ -0,0 +1,475 @@ +$ALGoFolderName = '.AL-Go' +$ALGoSettingsFile = Join-Path '.AL-Go' 'settings.json' +$RepoSettingsFile = Join-Path '.github' 'AL-Go-Settings.json' +$CustomTemplateRepoSettingsFile = Join-Path '.github' 'AL-Go-TemplateRepoSettings.doNotEdit.json' +$CustomTemplateProjectSettingsFile = Join-Path '.github' 'AL-Go-TemplateProjectSettings.doNotEdit.json' + +function MergeCustomObjectIntoOrderedDictionary { + Param( + [System.Collections.Specialized.OrderedDictionary] $dst, + [PSCustomObject] $src + ) + + # Loop through all properties in the source object + # If the property does not exist in the destination object, add it with the right type, but no value + # Types supported: PSCustomObject, Object[] and simple types + $src.PSObject.Properties.GetEnumerator() | ForEach-Object { + $prop = $_.Name + $srcProp = $src."$prop" + $srcPropType = $srcProp.GetType().Name + if (-not $dst.Contains($prop)) { + if ($srcPropType -eq "PSCustomObject") { + $dst.Add("$prop", [ordered]@{}) + } + elseif ($srcPropType -eq "Object[]") { + $dst.Add("$prop", @()) + } + else { + $dst.Add("$prop", $srcProp) + } + } + } + + # Loop through all properties in the destination object + # If the property does not exist in the source object, do nothing + # If the property exists in the source object, but is of a different type, throw an error + # If the property exists in the source object: + # If the property is an Object, call this function recursively to merge values + # If the property is an Object[], merge the arrays + # If the property is a simple type, replace the value in the destination object with the value from the source object + @($dst.Keys) | ForEach-Object { + $prop = $_ + if ($src.PSObject.Properties.Name -eq $prop) { + $dstProp = $dst."$prop" + $srcProp = $src."$prop" + $dstPropType = $dstProp.GetType().Name + $srcPropType = $srcProp.GetType().Name + if ($srcPropType -eq "PSCustomObject" -and $dstPropType -eq "OrderedDictionary") { + MergeCustomObjectIntoOrderedDictionary -dst $dst."$prop" -src $srcProp + } + elseif ($dstPropType -ne $srcPropType -and !($srcPropType -eq "Int64" -and $dstPropType -eq "Int32")) { + # Under Linux, the Int fields read from the .json file will be Int64, while the settings defaults will be Int32 + # This is not seen as an error and will not throw an error + throw "property $prop should be of type $dstPropType, is $srcPropType." + } + else { + if ($srcProp -is [Object[]]) { + $srcProp | ForEach-Object { + $srcElm = $_ + $srcElmType = $srcElm.GetType().Name + if ($srcElmType -eq "PSCustomObject") { + # Array of objects are not checked for uniqueness + $ht = [ordered]@{} + $srcElm.PSObject.Properties | Sort-Object -Property Name -Culture ([cultureinfo]::InvariantCulture) | ForEach-Object { + $ht[$_.Name] = $_.Value + } + $dst."$prop" += @($ht) + } + else { + # Add source element to destination array, but only if it does not already exist + $dst."$prop" = @($dst."$prop" + $srcElm | Select-Object -Unique) + } + } + } + else { + $dst."$prop" = $srcProp + } + } + } + } +} + +function GetDefaultSettings +( + [string] $repoName +) +{ + return [ordered]@{ + "type" = "PTE" + "unusedALGoSystemFiles" = @() + "projects" = @() + "powerPlatformSolutionFolder" = "" + "country" = "us" + "artifact" = "" + "companyName" = "" + "repoVersion" = "1.0" + "repoName" = "$repoName" + "versioningStrategy" = 0 + "runNumberOffset" = 0 + "appBuild" = 0 + "appRevision" = 0 + "keyVaultName" = "" + "licenseFileUrlSecretName" = "licenseFileUrl" + "ghTokenWorkflowSecretName" = "ghTokenWorkflow" + "adminCenterApiCredentialsSecretName" = "adminCenterApiCredentials" + "applicationInsightsConnectionStringSecretName" = "applicationInsightsConnectionString" + "keyVaultCertificateUrlSecretName" = "" + "keyVaultCertificatePasswordSecretName" = "" + "keyVaultClientIdSecretName" = "" + "keyVaultCodesignCertificateName" = "" + "codeSignCertificateUrlSecretName" = "codeSignCertificateUrl" + "codeSignCertificatePasswordSecretName" = "codeSignCertificatePassword" + "additionalCountries" = @() + "appDependencies" = @() + "projectName" = "" + "appFolders" = @() + "testDependencies" = @() + "testFolders" = @() + "bcptTestFolders" = @() + "pageScriptingTests" = @() + "restoreDatabases" = @() + "installApps" = @() + "installTestApps" = @() + "installOnlyReferencedApps" = $true + "generateDependencyArtifact" = $false + "skipUpgrade" = $false + "applicationDependency" = "18.0.0.0" + "updateDependencies" = $false + "installTestRunner" = $false + "installTestFramework" = $false + "installTestLibraries" = $false + "installPerformanceToolkit" = $false + "enableCodeCop" = $false + "enableUICop" = $false + "enableCodeAnalyzersOnTestApps" = $false + "customCodeCops" = @() + "failOn" = "error" + "treatTestFailuresAsWarnings" = $false + "rulesetFile" = "" + "enableExternalRulesets" = $false + "vsixFile" = "" + "assignPremiumPlan" = $false + "enableTaskScheduler" = $false + "doNotBuildTests" = $false + "doNotRunTests" = $false + "doNotRunBcptTests" = $false + "doNotRunPageScriptingTests" = $false + "doNotPublishApps" = $false + "doNotSignApps" = $false + "configPackages" = @() + "appSourceCopMandatoryAffixes" = @() + "deliverToAppSource" = [ordered]@{ + "mainAppFolder" = "" + "productId" = "" + "includeDependencies" = @() + "continuousDelivery" = $false + } + "obsoleteTagMinAllowedMajorMinor" = "" + "memoryLimit" = "" + "templateUrl" = "" + "templateSha" = "" + "templateBranch" = "" + "appDependencyProbingPaths" = @() + "useProjectDependencies" = $false + "runs-on" = "windows-latest" + "shell" = "" + "githubRunner" = "" + "githubRunnerShell" = "" + "cacheImageName" = "my" + "cacheKeepDays" = 3 + "alwaysBuildAllProjects" = $false + "incrementalBuilds" = [ordered]@{ + "onPush" = $false + "onPull_Request" = $true + "onSchedule" = $false + "retentionDays" = 30 + "mode" = "modifiedApps" # modifiedProjects, modifiedApps + } + "microsoftTelemetryConnectionString" = "InstrumentationKey=cd2cc63e-0f37-4968-b99a-532411a314b8;IngestionEndpoint=https://northeurope-2.in.applicationinsights.azure.com/" + "partnerTelemetryConnectionString" = "" + "sendExtendedTelemetryToMicrosoft" = $false + "environments" = @() + "buildModes" = @() + "useCompilerFolder" = $false + "pullRequestTrigger" = "pull_request_target" + "bcptThresholds" = [ordered]@{ + "DurationWarning" = 10 + "DurationError" = 25 + "NumberOfSqlStmtsWarning" = 5 + "NumberOfSqlStmtsError" = 10 + } + "fullBuildPatterns" = @() + "excludeEnvironments" = @() + "alDoc" = [ordered]@{ + "continuousDeployment" = $false + "deployToGitHubPages" = $true + "maxReleases" = 3 + "groupByProject" = $true + "includeProjects" = @() + "excludeProjects" = @() + "header" = "Documentation for {REPOSITORY} {VERSION}" + "footer" = "Documentation for {REPOSITORY} made with AL-Go for GitHub, ALDoc and DocFx" + "defaultIndexMD" = "## Reference documentation\n\nThis is the generated reference documentation for [{REPOSITORY}](https://github.com/{REPOSITORY}).\n\nYou can use the navigation bar at the top and the table of contents to the left to navigate your documentation.\n\nYou can change this content by creating/editing the **{INDEXTEMPLATERELATIVEPATH}** file in your repository or use the alDoc:defaultIndexMD setting in your repository settings file (.github/AL-Go-Settings.json)\n\n{RELEASENOTES}" + "defaultReleaseMD" = "## Release reference documentation\n\nThis is the generated reference documentation for [{REPOSITORY}](https://github.com/{REPOSITORY}).\n\nYou can use the navigation bar at the top and the table of contents to the left to navigate your documentation.\n\nYou can change this content by creating/editing the **{INDEXTEMPLATERELATIVEPATH}** file in your repository or use the alDoc:defaultReleaseMD setting in your repository settings file (.github/AL-Go-Settings.json)\n\n{RELEASENOTES}" + } + "trustMicrosoftNuGetFeeds" = $true + "nuGetFeedSelectMode" = "LatestMatching" + "commitOptions" = [ordered]@{ + "messageSuffix" = "" + "pullRequestAutoMerge" = $false + "pullRequestLabels" = @() + "createPullRequest" = $true + } + "trustedSigning" = [ordered]@{ + "Endpoint" = "" + "Account" = "" + "CertificateProfile" = "" + } + "useGitSubmodules" = "false" + "gitSubmodulesTokenSecretName" = "gitSubmodulesToken" + "shortLivedArtifactsRetentionDays" = 1 # 0 means use GitHub default + } +} + + +<# + .SYNOPSIS + Read settings from the settings files and merge them into an ordered dictionary. + .DESCRIPTION + This function reads settings from various files and merges them into an ordered dictionary. + The settings are read from the following files: + - ALGoOrgSettings (github Variable) = Organization settings variable + - .github/AL-Go-TemplateRepoSettings.doNotEdit.json = Repository settings from custom template + - .github/AL-Go-Settings.json = Repository Settings file + - ALGoRepoSettings (github Variable) = Repository settings variable + - .github/AL-Go-TemplateProjectSettings.doNotEdit.json = Project settings from custom template + - /.AL-Go/settings.json = Project settings file + - .github/.settings.json = Workflow settings file + - /.AL-Go/.settings.json = Project workflow settings file + - /.AL-Go/.settings.json = User settings file + .PARAMETER baseFolder + The base folder where the settings files are located. Default is $ENV:GITHUB_WORKSPACE when running in GitHub Actions. + .PARAMETER repoName + The name of the repository. Default is $ENV:GITHUB_REPOSITORY when running in GitHub Actions. + .PARAMETER project + The project path relative to the base folder. Default is '.' + .PARAMETER buildMode + The build mode to use when there are conditional settings. Default is "Default". + .PARAMETER workflowName + The name of the workflow. Default is $ENV:GITHUB_WORKFLOW when running in GitHub Actions. + .PARAMETER userName + The name of the user. Default is $ENV:GITHUB_ACTOR when running in GitHub Actions. + .PARAMETER branchName + The name of the branch to use for conditional settings. Default is $ENV:GITHUB_REF_NAME when running in GitHub Actions. + .PARAMETER orgSettingsVariableValue + The value of the organization settings variable. Default is $ENV:ALGoOrgSettings. + .PARAMETER repoSettingsVariableValue + The value of the repository settings variable. Default is $ENV:ALGoRepoSettings. + .PARAMETER silent + If specified, the function will not output any messages to the console. +#> +function ReadSettings { + Param( + [string] $baseFolder = "$ENV:GITHUB_WORKSPACE", + [string] $repoName = "$ENV:GITHUB_REPOSITORY", + [string] $project = '.', + [string] $buildMode = "Default", + [string] $workflowName = "$ENV:GITHUB_WORKFLOW", + [string] $userName = "$ENV:GITHUB_ACTOR", + [string] $branchName = "$ENV:GITHUB_REF_NAME", + [string] $orgSettingsVariableValue = "$ENV:ALGoOrgSettings", + [string] $repoSettingsVariableValue = "$ENV:ALGoRepoSettings", + [switch] $silent + ) + + # If the build is triggered by a pull request the refname will be the merge branch. To apply conditional settings we need to use the base branch + if (($env:GITHUB_EVENT_NAME -eq "pull_request") -and ($branchName -eq $ENV:GITHUB_REF_NAME)) { + $branchName = $env:GITHUB_BASE_REF + } + + function GetSettingsObject { + Param( + [string] $path + ) + + if (Test-Path $path) { + try { + if (!$silent.IsPresent) { Write-Host "Applying settings from $path" } + $settings = Get-Content $path -Encoding UTF8 | ConvertFrom-Json + if ($settings) { + return $settings + } + } + catch { + throw "Error reading $path. Error was $($_.Exception.Message).`n$($_.ScriptStackTrace)" + } + } + else { + if (!$silent.IsPresent) { Write-Host "No settings found in $path" } + } + return $null + } + + $repoName = $repoName.SubString("$repoName".LastIndexOf('/') + 1) + $githubFolder = Join-Path $baseFolder ".github" + $workflowName = $workflowName.Trim().Split([System.IO.Path]::getInvalidFileNameChars()) -join "" + + # Start with default settings + $settings = GetDefaultSettings -repoName $repoName + + # Read settings from files and merge them into the settings object + + $settingsObjects = @() + # Read settings from organization settings variable (parameter) + if ($orgSettingsVariableValue) { + $orgSettingsVariableObject = $orgSettingsVariableValue | ConvertFrom-Json + $settingsObjects += @($orgSettingsVariableObject) + } + + # Read settings from repository settings file + $customTemplateRepoSettingsObject = GetSettingsObject -Path (Join-Path $baseFolder $CustomTemplateRepoSettingsFile) + $settingsObjects += @($customTemplateRepoSettingsObject) + + # Read settings from repository settings file + $repoSettingsObject = GetSettingsObject -Path (Join-Path $baseFolder $RepoSettingsFile) + $settingsObjects += @($repoSettingsObject) + + # Read settings from repository settings variable (parameter) + if ($repoSettingsVariableValue) { + $repoSettingsVariableObject = $repoSettingsVariableValue | ConvertFrom-Json + $settingsObjects += @($repoSettingsVariableObject) + } + + if ($project) { + # Read settings from repository settings file + $customTemplateProjectSettingsObject = GetSettingsObject -Path (Join-Path $baseFolder $CustomTemplateProjectSettingsFile) + $settingsObjects += @($customTemplateProjectSettingsObject) + # Read settings from project settings file + $projectFolder = Join-Path $baseFolder $project -Resolve + $projectSettingsObject = GetSettingsObject -Path (Join-Path $projectFolder $ALGoSettingsFile) + $settingsObjects += @($projectSettingsObject) + } + + if ($workflowName) { + # Read settings from workflow settings file + $workflowSettingsObject = GetSettingsObject -Path (Join-Path $gitHubFolder "$workflowName.settings.json") + $settingsObjects += @($workflowSettingsObject) + if ($project) { + # Read settings from project workflow settings file + $projectWorkflowSettingsObject = GetSettingsObject -Path (Join-Path $projectFolder "$ALGoFolderName/$workflowName.settings.json") + # Read settings from user settings file + $userSettingsObject = GetSettingsObject -Path (Join-Path $projectFolder "$ALGoFolderName/$userName.settings.json") + $settingsObjects += @($projectWorkflowSettingsObject, $userSettingsObject) + } + } + + foreach($settingsJson in $settingsObjects) { + if ($settingsJson) { + MergeCustomObjectIntoOrderedDictionary -dst $settings -src $settingsJson + if ($settingsJson.PSObject.Properties.Name -eq "ConditionalSettings") { + foreach($conditionalSetting in $settingsJson.ConditionalSettings) { + if ("$conditionalSetting" -ne "") { + $conditionMet = $true + $conditions = @() + @{"buildModes" = $buildMode; "branches" = $branchName; "repositories" = $repoName; "projects" = $project; "workflows" = $workflowName; "users" = $userName}.GetEnumerator() | ForEach-Object { + $propName = $_.Key + $propValue = $_.Value + if ($conditionMet -and $conditionalSetting.PSObject.Properties.Name -eq $propName) { + $conditionMet = $propValue -and $conditionMet -and ($conditionalSetting."$propName" | Where-Object { $propValue -like $_ }) + $conditions += @("$($propName): $propValue") + } + } + if ($conditionMet) { + if (!$silent.IsPresent) { Write-Host "Applying conditional settings for $($conditions -join ", ")" } + MergeCustomObjectIntoOrderedDictionary -dst $settings -src $conditionalSetting.settings + } + } + } + } + } + } + + # runs-on is used for all jobs except for the build job (basically all jobs which doesn't need a container) + # gitHubRunner is used for the build job (or basically all jobs that needs a container) + # + # shell defaults to "powershell" unless runs-on is Ubuntu (Linux), then it defaults to pwsh + # + # gitHubRunner defaults to "runs-on", unless runs-on is Ubuntu (Linux) as this won't work. + # gitHubRunnerShell defaults to "shell" + # + # The exception for keeping gitHubRunner to Windows-Latest (when set to Ubuntu-*) will be removed when all jobs supports Ubuntu (Linux) + # At some point in the future (likely version 3.0), we will switch to Ubuntu (Linux) as default for "runs-on" + # + if ($settings.shell -eq "") { + if ($settings."runs-on" -like "*ubuntu-*") { + $settings.shell = "pwsh" + } + else { + $settings.shell = "powershell" + } + } + if ($settings.githubRunner -eq "") { + if ($settings."runs-on" -like "*ubuntu-*") { + $settings.githubRunner = "windows-latest" + } + else { + $settings.githubRunner = $settings."runs-on" + } + } + if ($settings.githubRunnerShell -eq "") { + $settings.githubRunnerShell = $settings.shell + } + + # Check that gitHubRunnerShell and Shell is valid + if ($settings.githubRunnerShell -ne "powershell" -and $settings.githubRunnerShell -ne "pwsh") { + throw "Invalid value for setting: gitHubRunnerShell: $($settings.githubRunnerShell)" + } + if ($settings.shell -ne "powershell" -and $settings.shell -ne "pwsh") { + throw "Invalid value for setting: shell: $($settings.githubRunnerShell)" + } + + if (($settings.githubRunner -like "*ubuntu-*") -and ($settings.githubRunnerShell -eq "powershell")) { + if (!$silent.IsPresent) { Write-Host "Switching shell to pwsh for ubuntu" } + $settings.githubRunnerShell = "pwsh" + } + + if($settings.projectName -eq '') { + $settings.projectName = $project # Default to project path as project name + } + + $settings | ValidateSettings + + $settings +} + +<# + .SYNOPSIS + Validate the settings against the settings schema file. + .PARAMETER settings + The settings to validate. +#> +function ValidateSettings { + Param( + [Parameter(Mandatory = $true, ValueFromPipeline = $true)] + $settings + ) + Process { + $settingsJson = ConvertTo-Json -InputObject $settings -Depth 99 -Compress + $settingsSchemaFile = Join-Path $PSScriptRoot "settings.schema.json" -Resolve + + $result = "" + try{ + $command = [scriptblock] { + $result = '' + Test-Json -Json $args[0] -SchemaFile $args[1] -ErrorVariable result -ErrorAction SilentlyContinue | Out-Null + return $result + } + + if($PSVersionTable.PSVersion.Major -lt 6) { # Test-Json is not available in PS5.1 + $result = pwsh -noprofile -Command $command -args $settingsJson, $settingsSchemaFile + } + else { + $result = Invoke-Command -ScriptBlock $command -ArgumentList $settingsJson, $settingsSchemaFile + } + } + catch { + OutputWarning "Error validating settings. Error: $($_.Exception.Message)" + } + if ($result) { + OutputWarning "Settings are not valid. Error: $result" + } + } +} + +Export-ModuleMember -Function ReadSettings +Export-ModuleMember -Variable ALGoFolderName, ALGoSettingsFile, RepoSettingsFile, CustomTemplateRepoSettingsFile, CustomTemplateProjectSettingsFile diff --git a/Actions/settings.schema.json b/Actions/.Modules/settings.schema.json similarity index 100% rename from Actions/settings.schema.json rename to Actions/.Modules/settings.schema.json diff --git a/Actions/AL-Go-Helper.ps1 b/Actions/AL-Go-Helper.ps1 index a9982e024..cddbd9ddc 100644 --- a/Actions/AL-Go-Helper.ps1 +++ b/Actions/AL-Go-Helper.ps1 @@ -3,18 +3,21 @@ Param( ) $gitHubHelperPath = Join-Path $PSScriptRoot 'Github-Helper.psm1' +$readSettingsModule = Join-Path $PSScriptRoot '.Modules/ReadSettings.psm1' if (Test-Path $gitHubHelperPath) { Import-Module $gitHubHelperPath # If we are adding more dependencies here, then localDevEnv and cloudDevEnv needs to be updated } +if (Test-Path $readSettingsModule) { + Import-Module $readSettingsModule +} + $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 $ALGoFolderName = '.AL-Go' $ALGoSettingsFile = Join-Path '.AL-Go' 'settings.json' $RepoSettingsFile = Join-Path '.github' 'AL-Go-Settings.json' -$CustomTemplateRepoSettingsFile = Join-Path '.github' 'AL-Go-TemplateRepoSettings.doNotEdit.json' -$CustomTemplateProjectSettingsFile = Join-Path '.github' 'AL-Go-TemplateProjectSettings.doNotEdit.json' [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'defaultCICDPushBranches', Justification = 'False positive.')] $defaultCICDPushBranches = @( 'main', 'release/*', 'feature/*' ) [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'defaultCICDPullRequestBranches', Justification = 'False positive.')] @@ -461,447 +464,6 @@ function DownloadAndImportBcContainerHelper([string] $baseFolder = $ENV:GITHUB_W . $bcContainerHelperPath @params } -function MergeCustomObjectIntoOrderedDictionary { - Param( - [System.Collections.Specialized.OrderedDictionary] $dst, - [PSCustomObject] $src - ) - - # Loop through all properties in the source object - # If the property does not exist in the destination object, add it with the right type, but no value - # Types supported: PSCustomObject, Object[] and simple types - $src.PSObject.Properties.GetEnumerator() | ForEach-Object { - $prop = $_.Name - $srcProp = $src."$prop" - $srcPropType = $srcProp.GetType().Name - if (-not $dst.Contains($prop)) { - if ($srcPropType -eq "PSCustomObject") { - $dst.Add("$prop", [ordered]@{}) - } - elseif ($srcPropType -eq "Object[]") { - $dst.Add("$prop", @()) - } - else { - $dst.Add("$prop", $srcProp) - } - } - } - - # Loop through all properties in the destination object - # If the property does not exist in the source object, do nothing - # If the property exists in the source object, but is of a different type, throw an error - # If the property exists in the source object: - # If the property is an Object, call this function recursively to merge values - # If the property is an Object[], merge the arrays - # If the property is a simple type, replace the value in the destination object with the value from the source object - @($dst.Keys) | ForEach-Object { - $prop = $_ - if ($src.PSObject.Properties.Name -eq $prop) { - $dstProp = $dst."$prop" - $srcProp = $src."$prop" - $dstPropType = $dstProp.GetType().Name - $srcPropType = $srcProp.GetType().Name - if ($srcPropType -eq "PSCustomObject" -and $dstPropType -eq "OrderedDictionary") { - MergeCustomObjectIntoOrderedDictionary -dst $dst."$prop" -src $srcProp - } - elseif ($dstPropType -ne $srcPropType -and !($srcPropType -eq "Int64" -and $dstPropType -eq "Int32")) { - # Under Linux, the Int fields read from the .json file will be Int64, while the settings defaults will be Int32 - # This is not seen as an error and will not throw an error - throw "property $prop should be of type $dstPropType, is $srcPropType." - } - else { - if ($srcProp -is [Object[]]) { - $srcProp | ForEach-Object { - $srcElm = $_ - $srcElmType = $srcElm.GetType().Name - if ($srcElmType -eq "PSCustomObject") { - # Array of objects are not checked for uniqueness - $ht = [ordered]@{} - $srcElm.PSObject.Properties | Sort-Object -Property Name -Culture ([cultureinfo]::InvariantCulture) | ForEach-Object { - $ht[$_.Name] = $_.Value - } - $dst."$prop" += @($ht) - } - else { - # Add source element to destination array, but only if it does not already exist - $dst."$prop" = @($dst."$prop" + $srcElm | Select-Object -Unique) - } - } - } - else { - $dst."$prop" = $srcProp - } - } - } - } -} - -function GetDefaultSettings -( - [string] $repoName -) -{ - return [ordered]@{ - "type" = "PTE" - "unusedALGoSystemFiles" = @() - "projects" = @() - "powerPlatformSolutionFolder" = "" - "country" = "us" - "artifact" = "" - "companyName" = "" - "repoVersion" = "1.0" - "repoName" = "$repoName" - "versioningStrategy" = 0 - "runNumberOffset" = 0 - "appBuild" = 0 - "appRevision" = 0 - "keyVaultName" = "" - "licenseFileUrlSecretName" = "licenseFileUrl" - "ghTokenWorkflowSecretName" = "ghTokenWorkflow" - "adminCenterApiCredentialsSecretName" = "adminCenterApiCredentials" - "applicationInsightsConnectionStringSecretName" = "applicationInsightsConnectionString" - "keyVaultCertificateUrlSecretName" = "" - "keyVaultCertificatePasswordSecretName" = "" - "keyVaultClientIdSecretName" = "" - "keyVaultCodesignCertificateName" = "" - "codeSignCertificateUrlSecretName" = "codeSignCertificateUrl" - "codeSignCertificatePasswordSecretName" = "codeSignCertificatePassword" - "additionalCountries" = @() - "appDependencies" = @() - "projectName" = "" - "appFolders" = @() - "testDependencies" = @() - "testFolders" = @() - "bcptTestFolders" = @() - "pageScriptingTests" = @() - "restoreDatabases" = @() - "installApps" = @() - "installTestApps" = @() - "installOnlyReferencedApps" = $true - "generateDependencyArtifact" = $false - "skipUpgrade" = $false - "applicationDependency" = "18.0.0.0" - "updateDependencies" = $false - "installTestRunner" = $false - "installTestFramework" = $false - "installTestLibraries" = $false - "installPerformanceToolkit" = $false - "enableCodeCop" = $false - "enableUICop" = $false - "enableCodeAnalyzersOnTestApps" = $false - "customCodeCops" = @() - "failOn" = "error" - "treatTestFailuresAsWarnings" = $false - "rulesetFile" = "" - "enableExternalRulesets" = $false - "vsixFile" = "" - "assignPremiumPlan" = $false - "enableTaskScheduler" = $false - "doNotBuildTests" = $false - "doNotRunTests" = $false - "doNotRunBcptTests" = $false - "doNotRunPageScriptingTests" = $false - "doNotPublishApps" = $false - "doNotSignApps" = $false - "configPackages" = @() - "appSourceCopMandatoryAffixes" = @() - "deliverToAppSource" = [ordered]@{ - "mainAppFolder" = "" - "productId" = "" - "includeDependencies" = @() - "continuousDelivery" = $false - } - "obsoleteTagMinAllowedMajorMinor" = "" - "memoryLimit" = "" - "templateUrl" = "" - "templateSha" = "" - "templateBranch" = "" - "appDependencyProbingPaths" = @() - "useProjectDependencies" = $false - "runs-on" = "windows-latest" - "shell" = "" - "githubRunner" = "" - "githubRunnerShell" = "" - "cacheImageName" = "my" - "cacheKeepDays" = 3 - "alwaysBuildAllProjects" = $false - "incrementalBuilds" = [ordered]@{ - "onPush" = $false - "onPull_Request" = $true - "onSchedule" = $false - "retentionDays" = 30 - "mode" = "modifiedApps" # modifiedProjects, modifiedApps - } - "microsoftTelemetryConnectionString" = "InstrumentationKey=cd2cc63e-0f37-4968-b99a-532411a314b8;IngestionEndpoint=https://northeurope-2.in.applicationinsights.azure.com/" - "partnerTelemetryConnectionString" = "" - "sendExtendedTelemetryToMicrosoft" = $false - "environments" = @() - "buildModes" = @() - "useCompilerFolder" = $false - "pullRequestTrigger" = "pull_request_target" - "bcptThresholds" = [ordered]@{ - "DurationWarning" = 10 - "DurationError" = 25 - "NumberOfSqlStmtsWarning" = 5 - "NumberOfSqlStmtsError" = 10 - } - "fullBuildPatterns" = @() - "excludeEnvironments" = @() - "alDoc" = [ordered]@{ - "continuousDeployment" = $false - "deployToGitHubPages" = $true - "maxReleases" = 3 - "groupByProject" = $true - "includeProjects" = @() - "excludeProjects" = @() - "header" = "Documentation for {REPOSITORY} {VERSION}" - "footer" = "Documentation for {REPOSITORY} made with AL-Go for GitHub, ALDoc and DocFx" - "defaultIndexMD" = "## Reference documentation\n\nThis is the generated reference documentation for [{REPOSITORY}](https://github.com/{REPOSITORY}).\n\nYou can use the navigation bar at the top and the table of contents to the left to navigate your documentation.\n\nYou can change this content by creating/editing the **{INDEXTEMPLATERELATIVEPATH}** file in your repository or use the alDoc:defaultIndexMD setting in your repository settings file (.github/AL-Go-Settings.json)\n\n{RELEASENOTES}" - "defaultReleaseMD" = "## Release reference documentation\n\nThis is the generated reference documentation for [{REPOSITORY}](https://github.com/{REPOSITORY}).\n\nYou can use the navigation bar at the top and the table of contents to the left to navigate your documentation.\n\nYou can change this content by creating/editing the **{INDEXTEMPLATERELATIVEPATH}** file in your repository or use the alDoc:defaultReleaseMD setting in your repository settings file (.github/AL-Go-Settings.json)\n\n{RELEASENOTES}" - } - "trustMicrosoftNuGetFeeds" = $true - "nuGetFeedSelectMode" = "LatestMatching" - "commitOptions" = [ordered]@{ - "messageSuffix" = "" - "pullRequestAutoMerge" = $false - "pullRequestLabels" = @() - "createPullRequest" = $true - } - "trustedSigning" = [ordered]@{ - "Endpoint" = "" - "Account" = "" - "CertificateProfile" = "" - } - "useGitSubmodules" = "false" - "gitSubmodulesTokenSecretName" = "gitSubmodulesToken" - "shortLivedArtifactsRetentionDays" = 1 # 0 means use GitHub default - } -} - -# Read settings from the settings files -# Settings are read from the following files: -# - ALGoOrgSettings (github Variable) = Organization settings variable -# - .github/AL-Go-TemplateRepoSettings.doNotEdit.json = Repository settings from custom template -# - .github/AL-Go-Settings.json = Repository Settings file -# - ALGoRepoSettings (github Variable) = Repository settings variable -# - .github/AL-Go-TemplateProjectSettings.doNotEdit.json = Project settings from custom template -# - /.AL-Go/settings.json = Project settings file -# - .github/.settings.json = Workflow settings file -# - /.AL-Go/.settings.json = Project workflow settings file -# - /.AL-Go/.settings.json = User settings file -function ReadSettings { - Param( - [string] $baseFolder = "$ENV:GITHUB_WORKSPACE", - [string] $repoName = "$ENV:GITHUB_REPOSITORY", - [string] $project = '.', - [string] $buildMode = "Default", - [string] $workflowName = "$ENV:GITHUB_WORKFLOW", - [string] $userName = "$ENV:GITHUB_ACTOR", - [string] $branchName = "$ENV:GITHUB_REF_NAME", - [string] $orgSettingsVariableValue = "$ENV:ALGoOrgSettings", - [string] $repoSettingsVariableValue = "$ENV:ALGoRepoSettings", - [switch] $silent - ) - - # If the build is triggered by a pull request the refname will be the merge branch. To apply conditional settings we need to use the base branch - if (($env:GITHUB_EVENT_NAME -eq "pull_request") -and ($branchName -eq $ENV:GITHUB_REF_NAME)) { - $branchName = $env:GITHUB_BASE_REF - } - - function GetSettingsObject { - Param( - [string] $path - ) - - if (Test-Path $path) { - try { - if (!$silent.IsPresent) { Write-Host "Applying settings from $path" } - $settings = Get-Content $path -Encoding UTF8 | ConvertFrom-Json - if ($settings) { - return $settings - } - } - catch { - throw "Error reading $path. Error was $($_.Exception.Message).`n$($_.ScriptStackTrace)" - } - } - else { - if (!$silent.IsPresent) { Write-Host "No settings found in $path" } - } - return $null - } - - $repoName = $repoName.SubString("$repoName".LastIndexOf('/') + 1) - $githubFolder = Join-Path $baseFolder ".github" - $workflowName = $workflowName.Trim().Split([System.IO.Path]::getInvalidFileNameChars()) -join "" - - # Start with default settings - $settings = GetDefaultSettings -repoName $repoName - - # Read settings from files and merge them into the settings object - - $settingsObjects = @() - # Read settings from organization settings variable (parameter) - if ($orgSettingsVariableValue) { - $orgSettingsVariableObject = $orgSettingsVariableValue | ConvertFrom-Json - $settingsObjects += @($orgSettingsVariableObject) - } - - # Read settings from repository settings file - $customTemplateRepoSettingsObject = GetSettingsObject -Path (Join-Path $baseFolder $CustomTemplateRepoSettingsFile) - $settingsObjects += @($customTemplateRepoSettingsObject) - - # Read settings from repository settings file - $repoSettingsObject = GetSettingsObject -Path (Join-Path $baseFolder $RepoSettingsFile) - $settingsObjects += @($repoSettingsObject) - - # Read settings from repository settings variable (parameter) - if ($repoSettingsVariableValue) { - $repoSettingsVariableObject = $repoSettingsVariableValue | ConvertFrom-Json - $settingsObjects += @($repoSettingsVariableObject) - } - - if ($project) { - # Read settings from repository settings file - $customTemplateProjectSettingsObject = GetSettingsObject -Path (Join-Path $baseFolder $CustomTemplateProjectSettingsFile) - $settingsObjects += @($customTemplateProjectSettingsObject) - # Read settings from project settings file - $projectFolder = Join-Path $baseFolder $project -Resolve - $projectSettingsObject = GetSettingsObject -Path (Join-Path $projectFolder $ALGoSettingsFile) - $settingsObjects += @($projectSettingsObject) - } - - if ($workflowName) { - # Read settings from workflow settings file - $workflowSettingsObject = GetSettingsObject -Path (Join-Path $gitHubFolder "$workflowName.settings.json") - $settingsObjects += @($workflowSettingsObject) - if ($project) { - # Read settings from project workflow settings file - $projectWorkflowSettingsObject = GetSettingsObject -Path (Join-Path $projectFolder "$ALGoFolderName/$workflowName.settings.json") - # Read settings from user settings file - $userSettingsObject = GetSettingsObject -Path (Join-Path $projectFolder "$ALGoFolderName/$userName.settings.json") - $settingsObjects += @($projectWorkflowSettingsObject, $userSettingsObject) - } - } - - foreach($settingsJson in $settingsObjects) { - if ($settingsJson) { - MergeCustomObjectIntoOrderedDictionary -dst $settings -src $settingsJson - if ($settingsJson.PSObject.Properties.Name -eq "ConditionalSettings") { - foreach($conditionalSetting in $settingsJson.ConditionalSettings) { - if ("$conditionalSetting" -ne "") { - $conditionMet = $true - $conditions = @() - @{"buildModes" = $buildMode; "branches" = $branchName; "repositories" = $repoName; "projects" = $project; "workflows" = $workflowName; "users" = $userName}.GetEnumerator() | ForEach-Object { - $propName = $_.Key - $propValue = $_.Value - if ($conditionMet -and $conditionalSetting.PSObject.Properties.Name -eq $propName) { - $conditionMet = $propValue -and $conditionMet -and ($conditionalSetting."$propName" | Where-Object { $propValue -like $_ }) - $conditions += @("$($propName): $propValue") - } - } - if ($conditionMet) { - if (!$silent.IsPresent) { Write-Host "Applying conditional settings for $($conditions -join ", ")" } - MergeCustomObjectIntoOrderedDictionary -dst $settings -src $conditionalSetting.settings - } - } - } - } - } - } - - # runs-on is used for all jobs except for the build job (basically all jobs which doesn't need a container) - # gitHubRunner is used for the build job (or basically all jobs that needs a container) - # - # shell defaults to "powershell" unless runs-on is Ubuntu (Linux), then it defaults to pwsh - # - # gitHubRunner defaults to "runs-on", unless runs-on is Ubuntu (Linux) as this won't work. - # gitHubRunnerShell defaults to "shell" - # - # The exception for keeping gitHubRunner to Windows-Latest (when set to Ubuntu-*) will be removed when all jobs supports Ubuntu (Linux) - # At some point in the future (likely version 3.0), we will switch to Ubuntu (Linux) as default for "runs-on" - # - if ($settings.shell -eq "") { - if ($settings."runs-on" -like "*ubuntu-*") { - $settings.shell = "pwsh" - } - else { - $settings.shell = "powershell" - } - } - if ($settings.githubRunner -eq "") { - if ($settings."runs-on" -like "*ubuntu-*") { - $settings.githubRunner = "windows-latest" - } - else { - $settings.githubRunner = $settings."runs-on" - } - } - if ($settings.githubRunnerShell -eq "") { - $settings.githubRunnerShell = $settings.shell - } - - # Check that gitHubRunnerShell and Shell is valid - if ($settings.githubRunnerShell -ne "powershell" -and $settings.githubRunnerShell -ne "pwsh") { - throw "Invalid value for setting: gitHubRunnerShell: $($settings.githubRunnerShell)" - } - if ($settings.shell -ne "powershell" -and $settings.shell -ne "pwsh") { - throw "Invalid value for setting: shell: $($settings.githubRunnerShell)" - } - - if (($settings.githubRunner -like "*ubuntu-*") -and ($settings.githubRunnerShell -eq "powershell")) { - if (!$silent.IsPresent) { Write-Host "Switching shell to pwsh for ubuntu" } - $settings.githubRunnerShell = "pwsh" - } - - if($settings.projectName -eq '') { - $settings.projectName = $project # Default to project path as project name - } - - $settings | ValidateSettings - - $settings -} - -<# - .SYNOPSIS - Validate the settings against the settings schema file. - .PARAMETER settings - The settings to validate. -#> -function ValidateSettings { - Param( - [Parameter(Mandatory = $true, ValueFromPipeline = $true)] - $settings - ) - Process { - $settingsJson = ConvertTo-Json -InputObject $settings -Depth 99 -Compress - $settingsSchemaFile = Join-Path $PSScriptRoot "settings.schema.json" -Resolve - - $result = "" - try{ - $command = [scriptblock] { - $result = '' - Test-Json -Json $args[0] -SchemaFile $args[1] -ErrorVariable result -ErrorAction SilentlyContinue | Out-Null - return $result - } - - if($PSVersionTable.PSVersion.Major -lt 6) { # Test-Json is not available in PS5.1 - $result = pwsh -noprofile -Command $command -args $settingsJson, $settingsSchemaFile - } - else { - $result = Invoke-Command -ScriptBlock $command -ArgumentList $settingsJson, $settingsSchemaFile - } - } - catch { - OutputWarning "Error validating settings. Error: $($_.Exception.Message)" - } - if ($result) { - OutputWarning "Settings are not valid. Error: $result" - } - } -} - function ExcludeUnneededApps { Param( [string[]] $folders, diff --git a/Templates/AppSource App/.AL-Go/cloudDevEnv.ps1 b/Templates/AppSource App/.AL-Go/cloudDevEnv.ps1 index 39e13e4ea..a07cdb19a 100644 --- a/Templates/AppSource App/.AL-Go/cloudDevEnv.ps1 +++ b/Templates/AppSource App/.AL-Go/cloudDevEnv.ps1 @@ -52,11 +52,13 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null $GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Github-Helper.psm1' -folder $tmpFolder -notifyAuthenticatedAttempt +$ReadSettingsModule = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/.Modules/ReadSettings.psm1' -folder $tmpFolder $ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1' -folder $tmpFolder DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/settings.schema.json' -folder $tmpFolder | Out-Null DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath +Import-Module $ReadSettingsModule . $ALGoHelperPath -local $baseFolder = GetBaseFolder -folder $PSScriptRoot diff --git a/Templates/AppSource App/.AL-Go/localDevEnv.ps1 b/Templates/AppSource App/.AL-Go/localDevEnv.ps1 index aeaaedb73..80622d5d0 100644 --- a/Templates/AppSource App/.AL-Go/localDevEnv.ps1 +++ b/Templates/AppSource App/.AL-Go/localDevEnv.ps1 @@ -56,11 +56,13 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null $GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Github-Helper.psm1' -folder $tmpFolder -notifyAuthenticatedAttempt +$ReadSettingsModule = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/.Modules/ReadSettings.psm1' -folder $tmpFolder $ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1' -folder $tmpFolder DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/settings.schema.json' -folder $tmpFolder | Out-Null DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath +Import-Module $ReadSettingsModule . $ALGoHelperPath -local $baseFolder = GetBaseFolder -folder $PSScriptRoot diff --git a/Templates/Per Tenant Extension/.AL-Go/cloudDevEnv.ps1 b/Templates/Per Tenant Extension/.AL-Go/cloudDevEnv.ps1 index 39e13e4ea..a07cdb19a 100644 --- a/Templates/Per Tenant Extension/.AL-Go/cloudDevEnv.ps1 +++ b/Templates/Per Tenant Extension/.AL-Go/cloudDevEnv.ps1 @@ -52,11 +52,13 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null $GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Github-Helper.psm1' -folder $tmpFolder -notifyAuthenticatedAttempt +$ReadSettingsModule = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/.Modules/ReadSettings.psm1' -folder $tmpFolder $ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1' -folder $tmpFolder DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/settings.schema.json' -folder $tmpFolder | Out-Null DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath +Import-Module $ReadSettingsModule . $ALGoHelperPath -local $baseFolder = GetBaseFolder -folder $PSScriptRoot diff --git a/Templates/Per Tenant Extension/.AL-Go/localDevEnv.ps1 b/Templates/Per Tenant Extension/.AL-Go/localDevEnv.ps1 index aeaaedb73..80622d5d0 100644 --- a/Templates/Per Tenant Extension/.AL-Go/localDevEnv.ps1 +++ b/Templates/Per Tenant Extension/.AL-Go/localDevEnv.ps1 @@ -56,11 +56,13 @@ Write-Host -ForegroundColor Yellow @' $tmpFolder = Join-Path ([System.IO.Path]::GetTempPath()) "$([Guid]::NewGuid().ToString())" New-Item -Path $tmpFolder -ItemType Directory -Force | Out-Null $GitHubHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Github-Helper.psm1' -folder $tmpFolder -notifyAuthenticatedAttempt +$ReadSettingsModule = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/.Modules/ReadSettings.psm1' -folder $tmpFolder $ALGoHelperPath = DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/AL-Go-Helper.ps1' -folder $tmpFolder DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/settings.schema.json' -folder $tmpFolder | Out-Null DownloadHelperFile -url 'https://raw.githubusercontent.com/microsoft/AL-Go-Actions/main/Packages.json' -folder $tmpFolder | Out-Null Import-Module $GitHubHelperPath +Import-Module $ReadSettingsModule . $ALGoHelperPath -local $baseFolder = GetBaseFolder -folder $PSScriptRoot diff --git a/Tests/AL-Go-Helper.Test.ps1 b/Tests/AL-Go-Helper.Test.ps1 index 57c5e114d..18719d85d 100644 --- a/Tests/AL-Go-Helper.Test.ps1 +++ b/Tests/AL-Go-Helper.Test.ps1 @@ -3,82 +3,6 @@ . (Join-Path $PSScriptRoot '../Actions/AL-Go-Helper.ps1') } - It 'MergeCustomObjectIntoOrderedDictionary' { - # This function is used to merge settings files into the settings object - # dest is the default settings object - $dest = [ordered]@{ - 'int0' = 0 - 'int1' = 1 - 'int2' = 2 - 'str1' = 'str1' - 'str2' = 'str2' - 'arr1' = @('a', 'b', 'c') - 'arr2' = @('a', 'b', 'c') - 'obj1' = [ordered]@{ - 'a' = 'a' - 'b' = 'b' - 'c' = 'c' - } - 'obj2' = [ordered]@{ - 'a' = 'a' - 'b' = 'b' - 'c' = 'c' - } - 'objarr1' = @([ordered]@{'a' = 'a'; 'b' = 'b'; 'c' = 'c'}, [ordered]@{'d' = 'd'; 'e' = 'e'; 'f' = 'f'}) - 'objarr2' = @([ordered]@{'a' = 'a'; 'b' = 'b'; 'c' = 'c'}, [ordered]@{'d' = 'd'; 'e' = 'e'; 'f' = 'f'}) - } - - $dest.Count | Should -Be 11 - - # source is the settings read from a file - $src = @{ - 'int1' = [Int64]::MaxValue - 'int2' = 3 - 'int3' = 4 - 'objarr2' = @([ordered]@{'g' = 'g'; 'h' = 'h'; 'i' = 'i'}, [ordered]@{'d' = 'd'; 'e' = 'e'; 'f' = 'f'}) - 'objarr3' = @([ordered]@{'g' = 'g'; 'h' = 'h'; 'i' = 'i'}, [ordered]@{'j' = 'j'; 'k' = 'k'; 'l' = 'l'}) - } | ConvertTo-Json | ConvertFrom-Json - $src.int2 = [Int32]3 - $src.int3 = [Int32]4 - - # Merge the settings - MergeCustomObjectIntoOrderedDictionary -dst $dest -src $src - $dest.Count | Should -Be 13 - $dest['int0'] | Should -Be 0 - $dest['int1'] | Should -Be ([Int64]::MaxValue) - $dest['int2'] | Should -Be 3 - $dest['int3'] | Should -Be 4 - $dest['objarr2'] | ConvertTo-Json | Should -Be (@([ordered]@{'a' = 'a'; 'b' = 'b'; 'c' = 'c'}, [ordered]@{'d' = 'd'; 'e' = 'e'; 'f' = 'f'}, [ordered]@{'g' = 'g'; 'h' = 'h'; 'i' = 'i'}, [ordered]@{'d' = 'd'; 'e' = 'e'; 'f' = 'f'}) | ConvertTo-Json) - $dest['objarr3'] | ConvertTo-Json | Should -Be (@([ordered]@{'g' = 'g'; 'h' = 'h'; 'i' = 'i'}, [ordered]@{'j' = 'j'; 'k' = 'k'; 'l' = 'l'}) | ConvertTo-Json) - - # source is the settings read from a file - # Check that multiple settings files are merged correctly one after the other - $src = @{ - 'str2' = 'str3' - 'str3' = 'str4' - 'arr2' = @('c', 'd', 'e') - 'arr3' = @('c', 'd', 'e') - 'obj2' = [ordered]@{'c' = 'c'; 'd' = 'd'; 'e' = 'e'} - 'obj3' = [ordered]@{'d' = 'd'; 'e' = 'e'; 'f' = 'f'} - } | ConvertTo-Json | ConvertFrom-Json - - # Check that applying the same settings twice doesn't change the result - 1..2 | ForEach-Object { - MergeCustomObjectIntoOrderedDictionary -dst $dest -src $src - $dest.Count | Should -Be 16 - $dest['int0'] | Should -Be 0 - $dest['int1'] | Should -Be ([Int64]::MaxValue) - $dest['int2'] | Should -Be 3 - $dest['int3'] | Should -Be 4 - $dest['str2'] | Should -Be 'str3' - $dest['str3'] | Should -Be 'str4' - $dest['arr2'] | Should -Be @('a', 'b', 'c', 'd', 'e') - $dest['arr3'] | Should -Be @('c', 'd', 'e') - $dest['obj2'] | ConvertTo-Json | Should -Be ([ordered]@{'a' = 'a'; 'b' = 'b'; 'c' = 'c'; 'd' = 'd'; 'e' = 'e'} | ConvertTo-Json) - $dest['obj3'] | ConvertTo-Json | Should -Be ([ordered]@{'d' = 'd'; 'e' = 'e'; 'f' = 'f'} | ConvertTo-Json) - } - } - It 'CheckAndCreateProjectFolder' { Mock Write-Host { } @@ -190,270 +114,3 @@ ) } } - - -Describe "ReadSettings" { - BeforeAll { - . (Join-Path $PSScriptRoot '../Actions/AL-Go-Helper.ps1') - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'scriptPath', Justification = 'False positive.')] - $schema = Get-Content -Path (Join-Path $PSScriptRoot '../Actions/settings.schema.json') -Raw - } - - It 'Reads settings from all settings locations' { - Mock Write-Host { } - Mock Out-Host { } - - Push-Location - $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) - $githubFolder = Join-Path $tempName ".github" - $ALGoFolder = Join-Path $tempName $ALGoFolderName - $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" - - New-Item $githubFolder -ItemType Directory | Out-Null - New-Item $ALGoFolder -ItemType Directory | Out-Null - New-Item $projectALGoFolder -ItemType Directory | Out-Null - - New-Item -Path (Join-Path $tempName "projectx/$ALGoFolderName") -ItemType Directory | Out-Null - New-Item -Path (Join-Path $tempName "projecty/$ALGoFolderName") -ItemType Directory | Out-Null - - # Create settings files - # Property: Repo: Project (single): Project (multi): Workflow: Workflow: User: - # if(branch=dev): - # Property1 repo1 single1 multi1 branch1 user1 - # Property2 repo2 workflow2 - # Property3 repo3 - # Arr1 @("repo1","repo2") - # Property4 single4 branch4 - # property5 multi5 - # property6 user6 - @{ "property1" = "repo1"; "property2" = "repo2"; "property3" = "repo3"; "arr1" = @("repo1","repo2") } | ConvertTo-Json -Depth 99 | - Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -encoding utf8 -Force - @{ "property1" = "single1"; "property4" = "single4" } | ConvertTo-Json -Depth 99 | - Set-Content -Path (Join-Path $ALGoFolder "settings.json") -encoding utf8 -Force - @{ "property1" = "multi1"; "property5" = "multi5" } | ConvertTo-Json -Depth 99 | - Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -encoding utf8 -Force - @{ "property2" = "workflow2"; "conditionalSettings" = @( @{ "branches" = @( 'dev' ); "settings" = @{ "property1" = "branch1"; "property4" = "branch4" } } ) } | ConvertTo-Json -Depth 99 | - Set-Content -Path (Join-Path $githubFolder "Workflow.settings.json") -encoding utf8 -Force - @{ "property1" = "user1"; "property6" = "user6" } | ConvertTo-Json -Depth 99 | - Set-Content -Path (Join-Path $projectALGoFolder "user.settings.json") -encoding utf8 -Force - - # No settings variables - $ENV:ALGoOrgSettings = '' - $ENV:ALGoRepoSettings = '' - - # Repo only - $repoSettings = ReadSettings -baseFolder $tempName -project '' -repoName 'repo' -workflowName '' -branchName '' -userName '' - $repoSettings.property1 | Should -Be 'repo1' - $repoSettings.property2 | Should -Be 'repo2' - $repoSettings.property3 | Should -Be 'repo3' - - # Repo + single project - $singleProjectSettings = ReadSettings -baseFolder $tempName -project '.' -repoName 'repo' -workflowName '' -branchName '' -userName '' - $singleProjectSettings.property1 | Should -Be 'single1' - $singleProjectSettings.property2 | Should -Be 'repo2' - $singleProjectSettings.property4 | Should -Be 'single4' - - # Repo + multi project - $multiProjectSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' - $multiProjectSettings.property1 | Should -Be 'multi1' - $multiProjectSettings.property2 | Should -Be 'repo2' - $multiProjectSettings.property5 | Should -Be 'multi5' - - # Repo + workflow - $workflowRepoSettings = ReadSettings -baseFolder $tempName -project '' -repoName 'repo' -workflowName 'Workflow' -branchName '' -userName '' - $workflowRepoSettings.property1 | Should -Be 'repo1' - $workflowRepoSettings.property2 | Should -Be 'workflow2' - - # Repo + single project + workflow - $workflowSingleSettings = ReadSettings -baseFolder $tempName -project '.' -repoName 'repo' -workflowName 'Workflow' -branchName '' -userName '' - $workflowSingleSettings.property1 | Should -Be 'single1' - $workflowSingleSettings.property2 | Should -Be 'workflow2' - $workflowSingleSettings.property4 | Should -Be 'single4' - $workflowSingleSettings.property3 | Should -Be 'repo3' - - # Repo + multi project + workflow + dev branch - $workflowMultiSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName 'Workflow' -branchName 'dev' -userName '' - $workflowMultiSettings.property1 | Should -Be 'branch1' - $workflowMultiSettings.property2 | Should -Be 'workflow2' - $workflowMultiSettings.property3 | Should -Be 'repo3' - $workflowMultiSettings.property4 | Should -Be 'branch4' - $workflowMultiSettings.property5 | Should -Be 'multi5' - { $workflowMultiSettings.property6 } | Should -Throw - - # Repo + multi project + workflow + dev branch + user - $userWorkflowMultiSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName 'Workflow' -branchName 'dev' -userName 'user' - $userWorkflowMultiSettings.property1 | Should -Be 'user1' - $userWorkflowMultiSettings.property2 | Should -Be 'workflow2' - $userWorkflowMultiSettings.property3 | Should -Be 'repo3' - $userWorkflowMultiSettings.property4 | Should -Be 'branch4' - $userWorkflowMultiSettings.property5 | Should -Be 'multi5' - $userWorkflowMultiSettings.property6 | Should -Be 'user6' - - # Org settings variable - # property 2 = orgsetting2 - # property 7 = orgsetting7 - # arr1 = @(org3) - gets merged - $ENV:ALGoOrgSettings = @{ "property2" = "orgsetting2"; "property7" = "orgsetting7"; "arr1" = @("org3") } | ConvertTo-Json -Depth 99 - - # Org(var) + Repo + multi project + workflow + dev branch + user - $withOrgSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName 'Workflow' -branchName 'dev' -userName 'user' - $withOrgSettings.property1 | Should -Be 'user1' - $withOrgSettings.property2 | Should -Be 'workflow2' - $withOrgSettings.property3 | Should -Be 'repo3' - $withOrgSettings.property4 | Should -Be 'branch4' - $withOrgSettings.property5 | Should -Be 'multi5' - $withOrgSettings.property6 | Should -Be 'user6' - $withOrgSettings.property7 | Should -Be 'orgsetting7' - $withOrgSettings.arr1 | Should -Be @("org3","repo1","repo2") - - # Repo settings variable - # property3 = reposetting3 - # property8 = reposetting8 - $ENV:ALGoRepoSettings = @{ "property3" = "reposetting3"; "property8" = "reposetting8" } | ConvertTo-Json -Depth 99 - - # Org(var) + Repo + Repo(var) + multi project + workflow + dev branch + user - $withRepoSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName 'Workflow' -branchName 'dev' -userName 'user' - $withRepoSettings.property1 | Should -Be 'user1' - $withRepoSettings.property2 | Should -Be 'workflow2' - $withRepoSettings.property3 | Should -Be 'reposetting3' - $withRepoSettings.property4 | Should -Be 'branch4' - $withRepoSettings.property5 | Should -Be 'multi5' - $withRepoSettings.property6 | Should -Be 'user6' - $withRepoSettings.property7 | Should -Be 'orgsetting7' - $withRepoSettings.property8 | Should -Be 'reposetting8' - - # Add conditional settings as repo(var) settings - $conditionalSettings = [ordered]@{ - "conditionalSettings" = @( - @{ - "branches" = @( 'branchx', 'branchy' ) - "settings" = @{ "property3" = "branchxy"; "property4" = "branchxy" } - } - @{ - "repositories" = @( 'repox', 'repoy' ) - "settings" = @{ "property3" = "repoxy"; "property4" = "repoxy" } - } - @{ - "projects" = @( 'projectx', 'projecty' ) - "settings" = @{ "property3" = "projectxy"; "property4" = "projectxy" } - } - @{ - "workflows" = @( 'workflowx', 'workflowy' ) - "settings" = @{ "property3" = "workflowxy"; "property4" = "workflowxy" } - } - @{ - "users" = @( 'userx', 'usery' ) - "settings" = @{ "property3" = "userxy"; "property4" = "userxy" } - } - @{ - "branches" = @( 'branchx', 'branchy' ) - "projects" = @( 'projectx','projecty' ) - "settings" = @{ "property3" = "bpxy"; "property4" = "bpxy" } - } - ) - } - $ENV:ALGoRepoSettings = $conditionalSettings | ConvertTo-Json -Depth 99 - - # Test that conditional settings are applied correctly - $conditionalSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName 'Workflow' -branchName 'branchy' -userName 'user' - $conditionalSettings.property3 | Should -Be 'branchxy' - $conditionalSettings.property4 | Should -Be 'branchxy' - - $conditionalSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repox' -workflowName 'Workflow' -branchName 'dev' -userName 'user' - $conditionalSettings.property3 | Should -Be 'repoxy' - $conditionalSettings.property4 | Should -Be 'branch4' - - $conditionalSettings = ReadSettings -baseFolder $tempName -project 'projectx' -repoName 'repo' -workflowName 'Workflow' -branchName 'branch' -userName 'user' - $conditionalSettings.property3 | Should -Be 'projectxy' - $conditionalSettings.property4 | Should -Be 'projectxy' - - $conditionalSettings = ReadSettings -baseFolder $tempName -project 'projectx' -repoName 'repo' -workflowName 'Workflowx' -branchName 'branch' -userName 'user' - $conditionalSettings.property3 | Should -Be 'workflowxy' - $conditionalSettings.property4 | Should -Be 'workflowxy' - - $conditionalSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName 'Workflow' -branchName 'branch' -userName 'usery' - $conditionalSettings.property3 | Should -Be 'userxy' - $conditionalSettings.property4 | Should -Be 'userxy' - - $conditionalSettings = ReadSettings -baseFolder $tempName -project 'projecty' -repoName 'repo' -workflowName 'Workflow' -branchName 'branchx' -userName 'user' - $conditionalSettings.property3 | Should -Be 'bpxy' - $conditionalSettings.property4 | Should -Be 'bpxy' - - # Invalid Org(var) setting should throw - $ENV:ALGoOrgSettings = 'this is not json' - { ReadSettings -baseFolder $tempName -project 'Project' } | Should -Throw - - $ENV:ALGoOrgSettings = '' - $ENV:ALGoRepoSettings = '' - - # Clean up - Pop-Location - Remove-Item -Path $tempName -Recurse -Force - } - - It 'Settings schema is valid' { - Test-Json -json $schema | Should -Be $true - } - - It 'All default settings are in the schema' { - $defaultSettings = GetDefaultSettings - - $schemaObj = $schema | ConvertFrom-Json - - $defaultSettings.Keys | ForEach-Object { - $property = $_ - $schemaObj.properties.PSObject.Properties.Name | Should -Contain $property - } - } - - It 'Default settings match schema' { - $defaultSettings = GetDefaultSettings - Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema | Should -Be $true - } - - It 'Shell setting can only be pwsh or powershell' { - $defaultSettings = GetDefaultSettings - $defaultSettings.shell = 42 - try { - Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema - } - catch { - $_.Exception.Message | Should -Be "The JSON is not valid with the schema: Value is `"integer`" but should be `"string`" at '/shell'" - } - - $defaultSettings.shell = "random" - try { - Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema - } - catch { - $_.Exception.Message | Should -Be "The JSON is not valid with the schema: The string value is not a match for the indicated regular expression at '/shell'" - } - } - - It 'Projects setting is an array of strings' { - # If the projects setting is not an array, it should throw an error - $defaultSettings = GetDefaultSettings - $defaultSettings.projects = "not an array" - try { - Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema - } - catch { - $_.Exception.Message | Should -Be "The JSON is not valid with the schema: Value is `"string`" but should be `"array`" at '/projects'" - } - - # If the projects setting is an array, but contains non-string values, it should throw an error - $defaultSettings.projects = @("project1", 42) - try { - Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema - } - catch { - $_.Exception.Message | Should -Be "The JSON is not valid with the schema: Value is `"integer`" but should be `"string`" at '/projects/1'" - } - - # If the projects setting is an array of strings, it should pass the schema validation - $defaultSettings.projects = @("project1") - Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema | Should -Be $true - $defaultSettings.projects = @("project1", "project2") - Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema | Should -Be $true - } -} diff --git a/Tests/ReadSettings.Test.ps1 b/Tests/ReadSettings.Test.ps1 new file mode 100644 index 000000000..1bcfe058f --- /dev/null +++ b/Tests/ReadSettings.Test.ps1 @@ -0,0 +1,268 @@ +Import-Module (Join-Path $PSScriptRoot '../Actions/.Modules/ReadSettings.psm1') + +InModuleScope ReadSettings { # Allows testing of private functions + Describe "ReadSettings" { + BeforeAll { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'scriptPath', Justification = 'False positive.')] + $schema = Get-Content -Path (Join-Path $PSScriptRoot '../Actions/.Modules/settings.schema.json') -Raw + } + + It 'Reads settings from all settings locations' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $ALGoFolder = Join-Path $tempName $ALGoFolderName + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $ALGoFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + New-Item -Path (Join-Path $tempName "projectx/$ALGoFolderName") -ItemType Directory | Out-Null + New-Item -Path (Join-Path $tempName "projecty/$ALGoFolderName") -ItemType Directory | Out-Null + + # Create settings files + # Property: Repo: Project (single): Project (multi): Workflow: Workflow: User: + # if(branch=dev): + # Property1 repo1 single1 multi1 branch1 user1 + # Property2 repo2 workflow2 + # Property3 repo3 + # Arr1 @("repo1","repo2") + # Property4 single4 branch4 + # property5 multi5 + # property6 user6 + @{ "property1" = "repo1"; "property2" = "repo2"; "property3" = "repo3"; "arr1" = @("repo1", "repo2") } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -encoding utf8 -Force + @{ "property1" = "single1"; "property4" = "single4" } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $ALGoFolder "settings.json") -encoding utf8 -Force + @{ "property1" = "multi1"; "property5" = "multi5" } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -encoding utf8 -Force + @{ "property2" = "workflow2"; "conditionalSettings" = @( @{ "branches" = @( 'dev' ); "settings" = @{ "property1" = "branch1"; "property4" = "branch4" } } ) } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "Workflow.settings.json") -encoding utf8 -Force + @{ "property1" = "user1"; "property6" = "user6" } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "user.settings.json") -encoding utf8 -Force + + # No settings variables + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # Repo only + $repoSettings = ReadSettings -baseFolder $tempName -project '' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $repoSettings.property1 | Should -Be 'repo1' + $repoSettings.property2 | Should -Be 'repo2' + $repoSettings.property3 | Should -Be 'repo3' + + # Repo + single project + $singleProjectSettings = ReadSettings -baseFolder $tempName -project '.' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $singleProjectSettings.property1 | Should -Be 'single1' + $singleProjectSettings.property2 | Should -Be 'repo2' + $singleProjectSettings.property4 | Should -Be 'single4' + + # Repo + multi project + $multiProjectSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $multiProjectSettings.property1 | Should -Be 'multi1' + $multiProjectSettings.property2 | Should -Be 'repo2' + $multiProjectSettings.property5 | Should -Be 'multi5' + + # Repo + workflow + $workflowRepoSettings = ReadSettings -baseFolder $tempName -project '' -repoName 'repo' -workflowName 'Workflow' -branchName '' -userName '' + $workflowRepoSettings.property1 | Should -Be 'repo1' + $workflowRepoSettings.property2 | Should -Be 'workflow2' + + # Repo + single project + workflow + $workflowSingleSettings = ReadSettings -baseFolder $tempName -project '.' -repoName 'repo' -workflowName 'Workflow' -branchName '' -userName '' + $workflowSingleSettings.property1 | Should -Be 'single1' + $workflowSingleSettings.property2 | Should -Be 'workflow2' + $workflowSingleSettings.property4 | Should -Be 'single4' + $workflowSingleSettings.property3 | Should -Be 'repo3' + + # Repo + multi project + workflow + dev branch + $workflowMultiSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName 'Workflow' -branchName 'dev' -userName '' + $workflowMultiSettings.property1 | Should -Be 'branch1' + $workflowMultiSettings.property2 | Should -Be 'workflow2' + $workflowMultiSettings.property3 | Should -Be 'repo3' + $workflowMultiSettings.property4 | Should -Be 'branch4' + $workflowMultiSettings.property5 | Should -Be 'multi5' + $workflowMultiSettings.property6 | Should -BeNullOrEmpty + + # Repo + multi project + workflow + dev branch + user + $userWorkflowMultiSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName 'Workflow' -branchName 'dev' -userName 'user' + $userWorkflowMultiSettings.property1 | Should -Be 'user1' + $userWorkflowMultiSettings.property2 | Should -Be 'workflow2' + $userWorkflowMultiSettings.property3 | Should -Be 'repo3' + $userWorkflowMultiSettings.property4 | Should -Be 'branch4' + $userWorkflowMultiSettings.property5 | Should -Be 'multi5' + $userWorkflowMultiSettings.property6 | Should -Be 'user6' + + # Org settings variable + # property 2 = orgsetting2 + # property 7 = orgsetting7 + # arr1 = @(org3) - gets merged + $ENV:ALGoOrgSettings = @{ "property2" = "orgsetting2"; "property7" = "orgsetting7"; "arr1" = @("org3") } | ConvertTo-Json -Depth 99 + + # Org(var) + Repo + multi project + workflow + dev branch + user + $withOrgSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName 'Workflow' -branchName 'dev' -userName 'user' + $withOrgSettings.property1 | Should -Be 'user1' + $withOrgSettings.property2 | Should -Be 'workflow2' + $withOrgSettings.property3 | Should -Be 'repo3' + $withOrgSettings.property4 | Should -Be 'branch4' + $withOrgSettings.property5 | Should -Be 'multi5' + $withOrgSettings.property6 | Should -Be 'user6' + $withOrgSettings.property7 | Should -Be 'orgsetting7' + $withOrgSettings.arr1 | Should -Be @("org3", "repo1", "repo2") + + # Repo settings variable + # property3 = reposetting3 + # property8 = reposetting8 + $ENV:ALGoRepoSettings = @{ "property3" = "reposetting3"; "property8" = "reposetting8" } | ConvertTo-Json -Depth 99 + + # Org(var) + Repo + Repo(var) + multi project + workflow + dev branch + user + $withRepoSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName 'Workflow' -branchName 'dev' -userName 'user' + $withRepoSettings.property1 | Should -Be 'user1' + $withRepoSettings.property2 | Should -Be 'workflow2' + $withRepoSettings.property3 | Should -Be 'reposetting3' + $withRepoSettings.property4 | Should -Be 'branch4' + $withRepoSettings.property5 | Should -Be 'multi5' + $withRepoSettings.property6 | Should -Be 'user6' + $withRepoSettings.property7 | Should -Be 'orgsetting7' + $withRepoSettings.property8 | Should -Be 'reposetting8' + + # Add conditional settings as repo(var) settings + $conditionalSettings = [ordered]@{ + "conditionalSettings" = @( + @{ + "branches" = @( 'branchx', 'branchy' ) + "settings" = @{ "property3" = "branchxy"; "property4" = "branchxy" } + } + @{ + "repositories" = @( 'repox', 'repoy' ) + "settings" = @{ "property3" = "repoxy"; "property4" = "repoxy" } + } + @{ + "projects" = @( 'projectx', 'projecty' ) + "settings" = @{ "property3" = "projectxy"; "property4" = "projectxy" } + } + @{ + "workflows" = @( 'workflowx', 'workflowy' ) + "settings" = @{ "property3" = "workflowxy"; "property4" = "workflowxy" } + } + @{ + "users" = @( 'userx', 'usery' ) + "settings" = @{ "property3" = "userxy"; "property4" = "userxy" } + } + @{ + "branches" = @( 'branchx', 'branchy' ) + "projects" = @( 'projectx', 'projecty' ) + "settings" = @{ "property3" = "bpxy"; "property4" = "bpxy" } + } + ) + } + $ENV:ALGoRepoSettings = $conditionalSettings | ConvertTo-Json -Depth 99 + + # Test that conditional settings are applied correctly + $conditionalSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName 'Workflow' -branchName 'branchy' -userName 'user' + $conditionalSettings.property3 | Should -Be 'branchxy' + $conditionalSettings.property4 | Should -Be 'branchxy' + + $conditionalSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repox' -workflowName 'Workflow' -branchName 'dev' -userName 'user' + $conditionalSettings.property3 | Should -Be 'repoxy' + $conditionalSettings.property4 | Should -Be 'branch4' + + $conditionalSettings = ReadSettings -baseFolder $tempName -project 'projectx' -repoName 'repo' -workflowName 'Workflow' -branchName 'branch' -userName 'user' + $conditionalSettings.property3 | Should -Be 'projectxy' + $conditionalSettings.property4 | Should -Be 'projectxy' + + $conditionalSettings = ReadSettings -baseFolder $tempName -project 'projectx' -repoName 'repo' -workflowName 'Workflowx' -branchName 'branch' -userName 'user' + $conditionalSettings.property3 | Should -Be 'workflowxy' + $conditionalSettings.property4 | Should -Be 'workflowxy' + + $conditionalSettings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName 'Workflow' -branchName 'branch' -userName 'usery' + $conditionalSettings.property3 | Should -Be 'userxy' + $conditionalSettings.property4 | Should -Be 'userxy' + + $conditionalSettings = ReadSettings -baseFolder $tempName -project 'projecty' -repoName 'repo' -workflowName 'Workflow' -branchName 'branchx' -userName 'user' + $conditionalSettings.property3 | Should -Be 'bpxy' + $conditionalSettings.property4 | Should -Be 'bpxy' + + # Invalid Org(var) setting should throw + $ENV:ALGoOrgSettings = 'this is not json' + { ReadSettings -baseFolder $tempName -project 'Project' } | Should -Throw + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'Settings schema is valid' { + Test-Json -json $schema | Should -Be $true + } + + It 'All default settings are in the schema' { + $defaultSettings = GetDefaultSettings + + $schemaObj = $schema | ConvertFrom-Json + + $defaultSettings.Keys | ForEach-Object { + $property = $_ + $schemaObj.properties.PSObject.Properties.Name | Should -Contain $property + } + } + + It 'Default settings match schema' { + $defaultSettings = GetDefaultSettings + Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema | Should -Be $true + } + + It 'Shell setting can only be pwsh or powershell' { + $defaultSettings = GetDefaultSettings + $defaultSettings.shell = 42 + try { + Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema + } + catch { + $_.Exception.Message | Should -Be "The JSON is not valid with the schema: Value is `"integer`" but should be `"string`" at '/shell'" + } + + $defaultSettings.shell = "random" + try { + Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema + } + catch { + $_.Exception.Message | Should -Be "The JSON is not valid with the schema: The string value is not a match for the indicated regular expression at '/shell'" + } + } + + It 'Projects setting is an array of strings' { + # If the projects setting is not an array, it should throw an error + $defaultSettings = GetDefaultSettings + $defaultSettings.projects = "not an array" + try { + Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema + } + catch { + $_.Exception.Message | Should -Be "The JSON is not valid with the schema: Value is `"string`" but should be `"array`" at '/projects'" + } + + # If the projects setting is an array, but contains non-string values, it should throw an error + $defaultSettings.projects = @("project1", 42) + try { + Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema + } + catch { + $_.Exception.Message | Should -Be "The JSON is not valid with the schema: Value is `"integer`" but should be `"string`" at '/projects/1'" + } + + # If the projects setting is an array of strings, it should pass the schema validation + $defaultSettings.projects = @("project1") + Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema | Should -Be $true + $defaultSettings.projects = @("project1", "project2") + Test-Json -json (ConvertTo-Json $defaultSettings) -schema $schema | Should -Be $true + } + } +}