src/Public/Invoke-PSMMStartup.ps1
|
function Invoke-PSMMStartup { <# .SYNOPSIS Runs the psmm startup loader: installs and imports the modules your psmm-config.json files declare. .DESCRIPTION Reads every psmm config source (inline JSON, the main config in ~/.psmm/psmm-config.json, its Includes, the profile-directory config and any legacy globs), resolves precedence and conflicts, then actions each active entry: Mode = Load imported into this session, in the foreground. Mode = InstallOnly disk/gallery work only, never imported. Mode = Ignore parsed but not actioned, and no gallery I/O at all. Mode decides load-vs-not; Install (CheckOnly / IfMissing / Latest) decides the disk/gallery policy. Scheduling is derived from the pair rather than declared by either: the import is the only thing that happens in the foreground, and every disk or gallery operation is deferred to a background thread job, in all nine cells. Nothing psmm does at startup waits on the network before your prompt appears. A module installed by that background job is reported, not imported - it is available next session rather than appearing behind a prompt you are already typing into. Set $PSMM_BackgroundStartup = $false to run the deferred work inline instead. The whole matrix is decided in one place, Get-PSMMEntryPlan; this function only executes the plans it hands back. Each imported module's import time is measured and shown in the report, so you always know which module is slowing your shell down. Intended use is one line in your $PROFILE: Import-Module psmm; Invoke-PSMMStartup .PARAMETER Quiet Suppress the per-module report and the config warnings. .EXAMPLE Invoke-PSMMStartup Loads/installs everything the config declares and prints the report. .EXAMPLE Invoke-PSMMStartup -Quiet Same, but silent (warnings are still collected; the psmm UI shows them). .LINK Show-PSModuleManager #> [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingWriteHost', '', Justification = 'The startup report is interactive host output by design, exactly like the original profile block.')] [CmdletBinding()] param([switch]$Quiet) $entries = Get-PSMMEntry $report = (-not $Quiet -and (Get-PSMMSetting -Name 'PSMM_StartupReport' -Default $true)) $sw = [System.Diagnostics.Stopwatch]::StartNew() # v2 report (design-system-v2 §8): collect one row per module, render at # the end via Get-PSMMStartupReportLines - same tokens as the TUI. $rows = [System.Collections.Generic.List[object]]::new() $mid = [char]0x00B7 # One decision per entry, from the one function that makes it (gh#29). # Nothing below re-derives policy - it only executes what the plan says. $work = @(foreach ($e in $entries) { [pscustomobject]@{ Entry = $e; Plan = (Get-PSMMEntryPlan -Entry $e) } }) $deferred = [System.Collections.Generic.List[object]]::new() foreach ($w in $work) { $e = $w.Entry $p = $w.Plan if ($p.Schedule -eq 'none') { continue } # Mode=Ignore: nothing, no I/O if (-not $p.Import) { $deferred.Add($p); continue } # InstallOnly, all three cells # The import IS the presence check, and it is free: a module that # imports is on disk, so IfMissing has nothing left to do. This is # what keeps a Get-Module -ListAvailable disk sweep off the hot path. $present = $true try { if (-not (Get-Module -Name $e.Name)) { Import-PSMMModuleTimed -Entry $e } $rows.Add([pscustomobject]@{ Kind = 'ok'; Name = $e.FriendlyName; Ms = [int]$e.ImportMs; Note = '' }) } catch [System.IO.FileNotFoundException] { $present = $false $note = if ($p.Install -eq 'none') { "missing $mid not loaded, check-only" } else { "missing $mid installing in the background, available next session" } $rows.Add([pscustomobject]@{ Kind = 'warn'; Name = $e.FriendlyName; Ms = $null; Note = $note }) } catch { $rows.Add([pscustomobject]@{ Kind = 'fail'; Name = $e.FriendlyName; Ms = $null; Note = "$($_.Exception.Message)" }) if (-not $report) { Write-Warning "Could not set up $($e.FriendlyName): $($_.Exception.Message)" } continue } # Latest always has background work; IfMissing only when it is missing. if ($p.Install -eq 'latest' -or ($p.Install -eq 'ifmissing' -and -not $present)) { $deferred.Add($p) } } if ($deferred.Count) { $useBackground = (Get-PSMMSetting -Name 'PSMM_BackgroundStartup' -Default $true) -and (Get-Command Start-ThreadJob -ErrorAction SilentlyContinue) if ($useBackground) { $null = Start-PSMMDeferredJob -Plans $deferred $label = "$($deferred[0].FriendlyName)$(if ($deferred.Count -gt 1) { " +$($deferred.Count - 1) more" })" $rows.Add([pscustomobject]@{ Kind = 'defer'; Name = $label; Ms = $null; Count = $deferred.Count Note = "working in the background $([char]0x2014) results in psmm" }) } else { # $PSMM_BackgroundStartup = $false: the user has explicitly asked # for the deferred work inline, network and all. foreach ($p in $deferred) { try { $result = Invoke-PSMMPlanAction -Plan $p $rows.Add($( if ("$result" -like 'FAILED *') { [pscustomobject]@{ Kind = 'warn'; Name = $p.FriendlyName; Ms = $null; Note = "missing $mid not installed, check-only" } } else { [pscustomobject]@{ Kind = 'skip'; Name = $p.FriendlyName; Ms = $null; Note = "$result, not imported" } } )) } catch { $rows.Add([pscustomobject]@{ Kind = 'fail'; Name = $p.FriendlyName; Ms = $null; Note = "$($_.Exception.Message)" }) if (-not $report) { Write-Warning "Could not set up $($p.FriendlyName): $($_.Exception.Message)" } } } } } $sw.Stop() if ($report) { Write-Host '' foreach ($l in (Get-PSMMStartupReportLines -Rows $rows -TotalMs $sw.ElapsedMilliseconds)) { Write-Host $l } } $warnings = Get-PSMMWarning if ($warnings.Count -and -not $Quiet) { foreach ($w in $warnings) { Write-Host "psmm config: $w" -ForegroundColor Yellow } } # Self-update: print the cached result of a PREVIOUS session's background # check (never a network call in the profile hot path), then kick the # once-a-day background re-check. $PSMM_UpdateCheck = $false disables both. if (-not $Quiet) { $u = Test-PSMMUpdateAvailable if ($u) { $reset = Get-PSMMAnsiReset Write-Host ("$(Get-PSMMAnsi -Token 'warn')$([char]0x21E1) psmm v$($u.Latest) is out (you have v$($u.Current))$reset " + "$(Get-PSMMAnsi -Token 'mute')$([char]0x2014)$reset $([char]27)[96m$($u.Command)$reset" + "$(Get-PSMMAnsi -Token 'mute'), then restart pwsh$reset") } } $null = Start-PSMMSelfUpdateCheck } |