Determine dependency's greatest matching version that exists on an NPM server from a semver version - node.js

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:
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.
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.

Related

webpack multicompile via node api (^4.44.2)

I have a Webpack configuration repository to separate the application boilerplate from the Webpack configuration. This application is dependent on the repository where the Webpack configuration is. I made a binary in the Webpack repository so that, in the application, I could compile and run this configuration in development. The configuration combines a common configuration with the past environment.
Problem: The scenario is beautiful but it is not working in the part of compiling and serving the application. Apparently my configuration is OK - i isolate them and test them separately. And I'm also following the v4 documentation for NODE-API.
As I have nowhere to turn, I'm sorry if I'm not on the right platform, i am studying how to compile different configurations of the same application (boilerplate) using webpack.
Link to the code.
i appreciate some repo example...
I came across several problems pointing to the Libs of the Webpack and Webpack-dev-server packages. However, today I got what I wanted. I will share here for future reference from other users.
My goal was to be able to trigger the development or production environment from a node script. Involving the construction of multiple FrontEnd applications, which was abstracted in webpack.config.
Now i can run mycli development on shell and this will trigger the construction of the configuration for that environment.
// mycli.js
#!/usr/bin/env node
const webpack = require('webpack')
const WebpackDevServer = require('webpack-dev-server')
const webpackConfiguration = require('./webpack/webpack.config')
const args = (process.argv && process.argv.length > 2) ? process.argv.slice(2) : []
const mode = args.length > 0 ? args[0] : 'development'
const config = webpackConfiguration(mode)
/**
* Minimum webpack configuration for cli validation.
* #see {#link webpackConfiguration} to further information
*/
const minConfig = {
entry: __dirname + '/test.js',
mode,
output: {
path: '/dist',
filename: 'bundle.js'
}
}
/** #type {WebpackCompilerInstance} compiler */
const compiler = webpack(minConfig)
switch (config.mode) {
case 'development':
/**
* Recommended by documentation:
* "If you're using dev-server through the Node.js API,
* the options in devServer will be ignored. Pass the options
* as a second parameter instead."
* #see {#link https://v4.webpack.js.org/configuration/dev-server/#devserver} for further information.
* #see {#link https://github.com/webpack/webpack-dev-server/tree/master/examples/api/simple} for example
*/
const devServerConfig = config.devserver;
if (config) delete config.devserver
const devServerOptions = Object.assign({}, devServerConfig, {
open: true,
stats: {
colors: true,
},
})
const devserverCallback = (err) => {
if (err) throw err
console.log('webpack-dev-server listening...')
}
new WebpackDevServer(compiler, devServerOptions).listen(devServerConfig.port, devServerConfig.host, devserverCallback)
break;
case 'production':
const compilerCallback = (err, stats) => {
console.log(stats, err)
if (err) throw err
process.stdout.write(`Stats: \n${stats} \n`)
console.log('Compiler has finished execution.')
}
compiler.run(compilerCallback)
break;
default:
console.error('No matching mode. Try "development" or "production".')
break;
}

What version of Node does my library support?

I'm hoping for a library or a tool which will run through my code and tell me what version of Node is required in order to run it. Perhaps better would be it alerts me to areas of the code which could be changed to support older versions.
Is there anything like that in the wild?
I'm not sure if this exactly what you are looking for, but there is an existing package.json property called "engines" where package developers can specify what version(s) they require. Not too difficult to use glob and semver packages to look through all package.json files with an "engines" requirement and compile that into an object of:
{
[version1]: [{ packageName, currentlySupported }, { ... }],
[version2]: [...],
...
}
Here is a rudimentary example of a script which will create that object for you:
npm install glob semver
checkversions.js:
const glob = require('glob');
const path = require('path');
const semver = require('semver');
const currentVersion = process.version;
const versions = {};
glob('node_modules/*/package.json', (err, files) => {
files.forEach((file) => {
const pkg = require(path.resolve(__dirname, file));
// only check add package if it specifies "engines"
if (pkg.engines && pkg.engines.node) {
const reqdVersion = pkg.engines.node.replace(/\s+/g, '');
// assume you are using a supported version
let currentlySupported = true;
// check if current node version satisfies package requirements
if (!semver.satisfies(currentVersion, reqdVersion)) {
currentlySupported = false;
}
if (!Array.isArray(versions[reqdVersion])) {
versions[reqdVersion] = [];
}
versions[reqdVersion].push({
package: file.replace(/node_modules\/(.*)\/package.json/, '$1'),
currentlySupported,
});
}
});
console.log(versions);
});
Run it:
node checkversions.js

