Многие пакеты представляют из себя приложения командной строки, так называемые cli-утилиты, взаимодействие (запуск команд, передача аргументов и опций, вывод результатов) которых с пользователем происходит интерактивным образом через терминал. Для обеспечения такой возможности (запуск файлов из командной строки) в npm существует секция bin конфигурационного файла package.json, например:

"bin": {
    "hexlet": "dist/bin/hexlet.js"
}

Эта запись означает, что при установке пакета в операционной системе будет физически создан файл (а точнее, его особая разновидность — символическая ссылка, symlink) с именем hexlet, запуск которого приведёт к запуску файла вашего проекта по адресу dist/bin/hexlet.js. Также обратите внимание, что количество ссылок может быть множество (столько, сколько сами укажете в секции bin).

Месторасположение символической ссылки и способ её запуска из командной строки различаются в зависимости от способа установки пакета: глобального (с флагом -g: npm -g install packageName) или локального. Рассмотрим каждый случай отдельно.

Глобальная установка: генерация ссылок и запуск исполняемых файлов

При глобальной установке пакета npm создаёт символические ссылки в каталоге по умолчанию /usr/bin (или /usr/local/bin, в разных системах значение может отличаться, кроме того оно может конфигурироваться с помощью npm). Это каталог для прикладных программ общего назначения, распространённый в операционных системах семейства UNIX, и путь к нему прописан в переменной окружения PATH. Именно поэтому мы можем запускать приложение по имени символической ссылки из командной строки, находясь в любой точке файловой системы (ведь при поиске исполняемых файлов командная оболочка ищет их последовательно по всем путям, прописанных в переменной окружения PATH).

Посмотреть, в какую директорию npm складывает ссылки на исполняемые файлы можно с помощью команды npm bin -g или npm bin --global:

Посмотреть содержание переменной окружения PATH в вашей системе можно так:

Если вы пользуетесь системой управления версиями nvm (Node Version Manager, позволяет удобно использовать разные версии nodejs в рамках одной операционной системы), то каталог для символических ссылок может быть другой, например:

Как видно, для каждой версии nodejs менеджер nvm создаёт отдельный каталог bin для ссылок на исполняемые файлы устанавливаемых на нём пакетов. Но суть от этого не меняется — самое главное, чтобы путь к каталогу был в переменной окружения PATH (в данном случае nvm автоматически добавляет путь к каталогу в PATH).

Пример

Давайте создадим и опубликуем пакет, который для краткости и наглядности будет очень простым, состоящим всего из двух исполняемых файлов, запуск которых выводит соответственно приветствие и прощание с Хекслетом.

Структура проекта:

~/projects/smallTalkWithHexlet$ ls -al
drwxrwxr-x  2 hex hex 4096 июн 12 16:35 ./
drwxrwxr-x 18 hex hex 4096 июн 12 16:22 ../
-rw-rw-r--  1 hex hex  340 июн 12 16:49 package.json
-rwxrwxr-x  1 hex hex   69 июн 12 16:29 sayBye.js*
-rwxrwxr-x  1 hex hex   52 июн 12 16:31 sayHi.js*

Содержимое исполняемого файла sayHi.js:

~/projects/smallTalkWithHexlet$ cat sayHi.js
#!/usr/bin/env node

console.log('Hello, Hexlet!');

Содержимое исполняемого файла sayBye.js:

~/projects/smallTalkWithHexlet$ cat sayBye.js
#!/usr/bin/env node

console.log('Bye-bye! See you later, Hexlet!');

Содержимое конфигурационнного файла package.json:

~/projects/smallTalkWithHexlet$ cat package.json
{
  "name": "small_talk_with_hexlet",
  "version": "1.0.0",
  "description": "Small talk with Hexlet",
  "main": "",
  "bin": {
    "sayHi": "./sayHi.js",
    "sayBye": "./sayBye.js"
  },
  "keywords": [
    "hexlet"
  ],
  "author": "hex",
  "license": "MIT"
}

