bare-tty 5.1.2 → 5.2.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.
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,11 +96,13 @@ 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
 
85
- const copy = Buffer.allocUnsafe(read)
102
+ // Unpooled, as a read too small to be given a buffer of its own would
103
+ // otherwise pin the whole pool it came from for as long as its consumer
104
+ // holds on to it.
105
+ const copy = Buffer.allocUnsafeSlow(read)
86
106
  copy.set(this._buffer.subarray(0, read))
87
107
 
88
108
  if (this.push(copy) === false && this.destroying === false) {
@@ -93,7 +113,7 @@ exports.ReadStream = class TTYReadStream extends Readable {
93
113
  }
94
114
 
95
115
  _onclose() {
96
- this._handle = null
116
+ this._state |= constants.state.CLOSED
97
117
 
98
118
  this._continueDestroy()
99
119
  }
@@ -101,7 +121,9 @@ exports.ReadStream = class TTYReadStream extends Readable {
101
121
 
102
122
  exports.WriteStream = class TTYWriteStream extends Writable {
103
123
  constructor(fd, opts = {}) {
104
- super()
124
+ super(opts)
125
+
126
+ validateFd(fd)
105
127
 
106
128
  this._fd = fd
107
129
  this._state = 0
@@ -111,9 +133,15 @@ exports.WriteStream = class TTYWriteStream extends Writable {
111
133
  this._pendingWriteBatch = null
112
134
  this._pendingDestroy = null
113
135
 
114
- this._handle = binding.init(fd, empty, this, this._onwrite, noop, this._onclose)
136
+ this._handle = binding.init(fd, empty, this, this._onwrite, noop, this._onclose, true)
115
137
 
116
- this._size = this.getWindowSize()
138
+ try {
139
+ this._refreshSize()
140
+ } catch (err) {
141
+ this._close()
142
+
143
+ throw err
144
+ }
117
145
 
118
146
  if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.start()
119
147
 
@@ -137,7 +165,25 @@ exports.WriteStream = class TTYWriteStream extends Writable {
137
165
  }
138
166
 
139
167
  getWindowSize() {
140
- return binding.getWindowSize(this._handle)
168
+ this._alive()
169
+
170
+ return [this._size[0], this._size[1]]
171
+ }
172
+
173
+ _refreshSize() {
174
+ const size = binding.getWindowSize(this._handle)
175
+
176
+ const changed = this._size === null || size[0] !== this._size[0] || size[1] !== this._size[1]
177
+
178
+ this._size = size
179
+
180
+ return changed
181
+ }
182
+
183
+ _alive() {
184
+ if (this._state & constants.state.CLOSING) {
185
+ throw errors.STREAM_IS_CLOSED('Stream is closed')
186
+ }
141
187
  }
142
188
 
143
189
  _writev(batch, cb) {
@@ -145,6 +191,8 @@ exports.WriteStream = class TTYWriteStream extends Writable {
145
191
  this._pendingWriteBatch = batch
146
192
 
147
193
  try {
194
+ coerceBatch(batch)
195
+
148
196
  binding.writev(
149
197
  this._handle,
150
198
  batch.map(({ chunk }) => chunk)
@@ -155,22 +203,21 @@ exports.WriteStream = class TTYWriteStream extends Writable {
155
203
  }
156
204
 
157
205
  _predestroy() {
158
- if (this._state & constants.state.CLOSING) return
159
- this._state |= constants.state.CLOSING
206
+ this._close()
207
+ }
160
208
 
161
- binding.close(this._handle)
209
+ _destroy(err, cb) {
210
+ if (this._state & constants.state.CLOSED) return cb(err)
162
211
 
163
- TTYWriteStream._streams.delete(this)
212
+ this._pendingDestroy = cb
164
213
 
165
- if (TTYWriteStream._streams.size === 0) TTYWriteStream._resize.stop()
214
+ this._close()
166
215
  }
167
216
 
168
- _destroy(err, cb) {
169
- if (this._state & constants.state.CLOSING) return cb(err)
217
+ _close() {
218
+ if (this._state & constants.state.CLOSING) return
170
219
  this._state |= constants.state.CLOSING
171
220
 
172
- this._pendingDestroy = cb
173
-
174
221
  binding.close(this._handle)
175
222
 
176
223
  TTYWriteStream._streams.delete(this)
@@ -198,15 +245,22 @@ exports.WriteStream = class TTYWriteStream extends Writable {
198
245
  }
199
246
 
200
247
  _onclose() {
201
- this._handle = null
248
+ this._state |= constants.state.CLOSED
202
249
 
203
250
  this._continueDestroy()
204
251
  }
205
252
 
206
253
  _onresize() {
207
- this._size = this.getWindowSize()
254
+ let changed
208
255
 
209
- this.emit('resize')
256
+ try {
257
+ changed = this._refreshSize()
258
+ } catch (err) {
259
+ this.emit('error', err)
260
+ return
261
+ }
262
+
263
+ if (changed) this.emit('resize')
210
264
  }
211
265
 
212
266
  static _streams = new Set()
@@ -216,7 +270,11 @@ exports.WriteStream = class TTYWriteStream extends Writable {
216
270
 
217
271
  exports.constants = constants
218
272
 
219
- exports.isTTY = binding.isTTY
273
+ exports.errors = errors
274
+
275
+ exports.isTTY = function isTTY(fd) {
276
+ return isValidFd(fd) && binding.isTTY(fd)
277
+ }
220
278
 
221
279
  exports.isatty = exports.isTTY // For Node.js compatibility
222
280
 
@@ -228,4 +286,54 @@ exports.WriteStream._resize
228
286
  })
229
287
  .unref()
230
288
 
289
+ function isValidFd(fd) {
290
+ return typeof fd === 'number' && Number.isInteger(fd) && fd >= 0 && fd <= 0x7fffffff
291
+ }
292
+
293
+ function validateFd(fd) {
294
+ if (typeof fd !== 'number') {
295
+ throw errors.INVALID_FD(`File descriptor must be a number, got ${typeof fd}`)
296
+ }
297
+
298
+ if (!isValidFd(fd)) {
299
+ throw errors.INVALID_FD(
300
+ `File descriptor must be an integer between 0 and ${0x7fffffff}, got ${fd}`
301
+ )
302
+ }
303
+ }
304
+
305
+ function validateMode(mode) {
306
+ if (typeof mode !== 'number') {
307
+ throw errors.INVALID_ARGUMENT(`Mode must be a number, got ${typeof mode}`)
308
+ }
309
+
310
+ if (!modes.has(mode)) {
311
+ throw errors.INVALID_ARGUMENT(`Mode must be one of ${[...modes].join(', ')}, got ${mode}`)
312
+ }
313
+ }
314
+
315
+ function validateInteger(value, name, min, max) {
316
+ if (typeof value !== 'number') {
317
+ throw errors.INVALID_ARGUMENT(`${name} must be a number, got ${typeof value}`)
318
+ }
319
+
320
+ if (!Number.isInteger(value) || value < min || value > max) {
321
+ throw errors.INVALID_ARGUMENT(
322
+ `${name} must be an integer between ${min} and ${max}, got ${value}`
323
+ )
324
+ }
325
+ }
326
+
327
+ function coerceBatch(batch) {
328
+ for (let i = 0; i < batch.length; i++) {
329
+ const chunk = batch[i].chunk
330
+
331
+ if (ArrayBuffer.isView(chunk) === false) {
332
+ throw errors.INVALID_ARGUMENT(`Chunk must be a string or a view, got ${typeof chunk}`)
333
+ }
334
+
335
+ batch[i].chunk = Buffer.coerce(chunk)
336
+ }
337
+ }
338
+
231
339
  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.2",
3
+ "version": "5.2.1",
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",
@@ -44,6 +51,7 @@
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