Posso instalar um pacote NPM de javascript em execução em Node.js?

91

Posso instalar um pacote NPM de um arquivo javascript em execução em Node.js? Por exemplo, gostaria de ter um script, vamos chamá-lo de "script.js" que de alguma forma (... usando NPM ou não ...) instale um pacote normalmente disponível através do NPM. Neste exemplo, gostaria de instalar "FFI". (npm install ffi)

Justin
fonte

Respostas:

109

Na verdade, é possível usar o npm programaticamente e ele foi descrito em revisões anteriores da documentação. Desde então, foi removido da documentação oficial, mas ainda existe no controle de origem com a seguinte declaração:

Embora o npm possa ser usado programaticamente, sua API deve ser usada apenas pela CLI e nenhuma garantia é feita em relação à sua adequação para qualquer outra finalidade. Se você quiser usar o npm para realizar alguma tarefa de maneira confiável, a coisa mais segura a fazer é invocar o comando npm desejado com os argumentos apropriados.

A versão semântica do npm se refere à própria CLI, e não à API subjacente. A API interna não tem garantia de permanecer estável, mesmo quando a versão do npm indica que nenhuma alteração significativa foi feita de acordo com semver .

Na documentação original, o seguinte é o exemplo de código fornecido:

var npm = require('npm')
npm.load(myConfigObject, function (er) {
  if (er) return handlError(er)
  npm.commands.install(['some', 'args'], function (er, data) {
    if (er) return commandFailed(er)
    // command succeeded, and data might have some info
  })
  npm.registry.log.on('log', function (message) { ... })
})

Como o npm existe na node_modulespasta, você pode usar require('npm')para carregá-lo como qualquer outro módulo. Para instalar um módulo, você vai querer usar npm.commands.install().

Se você precisar procurar na fonte, também está no GitHub . Aqui está um exemplo completo de trabalho do código, que é o equivalente a executar npm installsem nenhum argumento de linha de comando:

var npm = require('npm');
npm.load(function(err) {
  // handle errors

  // install module ffi
  npm.commands.install(['ffi'], function(er, data) {
    // log errors or data
  });

  npm.on('log', function(message) {
    // log installation progress
    console.log(message);
  });
});

Observe que o primeiro argumento para a função de instalação é uma matriz. Cada elemento da matriz é um módulo que o npm tentará instalar.

O uso mais avançado pode ser encontrado no npm-cli.jsarquivo de controle de origem.

hexacianida
fonte
5
caso isso ajude alguém - certifique-se de fazer isso npm install npm --saveprimeiro. Exemplo funciona muito bem :)
mikermcneil
6
Além disso, cuidado - npmtem muitas dependências, portanto, adicioná-lo ao seu módulo provavelmente fará com que demore MUITO mais tempo para baixar. Confira uma das child_processrespostas para aproveitar o npm global já instalado nas máquinas de seus usuários.
mikermcneil
1
Não passe npm.configpara npm.load! Nem mesmo @isaacs sabe que tipo de coisas estranhas acontecerão! Consulte github.com/npm/npm/issues/4861#issuecomment-40533836 Em vez disso, você pode simplesmente pular o primeiro argumento.
Georgii Ivankin
2
Como faço para definir o caminho de destino? (quando é diferente do process.cwd())
Gajus
1
Para aqueles que desejam importar NPM apesar dos avisos, global-npm é melhor (menor, sem dependências) do quenpm install npm --save
Xunnamius
26

sim. você pode usar child_process para executar um comando do sistema

var exec = require('child_process').exec,
    child;

 child = exec('npm install ffi',
 function (error, stdout, stderr) {
     console.log('stdout: ' + stdout);
     console.log('stderr: ' + stderr);
     if (error !== null) {
          console.log('exec error: ' + error);
     }
 });
O cérebro
fonte
2
Sim, você pode, no entanto, algumas dependências NÃO SERÃO instaladas (falando por experiência própria, porque uma vez eu realmente escrevi um servidor CI para node.js)
Matej
5
No Windows isso não funciona! Você tem que ligar ao npm.cmdinvés.
DUzun
25

para ver a saída também, você pode usar:

var child_process = require('child_process');
child_process.execSync('npm install ffi',{stdio:[0,1,2]});

desta forma, você pode assistir a instalação como faz manualmente e evitar surpresas como buffer cheio, etc.

Krankuba
fonte
1
esta é a única opção de todas as respostas que, por exemplo, permite que você execute npm install e obtenha a saída completa como se você estivesse executando o comando manualmente! obrigado!
Jörn Berkefeld
10

pode realmente ser um pouco fácil

