Перейти к содержанию

REPL

latest

Стабильность: 2 – Стабильная

АПИ является удовлетворительным. Совместимость с NPM имеет высший приоритет и не будет нарушена кроме случаев явной необходимости.

Модуль node:repl реализует цикл Read-Eval-Print-Loop (REPL): его можно запускать отдельно или встраивать в другие приложения. Подключение:

1
import repl from 'node:repl';
1
const repl = require('node:repl');

Дизайн и возможности

Модуль node:repl экспортирует класс repl.REPLServer. Во время работы экземпляры repl.REPLServer принимают построчный ввод пользователя, вычисляют его заданной функцией оценки и выводят результат. Ввод и вывод могут идти из stdin и в stdout или подключаться к любому stream Node.js.

Экземпляры repl.REPLServer поддерживают автодополнение, предпросмотр дополнений, простое редактирование строк в стиле Emacs, многострочный ввод, reverse-i-search в духе ZSH, поиск по подстроке в истории в духе ZSH, вывод с ANSI-оформлением, сохранение и восстановление состояния сессии REPL, восстановление после ошибок и настраиваемые функции оценки. Терминалы без ANSI и без редактирования в стиле Emacs переходят на ограниченный набор функций.

Команды и специальные клавиши

Поддерживаются такие специальные команды:

  • .break: при вводе многострочного выражения команда .break (или Ctrl+C) прерывает дальнейший ввод или обработку выражения.
  • .clear: сбрасывает context REPL в пустой объект и очищает текущий многострочный ввод.
  • .exit: закрывает поток ввода-вывода и завершает REPL.
  • .help: показать список специальных команд.
  • .save: сохранить текущую сессию REPL в файл: > .save ./file/to/save.js
  • .load: загрузить файл в текущую сессию REPL. > .load ./file/to/load.js
  • .editor: режим редактора (Ctrl+D — завершить, Ctrl+C — отмена).
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
> .editor
// Entering editor mode (^D to finish, ^C to cancel)
function welcome(name) {
  return `Hello ${name}!`;
}

welcome('Node.js User');

// ^D
'Hello Node.js User!'
>

Сочетания клавиш в REPL:

  • Ctrl+C: одно нажатие — как команда .break; дважды на пустой строке — как .exit.
  • Ctrl+D: как .exit.
  • Tab на пустой строке — глобальные и локальные (область видимости) переменные; при вводе текста — варианты автодополнения.

Привязки клавиш для reverse-i-search см. в reverse-i-search. Остальные — в привязках клавиш TTY.

Оценка по умолчанию

По умолчанию все экземпляры repl.REPLServer используют функцию оценки, которая вычисляет выражения JavaScript и даёт доступ к встроенным модулям Node.js. Поведение можно переопределить, передав другую функцию оценки при создании repl.REPLServer.

Выражения JavaScript

Встроенный оценщик поддерживает прямое вычисление выражений JavaScript:

1
2
3
4
5
6
> 1 + 1
2
> const m = 2
undefined
> m + 1
3

Если иное не ограничено блоками или функциями, переменные, объявленные явно или через const, let, var, оказываются в глобальной области видимости.

Глобальная и локальная область

Встроенный оценщик даёт доступ к переменным глобальной области. Переменную можно явно вывести в REPL, присвоив её объекту context у REPLServer:

1
2
3
4
import repl from 'node:repl';
const msg = 'message';

repl.start('> ').context.m = msg;
1
2
3
4
const repl = require('node:repl');
const msg = 'message';

repl.start('> ').context.m = msg;

Свойства context в REPL выглядят как локальные:

1
2
3
$ node repl_test.js
> m
'message'

По умолчанию свойства context не только для чтения. Чтобы задать глобальные только для чтения, используйте Object.defineProperty():

1
2
3
4
5
6
7
8
9
import repl from 'node:repl';
const msg = 'message';

const r = repl.start('> ');
Object.defineProperty(r.context, 'm', {
  configurable: false,
  enumerable: true,
  value: msg,
});
1
2
3
4
5
6
7
8
9
const repl = require('node:repl');
const msg = 'message';

const r = repl.start('> ');
Object.defineProperty(r.context, 'm', {
  configurable: false,
  enumerable: true,
  value: msg,
});

Доступ к встроенным модулям Node.js

