param( [string]$Remote = "origin", [string]$Branch = "codex/full-book-translation", [switch]$Web, [switch]$SkipPull, [switch]$CheckOnly, [switch]$KeepRunning ) $ErrorActionPreference = "Stop" [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 $RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path function Write-Step { param([string]$Message) Write-Host "" Write-Host "==> $Message" -ForegroundColor Cyan } function Invoke-Checked { param( [string]$Command, [string[]]$Arguments, [string]$WorkingDirectory = $RepoRoot ) Push-Location $WorkingDirectory try { & $Command @Arguments if ($LASTEXITCODE -ne 0) { throw "$Command $($Arguments -join ' ') failed with exit code $LASTEXITCODE." } } finally { Pop-Location } } function Add-PathIfExists { param([string]$PathToAdd) if ($PathToAdd -and (Test-Path -LiteralPath $PathToAdd)) { $script:PathParts += $PathToAdd } } function Stop-OldReadestDev { if ($KeepRunning) { return } Write-Step "Stopping old Readest desktop/dev processes" Get-Process -Name "Readest" -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue $repoLower = $RepoRoot.ToLowerInvariant() $currentPid = $PID Get-CimInstance Win32_Process | Where-Object { $_.ProcessId -ne $currentPid -and $_.CommandLine -and $_.CommandLine.ToLowerInvariant().Contains($repoLower) -and ($_.CommandLine -match "tauri\s+dev|next\s+dev|@readest/readest-app|epub-reviewer:dev") } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } } function Start-ReadestWeb { Write-Step "Starting Readest web development server" Write-Host "Refresh http://localhost:3000 to see the latest committed changes." Invoke-Checked "pnpm" @("--filter", "@readest/readest-app", "dev-web") } function Test-SubmoduleCheckout { param([string]$RelativePath) $fullPath = Join-Path $RepoRoot $RelativePath if (-not (Test-Path -LiteralPath (Join-Path $fullPath ".git"))) { return $false } & git -C $fullPath rev-parse --is-inside-work-tree 2>$null | Out-Null return $LASTEXITCODE -eq 0 } function Assert-CleanWorktree { $status = (& git -C $RepoRoot status --porcelain) 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 throw "Commit, stash, or discard local changes before launching the latest organization version." } } try { $PathParts = @() $runtimeRoot = Join-Path $env:USERPROFILE ".cache\codex-runtimes\codex-primary-runtime\dependencies" Add-PathIfExists "C:\w64devkit\bin" Add-PathIfExists (Join-Path $runtimeRoot "bin") Add-PathIfExists (Join-Path $runtimeRoot "node\bin") Add-PathIfExists (Join-Path $env:USERPROFILE ".cargo\bin") $env:Path = (($PathParts + ($env:Path -split ";" | Where-Object { $_ })) -join ";") $env:RUSTUP_TOOLCHAIN = "stable-x86_64-pc-windows-gnu" foreach ($command in @("git", "pnpm", "cargo")) { if (-not (Get-Command $command -ErrorAction SilentlyContinue)) { throw "Required command not found: $command" } } Write-Step "Readest latest launcher" Write-Host "Repository: $RepoRoot" Write-Host "Remote: $Remote" Write-Host "Branch: $Branch" $beforeHead = (& git -C $RepoRoot rev-parse HEAD).Trim() if (-not $SkipPull) { Assert-CleanWorktree Write-Step "Fetching latest code from organization remote" Invoke-Checked "git" @("-C", $RepoRoot, "fetch", "--prune", $Remote) $currentBranch = (& git -C $RepoRoot branch --show-current).Trim() if ($currentBranch -ne $Branch) { Write-Step "Switching to $Branch" & git -C $RepoRoot show-ref --verify --quiet "refs/heads/$Branch" if ($LASTEXITCODE -eq 0) { Invoke-Checked "git" @("-C", $RepoRoot, "switch", $Branch) } else { Invoke-Checked "git" @("-C", $RepoRoot, "switch", "--track", "-c", $Branch, "$Remote/$Branch") } } Write-Step "Fast-forwarding local branch" Invoke-Checked "git" @("-C", $RepoRoot, "pull", "--ff-only", $Remote, $Branch) } else { Write-Step "Skipping git pull by request" } $afterHead = (& git -C $RepoRoot rev-parse HEAD).Trim() Write-Step "Updating submodules" $requiredSubmodules = @( "packages/foliate-js", "packages/simplecc-wasm", "packages/js-mdict", "apps/readest-app/src-tauri/plugins/tauri-plugin-turso" ) $missingSubmodules = @($requiredSubmodules | Where-Object { -not (Test-SubmoduleCheckout $_) }) if ($missingSubmodules.Count -gt 0) { $submoduleArgs = @( "-C", $RepoRoot, "-c", "http.version=HTTP/1.1", "-c", "submodule.fetchJobs=1", "submodule", "update", "--init", "--depth", "1", "--" ) + $missingSubmodules Invoke-Checked "git" $submoduleArgs } else { Write-Host "Required submodules are already initialized." } $changedFiles = @() if ($beforeHead -ne $afterHead) { $changedFiles = (& git -C $RepoRoot diff --name-only $beforeHead $afterHead) } $needsInstall = -not (Test-Path -LiteralPath (Join-Path $RepoRoot "node_modules")) -or ($changedFiles -contains "pnpm-lock.yaml") -or ($changedFiles -contains "package.json") -or ($changedFiles -contains "apps/readest-app/package.json") if ($needsInstall) { Write-Step "Installing/updating dependencies" Invoke-Checked "pnpm" @("install", "--frozen-lockfile") } Write-Step "Building local submodule dependencies" Invoke-Checked "pnpm" @( "--dir", (Join-Path $RepoRoot "apps\readest-app\src-tauri\plugins\tauri-plugin-turso"), "install", "--ignore-workspace", "--frozen-lockfile" ) Invoke-Checked "pnpm" @( "--dir", (Join-Path $RepoRoot "apps\readest-app\src-tauri\plugins\tauri-plugin-turso"), "build" ) Write-Step "Preparing browser runtime assets" Invoke-Checked "pnpm" @("--filter", "@readest/readest-app", "setup-vendors") if ($CheckOnly) { Write-Step "Launcher check complete" exit 0 } Stop-OldReadestDev if ($Web) { Start-ReadestWeb } else { Write-Step "Starting Readest desktop with the latest code" Write-Host "Close the Readest window or press Ctrl+C here to stop the dev server." Invoke-Checked "pnpm" @("--filter", "@readest/readest-app", "tauri", "dev") } } catch { Write-Host "" Write-Host "Readest launcher failed:" -ForegroundColor Red Write-Host $_.Exception.Message -ForegroundColor Red Write-Host "" Write-Host "Press Enter to close this window." [void][Console]::ReadLine() exit 1 }