DEV Community

loading...

The New npm diff Command

Tierney Cyren
⬡.js - Developer Advocacy at Microsoft. Does: Node.js, Electron, TC39, OpenJS Foundation, npm. Is: twete expert. script kiddie. Stringly typed. Opinions mine. they/he. Demiboy.
・6 min read

With the recent release of npm@7, we've gotten a few neat new features in npm.

One of the ones that I imagine can go under the radar for most folks is the npm diff command. It's a relatively... advanced command that has immense potential utility.

Preface

There's a few things we should establish as baseline assumptions before digging in.

First, I'm going to be talking about a "local version" and a "remote version".

The local version will be a module in your current working directory - so, if I wanted to diff my local liblice module with the remote published version, I'd need to have that on disk and either have it as my current working directory or pass it as a path to the command. For the sake of this article, we're going to assume anytime we're diffing a local version it's in the current working directory.

The remote version will be one that's on your default registry. For the vast majority of folks, this will be the default npm registry (a.k.a. https://registry.npmjs.com). However, if you're trying to diff a module published to an alternative registry (say, an internal corporate registry) or if you've changed your default registry to a mirror or internal cache, that would be the registry the remote version would be checking. This is some super nice flexibility that comes free with the diff command that enables some pretty nice theoretical advanced workflows.

Diffing the Local Version with the Latest Remote Version

The raw command will diff the local version of a module with the remote version. This is particularly useful for module maintainers, contributors, and those floating local patches on a module (to patch a security vulnerability, for example).

npm diff
Enter fullscreen mode Exit fullscreen mode

This will output all differences between the local version and the remote version. With a single change (replacing pass with return) in README.md to use a better word, here's an example of what output would look like:

$ npm diff
diff --git a/README.md b/README.md
index v1.1.0..v1.1.0 100644
-------- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@

 ## Usage

-temporalts can pass all Node.js LTS release line temporal information, or the temporal information for a _specific_ release line.
+temporalts can return all Node.js LTS release line temporal information, or the temporal information for a _specific_ release line.

 ### `temporalts()`
Enter fullscreen mode Exit fullscreen mode

Diffing Individual Local File(s) with the Latest Remote Version

If you've made more than a single tiny change to your module, diffing a single file from the local version with the remote version.

# the general structure
npm diff [...paths]

# specific examples
npm diff README.md
npm diff /lib/handler.js
npm diff SECURITY.md /public/index.html
Enter fullscreen mode Exit fullscreen mode

Instead of getting a diff for all files, you'll only get diffs for any paths you passed. If you passed a single path, you'll get a single diff; if you passed multiple, you'll get multiple.

This functionality can be particularly useful in a handful of cases: generating changelogs, checking how something works in the currently published version, or even as a prepublish check to make sure you're only shipping changes you intended to ship.

Diffing Local Version with a Specific Remote Version

Similar to Diffing the Local Version with the Latest Remote Version, you can diff your local version of a module with the remote version, but with any specific version.

npm diff --diff=<version>
Enter fullscreen mode Exit fullscreen mode

As an example, here's an excerpt from running npm diff --diff=1.0.1 on temporalts@1.1.0. There's a single version between these two - v1.0.1 was also published, and we're able to compare what we currently have with a previous version that's not @latest. There's a few uses for this, like checking what's changed in a module while upgrading, changelog authoring, or pre-publish change validation (especially if you're using files in package.json to limit which files you're publishing).

We can also diff forward - for example, if I were to publish temporalts@2.0.0, I'd be able to diff between v1.1.0 and v2.0.0. This is useful, particularly if you're looking forward for upgrades or at pre-release versions.

$ npm diff --diff==1.0.0                                                                                
diff --git a/examples/12.js b/examples/12.js
deleted file mode 100644
index v1.0.0..v1.1.0 
-------- a/examples/12.js
+++ b/examples/12.js
@@ -1,12 +0,0 @@
-const temporalts = require('../')
-
-async function prettyPrint () {
-  const version = 'v12'
-  const data = await temporalts(version)
-
-  console.log()
-  console.log(`We are ${data.currentPercentOfLTSLifeSpanWithoutDecimal}% through the lifespan of the Node.js ${version} LTS release line.\n${data.currentPercentOfLTSLifeSpanAsProgressBar} `)
-  console.log()
-}
-
-prettyPrint()
\ No newline at end of file
diff --git a/helpers/fetchSchedule.js b/helpers/fetchSchedule.js
index v1.0.0..v1.1.0 100644
-------- a/helpers/fetchSchedule.js
+++ b/helpers/fetchSchedule.js
@@ -12,4 +12,4 @@
   }
 }

