从 semver 版本确定 NPM 服务器上存在的依赖项的最大匹配版本 [英] Determine dependency's greatest matching version that exists on an NPM server from a semver version

查看:89
本文介绍了从 semver 版本确定 NPM 服务器上存在的依赖项的最大匹配版本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个有助于固定依赖项的节点脚本.

如何根据 semver 版本确定 NPM 服务器上现有包的最大实现版本?

例如,我们有一个依赖项foo"它在 package.json 中指定为 ~1.2.3.在 NPM 上,存在已发布版本 1.2.5,这是与 ~1.2.3 兼容的最新发布版本.

我需要编写一个脚本,将foo"作为输入和~1.2.3,然后在服务器查询后,返回1.2.5.像这样:

await fetchRealizedVersion('foo', '~1.2.3');//解析为 1.2.5

我知道我可以执行诸如 yarn upgrade 之类的操作,然后解析锁定文件,但我正在寻找一种更直接的方法来完成此操作.希望有一个包可以将其归结为 API 调用,但在谷歌搜索后我没有找到任何东西.

解决方案

希望有一个包可以将其归结为 API 调用,"

简短回答: 很遗憾没有,据我所知,目前还没有一个包.

get-latest-您可能想尝试的版本包:

<块引用>

基本用法:

const getLatestVersion = require('get-latest-version')getLatestVersion('some-other-module', {range: '^1.0.0'}).then((version) => console.log(version))//最高版本匹配 ^1.0.0 范围.catch((err) => console.error(err))


或者,考虑使用/编写自定义 node.js 模块来执行以下步骤:

  1. 要么:

    • 使用 npm view 命令检索所有可用版本给定包的 NPM 注册表:例如:

      npm view 版本--json

    • 或者,直接向公众发出 https 请求 npm注册表位于 https://registry.npmjs.org 以检索给定包中的所有可用版本.

  1. 解析返回的 JSON 并将其与 semver 范围(例如 ~1.2.3)一起传递给 node-semver 包的 maxSatisfying() 方法.

    maxSatisfying() 方法在 docs 中有描述 为:

    <块引用>

    maxSatisfying(versions, range):返回列表中满足范围的最高版本,如果都不满足则返回null.


自定义模块 (A):

get-latest-version.js(如下)中提供的自定义示例模块基本上执行上述步骤.在这个例子中,我们执行了 npm view 命令.

get-latest-version.js

'use strict';//------------------------------------------------------------------------------//要求//------------------------------------------------------------------------------const { exec } = require('child_process');const { maxSatisfying } = require('semver');//------------------------------------------------------------------------------//数据//------------------------------------------------------------------------------const errorBadge = '\x1b[31;40mERR!\x1b[0m';//------------------------------------------------------------------------------//帮手//------------------------------------------------------------------------------/*** 从给定的 shell 命令捕获写入 stdout 的数据.** @param {String} command 要执行的 shell 命令.* @return {Promise<string>} 一个 Promise 对象,其履行值包含* 写入标准输出的数据.当被拒绝时,将返回一条错误消息.* @私人的*/函数shellExec(命令){返回新的承诺((解决,拒绝)=> {exec(command, (error, stdout, stderr) => {如果(错误){拒绝(新错误(`执行命令失败:'${command}'`));返回;}解决(stdout.trim());});});}//------------------------------------------------------------------------------//公共接口//------------------------------------------------------------------------------模块.出口 = {/*** 检索与包的给定范围匹配的最新版本.** @异步* @param {String} pkg 包名.* @param {String} range semver 范围.* @returns {Promise<string>} 一个 Promise 对象,当完成时返回* 匹配的最新版本.当被拒绝时,将返回一条错误消息.*/异步 fetchRealizedVersion(pkg, range) {尝试 {const response = await shellExec(`npm view ${pkg} versions --json`);const 版本 = JSON.parse(response);返回 maxSatisfying(versions, range);} catch ({ message: errorMssg }) {抛出错误([`${errorBadge} ${errorMssg}`,`${errorBadge} '${pkg}' 可能不在 npm 注册表中.].join('\n'));}}};


用法:

以下 index.js 演示了使用上述模块.

index.js

