Czy mogę zainstalować pakiet NPM z javascript działającego w Node.js?


93

Czy mogę zainstalować pakiet NPM z pliku javascript uruchomionego w Node.js? Na przykład chciałbym mieć skrypt, nazwijmy go „script.js”, który w jakiś sposób (… używając NPM lub nie…) instaluje pakiet zwykle dostępny przez NPM. W tym przykładzie chciałbym zainstalować „FFI”. (npm install ffi)

Odpowiedzi:


112

Rzeczywiście jest możliwe programowe użycie npm i zostało to nakreślone w starszych wersjach dokumentacji. Od tego czasu został usunięty z oficjalnej dokumentacji, ale nadal istnieje w kontroli źródła z następującym stwierdzeniem:

Chociaż npm może być używany programowo, jego interfejs API jest przeznaczony do użytku tylko przez interfejs CLI i nie ma żadnych gwarancji dotyczących jego przydatności do jakichkolwiek innych celów. Jeśli chcesz użyć npm do niezawodnego wykonania jakiegoś zadania, najbezpieczniej jest wywołać żądane polecenie npm z odpowiednimi argumentami.

Semantyczna wersja npm odnosi się do samego interfejsu wiersza polecenia, a nie do bazowego interfejsu API. Nie ma gwarancji, że wewnętrzny interfejs API pozostanie stabilny, nawet jeśli wersja npm wskazuje, że nie wprowadzono żadnych istotnych zmian zgodnie z semver .

W oryginalnej dokumentacji przedstawiono przykładowy kod:

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

Ponieważ npm istnieje w node_modulesfolderze, możesz go użyć require('npm')do załadowania go jak każdego innego modułu. Aby zainstalować moduł, będziesz chciał użyć npm.commands.install().

Jeśli chcesz zajrzeć do źródła, to również jest na GitHubie . Oto kompletny działający przykład kodu, który jest odpowiednikiem uruchomienia npm installbez żadnych argumentów wiersza poleceń:

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

Zauważ, że pierwszym argumentem funkcji install jest tablica. Każdy element tablicy to moduł, który npm spróbuje zainstalować.

Bardziej zaawansowane zastosowania można znaleźć w npm-cli.jspliku dotyczącym kontroli źródła.


5
gdyby to komukolwiek pomogło - upewnij się, że to zrobisz jako npm install npm --savepierwsza. Przykład działa świetnie :)
mikermcneil

6
Uważaj również - npmma wiele zależności, więc dodanie go do modułu najprawdopodobniej spowoduje, że pobieranie zajmie DUŻO więcej czasu. Zapoznaj się z jedną z child_processodpowiedzi, aby wykorzystać globalny npm już zainstalowany na komputerach użytkowników.
mikermcneil

1
Nie przechodź npm.configdo npm.load! Nawet @isaacs nie wie, jakie dziwne rzeczy będą się wtedy dziać! Zobacz github.com/npm/npm/issues/4861#issuecomment-40533836 Zamiast tego możesz po prostu pominąć pierwszy argument.
Georgii Ivankin

2
Jak ustawić ścieżkę docelową? (kiedy jest inny niż process.cwd())
Gajus

1
Dla tych, którzy chcą importować NPM pomimo ostrzeżeń, global-npm jest lepszy (mniejszy, bez zależności) niżnpm install npm --save
Xunnamius

28

Możesz użyć child_process . exec lub execSync, aby spawnować powłokę, a następnie wykonaj żądane polecenie w tej powłoce, buforując wszelkie wygenerowane dane wyjściowe:

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

Jeśli podano funkcję zwrotną, jest ona wywoływana z argumentami (błąd, stdout, stderr). W ten sposób możesz uruchomić instalację tak, jak robisz to ręcznie i zobaczyć pełne wyniki.

Metoda child_process.execSync () jest zasadniczo taka sama jak child_process.exec () z wyjątkiem tego, że metoda nie zwróci wartości, dopóki proces potomny nie zostanie całkowicie zamknięty.


2
jest to jedyna opcja spośród wszystkich odpowiedzi, która pozwala na przykład uruchomić instalację npm i uzyskać pełne dane wyjściowe, tak jakbyś wykonywał polecenie ręcznie! Dziękuję Ci!
Jörn Berkefeld

1
Co robi stdio: [0,1,2]?
Zach Smith

jeśli funkcja zwrotna jest dostarczona do child_process.exec, jest wywoływana z argumentami równoważnymi z [process.stdin, process.stdout, process.stderr] lub [0,1,2] zgodnie z dokumentem API
krankuba

26

tak. możesz użyć child_process do wykonania polecenia systemowego

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
Tak, możesz, jednak niektóre zależności BĘDĄ się nie instalować (z doświadczenia, bo kiedyś napisałem serwer CI dla node.js)
Matej

5
W systemie Windows to nie działa! Zamiast tego musisz zadzwonić npm.cmd.
DUzun

11

to może być trochę łatwe

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

2
Ma to również tę zaletę, że stderr (i stdout) są wypisywane zaraz po ich wystąpieniu, a nie na końcu wykonania!
mvermand

1
O ile wiem, nie jest to drukowane w takim samym stopniu, jak odpowiedź z @krankuba poniżej.
Zach Smith

6

Miałem czas, próbując zmusić pierwszy przykład do pracy w katalogu projektu, publikując tutaj, na wypadek gdyby ktoś inny to znalazł. O ile wiem, NPM nadal działa poprawnie, ładowany bezpośrednio, ale ponieważ zakłada CLI, musimy powtórzyć trochę konfigurację:

// 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 to pakiet, którego używa npm do pobierania metadanych pakietów i paczek z plikami. Posiada stabilny, publiczny interfejs API.


2

Jestem autorem modułu, który pozwala robić dokładnie to, o czym myślisz. Zobacz live-plugin-manager .

Możesz zainstalować i uruchomić praktycznie każdy pakiet z NPM, Github lub z folderu.

Oto przykład:

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

W powyższym kodzie instaluję momentpakiet w czasie wykonywania, ładuję go i uruchamiam. Na koniec go odinstalowałem.

Wewnętrznie nie uruchamiam npmcli, ale faktycznie pobieram pakiety i uruchamiam w piaskownicy maszyny wirtualnej węzła.


1

Świetne rozwiązanie autorstwa @hexacyanide, ale okazało się, że NPM nie emituje już zdarzenia „log” (przynajmniej od wersji 6.4.1). Zamiast tego opierają się na samodzielnym module https://github.com/npm/npmlog . Na szczęście jest to singleton, więc możemy dotrzeć do tej samej instancji, której NPM używa do dzienników i subskrybować zdarzenia dziennika:

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

  });

Jak widać z kodu, NPM również emituje metryki wydajnościowe process, więc możemy go również używać do monitorowania postępów.


1

Inną opcją, o której tutaj nie wspomniano, jest zrobienie rozwidlenia i uruchomienie CLI bezpośrednio z ./node_modules/npm/bin/npm-cli.js

Na przykład chcesz mieć możliwość zainstalowania modułów węzła z uruchamiania skryptu na komputerze, na którym nie jest zainstalowany NPM. I chcesz to zrobić z CLI. W takim przypadku po prostu zainstaluj NPM w swoich node_modules lokalnie podczas budowania programu ( npm i npm).

Następnie użyj tego w ten sposób:

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

Wtedy twój program mógłby być nawet spakowany do pliku binarnego, na przykład za pomocą pakietu PKG . W takim przypadku musisz użyć --ignore-scriptsopcji npm, ponieważ node-gyp jest wymagany do uruchomienia preinstalowanych skryptów

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.