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.
Files changed (2) hide show
  1. package/index.js +79 -173
  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,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
- 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
- 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) => resolve(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 ? 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('- ')
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 str = JSON.stringify({ cmd, tag, data })
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 opwait(interact.run())
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 password(ipc, key, cmd) {
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: 'password',
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
- let password = null
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 password(ipc, key, cmd)
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: 'confirm',
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 opwait(interact.run())
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
- 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
+ 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-rc.1",
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",