Встроенный оценщик подгружает встроенные модули Node.js в окружение REPL по мере использования. Например, если fs не объявлен иначе, ввод fs вычисляется по требованию как global.fs = require('node:fs').

1
> fs.createReadStream('./some/file');

Глобальные необработанные исключения

REPL использует модуль domain, чтобы перехватывать все необработанные исключения сессии.

Такое использование domain в REPL даёт такие эффекты:

  • Событие 'uncaughtException' для необработанных исключений испускается только в автономном REPL. Подписка на это событие внутри REPL, встроенного в другую программу Node.js, даёт ERR_INVALID_REPL_INPUT.
1
2
3
4
5
6
7
8
const r = repl.start();

r.write('process.on("uncaughtException", () => console.log("Foobar"));\n');
// Output stream includes:
//   TypeError [ERR_INVALID_REPL_INPUT]: Listeners for `uncaughtException`
//   cannot be used in the REPL

r.close();

Переменная _ (подчёркивание)

По умолчанию оценщик присваивает результат последнего вычисленного выражения специальной переменной _. Явное присваивание _ отключает это поведение.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
> [ 'a', 'b', 'c' ]
[ 'a', 'b', 'c' ]
> _.length
3
> _ += 1
Expression assignment to _ now disabled.
4
> 1 + 1
2
> _
4

Аналогично _error ссылается на последнюю ошибку, если она была. Явное присваивание _error отключает это поведение.

1
2
3
4
> throw new Error('foo');
Uncaught Error: foo
> _error.message
'foo'

Ключевое слово await

На верхнем уровне включена поддержка await.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
> await Promise.resolve(123)
123
> await Promise.reject(new Error('REPL await'))
Uncaught Error: REPL await
    at REPL2:1:54
> const timeout = util.promisify(setTimeout);
undefined
> const old = Date.now(); await timeout(1000); console.log(Date.now() - old);
1002
undefined

Известное ограничение: использование await в REPL нарушает лексическую область видимости для const.

Пример:

1
2
3
4
5
6
7
8
9
> const m = await Promise.resolve(123)
undefined
> m
123
> m = await Promise.resolve(234)
234
// redeclaring the constant does error
> const m = await Promise.resolve(345)
Uncaught SyntaxError: Identifier 'm' has already been declared

Флаг --no-experimental-repl-await отключает await верхнего уровня в REPL.

Обратный инкрементальный поиск

REPL поддерживает двунаправленный reverse-i-search в духе ZSH. Запуск: Ctrl+R — назад, Ctrl+S — вперёд.

Повторяющиеся записи истории пропускаются.

Запись принимается при нажатии любой клавиши, не относящейся к поиску. Отмена — Esc или Ctrl+C.

Смена направления сразу ищет следующую запись в новом направлении от текущей позиции.

Пользовательские функции оценки

При создании repl.REPLServer можно передать свою функцию оценки — например для полностью кастомного REPL.

Функция оценки принимает четыре аргумента:

  • code <string> Код для выполнения (например 1 + 1).
  • context <Object> Контекст выполнения — глобальный JavaScript или контекст экземпляра REPL в зависимости от useGlobal.
  • replResourceName <string> Идентификатор ресурса REPL для текущей оценки (удобно для отладки).
  • callback <Function> Вызывается по завершении оценки с двумя параметрами:
  • объект ошибки при ошибке оценки, иначе null/undefined;
  • результат оценки (не используется, если передана ошибка).

Ниже REPL возводит число в квадрат; при нечисловом вводе выводится ошибка:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import repl from 'node:repl';

function byThePowerOfTwo(number) {
  return number * number;
}

function myEval(code, context, replResourceName, callback) {
  if (isNaN(code)) {
    callback(new Error(`${code.trim()} is not a number`));
  } else {
    callback(null, byThePowerOfTwo(code));
  }
}

repl.start({ prompt: 'Enter a number: ', eval: myEval });
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const repl = require('node:repl');

function byThePowerOfTwo(number) {
  return number * number;
}

function myEval(code, context, replResourceName, callback) {
  if (isNaN(code)) {
    callback(new Error(`${code.trim()} is not a number`));
  } else {
    callback(null, byThePowerOfTwo(code));
  }
}

repl.start({ prompt: 'Enter a number: ', eval: myEval });

Восстанавливаемые ошибки

