Могу ли я установить пакет NPM из javascript, запущенного в Node.js?

93

Могу ли я установить пакет NPM из файла javascript, запущенного в Node.js? Например, я хотел бы иметь сценарий, назовем его "script.js", который каким-то образом (... используя NPM или нет ...) устанавливает пакет, обычно доступный через NPM. В этом примере я хочу установить «FFI». (npm установить ffi)

Джастин
источник

Ответы:

112

Действительно, можно использовать npm программно, и это было описано в более старых версиях документации. С тех пор он был удален из официальной документации, но все еще существует в системе контроля версий со следующим утверждением:

Хотя npm можно использовать программно, его API предназначен для использования только с помощью интерфейса командной строки, и не дается никаких гарантий относительно его пригодности для каких-либо других целей. Если вы хотите использовать 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())
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 install и получить полный вывод, как если бы вы выполняли команду вручную! Спасибо!
Йорн Беркефельд
1
Что stdio: [0,1,2]делать?
Зак Смит
если для 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)
Матей
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 ниже.
Зак Смит
6

Я чертовски потратил время, пытаясь заставить первый пример работать в каталоге проекта, размещая здесь сообщения на случай, если кто-то еще найдет это. Насколько я могу судить, NPM по-прежнему отлично работает при прямой загрузке, но поскольку он предполагает использование интерфейса командной строки, мы должны немного повторить настройку:

// 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 - это пакет, который npm использует для получения метаданных пакета и архивов. У него стабильный общедоступный API.

Джеймс А. Розен
источник
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, а на самом деле загружаю пакеты и запускаю их в изолированной программной среде виртуальной машины узла.

Давиде Икарди
источник
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 требуется для запуска сценариев предустановки.

тарх
источник