Puis-je installer un package NPM à partir de javascript exécuté dans Node.js?


91

Puis-je installer un package NPM à partir d'un fichier javascript exécuté dans Node.js? Par exemple, j'aimerais avoir un script, appelons-le "script.js" qui d'une manière ou d'une autre (... en utilisant NPM ou non ...) installe un package généralement disponible via NPM. Dans cet exemple, j'aimerais installer "FFI". (npm installer ffi)

Réponses:


109

Il est en effet possible d'utiliser npm par programme, et cela a été décrit dans les anciennes révisions de la documentation. Il a depuis été supprimé de la documentation officielle, mais existe toujours sur le contrôle de code source avec la déclaration suivante:

Bien que npm puisse être utilisé par programme, son API est destinée à être utilisée par la CLI uniquement, et aucune garantie n'est donnée concernant son aptitude à d'autres fins. Si vous souhaitez utiliser npm pour effectuer une tâche de manière fiable, la chose la plus sûre à faire est d'appeler la commande npm souhaitée avec les arguments appropriés.

La version sémantique de npm fait référence à l'interface de ligne de commande elle-même, plutôt qu'à l'API sous-jacente. L'API interne n'est pas garantie de rester stable même lorsque la version de npm indique qu'aucune modification de rupture n'a été apportée selon semver .

Dans la documentation d'origine, voici l'exemple de code fourni:

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

Puisque npm existe dans le node_modulesdossier, vous pouvez l'utiliser require('npm')pour le charger comme n'importe quel autre module. Pour installer un module, vous voudrez utiliser npm.commands.install().

Si vous avez besoin de chercher dans la source, c'est également sur GitHub . Voici un exemple de travail complet du code, qui équivaut à une exécution npm installsans aucun argument de ligne de commande:

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

Notez que le premier argument de la fonction d'installation est un tableau. Chaque élément du tableau est un module que npm tentera d'installer.

Une utilisation plus avancée peut être trouvée dans le npm-cli.jsfichier sur le contrôle de code source.


5
au cas où cela aiderait quelqu'un, assurez-vous de le faire d' npm install npm --saveabord. L'exemple fonctionne très bien :)
mikermcneil

6
Aussi, méfiez-vous - npma beaucoup de dépendances, donc l'ajouter à votre module entraînera probablement BEAUCOUP plus de temps à télécharger. Découvrez l'une des child_processréponses pour tirer parti du npm global déjà installé sur les machines de vos utilisateurs.
mikermcneil

1
Ne passez pas npm.configà npm.load! Même @isaacs ne sait pas quel genre de choses étranges se produiront alors! Voir github.com/npm/npm/issues/4861#issuecomment-40533836 Au lieu de cela, vous pouvez simplement ignorer le 1er argument.
Georgii Ivankin

2
Comment définir le chemin de destination? (quand c'est différent que le process.cwd())
Gajus

1
Pour ceux qui souhaitent importer NPM malgré les avertissements, global-npm est meilleur (plus petit, pas de dépendances) quenpm install npm --save
Xunnamius

26

Oui. vous pouvez utiliser child_process pour exécuter une commande système

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
Oui, vous pouvez, mais l'installation de certaines dépendances échouera (par expérience, car il était une fois en fait un serveur CI pour node.js)
Matej

5
Sous Windows, cela ne fonctionne pas! Vous devez appeler à la npm.cmdplace.
DUzun

26

Vous pouvez utiliser child_process . exec ou execSync pour générer un shell, puis exécutez la commande souhaitée dans ce shell, en mettant en mémoire tampon toute sortie générée:

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

Si une fonction de rappel est fournie, elle est appelée avec les arguments (error, stdout, stderr). De cette façon, vous pouvez exécuter l'installation comme vous le faites manuellement et voir la sortie complète.

La méthode child_process.execSync () est généralement identique à child_process.exec () à l'exception du fait que la méthode ne retournera pas tant que le processus enfant n'aura pas été complètement fermé.


2
c'est la seule option parmi toutes les réponses qui, par exemple, vous permet d'exécuter npm install et d'obtenir la sortie complète comme si vous exécutiez la commande manuellement! Merci!
Jörn Berkefeld

1
Que fait stdio: [0,1,2]-on?
Zach Smith

si une fonction de rappel est fournie à child_process.exec, elle est appelée avec les arguments équivalents à [process.stdin, process.stdout, process.stderr] ou [0,1,2] selon api doc
krankuba

11

ça peut être un peu facile

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

2
Cela présente également l'avantage que stderr (et stdout) sont imprimés au fur et à mesure qu'ils se produisent, pas à la fin de l'exécution!
mvermand

1
Cela ne s'imprime pas dans la même mesure que la réponse de @krankuba ci-dessous, pour autant que je sache.
Zach Smith

6

J'ai eu beaucoup de mal à essayer de faire fonctionner le premier exemple dans un répertoire de projet, en le publiant ici au cas où quelqu'un d'autre trouverait cela. Pour autant que je sache, NPM fonctionne toujours correctement chargé directement, mais comme il suppose CLI, nous devons nous répéter un peu la configuration:

// 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 est le package utilisé par npm pour récupérer les métadonnées et les archives tar. Il dispose d'une API publique stable.


2

Je suis l'auteur d'un module qui permet de faire exactement ce que vous avez en tête. Voir live-plugin-manager .

Vous pouvez installer et exécuter pratiquement n'importe quel package depuis NPM, Github ou depuis un dossier.

Voici un exemple:

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

Dans le code ci-dessus, j'installe le momentpackage au moment de l'exécution, le charge et l'exécute. À la fin, je le désinstalle.

En interne, je n'exécute pas npmcli mais je télécharge en fait des packages et je l' exécute dans un sandbox de VM de nœud.


1

Une excellente solution de @hexacyanide, mais il s'est avéré que NPM n'émet plus d'événement "log" (du moins à partir de la version 6.4.1). Au lieu de cela, ils s'appuient sur un module autonome https://github.com/npm/npmlog . Heureusement, c'est un singleton, nous pouvons donc atteindre la même instance que NPM utilise pour les journaux et nous abonner aux événements de journal:

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

  });

Comme vous pouvez le voir dans le code, NPM émet également des métriques de performances sur le process, nous pouvons donc également l'utiliser pour surveiller la progression.


1

Une autre option, qui n'a pas été mentionnée ici, est de faire un fork et d'exécuter CLI directement depuis ./node_modules/npm/bin/npm-cli.js

Par exemple, vous souhaitez pouvoir installer des modules de nœud à partir d'un script en cours d'exécution sur une machine sur laquelle NPM n'est pas installé. Et vous voulez le faire avec CLI. Dans ce cas, installez simplement NPM dans vos node_modules localement lors de la construction de votre programme ( npm i npm).

Ensuite, utilisez-le comme ceci:

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

Ensuite, votre programme pourrait même être compressé dans un fichier binaire, par exemple avec le package PKG . Dans ce cas, vous devez utiliser l' --ignore-scriptsoption npm, car node-gyp est requis pour exécuter des scripts de pré-installation

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.