Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/npm/cli.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Fritz <fritzy@github.com>2021-11-16 21:27:55 +0300
committerNathan Fritz <fritzy@github.com>2021-11-18 22:43:26 +0300
commit44e39ce7042a162f53a708ee4660e84638a66f6b (patch)
tree79af3760604b7ce37e3c733a5aeb3726531ae9c6 /workspaces/libnpmdiff
parentea2906ca32bda348b2833458729dc52081d64cf2 (diff)
separate github actions for workspaces
PR-URL: https://github.com/npm/cli/pull/4050 Credit: @fritzy Close: #4050 Reviewed-by: @wraithgar PR-URL: https://github.com/npm/cli/pull/4050 Credit: @fritzy Close: #4050 Reviewed-by: @wraithgar
Diffstat (limited to 'workspaces/libnpmdiff')
-rw-r--r--workspaces/libnpmdiff/.eslintrc.json3
-rw-r--r--workspaces/libnpmdiff/.gitignore99
-rw-r--r--workspaces/libnpmdiff/CHANGELOG.md30
-rw-r--r--workspaces/libnpmdiff/LICENSE15
-rw-r--r--workspaces/libnpmdiff/README.md98
-rw-r--r--workspaces/libnpmdiff/index.js62
-rw-r--r--workspaces/libnpmdiff/lib/format-diff.js96
-rw-r--r--workspaces/libnpmdiff/lib/should-print-patch.js22
-rw-r--r--workspaces/libnpmdiff/lib/tarball.js34
-rw-r--r--workspaces/libnpmdiff/lib/untar.js96
-rw-r--r--workspaces/libnpmdiff/package.json62
-rw-r--r--workspaces/libnpmdiff/tap-snapshots/test/format-diff.js.test.cjs152
-rw-r--r--workspaces/libnpmdiff/tap-snapshots/test/index.js.test.cjs115
-rw-r--r--workspaces/libnpmdiff/tap-snapshots/test/untar.js.test.cjs134
-rw-r--r--workspaces/libnpmdiff/test/fixtures/archive.tgzbin0 -> 74564 bytes
-rw-r--r--workspaces/libnpmdiff/test/fixtures/ruyadorno-simplistic-pkg-with-folders-1.0.0.tgzbin0 -> 573 bytes
-rw-r--r--workspaces/libnpmdiff/test/fixtures/simple-output-2.2.1.tgzbin0 -> 2227 bytes
-rw-r--r--workspaces/libnpmdiff/test/format-diff.js483
-rw-r--r--workspaces/libnpmdiff/test/index.js147
-rw-r--r--workspaces/libnpmdiff/test/should-print-patch.js28
-rw-r--r--workspaces/libnpmdiff/test/tarball.js96
-rw-r--r--workspaces/libnpmdiff/test/untar.js231
22 files changed, 2003 insertions, 0 deletions
diff --git a/workspaces/libnpmdiff/.eslintrc.json b/workspaces/libnpmdiff/.eslintrc.json
new file mode 100644
index 000000000..b39431d2c
--- /dev/null
+++ b/workspaces/libnpmdiff/.eslintrc.json
@@ -0,0 +1,3 @@
+{
+ "extends": ["@npmcli"]
+}
diff --git a/workspaces/libnpmdiff/.gitignore b/workspaces/libnpmdiff/.gitignore
new file mode 100644
index 000000000..0aba557bf
--- /dev/null
+++ b/workspaces/libnpmdiff/.gitignore
@@ -0,0 +1,99 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+lerna-debug.log*
+
+# Diagnostic reports (https://nodejs.org/api/report.html)
+report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+*.lcov
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# TypeScript v1 declaration files
+typings/
+
+# TypeScript cache
+*.tsbuildinfo
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Microbundle cache
+.rpt2_cache/
+.rts2_cache_cjs/
+.rts2_cache_es/
+.rts2_cache_umd/
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+.env.test
+
+# parcel-bundler cache (https://parceljs.org/)
+.cache
+
+# next.js build output
+.next
+
+# nuxt.js build output
+.nuxt
+
+# gatsby files
+.cache/
+public
+
+# vuepress build output
+.vuepress/dist
+
+# Serverless directories
+.serverless/
+
+# FuseBox cache
+.fusebox/
+
+# DynamoDB Local files
+.dynamodb/
+
+# Editors
+Session.vim
diff --git a/workspaces/libnpmdiff/CHANGELOG.md b/workspaces/libnpmdiff/CHANGELOG.md
new file mode 100644
index 000000000..b93b15b7b
--- /dev/null
+++ b/workspaces/libnpmdiff/CHANGELOG.md
@@ -0,0 +1,30 @@
+# Changelog
+
+## 2.0.3
+
+- fix name of options sent by the npm cli
+
+## 2.0.2
+
+- fix matching basename file filter
+
+## 2.0.1
+
+- fix for tarballs not listing folder names
+
+## 2.0.0
+
+- API rewrite:
+ - normalized all options
+ - specs to compare are now an array
+- fix context=0
+- added support to filtering by folder names
+
+## 1.0.1
+
+- fixed nameOnly option
+
+## 1.0.0
+
+- Initial release
+
diff --git a/workspaces/libnpmdiff/LICENSE b/workspaces/libnpmdiff/LICENSE
new file mode 100644
index 000000000..d3a1cdfd2
--- /dev/null
+++ b/workspaces/libnpmdiff/LICENSE
@@ -0,0 +1,15 @@
+The ISC License
+
+Copyright (c) GitHub Inc.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/workspaces/libnpmdiff/README.md b/workspaces/libnpmdiff/README.md
new file mode 100644
index 000000000..6c60f714b
--- /dev/null
+++ b/workspaces/libnpmdiff/README.md
@@ -0,0 +1,98 @@
+# libnpmdiff
+
+[![npm version](https://img.shields.io/npm/v/libnpmdiff.svg)](https://npm.im/libnpmdiff)
+[![license](https://img.shields.io/npm/l/libnpmdiff.svg)](https://npm.im/libnpmdiff)
+[![GitHub Actions](https://github.com/npm/libnpmdiff/workflows/node-ci/badge.svg)](https://github.com/npm/libnpmdiff/actions?query=workflow%3Anode-ci)
+[![Coverage Status](https://coveralls.io/repos/github/npm/libnpmdiff/badge.svg?branch=main)](https://coveralls.io/github/npm/libnpmdiff?branch=main)
+
+The registry diff lib.
+
+## Table of Contents
+
+* [Example](#example)
+* [Install](#install)
+* [Contributing](#contributing)
+* [API](#api)
+* [LICENSE](#license)
+
+## Example
+
+```js
+const libdiff = require('libnpmdiff')
+
+const patch = await libdiff([
+ 'abbrev@1.1.0',
+ 'abbrev@1.1.1'
+])
+console.log(
+ patch
+)
+```
+
+Returns:
+
+```patch
+diff --git a/package.json b/package.json
+index v1.1.0..v1.1.1 100644
+--- a/package.json
++++ b/package.json
+@@ -1,6 +1,6 @@
+ {
+ "name": "abbrev",
+- "version": "1.1.0",
++ "version": "1.1.1",
+ "description": "Like ruby's abbrev module, but in js",
+ "author": "Isaac Z. Schlueter <i@izs.me>",
+ "main": "abbrev.js",
+
+```
+
+## Install
+
+`$ npm install libnpmdiff`
+
+### Contributing
+
+The npm team enthusiastically welcomes contributions and project participation!
+There's a bunch of things you can do if you want to contribute! The
+[Contributor Guide](https://github.com/npm/cli/blob/latest/CONTRIBUTING.md)
+outlines the process for community interaction and contribution. Please don't
+hesitate to jump in if you'd like to, or even ask us questions if something
+isn't clear.
+
+All participants and maintainers in this project are expected to follow the
+[npm Code of Conduct](https://docs.npmjs.com/policies/conduct), and just
+generally be excellent to each other.
+
+Please refer to the [Changelog](CHANGELOG.md) for project history details, too.
+
+Happy hacking!
+
+### API
+
+#### `> libnpmdif([ a, b ], [opts]) -> Promise<String>`
+
+Fetches the registry tarballs and compare files between a spec `a` and spec `b`. **npm** spec types are usually described in `<pkg-name>@<version>` form but multiple other types are alsos supported, for more info on valid specs take a look at [`npm-package-arg`](https://github.com/npm/npm-package-arg).
+
+**Options**:
+
+- `color <Boolean>`: Should add ANSI colors to string output? Defaults to `false`.
+- `tagVersionPrefix <Sring>`: What prefix should be used to define version numbers. Defaults to `v`
+- `diffUnified <Number>`: How many lines of code to print before/after each diff. Defaults to `3`.
+- `diffFiles <Array<String>>`: If set only prints patches for the files listed in this array (also accepts globs). Defaults to `undefined`.
+- `diffIgnoreAllSpace <Boolean>`: Whether or not should ignore changes in whitespace (very useful to avoid indentation changes extra diff lines). Defaults to `false`.
+- `diffNameOnly <Boolean>`: Prints only file names and no patch diffs. Defaults to `false`.
+- `diffNoPrefix <Boolean>`: If true then skips printing any prefixes in filenames. Defaults to `false`.
+- `diffSrcPrefix <String>`: Prefix to be used in the filenames from `a`. Defaults to `a/`.
+- `diffDstPrefix <String>`: Prefix to be used in the filenames from `b`. Defaults to `b/`.
+- `diffText <Boolean>`: Should treat all files as text and try to print diff for binary files. Defaults to `false`.
+- ...`cache`, `registry`, `where` and other common options accepted by [pacote](https://github.com/npm/pacote#options)
+
+Returns a `Promise` that fullfils with a `String` containing the resulting patch diffs.
+
+Throws an error if either `a` or `b` are missing or if trying to diff more than two specs.
+
+## LICENSE
+
+[ISC](./LICENSE)
+
diff --git a/workspaces/libnpmdiff/index.js b/workspaces/libnpmdiff/index.js
new file mode 100644
index 000000000..6e68e6c4c
--- /dev/null
+++ b/workspaces/libnpmdiff/index.js
@@ -0,0 +1,62 @@
+const pacote = require('pacote')
+
+const formatDiff = require('./lib/format-diff.js')
+const getTarball = require('./lib/tarball.js')
+const untar = require('./lib/untar.js')
+
+// TODO: we test this condition in the diff command
+// so this error probably doesnt need to be here. Or
+// if it does we should figure out a standard code
+// so we can catch it in the cli and display it consistently
+const argsError = () =>
+ Object.assign(
+ new TypeError('libnpmdiff needs two arguments to compare'),
+ { code: 'EDIFFARGS' }
+ )
+const diff = async (specs, opts = {}) => {
+ if (specs.length !== 2) {
+ throw argsError()
+ }
+
+ const [
+ aManifest,
+ bManifest,
+ ] =
+ await Promise.all(specs.map(spec => pacote.manifest(spec, opts)))
+
+ const versions = {
+ a: aManifest.version,
+ b: bManifest.version,
+ }
+
+ // fetches tarball using pacote
+ const [a, b] = await Promise.all([
+ getTarball(aManifest, opts),
+ getTarball(bManifest, opts),
+ ])
+
+ // read all files
+ // populates `files` and `refs`
+ const {
+ files,
+ refs,
+ } = await untar([
+ {
+ prefix: 'a/',
+ item: a,
+ },
+ {
+ prefix: 'b/',
+ item: b,
+ },
+ ], opts)
+
+ return formatDiff({
+ files,
+ opts,
+ refs,
+ versions,
+ })
+}
+
+module.exports = diff
diff --git a/workspaces/libnpmdiff/lib/format-diff.js b/workspaces/libnpmdiff/lib/format-diff.js
new file mode 100644
index 000000000..211386cb5
--- /dev/null
+++ b/workspaces/libnpmdiff/lib/format-diff.js
@@ -0,0 +1,96 @@
+const EOL = '\n'
+
+const colorizeDiff = require('@npmcli/disparity-colors')
+const jsDiff = require('diff')
+
+const shouldPrintPatch = require('./should-print-patch.js')
+
+const formatDiff = ({ files, opts = {}, refs, versions }) => {
+ let res = ''
+ const srcPrefix = opts.diffNoPrefix ? '' : opts.diffSrcPrefix || 'a/'
+ const dstPrefix = opts.diffNoPrefix ? '' : opts.diffDstPrefix || 'b/'
+
+ for (const filename of files.values()) {
+ const names = {
+ a: `${srcPrefix}${filename}`,
+ b: `${dstPrefix}${filename}`,
+ }
+
+ let fileMode = ''
+ const filenames = {
+ a: refs.get(`a/${filename}`),
+ b: refs.get(`b/${filename}`),
+ }
+ const contents = {
+ a: filenames.a && filenames.a.content,
+ b: filenames.b && filenames.b.content,
+ }
+ const modes = {
+ a: filenames.a && filenames.a.mode,
+ b: filenames.b && filenames.b.mode,
+ }
+
+ if (contents.a === contents.b && modes.a === modes.b) {
+ continue
+ }
+
+ if (opts.diffNameOnly) {
+ res += `${filename}${EOL}`
+ continue
+ }
+
+ let patch = ''
+ let headerLength = 0
+ const header = str => {
+ headerLength++
+ patch += `${str}${EOL}`
+ }
+
+ // manually build a git diff-compatible header
+ header(`diff --git ${names.a} ${names.b}`)
+ if (modes.a === modes.b) {
+ fileMode = filenames.a.mode
+ } else {
+ if (modes.a && !modes.b) {
+ header(`deleted file mode ${modes.a}`)
+ } else if (!modes.a && modes.b) {
+ header(`new file mode ${modes.b}`)
+ } else {
+ header(`old mode ${modes.a}`)
+ header(`new mode ${modes.b}`)
+ }
+ }
+ /* eslint-disable-next-line max-len */
+ header(`index ${opts.tagVersionPrefix || 'v'}${versions.a}..${opts.tagVersionPrefix || 'v'}${versions.b} ${fileMode}`)
+
+ if (shouldPrintPatch(filename)) {
+ patch += jsDiff.createTwoFilesPatch(
+ names.a,
+ names.b,
+ contents.a || '',
+ contents.b || '',
+ '',
+ '',
+ {
+ context: opts.diffUnified === 0 ? 0 : opts.diffUnified || 3,
+ ignoreWhitespace: opts.diffIgnoreAllSpace,
+ }
+ ).replace(
+ '===================================================================\n',
+ ''
+ ).replace(/\t\n/g, '\n') // strip trailing tabs
+ headerLength += 2
+ } else {
+ header(`--- ${names.a}`)
+ header(`+++ ${names.b}`)
+ }
+
+ res += (opts.color
+ ? colorizeDiff(patch, { headerLength })
+ : patch)
+ }
+
+ return res.trim()
+}
+
+module.exports = formatDiff
diff --git a/workspaces/libnpmdiff/lib/should-print-patch.js b/workspaces/libnpmdiff/lib/should-print-patch.js
new file mode 100644
index 000000000..a95481140
--- /dev/null
+++ b/workspaces/libnpmdiff/lib/should-print-patch.js
@@ -0,0 +1,22 @@
+const { basename, extname } = require('path')
+
+const binaryExtensions = require('binary-extensions')
+
+// we should try to print patches as long as the
+// extension is not identified as binary files
+const shouldPrintPatch = (path, opts = {}) => {
+ if (opts.diffText) {
+ return true
+ }
+
+ const filename = basename(path)
+ const extension = (
+ filename.startsWith('.')
+ ? filename
+ : extname(filename)
+ ).substr(1)
+
+ return !binaryExtensions.includes(extension)
+}
+
+module.exports = shouldPrintPatch
diff --git a/workspaces/libnpmdiff/lib/tarball.js b/workspaces/libnpmdiff/lib/tarball.js
new file mode 100644
index 000000000..4d01d69c9
--- /dev/null
+++ b/workspaces/libnpmdiff/lib/tarball.js
@@ -0,0 +1,34 @@
+const { relative } = require('path')
+
+const npa = require('npm-package-arg')
+const pkgContents = require('@npmcli/installed-package-contents')
+const pacote = require('pacote')
+const { tarCreateOptions } = pacote.DirFetcher
+const tar = require('tar')
+
+// returns a simplified tarball when reading files from node_modules folder,
+// thus avoiding running the prepare scripts and the extra logic from packlist
+const nodeModulesTarball = (manifest, opts) =>
+ pkgContents({ path: manifest._resolved, depth: 1 })
+ .then(files =>
+ files.map(file => relative(manifest._resolved, file))
+ )
+ .then(files =>
+ tar.c(tarCreateOptions(manifest), files).concat()
+ )
+
+const tarball = (manifest, opts) => {
+ const resolved = manifest._resolved
+ const where = opts.where || process.cwd()
+
+ const fromNodeModules = npa(resolved).type === 'directory'
+ && /node_modules[\\/](@[^\\/]+\/)?[^\\/]+[\\/]?$/.test(relative(where, resolved))
+
+ if (fromNodeModules) {
+ return nodeModulesTarball(manifest, opts)
+ }
+
+ return pacote.tarball(manifest._resolved, opts)
+}
+
+module.exports = tarball
diff --git a/workspaces/libnpmdiff/lib/untar.js b/workspaces/libnpmdiff/lib/untar.js
new file mode 100644
index 000000000..16b69ab8f
--- /dev/null
+++ b/workspaces/libnpmdiff/lib/untar.js
@@ -0,0 +1,96 @@
+const tar = require('tar')
+const minimatch = require('minimatch')
+
+const normalizeMatch = str => str
+ .replace(/\\+/g, '/')
+ .replace(/^\.\/|^\./, '')
+
+// files and refs are mutating params
+// filterFiles, item, prefix and opts are read-only options
+const untar = ({ files, refs }, { filterFiles, item, prefix }) => {
+ tar.list({
+ filter: (path, entry) => {
+ const fileMatch = () =>
+ (!filterFiles.length ||
+ filterFiles.some(f => {
+ const pattern = normalizeMatch(f)
+ return minimatch(
+ normalizeMatch(path),
+ `{package/,}${pattern}`,
+ { matchBase: pattern.startsWith('*') }
+ )
+ }))
+
+ // expands usage of simple path filters, e.g: lib or src/
+ const folderMatch = () =>
+ filterFiles.some(f =>
+ normalizeMatch(path).startsWith(normalizeMatch(f)) ||
+ normalizeMatch(path).startsWith(`package/${normalizeMatch(f)}`))
+
+ if (
+ entry.type === 'File' &&
+ (fileMatch() || folderMatch())
+ ) {
+ const key = path.replace(/^[^/]+\/?/, '')
+ files.add(key)
+
+ // should skip reading file when using --name-only option
+ let content
+ try {
+ entry.setEncoding('utf8')
+ content = entry.concat()
+ } catch (e) {
+ /* istanbul ignore next */
+ throw Object.assign(
+ new Error('failed to read files'),
+ { code: 'EDIFFUNTAR' }
+ )
+ }
+
+ refs.set(`${prefix}${key}`, {
+ content,
+ mode: `100${entry.mode.toString(8)}`,
+ })
+ return true
+ }
+ },
+ })
+ .on('error', /* istanbul ignore next */ e => {
+ throw e
+ })
+ .end(item)
+}
+
+const readTarballs = async (tarballs, opts = {}) => {
+ const files = new Set()
+ const refs = new Map()
+ const arr = [].concat(tarballs)
+
+ const filterFiles = opts.diffFiles || []
+
+ for (const i of arr) {
+ untar({
+ files,
+ refs,
+ }, {
+ item: i.item,
+ prefix: i.prefix,
+ filterFiles,
+ })
+ }
+
+ // await to read all content from included files
+ const allRefs = [...refs.values()]
+ const contents = await Promise.all(allRefs.map(async ref => ref.content))
+
+ contents.forEach((content, index) => {
+ allRefs[index].content = content
+ })
+
+ return {
+ files,
+ refs,
+ }
+}
+
+module.exports = readTarballs
diff --git a/workspaces/libnpmdiff/package.json b/workspaces/libnpmdiff/package.json
new file mode 100644
index 000000000..129d9b90c
--- /dev/null
+++ b/workspaces/libnpmdiff/package.json
@@ -0,0 +1,62 @@
+{
+ "name": "libnpmdiff",
+ "version": "2.0.4",
+ "description": "The registry diff",
+ "repository": "https://github.com/npm/libnpmdiff",
+ "files": [
+ "index.js",
+ "lib"
+ ],
+ "engines": {
+ "node": ">=10"
+ },
+ "keywords": [
+ "npm",
+ "npmcli",
+ "libnpm",
+ "cli",
+ "diff"
+ ],
+ "author": "GitHub Inc.",
+ "contributors": [
+ {
+ "name": "Ruy Adorno",
+ "url": "https://ruyadorno.com",
+ "twitter": "ruyadorno"
+ }
+ ],
+ "license": "ISC",
+ "scripts": {
+ "eslint": "eslint",
+ "lint": "npm run eslint -- index.js \"lib/**/*.js\" \"test/*.js\"",
+ "lintfix": "npm run lint -- --fix",
+ "test": "tap test/*.js",
+ "posttest": "npm run lint",
+ "snap": "tap test/*.js",
+ "preversion": "npm test",
+ "postversion": "npm publish",
+ "prepublishOnly": "git push origin --follow-tags"
+ },
+ "tap": {
+ "check-coverage": true
+ },
+ "standard": {
+ "ignore": [
+ "/tap-snapshots/"
+ ]
+ },
+ "devDependencies": {
+ "eslint": "^8.1.0",
+ "tap": "^15.0.9"
+ },
+ "dependencies": {
+ "@npmcli/disparity-colors": "^1.0.1",
+ "@npmcli/installed-package-contents": "^1.0.7",
+ "binary-extensions": "^2.2.0",
+ "diff": "^5.0.0",
+ "minimatch": "^3.0.4",
+ "npm-package-arg": "^8.1.4",
+ "pacote": "^12.0.0",
+ "tar": "^6.1.0"
+ }
+}
diff --git a/workspaces/libnpmdiff/tap-snapshots/test/format-diff.js.test.cjs b/workspaces/libnpmdiff/tap-snapshots/test/format-diff.js.test.cjs
new file mode 100644
index 000000000..f735d8925
--- /dev/null
+++ b/workspaces/libnpmdiff/tap-snapshots/test/format-diff.js.test.cjs
@@ -0,0 +1,152 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/format-diff.js TAP added file > should output expected added file diff result 1`] = `
+diff --git a/foo.js b/foo.js
+new file mode 100755
+index v1.0.0..v2.0.0
+--- a/foo.js
++++ b/foo.js
+@@ -0,0 +1,2 @@
++"use strict"
++module.exports = "foo"
+`
+
+exports[`test/format-diff.js TAP binary file > should output expected bin file diff result 1`] = `
+diff --git a/foo.jpg b/foo.jpg
+index v1.0.0..v2.0.0 100644
+--- a/foo.jpg
++++ b/foo.jpg
+`
+
+exports[`test/format-diff.js TAP changed file mode > should output expected changed file mode diff result 1`] = `
+diff --git a/foo.js b/foo.js
+old mode 100644
+new mode 100755
+index v1.0.0..v2.0.0
+--- a/foo.js
++++ b/foo.js
+`
+
+exports[`test/format-diff.js TAP colored output > should output expected colored diff result 1`] = `
+diff --git a/foo.js b/foo.js
+index v1.0.0..v2.0.0 100644
+--- a/foo.js
++++ b/foo.js
+@@ -1,2 +1,2 @@
+ "use strict"
+-module.exports = "foo"
++module.exports = "foobar"
+`
+
+exports[`test/format-diff.js TAP diff options > should output expected diff result 1`] = `
+diff --git before/foo.js after/foo.js
+index v1.0.0..v2.0.0 100644
+--- before/foo.js
++++ after/foo.js
+@@ -4,4 +4,6 @@
+ const c = "c"
++const d = "d"
+ module.exports = () => a+
+ b+
+-c
++c+
++d
+`
+
+exports[`test/format-diff.js TAP diffUnified=0 > should output no context lines in output 1`] = `
+diff --git a/foo.js b/foo.js
+index v1.0.0..v2.0.0 100644
+--- a/foo.js
++++ b/foo.js
+@@ -3,2 +3,3 @@
+-const b = "b"
+-const c = "c"
++ const b = "b"
++ const c = "c"
++ const d = "d"
+@@ -7,1 +8,2 @@
+-c
++c+
++d
+`
+
+exports[`test/format-diff.js TAP format multiple files patch > should output expected result for multiple files 1`] = `
+diff --git a/foo.js b/foo.js
+index v1.0.0..v1.1.1 100644
+--- a/foo.js
++++ b/foo.js
+@@ -1,2 +1,2 @@
+ "use strict"
+-module.exports = "foo"
++module.exports = "foobar"
+diff --git a/lib/utils.js b/lib/utils.js
+index v1.0.0..v1.1.1 100644
+--- a/lib/utils.js
++++ b/lib/utils.js
+@@ -1,3 +1,4 @@
+ "use strict"
+ const bar = require("./bar.js")
+-module.exports = () => bar
++module.exports =
++ () => bar + "util"
+`
+
+exports[`test/format-diff.js TAP format removed file > should output expected removed file diff result 1`] = `
+diff --git a/foo.js b/foo.js
+deleted file mode 100644
+index v1.0.0..v2.0.0
+--- a/foo.js
++++ b/foo.js
+@@ -1,2 +0,0 @@
+-"use strict"
+-module.exports = "foo"
+/ No newline at end of file
+`
+
+exports[`test/format-diff.js TAP format simple diff > should output expected diff result 1`] = `
+diff --git a/foo.js b/foo.js
+index v1.0.0..v2.0.0 100644
+--- a/foo.js
++++ b/foo.js
+@@ -1,2 +1,2 @@
+ "use strict"
+-module.exports = "foo"
++module.exports = "foobar"
+`
+
+exports[`test/format-diff.js TAP noPrefix > should output result with no prefixes 1`] = `
+diff --git foo.js foo.js
+index v1.0.0..v2.0.0 100644
+Index: foo.js
+--- foo.js
++++ foo.js
+@@ -1,2 +1,2 @@
+ "use strict"
+-module.exports = "foo"
++module.exports = "foobar"
+`
+
+exports[`test/format-diff.js TAP nothing to diff > should output empty result 1`] = `
+
+`
+
+exports[`test/format-diff.js TAP respect --tag-version-prefix option > should output expected diff result 1`] = `
+diff --git a/foo.js b/foo.js
+index b1.0.0..b2.0.0 100644
+--- a/foo.js
++++ b/foo.js
+@@ -1,2 +1,2 @@
+ "use strict"
+-module.exports = "foo"
++module.exports = "foobar"
+`
+
+exports[`test/format-diff.js TAP using --name-only option > should output expected diff result 1`] = `
+foo.js
+lib/utils.js
+`
diff --git a/workspaces/libnpmdiff/tap-snapshots/test/index.js.test.cjs b/workspaces/libnpmdiff/tap-snapshots/test/index.js.test.cjs
new file mode 100644
index 000000000..21db3deac
--- /dev/null
+++ b/workspaces/libnpmdiff/tap-snapshots/test/index.js.test.cjs
@@ -0,0 +1,115 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/index.js TAP compare two diff specs > should output expected diff 1`] = `
+diff --git a/index.js b/index.js
+index v1.0.0..v2.0.0 100644
+--- a/index.js
++++ b/index.js
+@@ -1,2 +1,2 @@
+ module.exports =
+- "a1"
++ "a2"
+diff --git a/package.json b/package.json
+index v1.0.0..v2.0.0 100644
+--- a/package.json
++++ b/package.json
+@@ -1,4 +1,4 @@
+ {
+ "name": "a",
+- "version": "1.0.0"
++ "version": "2.0.0"
+ }
+`
+
+exports[`test/index.js TAP folder in node_modules nested, absolute path > should output expected diff 1`] = `
+diff --git a/package.json b/package.json
+index v2.0.0..v2.0.1 100644
+--- a/package.json
++++ b/package.json
+@@ -1,6 +1,6 @@
+ {
+ "name": "b",
+- "version": "2.0.0",
++ "version": "2.0.1",
+ "scripts": {
+ "prepare": "node prepare.js"
+ }
+diff --git a/prepare.js b/prepare.js
+index v2.0.0..v2.0.1 100644
+--- a/prepare.js
++++ b/prepare.js
+@@ -1,1 +0,0 @@
+-throw new Error("ERR")
+/ No newline at end of file
+`
+
+exports[`test/index.js TAP folder in node_modules nested, relative path > should output expected diff 1`] = `
+diff --git a/package.json b/package.json
+index v2.0.0..v2.0.1 100644
+--- a/package.json
++++ b/package.json
+@@ -1,6 +1,6 @@
+ {
+ "name": "b",
+- "version": "2.0.0",
++ "version": "2.0.1",
+ "scripts": {
+ "prepare": "node prepare.js"
+ }
+diff --git a/prepare.js b/prepare.js
+index v2.0.0..v2.0.1 100644
+--- a/prepare.js
++++ b/prepare.js
+@@ -1,1 +0,0 @@
+-throw new Error("ERR")
+/ No newline at end of file
+`
+
+exports[`test/index.js TAP folder in node_modules top-level, absolute path > should output expected diff 1`] = `
+diff --git a/package.json b/package.json
+index v1.0.0..v1.0.1 100644
+--- a/package.json
++++ b/package.json
+@@ -1,6 +1,6 @@
+ {
+ "name": "a",
+- "version": "1.0.0",
++ "version": "1.0.1",
+ "scripts": {
+ "prepare": "node prepare.js"
+ }
+diff --git a/prepare.js b/prepare.js
+index v1.0.0..v1.0.1 100644
+--- a/prepare.js
++++ b/prepare.js
+@@ -1,1 +0,0 @@
+-throw new Error("ERR")
+/ No newline at end of file
+`
+
+exports[`test/index.js TAP folder in node_modules top-level, relative path > should output expected diff 1`] = `
+diff --git a/package.json b/package.json
+index v1.0.0..v1.0.1 100644
+--- a/package.json
++++ b/package.json
+@@ -1,6 +1,6 @@
+ {
+ "name": "a",
+- "version": "1.0.0",
++ "version": "1.0.1",
+ "scripts": {
+ "prepare": "node prepare.js"
+ }
+diff --git a/prepare.js b/prepare.js
+index v1.0.0..v1.0.1 100644
+--- a/prepare.js
++++ b/prepare.js
+@@ -1,1 +0,0 @@
+-throw new Error("ERR")
+/ No newline at end of file
+`
diff --git a/workspaces/libnpmdiff/tap-snapshots/test/untar.js.test.cjs b/workspaces/libnpmdiff/tap-snapshots/test/untar.js.test.cjs
new file mode 100644
index 000000000..b1092feb6
--- /dev/null
+++ b/workspaces/libnpmdiff/tap-snapshots/test/untar.js.test.cjs
@@ -0,0 +1,134 @@
+/* IMPORTANT
+ * This snapshot file is auto-generated, but designed for humans.
+ * It should be checked into source control and tracked carefully.
+ * Re-generate by setting TAP_SNAPSHOT=1 and running tests.
+ * Make sure to inspect the output below. Do not ignore changes!
+ */
+'use strict'
+exports[`test/untar.js TAP filter files > should return list of filenames 1`] = `
+LICENSE
+README.md
+`
+
+exports[`test/untar.js TAP filter files > should return map of filenames with valid contents 1`] = `
+a/LICENSE: true
+a/README.md: true
+`
+
+exports[`test/untar.js TAP filter files by exact filename > should return no filenames 1`] = `
+
+`
+
+exports[`test/untar.js TAP filter files by exact filename > should return no filenames 2`] = `
+
+`
+
+exports[`test/untar.js TAP filter files using glob expressions > should return list of filenames 1`] = `
+lib/index.js
+lib/utils/b.js
+package-lock.json
+test/index.js
+`
+
+exports[`test/untar.js TAP filter files using glob expressions > should return map of filenames with valid contents 1`] = `
+a/lib/index.js: true
+a/lib/utils/b.js: true
+a/package-lock.json: true
+a/test/index.js: true
+`
+
+exports[`test/untar.js TAP match files by end of filename > should return list of filenames 1`] = `
+lib/index.js
+lib/utils/b.js
+test/index.js
+test/utils/b.js
+`
+
+exports[`test/untar.js TAP match files by end of filename > should return map of filenames with valid contents 1`] = `
+a/lib/index.js: true
+a/lib/utils/b.js: true
+a/test/index.js: true
+a/test/utils/b.js: true
+`
+
+exports[`test/untar.js TAP match files by simple folder name > should return list of filenames 1`] = `
+lib/index.js
+lib/utils/b.js
+`
+
+exports[`test/untar.js TAP match files by simple folder name > should return map of filenames with valid contents 1`] = `
+a/lib/index.js: true
+a/lib/utils/b.js: true
+`
+
+exports[`test/untar.js TAP match files by simple folder name variation > should return list of filenames 1`] = `
+test/index.js
+test/utils/b.js
+`
+
+exports[`test/untar.js TAP match files by simple folder name variation > should return map of filenames with valid contents 1`] = `
+a/test/index.js: true
+a/test/utils/b.js: true
+`
+
+exports[`test/untar.js TAP untar package with folders > should have read contents 1`] = `
+module.exports = 'b'
+
+`
+
+exports[`test/untar.js TAP untar package with folders > should return list of filenames 1`] = `
+lib/index.js
+lib/utils/b.js
+package-lock.json
+package.json
+test/index.js
+test/utils/b.js
+`
+
+exports[`test/untar.js TAP untar package with folders > should return map of filenames to its contents 1`] = `
+a/lib/index.js: true
+a/lib/utils/b.js: true
+a/package-lock.json: true
+a/package.json: true
+a/test/index.js: true
+a/test/utils/b.js: true
+`
+
+exports[`test/untar.js TAP untar simple package > should have read contents 1`] = `
+The MIT License (MIT)
+
+Copyright (c) Ruy Adorno (ruyadorno.com)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+`
+
+exports[`test/untar.js TAP untar simple package > should return list of filenames 1`] = `
+LICENSE
+index.js
+package.json
+README.md
+`
+
+exports[`test/untar.js TAP untar simple package > should return map of filenames to its contents 1`] = `
+a/LICENSE: true
+a/index.js: true
+a/package.json: true
+a/README.md: true
+`
diff --git a/workspaces/libnpmdiff/test/fixtures/archive.tgz b/workspaces/libnpmdiff/test/fixtures/archive.tgz
new file mode 100644
index 000000000..843a61123
--- /dev/null
+++ b/workspaces/libnpmdiff/test/fixtures/archive.tgz
Binary files differ
diff --git a/workspaces/libnpmdiff/test/fixtures/ruyadorno-simplistic-pkg-with-folders-1.0.0.tgz b/workspaces/libnpmdiff/test/fixtures/ruyadorno-simplistic-pkg-with-folders-1.0.0.tgz
new file mode 100644
index 000000000..11bbb44c4
--- /dev/null
+++ b/workspaces/libnpmdiff/test/fixtures/ruyadorno-simplistic-pkg-with-folders-1.0.0.tgz
Binary files differ
diff --git a/workspaces/libnpmdiff/test/fixtures/simple-output-2.2.1.tgz b/workspaces/libnpmdiff/test/fixtures/simple-output-2.2.1.tgz
new file mode 100644
index 000000000..8d442f4c1
--- /dev/null
+++ b/workspaces/libnpmdiff/test/fixtures/simple-output-2.2.1.tgz
Binary files differ
diff --git a/workspaces/libnpmdiff/test/format-diff.js b/workspaces/libnpmdiff/test/format-diff.js
new file mode 100644
index 000000000..f2fc7c77d
--- /dev/null
+++ b/workspaces/libnpmdiff/test/format-diff.js
@@ -0,0 +1,483 @@
+const t = require('tap')
+
+const formatDiff = require('../lib/format-diff.js')
+
+const normalizeWin = (str) => str
+ .replace(/\\+/g, '/')
+ .replace(/\r\n/g, '\n')
+
+t.cleanSnapshot = (str) => normalizeWin(str)
+
+t.test('format simple diff', t => {
+ const files = new Set([
+ 'foo.js',
+ ])
+ const refs = new Map(Object.entries({
+ 'a/foo.js': {
+ content: '"use strict"\nmodule.exports = "foo"\n',
+ mode: '100644',
+ },
+ 'b/foo.js': {
+ content: '"use strict"\nmodule.exports = "foobar"\n',
+ mode: '100644',
+ },
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '2.0.0',
+ }
+
+ t.matchSnapshot(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ }),
+ 'should output expected diff result'
+ )
+ t.end()
+})
+
+t.test('nothing to diff', t => {
+ const files = new Set([
+ 'foo.js',
+ ])
+ const refs = new Map(Object.entries({
+ 'a/foo.js': {
+ content: '"use strict"\nmodule.exports = "foo"\n',
+ mode: '100644',
+ },
+ 'b/foo.js': {
+ content: '"use strict"\nmodule.exports = "foo"\n',
+ mode: '100644',
+ },
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '1.0.0',
+ }
+
+ t.matchSnapshot(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ }),
+ 'should output empty result'
+ )
+ t.end()
+})
+
+t.test('format removed file', t => {
+ const files = new Set([
+ 'foo.js',
+ ])
+ const refs = new Map(Object.entries({
+ 'a/foo.js': {
+ content: '"use strict"\nmodule.exports = "foo"\n',
+ mode: '100644',
+ },
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '2.0.0',
+ }
+
+ t.matchSnapshot(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ }),
+ 'should output expected removed file diff result'
+ )
+ t.end()
+})
+
+t.test('changed file mode', t => {
+ const files = new Set([
+ 'foo.js',
+ ])
+ const refs = new Map(Object.entries({
+ 'a/foo.js': {
+ content: '"use strict"\nmodule.exports = "foo"\n',
+ mode: '100644',
+ },
+ 'b/foo.js': {
+ content: '"use strict"\nmodule.exports = "foo"\n',
+ mode: '100755',
+ },
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '2.0.0',
+ }
+
+ t.matchSnapshot(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ }),
+ 'should output expected changed file mode diff result'
+ )
+ t.end()
+})
+
+t.test('added file', t => {
+ const files = new Set([
+ 'foo.js',
+ ])
+ const refs = new Map(Object.entries({
+ 'b/foo.js': {
+ content: '"use strict"\nmodule.exports = "foo"\n',
+ mode: '100755',
+ },
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '2.0.0',
+ }
+
+ t.matchSnapshot(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ }),
+ 'should output expected added file diff result'
+ )
+ t.end()
+})
+
+t.test('binary file', t => {
+ const files = new Set([
+ 'foo.jpg',
+ ])
+ const refs = new Map(Object.entries({
+ 'a/foo.jpg': {
+ content: Buffer.from(''),
+ mode: '100644',
+ },
+ 'b/foo.jpg': {
+ content: Buffer.from(''),
+ mode: '100644',
+ },
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '2.0.0',
+ }
+
+ t.matchSnapshot(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ }),
+ 'should output expected bin file diff result'
+ )
+ t.end()
+})
+
+t.test('nothing to compare', t => {
+ const files = new Set([
+ 'foo.jpg',
+ ])
+ const refs = new Map(Object.entries({
+ 'a/foo.jpg': {},
+ 'b/foo.jpg': {},
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '2.0.0',
+ }
+
+ t.equal(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ }),
+ '',
+ 'should have no output'
+ )
+ t.end()
+})
+
+t.test('colored output', t => {
+ const files = new Set([
+ 'foo.js',
+ ])
+ const refs = new Map(Object.entries({
+ 'a/foo.js': {
+ content: '"use strict"\nmodule.exports = "foo"\n',
+ mode: '100644',
+ },
+ 'b/foo.js': {
+ content: '"use strict"\nmodule.exports = "foobar"\n',
+ mode: '100644',
+ },
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '2.0.0',
+ }
+
+ t.matchSnapshot(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ opts: {
+ color: true,
+ },
+ }),
+ 'should output expected colored diff result'
+ )
+ t.end()
+})
+
+t.test('using --name-only option', t => {
+ const files = new Set([
+ 'foo.js',
+ 'lib/bar.js',
+ 'lib/utils.js',
+ ])
+ const refs = new Map(Object.entries({
+ 'a/foo.js': {
+ content: '"use strict"\nmodule.exports = "foo"\n',
+ mode: '100644',
+ },
+ 'b/foo.js': {
+ content: '"use strict"\nmodule.exports = "foobar"\n',
+ mode: '100644',
+ },
+ 'a/lib/bar.js': {
+ content: '"use strict"\nmodule.exports = "bar"\n',
+ mode: '100644',
+ },
+ 'b/lib/bar.js': {
+ content: '"use strict"\nmodule.exports = "bar"\n',
+ mode: '100644',
+ },
+ 'a/lib/utils.js': {
+ content: '"use strict"\nconst bar = require("./bar.js")\n'
+ + 'module.exports = () => bar\n',
+ mode: '100644',
+ },
+ 'b/lib/utils.js': {
+ content: '"use strict"\nconst bar = require("./bar.js")\n'
+ + 'module.exports =\n () => bar + "util"\n',
+ mode: '100644',
+ },
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '2.0.0',
+ }
+
+ t.matchSnapshot(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ opts: {
+ diffNameOnly: true,
+ },
+ }),
+ 'should output expected diff result'
+ )
+ t.end()
+})
+
+t.test('respect --tag-version-prefix option', t => {
+ const files = new Set([
+ 'foo.js',
+ ])
+ const refs = new Map(Object.entries({
+ 'a/foo.js': {
+ content: '"use strict"\nmodule.exports = "foo"\n',
+ mode: '100644',
+ },
+ 'b/foo.js': {
+ content: '"use strict"\nmodule.exports = "foobar"\n',
+ mode: '100644',
+ },
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '2.0.0',
+ }
+
+ t.matchSnapshot(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ opts: {
+ tagVersionPrefix: 'b',
+ },
+ }),
+ 'should output expected diff result'
+ )
+ t.end()
+})
+
+t.test('diff options', t => {
+ const files = new Set([
+ 'foo.js',
+ ])
+ const refs = new Map(Object.entries({
+ 'a/foo.js': {
+ content: '"use strict"\nconst a = "a"\nconst b = "b"\n'
+ + 'const c = "c"\nmodule.exports = () => a+\nb+\nc\n',
+ mode: '100644',
+ },
+ 'b/foo.js': {
+ content: '"use strict"\nconst a = "a"\n const b = "b"\n'
+ + ' const c = "c"\n const d = "d"\n'
+ + 'module.exports = () => a+\nb+\nc+\nd\n',
+ mode: '100644',
+ },
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '2.0.0',
+ }
+
+ t.matchSnapshot(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ opts: {
+ diffUnified: 1,
+ diffIgnoreAllSpace: true,
+ diffSrcPrefix: 'before/',
+ diffDstPrefix: 'after/',
+ },
+ }),
+ 'should output expected diff result'
+ )
+ t.end()
+})
+
+t.test('diffUnified=0', t => {
+ const files = new Set([
+ 'foo.js',
+ ])
+ const refs = new Map(Object.entries({
+ 'a/foo.js': {
+ content: '"use strict"\nconst a = "a"\nconst b = "b"\n'
+ + 'const c = "c"\nmodule.exports = () => a+\nb+\nc\n',
+ mode: '100644',
+ },
+ 'b/foo.js': {
+ content: '"use strict"\nconst a = "a"\n const b = "b"\n'
+ + ' const c = "c"\n const d = "d"\n'
+ + 'module.exports = () => a+\nb+\nc+\nd\n',
+ mode: '100644',
+ },
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '2.0.0',
+ }
+
+ t.matchSnapshot(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ opts: {
+ diffUnified: 0,
+ },
+ }),
+ 'should output no context lines in output'
+ )
+ t.end()
+})
+
+t.test('noPrefix', t => {
+ const files = new Set([
+ 'foo.js',
+ ])
+ const refs = new Map(Object.entries({
+ 'a/foo.js': {
+ content: '"use strict"\nmodule.exports = "foo"\n',
+ mode: '100644',
+ },
+ 'b/foo.js': {
+ content: '"use strict"\nmodule.exports = "foobar"\n',
+ mode: '100644',
+ },
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '2.0.0',
+ }
+
+ t.matchSnapshot(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ opts: {
+ diffNoPrefix: true,
+ },
+ }),
+ 'should output result with no prefixes'
+ )
+ t.end()
+})
+
+t.test('format multiple files patch', t => {
+ const files = new Set([
+ 'foo.js',
+ 'lib/bar.js',
+ 'lib/utils.js',
+ ])
+ const refs = new Map(Object.entries({
+ 'a/foo.js': {
+ content: '"use strict"\nmodule.exports = "foo"\n',
+ mode: '100644',
+ },
+ 'b/foo.js': {
+ content: '"use strict"\nmodule.exports = "foobar"\n',
+ mode: '100644',
+ },
+ 'a/lib/bar.js': {
+ content: '"use strict"\nmodule.exports = "bar"\n',
+ mode: '100644',
+ },
+ 'b/lib/bar.js': {
+ content: '"use strict"\nmodule.exports = "bar"\n',
+ mode: '100644',
+ },
+ 'a/lib/utils.js': {
+ content: '"use strict"\nconst bar = require("./bar.js")\n'
+ + 'module.exports = () => bar\n',
+ mode: '100644',
+ },
+ 'b/lib/utils.js': {
+ content: '"use strict"\nconst bar = require("./bar.js")\n'
+ + 'module.exports =\n () => bar + "util"\n',
+ mode: '100644',
+ },
+ }))
+ const versions = {
+ a: '1.0.0',
+ b: '1.1.1',
+ }
+
+ t.matchSnapshot(
+ formatDiff({
+ files,
+ refs,
+ versions,
+ }),
+ 'should output expected result for multiple files'
+ )
+ t.end()
+})
diff --git a/workspaces/libnpmdiff/test/index.js b/workspaces/libnpmdiff/test/index.js
new file mode 100644
index 000000000..80b3daaa6
--- /dev/null
+++ b/workspaces/libnpmdiff/test/index.js
@@ -0,0 +1,147 @@
+const { resolve } = require('path')
+
+const t = require('tap')
+
+const diff = require('../index.js')
+
+const normalizePath = p => p
+ .replace(/\\+/g, '/')
+ .replace(/\r\n/g, '\n')
+
+t.cleanSnapshot = (str) => normalizePath(str)
+ .replace(normalizePath(process.execPath), 'node')
+
+const json = (obj) => `${JSON.stringify(obj, null, 2)}\n`
+
+t.test('compare two diff specs', async t => {
+ const path = t.testdir({
+ a1: {
+ 'package.json': json({
+ name: 'a',
+ version: '1.0.0',
+ }),
+ 'index.js': 'module.exports =\n "a1"\n',
+ },
+ a2: {
+ 'package.json': json({
+ name: 'a',
+ version: '2.0.0',
+ }),
+ 'index.js': 'module.exports =\n "a2"\n',
+ },
+ })
+
+ const a = `file:${resolve(path, 'a1')}`
+ const b = `file:${resolve(path, 'a2')}`
+
+ t.resolveMatchSnapshot(diff([a, b], {}), 'should output expected diff')
+})
+
+t.test('using single arg', async t => {
+ await t.rejects(
+ diff(['abbrev@1.0.3']),
+ /libnpmdiff needs two arguments to compare/,
+ 'should throw EDIFFARGS error'
+ )
+})
+
+t.test('too many args', async t => {
+ const args = ['abbrev@1.0.3', 'abbrev@1.0.4', 'abbrev@1.0.5']
+ await t.rejects(
+ diff(args),
+ /libnpmdiff needs two arguments to compare/,
+ 'should output diff against cwd files'
+ )
+})
+
+t.test('folder in node_modules', async t => {
+ const path = t.testdir({
+ node_modules: {
+ a: {
+ 'package.json': json({
+ name: 'a',
+ version: '1.0.0',
+ scripts: {
+ prepare: `${process.execPath} prepare.js`,
+ },
+ }),
+ 'prepare.js': 'throw new Error("ERR")',
+ node_modules: {
+ b: {
+ 'package.json': json({
+ name: 'b',
+ version: '2.0.0',
+ scripts: {
+ prepare: `${process.execPath} prepare.js`,
+ },
+ }),
+ 'prepare.js': 'throw new Error("ERR")',
+ },
+ },
+ },
+ },
+ packages: {
+ a: {
+ 'package.json': json({
+ name: 'a',
+ version: '1.0.1',
+ scripts: {
+ prepare: `${process.execPath} prepare.js`,
+ },
+ }),
+ 'prepare.js': '',
+ },
+ b: {
+ 'package.json': json({
+ name: 'b',
+ version: '2.0.1',
+ scripts: {
+ prepare: `${process.execPath} prepare.js`,
+ },
+ }),
+ 'prepare.js': '',
+ },
+ },
+ 'package.json': json({
+ name: 'my-project',
+ version: '1.0.0',
+ }),
+ })
+
+ t.test('top-level, absolute path', async t => {
+ t.resolveMatchSnapshot(diff([
+ `file:${resolve(path, 'node_modules/a')}`,
+ `file:${resolve(path, 'packages/a')}`,
+ ], { where: path }), 'should output expected diff')
+ })
+ t.test('top-level, relative path', async t => {
+ const _cwd = process.cwd()
+ process.chdir(path)
+ t.teardown(() => {
+ process.chdir(_cwd)
+ })
+
+ t.resolveMatchSnapshot(diff([
+ 'file:./node_modules/a',
+ 'file:./packages/a',
+ ], { where: path }), 'should output expected diff')
+ })
+ t.test('nested, absolute path', async t => {
+ t.resolveMatchSnapshot(diff([
+ `file:${resolve(path, 'node_modules/a/node_modules/b')}`,
+ `file:${resolve(path, 'packages/b')}`,
+ ], { where: path }), 'should output expected diff')
+ })
+ t.test('nested, relative path', async t => {
+ const _cwd = process.cwd()
+ process.chdir(path)
+ t.teardown(() => {
+ process.chdir(_cwd)
+ })
+
+ t.resolveMatchSnapshot(diff([
+ 'file:./node_modules/a/node_modules/b',
+ 'file:./packages/b',
+ ], { where: path }), 'should output expected diff')
+ })
+})
diff --git a/workspaces/libnpmdiff/test/should-print-patch.js b/workspaces/libnpmdiff/test/should-print-patch.js
new file mode 100644
index 000000000..97b15787d
--- /dev/null
+++ b/workspaces/libnpmdiff/test/should-print-patch.js
@@ -0,0 +1,28 @@
+const t = require('tap')
+const shouldPrintPatch = require('../lib/should-print-patch.js')
+
+t.test('valid filenames', t => {
+ t.ok(shouldPrintPatch('LICENSE'))
+ t.ok(shouldPrintPatch('.gitignore'))
+ t.ok(shouldPrintPatch('foo.md'))
+ t.ok(shouldPrintPatch('./bar.txt'))
+ t.ok(shouldPrintPatch('/a/b/c/bar.html'))
+ t.end()
+})
+
+t.test('invalid filenames', t => {
+ t.notOk(shouldPrintPatch('foo.exe'))
+ t.notOk(shouldPrintPatch('./foo.jpg'))
+ t.notOk(shouldPrintPatch('/a/b/c/bar.bin'))
+ t.end()
+})
+
+t.test('using --text/-a option', t => {
+ const opts = {
+ diffText: true,
+ }
+ t.ok(shouldPrintPatch('foo.exe', opts))
+ t.ok(shouldPrintPatch('./foo.jpg', opts))
+ t.ok(shouldPrintPatch('/a/b/c/bar.bin', opts))
+ t.end()
+})
diff --git a/workspaces/libnpmdiff/test/tarball.js b/workspaces/libnpmdiff/test/tarball.js
new file mode 100644
index 000000000..3a959be6e
--- /dev/null
+++ b/workspaces/libnpmdiff/test/tarball.js
@@ -0,0 +1,96 @@
+const { resolve } = require('path')
+
+const t = require('tap')
+const tar = require('tar')
+const pacote = require('pacote')
+pacote.tarball = () => {
+ throw new Error('Failed to detect node_modules tarball')
+}
+
+const tarball = require('../lib/tarball.js')
+
+const json = (obj) => `${JSON.stringify(obj, null, 2)}\n`
+
+t.test('returns a tarball from node_modules', t => {
+ t.plan(2)
+
+ const path = t.testdir({
+ node_modules: {
+ a: {
+ 'package.json': json({
+ name: 'a',
+ version: '1.0.0',
+ bin: { a: 'index.js' },
+ }),
+ 'index.js': '',
+ },
+ },
+ })
+
+ const _cwd = process.cwd()
+ process.chdir(path)
+ t.teardown(() => {
+ process.chdir(_cwd)
+ })
+
+ tarball({ bin: { a: 'index.js' }, _resolved: resolve(path, 'node_modules/a') }, { where: path })
+ .then(res => {
+ tar.list({
+ filter: path => {
+ t.match(
+ path,
+ /package.json|index.js/,
+ 'should return tarball with expected files'
+ )
+ },
+ })
+ .on('error', e => {
+ throw e
+ })
+ .end(res)
+ })
+})
+
+t.test('node_modules folder within a linked dir', async t => {
+ const path = t.testdir({
+ node_modules: {
+ a: t.fixture('symlink', '../packages/a'),
+ },
+ packages: {
+ a: {
+ node_modules: {
+ b: {
+ 'package.json': json({
+ name: 'a',
+ version: '1.0.0',
+ }),
+ },
+ },
+ },
+ },
+ })
+
+ const link = await tarball({ _resolved: resolve(path, 'node_modules/a/node_modules/b') }, {})
+ t.ok(link, 'should retrieve tarball from reading link')
+
+ const target = await tarball({ _resolved: resolve(path, 'packages/a/node_modules/b') }, {})
+ t.ok(target, 'should retrieve tarball from reading target')
+})
+
+t.test('pkg not in a node_modules folder', async t => {
+ const path = t.testdir({
+ packages: {
+ a: {
+ 'package.json': json({
+ name: 'a',
+ version: '1.0.0',
+ }),
+ },
+ },
+ })
+
+ t.throws(
+ () => tarball({ _resolved: resolve(path, 'packages/a') }, {}),
+ 'should call regular pacote.tarball method instead'
+ )
+})
diff --git a/workspaces/libnpmdiff/test/untar.js b/workspaces/libnpmdiff/test/untar.js
new file mode 100644
index 000000000..62be1c6ba
--- /dev/null
+++ b/workspaces/libnpmdiff/test/untar.js
@@ -0,0 +1,231 @@
+const { resolve } = require('path')
+const t = require('tap')
+const pacote = require('pacote')
+const untar = require('../lib/untar.js')
+
+t.test('untar simple package', async t => {
+ const item =
+ await pacote.tarball(resolve('./test/fixtures/simple-output-2.2.1.tgz'))
+
+ const {
+ files,
+ refs,
+ } = await untar({
+ item,
+ prefix: 'a/',
+ })
+
+ t.matchSnapshot([...files].join('\n'), 'should return list of filenames')
+ t.matchSnapshot(
+ [...refs.entries()].map(([k, v]) => `${k}: ${!!v}`).join('\n'),
+ 'should return map of filenames to its contents'
+ )
+ t.matchSnapshot(refs.get('a/LICENSE').content, 'should have read contents')
+})
+
+t.test('untar package with folders', async t => {
+ const item =
+ await pacote.tarball(resolve('./test/fixtures/archive.tgz'))
+
+ const {
+ files,
+ refs,
+ } = await untar({
+ item,
+ prefix: 'a/',
+ })
+
+ t.matchSnapshot([...files].join('\n'), 'should return list of filenames')
+ t.matchSnapshot(
+ [...refs.entries()].map(([k, v]) => `${k}: ${!!v}`).join('\n'),
+ 'should return map of filenames to its contents'
+ )
+ t.matchSnapshot(
+ refs.get('a/lib/utils/b.js').content,
+ 'should have read contents'
+ )
+})
+
+t.test('filter files', async t => {
+ const item =
+ await pacote.tarball(resolve('./test/fixtures/simple-output-2.2.1.tgz'))
+
+ const {
+ files,
+ refs,
+ } = await untar({
+ item,
+ prefix: 'a/',
+ }, {
+ diffFiles: [
+ './LICENSE',
+ 'missing-file',
+ 'README.md',
+ ],
+ })
+
+ t.matchSnapshot([...files].join('\n'), 'should return list of filenames')
+ t.matchSnapshot(
+ [...refs.entries()].map(([k, v]) => `${k}: ${!!v.content}`).join('\n'),
+ 'should return map of filenames with valid contents'
+ )
+})
+
+t.test('filter files using glob expressions', async t => {
+ const item =
+ await pacote.tarball(resolve('./test/fixtures/archive.tgz'))
+ const cwd = t.testdir({
+ lib: {
+ 'index.js': '',
+ utils: {
+ '/b.js': '',
+ },
+ },
+ 'package-lock.json': '',
+ 'package.json': '',
+ test: {
+ '/index.js': '',
+ utils: {
+ 'b.js': '',
+ },
+ },
+ })
+
+ const _cwd = process.cwd()
+ process.chdir(cwd)
+ t.teardown(() => {
+ process.chdir(_cwd)
+ })
+
+ const {
+ files,
+ refs,
+ } = await untar({
+ item,
+ prefix: 'a/',
+ }, {
+ diffFiles: [
+ './lib/**',
+ '*-lock.json',
+ 'test\\*', // windows-style sep should be normalized
+ ],
+ })
+
+ t.matchSnapshot([...files].join('\n'), 'should return list of filenames')
+ t.matchSnapshot(
+ [...refs.entries()].map(([k, v]) => `${k}: ${!!v.content}`).join('\n'),
+ 'should return map of filenames with valid contents'
+ )
+})
+
+t.test('match files by end of filename', async t => {
+ const item =
+ await pacote.tarball(resolve('./test/fixtures/archive.tgz'))
+
+ const {
+ files,
+ refs,
+ } = await untar({
+ item,
+ prefix: 'a/',
+ }, {
+ diffFiles: [
+ '*.js',
+ ],
+ })
+
+ t.matchSnapshot([...files].join('\n'), 'should return list of filenames')
+ t.matchSnapshot(
+ [...refs.entries()].map(([k, v]) => `${k}: ${!!v.content}`).join('\n'),
+ 'should return map of filenames with valid contents'
+ )
+})
+
+t.test('filter files by exact filename', async t => {
+ const item =
+ await pacote.tarball(resolve('./test/fixtures/archive.tgz'))
+
+ const {
+ files,
+ refs,
+ } = await untar({
+ item,
+ prefix: 'a/',
+ }, {
+ diffFiles: [
+ 'index.js',
+ ],
+ })
+
+ t.matchSnapshot([...files].join('\n'), 'should return no filenames')
+ t.matchSnapshot(
+ [...refs.entries()].map(([k, v]) => `${k}: ${!!v.content}`).join('\n'),
+ 'should return no filenames'
+ )
+})
+
+t.test('match files by simple folder name', async t => {
+ const item =
+ await pacote.tarball(resolve('./test/fixtures/archive.tgz'))
+
+ const {
+ files,
+ refs,
+ } = await untar({
+ item,
+ prefix: 'a/',
+ }, {
+ diffFiles: [
+ 'lib',
+ ],
+ })
+
+ t.matchSnapshot([...files].join('\n'), 'should return list of filenames')
+ t.matchSnapshot(
+ [...refs.entries()].map(([k, v]) => `${k}: ${!!v.content}`).join('\n'),
+ 'should return map of filenames with valid contents'
+ )
+})
+
+t.test('match files by simple folder name variation', async t => {
+ const item =
+ await pacote.tarball(resolve('./test/fixtures/archive.tgz'))
+
+ const {
+ files,
+ refs,
+ } = await untar({
+ item,
+ prefix: 'a/',
+ }, {
+ diffFiles: [
+ './test/',
+ ],
+ })
+
+ t.matchSnapshot([...files].join('\n'), 'should return list of filenames')
+ t.matchSnapshot(
+ [...refs.entries()].map(([k, v]) => `${k}: ${!!v.content}`).join('\n'),
+ 'should return map of filenames with valid contents'
+ )
+})
+
+t.test('filter out all files', async t => {
+ const item =
+ await pacote.tarball(resolve('./test/fixtures/simple-output-2.2.1.tgz'))
+
+ const {
+ files,
+ refs,
+ } = await untar({
+ item,
+ prefix: 'a/',
+ }, {
+ diffFiles: [
+ 'non-matching-pattern',
+ ],
+ })
+
+ t.equal(files.size, 0, 'should have no files')
+ t.equal(refs.size, 0, 'should have no refs')
+})