Create shell executable Global node module - node.js

I tried to create node module, I succeeded,
I used npm install -g at code directory, and it created this module folder in \AppData\Roaming\npm\node_modules\myfirstmodule, Now I want to make one file executable as command, like pm2. How can I do this? so I can type myfirstmodule in command prompt at any location and it will execute index.js from that module.

Your package.json can provide a map called bin which will make commands available. See this tutorial for more details.

Related

Is it possible to make the node command see a node_modules folder on a different location than the specified one?

I know I can install Node.js libraries with the command npm from the command line... So, if I type:
npm install mylibrary
It'll create a node_modules folder on my current location and it'll install mylibrary (if it exists on npm)... Let's say that I create a Node.js code using it as the following:
const mylibrary = require('mylibrary')
mylibrary.doSomething()
Since I've installed this library on my current folder with the last command, this node.js script will work only if I save it on a file on my terminal current location as well. If I name this file as file.js, I can execute it with:
node file.js
Well, my problem is that I have a very specific case where the node_modules folder cannot exist in the same location that my file.js. Also, I'd like to avoid having to install mylibrary globally. Is there any way of defining as a parameter the path that the command node will search for the node_modules folder? I've checked node --help and it seems there's a lot of options, but I didn't manage to make it work. Is it possible to do? Can I use a node_modules folder that's neither the one of my file.js path or a global installed library?
You can have a link for the node_modules folder. For example, to create the symbolink link in Linux do this:
cd /path/to/script/folder
ln -s /path/to/where/you/can/have/node_modules node_modules
If you can't have the link, use the full path to the node_modules folder in the require, for example:
const mylibrary = require('/path/to/node_modules/mylibrary')
Also, you can solve the problem by using the NODE_PATH environment variable, set it like this
export NODE_PATH=/path/to/node_modules

NPM - Conditional additions to global path

In a Node package.json file, you can map multiple executables to the PATH environmental variable on a global NPM install (npm install -g):
"bin": {
"foo": "./bin/foo.js",
"bar": "./bin/bar.js"
},
I have a unique project that requires mapping existing PATH variables on Operating Systems that do not have it. For example, I want to add a command named grep to PATH, if and only if it is being installed on a Windows computer. If the computer is running any other OS, the NPM installation will obviously fail.
Is there any way to run logic that pre-determines what bin options are available in the installation?
Oh snap - I just had an idea!
Would this work:
Parent module has npm (programmatic version) as a dependency.
On global installation, run a post-install script as declared in the package.json of parent module.
Post-install script does a check on the system to see which commands exist. This would be more mature than "Windows or not Windows" - it would try to exec a list of commands and see which ones fail.
For every command that doesn't exist, post-install script programmatically runs npm install -g on all sub-modules (one for each command, such as grep).
This would take a while and the npm module is huge, but it seems like it would work. No?
There doesn't seem to be a way to do this directly through package.json, but it might be possible (and desirable) to do something like:
Make a separate npm module for each executable you want to register (eg my-win-grep).
In the my-win-grep module, implement the executable code you want to run, and register the PATH/executable value in this module.
In the package.json for my-win-grep, include an os field that limits it to installing on windows.
In your original module, list my-win-grep as an optionalDependency.
In this way, if someone installs your original module on Windows, it would install my-win-grep, which would register an executable to run under the grep command.
For users on other systems, the my-win-grep module would not install because of the os requirement, but the main module would continue to install because it ignores failures under optionalDependencies. So the original grep executable would remain untouched.
Updated question
This approach does sound like it should work - as you say, the npm dependency is pretty large, but it does avoid having to preform additional symlinking, and still has the benefit outlined above of having each piece of OS specific functionality in a separate module.
The only thing to watch for, possibly, in the programmatic npm docs:
You cannot set configs individually for any single npm function at
this time. Since npm is a singleton, any call to npm.config.set will
change the value for all npm commands in that process
So this might just mean that you can't specify -g on your installs, but instead would have to set it globally before the first install. This shouldn't be a problem, but you'll probably need to test it out to find out exactly.
Lastly...
You might also want to have a look at https://github.com/lastboy/package-script - even if you don't use it, it might give you some inspiration for your implementation.