node require.cache delete does not result in reload of module

I'm writing tests for my npm module.
These tests require to install multiple versions of an npm module in order to check if the module will validate them as compatible or incompatible.
Somehow all uncache libraries or function I found on stackoverflow or the npm database are not working..
I install/uninstall npm modules by using my helper functions:
function _run_cmd(cmd, args) {
return new Promise((res, rej) => {
const child = spawn(cmd, args)
let resp = ''
child.stdout.on('data', function (buffer) {
resp += buffer.toString()
})
child.stdout.on('end', function() {
res(resp)
})
child.stdout.on('error', (err) => rej(err))
})
}
global.helper = {
npm: {
install: function (module) {
return _run_cmd('npm', ['install', module])
},
uninstall: function (module) {
decacheModule(module)
return _run_cmd('npm', ['uninstall', module])
}
}
}
This is my current decache function which should clear all modules caches (I tried others, including npm modules none of them worked)
function decacheModules() {
Object.keys(require.cache).forEach(function(key) {
delete require.cache[key]
})
}
I am installing multiple versions of the less module (https://www.npmjs.com/package/less)
In my first test I am installing a deprecated version which does not have a render-function.
In some other test I am installing an up-to-date version which has the render-function. Somehow if I test for that function that test does fail.
If I skip the first test the other test succeeds. (render-function exists).
This makes me believe that the deletion of require.cache has no impact...
I am using node v4.2.4.
If there is a reference to the old module:
var less = require('less');
Clear module cache will not lead that reference cleared and reload the module.
For that to work, at least you don't store module to variable, use the "require('less')" in place everywhere.

Is there a jest config that will fail tests on console.warn?

How do I configure jest tests to fail on warnings?
console.warn('stuff');
// fail test
You can use this simple override :
let error = console.error
console.error = function (message) {
error.apply(console, arguments) // keep default behaviour
throw (message instanceof Error ? message : new Error(message))
}
You can make it available across all tests using Jest setupFiles.
In package.json :
"jest": {
"setupFiles": [
"./tests/jest.overrides.js"
]
}
Then put the snippet into jest.overrides.js
For those using create-react-app, not wanting to run npm run eject, you can add the following code to ./src/setupTests.js:
global.console.warn = (message) => {
throw message
}
global.console.error = (message) => {
throw message
}
Now, jest will fail when messages are passed to console.warn or console.error.
create-react-app Docs - Initializing Test Environment
I implemented this recently using jest.spyOn introduced in v19.0.0 to mock the warn method of console (which is accesses via the global context / object).
Can then expect that the mocked warn was not called, as shown below.
describe('A function that does something', () => {
it('Should not trigger a warning', () => {
var warn = jest.spyOn(global.console, 'warn');
// Do something that may trigger warning via `console.warn`
doSomething();
// ... i.e.
console.warn('stuff');
// Check that warn was not called (fail on warning)
expect(warn).not.toHaveBeenCalled();
// Cleanup
warn.mockReset();
warn.mockRestore();
});
});
There is a useful npm package that helps you to achieve that: jest-fail-on-console
It's easily configurable.
Install:
npm i -D jest-fail-on-console
Configure:
In a file used in the setupFilesAfterEnv option of Jest, add this code:
import failOnConsole from 'jest-fail-on-console'
failOnConsole()
// or with options:
failOnConsole({ shouldFailOnWarn: false })
I decided to post a full example based on user1823021 answer
describe('#perform', () => {
var api
// the global.fetch is set to jest.fn() object globally
global.fetch = jest.fn()
var warn = jest.spyOn(global.console, 'warn');
beforeEach(function() {
// before every test, all mocks need to be resetted
api = new Api()
global.fetch.mockReset()
warn.mockReset()
});
it('triggers an console.warn if fetch fails', function() {
// In this test fetch mock throws an error
global.fetch.mockImplementationOnce(() => {
throw 'error triggered'
})
// I run the test
api.perform()
// I verify that the warn spy has been triggered
expect(warn).toHaveBeenCalledTimes(1);
expect(warn).toBeCalledWith("api call failed with error: ", "error triggered")
});
it('calls fetch function', function() {
// I create 2 more mock objects to verify the fetch parameters
const url = jest.fn()
const config = jest.fn()
api.url = url
api.config = config
// I run the test
api.perform()
// I verify that fetch has been called with url and config mocks
expect(global.fetch).toHaveBeenCalledTimes(1)
expect(global.fetch).toBeCalledWith(url, config)
expect(warn).toHaveBeenCalledTimes(0)
});
})
the #perform method I am testing
class Api {
constructor(auth) {
this._credentials = auth
}
perform = async () => {
try {
return await fetch(this.url, this.config)
} catch(error) {
console.warn('api call failed with error: ', error)
}
}
}
You can set the environment variable CI=true before running jest which will cause it to fail tests on warnings in addition to errors.
Example which runs all test files in the test folder:
CI=true jest ./test
Automated CI/CD pipelines such as Github Actions set CI to true by default, which can be one reason why a unit test will pass on your local machine when warnings are thrown, but fail in the pipeline.
(Here is the Github Actions documentation on default environment variables: https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables)

How to access multiple models from a controller

I have a Locations model and a Recorders model. I want to be able to pass all of the data for both data sets to my view model. How can I access them though because I think they're not in scope since I'm getting undefined errors because I'm calling 'all'
https://gist.github.com/3998302
var Main = function () {
this.index = function (req, resp, params) {
var self = this;
var data = {};
geddy.model.Locations.all(function(err, locations) {
data.locations = locations;
geddy.model.Recorders.all(function(err, recorders) {
data.recorders = recorders;
self.respond({params: params, data: data}, {
format: 'html'
, template: 'app/views/locations/index'
}
});
}););
};
};
exports.Main = Main;
Error snippet:
timers.js:103
if (!process.listeners('uncaughtException').length) throw e;
^
TypeError: Cannot call method 'all' of undefined
at index (G:\code\PeopleTracker\app\controllers\main.js:23:24)
at controller.BaseController._handleAction.callback (C:\Users\Chris\AppData\Roaming\npm\node_modules\geddy\lib\base_
controller.js:387:22)
So it looks like you're initializing the data variable to 'undefined'. Try data = {} instead. If that doesn't fix it, I'll do some troubleshooting with you.
EDIT
If that doesn't do it for you, try installing geddy again:
npm uninstall -g geddy && npm install -g geddy
If that doesn't do it, make sure that your DB is actually running, make sure that the models are defined (try geddy console to check your models), and make sure that you're on the latest stable version of node.
Very late to the party, but I believe you can just call
geddy.model.Locations.all(function(err, locations) {
geddy.model.Recorders.all(function(err, recorders) {
var data = {};
data.locations = locations;
data.recorders = recorders;
self.respond({params: params, data: data}, {
format: 'html'
, template: 'app/views/locations/index'
}
});
}););
You could also have the respond say
self.respond({params: params, locations: locations, recorders: recorders});
but if you want all of that data available from the data literal you need it defined in the lowest scope callback. The callbacks can read above their scope but they cannot write above it.

Resources