shims/vercel.ps1
|
#Requires -Version 7 <# Production guard and session isolation for the Vercel CLI. WHY THIS EXISTS AS A SHIM The module already wrapped `vercel`, but through a PowerShell alias. An alias exists only inside a PowerShell session that imported the module -- it covers neither git-bash, nor npm scripts, nor an agent's shell. Sitting in PATH is the only position every caller passes through. WHAT IS GUARDED, AND WHAT IS NOT Two refusals, and they are the ones on the roadmap: a `--prod` deployment from a branch that is not the default branch `env rm` explicitly targeting the production environment Deliberately NOT guarded: `rollback` (a repair gesture -- refusing it always lands during an incident), `promote` (promoting an already-built deployment from a side branch is a legitimate hotfix), and `rm` (what it deletes is not identifiable as production from the command line alone). SECURITY.md carries the same list. SESSION ISOLATION Vercel has no GH_CONFIG_DIR equivalent: its config directory is chosen only by `-Q, --global-config DIR`. Isolation therefore has to be written into the command line on every call. This shim injects it from the FOLDER -- and never when the caller already passed one, because a deliberate choice is not ours to overwrite. It injects only towards a directory that actually holds a session, or when the command is `login`/`logout`/`switch`, whose subject IS that directory. Pointing at an empty config would answer "not logged in" where `vercel` worked, which is a regression, not a protection. Exit code is the real CLI's, except on refusal, which exits 1. No param() block on purpose: [CmdletBinding()] would swallow -debug or -verbose as its own parameters instead of forwarding them. #> Set-StrictMode -Off $ErrorActionPreference = 'Stop' $ShimDir = $PSScriptRoot $Arguments = @($args) # Meme garde-fou anti-boucle que les autres shims : depuis que PATH designe une # jonction, un meme dossier porte deux noms, et deux entrees dans PATH feraient # s'appeler les shims sans fin. Le compteur interrompt, il ne desarme jamais. $Profondeur = 0 if ($env:DEVCTX_SHIM_DEPTH) { $Profondeur = [int]$env:DEVCTX_SHIM_DEPTH } if ($Profondeur -ge 3) { [Console]::Error.WriteLine('') [Console]::Error.WriteLine(' DevContext: shim loop detected -- a shim resolved to another shim.') [Console]::Error.WriteLine(' Two DevContext shim directories are probably both in PATH.') [Console]::Error.WriteLine(' Fix: pwsh -File installer-shims.ps1 -Verifier') [Console]::Error.WriteLine('') exit 1 } $env:DEVCTX_SHIM_DEPTH = $Profondeur + 1 # --- delegation ------------------------------------------------------------- function Resolve-RealExe { # Duplicated from the module rather than imported: this has to work when the # module is missing or broken, which is exactly when delegating matters most. # # ExternalScript as well as Application: on Windows, npm installs `vercel` # as a .ps1 alongside the .cmd, and filtering to Application alone would # miss it on a machine where only the .ps1 is reachable. $here = $ShimDir.TrimEnd('\', '/') Get-Command vercel -CommandType Application, ExternalScript -All -ErrorAction SilentlyContinue | Where-Object { (Split-Path $_.Source -Parent).TrimEnd('\', '/') -ne $here } | Select-Object -First 1 -ExpandProperty Source } $Traduit = $false try { . (Join-Path $PSScriptRoot '..' 'src' 'Langue.ps1') Set-CtxLangue | Out-Null $Traduit = $true } catch { $Traduit = $false } function Dire { param([string]$Cle, [string]$Secours, [object[]]$Arguments) if (-not $Traduit) { if ($Arguments) { return ($Secours -f $Arguments) } return $Secours } if ($Arguments) { return (T $Cle @Arguments) } T $Cle } function Invoke-Real { param([string[]]$Final = $Arguments) $exe = Resolve-RealExe if (-not $exe) { Write-Error (Dire 'garde.introuvable' '{0} not found in PATH (outside the shims).' @('vercel')) exit 127 } & $exe @Final exit $LASTEXITCODE } # --- decide ----------------------------------------------------------------- $module = $null $decision = $null try { $module = Import-Module (Join-Path $PSScriptRoot '..' 'DevContext.psd1') -Force -PassThru -ErrorAction Stop $decision = & $module { param($a, $p) Resolve-CtxVercelVerdict -Arguments $a -Path $p } $Arguments $PWD.Path } catch { Invoke-Real } if (-not $decision -or -not $decision.Verdict) { Invoke-Real } # stderr : la sortie de `vercel` est lue par des scripts de deploiement. if ($decision.Avertissement) { [Console]::Error.WriteLine(" $($decision.Avertissement)") } if ($decision.Verdict.Allowed) { $final = $Arguments if ($decision.ConfigDir) { $final = @('--global-config', $decision.ConfigDir) + $Arguments } Invoke-Real -Final $final } # --- refuse ----------------------------------------------------------------- # # Hors du try : une levee pendant l'affichage retomberait sinon dans Invoke-Real # et transformerait un refus en deploiement. & $module { param($v) Write-CtxVercelRefus -Verdict $v } $decision.Verdict exit 1 |