При нажатии Enter текущая строка передаётся в eval. Для многострочного ввода eval может возвращать экземпляр repl.Recoverable в callback:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
function myEval(cmd, context, filename, callback) {
  let result;
  try {
    result = vm.runInThisContext(cmd);
  } catch (e) {
    if (isRecoverableError(e)) {
      return callback(new repl.Recoverable(e));
    }
  }
  callback(null, result);
}

function isRecoverableError(error) {
  if (error.name === 'SyntaxError') {
    return /^(Unexpected end of input|Unexpected token)/.test(error.message);
  }
  return false;
}

Настройка вывода REPL

По умолчанию repl.REPLServer форматирует вывод через util.inspect() перед записью в переданный поток Writable (по умолчанию process.stdout). Опция showProxy в инспекции по умолчанию true, colors зависит от useColors у REPL.

Булево useColors при создании задаёт использование ANSI для цветного вывода util.inspect().

В автономном REPL можно менять параметры инспекции изнутри через inspect.replDefaults — зеркало defaultOptions из util.inspect().

1
2
3
4
5
6
7
> util.inspect.replDefaults.compact = false;
false
> [1]
[
  1
]
>

Чтобы полностью задать вывод repl.REPLServer, передайте свою функцию в опции writer. В примере весь текст переводится в верхний регистр:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import repl from 'node:repl';

const r = repl.start({ prompt: '> ', eval: myEval, writer: myWriter });

function myEval(cmd, context, filename, callback) {
  callback(null, cmd);
}

