pear-terminal 2.0.0-rc.2 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +79 -172
- package/package.json +3 -3
package/index.js
CHANGED
|
@@ -3,7 +3,6 @@ const readline = require('bare-readline')
|
|
|
3
3
|
const tty = require('bare-tty')
|
|
4
4
|
const fs = require('bare-fs')
|
|
5
5
|
const os = require('bare-os')
|
|
6
|
-
const Realm = require('bare-realm')
|
|
7
6
|
const { Writable: BareWritable, Readable: BareReadable } = require('bare-stream')
|
|
8
7
|
const { Writable, Readable } = require('streamx')
|
|
9
8
|
const hypercoreid = require('hypercore-id-encoding')
|
|
@@ -150,21 +149,16 @@ const stdio = new (class Stdio {
|
|
|
150
149
|
class Interact {
|
|
151
150
|
static rx =
|
|
152
151
|
/[\x1B\x9B][[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?\x07)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g // eslint-disable-line no-control-regex
|
|
153
|
-
|
|
154
152
|
constructor(header, params, opts = {}) {
|
|
155
153
|
this._header = header
|
|
156
154
|
this._params = params
|
|
157
155
|
this._defaults = opts.defaults || {}
|
|
158
|
-
this._load =
|
|
159
|
-
opts.load ??
|
|
160
|
-
(() => {
|
|
161
|
-
throw new Error('provide a load function to load params strings')
|
|
162
|
-
})
|
|
163
156
|
|
|
164
157
|
const mask = (data, cb) => {
|
|
165
158
|
if (data.length > 4) {
|
|
166
|
-
|
|
167
|
-
const
|
|
159
|
+
// is full line
|
|
160
|
+
const prompt = this._rl._prompt
|
|
161
|
+
const regex = new RegExp(`(${prompt})([\\x20-\\x7E]+)`, 'g') // match printable chars after prompt
|
|
168
162
|
const masked = data
|
|
169
163
|
.toString()
|
|
170
164
|
.replace(regex, (_, prompt, pwd) => prompt + '*'.repeat(pwd.length))
|
|
@@ -180,116 +174,46 @@ class Interact {
|
|
|
180
174
|
output: opts.masked ? new Writable({ write: mask }) : stdio.out
|
|
181
175
|
})
|
|
182
176
|
this._rl.on('close', () => {
|
|
183
|
-
console.log()
|
|
177
|
+
console.log() // newline
|
|
184
178
|
})
|
|
185
179
|
stdio.in?.setMode?.(tty.constants.MODE_RAW)
|
|
186
180
|
}
|
|
187
181
|
|
|
188
|
-
run(opts) {
|
|
189
|
-
const out = new Readable()
|
|
190
|
-
this._run(opts, out)
|
|
191
|
-
return out
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
async _run(opts, out) {
|
|
182
|
+
async run(opts) {
|
|
195
183
|
try {
|
|
196
|
-
|
|
197
|
-
const res = this.#autosubmit()
|
|
198
|
-
out.push({ type: 'autosubmit', value: res })
|
|
199
|
-
out.push(null)
|
|
200
|
-
return
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
stdio.out.write(this._header)
|
|
204
|
-
await this._loop(this._params, out, [], null)
|
|
205
|
-
out.push({ tag: 'final', data: { success: true } })
|
|
206
|
-
out.push(null)
|
|
207
|
-
} catch (err) {
|
|
208
|
-
out.destroy(err)
|
|
184
|
+
return await this.#run(opts)
|
|
209
185
|
} finally {
|
|
210
186
|
if (stdio.inAttached) stdio.in.destroy()
|
|
211
187
|
}
|
|
212
188
|
}
|
|
213
189
|
|
|
214
|
-
async
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
const done = await this._next(param, out, trail, field)
|
|
220
|
-
if (done) break
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
async _next(param, out, trail, field) {
|
|
190
|
+
async #run(opts = {}) {
|
|
191
|
+
if (opts.autosubmit) return this.#autosubmit()
|
|
192
|
+
stdio.out.write(this._header)
|
|
193
|
+
const fields = {}
|
|
194
|
+
const shave = {}
|
|
226
195
|
const defaults = this._defaults
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
param.params = selected.params
|
|
246
|
-
answer = param.params
|
|
247
|
-
tag = 'select'
|
|
248
|
-
} else if (typeof param.params === 'string') {
|
|
249
|
-
answer = param.params
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (typeof param.validation === 'string') {
|
|
253
|
-
const realm = new Realm()
|
|
254
|
-
param.validation = realm.evaluate(param.validation)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (param.validation && !(await param.validation(answer))) {
|
|
258
|
-
stdio.out.write(param.msg + '\n')
|
|
259
|
-
return false
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
if (typeof answer === 'string') answer = answer.replace(this.constructor.rx, '')
|
|
263
|
-
|
|
264
|
-
const base = param.name === field ? trail : trail.concat(param.name)
|
|
265
|
-
const isGroup = typeof param.params === 'string'
|
|
266
|
-
|
|
267
|
-
if (selection) {
|
|
268
|
-
const selected = base.concat(choice)
|
|
269
|
-
out.push({ tag, data: { trail: selected, name: choice, answer } })
|
|
270
|
-
if (isGroup) {
|
|
271
|
-
out.push({ tag: 'enter', data: { trail: selected, name: param.name, answer: answer } })
|
|
272
|
-
param.params = await this._load(param.params)
|
|
273
|
-
await this._loop(param.params, out, selected, choice)
|
|
274
|
-
out.push({ tag: 'exit', data: { trail: selected, name: param.name, answer: answer } })
|
|
196
|
+
while (this._params.length) {
|
|
197
|
+
const param = this._params.shift()
|
|
198
|
+
while (true) {
|
|
199
|
+
const deflt = defaults[param.name] ?? param.default
|
|
200
|
+
let answer = await this.#input(
|
|
201
|
+
`${param.prompt}${param.delim || ':'}${deflt && ' (' + deflt + ')'} `
|
|
202
|
+
)
|
|
203
|
+
if (answer.length === 0) answer = defaults[param.name] ?? deflt
|
|
204
|
+
if (!param.validation || (await param.validation(answer))) {
|
|
205
|
+
if (typeof answer === 'string') answer = answer.replace(this.constructor.rx, '')
|
|
206
|
+
fields[param.name] = answer
|
|
207
|
+
if (Array.isArray(param.shave) && param.shave.every((ix) => typeof ix === 'number')) {
|
|
208
|
+
shave[param.name] = param.shave
|
|
209
|
+
}
|
|
210
|
+
break
|
|
211
|
+
} else {
|
|
212
|
+
stdio.out.write(param.msg + '\n')
|
|
213
|
+
}
|
|
275
214
|
}
|
|
276
|
-
return true
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if (isGroup) {
|
|
280
|
-
out.push({ tag: 'enter', data: { trail: base, name: param.name, answer } })
|
|
281
|
-
param.params = await this._load(param.params)
|
|
282
|
-
await this._loop(param.params, out, base, param.name)
|
|
283
|
-
out.push({ tag: 'exit', data: { trail: base, name: param.name, answer } })
|
|
284
|
-
return true
|
|
285
215
|
}
|
|
286
|
-
|
|
287
|
-
const shave =
|
|
288
|
-
Array.isArray(param.shave) && param.shave.every((ix) => typeof ix === 'number')
|
|
289
|
-
? param.shave
|
|
290
|
-
: undefined
|
|
291
|
-
out.push({ tag, data: { trail: base, name: param.name, answer, shave } })
|
|
292
|
-
return true
|
|
216
|
+
return { fields, shave }
|
|
293
217
|
}
|
|
294
218
|
|
|
295
219
|
#autosubmit() {
|
|
@@ -306,19 +230,13 @@ class Interact {
|
|
|
306
230
|
return { fields, shave }
|
|
307
231
|
}
|
|
308
232
|
|
|
309
|
-
async #select(prompt, select) {
|
|
310
|
-
return (
|
|
311
|
-
(await this.#input(
|
|
312
|
-
prompt + ' [' + select.map(({ prompt }, index) => index + ':' + prompt).join(' ') + ']\n> '
|
|
313
|
-
)) || '0'
|
|
314
|
-
)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
233
|
async #input(prompt) {
|
|
318
234
|
stdio.out.write(prompt)
|
|
319
|
-
this._prompt = prompt
|
|
235
|
+
this._rl._prompt = prompt
|
|
320
236
|
const answer = await new Promise((resolve, reject) => {
|
|
321
|
-
this._rl.once('data', (data) =>
|
|
237
|
+
this._rl.once('data', (data) => {
|
|
238
|
+
resolve(data)
|
|
239
|
+
})
|
|
322
240
|
stdio.in?.once('data', (data) => {
|
|
323
241
|
if (data.length === 1 && data[0] === 3) {
|
|
324
242
|
reject(ERR_SIGINT('^C exit'))
|
|
@@ -326,7 +244,7 @@ class Interact {
|
|
|
326
244
|
}
|
|
327
245
|
})
|
|
328
246
|
})
|
|
329
|
-
return answer.toString().trim()
|
|
247
|
+
return answer.toString().trim() // remove return char
|
|
330
248
|
}
|
|
331
249
|
}
|
|
332
250
|
|
|
@@ -358,7 +276,13 @@ function indicator(value, type = 'success') {
|
|
|
358
276
|
if (type === 'diff') {
|
|
359
277
|
return value === 0 ? ansi.yellow('~') : value === 1 ? ansi.green('+') : ansi.red('-')
|
|
360
278
|
}
|
|
361
|
-
return value < 0
|
|
279
|
+
return value < 0
|
|
280
|
+
? ansi.cross + ' '
|
|
281
|
+
: value === 1
|
|
282
|
+
? ansi.tick + ' '
|
|
283
|
+
: value > 1
|
|
284
|
+
? ''
|
|
285
|
+
: ansi.gray('- ')
|
|
362
286
|
}
|
|
363
287
|
|
|
364
288
|
const outputter =
|
|
@@ -373,11 +297,12 @@ const outputter =
|
|
|
373
297
|
stdio.out.write(ansi.showCursor())
|
|
374
298
|
})
|
|
375
299
|
: null
|
|
376
|
-
if (typeof opts === 'boolean') opts = { json: opts }
|
|
300
|
+
if (typeof opts === 'boolean' || typeof opts === 'function') opts = { json: opts }
|
|
377
301
|
const { json = false, log } = opts
|
|
378
302
|
const promise = opwait(stream, ({ tag, data }) => {
|
|
379
303
|
if (json) {
|
|
380
|
-
const
|
|
304
|
+
const replacer = typeof json === 'function' ? json : null
|
|
305
|
+
const str = JSON.stringify({ cmd, tag, data }, replacer)
|
|
381
306
|
if (log) log(str)
|
|
382
307
|
else print(str)
|
|
383
308
|
return
|
|
@@ -408,7 +333,7 @@ const outputter =
|
|
|
408
333
|
}
|
|
409
334
|
let msg = Array.isArray(message) ? message.join('\n') : message
|
|
410
335
|
if (tag === 'final') {
|
|
411
|
-
msg += '\n'
|
|
336
|
+
if (!result.nonl) msg += '\n'
|
|
412
337
|
if (asTTY) {
|
|
413
338
|
stdio.out.write(ansi.showCursor())
|
|
414
339
|
dereg(false)
|
|
@@ -484,7 +409,7 @@ async function trust(ipc, key, cmd) {
|
|
|
484
409
|
}
|
|
485
410
|
])
|
|
486
411
|
|
|
487
|
-
await
|
|
412
|
+
await interact.run()
|
|
488
413
|
await ipc.permit({ key })
|
|
489
414
|
print('\n' + ansi.tick + ' pear://' + z32 + ' is now trusted\n')
|
|
490
415
|
print(act[cmd] + '\n')
|
|
@@ -492,7 +417,7 @@ async function trust(ipc, key, cmd) {
|
|
|
492
417
|
Bare.exit()
|
|
493
418
|
}
|
|
494
419
|
|
|
495
|
-
async function
|
|
420
|
+
async function passperm(ipc, key, cmd) {
|
|
496
421
|
const z32 = hypercoreid.normalize(key)
|
|
497
422
|
|
|
498
423
|
const explain = {
|
|
@@ -526,7 +451,7 @@ async function password(ipc, key, cmd) {
|
|
|
526
451
|
dialog,
|
|
527
452
|
[
|
|
528
453
|
{
|
|
529
|
-
name: '
|
|
454
|
+
name: 'value',
|
|
530
455
|
default: '',
|
|
531
456
|
prompt: ask,
|
|
532
457
|
delim,
|
|
@@ -536,12 +461,9 @@ async function password(ipc, key, cmd) {
|
|
|
536
461
|
],
|
|
537
462
|
{ masked: true }
|
|
538
463
|
)
|
|
539
|
-
|
|
540
|
-
await opwait(interact.run(), ({ tag, data }) => {
|
|
541
|
-
if (tag === 'input' && data.name === 'password') password = data.answer
|
|
542
|
-
})
|
|
464
|
+
const { fields } = await interact.run()
|
|
543
465
|
print(`\n${ansi.key} Hashing password...`)
|
|
544
|
-
await ipc.permit({ key, password })
|
|
466
|
+
await ipc.permit({ key, password: fields.value })
|
|
545
467
|
print('\n' + ansi.tick + ' ' + message[cmd] + '\n')
|
|
546
468
|
await ipc.close()
|
|
547
469
|
Bare.exit()
|
|
@@ -550,7 +472,7 @@ async function password(ipc, key, cmd) {
|
|
|
550
472
|
function permit(ipc, info, cmd) {
|
|
551
473
|
const key = info.key
|
|
552
474
|
if (info.encrypted) {
|
|
553
|
-
return
|
|
475
|
+
return passperm(ipc, key, cmd)
|
|
554
476
|
} else {
|
|
555
477
|
return trust(ipc, key, cmd)
|
|
556
478
|
}
|
|
@@ -559,7 +481,7 @@ function permit(ipc, info, cmd) {
|
|
|
559
481
|
async function confirm(dialog, ask, delim, validation, msg) {
|
|
560
482
|
const interact = new Interact(dialog, [
|
|
561
483
|
{
|
|
562
|
-
name: '
|
|
484
|
+
name: 'value',
|
|
563
485
|
default: '',
|
|
564
486
|
prompt: ask,
|
|
565
487
|
delim,
|
|
@@ -567,55 +489,40 @@ async function confirm(dialog, ask, delim, validation, msg) {
|
|
|
567
489
|
msg
|
|
568
490
|
}
|
|
569
491
|
])
|
|
570
|
-
await
|
|
492
|
+
await interact.run()
|
|
571
493
|
}
|
|
572
494
|
|
|
573
|
-
function explain(bail = {}) {
|
|
574
|
-
if (!bail.reason && bail.err) {
|
|
575
|
-
const known = errors.known()
|
|
576
|
-
if (known.includes(bail.err.code) === false) {
|
|
577
|
-
print(
|
|
578
|
-
errors.ERR_UNKNOWN(
|
|
579
|
-
'Unknown [ code: ' + (bail.err.code || '(none)') + ' ] ' + bail.err.stack
|
|
580
|
-
),
|
|
581
|
-
false
|
|
582
|
-
)
|
|
583
|
-
Bare.exit(1)
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
const messageUsage = (bail) => bail.err.message
|
|
587
|
-
const messageOnly = (bail) => bail.err.message
|
|
588
|
-
const opFail = (cmd) => cmd.err.info.message
|
|
589
|
-
const codemap = new Map([
|
|
590
|
-
['UNKNOWN_FLAG', (bail) => 'Unrecognized Flag: --' + bail.flag.name],
|
|
591
|
-
[
|
|
592
|
-
'UNKNOWN_ARG',
|
|
593
|
-
(bail) => 'Unrecognized Argument at index ' + bail.arg.index + ' with value ' + bail.arg.value
|
|
594
|
-
],
|
|
595
|
-
['MISSING_ARG', (bail) => bail.arg.value],
|
|
596
|
-
['INVALID', messageUsage],
|
|
597
|
-
['ERR_INVALID_INPUT', messageUsage],
|
|
598
|
-
['ERR_LEGACY', messageOnly],
|
|
599
|
-
['ERR_INVALID_TEMPLATE', messageOnly],
|
|
600
|
-
['ERR_DIR_NONEMPTY', messageOnly],
|
|
601
|
-
['ERR_OPERATION_FAILED', opFail]
|
|
602
|
-
])
|
|
603
|
-
const nouse = [messageOnly, opFail]
|
|
604
|
-
const code = codemap.has(bail.err?.code) ? bail.err.code : bail.reason
|
|
605
|
-
const ref = codemap.get(code)
|
|
606
|
-
const reason = codemap.has(code) ? (codemap.get(code)(bail) ?? bail.reason) : bail.reason
|
|
607
|
-
Bare.exitCode = 1
|
|
608
|
-
|
|
609
|
-
print(reason, false)
|
|
610
495
|
|
|
611
|
-
if (nouse.some((fn) => fn === ref) || codemap.has(code) === false) return
|
|
612
496
|
|
|
613
|
-
|
|
497
|
+
function password(prompt = 'Password: ') {
|
|
498
|
+
return new Promise((resolve) => {
|
|
499
|
+
const stdin = new tty.ReadStream(0)
|
|
500
|
+
const stdout = new tty.WriteStream(1)
|
|
501
|
+
let str = ''
|
|
502
|
+
|
|
503
|
+
stdin.setRawMode(true)
|
|
504
|
+
stdout.write(prompt)
|
|
505
|
+
|
|
506
|
+
stdin.on('data', (chunk) => {
|
|
507
|
+
const c = chunk[0]
|
|
508
|
+
if (c === 3) {
|
|
509
|
+
stdout.write('^C\n')
|
|
510
|
+
Bare.exit(130)
|
|
511
|
+
} else if (c === 13 || c === 10) {
|
|
512
|
+
stdin.setRawMode(false)
|
|
513
|
+
stdin.destroy()
|
|
514
|
+
stdout.write('\n')
|
|
515
|
+
return resolve(str)
|
|
516
|
+
} else if (c === 127 || c < 32) return
|
|
517
|
+
str += String.fromCharCode(c)
|
|
518
|
+
stdout.write('*')
|
|
519
|
+
})
|
|
520
|
+
})
|
|
614
521
|
}
|
|
615
522
|
|
|
616
523
|
module.exports = {
|
|
617
|
-
explain,
|
|
618
524
|
usage,
|
|
525
|
+
password,
|
|
619
526
|
permit,
|
|
620
527
|
stdio,
|
|
621
528
|
ansi,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pear-terminal",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"description": "Pear Terminal User Interface library",
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"format": "prettier --write .",
|
|
17
17
|
"lint": "prettier --check . && lunte",
|
|
18
18
|
"test:gen": "brittle -r test/all.js test/*.test.js",
|
|
19
|
-
"test": "brittle-bare test/all.js"
|
|
19
|
+
"test": "brittle-bare test/all.js",
|
|
20
|
+
"test:bare": "brittle-bare test/all.js"
|
|
20
21
|
},
|
|
21
22
|
"devDependencies": {
|
|
22
23
|
"bare-module": "^5.0.2",
|
|
@@ -32,7 +33,6 @@
|
|
|
32
33
|
"bare-events": "^2.6.1",
|
|
33
34
|
"bare-fs": "^4.2.0",
|
|
34
35
|
"bare-readline": "^1.0.9",
|
|
35
|
-
"bare-realm": "^2.0.0",
|
|
36
36
|
"bare-stream": "^2.7.0",
|
|
37
37
|
"bare-tty": "^5.0.2",
|
|
38
38
|
"hypercore-id-encoding": "^1.3.0",
|