Como usar executáveis ​​de um pacote instalado localmente em node_modules?

493

Como eu uso uma versão local de um módulo no Windows node.js. Por exemplo, no meu aplicativo, instalei o café-script:

npm install coffee-script

Isso o instala ./node_modulese o comando coffee está ./node_modules/.bin/coffee. Existe uma maneira de executar este comando quando estou na pasta principal do meu projeto? Eu acho que estou procurando algo semelhante ao bundle execno bundler. Basicamente, eu gostaria de especificar uma versão do script de café que todos os envolvidos no projeto devem usar.

Sei que posso adicionar o -gsinalizador para instalá-lo globalmente, para que o café funcione bem em qualquer lugar, mas e se eu quiser ter versões diferentes de café por projeto?

typeoneerror
fonte
9
Muitas instruções que li dizem coisas do tipo npm install niftycommande então niftycommand. Mas isso nunca funcionará, a menos que você tenha ./node_modules/.bin no seu caminho, certo?
Bennett McElwee
2
Há uma descrição muito boa aqui: firstdoit.com/… - Basicamente, você deve colocar seu coffeecomando na npm scriptsseção, como "build": "coffee -co target/directory source/directoy", so you can run npm run build` do terminal posteriormente.
precisa saber é o seguinte
@BennyNeugebauer de fato, que é o que eu tenho feito ultimamente em vez de mexer com PATH
typeoneerror
12
Use npxque vem com npm 5.2.0 medium.com/@maybekatz/…
onmyway133

Respostas:

568

ATUALIZAÇÃO : Como Seyeong Jeong aponta na resposta abaixo, desde a npm 5.2.0 você pode usar npx [command], o que é mais conveniente.

RESPOSTA ANTIGA para versões anteriores à 5.2.0 :

O problema de colocar

./node_modules/.bin

no PATH é que ele só funciona quando o diretório de trabalho atual é a raiz da estrutura de diretórios do projeto (ou seja, a localização de node_modules)

Independentemente do diretório de trabalho, você pode obter o caminho dos binários instalados localmente com

npm bin

Para executar um coffeebinário instalado localmente, independentemente de onde você está na hierarquia de diretórios do projeto, você pode usar esta construção bash

PATH=$(npm bin):$PATH coffee

Eu alias isso para npm-exec

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

Então agora eu posso

npm-exec coffee

para executar a cópia correta do café, não importa onde eu esteja

$ 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
fonte
17
você pode até dar um passo adiante ealias coffee="npm-exec coffee"
regular
6
A saída muda quando você cd para outro projeto. Não muda quando você cd dentro de um projeto. npm binprocura na cadeia de 'diretórios ancestrais' no cwd por um diretório node_modules. Esse é exatamente o comportamento desejado se você desejar especificamente usar os binários dos módulos listados no package.json do projeto.
regular
11
oh Deus! realmente preciso fazer algo assim para que meus módulos locais funcionem? é impraticável explicá-lo a uma equipe! não há nada um pouco mais direto?
Alexian
17
Você sempre pode usar scripts npm, pois eles sempre pesquisam os binários locais primeiro. Você pode configurar aliases para cada um dos seus binários lá ou apenas usar nomes genéricos como "build".
Joe Zim
6
@philosodad, na verdade não, você não. A PATHestará de volta ao que era antes da chamada do comando. Definir uma variável de ambiente na mesma linha, antes de executar um comando, afeta apenas o ambiente desse comando.
regular
410

Bom exemplo

Você não precisa $PATHmais manipular !

No [email protected] , o npm é enviado com um npxpacote que permite executar comandos de um node_modules/.bincache local ou central.

Basta executar:

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

Por padrão, npxverifica se <command>existe $PATHou nos binários do projeto local e o executa.

Chamando npx <command> quando <command>já não está na sua $PATHvontade instalar automaticamente um pacote com esse nome a partir do registro NPM para você, e invocá-lo. Quando terminar, o pacote instalado não estará em nenhum lugar do seu mundo, portanto você não precisará se preocupar com poluição a longo prazo. Você pode impedir esse comportamento fornecendo a --no-installopção

Para npm < 5.2.0, você pode instalar o npxpacote manualmente executando o seguinte comando:

$ npm install -g npx
Seyeong Jeong
fonte
1
Eu não gosto de instalar pacotes npm globais de terceiros npme package.jsonfornece quase a mesma funcionalidade.
guneysus
Se "Path deve ser uma cadeia indefinidos Recebidos" aparecer, aqui é uma correção: github.com/zkat/npx/issues/144#issuecomment-391031816
Valeriy Katkov
1
Essa resposta é boa. Mas eu só quero dizer que npxé coxo. Deveria ter sido npm runou npm execou algo assim.
William Entriken
@WilliamEntriken Por algumas razões, npm run [my-local-package]não está funcionando no meu Ubuntu, embora parecesse funcionar em um dispositivo Windows.
Clockwork
97

Use o npm bincomando para obter o diretório modules / bin do nó do seu projeto

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

por exemplo

$ $(npm bin)/bower install
jassa
fonte
4
Eu gosto desta solução simples e genérica. Faz um alias parecer desnecessário.
Matt Montag 31/05
Parece ser a próxima melhor solução que é elegante e mais seguro do que ter que fazerexport PATH="./node_modules/.bin:$PATH"
jontsai
1
@ inf3rno o comando é $(npm bin)/jasmine, não node $(npm bin)/jasmine(você provavelmente descobriu, mas esclarece para os outros).
Jassa
5
Não é uma solução ruim, mas não roda em uma linha de comando padrão do Windows com $. Colocá-lo na seção de scripts package.json é uma abordagem melhor, pois é mais compatível.
Timothy Gonzalez
77

Usar npm run[-script] <script name>

Depois de usar o npm para instalar o pacote bin no ./node_modulesdiretório local , modifique package.jsonpara adicionar da <script name>seguinte maneira:

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

Seria bom se o npm install tivesse uma opção --add-script ou algo assim ou se o npm run funcionasse sem adicionar ao bloco de scripts.

jla
fonte
5
Eu achei essa abordagem mais uniforme ao lidar com vários desenvolvedores em um projeto - evita a necessidade de configurar qualquer coisa localmente ... você só npm installentão tem acesso às suas dependências de desenvolvimento. A única desvantagem menor é que você precisa npm run eslint(ou o que seja). Você pode criar um script chamado "start" que execute gulp, para que você só precise digitar npm startpara iniciar seu servidor de desenvolvimento. Coisas bem legais e sem graça, então seus amigos do Windows ainda gostam de você. :)
jpoveda
1
adicionando um alias para colocar US $ (bin NPM) em seu caminho é inteligente, mas o fato de que isso vai funcionar para as pessoas sem configuração local conquista meu coração
Conrad.Dean
12
isso precisa de mais votos! Passe argumentos para seus scripts depois de --like:npm run learnyounode -- --normal-switches --watch -d *.js
ptim
Eu também acho essa a melhor solução. Há uma explicação detalhada aqui: lostechies.com/derickbailey/2012/04/24/…
adampasz
1
Isso é o que eu costumo fazer, mas por alguns motivos, em um dispositivo Ubuntu, npm run ts-nodenão está funcionando para mim. Eu só vou ter que voltar ao npx.
Clockwork
42

Use npm-run.

No leia-me:

npm-run

Encontre e execute executáveis ​​locais a partir de node_modules

Qualquer executável disponível para um script de ciclo de vida npm está disponível para npm-run.

Uso

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

Instalação

$ npm install -g npm-run
mightyiam
fonte
8
Já não, consulte a npx referenciada acima ... stackoverflow.com/a/45164863/3246805
tj
41

Atualização: não recomendo mais esse método, pelos motivos de segurança mencionados e pelo menos pelo mais novonpm bin comando . Resposta original abaixo:

Como você descobriu, existem binários instalados localmente ./node_modules/.bin. Para sempre executar binários neste diretório, em vez de binários disponíveis globalmente, se houver, sugiro que você coloque o ./node_modules/.binprimeiro no seu caminho:

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

Se você colocar isso no seu ~/.profile, coffeesempre estará ./node_modules/.bin/coffeedisponível, caso contrário /usr/local/bin/coffee(ou qualquer prefixo no qual você esteja instalando os módulos do nó).

Linus Gustav Larsson Thiel
fonte
1
essa é provavelmente a melhor solução. Também criei um script bash chamado "watch" no meu projeto:./node_modules/.bin/coffee --output lib/ --compile --bare --watch src
typeoneerror 13/03/12
72
Perigo, Will Robinson! O uso de caminhos relativos em seu $ PATH abre uma brecha de segurança do tamanho de um planeta, especialmente se você os colocar logo na frente como o primeiro item. Se o diretório que você está é gravável por todos (dizer em algum lugar /tmp), qualquer processo ou usuário pode seqüestrar sua sessão, colocando versões maliciosas de comandos comuns (como ls, cp, etc.) lá. Isso pode gerar subconchas 'invisíveis' capturando suas senhas, etc.
Ack
funcionará apenas na raiz e em nenhum outro lugar. o alias npm-exec='PATH=$(npm bin):$PATH'é mais liso.
Oligofren 5/05
1
Quão ruim é isso se você não colocar como a primeira coisa na sua PATH, mas a última (usando o $(npm bin)formulário)? para que eles não possam sobrescrever o material existente, e você já estaria confiando nos executáveis ​​no npm bindiretório, independentemente da PATHvar; o modelo de ameaça seria o seguinte: a) alguém mal-intencionado obtém acesso ao seu sistema de arquivos; b) adiciona executáveis ​​com nomes próximos às ferramentas do sistema; ec) você digita incorretamente? Tentando entender cenários que tornam isso ruim, já que você já confia em executáveis ​​estrangeiros ao usar npmprogramas instalados.
Osdiab
Você pode fazer truques de shell com um alias e pode localizar manualmente e isso "funciona", mas não é o ideal.
Killscreen
22

A solução PATH tem o problema de que, se $ (npm bin) for colocado em seu .profile / .bashrc / etc, ele é avaliado uma vez e é definido para sempre no diretório em que o caminho foi avaliado pela primeira vez. Se, em vez disso, você modificar o caminho atual, toda vez que você executa o script, seu caminho cresce.

Para contornar esses problemas, crio uma função e a usei. Não modifica seu ambiente e é simples de usar:

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

Isso pode ser usado assim sem fazer alterações no seu ambiente:

npm-exec r.js <args>
Bob9630
fonte
2
Eu gosto disso! Eu simplesmente chamado minha funçãon
jontsai
Isso é ótimo! Obrigado por compartilhar. Eu adicionei uma versão de casca de peixe abaixo.
97881905305106
22

Se você deseja manter o npm, o npx deve fazer o que você precisa.


Se mudar para o fio (uma substituição de npm pelo facebook) é uma opção para você, você pode ligar para:

 yarn yourCmd

scripts dentro do package.json terão precedência, se nenhum for encontrado, ele procurará dentro da ./node_modules/.bin/pasta.

Ele também exibe o que foi executado:

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

Portanto, você não precisa configurar scripts para cada comando no seu package.json.


Se você tivesse um script definido .scriptsdentro de seu package.json:

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

yarn tscseria equivalente yarn run tscou npm run tsc:

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

atualização: se você estiver no npm recente (versão> 5.2)

Você pode usar:

npx <command>

npxprocura pelo comando no .bindiretório do seunode_modules

resposta antiga:

Para Windows

Armazene o seguinte em um arquivo chamado npm-exec.bate adicione-o ao seu%PATH%

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

Uso

Então você pode usá-lo como npm-exec <command> <arg0> <arg1> ...

Por exemplo

Para executar wdioinstalado no diretório local node_modules, faça:

npm-exec wdio wdio.conf.js

ou seja, ele será executado .\node_modules\.bin\wdio wdio.conf.js

Dheeraj Bhaskar
fonte
Isso não funciona ao passar mais de um argumento. Por exemplo, npm-exec gulp <some_task>
OK999 04/04
@ OK9999 Tenho certeza de que algumas pequenas modificações permitirão a passagem de argumentos (porque quando você a passa aqui, ela é citada em ""); O que eu sugiro é copiar e colar o arquivo gole de bin a sua raiz do projeto (algumas modificações necessárias do arquivo, mas ele vai funcionar sem escrever novo código etc)
Dheeraj Bhaskar
Sim, acabei fazendo isso. A pasta node_modules deve estar na pasta em que o arquivo gulpfile existe
OK999 11/11
7

Prefiro não confiar em aliases do shell ou em outro pacote.

Adicionando uma linha simples à scriptsseção do seu package.json, você pode executar comandos locais do 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
fonte
5

Se você deseja que sua variável PATH seja atualizada corretamente com base no seu diretório de trabalho atual, adicione-a ao final de seu .bashrcequivalente (ou depois de qualquer coisa que o 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

Isso pode adicionar um pequeno atraso toda vez que o prompt do bash é renderizado (dependendo do tamanho do seu projeto, provavelmente), por isso é desativado por padrão.

Você pode habilitá-lo e desabilitá-lo em seu terminal executando node-modee node-mode-off, respectivamente.

namuol
fonte
4

Sempre usei a mesma abordagem que o @guneysus para resolver esse problema, que está criando um script no arquivo package.json e o usando executando o npm run script-name.

No entanto, nos últimos meses eu tenho usado o npx e adoro isso.

Por exemplo, baixei um projeto Angular e não queria instalar a CLI Angular globalmente. Portanto, com o npx instalado, em vez de usar o comando angular global cli (se eu o tivesse instalado) assim:

ng serve

Eu posso fazer isso no console:

npx ng serve

Aqui está um artigo que escrevi sobre o NPX e que é mais aprofundado.

Jair Reina
fonte
2

O zxc é como "bundle exec" para nodejs. É semelhante ao uso PATH=$(npm bin):$PATH:

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

A mesma solução aceita do @regular, mas o sabor da casca do peixe

if not contains (npm bin) $PATH
    set PATH (npm bin) $PATH
end
Pioneer Skies
fonte
1

Você também pode usar o direnv e alterar a variável $ PATH apenas na sua pasta de trabalho.

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

Adicione este script ao seu .bashrc. Então você pode ligar coffeeou qualquer coisa localmente. Isso é útil para o seu laptop, mas não o use no 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 faz um aliasse de cdcomando e, após cada chamada cd, verifica node_modules/.bine adiciona-o ao seu$PATH .

note2 : você pode alterar a terceira linha para NODE_MODULES=$(npm bin);. Mas isso tornaria o cdcomando muito lento.

Tsutomu Kawamura
fonte
1
Use em $(npm bin)vez de codificar ./node_modules/.bin.
precisa saber é
Hmm, $(npm bin)parece muito lento para usar com cada cdcomando. Eu restaurei o código e adicionei uma nota para ele.
Tsutomu Kawamura 26/01
1

Para Windows, use este:

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

Encontrei o mesmo problema e não gosto particularmente de usar aliases (como sugerido pelo regular ), e se você não gostar também, aqui está outra solução alternativa que eu uso: primeiro você deve criar um pequeno script bash executável, 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 "$@"

e então você pode usar qualquer executável em seu local /binusando este comando:

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

Se você estiver usando o scriptspackage.json, então:

...,
scripts: {
    'start': './setenv.sh <command>'
}
nkh
fonte
2
esse script setenv não é necessário para scripts package.json. O npm já adiciona o diretório node_modules / .bin local ao caminho para você ao executar o npm run {scripts}.
jasonkarns
0

Eu adoraria saber se essa é uma idéia insegura / ruim, mas depois de pensar um pouco sobre isso, não vejo um problema aqui:

Modificando a solução insegura de Linus para adicioná-lo até o fim, usando npm binpara encontrar o diretório e fazendo o script chamar somente npm binquando a package.jsonestá presente em um pai (para velocidade), é isso que eu criei 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
}

Pois bash, em vez de usar o precmdgancho, você pode usar a $PROMPT_COMMANDvariável (não testei isso, mas você entendeu):

__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
fonte
A adição npm binao final de $PATHpode não executar o que o usuário espera: basicamente outro executável, mas provavelmente um pacote instalado globalmente com outra versão!
precisa saber é o seguinte
0

Eu sou um Windowsusuário e é isso que funcionou para mim:

// 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

Boa sorte.

Akash
fonte
0

Caso você esteja usando fish shelle não queira adicionar $pathpor motivos de segurança. Podemos adicionar a função abaixo para executar executáveis ​​de nó 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

Agora você pode executar coisas como:

n coffee

ou mais argumentos como:

n browser-sync --version

Observe que, se você é bashusuário, as respostas @ Bob9630 são o caminho a seguir, aproveitando o bash's $@, que não está disponível no fishshell.

LeOn - Han Li
fonte
-9

Inclua coffee-script no package.json com a versão específica requerida em cada projeto, normalmente assim:

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

Em seguida, execute o npm install para instalar dependências em cada projeto. Isso instalará a versão especificada do script café, que estará acessível localmente para cada projeto.

almypal
fonte
Sim, cheguei até onde afirmei na minha pergunta. como chamo especificamente o do meu projeto além de ./node_modules/.bin/coffee?
typeoneerror
Se você executou o npm install com o package.json na pasta principal do seu projeto, você deve ter uma pasta ./node_modules/.bin/coffee nesta pasta. O uso de ./node_modules/coffee-script/bin/coffee executará a versão local do café enquanto apenas executando o café executará a instalação global. Se você tiver outra versão do café instalada em outro caminho dentro desta pasta do projeto, poderá acessá-la usando ./path/to/this/installation/coffee.
almypal
Isto não funcionou para mim. Estou tentando usar o "svgo", e ele só funciona quando instalado globalmente. Eu tentei npm install svgo, assim como npm installcom o package.json. Ambos os métodos foram instalados "com sucesso", mas o comando "svgo" ainda não está disponível.
Ryan Wheale
1
Grunt usa isso de uma maneira inteligente, e o IMHO também deve usar outros pacotes. Primeiro você instala o grunt-clipacote globalmente, depois no diretório do projeto, instala qualquer versão (modificada) do gruntpacote e, quando você executa grunt, ele usa essa versão local.
ack