← Все статьи

Node.js: Factory и Strategy для кросс-платформенного npm-пакета

Порт EADDRINUSE на Windows, macOS и Linux — без if/else по ОС. Strategy, Factory, TypeScript-контракты и защита от shell injection.

Содержание

Коротко

Кросс-платформенные CLI и утилиты на Node.js часто превращаются в лабиринт if (process.platform === …). На Dev.to показали, как паттерны Strategy и Factory собирают утилиту для освобождения порта (EADDRINUSE): команды разных ОС прячутся за интерфейсами, ядро остаётся тестируемым.

Что произошло

Типичная боль локальной разработки: сервер не стартует, потому что порт занят. На Windows нужны netstat и taskkill, на macOS и Linuxlsof и kill. Если размазать эти ветки по всему коду, нарушается принцип единственной ответственности, unit-тесты превращаются в моки shell, а каждая новая ОС — ещё один switch.

Автор выстраивает слой так. TypeScript-интерфейсы описывают контракт «найти процесс на порту / завершить» без строк shell в бизнес-логике. Factory выбирает платформенную реализацию Strategy. Исполнитель команд без состояния запускает argv-массивы и не склеивает пользовательский ввод в одну строку — меньше риска внедрения в shell.

Сверху — общий API и CLI на одном ядре: и программный вызов из другого пакета, и терминальная команда делят одну реализацию. Пример домена — «освободить порт перед dev-сервером», но тот же каркас подходит для любой environmental variance: разные бинарники, сетевые утилиты, обработка ошибок.

Почему это важно

npm-пакеты, которые «работают у меня на Mac», регулярно падают в Linux CI или у коллег на Windows. Вынос платформенных деталей в Strategy делает поведение предсказуемым и подменяемым в тестах — вы подставляете fake-реализацию, а не парсите настоящий вывод lsof.

Для full-stack команд это шаблон не только для CLI. Любой адаптер к внешним системам — облако, CI, локальный Docker — выигрывает от разделения «политика vs механизм»: ядро знает что нужно сделать, Strategy знает как на конкретной платформе.

На практике

  1. Опишите интерфейс домена (PortManager, FileLocker и т.п.) до первой shell-команды.
  2. Одна Factory на точку выбора платформы; не размазывайте platform по десяти файлам.
  3. Runner принимает массив аргументов, не строку exec(`kill ${pid}`) — особенно если pid когда-нибудь придёт извне.
  4. Тесты: fake Strategy с записью вызовов; интеграционные — только на целевой ОС в matrix CI.
  5. CLI (commander / yargs) — тонкая обёртка над тем же сервисом, что экспортирует "exports" в package.json.

Итог

Factory и Strategy в Node.js — не академическая схема, а способ держать кросс-платформенный npm-пакет читаемым. Материал на Dev.to с кодом полезен, если ваши утилиты раздуваются от switch (os.platform()).