Kann ich ein NPM-Paket aus Javascript installieren, das in Node.js ausgeführt wird?

91

Kann ich ein NPM-Paket aus einer Javascript-Datei installieren, die in Node.js ausgeführt wird? Zum Beispiel möchte ich ein Skript haben, nennen wir es "script.js", das irgendwie (... mit NPM oder nicht ...) ein Paket installiert, das normalerweise über NPM verfügbar ist. In diesem Beispiel möchte ich "FFI" installieren. (npm install ffi)

Justin
quelle

Antworten:

109

Es ist in der Tat möglich, npm programmgesteuert zu verwenden, und dies wurde in älteren Revisionen der Dokumentation beschrieben. Es wurde inzwischen aus der offiziellen Dokumentation entfernt, existiert jedoch weiterhin in der Quellcodeverwaltung mit der folgenden Aussage:

Obwohl npm programmgesteuert verwendet werden kann, ist seine API nur für die Verwendung durch die CLI vorgesehen, und es werden keine Garantien hinsichtlich seiner Eignung für andere Zwecke gegeben. Wenn Sie npm verwenden möchten, um eine Aufgabe zuverlässig auszuführen, ist es am sichersten, den gewünschten npm-Befehl mit den entsprechenden Argumenten aufzurufen.

Die semantische Version von npm bezieht sich eher auf die CLI selbst als auf die zugrunde liegende API. Es ist nicht garantiert, dass die interne API stabil bleibt, selbst wenn die Version von npm angibt, dass laut Semver keine wesentlichen Änderungen vorgenommen wurden .

In der Originaldokumentation wurde das folgende Codebeispiel bereitgestellt:

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

Da npm im node_modulesOrdner vorhanden ist, können Sie require('npm')ihn wie jedes andere Modul laden. Um ein Modul zu installieren, möchten Sie verwenden npm.commands.install().

Wenn Sie in der Quelle suchen müssen, dann ist es auch auf GitHub . Hier ist ein vollständiges Arbeitsbeispiel für den Code, der dem Ausführen npm installohne Befehlszeilenargumente entspricht:

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

Beachten Sie, dass das erste Argument für die Installationsfunktion ein Array ist. Jedes Element des Arrays ist ein Modul, das npm zu installieren versucht.

Eine erweiterte Verwendung finden Sie in der npm-cli.jsDatei zur Quellcodeverwaltung.

Hexacyanid
quelle
5
Falls dies jemandem hilft, stellen Sie sicher, dass Sie es npm install npm --savezuerst tun . Beispiel funktioniert
super
6
Vorsicht, es npmgibt viele Abhängigkeiten. Wenn Sie es also zu Ihrem Modul hinzufügen , dauert das Herunterladen höchstwahrscheinlich VIEL länger. Schauen Sie sich eine der child_processAntworten an, um die globale npm zu nutzen, die bereits auf den Computern Ihrer Benutzer installiert ist.
Mikermcneil
1
Geben Sie es nicht npm.configan npm.load! Selbst @isaacs weiß nicht, was für seltsame Dinge dann passieren werden! Siehe github.com/npm/npm/issues/4861#issuecomment-40533836 Stattdessen können Sie einfach das erste Argument überspringen.
Georgii Ivankin
Der Link zur Dokumentation scheint zu einem 404 zu führen. Gibt es eine neue URL oder ist etwas passiert?
Saad
1
Wie lege ich den Zielpfad fest? (wenn es anders ist als das process.cwd())
Gajus
26

Ja. Mit child_process können Sie einen Systembefehl ausführen

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);
     }
 });
Das Gehirn
quelle
2
Ja, Sie können, aber einige Abhängigkeiten werden nicht installiert (aus Erfahrung, weil ich einmal tatsächlich einen CI- Server für node.js geschrieben habe)
Matej
5
Unter Windows funktioniert das nicht! Sie müssen npm.cmdstattdessen anrufen .
DUzun
25

Um auch die Ausgabe zu sehen, können Sie Folgendes verwenden:

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

Auf diese Weise können Sie die Installation wie manuell ansehen und Überraschungen wie vollen Puffer usw. vermeiden.

krankuba
quelle
1
Dies ist die einzige Option aus allen Antworten, mit der Sie beispielsweise npm install ausführen und die vollständige Ausgabe erhalten können, als würden Sie den Befehl manuell ausführen! Danke!
Jörn Berkefeld
10

es kann tatsächlich ein bisschen einfach sein

var exec = require('child_process').exec;
child = exec('npm install ffi').stderr.pipe(process.stderr);
Vyacheslav Shebanov
quelle
1
Dies hat auch den Vorteil, dass stderr (und stdout) so gedruckt werden, wie sie auftreten, nicht am Ende der Ausführung!
Mvermand
6

Ich hatte verdammt viel Zeit damit, das erste Beispiel in einem Projektverzeichnis zum Laufen zu bringen und hier zu posten, falls jemand anderes dies findet. Soweit ich das beurteilen kann, funktioniert NPM immer noch gut, wenn es direkt geladen wird. Da jedoch CLI vorausgesetzt wird, müssen wir uns ein wenig wiederholen, um es einzurichten:

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

pacote ist das Paket, mit dem npm Paketmetadaten und Tarballs abruft. Es hat eine stabile, öffentliche API.

James A. Rosen
quelle
2

Ich bin der Autor eines Moduls, mit dem Sie genau das tun können, was Sie sich vorgestellt haben. Siehe Live-Plugin-Manager .

Sie können praktisch jedes Paket von NPM, Github oder aus einem Ordner installieren und ausführen.

Hier ein Beispiel:

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

Im obigen Code installiere ich das momentPaket zur Laufzeit, lade es und führe es aus. Am Ende deinstalliere ich es.

Intern npmstarte ich nicht cli, sondern lade Pakete herunter und laufe in einer Node-VM-Sandbox.

Davide Icardi
quelle
1

Eine großartige Lösung von @hexacyanide, aber es stellte sich heraus, dass NPM kein "log" -Ereignis mehr ausgibt (zumindest ab Version 6.4.1). Stattdessen verlassen sie sich auf ein eigenständiges Modul https://github.com/npm/npmlog . Glücklicherweise ist es ein Singleton, sodass wir dieselbe Instanz erreichen können, die NPM für Protokolle verwendet, und Protokollereignisse abonnieren können:

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

  });

Wie Sie dem Code entnehmen können, gibt NPM auch Leistungsmetriken für den Code aus, processsodass wir ihn auch zur Überwachung des Fortschritts verwenden können.

Dmitry Sheiko
quelle
1

Eine andere Option, die hier nicht erwähnt wurde, ist das Ausführen und Ausführen von CLI direkt von ./node_modules/npm/bin/npm-cli.js

Beispielsweise möchten Sie in der Lage sein, Knotenmodule vom ausgeführten Skript auf einem Computer zu installieren, auf dem NPM nicht installiert ist. Und Sie möchten es mit CLI tun. In diesem Fall installieren Sie NPM einfach lokal in Ihren node_modules, während Sie Ihr Programm erstellen (npm i npm ).

Dann benutze es so:

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

Dann könnte Ihr Programm sogar in eine Binärdatei gepackt werden, zum Beispiel mit einem PKG- Paket. In diesem Fall müssen Sie die --ignore-scriptsOption npm verwenden, da Node-Gyp zum Ausführen von Vorinstallationsskripten erforderlich ist

Tarkh
quelle