What is the best way to run npm packages on demand as mircoservices without installing it locally? - node.js

Let's say I have these npm packages published to npm:
service1#v1.0
service1#v2.0
service2#v1.0
each package has a single function:
function run(extraStr) {
return 'package_name_and_version' + extraStr; // i.e. service1 v1.0 extraStr
}
And I want to write nodejs code that use the packages without installing it locally
var server = require("my-server-sdk");
// get(package_name, version, function_in_package, arguments, callback)
server.get('service1', '2.0', 'run', ['app1'], (err, result) => {
console.log(result); // this should print service1 v2.0 app1
});
where my-server-sdk is an sdk that interface with my server's api where it install the required packages and cache it for later use.
What is the best way to do that? what the security concerns and how to prevent any?
this is a simple diagram for what I want
NOTE: service1#v1.0
service1#v2.0
service2#v1.0
are just examples to any packages in npm i.e. lodash
Caching example:
Let's say we have TTL equal 60 minutes.
client1 requested a function from lodash and another function from underscore at 01:00.
Now in the server lodash and underscore are installed with timestamp 01:00.
client2 requested a function from underscore at 01:30 which get used instantly because underscore is installed before but it timestamp got updated to 1:30.
At 02:01 lodash get deleted because it didn't get used on the past TTL currenttime - lodash_timestamp > TTL but underscore stays because currenttime - underscore_timestamp < TTL
So when client3 request lodash at 02:30 it get intsalled again with 02:30 as a timestamp.

