From feb18948f530cf02720fccad3fa9165754be6666 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Tue, 29 Aug 2023 13:34:19 +0200 Subject: [PATCH] Utilize pagination when querying GraalPy GitHub releases --- __tests__/utils.test.ts | 25 ++++++++++++++++++++++++- src/install-graalpy.ts | 26 +++++++++++++++++--------- src/utils.ts | 23 +++++++++++++++++++++++ 3 files changed, 64 insertions(+), 10 deletions(-) diff --git a/__tests__/utils.test.ts b/__tests__/utils.test.ts index 85b127a4..d294293b 100644 --- a/__tests__/utils.test.ts +++ b/__tests__/utils.test.ts @@ -11,7 +11,8 @@ import { isCacheFeatureAvailable, getVersionInputFromFile, getVersionInputFromPlainFile, - getVersionInputFromTomlFile + getVersionInputFromTomlFile, + getNextPageUrl } from '../src/utils'; jest.mock('@actions/cache'); @@ -136,3 +137,25 @@ describe('Version from file test', () => { } ); }); + +describe('getNextPageUrl', () => { + it('GitHub API pagination next page is parsed correctly', () => { + function generateResponse(link: string) { + return { + statusCode: 200, + result: null, + headers: { + link: link + } + }; + } + const page1Links = + '; rel="next", ; rel="last"'; + expect(getNextPageUrl(generateResponse(page1Links))).toStrictEqual( + 'https://api.github.com/repositories/129883600/releases?page=2' + ); + const page2Links = + '; rel="prev", ; rel="first"'; + expect(getNextPageUrl(generateResponse(page2Links))).toBeNull(); + }); +}); diff --git a/src/install-graalpy.ts b/src/install-graalpy.ts index 8f39521f..05e649e4 100644 --- a/src/install-graalpy.ts +++ b/src/install-graalpy.ts @@ -14,7 +14,8 @@ import { IGraalPyManifestRelease, createSymlinkInFolder, isNightlyKeyword, - getBinaryDirectory + getBinaryDirectory, + getNextPageUrl } from './utils'; const TOKEN = core.getInput('token'); @@ -106,7 +107,6 @@ export async function installGraalPy( } export async function getAvailableGraalPyVersions() { - const url = 'https://api.github.com/repos/oracle/graalpython/releases'; const http: httpm.HttpClient = new httpm.HttpClient('tool-cache'); let headers: ifm.IHeaders = {}; @@ -114,14 +114,22 @@ export async function getAvailableGraalPyVersions() { headers.authorization = AUTH; } - const response = await http.getJson(url, headers); - if (!response.result) { - throw new Error( - `Unable to retrieve the list of available GraalPy versions from '${url}'` - ); - } + let url: string | null = + 'https://api.github.com/repos/oracle/graalpython/releases'; + const result: IGraalPyManifestRelease[] = []; + do { + const response: ifm.ITypedResponse = + await http.getJson(url, headers); + if (!response.result) { + throw new Error( + `Unable to retrieve the list of available GraalPy versions from '${url}'` + ); + } + result.push(...response.result); + url = getNextPageUrl(response); + } while (url); - return response.result; + return result; } async function createGraalPySymlink( diff --git a/src/utils.ts b/src/utils.ts index 2f4f4555..28ad8ef7 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -6,6 +6,7 @@ import * as path from 'path'; import * as semver from 'semver'; import * as toml from '@iarna/toml'; import * as exec from '@actions/exec'; +import * as ifm from '@actions/http-client/interfaces'; export const IS_WINDOWS = process.platform === 'win32'; export const IS_LINUX = process.platform === 'linux'; @@ -271,3 +272,25 @@ export function getVersionInputFromFile(versionFile: string): string[] { export function getBinaryDirectory(installDir: string) { return IS_WINDOWS ? installDir : path.join(installDir, 'bin'); } + +/** + * Extract next page URL from a HTTP response "link" header. Such headers are used in GitHub APIs. + */ +export function getNextPageUrl(response: ifm.ITypedResponse) { + const responseHeaders = response.headers; + const linkHeader = responseHeaders.link; + if (typeof linkHeader === 'string') { + for (let link of linkHeader.split(/\s*,\s*/)) { + const match = link.match(/<([^>]+)>(.*)/); + if (match) { + const url = match[1]; + for (let param of match[2].split(/\s*;\s*/)) { + if (param.match(/rel="?next"?/)) { + return url; + } + } + } + } + } + return null; +}