function myWriter(output) {
  return output.toUpperCase();
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const repl = require('node:repl');

const r = repl.start({ prompt: '> ', eval: myEval, writer: myWriter });

function myEval(cmd, context, filename, callback) {
  callback(null, cmd);
}

function myWriter(output) {
  return output.toUpperCase();
}

Класс: REPLServer

Экземпляры repl.REPLServer создаются через repl.start() или напрямую оператором new.

1
2
3
4
5
6
import repl from 'node:repl';

const options = { useColors: true };

const firstInstance = repl.start(options);
const secondInstance = new repl.REPLServer(options);
1
2
3
4
5
6
const repl = require('node:repl');

const options = { useColors: true };

const firstInstance = repl.start(options);
const secondInstance = new repl.REPLServer(options);

Событие: 'exit'

Событие 'exit' испускается при выходе из REPL: команда .exit, двойное Ctrl+C (SIGINT) или Ctrl+D ('end' на потоке ввода). Обработчик вызывается без аргументов.

1
2
3
4
replServer.on('exit', () => {
  console.log('Received "exit" event from repl!');
  process.exit();
});

Событие: 'reset'

Событие 'reset' испускается при сбросе контекста REPL — при вводе .clear, кроме случая встроенного оценщика и repl.REPLServer с useGlobal: true. Обработчику передаётся только ссылка на context.

Обычно используют для повторной инициализации контекста:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import repl from 'node:repl';

function initializeContext(context) {
  context.m = 'test';
}

const r = repl.start({ prompt: '> ' });
initializeContext(r.context);

r.on('reset', initializeContext);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const repl = require('node:repl');

function initializeContext(context) {
  context.m = 'test';
}

const r = repl.start({ prompt: '> ' });
initializeContext(r.context);

r.on('reset', initializeContext);

После запуска глобальную 'm' можно менять, а .clear вернёт начальное значение:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ ./node example.js
> m
'test'
> m = 1
1
> m
1
> .clear
Clearing context...
> m
'test'
>

replServer.defineCommand(keyword, cmd)

  • keyword <string> Ключевое слово команды (без ведущей .).
  • cmd <Object> | <Function> Функция, вызываемая при обработке команды.

replServer.defineCommand() добавляет новые команды с префиксом . в экземпляр REPL. Ввод: . и keyword. cmd — либо Function, либо Object со свойствами:

  • help <string> Текст для .help (необязательно).
  • action <Function> Выполняемая функция, опционально с одним строковым аргументом.

Пример двух новых команд:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import repl from 'node:repl';

const replServer = repl.start({ prompt: '> ' });
replServer.defineCommand('sayhello', {
  help: 'Say hello',
  action(name) {
    this.clearBufferedCommand();
    console.log(`Hello, ${name}!`);
    this.displayPrompt();
  },
});
replServer.defineCommand('saybye', function saybye() {
  console.log('Goodbye!');
  this.close();
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
const repl = require('node:repl');

const replServer = repl.start({ prompt: '> ' });
replServer.defineCommand('sayhello', {
  help: 'Say hello',
  action(name) {
    this.clearBufferedCommand();
    console.log(`Hello, ${name}!`);
    this.displayPrompt();
  },
});
replServer.defineCommand('saybye', function saybye() {
  console.log('Goodbye!');
  this.close();
});

Команды можно вызывать в REPL:

1
2
3
4
> .sayhello Node.js User
Hello, Node.js User!
> .saybye
Goodbye!

replServer.displayPrompt([preserveCursor])

replServer.displayPrompt() готовит REPL к вводу: выводит настроенный prompt на новой строке в output и возобновляет приём в input.

При многострочном вводе вместо prompt выводится '|'.

При preserveCursor: true позиция курсора не сбрасывается в 0.

Обычно вызывается из action команд, зарегистрированных через replServer.defineCommand().

replServer.clearBufferedCommand()

replServer.clearBufferedCommand() очищает буферизованную, но ещё не выполненную команду. Обычно вызывается из action команд replServer.defineCommand().

replServer.setupHistory(historyConfig, callback)

Добавлено в: v11.10.0

  • historyConfig <Object> | <string> Путь к файлу истории. Если строка — это путь к файлу. Если объект, возможны свойства:
  • filePath <string> путь к файлу истории
  • size <number> Максимум строк истории. Чтобы отключить историю, укажите 0. Имеет смысл только если terminal равен true (пользователем или проверкой output), иначе кэш истории не инициализируется. По умолчанию: 30.
  • removeHistoryDuplicates <boolean> При true при добавлении строки, дублирующей более старую, старая удаляется. По умолчанию: false.
  • onHistoryFileLoaded <Function> вызывается при готовности записи истории или при ошибке
  • callback <Function> то же (необязателен, если задан onHistoryFileLoaded в historyConfig)
  • err <Error>
  • repl <repl.REPLServer>

Инициализирует файл истории для экземпляра REPL. При запуске node с интерактивным REPL файл истории создаётся по умолчанию; при программном создании REPL — нет. Используйте этот метод для файла истории при программной работе с REPL.

repl.builtinModules

Стабильность: 0 – устарело или набрало много негативных отзывов

Используйте module.builtinModules вместо этого API.

Список имён части модулей Node.js, например 'http'.

Доступна автоматическая миграция (исходники):

1
npx codemod@latest @nodejs/repl-builtin-modules

repl.start([options])

Добавлено в: v0.1.91

  • options <Object> | <string>
  • prompt <string> Приглашение ввода. По умолчанию: '> ' (с пробелом в конце).
  • input <stream.Readable> Поток Readable для ввода REPL. По умолчанию: process.stdin.
  • output <stream.Writable> Поток Writable для вывода REPL. По умолчанию: process.stdout.
  • terminal <boolean> При true вывод output считается TTY. По умолчанию: проверка isTTY у output при создании.
  • eval <Function> Функция оценки каждой строки ввода. По умолчанию: асинхронная обёртка над eval(). Ошибка с repl.Recoverable означает неполный ввод и запрос дополнительных строк. См. раздел пользовательские функции вычисления.
  • useColors <boolean> При true встроенный writer добавляет ANSI-цвета к выводу. При своём writer не действует. По умолчанию: проверка цветов на output, если у репла terminal равен true.
  • useGlobal <boolean> При true встроенный оценщик использует JavaScript global, а не отдельный контекст REPL. CLI node задаёт true. По умолчанию: false.
  • ignoreUndefined <boolean> При true встроенный writer не выводит результат команды, если он undefined. По умолчанию: false.
  • writer <Function> Форматирование вывода каждой команды перед записью в output. По умолчанию: util.inspect().
  • completer <Function> Необязательно: своё автодополнение по Tab. Пример — readline.InterfaceCompleter.
  • replMode <symbol> Режим оценщика: strict или sloppy. Значения:
    • repl.REPL_MODE_SLOPPY — нестрогий режим.
    • repl.REPL_MODE_STRICT — строгий режим (как 'use strict' перед каждой строкой).
  • breakEvalOnSigint <boolean> Остановить оценку текущего кода при SIGINT (Ctrl+C). Несовместимо с пользовательским eval. По умолчанию: false.
  • preview <boolean> Печатать ли предпросмотр автодополнения и вывода. По умолчанию: true со встроенным eval и false с пользовательским eval. Если terminal ложен, предпросмотра нет, preview не влияет.
  • handleError <Function> Настройка обработки ошибок в REPL. Первый аргумент — исключение; синхронно вернуть одно из:
    • 'print' — вывести ошибку в поток вывода (по умолчанию).
    • 'ignore' — пропустить остальную обработку ошибки.
    • 'unhandled' — считать исключение полностью необработанным; оно уйдёт в глобальные обработчики, например 'uncaughtException'. 'unhandled' при уже закрытом REPLServer может быть нежелателен — зависит от задачи.
  • Возвращает: <repl.REPLServer>

repl.start() создаёт и запускает экземпляр repl.REPLServer.

Если options — строка, она задаёт приглашение:

1
2
3
4
import repl from 'node:repl';

// приглашение в стиле Unix
repl.start('$ ');
1
2
3
4
const repl = require('node:repl');

// приглашение в стиле Unix
repl.start('$ ');

REPL Node.js

Сам Node.js использует node:repl для интерактивного выполнения JavaScript. Запуск: бинарник node без аргументов (или с -i):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ node
> const a = [1, 2, 3];
undefined
> a
[ 1, 2, 3 ]
> a.forEach((v) => {
...   console.log(v);
...   });
1
2
3

Переменные окружения

Поведение REPL Node.js можно настроить переменными окружения:

  • NODE_REPL_HISTORY: при корректном пути история REPL сохраняется в этот файл, а не в .node_repl_history в домашнем каталоге. Пустая строка '' отключает сохранение. Пробелы по краям обрезаются. В Windows пустые значения переменных недопустимы — отключите историю одним или несколькими пробелами.
  • NODE_REPL_HISTORY_SIZE: сколько строк истории сохранять. Должно быть положительным числом. По умолчанию: 1000.
  • NODE_REPL_MODE: 'sloppy' или 'strict'. По умолчанию: 'sloppy' — код работает не в строгом режиме.

Постоянная история

По умолчанию REPL сохраняет историю между сессиями в файле .node_repl_history в домашнем каталоге. Отключение: NODE_REPL_HISTORY=''.

Продвинутые редакторы строк

Для внешних построчных редакторов запустите Node.js с NODE_NO_READLINE=1. Основной и отладочный REPL войдут в «канонический» режим терминала, можно использовать rlwrap.

Например, в .bashrc:

1
alias node="env NODE_NO_READLINE=1 rlwrap node"

Несколько REPL в одном процессе

Можно запустить несколько экземпляров REPL в одном процессе Node.js с общим global (useGlobal: true) и разными интерфейсами ввода-вывода.

Ниже — отдельные REPL на stdin, Unix-сокете и TCP-сокете с общим global:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import net from 'node:net';
import repl from 'node:repl';
import process from 'node:process';
import fs from 'node:fs';

let connections = 0;

repl.start({
  prompt: 'Node.js via stdin> ',
  useGlobal: true,
  input: process.stdin,
  output: process.stdout,
});

const unixSocketPath = '/tmp/node-repl-sock';

// Если файл сокета уже есть — удаляем
fs.rmSync(unixSocketPath, { force: true });

net.createServer((socket) => {
  connections += 1;
  repl.start({
    prompt: 'Node.js via Unix socket> ',
    useGlobal: true,
    input: socket,
    output: socket,
  }).on('exit', () => {
    socket.end();
  });
}).listen(unixSocketPath);

net.createServer((socket) => {
  connections += 1;
  repl.start({
    prompt: 'Node.js via TCP socket> ',
    useGlobal: true,
    input: socket,
    output: socket,
  }).on('exit', () => {
    socket.end();
  });
}).listen(5001);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const net = require('node:net');
const repl = require('node:repl');
const fs = require('node:fs');

let connections = 0;

repl.start({
  prompt: 'Node.js via stdin> ',
  useGlobal: true,
  input: process.stdin,
  output: process.stdout,
});

const unixSocketPath = '/tmp/node-repl-sock';

// Если файл сокета уже есть — удаляем
fs.rmSync(unixSocketPath, { force: true });

net.createServer((socket) => {
  connections += 1;
  repl.start({
    prompt: 'Node.js via Unix socket> ',
    useGlobal: true,
    input: socket,
    output: socket,
  }).on('exit', () => {
    socket.end();
  });
}).listen(unixSocketPath);

net.createServer((socket) => {
  connections += 1;
  repl.start({
    prompt: 'Node.js via TCP socket> ',
    useGlobal: true,
    input: socket,
    output: socket,
  }).on('exit', () => {
    socket.end();
  });
}).listen(5001);

Запуск из командной строки поднимает REPL на stdin. Другие клиенты могут подключаться через Unix- или TCP-сокет: для TCP удобен telnet, для Unix и TCP — socat.

REPL на Unix-сокете позволяет подключаться к долгоживущему процессу Node.js без перезапуска.

Примеры

Полноценный «терминальный» REPL поверх net.Server и net.Socket

Пример «полноценного» терминального REPL на net.Server и net.Socket.

Скрипт поднимает сервер на порту 1337, клиенты подключаются сокетом к экземпляру REPL.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// repl-server.js
import repl from 'node:repl';
import net from 'node:net';

net
  .createServer((socket) => {
    const r = repl.start({
      prompt: `socket ${socket.remoteAddress}:${socket.remotePort}> `,
      input: socket,
      output: socket,
      terminal: true,
      useGlobal: false,
    });
    r.on('exit', () => {
      socket.end();
    });
    r.context.socket = socket;
  })
  .listen(1337);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// repl-server.js
const repl = require('node:repl');
const net = require('node:net');

net
  .createServer((socket) => {
    const r = repl.start({
      prompt: `socket ${socket.remoteAddress}:${socket.remotePort}> `,
      input: socket,
      output: socket,
      terminal: true,
      useGlobal: false,
    });
    r.on('exit', () => {
      socket.end();
    });
    r.context.socket = socket;
  })
  .listen(1337);

Клиент ниже подключается к этому серверу на порту 1337.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// repl-client.js
import net from 'node:net';
import process from 'node:process';

const sock = net.connect(1337);

process.stdin.pipe(sock);
sock.pipe(process.stdout);

sock.on('connect', () => {
  process.stdin.resume();
  process.stdin.setRawMode(true);
});

sock.on('close', () => {
  process.stdin.setRawMode(false);
  process.stdin.pause();
  sock.removeListener('close', done);
});

process.stdin.on('end', () => {
  sock.destroy();
  console.log();
});

process.stdin.on('data', (b) => {
  if (b.length === 1 && b[0] === 4) {
    process.stdin.emit('end');
  }
});
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// repl-client.js
const net = require('node:net');

const sock = net.connect(1337);

process.stdin.pipe(sock);
sock.pipe(process.stdout);

sock.on('connect', () => {
  process.stdin.resume();
  process.stdin.setRawMode(true);
});

sock.on('close', () => {
  process.stdin.setRawMode(false);
  process.stdin.pause();
  sock.removeListener('close', done);
});

process.stdin.on('end', () => {
  sock.destroy();
  console.log();
});

process.stdin.on('data', (b) => {
  if (b.length === 1 && b[0] === 4) {
    process.stdin.emit('end');
  }
});

Для проверки откройте два терминала: в одном node repl-server.js, в другом node repl-client.js.

Исходный код: https://gist.github.com/TooTallNate/2209310.

REPL поверх curl

Пример запуска экземпляра REPL через curl().

Скрипт поднимает HTTP-сервер на порту 8000, соединение можно установить через curl().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import http from 'node:http';
import repl from 'node:repl';

const server = http.createServer((req, res) => {
  res.setHeader('content-type', 'multipart/octet-stream');

  repl.start({
    prompt: 'curl repl> ',
    input: req,
    output: res,
    terminal: false,
    useColors: true,
    useGlobal: false,
  });
});

server.listen(8000);
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
const http = require('node:http');
const repl = require('node:repl');

const server = http.createServer((req, res) => {
  res.setHeader('content-type', 'multipart/octet-stream');

  repl.start({
    prompt: 'curl repl> ',
    input: req,
    output: res,
    terminal: false,
    useColors: true,
    useGlobal: false,
  });
});

server.listen(8000);

При работающем скрипте подключитесь командой curl --no-progress-meter -sSNT. localhost:8000.

Предупреждение: пример только для демонстрации запуска REPL с разными потоками ввода-вывода. Не используйте в продакшене и там, где важна безопасность, без дополнительных мер. В реальных приложениях учитывайте риски: защищённый ввод, закрытые сетевые интерфейсы и т.д.

Исходный код: https://gist.github.com/TooTallNate/2053342.

Комментарии