diff --git a/.github/workflows/windows-full-ci.yml b/.github/workflows/windows-full-ci.yml deleted file mode 100644 index 0569683..0000000 --- a/.github/workflows/windows-full-ci.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: windows-full-ci - -on: - pull_request: - paths: - - "herdr-plugin.toml" - - "plannotator-tui.version" - - "scripts/fetch-plannotator-tui.*" - - "scripts/plannotator-tui.sh" - - "scripts/smoke.sh" - - "scripts/test-fetch-plannotator-tui.*" - - "scripts/test-herdr-windows-plugin.ps1" - - "scripts/test-http-server.py" - - "scripts/test-windows-full-manifest.py" - - ".github/workflows/windows-full-ci.yml" - merge_group: - -concurrency: - group: windows-full-ci-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - windows-full: - runs-on: windows-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - name: local override and idempotence - run: ./scripts/test-fetch-plannotator-tui.ps1 -Case LocalOverride - - name: loopback download and checksum preservation - run: ./scripts/test-fetch-plannotator-tui.ps1 -Case Download - - uses: actions/checkout@v4 - with: - repository: plannotator/plannotator-tui - ref: dbc99a070dc169ffd9763976a8ea60406eabf268 - path: plannotator-tui-source - persist-credentials: false - - name: manifest structure and development parity - run: | - python scripts/test-windows-full-manifest.py - python plannotator-tui-source/herdr/test-manifest.py ` - plannotator-tui-source/herdr/herdr-plugin.toml herdr-plugin.toml - - name: pinned Herdr link and list - run: ./scripts/test-herdr-windows-plugin.ps1 - - unix-regression: - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest] - runs-on: ${{ matrix.os }} - timeout-minutes: 10 - steps: - - uses: actions/checkout@v4 - - run: bash scripts/test-fetch-plannotator-tui.sh diff --git a/herdr-plugin.toml b/herdr-plugin.toml index 0734241..f3cbaef 100644 --- a/herdr-plugin.toml +++ b/herdr-plugin.toml @@ -44,27 +44,21 @@ command = ["bun", "src/manager.ts"] # --------------------------------------------------------------------------------------- # Document review with plannotator-tui (https://github.com/plannotator/plannotator-tui). -# Herdr resolves explicit relative programs against $HERDR_PLUGIN_ROOT. A platform release -# binary is fetched into bin/ at build time. Where it opens (overlay | split | popup) remains -# the user's choice in plannotator-tui's config file. +# Commands resolve through $HERDR_PLUGIN_ROOT: the review pane is opened with --cwd set to +# the folder under review, so a path relative to the plugin root would not resolve there. +# A prebuilt binary is fetched into bin/ at build time; macOS and Linux for now. Where it +# opens (overlay | split | popup) is the user's choice in ~/.config/plannotator-tui/config.toml. [[build]] platforms = ["macos", "linux"] command = ["bash", "scripts/fetch-plannotator-tui.sh"] -[[build]] -platforms = ["windows"] -command = [ - "powershell.exe", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", - "-File", "scripts/fetch-plannotator-tui.ps1", -] - [[panes]] id = "doc" title = "Annotate" placement = "overlay" -platforms = ["macos", "linux", "windows"] -command = ["./bin/plannotator-tui.exe", "herdr", "pane"] +platforms = ["macos", "linux"] +command = ["sh", "-c", "exec bash \"$HERDR_PLUGIN_ROOT/scripts/plannotator-tui.sh\" herdr pane"] # Both actions run the launcher, which reads Herdr's invocation context: the focused pane's # folder (open) or the clicked file:// link (open-link), and the focused pane's agent as @@ -74,24 +68,24 @@ id = "open" title = "Annotate: open here" description = "Review the focused pane's folder in plannotator-tui and send feedback to its agent." contexts = ["workspace", "pane"] -platforms = ["macos", "linux", "windows"] -command = ["./bin/plannotator-tui.exe", "herdr", "open"] +platforms = ["macos", "linux"] +command = ["sh", "-c", "exec bash \"$HERDR_PLUGIN_ROOT/scripts/plannotator-tui.sh\" herdr open"] [[actions]] id = "open-link" title = "Annotate this file" description = "Open a Ctrl-clicked Markdown file in plannotator-tui." contexts = ["pane"] -platforms = ["macos", "linux", "windows"] -command = ["./bin/plannotator-tui.exe", "herdr", "open"] +platforms = ["macos", "linux"] +command = ["sh", "-c", "exec bash \"$HERDR_PLUGIN_ROOT/scripts/plannotator-tui.sh\" herdr open"] [[actions]] id = "last" title = "Annotate: agent's last message" description = "Review the focused agent's most recent message in plannotator-tui and send feedback back." contexts = ["pane"] -platforms = ["macos", "linux", "windows"] -command = ["./bin/plannotator-tui.exe", "herdr", "last"] +platforms = ["macos", "linux"] +command = ["sh", "-c", "exec bash \"$HERDR_PLUGIN_ROOT/scripts/plannotator-tui.sh\" herdr last"] # Ctrl-click on a file:// Markdown link. Anchored on the scheme so web links never match. [[link_handlers]] @@ -99,4 +93,4 @@ id = "markdown-file" title = "Annotate this file" pattern = "^file://.*\\.(md|markdown|mdx)$" action = "open-link" -platforms = ["macos", "linux", "windows"] +platforms = ["macos", "linux"] diff --git a/plannotator-tui.version b/plannotator-tui.version index a918a2a..8f0916f 100644 --- a/plannotator-tui.version +++ b/plannotator-tui.version @@ -1 +1 @@ -0.6.0 +0.5.0 diff --git a/scripts/fetch-plannotator-tui.ps1 b/scripts/fetch-plannotator-tui.ps1 deleted file mode 100644 index 8edbeb4..0000000 --- a/scripts/fetch-plannotator-tui.ps1 +++ /dev/null @@ -1,157 +0,0 @@ -$ErrorActionPreference = "Stop" -Set-Location (Join-Path $PSScriptRoot "..") - -$versionContents = Get-Content -LiteralPath "plannotator-tui.version" -Raw -$version = if ($null -eq $versionContents) { "" } else { [string]$versionContents } -$version = $version.Trim() -if (-not $version) { throw "plannotator-tui.version is empty" } - -$destinationDirectory = Join-Path (Get-Location).Path "bin" -$destination = Join-Path $destinationDirectory "plannotator-tui.exe" -$stamp = Join-Path $destinationDirectory "plannotator-tui.version" -New-Item -ItemType Directory -Force $destinationDirectory | Out-Null - -$localOverride = [Environment]::GetEnvironmentVariable("PLANNOTATOR_TUI_BIN", "Process") -$hasLocalOverride = $null -ne $localOverride -$installed = if (Test-Path -LiteralPath $stamp -PathType Leaf) { - ([string](Get-Content -LiteralPath $stamp -Raw)).Trim() -} else { - "" -} - -if ((Test-Path -LiteralPath $destination -PathType Leaf) -and - $installed -eq $version -and -not $hasLocalOverride) { - Write-Output "plannotator-tui $version already installed" - exit 0 -} - -function Install-PlannotatorTui { - param([Parameter(Mandatory = $true)][string]$Source) - - $candidate = Join-Path $destinationDirectory ("plannotator-tui-" + [guid]::NewGuid() + ".tmp") - $backup = Join-Path $destinationDirectory ("plannotator-tui-" + [guid]::NewGuid() + ".bak") - $stampBackup = Join-Path $destinationDirectory ("plannotator-tui-version-" + [guid]::NewGuid() + ".bak") - $hadDestination = Test-Path -LiteralPath $destination -PathType Leaf - $hadStamp = Test-Path -LiteralPath $stamp -PathType Leaf - $replacementCompleted = $false - $keepRecoveryFiles = $false - try { - if ($hadStamp) { - Copy-Item -LiteralPath $stamp -Destination $stampBackup - } - Copy-Item -LiteralPath $Source -Destination $candidate - if ($hadDestination) { - try { - [System.IO.File]::Replace( - [System.IO.Path]::GetFullPath($candidate), - [System.IO.Path]::GetFullPath($destination), - [System.IO.Path]::GetFullPath($backup) - ) - } catch { - throw "failed to replace ${destination}: $($_.Exception.Message)" - } - } else { - Move-Item -LiteralPath $candidate -Destination $destination - } - $replacementCompleted = $true - Set-Content -LiteralPath $stamp -NoNewline -Value $version - } catch { - $installFailure = $_ - if ($replacementCompleted) { - try { - if ($hadDestination) { - Remove-Item -LiteralPath $destination -Force - Move-Item -LiteralPath $backup -Destination $destination - } else { - Remove-Item -LiteralPath $destination -Force - } - if ($hadStamp) { - Remove-Item -LiteralPath $stamp -Force -ErrorAction SilentlyContinue - Move-Item -LiteralPath $stampBackup -Destination $stamp - } else { - Remove-Item -LiteralPath $stamp -Force -ErrorAction SilentlyContinue - } - $replacementCompleted = $false - } catch { - $keepRecoveryFiles = $true - throw ( - "$($installFailure.Exception.Message); rollback also failed: " + - $_.Exception.Message - ) - } - } - throw $installFailure - } finally { - Remove-Item -LiteralPath $candidate -Force -ErrorAction SilentlyContinue - if (-not $keepRecoveryFiles) { - Remove-Item -LiteralPath $backup -Force -ErrorAction SilentlyContinue - Remove-Item -LiteralPath $stampBackup -Force -ErrorAction SilentlyContinue - } - } -} - -if ($hasLocalOverride) { - if (-not (Test-Path -LiteralPath $localOverride -PathType Leaf)) { - throw "PLANNOTATOR_TUI_BIN is not a file: $localOverride" - } - Install-PlannotatorTui -Source $localOverride - Write-Output "installed plannotator-tui from $localOverride (local build, stamped $version)" - exit 0 -} - -try { - $architecture = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString() - $target = switch ($architecture) { - "X64" { "x86_64-pc-windows-msvc" } - "Arm64" { "aarch64-pc-windows-msvc" } - default { throw "no plannotator-tui release target for Windows/$architecture" } - } - - $asset = "plannotator-tui-$target.exe" - $releaseBaseOverride = [Environment]::GetEnvironmentVariable( - "PLANNOTATOR_TUI_RELEASE_BASE", - "Process" - ) - # PLANNOTATOR_TUI_RELEASE_BASE is a test-only seam for a loopback fixture server. - $base = if ($null -ne $releaseBaseOverride) { - $releaseBaseOverride.TrimEnd([char]"/") - } else { - "https://github.com/plannotator/plannotator-tui/releases/download/v$version" - } - - $temporary = Join-Path ([System.IO.Path]::GetTempPath()) ("plannotator-tui-" + [guid]::NewGuid()) - try { - New-Item -ItemType Directory $temporary | Out-Null - $downloadedAsset = Join-Path $temporary $asset - $checksumFile = Join-Path $temporary "SHA256SUMS" - Invoke-WebRequest -UseBasicParsing "$base/$asset" -OutFile $downloadedAsset - Invoke-WebRequest -UseBasicParsing "$base/SHA256SUMS" -OutFile $checksumFile - - $matches = @( - Get-Content -LiteralPath $checksumFile | Where-Object { - $fields = @($_ -split "\s+") - $fields.Count -ge 2 -and $fields[-1] -ceq $asset - } - ) - if ($matches.Count -ne 1) { - throw "expected exactly one checksum for $asset in $base/SHA256SUMS; found $($matches.Count)" - } - $checksumFields = $matches[0].Trim() -split "\s+" - $expected = $checksumFields[0].ToLowerInvariant() - $actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $downloadedAsset).Hash.ToLowerInvariant() - if ($actual -ne $expected) { - throw "sha256 mismatch for ${asset}: expected $expected, got $actual" - } - - Install-PlannotatorTui -Source $downloadedAsset - Write-Output "installed plannotator-tui $version ($target)" - } finally { - Remove-Item -LiteralPath $temporary -Recurse -Force -ErrorAction SilentlyContinue - } -} catch { - Write-Warning ( - "Full review is unavailable until the plugin is reinstalled or updated: " + - $_.Exception.Message - ) - exit 0 -} diff --git a/scripts/fetch-plannotator-tui.sh b/scripts/fetch-plannotator-tui.sh index 9917b3c..22cc08e 100755 --- a/scripts/fetch-plannotator-tui.sh +++ b/scripts/fetch-plannotator-tui.sh @@ -3,7 +3,7 @@ # (cwd = plugin root) and by hand for local testing. # # plannotator-tui.version the release to install (one line, e.g. 0.1.0) -# bin/plannotator-tui.exe the binary +# bin/plannotator-tui the binary # bin/plannotator-tui.version what is installed; matching the pin means nothing to do # # Modes, in order: @@ -19,21 +19,19 @@ cd "$(dirname "$0")/.." version="$(tr -d '[:space:]' < plannotator-tui.version)" [ -n "$version" ] || { echo "plannotator-tui.version is empty" >&2; exit 1; } mkdir -p bin -destination="bin/plannotator-tui.exe" -stamp="bin/plannotator-tui.version" installed="$(cat bin/plannotator-tui.version 2>/dev/null || true)" -if [ -x "$destination" ] && [ "$installed" = "$version" ] && [ -z "${PLANNOTATOR_TUI_BIN:-}" ]; then +if [ -x bin/plannotator-tui ] && [ "$installed" = "$version" ] && [ -z "${PLANNOTATOR_TUI_BIN:-}" ]; then echo "plannotator-tui $version already installed" exit 0 fi if [ -n "${PLANNOTATOR_TUI_BIN:-}" ]; then [ -x "$PLANNOTATOR_TUI_BIN" ] || { echo "PLANNOTATOR_TUI_BIN is not an executable: $PLANNOTATOR_TUI_BIN" >&2; exit 1; } - rm -f "$destination" - cp "$PLANNOTATOR_TUI_BIN" "$destination" - chmod +x "$destination" - printf '%s' "$version" > "$stamp" + cp "$PLANNOTATOR_TUI_BIN" bin/plannotator-tui.tmp + chmod +x bin/plannotator-tui.tmp + mv bin/plannotator-tui.tmp bin/plannotator-tui + echo "$version" > bin/plannotator-tui.version echo "installed plannotator-tui from $PLANNOTATOR_TUI_BIN (local build, stamped $version)" exit 0 fi @@ -76,8 +74,6 @@ fi [ "$actual" = "$expected" ] || give_up "sha256 mismatch for $asset: expected $expected, got $actual" chmod +x "$tmp/$asset" -rm -f "$destination" -cp "$tmp/$asset" "$destination" -chmod +x "$destination" -printf '%s' "$version" > "$stamp" +mv "$tmp/$asset" bin/plannotator-tui +echo "$version" > bin/plannotator-tui.version echo "installed plannotator-tui $version ($target)" diff --git a/scripts/plannotator-tui.sh b/scripts/plannotator-tui.sh index 2ac09fa..480dba0 100755 --- a/scripts/plannotator-tui.sh +++ b/scripts/plannotator-tui.sh @@ -1,10 +1,11 @@ #!/usr/bin/env bash -# Compatibility wrapper for released and local workflows. The current manifest invokes the -# staged binary directly; remove this wrapper after one compatibility release. +# Run the bundled plannotator-tui, or say clearly why it cannot run. Herdr invokes this for +# the review pane and actions; the pane's cwd is the folder under review, so the binary is +# located relative to this script, never to the cwd. set -euo pipefail root="$(cd "$(dirname "$0")/.." && pwd)" -if [ -x "$root/bin/plannotator-tui.exe" ]; then - exec "$root/bin/plannotator-tui.exe" "$@" +if [ -x "$root/bin/plannotator-tui" ]; then + exec "$root/bin/plannotator-tui" "$@" fi msg="plannotator-tui is not installed. Reinstall the plugin: herdr plugin install plannotator/herdr-annotate" echo "$msg" >&2 diff --git a/scripts/smoke.sh b/scripts/smoke.sh index ed4daee..0871cef 100755 --- a/scripts/smoke.sh +++ b/scripts/smoke.sh @@ -24,7 +24,7 @@ actions() { herdr plugin action list --plugin annotate | python3 -c " import json,sys; print(','.join(sorted(a['action_id'] for a in json.load(sys.stdin)['result']['actions'])))"; } bin_version() { local root; root="$(plugin_json | field "p['plugin_root']")" - local bin="$root/bin/plannotator-tui.exe"; [ -x "$bin" ] && "$bin" --version | awk '{print $2}' || echo none + local bin="$root/bin/plannotator-tui"; [ -x "$bin" ] && "$bin" --version | awk '{print $2}' || echo none } pin() { local root; root="$(plugin_json | field "p['plugin_root']")"; tr -d '[:space:]' < "$root/plannotator-tui.version" 2>/dev/null || echo none; } check() { if [ "$2" = "$3" ]; then echo " ok $1: $2"; else echo " FAIL $1: got '$2', want '$3'" >&2; failures=$((failures+1)); fi; } diff --git a/scripts/test-fetch-plannotator-tui.ps1 b/scripts/test-fetch-plannotator-tui.ps1 deleted file mode 100644 index 91df31b..0000000 --- a/scripts/test-fetch-plannotator-tui.ps1 +++ /dev/null @@ -1,185 +0,0 @@ -param( - [Parameter(Mandatory = $true)] - [ValidateSet("LocalOverride", "Download")] - [string]$Case -) - -$ErrorActionPreference = "Stop" -Set-StrictMode -Version Latest -if (Test-Path variable:PSNativeCommandUseErrorActionPreference) { - $PSNativeCommandUseErrorActionPreference = $false -} - -$repositoryRoot = Split-Path -Parent $PSScriptRoot -$testRoot = Join-Path $env:RUNNER_TEMP ("plannotator full fetch " + $Case + " " + [guid]::NewGuid()) -$pluginRoot = Join-Path $testRoot "plugin root with spaces" -$pluginScripts = Join-Path $pluginRoot "scripts" -$fetcher = Join-Path $pluginScripts "fetch-plannotator-tui.ps1" -$destination = Join-Path $pluginRoot "bin/plannotator-tui.exe" -$stamp = Join-Path $pluginRoot "bin/plannotator-tui.version" -$oldLocalOverride = [Environment]::GetEnvironmentVariable("PLANNOTATOR_TUI_BIN", "Process") -$oldReleaseBase = [Environment]::GetEnvironmentVariable("PLANNOTATOR_TUI_RELEASE_BASE", "Process") - -function Assert-True { - param([bool]$Condition, [string]$Message) - if (-not $Condition) { throw $Message } -} - -function Invoke-Fetcher { - $output = & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -File $fetcher *>&1 | - Out-String - [pscustomobject]@{ ExitCode = $LASTEXITCODE; Output = $output } -} - -function Assert-BytesEqual { - param([string]$Left, [string]$Right, [string]$Message) - $leftBytes = [Convert]::ToBase64String([System.IO.File]::ReadAllBytes($Left)) - $rightBytes = [Convert]::ToBase64String([System.IO.File]::ReadAllBytes($Right)) - Assert-True ($leftBytes -ceq $rightBytes) $Message -} - -function Start-FixtureServer { - param([string]$Root, [string]$PortFile) - $start = [System.Diagnostics.ProcessStartInfo]::new() - $start.FileName = (Get-Command python).Source - $start.UseShellExecute = $false - $start.ArgumentList.Add((Join-Path $repositoryRoot "scripts/test-http-server.py")) - $start.ArgumentList.Add($Root) - $start.ArgumentList.Add($PortFile) - $process = [System.Diagnostics.Process]::Start($start) - $deadline = [DateTime]::UtcNow.AddSeconds(15) - while (-not (Test-Path -LiteralPath $PortFile -PathType Leaf)) { - if ($process.HasExited) { throw "fixture server exited with $($process.ExitCode)" } - if ([DateTime]::UtcNow -gt $deadline) { throw "fixture server did not publish its port" } - Start-Sleep -Milliseconds 100 - } - $process -} - -try { - New-Item -ItemType Directory -Force $pluginScripts | Out-Null - Copy-Item -LiteralPath (Join-Path $repositoryRoot "scripts/fetch-plannotator-tui.ps1") -Destination $fetcher - Copy-Item -LiteralPath (Join-Path $repositoryRoot "plannotator-tui.version") -Destination $pluginRoot - - if ($Case -eq "LocalOverride") { - $sourceDirectory = Join-Path $testRoot "synthetic source with spaces" - $source = Join-Path $sourceDirectory "plannotator-tui local.exe" - New-Item -ItemType Directory -Force $sourceDirectory | Out-Null - Set-Content -LiteralPath $source -NoNewline -Value "local override bytes" - New-Item -ItemType Directory -Force (Split-Path -Parent $destination) | Out-Null - Set-Content -LiteralPath $destination -NoNewline -Value "old destination bytes" - Set-Content -LiteralPath $stamp -NoNewline -Value "old-version" - - $env:PLANNOTATOR_TUI_BIN = $source - $result = Invoke-Fetcher - Assert-True ($result.ExitCode -eq 0) "local override failed: $($result.Output)" - Assert-BytesEqual $source $destination "local override bytes differ" - Assert-True ((Get-Content -LiteralPath $stamp -Raw) -ceq "0.6.0") "local stamp differs" - - $env:PLANNOTATOR_TUI_BIN = $null - $env:PLANNOTATOR_TUI_RELEASE_BASE = "http://127.0.0.1:1/must-not-be-requested" - $beforeHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $destination).Hash - $result = Invoke-Fetcher - Assert-True ($result.ExitCode -eq 0) "idempotent run failed: $($result.Output)" - Assert-True ($result.Output -match "already installed") "idempotent run did not short-circuit" - Assert-True ( - (Get-FileHash -Algorithm SHA256 -LiteralPath $destination).Hash -ceq $beforeHash - ) "idempotent run replaced the destination" - - $env:PLANNOTATOR_TUI_BIN = Join-Path $testRoot "missing explicit override.exe" - $result = Invoke-Fetcher - Assert-True ($result.ExitCode -ne 0) "missing explicit override exited successfully" - Assert-True ($result.Output -match "PLANNOTATOR_TUI_BIN is not a file") "missing override error differs" - Assert-True ( - (Get-FileHash -Algorithm SHA256 -LiteralPath $destination).Hash -ceq $beforeHash - ) "missing override changed the destination" - - $env:PLANNOTATOR_TUI_BIN = $null - Set-Content -LiteralPath (Join-Path $pluginRoot "plannotator-tui.version") -NoNewline -Value "" - $result = Invoke-Fetcher - Assert-True ($result.ExitCode -ne 0) "empty version pin exited successfully" - Assert-True ($result.Output -match "plannotator-tui.version is empty") "empty pin error differs" - Assert-True ( - (Get-FileHash -Algorithm SHA256 -LiteralPath $destination).Hash -ceq $beforeHash - ) "empty pin changed the destination" - } else { - $webRoot = Join-Path $testRoot "loopback release with spaces" - $portFile = Join-Path $testRoot "fixture-server.port" - $asset = "plannotator-tui-x86_64-pc-windows-msvc.exe" - $source = Join-Path $webRoot $asset - New-Item -ItemType Directory -Force $webRoot | Out-Null - Set-Content -LiteralPath $source -NoNewline -Value "downloaded fixture bytes" - $hash = (Get-FileHash -Algorithm SHA256 -LiteralPath $source).Hash.ToLowerInvariant() - Set-Content -LiteralPath (Join-Path $webRoot "SHA256SUMS") -Value "$hash $asset" - $server = Start-FixtureServer -Root $webRoot -PortFile $portFile - try { - $port = (Get-Content -LiteralPath $portFile -Raw).Trim() - $env:PLANNOTATOR_TUI_BIN = $null - $env:PLANNOTATOR_TUI_RELEASE_BASE = "http://127.0.0.1:$port" - $result = Invoke-Fetcher - Assert-True ($result.ExitCode -eq 0) "download fixture failed: $($result.Output)" - Assert-True ($result.Output -match "x86_64-pc-windows-msvc") "x64 target was not selected" - Assert-BytesEqual $source $destination "downloaded destination bytes differ" - Assert-True ((Get-Content -LiteralPath $stamp -Raw) -ceq "0.6.0") "download stamp differs" - - Set-Content -LiteralPath $stamp -NoNewline -Value "preserve-this-stamp" - $beforeHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $destination).Hash - Set-Content -LiteralPath (Join-Path $webRoot "SHA256SUMS") -Value ("0" * 64 + " $asset") - $result = Invoke-Fetcher - Assert-True ($result.ExitCode -eq 0) "wrong checksum was fatal: $($result.Output)" - Assert-True ($result.Output -match "Full review is unavailable") "wrong checksum warning differs" - Assert-True ( - (Get-FileHash -Algorithm SHA256 -LiteralPath $destination).Hash -ceq $beforeHash - ) "wrong checksum changed the prior destination" - Assert-True ( - (Get-Content -LiteralPath $stamp -Raw) -ceq "preserve-this-stamp" - ) "wrong checksum changed the prior stamp" - - Set-Content -LiteralPath $source -NoNewline -Value "replacement bytes while destination is locked" - $replacementHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $source).Hash.ToLowerInvariant() - Set-Content -LiteralPath (Join-Path $webRoot "SHA256SUMS") ` - -Value "$replacementHash $asset" - Set-Content -LiteralPath $stamp -NoNewline -Value "preserve-locked-stamp" - $beforeHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $destination).Hash - $lock = [System.IO.File]::Open( - $destination, - [System.IO.FileMode]::Open, - [System.IO.FileAccess]::Read, - [System.IO.FileShare]::Read - ) - try { - $result = Invoke-Fetcher - } finally { - $lock.Dispose() - } - Assert-True ($result.ExitCode -eq 0) "locked destination was fatal: $($result.Output)" - Assert-True ($result.Output -match "Full review is unavailable") "locked warning differs" - Assert-True ($result.Output -match "plannotator-tui.exe") "locked warning omits destination" - Assert-True ( - (Get-FileHash -Algorithm SHA256 -LiteralPath $destination).Hash -ceq $beforeHash - ) "locked replacement changed the prior destination" - Assert-True ( - (Get-Content -LiteralPath $stamp -Raw) -ceq "preserve-locked-stamp" - ) "locked replacement changed the prior stamp" - } finally { - if ($null -ne $server -and -not $server.HasExited) { - $server.Kill($true) - $server.WaitForExit() - } - } - } -} finally { - if ($null -eq $oldLocalOverride) { - $env:PLANNOTATOR_TUI_BIN = $null - } else { - $env:PLANNOTATOR_TUI_BIN = $oldLocalOverride - } - if ($null -eq $oldReleaseBase) { - $env:PLANNOTATOR_TUI_RELEASE_BASE = $null - } else { - $env:PLANNOTATOR_TUI_RELEASE_BASE = $oldReleaseBase - } - Remove-Item -LiteralPath $testRoot -Recurse -Force -ErrorAction SilentlyContinue -} - -exit 0 diff --git a/scripts/test-fetch-plannotator-tui.sh b/scripts/test-fetch-plannotator-tui.sh deleted file mode 100644 index 4c0edd5..0000000 --- a/scripts/test-fetch-plannotator-tui.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -repository_root="$(cd "$(dirname "$0")/.." && pwd)" -test_root="$(mktemp -d)" -trap 'rm -rf "$test_root"' EXIT -plugin_root="$test_root/plugin root with spaces" -source_root="$test_root/source binary with spaces" -mkdir -p "$plugin_root/scripts" "$plugin_root/bin" "$source_root" -cp "$repository_root/scripts/fetch-plannotator-tui.sh" "$plugin_root/scripts/" -cp "$repository_root/scripts/plannotator-tui.sh" "$plugin_root/scripts/" -cp "$repository_root/plannotator-tui.version" "$plugin_root/" - -source_binary="$source_root/plannotator-tui local" -cat > "$source_binary" <<'EOF' -#!/usr/bin/env sh -printf 'plannotator-tui 0.6.0\n' -EOF -chmod +x "$source_binary" -printf 'old destination' > "$plugin_root/bin/plannotator-tui.exe" -chmod +x "$plugin_root/bin/plannotator-tui.exe" -printf 'old-version' > "$plugin_root/bin/plannotator-tui.version" - -PLANNOTATOR_TUI_BIN="$source_binary" bash "$plugin_root/scripts/fetch-plannotator-tui.sh" -cmp "$source_binary" "$plugin_root/bin/plannotator-tui.exe" -test "$(cat "$plugin_root/bin/plannotator-tui.version")" = 0.6.0 -test ! -e "$plugin_root/bin/plannotator-tui" -test "$("$plugin_root/bin/plannotator-tui.exe" --version)" = "plannotator-tui 0.6.0" -test "$(bash "$plugin_root/scripts/plannotator-tui.sh" --version)" = "plannotator-tui 0.6.0" - -output="$(bash "$plugin_root/scripts/fetch-plannotator-tui.sh")" -case "$output" in - *"already installed"*) ;; - *) echo "idempotent fetch did not short-circuit: $output" >&2; exit 1 ;; -esac diff --git a/scripts/test-herdr-windows-plugin.ps1 b/scripts/test-herdr-windows-plugin.ps1 deleted file mode 100644 index 5ffadb0..0000000 --- a/scripts/test-herdr-windows-plugin.ps1 +++ /dev/null @@ -1,108 +0,0 @@ -$ErrorActionPreference = "Stop" -Set-StrictMode -Version Latest -if (Test-Path variable:PSNativeCommandUseErrorActionPreference) { - $PSNativeCommandUseErrorActionPreference = $false -} - -$repositoryRoot = Split-Path -Parent $PSScriptRoot -$testRoot = Join-Path $env:RUNNER_TEMP ("pinned herdr plugin " + [guid]::NewGuid()) -$pluginRoot = Join-Path $testRoot "plugin root with spaces" -$archive = Join-Path $testRoot "herdr-windows-x86_64.zip" -$expanded = Join-Path $testRoot "herdr" -$synthetic = Join-Path $testRoot "synthetic plannotator-tui.exe" -$oldEnvironment = @{} -foreach ($name in @( - "PLANNOTATOR_TUI_BIN", - "XDG_CONFIG_HOME", - "XDG_STATE_HOME", - "HERDR_CONFIG_PATH", - "HERDR_SESSION", - "HERDR_SOCKET_PATH", - "HERDR_CLIENT_SOCKET_PATH" -)) { - $oldEnvironment[$name] = [Environment]::GetEnvironmentVariable($name, "Process") -} - -function Assert-True { - param([bool]$Condition, [string]$Message) - if (-not $Condition) { throw $Message } -} - -function Invoke-Herdr { - param([string]$Executable, [string[]]$Arguments) - $output = & $Executable @Arguments *>&1 | Out-String - if ($LASTEXITCODE -ne 0) { throw "herdr $($Arguments -join ' ') failed: $output" } - $output -} - -try { - New-Item -ItemType Directory -Force (Join-Path $pluginRoot "scripts") | Out-Null - Copy-Item -LiteralPath (Join-Path $repositoryRoot "herdr-plugin.toml") -Destination $pluginRoot - Copy-Item -LiteralPath (Join-Path $repositoryRoot "plannotator-tui.version") -Destination $pluginRoot - Copy-Item -LiteralPath (Join-Path $repositoryRoot "scripts/fetch-plannotator-tui.ps1") ` - -Destination (Join-Path $pluginRoot "scripts") - Set-Content -LiteralPath $synthetic -NoNewline -Value "synthetic executable" - $env:PLANNOTATOR_TUI_BIN = $synthetic - & powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass ` - -File (Join-Path $pluginRoot "scripts/fetch-plannotator-tui.ps1") - if ($LASTEXITCODE -ne 0) { throw "local override failed before pinned-Herdr link" } - Assert-True ( - (Test-Path -LiteralPath (Join-Path $pluginRoot "bin/plannotator-tui.exe") -PathType Leaf) - ) "local override did not stage the Full executable" - - Invoke-WebRequest -UseBasicParsing ` - "https://github.com/herdrdev/herdr/releases/download/v0.8.2/herdr-windows-x86_64.zip" ` - -OutFile $archive - $archiveHash = (Get-FileHash -Algorithm SHA256 -LiteralPath $archive).Hash.ToLowerInvariant() - Assert-True ( - $archiveHash -ceq "0ab3d0fe1434d55757997542b978c771d642987bb15a7130f4160f0db38821d5" - ) "pinned Herdr archive checksum differs" - Expand-Archive -LiteralPath $archive -DestinationPath $expanded - $herdr = Get-ChildItem -LiteralPath $expanded -Filter "herdr.exe" -File -Recurse | - Select-Object -First 1 - Assert-True ($null -ne $herdr) "pinned Herdr archive contains no herdr.exe" - - $env:PLANNOTATOR_TUI_BIN = $null - $env:XDG_CONFIG_HOME = Join-Path $testRoot "isolated config" - $env:XDG_STATE_HOME = Join-Path $testRoot "isolated state" - $env:HERDR_CONFIG_PATH = $null - $env:HERDR_SESSION = $null - $env:HERDR_SOCKET_PATH = $null - $env:HERDR_CLIENT_SOCKET_PATH = $null - $link = Invoke-Herdr -Executable $herdr.FullName -Arguments @("plugin", "link", $pluginRoot, "--enabled") - $linked = $link | ConvertFrom-Json - Assert-True ($linked.result.type -ceq "plugin_linked") "pinned Herdr did not link the plugin" - - $listedText = Invoke-Herdr -Executable $herdr.FullName -Arguments @( - "plugin", "list", "--plugin", "annotate", "--json" - ) - $listed = $listedText | ConvertFrom-Json - $plugins = @($listed.result.plugins) - Assert-True ($plugins.Count -eq 1) "pinned Herdr did not list exactly one Annotate plugin" - $plugin = $plugins[0] - $actionIds = @($plugin.actions | ForEach-Object { $_.id }) - foreach ($id in @("capture", "copy-context", "manage", "open", "open-link", "last")) { - Assert-True ($actionIds -contains $id) "pinned Herdr omitted action $id" - } - $paneIds = @($plugin.panes | ForEach-Object { $_.id }) - foreach ($id in @("editor", "manager", "doc")) { - Assert-True ($paneIds -contains $id) "pinned Herdr omitted pane $id" - } - foreach ($id in @("open", "open-link", "last")) { - $action = @($plugin.actions | Where-Object { $_.id -ceq $id }) - Assert-True ($action.Count -eq 1 -and @($action[0].platforms) -contains "windows") ` - "pinned Herdr did not retain the Windows gate for action $id" - } - $doc = @($plugin.panes | Where-Object { $_.id -ceq "doc" }) - Assert-True ($doc.Count -eq 1 -and @($doc[0].platforms) -contains "windows") ` - "pinned Herdr did not retain the Windows gate for pane doc" -} finally { - foreach ($name in $oldEnvironment.Keys) { - if ($null -eq $oldEnvironment[$name]) { - [Environment]::SetEnvironmentVariable($name, $null, "Process") - } else { - [Environment]::SetEnvironmentVariable($name, $oldEnvironment[$name], "Process") - } - } - Remove-Item -LiteralPath $testRoot -Recurse -Force -ErrorAction SilentlyContinue -} diff --git a/scripts/test-http-server.py b/scripts/test-http-server.py deleted file mode 100644 index 9f89b92..0000000 --- a/scripts/test-http-server.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 -"""Serve one fixture directory on an ephemeral loopback port.""" - -from __future__ import annotations - -import sys -from functools import partial -from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer -from pathlib import Path - - -class QuietHandler(SimpleHTTPRequestHandler): - def log_message(self, _format: str, *_args: object) -> None: - pass - - -def main() -> None: - root = Path(sys.argv[1]).resolve() - port_file = Path(sys.argv[2]).resolve() - handler = partial(QuietHandler, directory=str(root)) - server = ThreadingHTTPServer(("127.0.0.1", 0), handler) - port_file.write_text(str(server.server_port), encoding="ascii") - server.serve_forever() - - -if __name__ == "__main__": - main() diff --git a/scripts/test-windows-full-manifest.py b/scripts/test-windows-full-manifest.py deleted file mode 100644 index 5ff4777..0000000 --- a/scripts/test-windows-full-manifest.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python3 -"""Structural assertions for the distributed Full-mode manifest.""" - -from __future__ import annotations - -import sys -import tomllib -from pathlib import Path - - -PROGRAM = "./bin/plannotator-tui.exe" -EXPECTED_COMMANDS = { - ("panes", "doc"): [PROGRAM, "herdr", "pane"], - ("actions", "open"): [PROGRAM, "herdr", "open"], - ("actions", "open-link"): [PROGRAM, "herdr", "open"], - ("actions", "last"): [PROGRAM, "herdr", "last"], -} -UNIX_BUILD = ["bash", "scripts/fetch-plannotator-tui.sh"] -WINDOWS_BUILD = [ - "powershell.exe", - "-NoProfile", - "-NonInteractive", - "-ExecutionPolicy", - "Bypass", - "-File", - "scripts/fetch-plannotator-tui.ps1", -] - - -def fail(message: str) -> None: - raise AssertionError(message) - - -def platforms(manifest: dict[str, object], item: dict[str, object]) -> set[str]: - value = item.get("platforms", manifest.get("platforms", [])) - if not isinstance(value, list) or not all(isinstance(entry, str) for entry in value): - fail(f"invalid platforms: {value!r}") - return set(value) - - -def effective_entry( - manifest: dict[str, object], table: str, entry_id: str -) -> dict[str, object]: - items = manifest.get(table, []) - if not isinstance(items, list): - fail(f"[[{table}]] is not an array") - matches = [ - item - for item in items - if isinstance(item, dict) - and item.get("id") == entry_id - and "windows" in platforms(manifest, item) - ] - if len(matches) != 1: - fail(f"expected one effective Windows {table}.{entry_id}, found {len(matches)}") - return matches[0] - - -def main() -> None: - root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path(__file__).resolve().parent.parent - manifest_path = root / "herdr-plugin.toml" - with manifest_path.open("rb") as handle: - manifest = tomllib.load(handle) - - if "windows" not in platforms(manifest, {}): - fail("top-level manifest does not include Windows") - if (root / "plannotator-tui.version").read_text(encoding="utf-8").strip() != "0.6.0": - fail("plannotator-tui.version is not 0.6.0") - - builds = manifest.get("build", []) - if not isinstance(builds, list): - fail("[[build]] is not an array") - windows_builds = [ - item - for item in builds - if isinstance(item, dict) and "windows" in platforms(manifest, item) - ] - if len(windows_builds) != 1: - fail(f"expected one effective Windows Full build, found {len(windows_builds)}") - if windows_builds[0].get("command") != WINDOWS_BUILD: - fail(f"unexpected Windows build argv: {windows_builds[0].get('command')!r}") - unix_builds = [ - item - for item in builds - if isinstance(item, dict) and platforms(manifest, item) == {"macos", "linux"} - ] - if len(unix_builds) != 1 or unix_builds[0].get("command") != UNIX_BUILD: - fail(f"unexpected Unix build entries: {unix_builds!r}") - - for (table, entry_id), expected in EXPECTED_COMMANDS.items(): - command = effective_entry(manifest, table, entry_id).get("command") - if command != expected: - fail(f"unexpected {table}.{entry_id} argv: {command!r}") - forbidden = {"sh", "bash", "-c"} - if any( - not isinstance(argument, str) - or argument.lower() in forbidden - or argument.lower().endswith(".sh") - or "$" in argument - for argument in command - ): - fail(f"shell or interpolation found in {table}.{entry_id}: {command!r}") - - handler = effective_entry(manifest, "link_handlers", "markdown-file") - if handler.get("action") != "open-link": - fail(f"markdown-file points to {handler.get('action')!r}") - - -if __name__ == "__main__": - main()