scripts/Verify-QuietCliArchive.ps1
|
#requires -Version 7.6 #requires -PSEdition Core [CmdletBinding()] param([Parameter(Mandatory)][string]$ArchivePath,[Parameter(Mandatory)][string]$OutputPath,[ValidateSet('QuietCli','.')][string]$ModuleDirectory='QuietCli') $ErrorActionPreference='Stop' $root=Split-Path -Parent $PSScriptRoot Import-Module (Join-Path $root 'FrenchExDev.QuietCli.PoSh.psd1') -DisableNameChecking $temporary=Join-Path ([IO.Path]::GetTempPath()) ('quietcli-distribution-'+[guid]::NewGuid().ToString('N')) [IO.Directory]::CreateDirectory($temporary)|Out-Null try{ [IO.Compression.ZipFile]::ExtractToDirectory([IO.Path]::GetFullPath($ArchivePath),$temporary) $runtime=[IO.Path]::GetFullPath((Join-Path $temporary $ModuleDirectory)) $package=Get-Content (Join-Path $runtime 'package-manifest.json') -Raw|ConvertFrom-Json $verified=0 foreach($file in $package.Files){ $target=[IO.Path]::GetFullPath((Join-Path $runtime $file.Path)) if(!$target.StartsWith($runtime+[IO.Path]::DirectorySeparatorChar,[StringComparison]::Ordinal)){throw 'Package manifest escapes extraction directory.'} if((Get-FileHash $target -Algorithm SHA256).Hash.ToLowerInvariant() -cne $file.SHA256){throw "Package hash mismatch: $($file.Path)"} $verified++ } if(Test-Path -LiteralPath (Join-Path $runtime 'modules')){throw 'The runtime archive must not contain consumer module copies.'} $body=@' $ErrorActionPreference='Stop' $runtime='__RUNTIME__';$work='__WORK__' $env:QUIETCLI_DB_PATH=Join-Path $work 'history.sqlite3' $env:QUIETCLI_CONFIG_PATH=Join-Path $work 'settings.json' $env:QUIETCLI_TRACKING='0';$env:QUIETCLI_RECALL='0';$env:QUIETCLI_TOKENIZER='o200k_base' Set-Location $work Import-Module (Join-Path $runtime 'FrenchExDev.QuietCli.PoSh.psd1') -DisableNameChecking $module=Get-Module FrenchExDev.QuietCli.PoSh foreach($name in @('quiet','pump')){ if(!$module.ExportedFunctions.ContainsKey($name)){throw "Missing standalone public function: $name"} $state=@{Lines=[Collections.Generic.List[string]]::new()} $result=& $name -AsObject -State $state -OnOutput {param($output,$state) if($output.Stream -eq 'stdout'){$state.Lines.Add($output.Text)}} -Complete {param($state,$capture) @{Lines=$state.Lines.ToArray()}} (Get-Process -Id $PID).Path -NoProfile -Command '[Console]::WriteLine("standalone")' if($result.Outcome -ne 'Success' -or $result.Data.Lines.Count -ne 1 -or $result.Data.Lines[0] -cne 'standalone'){throw "Standalone command failed: $name"} } $token=Measure-QuietToken -Text hello if($token.Tokens -ne 1){throw 'Bundled tokenizer verification failed.'} @{Outcome='Success';Version=$module.Version.ToString();PowerShell=$PSVersionTable.PSVersion.ToString();Platform=$PSVersionTable.OS PublicCommands=@('quiet','pump');CallbackParameter='OnOutput';RequiredModules=@($module.RequiredModules|ForEach-Object Name) Output='standalone';Tokenizer=$token.Profile;TokenizerVersion=$token.Version}|ConvertTo-Json -Depth 8 -Compress '@ $body=$body.Replace('__RUNTIME__',$runtime.Replace("'","''")).Replace('__WORK__',$temporary.Replace("'","''")) $encoded=[Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($body)) $child=pump -AsObject -Parse {param($capture) $capture.Stdout|ConvertFrom-Json} (Get-Process -Id $PID).Path -NoProfile -EncodedCommand $encoded if($child.Outcome -ne 'Success'){throw 'Standalone runtime verification failed; inspect the bounded child diagnostics.'} $observation=$child.Data $report=@{SchemaVersion=2;Outcome='Success';ArchiveSHA256=(Get-FileHash $ArchivePath -Algorithm SHA256).Hash;VerifiedFiles=$verified;FreshProcess=$true;Observation=$observation} }finally{ $expected=Join-Path ([IO.Path]::GetTempPath()) (Split-Path -Leaf $temporary) if([IO.Path]::GetFullPath($temporary) -cne [IO.Path]::GetFullPath($expected)){throw 'Unexpected extraction cleanup target.'} if(Test-Path -LiteralPath $temporary){Remove-Item -LiteralPath $temporary -Recurse -Force} } [IO.File]::WriteAllText([IO.Path]::GetFullPath($OutputPath),($report|ConvertTo-Json -Depth 10),[Text.UTF8Encoding]::new($false)) [pscustomobject]@{Outcome=$report.Outcome;Data=@{VerifiedFiles=$verified;Observation=$report.Observation;Report=[IO.Path]::GetFullPath($OutputPath)}}|ConvertTo-Json -Depth 10 -Compress |