Чи можу я встановити пакет NPM з javascript, що працює в Node.js?


91

Чи можу я встановити пакет NPM із файлу javascript, запущеного в Node.js? Наприклад, я хотів би мати сценарій, назвемо його "script.js", який якимось чином (... з використанням NPM чи ні ...) встановлює пакет, зазвичай доступний через NPM. У цьому прикладі я хотів би встановити "FFI". (npm встановити ffi)

Відповіді:


109

Дійсно можливо використовувати 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 Натомість ви можете просто пропустити перший аргумент.
Георгій Іванкін

2
Як встановити шлях призначення? (коли інакше, ніж process.cwd())
Гаджус

1
Для тих, хто хоче імпортувати NPM, незважаючи на попередження, глобальний npm кращий (менший, без залежностей), ніжnpm install npm --save
Xunnamius,

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)
Матей

5
На вікнах це не працює! npm.cmdНатомість вам потрібно зателефонувати .
DUzun

26

Ви можете використовувати child_process . exec або execSync, щоб створити оболонку, а потім виконати потрібну команду в цій оболонці, буферизуючи будь-який згенерований результат:

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

Якщо передбачена функція зворотного виклику, вона викликається з аргументами (помилка, stdout, stderr). Таким чином, ви можете запустити інсталяцію, як ви робите це вручну і побачити повний результат.

Метод child_process.execSync (), як правило, ідентичний child_process.exec (), за винятком того, що метод не повертається, поки дочірній процес повністю не закриється.


2
це єдиний варіант з усіх відповідей, який, наприклад, дозволяє вам запустити npm install і отримати повний результат, як якщо б ви виконували команду вручну! спасибі!
Йорн Беркефельд,

1
Що робить stdio: [0,1,2]?
Зак Сміт,

якщо функція зворотного виклику надається child_process.exec, вона викликається з аргументами, еквівалентними [process.stdin, process.stdout, process.stderr] або [0,1,2] відповідно до api doc
krankuba

11

насправді це може бути трохи просто

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

2
Це також має ту перевагу, що stderr (і stdout) друкуються по мірі їх виникнення, а не в кінці виконання!
mvermand

1
Це, наскільки я можу зрозуміти, друкується не в такій мірі, як відповідь від @krankuba нижче.
Зак Сміт,

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

Я автор модуля, який дозволяє робити саме те, що ви маєте на увазі. Див. Live-plugin-manager .

Ви можете встановити та запустити практично будь-який пакет із 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 вузла.


1

Чудове рішення від @hexacyanide, але виявилося, що NPM більше не видає події "log" (принаймні станом на версію 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 необхідний для запуску попередньо встановлених сценаріїв

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.