Небольшое отступление про исполняемые файлы:

  1. Для возможности запуска файла из командной строки у пользователя должно быть право на выполнение (атрибут x) этого файла.
  2. Если исполняемый файл содержит код, то надо указать командной оболочке интерпретатор, который будет исполнять этот код при запуске исполняемого файла. Это делается с помощью так называемого шебанга. В примерах выше (#!/usr/bin/env node) мы указали в качестве интерпретатора node, а путь к нему задали не абсолютный (в разных системах node может лежать по совершенно разным путям), а с помощью специальной утилиты env.

Итак, пакет опубликован и теперь доступен для установки под именем small_talk_with_hexlet (если решите опубликовать аналогичный пакет, то вам надо будет придумать своё уникальное имя для него).

Теперь установим этот пакет в систему глобально.

Но сначала убедимся, что никаких символических ссылок в директории не существует.

Здесь с помощью фильтра grep мы попытались найти файлы, содержащие в своём имени (как наши ссылки в секции bin) строчку "say". Но поиск не дал результатов, потому что таких файлов (до установки пакета) нет.

Далее глобально устанавливаем пакет в систему:

Как видно из лога установки выше, создалось две символические ссылки с указанием на какие файлы приложения они ведут.

Теперь снова проверим с помощью grep директорию установки исполняемых файлов.

Как и ожидалось, в этом каталоге лежат символические ссылки (об этом свидетельствует первый символ l, определяющий тип файла в строке атрибутов файла lrwxrwxrwx), которые мы можем запускать из любой точки файловой системы:

Из примера видно, что мы можем успешно запускать одни и те же команды из разных директорий. Почему так происходит подробно обсуждалось выше.

Что происходит при локальной установке

В подавляющем большинстве случаев для целей разработки пакеты устанавливаются не глобально, а локально. При такой установке они "привязываются" к конкретному проекту и размещаются внутри его каталога по пути ./node_modules/. При этом ссылки на исполняемые файлы устанавливаемых пакетов npm размещает в каталоге ./node_modules/.bin.

Ссылки на исполняемые файлы локально установленных пакетов "заточены" на использование их в скриптах (секция scripts конфигурационного файла package.json), для чего существует особенный синтаксис. Эту тему мы проходили в одном из предыдущих уроков, посвящённых скриптам.

Естественно, к символическим ссылкам можно также обратиться напрямую, указав нужный путь. Давайте рассмотрим это на примере, подключив к нашему разрабатываемому проекту small_talk_with_hexlet пакет babel-node:

~/projects/smallTalkWithHexlet$ npm install --save-dev @babel/node @babel/core

Если в корневом каталоге проекта установить какой-либо пакет с флагом --save-dev (npm i some_package --save-dev) — то он автоматически добавится в зависимости проекта: в раздел devDependencies файла package.json. Этой возможностью мы и воспользовались.

Как и ожидалось, появился каталог ./node_modules/, в котором лежит код подключенного пакета, а также других пакетов, от которых он, в свою очередь, зависит.

Узнать место, где npm складывает ссылки на исполняемые файлы локально подключаемых пакетов, позволяет команда npm bin (обратите внимание, что здесь нет параметра --global или -g). В нашем случае это каталог ./node_modules/.bin, заглянем в него:

Давайте запустим babel-node (REPL-утилита, позволяющая "на лету" комплировать и исполнять новый ES6 код):

Программа отработала корректно — мы успешно сделали вычисление в REPL-режиме babel-node.

Затем была попытка запустить исполняемый файл из командной строки только по имени, но она привела к неудаче — bash: babel-node: command not found — командная оболочка просто не нашла его (в отличие от того, как это было бы при глобальной установке).

Установка пакета из локального источника

В npm существует команда npm link. Она выполняет глобальную установку пакета, который расположен в операционной системе пользователя, и создаёт необходимые символические ссылки для запуска приложения из любой точки файловой системы.

Заключение

В этом уроке мы рассмотрели как общие принципы, так и отдельные нюансы работы Npm с исполняемыми файлами проекта. Важно отметить, что эти принципы характерны для большинства других пакетных менеджеров из разных языков (например, composer в php). Поэтому, если в будущем вам придётся столкнуться с чем-то из них, то это не должно вызвать затруднений.


Дополнительные материалы

  1. npm link
Мы учим программированию с нуля до стажировки и работы. Попробуйте наш бесплатный курс «Введение в программирование» или полные программы обучения по Node, PHP, Python и Java.

Хекслет

Подробнее о том, почему наше обучение работает →