从 semver 版本确定 NPM 服务器上存在的依赖项的最大匹配版本
Determine dependency's greatest matching version that exists on an NPM server from a semver version
我正在编写一个有助于固定依赖项的节点脚本。
如何根据 semver 版本确定 NPM 服务器上现有包的最大实现版本?
例如,我们有一个依赖项“foo”,它在 package.json 中指定为 ~1.2.3
。
在 NPM 上,存在已发布版本 1.2.5
,这是与 ~1.2.3
.
兼容的最新发布版本
我需要编写一个脚本,将“foo”和 ~1.2.3
作为输入,然后在服务器查询后,return 1.2.5
。像这样:
await fetchRealizedVersion('foo', '~1.2.3'); // resolves to 1.2.5
我知道我可以做类似 yarn upgrade
的事情,然后解析锁定文件,但我正在寻找一种更直接的方法来完成此操作。
希望有一个包可以将其归结为 API 调用,但在谷歌搜索后我没有找到任何东西。
"Hopefully there is a package that boils this down to an API call,"
简短回答: 很遗憾,没有,据我所知,目前还没有软件包。
编辑: 有一个 get-latest-version
包你可能想试试:
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))
或者,考虑 utilizing/writing 自定义 node.js 模块来执行以下步骤:
或者:
Shell 输出 npm view 命令以检索 NPM 注册表中给定包的所有可用版本:例如:
npm view <pkg> versions --json
或者,直接向 https://registry.npmjs.org
的 public npm registry 发出 https
请求,以检索给定软件包的所有可用版本。
解析返回的 JSON 并将其与 semver 范围(例如 ~1.2.3
)一起传递给 node-semver 包的 maxSatisfying()
方法.
maxSatisfying()
方法在docs中描述为:
maxSatisfying(versions, range)
: Return the highest version in the list that satisfies the range, or null
if none of them do.
自定义模块 (A):
get-latest-version.js
(下文)中提供的自定义示例模块主要执行上述步骤。在此示例中,我们 shell 输出了 npm view
命令。
获取最新-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'));
}
}
};
用法:
下面index.js
演示使用上述模块
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);
}
})();
如您所见,在该示例中,我们获取了 eslint 软件包的最新发布版本,该版本与 semver tilde 范围 ~5.15.0
.
兼容
满足~5.15.0
的latest/maximum版本打印到控制台:
$ node ./index.js
5.15.3
注意:您始终可以使用在线 semver calculator 仔细检查结果,它实际上使用了 node-semver
包。
另一个用法示例:
下面index.js
演示了使用上述模块获取多个包和不同范围的latest/maximum版本。
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);
}
})();
最后一个例子的结果如下:
$ 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.
附加说明: get-latest-version.js
中的 shellExec
辅助函数当前承诺 child_process
模块的 exec()
method to shell out the npm view
command. However, since node.js version 12 the built-in util.promisify
提供了另一种方法来承诺 exec()
方法 (如 exec
的文档中所示),因此您可能更愿意这样做。
自定义模块(B):
如果您想避免 shell 发出 npm view
命令,您可以考虑直接向 https://registry.npmjs.org
端点发出请求(这与 npm view
命令发送一个 https GET
请求到).
get-latest-version.js
的修改版本(下)本质上使用了内置 https.get
.
的 promisified 版本
用法与之前在“用法”部分中演示的用法相同。
获取最新-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'));
}
}
};
注意示例自定义模块(A 和 B)中使用的 node-semver 版本不是当前最新版本(即 7.3.2
).而是使用版本 ^5.7.1
- 这与 npm cli 工具使用的版本相同。
我正在编写一个有助于固定依赖项的节点脚本。
如何根据 semver 版本确定 NPM 服务器上现有包的最大实现版本?
例如,我们有一个依赖项“foo”,它在 package.json 中指定为 ~1.2.3
。
在 NPM 上,存在已发布版本 1.2.5
,这是与 ~1.2.3
.
我需要编写一个脚本,将“foo”和 ~1.2.3
作为输入,然后在服务器查询后,return 1.2.5
。像这样:
await fetchRealizedVersion('foo', '~1.2.3'); // resolves to 1.2.5
我知道我可以做类似 yarn upgrade
的事情,然后解析锁定文件,但我正在寻找一种更直接的方法来完成此操作。
希望有一个包可以将其归结为 API 调用,但在谷歌搜索后我没有找到任何东西。
"Hopefully there is a package that boils this down to an API call,"
简短回答: 很遗憾,没有,据我所知,目前还没有软件包。
编辑: 有一个 get-latest-version
包你可能想试试:
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))
或者,考虑 utilizing/writing 自定义 node.js 模块来执行以下步骤:
或者:
Shell 输出 npm view 命令以检索 NPM 注册表中给定包的所有可用版本:例如:
npm view <pkg> versions --json
或者,直接向
https://registry.npmjs.org
的 public npm registry 发出https
请求,以检索给定软件包的所有可用版本。
解析返回的 JSON 并将其与 semver 范围(例如
~1.2.3
)一起传递给 node-semver 包的maxSatisfying()
方法.maxSatisfying()
方法在docs中描述为:maxSatisfying(versions, range)
: Return the highest version in the list that satisfies the range, ornull
if none of them do.
自定义模块 (A):
get-latest-version.js
(下文)中提供的自定义示例模块主要执行上述步骤。在此示例中,我们 shell 输出了 npm view
命令。
获取最新-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'));
}
}
};
用法:
下面index.js
演示使用上述模块
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);
}
})();
如您所见,在该示例中,我们获取了 eslint 软件包的最新发布版本,该版本与 semver tilde 范围 ~5.15.0
.
满足~5.15.0
的latest/maximum版本打印到控制台:
$ node ./index.js 5.15.3
注意:您始终可以使用在线 semver calculator 仔细检查结果,它实际上使用了 node-semver
包。
另一个用法示例:
下面index.js
演示了使用上述模块获取多个包和不同范围的latest/maximum版本。
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);
}
})();
最后一个例子的结果如下:
$ 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.
附加说明: get-latest-version.js
中的 shellExec
辅助函数当前承诺 child_process
模块的 exec()
method to shell out the npm view
command. However, since node.js version 12 the built-in util.promisify
提供了另一种方法来承诺 exec()
方法 (如 exec
的文档中所示),因此您可能更愿意这样做。
自定义模块(B):
如果您想避免 shell 发出 npm view
命令,您可以考虑直接向 https://registry.npmjs.org
端点发出请求(这与 npm view
命令发送一个 https GET
请求到).
get-latest-version.js
的修改版本(下)本质上使用了内置 https.get
.
用法与之前在“用法”部分中演示的用法相同。
获取最新-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'));
}
}
};
注意示例自定义模块(A 和 B)中使用的 node-semver 版本不是当前最新版本(即 7.3.2
).而是使用版本 ^5.7.1
- 这与 npm cli 工具使用的版本相同。