Dapatkah saya menginstal paket NPM dari javascript yang berjalan di Node.js?

93

Dapatkah saya menginstal paket NPM dari file javascript yang berjalan di Node.js? Misalnya, saya ingin memiliki skrip, sebut saja "script.js" yang entah bagaimana (... menggunakan NPM atau tidak ...) memasang paket yang biasanya tersedia melalui NPM. Dalam contoh ini, saya ingin menginstal "FFI". (npm instal ffi)

Justin
sumber

Jawaban:

112

Memang mungkin untuk menggunakan npm secara terprogram, dan itu diuraikan dalam revisi dokumentasi yang lebih lama. Sejak itu telah dihapus dari dokumentasi resmi, tetapi masih ada di kontrol sumber dengan pernyataan berikut:

Meskipun npm dapat digunakan secara terprogram, API-nya dimaksudkan untuk digunakan hanya oleh CLI, dan tidak ada jaminan yang dibuat terkait kesesuaiannya untuk tujuan lain apa pun. Jika Anda ingin menggunakan npm untuk melakukan beberapa tugas dengan andal, hal teraman yang harus dilakukan adalah menjalankan perintah npm yang diinginkan dengan argumen yang sesuai.

Versi semantik npm mengacu pada CLI itu sendiri, bukan API yang mendasarinya. API internal tidak dijamin akan tetap stabil meskipun versi npm menunjukkan tidak ada perubahan yang melanggar yang telah dibuat menurut semver .

Dalam dokumentasi asli, berikut ini adalah contoh kode yang diberikan:

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

Karena npm ada di node_modulesfolder, Anda dapat menggunakannya require('npm')untuk memuatnya seperti modul lainnya. Untuk memasang modul, Anda akan ingin menggunakan npm.commands.install().

Jika Anda perlu melihat sumbernya maka itu juga ada di GitHub . Berikut adalah contoh lengkap kode yang berfungsi, yang setara dengan menjalankan npm installtanpa argumen baris perintah apa pun:

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

Perhatikan bahwa argumen pertama untuk fungsi instal adalah array. Setiap elemen dari array adalah modul yang akan coba dipasang oleh npm .

Penggunaan lebih lanjut dapat ditemukan di npm-cli.jsfile pada kontrol sumber.

hexacyanide
sumber
5
dalam hal ini membantu siapa saja - pastikan Anda melakukannya npm install npm --saveterlebih dahulu. Contoh bekerja dengan baik :)
mikermcneil
6
Selain itu, waspadalah-- npmmemiliki banyak dependensi, jadi menambahkannya ke modul Anda kemungkinan besar akan mengakibatkan proses download JAUH lebih lama. Lihat salah satu child_processjawaban untuk memanfaatkan npm global yang sudah terinstal di mesin pengguna Anda.
mikermcneil
1
Tidak lulus npm.configuntuk npm.load! Bahkan @isaacs tidak tahu hal aneh seperti apa yang akan terjadi nanti! Lihat github.com/npm/npm/issues/4861#issuecomment-40533836 Alih-alih, Anda dapat melewati argumen pertama.
Georgii Ivankin
2
Bagaimana cara mengatur jalur tujuan? (bila berbeda dengan process.cwd())
Gajus
1
Bagi mereka yang ingin mengimpor NPM meskipun ada peringatan, global-npm lebih baik (lebih kecil, tidak ada ketergantungan) daripadanpm install npm --save
Xunnamius
28

Anda dapat menggunakan child_process . exec atau execSync untuk menelurkan shell kemudian menjalankan perintah yang diinginkan di dalam shell itu, melakukan buffering pada setiap output yang dihasilkan:

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

Jika fungsi callback disediakan, itu dipanggil dengan argumen (error, stdout, stderr). Dengan cara ini Anda dapat menjalankan instalasi seperti Anda melakukannya secara manual dan melihat output lengkapnya.

Metode child_process.execSync () umumnya identik dengan child_process.exec () dengan pengecualian bahwa metode tersebut tidak akan kembali sampai proses anak ditutup sepenuhnya.

