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.
Files changed (2) hide show
  1. package/index.js +79 -172
  2. 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
- const prompt = this._prompt
167
- const regex = new RegExp(`(${prompt})([\\x20-\\x7E]+)`, 'g')
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
- if (opts?.autosubmit) {
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 _loop(params, out, trail, field) {
215
- params = Array.isArray(params) ? params : [params]
216
- while (params.length) {
217
- const param = params.shift()
218
- while (true) {
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
- const deflt = defaults[param.name] ?? param.default
228
- const selection = Array.isArray(param.select)
229
-
230
- let answer = selection
231
- ? await this.#select(param.prompt, param.select)
232
- : typeof param.params === 'string'
233
- ? ''
234
- : await this.#input(`${param.prompt}${param.delim || ':'}${deflt && ' (' + deflt + ')'} `)
235
-
236
- if (answer.length === 0) answer = defaults[param.name] ?? deflt
237
-
238
- let choice = null
239
- let tag = 'input'
240
-
241
- if (selection) {
242
- const ix = Number(answer) || 0
243
- const selected = param.select[ix] ?? param.select[0]
244
- choice = selected.prompt ?? selected.name ?? String(ix)
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) => resolve(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 ? ansi.cross + ' ' : value > 0 ? ansi.tick + ' ' : ansi.gray('- ')
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 str = JSON.stringify({ cmd, tag, data })
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 opwait(interact.run())
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 password(ipc, key, cmd) {
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: 'password',
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
- let password = null
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 password(ipc, key, cmd)
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: 'confirm',
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 opwait(interact.run())
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
- print('\n' + bail.command.usage())
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.0-rc.2",
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",