From 686cb17442446cea83876a70b887a30efc8704d5 Mon Sep 17 00:00:00 2001
From: Sergei Zharinov <zharinov@users.noreply.github.com>
Date: Sat, 7 Jan 2023 11:34:22 +0300
Subject: [PATCH] feat(github-releases): Fetch releases with cacheable GraphQL
 (#19623)

---
 .../datasource/github-releases/index.spec.ts  | 62 +++++++++++++------
 .../datasource/github-releases/index.ts       | 41 ++++++------
 2 files changed, 66 insertions(+), 37 deletions(-)

diff --git a/lib/modules/datasource/github-releases/index.spec.ts b/lib/modules/datasource/github-releases/index.spec.ts
index 74f67079f5..2491c1a71f 100644
--- a/lib/modules/datasource/github-releases/index.spec.ts
+++ b/lib/modules/datasource/github-releases/index.spec.ts
@@ -1,5 +1,5 @@
 import { getDigest, getPkgReleases } from '..';
-import * as httpMock from '../../../../test/http-mock';
+import * as githubGraphql from '../../../util/github/graphql';
 import * as _hostRules from '../../../util/host-rules';
 import { GitHubReleaseMocker } from './test';
 import { GithubReleasesDatasource } from '.';
@@ -9,19 +9,6 @@ const hostRules: any = _hostRules;
 
 const githubApiHost = 'https://api.github.com';
 
-const responseBody = [
-  { tag_name: 'a', published_at: '2020-03-09T13:00:00Z' },
-  { tag_name: 'v', published_at: '2020-03-09T12:00:00Z' },
-  { tag_name: '1.0.0', published_at: '2020-03-09T11:00:00Z' },
-  { tag_name: 'v1.1.0', draft: false, published_at: '2020-03-09T10:00:00Z' },
-  { tag_name: '1.2.0', draft: true, published_at: '2020-03-09T10:00:00Z' },
-  {
-    tag_name: '2.0.0',
-    published_at: '2020-04-09T10:00:00Z',
-    prerelease: true,
-  },
-];
-
 describe('modules/datasource/github-releases/index', () => {
   beforeEach(() => {
     jest.resetAllMocks();
@@ -33,10 +20,49 @@ describe('modules/datasource/github-releases/index', () => {
 
   describe('getReleases', () => {
     it('returns releases', async () => {
-      httpMock
-        .scope(githubApiHost)
-        .get('/repos/some/dep/releases?per_page=100')
-        .reply(200, responseBody);
+      jest.spyOn(githubGraphql, 'queryReleases').mockResolvedValueOnce([
+        {
+          id: 1,
+          url: 'https://example.com',
+          name: 'some/dep2',
+          description: 'some description',
+          version: 'a',
+          releaseTimestamp: '2020-03-09T13:00:00Z',
+        },
+        {
+          id: 2,
+          url: 'https://example.com',
+          name: 'some/dep2',
+          description: 'some description',
+          version: 'v',
+          releaseTimestamp: '2020-03-09T12:00:00Z',
+        },
+        {
+          id: 3,
+          url: 'https://example.com',
+          name: 'some/dep2',
+          description: 'some description',
+          version: '1.0.0',
+          releaseTimestamp: '2020-03-09T11:00:00Z',
+        },
+        {
+          id: 4,
+          url: 'https://example.com',
+          name: 'some/dep2',
+          description: 'some description',
+          version: 'v1.1.0',
+          releaseTimestamp: '2020-03-09T10:00:00Z',
+        },
+        {
+          id: 5,
+          url: 'https://example.com',
+          name: 'some/dep2',
+          description: 'some description',
+          version: '2.0.0',
+          releaseTimestamp: '2020-04-09T10:00:00Z',
+          isStable: false,
+        },
+      ]);
 
       const res = await getPkgReleases({
         datasource: GithubReleasesDatasource.id,
diff --git a/lib/modules/datasource/github-releases/index.ts b/lib/modules/datasource/github-releases/index.ts
index 1972603123..b630ac3d62 100644
--- a/lib/modules/datasource/github-releases/index.ts
+++ b/lib/modules/datasource/github-releases/index.ts
@@ -1,6 +1,8 @@
 // TODO: types (#7154)
+import is from '@sindresorhus/is';
 import hasha from 'hasha';
 import { logger } from '../../../logger';
+import { queryReleases } from '../../../util/github/graphql';
 import type {
   GithubDigestFile,
   GithubRestAsset,
@@ -10,7 +12,12 @@ import { getApiBaseUrl, getSourceUrl } from '../../../util/github/url';
 import { GithubHttp } from '../../../util/http/github';
 import { newlineRegex, regEx } from '../../../util/regex';
 import { Datasource } from '../datasource';
-import type { DigestConfig, GetReleasesConfig, ReleaseResult } from '../types';
+import type {
+  DigestConfig,
+  GetReleasesConfig,
+  Release,
+  ReleaseResult,
+} from '../types';
 
 export const cacheNamespace = 'datasource-github-releases';
 
@@ -213,24 +220,20 @@ export class GithubReleasesDatasource extends Datasource {
    *  - Return a dependency object containing sourceUrl string and releases array
    */
   async getReleases(config: GetReleasesConfig): Promise<ReleaseResult> {
-    const { packageName: repo, registryUrl } = config;
-    const apiBaseUrl = getApiBaseUrl(registryUrl);
-    const url = `${apiBaseUrl}repos/${repo}/releases?per_page=100`;
-    const res = await this.http.getJson<GithubRestRelease[]>(url, {
-      paginate: true,
+    const releasesResult = await queryReleases(config, this.http);
+    const releases = releasesResult.map((item) => {
+      const { version, releaseTimestamp, isStable } = item;
+      const result: Release = {
+        version,
+        gitRef: version,
+        releaseTimestamp,
+      };
+      if (is.boolean(isStable)) {
+        result.isStable = isStable;
+      }
+      return result;
     });
-    const githubReleases = res.body;
-    const dependency: ReleaseResult = {
-      sourceUrl: getSourceUrl(repo, registryUrl),
-      releases: githubReleases
-        .filter(({ draft }) => draft !== true)
-        .map(({ tag_name, published_at, prerelease }) => ({
-          version: tag_name,
-          gitRef: tag_name,
-          releaseTimestamp: published_at,
-          isStable: prerelease ? false : undefined,
-        })),
-    };
-    return dependency;
+    const sourceUrl = getSourceUrl(config.packageName, config.registryUrl);
+    return { sourceUrl, releases };
   }
 }
-- 
GitLab