krankuba
sumber
2
ini adalah satu-satunya pilihan dari semua jawaban yang memungkinkan Anda menjalankan npm install dan mendapatkan output penuh seolah-olah Anda menjalankan perintah secara manual! Terima kasih!
Jörn Berkefeld
1
Apa yang stdio: [0,1,2]dilakukannya?
Zach Smith
jika fungsi callback disediakan untuk child_process.exec, itu dipanggil dengan argumen yang setara dengan [process.stdin, process.stdout, process.stderr] atau [0,1,2] sesuai dokumen api
krankuba
26

Iya. Anda dapat menggunakan child_process untuk menjalankan perintah sistem

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);
     }
 });
Otak
sumber
2
Ya, Anda bisa, namun beberapa dependensi AKAN gagal diinstal (berbicara dari pengalaman, karena suatu ketika saya benar-benar menulis server CI untuk node.js)
Matej
5
Di windows ini tidak berhasil! Anda harus menelepon npm.cmdsebagai gantinya.
DUzun
11

sebenarnya bisa sedikit mudah

var exec = require('child_process').exec;
child = exec('npm install ffi').stderr.pipe(process.stderr);
Vyacheslav Shebanov
sumber
2
Ini juga memiliki keuntungan bahwa stderr (dan stdout) dicetak saat terjadi, bukan pada akhir eksekusi!
mvermand
1
Ini tidak tercetak sejauh jawaban dari @krankuba di bawah, sejauh yang saya tahu.
Zach Smith
6

Saya sempat mencoba mendapatkan contoh pertama untuk bekerja di dalam direktori proyek, memposting di sini jika ada orang lain yang menemukan ini. Sejauh yang saya tahu, NPM masih berfungsi dengan baik saat dimuat secara langsung, tetapi karena mengasumsikan CLI, kami harus mengulangi sedikit pengaturannya:

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

pacote adalah paket yang digunakan npm untuk mengambil metadata paket dan tarball. Ini memiliki API publik yang stabil.

James A. Rosen
sumber
2

Saya penulis modul yang memungkinkan untuk melakukan apa yang Anda pikirkan. Lihat live-plugin-manager .

Anda dapat menginstal dan menjalankan hampir semua paket dari NPM, Github atau dari folder.

Berikut contohnya:

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

Dalam kode di atas saya menginstal momentpaket saat runtime, memuat dan menjalankannya. Pada akhirnya saya mencopotnya.

Secara internal saya tidak menjalankan npmcli tetapi sebenarnya mengunduh paket dan berjalan di dalam kotak pasir VM node.

Davide Icardi
sumber
1

Sebuah solusi hebat dari @hexacyanide, tetapi ternyata NPM tidak lagi mengeluarkan peristiwa "log" (setidaknya pada versi 6.4.1). Sebaliknya, mereka mengandalkan modul mandiri https://github.com/npm/npmlog . Untungnya ini tunggal, jadi kita bisa mencapai contoh yang sama yang digunakan NPM untuk log dan berlangganan kejadian 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" );    
    });

  });

Seperti yang Anda lihat dari kode, NPM juga memancarkan metrik kinerja process, jadi kami juga dapat menggunakannya untuk memantau kemajuan.

Dmitry Sheiko
sumber
1

Opsi lain, yang tidak disebutkan di sini, adalah melakukan fork dan menjalankan CLI langsung dari ./node_modules/npm/bin/npm-cli.js

Misalnya Anda ingin dapat menginstal modul node dari menjalankan skrip pada mesin, yang tidak memiliki NPM yang diinstal. Dan Anda ingin melakukannya dengan CLI. Dalam hal ini, instal NPM di node_modules Anda secara lokal saat membangun program Anda ( npm i npm).

Kemudian gunakan seperti ini:

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

Kemudian program Anda bahkan bisa dikemas ke file biner, misalnya dengan paket PKG . Dalam hal ini Anda perlu menggunakan --ignore-scriptsopsi npm, karena node-gyp diperlukan untuk menjalankan skrip prainstal

tarkh
sumber