What “Bin” does in package.json?
Recently, I needed to execute a binary executable generated from one of my Haskell projects within a Node.js project. The manual process of copying the binary into my Node.js project directory and setting its executable path before running the project proved to be error-prone and inconvenient. Then I discovered that package.json
provides a solution to this problem out of the box in the name of bin
field
In this article, we’ll explore what cool things we can do using bin,
and stay tuned for the next article, where I will explain how I made a cross-platform script by leveraging this feature.
Shall we get started then?
Bin in a Nutshell
The bin feature allows you to execute not only bash scripts but any file (with the help of shebang) under the command name of your choice.
Still unclear on what bin does? Let’s kick things off with an example to grasp how to utilize bin before delving into its inner workings
Requirement
Suppose we have a file named welcome
(note, there’s no file extension here) that simply prints the argument it receives:
#!/usr/bin/env bash
echo "Hello $1"
Note: The shebang in the first line instructs to execute this file as a bash script.
Now, let’s try to call this file in one of our JavaScript files:
const { execSync } = require('child_process');
function welcomeUser() {
const name = getCurrentUsername();
execSync(`welcome ${name}`, {stdio : 'inherit'})
}
However, calling the welcomeUser
function will result in an error:
bash: welcome: command not found
But after setting it in the path and giving the script enough permission, we can run the function without any issue
export PATH = $PATH:${PWD}
chmod 777 welcome
But is this approach neat? And can we expect users to perform these setup steps when using our code as a npm package?
There’s a better way, and it is by utilizing the bin
field provided by package.json
Specifying the command in bin field of Package.json
The bin
field of package.json
requires an object representing a map from the command name to the file it needs to execute:
{
...
bin :
{
"command-name" : "path/to/file"
}
}
In our case it would be,
bin : {
"welcome" : "./welcome"
}
Alternatively, if we only require one command and our package name is also welcome
, we can simplify it:
{ name : "welcome"
, ...
, bin : "./welcome"
}
Note: Both of the above configuration enables us to execute the
welcome
file by running thewelcome
command.
Final Step — Linking
Even after specifying bin
in package.json
, we'll encounter the same error because simply declaring bin
won't make the command available in our path. The final piece of the puzzle is executing:
npm link
Now we can execute the function without any issue.
How Bin works?
Are you wondering how declaring a command in bin is making it available in the path? where the script is getting stored? How its getting stored? Is the file getting copied to some global namespace or is it getting added directly to path or whats the magic?
Here’s a brief explanation on what goes behind the scenes.
- Symbolic Links
When you declare a command in the bin
field, npm automatically creates a symbolic link for that command.
Symbolic links, also known as symlinks or soft links, are special files that act as pointers to other files or directories. They provide a way to access a file or directory from multiple locations in the file system without duplicating the content.
2. Linking Process
- When you run
npm link
, npm creates symbolic links for all the commands specified in thebin
field of your package - These symbolic links are placed in the appropriate directory based on your system configuration. On Unix-based systems like Linux and macOS, npm typically creates symbolic links in the
/usr/local/bin
directory. On Windows, it creates them in theC:\Users\{Username}\AppData\Roaming\npm
directory. - Additionally, when someone installs your package as a dependency, npm automatically creates a symbolic link in the
node_modules/.bin
directory of their project.
If you are in any unix-based machine, you can test this by executing:
which welcome
This will print
usr/local/bin/welcome
By creating symbolic links with npm link, your command becomes globally available, so be cautious not to use any existing command names. However, if installed as a dependency, it’s confined to the node_modules/.bin
folder within the project, accessible only via npm scripts.
Postscripts🤔
Furthermore, the versatility of the bin field extends beyond bash scripts. With the aid of a shebang (#!) in your script files, you can execute scripts written in any language.
In my next article, I will be explaining how I leveraged this bin
feature and node js to execute different bash scripts depending upon the platform of my users. So stay tuned🎇️
If you have any questions or suggestions, feel free to mention them. As I’m relatively new to this feature, I’m eager to hear your feedback!