src/App/Startup.ps1
|
# Startup — Punto de entrada del v3. # Construye el grafo de servicios + UI y renderiza MainScreen con datos reales. function Start-RepoNavV3 { param( [string] $ThemeKey = 'midnight', [string] $ReposPath = '' ) # UTF-8 output para glifos Unicode (●, ⎇, ▲, etc.) try { [Console]::OutputEncoding = [Text.Encoding]::UTF8 $OutputEncoding = [Text.Encoding]::UTF8 } catch { # Algunos hosts (ISE legacy) no soportan setear OutputEncoding — seguimos # con el default. No queremos abortar el startup por esto. $null = $_ } if (-not [AnsiService]::SupportsTrueColor()) { Write-Warning "Esta terminal no parece soportar truecolor. La paleta puede degradarse." } # 1) Servicios $settingsSvc = [SettingsService]::new() $settings = $settingsSvc.Load() # ThemeKey: param explícito > settings persistidos > default 'midnight' $effectiveTheme = if ($PSBoundParameters.ContainsKey('ThemeKey')) { $ThemeKey } elseif ($settings.ThemeKey) { $settings.ThemeKey } else { 'midnight' } # Validamos el theme antes de instanciar el service. Si el key no existe # (typo del usuario o settings stale apuntando a un theme removido), aviso # y caigo a midnight — preferible a tirar excepción cruda en el primer render. if (-not $global:RNThemes.ContainsKey($effectiveTheme)) { $available = @($global:RNThemes.Keys | Sort-Object) -join ', ' Write-Warning "Theme '$effectiveTheme' no existe. Disponibles: $available. Usando 'midnight'." $effectiveTheme = 'midnight' } $themeSvc = [ThemeService]::new($effectiveTheme) $i18nSvc = [I18nService]::new($settings.Language) $gitSvc = [GitService]::new() $disco = [RepoDiscoveryService]::new($gitSvc) # Cableamos el callback OnFetchSuccess: cada vez que GitService completa un # pull/push/fetch, actualizamos LastFetchByRepo + persistimos. El scriptblock # captura $settingsSvc / $settings por closure — sobreviven mientras Startup # esté en stack (lo cual es siempre durante la sesión). $gitSvc.OnFetchSuccess = { param($repoPath) if (-not $repoPath) { return } # Derivamos el repoId del path (mismo algoritmo que Repo.Id en GetRepoState). $name = Split-Path $repoPath -Leaf $repoId = $name.ToLower() -replace '[^a-z0-9]', '-' $settings.RecordFetch($repoId) # Persiste async-fire-and-forget — Save() no bloquea visible al usuario, # y si falla por race con otro Save (poco probable), el siguiente lo arregla. $null = $settingsSvc.Save($settings) }.GetNewClosure() # 2) UI primitives + componentes $renderer = [Renderer]::new($themeSvc) $primitives = [Primitives]::new($themeSvc) $frame = [Frame]::new($themeSvc, $renderer) $header = [AppHeader]::new($themeSvc, $renderer, $primitives) $statusBar = [StatusBar]::new($themeSvc, $renderer, $primitives) # 3) Path raíz: param explícito > cwd > carpeta padre del entry if (-not $ReposPath) { $ReposPath = (Get-Location).Path # Si estamos parados DENTRO de un repo, escaneamos al padre if ([GitService]::new().IsGitRepo($ReposPath)) { $ReposPath = Split-Path $ReposPath -Parent } } # 4) Discover — modo controlado por Settings.AutoLoadMode # All → carga todos los repos con git status (default UX rico) # Favorites → solo los que están en FavoriteIds; el resto unloaded # None → todos unloaded; user pulsa R o navega para cargar $autoLoadMode = if ($settings.AutoLoadMode) { $settings.AutoLoadMode } else { 'All' } if ($autoLoadMode -eq 'All') { # Paralelo cuando hay suficientes repos para que valga la pena el # spinup. DiscoverParallel ya hace el threshold internamente — fallback # a secuencial si N < 5. $repos = $disco.DiscoverParallel($ReposPath) } else { $shallow = $disco.DiscoverShallow($ReposPath) if ($autoLoadMode -eq 'Favorites' -and $settings.FavoriteIds.Count -gt 0) { # Cargo status solo de los favoritos. El resto queda unloaded. $favSet = @{} foreach ($id in $settings.FavoriteIds) { $favSet[$id] = $true } $repos = @() foreach ($r in $shallow) { if ($favSet.ContainsKey($r.Id)) { $repos += $gitSvc.GetRepoState($r.Path) } else { $repos += $r } } } else { # 'None' o 'Favorites' sin favoritos definidos: todos unloaded. $repos = $shallow } } # 5) Loop interactivo — le pasamos settingsSvc para favoritos persistentes. $main = [MainScreen]::new($themeSvc, $renderer, $primitives, $frame, $header, $statusBar, $gitSvc, $settingsSvc) $main.I18n = $i18nSvc $selected = $main.RunInteractive($ReposPath, $repos, '3.0.0-dev') # 6) Si seleccionó algo, cambia el cwd al path del repo. En PS \$PWD es estado de # la sesión (no del scope), entonces Set-Location adentro del script propaga al # shell del usuario MIENTRAS el script se invoque en el mismo proceso PS — o sea # `.\repo-nav.ps1` o `& '...repo-nav.ps1'`. Si lo lanzás con `pwsh script.ps1` # (proceso hijo), no propaga y vas a quedar en el cwd original. if ($selected) { Set-Location -LiteralPath $selected.Path } } |