UNPKG

next

Version:

The React Framework

144 lines (143 loc) • 4.94 kB
import { emitKeypressEvents } from 'readline'; import { PassThrough } from 'stream'; import cliSelect from 'next/dist/compiled/cli-select'; import { bold, cyan } from '../picocolors'; export async function promptUpgrade({ message, signal, canUpdate, onShown }) { if (signal.aborted) { return 'skip'; } const input = process.stdin; const terminal = process.stdout; const values = { ...canUpdate ? { update: 'Upgrade now' } : {}, skip: 'Skip', dismiss: 'Skip until next version' }; const labels = Object.values(values); const heading = `${message}\n\n`; const wasRaw = input.isRaw ?? false; const wasFlowing = input.readableFlowing; // Isolate cancellation from other consumers of stdin. cli-select can close // this stream without changing the real terminal's original raw/flow state. const keys = Object.assign(new PassThrough(), { setRawMode () {} }); let interrupted = false; let cancelled = false; let resized = false; let selectedIndex = 0; let shown = false; const renderValue = (value, selected)=>{ if (value === labels[0]) { // We own this screen. Redraw from the top instead of relying on // cli-select's one-row-per-choice cursor movement when choices wrap. terminal.write(`\x1b[H\x1b[2J${heading}`); // Redraws must not count the same rendered menu as another nudge. if (!shown) { shown = true; onShown == null ? void 0 : onShown(); } } if (selected) { selectedIndex = labels.indexOf(value); } return selected ? cyan(bold(value)) : value; }; const cancel = ()=>{ cancelled = true; keys.emit('keypress', '', { name: 'escape' }); }; const onKey = (text, key)=>{ if ((key == null ? void 0 : key.ctrl) && key.name === 'c') { interrupted = true; } if ((key == null ? void 0 : key.name) === 'escape') { cancelled = true; } keys.emit('keypress', text, key); }; const onResize = ()=>{ resized = true; // A terminal write can emit resize while cli-select is still opening. // Wait until its selection callback is installed before cancelling it. queueMicrotask(()=>{ if (resized && !restored) { keys.emit('keypress', '', { name: 'escape' }); } }); }; let restored = false; const restore = ()=>{ if (restored) { return; } restored = true; input.removeListener('keypress', onKey); terminal.removeListener('resize', onResize); signal.removeEventListener('abort', cancel); process.removeListener('exit', restore); keys.destroy(); try { input.setRawMode(wasRaw); } finally{ if (wasFlowing !== true) { input.pause(); } terminal.write('\x1b[?1049l\x1b[?25h'); } }; // CLI signal handlers may exit synchronously, before the promise settles. process.once('exit', restore); try { // Keep startup output on the normal screen while the menu owns the terminal. terminal.write('\x1b[?1049h'); emitKeypressEvents(input); input.on('keypress', onKey); terminal.on('resize', onResize); signal.addEventListener('abort', cancel, { once: true }); input.setRawMode(true); input.resume(); while(true){ resized = false; const selection = cliSelect({ values, defaultValue: selectedIndex, selected: cyan('❯'), unselected: ' ', indentation: 2, cleanup: true, // cli-select types inputStream as a WriteStream, but only consumes // keypress events and the raw-mode methods supplied by this proxy. inputStream: keys, outputStream: terminal, valueRenderer: renderValue }); if (signal.aborted || cancelled) { cancel(); } try { const { id } = await selection; return signal.aborted || cancelled ? 'skip' : id; } catch (error) { // cli-select rejects without a reason for Escape / Ctrl+C. if (error) { throw error; } if (!resized || signal.aborted || cancelled || interrupted) { return interrupted ? 'interrupt' : 'skip'; } } } } finally{ restore(); } } //# sourceMappingURL=prompt.js.map