-module.exports = fetchSchedule
+module.exports.fetchSchedule = fetchSchedule
diff --git a/index.js b/index.js
index v1.0.0..v1.1.0 100644
-------- a/index.js
+++ b/index.js
@@ -1,3 +1,3 @@
-const lts = require('exports/lts.js')
+const lts = require('./exports/lts.js')

[... more file diffs, dropped for length]
Enter fullscreen mode Exit fullscreen mode

Diffing Individual Local File(s) with a Specific Remote Version

As an extension of Diffing Local Version with a Specific Remote Version, you can also pass in a single file to diff.

# the general structure
npm diff --diff=<version> [...paths]

# specific examples
npm diff --diff=<version> README.md
npm diff --diff=<version> /lib/handler.js
npm diff --diff=<version> SECURITY.md /public/index.html
Enter fullscreen mode Exit fullscreen mode

As an example, if we diff index.js:

$ npm diff --diff==1.0.0 index.js
diff --git a/index.js b/index.js
index v1.0.0..v1.1.0 100644
-------- a/index.js
+++ b/index.js
@@ -1,3 +1,3 @@
-const lts = require('exports/lts.js')
+const lts = require('./exports/lts.js')

 module.exports = lts
Enter fullscreen mode Exit fullscreen mode

Diffing Two Remote Versions

There is also a very valid reason you may want to diff two versions of the same module you've got that aren't locally stored. npm diff allows you to do this with package identifiers (a.k.a. pkg-identifier) which is something along the lines of package@semver-range - so, for example, node@12.10.0, fastify@latest, babel@7.0.0-rc.1, and npm@7.

# the general structure
npm diff --diff=<pkg-identifier> --diff=<pkg-identifier>

# specific example
npm diff --diff=gatsby@2.32.3 --diff=gatsby@latest
Enter fullscreen mode Exit fullscreen mode

If we try to run the latter command - comparing a slightly older version of Gatsby to the latest version - we'll get about 19mb of diff output at time of publishing (the latest version of Gatsby is presently gatsby@3.2.0 if you'd like to try to reproduce this output exactly). Unfortunately, that's far too much to include in a blog post, but you should try running it yourself.

As with any actively developed module - or any sufficiently modified module from one package identifier to another - this diff will only get bigger as the maintainers published newer and newer versions, making more changes.

Diffing Individual Files from Two Remote Versions

As with previous npm diff commands, you can pass in individual files or paths to filter your diff's output and you'll get just a diff for that file or path.

# the general structure
npm diff --diff=<pkg-identifier> --diff=<pkg-identifier>

# specific examples
npm diff --diff=fastify@3.13.0 --diff=fastify@latest package.json
npm diff --diff=fastify@3.13.0 --diff=fastify@latest /lib/errors.js
npm diff --diff=fastify@3.13.0 --diff=fastify@latest README.md /lib/context.js
Enter fullscreen mode Exit fullscreen mode

Here's what the output for the first command there looks like:

$ npm diff --diff=fastify@3.13.0 --diff=fastify@latest package.json
diff --git a/package.json b/package.json
index v3.13.0..v3.14.1 100644
-------- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "fastify",
-  "version": "3.13.0",
+  "version": "3.14.1",
   "description": "Fast and low overhead web framework, for Node.js",
   "main": "fastify.js",
   "type": "commonjs",
@@ -177,7 +177,7 @@
     "abstract-logging": "^2.0.0",
     "ajv": "^6.12.2",
     "avvio": "^7.1.2",
-    "fast-json-stringify": "^2.2.1",
+    "fast-json-stringify": "^2.5.2",
     "fastify-error": "^0.3.0",
     "fastify-warning": "^0.2.0",
     "find-my-way": "^4.0.0",
Enter fullscreen mode Exit fullscreen mode

Useful Flags

The npm diff command also provides some helpful flags to limit the diff output to only relevant changes, depending on your goal.

  • --diff-ignore-all-space: Ignores all changes that are exclusively space. Extremely useful for limiting changes to only what matters, especially after linter runs.
  • --diff-name-only: Limits outputs to only filenames of files with changes for the command. Extremely useful for getting an overview of what's changed and figuring out which files to drill down into.

Discussion (0)