From d78d211b4c6660f248b9a953542f9eff14443fcb Mon Sep 17 00:00:00 2001
From: Michael Kriese <michael.kriese@visualon.de>
Date: Wed, 11 Mar 2020 07:17:42 +0100
Subject: [PATCH] feat: npm resolutions (#5689)

---
 .../npm/__fixtures__/inputs/01-glob.json      |   3 +-
 lib/manager/npm/__fixtures__/inputs/01.json   |   5 +-
 lib/manager/npm/__fixtures__/outputs/011.json |   5 +-
 lib/manager/npm/__fixtures__/outputs/012.json |   5 +-
 lib/manager/npm/__fixtures__/outputs/013.json |   5 +-
 .../extract/__snapshots__/index.spec.ts.snap  | 185 ++++++++++++++++++
 lib/manager/npm/extract/index.ts              |  14 +-
 lib/manager/npm/update.spec.ts                |  15 ++
 lib/manager/npm/update.ts                     |   3 +-
 9 files changed, 233 insertions(+), 7 deletions(-)

diff --git a/lib/manager/npm/__fixtures__/inputs/01-glob.json b/lib/manager/npm/__fixtures__/inputs/01-glob.json
index 25031e31d3..9f31f8be60 100644
--- a/lib/manager/npm/__fixtures__/inputs/01-glob.json
+++ b/lib/manager/npm/__fixtures__/inputs/01-glob.json
@@ -36,5 +36,6 @@
   "repository": {
     "type": "git",
     "url": "http://github.com/singapore/renovate.git"
-  }
+  },
+  "workspaces": []
 }
diff --git a/lib/manager/npm/__fixtures__/inputs/01.json b/lib/manager/npm/__fixtures__/inputs/01.json
index 603fb8ef5e..661bcfa896 100644
--- a/lib/manager/npm/__fixtures__/inputs/01.json
+++ b/lib/manager/npm/__fixtures__/inputs/01.json
@@ -25,7 +25,10 @@
     "@angular/core": "4.0.0-beta.1"
   },
   "resolutions": {
-    "config": "1.21.0"
+    "config": "1.21.0",
+    "**/@angular/cli": "8.0.0",
+    "**/angular": "1.33.0",
+    "config/glob": "1.0.0"
   },
   "homepage": "https://keylocation.sg",
   "keywords": [
diff --git a/lib/manager/npm/__fixtures__/outputs/011.json b/lib/manager/npm/__fixtures__/outputs/011.json
index 6c53843e71..badff4fe13 100644
--- a/lib/manager/npm/__fixtures__/outputs/011.json
+++ b/lib/manager/npm/__fixtures__/outputs/011.json
@@ -25,7 +25,10 @@
     "@angular/core": "4.0.0-beta.1"
   },
   "resolutions": {
-    "config": "1.21.0"
+    "config": "1.21.0",
+    "**/@angular/cli": "8.0.0",
+    "**/angular": "1.33.0",
+    "config/glob": "1.0.0"
   },
   "homepage": "https://keylocation.sg",
   "keywords": [
diff --git a/lib/manager/npm/__fixtures__/outputs/012.json b/lib/manager/npm/__fixtures__/outputs/012.json
index 31e1dfb320..59155e560c 100644
--- a/lib/manager/npm/__fixtures__/outputs/012.json
+++ b/lib/manager/npm/__fixtures__/outputs/012.json
@@ -25,7 +25,10 @@
     "@angular/core": "4.0.0-beta.1"
   },
   "resolutions": {
-    "config": "1.21.0"
+    "config": "1.21.0",
+    "**/@angular/cli": "8.0.0",
+    "**/angular": "1.33.0",
+    "config/glob": "1.0.0"
   },
   "homepage": "https://keylocation.sg",
   "keywords": [
diff --git a/lib/manager/npm/__fixtures__/outputs/013.json b/lib/manager/npm/__fixtures__/outputs/013.json
index ebb828dbb7..75002dcbec 100644
--- a/lib/manager/npm/__fixtures__/outputs/013.json
+++ b/lib/manager/npm/__fixtures__/outputs/013.json
@@ -25,7 +25,10 @@
     "@angular/core": "4.0.0-beta.1"
   },
   "resolutions": {
-    "config": "1.21.0"
+    "config": "1.21.0",
+    "**/@angular/cli": "8.0.0",
+    "**/angular": "1.33.0",
+    "config/glob": "1.0.0"
   },
   "homepage": "https://keylocation.sg",
   "keywords": [
diff --git a/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap b/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap
index cc6a89d49a..f9b6e422bb 100644
--- a/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap
+++ b/lib/manager/npm/extract/__snapshots__/index.spec.ts.snap
@@ -534,6 +534,43 @@ Object {
       "depType": "devDependencies",
       "prettyDepType": "devDependency",
     },
+    Object {
+      "currentValue": "1.21.0",
+      "datasource": "npm",
+      "depName": "config",
+      "depType": "resolutions",
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "8.0.0",
+      "datasource": "npm",
+      "depName": "@angular/cli",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "**/@angular/cli",
+      },
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "1.33.0",
+      "datasource": "npm",
+      "depName": "angular",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "**/angular",
+      },
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "1.0.0",
+      "datasource": "npm",
+      "depName": "glob",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "config/glob",
+      },
+      "prettyDepType": "resolutions",
+    },
   ],
   "ignoreNpmrcFile": undefined,
   "lernaClient": "npm",
