ฉันสามารถติดตั้งแพ็คเกจ NPM จาก javascript ที่ทำงานใน Node.js ได้หรือไม่


93

ฉันสามารถติดตั้งแพ็คเกจ NPM จากไฟล์ javascript ที่ทำงานใน Node.js ได้หรือไม่ ตัวอย่างเช่นฉันต้องการมีสคริปต์เรียกมันว่า "script.js" (... โดยใช้ NPM หรือไม่ ... ) ติดตั้งแพคเกจที่มักจะมีให้ผ่าน NPM ในตัวอย่างนี้ฉันต้องการติดตั้ง "FFI" (npm ติดตั้ง ffi)

คำตอบ:


112

เป็นไปได้ที่จะใช้npm แบบเป็นโปรแกรมและมีการระบุไว้ในเอกสารประกอบฉบับแก้ไขรุ่นเก่า ตั้งแต่นั้นมาได้ถูกลบออกจากเอกสารอย่างเป็นทางการ แต่ยังคงมีอยู่ในการควบคุมแหล่งที่มาด้วยคำสั่งต่อไปนี้:

แม้ว่าจะสามารถใช้ npm แบบเป็นโปรแกรมได้ แต่ API นั้นมีไว้สำหรับใช้โดย CLI เท่านั้นและไม่มีการรับประกันใด ๆ เกี่ยวกับความเหมาะสมสำหรับวัตถุประสงค์อื่นใด หากคุณต้องการใช้ npm เพื่อทำงานบางอย่างอย่างน่าเชื่อถือสิ่งที่ปลอดภัยที่สุดที่ต้องทำคือเรียกใช้คำสั่ง npm ที่ต้องการพร้อมอาร์กิวเมนต์ที่เหมาะสม

npm เวอร์ชันความหมายหมายถึง CLI เองแทนที่จะเป็น API ที่อยู่ภายใต้ API ที่ภายในไม่รับประกันว่าจะยังคงมีเสถียรภาพแม้ในขณะที่รุ่น NPM บ่งชี้ว่าไม่มีการเปลี่ยนแปลงทำลายได้รับการทำตาม semver

ในเอกสารต้นฉบับต่อไปนี้เป็นตัวอย่างโค้ดที่ให้มา:

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

เนื่องจากมีnpmอยู่ในnode_modulesโฟลเดอร์คุณจึงใช้require('npm')โหลดได้เหมือนกับโมดูลอื่น ๆ npm.commands.install()การติดตั้งโมดูลที่คุณจะต้องการใช้

หากคุณต้องการดูในแหล่งที่มามันก็อยู่ในGitHubเช่นกัน นี่คือตัวอย่างการทำงานที่สมบูรณ์ของโค้ดซึ่งเทียบเท่ากับการทำงานnpm installโดยไม่มีอาร์กิวเมนต์บรรทัดคำสั่ง:

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

โปรดสังเกตว่าอาร์กิวเมนต์แรกของฟังก์ชันการติดตั้งคืออาร์เรย์ แต่ละองค์ประกอบของอาร์เรย์เป็นโมดูลที่npmจะพยายามติดตั้ง

การใช้งานขั้นสูงเพิ่มเติมสามารถพบได้ในnpm-cli.jsไฟล์ในการควบคุมแหล่งที่มา


5
ในกรณีนี้จะช่วยใครได้ - ให้แน่ใจว่าคุณทำnpm install npm --saveก่อน ตัวอย่างใช้งานได้ดี :)
mikermcneil

6
นอกจากนี้โปรดระวัง - npmมีการอ้างอิงจำนวนมากดังนั้นการเพิ่มลงในโมดูลของคุณมักจะส่งผลให้ใช้เวลาดาวน์โหลดนานขึ้น ดูหนึ่งในchild_processคำตอบเพื่อใช้ประโยชน์จาก npm ทั่วโลกที่ติดตั้งไว้แล้วในเครื่องของผู้ใช้ของคุณ
mikermcneil

1
ไม่ผ่านnpm.configไปnpm.load! แม้แต่ @isaacs ก็ไม่รู้ว่าจะเกิดเรื่องแปลก ๆ แบบไหน! ดูgithub.com/npm/npm/issues/4861#issuecomment-40533836แต่คุณสามารถข้ามอาร์กิวเมนต์แรกได้
Georgii Ivankin

2
ฉันจะกำหนดเส้นทางปลายทางได้อย่างไร? (เมื่อมันแตกต่างจากที่process.cwd())
Gajus

1
สำหรับผู้ที่ต้องการนำเข้า NPM แม้จะมีคำเตือนglobal-npmจะดีกว่า (เล็กกว่าไม่มีการอ้างอิง) มากกว่าnpm install npm --save
Xunnamius

28

คุณสามารถใช้child_process execหรือexecSyncเพื่อวางไข่เชลล์จากนั้นดำเนินการคำสั่งที่ต้องการภายในเชลล์นั้นบัฟเฟอร์เอาต์พุตที่สร้างขึ้น:

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

หากมีฟังก์ชันเรียกกลับฟังก์ชันจะถูกเรียกด้วยอาร์กิวเมนต์ (error, stdout, stderr) ด้วยวิธีนี้คุณสามารถเรียกใช้การติดตั้งได้เหมือนที่คุณทำด้วยตนเองและดูผลลัพธ์ทั้งหมด