There is the npmi package which gives an API to npm install.
The logic I would use is:
Get the specific package and version from npm (install if is not already installed)
Require the package inside nodejs
Run the specified method with the specified parameters
Return the results to the client
var npmi = require('npmi');
var path = require('path');
function runModule(moduleName, moduleVersion, moduleMethod, moduleMethodParams) {
return new Promise((resolve, reject) => {
var options = {
name: moduleName, // your module name
version: moduleVersion, // expected version [default: 'latest']
forceInstall: false, // force install if set to true (even if already installed, it will do a reinstall) [default: false]
npmLoad: { // npm.load(options, callback): this is the "options" given to npm.load()
loglevel: 'silent' // [default: {loglevel: 'silent'}]
}
};
options.path = './' + options.name + "#" + options.version,
npmi(options, function(err, result) {
if (err) {
if (err.code === npmi.LOAD_ERR) console.log('npm load error');
else if (err.code === npmi.INSTALL_ERR) console.log('npm install error');
console.log(err.message);
return reject(err)
}
// installed
console.log(options.name + '#' + options.version + ' installed successfully in ' + path.resolve(options.path));
var my_module = require(path.resolve(options.path, "node_modules", options.name))
console.log("Running :", options.name + '#' + options.version)
console.log("Method :", moduleMethod);
console.log("With params :", ...moduleMethodParams)
resolve(my_module[moduleMethod](...moduleMethodParams))
});
})
}
runModule('lodash', '4.10.0', 'fill', [Array(3), 2])
.then(result => console.log("Result :", result))
runModule('lodash', '3.10.0', 'fill', [Array(3), 2])
.then(result => console.log("Result :", result))
You could see now that there are 2 created folders (lodash#3.10.0 , lodash#4.10.0) indicating the package name and version.
I have made the assumptions that npm is in path and the server has the permissions to install packages in current directory, also that the "MODULE_NAME#MODULE_VERSION" is a valid folder name under the current OS.

Related

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

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.

Getting error while connection to Ibm_db2 from a Node js platform

I am trying to connect to a DB2 server but I am getting bellow given error.
I'm following the given documentation: npm db2 Doc
I have done npm i ibm_db2
Code:
const ibmdb = require('ibm_db');
const connectQuery =
'DATABASE=' +
DATABASE +
';HOSTNAME=' +
HOSTNAME +
';UID=' +
UID +
';PWD=' +
PWD +
';PORT=' +
PORT +
';PROTOCOL=TCPIP';
ibmdb.open(connectQuery, function(err, conn) {
if (err) return console.log(err);
conn.query('select 1 from sysibm.sysdummy1', function(err, data) {
if (err) console.log('err');
else console.log('data');
conn.close(function() {
console.log('done');
});
});
});
Error:
Error: Could not locate the bindings file. Tried:
→ ...\node_modules\ibm_db\build\odbc_bindings.node
→ ...\node_modules\ibm_db\build\Debug\odbc_bindings.node
→ ...\node_modules\ibm_db\build\Release\odbc_bindings.node
→ ...\node_modules\ibm_db\out\Debug\odbc_bindings.node
→ ...\node_modules\ibm_db\Debug\odbc_bindings.node
Is there any other node package to establish connection ?
I have the same issue on windows 10. Because your ibm_db module is not installed successfully.
Download directly clidriver generated by IBM. After setting IBM_DB_HOME environment variable to point the directory, and reinstall ibm_db module to skip downloading clidriver.

Loopback getting error - Major change in User validatePassword function in the same release itself (3.0.0)

I am using loopback 3.0.0, and I have set up a new server recently, about one week ago. For that, I have ran the command npm install by putting the package.son file.
But in that installed files, the node_modules/loopback/common/user.js module has changed with major changes.
Egs:
Old file:
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
User.validatePassword = function(plain) {
var err;
if (plain && typeof plain === 'string' && plain.length <= MAX_PASSWORD_LENGTH) {
return true;
}
if (plain.length > MAX_PASSWORD_LENGTH) {
err = new Error(g.f('Password too long: %s', plain));
err.code = 'PASSWORD_TOO_LONG';
} else {
err = new Error(g.f('Invalid password: %s', plain));
err.code = 'INVALID_PASSWORD';
}
err.statusCode = 422;
throw err;
};
New File:
// Copyright IBM Corp. 2014,2018. All Rights Reserved.
User.validatePassword = function(plain) {
var err;
if (!plain || typeof plain !== 'string') {
err = new Error(g.f('Invalid password.'));
err.code = 'INVALID_PASSWORD';
err.statusCode = 422;
throw err;
}
// Bcrypt only supports up to 72 bytes; the rest is silently dropped.
var len = Buffer.byteLength(plain, 'utf8');
if (len > MAX_PASSWORD_LENGTH) {
err = new Error(g.f('The password entered was too long. Max length is %d (entered %d)',
MAX_PASSWORD_LENGTH, len));
err.code = 'PASSWORD_TOO_LONG';
err.statusCode = 422;
throw err;
}
};
I have developed my code with the same version but with old code which they have provided in the same version(3.0.0.). Here you can see, in the new code there is no return statement, so the code is infinitely waiting for the return and being time out. In both places the package.json file contains the same version: "loopback": "^3.0.0"
I hope it's not recommended to copy the node_modules from our developement server to production server.
So how can we handle these type of issues?
When specifying a version number in the package.json there are a few different ways https://docs.npmjs.com/files/package.json#dependencies:
The way you have is the default, ^ which means
compatible with version
So ^3.0.0 will only install 3.0.0 if it is the latest minor and fix versions otherwise it will take whatever the latest version of loopback is on that day. Today that is 3.19.3.
The issue was introduced in version v3.10.1(thanks #vasan) so locally maybe you had version 3.10.0 then on the server you had 3.10.1
There is a good explanation about the version numbers in this question What's the difference between tilde(~) and caret(^) in package.json?
I would suggest using an exact version, i.e. 3.19.3 then using a service like rennovate, https://github.com/renovate-bot, to update your project to keep up to date with security patches
There is also another guard against this, package-lock.json https://docs.npmjs.com/files/package-lock.json introduced in version 5 of npm. If you check this file in it will make sure that wherever you run npm install the exact version of the npm module is installed wherever you run it.

Get list of all NPM dependencies minimum node.js engine version

We have an NPM project X. I want to get a distinct list of all the dependencies in the project and the minimum Node.js (engine) version that they need.
How can I do this?
The motivation is of course to discovery what the minimum Nodejs version we need to run in development and production.
npm ls | grep "engines"
something like that, except the above won't work, hopefully there is something more robust
I was able to accomplish this like so:
let npm = require('npm');
npm.load({}, function(err, npm) {
if(err) throw err;
npm.config.set('global', false); // => we don't want to consider global deps
npm.commands.list([], true, function(err, pkgInfo) {
let enginesList = Object.keys(pkgInfo.dependencies).map(function(k){
return {
dep: k,
engines: pkgInfo.dependencies[k].engines || {}
}
});
enginesList.forEach(function(val){
console.log(val.dep + ' => ', val.engines);
});
});
});

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.

Resources