Private/Get-DFCachedCommandOutput.ps1
|
#Requires -Version 7.0 function Get-DFCachedCommandOutput { <# .SYNOPSIS Returns the cached stdout of a deterministic external command, regenerating only when the resolved executable itself has changed. .DESCRIPTION For companions like carapace/zoxide/mdcat/scoop-search whose init output is a pure function of the tool's own build -- verified byte-identical across repeated runs (see docs/superpowers/specs/2026-09-05-startup-perf-audit.md) -- with no session input to fingerprint on (unlike vivid's theme name, see Tools/vivid.ps1). The fingerprint is the resolved executable's path plus its LastWriteTimeUtc: a file stat, not a process spawn, so the cache-hit path never pays for a version check. A tool upgrade (which rewrites the file) or a switch to a differently-located binary both correctly invalidate the cache. Falls back to always calling -Generate, uncached, when -Executable does not resolve, or it resolves to something with no backing file (a function or alias stand-in, e.g. how tests/scoop.Tests.ps1 stubs scoop-search -- Get-Command's .Source on a function is not a usable file path) -- these companions register real functionality (completions, cd hooks), so degrading to "slower but correct" beats "skip it entirely" the way Tools/vivid.ps1's cosmetic LS_COLORS cache does. Never caches a falsy result (empty string or $null) -- a transient failure is retried next session rather than remembered. .PARAMETER Name Cache key, distinct per companion (e.g. 'carapace-init'). Backs the files $XDG_CACHE_HOME/dotforge/<Name>.txt and <Name>.key. .PARAMETER Executable The command name to resolve and fingerprint (e.g. 'carapace'). .PARAMETER Generate Scriptblock producing the real output on a cache miss. .PARAMETER ExtraKey Optional text folded into the fingerprint, for output that also depends on something besides the executable (carapace's init also depends on the spec files in its specs folder). When it changes, the cache regenerates. Keep it cheap to compute: it runs on every call. .PARAMETER Force Bypass the cache and regenerate unconditionally. .EXAMPLE Get-DFCachedCommandOutput -Name 'carapace-init' -Executable 'carapace' -Generate { carapace _carapace powershell | Out-String } .OUTPUTS [string] #> [CmdletBinding()] [OutputType([string])] param( [Parameter(Mandatory)][string]$Name, [Parameter(Mandatory)][string]$Executable, [Parameter(Mandatory)][scriptblock]$Generate, [string]$ExtraKey, [switch]$Force ) $cmd = Get-Command $Executable -ErrorAction Ignore if (-not $cmd -or -not $cmd.Source -or -not (Test-Path $cmd.Source -PathType Leaf)) { return & $Generate } $target = Resolve-DFExecutableTarget -Path $cmd.Source $fingerprint = "$target|$((Get-Item $target).LastWriteTimeUtc.Ticks)" if ($ExtraKey) { $fingerprint += "|$ExtraKey" } Get-DFFingerprintCache -Name $Name -Fingerprint $fingerprint -Generate $Generate -Force:$Force } function Resolve-DFExecutableTarget { <# .SYNOPSIS Resolves a launcher (scoop shim or filesystem link) to the real executable it starts, so its file identity tracks tool upgrades. .DESCRIPTION A scoop shim (`shims\<name>.exe`) is a generic launcher that scoop never rewrites on upgrade -- the tool's identity lives in the sibling `<name>.shim` file's `path = "..."` line. Fingerprinting the shim itself would leave Get-DFCachedCommandOutput's cache stale across upgrades. Symlinks (e.g. winget's Links directory) are followed to their final target. Any failure -- unreadable or malformed shim, missing target -- degrades silently to the input path. Catalogued in docs/external-dependencies.md. .PARAMETER Path Absolute path of the resolved command (Get-Command's .Source). .EXAMPLE Resolve-DFExecutableTarget -Path (Get-Command carapace).Source .OUTPUTS [string] #> [CmdletBinding()] [OutputType([string])] param([Parameter(Mandatory)][string]$Path) try { $shim = [IO.Path]::ChangeExtension($Path, '.shim') if (Test-Path -LiteralPath $shim -PathType Leaf) { foreach ($line in Get-Content -LiteralPath $shim) { if ($line -match '^\s*path\s*=\s*"?(.+?)"?\s*$') { $target = ConvertTo-DFPath $Matches[1] if (Test-Path -LiteralPath $target -PathType Leaf) { return $target } break } } return $Path } $link = [IO.File]::ResolveLinkTarget($Path, $true) if ($link -and $link.Exists) { return $link.FullName } } catch { Write-Verbose "Resolve-DFExecutableTarget: falling back to '$Path': $_" } $Path } |