Install different packages depending on node version - node.js

I have a project that depends on the websocket package. However, for node 10.x, the latest version (1.0.31) of websocket works, while on node 4.x version 10.0.24 works but the 10.0.31 does not. Is it possible to specify different package (versions) per nodejs version to handle cases like this, e.g. like so
node 4.x and older -> websockets 1.0.24
all other node versions -> websockets 1.0.31
Preferable it should work in both npm and yarn, but if it only works in either that's fine as well.
The node 10 version is used in dev setups, while the node 4.x is used in a legacy embedded platform that cannot run docker or be upgraded.

Consider utilizing a postinstall script in the scripts section of your projects package.json. For instance:
package.json
"scripts": {
"postinstall": "node install-websocket"
},
As you can see, the postinstall script invokes a nodejs script, arbitrarily named install-websocket.js.
install-websocket.js
const execSync = require('child_process').execSync;
const nodeMajorVersion = process.version.replace(/^v/, '').split('.')[0];
const websocketVersion = nodeMajorVersion <= '4' ? '1.0.24' : '1.0.31';
execSync('npm install websocket#' + websocketVersion, {
cwd: __dirname,
stdio: 'inherit'
});
The install-websocket.js script essentially performs the following tasks:
Gets the version of node.js using process.version which returns a string, e.g. v13.10.1
To obtain the Major version from that string (i.e. 13 in that aforementioned example) we use a combination of the replace() and split() methods.
The conditional (ternary) operator ascertains
which version of websocket to subsequently install - based on whether the value of nodeMajorVersion is <= 4.
Finally we "shell out" the appropriate npm install websocket#x.x.x command using execSync.
Note: If you're concerned about execSync being synchronous, then utilize the asynchronous exec instead.
Additional Notes:
Given the code shown above it assumes the install-websocket.js file resides in the root of your project directory, i.e. at the same level as package.json.
my-project
├── package.json
├── install-websocket.js
└── ...
It's important for the install-websocket.js file to exist at this location for following two reasons:
Primarily, and most importantly, because you'll have noticed that we specify __dirname for the value of execSync's cwd option. The value of __dirname in this context is the pathame to the parent directory of wherever the install-websocket.js file resides.
Essentially by setting the cwd option to this specific pathname, (i.e. the path to the project directory), we ensure that when the npm install websocket#x.x.x command is run it gets installed in the same location as where your project resides - regardless of whether it's installed locally or globally.
The postinstall script in package.json expects the install-websocket.js file to reside there too. Note how it currently runs; node install-websocket, and doesn't assume the file exists elsewhere, i.e. it's not running something like: node ./some/path/to/install-websocket
If consumers of your package have npm configured to ignore-scripts then websocket's simply will not be installed because the postinstall script will not be invoked.

Related

Problem when using process.cwd() in published npm package

I'm dipping my toes into cli tooling by building a simple program for automating Gerrit commits. Everything works locally, but after publishing the package to npm and installing it globally it looks like process.cwd() behaves differently. The program exits, but no console.log(). Even a simple console.log(process.cwd()) is ignored (again works locally). Is it possible to use process.cwd() when running a globally installed npm package?
console.log(process.cwd());
const getCurrentBranchName = (p = process.cwd()) => {
const gitHeadPath = `${p}/.git/HEAD`;
return fs.existsSync(gitHeadPath)
? fs.readFileSync(gitHeadPath, "utf-8").trim().split("/").pop()
: (console.log("not a git repo"), process.exit(0));
}
const currentBranch = getCurrentBranchName();
When ran locally (with node index):
$ /Users/jpap/.npm-packages/lib/node_modules/gittest
$ not a git repo
You haven't proved the problem is with process.cwd()
The code in your question and the results your describe only indicate that the console.log() calls aren't executing.
You could easily test this by adding the following to the top of your code:
console.log('My name is Inigo Montoya. You killed my father. Prepare to die!')
What you are publishing is likely not the same as what you are running locally
For example, you could be using a bundler (e.g. Rollup) configured to strip console.log calls.
This is easy to confirm. Simple look at the code of the npm installed version:
Use npm root -g to find out where your global packages are installed. For non-global packages, look in node_modules.
Find your package's subdir.
Look at the code, or diff it with the source code.
I suspect you will see all console.log statements removed.

Prevent npm start if node version mismatch

