From acb7dd4c93791352b8142f16fd22217772c668ba Mon Sep 17 00:00:00 2001 From: akai Date: Fri, 10 Jul 2026 12:52:06 +0800 Subject: [PATCH] fix: recover local Readest launcher --- scripts/open-readest-latest.ps1 | 46 ++++++++++++++++++++++++++-- scripts/test-open-readest-latest.ps1 | 24 +++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 scripts/test-open-readest-latest.ps1 diff --git a/scripts/open-readest-latest.ps1 b/scripts/open-readest-latest.ps1 index 94a6c96a..f98f312e 100644 --- a/scripts/open-readest-latest.ps1 +++ b/scripts/open-readest-latest.ps1 @@ -1,5 +1,5 @@ param( - [string]$Remote = "origin", + [string]$Remote = "akai-tools", [string]$Branch = "codex/readest-inline-review-mode", [switch]$SkipPull, [switch]$CheckOnly, @@ -62,14 +62,54 @@ function Stop-OldReadestDev { ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } + + $portConnection = Get-NetTCPConnection -State Listen -LocalPort 3000 -ErrorAction SilentlyContinue | + Select-Object -First 1 + if (-not $portConnection) { + return + } + + $ownerPid = $portConnection.OwningProcess + $processLineage = @() + $lineagePid = $ownerPid + while ($lineagePid -and $processLineage.Count -lt 8) { + $lineageProcess = Get-CimInstance Win32_Process -Filter "ProcessId = $lineagePid" -ErrorAction SilentlyContinue + if (-not $lineageProcess) { + break + } + $processLineage += $lineageProcess + $lineagePid = $lineageProcess.ParentProcessId + } + + $isReadestDev = $processLineage | Where-Object { + $_.Name -ieq "Readest.exe" -or + ($_.CommandLine -and + $_.CommandLine -match "(?i)readest" -and + $_.CommandLine -match "(?i)next(.+?)dev|start-server\.js|@readest/readest-app|tauri(.+?)dev") + } + if (-not $isReadestDev) { + $owner = $processLineage | Select-Object -First 1 + $ownerDescription = if ($owner.CommandLine) { $owner.CommandLine } else { "$($owner.Name) (PID $ownerPid)" } + throw "Port 3000 is occupied by an unrelated process: $ownerDescription. Stop it or launch Readest after freeing port 3000." + } + + Write-Host "Stopping old Readest/Next listener on port 3000 (PID $ownerPid)." -ForegroundColor Yellow + Stop-Process -Id $ownerPid -Force -ErrorAction SilentlyContinue + for ($attempt = 0; $attempt -lt 20; $attempt++) { + Start-Sleep -Milliseconds 250 + if (-not (Get-NetTCPConnection -State Listen -LocalPort 3000 -ErrorAction SilentlyContinue)) { + return + } + } + throw "Port 3000 is still occupied after stopping the old Readest/Next process. Close the old development terminal and try again." } function Assert-CleanWorktree { - $status = (& git -C $RepoRoot status --porcelain) + $status = @(& git -C $RepoRoot status --porcelain --untracked-files=no --ignore-submodules=dirty) if ($status) { Write-Host "" Write-Host "Local changes were found. The launcher will not pull or overwrite them:" -ForegroundColor Yellow - & git -C $RepoRoot status --short + $status | ForEach-Object { Write-Host $_ } throw "Commit, stash, or discard local changes before launching the latest organization version." } } diff --git a/scripts/test-open-readest-latest.ps1 b/scripts/test-open-readest-latest.ps1 new file mode 100644 index 00000000..ba8ca237 --- /dev/null +++ b/scripts/test-open-readest-latest.ps1 @@ -0,0 +1,24 @@ +$ErrorActionPreference = "Stop" + +$ScriptPath = Join-Path $PSScriptRoot "open-readest-latest.ps1" +$ScriptText = Get-Content -LiteralPath $ScriptPath -Raw + +function Assert-Match { + param( + [string]$Pattern, + [string]$Message + ) + + if ($ScriptText -notmatch $Pattern) { + throw $Message + } +} + +Assert-Match '\[string\]\$Remote\s*=\s*"akai-tools"' "Launcher must pull from akai-tools by default." +Assert-Match 'status\s+--porcelain\s+--untracked-files=no\s+--ignore-submodules=dirty' "Clean check must ignore runtime logs and dirty submodule contents." +Assert-Match 'Get-NetTCPConnection[^\r\n]*-LocalPort\s+3000' "Launcher must inspect the Tauri dev port before starting." +Assert-Match 'Stop-Process[^\r\n]*-Id\s+\$ownerPid' "Launcher must stop an old Readest/Next process that owns port 3000." +Assert-Match 'Port 3000 is occupied' "Launcher must report an actionable error for an unrelated port owner." +Assert-Match 'Port 3000 is still occupied' "Launcher must wait for the previous dev server to release the port." + +Write-Host "open-readest-latest launcher checks passed."