¿Cómo usar ejecutables de un paquete instalado localmente en node_modules?

493

¿Cómo uso una versión local de un módulo en node.js ? Por ejemplo, en mi aplicación, instalé coffee-script:

npm install coffee-script

Esto lo instala ./node_modulesy el comando coffee está en./node_modules/.bin/coffee . ¿Hay alguna manera de ejecutar este comando cuando estoy en la carpeta principal de mi proyecto? Supongo que estoy buscando algo similar a bundle execen bundler. Básicamente, me gustaría especificar una versión de coffee-script que todos los involucrados en el proyecto deberían usar.

Sé que puedo agregar la -gbandera para instalarlo globalmente para que el café funcione bien en cualquier lugar, pero ¿y si quisiera tener diferentes versiones de café por proyecto?

typeoneerror
fuente
99
Muchas instrucciones que leo dicen cosas como npm install niftycommandy luego niftycommand. Pero esto nunca funcionará a menos que tenga ./node_modules/.bin en su camino, ¿verdad?
Bennett McElwee
2
Aquí hay una muy buena redacción: firstdoit.com/… - Básicamente, recomienda que coloque su coffeecomando en la npm scriptssección, como "build": "coffee -co target/directory source/directoy", so you can run npm run build` desde el terminal después.
Benny Neugebauer
@BennyNeugebauer, de hecho, eso es lo que he estado haciendo últimamente en lugar de jugar con PATH
typeoneerror
12
Uso npxque viene con npm 5.2.0 medium.com/@maybekatz/…
onmyway133

Respuestas:

568

ACTUALIZACIÓN : Como Seyeong Jeong señala en su respuesta a continuación, desde npm 5.2.0 puede usar npx [command], lo cual es más conveniente.

ANTIGUA RESPUESTA para versiones anteriores a 5.2.0 :

El problema con poner

./node_modules/.bin

en su RUTA es que solo funciona cuando su directorio de trabajo actual es la raíz de la estructura de directorios de su proyecto (es decir, la ubicación de node_modules )

Independientemente de cuál sea su directorio de trabajo, puede obtener la ruta de los binarios instalados localmente con

npm bin

Para ejecutar un coffeebinario instalado localmente, independientemente de dónde se encuentre en la jerarquía del directorio del proyecto, puede usar esta construcción bash

PATH=$(npm bin):$PATH coffee

Alias ​​esto a npm-exec

alias npm-exec='PATH=$(npm bin):$PATH'

Entonces ahora puedo

npm-exec coffee

para ejecutar la copia correcta de café sin importar dónde esté

$ pwd
/Users/regular/project1

$ npm-exec which coffee
/Users/regular/project1/node_modules/.bin/coffee

$ cd lib/
$ npm-exec which coffee
/Users/regular/project1/node_modules/.bin/coffee

$ cd ~/project2
$ npm-exec which coffee
/Users/regular/project2/node_modules/.bin/coffee
regular
fuente
17
incluso puede ir un paso más allá yalias coffee="npm-exec coffee"
el
66
La salida cambia cuando se cd en otro proyecto. No cambia cuando cd dentro de un proyecto. npm binbusca en la cadena de 'directorios ancestros' al cwd un directorio node_modules. Este es exactamente el comportamiento deseado si desea utilizar específicamente los binarios de los módulos enumerados en el paquete del proyecto .json.
Regular
11
¡oh Dios mío! ¿Realmente tengo que hacer algo así para que mis módulos locales funcionen? ¡Es muy poco práctico explicarlo a un equipo! no hay nada un poco más directo?
Alexian
17
Siempre puede usar scripts npm ya que siempre buscan primero los binarios locales. Puede configurar alias para cada uno de sus binarios allí o simplemente usar nombres genéricos como "compilar".
Joe Zim
66
@philosodad, en realidad no, no lo haces. El PATHhabrá de nuevo a lo que era antes de la invocación de mandato. Establecer una variable de entorno en la misma línea, antes de ejecutar un comando, solo afecta el entorno de ese comando.
regular de
410

Buen ejemplo

¡Ya no tienes que manipular $PATH!

Desde [email protected] , npm se envía con un npxpaquete que le permite ejecutar comandos desde un localnode_modules/.bin caché o central.

Simplemente ejecute:

$ npx [options] <command>[@version] [command-arg]...

Por defecto, npxverificará si <command>existe $PATHo en los binarios locales del proyecto y lo ejecutará.

Si llama npx <command>cuando aún <command>no está en su $PATH, se instalará automáticamente un paquete con ese nombre desde el registro de NPM y lo invocará. Cuando termine, el paquete instalado no estará en ninguna parte de sus globales, por lo que no tendrá que preocuparse por la contaminación a largo plazo. Puede evitar este comportamiento al proporcionar la --no-installopción.

Para npm < 5.2.0, puede instalar el npxpaquete manualmente ejecutando el siguiente comando:

$ npm install -g npx
Seyeong Jeong
fuente
1
No me gusta instalar paquetes npm globales de terceros npmy package.jsonproporciona casi la misma funcionalidad.
guneysus
Si aparece el mensaje "La ruta debe ser una cadena. Recibido indefinido", aquí hay una solución: github.com/zkat/npx/issues/144#issuecomment-391031816
Valeriy Katkov
1
Esta respuesta es buena. Pero solo quiero decir que npxes cojo. Debería haber sido npm runo npm execo algo así.
William Entriken
@WilliamEntriken Por algunas razones, npm run [my-local-package]no funciona en mi Ubuntu, aunque parecía funcionar en un dispositivo con Windows.
Clockwork
97

Use el npm bincomando para obtener el directorio de módulos de nodo / bin de su proyecto

$ $(npm bin)/<binary-name> [args]

p.ej

$ $(npm bin)/bower install
jassa
fuente
44
Me gusta esta solución simple y genérica. Hace que un alias parezca innecesario.
Matt Montag
Parece ser la mejor solución que es elegante y más seguro que tener que hacerloexport PATH="./node_modules/.bin:$PATH"
jontsai
1
@ inf3rno el comando $(npm bin)/jasmineno lo es node $(npm bin)/jasmine(probablemente lo descubriste pero aclaraste a los demás).
jassa
55
No es una mala solución, pero no se ejecuta en una línea de comandos estándar de Windows con $. Ponerlo en la sección de scripts package.json es un mejor enfoque, ya que es más compatible.
Timothy Gonzalez
77

Utilizar npm run[-script] <script name>

Después de usar npm para instalar el paquete bin en su ./node_modulesdirectorio local , modifíquelo package.jsonpara agregarlo <script name>así:

$ npm install --save learnyounode
$ edit packages.json
>>> in packages.json
...
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "learnyounode": "learnyounode"
},
...
$ npm run learnyounode

Sería bueno si npm install tuviera una opción --add-script o algo así o si npm run funcionaría sin agregar al bloque de scripts.

jla
fuente
55
Encontré que este enfoque es más uniforme cuando se trata con múltiples desarrolladores en un proyecto: evita la necesidad de configurar cualquier cosa localmente ... en npm installese momento tiene acceso a sus dependencias de desarrollo. El único inconveniente menor es que necesitas npm run eslint(o lo que sea). Puede crear una secuencia de comandos llamada "inicio" que ejecute gulp para que solo necesite escribir npm startpara iniciar su servidor de desarrollo. Cosas geniales y nada de bondad, así que a tus amigos de Windows todavía les gustas. :)
jpoveda
1
añadiendo un alias para poner $ (bin NPM) en su camino es inteligente, pero el hecho de que esto funcionará para las personas sin configuración local, gana mi corazón
Conrad.Dean
12
¡Esto necesita más votos a favor! Pase argumentos a sus guiones después --como:npm run learnyounode -- --normal-switches --watch -d *.js
ptim
También encuentro esta la mejor solución. Aquí hay una explicación detallada: lostechies.com/derickbailey/2012/04/24/…
adampasz
1
Esto es lo que generalmente uso, pero por alguna razón, en un dispositivo Ubuntu, npm run ts-nodeno funciona para mí. Solo tendré que volver a ordenar a npx.
Clockwork
42

Utilizar npm-run .

Del léame:

npm-run

Encuentra y ejecuta ejecutables locales desde node_modules

Cualquier ejecutable disponible para un script de ciclo de vida npm está disponible para npm-run .

Uso

$ npm install mocha # mocha installed in ./node_modules
$ npm-run mocha test/* # uses locally installed mocha executable 

Instalación

$ npm install -g npm-run
mightyiam
fuente
8
Ya no, vea npx al que se hace referencia anteriormente ... stackoverflow.com/a/45164863/3246805
tj
41

Actualización: ya no recomiendo este método, tanto por las razones de seguridad mencionadas como por las más recientes.npm bin comando . Respuesta original a continuación:

Como descubrió, cualquier binario instalado localmente está en ./node_modules/.bin. Para ejecutar siempre los archivos binarios en este directorio en lugar de los archivos binarios disponibles a nivel mundial, si está presente, le sugiero que ponga ./node_modules/.binprimero en su ruta:

export PATH="./node_modules/.bin:$PATH"

Si pone esto en su ~/.profile, coffeesiempre estará ./node_modules/.bin/coffeedisponible, de lo contrario /usr/local/bin/coffee(o cualquier prefijo en el que esté instalando los módulos de nodo).

Linus Gustav Larsson Thiel
fuente
1
Esa es probablemente la mejor solución. También creé un script bash llamado "watch" en mi proyecto:./node_modules/.bin/coffee --output lib/ --compile --bare --watch src
typeoneerror
72
¡Peligro, Will Robinson! El uso de rutas relativas en su $ PATH abre un agujero de seguridad del tamaño de un planeta, especialmente si los coloca al frente como el primer elemento. Si el directorio que está en ser escrito por todo el mundo (en algún lugar de decir /tmp), cualquier proceso o usuario puede secuestrar la sesión poniendo versiones maliciosas de los comandos ordinarios (como ls, cp, etc.) allí. Estos pueden generar subcapas 'invisibles' que capturan sus contraseñas, etc.
ack
solo funcionará en la raíz y no en otros lugares. El alias npm-exec='PATH=$(npm bin):$PATH'es más elegante.
oligofren
1
¿Qué tan malo es esto si no lo pones como la primera cosa en tu PATH, pero la última (usando el $(npm bin)formulario)? para que no puedan sobrescribir sus cosas existentes, y ya habría estado confiando en los ejecutables en el npm bindirectorio independientemente de la PATHvar; ¿sería el modelo de amenaza que a) alguien malicioso obtiene acceso a su sistema de archivos, b) agregan ejecutables con nombres cercanos a esas herramientas del sistema, yc) escribe mal? Tratando de comprender los escenarios que hacen que esto sea malo, dado que ya confía en ejecutables extranjeros cuando utiliza npmprogramas instalados.
osdiab
Puede hacer trucos de shell con un alias y puede trazar manualmente y esto "funciona", pero no es lo ideal.
Killscreen
22

La solución PATH tiene el problema de que si $ (npm bin) se coloca en su .profile / .bashrc / etc, se evalúa una vez y se establece para siempre en el directorio en el que se evaluó la ruta por primera vez. Si en cambio modifica la ruta actual, entonces Cada vez que ejecute el script, su camino crecerá.

Para solucionar estos problemas, creo una función y la utilicé. No modifica su entorno y es fácil de usar:

function npm-exec {
   $(npm bin)/$@  
}

Esto se puede usar así sin hacer ningún cambio en su entorno:

npm-exec r.js <args>
Bob9630
fuente
2
¡Me gusta esto! Simplemente nombré mi funciónn
jontsai
¡Esto es genial! Gracias por compartir. Agregué una versión de concha de pescado a continuación.
LeOn - Han Li
22

Si desea mantener npm, entonces npx debe hacer lo que necesita.


Si cambiar a hilo (un reemplazo de npm por facebook) es una opción para usted, puede llamar a:

 yarn yourCmd

las secuencias de comandos dentro del paquete.json tendrán prioridad, si no se encuentra ninguna, se verá dentro de ./node_modules/.bin/ carpeta.

También genera lo que ejecutó:

$ yarn tsc
yarn tsc v0.27.5
$ "/home/philipp/rate-pipeline/node_modules/.bin/tsc"

Por lo tanto, no tiene que configurar scripts para cada comando en su package.json.


Si tenía un script definido .scriptsdentro de su package.json:

"tsc": "tsc" // each command defined in the scripts will be executed from `./node_modules/.bin/` first

yarn tscsería equivalente a yarn run tsco npm run tsc:

 yarn tsc
 yarn tsc v0.27.5
 $ tsc
k0pernikus
fuente
14

actualización: si está en el npm reciente (versión> 5.2)

Puedes usar:

npx <command>

npxbusca el comando en el .bindirectorio de sunode_modules

vieja respuesta:

Para ventanas

Almacene lo siguiente en un archivo llamado npm-exec.baty agréguelo a su%PATH%

@echo off
set cmd="npm bin"
FOR /F "tokens=*" %%i IN (' %cmd% ') DO SET modules=%%i
"%modules%"\%*

Uso

Entonces puedes usarlo como npm-exec <command> <arg0> <arg1> ...

Por ejemplo

Para ejecutar wdioinstalado en el directorio local node_modules, haga:

npm-exec wdio wdio.conf.js

es decir, se ejecutará .\node_modules\.bin\wdio wdio.conf.js

Dheeraj Bhaskar
fuente
Esto no funciona al pasar más de 1 argumento. Por ejemplo, npm-exec gulp <some_task>
OK999
@ OK9999 Estoy seguro de que alguna modificación menor permitirá pasar argumentos (porque cuando la pasa aquí, aparece entre comillas en ""); Lo que sugiero es que copie y pegue el archivo gulp desde bin a la raíz del proyecto (se necesitan algunas modificaciones del archivo, pero simplemente funcionará sin escribir un nuevo código, etc.)
Dheeraj Bhaskar
Sí, terminé haciéndolo. La carpeta node_modules debe estar en la carpeta donde existe el archivo Gulpfile
OK999
7

Prefiero no confiar en alias de shell u otro paquete.

Al agregar una línea simple a la scriptssección de su package.json, puede ejecutar comandos locales npm como

npm run webpack

package.json

{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "webpack": "webpack"
  },
  "devDependencies": {
    "webpack": "^4.1.1",
    "webpack-cli": "^2.0.11"
  }
}
guneysus
fuente
5

Si desea que su variable PATH se actualice correctamente en función de su directorio de trabajo actual, agregue esto al final de su .bashrcequivalente (o después de cualquier cosa que defina PATH):

__OLD_PATH=$PATH
function updatePATHForNPM() {
  export PATH=$(npm bin):$__OLD_PATH
}

function node-mode() {
  PROMPT_COMMAND=updatePATHForNPM
}

function node-mode-off() {
  unset PROMPT_COMMAND
  PATH=$__OLD_PATH
}

# Uncomment to enable node-mode by default:
# node-mode

Esto puede agregar un pequeño retraso cada vez que se procesa el indicador bash (según el tamaño de su proyecto, lo más probable), por lo que está deshabilitado de forma predeterminada.

Puede habilitarlo y deshabilitarlo dentro de su terminal ejecutando node-modey node-mode-off, respectivamente.

namuol
fuente
4

Siempre he usado el mismo enfoque que @guneysus para resolver este problema, que es crear un script en el archivo package.json y usarlo ejecutando npm run script-name.

Sin embargo, en los últimos meses he estado usando npx y me encanta.

Por ejemplo, descargué un proyecto Angular y no quería instalar Angular CLI a nivel mundial. Entonces, con npx instalado, en lugar de usar el comando global angular cli (si lo hubiera instalado) así:

ng serve

Puedo hacer esto desde la consola:

npx ng serve

Aquí hay un artículo que escribí sobre NPX y que profundiza en él.

Jair Reina
fuente
2

zxc es como "bundle exec" para nodejs. Es similar a usar PATH=$(npm bin):$PATH:

$ npm install -g zxc
$ npm install gulp
$ zxc which gulp
/home/nathan/code/project1/node_modules/.bin/gulp
Nathan
fuente
2

La misma solución aceptada de @regular, pero sabor a concha de pescado

if not contains (npm bin) $PATH
    set PATH (npm bin) $PATH
end
Cielos pioneros
fuente
1

También puede usar direnv y cambiar la variable $ PATH solo en su carpeta de trabajo.

$ cat .envrc
> export PATH=$(npm bin):$PATH
Erem
fuente
1

Agregue este script a su .bashrc. Entonces puedes llamar coffeeo hacer cualquier cosa localmente. Esto es útil para su computadora portátil, pero no lo use en su servidor.

DEFAULT_PATH=$PATH;

add_local_node_modules_to_path(){
  NODE_MODULES='./node_modules/.bin';
  if [ -d $NODE_MODULES ]; then
    PATH=$DEFAULT_PATH:$NODE_MODULES;
  else
    PATH=$DEFAULT_PATH;
  fi
}

cd () {
  builtin cd "$@";
  add_local_node_modules_to_path;
}

add_local_node_modules_to_path;

nota : este script hace un alias de cdcomando, y después de cada llamada cdlo verifica node_modules/.biny lo agrega a su $PATH.

nota2 : puede cambiar la tercera línea a NODE_MODULES=$(npm bin);. Pero eso haría que el cdcomando fuera demasiado lento.

Tsutomu Kawamura
fuente
1
Usar en $(npm bin)lugar de codificar ./node_modules/.bin.
bfontaine
Hmm, $(npm bin)parece demasiado lento para usar con cada cdcomando. He restaurado el código y agregué una nota.
Tsutomu Kawamura
1

Para Windows use esto:

/* cmd into "node_modules" folder */
"%CD%\.bin\grunt" --version
b3wii
fuente
0

Encontré el mismo problema y no me gusta especialmente el uso de alias (como se sugiere con regularidad ), y si no te gustan también, aquí hay otra solución alternativa que uso, primero tienes que crear un pequeño script ejecutable bash, diga setenv.sh :

#!/bin/sh

# Add your local node_modules bin to the path
export PATH="$(npm bin):$PATH"

# execute the rest of the command
exec "$@"

y luego puedes usar cualquier ejecutable en tu local /binusando este comando:

./setenv.sh <command>
./setenv.sh 6to5-node server.js
./setenv.sh grunt

Si está utilizando scriptspackage.json, entonces:

...,
scripts: {
    'start': './setenv.sh <command>'
}
nkh
fuente
2
esta secuencia de comandos setenv no es necesaria para las secuencias de comandos package.json. npm ya antepone el directorio local node_modules / .bin a la ruta cuando ejecuta npm run {scripts}.
jasonkarns
0

Me encantaría saber si esta es una idea insegura / mala, pero después de pensarlo un poco, no veo un problema aquí:

Modificando la solución insegura de Linus para agregarlo al final, usando npm binpara encontrar el directorio y haciendo que el script solo llame npm bincuando hay un package.jsonpresente en un padre (por velocidad), esto es lo que se me ocurrió zsh:

find-up () {
  path=$(pwd)
  while [[ "$path" != "" && ! -e "$path/$1" ]]; do
    path=${path%/*}
  done
  echo "$path"
}

precmd() {
  if [ "$(find-up package.json)" != "" ]; then
    new_bin=$(npm bin)
    if [ "$NODE_MODULES_PATH" != "$new_bin" ]; then
      export PATH=${PATH%:$NODE_MODULES_PATH}:$new_bin
      export NODE_MODULES_PATH=$new_bin
    fi
  else
    if [ "$NODE_MODULES_PATH" != "" ]; then
      export PATH=${PATH%:$NODE_MODULES_PATH}
      export NODE_MODULES_PATH=""
    fi
  fi
}

Para bash, en lugar de usar el precmdgancho, puede usar la $PROMPT_COMMANDvariable (no lo he probado pero se entiende):

__add-node-to-path() {
  if [ "$(find-up package.json)" != "" ]; then
    new_bin=$(npm bin)
    if [ "$NODE_MODULES_PATH" != "$new_bin" ]; then
      export PATH=${PATH%:$NODE_MODULES_PATH}:$new_bin
      export NODE_MODULES_PATH=$new_bin
    fi
  else
    if [ "$NODE_MODULES_PATH" != "" ]; then
      export PATH=${PATH%:$NODE_MODULES_PATH}
      export NODE_MODULES_PATH=""
    fi
  fi   
}

export PROMPT_COMMAND="__add-node-to-path"
osdiab
fuente
Agregar npm binal final de $PATHpuede no ejecutar lo que el usuario espera: ¡básicamente otro ejecutable pero más probablemente un paquete instalado globalmente con otra versión!
LoganMzz
0

Soy un Windowsusuario y esto es lo que funcionó para mí:

// First set some variable - i.e. replace is with "xo"
D:\project\root> set xo="./node_modules/.bin/"

// Next, work with it
D:\project\root> %xo%/bower install

Buena suerte.

Akash
fuente
0

En caso de que esté usando fish shelly no quiera agregar $pathpor razones de seguridad. Podemos agregar la siguiente función para ejecutar ejecutables de nodo local.

### run executables in node_module/.bin directory
function n 
  set -l npmbin (npm bin)   
  set -l argvCount (count $argv)
  switch $argvCount
    case 0
      echo please specify the local node executable as 1st argument
    case 1
      # for one argument, we can eval directly 
      eval $npmbin/$argv
    case '*'
      set --local executable $argv[1]
      # for 2 or more arguments we cannot append directly after the $npmbin/ since the fish will apply each array element after the the start string: $npmbin/arg1 $npmbin/arg2... 
      # This is just how fish interoperate array. 
      set --erase argv[1]
      eval $npmbin/$executable $argv 
  end
end

Ahora puedes ejecutar cosas como:

n coffee

o más argumentos como:

n browser-sync --version

Tenga en cuenta que si es bashusuario, entonces las respuestas de @ Bob9630 son el camino a seguir aprovechando bash $@, que no está disponible en fishshell.

LeOn - Han Li
fuente
-9

Incluya coffee-script en package.json con la versión específica requerida en cada proyecto, típicamente así:

"dependencies":{
  "coffee-script": ">= 1.2.0"

Luego ejecute npm install para instalar dependencias en cada proyecto. Esto instalará la versión especificada de coffee-script que será accesible localmente para cada proyecto.

almypal
fuente
Sí, llegué tan lejos como dije en mi pregunta. ¿Cómo llamo específicamente al que está en mi proyecto además de ./node_modules/.bin/coffee?
typeoneerror
Si ha ejecutado npm install con package.json en la carpeta principal de su proyecto, debería tener una carpeta ./node_modules/.bin/coffee en esta carpeta. El uso de ./node_modules/coffee-script/bin/coffee ejecutará la versión local de coffee, mientras que solo ejecutar coffee ejecutará la instalación global. Si tiene otra versión de café instalada en otra ruta dentro de esta carpeta de proyecto, puede acceder a ella usando ./path/to/this/installation/coffee.
almypal
Esto no funcionó para mí. Estoy tratando de usar "svgo", y solo funciona cuando se instala globalmente. Lo he intentado npm install svgotan bien como npm installcon package.json. Ambos métodos se instalaron "correctamente", pero el comando "svgo" aún no está disponible.
Ryan Wheale 05 de
1
Grunt usa esto de una manera inteligente, y en mi humilde opinión, deberían otros paquetes. Primero instala el grunt-clipaquete globalmente, luego en el directorio de su proyecto instale cualquier versión (modificada) del gruntpaquete, luego, cuando lo ejecute grunt, usará esta versión local.
ack