pear-terminal 1.0.0 → 1.1.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/README.md +50 -42
- package/index.js +260 -126
- package/package.json +17 -5
package/README.md
CHANGED
|
@@ -16,10 +16,9 @@ Ask user to trust or unlock an app/template.
|
|
|
16
16
|
|
|
17
17
|
Returns `Promise<void>`.
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
- `ipc`: `{ permit({ key, password? }), close() }`
|
|
20
|
+
- `info`: `{ key: Buffer|string, encrypted: boolean }`
|
|
21
|
+
- `cmd`: `'run'|'init'|'stage'|'seed'|'dump'|'info'`
|
|
23
22
|
|
|
24
23
|
### `confirm(dialog, ask, delim, validation, msg)`
|
|
25
24
|
|
|
@@ -27,20 +26,20 @@ One-shot confirmation prompt.
|
|
|
27
26
|
|
|
28
27
|
Returns `Promise<void>`.
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
- `dialog`: `string` preface text
|
|
30
|
+
- `ask`: `string` prompt label
|
|
31
|
+
- `delim`: `string` delimiter (e.g. `':'` or '?'`)
|
|
32
|
+
- `validation`: `(value:string) => boolean|Promise<boolean>`
|
|
33
|
+
- `msg`: `string` error message on invalid input
|
|
35
34
|
|
|
36
35
|
### `const interact = new Interact(header, params[, opts])`
|
|
37
36
|
|
|
38
37
|
Interactive prompt runner.
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
- `header`: `string` shown once before prompts
|
|
40
|
+
- `params`: `Array<{ name, prompt, default?, delim?, validation?, msg?, shave? }>`
|
|
41
|
+
- `opts.masked`: `boolean` mask user input (passwords)
|
|
42
|
+
- `opts.defaults`: `{ [name:string]: any }` fallback values
|
|
44
43
|
|
|
45
44
|
#### `interact.run([opts])`
|
|
46
45
|
|
|
@@ -48,7 +47,7 @@ Process prompts and return answers.
|
|
|
48
47
|
|
|
49
48
|
Returns `Promise<{ fields, shave }>`.
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
- `opts.autosubmit`: `boolean` fill with defaults without prompting
|
|
52
51
|
|
|
53
52
|
### `stdio`
|
|
54
53
|
|
|
@@ -56,11 +55,11 @@ Thin stdio wrapper with Bare/TTY streams.
|
|
|
56
55
|
|
|
57
56
|
Returns object with:
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
- `in`, `out`, `err`: lazy streams
|
|
59
|
+
- `size() -> { width, height }`
|
|
60
|
+
- `raw(bool) -> void` set raw mode
|
|
61
|
+
- `drained(stream) -> Promise<void>`
|
|
62
|
+
- `inAttached`: `boolean`
|
|
64
63
|
|
|
65
64
|
### `ansi`
|
|
66
65
|
|
|
@@ -68,10 +67,10 @@ ANSI styling helpers (no-op on Windows).
|
|
|
68
67
|
|
|
69
68
|
Returns object with:
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
70
|
+
- text: `bold, dim, italic, underline, inverse, red, green, yellow, gray`
|
|
71
|
+
- cursor: `upHome(n)`, `hideCursor()`, `showCursor()`
|
|
72
|
+
- links: `link(url, text?)`
|
|
73
|
+
- glyphs: `sep, tick, cross, warning, pear, dot, key, down, up`
|
|
75
74
|
|
|
76
75
|
### `indicator(value[, type])`
|
|
77
76
|
|
|
@@ -79,8 +78,8 @@ Status glyph helper.
|
|
|
79
78
|
|
|
80
79
|
Returns `string`.
|
|
81
80
|
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
- `value`: `true|false|null|number` (`>0` success, `<0` fail, `0|null` neutral)
|
|
82
|
+
- `type`: `'success'|'diff'` (`diff`: `+ | - | ~`)
|
|
84
83
|
|
|
85
84
|
### `status(message[, success])`
|
|
86
85
|
|
|
@@ -88,8 +87,8 @@ Live status line (TTY-aware).
|
|
|
88
87
|
|
|
89
88
|
Returns `void`.
|
|
90
89
|
|
|
91
|
-
|
|
92
|
-
|
|
90
|
+
- `message`: `string`
|
|
91
|
+
- `success`: `boolean|null|number` (see `indicator`)
|
|
93
92
|
|
|
94
93
|
### `print(message[, success])`
|
|
95
94
|
|
|
@@ -97,8 +96,8 @@ Plain line print with optional status glyph.
|
|
|
97
96
|
|
|
98
97
|
Returns `void`.
|
|
99
98
|
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
- `message`: `string`
|
|
100
|
+
- `success`: `boolean|null|number`
|
|
102
101
|
|
|
103
102
|
### `byteDiff({ type, sizes, message })`
|
|
104
103
|
|
|
@@ -106,9 +105,9 @@ Pretty-print byte deltas.
|
|
|
106
105
|
|
|
107
106
|
Returns `void`.
|
|
108
107
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
- `type`: any value passed to `indicator(..., 'diff')`
|
|
109
|
+
- `sizes`: `number[]` byte changes (signed)
|
|
110
|
+
- `message`: `string`
|
|
112
111
|
|
|
113
112
|
### `outputter(cmd[, taggers])`
|
|
114
113
|
|
|
@@ -116,23 +115,32 @@ Create a stream consumer that routes tagged events to `print/status` (TTY) or JS
|
|
|
116
115
|
|
|
117
116
|
Returns `(opts, stream, info?, ipc?) -> Promise<void>`.
|
|
118
117
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
118
|
+
- `cmd`: `string` command name
|
|
119
|
+
- `taggers`: `{ [tag]: (data, info, ipc) => string|{ output, message, success }|false|Promise<...> }`
|
|
120
|
+
- `output`: `'print'|'status'`
|
|
121
|
+
- `message`: `string|string[]`
|
|
122
|
+
- `success`: `boolean`
|
|
123
|
+
- `opts`: `{ json?: boolean, log?: (msg, { output, success? }) => void, ctrlTTY?: boolean }`
|
|
124
|
+
- `stream`: `Readable|Array` of `{ tag, data }` events
|
|
125
|
+
- `info`: any extra context
|
|
126
|
+
- `ipc`: optional IPC handle
|
|
128
127
|
|
|
129
128
|
Behavior:
|
|
129
|
+
|
|
130
130
|
- `opts.json === true` → emits JSON lines: `{ cmd, tag, data }`
|
|
131
131
|
- `tagger` result `false`:
|
|
132
132
|
- for `tag==='final'` prints default success/failure
|
|
133
133
|
- otherwise suppressed
|
|
134
134
|
- TTY: hides/shows cursor, handles Ctrl+C cleanup
|
|
135
135
|
|
|
136
|
+
### `explain(bail)`
|
|
137
|
+
|
|
138
|
+
Processes failure mode flow for various Pear scenarios that prints sensible output showing stacks for operational errors and beautified output user errors.
|
|
139
|
+
|
|
140
|
+
Returns `void`.
|
|
141
|
+
|
|
142
|
+
- `bail`: a [paparam](https://github.com/holepunchto/paparam) bail object
|
|
143
|
+
|
|
136
144
|
### `isTTY`
|
|
137
145
|
|
|
138
146
|
`boolean` indicating stdin TTY status.
|
|
@@ -145,4 +153,4 @@ Returns `string`.
|
|
|
145
153
|
|
|
146
154
|
## License
|
|
147
155
|
|
|
148
|
-
Apache-2.0
|
|
156
|
+
Apache-2.0
|
package/index.js
CHANGED
|
@@ -1,23 +1,42 @@
|
|
|
1
1
|
'use strict'
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
const readline = require('bare-readline')
|
|
4
4
|
const tty = require('bare-tty')
|
|
5
5
|
const fs = require('bare-fs')
|
|
6
|
-
const
|
|
6
|
+
const os = require('bare-os')
|
|
7
7
|
const { Writable: BareWritable, Readable: BareReadable } = require('bare-stream')
|
|
8
|
-
const {
|
|
8
|
+
const { Writable, Readable } = require('streamx')
|
|
9
9
|
const hypercoreid = require('hypercore-id-encoding')
|
|
10
10
|
const byteSize = require('tiny-byte-size')
|
|
11
11
|
const { isWindows } = require('which-runtime')
|
|
12
12
|
const { CHECKOUT } = require('pear-constants')
|
|
13
13
|
const gracedown = require('pear-gracedown')
|
|
14
|
+
const errors = require('pear-errors')
|
|
14
15
|
const opwait = require('pear-opwait')
|
|
15
16
|
const isTTY = tty.isTTY(0)
|
|
16
17
|
|
|
18
|
+
function ERR_SIGINT(msg) {
|
|
19
|
+
return new errors(msg, ERR_SIGINT)
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
const pt = (arg) => arg
|
|
18
23
|
const es = () => ''
|
|
19
24
|
const ansi = isWindows
|
|
20
|
-
? {
|
|
25
|
+
? {
|
|
26
|
+
bold: pt,
|
|
27
|
+
dim: pt,
|
|
28
|
+
italic: pt,
|
|
29
|
+
underline: pt,
|
|
30
|
+
inverse: pt,
|
|
31
|
+
red: pt,
|
|
32
|
+
green: pt,
|
|
33
|
+
yellow: pt,
|
|
34
|
+
gray: pt,
|
|
35
|
+
upHome: es,
|
|
36
|
+
link: pt,
|
|
37
|
+
hideCursor: es,
|
|
38
|
+
showCursor: es
|
|
39
|
+
}
|
|
21
40
|
: {
|
|
22
41
|
bold: (s) => `\x1B[1m${s}\x1B[22m`,
|
|
23
42
|
dim: (s) => `\x1B[2m${s}\x1B[22m`,
|
|
@@ -39,30 +58,36 @@ ansi.tick = isWindows ? '^' : ansi.green('✔')
|
|
|
39
58
|
ansi.cross = isWindows ? 'x' : ansi.red('✖')
|
|
40
59
|
ansi.warning = isWindows ? '!' : '⚠️'
|
|
41
60
|
ansi.pear = isWindows ? '*' : '🍐'
|
|
42
|
-
ansi.dot = isWindows ? '
|
|
61
|
+
ansi.dot = isWindows ? 'o' : '•'
|
|
43
62
|
ansi.key = isWindows ? '>' : '🔑'
|
|
44
63
|
ansi.down = isWindows ? '↓' : '⬇'
|
|
45
64
|
ansi.up = isWindows ? '↑' : '⬆'
|
|
46
65
|
|
|
47
|
-
const stdio = new class Stdio {
|
|
66
|
+
const stdio = new (class Stdio {
|
|
48
67
|
static WriteStream = class FdWriteStream extends BareWritable {
|
|
49
|
-
constructor
|
|
50
|
-
super({
|
|
68
|
+
constructor(fd) {
|
|
69
|
+
super({
|
|
70
|
+
map: (data) => (typeof data === 'string' ? Buffer.from(data) : data)
|
|
71
|
+
})
|
|
51
72
|
this.fd = fd
|
|
52
73
|
}
|
|
53
74
|
|
|
54
|
-
_writev
|
|
55
|
-
fs.writev(
|
|
75
|
+
_writev(batch, cb) {
|
|
76
|
+
fs.writev(
|
|
77
|
+
this.fd,
|
|
78
|
+
batch.map(({ chunk }) => chunk),
|
|
79
|
+
cb
|
|
80
|
+
)
|
|
56
81
|
}
|
|
57
82
|
}
|
|
58
83
|
|
|
59
84
|
static ReadStream = class FdReadStream extends BareReadable {
|
|
60
|
-
constructor
|
|
85
|
+
constructor(fd) {
|
|
61
86
|
super()
|
|
62
87
|
this.fd = fd
|
|
63
88
|
}
|
|
64
89
|
|
|
65
|
-
_read
|
|
90
|
+
_read(size) {
|
|
66
91
|
const buffer = Buffer.alloc(size)
|
|
67
92
|
fs.read(this.fd, buffer, 0, size, null, (err, bytesRead) => {
|
|
68
93
|
if (err) return this.destroy(err)
|
|
@@ -73,57 +98,71 @@ const stdio = new class Stdio {
|
|
|
73
98
|
}
|
|
74
99
|
|
|
75
100
|
drained = Writable.drained
|
|
76
|
-
constructor
|
|
101
|
+
constructor() {
|
|
77
102
|
this._in = null
|
|
78
103
|
this._out = null
|
|
79
104
|
this._err = null
|
|
80
105
|
this.rawMode = false
|
|
81
106
|
}
|
|
82
107
|
|
|
83
|
-
get inAttached
|
|
108
|
+
get inAttached() {
|
|
109
|
+
return this._in !== null
|
|
110
|
+
}
|
|
84
111
|
|
|
85
|
-
get in
|
|
112
|
+
get in() {
|
|
86
113
|
if (this._in === null) {
|
|
87
114
|
this._in = tty.isTTY(0) ? new tty.ReadStream(0) : new this.constructor.ReadStream(0)
|
|
88
|
-
this._in.once('close', () => {
|
|
115
|
+
this._in.once('close', () => {
|
|
116
|
+
this._in = null
|
|
117
|
+
})
|
|
89
118
|
}
|
|
90
119
|
return this._in
|
|
91
120
|
}
|
|
92
121
|
|
|
93
|
-
get out
|
|
94
|
-
if (this._out === null)
|
|
122
|
+
get out() {
|
|
123
|
+
if (this._out === null) {
|
|
124
|
+
this._out = tty.isTTY(1) ? new tty.WriteStream(1) : new this.constructor.WriteStream(1)
|
|
125
|
+
}
|
|
95
126
|
return this._out
|
|
96
127
|
}
|
|
97
128
|
|
|
98
|
-
get err
|
|
99
|
-
if (this._err === null)
|
|
129
|
+
get err() {
|
|
130
|
+
if (this._err === null) {
|
|
131
|
+
this._err = tty.isTTY(2) ? new tty.WriteStream(2) : new this.constructor.WriteStream(2)
|
|
132
|
+
}
|
|
100
133
|
return this._err
|
|
101
134
|
}
|
|
102
135
|
|
|
103
|
-
size
|
|
136
|
+
size() {
|
|
104
137
|
if (!this.out.getWindowSize) return [80, 80]
|
|
105
138
|
const [width, height] = this.out.getWindowSize()
|
|
106
139
|
return { width, height }
|
|
107
140
|
}
|
|
108
141
|
|
|
109
|
-
raw
|
|
142
|
+
raw(rawMode) {
|
|
110
143
|
this.rawMode = !!rawMode
|
|
111
|
-
return this.in.setMode?.(
|
|
144
|
+
return this.in.setMode?.(
|
|
145
|
+
this.rawMode ? this.tty.constants.MODE_RAW : this.tty.constants.MODE_NORMAL
|
|
146
|
+
)
|
|
112
147
|
}
|
|
113
|
-
}()
|
|
148
|
+
})()
|
|
114
149
|
|
|
115
150
|
class Interact {
|
|
116
|
-
static rx =
|
|
117
|
-
|
|
151
|
+
static rx =
|
|
152
|
+
/[\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
|
+
constructor(header, params, opts = {}) {
|
|
118
154
|
this._header = header
|
|
119
155
|
this._params = params
|
|
120
156
|
this._defaults = opts.defaults || {}
|
|
121
157
|
|
|
122
158
|
const mask = (data, cb) => {
|
|
123
|
-
if (data.length > 4) {
|
|
159
|
+
if (data.length > 4) {
|
|
160
|
+
// is full line
|
|
124
161
|
const prompt = this._rl._prompt
|
|
125
162
|
const regex = new RegExp(`(${prompt})([\\x20-\\x7E]+)`, 'g') // match printable chars after prompt
|
|
126
|
-
const masked = data
|
|
163
|
+
const masked = data
|
|
164
|
+
.toString()
|
|
165
|
+
.replace(regex, (_, prompt, pwd) => prompt + '*'.repeat(pwd.length))
|
|
127
166
|
stdio.out.write(masked)
|
|
128
167
|
} else {
|
|
129
168
|
stdio.out.write(data)
|
|
@@ -135,15 +174,13 @@ class Interact {
|
|
|
135
174
|
input: stdio.in,
|
|
136
175
|
output: opts.masked ? new Writable({ write: mask }) : stdio.out
|
|
137
176
|
})
|
|
138
|
-
|
|
139
|
-
this._rl.input?.setMode?.(tty.constants.MODE_RAW)
|
|
140
177
|
this._rl.on('close', () => {
|
|
141
|
-
console.log() //
|
|
142
|
-
Bare.exit()
|
|
178
|
+
console.log() // newline
|
|
143
179
|
})
|
|
180
|
+
stdio.in?.setMode?.(tty.constants.MODE_RAW)
|
|
144
181
|
}
|
|
145
182
|
|
|
146
|
-
async run
|
|
183
|
+
async run(opts) {
|
|
147
184
|
try {
|
|
148
185
|
return await this.#run(opts)
|
|
149
186
|
} finally {
|
|
@@ -151,7 +188,7 @@ class Interact {
|
|
|
151
188
|
}
|
|
152
189
|
}
|
|
153
190
|
|
|
154
|
-
async #run
|
|
191
|
+
async #run(opts = {}) {
|
|
155
192
|
if (opts.autosubmit) return this.#autosubmit()
|
|
156
193
|
stdio.out.write(this._header)
|
|
157
194
|
const fields = {}
|
|
@@ -161,13 +198,16 @@ class Interact {
|
|
|
161
198
|
const param = this._params.shift()
|
|
162
199
|
while (true) {
|
|
163
200
|
const deflt = defaults[param.name] ?? param.default
|
|
164
|
-
let answer = await this.#input(
|
|
165
|
-
|
|
201
|
+
let answer = await this.#input(
|
|
202
|
+
`${param.prompt}${param.delim || ':'}${deflt && ' (' + deflt + ')'} `
|
|
203
|
+
)
|
|
166
204
|
if (answer.length === 0) answer = defaults[param.name] ?? deflt
|
|
167
|
-
if (!param.validation || await param.validation(answer)) {
|
|
205
|
+
if (!param.validation || (await param.validation(answer))) {
|
|
168
206
|
if (typeof answer === 'string') answer = answer.replace(this.constructor.rx, '')
|
|
169
207
|
fields[param.name] = answer
|
|
170
|
-
if (Array.isArray(param.shave) && param.shave.every((ix) => typeof ix === 'number'))
|
|
208
|
+
if (Array.isArray(param.shave) && param.shave.every((ix) => typeof ix === 'number')) {
|
|
209
|
+
shave[param.name] = param.shave
|
|
210
|
+
}
|
|
171
211
|
break
|
|
172
212
|
} else {
|
|
173
213
|
stdio.out.write(param.msg + '\n')
|
|
@@ -177,112 +217,144 @@ class Interact {
|
|
|
177
217
|
return { fields, shave }
|
|
178
218
|
}
|
|
179
219
|
|
|
180
|
-
#autosubmit
|
|
220
|
+
#autosubmit() {
|
|
181
221
|
const fields = {}
|
|
182
222
|
const shave = {}
|
|
183
223
|
const defaults = this._defaults
|
|
184
224
|
while (this._params.length) {
|
|
185
225
|
const param = this._params.shift()
|
|
186
226
|
fields[param.name] = defaults[param.name] ?? param.default
|
|
187
|
-
if (Array.isArray(param.shave) && param.shave.every((ix) => typeof ix === 'number'))
|
|
227
|
+
if (Array.isArray(param.shave) && param.shave.every((ix) => typeof ix === 'number')) {
|
|
228
|
+
shave[param.name] = param.shave
|
|
229
|
+
}
|
|
188
230
|
}
|
|
189
231
|
return { fields, shave }
|
|
190
232
|
}
|
|
191
233
|
|
|
192
|
-
async #input
|
|
234
|
+
async #input(prompt) {
|
|
193
235
|
stdio.out.write(prompt)
|
|
194
236
|
this._rl._prompt = prompt
|
|
195
|
-
const answer =
|
|
196
|
-
|
|
237
|
+
const answer = await new Promise((resolve, reject) => {
|
|
238
|
+
this._rl.once('data', (data) => {
|
|
239
|
+
resolve(data)
|
|
240
|
+
})
|
|
241
|
+
stdio.in?.once('data', (data) => {
|
|
242
|
+
if (data.length === 1 && data[0] === 3) {
|
|
243
|
+
reject(ERR_SIGINT('^C exit'))
|
|
244
|
+
os.kill(Pear.pid, 'SIGINT')
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
})
|
|
248
|
+
return answer.toString().trim() // remove return char
|
|
197
249
|
}
|
|
198
250
|
}
|
|
199
251
|
|
|
200
252
|
let statusFrag = ''
|
|
201
253
|
|
|
202
|
-
function status
|
|
254
|
+
function status(msg, success) {
|
|
203
255
|
msg = msg || ''
|
|
204
256
|
const done = typeof success === 'boolean'
|
|
205
257
|
if (msg) stdio.out.write(`\x1B[2K\r${indicator(success)}${msg}\n${done ? '' : ansi.upHome()}`)
|
|
206
258
|
statusFrag = msg.slice(0, 3)
|
|
207
259
|
}
|
|
208
260
|
|
|
209
|
-
function print
|
|
261
|
+
function print(message, success) {
|
|
210
262
|
statusFrag = ''
|
|
211
263
|
console.log(`${typeof success !== 'undefined' ? indicator(success) : ''}${message}`)
|
|
212
264
|
}
|
|
213
265
|
|
|
214
|
-
function byteDiff
|
|
266
|
+
function byteDiff({ type, sizes, message }) {
|
|
215
267
|
statusFrag = ''
|
|
216
268
|
sizes = sizes.map((size) => (size > 0 ? '+' : '') + byteSize(size)).join(', ')
|
|
217
269
|
print(indicator(type, 'diff') + ' ' + message + ' (' + sizes + ')')
|
|
218
270
|
}
|
|
219
271
|
|
|
220
|
-
function indicator
|
|
272
|
+
function indicator(value, type = 'success') {
|
|
221
273
|
if (value === undefined) return ''
|
|
222
274
|
if (value === true) value = 1
|
|
223
275
|
else if (value === false) value = -1
|
|
224
|
-
else if (value
|
|
225
|
-
if (type === 'diff')
|
|
226
|
-
|
|
276
|
+
else if (value === null) value = 0
|
|
277
|
+
if (type === 'diff') {
|
|
278
|
+
return value === 0 ? ansi.yellow('~') : value === 1 ? ansi.green('+') : ansi.red('-')
|
|
279
|
+
}
|
|
280
|
+
return value < 0 ? ansi.cross + ' ' : value > 0 ? ansi.tick + ' ' : ansi.gray('- ')
|
|
227
281
|
}
|
|
228
282
|
|
|
229
|
-
const outputter =
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
if (
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
const transform = Promise.resolve(typeof taggers[tag] === 'function' ? taggers[tag](data, info, ipc) : taggers[tag] || false)
|
|
250
|
-
transform.then((result) => {
|
|
251
|
-
if (result === undefined) return
|
|
252
|
-
if (typeof result === 'string') result = { output: 'print', message: result }
|
|
253
|
-
if (result === false) {
|
|
254
|
-
if (tag === 'final') result = { output: 'print', message: (data.message ?? data.success ? 'Success' : 'Failure') }
|
|
255
|
-
else result = {}
|
|
256
|
-
}
|
|
257
|
-
result.success = result.success ?? data?.success
|
|
258
|
-
const { output, message, success = data?.success } = result
|
|
259
|
-
if (log) {
|
|
260
|
-
const logOpts = { output, ...(typeof success === 'boolean' ? { success } : {}) }
|
|
261
|
-
if (Array.isArray(message) === false) log(message, logOpts)
|
|
262
|
-
else for (const msg of message) log(msg, logOpts)
|
|
283
|
+
const outputter =
|
|
284
|
+
(cmd, taggers = {}) =>
|
|
285
|
+
(opts, stream, info = {}, ipc) => {
|
|
286
|
+
if (Array.isArray(stream)) stream = Readable.from(stream)
|
|
287
|
+
const asTTY = opts.ctrlTTY ?? isTTY
|
|
288
|
+
if (asTTY) stdio.out.write(ansi.hideCursor())
|
|
289
|
+
const dereg = asTTY
|
|
290
|
+
? gracedown(() => {
|
|
291
|
+
if (!isWindows) stdio.out.write('\x1B[1K\x1B[G' + statusFrag) // clear ^C
|
|
292
|
+
stdio.out.write(ansi.showCursor())
|
|
293
|
+
})
|
|
294
|
+
: null
|
|
295
|
+
if (typeof opts === 'boolean') opts = { json: opts }
|
|
296
|
+
const { json = false, log } = opts
|
|
297
|
+
const promise = opwait(stream, ({ tag, data }) => {
|
|
298
|
+
if (json) {
|
|
299
|
+
const str = JSON.stringify({ cmd, tag, data })
|
|
300
|
+
if (log) log(str)
|
|
301
|
+
else print(str)
|
|
263
302
|
return
|
|
264
303
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
304
|
+
|
|
305
|
+
const transform = Promise.resolve(
|
|
306
|
+
typeof taggers[tag] === 'function' ? taggers[tag](data, info, ipc) : taggers[tag] || false
|
|
307
|
+
)
|
|
308
|
+
transform.then(
|
|
309
|
+
(result) => {
|
|
310
|
+
if (result === undefined) return
|
|
311
|
+
if (typeof result === 'string') result = { output: 'print', message: result }
|
|
312
|
+
if (result === false) {
|
|
313
|
+
if (tag === 'final') {
|
|
314
|
+
result = {
|
|
315
|
+
output: 'print',
|
|
316
|
+
message: (data.message ?? data.success) ? 'Success' : 'Failure'
|
|
317
|
+
}
|
|
318
|
+
} else result = {}
|
|
319
|
+
}
|
|
320
|
+
result.success = result.success ?? data?.success
|
|
321
|
+
const { output, message, success = data?.success } = result
|
|
322
|
+
if (log) {
|
|
323
|
+
const logOpts = { output, ...(typeof success === 'boolean' ? { success } : {}) }
|
|
324
|
+
if (Array.isArray(message) === false) log(message, logOpts)
|
|
325
|
+
else for (const msg of message) log(msg, logOpts)
|
|
326
|
+
return
|
|
327
|
+
}
|
|
328
|
+
let msg = Array.isArray(message) ? message.join('\n') : message
|
|
329
|
+
if (tag === 'final') {
|
|
330
|
+
msg += '\n'
|
|
331
|
+
if (asTTY) {
|
|
332
|
+
stdio.out.write(ansi.showCursor())
|
|
333
|
+
dereg(false)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (output === 'print') print(msg, success)
|
|
338
|
+
else if (output === 'status') status(msg, success)
|
|
339
|
+
},
|
|
340
|
+
(err) => stream.destroy(err)
|
|
341
|
+
)
|
|
277
342
|
})
|
|
278
|
-
|
|
343
|
+
|
|
344
|
+
return promise
|
|
345
|
+
}
|
|
279
346
|
|
|
280
347
|
const banner = `${ansi.bold('Pear')} ~ ${ansi.dim('Welcome to the Internet of Peers')}`
|
|
281
348
|
const version = `${CHECKOUT.fork || 0}.${CHECKOUT.length || 'dev'}.${CHECKOUT.key}`
|
|
282
349
|
const header = ` ${banner}
|
|
283
350
|
${ansi.pear + ' '}${ansi.bold(ansi.gray('v' + version))}
|
|
284
351
|
`
|
|
285
|
-
const urls =
|
|
352
|
+
const urls =
|
|
353
|
+
ansi.link('https://pears.com', 'pears.com') +
|
|
354
|
+
' | ' +
|
|
355
|
+
ansi.link('https://holepunch.to', 'holepunch.to') +
|
|
356
|
+
' | ' +
|
|
357
|
+
ansi.link('https://keet.io', 'keet.io')
|
|
286
358
|
|
|
287
359
|
const footer = {
|
|
288
360
|
overview: ` ${ansi.bold('Legend:')} [arg] = optional, <arg> = required, | = or \n Run ${ansi.bold('pear help')} to output full help for all commands\n For command help: ${ansi.bold('pear help [cmd]')} or ${ansi.bold('pear [cmd] -h')}\n
|
|
@@ -294,11 +366,13 @@ ${urls}\n${ansi.bold(ansi.dim('Pear'))} ~ ${ansi.dim('Welcome to the IoP')}
|
|
|
294
366
|
|
|
295
367
|
const usage = { header, version, banner, footer }
|
|
296
368
|
|
|
297
|
-
async function trust
|
|
369
|
+
async function trust(ipc, key, cmd) {
|
|
298
370
|
const explain = {
|
|
299
|
-
run:
|
|
371
|
+
run:
|
|
372
|
+
'Be sure that software is trusted before running it\n' +
|
|
300
373
|
'\nType "TRUST" to allow execution or anything else to exit\n\n',
|
|
301
|
-
init:
|
|
374
|
+
init:
|
|
375
|
+
'This template is not trusted.\n' +
|
|
302
376
|
'\nType "TRUST" to trust this template, or anything else to exit\n\n'
|
|
303
377
|
}
|
|
304
378
|
|
|
@@ -337,22 +411,20 @@ async function trust (ipc, key, cmd) {
|
|
|
337
411
|
Bare.exit()
|
|
338
412
|
}
|
|
339
413
|
|
|
340
|
-
async function password
|
|
414
|
+
async function password(ipc, key, cmd) {
|
|
341
415
|
const z32 = hypercoreid.normalize(key)
|
|
342
416
|
|
|
343
417
|
const explain = {
|
|
344
|
-
run:
|
|
418
|
+
run:
|
|
419
|
+
'pear://' +
|
|
420
|
+
z32 +
|
|
421
|
+
' is an encrypted application. \n' +
|
|
345
422
|
'\nEnter the password to run the app.\n\n',
|
|
346
|
-
stage: 'This application is encrypted.\n' +
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
'\nEnter the password to dump the app.\n\n',
|
|
352
|
-
init: 'This template is encrypted.\n' +
|
|
353
|
-
'\nEnter the password to init from the template.\n\n',
|
|
354
|
-
info: 'This application is encrypted.\n' +
|
|
355
|
-
'\nEnter the password to retrieve info.\n\n'
|
|
423
|
+
stage: 'This application is encrypted.\n' + '\nEnter the password to stage the app.\n\n',
|
|
424
|
+
seed: 'This application is encrypted.\n' + '\nEnter the password to seed the app.\n\n',
|
|
425
|
+
dump: 'This application is encrypted.\n' + '\nEnter the password to dump the app.\n\n',
|
|
426
|
+
init: 'This template is encrypted.\n' + '\nEnter the password to init from the template.\n\n',
|
|
427
|
+
info: 'This application is encrypted.\n' + '\nEnter the password to retrieve info.\n\n'
|
|
356
428
|
}
|
|
357
429
|
|
|
358
430
|
const message = {
|
|
@@ -369,16 +441,20 @@ async function password (ipc, key, cmd) {
|
|
|
369
441
|
const delim = ':'
|
|
370
442
|
const validation = (key) => key.length > 0
|
|
371
443
|
const msg = '\nPlease, enter a valid password.\n'
|
|
372
|
-
const interact = new Interact(
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
444
|
+
const interact = new Interact(
|
|
445
|
+
dialog,
|
|
446
|
+
[
|
|
447
|
+
{
|
|
448
|
+
name: 'value',
|
|
449
|
+
default: '',
|
|
450
|
+
prompt: ask,
|
|
451
|
+
delim,
|
|
452
|
+
validation,
|
|
453
|
+
msg
|
|
454
|
+
}
|
|
455
|
+
],
|
|
456
|
+
{ masked: true }
|
|
457
|
+
)
|
|
382
458
|
const { fields } = await interact.run()
|
|
383
459
|
print(`\n${ansi.key} Hashing password...`)
|
|
384
460
|
await ipc.permit({ key, password: fields.value })
|
|
@@ -387,7 +463,7 @@ async function password (ipc, key, cmd) {
|
|
|
387
463
|
Bare.exit()
|
|
388
464
|
}
|
|
389
465
|
|
|
390
|
-
function permit
|
|
466
|
+
function permit(ipc, info, cmd) {
|
|
391
467
|
const key = info.key
|
|
392
468
|
if (info.encrypted) {
|
|
393
469
|
return password(ipc, key, cmd)
|
|
@@ -396,7 +472,7 @@ function permit (ipc, info, cmd) {
|
|
|
396
472
|
}
|
|
397
473
|
}
|
|
398
474
|
|
|
399
|
-
async function confirm
|
|
475
|
+
async function confirm(dialog, ask, delim, validation, msg) {
|
|
400
476
|
const interact = new Interact(dialog, [
|
|
401
477
|
{
|
|
402
478
|
name: 'value',
|
|
@@ -410,4 +486,62 @@ async function confirm (dialog, ask, delim, validation, msg) {
|
|
|
410
486
|
await interact.run()
|
|
411
487
|
}
|
|
412
488
|
|
|
413
|
-
|
|
489
|
+
function explain(bail = {}) {
|
|
490
|
+
if (!bail.reason && bail.err) {
|
|
491
|
+
const known = errors.known()
|
|
492
|
+
if (known.includes(bail.err.code) === false) {
|
|
493
|
+
print(
|
|
494
|
+
errors.ERR_UNKNOWN(
|
|
495
|
+
'Unknown [ code: ' + (bail.err.code || '(none)') + ' ] ' + bail.err.stack
|
|
496
|
+
),
|
|
497
|
+
false
|
|
498
|
+
)
|
|
499
|
+
Bare.exit(1)
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const messageUsage = (bail) => bail.err.message
|
|
503
|
+
const messageOnly = (bail) => bail.err.message
|
|
504
|
+
const opFail = (cmd) => cmd.err.info.message
|
|
505
|
+
const codemap = new Map([
|
|
506
|
+
['UNKNOWN_FLAG', (bail) => 'Unrecognized Flag: --' + bail.flag.name],
|
|
507
|
+
[
|
|
508
|
+
'UNKNOWN_ARG',
|
|
509
|
+
(bail) => 'Unrecognized Argument at index ' + bail.arg.index + ' with value ' + bail.arg.value
|
|
510
|
+
],
|
|
511
|
+
['MISSING_ARG', (bail) => bail.arg.value],
|
|
512
|
+
['INVALID', messageUsage],
|
|
513
|
+
['ERR_INVALID_INPUT', messageUsage],
|
|
514
|
+
['ERR_LEGACY', messageOnly],
|
|
515
|
+
['ERR_INVALID_TEMPLATE', messageOnly],
|
|
516
|
+
['ERR_DIR_NONEMPTY', messageOnly],
|
|
517
|
+
['ERR_OPERATION_FAILED', opFail]
|
|
518
|
+
])
|
|
519
|
+
const nouse = [messageOnly, opFail]
|
|
520
|
+
const code = codemap.has(bail.err?.code) ? bail.err.code : bail.reason
|
|
521
|
+
const ref = codemap.get(code)
|
|
522
|
+
const reason = codemap.has(code) ? (codemap.get(code)(bail) ?? bail.reason) : bail.reason
|
|
523
|
+
Bare.exitCode = 1
|
|
524
|
+
|
|
525
|
+
print(reason, false)
|
|
526
|
+
|
|
527
|
+
if (nouse.some((fn) => fn === ref) || codemap.has(code) === false) return
|
|
528
|
+
|
|
529
|
+
print('\n' + bail.command.usage())
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
module.exports = {
|
|
533
|
+
explain,
|
|
534
|
+
usage,
|
|
535
|
+
permit,
|
|
536
|
+
stdio,
|
|
537
|
+
ansi,
|
|
538
|
+
indicator,
|
|
539
|
+
status,
|
|
540
|
+
print,
|
|
541
|
+
outputter,
|
|
542
|
+
isTTY,
|
|
543
|
+
confirm,
|
|
544
|
+
byteSize,
|
|
545
|
+
byteDiff,
|
|
546
|
+
Interact
|
|
547
|
+
}
|
package/package.json
CHANGED
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pear-terminal",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"description": "Pear Terminal User Interface library",
|
|
7
7
|
"license": "Apache-2.0",
|
|
8
8
|
"files": [],
|
|
9
|
+
"imports": {
|
|
10
|
+
"process": {
|
|
11
|
+
"bare": "bare-process",
|
|
12
|
+
"default": "process"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
9
15
|
"scripts": {
|
|
16
|
+
"format": "prettier --write .",
|
|
17
|
+
"lint": "prettier --check . && lunte",
|
|
10
18
|
"test:gen": "brittle -r test/all.js test/*.test.js",
|
|
11
|
-
"test": "brittle-bare
|
|
12
|
-
"
|
|
19
|
+
"test": "brittle-bare test/all.js",
|
|
20
|
+
"test:bare": "brittle-bare test/all.js"
|
|
13
21
|
},
|
|
14
22
|
"devDependencies": {
|
|
15
23
|
"bare-module": "^5.0.2",
|
|
16
|
-
"bare-os": "^3.6.
|
|
24
|
+
"bare-os": "^3.6.2",
|
|
25
|
+
"bare-process": "^4.2.2",
|
|
17
26
|
"brittle": "^3.0.0",
|
|
27
|
+
"lunte": "^1.0.0",
|
|
18
28
|
"pear-ipc": "^5.5.0",
|
|
19
|
-
"
|
|
29
|
+
"prettier": "^3.6.2",
|
|
30
|
+
"prettier-config-holepunch": "^2.0.0"
|
|
20
31
|
},
|
|
21
32
|
"dependencies": {
|
|
22
33
|
"bare-events": "^2.6.1",
|
|
@@ -26,6 +37,7 @@
|
|
|
26
37
|
"bare-tty": "^5.0.2",
|
|
27
38
|
"hypercore-id-encoding": "^1.3.0",
|
|
28
39
|
"pear-constants": "^1.0.0",
|
|
40
|
+
"pear-errors": "^1.0.1",
|
|
29
41
|
"pear-gracedown": "^1.0.0",
|
|
30
42
|
"pear-opwait": "^1.0.0",
|
|
31
43
|
"streamx": "^2.22.1",
|