UNPKG

4.04 kBJavaScriptView Raw
1'use strict'
2
3const cli = require('../')
4const errors = require('./errors')
5const util = require('./util')
6const color = cli.color
7const ansi = require('ansi-escapes')
8const Spinner = require('./spinner')
9const nodeUtil = require('util')
10
11function promptMasked (options) {
12 return new Promise(function (resolve, reject) {
13 let stdin = process.stdin
14 let stderr = process.stderr
15 let input = ''
16 stdin.setEncoding('utf8')
17 stderr.write(ansi.eraseLine)
18 stderr.write(ansi.cursorLeft)
19 cli.console.writeError(options.prompt)
20 stdin.resume()
21 stdin.setRawMode(true)
22
23 function stop () {
24 if (!options.hide) {
25 stderr.write(
26 ansi.cursorHide +
27 ansi.cursorLeft +
28 options.prompt +
29 input.replace(/./g, '*') +
30 '\n' +
31 ansi.cursorShow)
32 } else {
33 stderr.write('\n')
34 }
35 stdin.removeListener('data', fn)
36 stdin.setRawMode(false)
37 stdin.pause()
38 }
39
40 function enter () {
41 if (input.length === 0) return
42 stop()
43 resolve(input)
44 }
45
46 function ctrlc () {
47 reject(new Error(''))
48 stop()
49 }
50
51 function backspace () {
52 if (input.length === 0) return
53 input = input.substr(0, input.length - 1)
54 stderr.write(ansi.cursorBackward(1))
55 stderr.write(ansi.eraseEndLine)
56 }
57
58 function newchar (c) {
59 input += c
60 stderr.write(options.hide ? '*'.repeat(c.length) : c)
61 }
62
63 let fn = function (c) {
64 switch (c) {
65 case '\u0004': // Ctrl-d
66 case '\r':
67 case '\n':
68 return enter()
69 case '\u0003': // Ctrl-c
70 return ctrlc()
71 default:
72 // backspace
73 if (c.charCodeAt(0) === 127) return backspace()
74 else return newchar(c)
75 }
76 }
77 stdin.on('data', fn)
78 })
79}
80
81function PromptMaskError (message) {
82 Error.call(this)
83 Error.captureStackTrace(this, this.constructor)
84 this.name = this.constructor.name
85 this.message = message
86}
87
88nodeUtil.inherits(PromptMaskError, Error)
89
90exports.PromptMaskError = PromptMaskError
91
92function prompt (name, options) {
93 options = options || {}
94 options.name = name
95 options.prompt = name ? color.dim(`${name}: `) : color.dim('> ')
96 let isTTY = process.env.TERM !== 'dumb' && process.stdin.isTTY
97 let spinnerTask
98 if (options.mask || options.hide) {
99 if (!isTTY) {
100 return Promise.reject(new PromptMaskError(`CLI needs to prompt for ${options.name || options.prompt} but stdin is not a tty.`))
101 }
102
103 spinnerTask = function () {
104 return promptMasked(options)
105 }
106 } else {
107 spinnerTask = function () {
108 return new Promise(function (resolve) {
109 process.stdin.setEncoding('utf8')
110 cli.console.writeError(options.prompt)
111 process.stdin.resume()
112 process.stdin.once('data', function (data) {
113 process.stdin.pause()
114 data = data.trim()
115 if (data === '') {
116 resolve(prompt(name))
117 } else {
118 resolve(data)
119 }
120 })
121 })
122 }
123 }
124 return Spinner.prompt(spinnerTask)
125}
126
127function confirmApp (app, confirm, message) {
128 return new Promise(function (resolve, reject) {
129 if (confirm) {
130 if (confirm === app) return resolve()
131 return reject(new Error(`Confirmation ${cli.color.bold.red(confirm)} did not match ${cli.color.bold.red(app)}. Aborted.`))
132 }
133 if (!message) {
134 message = `WARNING: Destructive Action
135This command will affect the app ${cli.color.bold.red(app)}`
136 }
137 errors.warn(message)
138 errors.warn(`To proceed, type ${cli.color.bold.red(app)} or re-run this command with ${cli.color.bold.red('--confirm', app)}`)
139 console.error()
140 prompt().then(function (confirm) {
141 if (confirm === app) {
142 return resolve()
143 }
144 return reject(new Error(`Confirmation did not match ${cli.color.bold.red(app)}. Aborted.`))
145 })
146 })
147}
148
149exports.prompt = util.promiseOrCallback(prompt)
150exports.confirmApp = util.promiseOrCallback(confirmApp)