diff --git a/Actions/.Modules/settings.schema.json b/Actions/.Modules/settings.schema.json index bd7180fe0..52aeee714 100644 --- a/Actions/.Modules/settings.schema.json +++ b/Actions/.Modules/settings.schema.json @@ -751,6 +751,10 @@ }, "value": { "description": "The default value for the workflow input (can be string, boolean, or number)" + }, + "hide": { + "type": "boolean", + "description": "If true, the input is removed from the workflow and the configured value is used directly" } }, "required": ["name", "value"] diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index 82a8d5d22..2b4f7cbef 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -330,10 +330,36 @@ function ApplyWorkflowDefaultInputs { return } + # Helper to convert values to GitHub expression literals + $convertToExpressionLiteral = { + param($value) + + if ($value -is [bool]) { + return $value.ToString().ToLower() + } + + if ($value -is [int] -or $value -is [long] -or $value -is [System.Int16] -or $value -is [byte] -or $value -is [System.SByte] -or $value -is [System.UInt16] -or $value -is [System.UInt32] -or $value -is [System.UInt64]) { + return $value.ToString() + } + + if ($value -is [double] -or $value -is [single] -or $value -is [decimal]) { + return $value.ToString([System.Globalization.CultureInfo]::InvariantCulture) + } + + $escapedValue = ($value -as [string]) -replace "'", "''" + return "'$escapedValue'" + } + + $inputsToHide = @{} + # Apply defaults to matching inputs foreach ($default in $repoSettings.workflowDefaultInputs) { $inputName = $default.name $defaultValue = $default.value + $hideInput = $false + if ($default['hide']) { + $hideInput = [bool]$default.hide + } # Check if this input exists in the workflow $inputSection = $inputs.Get("$($inputName):/") @@ -410,6 +436,22 @@ function ApplyWorkflowDefaultInputs { throw $validationError } + if ($hideInput) { + # Store this input to hide it later + $inputsToHide[$inputName] = [pscustomobject]@{ + value = $defaultValue + expression = & $convertToExpressionLiteral $defaultValue + } + continue + } + else { + # If hide is false, ensure the input is not in the hide list + # This handles the case where a previous entry had hide=true but a later entry has hide=false + if ($inputsToHide.ContainsKey($inputName)) { + $inputsToHide.Remove($inputName) + } + } + # Convert the default value to the appropriate YAML format $yamlValue = $defaultValue if ($defaultValue -is [bool]) { @@ -465,11 +507,82 @@ function ApplyWorkflowDefaultInputs { $inputs.Replace("$($inputName):/", $inputSection.content) } + # Remove hidden inputs from the inputs section + if ($inputsToHide.Count -gt 0) { + # Collect all inputs to remove with their positions + $inputsToRemove = @() + foreach ($entry in $inputsToHide.GetEnumerator()) { + $name = $entry.Key + $start = 0 + $count = 0 + # Find the input including all its content (type, default, etc.) + # Use the name with colon to match the full input section + if ($inputs.Find("$($name):", [ref] $start, [ref] $count)) { + $inputsToRemove += [pscustomobject]@{ + Name = $name + Start = $start + Count = $count + } + } + else { + OutputWarning "Workflow '$workflowName': Unable to hide input '$name' because it was not found in the workflow file." + } + } + + # Remove inputs in reverse order to maintain correct indices + foreach ($item in $inputsToRemove | Sort-Object -Property Start -Descending) { + $inputs.Remove($item.Start, $item.Count) + } + } + # Update the workflow_dispatch section with modified inputs $workflowDispatch.Replace('inputs:/', $inputs.content) # Update the on: section with modified workflow_dispatch $yaml.Replace('on:/workflow_dispatch:/', $workflowDispatch.content) + + # Replace all references to hidden inputs with their literal values + if ($inputsToHide.Count -gt 0) { + foreach ($entry in $inputsToHide.GetEnumerator()) { + $inputName = $entry.Key + $expressionValue = $entry.Value.expression + $rawValue = $entry.Value.value + + # Replace references in expressions: ${{ github.event.inputs.name }} and ${{ inputs.name }} + # These use the expression literal format (true/false for boolean, unquoted number, 'quoted' string) + # Use regex to match any whitespace variations: ${{inputs.name}}, ${{ inputs.name }}, ${{ inputs.name}}, ${{inputs.name }} + # Replace the entire ${{ ... }} expression with just the value + $pattern = "`\$\{\{\s*github\.event\.inputs\.$inputName\s*\}\}" + $yaml.ReplaceAll($pattern, $expressionValue, $true) + $pattern = "`\$\{\{\s*inputs\.$inputName\s*\}\}" + $yaml.ReplaceAll($pattern, $expressionValue, $true) + + # Replace references in if conditions: github.event.inputs.name and inputs.name (without ${{ }}) + # github.event.inputs are always strings (user input from UI) + # inputs.* are typed values (boolean/number/string/choice) + + # For github.event.inputs.NAME: always use string literal format + # Convert the value to a string literal (always quoted) for comparisons + if ($rawValue -is [bool]) { + $stringValue = $rawValue.ToString().ToLower() + } + else { + $stringValue = $rawValue.ToString().Replace("'", "''") + } + $stringLiteral = "'$stringValue'" + $yaml.ReplaceAll("github.event.inputs.$inputName", $stringLiteral) + + # For inputs.NAME: use expression literal format (typed value) + # Replace inputs.NAME but be careful not to match patterns like: + # - needs.inputs.outputs.NAME (where a job is named "inputs") + # - steps.CreateInputs.outputs.NAME (where "inputs" is part of a word) + # Use negative lookbehind (?` or `inputs.` in the workflow file are replaced with the configured value when the "Update AL-Go System Files" workflow runs. + +Example configuration: + +```json +{ + "workflowDefaultInputs": [ + { "name": "directCommit", "value": true }, + { "name": "useGhTokenWorkflow", "value": true, "hide": true } + ] +} +``` + +### Issues & Discussions + +- Discussion 1952 Hide workflow_dispatch input + ## v8.1 ### Custom AL-Go files diff --git a/Scenarios/settings.md b/Scenarios/settings.md index 193e54878..c5054656e 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -83,7 +83,7 @@ The repository settings are only read from the repository settings file (.github | useGitSubmodules | If your repository is using Git Submodules, you can set the `useGitSubmodules` setting to `"true"` or `"recursive"` in order to use these submodules during build workflows. If `useGitSubmodules` is not set, git submodules are not initialized. If the submodules reside in private repositories, you need to define a `gitSubmodulesToken` secret. Read [this](https://aka.ms/algosecrets#gitSubmodulesToken) for more information. | | commitOptions | If you want more control over how AL-Go creates pull requests or commits changes to the repository you can define `commitOptions`. It is a structure defining how you want AL-Go to handle automated commits or pull requests coming from AL-Go (e.g. for Update AL-Go System Files). The structure contains the following properties:
**messageSuffix** = A string you want to append to the end of commits/pull requests created by AL-Go. This can be useful if you are using the Azure Boards integration (or similar integration) to link commits to work items.
`createPullRequest` : A boolean defining whether AL-Go should create a pull request or attempt to push directly in the branch.
**pullRequestAutoMerge** = A boolean defining whether you want AL-Go pull requests to be set to auto-complete. This will auto-complete the pull requests once all checks are green and all required reviewers have approved.
**pullRequestMergeMethod** = A string defining which merge method to use when auto-merging pull requests. Valid values are "merge" and "squash". Default is "squash".
**pullRequestLabels** = A list of labels to add to the pull request. The labels need to be created in the repository before they can be applied.
If you want different behavior in different AL-Go workflows you can add the `commitOptions` setting to your [workflow-specific settings files](https://github.com/microsoft/AL-Go/blob/main/Scenarios/settings.md#where-are-the-settings-located). | | incrementalBuilds | A structure defining how you want AL-Go to handle incremental builds. When using incremental builds for a build, AL-Go will look for the latest successful CI/CD build, newer than the defined `retentionDays` and only rebuild projects or apps (based on `mode`) which needs to be rebuilt. The structure supports the following properties:
**onPush** = Determines whether incremental builds is enabled in CI/CD triggered by a merge/push event. Default is **false**.
**onPull_Request** = Determines whether incremental builds is enabled in Pull Requests. Default is **true**.
**onSchedule** = Determines whether incremental builds is enabled in CI/CD when running on a schedule. Default is **false**.
**retentionDays** = Number of days a successful build is good (and can be used for incremental builds). Default is **30**.
**mode** = Specifies the mode for incremental builds. Currently, two values are supported. Use **modifiedProjects** when you want to rebuild all apps in all modified projects and depending projects or **modifiedApps** if you want to rebuild modified apps and all apps with dependencies to this app.
**NOTE:** when running incremental builds, it is recommended to also set `workflowConcurrency` for the CI/CD workflow, as defined [here](https://aka.ms/algosettings#workflowConcurrency). | -| workflowDefaultInputs | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
  **name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
**Important validation rules:**
  • The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice)
  • For choice inputs, the value must be one of the options declared in the workflow
  • Choice validation is case-sensitive
Type and choice validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to all workflows that have matching input names.
**Usage:** This setting can be used on its own in repository settings to apply defaults to all workflows with matching input names. Alternatively, you can use it within [conditional settings](#conditional-settings) to apply defaults only to specific workflows, branches, or other conditions.
**Important:** When multiple conditional settings blocks match and both define `workflowDefaultInputs`, the arrays are merged (all entries are kept). When the defaults are applied to workflows, the last matching entry for each input name wins.
**Example:**
`"workflowDefaultInputs": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
`]` | +| workflowDefaultInputs | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
  **name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
**Important validation rules:**
  • The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice)
  • For choice inputs, the value must be one of the options declared in the workflow
  • Choice validation is case-sensitive
Type and choice validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to all workflows that have matching input names.
**Usage:** This setting can be used on its own in repository settings to apply defaults to all workflows with matching input names. Alternatively, you can use it within [conditional settings](#conditional-settings) to apply defaults only to specific workflows, branches, or other conditions.
  **hide** (optional) = Set to `true` to remove the input from the workflow UI and use the configured value automatically. When hiding an input, all usages of `github.event.inputs.` or `inputs.` are replaced with the configured value when the "Update AL-Go System Files" workflow runs.
**Important:** When multiple conditional settings blocks match and both define `workflowDefaultInputs`, the arrays are merged (all entries are kept). When the defaults are applied to workflows, the last matching entry for each input name wins.
**Example:**
`"workflowDefaultInputs": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true, "hide": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
`]` | diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index ba31d127c..20cb29f5b 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -204,6 +204,105 @@ Describe "YamlClass Tests" { $srcContent | Should -Be ($resultContent -join "`n") } + + It 'Test YamlClass ReplaceAll with regex - negative lookbehind and word boundaries' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + $yamlContent = @( + "needs: [ inputs ]", + "myOutput: `${{ needs.inputs.outputs.output }}", + "myInput: `${{ inputs.output }}", + "if: inputs.runTests == 'true'", + "step: CreateInputs" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Replace "inputs.output" where "inputs" is NOT preceded by a dot (negative lookbehind) + # and "output" is a complete word (word boundary) + # This matches the actual pattern used in the hide feature: (?> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ Inputs ]", + " steps:", + " - name: Deploy", + " env:", + " branch: `${{ needs.Inputs.outputs.branch }}", + " run: echo Deploying to `$branch" ) - } - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected boolean value*" - } + $yaml = [Yaml]::new($yamlContent) - It 'ApplyWorkflowDefaultInputs validates number type mismatch' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Create settings with hide flag for the branch input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "branch"; "value" = "production"; "hide" = $true } + ) + } - # Create a test workflow YAML with number input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " numberInput:", - " type: number", - " default: 0", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Job Output" + + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('branch:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify that needs.Inputs.outputs.branch is NOT replaced + # This is a job output reference, not a workflow input reference + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.Inputs\.outputs\.branch" + + # Verify that direct input references would be replaced if they existed + # (but they don't exist in this workflow, so we just verify the job reference remains) + $content | Should -Not -Match "github\.event\.inputs\.branch" + $content | Should -Not -Match "`\$\{\{ inputs\.branch \}\}" + } + + It 'does not replace parts of job output references with input names like output' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Edge case: input named "output" should not replace "inputs.outputs.output" or "outputs.output" + # Using lowercase "inputs" as job name to test the problematic case + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " output:", + " type: string", + " default: 'default'", + "jobs:", + " inputs:", + " runs-on: ubuntu-latest", + " outputs:", + " output: `${{ steps.CreateInputs.outputs.output }}", + " steps:", + " - name: Create inputs", + " id: CreateInputs", + " run: echo 'output=test' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ inputs ]", + " steps:", + " - name: Deploy", + " env:", + " myOutput: `${{ needs.inputs.outputs.output }}", + " run: echo Using `$myOutput" + ) - $yaml = [Yaml]::new($yamlContent) + $yaml = [Yaml]::new($yamlContent) - # Create settings with wrong type (string instead of number) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "numberInput"; "value" = "not a number" } + # Create settings with hide flag for the "output" input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "output"; "value" = "hidden"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Input Named Output" + + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('output:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify that needs.inputs.outputs.output is NOT replaced + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.inputs\.outputs\.output" + $content | Should -Match "steps\.CreateInputs\.outputs\.output" + + # Verify that direct input references would be replaced if they existed + $content | Should -Not -Match "github\.event\.inputs\.output" + $content | Should -Not -Match "`\$\{\{ inputs\.output \}\}" + } + + It 'does not replace job output references when input is named outputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Edge case: input named "outputs" should not replace "inputs.outputs" in job output contexts + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " outputs:", + " type: string", + " default: 'default'", + "jobs:", + " Initialization:", + " runs-on: ubuntu-latest", + " outputs:", + " telemetryScopeJson: `${{ steps.init.outputs.telemetryScopeJson }}", + " steps:", + " - name: Initialize", + " id: init", + " run: echo 'telemetryScopeJson={}' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ Initialization ]", + " steps:", + " - name: Deploy", + " env:", + " telemetryScope: `${{ needs.Initialization.outputs.telemetryScopeJson }}", + " run: echo Using `$telemetryScope", + " - name: Use input", + " run: echo `${{ inputs.outputs }}" ) - } - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected number value*" - } + $yaml = [Yaml]::new($yamlContent) - It 'ApplyWorkflowDefaultInputs validates string type mismatch' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Create settings with hide flag for the "outputs" input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "outputs"; "value" = "hidden-value"; "hide" = $true } + ) + } - # Create a test workflow YAML with string input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " stringInput:", - " type: string", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Input Named Outputs" + + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('outputs:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify that needs.Initialization.outputs.telemetryScopeJson is NOT replaced + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.Initialization\.outputs\.telemetryScopeJson" + $content | Should -Match "steps\.init\.outputs\.telemetryScopeJson" + + # Verify that the actual input reference WAS replaced + $content | Should -Match "echo 'hidden-value'" + $content | Should -Not -Match "github\.event\.inputs\.outputs" + $content | Should -Not -Match "`\$\{\{ inputs\.outputs \}\}" + } + + It 'does not replace job outputs when job is named inputs and input is named outputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # This is the real edge case: job named "inputs" (lowercase) with outputs, and an input also named "outputs" + # needs.inputs.outputs.something should NOT be replaced (it's a job output reference) + # but ${{ inputs.outputs }} should be replaced (it's a workflow input reference) + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " outputs:", + " type: string", + " default: 'default'", + "jobs:", + " inputs:", + " runs-on: ubuntu-latest", + " outputs:", + " myValue: `${{ steps.init.outputs.myValue }}", + " steps:", + " - name: Initialize", + " id: init", + " run: echo 'myValue=test' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ inputs ]", + " steps:", + " - name: Use job output", + " env:", + " value: `${{ needs.inputs.outputs.myValue }}", + " run: echo Job output is `$value", + " - name: Use input", + " run: echo Input is `${{ inputs.outputs }}" + ) - $yaml = [Yaml]::new($yamlContent) + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flag for the "outputs" input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "outputs"; "value" = "hidden-input-value"; "hide" = $true } + ) + } - # Create settings with wrong type (boolean instead of string) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "stringInput"; "value" = $true } + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Job Named Inputs" + + # Verify the hidden input was removed + $inputsSection = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputsSection.Find('outputs:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify that needs.inputs.outputs.myValue is NOT replaced (job output reference) + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.inputs\.outputs\.myValue" + + # Verify that steps.init.outputs.myValue is NOT replaced (step output reference) + $content | Should -Match "steps\.init\.outputs\.myValue" + + # Verify that inputs.outputs WAS replaced with the hidden value + $content | Should -Match "echo Input is 'hidden-input-value'" + $content | Should -Not -Match "`\$\{\{ inputs\.outputs \}\}" + } + + It 'silently skips hiding non-existent input' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with only one input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " existingInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.existingInput }}" ) - } - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected string value*" - } + $yaml = [Yaml]::new($yamlContent) - It 'ApplyWorkflowDefaultInputs validates choice type' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Create settings trying to hide an input that doesn't exist + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "existingInput"; "value" = "test-value" }, + @{ "name" = "nonExistentInput"; "value" = "hidden-value"; "hide" = $true } + ) + } - # Create a test workflow YAML with choice input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " choiceInput:", - " type: choice", - " options:", - " - option1", - " - option2", - " default: option1", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Mock OutputWarning to verify no warning is issued + Mock OutputWarning { } + + # Apply the defaults - should not throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Non-Existent" } | Should -Not -Throw + + # Verify no warning was issued + Assert-MockCalled OutputWarning -Times 0 + + # Verify the existing input was updated but not hidden + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('existingInput:', [ref] $null, [ref] $null) | Should -Be $true + $yaml.Get('on:/workflow_dispatch:/inputs:/existingInput:/default:').content -join '' | Should -Be "default: 'test-value'" + + # Verify the workflow content was not affected by the non-existent input + $content = $yaml.content -join "`n" + $content | Should -Match "`\$\{\{ inputs\.existingInput \}\}" + } + + It 'hides multiple inputs in the same workflow' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with multiple inputs to hide + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " visibleInput:", + " type: string", + " default: ''", + " hiddenInput1:", + " type: boolean", + " default: false", + " hiddenInput2:", + " type: string", + " default: ''", + " hiddenInput3:", + " type: number", + " default: 0", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Use inputs", + " run: echo `${{ inputs.hiddenInput1 }} `${{ inputs.hiddenInput2 }} `${{ inputs.hiddenInput3 }}", + " - name: Use visible", + " run: echo `${{ inputs.visibleInput }}" + ) - $yaml = [Yaml]::new($yamlContent) + $yaml = [Yaml]::new($yamlContent) + + # Create settings with multiple hidden inputs + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "visibleInput"; "value" = "visible" }, + @{ "name" = "hiddenInput1"; "value" = $true; "hide" = $true }, + @{ "name" = "hiddenInput2"; "value" = "hidden-string"; "hide" = $true }, + @{ "name" = "hiddenInput3"; "value" = 42; "hide" = $true } + ) + } - # Create settings with correct type (string for choice) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "choiceInput"; "value" = "option2" } + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Multiple Hidden" + + # Verify all hidden inputs were removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('hiddenInput1:', [ref] $null, [ref] $null) | Should -Be $false + $inputs.Find('hiddenInput2:', [ref] $null, [ref] $null) | Should -Be $false + $inputs.Find('hiddenInput3:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify visible input still exists + $inputs.Find('visibleInput:', [ref] $null, [ref] $null) | Should -Be $true + + # Verify all references were replaced + $content = $yaml.content -join "`n" + $content | Should -Match "echo true 'hidden-string' 42" + $content | Should -Match "echo `\$\{\{ inputs\.visibleInput \}\}" + $content | Should -Not -Match "inputs\.hiddenInput1" + $content | Should -Not -Match "inputs\.hiddenInput2" + $content | Should -Not -Match "inputs\.hiddenInput3" + } + + It 'replaces hidden input references in different expression contexts' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with inputs used in various contexts + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " hiddenBool:", + " type: boolean", + " default: false", + " hiddenString:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " if: inputs.hiddenBool == true", + " env:", + " MY_VAR: `${{ inputs.hiddenString }}", + " steps:", + " - name: Checkout", + " uses: actions/checkout@v4", + " with:", + " ref: `${{ inputs.hiddenString }}", + " - name: Step with if", + " if: inputs.hiddenBool", + " run: echo Running", + " - name: Step with env", + " env:", + " BRANCH: `${{ inputs.hiddenString }}", + " run: echo `$BRANCH" ) - } - # Apply the defaults - should succeed - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - $yaml.Get('on:/workflow_dispatch:/inputs:/choiceInput:/default:').content -join '' | Should -Be "default: 'option2'" - } + $yaml = [Yaml]::new($yamlContent) - It 'ApplyWorkflowDefaultInputs validates choice value is in available options' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Create settings with hide flags + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "hiddenBool"; "value" = $true; "hide" = $true }, + @{ "name" = "hiddenString"; "value" = "main"; "hide" = $true } + ) + } - # Create a test workflow YAML with choice input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " choiceInput:", - " type: choice", - " options:", - " - option1", - " - option2", - " - option3", - " default: option1", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Expression Contexts" + + # Verify all references were replaced correctly in different contexts + $content = $yaml.content -join "`n" + $content | Should -Match "if: true == true" # Job-level if + $content | Should -Match "MY_VAR: 'main'" # Job-level env + $content | Should -Match "ref: 'main'" # Action with parameter + $content | Should -Match "if: true" # Step-level if + $content | Should -Match "BRANCH: 'main'" # Step-level env + $content | Should -Not -Match "inputs\.hiddenBool" + $content | Should -Not -Match "inputs\.hiddenString" + } + + It 'does not replace partial input name matches' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with inputs that have overlapping names + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " test:", + " type: string", + " default: ''", + " testInput:", + " type: string", + " default: ''", + " mytest:", + " type: string", + " default: ''", + "jobs:", + " run:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Use inputs", + " run: echo `${{ inputs.test }} `${{ inputs.testInput }} `${{ inputs.mytest }}" + ) - $yaml = [Yaml]::new($yamlContent) + $yaml = [Yaml]::new($yamlContent) - # Create settings with invalid choice value - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "choiceInput"; "value" = "invalidOption" } + # Hide only the "test" input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "test"; "value" = "hidden-test"; "hide" = $true }, + @{ "name" = "testInput"; "value" = "visible-testInput" }, + @{ "name" = "mytest"; "value" = "visible-mytest" } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Partial Match" + + # Verify only "test" was hidden, not "testInput" or "mytest" + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('test:', [ref] $null, [ref] $null) | Should -Be $false + $inputs.Find('testInput:', [ref] $null, [ref] $null) | Should -Be $true + $inputs.Find('mytest:', [ref] $null, [ref] $null) | Should -Be $true + + # Verify only inputs.test was replaced + $content = $yaml.content -join "`n" + $content | Should -Match "echo 'hidden-test' `\$\{\{ inputs\.testInput \}\} `\$\{\{ inputs\.mytest \}\}" + } + + It 'hides choice type inputs correctly' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with choice input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " releaseType:", + " type: choice", + " options:", + " - Release", + " - Prerelease", + " - Draft", + " default: Release", + "jobs:", + " release:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Create release", + " run: echo Creating `${{ inputs.releaseType }} release" ) - } - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*not a valid choice*" - } + $yaml = [Yaml]::new($yamlContent) - It 'ApplyWorkflowDefaultInputs validates choice value with case-sensitive matching' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Hide the choice input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "releaseType"; "value" = "Prerelease"; "hide" = $true } + ) + } - # Create a test workflow YAML with choice input using mixed case options - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " releaseTypeInput:", - " type: choice", - " options:", - " - Release", - " - Prerelease", - " - Draft", - " default: Release", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Choice" + + # Verify the choice input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('releaseType:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify the reference was replaced with the choice value + $content = $yaml.content -join "`n" + $content | Should -Match "echo Creating 'Prerelease' release" + $content | Should -Not -Match "inputs\.releaseType" + } + + It 'hides environment type inputs correctly' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with environment input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " environment:", + " type: environment", + " default: ''", + "jobs:", + " deploy:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Deploy", + " run: echo Deploying to `${{ inputs.environment }}" + ) - $yaml = [Yaml]::new($yamlContent) + $yaml = [Yaml]::new($yamlContent) + + # Hide the environment input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "environment"; "value" = "production"; "hide" = $true } + ) + } - # Test 1: Exact case match should succeed - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "releaseTypeInput"; "value" = "Prerelease" } + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Environment" + + # Verify the environment input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('environment:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify the reference was replaced + $content = $yaml.content -join "`n" + $content | Should -Match "echo Deploying to 'production'" + $content | Should -Not -Match "inputs\.environment" + } + + It 'handles input references without whitespace' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with various spacing patterns + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: No spaces", + " run: echo `${{inputs.myInput}}", + " - name: Normal spaces", + " run: echo `${{ inputs.myInput }}", + " - name: Asymmetric left", + " run: echo `${{ inputs.myInput}}", + " - name: Asymmetric right", + " run: echo `${{inputs.myInput }}", + " - name: Multiple on same line", + " run: echo `${{inputs.myInput}} and `${{ inputs.myInput }}" ) - } - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - $yaml.Get('on:/workflow_dispatch:/inputs:/releaseTypeInput:/default:').content -join '' | Should -Be "default: 'Prerelease'" + $yaml = [Yaml]::new($yamlContent) + + # Hide the input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "myInput"; "value" = "test-value"; "hide" = $true } + ) + } - # Test 2: Wrong case should fail with case-sensitive error message - $yaml2 = [Yaml]::new($yamlContent) - $repoSettings2 = @{ - "workflowDefaultInputs" = @( - @{ "name" = "releaseTypeInput"; "value" = "prerelease" } + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Spacing" + + # Verify all variations were replaced with the same normalized output + $content = $yaml.content -join "`n" + + # All spacing variations should be replaced with just the value (in quotes for strings) + # Verify the four single-value lines all got replaced the same way + ($content -split "`n" | Where-Object { $_ -match "^\s+run: echo 'test-value'`$" }).Count | Should -Be 4 -Because "Four lines with single value should all be normalized" + + # Verify the multiple-on-same-line case + $content -match "(?m)^\s+run: echo 'test-value' and 'test-value'`$" | Should -Be $true -Because "Multiple on same line should both be replaced" + + # Verify no input references remain + $content | Should -Not -Match "inputs\.myInput" + } + + It 'replaces hidden input references in complex expressions' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with complex expressions + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " enableTests:", + " type: boolean", + " default: false", + " branch:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " if: inputs.enableTests == true && inputs.branch != ''", + " steps:", + " - name: Conditional", + " if: inputs.enableTests && needs.job.outputs.value", + " run: echo Testing", + " - name: Fallback", + " run: echo `${{ inputs.branch || 'main' }}" ) - } - { ApplyWorkflowDefaultInputs -yaml $yaml2 -repoSettings $repoSettings2 -workflowName "Test Workflow" } | Should -Throw "*case-sensitive match required*" + $yaml = [Yaml]::new($yamlContent) - # Test 3: Uppercase version should also fail - $yaml3 = [Yaml]::new($yamlContent) - $repoSettings3 = @{ - "workflowDefaultInputs" = @( - @{ "name" = "releaseTypeInput"; "value" = "PRERELEASE" } + # Hide both inputs + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "enableTests"; "value" = $true; "hide" = $true }, + @{ "name" = "branch"; "value" = "develop"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Complex Expressions" + + # Verify complex expressions were handled correctly + $content = $yaml.content -join "`n" + $content | Should -Match "if: true == true && 'develop' != ''" + $content | Should -Match "if: true && needs\.job\.outputs\.value" + $content | Should -Match "echo `\$\{\{ 'develop' \|\| 'main' \}\}" + $content | Should -Not -Match "inputs\.enableTests" + $content | Should -Not -Match "inputs\.branch" + } + + It 'handles case-insensitive input references' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with mixed case references + # Note: GitHub Actions itself is case-sensitive for input references in workflows + # This test verifies our hide feature respects the actual case used + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Lower case", + " run: echo `${{ inputs.myInput }}" ) - } - { ApplyWorkflowDefaultInputs -yaml $yaml3 -repoSettings $repoSettings3 -workflowName "Test Workflow" } | Should -Throw "*case-sensitive match required*" - } + $yaml = [Yaml]::new($yamlContent) - It 'ApplyWorkflowDefaultInputs handles inputs without type specification' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Hide the input (case should not matter for input name matching) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "MYINPUT"; "value" = "test-value"; "hide" = $true } + ) + } - # Create a test workflow YAML without type (defaults to string) - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " noTypeInput:", - " description: Input without type", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Case Sensitivity" + + # Verify the input was hidden (case-insensitive matching for input names) + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('myInput:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify the reference was replaced + $content = $yaml.content -join "`n" + $content | Should -Match "echo 'test-value'" + } + + It 'handles empty string values when hiding' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " emptyInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.emptyInput }}" + ) - $yaml = [Yaml]::new($yamlContent) + $yaml = [Yaml]::new($yamlContent) + + # Hide with empty string value + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "emptyInput"; "value" = ""; "hide" = $true } + ) + } - # Create settings with string value (should work without warning) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "noTypeInput"; "value" = "string value" } + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Empty Value" + + # Verify the input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('emptyInput:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify the reference was replaced with empty quoted string + $content = $yaml.content -join "`n" + $content | Should -Match "echo ''" + $content | Should -Not -Match "inputs\.emptyInput" + } + + It 'handles special characters in hidden values' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " input1:", + " type: string", + " default: ''", + " input2:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.input1 }} and `${{ inputs.input2 }}" ) - } - # Apply the defaults - should succeed - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - $yaml.Get('on:/workflow_dispatch:/inputs:/noTypeInput:/default:').content -join '' | Should -Be "default: 'string value'" - } + $yaml = [Yaml]::new($yamlContent) - It 'ApplyWorkflowDefaultInputs escapes single quotes in string values' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Hide with values containing special characters + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "input1"; "value" = "value with 'quotes'"; "hide" = $true }, + @{ "name" = "input2"; "value" = "+0.1"; "hide" = $true } + ) + } - # Create a test workflow YAML with string input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " nameInput:", - " type: string", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Special Chars" + + # Verify values are properly escaped + $content = $yaml.content -join "`n" + $content | Should -Match "echo 'value with ''quotes''' and '\+0\.1'" + $content | Should -Not -Match "inputs\.input1" + $content | Should -Not -Match "inputs\.input2" + } + + It 'handles hidden input with no references in workflow' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML where input is defined but never referenced + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " unusedInput:", + " type: string", + " default: ''", + " usedInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.usedInput }}" + ) - $yaml = [Yaml]::new($yamlContent) + $yaml = [Yaml]::new($yamlContent) + + # Hide the unused input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "unusedInput"; "value" = "hidden-unused"; "hide" = $true }, + @{ "name" = "usedInput"; "value" = "visible" } + ) + } - # Create settings with string value containing single quote - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "nameInput"; "value" = "O'Brien" } + # Apply the defaults - should not throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Unused Input" } | Should -Not -Throw + + # Verify the unused input was still removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('unusedInput:', [ref] $null, [ref] $null) | Should -Be $false + $inputs.Find('usedInput:', [ref] $null, [ref] $null) | Should -Be $true + } + + It 'handles workflow with all inputs hidden' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with only inputs that will be hidden + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " input1:", + " type: string", + " default: ''", + " input2:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.input1 }} `${{ inputs.input2 }}" ) - } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + $yaml = [Yaml]::new($yamlContent) - # Verify single quote is escaped per YAML spec (doubled) - $yaml.Get('on:/workflow_dispatch:/inputs:/nameInput:/default:').content -join '' | Should -Be "default: 'O''Brien'" - } + # Hide all inputs + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "input1"; "value" = "hidden1"; "hide" = $true }, + @{ "name" = "input2"; "value" = $true; "hide" = $true } + ) + } - It 'ApplyWorkflowDefaultInputs applies last value when multiple entries have same input name' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - All Hidden" + + # Verify all inputs were removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('input1:', [ref] $null, [ref] $null) | Should -Be $false + $inputs.Find('input2:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify references were replaced + $content = $yaml.content -join "`n" + $content | Should -Match "echo 'hidden1' true" + } + + It 'applies last value when duplicate entries have different hide flags' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.myInput }}" + ) - # Create a test workflow YAML - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " input1:", - " type: string", - " default: ''", - " input2:", - " type: boolean", - " default: false", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + $yaml = [Yaml]::new($yamlContent) - $yaml = [Yaml]::new($yamlContent) + # Create settings with duplicate entries where first has hide=false, last has hide=true + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "myInput"; "value" = "first-value"; "hide" = $false }, + @{ "name" = "myInput"; "value" = "final-value"; "hide" = $true } + ) + } - # Create settings with duplicate entries for input1 - simulating merged conditional settings - # This can happen when multiple conditionalSettings blocks both match and both define the same input - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "input1"; "value" = "first-value" }, - @{ "name" = "input2"; "value" = $false }, - @{ "name" = "input1"; "value" = "second-value" }, # Duplicate input1 - @{ "name" = "input1"; "value" = "final-value" } # Another duplicate input1 + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Duplicate Hide" + + # Verify last entry wins - input should be hidden + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('myInput:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify the final hidden value was used + $content = $yaml.content -join "`n" + $content | Should -Match "echo 'final-value'" + $content | Should -Not -Match "inputs\.myInput" + } + + It 'applies last value when duplicate entries have hide reversed' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.myInput }}" ) - } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + $yaml = [Yaml]::new($yamlContent) - # Verify "last wins" - the final value for input1 should be applied - $yaml.Get('on:/workflow_dispatch:/inputs:/input1:/default:').content -join '' | Should -Be "default: 'final-value'" - $yaml.Get('on:/workflow_dispatch:/inputs:/input2:/default:').content -join '' | Should -Be 'default: false' - } + # Create settings with duplicate entries where first has hide=true, last has hide=false + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "myInput"; "value" = "first-value"; "hide" = $true }, + @{ "name" = "myInput"; "value" = "final-value"; "hide" = $false } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Duplicate Hide Reversed" + + # Verify last entry wins - input should NOT be hidden + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('myInput:', [ref] $null, [ref] $null) | Should -Be $true + + # Verify the final value was applied to the default + $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:').content -join '' | Should -Be "default: 'final-value'" + + # Verify the reference was NOT replaced (input is visible) + $content = $yaml.content -join "`n" + $content | Should -Match "echo `\$\{\{ inputs\.myInput \}\}" + $content | Should -Not -Match "echo 'final-value'" + } + + } # End of Context 'ApplyWorkflowDefaultInputs - Hide Feature' } Describe "ResolveFilePaths" {