From b3ad425b530c8051df67605d2c840afbe210a42d Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Tue, 23 Apr 2019 10:48:09 +0200
Subject: [PATCH] feat(npm): support package aliases (#3577)

Closes #3575
---
 lib/manager/npm/extract/index.js              | 13 ++++++
 lib/manager/npm/update.js                     |  3 ++
 .../npm/__snapshots__/update.spec.js.snap     |  2 +
 .../extract/__snapshots__/index.spec.js.snap  | 46 +++++++++++++++++++
 test/manager/npm/extract/index.spec.js        | 16 +++++++
 test/manager/npm/update.spec.js               | 17 +++++++
 6 files changed, 97 insertions(+)

diff --git a/lib/manager/npm/extract/index.js b/lib/manager/npm/extract/index.js
index 4c1f5f6db0..cc132f9fb2 100644
--- a/lib/manager/npm/extract/index.js
+++ b/lib/manager/npm/extract/index.js
@@ -169,6 +169,19 @@ async function extractPackageFile(content, fileName, config) {
       }
       return dep;
     }
+    if (dep.currentValue.startsWith('npm:')) {
+      dep.npmPackageAlias = true;
+      const valSplit = dep.currentValue.replace('npm:', '').split('@');
+      if (valSplit.length === 2) {
+        dep.lookupName = valSplit[0];
+        dep.currentValue = valSplit[1];
+      } else if (valSplit.length === 3) {
+        dep.lookupName = valSplit[0] + '@' + valSplit[1];
+        dep.currentValue = valSplit[2];
+      } else {
+        logger.info('Invalid npm package alias: ' + dep.currentValue);
+      }
+    }
     if (dep.currentValue.startsWith('file:')) {
       dep.skipReason = 'file';
       hasFileRefs = true;
diff --git a/lib/manager/npm/update.js b/lib/manager/npm/update.js
index 31f824b100..bf81a6914b 100644
--- a/lib/manager/npm/update.js
+++ b/lib/manager/npm/update.js
@@ -24,6 +24,9 @@ function updateDependency(fileContent, upgrade) {
       );
     }
   }
+  if (upgrade.npmPackageAlias) {
+    newValue = `npm:${upgrade.lookupName}@${newValue}`;
+  }
   logger.debug(`npm.updateDependency(): ${depType}.${depName} = ${newValue}`);
   try {
     const parsedContents = JSON.parse(fileContent);
diff --git a/test/manager/npm/__snapshots__/update.spec.js.snap b/test/manager/npm/__snapshots__/update.spec.js.snap
index 21bae26179..a3b4198523 100644
--- a/test/manager/npm/__snapshots__/update.spec.js.snap
+++ b/test/manager/npm/__snapshots__/update.spec.js.snap
@@ -11,3 +11,5 @@ exports[`workers/branch/package-json .updateDependency(fileContent, depType, dep
 exports[`workers/branch/package-json .updateDependency(fileContent, depType, depName, newValue) replaces a github fully specified version 1`] = `"{\\"dependencies\\":{\\"n\\":\\"git+https://github.com/owner/n#v1.1.0\\"}}"`;
 
 exports[`workers/branch/package-json .updateDependency(fileContent, depType, depName, newValue) replaces a github short hash 1`] = `"{\\"dependencies\\":{\\"gulp\\":\\"gulpjs/gulp#0000000\\"}}"`;
+
+exports[`workers/branch/package-json .updateDependency(fileContent, depType, depName, newValue) replaces a npm package alias 1`] = `"{\\"dependencies\\":{\\"hapi\\":\\"npm:@hapi/hapi@18.3.1\\"}}"`;
diff --git a/test/manager/npm/extract/__snapshots__/index.spec.js.snap b/test/manager/npm/extract/__snapshots__/index.spec.js.snap
index 4af3f2c25a..1161e93147 100644
--- a/test/manager/npm/extract/__snapshots__/index.spec.js.snap
+++ b/test/manager/npm/extract/__snapshots__/index.spec.js.snap
@@ -287,6 +287,52 @@ Object {
 }
 `;
 
+exports[`manager/npm/extract .extractPackageFile() extracts npm package alias 1`] = `
+Object {
+  "deps": Array [
+    Object {
+      "currentValue": "1",
+      "datasource": "npm",
+      "depName": "a",
+      "depType": "dependencies",
+      "lookupName": "foo",
+      "npmPackageAlias": true,
+      "prettyDepType": "dependency",
+    },
+    Object {
+      "currentValue": "1.2.3",
+      "datasource": "npm",
+      "depName": "b",
+      "depType": "dependencies",
+      "lookupName": "@foo/bar",
+      "npmPackageAlias": true,
+      "prettyDepType": "dependency",
+    },
+    Object {
+      "currentValue": "npm:foo",
+      "depName": "c",
+      "depType": "dependencies",
+      "npmPackageAlias": true,
+      "prettyDepType": "dependency",
+      "skipReason": "unknown-version",
+    },
+  ],
+  "lernaClient": undefined,
+  "lernaDir": undefined,
+  "lernaPackages": undefined,
+  "npmLock": undefined,
+  "npmrc": undefined,
+  "packageJsonName": undefined,
+  "packageJsonType": "app",
+  "packageJsonVersion": undefined,
+  "pnpmShrinkwrap": undefined,
+  "skipInstalls": true,
+  "yarnLock": undefined,
+  "yarnWorkspacesPackages": undefined,
+  "yarnrc": undefined,
+}
+`;
+
 exports[`manager/npm/extract .extractPackageFile() finds a lock file 1`] = `
 Object {
   "deps": Array [
diff --git a/test/manager/npm/extract/index.spec.js b/test/manager/npm/extract/index.spec.js
index a59e8053c5..a8f15aa49a 100644
--- a/test/manager/npm/extract/index.spec.js
+++ b/test/manager/npm/extract/index.spec.js
@@ -202,6 +202,22 @@ describe('manager/npm/extract', () => {
       );
       expect(res).toMatchSnapshot();
     });
+    it('extracts npm package alias', async () => {
+      const pJson = {
+        dependencies: {
+          a: 'npm:foo@1',
+          b: 'npm:@foo/bar@1.2.3',
+          c: 'npm:foo',
+        },
+      };
+      const pJsonStr = JSON.stringify(pJson);
+      const res = await npmExtract.extractPackageFile(
+        pJsonStr,
+        'package.json',
+        defaultConfig
+      );
+      expect(res).toMatchSnapshot();
+    });
   });
   describe('.postExtract()', () => {
     it('runs', async () => {
diff --git a/test/manager/npm/update.spec.js b/test/manager/npm/update.spec.js
index f9a7da5c69..5f124f9de2 100644
--- a/test/manager/npm/update.spec.js
+++ b/test/manager/npm/update.spec.js
@@ -41,6 +41,23 @@ describe('workers/branch/package-json', () => {
       const res = npmUpdater.updateDependency(input, upgrade);
       expect(res).toMatchSnapshot();
     });
+    it('replaces a npm package alias', () => {
+      const upgrade = {
+        depType: 'dependencies',
+        depName: 'hapi',
+        npmPackageAlias: true,
+        lookupName: '@hapi/hapi',
+        currentValue: '18.3.0',
+        newValue: '18.3.1',
+      };
+      const input = JSON.stringify({
+        dependencies: {
+          hapi: 'npm:@hapi/hapi@18.3.0',
+        },
+      });
+      const res = npmUpdater.updateDependency(input, upgrade);
+      expect(res).toMatchSnapshot();
+    });
     it('replaces a github short hash', () => {
       const upgrade = {
         depType: 'dependencies',
-- 
GitLab