@@ -631,6 +668,43 @@ Object {
       "depType": "devDependencies",
       "prettyDepType": "devDependency",
     },
+    Object {
+      "currentValue": "1.21.0",
+      "datasource": "npm",
+      "depName": "config",
+      "depType": "resolutions",
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "8.0.0",
+      "datasource": "npm",
+      "depName": "@angular/cli",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "**/@angular/cli",
+      },
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "1.33.0",
+      "datasource": "npm",
+      "depName": "angular",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "**/angular",
+      },
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "1.0.0",
+      "datasource": "npm",
+      "depName": "glob",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "config/glob",
+      },
+      "prettyDepType": "resolutions",
+    },
   ],
   "ignoreNpmrcFile": undefined,
   "lernaClient": "yarn",
@@ -728,6 +802,43 @@ Object {
       "depType": "devDependencies",
       "prettyDepType": "devDependency",
     },
+    Object {
+      "currentValue": "1.21.0",
+      "datasource": "npm",
+      "depName": "config",
+      "depType": "resolutions",
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "8.0.0",
+      "datasource": "npm",
+      "depName": "@angular/cli",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "**/@angular/cli",
+      },
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "1.33.0",
+      "datasource": "npm",
+      "depName": "angular",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "**/angular",
+      },
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "1.0.0",
+      "datasource": "npm",
+      "depName": "glob",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "config/glob",
+      },
+      "prettyDepType": "resolutions",
+    },
   ],
   "ignoreNpmrcFile": undefined,
   "lernaClient": undefined,
@@ -847,6 +958,43 @@ Object {
       "depType": "devDependencies",
       "prettyDepType": "devDependency",
     },
+    Object {
+      "currentValue": "1.21.0",
+      "datasource": "npm",
+      "depName": "config",
+      "depType": "resolutions",
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "8.0.0",
+      "datasource": "npm",
+      "depName": "@angular/cli",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "**/@angular/cli",
+      },
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "1.33.0",
+      "datasource": "npm",
+      "depName": "angular",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "**/angular",
+      },
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "1.0.0",
+      "datasource": "npm",
+      "depName": "glob",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "config/glob",
+      },
+      "prettyDepType": "resolutions",
+    },
   ],
   "ignoreNpmrcFile": undefined,
   "lernaClient": "npm",
@@ -966,6 +1114,43 @@ Object {
       "depType": "devDependencies",
       "prettyDepType": "devDependency",
     },
