pear-terminal 2.0.0-rc.1 → 2.0.0
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 -173
- 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,117 +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
|
-
realm.destroy()
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (param.validation && !(await param.validation(answer))) {
|
|
259
|
-
stdio.out.write(param.msg + '\n')
|
|
260
|
-
return false
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (typeof answer === 'string') answer = answer.replace(this.constructor.rx, '')
|
|
264
|
-
|
|
265
|
-
const base = param.name === field ? trail : trail.concat(param.name)
|
|
266
|
-
const isGroup = typeof param.params === 'string'
|
|
267
|
-
|
|
268
|
-
if (selection) {
|
|
269
|
-
const selected = base.concat(choice)
|
|
270
|
-
out.push({ tag, data: { trail: selected, name: choice, answer } })
|
|
271
|
-
if (isGroup) {
|
|
272
|
-
out.push({ tag: 'enter', data: { trail: selected, name: param.name, answer: answer } })
|
|
273
|
-
param.params = await this._load(param.params)
|
|
274
|
-
await this._loop(param.params, out, selected, choice)
|
|
275
|
-
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
|
+
}
|
|
276
214
|
}
|
|
277
|
-
return true
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (isGroup) {
|
|
281
|
-
out.push({ tag: 'enter', data: { trail: base, name: param.name, answer } })
|
|
282
|
-
param.params = await this._load(param.params)
|
|
283
|
-
await this._loop(param.params, out, base, param.name)
|
|
284
|
-
out.push({ tag: 'exit', data: { trail: base, name: param.name, answer } })
|
|
285
|
-
return true
|
|
286
215
|
}
|
|
287
|
-
|
|
288
|
-
const shave =
|
|
289
|
-
Array.isArray(param.shave) && param.shave.every((ix) => typeof ix === 'number')
|
|
290
|
-
? param.shave
|
|
291
|
-
: undefined
|
|
292
|
-
out.push({ tag, data: { trail: base, name: param.name, answer, shave } })
|
|
293
|
-
return true
|
|
216
|
+
return { fields, shave }
|
|
294
217
|
}
|
|
295
218
|
|
|
296
219
|
#autosubmit() {
|
|
@@ -307,19 +230,13 @@ class Interact {
|
|
|
307
230
|
return { fields, shave }
|
|
308
231
|
}
|
|
309
232
|
|
|
310
|
-
async #select(prompt, select) {
|
|
311
|
-
return (
|
|
312
|
-
(await this.#input(
|
|
313
|
-
prompt + ' [' + select.map(({ prompt }, index) => index + ':' + prompt).join(' ') + ']\n> '
|
|
314
|
-
)) || '0'
|
|
315
|
-
)
|
|
316
|
-
}
|
|
317
|
-
|
|
318
233
|
async #input(prompt) {
|
|
319
234
|
stdio.out.write(prompt)
|
|
320
|
-
this._prompt = prompt
|
|
235
|
+
this._rl._prompt = prompt
|
|
321
236
|
const answer = await new Promise((resolve, reject) => {
|
|
322
|
-
this._rl.once('data', (data) =>
|
|
237
|
+
this._rl.once('data', (data) => {
|
|
238
|
+
resolve(data)
|
|
239
|
+
})
|
|
323
240
|
stdio.in?.once('data', (data) => {
|
|
324
241
|
if (data.length === 1 && data[0] === 3) {
|
|
325
242
|
reject(ERR_SIGINT('^C exit'))
|
|
@@ -327,7 +244,7 @@ class Interact {
|
|
|
327
244
|
}
|
|
328
245
|
})
|
|
329
246
|
})
|
|
330
|
-
return answer.toString().trim()
|
|
247
|
+
return answer.toString().trim() // remove return char
|
|
331
248
|
}
|
|
332
249
|
}
|
|
333
250
|
|
|
@@ -359,7 +276,13 @@ function indicator(value, type = 'success') {
|
|
|
359
276
|
if (type === 'diff') {
|
|
360
277
|
return value === 0 ? ansi.yellow('~') : value === 1 ? ansi.green('+') : ansi.red('-')
|
|
361
278
|
}
|
|
362
|
-
return value < 0
|
|
279
|
+
return value < 0
|
|
280
|
+
? ansi.cross + ' '
|
|
281
|
+
: value === 1
|
|
282
|
+
? ansi.tick + ' '
|
|
283
|
+
: value > 1
|
|
284
|
+
? ''
|
|
285
|
+
: ansi.gray('- ')
|
|
363
286
|
}
|
|
364
287
|
|
|
365
288
|
const outputter =
|
|
@@ -374,11 +297,12 @@ const outputter =
|
|
|
374
297
|
stdio.out.write(ansi.showCursor())
|
|
375
298
|
})
|
|
376
299
|
: null
|
|
377
|
-
if (typeof opts === 'boolean') opts = { json: opts }
|
|
300
|
+
if (typeof opts === 'boolean' || typeof opts === 'function') opts = { json: opts }
|
|
378
301
|
const { json = false, log } = opts
|
|
379
302
|
const promise = opwait(stream, ({ tag, data }) => {
|
|
380
303
|
if (json) {
|
|
381
|
-
const
|
|
304
|
+
const replacer = typeof json === 'function' ? json : null
|
|
305
|
+
const str = JSON.stringify({ cmd, tag, data }, replacer)
|
|
382
306
|
if (log) log(str)
|
|
383
307
|
else print(str)
|
|
384
308
|
return
|
|
@@ -409,7 +333,7 @@ const outputter =
|
|
|
409
333
|
}
|
|
410
334
|
let msg = Array.isArray(message) ? message.join('\n') : message
|
|
411
335
|
if (tag === 'final') {
|
|
412
|
-
msg += '\n'
|
|
336
|
+
if (!result.nonl) msg += '\n'
|
|
413
337
|
if (asTTY) {
|
|
414
338
|
stdio.out.write(ansi.showCursor())
|
|
415
339
|
dereg(false)
|
|
@@ -485,7 +409,7 @@ async function trust(ipc, key, cmd) {
|
|
|
485
409
|
}
|
|
486
410
|
])
|
|
487
411
|
|
|
488
|
-
await
|
|
412
|
+
await interact.run()
|
|
489
413
|
await ipc.permit({ key })
|
|
490
414
|
print('\n' + ansi.tick + ' pear://' + z32 + ' is now trusted\n')
|
|
491
415
|
print(act[cmd] + '\n')
|
|
@@ -493,7 +417,7 @@ async function trust(ipc, key, cmd) {
|
|
|
493
417
|
Bare.exit()
|
|
494
418
|
}
|
|
495
419
|
|
|
496
|
-
async function
|
|
420
|
+
async function passperm(ipc, key, cmd) {
|
|
497
421
|
const z32 = hypercoreid.normalize(key)
|
|
498
422
|
|
|
499
423
|
const explain = {
|
|
@@ -527,7 +451,7 @@ async function password(ipc, key, cmd) {
|
|
|
527
451
|
dialog,
|
|
528
452
|
[
|
|
529
453
|
{
|
|
530
|
-
name: '
|
|
454
|
+
name: 'value',
|
|
531
455
|
default: '',
|
|
532
456
|
prompt: ask,
|
|
533
457
|
delim,
|
|
@@ -537,12 +461,9 @@ async function password(ipc, key, cmd) {
|
|
|
537
461
|
],
|
|
538
462
|
{ masked: true }
|
|
539
463
|
)
|
|
540
|
-
|
|
541
|
-
await opwait(interact.run(), ({ tag, data }) => {
|
|
542
|
-
if (tag === 'input' && data.name === 'password') password = data.answer
|
|
543
|
-
})
|
|
464
|
+
const { fields } = await interact.run()
|
|
544
465
|
print(`\n${ansi.key} Hashing password...`)
|
|
545
|
-
await ipc.permit({ key, password })
|
|
466
|
+
await ipc.permit({ key, password: fields.value })
|
|
546
467
|
print('\n' + ansi.tick + ' ' + message[cmd] + '\n')
|
|
547
468
|
await ipc.close()
|
|
548
469
|
Bare.exit()
|
|
@@ -551,7 +472,7 @@ async function password(ipc, key, cmd) {
|
|
|
551
472
|
function permit(ipc, info, cmd) {
|
|
552
473
|
const key = info.key
|
|
553
474
|
if (info.encrypted) {
|
|
554
|
-
return
|
|
475
|
+
return passperm(ipc, key, cmd)
|
|
555
476
|
} else {
|
|
556
477
|
return trust(ipc, key, cmd)
|
|
557
478
|
}
|
|
@@ -560,7 +481,7 @@ function permit(ipc, info, cmd) {
|
|
|
560
481
|
async function confirm(dialog, ask, delim, validation, msg) {
|
|
561
482
|
const interact = new Interact(dialog, [
|
|
562
483
|
{
|
|
563
|
-
name: '
|
|
484
|
+
name: 'value',
|
|
564
485
|
default: '',
|
|
565
486
|
prompt: ask,
|
|
566
487
|
delim,
|
|
@@ -568,55 +489,40 @@ async function confirm(dialog, ask, delim, validation, msg) {
|
|
|
568
489
|
msg
|
|
569
490
|
}
|
|
570
491
|
])
|
|
571
|
-
await
|
|
492
|
+
await interact.run()
|
|
572
493
|
}
|
|
573
494
|
|
|
574
|
-
function explain(bail = {}) {
|
|
575
|
-
if (!bail.reason && bail.err) {
|
|
576
|
-
const known = errors.known()
|
|
577
|
-
if (known.includes(bail.err.code) === false) {
|
|
578
|
-
print(
|
|
579
|
-
errors.ERR_UNKNOWN(
|
|
580
|
-
'Unknown [ code: ' + (bail.err.code || '(none)') + ' ] ' + bail.err.stack
|
|
581
|
-
),
|
|
582
|
-
false
|
|
583
|
-
)
|
|
584
|
-
Bare.exit(1)
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
const messageUsage = (bail) => bail.err.message
|
|
588
|
-
const messageOnly = (bail) => bail.err.message
|
|
589
|
-
const opFail = (cmd) => cmd.err.info.message
|
|
590
|
-
const codemap = new Map([
|
|
591
|
-
['UNKNOWN_FLAG', (bail) => 'Unrecognized Flag: --' + bail.flag.name],
|
|
592
|
-
[
|
|
593
|
-
'UNKNOWN_ARG',
|
|
594
|
-
(bail) => 'Unrecognized Argument at index ' + bail.arg.index + ' with value ' + bail.arg.value
|
|
595
|
-
],
|
|
596
|
-
['MISSING_ARG', (bail) => bail.arg.value],
|
|
597
|
-
['INVALID', messageUsage],
|
|
598
|
-
['ERR_INVALID_INPUT', messageUsage],
|
|
599
|
-
['ERR_LEGACY', messageOnly],
|
|
600
|
-
['ERR_INVALID_TEMPLATE', messageOnly],
|
|
601
|
-
['ERR_DIR_NONEMPTY', messageOnly],
|
|
602
|
-
['ERR_OPERATION_FAILED', opFail]
|
|
603
|
-
])
|
|
604
|
-
const nouse = [messageOnly, opFail]
|
|
605
|
-
const code = codemap.has(bail.err?.code) ? bail.err.code : bail.reason
|
|
606
|
-
const ref = codemap.get(code)
|
|
607
|
-
const reason = codemap.has(code) ? (codemap.get(code)(bail) ?? bail.reason) : bail.reason
|
|
608
|
-
Bare.exitCode = 1
|
|
609
|
-
|
|
610
|
-
print(reason, false)
|
|
611
495
|
|
|
612
|
-
if (nouse.some((fn) => fn === ref) || codemap.has(code) === false) return
|
|
613
496
|
|
|
614
|
-
|
|
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
|
+
process.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
|
+
})
|
|
615
521
|
}
|
|
616
522
|
|
|
617
523
|
module.exports = {
|
|
618
|
-
explain,
|
|
619
524
|
usage,
|
|
525
|
+
password,
|
|
620
526
|
permit,
|
|
621
527
|
stdio,
|
|
622
528
|
ansi,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pear-terminal",
|
|
3
|
-
"version": "2.0.0
|
|
3
|
+
"version": "2.0.0",
|
|
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",
|