var exec = require('child_process').exec;
child = exec('npm install ffi').stderr.pipe(process.stderr);
Vyacheslav Shebanov
fonte
1
Isso também tem a vantagem de que stderr (e stdout) são impressos conforme ocorrem, e não no final da execução!
mvermand
6

Tive muita dificuldade em fazer o primeiro exemplo funcionar dentro de um diretório de projeto, postando aqui no caso de alguém encontrar isso. Pelo que eu posso dizer, o NPM ainda funciona bem carregado diretamente, mas como ele assume CLI, temos que nos repetir um pouco ao configurá-lo:

// this must come before load to set your project directory
var previous = process.cwd();
process.chdir(project);

// this is the part missing from the example above
var conf = {'bin-links': false, verbose: true, prefix: project}

// this is all mostly the same

var cli = require('npm');
cli.load(conf, (err) => {
    // handle errors
    if(err) {
        return reject(err);
    }

    // install module
    cli.commands.install(['ffi'], (er, data) => {
        process.chdir(previous);
        if(err) {
            reject(err);
        }
        // log errors or data
        resolve(data);
    });

    cli.on('log', (message) => {
        // log installation progress
        console.log(message);
    });
});
Megamind
fonte
3

pacote é o pacote que o npm usa para buscar metadados e tarballs do pacote. Ele tem uma API pública estável.

James A. Rosen
fonte
2

Sou autor de um módulo que permite fazer exatamente o que você tem em mente. Veja live-plugin-manager .

Você pode instalar e executar praticamente qualquer pacote do NPM, Github ou de uma pasta.

Aqui está um exemplo:

import {PluginManager} from "live-plugin-manager";

const manager = new PluginManager();

async function run() {
  await manager.install("moment");

  const moment = manager.require("moment");
  console.log(moment().format());

  await manager.uninstall("moment");
}

run();

No código acima eu instalo o momentpacote em tempo de execução, carrego e executo. No final, desinstalo-o.

Internamente, eu não executo o npmcli, mas na verdade faço o download dos pacotes e executo dentro de uma sandbox de VM do nó.

Davide Icardi
fonte
1

Uma ótima solução por @hexacyanide, mas descobriu-se que o NPM não emite mais evento "log" (pelo menos a partir da versão 6.4.1). Em vez disso, eles contam com um módulo independente https://github.com/npm/npmlog . Felizmente, é um singleton, então podemos alcançar a mesma instância que o NPM usa para registros e se inscrever para eventos de registro:

const npmlog = require( "npm/node_modules/npmlog" ),
      npm = require( "npm" );

npmlog.on( "log", msg => {
   console.log({ msg });
});

 process.on("time", milestone => {
   console.log({ milestone });
 });

 process.on("timeEnd", milestone => {
   console.log({ milestone });    
 });

 npm.load({
    loaded: false,
    progress: false,
    "no-audit": true
  }, ( err ) => {

 npm.commands.install( installDirectory, [
      "cross-env@^5.2.0",
      "shelljs@^0.8.2"
    ], ( err, data ) => {
       console.log( "done" );    
    });

  });

Como você pode ver no código, o NPM também emite métricas de desempenho no process, portanto, também podemos usá-lo para monitorar o progresso.

Dmitry Sheiko
fonte
1

Outra opção, que não foi mencionada aqui, é fazer fork e executar CLI diretamente de ./node_modules/npm/bin/npm-cli.js

Por exemplo, você deseja instalar os módulos de nó a partir da execução do script na máquina, que não possui o NPM instalado. E você deseja fazer isso com CLI. Neste caso, basta instalar o NPM em seus node_modules localmente enquanto constrói seu programa (npm i npm ).

Em seguida, use-o assim:

// Require child_process module
const { fork } = require('child_process');
// Working directory for subprocess of installer
const cwd = './path-where-to-run-npm-command'; 
// CLI path FROM cwd path! Pay attention
// here - path should be FROM your cwd directory
// to your locally installed npm module
const cli = '../node_modules/npm/bin/npm-cli.js';
// NPM arguments to run with
// If your working directory already contains
// package.json file, then just install it!
const args = ['install']; // Or, i.e ['audit', 'fix']

// Run installer
const installer = fork(cli, args, {
  silent: true,
  cwd: cwd
});

// Monitor your installer STDOUT and STDERR
installer.stdout.on('data', (data) => {
  console.log(data);
});
installer.stderr.on('data', (data) => {
  console.log(data);
});

// Do something on installer exit
installer.on('exit', (code) => {
  console.log(`Installer process finished with code ${code}`);
});

Então seu programa pode ser compactado em um arquivo binário, por exemplo, com o pacote PKG . Neste caso, você precisa usar a --ignore-scriptsopção npm, porque o node-gyp é necessário para executar scripts de pré-instalação

Tarkh
fonte