From 2ce23baf53b1ce7d11b8efb80c598ddaf9cef9e7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kat=20March=C3=A1n?= <kzm@zkat.tech>
Date: Mon, 18 Feb 2019 14:58:47 -0800
Subject: [PATCH 1/2] lock-verify@2.1.0

Adds support for package aliases
---
 node_modules/lock-verify/index.js     | 14 ++++++++---
 node_modules/lock-verify/package.json | 35 +++++++++++++--------------
 package-lock.json                     |  8 +++---
 package.json                          |  2 +-
 4 files changed, 33 insertions(+), 26 deletions(-)

diff --git a/node_modules/lock-verify/index.js b/node_modules/lock-verify/index.js
index 22721329134d7..cf673888faf01 100644
--- a/node_modules/lock-verify/index.js
+++ b/node_modules/lock-verify/index.js
@@ -36,9 +36,17 @@ function lockVerify(check) {
         if (spec.registry) {
           // Can't match tags to package-lock w/o network
           if (spec.type === 'tag') return
-          if (!semver.satisfies(lock.version, spec.fetchSpec)) {
-            errors.push("Invalid: lock file's " + name + '@' + lock.version + ' does not satisfy ' + name + '@' + spec.fetchSpec)
-            return
+          if (spec.type === 'alias') {
+            const lockSpec = npa.resolve(name, lock.version)
+            if (!semver.satisfies(lockSpec.subSpec.fetchSpec, spec.subSpec.fetchSpec)) {
+              errors.push("Invalid: lock file's " + name + '@' + lock.version + ' does not satisfy ' + name + '@' + spec.rawSpec)
+              return
+            }
+          } else {
+            if (!semver.satisfies(lock.version, spec.fetchSpec)) {
+              errors.push("Invalid: lock file's " + name + '@' + lock.version + ' does not satisfy ' + name + '@' + spec.fetchSpec)
+              return
+            }
           }
         } else if (spec.type === 'git') {
           // can't verify git w/o network
diff --git a/node_modules/lock-verify/package.json b/node_modules/lock-verify/package.json
index 0f2002f549e5a..621c12fb76e87 100644
--- a/node_modules/lock-verify/package.json
+++ b/node_modules/lock-verify/package.json
@@ -1,33 +1,30 @@
 {
-  "_args": [
-    [
-      "lock-verify@2.0.2",
-      "/Users/rebecca/code/npm"
-    ]
-  ],
-  "_from": "lock-verify@2.0.2",
-  "_id": "lock-verify@2.0.2",
+  "_from": "lock-verify@2.1.0",
+  "_id": "lock-verify@2.1.0",
   "_inBundle": false,
-  "_integrity": "sha512-QNVwK0EGZBS4R3YQ7F1Ox8p41Po9VGl2QG/2GsuvTbkJZYSsPeWHKMbbH6iZMCHWSMww5nrJroZYnGzI4cePuw==",
+  "_integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==",
   "_location": "/lock-verify",
   "_phantomChildren": {},
   "_requested": {
     "type": "version",
     "registry": true,
-    "raw": "lock-verify@2.0.2",
+    "raw": "lock-verify@2.1.0",
     "name": "lock-verify",
     "escapedName": "lock-verify",
-    "rawSpec": "2.0.2",
+    "rawSpec": "2.1.0",
     "saveSpec": null,
-    "fetchSpec": "2.0.2"
+    "fetchSpec": "2.1.0"
   },
   "_requiredBy": [
+    "#USER",
     "/",
-    "/libcipm"
+    "/libcipm",
+    "/libnpm"
   ],
-  "_resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.0.2.tgz",
-  "_spec": "2.0.2",
-  "_where": "/Users/rebecca/code/npm",
+  "_resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.1.0.tgz",
+  "_shasum": "fff4c918b8db9497af0c5fa7f6d71555de3ceb47",
+  "_spec": "lock-verify@2.1.0",
+  "_where": "/Users/zkat/Documents/code/work/npm",
   "author": {
     "name": "Rebecca Turner",
     "email": "me@re-becca.org",
@@ -36,10 +33,12 @@
   "bugs": {
     "url": "https://github.com/iarna/lock-verify/issues"
   },
+  "bundleDependencies": false,
   "dependencies": {
-    "npm-package-arg": "^5.1.2 || 6",
+    "npm-package-arg": "^6.1.0",
     "semver": "^5.4.1"
   },
+  "deprecated": false,
   "description": "Report if your package.json is out of sync with your package-lock.json.",
   "devDependencies": {
     "@iarna/cli": "^1.2.0"
@@ -59,5 +58,5 @@
   "scripts": {
     "test": "echo \"Error: no test specified\" && exit 1"
   },
-  "version": "2.0.2"
+  "version": "2.1.0"
 }
diff --git a/package-lock.json b/package-lock.json
index eefd8067bbce2..11cefb7f720ab 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2812,11 +2812,11 @@
       }
     },
     "lock-verify": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.0.2.tgz",
-      "integrity": "sha512-QNVwK0EGZBS4R3YQ7F1Ox8p41Po9VGl2QG/2GsuvTbkJZYSsPeWHKMbbH6iZMCHWSMww5nrJroZYnGzI4cePuw==",
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/lock-verify/-/lock-verify-2.1.0.tgz",
+      "integrity": "sha512-vcLpxnGvrqisKvLQ2C2v0/u7LVly17ak2YSgoK4PrdsYBXQIax19vhKiLfvKNFx7FRrpTnitrpzF/uuCMuorIg==",
       "requires": {
-        "npm-package-arg": "^5.1.2 || 6",
+        "npm-package-arg": "^6.1.0",
         "semver": "^5.4.1"
       }
     },
diff --git a/package.json b/package.json
index d08661a8e8f0e..6920c329af406 100644
--- a/package.json
+++ b/package.json
@@ -76,7 +76,7 @@
     "libnpm": "^2.0.1",
     "libnpmhook": "^5.0.2",
     "libnpx": "^10.2.0",
-    "lock-verify": "^2.0.2",
+    "lock-verify": "^2.1.0",
     "lockfile": "^1.0.4",
     "lodash._baseuniq": "~4.6.0",
     "lodash.clonedeep": "~4.5.0",

From 9f041a2cae37081e9b5b847ee3a00a60b9397aab Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Kat=20March=C3=A1n?= <kzm@zkat.tech>
Date: Wed, 11 Apr 2018 14:45:12 -0700
Subject: [PATCH 2/2] install: add support for package aliases

---
 lib/install.js                          |  24 ++-
 lib/install/action/extract.js           |   3 +-
 lib/install/action/global-install.js    |   3 +-
 lib/install/action/global-link.js       |   3 +-
 lib/install/and-add-parent-to-errors.js |   3 +-
 lib/install/deps.js                     |  23 +-
 lib/install/is-only-optional.js         |   3 +-
 lib/ls.js                               |  12 +-
 lib/outdated.js                         |  92 ++++----
 lib/shrinkwrap.js                       |   2 +
 lib/utils/module-name.js                |   1 +
 test/tap/404-parent.js                  |   2 +-
 test/tap/aliases.js                     | 268 ++++++++++++++++++++++++
 test/tap/install-actions.js             |   6 +-
 14 files changed, 382 insertions(+), 63 deletions(-)
 create mode 100644 test/tap/aliases.js

diff --git a/lib/install.js b/lib/install.js
index e15bc47919100..34a02962c8a8e 100644
--- a/lib/install.js
+++ b/lib/install.js
@@ -401,7 +401,7 @@ Installer.prototype.normalizeCurrentTree = function (cb) {
   if (this.currentTree.error) {
     for (let child of this.currentTree.children) {
       if (!child.fakeChild && isExtraneous(child)) {
-        this.currentTree.package.dependencies[child.package.name] = computeVersionSpec(this.currentTree, child)
+        this.currentTree.package.dependencies[moduleName(child)] = computeVersionSpec(this.currentTree, child)
       }
     }
   }
@@ -825,7 +825,11 @@ Installer.prototype.printInstalledForHuman = function (diffs, auditResult) {
   var report = ''
   if (this.args.length && (added || updated)) {
     report += this.args.map((p) => {
-      return `+ ${p.name}@${p.version}`
+      return `+ ${p.name}@${p.version}${
+        !p._requested.name || p._requested.name === p.name
+          ? ''
+          : ` (as ${p._requested.name})`
+      }`
     }).join('\n') + '\n'
   }
   var actions = []
@@ -922,10 +926,14 @@ Installer.prototype.printInstalledForJSON = function (diffs, auditResult) {
   function recordAction (action) {
     var mutation = action[0]
     var child = action[1]
+    const isAlias = child.package && child.package._requested && child.package._requested.type === 'alias'
+    const name = isAlias
+      ? child.package._requested.name
+      : child.package && child.package.name
     var result = {
       action: mutation,
-      name: moduleName(child),
-      version: child.package && child.package.version,
+      name,
+      version: child.package && `${isAlias ? `npm:${child.package.name}@` : ''}${child.package.version}`,
       path: child.path
     }
     if (mutation === 'move') {
@@ -947,10 +955,16 @@ Installer.prototype.printInstalledForParseable = function (diffs) {
     } else if (mutation === 'update') {
       var previousVersion = child.oldPkg.package && child.oldPkg.package.version
     }
+    const isAlias = child.package._requested && child.package._requested.type === 'alias'
+    const version = child.package && isAlias
+      ? `npm:${child.package.name}@${child.package.version}`
+      : child.package
+        ? child.package.version
+        : ''
     output(
       mutation + '\t' +
       moduleName(child) + '\t' +
-      (child.package ? child.package.version : '') + '\t' +
+      version + '\t' +
       (child.path ? path.relative(self.where, child.path) : '') + '\t' +
       (previousVersion || '') + '\t' +
       (previousPath || ''))
diff --git a/lib/install/action/extract.js b/lib/install/action/extract.js
index c1c17cdf6c4f3..32a4f4e004ad7 100644
--- a/lib/install/action/extract.js
+++ b/lib/install/action/extract.js
@@ -6,6 +6,7 @@ const figgyPudding = require('figgy-pudding')
 const stat = BB.promisify(require('graceful-fs').stat)
 const gentlyRm = BB.promisify(require('../../utils/gently-rm.js'))
 const mkdirp = BB.promisify(require('mkdirp'))
+const moduleName = require('../../utils/module-name.js')
 const moduleStagingPath = require('../module-staging-path.js')
 const move = require('../../utils/move.js')
 const npa = require('npm-package-arg')
@@ -113,7 +114,7 @@ function readBundled (pkg, staging, extractTo) {
 }
 
 function stageBundledModule (bundler, child, staging, parentPath) {
-  const stageFrom = path.join(parentPath, 'node_modules', child.package.name)
+  const stageFrom = path.join(parentPath, 'node_modules', moduleName(child))
   const stageTo = moduleStagingPath(staging, child)
 
   return BB.map(child.children, (child) => {
diff --git a/lib/install/action/global-install.js b/lib/install/action/global-install.js
index bdc121b693c57..44d2f628f2c73 100644
--- a/lib/install/action/global-install.js
+++ b/lib/install/action/global-install.js
@@ -3,12 +3,13 @@ var path = require('path')
 var npm = require('../../npm.js')
 var Installer = require('../../install.js').Installer
 var packageId = require('../../utils/package-id.js')
+var moduleName = require('../../utils/module-name.js')
 
 module.exports = function (staging, pkg, log, next) {
   log.silly('global-install', packageId(pkg))
   var globalRoot = path.resolve(npm.globalDir, '..')
   npm.config.set('global', true)
-  var install = new Installer(globalRoot, false, [pkg.package.name + '@' + pkg.package._requested.fetchSpec])
+  var install = new Installer(globalRoot, false, [moduleName(pkg) + '@' + pkg.package._requested.rawSpec])
   install.link = false
   install.run(function () {
     npm.config.set('global', false)
diff --git a/lib/install/action/global-link.js b/lib/install/action/global-link.js
index f109e5b88a19f..c9d9a8feb2af7 100644
--- a/lib/install/action/global-link.js
+++ b/lib/install/action/global-link.js
@@ -1,8 +1,9 @@
 'use strict'
+var moduleName = require('../../utils/module-name.js')
 var npm = require('../../npm.js')
 var packageId = require('../../utils/package-id.js')
 
 module.exports = function (staging, pkg, log, next) {
   log.silly('global-link', packageId(pkg))
-  npm.link(pkg.package.name, next)
+  npm.link(moduleName(pkg), next)
 }
diff --git a/lib/install/and-add-parent-to-errors.js b/lib/install/and-add-parent-to-errors.js
index 62a86bd4a6c36..fe4128230b1af 100644
--- a/lib/install/and-add-parent-to-errors.js
+++ b/lib/install/and-add-parent-to-errors.js
@@ -1,4 +1,5 @@
 'use strict'
+var moduleName = require('../utils/module-name.js')
 var validate = require('aproba')
 
 module.exports = function (parent, cb) {
@@ -6,7 +7,7 @@ module.exports = function (parent, cb) {
   return function (er) {
     if (!er) return cb.apply(null, arguments)
     if (er instanceof Error && parent && parent.package && parent.package.name) {
-      er.parent = parent.package.name
+      er.parent = moduleName(parent)
     }
     cb(er)
   }
diff --git a/lib/install/deps.js b/lib/install/deps.js
index 3236faf6c413e..3fe370140adc3 100644
--- a/lib/install/deps.js
+++ b/lib/install/deps.js
@@ -66,6 +66,10 @@ function doesChildVersionMatch (child, requested, requestor) {
     }
   }
 
+  if (requested.type === 'alias') {
+    return doesChildVersionMatch(child, requested.subSpec, requestor)
+  }
+
   if (!registryTypes[requested.type]) {
     var childReq = child.package._requested
     if (childReq) {
@@ -81,7 +85,7 @@ function doesChildVersionMatch (child, requested, requestor) {
     // You'll see this scenario happen with at least tags and git dependencies.
     // Some buggy clients will write spaces into the module name part of a _from.
     if (child.package._from) {
-      var fromReq = npa.resolve(moduleName(child), child.package._from.replace(new RegExp('^\\s*' + moduleName(child) + '\\s*@'), ''))
+      var fromReq = npa(child.package._from)
       if (fromReq.rawSpec === requested.rawSpec) return true
       if (fromReq.type === requested.type && fromReq.saveSpec && fromReq.saveSpec === requested.saveSpec) return true
     }
@@ -298,11 +302,13 @@ function computeVersionSpec (tree, child) {
   var requested
   var childReq = child.package._requested
   if (child.isLink) {
-    requested = npa.resolve(child.package.name, 'file:' + child.realpath, getTop(tree).path)
+    requested = npa.resolve(moduleName(child), 'file:' + child.realpath, getTop(tree).path)
   } else if (childReq && (isNotEmpty(childReq.saveSpec) || (isNotEmpty(childReq.rawSpec) && isNotEmpty(childReq.fetchSpec)))) {
     requested = child.package._requested
   } else if (child.package._from) {
     requested = npa(child.package._from, tree.path)
+  } else if (child.name && child.name !== child.package.name) {
+    requested = npa.resolve(child.name, `npm:${child.package.name}@${child.package.version})`)
   } else {
     requested = npa.resolve(child.package.name, child.package.version)
   }
@@ -314,6 +320,9 @@ function computeVersionSpec (tree, child) {
         !npm.config.get('save-exact')) {
       rangeDescriptor = npm.config.get('save-prefix')
     }
+    if (requested.type === 'alias') {
+      rangeDescriptor = `npm:${requested.subSpec.name}@${rangeDescriptor}`
+    }
     return rangeDescriptor + version
   } else if (requested.type === 'directory' || requested.type === 'file') {
     return 'file:' + unixFormatPath(path.relative(getTop(tree).path, requested.fetchSpec))
@@ -333,7 +342,7 @@ exports.removeDeps = function (args, tree, saveToDependencies, next) {
   for (let pkg of args) {
     var pkgName = moduleName(pkg)
     var toRemove = tree.children.filter(moduleNameMatches(pkgName))
-    var pkgToRemove = toRemove[0] || createChild({package: {name: pkgName}})
+    var pkgToRemove = toRemove[0] || createChild({name: pkgName})
     var saveType = getSaveType(tree, pkg) || 'dependencies'
     if (tree.isTop && saveToDependencies) {
       pkgToRemove.save = saveType
@@ -661,11 +670,13 @@ function resolveWithNewModule (pkg, tree, log, next) {
     addBundled(pkg, (bundleErr) => {
       var parent = earliestInstallable(tree, tree, pkg, log) || tree
       var isLink = pkg._requested.type === 'directory'
+      var name = pkg._requested.name || pkg.name
       var child = createChild({
+        name,
         package: pkg,
         parent: parent,
-        path: path.join(parent.isLink ? parent.realpath : parent.path, 'node_modules', pkg.name),
-        realpath: isLink ? pkg._requested.fetchSpec : path.join(parent.realpath, 'node_modules', pkg.name),
+        path: path.join(parent.isLink ? parent.realpath : parent.path, 'node_modules', name),
+        realpath: isLink ? pkg._requested.fetchSpec : path.join(parent.realpath, 'node_modules', name),
         children: pkg._bundled || [],
         isLink: isLink,
         isInLink: parent.isLink,
@@ -768,7 +779,7 @@ var earliestInstallable = exports.earliestInstallable = function (requiredBy, tr
   validate('OOOO', arguments)
 
   function undeletedModuleMatches (child) {
-    return !child.removed && moduleName(child) === pkg.name
+    return !child.removed && moduleName(child) === ((pkg._requested && pkg._requested.name) || pkg.name)
   }
   const undeletedMatches = tree.children.filter(undeletedModuleMatches)
   if (undeletedMatches.length) {
diff --git a/lib/install/is-only-optional.js b/lib/install/is-only-optional.js
index f1b731578d942..81e227bae7a89 100644
--- a/lib/install/is-only-optional.js
+++ b/lib/install/is-only-optional.js
@@ -2,6 +2,7 @@
 module.exports = isOptional
 
 const isOptDep = require('./is-opt-dep.js')
+const moduleName = require('../utils/module-name.js')
 
 function isOptional (node, seen) {
   if (!seen) seen = new Set()
@@ -15,6 +16,6 @@ function isOptional (node, seen) {
   const swOptional = node.fromShrinkwrap && node.package._optional
   return node.requiredBy.every(function (req) {
     if (req.fakeChild && swOptional) return true
-    return isOptDep(req, node.package.name) || isOptional(req, seen)
+    return isOptDep(req, moduleName(node)) || isOptional(req, seen)
   })
 }
diff --git a/lib/ls.js b/lib/ls.js
index 8653fc718ae4a..78a2b1d791c7d 100644
--- a/lib/ls.js
+++ b/lib/ls.js
@@ -12,6 +12,7 @@ var readPackageTree = require('read-package-tree')
 var archy = require('archy')
 var semver = require('semver')
 var color = require('ansicolors')
+var moduleName = require('./utils/module-name.js')
 var npa = require('npm-package-arg')
 var sortedObject = require('sorted-object')
 var npm = require('./npm.js')
@@ -59,7 +60,9 @@ var lsFromTree = ls.fromTree = function (dir, physicalTree, args, silent, cb) {
     args = []
   } else {
     args = args.map(function (a) {
-      if (typeof a === 'object') {
+      if (typeof a === 'object' && a.package._requested.type === 'alias') {
+        return [moduleName(a), `npm:${a.package.name}@${a.package.version}`, a]
+      } else if (typeof a === 'object') {
         return [a.package.name, a.package.version, a]
       } else {
         var p = npa(a)
@@ -392,8 +395,11 @@ function makeArchy_ (data, long, dir, depth, parent, d) {
   }
 
   var out = {}
-  // the top level is a bit special.
-  out.label = data._id || ''
+  if (data._requested && data._requested.type === 'alias') {
+    out.label = `${d}@npm:${data._id}`
+  } else {
+    out.label = data._id || ''
+  }
   if (data._found === 'explicit' && data._id) {
     if (npm.color) {
       out.label = color.bgBlack(color.yellow(out.label.trim())) + ' '
diff --git a/lib/outdated.js b/lib/outdated.js
index baeb5e7179df4..197b71962500e 100644
--- a/lib/outdated.js
+++ b/lib/outdated.js
@@ -20,30 +20,30 @@ outdated.usage = 'npm outdated [[<@scope>/]<pkg> ...]'
 
 outdated.completion = require('./utils/completion/installed-deep.js')
 
-var os = require('os')
-var url = require('url')
-var path = require('path')
-var readPackageTree = require('read-package-tree')
-var asyncMap = require('slide').asyncMap
-var color = require('ansicolors')
-var styles = require('ansistyles')
-var table = require('text-table')
-var semver = require('semver')
-var npa = require('libnpm/parse-arg')
-var pickManifest = require('npm-pick-manifest')
-var fetchPackageMetadata = require('./fetch-package-metadata.js')
-var mutateIntoLogicalTree = require('./install/mutate-into-logical-tree.js')
-var npm = require('./npm.js')
+const os = require('os')
+const url = require('url')
+const path = require('path')
+const readPackageTree = require('read-package-tree')
+const asyncMap = require('slide').asyncMap
+const color = require('ansicolors')
+const styles = require('ansistyles')
+const table = require('text-table')
+const semver = require('semver')
+const npa = require('libnpm/parse-arg')
+const pickManifest = require('npm-pick-manifest')
+const fetchPackageMetadata = require('./fetch-package-metadata.js')
+const mutateIntoLogicalTree = require('./install/mutate-into-logical-tree.js')
+const npm = require('./npm.js')
 const npmConfig = require('./config/figgy-config.js')
 const figgyPudding = require('figgy-pudding')
 const packument = require('libnpm/packument')
-var long = npm.config.get('long')
-var isExtraneous = require('./install/is-extraneous.js')
-var computeMetadata = require('./install/deps.js').computeMetadata
-var computeVersionSpec = require('./install/deps.js').computeVersionSpec
-var moduleName = require('./utils/module-name.js')
-var output = require('./utils/output.js')
-var ansiTrim = require('./utils/ansi-trim')
+const long = npm.config.get('long')
+const isExtraneous = require('./install/is-extraneous.js')
+const computeMetadata = require('./install/deps.js').computeMetadata
+const computeVersionSpec = require('./install/deps.js').computeVersionSpec
+const moduleName = require('./utils/module-name.js')
+const output = require('./utils/output.js')
+const ansiTrim = require('./utils/ansi-trim')
 
 const OutdatedConfig = figgyPudding({
   also: {},
@@ -215,8 +215,8 @@ function makeJSON (list, opts) {
 
 function outdated_ (args, path, tree, parentHas, depth, opts, cb) {
   if (!tree.package) tree.package = {}
-  if (path && tree.package.name) path += ' > ' + tree.package.name
-  if (!path && tree.package.name) path = tree.package.name
+  if (path && moduleName(tree)) path += ' > ' + tree.package.name
+  if (!path && moduleName(tree)) path = tree.package.name
   if (depth > opts.depth) {
     return cb(null, [])
   }
@@ -298,10 +298,10 @@ function outdated_ (args, path, tree, parentHas, depth, opts, cb) {
 
   var has = Object.create(parentHas)
   tree.children.forEach(function (child) {
-    if (child.package.name && child.package.private) {
+    if (moduleName(child) && child.package.private) {
       deps = deps.filter(function (dep) { return dep !== child })
     }
-    has[child.package.name] = {
+    has[moduleName(child)] = {
       version: child.isLink ? 'linked' : child.package.version,
       from: child.isLink ? 'file:' + child.path : child.package._from
     }
@@ -349,13 +349,6 @@ function shouldUpdate (args, tree, dep, has, req, depth, pkgpath, opts, cb, type
       cb)
   }
 
-  function doIt (wanted, latest) {
-    if (!long) {
-      return cb(null, [[tree, dep, curr && curr.version, wanted, latest, req, null, pkgpath]])
-    }
-    cb(null, [[tree, dep, curr && curr.version, wanted, latest, req, type, pkgpath]])
-  }
-
   if (args.length && args.indexOf(dep) === -1) return skip()
 
   if (tree.isLink && req == null) return skip()
@@ -374,11 +367,22 @@ function shouldUpdate (args, tree, dep, has, req, depth, pkgpath, opts, cb, type
   } else if (parsed.type === 'file') {
     return updateLocalDeps()
   } else {
-    return packument(dep, opts.concat({
+    return packument(parsed, opts.concat({
       'prefer-online': true
     })).nodeify(updateDeps)
   }
 
+  function doIt (wanted, latest) {
+    let c = curr && curr.version
+    if (parsed.type === 'alias') {
+      c = `npm:${parsed.subSpec.name}@${c}`
+    }
+    if (!long) {
+      return cb(null, [[tree, dep, c, wanted, latest, req, null, pkgpath]])
+    }
+    cb(null, [[tree, dep, c, wanted, latest, req, type, pkgpath]])
+  }
+
   function updateLocalDeps (latestRegistryVersion) {
     fetchPackageMetadata('file:' + parsed.fetchSpec, '.', (er, localDependency) => {
       if (er) return cb()
@@ -405,6 +409,9 @@ function shouldUpdate (args, tree, dep, has, req, depth, pkgpath, opts, cb, type
   function updateDeps (er, d) {
     if (er) return cb(er)
 
+    if (parsed.type === 'alias') {
+      req = parsed.subSpec.rawSpec
+    }
     try {
       var l = pickManifest(d, 'latest')
       var m = pickManifest(d, req)
@@ -421,11 +428,20 @@ function shouldUpdate (args, tree, dep, has, req, depth, pkgpath, opts, cb, type
     var dFromUrl = m._from && url.parse(m._from).protocol
     var cFromUrl = curr && curr.from && url.parse(curr.from).protocol
 
-    if (!curr ||
-        (dFromUrl && cFromUrl && m._from !== curr.from) ||
-        m.version !== curr.version ||
-        m.version !== l.version) {
-      doIt(m.version, l.version)
+    if (
+      !curr ||
+      (dFromUrl && cFromUrl && m._from !== curr.from) ||
+      m.version !== curr.version ||
+      m.version !== l.version
+    ) {
+      if (parsed.type === 'alias') {
+        doIt(
+          `npm:${parsed.subSpec.name}@${m.version}`,
+          `npm:${parsed.subSpec.name}@${l.version}`
+        )
+      } else {
+        doIt(m.version, l.version)
+      }
     } else {
       skip()
     }
diff --git a/lib/shrinkwrap.js b/lib/shrinkwrap.js
index b1f60ac7a9eea..75d58bf8e4f0b 100644
--- a/lib/shrinkwrap.js
+++ b/lib/shrinkwrap.js
@@ -162,6 +162,8 @@ function childVersion (top, child, req) {
     return 'file:' + unixFormatPath(path.relative(top.path, child.package._resolved || req.fetchSpec))
   } else if (!isRegistry(req) && !child.fromBundle) {
     return child.package._resolved || req.saveSpec || req.rawSpec
+  } else if (req.type === 'alias') {
+    return `npm:${child.package.name}@${child.package.version}`
   } else {
     return child.package.version
   }
diff --git a/lib/utils/module-name.js b/lib/utils/module-name.js
index 89957b181fd05..18f54e4118d6d 100644
--- a/lib/utils/module-name.js
+++ b/lib/utils/module-name.js
@@ -21,6 +21,7 @@ function isNotEmpty (str) {
 
 var unknown = 0
 function moduleName (tree) {
+  if (tree.name) { return tree.name }
   var pkg = tree.package || tree
   if (isNotEmpty(pkg.name) && typeof pkg.name === 'string') return pkg.name.trim()
   var pkgName = pathToPackageName(tree.path)
diff --git a/test/tap/404-parent.js b/test/tap/404-parent.js
index 4321f7d829b25..67835efc1df10 100644
--- a/test/tap/404-parent.js
+++ b/test/tap/404-parent.js
@@ -14,7 +14,7 @@ test('404-parent: if parent exists, specify parent in error message', function (
   rimraf.sync(path.resolve(pkg, 'node_modules'))
   performInstall(function (err) {
     t.ok(err instanceof Error, 'error was returned')
-    t.ok(err.parent === '404-parent-test', "error's parent set")
+    t.equal(err.parent, '404-parent', "error's parent set")
     t.end()
   })
 })
diff --git a/test/tap/aliases.js b/test/tap/aliases.js
new file mode 100644
index 0000000000000..19f0b1293e935
--- /dev/null
+++ b/test/tap/aliases.js
@@ -0,0 +1,268 @@
+'use strict'
+
+const BB = require('bluebird')
+
+const common = require('../common-tap.js')
+const fs = require('graceful-fs')
+const mockTar = require('../util/mock-tarball.js')
+const mr = common.fakeRegistry.compat
+const path = require('path')
+const rimraf = BB.promisify(require('rimraf'))
+const Tacks = require('tacks')
+const { test } = require('tap')
+
+const { Dir, File } = Tacks
+const readdirAsync = BB.promisify(fs.readdir)
+const readFileAsync = BB.promisify(fs.readFile)
+
+const testDir = path.join(__dirname, path.basename(__filename, '.js'))
+
+let server
+test('setup', t => {
+  mr({}, (err, s) => {
+    t.ifError(err, 'registry mocked successfully')
+    server = s
+    t.end()
+  })
+})
+
+test('installs an npm: protocol alias package', t => {
+  const fixture = new Tacks(Dir({
+    'package.json': File({})
+  }))
+  fixture.create(testDir)
+  const packument = {
+    name: 'foo',
+    'dist-tags': { latest: '1.2.4' },
+    versions: {
+      '1.2.3': {
+        name: 'foo',
+        version: '1.2.3',
+        dist: {
+          tarball: `${server.registry}/foo/-/foo-1.2.3.tgz`
+        }
+      },
+      '1.2.4': {
+        name: 'foo',
+        version: '1.2.4',
+        dist: {
+          tarball: `${server.registry}/foo/-/foo-1.2.4.tgz`
+        }
+      }
+    }
+  }
+  server.get('/foo').reply(200, packument)
+  return mockTar({
+    'package.json': JSON.stringify({
+      name: 'foo',
+      version: '1.2.3'
+    })
+  }).then(tarball => {
+    server.get('/foo/-/foo-1.2.3.tgz').reply(200, tarball)
+    server.get('/foo/-/foo-1.2.4.tgz').reply(200, tarball)
+    return common.npm([
+      'install', 'foo@1.2.3',
+      '--cache', path.join(testDir, 'npmcache'),
+      '--registry', server.registry
+    ], { cwd: testDir })
+  }).then(([code, stdout, stderr]) => {
+    t.equal(code, 0)
+    t.comment(stdout)
+    t.comment(stderr)
+    return common.npm([
+      'install', 'bar@npm:foo@1.2.3',
+      '--cache', path.join(testDir, 'npmcache'),
+      '--registry', server.registry
+    ], { cwd: testDir })
+  }).then(([code, stdout, stderr]) => {
+    t.equal(code, 0)
+    t.comment(stdout)
+    t.comment(stderr)
+    t.match(stdout, /\+ foo@1\.2\.3 \(as bar\)/, 'useful message')
+    return readFileAsync(
+      path.join(testDir, 'node_modules', 'bar', 'package.json'),
+      'utf8'
+    )
+  }).then(JSON.parse).then(pkg => {
+    t.similar(pkg, {
+      name: 'foo',
+      version: '1.2.3'
+    }, 'successfully installed foo as bar in node_modules')
+    return common.npm(['ls', '--json'], { cwd: testDir })
+  }).then(([code, stdout, stderr]) => {
+    t.comment(stdout)
+    t.comment(stderr)
+    t.equal(code, 0, 'ls is clean')
+    t.deepEqual(JSON.parse(stdout), {
+      dependencies: {
+        bar: {
+          version: '1.2.3',
+          from: 'bar@npm:foo@1.2.3',
+          resolved: 'http://localhost:1337/foo/-/foo-1.2.3.tgz'
+        },
+        foo: {
+          version: '1.2.3',
+          from: 'foo@1.2.3',
+          resolved: 'http://localhost:1337/foo/-/foo-1.2.3.tgz'
+        }
+      }
+    }, 'both dependencies listed correctly')
+    return common.npm([
+      'outdated', '--json',
+      '--registry', server.registry
+    ], { cwd: testDir })
+  }).then(([code, stdout, stderr]) => {
+    t.equal(code, 1, 'non-zero because some packages outdated')
+    t.comment(stdout)
+    t.comment(stderr)
+    const parsed = JSON.parse(stdout)
+    t.deepEqual(parsed, {
+      foo: {
+        current: '1.2.3',
+        wanted: '1.2.4',
+        latest: '1.2.4',
+        location: 'node_modules/foo'
+      },
+      bar: {
+        current: 'npm:foo@1.2.3',
+        wanted: 'npm:foo@1.2.4',
+        latest: 'npm:foo@1.2.4',
+        location: 'node_modules/bar'
+      }
+    }, 'both regular and aliased dependency reported')
+    return common.npm([
+      'update',
+      '--registry', server.registry
+    ], { cwd: testDir })
+  }).then(([code, stdout, stderr]) => {
+    t.equal(code, 0, 'update succeeded')
+    t.comment(stdout)
+    t.comment(stderr)
+    return common.npm(['ls', '--json'], { cwd: testDir })
+  }).then(([code, stdout, stderr]) => {
+    t.equal(code, 0, 'ls succeeded')
+    t.comment(stdout)
+    t.comment(stderr)
+    const parsed = JSON.parse(stdout)
+    t.deepEqual(parsed, {
+      dependencies: {
+        bar: {
+          version: '1.2.4',
+          from: 'bar@npm:foo@1.2.4',
+          resolved: 'http://localhost:1337/foo/-/foo-1.2.4.tgz'
+        },
+        foo: {
+          version: '1.2.4',
+          from: 'foo@1.2.4',
+          resolved: 'http://localhost:1337/foo/-/foo-1.2.4.tgz'
+        }
+      }
+    }, 'ls shows updated packages')
+    return common.npm([
+      'rm', 'bar',
+      '--registry', server.registry
+    ], { cwd: testDir })
+  }).then(([code, stdout, stderr]) => {
+    t.equal(code, 0, 'rm succeeded')
+    t.comment(stdout)
+    t.comment(stderr)
+    t.match(stdout, 'removed 1 package', 'notified of removed package')
+    return readdirAsync(path.join(testDir, 'node_modules'))
+  }).then(dir => {
+    t.deepEqual(dir, ['foo'], 'regular foo left in place')
+  }).then(() => rimraf(testDir))
+})
+
+test('installs a tarball dep as a different name than package.json', t => {
+  return mockTar({
+    'package.json': JSON.stringify({
+      name: 'foo',
+      version: '1.2.3'
+    })
+  }).then(tarball => {
+    const fixture = new Tacks(Dir({
+      'package.json': File({}),
+      'foo.tgz': File(tarball)
+    }))
+    fixture.create(testDir)
+    return common.npm([
+      'install', 'file:foo.tgz',
+      '--cache', path.join(testDir, 'npmcache'),
+      '--registry', server.registry
+    ], { cwd: testDir })
+  }).then(([code, stdout, stderr]) => {
+    return common.npm([
+      'install', 'bar@file:foo.tgz',
+      '--cache', path.join(testDir, 'npmcache'),
+      '--registry', server.registry
+    ], { cwd: testDir })
+  }).then(([code, stdout, stderr]) => {
+    t.comment(stdout)
+    t.comment(stderr)
+    t.match(stdout, /^\+ foo@1\.2\.3 \(as bar\)/, 'useful message')
+    return readFileAsync(
+      path.join(testDir, 'node_modules', 'bar', 'package.json'),
+      'utf8'
+    )
+  }).then(JSON.parse).then(pkg => {
+    t.similar(pkg, {
+      name: 'foo',
+      version: '1.2.3'
+    }, 'successfully installed foo as bar in node_modules')
+    return common.npm(['ls', '--json'], { cwd: testDir })
+  }).then(([code, stdout, stderr]) => {
+    t.comment(stdout)
+    t.comment(stderr)
+    t.similar(JSON.parse(stdout), {
+      dependencies: {
+        bar: {
+          version: '1.2.3',
+          from: 'file:foo.tgz'
+        },
+        foo: {
+          version: '1.2.3',
+          from: 'file:foo.tgz'
+        }
+      }
+    }, 'both dependencies present')
+  }).then(() => rimraf(testDir))
+})
+
+test('installs a symlink dep as a different name than package.json', t => {
+  const fixture = new Tacks(Dir({
+    'package.json': File({}),
+    'foo': Dir({
+      'package.json': File({
+        name: 'foo',
+        version: '1.2.3'
+      })
+    })
+  }))
+  fixture.create(testDir)
+  return common.npm([
+    'install', 'bar@file:foo',
+    '--cache', path.join(testDir, 'npmcache'),
+    '--registry', server.registry
+  ], { cwd: testDir }).then(([code, stdout, stderr]) => {
+    t.comment(stdout)
+    t.comment(stderr)
+    t.match(stdout, /^\+ foo@1\.2\.3 \(as bar\)/, 'useful message')
+    return readFileAsync(
+      path.join(testDir, 'node_modules', 'bar', 'package.json'),
+      'utf8'
+    )
+  }).then(JSON.parse).then(pkg => {
+    t.similar(pkg, {
+      name: 'foo',
+      version: '1.2.3'
+    }, 'successfully installed foo as bar in node_modules')
+  }).then(() => rimraf(testDir))
+})
+
+test('cleanup', t => {
+  server.close()
+  return rimraf(testDir)
+})
+
+test('npm audit supports aliases')
+test('npm audit fix supports aliases')
diff --git a/test/tap/install-actions.js b/test/tap/install-actions.js
index 6ca6e33534381..b34be3ad0dd70 100644
--- a/test/tap/install-actions.js
+++ b/test/tap/install-actions.js
@@ -91,6 +91,7 @@ test('->dep:b,->optdep:a->dep:b', function (t) {
   moduleA.requires = [moduleB]
 
   var tree = {
+    name: 'tree',
     path: '/',
     package: {
       dependencies: {
@@ -109,13 +110,8 @@ test('->dep:b,->optdep:a->dep:b', function (t) {
   moduleA.parent = tree
   moduleB.parent = tree
 
-  t.plan(3)
   return actions.postinstall('/', moduleA, mockLog).then(() => {
-    throw new Error('was not supposed to succeed')
-  }, (err) => {
-    t.ok(err && err.code === 'ELIFECYCLE', 'Lifecycle failed')
     t.ok(moduleA.failed, 'moduleA (optional dep) is marked failed')
     t.ok(!moduleB.failed, 'moduleB (direct dep of moduleA) is marked as failed')
-    t.end()
   })
 })