-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- As proposed in RFC: npm/rfcs#144
- Loading branch information
Showing
54 changed files
with
9,844 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
--- | ||
title: npm-diff | ||
section: 1 | ||
description: The registry diff command | ||
--- | ||
|
||
### Synopsis | ||
|
||
```bash | ||
npm diff | ||
npm diff <pkg-name> | ||
npm diff <version-a> [<version-b>] | ||
npm diff <spec-a> [<spec-b>] | ||
``` | ||
|
||
### Description | ||
|
||
Similar to its `git diff` counterpart, this command will print diff patches | ||
of files for packages published to the npm registry. | ||
|
||
A variation of different arguments are supported, along with a range of | ||
familiar options from [git diff](https://git-scm.com/docs/git-diff#_options). | ||
|
||
* `npm diff <spec-a> <spec-b>` | ||
|
||
Compares two package versions using their registry specifiers, e.g: | ||
`npm diff foo@1.0.0 foo@^2.0.0`. It's also possible to compare across forks | ||
of any package, e.g: `npm diff foo@1.0.0 foo-fork@1.0.0`. | ||
|
||
Any valid spec can be used, so that it's also possible to compare | ||
directories or git repositories, e.g: `npm diff foo@latest ./packages/foo` | ||
|
||
* `npm diff` (in a package directory, no arguments): | ||
|
||
If the package is published to the registry, `npm diff` will fetch the | ||
tarball version tagged as `latest` (this value can be configured using the | ||
`tag` option) and proceed to compare the contents of files present in that | ||
tarball, with the current files in your local file system. | ||
|
||
This workflow provides a handy way for package authors to see what | ||
package-tracked files have been changed in comparison with the latest | ||
published version of that package. | ||
|
||
* `npm diff <version-a> [<version-b>]` | ||
|
||
Using `npm diff` along with semver-valid version numbers is a shorthand | ||
to compare different versions of the current package. It needs to be run | ||
from a package directory, such that for a package named `foo` running | ||
`npm diff 1.0.0 1.0.1` is the same as running | ||
`npm diff foo@1.0.0 foo@1.0.1`. If only a single argument `<version-a>` is | ||
provided, then the current local file system is going to be compared | ||
against that version. | ||
|
||
* `npm diff <pkg-name>` | ||
|
||
When using a single package name (with no version or tag specifier) as an | ||
argument, `npm diff` will work in a similar way to | ||
[`npm-outdated`](npm-outdated) and reach for the registry to figure out | ||
what current published version of the package named <pkg-name> will satisfy | ||
its dependent declared semver-range. Once that specific version is known | ||
`npm diff` will print diff patches comparing the current version of | ||
<pkg-name> found in the local file system with that specific version | ||
returned by the registry. | ||
|
||
* `npm diff <spec-a>` (single specifier argument) | ||
|
||
Similar to using only a single package name, it's also possible to declare | ||
a full registry specifier version if you wish to compare the local version | ||
of a installed package with the specific version/tag/semver-range provided | ||
in `<spec-a>`. e.g: (assuming foo@1.0.0 is installed in the current | ||
`node_modules` folder) running `npm diff foo@2.0.0` will effectively be | ||
an alias to `npm diff foo@1.0.0 foo@2.0.0`. | ||
|
||
#### Filtering files | ||
|
||
It's possible to also specify file names or globs pattern matching in order to | ||
limit the result of diff patches to only a subset of files for a given package. | ||
|
||
Given the fact that paths are also valid specs, a separator `--` is required | ||
when specifying sets of files to filter in diff. Any extra argument declared | ||
after `--` will be treated as a filenames/globs and diff results will be | ||
limited to files included or matched by those. e.g: | ||
|
||
`npm diff foo@2 -- lib/* CHANGELOG.md` | ||
|
||
Note: When using `npm diff` with two spec/version arguments, the separator `--` | ||
becomes redudant and can be removed, e.g: `npm diff foo@1.0.0 foo@1.0.1 lib/*` | ||
|
||
### Configuration | ||
|
||
#### name-only | ||
|
||
* Type: Boolean | ||
* Default: false | ||
|
||
When set to `true` running `npm diff` only returns the names of the files that | ||
have any difference. | ||
|
||
#### unified | ||
|
||
* Alias: `-U` | ||
* Type: number | ||
* Default: `3` | ||
|
||
The number of lines of context to print in the unified diff format output. | ||
|
||
#### ignore-all-space | ||
|
||
* Alias: `-w` | ||
* Type: Boolean | ||
* Default: false | ||
|
||
Ignore whitespace when comparing lines. This ignores differences even if one | ||
line has whitespace where the other line has none. | ||
|
||
#### no-prefix | ||
|
||
* Type: Boolean | ||
* Default: false | ||
|
||
Do not show any source or destination prefix. | ||
|
||
#### src-prefix | ||
|
||
* Type: String | ||
* Default: `"a/"` | ||
|
||
Show the given source prefix in diff patches headers instead of using "a/". | ||
|
||
#### dst-prefix | ||
|
||
* Type: String | ||
* Default: `"b/"` | ||
|
||
Show the given source prefix in diff patches headers instead of using "b/". | ||
|
||
#### text | ||
|
||
* Alias: `-a` | ||
* Type: Boolean | ||
* Default: false | ||
|
||
Treat all files as text. | ||
|
||
#### tag | ||
|
||
* Type: String | ||
* Default: `"latest"` | ||
|
||
The tag used to fetch the tarball that will be compared with local file system | ||
files when running npm diff with no arguments. | ||
|
||
|
||
## See Also | ||
|
||
* [npm outdated](/commands/npm-outdated) | ||
* [npm install](/commands/npm-install) | ||
* [npm config](/commands/npm-config) | ||
* [npm registry](/using-npm/registry) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
const semver = require('semver') | ||
const libdiff = require('libnpmdiff') | ||
const npa = require('npm-package-arg') | ||
const Arborist = require('@npmcli/arborist') | ||
const npmlog = require('npmlog') | ||
const pacote = require('pacote') | ||
const pickManifest = require('npm-pick-manifest') | ||
|
||
const npm = require('./npm.js') | ||
const usageUtil = require('./utils/usage.js') | ||
const output = require('./utils/output.js') | ||
const completion = require('./utils/completion/none.js') | ||
const readLocalPkg = require('./utils/read-local-package.js') | ||
|
||
const usage = usageUtil( | ||
'diff', | ||
'npm diff' + | ||
'\nnpm diff [--ignore-all-space] [--name-only] [-- <path>...]' + | ||
'\nnpm diff <pkg-name>' + | ||
'\nnpm diff <version-a> [<version-b>]' + | ||
'\nnpm diff <spec-a> [<spec-b>]' | ||
) | ||
|
||
const cmd = (args, cb) => diff(args).then(() => cb()).catch(cb) | ||
|
||
const diff = async (args) => { | ||
const [a, b, ...files] = parseArgs(args) | ||
const specs = await retrieveSpecs([a, b]) | ||
npmlog.info(`diff a:${specs.a} b:${specs.b}`) | ||
const res = await libdiff(specs, { | ||
...npm.flatOptions, | ||
...{ diffOpts: { | ||
files, | ||
...getDiffOpts(), | ||
}}, | ||
}) | ||
return output(res) | ||
} | ||
|
||
const parseArgs = (args) => { | ||
const argv = npm.config.parsedArgv.cooked | ||
const sep = argv.indexOf('--') | ||
|
||
if (sep > -1) { | ||
const files = argv.slice(sep + 1) | ||
const notFiles = argv.slice(0, sep) | ||
const [a, b] = args.map(arg => notFiles.includes(arg) ? arg : undefined) | ||
return [a, b, ...files] | ||
} | ||
|
||
return args | ||
} | ||
|
||
const retrieveSpecs = async (args) => { | ||
const [a, b] = await convertVersionsToSpecs(args) | ||
|
||
if (!a) { | ||
const spec = await defaultSpec() | ||
return { a: spec } | ||
} | ||
|
||
if (!b) | ||
return await transformSingleSpec(a) | ||
|
||
return { a, b } | ||
} | ||
|
||
const convertVersionsToSpecs = (args) => | ||
Promise.all(args.map(async arg => { | ||
if (semver.valid(arg)) { | ||
let pkgName | ||
try { | ||
pkgName = await readLocalPkg() | ||
} catch (e) {} | ||
|
||
if (!pkgName) { | ||
throw new Error( | ||
'Needs to be run from a project dir in order to use versions.\n\n' + | ||
`Usage:\n${usage}` | ||
) | ||
} | ||
|
||
return `${pkgName}@${arg}` | ||
} | ||
return arg | ||
})) | ||
|
||
const defaultSpec = async () => { | ||
let pkgName | ||
try { | ||
pkgName = await readLocalPkg() | ||
} catch (e) {} | ||
|
||
if (!pkgName) { | ||
throw new Error( | ||
'Needs multiple arguments to compare or run from a project dir.\n\n' + | ||
`Usage:\n${usage}` | ||
) | ||
} | ||
|
||
return `${pkgName}@${npm.flatOptions.defaultTag}` | ||
} | ||
|
||
const transformSingleSpec = async (a) => { | ||
const spec = npa(a) | ||
let pkgName | ||
|
||
try { | ||
pkgName = await readLocalPkg() | ||
} catch (e) {} | ||
|
||
if (!pkgName) { | ||
throw new Error( | ||
'Needs to be run from a project dir in order to use a single package name.\n\n' + | ||
`Usage:\n${usage}` | ||
) | ||
} | ||
|
||
// when using a single package name as arg and it's part of the current | ||
// install tree, then retrieve the current installed version and compare | ||
// it against the same value `npm outdated` would suggest you to update to | ||
if (spec.registry && spec.name !== pkgName) { | ||
const opts = { | ||
...npm.flatOptions, | ||
path: npm.flatOptions.prefix, | ||
} | ||
const arb = new Arborist(opts) | ||
const actualTree = await arb.loadActual(opts) | ||
const [node] = [ | ||
...actualTree.inventory | ||
.query('name', spec.name) | ||
.values(), | ||
] | ||
|
||
if (!node || !node.name || !node.package || !node.package.version) { | ||
throw new Error( | ||
`Package ${a} not found in the current installed tree.\n\n` + | ||
`Usage:\n${usage}` | ||
) | ||
} | ||
|
||
const tryRootNodeSpec = () => | ||
(actualTree.edgesOut.get(spec.name) || {}).spec | ||
|
||
const tryAnySpec = () => { | ||
for (const edge of node.edgesIn) | ||
return edge.spec | ||
} | ||
|
||
const aSpec = node.package.version | ||
|
||
// finds what version of the package to compare against, if a exact | ||
// version or tag was passed than it should use that, otherwise | ||
// work from the top of the arborist tree to find the original semver | ||
// range declared in the package that depends on the package. | ||
let bSpec | ||
if (spec.rawSpec) | ||
bSpec = spec.rawSpec | ||
else { | ||
const bTargetVersion = | ||
tryRootNodeSpec() | ||
|| tryAnySpec() | ||
|| `${npm.flatOptions.savePrefix}${node.package.version}` | ||
|
||
// figure out what to compare against, | ||
// follows same logic to npm outdated "Wanted" results | ||
const packument = await pacote.packument(spec, { | ||
...npm.flatOptions, | ||
preferOnline: true, | ||
}) | ||
bSpec = pickManifest( | ||
packument, | ||
bTargetVersion, | ||
{ ...npm.flatOptions } | ||
).version | ||
} | ||
|
||
return { | ||
a: `${spec.name}@${aSpec}`, | ||
b: `${spec.name}@${bSpec}`, | ||
} | ||
} | ||
|
||
return { a } | ||
} | ||
|
||
const getDiffOpts = () => ({ | ||
nameOnly: npm.config.get('name-only', 'cli'), | ||
context: npm.config.get('unified', 'cli') || | ||
npm.config.get('U', 'cli'), | ||
ignoreWhitespace: npm.config.get('ignore-all-space', 'cli') || | ||
npm.config.get('w', 'cli'), | ||
noPrefix: npm.config.get('no-prefix', 'cli'), | ||
srcPrefix: npm.config.get('src-prefix', 'cli'), | ||
dstPrefix: npm.config.get('dst-prefix', 'cli'), | ||
text: npm.config.get('text', 'cli') || | ||
npm.config.get('a', 'cli'), | ||
}) | ||
|
||
module.exports = Object.assign(cmd, { completion, usage }) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -119,6 +119,7 @@ const cmdList = [ | |
'prefix', | ||
'bin', | ||
'whoami', | ||
'diff', | ||
'dist-tag', | ||
'ping', | ||
|
||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.