I have a project that needs Node >= V14 and would like to prevent npm script execution if node version does not match.
Using .npmrc and engines in package.json, I can prevent npm install from running if node version does not match.
However, is there any way to prevent npm start from executing if appropriate node version is not found?
Short answer: NPM does not provide a built-in feature to achieve this.
Solution:
However your requirement can be met by utilizing a custom node.js helper script:
Save the following check-version.js script in the root of your project directory, i.e. save it at the same level where package.json resides
check-version.js
const MIN_VERSION = 14;
const nodeVersion = process.version.replace(/^v/, '');
const [ nodeMajorVersion ] = nodeVersion.split('.');
if (nodeMajorVersion < MIN_VERSION) {
console.warn(`node version ${nodeVersion} is incompatible with this module. ` +
`Expected version >=${MIN_VERSION}`);
process.exit(1);
}
In the scripts section of your package.json define your start script as follows:
package.json
...
"scripts": {
"start": "node check-version && echo \"Running npm start\""
},
....
Note Replace the echo \"Running npm start\" part (above) with whatever your current start command is.
Explanation:
In check-version.js we obtain the Node.js version string via process.version and remove the v prefix using the replace() method.
Note: You may prefer to use process.versions.node instead of replace to obtain the version string without the prepended v.
Next we obtain the Major version only from the version string and assign it to the nodeMajorVersion variable.
Finally in the if statement we check whether the nodeMajorVersion is less than the expected minimum node.js version (MIN_VERSION). If it is less than the expected version we warn the user and call the process.exit() method with the exit code as 1.
Depends on what your start does, but if it's your code:
if (process.versions.node.split('.')[0] < 14) process.exit(1)

how to require a .js file in node repl

so i usually use ruby irb, and I can pull .rb files I wrote into the console environment by running
load './script.rb'
and then all of the functions I wrote in script.rb will be available.
I cannot figure out for the life of me how to do this in the node "console" environment!
You can load JavaScript files using the require function. The following example assume that the Node.js process was started at the directory where your file is located.
require('./script.js');
This will execute the contents of the file.
If you have exported functions or objects, you can assign them to a variable and use them later.
const myFunction = require('./script.js').myFunction;
myFunction();
Like many other development frameworks/languages, Node has a Modules/Package System which, is a CommonJS variant. To load a Module use require(). The usage of require() is the same when running JavaScript files or running in the REPL.
You can require Node Core Modules, NPM Installed Packages or your own local modules. When loading NPM Packages specified in a package.json or a local module, Node will load them from the Current Working Directory(CWD), you can check this using process.cwd(). The CWD will be set to the absolute path of the directory you launched the REPL from.
You can launch the REPL via running node in your CLI and require your packages like below.
// Core Package
const os = require('os')`
console.log(os)
// NPM Package
const moment = require('moment')
console.log(moment)
// Local Package
const myPackage = require('./myPackage')
console.log(myPackage)
You can also pre-require module(s) using the -r flag when running node. The below will launch the Node REPL with the os package preloaded. You can then access the os package using the variable os
node -r os
console.log(os)
In the future, Node may also support ECMAScript Modules (ie. import). You can read more detailed info about that in the Enhancement Proposal.

Specify the path to node when running executable npm moduels

I'm fairly new to node and npm so imagine this might be the way I'm trying to use it. My understanding so far and questions are as follows.
When installing modules with npm if they have executables they are created in node_moadules/.bin
For example npm install istanbul creates
node_moadules/.bin/istanbul.cmd with the following contents
#IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\..\istanbul\lib\cli.js" %*
) ELSE (
#SETLOCAL
#SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\..\istanbul\lib\cli.js" %*
)
This seems to be roughly similar for other npm modules installed.
1) When would there be a node.exe in the bin directory (%~dp0 is the path of the batch file - so node_moadules/.bin)? When using npm istanbul it will use the above script but I don't understand when it would use the IF block, it seems it would always go into the ELSE which uses node on the path to run it. This will work as intended when I have node installed and on my path. However this would seem to be a problem if you don't have it on your path or need to use a specific version in a project (different from the version on your path).
2) I can fix this by manually editing the above script to point to the specific node version but don't want to have to do this for all module executables installed with npm. Is there a way to configure this in npm so it uses a specific path to node when it generates these executable files?
A bit more background
We're using the gradle-node-plugin in our build so we don't have to manage node and npm on our build machines, this does it for us. However this means we don't have node on the path.
The plugin allows me to install npm modules but there is no clear way to then run the executables, using the version of node unpacked by the plugin, without manually editing the generated executable scripts as in 2), or instead of calling npm istanbul calling something like this:
<path_to>\node <path_to>\node_modules\istanbul\lib\cli.js
This also doesn't seem right as I'm hardcoding to the cli.js executable path within the module which could presumably change in future versions of that module.
If anyone could explain this, or tell me where the specific part of the docs that do are that would be helpful. I've tried to search but probably not using the right terms.

NPM: module installation for a command line node tool?

I have a command line tool written in node. I'd like to:
Have the app be able to load its dependencies and work. Currently, after npm install -g <somemodule> that module is still not available. Things didn't used to work this way.
Not have to run npm link on every folder, as I have read in the NPM 1.0 docs. The above docs also talks about $PATH, which seems unrelated to the topic as I care about node modules, not binaries.
How can/should a node command line tool handle its dependencies so that the command line tool can run from any directory?
You can add following in the main file of your node.js app, assuming your file name is node-binary.js.
#! /usr/bin/env node
// your app code
console.log('TEST node binary');
And, in package.json file you need to specify which is the entry point of your app
...
"preferGlobal": "true",
"bin": {
"node-binary": "node-binary.js"
},
...
and run the command npm link in the app directory. You should now be able to use node-binary command from any directory.
Hope that helps... :)

Resources