From 5db51f52a2e2f0b2be086344dc5460dd7afdbfaa Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Sat, 24 Jun 2023 10:43:32 +0200
Subject: [PATCH] fix(node): allow unstable updates within same major (#22955)

---
 lib/modules/versioning/index.spec.ts          |  1 +
 lib/modules/versioning/node/index.ts          |  1 +
 lib/modules/versioning/types.ts               |  5 ++++
 .../repository/process/lookup/filter.ts       | 25 +++++++++++++------
 .../repository/process/lookup/index.spec.ts   | 23 +++++++++++++++++
 5 files changed, 47 insertions(+), 8 deletions(-)

diff --git a/lib/modules/versioning/index.spec.ts b/lib/modules/versioning/index.spec.ts
index 9fe12ce0d6..017d198e1b 100644
--- a/lib/modules/versioning/index.spec.ts
+++ b/lib/modules/versioning/index.spec.ts
@@ -73,6 +73,7 @@ describe('modules/versioning/index', () => {
 
   describe('should return the same interface', () => {
     const optionalFunctions = [
+      'allowUnstableMajorUpgrades',
       'isLessThanRange',
       'valueToVersion',
       'constructor',
diff --git a/lib/modules/versioning/node/index.ts b/lib/modules/versioning/node/index.ts
index d243a44259..35eb921153 100644
--- a/lib/modules/versioning/node/index.ts
+++ b/lib/modules/versioning/node/index.ts
@@ -85,6 +85,7 @@ export const api: VersioningApi = {
   matches,
   getSatisfyingVersion,
   minSatisfyingVersion,
+  allowUnstableMajorUpgrades: true,
 };
 
 export default api;
diff --git a/lib/modules/versioning/types.ts b/lib/modules/versioning/types.ts
index a1808b08cb..6c9807d7c3 100644
--- a/lib/modules/versioning/types.ts
+++ b/lib/modules/versioning/types.ts
@@ -106,6 +106,11 @@ export interface VersioningApi {
    * @param superRange - the dom range
    */
   subset?(subRange: string, superRange: string): boolean | undefined;
+
+  /**
+   * Return whether unstable-to-unstable upgrades within the same major version are allowed.
+   */
+  allowUnstableMajorUpgrades?: boolean;
 }
 
 export interface VersioningApiConstructor {
diff --git a/lib/workers/repository/process/lookup/filter.ts b/lib/workers/repository/process/lookup/filter.ts
index 50d1959f0d..e94cfbca15 100644
--- a/lib/workers/repository/process/lookup/filter.ts
+++ b/lib/workers/repository/process/lookup/filter.ts
@@ -131,12 +131,21 @@ export function filterVersions(
 
   // if current is unstable then allow unstable in the current major only
   // Allow unstable only in current major
-  return filteredVersions.filter(
-    (v) =>
-      isVersionStable(v.version) ||
-      (versioning.getMajor(v.version) === versioning.getMajor(currentVersion) &&
-        versioning.getMinor(v.version) ===
-          versioning.getMinor(currentVersion) &&
-        versioning.getPatch(v.version) === versioning.getPatch(currentVersion))
-  );
+  return filteredVersions.filter((v) => {
+    if (isVersionStable(v.version)) {
+      return true;
+    }
+    if (
+      versioning.getMajor(v.version) !== versioning.getMajor(currentVersion)
+    ) {
+      return false;
+    }
+    if (versioning.allowUnstableMajorUpgrades) {
+      return true;
+    }
+    return (
+      versioning.getMinor(v.version) === versioning.getMinor(currentVersion) &&
+      versioning.getPatch(v.version) === versioning.getPatch(currentVersion)
+    );
+  });
 }
diff --git a/lib/workers/repository/process/lookup/index.spec.ts b/lib/workers/repository/process/lookup/index.spec.ts
index 6a3122b538..473179f018 100644
--- a/lib/workers/repository/process/lookup/index.spec.ts
+++ b/lib/workers/repository/process/lookup/index.spec.ts
@@ -13,6 +13,7 @@ import { PackagistDatasource } from '../../../../modules/datasource/packagist';
 import { PypiDatasource } from '../../../../modules/datasource/pypi';
 import { id as dockerVersioningId } from '../../../../modules/versioning/docker';
 import { id as gitVersioningId } from '../../../../modules/versioning/git';
+import { id as nodeVersioningId } from '../../../../modules/versioning/node';
 import { id as npmVersioningId } from '../../../../modules/versioning/npm';
 import { id as pep440VersioningId } from '../../../../modules/versioning/pep440';
 import { id as poetryVersioningId } from '../../../../modules/versioning/poetry';
@@ -45,6 +46,11 @@ describe('workers/repository/process/lookup/index', () => {
     'getReleases'
   );
 
+  const getGithubTags = jest.spyOn(
+    GithubTagsDatasource.prototype,
+    'getReleases'
+  );
+
   const getDockerReleases = jest.spyOn(
     DockerDatasource.prototype,
     'getReleases'
@@ -999,6 +1005,23 @@ describe('workers/repository/process/lookup/index', () => {
       ]);
     });
 
+    it('should allow unstable versions in same major for node', async () => {
+      config.currentValue = '20.3.0';
+      config.packageName = 'node';
+      config.datasource = GithubTagsDatasource.id;
+      config.versioning = nodeVersioningId;
+      getGithubTags.mockResolvedValueOnce({
+        releases: [
+          { version: '20.3.0' },
+          { version: '20.3.1' },
+          { version: '21.0.0' },
+        ],
+      });
+      expect((await lookup.lookupUpdates(config)).updates).toMatchObject([
+        { newValue: '20.3.1', updateType: 'patch' },
+      ]);
+    });
+
     it('should return pendingChecks', async () => {
       config.currentValue = '1.4.4';
       config.packageName = 'some/action';
-- 
GitLab