From b84ce2ad55e120d5323afaa88338880f68fc6a3e Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Sat, 18 Dec 2021 16:39:55 +0100
Subject: [PATCH] refactor(npm): updateLockedDependency result (#13182)

---
 .../update/locked-dependency/index.spec.ts    | 22 ++++++------
 .../npm/update/locked-dependency/index.ts     | 13 +++----
 .../locked-dependency/package-lock/index.ts   | 35 ++++++++++---------
 lib/manager/types.ts                          |  7 +++-
 lib/workers/branch/get-updated.spec.ts        |  6 ++--
 lib/workers/branch/get-updated.ts             |  2 +-
 6 files changed, 47 insertions(+), 38 deletions(-)

diff --git a/lib/manager/npm/update/locked-dependency/index.spec.ts b/lib/manager/npm/update/locked-dependency/index.spec.ts
index 022b2f4077..ca7266083b 100644
--- a/lib/manager/npm/update/locked-dependency/index.spec.ts
+++ b/lib/manager/npm/update/locked-dependency/index.spec.ts
@@ -33,7 +33,7 @@ describe('manager/npm/update/locked-dependency/index', () => {
     it('validates filename', async () => {
       expect(
         await updateLockedDependency({ ...config, lockFile: 'yarn.lock' })
-      ).toBeNull();
+      ).toMatchObject({});
     });
     it('validates versions', async () => {
       expect(
@@ -41,12 +41,12 @@ describe('manager/npm/update/locked-dependency/index', () => {
           ...config,
           newVersion: '^2.0.0',
         })
-      ).toBeNull();
+      ).toMatchObject({});
     });
     it('returns null for unparseable files', async () => {
       expect(
         await updateLockedDependency({ ...config, lockFileContent: 'not json' })
-      ).toBeNull();
+      ).toMatchObject({});
     });
     it('rejects lockFileVersion 2', async () => {
       expect(
@@ -54,10 +54,10 @@ describe('manager/npm/update/locked-dependency/index', () => {
           ...config,
           lockFileContent: lockFileContent.replace(': 1,', ': 2,'),
         })
-      ).toBeNull();
+      ).toMatchObject({});
     });
     it('returns null if no locked deps', async () => {
-      expect(await updateLockedDependency(config)).toBeNull();
+      expect(await updateLockedDependency(config)).toMatchObject({});
     });
     it('rejects null if no constraint found', async () => {
       expect(
@@ -68,7 +68,7 @@ describe('manager/npm/update/locked-dependency/index', () => {
           currentVersion: '10.0.0',
           newVersion: '11.0.0',
         })
-      ).toBeNull();
+      ).toMatchObject({});
     });
     it('remediates in-range', async () => {
       const res = await updateLockedDependency({
@@ -78,7 +78,7 @@ describe('manager/npm/update/locked-dependency/index', () => {
         newVersion: '1.2.12',
       });
       expect(
-        JSON.parse(res['package-lock.json']).dependencies.mime.version
+        JSON.parse(res.files['package-lock.json']).dependencies.mime.version
       ).toBe('1.2.12');
     });
     it('fails to remediate if parent dep cannot support', async () => {
@@ -98,15 +98,15 @@ describe('manager/npm/update/locked-dependency/index', () => {
         currentVersion: '1.0.0',
         newVersion: '2.0.0',
       });
-      expect(res).toBeNull();
+      expect(res).toMatchObject({});
     });
     it('remediates express', async () => {
       config.depName = 'express';
       config.currentVersion = '4.0.0';
       config.newVersion = '4.1.0';
       const res = await updateLockedDependency(config);
-      expect(res['package.json']).toContain('"express": "4.1.0"');
-      const packageLock = JSON.parse(res['package-lock.json']);
+      expect(res.files['package.json']).toContain('"express": "4.1.0"');
+      const packageLock = JSON.parse(res.files['package-lock.json']);
       expect(packageLock.dependencies.express.version).toBe('4.1.0');
     });
     it('remediates mime', async () => {
@@ -130,7 +130,7 @@ describe('manager/npm/update/locked-dependency/index', () => {
         .get('/type-is')
         .reply(200, typeIsJson);
       const res = await updateLockedDependency(config);
-      const packageLock = JSON.parse(res['package-lock.json']);
+      const packageLock = JSON.parse(res.files['package-lock.json']);
       expect(packageLock.dependencies.mime.version).toBe('1.4.1');
       expect(packageLock.dependencies.express.version).toBe('4.16.0');
       expect(httpMock.getTrace()).toMatchSnapshot();
diff --git a/lib/manager/npm/update/locked-dependency/index.ts b/lib/manager/npm/update/locked-dependency/index.ts
index 08198cd155..e32de991b9 100644
--- a/lib/manager/npm/update/locked-dependency/index.ts
+++ b/lib/manager/npm/update/locked-dependency/index.ts
@@ -1,19 +1,20 @@
 import { logger } from '../../../../logger';
 import * as semver from '../../../../versioning/semver';
-import type { UpdateLockedConfig } from '../../../types';
+import type { UpdateLockedConfig, UpdateLockedResult } from '../../../types';
 import * as packageLock from './package-lock';
 
-export function updateLockedDependency(
+export async function updateLockedDependency(
   config: UpdateLockedConfig
-): Promise<Record<string, string>> {
+): Promise<UpdateLockedResult> {
   const { currentVersion, newVersion, lockFile } = config;
   if (!(semver.isVersion(currentVersion) && semver.isVersion(newVersion))) {
     logger.warn({ config }, 'Update versions are not valid');
-    return null;
+    return { status: 'update-failed' };
   }
   if (lockFile.endsWith('package-lock.json')) {
-    return packageLock.updateLockedDependency(config);
+    const res = await packageLock.updateLockedDependency(config);
+    return res;
   }
   logger.debug({ lockFile }, 'Unsupported lock file');
-  return null;
+  return { status: 'update-failed' };
 }
diff --git a/lib/manager/npm/update/locked-dependency/package-lock/index.ts b/lib/manager/npm/update/locked-dependency/package-lock/index.ts
index 0aa2d7e458..43fcc91f78 100644
--- a/lib/manager/npm/update/locked-dependency/package-lock/index.ts
+++ b/lib/manager/npm/update/locked-dependency/package-lock/index.ts
@@ -2,7 +2,7 @@ import detectIndent from 'detect-indent';
 import type { PackageJson } from 'type-fest';
 import { logger } from '../../../../../logger';
 import { api as semver } from '../../../../../versioning/npm';
-import type { UpdateLockedConfig } from '../../../../types';
+import type { UpdateLockedConfig, UpdateLockedResult } from '../../../../types';
 import { updateDependency } from '../../dependency';
 import { findFirstParentVersion } from '../common/parent-version';
 import { findDepConstraints } from './dep-constraints';
@@ -12,7 +12,7 @@ import type { PackageLockOrEntry } from './types';
 export async function updateLockedDependency(
   config: UpdateLockedConfig,
   isParentUpdate = false
-): Promise<Record<string, string>> {
+): Promise<UpdateLockedResult> {
   const {
     depName,
     currentVersion,
@@ -35,11 +35,11 @@ export async function updateLockedDependency(
       packageLockJson = JSON.parse(lockFileContent);
     } catch (err) {
       logger.warn({ err }, 'Failed to parse files');
-      return null;
+      return { status: 'update-failed' };
     }
     if (packageLockJson.lockfileVersion === 2) {
       logger.debug('Only lockfileVersion 1 is supported');
-      return null;
+      return { status: 'update-failed' };
     }
     const lockedDeps = getLockedDependencies(
       packageLockJson,
@@ -50,15 +50,15 @@ export async function updateLockedDependency(
       logger.debug(
         `${depName}@${currentVersion} not found in ${lockFile} - no work to do`
       );
-      // Don't return null if we're a parent update or else the whole update will fail
+      // Don't return {} if we're a parent update or else the whole update will fail
       // istanbul ignore if: too hard to replicate
       if (isParentUpdate) {
-        const res = {};
-        res[packageFile] = packageFileContent;
-        res[lockFile] = lockFileContent;
+        const res: UpdateLockedResult = { status: 'update-failed', files: {} };
+        res.files[packageFile] = packageFileContent;
+        res.files[lockFile] = lockFileContent;
         return res;
       }
-      return null;
+      return { status: 'update-failed' };
     }
     logger.debug(
       `Found matching dependencies with length ${lockedDeps.length}`
@@ -76,7 +76,7 @@ export async function updateLockedDependency(
         { depName, currentVersion, newVersion },
         'Could not find constraints for the locked dependency - cannot remediate'
       );
-      return null;
+      return { status: 'update-failed' };
     }
     const parentUpdates: UpdateLockedConfig[] = [];
     for (const {
@@ -122,7 +122,7 @@ export async function updateLockedDependency(
           logger.debug(
             `Update of ${depName} to ${newVersion} cannot be achieved due to parent ${parentDepName}`
           );
-          return null;
+          return { status: 'update-failed' };
         }
       } else if (depType) {
         // The constaint comes from the package.json file, so we need to update it
@@ -162,15 +162,16 @@ export async function updateLockedDependency(
         true
       );
       // istanbul ignore if: hard to test due to recursion
-      if (!parentUpdateResult) {
+      if (!parentUpdateResult.files) {
         logger.debug(
           `Update of ${depName} to ${newVersion} impossible due to failed update of parent ${parentUpdate.depName} to ${parentUpdate.newVersion}`
         );
-        return null;
+        return { status: 'update-failed' };
       }
       newPackageJsonContent =
-        parentUpdateResult[packageFile] || newPackageJsonContent;
-      newLockFileContent = parentUpdateResult[lockFile] || newLockFileContent;
+        parentUpdateResult.files[packageFile] || newPackageJsonContent;
+      newLockFileContent =
+        parentUpdateResult.files[lockFile] || newLockFileContent;
     }
     const files = {};
     if (newLockFileContent) {
@@ -179,9 +180,9 @@ export async function updateLockedDependency(
     if (newPackageJsonContent) {
       files[packageFile] = newPackageJsonContent;
     }
-    return files;
+    return { status: 'updated', files };
   } catch (err) /* istanbul ignore next */ {
     logger.error({ err }, 'updateLockedDependency() error');
-    return null;
+    return { status: 'update-failed' };
   }
 }
diff --git a/lib/manager/types.ts b/lib/manager/types.ts
index 036ae7ce56..22a154a290 100644
--- a/lib/manager/types.ts
+++ b/lib/manager/types.ts
@@ -230,6 +230,11 @@ export interface UpdateLockedConfig {
   newVersion?: string;
 }
 
+export interface UpdateLockedResult {
+  status: 'updated' | 'update-failed';
+  files?: Record<string, string>;
+}
+
 export interface GlobalManagerConfig {
   npmrc?: string;
   npmrcMerge?: boolean;
@@ -271,7 +276,7 @@ export interface ManagerApi extends ModuleApi {
 
   updateLockedDependency?(
     config: UpdateLockedConfig
-  ): Result<Record<string, string | null>>;
+  ): Result<UpdateLockedResult>;
 }
 
 // TODO: name and properties used by npm manager
diff --git a/lib/workers/branch/get-updated.spec.ts b/lib/workers/branch/get-updated.spec.ts
index 8e9b19a2c4..e8633cc31a 100644
--- a/lib/workers/branch/get-updated.spec.ts
+++ b/lib/workers/branch/get-updated.spec.ts
@@ -160,7 +160,8 @@ describe('workers/branch/get-updated', () => {
         isRemediation: true,
       } as never);
       npm.updateLockedDependency.mockResolvedValueOnce({
-        'package-lock.json': 'new contents',
+        status: 'updated',
+        files: { 'package-lock.json': 'new contents' },
       });
       const res = await getUpdatedPackageFiles(config);
       expect(res).toMatchSnapshot({
@@ -177,7 +178,8 @@ describe('workers/branch/get-updated', () => {
       config.reuseExistingBranch = true;
       git.getFile.mockResolvedValueOnce('existing content');
       npm.updateLockedDependency.mockResolvedValue({
-        'package-lock.json': 'new contents',
+        status: 'updated',
+        files: { 'package-lock.json': 'new contents' },
       });
       const res = await getUpdatedPackageFiles(config);
       expect(res).toMatchSnapshot({
diff --git a/lib/workers/branch/get-updated.ts b/lib/workers/branch/get-updated.ts
index 2bd880163a..8bc474b7d1 100644
--- a/lib/workers/branch/get-updated.ts
+++ b/lib/workers/branch/get-updated.ts
@@ -74,7 +74,7 @@ export async function getUpdatedPackageFiles(
         });
       }
       const updateLockedDependency = get(manager, 'updateLockedDependency');
-      const files = await updateLockedDependency({
+      const { files } = await updateLockedDependency({
         ...upgrade,
         packageFileContent,
         lockFileContent,
-- 
GitLab