'use strict';const { fetchRealizedVersion } = require('./get-latest-version.js');(异步函数(){尝试 {const latest = await fetchRealizedVersion('eslint', '~5.15.0');控制台日志(最新);//-->5.15.3} 捕获({ 消息:errMssg }){控制台错误(errMssg);}})();

如您所见,在该示例中,我们获得了 eslint 包的最新发布版本,该包与 semver 波浪号范围 ~5.15.0 兼容.

将满足~5.15.0的最新/最大版本打印到控制台:

<块引用>

$ node ./index.js5.15.3

注意:您可以随时使用在线semver计算器 实际上使用了 node-semver 包.

另一个用法示例:

以下 index.js 演示了使用上述模块获取多个包和不同范围的最新/最大版本.

index.js

'use strict';const { fetchRealizedVersion } = require('./get-latest-version.js');const 标准 = [{pkg: 'eslint',范围:'^4.9.0'},{pkg: 'eslint',范围:'~5.0.0'},{pkg: '灯塔',范围:'~1.0.0'},{pkg: '灯塔',范围:'^1.0.4'},{pkg: '纱线',范围:'~1.3.0'},{pkg: '纱线',范围:'^1.3.0'},{pkg: '纱线',范围:'^20.3.0'},{pkg: 'quuxbarfoo',范围:'~1.3.0'}];(异步函数(){//每个请求都是并行发送和读取的.const promises = criteria.map(async ({ pkg, range }) => {尝试 {返回等待 fetchRealizedVersion(pkg, range);} 捕获({ 消息:errMssg }){返回错误消息;}});//按顺序记录每个最新"semver.for(const 最新的承诺){控制台日志(等待最新);}})();

最后一个例子的结果如下:

<块引用>

$ node ./index.js4.19.15.0.11.0.61.6.51.3.21.22.4空值呃!执行命令失败:'npm view quuxbarfoo versions --json'呃!'quuxbarfoo' 可能不在 npm 注册表中.


附加说明:get-latest-version.js 中的 shellExec 辅助函数当前承诺 child_process 模块的 exec() 方法来封装 npm view 命令.但是,由于 node.js 版本 12 内置 util.promisify 提供了另一种方法来保证 exec() 方法 (如 exec 的文档所示),所以你可能更喜欢这样做.


自定义模块 (B):

如果您想避免使用 npm view 命令,您可以考虑直接向 https://registry.npmjs.org 端点发出请求(它与 npm view 命令发送 https GET 请求的端点相同).

get-latest-version.js 的修改版本(如下)本质上使用了内置 https.get.

用法与前面用法"部分中演示的相同.

get-latest-version.js

'use strict';//------------------------------------------------------------------------------//要求//------------------------------------------------------------------------------const https = require('https');const { maxSatisfying } = require('semver');//------------------------------------------------------------------------------//数据//------------------------------------------------------------------------------const endPoint = 'https://registry.npmjs.org';const errorBadge = '\x1b[31;40mERR!\x1b[0m';//------------------------------------------------------------------------------//帮手//------------------------------------------------------------------------------/*** 从 npm 注册表请求给定包的 JSON.** @param {String} pkg 包名.* @return {Promise<json>} 一个 Promise 对象,当完成时返回 JSON* 特定包的元数据.当被拒绝时,将返回一条错误消息.* @私人的*/函数 fetchPackageInfo(pkg) {返回新的承诺((解决,拒绝)=> {https.get(`${endPoint}/${pkg}/`, response => {const { statusCode, headers: { 'content-type': contentType } } = response;如果(状态码!== 200){拒绝(新错误(`对 ${endPoint} 的请求失败.${statusCode}`));返回;}如果 (!/^application\/json/.test(contentType)) {拒绝(新错误(`预期的应用程序/json,但收到${contentType}`));返回;}让数据 = '';response.on('data', chunk => {数据 += 块;});response.on('end', () => {解决(数据);});}).on('错误', 错误 => {拒绝(新错误(`找不到${endPoint}`));});});}//------------------------------------------------------------------------------//公共接口//------------------------------------------------------------------------------模块.出口 = {/*** 检索与包的给定范围匹配的最新版本.** @异步* @param {String} pkg 包名.* @param {String} range semver 范围.* @returns {Promise<string>} 一个 Promise 对象,当完成时返回* 匹配的最新版本.当被拒绝时,将返回一条错误消息.*/异步 fetchRealizedVersion(pkg, range) {尝试 {const 响应 = 等待 fetchPackageInfo(pkg);const { 版本:allVersionInfo } = JSON.parse(response);//响应包括包的所有版本的所有元数据.//让我们创建一个只包含 `version` 信息的数组.const 版本 = [];Object.keys(allVersionInfo).forEach(key => {版本.push(allVersionInfo[key].version)});返回 maxSatisfying(versions, range);} catch ({ message: errorMssg }) {抛出错误([`${errorBadge} ${errorMssg}`,`${errorBadge} '${pkg}' 可能不在 npm 注册表中.`].join('\n'));}}};


注意node-semver的版本示例自定义模块(A 和 B)中使用的不是当前最新版本(即 7.3.2).使用版本 ^5.7.1 - 与 npm cli 工具.


I'm writing a node script which helps pin dependencies.

How can I determine the greatest realized version of a package existing on an NPM server, from a semver version?

For example, we have a dependency "foo" which is specified in a package.json as ~1.2.3. Out on NPM, there exists published version 1.2.5, which is the latest published version compatible with ~1.2.3.

I need to write a script that would take as input "foo" and ~1.2.3, then after a server query, return 1.2.5. Something like this:

await fetchRealizedVersion('foo', '~1.2.3'); // resolves to 1.2.5

I understand I could do something like yarn upgrade and then parse the lock file, but I am looking for a more direct way of accomplishing this. Hopefully there is a package that boils this down to an API call, but I'm not finding anything after googling around.

解决方案

"Hopefully there is a package that boils this down to an API call,"

Short Answer: Unfortunately no, there is not a package that currently exists as far as I know.

Edit: There is the get-latest-version package you may want to try:

Basic usage:

const getLatestVersion = require('get-latest-version')

getLatestVersion('some-other-module', {range: '^1.0.0'})
 .then((version) => console.log(version)) // highest version matching ^1.0.0 range
 .catch((err) => console.error(err))


Alternatively, consider utilizing/writing a custom node.js module to perform the following steps:

  1. Either:

    • Shell out the npm view command to retrieve all versions that are available in the NPM registry for a given package: For instance:

      npm view <pkg> versions --json
      

    • Or, directly make a https request to the public npm registry at https://registry.npmjs.org to retrieve all versions available in for a given package.

  1. Parse the JSON returned and pass it, along with the semver range (e.g. ~1.2.3), to the node-semver package's maxSatisfying() method.

    The maxSatisfying() method is described in the docs as:

    maxSatisfying(versions, range): Return the highest version in the list that satisfies the range, or null if none of them do.


Custom module (A):

The custom example module provided in get-latest-version.js (below) essentially performs the aforementioned steps. In this example we shell out the npm view command.

get-latest-version.js

'use strict';

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const { exec } = require('child_process');
const { maxSatisfying } = require('semver');

//------------------------------------------------------------------------------
// Data
//------------------------------------------------------------------------------

const errorBadge = '\x1b[31;40mERR!\x1b[0m';

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
 * Captures the data written to stdout from a given shell command.
 *
 * @param {String} command The shell command to execute.
 * @return {Promise<string>} A Promise object whose fulfillment value holds the
 * data written to stdout. When rejected an error message is returned.
 * @private
 */
function shellExec(command) {
  return new Promise((resolve, reject) => {
    exec(command, (error, stdout, stderr) => {
      if (error) {
        reject(new Error(`Failed executing command: '${command}'`));
        return;
      }
      resolve(stdout.trim());
    });
  });
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

module.exports = {

  /**
   * Retrieves the latest version that matches the given range for a package.
   *
   * @async
   * @param {String} pkg The package name.
   * @param {String} range The semver range.
   * @returns {Promise<string>} A Promise object that when fulfilled returns the
   * latest version that matches. When rejected an error message is returned.
   */
  async fetchRealizedVersion(pkg, range) {
    try {
      const response = await shellExec(`npm view ${pkg} versions --json`);
      const versions = JSON.parse(response);

      return maxSatisfying(versions, range);

    } catch ({ message: errorMssg }) {
      throw Error([
        `${errorBadge} ${errorMssg}`,
        `${errorBadge} '${pkg}' is probably not in the npm registry.`
      ].join('\n'));
    }
  }

};


Usage:

The following index.js demonstrates using the aforementioned module.

index.js

'use strict';

const { fetchRealizedVersion } = require('./get-latest-version.js');

(async function() {
  try {
    const latest = await fetchRealizedVersion('eslint', '~5.15.0');
    console.log(latest); // --> 5.15.3
  } catch ({ message: errMssg }) {
    console.error(errMssg);
  }
})();

As you can see, in that example we obtain the latest published version for the eslint package that is compatible with the semver tilde range ~5.15.0.

The latest/maximum version that satisfies ~5.15.0 is printed to the console:

$ node ./index.js
5.15.3

Note: You can always double check the results using the online semver calculator which actually utilizes the node-semver package.

Another Usage Example:

The following index.js demonstrates using the aforementioned module to obtain the latest/maximum version for multiple packages and different ranges.

index.js

'use strict';

const { fetchRealizedVersion } = require('./get-latest-version.js');

const criteria = [
  {
    pkg: 'eslint',
    range: '^4.9.0'
  },
  {
    pkg: 'eslint',
    range: '~5.0.0'
  },
  {
    pkg: 'lighthouse',
    range: '~1.0.0'
  },
  {
    pkg: 'lighthouse',
    range: '^1.0.4'
  },
  {
    pkg: 'yarn',
    range: '~1.3.0'
  },
  {
    pkg: 'yarn',
    range: '^1.3.0'
  },
  {
    pkg: 'yarn',
    range: '^20.3.0'
  },
  {
    pkg: 'quuxbarfoo',
    range: '~1.3.0'
  }
];


(async function () {

  // Each request is sent and read in parallel.
  const promises = criteria.map(async ({ pkg, range }) => {
    try {
      return await fetchRealizedVersion(pkg, range);
    } catch ({ message: errMssg }) {
      return errMssg;
    }
  });

  // Log each 'latest' semver in sequence.
  for (const latest of promises) {
    console.log(await latest);
  }
})();

The result for that last example is as follows:

$ node ./index.js
4.19.1
5.0.1
1.0.6
1.6.5
1.3.2
1.22.4
null
ERR! Failed executing command: 'npm view quuxbarfoo versions --json'
ERR! 'quuxbarfoo' is probably not in the npm registry.


Additional Note: The shellExec helper function in get-latest-version.js currently promisifies the child_process module's exec() method to shell out the npm view command. However, since node.js version 12 the built-in util.promisify provides another way to promisify the exec() method (as shown in the docs for exec), so you may prefer to do it that way instead.


Custom module (B):

If you wanted to avoid shelling out the npm view command you could consider making a request directly to the https://registry.npmjs.org endpoint instead (which is the same endpoint that the npm view command sends a https GET request to).

The modified version of get-latest-version.js (below) essentially utilizes a promisified version of the builtin https.get.

Usage is the same as demonstrated previously in the "Usage" section.

get-latest-version.js

'use strict';

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const https = require('https');
const { maxSatisfying } = require('semver');

//------------------------------------------------------------------------------
// Data
//------------------------------------------------------------------------------

const endPoint = 'https://registry.npmjs.org';
const errorBadge = '\x1b[31;40mERR!\x1b[0m';

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

/**
 * Requests JSON for a given package from the npm registry.
 *
 * @param {String} pkg The package name.
 * @return {Promise<json>} A Promise object that when fulfilled returns the JSON
 * metadata for the specific package. When rejected an error message is returned.
 * @private
 */
function fetchPackageInfo(pkg) {

  return new Promise((resolve, reject) => {

    https.get(`${endPoint}/${pkg}/`, response => {

      const { statusCode, headers: { 'content-type': contentType } } = response;

      if (statusCode !== 200) {
        reject(new Error(`Request to ${endPoint} failed. ${statusCode}`));
        return;
      }

      if (!/^application\/json/.test(contentType)) {
        reject(new Error(`Expected application/json but received ${contentType}`));
        return;
      }

      let data = '';

      response.on('data', chunk => {
        data += chunk;
      });

      response.on('end', () => {
        resolve(data);
      });

    }).on('error', error => {
      reject(new Error(`Cannot find ${endPoint}`));
    });
  });
}

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

module.exports = {

  /**
   * Retrieves the latest version that matches the given range for a package.
   *
   * @async
   * @param {String} pkg The package name.
   * @param {String} range The semver range.
   * @returns {Promise<string>} A Promise object that when fulfilled returns the
   * latest version that matches. When rejected an error message is returned.
   */
  async fetchRealizedVersion(pkg, range) {
    try {
      const response = await fetchPackageInfo(pkg);
      const { versions: allVersionInfo } = JSON.parse(response);

      // The response includes all metadata for all versions of a package.
      // Let's create an Array holding just the `version` info.
      const versions = [];
      Object.keys(allVersionInfo).forEach(key => {
        versions.push(allVersionInfo[key].version)
      });

     return maxSatisfying(versions, range);

    } catch ({ message: errorMssg }) {
      throw Error([
        `${errorBadge} ${errorMssg}`,
        `${errorBadge} '${pkg}' is probably not in the npm registry.`
      ].join('\n'));
    }
  }

};


Note The version of node-semver used in the example custom modules (A & B) IS NOT the current latest version (i.e. 7.3.2). Version ^5.7.1 was used instead - which is the same version used by the npm cli tool.


这篇关于从 semver 版本确定 NPM 服务器上存在的依赖项的最大匹配版本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