From 10cbb3b256a36acedd83e90319ae03b5eb3801d0 Mon Sep 17 00:00:00 2001 From: Ivan Fontosh Date: Mon, 3 Aug 2026 14:17:37 +0800 Subject: [PATCH] fix(release): unlock rolldown before npm ci in prepare-release Co-authored-by: Cursor --- scripts/ttrpg-release/prepare-release.ps1 | 67 ++++++++++++++++++++++- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/scripts/ttrpg-release/prepare-release.ps1 b/scripts/ttrpg-release/prepare-release.ps1 index ea76a63..80a9a30 100644 --- a/scripts/ttrpg-release/prepare-release.ps1 +++ b/scripts/ttrpg-release/prepare-release.ps1 @@ -51,6 +51,62 @@ function Test-IsNpmCi([string[]]$NpmArgs) { return ($NpmArgs.Count -eq 1 -and $NpmArgs[0] -eq 'ci') } +# Stops Vite/Electron/dev.mjs/TTRPGPlayer that lock native .node files under node_modules +# (typical EPERM unlink on @rolldown/binding-win32-x64-msvc during npm ci). +function Stop-DndPlayerLocks { + param([string]$ProjectRoot) + + Write-Host ' Stopping processes that may lock node_modules...' + $killed = [System.Collections.Generic.HashSet[int]]::new() + + foreach ($name in @('TTRPGPlayer', 'electron')) { + Get-Process -Name $name -ErrorAction SilentlyContinue | ForEach-Object { + Stop-Process -Id $_.Id -Force -ErrorAction SilentlyContinue + [void]$killed.Add([int]$_.Id) + } + } + + Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue | ForEach-Object { + $cmd = [string]$_.CommandLine + if (-not $cmd) { return } + $touches = ($cmd -match [regex]::Escape($ProjectRoot)) -or ($cmd -match 'dnd_player') + if (-not $touches) { return } + if ($cmd -match 'dev\.mjs|vite|electron|esbuild|rolldown|tsx|scripts[/\\]dev') { + Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue + [void]$killed.Add([int]$_.ProcessId) + } + } + + foreach ($port in @(5173, 5174)) { + try { + Get-NetTCPConnection -LocalPort $port -ErrorAction Stop | ForEach-Object { + $ownerPid = [int]$_.OwningProcess + if ($ownerPid -gt 0) { + Stop-Process -Id $ownerPid -Force -ErrorAction SilentlyContinue + [void]$killed.Add($ownerPid) + } + } + } catch { + # NetTCPIP may be unavailable in some shells — ignore + } + } + + Start-Sleep -Seconds 2 + + $rolldownDir = Join-Path $ProjectRoot 'node_modules\@rolldown\binding-win32-x64-msvc' + if (Test-Path -LiteralPath $rolldownDir) { + try { + Remove-Item -LiteralPath $rolldownDir -Recurse -Force -ErrorAction Stop + Write-Host ' [OK] removed locked @rolldown/binding-win32-x64-msvc (npm ci will restore)' + } catch { + Write-Host " [--] rolldown binding still locked: $($_.Exception.Message)" -ForegroundColor Yellow + } + } + + Write-Host " Cleared $($killed.Count) process id(s); waiting for file unlock" + Start-Sleep -Seconds 1 +} + function Invoke-NpmRaw { param( [string[]]$NpmArgs, @@ -101,8 +157,14 @@ function Invoke-Npm { Write-Host " > $Label" $exitCode = Invoke-NpmRaw -NpmArgs $NpmArgs -WorkingDirectory $WorkingDirectory if ($exitCode -ne 0 -and (Test-IsNpmCi $NpmArgs)) { - Write-Host " [--] npm ci failed (exit $exitCode). Retrying via ffmpeg-static mirror..." -ForegroundColor Yellow - $exitCode = Invoke-NpmCiWithFfmpegMirror $WorkingDirectory + Write-Host " [--] npm ci failed (exit $exitCode). Clearing locks and retrying..." -ForegroundColor Yellow + Stop-DndPlayerLocks $WorkingDirectory + $exitCode = Invoke-NpmRaw -NpmArgs $NpmArgs -WorkingDirectory $WorkingDirectory + if ($exitCode -ne 0) { + Write-Host " [--] npm ci failed again. Retrying via ffmpeg-static mirror..." -ForegroundColor Yellow + Stop-DndPlayerLocks $WorkingDirectory + $exitCode = Invoke-NpmCiWithFfmpegMirror $WorkingDirectory + } } if ($exitCode -ne 0) { throw "$Label failed (exit $exitCode)" @@ -418,6 +480,7 @@ if ($SkipGit) { # Step 3 - Windows build Write-Step 3 'Build Windows (npm ci + pack:win)' +Stop-DndPlayerLocks $projectRoot Invoke-Npm 'npm ci' @('ci') $projectRoot Invoke-Npm 'npm run pack:win' @('run', 'pack:win') $projectRoot Write-Ok 'Windows build finished'