/** * Line-oriented stdin for Bare/Pear (no node:readline) and Node fallback. * Pear-terminal uses bare-readline + bare-stdio; we mirror that after Node readline. */ /** * @param {import('stream').Readable} stdin * @param {import('stream').Writable} stdout * @returns {(prompt: string) => Promise} */ export async function createBareReadlineQuestion(stdin, stdout) { const mod = await import('bare-readline') const createInterface = mod.createInterface ?? mod.default?.createInterface if (typeof createInterface !== 'function') { throw new Error('bare-readline: createInterface missing') } const rl = createInterface({ input: stdin, output: stdout, prompt: '' }) return (prompt) => new Promise((resolve) => { rl.setPrompt(prompt) rl.prompt() rl.once('line', (line) => { resolve(line) }) }) } /** * @param {import('stream').Readable} stdin * @param {import('stream').Writable} stdout * @returns {(prompt: string) => Promise} */ export function createStreamLineReader(stdin, stdout) { let buf = '' /** @type {string[]} */ const queue = [] /** @type {((line: string | null) => void)[]} */ const waiters = [] function deliver(line) { if (waiters.length) waiters.shift()(line) else queue.push(line) } function onData(chunk) { const s = typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8') buf += s let i while ((i = buf.indexOf('\n')) !== -1) { const raw = buf.slice(0, i) buf = buf.slice(i + 1) deliver(raw.replace(/\r$/, '')) } } function flushRemainder() { if (buf.length) { deliver(buf.replace(/\r$/, '')) buf = '' } } function onEnd() { flushRemainder() while (waiters.length) waiters.shift()(null) } if (typeof stdin.setEncoding === 'function') stdin.setEncoding('utf8') stdin.on('data', onData) stdin.on('end', onEnd) stdin.on('error', onEnd) if (typeof stdin.resume === 'function') stdin.resume() return function readLine(prompt) { if (typeof stdout.write === 'function') stdout.write(prompt) if (queue.length) return Promise.resolve(queue.shift() ?? null) return new Promise((resolve) => waiters.push(resolve)) } } /** * @returns {boolean} */ export function looksLikeInteractiveStdin(stdin) { return Boolean( stdin && typeof stdin.on === 'function' && typeof stdin.resume === 'function' ) }