1const readline = require('readline') 2const Mute = require('mute-stream') 3 4module.exports = async function read ({ 5 default: def = '', 6 input = process.stdin, 7 output = process.stdout, 8 completer, 9 prompt = '', 10 silent, 11 timeout, 12 edit, 13 terminal, 14 replace, 15}) { 16 if (typeof def !== 'undefined' && typeof def !== 'string' && typeof def !== 'number') { 17 throw new Error('default value must be string or number') 18 } 19 20 let editDef = false 21 prompt = prompt.trim() + ' ' 22 terminal = !!(terminal || output.isTTY) 23 24 if (def) { 25 if (silent) { 26 prompt += '(<default hidden>) ' 27 } else if (edit) { 28 editDef = true 29 } else { 30 prompt += '(' + def + ') ' 31 } 32 } 33 34 const m = new Mute({ replace, prompt }) 35 m.pipe(output, { end: false }) 36 output = m 37 38 return new Promise((resolve, reject) => { 39 const rl = readline.createInterface({ input, output, terminal, silent: true, completer }) 40 const timer = timeout && setTimeout(() => onError(new Error('timed out')), timeout) 41 42 output.unmute() 43 rl.setPrompt(prompt) 44 rl.prompt() 45 46 if (silent) { 47 output.mute() 48 } else if (editDef) { 49 rl.line = def 50 rl.cursor = def.length 51 rl._refreshLine() 52 } 53 54 const done = () => { 55 rl.close() 56 clearTimeout(timer) 57 output.mute() 58 output.end() 59 } 60 61 const onError = (er) => { 62 done() 63 reject(er) 64 } 65 66 rl.on('error', onError) 67 rl.on('line', (line) => { 68 if (silent && terminal) { 69 output.unmute() 70 } 71 done() 72 // truncate the \n at the end. 73 const res = line.replace(/\r?\n?$/, '') || def || '' 74 return resolve(res) 75 }) 76 77 rl.on('SIGINT', () => { 78 rl.close() 79 onError(new Error('canceled')) 80 }) 81 }) 82} 83