Posso installare un pacchetto NPM da javascript in esecuzione in Node.js?


91

Posso installare un pacchetto NPM da un file javascript in esecuzione in Node.js? Ad esempio, mi piacerebbe avere uno script, chiamiamolo "script.js" che in qualche modo (... utilizzando NPM o meno ...) installa un pacchetto solitamente disponibile tramite NPM. In questo esempio, vorrei installare "FFI". (npm installa ffi)

Risposte:


109

È effettivamente possibile utilizzare npm a livello di programmazione, ed è stato descritto nelle revisioni precedenti della documentazione. Da allora è stato rimosso dalla documentazione ufficiale, ma esiste ancora nel controllo del codice sorgente con la seguente dichiarazione:

Sebbene npm possa essere utilizzato a livello di codice, la sua API è pensata per essere utilizzata solo dalla CLI e non viene fornita alcuna garanzia in merito alla sua idoneità per qualsiasi altro scopo. Se si desidera utilizzare npm per eseguire in modo affidabile alcune attività, la cosa più sicura da fare è invocare il comando npm desiderato con gli argomenti appropriati.

La versione semantica di npm fa riferimento alla stessa CLI, piuttosto che all'API sottostante. Non è garantito che l'API interna rimanga stabile anche quando la versione di npm indica che non sono state apportate modifiche sostanziali in base al semver .

Nella documentazione originale, il seguente è il codice di esempio fornito:

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) { ... })
})

Poiché npm esiste nella node_modulescartella, puoi usarlo require('npm')per caricarlo come qualsiasi altro modulo. Per installare un modulo, vorrai usare npm.commands.install().

Se hai bisogno di guardare nel codice sorgente, è anche su GitHub . Ecco un esempio funzionante completo del codice, che è l'equivalente dell'esecuzione npm installsenza argomenti della riga di 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);
  });
});

Notare che il primo argomento della funzione di installazione è un array. Ogni elemento dell'array è un modulo che npm tenterà di installare.

Un utilizzo più avanzato può essere trovato nel npm-cli.jsfile sul controllo del codice sorgente.


5
nel caso in cui questo aiuti qualcuno, assicurati di farlo npm install npm --save prima. L'esempio funziona alla grande :)
mikermcneil

6
Inoltre, fai attenzione: npmha molte dipendenze, quindi aggiungerlo al tuo modulo molto probabilmente richiederà MOLTO più tempo per il download. child_processDai un'occhiata a una delle risposte per sfruttare l'npm globale già installato sulle macchine dei tuoi utenti.
mikermcneil

1
Non passare npm.configa npm.load! Anche @isaacs non sa che tipo di cose strane succederanno allora! Vedi github.com/npm/npm/issues/4861#issuecomment-40533836 Invece, puoi semplicemente saltare il primo argomento.
Georgii Ivankin

2
Come si imposta il percorso di destinazione? (quando è diverso che il process.cwd())
Gajus

1
Per coloro che desiderano importare NPM nonostante gli avvisi, global-npm è migliore (più piccolo, senza dipendenze) dinpm install npm --save
Xunnamius

26

sì. puoi usare child_process per eseguire un comando di 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);
     }
 });

2
Sì, puoi, tuttavia alcune dipendenze NON verranno installate (parlando per esperienza, perché una volta ho effettivamente scritto un server CI per node.js)
Matej

5
Su Windows questo non funziona! Devi chiamare npm.cmdinvece.
DUzun

26

Puoi usare child_process . exec o execSync per generare una shell, quindi eseguire il comando desiderato all'interno di quella shell, bufferizzando qualsiasi output generato:

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

Se viene fornita una funzione di callback, viene chiamata con gli argomenti (errore, stdout, stderr). In questo modo puoi eseguire l'installazione come fai manualmente e vedere l'output completo.

Il metodo child_process.execSync () è generalmente identico a child_process.exec () con l'eccezione che il metodo non restituirà fino a quando il processo figlio non sarà completamente chiuso.


2
questa è l'unica opzione tra tutte le risposte che, ad esempio, ti consente di eseguire npm install e ottenere l'output completo come se stessi eseguendo il comando manualmente! grazie!
Jörn Berkefeld

1
Cosa fa stdio: [0,1,2]?
Zach Smith,

se viene fornita una funzione di callback a child_process.exec, viene chiamata con argomenti equivalenti a [process.stdin, process.stdout, process.stderr] o [0,1,2] come da api doc
krankuba

11

può effettivamente essere un po 'facile

var exec = require('child_process').exec;
child = exec('npm install ffi').stderr.pipe(process.stderr);

2
Questo ha anche il vantaggio che stderr (e stdout) vengono stampati mentre si verificano, non alla fine dell'esecuzione!
mvermand

1
Questo non viene stampato nella stessa misura della risposta di @krankuba di seguito, per quanto ne so.
Zach Smith,

6

Ho avuto un sacco di tempo cercando di far funzionare il primo esempio all'interno di una directory di progetto, postando qui nel caso in cui qualcun altro lo trovi. Per quanto ne so, NPM funziona ancora bene caricato direttamente, ma poiché presuppone CLI, dobbiamo ripeterci un po 'impostandolo:

// 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);
    });
});

3

pacote è il pacchetto che npm usa per recuperare i metadati e i tarball dei pacchetti. Ha un'API pubblica e stabile.


2

Sono l'autore di un modulo che ti permette di fare esattamente quello che hai in mente. Vedi live-plugin-manager .

Puoi installare ed eseguire praticamente qualsiasi pacchetto da NPM, Github o da una cartella.

Ecco un esempio:

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();

Nel codice sopra installo il momentpacchetto in fase di esecuzione, lo carico ed eseguo. Alla fine lo disinstallo.

Internamente non npmeseguo cli ma in realtà scarico i pacchetti ed eseguo all'interno di una sandbox VM del nodo.


1

Un'ottima soluzione di @hexacyanide, ma si è scoperto che NPM non emette più l'evento "log" (almeno a partire dalla versione 6.4.1). Invece si basano su un modulo autonomo https://github.com/npm/npmlog . Fortunatamente è un singleton, quindi possiamo raggiungere la stessa istanza utilizzata da NPM per i log e iscriverti per gli eventi di log:

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" );    
    });

  });

Come puoi vedere dal codice, NPM emette anche metriche delle prestazioni su process, quindi possiamo usarlo anche per monitorare lo stato di avanzamento.


1

Un'altra opzione, che non è stata menzionata qui, è eseguire il fork ed eseguire la CLI direttamente da ./node_modules/npm/bin/npm-cli.js

Ad esempio, si desidera poter installare i moduli del nodo dallo script in esecuzione sulla macchina, su cui non è installato NPM. E tu vuoi farlo con CLI. In questo caso, installa solo NPM nel tuo node_modules in locale durante la creazione del tuo programma ( npm i npm).

Quindi usalo in questo modo:

// 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}`);
});

Quindi il tuo programma potrebbe anche essere compresso in un file binario, ad esempio con il pacchetto PKG . In questo caso è necessario utilizzare --ignore-scriptsl'opzione npm, poiché node-gyp richiedeva di eseguire gli script di preinstallazione

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.