Can I install a NPM package from a javascript file running in Node.js? For example, I'd like to have a script, let's call it "script.js" that somehow (...using NPM or not...) install a package usually available through NPM. In this example, I'd like to install "FFI". (npm install ffi)
问题:
回答1:
It is indeed possible to use npm programmatically, and it was outlined in older revisions of the documentation. It has since been removed from the official documentation, but still exists on source control with the following statement:
Although npm can be used programmatically, its API is meant for use by the CLI only, and no guarantees are made regarding its fitness for any other purpose. If you want to use npm to reliably perform some task, the safest thing to do is to invoke the desired npm command with appropriate arguments.
The semantic version of npm refers to the CLI itself, rather than the underlying API. The internal API is not guaranteed to remain stable even when npm's version indicates no breaking changes have been made according to semver.
In the original documentation, the following is the code sample that was provided:
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) { ... })
})
Since npm exists in the node_modules
folder, you can use require('npm')
to load it like any other module. To install a module, you will want to use npm.commands.install()
.
If you need to look in the source then it's also on GitHub. Here's a complete working example of the code, which is the equivalent of running npm install
without any command-line arguments:
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);
});
});
Note that the first argument to the install function is an array. Each element of the array is a module that npm will attempt to install.
More advanced use can be found in the npm-cli.js
file on source control.
回答2:
yes. you can use child_process to execute a system command
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);
}
});
回答3:
if you want to have output as well you can use:
var child_process = require('child_process');
child_process.execSync("npm install ffi",{stdio:[0,1,2]});
this way you can watch the installation like you do it on hand and avoid bad surprises (buffer full, etc)
回答4:
it can actually be a bit easy
var exec = require('child_process').exec;
child = exec('npm install ffi').stderr.pipe(process.stderr);
回答5:
I had a heck of a time trying to get the first example to work inside a project directory, posting here in case anyone else finds this. As far as I can tell, NPM still works fine loaded directly, but because it assumes CLI, we have to repeat ourselves a little setting it up:
// 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);
});
});
回答6:
I'm the author of a module that allow to do exactly what you have in mind. See live-plugin-manager.
You can install and run virtually any package from NPM, Github or from a folder.
Here an example:
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();
In the above code I install moment
package at runtime, load and execute it. At the end I uninstall it.
Internally I don't run npm
cli but actually download packages and run inside a node VM sandbox.
回答7:
pacote is the package that npm uses to fetch package metadata and tarballs. It has a stable, public API.
回答8:
A great solution by @hexacyanide, but it turned out that NPM doesn't emit "log" event anymore (at least as of version 6.4.1). Instead they rely on a standalone module https://github.com/npm/npmlog. Fortunately it's a singleton, so we can reach the very same instance NPM uses for logs and subscribe for log events:
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" );
});
});
As you can see from the code, NPM also emits performance metrics on the process
, so we can also use it to monitor the progress.