Why does running my globally npm linked package simply open the bin file?

If I'm developing the npm package foo and I want it to be globally installed as a command-line application, I can do this simply by adding to my package.json:
"bin": {
"foo": "./bin/foo.js"
}
Somebody who installs my package globally via npm will have the appropriate batch file and shell script added to their global npm prefix directory. However, suppose I want to be able to launch my package from the shell (or, in the case of Windows, the command prompt). I could do this by creating a batch file/shell script somewhere in one of my PATH directories that simply directly runs my package e.g. #node C:\my\package\directory\bin\foo %*.
This is a fairly simple and obvious solution, but I felt that npm link was better suited, as it feels less hacky and is theoretically designed to do this exact thing. I run npm link in my package directory, then test it by running foo from the command line. Rather than executing my script, though, foo.js is actually opened in my default editor. Investigating in the prefix directory, it turns out that the foo.cmd file (the contents of the foo shell script are similar) that npm created contains this:
"%~dp0\node_modules\foo\bin\foo.js" %*
Compare with the batch file created by npm install -g:
#IF EXIST "%~dp0\node.exe" (
"%~dp0\node.exe" "%~dp0\node_modules\npm\bin\npm-cli.js" %*
) ELSE (
#SETLOCAL
#SET PATHEXT=%PATHEXT:;.JS;=;%
node "%~dp0\node_modules\npm\bin\npm-cli.js" %*
)
Why does npm link produce script files that launch a package's bin file instead of launching node with the bin file as an argument? How can I fix this behavior?
The solution is to add #!/usr/bin/env node at the beginning of your bin script. I have no idea why though. I found out by comparing my script to others that worked.
What version of npm are you running? Latest is 2.6.0 ;there have been a lot of improvements to npm -- especially around conflicts and race conditions during install -- recently. Can you try updating your npm installation?
To update npm on Windows, follow the instructions here: https://github.com/npm/npm/wiki/Troubleshooting#upgrading-on-windows

How can I test my module locally using a symlink?

I am trying to run a module that I am developing on my local machine. So I have create a test nodejs application and within this test application I created the 'node_modules' director. The folder structure looks like it would if I had used npm install and specified my module as a dependency. Within the node_modules folder I have create a symlink to the directory where my module under test is.
When I attempt to run my test application node complains: Cannot find module 'my_module'.
I can figure out why this wouldn't work. Can this be done in this way?
Thanks!
The recommended way for doing this is:
In your my_module directory, do npm link .. npm will tell you it has made my_module available for linking.
Then, in your test_app directory, do npm link my_module. npm will now make my_module available to your app.
Note that you can still get a a "cannot find module" error this way, but this is then most likely because your my_module module is structured wrongly. By default, Node.js will look for an index.js file in the module's root directory. Otherwise, you need to specify a main entry in the module's package.json, containing the path to your main js file, relative to the module's root directory.

Directly call globally installed Node.js modules

Supposed I want to write a module for Node.js that shall be installed globally. I do not want to write any C++ (or something else), but plain Node.js code.
Basically, this is very easy. Just write the module, and install it using npm install -g.
Now, most globally installed modules provide the possibility to call them directly, e.g. you can type express at your command prompt and run the globally installed application bootstrapper of Express.
Now my question is: How do I achieve this?
If I simply install a module globally, this does not make one of the files available as an executable, or puts that file onto the PATH.
What steps do I need to do in order to achieve this?
So my question is basically: What steps need to be done to create globally available executable out of a "normal" Node.js module?
You need to write an executable file. This is what will be executed when a user types your command. Here's an example taken from JSHint:
#!/usr/bin/env node
require("./../src/cli/cli.js").interpret(process.argv);
Convention says to place this file in a bin directory in the root of your project. You then just need to update your package.json file to tell it where to find your executable:
{
"bin": {
"jshint": "./bin/jshint"
}
}
In this case, the executable file can be run from the terminal with the jshint command. When it runs, it simply requires another file and calls a method in it, passing through any command line arguments.

Resources