Skip to content

[ENHANCEMENT] minimum versions or minimum version in relation to #649

@sdavids

Description

@sdavids

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')
// null

I 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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions