fix(release): read release-notes prompt from /dev/tty to avoid EAGAIN

npm run wrapping leaves stdin in non-blocking mode while still reporting
isTTY=true, so fs.readSync(0, ...) threw EAGAIN and crashed `npm run version`
at the "Generate release notes via AI?" prompt. Read from /dev/tty (always
blocking) when available, and fall back to stdin with an Atomics.wait retry
on EAGAIN for platforms without /dev/tty.
This commit is contained in:
johannesjo 2026-05-16 13:23:29 +02:00
parent 25d637e917
commit b9a4431432

View file

@ -270,21 +270,54 @@ const writeFileEnsuringDir = (filePath, content) => {
};
const readLineSync = () => {
// Open /dev/tty directly when available: stdin (fd 0) may be in non-blocking
// mode when this script is invoked through `npm run`, which makes
// fs.readSync(0, ...) throw EAGAIN immediately. /dev/tty is always blocking.
let fd = 0;
let opened = false;
try {
fd = fs.openSync('/dev/tty', 'r');
opened = true;
} catch {
fd = 0;
}
const buffer = Buffer.alloc(1);
const chars = [];
const sharedBuf = new SharedArrayBuffer(4);
const waitView = new Int32Array(sharedBuf);
while (true) {
const bytesRead = fs.readSync(0, buffer, 0, 1, null);
if (bytesRead === 0) {
break;
}
try {
while (true) {
let bytesRead;
try {
bytesRead = fs.readSync(fd, buffer, 0, 1, null);
} catch (err) {
if (err.code === 'EAGAIN') {
Atomics.wait(waitView, 0, 0, 20);
continue;
}
throw err;
}
if (bytesRead === 0) {
break;
}
const char = buffer.toString('utf8', 0, bytesRead);
if (char === '\n') {
break;
const char = buffer.toString('utf8', 0, bytesRead);
if (char === '\n') {
break;
}
if (char !== '\r') {
chars.push(char);
}
}
if (char !== '\r') {
chars.push(char);
} finally {
if (opened) {
try {
fs.closeSync(fd);
} catch {
/* ignore */
}
}
}