【问题标题】:Can I install a NPM package from javascript running in Node.js?我可以从 Node.js 中运行的 javascript 安装 NPM 包吗?
【发布时间】:2013-04-04 03:56:46
【问题描述】:

我可以从 Node.js 中运行的 javascript 文件安装 NPM 包吗?例如,我想要一个脚本,我们称它为“script.js”,它以某种方式(......是否使用 NPM......)安装通常可通过 NPM 获得的包。在这个例子中,我想安装“FFI”。 (npm install ffi)

【问题讨论】:

    标签: javascript node.js npm


    【解决方案1】:

    确实可以以编程方式使用 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);
      });
    });
    

    注意 install 函数的第一个参数是一个数组。数组的每个元素都是一个 npm 将尝试安装的模块。

    更多高级用法可以在源代码管理的npm-cli.js 文件中找到。

    【讨论】:

    • 如果这对任何人都有帮助 - 请确保您首先使用npm install npm --save。示例效果很好:)
    • 另外,请注意——npm 有很多依赖项,因此将其添加到您的模块很可能会导致下载时间更长。查看child_process 答案之一,以利用已安装在用户计算机上的全局 npm。
    • 不要将npm.config 传递给npm.load!连@isaacs 都不知道到时候会发生什么样的怪事!请参阅github.com/npm/npm/issues/4861#issuecomment-40533836 相反,您可以跳过第一个参数。
    • 如何设置目标路径? (当 process.cwd() 不同时)
    • 对于那些不顾警告仍希望导入 NPM 的人,global-npmnpm install npm --save 更好(更小,无依赖关系)
    【解决方案2】:

    您可以使用 child_process.execexecSync 生成一个 shell,然后在该 shell 中执行所需的命令,缓冲任何生成的输出:

    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() 相同,只是该方法在子进程完全关闭之前不会返回。

    【讨论】:

    • 这是所有答案中唯一的选项,例如,它可以让您运行 npm install 并获得完整的输出,就像您手动执行命令一样!谢谢!
    • stdio: [0,1,2] 是做什么的?
    • 如果给 child_process.exec 提供了一个回调函数,则调用它的参数相当于 [process.stdin, process.stdout, process.stderr] 或 [0,1,2] api doc
    【解决方案3】:

    是的。您可以使用 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);
         }
     });
    

    【讨论】:

    • 是的,您可以,但是某些依赖项将无法安装(根据经验,因为曾几何时,我实际上为 node.js 编写了一个 CI 服务器)
    • 在 Windows 上这不起作用!你必须打电话给npm.cmd
    【解决方案4】:

    其实可以简单一点

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

    【讨论】:

    • 这还有一个优点,即 stderr(和 stdout)在发生时打印,而不是在执行结束时打印!
    • 据我所知,这与下面@krankuba 的答案的打印程度不同。
    【解决方案5】:

    我花了很长时间试图让第一个示例在项目目录中工作,在此处发布以防其他人发现它。据我所知,直接加载 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);
        });
    });
    

    【讨论】:

    • 这似乎在npm >v7.x 中发生了变化:commands.install 不再遵循process.chdir 行为,在cli.load 回调中强制cli.prefix = <myDestinationFolder> 似乎可以解决它(尽管可能有更好的方法,但我还没有找到)
    • 这里是使用 npm v7.5.2 的示例:github.com/probot/create-probot-app/blob/…
    【解决方案6】:

    pacote 是 npm 用来获取包元数据和 tarball 的包。它有一个稳定的公共 API。

    【讨论】:

      【解决方案7】:

      这里没有提到的另一个选项是直接从./node_modules/npm/bin/npm-cli.js 执行 fork 并运行 CLI

      例如,您希望能够通过在未安装 NPM 的机器上运行脚本来安装节点模块。你确实想用 CLI 来做。在这种情况下,只需在构建程序时在本地的 node_modules 中安装 NPM (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

      【讨论】:

        【解决方案8】:

        我是一个模块的作者,它可以完全按照你的想法去做。 见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 包,加载并执行它。最后我把它卸载了。

        在内部我不运行npm cli,而是实际下载包并在节点虚拟机沙箱中运行。

        【讨论】:

          【解决方案9】:

          @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 上发出性能指标,因此我们也可以使用它来监控进度。

          【讨论】:

            【解决方案10】:

            tarkh's great answer 上添加更多内容。如果您不打算显式安装 npm CLI 并且已经通过 package.json 文件安装 npm。那么./node_modules/npm/bin/npm-cli.js中的文件就是可以用来运行命令的CLI。

            为了使其全局可用,请运行以下命令并将二进制文件添加到路径中。

            ln -sf /usr/app/node_modules/npm/bin/npm-cli.js /usr/bin/npm
            

            此外,如果它对某人有帮助。下面介绍了如何模仿 npm install 命令来安装 package.json 中的所有包。

            const npm = require('npm');
            const Bluebird = require('bluebird');
            
                async installDependencies() {
                        await Bluebird.promisify(npm.load)({
                            loglevel: 'silent',
                            progress: false,
                        });
                        await Bluebird.promisify(npm.install)(
                            "/path/to/directory/where/package.json/is",
                        );
                    }
            

            【讨论】:

              猜你喜欢
              • 2016-07-02
              • 1970-01-01
              • 2014-09-04
              • 1970-01-01
              • 2017-10-30
              • 1970-01-01
              • 2014-01-13
              • 1970-01-01
              • 2015-09-16
              相关资源
              最近更新 更多