From 3b472a4e313e4d0d540a6d45b61a71ad187dedd0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:52:24 +0000 Subject: [PATCH] Move pagination safeguard to non-JetBrains installers --- .../distributors/adopt-installer.test.ts | 29 +++++++++++++++++ .../distributors/jetbrains-installer.test.ts | 31 ------------------- .../distributors/semeru-installer.test.ts | 26 ++++++++++++++++ .../distributors/temurin-installer.test.ts | 29 +++++++++++++++++ src/distributions/adopt/installer.ts | 11 +++++++ src/distributions/jetbrains/installer.ts | 10 +----- src/distributions/semeru/installer.ts | 11 +++++++ src/distributions/temurin/installer.ts | 11 +++++++ 8 files changed, 118 insertions(+), 40 deletions(-) diff --git a/__tests__/distributors/adopt-installer.test.ts b/__tests__/distributors/adopt-installer.test.ts index fa96279d..5442d3f6 100644 --- a/__tests__/distributors/adopt-installer.test.ts +++ b/__tests__/distributors/adopt-installer.test.ts @@ -14,6 +14,7 @@ import * as core from '@actions/core'; describe('getAvailableVersions', () => { let spyHttpClient: jest.SpyInstance; let spyCoreError: jest.SpyInstance; + let spyCoreWarning: jest.SpyInstance; beforeEach(() => { spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); @@ -26,6 +27,8 @@ describe('getAvailableVersions', () => { // Mock core.error to suppress error logs spyCoreError = jest.spyOn(core, 'error'); spyCoreError.mockImplementation(() => {}); + spyCoreWarning = jest.spyOn(core, 'warning'); + spyCoreWarning.mockImplementation(() => {}); }); afterEach(() => { @@ -166,6 +169,32 @@ describe('getAvailableVersions', () => { expect(spyHttpClient).toHaveBeenNthCalledWith(2, nextPageUrl); }); + it('stops pagination after 1000 pages as a safeguard', async () => { + const nextPageUrl = 'https://example.com/releases?page=2'; + spyHttpClient.mockReturnValue({ + statusCode: 200, + headers: {link: `<${nextPageUrl}>; rel="next"`}, + result: [{version_data: {semver: '17.0.1'}, binaries: []}] as any + }); + + const distribution = new AdoptDistribution( + { + version: '11', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }, + AdoptImplementation.Hotspot + ); + + await distribution['getAvailableVersions'](); + + expect(spyHttpClient).toHaveBeenCalledTimes(1000); + expect(spyCoreWarning).toHaveBeenCalledWith( + expect.stringContaining('Reached pagination safeguard limit (1000 pages)') + ); + }); + it.each([ [AdoptImplementation.Hotspot, 'jdk', 'Java_Adopt_jdk'], [AdoptImplementation.Hotspot, 'jre', 'Java_Adopt_jre'], diff --git a/__tests__/distributors/jetbrains-installer.test.ts b/__tests__/distributors/jetbrains-installer.test.ts index afc41c47..bf1dc15d 100644 --- a/__tests__/distributors/jetbrains-installer.test.ts +++ b/__tests__/distributors/jetbrains-installer.test.ts @@ -9,8 +9,6 @@ import * as core from '@actions/core'; describe('getAvailableVersions', () => { let spyHttpClient: jest.SpyInstance; let spyCoreError: jest.SpyInstance; - let spyCoreWarning: jest.SpyInstance; - let spyHeadRequest: jest.SpyInstance; beforeEach(() => { spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); @@ -23,14 +21,6 @@ describe('getAvailableVersions', () => { // Mock core.error to suppress error logs spyCoreError = jest.spyOn(core, 'error'); spyCoreError.mockImplementation(() => {}); - spyCoreWarning = jest.spyOn(core, 'warning'); - spyCoreWarning.mockImplementation(() => {}); - spyHeadRequest = jest.spyOn(HttpClient.prototype, 'head'); - spyHeadRequest.mockResolvedValue({ - message: { - statusCode: 404 - } - } as any); }); afterEach(() => { @@ -60,27 +50,6 @@ describe('getAvailableVersions', () => { os.platform() === 'win32' ? manifestData.length : manifestData.length + 2; expect(availableVersions.length).toBe(length); }, 10_000); - - it('stops pagination after 1000 pages as a safeguard', async () => { - spyHttpClient.mockResolvedValue({ - statusCode: 200, - headers: {}, - result: [{tag_name: 'jbr17-b87.7', name: 'jbr17-b87.7', prerelease: false}] - }); - - const distribution = new JetBrainsDistribution({ - version: '17', - architecture: 'x64', - packageType: 'jdk', - checkLatest: false - }); - await distribution['getAvailableVersions'](); - - expect(spyHttpClient).toHaveBeenCalledTimes(1000); - expect(spyCoreWarning).toHaveBeenCalledWith( - expect.stringContaining('Reached pagination safeguard limit (1000 pages)') - ); - }, 20_000); }); describe('findPackageForDownload', () => { diff --git a/__tests__/distributors/semeru-installer.test.ts b/__tests__/distributors/semeru-installer.test.ts index d415e747..ffef9530 100644 --- a/__tests__/distributors/semeru-installer.test.ts +++ b/__tests__/distributors/semeru-installer.test.ts @@ -9,6 +9,7 @@ import * as core from '@actions/core'; describe('getAvailableVersions', () => { let spyHttpClient: jest.SpyInstance; let spyCoreError: jest.SpyInstance; + let spyCoreWarning: jest.SpyInstance; beforeEach(() => { spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); @@ -20,6 +21,8 @@ describe('getAvailableVersions', () => { // Mock core.error to suppress error logs spyCoreError = jest.spyOn(core, 'error'); spyCoreError.mockImplementation(() => {}); + spyCoreWarning = jest.spyOn(core, 'warning'); + spyCoreWarning.mockImplementation(() => {}); }); afterEach(() => { @@ -109,6 +112,29 @@ describe('getAvailableVersions', () => { expect(spyHttpClient).toHaveBeenNthCalledWith(2, nextPageUrl); }); + it('stops pagination after 1000 pages as a safeguard', async () => { + const nextPageUrl = 'https://example.com/releases?page=2'; + spyHttpClient.mockReturnValue({ + statusCode: 200, + headers: {link: `<${nextPageUrl}>; rel="next"`}, + result: [{version_data: {semver: '17.0.1'}, binaries: []}] as any + }); + + const distribution = new SemeruDistribution({ + version: '8', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }); + + await distribution['getAvailableVersions'](); + + expect(spyHttpClient).toHaveBeenCalledTimes(1000); + expect(spyCoreWarning).toHaveBeenCalledWith( + expect.stringContaining('Reached pagination safeguard limit (1000 pages)') + ); + }); + it.each([ ['jdk', 'Java_IBM_Semeru_jdk'], ['jre', 'Java_IBM_Semeru_jre'] diff --git a/__tests__/distributors/temurin-installer.test.ts b/__tests__/distributors/temurin-installer.test.ts index 93930179..49a52a7d 100644 --- a/__tests__/distributors/temurin-installer.test.ts +++ b/__tests__/distributors/temurin-installer.test.ts @@ -12,6 +12,7 @@ import * as core from '@actions/core'; describe('getAvailableVersions', () => { let spyHttpClient: jest.SpyInstance; let spyCoreError: jest.SpyInstance; + let spyCoreWarning: jest.SpyInstance; beforeEach(() => { spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); @@ -23,6 +24,8 @@ describe('getAvailableVersions', () => { // Mock core.error to suppress error logs spyCoreError = jest.spyOn(core, 'error'); spyCoreError.mockImplementation(() => {}); + spyCoreWarning = jest.spyOn(core, 'warning'); + spyCoreWarning.mockImplementation(() => {}); }); afterEach(() => { @@ -123,6 +126,32 @@ describe('getAvailableVersions', () => { expect(spyHttpClient).toHaveBeenNthCalledWith(2, nextPageUrl); }); + it('stops pagination after 1000 pages as a safeguard', async () => { + const nextPageUrl = 'https://example.com/releases?page=2'; + spyHttpClient.mockReturnValue({ + statusCode: 200, + headers: {link: `<${nextPageUrl}>; rel="next"`}, + result: [{version_data: {semver: '17.0.1'}, binaries: []}] as any + }); + + const distribution = new TemurinDistribution( + { + version: '8', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }, + TemurinImplementation.Hotspot + ); + + await distribution['getAvailableVersions'](); + + expect(spyHttpClient).toHaveBeenCalledTimes(1000); + expect(spyCoreWarning).toHaveBeenCalledWith( + expect.stringContaining('Reached pagination safeguard limit (1000 pages)') + ); + }); + it.each([ [TemurinImplementation.Hotspot, 'jdk', 'Java_Temurin-Hotspot_jdk'], [TemurinImplementation.Hotspot, 'jre', 'Java_Temurin-Hotspot_jre'] diff --git a/src/distributions/adopt/installer.ts b/src/distributions/adopt/installer.ts index 5624cf5c..3827a0c8 100644 --- a/src/distributions/adopt/installer.ts +++ b/src/distributions/adopt/installer.ts @@ -20,6 +20,8 @@ import { renameWinArchive } from '../../util'; +const MAX_PAGINATION_PAGES = 1000; + export enum AdoptImplementation { Hotspot = 'Hotspot', OpenJ9 = 'OpenJ9' @@ -129,11 +131,13 @@ export class AdoptDistribution extends JavaBase { const requestArguments = `${baseRequestArguments}&page_size=20&page=0`; let availableVersionsUrl: string | null = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`; const availableVersions: IAdoptAvailableVersions[] = []; + let pageCount = 0; if (core.isDebug()) { core.debug(`Gathering available versions from '${availableVersionsUrl}'`); } while (availableVersionsUrl) { + pageCount++; const response = await this.http.getJson(availableVersionsUrl); const paginationPage = response.result; @@ -143,6 +147,13 @@ export class AdoptDistribution extends JavaBase { } availableVersions.push(...paginationPage); + + if (pageCount >= MAX_PAGINATION_PAGES && availableVersionsUrl) { + core.warning( + `Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing Adopt releases.` + ); + break; + } } if (core.isDebug()) { diff --git a/src/distributions/jetbrains/installer.ts b/src/distributions/jetbrains/installer.ts index 15c4cd6f..e3458e1e 100644 --- a/src/distributions/jetbrains/installer.ts +++ b/src/distributions/jetbrains/installer.ts @@ -16,8 +16,6 @@ import {extractJdkFile, isVersionSatisfies} from '../../util'; import {OutgoingHttpHeaders} from 'http'; import {HttpCodes} from '@actions/http-client'; -const MAX_PAGINATION_PAGES = 1000; - export class JetBrainsDistribution extends JavaBase { constructor(installerOptions: JavaInstallerOptions) { super('JetBrains', installerOptions); @@ -95,7 +93,7 @@ export class JetBrainsDistribution extends JavaBase { const rawVersions: IJetBrainsRawVersion[] = []; const bearerToken = process.env.GITHUB_TOKEN; - while (page_index <= MAX_PAGINATION_PAGES) { + while (true) { const requestArguments = `per_page=100&page=${page_index}`; const requestHeaders: OutgoingHttpHeaders = {}; @@ -131,12 +129,6 @@ export class JetBrainsDistribution extends JavaBase { page_index++; } - if (page_index > MAX_PAGINATION_PAGES) { - core.warning( - `Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing JetBrains runtime releases.` - ); - } - if (this.stable) { // Add versions not available from the API but are downloadable const hidden = ['11_0_10b1145.115', '11_0_11b1341.60']; diff --git a/src/distributions/semeru/installer.ts b/src/distributions/semeru/installer.ts index 9b161909..4b1317fa 100644 --- a/src/distributions/semeru/installer.ts +++ b/src/distributions/semeru/installer.ts @@ -18,6 +18,8 @@ import fs from 'fs'; import path from 'path'; import {ISemeruAvailableVersions} from './models'; +const MAX_PAGINATION_PAGES = 1000; + const supportedArchitectures = [ 'x64', 'x86', @@ -159,11 +161,13 @@ export class SemeruDistribution extends JavaBase { const requestArguments = `${baseRequestArguments}&page_size=20&page=0`; let availableVersionsUrl: string | null = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`; const availableVersions: ISemeruAvailableVersions[] = []; + let pageCount = 0; if (core.isDebug()) { core.debug(`Gathering available versions from '${availableVersionsUrl}'`); } while (availableVersionsUrl) { + pageCount++; const response = await this.http.getJson( availableVersionsUrl ); @@ -174,6 +178,13 @@ export class SemeruDistribution extends JavaBase { } availableVersions.push(...paginationPage); + + if (pageCount >= MAX_PAGINATION_PAGES && availableVersionsUrl) { + core.warning( + `Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing Semeru releases.` + ); + break; + } } if (core.isDebug()) { diff --git a/src/distributions/temurin/installer.ts b/src/distributions/temurin/installer.ts index 9b5aaa55..0f566139 100644 --- a/src/distributions/temurin/installer.ts +++ b/src/distributions/temurin/installer.ts @@ -20,6 +20,8 @@ import { renameWinArchive } from '../../util'; +const MAX_PAGINATION_PAGES = 1000; + export enum TemurinImplementation { Hotspot = 'Hotspot' } @@ -127,11 +129,13 @@ export class TemurinDistribution extends JavaBase { const requestArguments = `${baseRequestArguments}&page_size=20&page=0`; let availableVersionsUrl: string | null = `https://api.adoptium.net/v3/assets/version/${versionRange}?${requestArguments}`; const availableVersions: ITemurinAvailableVersions[] = []; + let pageCount = 0; if (core.isDebug()) { core.debug(`Gathering available versions from '${availableVersionsUrl}'`); } while (availableVersionsUrl) { + pageCount++; const response = await this.http.getJson( availableVersionsUrl ); @@ -143,6 +147,13 @@ export class TemurinDistribution extends JavaBase { } availableVersions.push(...paginationPage); + + if (pageCount >= MAX_PAGINATION_PAGES && availableVersionsUrl) { + core.warning( + `Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing Temurin releases.` + ); + break; + } } if (core.isDebug()) {