From c56f5b9e72d14c68a7c2dcd36fa0eacce8bb687c Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Mon, 28 Mar 2022 10:55:14 +0200
Subject: [PATCH] fix(npm): allow for missing dist-tags/latest (#14821)

Closes #14814
---
 lib/config/presets/npm/index.ts        |  4 ++--
 lib/modules/datasource/npm/get.spec.ts | 21 +++++++++++++++++++++
 lib/modules/datasource/npm/get.ts      |  8 ++++----
 lib/modules/datasource/npm/types.ts    |  2 +-
 4 files changed, 28 insertions(+), 7 deletions(-)

diff --git a/lib/config/presets/npm/index.ts b/lib/config/presets/npm/index.ts
index 6c9b5cc508..d78e5dd3b8 100644
--- a/lib/config/presets/npm/index.ts
+++ b/lib/config/presets/npm/index.ts
@@ -31,11 +31,11 @@ export async function getPreset({
       );
     }
     const body = (await http.getJson<NpmResponse>(packageUrl)).body;
-    dep = body.versions[body['dist-tags'].latest];
+    dep = body.versions[body['dist-tags']?.latest];
   } catch (err) {
     throw new Error(PRESET_DEP_NOT_FOUND);
   }
-  if (!dep['renovate-config']) {
+  if (!dep?.['renovate-config']) {
     throw new Error(PRESET_RENOVATE_CONFIG_NOT_FOUND);
   }
   const presetConfig = dep['renovate-config'][presetName];
diff --git a/lib/modules/datasource/npm/get.spec.ts b/lib/modules/datasource/npm/get.spec.ts
index 1c3dce29be..179a7711b5 100644
--- a/lib/modules/datasource/npm/get.spec.ts
+++ b/lib/modules/datasource/npm/get.spec.ts
@@ -286,6 +286,27 @@ describe('modules/datasource/npm/get', () => {
     `);
   });
 
+  it('handles missing dist-tags latest', async () => {
+    setNpmrc('registry=https://test.org\n_authToken=XXX');
+
+    httpMock
+      .scope('https://test.org')
+      .get('/@neutrinojs%2Freact')
+      .reply(200, {
+        name: '@neutrinojs/react',
+        repository: {
+          type: 'git',
+          url: 'https://github.com/neutrinojs/neutrino/tree/master/packages/react',
+        },
+        versions: { '1.0.0': {} },
+      });
+    const registryUrl = resolveRegistryUrl('@neutrinojs/react');
+    const dep = await getDependency(http, registryUrl, '@neutrinojs/react');
+
+    expect(dep.sourceUrl).toBe('https://github.com/neutrinojs/neutrino');
+    expect(dep.sourceDirectory).toBe('packages/react');
+  });
+
   it('handles mixed sourceUrls in releases', async () => {
     setNpmrc('registry=https://test.org\n_authToken=XXX');
 
diff --git a/lib/modules/datasource/npm/get.ts b/lib/modules/datasource/npm/get.ts
index 44d1c7aa93..169b8330cd 100644
--- a/lib/modules/datasource/npm/get.ts
+++ b/lib/modules/datasource/npm/get.ts
@@ -87,9 +87,9 @@ export async function getDependency(
       return null;
     }
 
-    const latestVersion = res.versions[res['dist-tags'].latest];
-    res.repository = res.repository || latestVersion.repository;
-    res.homepage = res.homepage || latestVersion.homepage;
+    const latestVersion = res.versions[res['dist-tags']?.latest];
+    res.repository = res.repository || latestVersion?.repository;
+    res.homepage = res.homepage || latestVersion?.homepage;
 
     const { sourceUrl, sourceDirectory } = getPackageSource(res.repository);
 
@@ -105,7 +105,7 @@ export async function getDependency(
       registryUrl,
     };
 
-    if (latestVersion.deprecated) {
+    if (latestVersion?.deprecated) {
       dep.deprecationMessage = `On registry \`${registryUrl}\`, the "latest" version of dependency \`${packageName}\` has the following deprecation notice:\n\n\`${latestVersion.deprecated}\`\n\nMarking the latest version of an npm package as deprecated results in the entire package being considered deprecated, so contact the package author you think this is a mistake.`;
       dep.deprecationSource = id;
     }
diff --git a/lib/modules/datasource/npm/types.ts b/lib/modules/datasource/npm/types.ts
index 10b600a75b..facc963e7a 100644
--- a/lib/modules/datasource/npm/types.ts
+++ b/lib/modules/datasource/npm/types.ts
@@ -42,7 +42,7 @@ export interface NpmDependency extends ReleaseResult {
   homepage: string;
   sourceUrl: string;
   versions: Record<string, any>;
-  'dist-tags': Record<string, string>;
+  'dist-tags'?: Record<string, string>;
   sourceDirectory?: string;
 }
 
-- 
GitLab