Repository navigation
Open
Description
Right now, we have the minVersion function:
minVersion('14.0.0 || 16.1.0 || 18.0.0') // => '14.0.0'and the minSatisfying function:
minSatisfying([parse('16.1.0')],'14.0.0 || 16.1.0 || 18.0.0')
// SemVer {
// options: {},
// loose: false,
// includePrerelease: false,
// raw: '16.1.0',
// major: 16,
// minor: 1,
// patch: 0,
// prerelease: [],
// build: [],
// version: '16.1.0'
// }
minSatisfying([parse('16.0.0')],'14.0.0 || 16.1.0 || 18.0.0')
// nullI suggest the addition of one or two functions:
to_be_named(version, range, options)
to_be_named('13.0.0', '14.0.0 || 16.1.0 || 18.0.0') // => '14.0.0'
to_be_named('14.0.0', '14.0.0 || 16.1.0 || 18.0.0') // => '14.0.0'
to_be_named('14.0.1', '14.0.0 || 16.1.0 || 18.0.0') // => '14.0.1'
to_be_named('15.0.0', '14.0.0 || 16.1.0 || 18.0.0') // => '16.1.0'
to_be_named('16.0.0', '14.0.0 || 16.1.0 || 18.0.0') // => '16.1.0'
to_be_named('16.1.0', '14.0.0 || 16.1.0 || 18.0.0') // => '16.1.0'
to_be_named('16.1.1', '14.0.0 || 16.1.0 || 18.0.0') // => '16.1.1'
to_be_named('18.0.0', '14.0.0 || 16.1.0 || 18.0.0') // => '18.0.0'
to_be_named('18.0.1', '14.0.0 || 16.1.0 || 18.0.0') // => '18.0.1'
to_be_named('19.0.0', '14.0.0 || 16.1.0 || 18.0.0') // => '19.0.0'or:
to_be_named2(range, options)
to_be_named2('a || b || c') // => [ minVersion(a), minVersion(b), minVersion(c) ]
to_be_named2('8.0.0', '10.0.1', '>12.0.0 || <14.0.0 || ~18.0.0' || ^20.0.0)
// => ['8.0.0', '10.0.1', '12.0.1', '0.0.0', '18.0.0', '20.0.0' ]My use case is the following script:
minimum-engines.mjs
#!/usr/bin/env node
/*
Copyright (c) 2023, Sebastian Davids
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Prerequisite:
// npm i --save-dev semver
// Usage:
// node minimum-engines.mjs node_modules
import { compare, minVersion, satisfies, validRange } from 'semver';
import { readFile, readdir } from 'node:fs/promises';
import { resolve } from 'node:path';
if (process.argv.length < 3) {
console.error('You need to supply the path to the node_modules folder');
process.exit(1);
}
const nodeModulesPath = process.argv[2];
const getPackageJsonFiles = async function* (path) {
const dirEntries = await readdir(path, { withFileTypes: true });
for (const entry of dirEntries) {
const file = resolve(path, entry.name);
if (entry.isDirectory()) {
yield* getPackageJsonFiles(file);
} else if (entry.name === 'package.json') {
yield file;
}
}
};
const getEngines = async (path) => {
const json = await readFile(path, 'utf8');
try {
const { engines = {} } = JSON.parse(json);
return engines;
} catch {
return {};
}
};
const mergeEngine = (engines, engine, value) => {
const range = value.trim();
if (range === '' || validRange(range) === null) {
return;
}
const current = engines[engine] ?? '0.0.0';
if (satisfies(current, range)) {
return;
}
// TODO does not handle the following case:
// range: 14.0.0 || 16.1.0 || 18.0.0
// current: 16.0.0
const version = minVersion(range).raw;
const newCurrent = compare(version, current) === -1 ? current : version;
// 16.0.0 instead of 16.1.0
engines[engine] = newCurrent;
//
};
const result = {};
try {
for await (const file of getPackageJsonFiles(nodeModulesPath)) {
const engines = await getEngines(file);
for (const engine of Object.keys(engines)) {
mergeEngine(result, engine, engines[engine]);
}
}
console.log(JSON.stringify(result));
} catch (e) {
console.error(e);
}It will crawl the given node_modules folder and return an object one can use for the engines in your project.
Example output
{"node":"18.12.0","npm":"1.0.0","iojs":"1.0.0"}See the TODO for the case the script cannot handle right now without one of the suggested function additions.
Metadata
Metadata
Assignees
Labels
No labels
Activity
sdavids commentedon Nov 5, 2023
I have updated my script to handle the case but I still think there is value in adding either method to semver.
minimum-engines.mjs
ljharb commentedon Nov 5, 2023
I'm really confused what this is asking for. if you have a range that doesn't include v13, then v13 has no relationship to it, it's just incompatible.
sdavids commentedon Nov 5, 2023
The question for
to_be_namedis:Give me the version included in the range equal or higher to the given version; if the given version is higher than any included version than return it.
Examples:
Range: >14.0.0 || >16.0.0 || > 18.0.0
Given version: 16.0.0
Result: 16.0.0 -- version included in range so return it
Range: >14.0.0 || >16.1.0 || > 18.0.0
Given version: 16.0.0
Result: 16.1.0 -- version not included in range so select the minimum version higher than the given version
Range: >14.0.0 || >16.1.0 || > 18.0.0
Given version: 20.0.0
Result: 20.0.0 -- version higher than all versions in range so return it
more examples above.
The question for
to_be_named2is:Give me the minimum version for each part of a composite range.
ljharb commentedon Nov 5, 2023
What's the use case tho?
sdavids commentedon Nov 5, 2023
See the script above for one —- it determines the minimum engines for a given project.
ljharb commentedon Nov 5, 2023
That's already what
minVersiondoes, when given theengines.noderange.sdavids commentedon Nov 5, 2023
range 1
range 2
16.0.0will not satisfy the first range but16.1.0will satisfy bothA third possible function would be
ljharb commentedon Nov 5, 2023
ok so you want the minimum version that satisfies both ranges?
I agree there's not a simple way to do that. I have
npx ls-enginesif you want to know what, based on your dep graph, your package.json's engines field should be. Does that address your use case?sdavids commentedon Nov 5, 2023
The results of my script above and
nix ls-engines --mode=actualare different.My script:
{"node":"16.14.0"}ls-engines:
{"node": ">= 14.13"}I ran it on: https://github.com/terser/html-minifier-terser
It has a
vite@^4.3.9dependency with a range^14.18.0 || >=16.0.0and aeslint-config-standard@^17.1.0dependency which has the transitive dependencyeslint-plugin-n@16.0.1with a range of>=16.0.0.So I am not sure about
ls-engine's suggestion of addingRunning it just on the non-dev dependencies I get:
My script: {"node":"14.0.0"}
ls-engines: {"node": ">= 14.13"}
I guess ls-engines also includes the range of the project's package.json (
^14.13.1 || >=16.0.0) and not just the ranges in thenode_modules(--mode=actual)?ljharb commentedon Nov 5, 2023
la-engines only considers prod deps by default; run it with --dev if you want those included.
ofc, on a published package engines should only consider prod deps anyways.
sdavids commentedon Nov 5, 2023
gives:
{"node": ">= 16.14"}-- the same result as my script above.With explicit engines field:
Without explicit engines field:
Should dependency graph engines not be
{"node": ">= 14"}in both case?ljharb commentedon Nov 5, 2023
Feel free to file a bug on the repo if its output is confusing.