Top 10 npm power-user commands every JavaScript developer should know
Power-user commands can simplify complex tasks and provide deeper insights into your project's dependencies and configurations. From pinpointing vulnerabilities to generating Software Bill of Materials (SBOM), these commands will make you a more efficient and security-conscious developer.
The npm
package manager is an indispensable tool in the JavaScript and Node.js ecosystem. It is the backbone for managing dependencies, scripts, and configurations that streamline development workflows. Whether building a small project or a large-scale application, mastering npm
commands can significantly enhance your productivity and code quality.
Snyk for Secure JavaScript projects
Security is a critical aspect of modern software development. Snyk integrates seamlessly with your JavaScript development workflow to scan for vulnerabilities in your source code, open-source packages, container images, and cloud configurations.
As you review these npm power-user
commands, consider enhancing your security posture by signing up for a free Snyk account.
1. Forwarding command-line flags to npm run script with --
In complex projects, you often need to pass additional command-line flags to scripts executed via npm run
. This is especially useful when you want to adjust the behavior of a script without modifying the script itself. For example, you might want to dynamically change the environment, enable debugging, or pass configuration options.
Example usage in an npm run script
To forward command-line flags to the program executed by an npm run
script, you use the double-dash --
syntax. Everything after --
is passed directly to the script.
Here’s a basic example to illustrate this:
{
"scripts": {
"start": "node app.js"
}
}
You can run the start
script and pass additional flags to the app.js
program as follows:
npm run start -- --port=3000 --env=production
In this case, --port=3000
and --env=production
are forwarded to the script app.js
as positional arguments.
Security considerations
Passing flags dynamically can be powerful but also introduces potential security risks. Ensure that the scripts and the flags you pass are secure and do not expose sensitive information or allow unintended behavior. Always validate and sanitize inputs when dealing with dynamic flags.
2. Inspecting dependency trees with npm ls
As a JavaScript developer, you have to understand your project's dependency tree to identify which vulnerabilities impact packages. The npm ls
command is one of those power-user commands that help you inspect your project's dependency tree. This command provides an ASCII tree view of all the packages your project depends on, including their versions and hierarchical relationships.
The npm ls command syntax and filtering options
The basic syntax for the npm ls
command is:
npm ls [<package-name>]
This command can list all dependencies or focus on a specific package by providing its name. Additionally, you can filter the output to show only production or development dependencies using the --production
or --development
flags, respectively.
Example of finding dependencies affected by a specific vulnerability
Let's say you discovered a denial of service vulnerability in the ms npm
package. To identify which of your project's dependencies are impacted by this vulnerable version, you can use the npm ls
command:
This command will output the dependency tree, highlighting where ms
is used. Here's an example output:
> npm ls ms
goof@1.0.1 /Users/lirantal/projects/repos/nodejs-goof
├─┬ express-session@1.17.2
│ └─┬ debug@2.6.9
│ └── ms@2.0.0
├─┬ express@4.12.4
│ ├─┬ debug@2.2.0
│ │ └── ms@0.7.1
│ ├─┬ finalhandler@0.3.6
│ │ └─┬ debug@2.2.0
│ │ └── ms@0.7.1
│ └─┬ send@0.12.3
│ ├─┬ debug@2.2.0
│ │ └── ms@0.7.1 deduped
│ └── ms@0.7.1
├─┬ humanize-ms@1.0.1
│ └── ms@0.6.2
├─┬ method-override@3.0.0
│ └─┬ debug@3.1.0
│ └── ms@2.0.0
├─┬ mongoose@4.2.4
│ ├─┬ mquery@1.6.3
│ │ └─┬ debug@2.2.0
│ │ └── ms@0.7.1
│ └── ms@0.7.1
├─┬ morgan@1.10.0
│ └─┬ debug@2.6.9
│ └── ms@2.0.0
You can also be specific and search for ms@0.7.1
.
3. Understanding transitive dependencies with npm, why
In large JavaScript projects, managing dependencies can become complex, especially when dealing with transitive dependencies - those not directly included in your package.json
but are required by your direct dependencies.
Identifying why a particular package is included in your project can be crucial for debugging and securing your application. The npm why
command addresses this need by providing detailed information about why a specific package is installed.
The npm why
command is straightforward to use. The basic syntax is:
> npm why ms@0.7.1
ms@0.7.1
node_modules/send/node_modules/ms
ms@"0.7.1" from send@0.12.3
node_modules/send
send@"0.12.3" from express@4.12.4
node_modules/express
express@"4.12.4" from the root project
send@"0.12.3" from serve-static@1.9.3
node_modules/serve-static
serve-static@"~1.9.3" from express@4.12.4
node_modules/express
express@"4.12.4" from the root project
ms@"0.7.1" from debug@2.2.0
node_modules/send/node_modules/debug
debug@"~2.2.0" from send@0.12.3
node_modules/send
send@"0.12.3" from express@4.12.4
node_modules/express
express@"4.12.4" from the root project
send@"0.12.3" from serve-static@1.9.3
node_modules/serve-static
serve-static@"~1.9.3" from express@4.12.4
node_modules/express
express@"4.12.4" from the root project
As you can see, this npm
command will output a detailed explanation of why ms
is included, showing the dependency chain that led to its installation.
Bonus tips for the npm why command
Specify the version: If you know the specific version causing issues, include it in the command for more precise results:
npm why ms@0.7.1
Use the JSON output: For a richer, more detailed output that includes dependency types and other metadata, use the
--json
flag:
npm why ms@0.7.1 --json
This can be particularly useful for automated scripts or deeper analysis.
Understanding transitive dependencies is crucial for maintaining a secure and efficient codebase. Tools like Snyk Open Source can further help by scanning your dependencies for known vulnerabilities and providing actionable insights. Sign up for a free Snyk account here to secure your projects today.
If you are new to dependency management and want to understand how lockfiles work, another helpful resource is the article “What is package lock JSON and how a lockfile works for yarn and npm packages?”. This article explains semantic versioning, shrinkwrap lock files, drifting lockfiles, and other important concepts concerning npm
lockfiles.
4. Listing available life-cycle scripts with npm run
When working on a JavaScript or Node.js project, you often need to run various scripts defined in your package.json
file, right? Be it npm run test
or npm run build
as some prime examples.
However, remembering the exact names of all these npm lifecycle
scripts can be challenging, especially in larger projects or if you regularly work on multiple projects (backend vs frontend).
The npm run
command comes to the rescue! It solves this problem by listing all available life-cycle scripts in your project, making it easy to see what scripts are available and how to run them.
Here's a practical example of viewing all npm run
scripts in a project:
> npm run
Lifecycle scripts included in goof@1.0.1:
start
NODE_OPTIONS=--openssl-legacy-provider node app.js
test
snyk test
available via `npm run-script`:
dev
NODE_OPTIONS=--openssl-legacy-provider nodemon ./app.js
build
browserify -r jquery > public/js/bundle.js
cleanup
mongo express-todo --eval 'db.todos.remove({});'
The output lists out all the available npm run
scripts in the project, along with the commands they execute, saving you a few seconds from opening the package.json
file in your IDE or the terminal!
5. Advanced dependency lookups with npm query
I imagine this is an unknown feature of the npm
package manager, allowing you to filter and select dependencies based on their attributes. You may need to do that to understand the third-party dependency's impact or to perform targeted actions but locating the right npm
dependency can be tricky.
The npm query
command, introduced in npm@8
, provides a powerful way to perform advanced dependency lookups using a query language. This command allows you to filter and select dependencies based on their attributes, making it easier to manage and audit your project's dependencies.
How to use npm query syntax
The command syntax for npm query
is:
npm query "<query>"
The query part is the tricky part. It is its language (DSL) that supports various attributes and operators to refine your search. For instance, you can filter dependencies based on their lifecycle scripts, versions, or other metadata that third-party packages have.
Example: Finding dependencies with a postinstall npm script
Suppose you want to find all dependencies in your project that have a postinstall
script. This can be useful for identifying packages that execute scripts during installation, which could pose security risks or affect your build process. You can use the following query:
npm query ":attr(scripts, [postinstall])"
This command will return a list of dependencies with a postinstall
script defined in their package.json
file.
The output might look something like this:
[
{
"name": "some-package",
"version": "1.0.0",
"scripts": {
"postinstall": "node setup.js"
}
}
]
In this example, some-package
has a postinstall
script that runs node setup.js
. This information can help you audit your dependencies for potential security risks or unwanted behavior.
Bonus tip: use the open-source supply chain security npq tool to take a preventative action to find when dependencies you install have postinstall
or preinstall
scripts before installing them..
6. Comparing versions with npm diff
Understanding the changes between different versions of a package is crucial for maintaining a secure and stable codebase. It is useful if you want to track down what has changed between two versions of a package.
The npm diff
command helps you compare two versions of a package. This is particularly useful for identifying potential security issues, deprecated features, changed dependencies, or significant modifications that could impact your application.
The npm diff
command is straightforward to use and resembles the git diff
command:
npm diff --diff=<package@version1> --diff=<package@version2>
Let's say you want to compare the changes between versions 2.1.2 and 2.1.3 of the ms
package. You would run the following command:
npm diff --diff=ms@2.1.3 --diff=ms@2.1.2
The output will show the differences in the code, similar to a git diff output:
diff --git a/index.js b/index.js
index v2.1.3..v2.1.2 100644
--- a/index.js
+++ b/index.js
@@ -23,7 +23,7 @@
* @api public
*/
-module.exports = function (val, options) {
+module.exports = function(val, options) {
options = options || {};
var type = typeof val;
if (type === 'string' && val.length > 0) {
diff --git a/package.json b/package.json
index v2.1.3..v2.1.2 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
{
"name": "ms",
- "version": "2.1.3",
+ "version": "2.1.2",
"description": "Tiny millisecond conversion utility",
- "repository": "vercel/ms",
+ "repository": "zeit/ms",
"main": "./index",
"files": [
"index.js"
@@ -28,11 +28,10 @@
},
"license": "MIT",
"devDependencies": {
- "eslint": "4.18.2",
+ "eslint": "4.12.1",
"expect.js": "0.3.1",
"husky": "0.14.3",
"lint-staged": "5.0.0",
- "mocha": "4.0.1",
- "prettier": "2.0.5"
+ "mocha": "4.0.1"
}
}
diff --git a/license.md b/license.md
index v2.1.3..v2.1.2 100644
--- a/license.md
+++ b/license.md
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2020 Vercel, Inc.
+Copyright (c) 2016 Zeit, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/readme.md b/readme.md
index v2.1.3..v2.1.2 100644
--- a/readme.md
+++ b/readme.md
@@ -1,6 +1,7 @@
# ms
-![CI](https://github.com/vercel/ms/workflows/CI/badge.svg)
+[![Build Status](https://travis-ci.org/zeit/ms.svg?branch=master)](https://travis-ci.org/zeit/ms)
+[![Join the community on Spectrum](https://withspectrum.github.io/badge/badge.svg)](https://spectrum.chat/zeit)
Use this package to easily convert various time formats to milliseconds.
You can now review the changes to ensure they do not introduce security vulnerabilities or breaking changes.
7. Creating and scanning SBOMs with npm sbom
An SBOM stands for Software Bill of Materials, and it is a detailed list of all components and dependencies in your software, akin to a recipe that lists all ingredients.
This transparency helps identify potential vulnerabilities and ensures compliance with various security standards. SBOMs are particularly important in supply chain security, where understanding your software's components is crucial for managing risks and ensuring trust.
Security teams, more often than not, use SBOMs to ensure compliance with industry standards and regulations, such as the Cybersecurity Executive Order.
How to use npm sbom and integrate it with Snyk
The npm sbom
command generates an SBOM for your project or workspace.
It is especially useful when combined with Snyk, a developer security tool that scans for vulnerabilities in source code, open-source packages, container images, and cloud configurations.
What you can do is rely on npm
as the package manager to generate an SBOM for all of the project dependencies and then send that full dependency tree result to Snyk to scan for vulnerabilities. And you can do all of that straight from the CLI.
Here's the basic syntax to create an SBOM in the CycloneDX format using npm sbom
:
npm sbom --sbom-format cyclonedx > sbom.cdx.json
Now, you can scan this SBOM for vulnerabilities using Snyk:
snyk sbom test --experimental --file=sbom.cdx.json
This will generate an output of vulnerabilities found in the SBOM, including severity levels as follows:
× [HIGH] Regular Expression Denial of Service (ReDoS)
Introduced through: pkg:npm/negotiator@0.2.8
URL: https://security.snyk.io/vuln/npm:negotiator:20160616
× [HIGH] Uninitialized Memory Exposure
Introduced through: pkg:npm/npmconf@0.0.24
URL: https://security.snyk.io/vuln/npm:npmconf:20180512
× [HIGH] Prototype Override Protection Bypass
Introduced through: pkg:npm/qs@2.2.4
URL: https://security.snyk.io/vuln/npm:qs:20170213
× [CRITICAL] Incomplete List of Disallowed Inputs
Introduced through: pkg:npm/babel-traverse@6.26.0
URL: https://security.snyk.io/vuln/SNYK-JS-BABELTRAVERSE-5962463
× [CRITICAL] Prototype Pollution
Introduced through: pkg:npm/handlebars@4.0.11
URL: https://security.snyk.io/vuln/SNYK-JS-HANDLEBARS-534988
× [CRITICAL] Server-side Request Forgery (SSRF)
Introduced through: pkg:npm/parse-url@5.0.1
URL: https://security.snyk.io/vuln/SNYK-JS-PARSEURL-2936249
× [CRITICAL] Arbitrary File Write via Archive Extraction (Zip Slip)
Introduced through: pkg:npm/adm-zip@0.4.7
URL: https://security.snyk.io/vuln/npm:adm-zip:20180415
╭──────────────────────────────────────────────────────────────────────╮
│ Test summary │
│ Organization: a30b7399-4e0c-4f6e-ba84-b27e131db54c │
│ Test type: Software Bill of Materials │
│ Path: sbom.cdx.json │
│ │
│ Open issues: 148 [ 4 CRITICAL 64 HIGH 72 MEDIUM 8 LOW ] │
╰──────────────────────────────────────────────────────────────────────╯
8. Pinning dependencies with overrides in package.json
As you might have experienced by now, dependency management can be a double-edged sword. While npm
makes it incredibly easy to include and manage dependencies, it also means that your project can be vulnerable to security issues arising from transitive dependencies.
One significant issue developers face is the introduction of vulnerabilities or breaking changes in these transitive dependencies. For instance, you might encounter a situation where a widely-used package suddenly introduces a vulnerability or a breaking change, affecting your entire project.
npm
provides a powerful feature called overrides
in the package.json
file to mitigate risks. This feature allows you to pin down specific versions of direct and transitive dependencies, ensuring that your project uses only the versions you trust.
A real-world case where overrides
were invaluable was in the peacenotwar and node-ipc package protestware security incident back in March 2022 to help mitigate disruption to package consumers.
How to use the overrides configuration in package.json
To use the overrides
feature, add an overrides
section in your package.json
file. This section specifies which versions of dependencies should be used, regardless of what is specified in the dependencies' own package.json
files.
Here is a basic example of how to use overrides
:
{
"name": "your-project",
"version": "1.0.0",
"dependencies": {
"some-package": "^2.0.0"
},
"overrides": {
"node-ipc@>9.2.1 <10": "9.2.1",
"node-ipc@>10.1.0": "10.1.0"
}
}
In this example:
The
node-ipc
package is pinned to version 9.2.1 for versions greater than 9.2.1 and less than 10.The
node-ipc
package is pinned to version 10.1.0 for versions greater than 10.1.0.
Enhancing security with Snyk
While pinning dependencies can help mitigate some risks, it is not a silver bullet. Regularly scanning your project for vulnerabilities is crucial. A developer-first security tool like Snyk will detect such cases of malware, protestware, and general security vulnerabilities in your dependencies as quickly as possible and help you remediate them.
To get started with Snyk, sign up for a free account here.
9. Local package development with npm install
When developing npm
packages locally, you often need to test them within another project. Suppose you're unaware of the built-in capability of npm. In that case, you'd be involved in publishing the package to the npm
registry, which can be cumbersome and time-consuming, especially when you need to make frequent changes.
The npm install <path-to-package-in-disk-directory>
command solves this problem by allowing you to install and link local packages directly from your development directory. This creates a soft link to the on-disk directory where you are developing the package, enabling seamless updates and testing without the need for repeated publishing (you also don't even need to uninstall and re-install locally, thanks to the symlink).
To use this command, navigate to the root directory of the project where you want to install the local package. Then, run the following command:
npm install /path/to/local/package
Replace /path/to/local/package
with the actual path to your local package directory. This command creates a symbolic link from the node_module's
directory of your project to the local package directory.
Practical benefits of locally installing packages with npm
Immediate updates: Changes to the local package are instantly reflected in the project, eliminating the need for republishing.
Simplified debugging: You can debug and test your package in real time within the context of the larger project.
Efficient workflow: Streamlines the development process by reducing the overhead associated with package versioning and publishing.
10. Security and compatibility with a .npmrc configuration
The .npmrc
file is a configuration file for npm
that allows you to customize the behavior of npm
commands. It plays a crucial role in enhancing both the security and compatibility of your Node.js projects. By configuring certain settings in the .npmrc
file, you can protect your projects from malicious packages and ensure that your dependencies are compatible with specific Node.js runtime versions.
Ignore scripts for enhanced security
One of the most effective ways to protect your project from harmful and malicious packages is by disabling the execution of lifecycle scripts. These scripts can run arbitrary commands during the installation process, which poses a security risk.
By setting ignore-scripts=true
in your .npmrc
file, you can prevent these scripts from running:
# .npmrc
ignore-scripts=true
This configuration ensures that npm
will not execute any preinstall
, postinstall
, or other lifecycle scripts, thereby reducing the risk of executing malicious code.
Node.js runtime version compatibility
Another important aspect of maintaining a secure and stable project is ensuring compatibility with specific Node.js runtime versions. This is particularly useful if your project relies on older or unsupported Node.js versions. By setting the node-version
in your .npmrc
file, you can instruct npm
to only upgrade dependencies that are compatible with the specified Node.js version:
# .npmrc
node-version=14.0.0
This configuration ensures that npm
will only consider dependencies that match the specified Node.js version range in their engines
manifest file configuration.
Next steps
Power-user npm
commands are useful not only for managing dependencies but also for enhancing security and efficiency in your development workflow.
You might also enjoy reading about some other Node.js related articles:
Top Ten npm security best practices to secure your JavaScript projects.
Ten best practices to containerize Node.js web applications with Docker
Brian Clark’s Best practices for creating a modern npm package with security in mind.