1 | 'use strict'
|
2 |
|
3 | const cli = require('../')
|
4 | const errors = require('./errors')
|
5 | const util = require('./util')
|
6 | const color = cli.color
|
7 | const ansi = require('ansi-escapes')
|
8 | const Spinner = require('./spinner')
|
9 | const nodeUtil = require('util')
|
10 |
|
11 | function 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':
|
66 | case '\r':
|
67 | case '\n':
|
68 | return enter()
|
69 | case '\u0003':
|
70 | return ctrlc()
|
71 | default:
|
72 |
|
73 | if (c.charCodeAt(0) === 127) return backspace()
|
74 | else return newchar(c)
|
75 | }
|
76 | }
|
77 | stdin.on('data', fn)
|
78 | })
|
79 | }
|
80 |
|
81 | function PromptMaskError (message) {
|
82 | Error.call(this)
|
83 | Error.captureStackTrace(this, this.constructor)
|
84 | this.name = this.constructor.name
|
85 | this.message = message
|
86 | }
|
87 |
|
88 | nodeUtil.inherits(PromptMaskError, Error)
|
89 |
|
90 | exports.PromptMaskError = PromptMaskError
|
91 |
|
92 | function 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 |
|
127 | function 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
|
135 | This 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 |
|
149 | exports.prompt = util.promiseOrCallback(prompt)
|
150 | exports.confirmApp = util.promiseOrCallback(confirmApp)
|