core/private/Invoke-JaxPsakeRunner.ps1
|
function Invoke-JaxPsakeRunner { [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [System.Collections.IDictionary] $Entity, [hashtable] $Context = @{}, [hashtable] $CommonParameters = @{} ) Write-Debug "FUNC: $($MyInvocation.MyCommand.Name) Args: $($Entity['Key']) Psake=$($Entity['PsakeFile'])" Initialize-JaxPsakeProvider -RepoRoot $Context['RepoRoot'] @CommonParameters $buildFile = $Entity['PsakeFile'] if ([string]::IsNullOrWhiteSpace($buildFile) -and $Context.ContainsKey('PsakeFile')) { $buildFile = $Context['PsakeFile'] } if ([string]::IsNullOrWhiteSpace($buildFile)) { throw "Psake runner requires 'PsakeFile' (entity or context)." } $buildFile = Resolve-JaxRepoRootedPath -Path $buildFile -RepoRoot $Context['RepoRoot'] -WorkingDir $Context['WorkingDir'] @CommonParameters $taskList = @() if ($Entity.Contains('Tasks') -and $null -ne $Entity['Tasks']) { $tasks = $Entity['Tasks'] if ($tasks -is [string]) { $taskList = @($tasks) } else { $taskList = @($tasks) } } $properties = @{} $baseProps = $null if ($Context.ContainsKey('PsakeProperties') -and $Context['PsakeProperties'] -is [System.Collections.IDictionary]) { if ($null -eq $baseProps) { $baseProps = $Context['PsakeProperties'] } else { $baseProps = Merge-JaxHashtable -Base $baseProps -Overlay $Context['PsakeProperties'] @CommonParameters } } if ($Context.ContainsKey('RunConfig') -and $Context['RunConfig'] -is [System.Collections.IDictionary]) { if ($null -eq $baseProps) { $baseProps = $Context['RunConfig'] } else { # precedence: psake properties override run-config defaults $baseProps = Merge-JaxHashtable -Base $Context['RunConfig'] -Overlay $baseProps @CommonParameters } } if ($null -ne $baseProps) { $properties = Merge-JaxHashtable -Base @{} -Overlay $baseProps @CommonParameters } if ($Entity['Args'] -is [System.Collections.IDictionary]) { $properties = Merge-JaxHashtable -Base $properties -Overlay $Entity['Args'] @CommonParameters } # CLI DynamicParam-bound task params: apply last (highest precedence) if ($Context.ContainsKey('CliArgs') -and $Context['CliArgs'] -is [System.Collections.IDictionary]) { $properties = Merge-JaxHashtable -Base $properties -Overlay $Context['CliArgs'] @CommonParameters } $taskListDisplay = if ($taskList.Count -gt 0) { $taskList -join ', ' } else { '<default>' } $commandPreview = "Invoke-psake -buildFile `"$buildFile`" -taskList @('$taskListDisplay')" if ($Context['DryRun']) { return $commandPreview } $repoRoot = $Context['RepoRoot'] if ([string]::IsNullOrWhiteSpace($repoRoot)) { $repoRoot = Get-JaxRepoRoot @CommonParameters } if (-not [string]::IsNullOrWhiteSpace($repoRoot)) { $propertiesForLog = $properties if ($Context.ContainsKey('VaultSecretsResolved') -and [bool]$Context['VaultSecretsResolved']) { $propertiesForLog = '[redacted: Vault-resolved properties]' } $payload = @{ buildFile = $buildFile taskList = $taskList properties = $propertiesForLog } $safePayload = Convert-JaxToJsonSafeValue -Value $payload -MaxDepth 40 @CommonParameters $logPath = Write-JaxLogFile -Content ($safePayload | ConvertTo-Json -Depth 40) -Category 'psake-params' -Extension 'json' -RepoRoot $repoRoot @CommonParameters Write-Host ("🪵 → 🧾 Psake params saved: {0}" -f (Format-JaxPathLink -Path $logPath @CommonParameters)) } if ($Context.ContainsKey('RunConfigPath') -and -not [string]::IsNullOrWhiteSpace($Context['RunConfigPath'])) { Write-Host ("🔍 Run config: {0}" -f (Format-JaxPathLink -Path $Context['RunConfigPath'] @CommonParameters)) } if ($taskList.Count -gt 0) { Write-JaxConsoleLine -Text "🔥 Executing task " -Color 'DarkGray' -NoNewline @CommonParameters Write-JaxConsoleLine -Text ("'{0}'" -f ($taskList -join ', ')) -Color 'DeepSkyBlue' @CommonParameters } Write-Host "🪵 → 🍵 Invoking psake with these parameters." Write-Host ("🚀 Invoking psake: {0}" -f (Format-JaxPathLink -Path $buildFile @CommonParameters)) # --- Terminal tab title integration (Ghostty/cmux only, zero impact elsewhere) --- $mainTaskForTitle = if ($taskList.Count -gt 0) { $taskList[0] } else { [string]$Entity['Key'] } $envForTitle = $null $flowForTitle = $null try { # Try several sources for a clean env/flow name. The formatter also normalizes aggressively now. $candidates = @() if ($Context.ContainsKey('RunConfig') -and $Context['RunConfig']) { $candidates += $Context['RunConfig'] } if ($properties -and $properties.Contains('env')) { $candidates += $properties['env'] } if ($properties -and $properties.Contains('flow')) { $candidates += $properties['flow'] } if ($Context.ContainsKey('SelectedEnv') -and $Context['SelectedEnv']) { $candidates += $Context['SelectedEnv'] } foreach ($c in $candidates) { if (-not $envForTitle) { if ($c -is [System.Collections.IDictionary]) { if ($c.Contains('name')) { $envForTitle = $c['name'] } elseif ($c.Contains('env')) { $envForTitle = $c['env'] } } elseif ($c.PSObject) { if ($c.PSObject.Properties.Match('Name').Count -gt 0) { $envForTitle = [string]$c.Name } elseif ($c.PSObject.Properties.Match('name').Count -gt 0) { $envForTitle = [string]$c.name } } } if (-not $flowForTitle) { if ($c -is [System.Collections.IDictionary]) { if ($c.Contains('flow')) { $flowForTitle = $c['flow'] } elseif ($c.Contains('configuration')) { $flowForTitle = $c['configuration'] } } elseif ($c.PSObject) { if ($c.PSObject.Properties.Match('Flow').Count -gt 0) { $flowForTitle = [string]$c.Flow } elseif ($c.PSObject.Properties.Match('Configuration').Count -gt 0) { $flowForTitle = [string]$c.Configuration } } } } } catch {} # Set title for the outer psake task immediately try { $outerTitle = Get-JaxTabTitleForRun -EntityKey $mainTaskForTitle -EnvName $envForTitle -FlowName $flowForTitle if ($outerTitle) { Set-JaxTerminalTabTitle -Title $outerTitle } } catch {} # Register a TaskSetup so every inner psake task ("EnsureFlutter", "RestoreFlutterApp", etc.) # updates the tab title in supported terminals. This is the key UX the user asked for. try { $titleEnv = $envForTitle $titleFlow = $flowForTitle $titleMain = $mainTaskForTitle TaskSetup { param($task) try { $inner = '' if ($null -ne $task) { if ($task.PSObject.Properties.Match('Name').Count -gt 0) { $inner = [string]$task.Name } elseif ($task -is [string]) { $inner = $task } } if ([string]::IsNullOrWhiteSpace($inner) -and $psake -and $psake.context -and $psake.context.Count -gt 0) { $peek = $psake.context.Peek() if ($peek -and $peek.PSObject.Properties.Match('currentTaskName').Count -gt 0) { $inner = [string]$peek.currentTaskName } } $t = Get-JaxTabTitleForRun -EntityKey $titleMain -EnvName $titleEnv -FlowName $titleFlow -CurrentPsakeTask $inner if ($t) { Set-JaxTerminalTabTitle -Title $t } } catch { # never break a build because of title } } } catch { # If TaskSetup registration fails for any reason (old psake, scope, etc.) we just skip titles for inner tasks. } # IMPORTANT: # psake uses an internal variable named `$module` during initialization: # . $module $initialization # If we pass a property key named `module`, psake will create a variable `$module` (from -properties) # and clobber its internal `$module`, breaking initialization. # # But our tasks rely on `$properties.module.*` heavily. # Solution: temporarily move `module` to a safe key so psake won't create `$module`, # then re-inject it AFTER psake initialization via the -initialization hook. $psakePropertiesObject = $properties $initialization = $null $originalModuleKey = $null if ($psakePropertiesObject -is [System.Collections.IDictionary]) { foreach ($k in @($psakePropertiesObject.Keys)) { if ($null -eq $k) { continue } if (([string]$k).ToLowerInvariant() -eq 'module') { $originalModuleKey = [string]$k break } } } if (-not [string]::IsNullOrWhiteSpace($originalModuleKey)) { $moduleKey = '__jax_module' $hasInternalKey = $false foreach ($k in @($psakePropertiesObject.Keys)) { if ($null -ne $k -and ([string]$k).ToLowerInvariant() -eq $moduleKey) { $hasInternalKey = $true break } } if ($hasInternalKey) { throw "Invoke-JaxPsakeRunner: internal key collision: properties already contains '$moduleKey'." } $psakePropertiesObject[$moduleKey] = $psakePropertiesObject[$originalModuleKey] $psakePropertiesObject.Remove($originalModuleKey) | Out-Null $initialization = { # restore module tree for tasks (after psake init) $hasModule = $false foreach ($k in @($properties.Keys)) { if ($null -ne $k -and ([string]$k).ToLowerInvariant() -eq 'module') { $hasModule = $true break } } if (-not $hasModule -and $properties -is [System.Collections.IDictionary] -and $properties.Keys -contains '__jax_module') { $properties['module'] = $properties['__jax_module'] $properties.Remove('__jax_module') | Out-Null } } } # Wrap the actual psake invocation so that even if a task throws, the build aborts, # or the user hits Ctrl-C, we finalize the tab title. On finish we leave the last # task on the tab stamped with ✓ (success) or ✗ (failure) rather than restoring the # pre-jax label. The catch only flags failure (so the finally picks ✗) and rethrows. try { if ($taskList.Count -gt 0) { if ($null -ne $initialization) { return Invoke-psake -buildFile $buildFile -taskList $taskList -properties $psakePropertiesObject -initialization $initialization -nologo @CommonParameters } return Invoke-psake -buildFile $buildFile -taskList $taskList -properties $psakePropertiesObject -nologo @CommonParameters } if ($null -ne $initialization) { return Invoke-psake -buildFile $buildFile -properties $psakePropertiesObject -initialization $initialization -nologo @CommonParameters } return Invoke-psake -buildFile $buildFile -properties $psakePropertiesObject -nologo @CommonParameters } catch { $script:JaxTitleRunFailed = $true throw } finally { try { Complete-JaxTerminalTitle } catch {} } } |