โดยทั่วไปเมธอด child_process.execSync () จะเหมือนกับ child_process.exec () ยกเว้นว่าเมธอดจะไม่ส่งคืนจนกว่ากระบวนการย่อยจะปิดลงอย่างสมบูรณ์


2
นี่เป็นตัวเลือกเดียวจากคำตอบทั้งหมดที่เช่นให้คุณรันการติดตั้ง npm และรับผลลัพธ์แบบเต็มราวกับว่าคุณกำลังดำเนินการคำสั่งด้วยตนเอง! ขอบคุณ!
Jörn Berkefeld

1
อะไรstdio: [0,1,2]ทำอย่างไร
Zach Smith

หากมีการจัดเตรียมฟังก์ชันเรียกกลับให้กับ child_process.exec ฟังก์ชันนี้จะถูกเรียกด้วยอาร์กิวเมนต์ที่เทียบเท่ากับ [process.stdin, process.stdout, process.stderr] หรือ [0,1,2] ตามapi doc
krankuba

26

ใช่. คุณสามารถใช้ child_process เพื่อรันคำสั่งระบบ

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
ใช่คุณสามารถอย่างไรก็ตามการอ้างอิงบางอย่างจะล้มเหลวในการติดตั้ง (พูดจากประสบการณ์เพราะครั้งหนึ่งฉันเขียนเซิร์ฟเวอร์CIสำหรับ node.js)
Matej

5
บน windows นี่ใช้ไม่ได้! คุณต้องโทรnpm.cmdแทน
DUzun

11

มันอาจจะง่ายหน่อย

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

2
นอกจากนี้ยังมีข้อดีที่ stderr (และ stdout) ถูกพิมพ์เมื่อเกิดขึ้นไม่ใช่เมื่อสิ้นสุดการดำเนินการ!
mvermand

1
สิ่งนี้ไม่ได้พิมพ์ออกมาในระดับเดียวกับคำตอบจาก @krankuba ด้านล่างเท่าที่ฉันสามารถบอกได้
Zach Smith

6

ฉันมีช่วงเวลาหนึ่งที่พยายามหาตัวอย่างแรกเพื่อทำงานในไดเร็กทอรีโปรเจ็กต์โพสต์ที่นี่ในกรณีที่มีใครพบสิ่งนี้ เท่าที่ฉันบอกได้ NPM ยังคงทำงานได้ดีในการโหลดโดยตรง แต่เนื่องจากถือว่า CLI เราต้องทำซ้ำด้วยตัวเองเล็กน้อยในการตั้งค่า:

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


2

ฉันเป็นผู้เขียนโมดูลที่อนุญาตให้ทำในสิ่งที่คุณคิดได้ ดูสดปลั๊กอินผู้จัดการ

คุณสามารถติดตั้งและเรียกใช้แพ็กเกจแทบใดก็ได้จาก NPM, Github หรือจากโฟลเดอร์

นี่คือตัวอย่าง:

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

ในรหัสด้านบนฉันติดตั้งmomentแพ็คเกจที่รันไทม์โหลดและดำเนินการ ในตอนท้ายฉันถอนการติดตั้ง

ภายในฉันไม่ได้เรียกใช้npmcli แต่จริงๆแล้วดาวน์โหลดแพ็คเกจและเรียกใช้ภายในโหนด VM sandbox


1

วิธีแก้ปัญหาที่ยอดเยี่ยมโดย @hexacyanide แต่ปรากฎว่า NPM ไม่ปล่อยเหตุการณ์ "บันทึก" อีกต่อไป (อย่างน้อยก็ในเวอร์ชัน 6.4.1) แต่พวกเขาพึ่งพาแบบสแตนด์อโลนโมดูลhttps://github.com/npm/npmlog โชคดีที่เป็นซิงเกิลตันดังนั้นเราจึงสามารถเข้าถึงอินสแตนซ์เดียวกันที่ NPM ใช้สำหรับบันทึกและสมัครรับข้อมูลบันทึกเหตุการณ์:

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

  });

ดังที่คุณเห็นจากโค้ด NPM ยังแสดงเมตริกประสิทธิภาพบนprocessดังนั้นเราจึงสามารถใช้เพื่อตรวจสอบความคืบหน้าได้


1

อีกทางเลือกหนึ่งซึ่งไม่ได้กล่าวถึงในที่นี้คือการแยกและเรียกใช้ CLI จาก ./node_modules/npm/bin/npm-cli.js

ตัวอย่างเช่นคุณต้องการสามารถติดตั้งโมดูลโหนดจากการรันสคริปต์บนเครื่องซึ่งไม่ได้ติดตั้ง NPM และคุณต้องการทำกับ CLI ในกรณีนี้เพียงแค่ติดตั้ง NPM ใน node_modules ของคุณในเครื่องในขณะที่สร้างโปรแกรมของคุณ ( npm i npm)

จากนั้นใช้ดังนี้:

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

จากนั้นโปรแกรมของคุณอาจบรรจุลงในไฟล์ไบนารีตัวอย่างเช่นด้วยแพ็คเกจPKG ในกรณีนี้คุณต้องใช้--ignore-scriptsอ็อพชัน npm เนื่องจาก node-gyp จำเป็นต้องรันสคริปต์ก่อนการติดตั้ง

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.