Using NPM Programatically
Did you know that you can run
npm commands programatically, giving you access to their output? For example, if you wanted to get the exact version of a 3rd-party package installed in your
node_modules and display it somewhere in your app?
In this post, I'll show you how to do just that, and how I've recently utilized this in a project.
In my day job, as part of our design system library ecosystem, we're building an internal code sandbox (think of it as a mix between Seek OSS Playroom and QuickPaste). It allows our users try the components from our component library (let's call it
@wtf-ds/core) and any other supplementary React code right there in the browser, without having to create a new project in their own environment.
One of the features we were looking to add was a way to display the currently installed versions of the dependencies that users have access to, somewhere in the UI. The sandbox automatically includes
styled-components and several component library packages in the browser editor, and users should have a way to know what specific versions of those packages they're working with.
It may be tempting to just pull this information from
package.json at first:
However, we quickly run into a problem.
Most of the time, the version specified in
package.json won't be exact. It can be either the caret notation (ie.
^5.3.3), or the tilda (
~5.3.3), or perhaps just
latest. This doesn't exactly give us what we want. An approximate version number is better than nothing - of course - but it's also not as useful as the exact one would be.
For example, whenever there's a new version of
5.3.5), and you do a fresh
npm install, the
^5.3.3notation won't accurately reflect the actual, currently installed version.
We can't rely on the value inside
package.json. So how do we solve this?
Well, if we were looking for this info ad-hoc, we could simply run the
npm list command in the terminal:
which gives us all instances of this package in our
node_modules, including any nested dependencies:
We could reduce this down by adding the
which now gives us just the top-level instances, ie. what we need:
As you can see above, our
styled-components set to
^5.3.3 but the actual installed version is
5.3.5 (latest at the time of writing this). This is the version we'd like our users to see, so we can't use the caret notation - we need a way to show this version instead.
Turns out, you can run
npm commands programatically! 🤯
This means that we can now run those
npm list commands from within a Node script, and store the output in a simple JSON file - which can then be accessed in our React code.
So, I've created a seperate script (called
dependencies.js) which parses the output of those commands for each package, and saves that information in a
dependencies.json file. This file is then imported in our Next.js app, and the values displayed in the sandbox UI.
To ensure that this file is always up-to-date, it can be run as a
postinstall script in
The script itself is as follows:
So, what's happening here?
First, we create a "promisified" version of
exec by wrapping it with
Then we read our package info from
package.json, and create an array of our dependency names:
Then, we filter out only the packages we're interested in:
This will ensure that we're only showing the relevant packages to our users. Because our "promisified"
exec method returns a Promise object, and we need one for each of the packages (above), we will need to store these promises in an array that can be resolved later:
And now for the ✨magic✨
For each of the packages in the above array, we run the
npm list command:
This gives us the currently installed version, and the output can be accessed via the
Since we only care about the version number, and not everything else in the output, we can parse it and get just the version number itself:
There's probably a more elegant way to do this with regex, but I will leave that for you to optimize 😉
With our array of promises ready, all that's left is to resolve them. We do this by using
This gives us the result, which is the
data that we'd like to store in our JSON file. The resulting output will look something like this:
We can now import this in our React code, and display the relevant data on the UI
And that's it! 🎉 This is a fairly simple use case, but as you can see we've only scratched the surface here, and hopefully this gives you an idea of what's possible.
The full script is also available as a gist here.