diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..99547e2 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text=auto +*.sh text eol=lf +*.yml text eol=lf +*.yaml text eol=lf diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 75dbec9..fd9d21d 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -43,6 +43,11 @@ jobs: - name: Checkout uses: actions/checkout@v7 + - name: Set up Python + uses: actions/setup-python@v7 + with: + python-version: "3.11" + - name: Test install and uninstall shell: powershell run: .\scripts\test-installers.ps1 diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b6774..9e1c4f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ - Added `CAVEMAN_OPENCODE_DEFAULT_LEVEL` install-time override for `lite`, `full`, `ultra`, `wenyan`, `wenyan-lite`, and `wenyan-ultra`. - Accepted trailing commas in existing OpenCode configuration without changing comma-like text inside JSON strings. - Added functional install/uninstall tests for POSIX and Windows PowerShell environments. +- Replaced the Windows `Expand-Archive` dependency with .NET ZIP extraction for hosts where `Microsoft.PowerShell.Archive` cannot load. +- Enforced LF endings for shell scripts and workflow files across Windows and POSIX checkouts. ## 0.2.0 diff --git a/install-opencode.ps1 b/install-opencode.ps1 index f63c5b0..1d32e42 100644 --- a/install-opencode.ps1 +++ b/install-opencode.ps1 @@ -92,6 +92,11 @@ function ConvertFrom-OpenCodeJson([string]$Content) { } } +function Expand-ZipArchive([string]$Path, [string]$DestinationPath) { + Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop + [System.IO.Compression.ZipFile]::ExtractToDirectory($Path, $DestinationPath) +} + if (-not (Test-Path -LiteralPath (Join-Path $sourceOpenCode "AGENTS.md"))) { $archiveUrl = $env:CAVEMAN_OPENCODE_ARCHIVE_URL if ([string]::IsNullOrWhiteSpace($archiveUrl)) { @@ -103,8 +108,8 @@ if (-not (Test-Path -LiteralPath (Join-Path $sourceOpenCode "AGENTS.md"))) { New-Item -ItemType Directory -Force $tempDir | Out-Null Write-Host "Downloading Caveman for OpenCode from $archiveUrl" - Invoke-WebRequest -Uri $archiveUrl -OutFile $zipPath - Expand-Archive -LiteralPath $zipPath -DestinationPath $tempDir -Force + Invoke-WebRequest -UseBasicParsing -Uri $archiveUrl -OutFile $zipPath + Expand-ZipArchive -Path $zipPath -DestinationPath $tempDir $sourceDir = Get-ChildItem -LiteralPath $tempDir -Directory | Select-Object -First 1 if (-not $sourceDir) { diff --git a/scripts/test-installers.ps1 b/scripts/test-installers.ps1 index d4786a0..ac50303 100644 --- a/scripts/test-installers.ps1 +++ b/scripts/test-installers.ps1 @@ -10,9 +10,10 @@ $repo = Split-Path -Parent $PSScriptRoot $testRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("caveman-opencode-test-" + [guid]::NewGuid().ToString("N")) $originalUserProfile = $env:USERPROFILE $originalDefaultLevel = $env:CAVEMAN_OPENCODE_DEFAULT_LEVEL +$originalArchiveUrl = $env:CAVEMAN_OPENCODE_ARCHIVE_URL +$serverProcess = $null try { - $env:USERPROFILE = $testRoot $env:CAVEMAN_OPENCODE_DEFAULT_LEVEL = "ultra" $configDir = Join-Path $testRoot ".config\opencode" @@ -37,7 +38,57 @@ try { } '@ | Set-Content -LiteralPath $configFile -Encoding UTF8 - & (Join-Path $repo "install-opencode.ps1") + Add-Type -AssemblyName System.IO.Compression.FileSystem -ErrorAction Stop + $archiveRoot = Join-Path $testRoot "archive" + $archiveSource = Join-Path $archiveRoot "caveman-opencode-main" + $archivePath = Join-Path $testRoot "source.zip" + New-Item -ItemType Directory -Force -Path $archiveSource | Out-Null + Copy-Item -Recurse -Force -LiteralPath (Join-Path $repo ".opencode") -Destination $archiveSource + [System.IO.Compression.ZipFile]::CreateFromDirectory($archiveRoot, $archivePath) + + $portProbe = [System.Net.Sockets.TcpListener]::new([System.Net.IPAddress]::Loopback, 0) + $portProbe.Start() + $port = ([System.Net.IPEndPoint]$portProbe.LocalEndpoint).Port + $portProbe.Stop() + $serverUrl = "http://127.0.0.1:$port/source.zip" + $serverOutputPath = Join-Path $testRoot "archive-server.out" + $serverErrorPath = Join-Path $testRoot "archive-server.err" + $python = (Get-Command python -ErrorAction Stop).Source + $pythonArguments = "-m http.server $port --bind 127.0.0.1 --directory `"$testRoot`"" + $serverProcess = Start-Process -FilePath $python -ArgumentList $pythonArguments -PassThru -WindowStyle Hidden -RedirectStandardOutput $serverOutputPath -RedirectStandardError $serverErrorPath + + $remoteDir = Join-Path $testRoot "remote-installer" + $remoteInstaller = Join-Path $remoteDir "install-opencode.ps1" + New-Item -ItemType Directory -Force -Path $remoteDir | Out-Null + Copy-Item -Force -LiteralPath (Join-Path $repo "install-opencode.ps1") -Destination $remoteInstaller + $env:CAVEMAN_OPENCODE_ARCHIVE_URL = $serverUrl + + $serverDeadline = [DateTime]::UtcNow.AddSeconds(10) + $serverReady = $false + while (-not $serverReady -and [DateTime]::UtcNow -lt $serverDeadline) { + if ($serverProcess.HasExited) { + $serverDetails = Get-Content -LiteralPath $serverErrorPath -Raw -ErrorAction SilentlyContinue + throw "Archive server exited before startup. $serverDetails" + } + $client = [System.Net.Sockets.TcpClient]::new() + try { + $client.Connect("127.0.0.1", $port) + $serverReady = $client.Connected + } catch { + Start-Sleep -Milliseconds 100 + } finally { + $client.Close() + } + } + Assert-True $serverReady "archive server should start" + + $env:USERPROFILE = $testRoot + try { + & $remoteInstaller + } catch { + $serverDetails = Get-Content -LiteralPath $serverErrorPath -Raw -ErrorAction SilentlyContinue + throw "Remote installer failed: $($_.Exception.Message). Archive server: $serverDetails" + } $config = Get-Content -LiteralPath $configFile -Raw -Encoding UTF8 | ConvertFrom-Json $skills = @("caveman", "caveman-commit", "caveman-review", "caveman-help", "caveman-compress") @@ -74,6 +125,15 @@ try { } else { $env:CAVEMAN_OPENCODE_DEFAULT_LEVEL = $originalDefaultLevel } + if ($null -eq $originalArchiveUrl) { + Remove-Item Env:CAVEMAN_OPENCODE_ARCHIVE_URL -ErrorAction SilentlyContinue + } else { + $env:CAVEMAN_OPENCODE_ARCHIVE_URL = $originalArchiveUrl + } + if ($null -ne $serverProcess -and -not $serverProcess.HasExited) { + Stop-Process -Id $serverProcess.Id -Force -ErrorAction SilentlyContinue + [void]$serverProcess.WaitForExit(5000) + } if (Test-Path -LiteralPath $testRoot) { Remove-Item -Recurse -Force -LiteralPath $testRoot }