+    Object {
+      "currentValue": "1.21.0",
+      "datasource": "npm",
+      "depName": "config",
+      "depType": "resolutions",
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "8.0.0",
+      "datasource": "npm",
+      "depName": "@angular/cli",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "**/@angular/cli",
+      },
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "1.33.0",
+      "datasource": "npm",
+      "depName": "angular",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "**/angular",
+      },
+      "prettyDepType": "resolutions",
+    },
+    Object {
+      "currentValue": "1.0.0",
+      "datasource": "npm",
+      "depName": "glob",
+      "depType": "resolutions",
+      "managerData": Object {
+        "key": "config/glob",
+      },
+      "prettyDepType": "resolutions",
+    },
   ],
   "ignoreNpmrcFile": undefined,
   "lernaClient": undefined,
diff --git a/lib/manager/npm/extract/index.ts b/lib/manager/npm/extract/index.ts
index 3bfb283c34..5d6d315c38 100644
--- a/lib/manager/npm/extract/index.ts
+++ b/lib/manager/npm/extract/index.ts
@@ -22,6 +22,13 @@ import * as nodeVersioning from '../../../versioning/node';
 import * as datasourceNpm from '../../../datasource/npm';
 import * as datasourceGithubTags from '../../../datasource/github-tags';
 
+function parseDepName(depType: string, key: string): string {
+  if (depType !== 'resolutions') return key;
+
+  const [, depName] = /((?:@[^/]+\/)?[^/@]+)$/.exec(key);
+  return depName;
+}
+
 export async function extractPackageFile(
   content: string,
   fileName: string,
@@ -136,6 +143,7 @@ export async function extractPackageFile(
     peerDependencies: 'peerDependency',
     engines: 'engine',
     volta: 'volta',
+    resolutions: 'resolutions',
   };
 
   function extractDependency(
@@ -272,13 +280,17 @@ export async function extractPackageFile(
   for (const depType of Object.keys(depTypes)) {
     if (packageJson[depType]) {
       try {
-        for (const [depName, val] of Object.entries(
+        for (const [key, val] of Object.entries(
           packageJson[depType] as NpmPackageDependeny
         )) {
+          const depName = parseDepName(depType, key);
           const dep: PackageDependency = {
             depType,
             depName,
           };
+          if (depName !== key) {
+            dep.managerData = { key };
+          }
           Object.assign(dep, extractDependency(depType, depName, val));
           if (depName === 'node') {
             // This is a special case for Node.js to group it together with other managers
diff --git a/lib/manager/npm/update.spec.ts b/lib/manager/npm/update.spec.ts
index ed94b554a5..e1e58a8053 100644
--- a/lib/manager/npm/update.spec.ts
+++ b/lib/manager/npm/update.spec.ts
@@ -133,6 +133,21 @@ describe('workers/branch/package-json', () => {
         '1.22.0'
       );
     });
+    it('updates glob resolutions without dep', () => {
+      const upgrade = {
+        depType: 'resolutions',
+        depName: '@angular/cli',
+        managerData: { key: '**/@angular/cli' },
+        newValue: '8.1.0',
+      };
+      const testContent = npmUpdater.updateDependency({
+        fileContent: input01Content,
+        upgrade,
+      });
+      expect(JSON.parse(testContent).resolutions['**/@angular/cli']).toEqual(
+        '8.1.0'
+      );
+    });
     it('replaces only the first instance of a value', () => {
       const upgrade = {
         depType: 'devDependencies',
diff --git a/lib/manager/npm/update.ts b/lib/manager/npm/update.ts
index 04150ffe55..df8abe2ea3 100644
--- a/lib/manager/npm/update.ts
+++ b/lib/manager/npm/update.ts
@@ -61,7 +61,8 @@ export function updateDependency({
   fileContent,
   upgrade,
 }: UpdateDependencyConfig): string | null {
-  const { depType, depName } = upgrade;
+  const { depType, managerData } = upgrade;
+  const depName = managerData?.key || upgrade.depName;
   let { newValue } = upgrade;
   if (upgrade.currentRawValue) {
     if (upgrade.currentDigest) {
-- 
GitLab