bare-tty 5.1.1 → 5.2.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/binding.c CHANGED
@@ -9,7 +9,6 @@ typedef struct {
9
9
 
10
10
  struct {
11
11
  uv_write_t write;
12
- uv_shutdown_t shutdown;
13
12
  } requests;
14
13
 
15
14
  uv_buf_t read;
@@ -192,13 +191,13 @@ static js_value_t *
192
191
  bare_tty_init(js_env_t *env, js_callback_info_t *info) {
193
192
  int err;
194
193
 
195
- size_t argc = 6;
196
- js_value_t *argv[6];
194
+ size_t argc = 7;
195
+ js_value_t *argv[7];
197
196
 
198
197
  err = js_get_callback_info(env, info, &argc, argv, NULL, NULL);
199
198
  assert(err == 0);
200
199
 
201
- assert(argc == 6);
200
+ assert(argc == 7);
202
201
 
203
202
  uv_loop_t *loop;
204
203
  err = js_get_env_loop(env, &loop);
@@ -217,22 +216,33 @@ bare_tty_init(js_env_t *env, js_callback_info_t *info) {
217
216
  err = uv_tty_init(loop, &tty->handle, fd, 1);
218
217
 
219
218
  if (err < 0) {
220
- js_throw_error(env, uv_err_name(err), uv_strerror(err));
219
+ err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
220
+ assert(err == 0);
221
+
221
222
  return NULL;
222
223
  }
223
224
 
224
- err = uv_stream_set_blocking((uv_stream_t *) &tty->handle, true);
225
+ bool blocking;
226
+ err = js_get_value_bool(env, argv[6], &blocking);
227
+ assert(err == 0);
225
228
 
226
- // Not all platforms support blocking TTY handles.
227
- (void) err;
229
+ if (blocking) {
230
+ err = uv_stream_set_blocking((uv_stream_t *) &tty->handle, true);
231
+
232
+ // Not all platforms support blocking TTY handles.
233
+ (void) err;
234
+ }
228
235
 
229
236
  tty->env = env;
230
237
  tty->closing = false;
231
238
  tty->exiting = false;
232
239
 
233
- err = js_get_typedarray_info(env, argv[1], NULL, (void **) &tty->read.base, (size_t *) &tty->read.len, NULL, NULL);
240
+ size_t read_len;
241
+ err = js_get_typedarray_info(env, argv[1], NULL, (void **) &tty->read.base, &read_len, NULL, NULL);
234
242
  assert(err == 0);
235
243
 
244
+ tty->read.len = read_len;
245
+
236
246
  err = js_create_reference(env, argv[2], 1, &tty->ctx);
237
247
  assert(err == 0);
238
248
 
@@ -277,18 +287,31 @@ bare_tty_writev(js_env_t *env, js_callback_info_t *info) {
277
287
 
278
288
  js_value_t **elements = malloc(sizeof(js_value_t *) * bufs_len);
279
289
 
290
+ if ((bufs == NULL || elements == NULL) && bufs_len > 0) {
291
+ free(bufs);
292
+ free(elements);
293
+
294
+ err = js_throw_error(env, uv_err_name(UV_ENOMEM), uv_strerror(UV_ENOMEM));
295
+ assert(err == 0);
296
+
297
+ return NULL;
298
+ }
299
+
280
300
  uint32_t fetched;
281
301
  err = js_get_array_elements(env, arr, elements, bufs_len, 0, &fetched);
282
302
  assert(err == 0);
283
303
  assert(fetched == bufs_len);
284
304
 
285
-
286
305
  for (uint32_t i = 0; i < bufs_len; i++) {
287
306
  js_value_t *item = elements[i];
288
307
 
289
308
  uv_buf_t *buf = &bufs[i];
290
- err = js_get_typedarray_info(env, item, NULL, (void **) &buf->base, (size_t *) &buf->len, NULL, NULL);
309
+
310
+ size_t buf_len;
311
+ err = js_get_typedarray_info(env, item, NULL, (void **) &buf->base, &buf_len, NULL, NULL);
291
312
  assert(err == 0);
313
+
314
+ buf->len = buf_len;
292
315
  }
293
316
 
294
317
  free(elements);
@@ -302,7 +325,9 @@ bare_tty_writev(js_env_t *env, js_callback_info_t *info) {
302
325
  free(bufs);
303
326
 
304
327
  if (err < 0) {
305
- js_throw_error(env, uv_err_name(err), uv_strerror(err));
328
+ err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
329
+ assert(err == 0);
330
+
306
331
  return NULL;
307
332
  }
308
333
 
@@ -328,7 +353,9 @@ bare_tty_resume(js_env_t *env, js_callback_info_t *info) {
328
353
  err = uv_read_start((uv_stream_t *) &tty->handle, bare_tty__on_alloc, bare_tty__on_read);
329
354
 
330
355
  if (err < 0) {
331
- js_throw_error(env, uv_err_name(err), uv_strerror(err));
356
+ err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
357
+ assert(err == 0);
358
+
332
359
  return NULL;
333
360
  }
334
361
 
@@ -354,7 +381,9 @@ bare_tty_pause(js_env_t *env, js_callback_info_t *info) {
354
381
  err = uv_read_stop((uv_stream_t *) &tty->handle);
355
382
 
356
383
  if (err < 0) {
357
- js_throw_error(env, uv_err_name(err), uv_strerror(err));
384
+ err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
385
+ assert(err == 0);
386
+
358
387
  return NULL;
359
388
  }
360
389
 
@@ -377,6 +406,8 @@ bare_tty_close(js_env_t *env, js_callback_info_t *info) {
377
406
  err = js_get_arraybuffer_info(env, argv[0], (void **) &tty, NULL);
378
407
  assert(err == 0);
379
408
 
409
+ if (tty->closing) return NULL;
410
+
380
411
  err = uv_tty_set_mode(&tty->handle, UV_TTY_MODE_NORMAL);
381
412
 
382
413
  // Resetting the mode won't always work, in particular on Windows when we
@@ -413,7 +444,9 @@ bare_tty_set_mode(js_env_t *env, js_callback_info_t *info) {
413
444
  err = uv_tty_set_mode(&tty->handle, mode);
414
445
 
415
446
  if (err < 0) {
416
- js_throw_error(env, uv_err_name(err), uv_strerror(err));
447
+ err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
448
+ assert(err == 0);
449
+
417
450
  return NULL;
418
451
  }
419
452
 
@@ -440,7 +473,9 @@ bare_tty_get_window_size(js_env_t *env, js_callback_info_t *info) {
440
473
  err = uv_tty_get_winsize(&tty->handle, &width, &height);
441
474
 
442
475
  if (err < 0) {
443
- js_throw_error(env, uv_err_name(err), uv_strerror(err));
476
+ err = js_throw_error(env, uv_err_name(err), uv_strerror(err));
477
+ assert(err == 0);
478
+
444
479
  return NULL;
445
480
  }
446
481
 
package/index.d.ts CHANGED
@@ -1,5 +1,10 @@
1
1
  import { Readable, ReadableOptions, Writable, WritableEvents, WritableOptions } from 'bare-stream'
2
2
  import constants from './lib/constants'
3
+ import TTYError from './lib/errors'
4
+
5
+ interface ReadStreamOptions extends ReadableOptions {
6
+ readBufferSize?: number
7
+ }
3
8
 
4
9
  interface ReadStream extends Readable {
5
10
  readonly fd: number
@@ -10,7 +15,7 @@ interface ReadStream extends Readable {
10
15
  }
11
16
 
12
17
  declare class ReadStream extends Readable {
13
- constructor(fd: number, opts?: ReadableOptions)
18
+ constructor(fd: number, opts?: ReadStreamOptions)
14
19
  }
15
20
 
16
21
  interface WriteStreamEvents extends WritableEvents {
@@ -32,4 +37,13 @@ declare class WriteStream<M extends WriteStreamEvents = WriteStreamEvents> exten
32
37
 
33
38
  declare function isTTY(fd: number): boolean
34
39
 
35
- export { ReadStream, WriteStream, isTTY, isTTY as isatty, constants }
40
+ export {
41
+ type ReadStreamOptions,
42
+ ReadStream,
43
+ WriteStream,
44
+ isTTY,
45
+ isTTY as isatty,
46
+ constants,
47
+ type TTYError,
48
+ TTYError as errors
49
+ }
package/index.js CHANGED
@@ -2,24 +2,28 @@ const { Readable, Writable } = require('bare-stream')
2
2
  const Signal = require('bare-signals')
3
3
  const binding = require('./binding')
4
4
  const constants = require('./lib/constants')
5
+ const errors = require('./lib/errors')
5
6
 
6
7
  const defaultReadBufferSize = 65536
7
8
  const empty = Buffer.alloc(0)
9
+ const modes = new Set([constants.mode.NORMAL, constants.mode.RAW, constants.mode.IO])
8
10
 
9
11
  exports.ReadStream = class TTYReadStream extends Readable {
10
12
  constructor(fd, opts = {}) {
11
- super()
13
+ super(opts)
12
14
 
13
- const { readBufferSize = defaultReadBufferSize, allowHalfOpen = true } = opts
15
+ const { readBufferSize = defaultReadBufferSize } = opts
16
+
17
+ validateFd(fd)
18
+ validateInteger(readBufferSize, 'Read buffer size', 1, 0x7fffffff)
14
19
 
15
20
  this._fd = fd
16
21
  this._state = 0
17
- this._allowHalfOpen = allowHalfOpen
18
22
  this._buffer = Buffer.alloc(readBufferSize)
19
23
 
20
24
  this._pendingDestroy = null
21
25
 
22
- this._handle = binding.init(fd, this._buffer, this, noop, this._onread, this._onclose)
26
+ this._handle = binding.init(fd, this._buffer, this, noop, this._onread, this._onclose, false)
23
27
  }
24
28
 
25
29
  get fd() {
@@ -31,7 +35,12 @@ exports.ReadStream = class TTYReadStream extends Readable {
31
35
  }
32
36
 
33
37
  setMode(mode) {
38
+ validateMode(mode)
39
+
40
+ this._alive()
41
+
34
42
  binding.setMode(this._handle, mode)
43
+
35
44
  return this
36
45
  }
37
46
 
@@ -39,6 +48,12 @@ exports.ReadStream = class TTYReadStream extends Readable {
39
48
  return this.setMode(enabled ? constants.mode.RAW : constants.mode.NORMAL)
40
49
  }
41
50
 
51
+ _alive() {
52
+ if (this._state & constants.state.CLOSING) {
53
+ throw errors.STREAM_IS_CLOSED('Stream is closed')
54
+ }
55
+ }
56
+
42
57
  _read() {
43
58
  if ((this._state & constants.state.READING) === 0) {
44
59
  this._state |= constants.state.READING
@@ -48,18 +63,21 @@ exports.ReadStream = class TTYReadStream extends Readable {
48
63
  }
49
64
 
50
65
  _predestroy() {
51
- if (this._state & constants.state.CLOSING) return
52
- this._state |= constants.state.CLOSING
53
-
54
- binding.close(this._handle)
66
+ this._close()
55
67
  }
56
68
 
57
69
  _destroy(err, cb) {
58
- if (this._state & constants.state.CLOSING) return cb(err)
59
- this._state |= constants.state.CLOSING
70
+ if (this._state & constants.state.CLOSED) return cb(err)
60
71
 
61
72
  this._pendingDestroy = cb
62
73
 
74
+ this._close()
75
+ }
76
+
77
+ _close() {
78
+ if (this._state & constants.state.CLOSING) return
79
+ this._state |= constants.state.CLOSING
80
+
63
81
  binding.close(this._handle)
64
82
  }
65
83
 
@@ -78,7 +96,6 @@ exports.ReadStream = class TTYReadStream extends Readable {
78
96
 
79
97
  if (read === 0) {
80
98
  this.push(null)
81
- if (this._allowHalfOpen === false) this.end()
82
99
  return
83
100
  }
84
101
 
@@ -93,7 +110,7 @@ exports.ReadStream = class TTYReadStream extends Readable {
93
110
  }
94
111
 
95
112
  _onclose() {
96
- this._handle = null
113
+ this._state |= constants.state.CLOSED
97
114
 
98
115
  this._continueDestroy()
99
116
  }
@@ -101,7 +118,9 @@ exports.ReadStream = class TTYReadStream extends Readable {
101
118
 
102
119
  exports.WriteStream = class TTYWriteStream extends Writable {
103
120
  constructor(fd, opts = {}) {
104
- super()
121
+ super(opts)
122
+
123
+ validateFd(fd)
105
124
 
106
125
  this._fd = fd
107
126
  this._state = 0
@@ -111,9 +130,15 @@ exports.WriteStream = class TTYWriteStream extends Writable {
111
130
  this._pendingWriteBatch = null
112
131
  this._pendingDestroy = null
113
132
 
114
- this._handle = binding.init(fd, empty, this, this._onwrite, noop, this._onclose)
133
+ this._handle = binding.init(fd, empty, this, this._onwrite, noop, this._onclose, true)
115
134
 
116
- this._size = this.getWindowSize()
135
+ try {
136
+ this._refreshSize()
137
+ } catch (err) {
138
+ this._close()
139
+
140
+ throw err
141
+ }
117
142
 
118
143
  if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.start()
119
144
 
@@ -137,7 +162,25 @@ exports.WriteStream = class TTYWriteStream extends Writable {
137
162
  }
138
163
 
139
164
  getWindowSize() {
140
- return binding.getWindowSize(this._handle)
165
+ this._alive()
166
+
167
+ return [this._size[0], this._size[1]]
168
+ }
169
+
170
+ _refreshSize() {
171
+ const size = binding.getWindowSize(this._handle)
172
+
173
+ const changed = this._size === null || size[0] !== this._size[0] || size[1] !== this._size[1]
174
+
175
+ this._size = size
176
+
177
+ return changed
178
+ }
179
+
180
+ _alive() {
181
+ if (this._state & constants.state.CLOSING) {
182
+ throw errors.STREAM_IS_CLOSED('Stream is closed')
183
+ }
141
184
  }
142
185
 
143
186
  _writev(batch, cb) {
@@ -145,6 +188,8 @@ exports.WriteStream = class TTYWriteStream extends Writable {
145
188
  this._pendingWriteBatch = batch
146
189
 
147
190
  try {
191
+ coerceBatch(batch)
192
+
148
193
  binding.writev(
149
194
  this._handle,
150
195
  batch.map(({ chunk }) => chunk)
@@ -155,22 +200,21 @@ exports.WriteStream = class TTYWriteStream extends Writable {
155
200
  }
156
201
 
157
202
  _predestroy() {
158
- if (this._state & constants.state.CLOSING) return
159
- this._state |= constants.state.CLOSING
203
+ this._close()
204
+ }
160
205
 
161
- binding.close(this._handle)
206
+ _destroy(err, cb) {
207
+ if (this._state & constants.state.CLOSED) return cb(err)
162
208
 
163
- TTYWriteStream._streams.delete(this)
209
+ this._pendingDestroy = cb
164
210
 
165
- if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.stop()
211
+ this._close()
166
212
  }
167
213
 
168
- _destroy(err, cb) {
169
- if (this._state & constants.state.CLOSING) return cb(err)
214
+ _close() {
215
+ if (this._state & constants.state.CLOSING) return
170
216
  this._state |= constants.state.CLOSING
171
217
 
172
- this._pendingDestroy = cb
173
-
174
218
  binding.close(this._handle)
175
219
 
176
220
  TTYWriteStream._streams.delete(this)
@@ -198,15 +242,22 @@ exports.WriteStream = class TTYWriteStream extends Writable {
198
242
  }
199
243
 
200
244
  _onclose() {
201
- this._handle = null
245
+ this._state |= constants.state.CLOSED
202
246
 
203
247
  this._continueDestroy()
204
248
  }
205
249
 
206
250
  _onresize() {
207
- this._size = this.getWindowSize()
251
+ let changed
208
252
 
209
- this.emit('resize')
253
+ try {
254
+ changed = this._refreshSize()
255
+ } catch (err) {
256
+ this.emit('error', err)
257
+ return
258
+ }
259
+
260
+ if (changed) this.emit('resize')
210
261
  }
211
262
 
212
263
  static _streams = new Set()
@@ -216,7 +267,11 @@ exports.WriteStream = class TTYWriteStream extends Writable {
216
267
 
217
268
  exports.constants = constants
218
269
 
219
- exports.isTTY = binding.isTTY
270
+ exports.errors = errors
271
+
272
+ exports.isTTY = function isTTY(fd) {
273
+ return isValidFd(fd) && binding.isTTY(fd)
274
+ }
220
275
 
221
276
  exports.isatty = exports.isTTY // For Node.js compatibility
222
277
 
@@ -228,4 +283,54 @@ exports.WriteStream._resize
228
283
  })
229
284
  .unref()
230
285
 
286
+ function isValidFd(fd) {
287
+ return typeof fd === 'number' && Number.isInteger(fd) && fd >= 0 && fd <= 0x7fffffff
288
+ }
289
+
290
+ function validateFd(fd) {
291
+ if (typeof fd !== 'number') {
292
+ throw errors.INVALID_FD(`File descriptor must be a number, got ${typeof fd}`)
293
+ }
294
+
295
+ if (!isValidFd(fd)) {
296
+ throw errors.INVALID_FD(
297
+ `File descriptor must be an integer between 0 and ${0x7fffffff}, got ${fd}`
298
+ )
299
+ }
300
+ }
301
+
302
+ function validateMode(mode) {
303
+ if (typeof mode !== 'number') {
304
+ throw errors.INVALID_ARGUMENT(`Mode must be a number, got ${typeof mode}`)
305
+ }
306
+
307
+ if (!modes.has(mode)) {
308
+ throw errors.INVALID_ARGUMENT(`Mode must be one of ${[...modes].join(', ')}, got ${mode}`)
309
+ }
310
+ }
311
+
312
+ function validateInteger(value, name, min, max) {
313
+ if (typeof value !== 'number') {
314
+ throw errors.INVALID_ARGUMENT(`${name} must be a number, got ${typeof value}`)
315
+ }
316
+
317
+ if (!Number.isInteger(value) || value < min || value > max) {
318
+ throw errors.INVALID_ARGUMENT(
319
+ `${name} must be an integer between ${min} and ${max}, got ${value}`
320
+ )
321
+ }
322
+ }
323
+
324
+ function coerceBatch(batch) {
325
+ for (let i = 0; i < batch.length; i++) {
326
+ const chunk = batch[i].chunk
327
+
328
+ if (ArrayBuffer.isView(chunk) === false) {
329
+ throw errors.INVALID_ARGUMENT(`Chunk must be a string or a view, got ${typeof chunk}`)
330
+ }
331
+
332
+ batch[i].chunk = Buffer.coerce(chunk)
333
+ }
334
+ }
335
+
231
336
  function noop() {}
@@ -1,6 +1,6 @@
1
1
  declare const constants: {
2
2
  mode: { NORMAL: number; RAW: number; IO: number }
3
- state: { READING: number; CLOSING: number }
3
+ state: { READING: number; CLOSING: number; CLOSED: number }
4
4
  }
5
5
 
6
6
  export = constants
package/lib/constants.js CHANGED
@@ -9,7 +9,8 @@ module.exports = exports = {
9
9
 
10
10
  state: {
11
11
  READING: 0x1,
12
- CLOSING: 0x2
12
+ CLOSING: 0x2,
13
+ CLOSED: 0x4
13
14
  }
14
15
  }
15
16
 
@@ -0,0 +1,5 @@
1
+ declare class TTYError extends Error {
2
+ readonly code: string
3
+ }
4
+
5
+ export = TTYError
package/lib/errors.js ADDED
@@ -0,0 +1,26 @@
1
+ module.exports = class TTYError extends Error {
2
+ constructor(msg, fn = TTYError, code = fn.name) {
3
+ super(`${code}: ${msg}`)
4
+ this.code = code
5
+
6
+ if (Error.captureStackTrace) {
7
+ Error.captureStackTrace(this, fn)
8
+ }
9
+ }
10
+
11
+ get name() {
12
+ return 'TTYError'
13
+ }
14
+
15
+ static STREAM_IS_CLOSED(msg) {
16
+ return new TTYError(msg, TTYError.STREAM_IS_CLOSED)
17
+ }
18
+
19
+ static INVALID_FD(msg) {
20
+ return new TTYError(msg, TTYError.INVALID_FD)
21
+ }
22
+
23
+ static INVALID_ARGUMENT(msg) {
24
+ return new TTYError(msg, TTYError.INVALID_ARGUMENT)
25
+ }
26
+ }
package/package.json CHANGED
@@ -1,14 +1,21 @@
1
1
  {
2
2
  "name": "bare-tty",
3
- "version": "5.1.1",
3
+ "version": "5.2.0",
4
4
  "description": "Native TTY streams for JavaScript",
5
5
  "exports": {
6
+ "./package": "./package.json",
6
7
  ".": {
7
8
  "types": "./index.d.ts",
8
9
  "default": "./index.js"
9
10
  },
10
- "./package": "./package.json",
11
- "./constants": "./lib/constants.js"
11
+ "./constants": {
12
+ "types": "./lib/constants.d.ts",
13
+ "default": "./lib/constants.js"
14
+ },
15
+ "./errors": {
16
+ "types": "./lib/errors.d.ts",
17
+ "default": "./lib/errors.js"
18
+ }
12
19
  },
13
20
  "files": [
14
21
  "index.js",
@@ -40,10 +47,11 @@
40
47
  },
41
48
  "dependencies": {
42
49
  "bare-events": "^2.2.0",
43
- "bare-signals": "^4.0.0",
50
+ "bare-signals": "^5.0.0",
44
51
  "bare-stream": "^2.0.0"
45
52
  },
46
53
  "devDependencies": {
54
+ "bare-subprocess": "^6.0.0",
47
55
  "brittle": "^3.2.1",
48
56
  "cmake-bare": "^1.1.6",
49
57
  "lunte": "^1.8.0",
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file