Skip to main content

Top 10 npm power-user commands every JavaScript developer should know

Écrit par:
0 minutes de lecture

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: