fix(release): kill broader locks before npm ci (rolldown EPERM)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Ivan Fontosh
2026-08-03 14:17:47 +08:00
parent 1bf3fa466d
commit b4db1c242c
+52 -8
View File
@@ -3,18 +3,60 @@ import { promisify } from 'node:util';
const execFileAsync = promisify(execFile);
/** Stop vite/electron dev servers that lock dnd_player node_modules during npm ci. */
/**
* Stop Vite/Electron/dev.mjs/TTRPGPlayer that lock native .node files under
* dnd_player/node_modules (EPERM unlink on @rolldown during npm ci).
* @param {string} projectRoot
*/
export async function killBlockingDevProcesses(projectRoot) {
if (process.platform !== 'win32') return;
const rootEsc = projectRoot.replace(/'/g, "''");
const script = `
$ErrorActionPreference = 'SilentlyContinue'
$root = '${rootEsc}'
$killed = [System.Collections.Generic.HashSet[int]]::new()
foreach ($name in @('TTRPGPlayer','electron')) {
Get-Process -Name $name | ForEach-Object {
Stop-Process -Id $_.Id -Force
[void]$killed.Add([int]$_.Id)
}
}
Get-CimInstance Win32_Process -Filter "Name='node.exe'" | ForEach-Object {
$cmd = [string]$_.CommandLine
if (-not $cmd) { return }
$touches = ($cmd -like ('*' + $root + '*')) -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
[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
[void]$killed.Add($ownerPid)
}
}
} catch {}
}
Start-Sleep -Seconds 2
$rolldown = Join-Path $root 'node_modules\\@rolldown\\binding-win32-x64-msvc'
if (Test-Path -LiteralPath $rolldown) {
Remove-Item -LiteralPath $rolldown -Recurse -Force -ErrorAction SilentlyContinue
}
Start-Sleep -Seconds 1
Write-Output ('killed=' + $killed.Count)
`;
try {
const { stdout } = await execFileAsync(
'powershell',
[
'-NoProfile',
'-Command',
`Get-CimInstance Win32_Process -Filter "name='node.exe'" | Where-Object { $_.CommandLine -match 'dnd_player' -and ($_.CommandLine -match 'vite dev|electron') } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue }`,
],
{ windowsHide: true },
['-NoProfile', '-Command', script],
{ windowsHide: true, timeout: 30000 },
);
void stdout;
} catch {
@@ -24,6 +66,7 @@ export async function killBlockingDevProcesses(projectRoot) {
/**
* @param {string} cwd
* @param {string} scriptName
* @param {string[]} args
* @param {(line: string) => void} onLine
*/
@@ -62,7 +105,7 @@ export function runPowerShellScript(cwd, scriptName, args, onLine) {
});
}
/** @param {string} projectRoot */
/** @param {string} projectRoot @param {(line: string) => void} onLine */
export async function gitPull(projectRoot, onLine) {
onLine('git pull…');
await new Promise((resolve, reject) => {
@@ -88,6 +131,7 @@ export async function gitPull(projectRoot, onLine) {
/** @param {string} line @returns {number | null} */
export function progressFromLogLine(line) {
if (/Mac artifacts check/i.test(line)) return 12;
if (/Stopping processes that may lock/i.test(line)) return 20;
if (/Build Windows/i.test(line)) return 22;
if (/npm ci/i.test(line)) return 28;
if (/pack:win/i.test(line)) return 38;