- Home »

Интерактивные командные приглашения в Node.js — как использовать
Когда деплоишь очередной проект на своём сервере, частенько возникают ситуации, когда нужно быстро настроить что-то “на лету” или собрать информацию от пользователя прямо в консоли. И вот тут-то обычные console.log
и process.argv
уже не катят — нужно что-то более интерактивное. Сегодня разберём, как сделать ваши Node.js скрипты максимально user-friendly с помощью интерактивных командных приглашений.
Эта штука особенно полезна для системных администраторов и девопсов, которые пишут скрипты автоматизации, установщики приложений или утилиты для настройки серверов. Представьте — один скрипт, который может интерактивно настроить nginx, спросить у вас пароли, выбрать нужные модули и всё это с валидацией и красивым интерфейсом прямо в терминале.
Как это работает под капотом?
Интерактивные командные приглашения в Node.js работают через стандартные потоки ввода/вывода (stdin/stdout). Основная магия происходит в модуле readline
, который входит в стандартную библиотеку Node.js, но для продвинутых задач лучше использовать специализированные пакеты.
Принцип работы довольно простой:
- Скрипт отображает вопрос в терминале
- Приостанавливает выполнение и ждёт ввода пользователя
- Получает введённые данные, валидирует их
- Продолжает выполнение с полученными данными
Самые популярные библиотеки для этого дела:
- inquirer — самая мощная и функциональная
- prompts — лёгкая альтернатива с современным API
- readline-sync — простая синхронная библиотека
- enquirer — быстрая и стильная альтернатива inquirer
Пошаговая настройка с Inquirer
Начнём с самой популярной библиотеки — inquirer
. Она проверена временем и используется в куче известных проектов типа Angular CLI, Vue CLI и других.
Устанавливаем пакет:
npm install inquirer
# или
yarn add inquirer
Базовый пример использования:
const inquirer = require('inquirer');
async function setupServer() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'serverName',
message: 'Введите имя сервера:',
default: 'web-server-01'
},
{
type: 'list',
name: 'webServer',
message: 'Выберите веб-сервер:',
choices: ['nginx', 'apache', 'lighttpd'],
default: 'nginx'
},
{
type: 'checkbox',
name: 'modules',
message: 'Выберите модули для установки:',
choices: [
'SSL/TLS',
'Gzip compression',
'Rate limiting',
'Load balancing'
]
},
{
type: 'confirm',
name: 'autoStart',
message: 'Запускать сервер автоматически?',
default: true
}
]);
console.log('Настройки сервера:', answers);
}
setupServer();
Типы вопросов и их применение
Inquirer поддерживает множество типов вопросов, каждый из которых подходит для разных сценариев:
Тип | Описание | Когда использовать |
---|---|---|
input |
Обычный текстовый ввод | Имена серверов, пути к файлам |
password |
Скрытый ввод пароля | Пароли, API ключи |
list |
Выбор из списка | Выбор версии ПО, типа сервера |
checkbox |
Множественный выбор | Выбор модулей, плагинов |
confirm |
Да/Нет вопрос | Подтверждение действий |
number |
Числовой ввод | Порты, лимиты, таймауты |
Пример продвинутого использования с валидацией:
const questions = [
{
type: 'input',
name: 'port',
message: 'Введите порт для веб-сервера:',
default: '80',
validate: function(value) {
const port = parseInt(value);
if (isNaN(port) || port < 1 || port > 65535) {
return 'Порт должен быть числом от 1 до 65535';
}
if (port < 1024 && process.getuid() !== 0) {
return 'Для портов < 1024 нужны права root';
}
return true;
}
},
{
type: 'password',
name: 'dbPassword',
message: 'Введите пароль для базы данных:',
validate: function(value) {
if (value.length < 8) {
return 'Пароль должен быть минимум 8 символов';
}
return true;
}
},
{
type: 'list',
name: 'environment',
message: 'Выберите окружение:',
choices: ['production', 'staging', 'development'],
when: function(answers) {
// Показываем этот вопрос только если порт не 80
return answers.port !== '80';
}
}
];
Альтернативы: Prompts и Enquirer
Если inquirer кажется слишком тяжёлым (а он действительно довольно увесистый), есть более лёгкие альтернативы.
Prompts — современная библиотека с Promise-based API:
npm install prompts
const prompts = require('prompts');
const questions = [
{
type: 'text',
name: 'hostname',
message: 'Hostname сервера:'
},
{
type: 'select',
name: 'os',
message: 'Операционная система:',
choices: [
{ title: 'Ubuntu 20.04', value: 'ubuntu20' },
{ title: 'CentOS 8', value: 'centos8' },
{ title: 'Debian 11', value: 'debian11' }
]
}
];
(async () => {
const response = await prompts(questions);
console.log(response);
})();
Enquirer — быстрая альтернатива с красивым интерфейсом:
npm install enquirer
const { prompt } = require('enquirer');
const questions = [
{
type: 'input',
name: 'name',
message: 'Название проекта:'
},
{
type: 'multiselect',
name: 'services',
message: 'Выберите сервисы:',
choices: ['Redis', 'MongoDB', 'PostgreSQL', 'Elasticsearch']
}
];
prompt(questions)
.then(answers => console.log(answers))
.catch(console.error);
Практические кейсы использования
Вот несколько реальных примеров, где интерактивные приглашения просто незаменимы:
1. Установщик приложения
const inquirer = require('inquirer');
const fs = require('fs');
const path = require('path');
async function installApp() {
const config = await inquirer.prompt([
{
type: 'input',
name: 'appName',
message: 'Название приложения:',
validate: input => input.length > 0 || 'Название не может быть пустым'
},
{
type: 'input',
name: 'domain',
message: 'Домен:',
validate: input => {
const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9]\.[a-zA-Z]{2,}$/;
return domainRegex.test(input) || 'Введите корректный домен';
}
},
{
type: 'checkbox',
name: 'features',
message: 'Дополнительные возможности:',
choices: [
'SSL сертификат (Let\'s Encrypt)',
'Backup настройка',
'Monitoring (Prometheus)',
'Логирование (ELK Stack)'
]
}
]);
// Генерируем конфиг файл
const configContent = `
module.exports = {
appName: '${config.appName}',
domain: '${config.domain}',
features: ${JSON.stringify(config.features, null, 2)}
};
`;
fs.writeFileSync('./app.config.js', configContent);
console.log('✅ Конфигурация сохранена!');
}
installApp();
2. Генератор nginx конфигурации
async function generateNginxConfig() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'serverName',
message: 'Server name (домен):',
default: 'example.com'
},
{
type: 'input',
name: 'root',
message: 'Document root:',
default: '/var/www/html'
},
{
type: 'input',
name: 'upstreamPort',
message: 'Upstream порт:',
default: '3000'
},
{
type: 'confirm',
name: 'ssl',
message: 'Включить SSL?',
default: true
},
{
type: 'confirm',
name: 'gzip',
message: 'Включить Gzip?',
default: true
}
]);
const nginxConfig = `
server {
listen 80;
server_name ${answers.serverName};
root ${answers.root};
index index.html index.htm;
${answers.ssl ? `
listen 443 ssl http2;
ssl_certificate /etc/letsencrypt/live/${answers.serverName}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${answers.serverName}/privkey.pem;
` : ''}
${answers.gzip ? `
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
` : ''}
location / {
proxy_pass http://127.0.0.1:${answers.upstreamPort};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
`;
fs.writeFileSync(`/etc/nginx/sites-available/${answers.serverName}`, nginxConfig);
console.log(`✅ Конфигурация nginx создана для ${answers.serverName}`);
}
generateNginxConfig();
Продвинутые возможности
Самое интересное начинается, когда комбинируешь интерактивные приглашения с другими инструментами:
Интеграция с Docker
const inquirer = require('inquirer');
const { exec } = require('child_process');
const util = require('util');
const execAsync = util.promisify(exec);
async function deployWithDocker() {
const config = await inquirer.prompt([
{
type: 'input',
name: 'imageName',
message: 'Название Docker образа:'
},
{
type: 'input',
name: 'containerName',
message: 'Название контейнера:'
},
{
type: 'input',
name: 'port',
message: 'Порт для проброса:',
default: '80'
},
{
type: 'editor',
name: 'envVars',
message: 'Environment переменные (каждая с новой строки):',
default: 'NODE_ENV=production\nPORT=3000'
}
]);
// Парсим переменные окружения
const envVars = config.envVars.split('\n')
.filter(line => line.trim())
.map(line => `-e ${line.trim()}`)
.join(' ');
const dockerCommand = `docker run -d --name ${config.containerName} -p ${config.port}:${config.port} ${envVars} ${config.imageName}`;
console.log('Выполняем команду:', dockerCommand);
try {
const { stdout } = await execAsync(dockerCommand);
console.log('✅ Контейнер запущен:', stdout);
} catch (error) {
console.error('❌ Ошибка при запуске:', error.message);
}
}
deployWithDocker();
Создание CLI инструмента
#!/usr/bin/env node
const inquirer = require('inquirer');
const commander = require('commander');
const program = new commander.Command();
program
.name('server-setup')
.description('Утилита для настройки сервера')
.version('1.0.0');
program
.command('init')
.description('Интерактивная настройка сервера')
.action(async () => {
const answers = await inquirer.prompt([
{
type: 'list',
name: 'action',
message: 'Что настраиваем?',
choices: [
'Новый веб-сервер',
'База данных',
'Load balancer',
'Monitoring'
]
}
]);
switch (answers.action) {
case 'Новый веб-сервер':
await setupWebServer();
break;
case 'База данных':
await setupDatabase();
break;
// ... другие случаи
}
});
program.parse();
Сравнение популярных библиотек
Библиотека | Размер | Типы вопросов | Плюсы | Минусы |
---|---|---|---|---|
inquirer | ~500KB | 8 типов | Много возможностей, большое сообщество | Тяжёлая, медленная загрузка |
prompts | ~50KB | 15 типов | Лёгкая, современный API | Меньше документации |
enquirer | ~100KB | 20+ типов | Быстрая, красивая | Иногда глючит в разных терминалах |
readline-sync | ~30KB | 5 типов | Очень простая | Синхронная, блокирует поток |
Интересные факты и нестандартные применения
Вот несколько креативных способов использования интерактивных приглашений:
- Интерактивное логирование — можно создать скрипт, который при ошибке спрашивает у разработчика дополнительную информацию и автоматически создаёт тикет в Jira
- Динамические конфигурации — скрипт может подключаться к серверу, анализировать его характеристики и предлагать оптимальные настройки
- Мониторинг в реальном времени — создайте интерактивный dashboard прямо в терминале с возможностью управления сервисами
- Автоматизация деплоя — скрипт может спрашивать у вас, какую версию деплоить, на какие сервера, и выполнять все необходимые проверки
Пример интерактивного мониторинга
const inquirer = require('inquirer');
const { exec } = require('child_process');
async function serverMonitoring() {
while (true) {
const action = await inquirer.prompt([
{
type: 'list',
name: 'command',
message: 'Выберите действие:',
choices: [
'Показать нагрузку CPU',
'Показать использование памяти',
'Показать активные процессы',
'Перезапустить сервис',
'Выход'
]
}
]);
switch (action.command) {
case 'Показать нагрузку CPU':
exec('top -bn1 | grep "Cpu(s)" | awk \'{print $2}\' | cut -d\'%\' -f1', (err, stdout) => {
console.log(`CPU загрузка: ${stdout.trim()}%`);
});
break;
case 'Перезапустить сервис':
const service = await inquirer.prompt([
{
type: 'input',
name: 'serviceName',
message: 'Название сервиса:'
}
]);
exec(`sudo systemctl restart ${service.serviceName}`, (err, stdout) => {
if (err) {
console.error('Ошибка:', err.message);
} else {
console.log(`✅ Сервис ${service.serviceName} перезапущен`);
}
});
break;
case 'Выход':
return;
}
}
}
serverMonitoring();
Автоматизация и интеграция с другими инструментами
Интерактивные приглашения открывают массу возможностей для автоматизации рутинных задач. Вот как можно интегрировать их с популярными инструментами:
Интеграция с PM2
async function pm2Setup() {
const config = await inquirer.prompt([
{
type: 'input',
name: 'appName',
message: 'Название приложения:'
},
{
type: 'input',
name: 'script',
message: 'Путь к скрипту:',
default: 'app.js'
},
{
type: 'number',
name: 'instances',
message: 'Количество инстансов:',
default: 1
},
{
type: 'list',
name: 'mode',
message: 'Режим запуска:',
choices: ['fork', 'cluster']
}
]);
const pm2Config = {
name: config.appName,
script: config.script,
instances: config.instances,
exec_mode: config.mode,
env: {
NODE_ENV: 'production'
}
};
fs.writeFileSync('ecosystem.config.js', `module.exports = { apps: [${JSON.stringify(pm2Config, null, 2)}] };`);
console.log('PM2 конфигурация создана!');
}
pm2Setup();
Создание SSL сертификатов
async function setupSSL() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'domain',
message: 'Домен для SSL сертификата:'
},
{
type: 'input',
name: 'email',
message: 'Email для Let\'s Encrypt:'
},
{
type: 'confirm',
name: 'staging',
message: 'Использовать staging сервер? (для тестов)',
default: false
}
]);
const certbotCommand = `certbot certonly --nginx -d ${answers.domain} --email ${answers.email} --agree-tos --non-interactive ${answers.staging ? '--staging' : ''}`;
console.log('Получаем SSL сертификат...');
exec(certbotCommand, (error, stdout, stderr) => {
if (error) {
console.error('Ошибка получения сертификата:', error.message);
return;
}
console.log('✅ SSL сертификат получен!');
console.log(stdout);
});
}
setupSSL();
Если планируете активно использовать такие скрипты на продакшене, рекомендую взять себе нормальный VPS или даже выделенный сервер — на shared хостинге особо не поразвернёшься с Node.js скриптами.
Полезные ссылки
Заключение и рекомендации
Интерактивные командные приглашения — это мощный инструмент для создания user-friendly скриптов автоматизации. Они особенно полезны в следующих случаях:
- Для системных администраторов — создание установщиков, конфигураторов, утилит мониторинга
- Для DevOps — автоматизация деплоя, настройка CI/CD пайплайнов
- Для разработчиков — создание CLI инструментов, генераторов кода
Мои рекомендации по выбору библиотеки:
- Используйте inquirer, если нужна максимальная функциональность и стабильность
- Выбирайте prompts для лёгких и быстрых скриптов
- Берите enquirer, если важен красивый интерфейс
- Используйте readline-sync только для простейших задач
Помните про валидацию пользовательского ввода — это критически важно для продакшен скриптов. Всегда предусматривайте обработку ошибок и предоставляйте понятные сообщения пользователю.
И последний совет — документируйте свои скрипты! Через полгода вы сами забудете, что делает ваш "быстрый скрипт для настройки сервера" 😄
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.