Merge pull request #1 from johnoliver/copilot/utilize-link-header-pagination

Add pagination failsafe for Adopt based repositories
This commit is contained in:
John 2026-06-03 17:49:56 +01:00 committed by GitHub
commit b06c5c8844
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 340 additions and 126 deletions

View File

@ -14,6 +14,7 @@ import * as core from '@actions/core';
describe('getAvailableVersions', () => { describe('getAvailableVersions', () => {
let spyHttpClient: jest.SpyInstance; let spyHttpClient: jest.SpyInstance;
let spyCoreError: jest.SpyInstance; let spyCoreError: jest.SpyInstance;
let spyCoreWarning: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
@ -26,6 +27,8 @@ describe('getAvailableVersions', () => {
// Mock core.error to suppress error logs // Mock core.error to suppress error logs
spyCoreError = jest.spyOn(core, 'error'); spyCoreError = jest.spyOn(core, 'error');
spyCoreError.mockImplementation(() => {}); spyCoreError.mockImplementation(() => {});
spyCoreWarning = jest.spyOn(core, 'warning');
spyCoreWarning.mockImplementation(() => {});
}); });
afterEach(() => { afterEach(() => {
@ -136,22 +139,19 @@ describe('getAvailableVersions', () => {
); );
it('load available versions', async () => { it('load available versions', async () => {
const nextPageUrl =
'https://api.adoptopenjdk.net/v3/assets/version/%5B1.0,100.0%5D?page=1&page_size=20';
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
spyHttpClient spyHttpClient
.mockReturnValueOnce({ .mockReturnValueOnce({
statusCode: 200, statusCode: 200,
headers: {}, headers: {link: `<${nextPageUrl}>; rel="next"`},
result: manifestData as any result: manifestData as any
}) })
.mockReturnValueOnce({ .mockReturnValueOnce({
statusCode: 200, statusCode: 200,
headers: {}, headers: {},
result: manifestData as any result: manifestData as any
})
.mockReturnValueOnce({
statusCode: 200,
headers: {},
result: []
}); });
const distribution = new AdoptDistribution( const distribution = new AdoptDistribution(
@ -166,6 +166,33 @@ describe('getAvailableVersions', () => {
const availableVersions = await distribution['getAvailableVersions'](); const availableVersions = await distribution['getAvailableVersions']();
expect(availableVersions).not.toBeNull(); expect(availableVersions).not.toBeNull();
expect(availableVersions.length).toBe(manifestData.length * 2); expect(availableVersions.length).toBe(manifestData.length * 2);
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([ it.each([

View File

@ -9,6 +9,7 @@ import * as core from '@actions/core';
describe('getAvailableVersions', () => { describe('getAvailableVersions', () => {
let spyHttpClient: jest.SpyInstance; let spyHttpClient: jest.SpyInstance;
let spyCoreError: jest.SpyInstance; let spyCoreError: jest.SpyInstance;
let spyCoreWarning: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
@ -20,6 +21,8 @@ describe('getAvailableVersions', () => {
// Mock core.error to suppress error logs // Mock core.error to suppress error logs
spyCoreError = jest.spyOn(core, 'error'); spyCoreError = jest.spyOn(core, 'error');
spyCoreError.mockImplementation(() => {}); spyCoreError.mockImplementation(() => {});
spyCoreWarning = jest.spyOn(core, 'warning');
spyCoreWarning.mockImplementation(() => {});
}); });
afterEach(() => { afterEach(() => {
@ -82,22 +85,19 @@ describe('getAvailableVersions', () => {
); );
it('load available versions', async () => { it('load available versions', async () => {
const nextPageUrl =
'https://api.adoptopenjdk.net/v3/assets/version/%5B1.0,100.0%5D?page=1&page_size=20';
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
spyHttpClient spyHttpClient
.mockReturnValueOnce({ .mockReturnValueOnce({
statusCode: 200, statusCode: 200,
headers: {}, headers: {link: `<${nextPageUrl}>; rel="next"`},
result: manifestData as any result: manifestData as any
}) })
.mockReturnValueOnce({ .mockReturnValueOnce({
statusCode: 200, statusCode: 200,
headers: {}, headers: {},
result: manifestData as any result: manifestData as any
})
.mockReturnValueOnce({
statusCode: 200,
headers: {},
result: []
}); });
const distribution = new SemeruDistribution({ const distribution = new SemeruDistribution({
@ -109,6 +109,30 @@ describe('getAvailableVersions', () => {
const availableVersions = await distribution['getAvailableVersions'](); const availableVersions = await distribution['getAvailableVersions']();
expect(availableVersions).not.toBeNull(); expect(availableVersions).not.toBeNull();
expect(availableVersions.length).toBe(manifestData.length * 2); expect(availableVersions.length).toBe(manifestData.length * 2);
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([ it.each([

View File

@ -12,6 +12,7 @@ import * as core from '@actions/core';
describe('getAvailableVersions', () => { describe('getAvailableVersions', () => {
let spyHttpClient: jest.SpyInstance; let spyHttpClient: jest.SpyInstance;
let spyCoreError: jest.SpyInstance; let spyCoreError: jest.SpyInstance;
let spyCoreWarning: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
@ -23,6 +24,8 @@ describe('getAvailableVersions', () => {
// Mock core.error to suppress error logs // Mock core.error to suppress error logs
spyCoreError = jest.spyOn(core, 'error'); spyCoreError = jest.spyOn(core, 'error');
spyCoreError.mockImplementation(() => {}); spyCoreError.mockImplementation(() => {});
spyCoreWarning = jest.spyOn(core, 'warning');
spyCoreWarning.mockImplementation(() => {});
}); });
afterEach(() => { afterEach(() => {
@ -93,22 +96,19 @@ describe('getAvailableVersions', () => {
); );
it('load available versions', async () => { it('load available versions', async () => {
const nextPageUrl =
'https://api.adoptium.net/v3/assets/version/%5B1.0,100.0%5D?page=1&page_size=20';
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
spyHttpClient spyHttpClient
.mockReturnValueOnce({ .mockReturnValueOnce({
statusCode: 200, statusCode: 200,
headers: {}, headers: {link: `<${nextPageUrl}>; rel="next"`},
result: manifestData as any result: manifestData as any
}) })
.mockReturnValueOnce({ .mockReturnValueOnce({
statusCode: 200, statusCode: 200,
headers: {}, headers: {},
result: manifestData as any result: manifestData as any
})
.mockReturnValueOnce({
statusCode: 200,
headers: {},
result: []
}); });
const distribution = new TemurinDistribution( const distribution = new TemurinDistribution(
@ -123,6 +123,33 @@ describe('getAvailableVersions', () => {
const availableVersions = await distribution['getAvailableVersions'](); const availableVersions = await distribution['getAvailableVersions']();
expect(availableVersions).not.toBeNull(); expect(availableVersions).not.toBeNull();
expect(availableVersions.length).toBe(manifestData.length * 2); expect(availableVersions.length).toBe(manifestData.length * 2);
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([ it.each([

View File

@ -4,6 +4,7 @@ import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import { import {
convertVersionToSemver, convertVersionToSemver,
getNextPageUrlFromLinkHeader,
getVersionFromFileContent, getVersionFromFileContent,
isVersionSatisfies, isVersionSatisfies,
isCacheFeatureAvailable, isCacheFeatureAvailable,
@ -85,6 +86,27 @@ describe('convertVersionToSemver', () => {
}); });
}); });
describe('getNextPageUrlFromLinkHeader', () => {
it.each([
[
{
link: '<https://api.adoptium.net/v3/info/release_versions?page=1&page_size=10>; rel="next"'
},
'https://api.adoptium.net/v3/info/release_versions?page=1&page_size=10'
],
[
{
Link: '<https://example.com/last?page=5>; rel="last", <https://example.com/next?page=2>; rel="next"'
},
'https://example.com/next?page=2'
],
[{link: '<https://example.com/last?page=5>; rel="last"'}, null],
[undefined, null]
])('returns %s -> %s', (headers, expected) => {
expect(getNextPageUrlFromLinkHeader(headers)).toBe(expected);
});
});
describe('getVersionFromFileContent', () => { describe('getVersionFromFileContent', () => {
describe('.sdkmanrc', () => { describe('.sdkmanrc', () => {
it.each([ it.each([

18
dist/cleanup/index.js vendored
View File

@ -51888,7 +51888,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.renameWinArchive = exports.getGitHubHttpHeaders = exports.convertVersionToSemver = exports.getVersionFromFileContent = exports.isCacheFeatureAvailable = exports.isGhes = exports.isJobStatusSuccess = exports.getToolcachePath = exports.isVersionSatisfies = exports.getDownloadArchiveExtension = exports.extractJdkFile = exports.getVersionFromToolcachePath = exports.getBooleanInput = exports.getTempDir = void 0; exports.renameWinArchive = exports.getNextPageUrlFromLinkHeader = exports.getGitHubHttpHeaders = exports.convertVersionToSemver = exports.getVersionFromFileContent = exports.isCacheFeatureAvailable = exports.isGhes = exports.isJobStatusSuccess = exports.getToolcachePath = exports.isVersionSatisfies = exports.getDownloadArchiveExtension = exports.extractJdkFile = exports.getVersionFromToolcachePath = exports.getBooleanInput = exports.getTempDir = void 0;
const os_1 = __importDefault(__nccwpck_require__(22037)); const os_1 = __importDefault(__nccwpck_require__(22037));
const path_1 = __importDefault(__nccwpck_require__(71017)); const path_1 = __importDefault(__nccwpck_require__(71017));
const fs = __importStar(__nccwpck_require__(57147)); const fs = __importStar(__nccwpck_require__(57147));
@ -52055,6 +52055,22 @@ function getGitHubHttpHeaders() {
return headers; return headers;
} }
exports.getGitHubHttpHeaders = getGitHubHttpHeaders; exports.getGitHubHttpHeaders = getGitHubHttpHeaders;
function getNextPageUrlFromLinkHeader(headers) {
var _a, _b;
if (!headers) {
return null;
}
const linkHeader = (_a = headers.link) !== null && _a !== void 0 ? _a : headers.Link;
if (!linkHeader) {
return null;
}
const normalizedLinkHeader = Array.isArray(linkHeader)
? linkHeader.join(',')
: linkHeader;
const nextLinkMatch = normalizedLinkHeader.match(/<([^>]+)>\s*;\s*rel="?next"?/i);
return (_b = nextLinkMatch === null || nextLinkMatch === void 0 ? void 0 : nextLinkMatch[1]) !== null && _b !== void 0 ? _b : null;
}
exports.getNextPageUrlFromLinkHeader = getNextPageUrlFromLinkHeader;
// Rename archive to add extension because after downloading // Rename archive to add extension because after downloading
// archive does not contain extension type and it leads to some issues // archive does not contain extension type and it leads to some issues
// on Windows runners without PowerShell Core. // on Windows runners without PowerShell Core.

105
dist/setup/index.js vendored
View File

@ -77569,6 +77569,7 @@ const path_1 = __importDefault(__nccwpck_require__(71017));
const semver_1 = __importDefault(__nccwpck_require__(11383)); const semver_1 = __importDefault(__nccwpck_require__(11383));
const base_installer_1 = __nccwpck_require__(59741); const base_installer_1 = __nccwpck_require__(59741);
const util_1 = __nccwpck_require__(92629); const util_1 = __nccwpck_require__(92629);
const MAX_PAGINATION_PAGES = 1000;
var AdoptImplementation; var AdoptImplementation;
(function (AdoptImplementation) { (function (AdoptImplementation) {
AdoptImplementation["Hotspot"] = "Hotspot"; AdoptImplementation["Hotspot"] = "Hotspot";
@ -77650,24 +77651,26 @@ class AdoptDistribution extends base_installer_1.JavaBase {
`release_type=${releaseType}`, `release_type=${releaseType}`,
`jvm_impl=${this.jvmImpl.toLowerCase()}` `jvm_impl=${this.jvmImpl.toLowerCase()}`
].join('&'); ].join('&');
// need to iterate through all pages to retrieve the list of all versions const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
// Adopt API doesn't provide way to retrieve the count of pages to iterate so infinity loop let availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
let page_index = 0;
const availableVersions = []; const availableVersions = [];
while (true) { let pageCount = 0;
const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`; if (core.isDebug()) {
const availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`; core.debug(`Gathering available versions from '${availableVersionsUrl}'`);
if (core.isDebug() && page_index === 0) { }
// url is identical except page_index so print it once for debug while (availableVersionsUrl) {
core.debug(`Gathering available versions from '${availableVersionsUrl}'`); pageCount++;
} const response = yield this.http.getJson(availableVersionsUrl);
const paginationPage = (yield this.http.getJson(availableVersionsUrl)).result; const paginationPage = response.result;
availableVersionsUrl = (0, util_1.getNextPageUrlFromLinkHeader)(response.headers);
if (paginationPage === null || paginationPage.length === 0) { if (paginationPage === null || paginationPage.length === 0) {
// break infinity loop because we have reached end of pagination
break; break;
} }
availableVersions.push(...paginationPage); availableVersions.push(...paginationPage);
page_index++; if (pageCount >= MAX_PAGINATION_PAGES) {
core.warning(`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing Adopt releases.`);
break;
}
} }
if (core.isDebug()) { if (core.isDebug()) {
core.startGroup('Print information about available versions'); core.startGroup('Print information about available versions');
@ -79730,6 +79733,7 @@ const core = __importStar(__nccwpck_require__(42186));
const tc = __importStar(__nccwpck_require__(27784)); const tc = __importStar(__nccwpck_require__(27784));
const fs_1 = __importDefault(__nccwpck_require__(57147)); const fs_1 = __importDefault(__nccwpck_require__(57147));
const path_1 = __importDefault(__nccwpck_require__(71017)); const path_1 = __importDefault(__nccwpck_require__(71017));
const MAX_PAGINATION_PAGES = 1000;
const supportedArchitectures = [ const supportedArchitectures = [
'x64', 'x64',
'x86', 'x86',
@ -79825,24 +79829,26 @@ class SemeruDistribution extends base_installer_1.JavaBase {
`release_type=${releaseType}`, `release_type=${releaseType}`,
`jvm_impl=openj9` `jvm_impl=openj9`
].join('&'); ].join('&');
// need to iterate through all pages to retrieve the list of all versions const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
// Adoptium API doesn't provide way to retrieve the count of pages to iterate so infinity loop let availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
let page_index = 0;
const availableVersions = []; const availableVersions = [];
while (true) { let pageCount = 0;
const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`; if (core.isDebug()) {
const availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`; core.debug(`Gathering available versions from '${availableVersionsUrl}'`);
if (core.isDebug() && page_index === 0) { }
// url is identical except page_index so print it once for debug while (availableVersionsUrl) {
core.debug(`Gathering available versions from '${availableVersionsUrl}'`); pageCount++;
} const response = yield this.http.getJson(availableVersionsUrl);
const paginationPage = (yield this.http.getJson(availableVersionsUrl)).result; const paginationPage = response.result;
availableVersionsUrl = (0, util_1.getNextPageUrlFromLinkHeader)(response.headers);
if (paginationPage === null || paginationPage.length === 0) { if (paginationPage === null || paginationPage.length === 0) {
// break infinity loop because we have reached end of pagination
break; break;
} }
availableVersions.push(...paginationPage); availableVersions.push(...paginationPage);
page_index++; if (pageCount >= MAX_PAGINATION_PAGES) {
core.warning(`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing Semeru releases.`);
break;
}
} }
if (core.isDebug()) { if (core.isDebug()) {
core.startGroup('Print information about available versions'); core.startGroup('Print information about available versions');
@ -79920,6 +79926,7 @@ const path_1 = __importDefault(__nccwpck_require__(71017));
const semver_1 = __importDefault(__nccwpck_require__(11383)); const semver_1 = __importDefault(__nccwpck_require__(11383));
const base_installer_1 = __nccwpck_require__(59741); const base_installer_1 = __nccwpck_require__(59741);
const util_1 = __nccwpck_require__(92629); const util_1 = __nccwpck_require__(92629);
const MAX_PAGINATION_PAGES = 1000;
var TemurinImplementation; var TemurinImplementation;
(function (TemurinImplementation) { (function (TemurinImplementation) {
TemurinImplementation["Hotspot"] = "Hotspot"; TemurinImplementation["Hotspot"] = "Hotspot";
@ -79999,24 +80006,26 @@ class TemurinDistribution extends base_installer_1.JavaBase {
`release_type=${releaseType}`, `release_type=${releaseType}`,
`jvm_impl=${this.jvmImpl.toLowerCase()}` `jvm_impl=${this.jvmImpl.toLowerCase()}`
].join('&'); ].join('&');
// need to iterate through all pages to retrieve the list of all versions const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
// Adoptium API doesn't provide way to retrieve the count of pages to iterate so infinity loop let availableVersionsUrl = `https://api.adoptium.net/v3/assets/version/${versionRange}?${requestArguments}`;
let page_index = 0;
const availableVersions = []; const availableVersions = [];
while (true) { let pageCount = 0;
const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`; if (core.isDebug()) {
const availableVersionsUrl = `https://api.adoptium.net/v3/assets/version/${versionRange}?${requestArguments}`; core.debug(`Gathering available versions from '${availableVersionsUrl}'`);
if (core.isDebug() && page_index === 0) { }
// url is identical except page_index so print it once for debug while (availableVersionsUrl) {
core.debug(`Gathering available versions from '${availableVersionsUrl}'`); pageCount++;
} const response = yield this.http.getJson(availableVersionsUrl);
const paginationPage = (yield this.http.getJson(availableVersionsUrl)).result; const paginationPage = response.result;
availableVersionsUrl = (0, util_1.getNextPageUrlFromLinkHeader)(response.headers);
if (paginationPage === null || paginationPage.length === 0) { if (paginationPage === null || paginationPage.length === 0) {
// break infinity loop because we have reached end of pagination
break; break;
} }
availableVersions.push(...paginationPage); availableVersions.push(...paginationPage);
page_index++; if (pageCount >= MAX_PAGINATION_PAGES) {
core.warning(`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing Temurin releases.`);
break;
}
} }
if (core.isDebug()) { if (core.isDebug()) {
core.startGroup('Print information about available versions'); core.startGroup('Print information about available versions');
@ -80642,7 +80651,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.renameWinArchive = exports.getGitHubHttpHeaders = exports.convertVersionToSemver = exports.getVersionFromFileContent = exports.isCacheFeatureAvailable = exports.isGhes = exports.isJobStatusSuccess = exports.getToolcachePath = exports.isVersionSatisfies = exports.getDownloadArchiveExtension = exports.extractJdkFile = exports.getVersionFromToolcachePath = exports.getBooleanInput = exports.getTempDir = void 0; exports.renameWinArchive = exports.getNextPageUrlFromLinkHeader = exports.getGitHubHttpHeaders = exports.convertVersionToSemver = exports.getVersionFromFileContent = exports.isCacheFeatureAvailable = exports.isGhes = exports.isJobStatusSuccess = exports.getToolcachePath = exports.isVersionSatisfies = exports.getDownloadArchiveExtension = exports.extractJdkFile = exports.getVersionFromToolcachePath = exports.getBooleanInput = exports.getTempDir = void 0;
const os_1 = __importDefault(__nccwpck_require__(22037)); const os_1 = __importDefault(__nccwpck_require__(22037));
const path_1 = __importDefault(__nccwpck_require__(71017)); const path_1 = __importDefault(__nccwpck_require__(71017));
const fs = __importStar(__nccwpck_require__(57147)); const fs = __importStar(__nccwpck_require__(57147));
@ -80809,6 +80818,22 @@ function getGitHubHttpHeaders() {
return headers; return headers;
} }
exports.getGitHubHttpHeaders = getGitHubHttpHeaders; exports.getGitHubHttpHeaders = getGitHubHttpHeaders;
function getNextPageUrlFromLinkHeader(headers) {
var _a, _b;
if (!headers) {
return null;
}
const linkHeader = (_a = headers.link) !== null && _a !== void 0 ? _a : headers.Link;
if (!linkHeader) {
return null;
}
const normalizedLinkHeader = Array.isArray(linkHeader)
? linkHeader.join(',')
: linkHeader;
const nextLinkMatch = normalizedLinkHeader.match(/<([^>]+)>\s*;\s*rel="?next"?/i);
return (_b = nextLinkMatch === null || nextLinkMatch === void 0 ? void 0 : nextLinkMatch[1]) !== null && _b !== void 0 ? _b : null;
}
exports.getNextPageUrlFromLinkHeader = getNextPageUrlFromLinkHeader;
// Rename archive to add extension because after downloading // Rename archive to add extension because after downloading
// archive does not contain extension type and it leads to some issues // archive does not contain extension type and it leads to some issues
// on Windows runners without PowerShell Core. // on Windows runners without PowerShell Core.

62
package-lock.json generated
View File

@ -1465,6 +1465,18 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@nodable/entities": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.1.tgz",
"integrity": "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/nodable"
}
],
"license": "MIT"
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -3032,9 +3044,9 @@
"dev": true "dev": true
}, },
"node_modules/fast-xml-builder": { "node_modules/fast-xml-builder": {
"version": "1.1.4", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz",
"integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -3043,13 +3055,14 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"path-expression-matcher": "^1.1.3" "path-expression-matcher": "^1.5.0",
"xml-naming": "^0.1.0"
} }
}, },
"node_modules/fast-xml-parser": { "node_modules/fast-xml-parser": {
"version": "5.5.10", "version": "5.8.0",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.10.tgz", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.8.0.tgz",
"integrity": "sha512-go2J2xODMc32hT+4Xr/bBGXMaIoiCwrwp2mMtAvKyvEFW6S/v5Gn2pBmE4nvbwNjGhpcAiOwEv7R6/GZ6XRa9w==", "integrity": "sha512-6bIM7fsJxeo3uXv7OncQYsBAMPJ7V16Slahl/6M98C/i2q+vB1+4a0MtrvYwDFEUrwDSbAmeLDRXsOBwrL7yAg==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -3058,9 +3071,11 @@
], ],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-xml-builder": "^1.1.4", "@nodable/entities": "^2.1.0",
"path-expression-matcher": "^1.2.1", "fast-xml-builder": "^1.2.0",
"strnum": "^2.2.2" "path-expression-matcher": "^1.5.0",
"strnum": "^2.3.0",
"xml-naming": "^0.1.0"
}, },
"bin": { "bin": {
"fxparser": "src/cli/cli.js" "fxparser": "src/cli/cli.js"
@ -4562,9 +4577,9 @@
} }
}, },
"node_modules/path-expression-matcher": { "node_modules/path-expression-matcher": {
"version": "1.4.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.4.0.tgz", "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz",
"integrity": "sha512-s4DQMxIdhj3jLFWd9LxHOplj4p9yQ4ffMGowFf3cpEgrrJjEhN0V5nxw4Ye1EViAGDoL4/1AeO6qHpqYPOzE4Q==", "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -5102,9 +5117,9 @@
} }
}, },
"node_modules/strnum": { "node_modules/strnum": {
"version": "2.2.3", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz", "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.3.0.tgz",
"integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==", "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@ -5438,6 +5453,21 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0" "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
} }
}, },
"node_modules/xml-naming": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz",
"integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"engines": {
"node": ">=16.0.0"
}
},
"node_modules/xmlbuilder2": { "node_modules/xmlbuilder2": {
"version": "4.0.3", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-4.0.3.tgz", "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-4.0.3.tgz",

View File

@ -14,11 +14,14 @@ import {
} from '../base-models'; } from '../base-models';
import { import {
extractJdkFile, extractJdkFile,
getNextPageUrlFromLinkHeader,
getDownloadArchiveExtension, getDownloadArchiveExtension,
isVersionSatisfies, isVersionSatisfies,
renameWinArchive renameWinArchive
} from '../../util'; } from '../../util';
const MAX_PAGINATION_PAGES = 1000;
export enum AdoptImplementation { export enum AdoptImplementation {
Hotspot = 'Hotspot', Hotspot = 'Hotspot',
OpenJ9 = 'OpenJ9' OpenJ9 = 'OpenJ9'
@ -125,30 +128,35 @@ export class AdoptDistribution extends JavaBase {
`jvm_impl=${this.jvmImpl.toLowerCase()}` `jvm_impl=${this.jvmImpl.toLowerCase()}`
].join('&'); ].join('&');
// need to iterate through all pages to retrieve the list of all versions const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
// Adopt API doesn't provide way to retrieve the count of pages to iterate so infinity loop let availableVersionsUrl: string | null =
let page_index = 0; `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
const availableVersions: IAdoptAvailableVersions[] = []; const availableVersions: IAdoptAvailableVersions[] = [];
while (true) { let pageCount = 0;
const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`; if (core.isDebug()) {
const availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`; core.debug(`Gathering available versions from '${availableVersionsUrl}'`);
if (core.isDebug() && page_index === 0) { }
// url is identical except page_index so print it once for debug
core.debug(
`Gathering available versions from '${availableVersionsUrl}'`
);
}
const paginationPage = ( while (availableVersionsUrl) {
await this.http.getJson<IAdoptAvailableVersions[]>(availableVersionsUrl) pageCount++;
).result; const response =
await this.http.getJson<IAdoptAvailableVersions[]>(
availableVersionsUrl
);
const paginationPage = response.result;
availableVersionsUrl = getNextPageUrlFromLinkHeader(response.headers);
if (paginationPage === null || paginationPage.length === 0) { if (paginationPage === null || paginationPage.length === 0) {
// break infinity loop because we have reached end of pagination
break; break;
} }
availableVersions.push(...paginationPage); availableVersions.push(...paginationPage);
page_index++;
if (pageCount >= MAX_PAGINATION_PAGES) {
core.warning(
`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing Adopt releases.`
);
break;
}
} }
if (core.isDebug()) { if (core.isDebug()) {

View File

@ -7,6 +7,7 @@ import {
import semver from 'semver'; import semver from 'semver';
import { import {
extractJdkFile, extractJdkFile,
getNextPageUrlFromLinkHeader,
getDownloadArchiveExtension, getDownloadArchiveExtension,
isVersionSatisfies, isVersionSatisfies,
renameWinArchive renameWinArchive
@ -17,6 +18,8 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import {ISemeruAvailableVersions} from './models'; import {ISemeruAvailableVersions} from './models';
const MAX_PAGINATION_PAGES = 1000;
const supportedArchitectures = [ const supportedArchitectures = [
'x64', 'x64',
'x86', 'x86',
@ -155,32 +158,35 @@ export class SemeruDistribution extends JavaBase {
`jvm_impl=openj9` `jvm_impl=openj9`
].join('&'); ].join('&');
// need to iterate through all pages to retrieve the list of all versions const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
// Adoptium API doesn't provide way to retrieve the count of pages to iterate so infinity loop let availableVersionsUrl: string | null =
let page_index = 0; `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
const availableVersions: ISemeruAvailableVersions[] = []; const availableVersions: ISemeruAvailableVersions[] = [];
while (true) { let pageCount = 0;
const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`; if (core.isDebug()) {
const availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`; core.debug(`Gathering available versions from '${availableVersionsUrl}'`);
if (core.isDebug() && page_index === 0) { }
// url is identical except page_index so print it once for debug
core.debug(
`Gathering available versions from '${availableVersionsUrl}'`
);
}
const paginationPage = ( while (availableVersionsUrl) {
pageCount++;
const response =
await this.http.getJson<ISemeruAvailableVersions[]>( await this.http.getJson<ISemeruAvailableVersions[]>(
availableVersionsUrl availableVersionsUrl
) );
).result; const paginationPage = response.result;
availableVersionsUrl = getNextPageUrlFromLinkHeader(response.headers);
if (paginationPage === null || paginationPage.length === 0) { if (paginationPage === null || paginationPage.length === 0) {
// break infinity loop because we have reached end of pagination
break; break;
} }
availableVersions.push(...paginationPage); availableVersions.push(...paginationPage);
page_index++;
if (pageCount >= MAX_PAGINATION_PAGES) {
core.warning(
`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing Semeru releases.`
);
break;
}
} }
if (core.isDebug()) { if (core.isDebug()) {

View File

@ -14,11 +14,14 @@ import {
} from '../base-models'; } from '../base-models';
import { import {
extractJdkFile, extractJdkFile,
getNextPageUrlFromLinkHeader,
getDownloadArchiveExtension, getDownloadArchiveExtension,
isVersionSatisfies, isVersionSatisfies,
renameWinArchive renameWinArchive
} from '../../util'; } from '../../util';
const MAX_PAGINATION_PAGES = 1000;
export enum TemurinImplementation { export enum TemurinImplementation {
Hotspot = 'Hotspot' Hotspot = 'Hotspot'
} }
@ -123,32 +126,36 @@ export class TemurinDistribution extends JavaBase {
`jvm_impl=${this.jvmImpl.toLowerCase()}` `jvm_impl=${this.jvmImpl.toLowerCase()}`
].join('&'); ].join('&');
// need to iterate through all pages to retrieve the list of all versions const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
// Adoptium API doesn't provide way to retrieve the count of pages to iterate so infinity loop let availableVersionsUrl: string | null =
let page_index = 0; `https://api.adoptium.net/v3/assets/version/${versionRange}?${requestArguments}`;
const availableVersions: ITemurinAvailableVersions[] = []; const availableVersions: ITemurinAvailableVersions[] = [];
while (true) { let pageCount = 0;
const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`; if (core.isDebug()) {
const availableVersionsUrl = `https://api.adoptium.net/v3/assets/version/${versionRange}?${requestArguments}`; core.debug(`Gathering available versions from '${availableVersionsUrl}'`);
if (core.isDebug() && page_index === 0) { }
// url is identical except page_index so print it once for debug
core.debug(
`Gathering available versions from '${availableVersionsUrl}'`
);
}
const paginationPage = ( while (availableVersionsUrl) {
pageCount++;
const response =
await this.http.getJson<ITemurinAvailableVersions[]>( await this.http.getJson<ITemurinAvailableVersions[]>(
availableVersionsUrl availableVersionsUrl
) );
).result; const paginationPage = response.result;
availableVersionsUrl = getNextPageUrlFromLinkHeader(response.headers);
if (paginationPage === null || paginationPage.length === 0) { if (paginationPage === null || paginationPage.length === 0) {
// break infinity loop because we have reached end of pagination
break; break;
} }
availableVersions.push(...paginationPage); availableVersions.push(...paginationPage);
page_index++;
if (pageCount >= MAX_PAGINATION_PAGES) {
core.warning(
`Reached pagination safeguard limit (${MAX_PAGINATION_PAGES} pages) while listing Temurin releases.`
);
break;
}
} }
if (core.isDebug()) { if (core.isDebug()) {

View File

@ -201,6 +201,28 @@ export function getGitHubHttpHeaders(): OutgoingHttpHeaders {
return headers; return headers;
} }
export function getNextPageUrlFromLinkHeader(
headers?: Record<string, string | string[] | undefined>
): string | null {
if (!headers) {
return null;
}
const linkHeader = headers.link ?? headers.Link;
if (!linkHeader) {
return null;
}
const normalizedLinkHeader = Array.isArray(linkHeader)
? linkHeader.join(',')
: linkHeader;
const nextLinkMatch = normalizedLinkHeader.match(
/<([^>]+)>\s*;\s*rel="?next"?/i
);
return nextLinkMatch?.[1] ?? null;
}
// Rename archive to add extension because after downloading // Rename archive to add extension because after downloading
// archive does not contain extension type and it leads to some issues // archive does not contain extension type and it leads to some issues
// on Windows runners without PowerShell Core. // on Windows runners without PowerShell Core.