Compare commits

..

1 Commits

Author SHA1 Message Date
Nicolas Baumann
3844707995
Merge 301143a197 into 307d3a25a0 2026-06-08 14:48:48 -04:00
25 changed files with 6621 additions and 8957 deletions

View File

@ -10,9 +10,5 @@ on:
jobs: jobs:
call-codeQL-analysis: call-codeQL-analysis:
permissions:
actions: read
contents: read
security-events: write
name: CodeQL analysis name: CodeQL analysis
uses: actions/reusable-workflows/.github/workflows/codeql-analysis.yml@main uses: actions/reusable-workflows/.github/workflows/codeql-analysis.yml@main

View File

@ -86,32 +86,6 @@ jobs:
run: bash __tests__/verify-java.sh "${{ matrix.version }}" "${{ steps.setup-java.outputs.path }}" run: bash __tests__/verify-java.sh "${{ matrix.version }}" "${{ steps.setup-java.outputs.path }}"
shell: bash shell: bash
setup-java-alpine-linux:
name: ${{ matrix.distribution }} ${{ matrix.version }} (jdk-x64) - alpine-linux - ${{ matrix.os }}
runs-on: ${{ matrix.os }}
container:
image: alpine:latest
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
distribution: ['temurin', 'sapmachine']
version: ['21', '17']
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install bash
run: apk add --no-cache bash
- name: setup-java
uses: ./
id: setup-java
with:
java-version: ${{ matrix.version }}
distribution: ${{ matrix.distribution }}
- name: Verify Java
run: bash __tests__/verify-java.sh "${{ matrix.version }}" "${{ steps.setup-java.outputs.path }}"
shell: bash
setup-java-major-minor-versions: setup-java-major-minor-versions:
name: ${{ matrix.distribution }} ${{ matrix.version }} (jdk-x64) - ${{ matrix.os }} name: ${{ matrix.distribution }} ${{ matrix.version }} (jdk-x64) - ${{ matrix.os }}
needs: setup-java-major-versions needs: setup-java-major-versions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
.licenses/npm/ms.dep.yml generated

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -4,7 +4,6 @@ import {
AdoptDistribution, AdoptDistribution,
AdoptImplementation AdoptImplementation
} from '../../src/distributions/adopt/installer'; } from '../../src/distributions/adopt/installer';
import {TemurinDistribution} from '../../src/distributions/temurin/installer';
import {JavaInstallerOptions} from '../../src/distributions/base-models'; import {JavaInstallerOptions} from '../../src/distributions/base-models';
import os from 'os'; import os from 'os';
@ -15,7 +14,6 @@ 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');
@ -28,8 +26,6 @@ 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(() => {
@ -140,19 +136,22 @@ 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: {link: `<${nextPageUrl}>; rel="next"`}, headers: {},
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(
@ -167,34 +166,6 @@ 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://api.adoptopenjdk.net/v3/assets/version/%5B1.0,100.0%5D?page=2&page_size=20';
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([
@ -257,38 +228,6 @@ describe('getAvailableVersions', () => {
}); });
describe('findPackageForDownload', () => { describe('findPackageForDownload', () => {
it('returns Temurin result and does not query Adopt API when Temurin succeeds', async () => {
const temurinRelease = {
version: '11.0.31+11',
url: 'https://example.test/temurin-11.tar.gz'
};
const temurinFindPackageForDownload = jest
.fn()
.mockResolvedValue(temurinRelease);
const temurinDistribution = {
findPackageForDownload: temurinFindPackageForDownload
} as unknown as TemurinDistribution;
const distribution = new AdoptDistribution(
{
version: '11',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
},
AdoptImplementation.Hotspot,
temurinDistribution
);
const adoptLookupSpy = jest.fn();
distribution['getAvailableVersions'] = adoptLookupSpy;
const resolvedVersion = await distribution['findPackageForDownload']('11');
expect(resolvedVersion).toEqual(temurinRelease);
expect(temurinFindPackageForDownload).toHaveBeenCalledWith('11');
expect(adoptLookupSpy).not.toHaveBeenCalled();
});
it.each([ it.each([
['9', '9.0.7+10'], ['9', '9.0.7+10'],
['15', '15.0.2+7'], ['15', '15.0.2+7'],
@ -311,11 +250,6 @@ describe('findPackageForDownload', () => {
}, },
AdoptImplementation.Hotspot AdoptImplementation.Hotspot
); );
// Mock Temurin to fail so fallback to AdoptOpenJDK is tested
distribution['temurinDistribution']!['findPackageForDownload'] =
async () => {
throw new Error('No matching version found for SemVer');
};
distribution['getAvailableVersions'] = async () => manifestData as any; distribution['getAvailableVersions'] = async () => manifestData as any;
const resolvedVersion = await distribution['findPackageForDownload'](input); const resolvedVersion = await distribution['findPackageForDownload'](input);
expect(resolvedVersion.version).toBe(expected); expect(resolvedVersion.version).toBe(expected);
@ -331,11 +265,6 @@ describe('findPackageForDownload', () => {
}, },
AdoptImplementation.Hotspot AdoptImplementation.Hotspot
); );
// Mock Temurin to fail so fallback to AdoptOpenJDK is tested
distribution['temurinDistribution']!['findPackageForDownload'] =
async () => {
throw new Error('No matching version found for SemVer');
};
distribution['getAvailableVersions'] = async () => manifestData as any; distribution['getAvailableVersions'] = async () => manifestData as any;
await expect( await expect(
distribution['findPackageForDownload']('9.0.8') distribution['findPackageForDownload']('9.0.8')
@ -352,11 +281,6 @@ describe('findPackageForDownload', () => {
}, },
AdoptImplementation.Hotspot AdoptImplementation.Hotspot
); );
// Mock Temurin to fail so fallback to AdoptOpenJDK is tested
distribution['temurinDistribution']!['findPackageForDownload'] =
async () => {
throw new Error('No matching version found for SemVer');
};
distribution['getAvailableVersions'] = async () => manifestData as any; distribution['getAvailableVersions'] = async () => manifestData as any;
await expect(distribution['findPackageForDownload']('7.x')).rejects.toThrow( await expect(distribution['findPackageForDownload']('7.x')).rejects.toThrow(
/No matching version found for SemVer */ /No matching version found for SemVer */
@ -373,11 +297,6 @@ describe('findPackageForDownload', () => {
}, },
AdoptImplementation.Hotspot AdoptImplementation.Hotspot
); );
// Mock Temurin to fail so fallback to AdoptOpenJDK is tested
distribution['temurinDistribution']!['findPackageForDownload'] =
async () => {
throw new Error('No matching version found for SemVer');
};
distribution['getAvailableVersions'] = async () => []; distribution['getAvailableVersions'] = async () => [];
await expect(distribution['findPackageForDownload']('11')).rejects.toThrow( await expect(distribution['findPackageForDownload']('11')).rejects.toThrow(
/No matching version found for SemVer */ /No matching version found for SemVer */

View File

@ -9,7 +9,6 @@ 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');
@ -21,8 +20,6 @@ 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(() => {
@ -85,19 +82,22 @@ 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: {link: `<${nextPageUrl}>; rel="next"`}, headers: {},
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,31 +109,6 @@ 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://api.adoptopenjdk.net/v3/assets/version/%5B1.0,100.0%5D?page=2&page_size=20';
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,7 +12,6 @@ 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');
@ -24,8 +23,6 @@ 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(() => {
@ -96,19 +93,22 @@ 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: {link: `<${nextPageUrl}>; rel="next"`}, headers: {},
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,34 +123,6 @@ 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://api.adoptium.net/v3/assets/version/%5B1.0,100.0%5D?page=2&page_size=20';
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,12 +4,10 @@ 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,
isGhes, isGhes
validatePaginationUrl
} from '../src/util'; } from '../src/util';
jest.mock('@actions/cache'); jest.mock('@actions/cache');
@ -29,11 +27,7 @@ describe('isVersionSatisfies', () => {
['2.5.1+3', '2.5.1+3', true], ['2.5.1+3', '2.5.1+3', true],
['2.5.1+3', '2.5.1+2', false], ['2.5.1+3', '2.5.1+2', false],
['15.0.0+14', '15.0.0+14.1.202003190635', false], ['15.0.0+14', '15.0.0+14.1.202003190635', false],
['15.0.0+14.1.202003190635', '15.0.0+14.1.202003190635', true], ['15.0.0+14.1.202003190635', '15.0.0+14.1.202003190635', true]
// 4-segment versions (e.g. JetBrains Runtime '17.0.8.1+1080.1') are not
// valid semver — they should be rejected, not throw.
['25.0.3+480.61', '17.0.8.1+1080.1', false],
['17', '17.0.8.1+1080.1', false]
])( ])(
'%s, %s -> %s', '%s, %s -> %s',
(inputRange: string, inputVersion: string, expected: boolean) => { (inputRange: string, inputVersion: string, expected: boolean) => {
@ -91,78 +85,6 @@ 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://api.adoptium.net/v3/versions?page=3>; type="application/json"; rel="next"'
},
'https://api.adoptium.net/v3/versions?page=3'
],
[{link: '<https://example.com/last?page=5>; rel="last"'}, null],
[{link: '<https://example.com/page?p=2>; rel="nextsomething"'}, null],
[undefined, null]
])('returns %s -> %s', (headers, expected) => {
expect(getNextPageUrlFromLinkHeader(headers)).toBe(expected);
});
});
describe('validatePaginationUrl', () => {
it('accepts URL with matching origin', () => {
expect(
validatePaginationUrl(
'https://api.adoptium.net/v3/assets?page=2',
'https://api.adoptium.net'
)
).toBe(true);
});
it('rejects URL with different host', () => {
expect(
validatePaginationUrl(
'https://evil.example.com/steal?data=1',
'https://api.adoptium.net'
)
).toBe(false);
});
it('rejects URL with different protocol', () => {
expect(
validatePaginationUrl(
'http://api.adoptium.net/v3/assets?page=2',
'https://api.adoptium.net'
)
).toBe(false);
});
it('returns false for invalid URL', () => {
expect(validatePaginationUrl('not-a-url', 'https://api.adoptium.net')).toBe(
false
);
});
it('accepts URL with explicit default port', () => {
expect(
validatePaginationUrl(
'https://api.adoptium.net:443/v3/assets?page=2',
'https://api.adoptium.net'
)
).toBe(true);
});
});
describe('getVersionFromFileContent', () => { describe('getVersionFromFileContent', () => {
describe('.sdkmanrc', () => { describe('.sdkmanrc', () => {
it.each([ it.each([

4765
dist/cleanup/index.js vendored

File diff suppressed because one or more lines are too long

6592
dist/setup/index.js vendored

File diff suppressed because one or more lines are too long

3630
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -40,20 +40,20 @@
"xmlbuilder2": "^4.0.3" "xmlbuilder2": "^4.0.3"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^30.0.0", "@types/jest": "^29.5.14",
"@types/node": "^25.9.3", "@types/node": "^24.1.0",
"@types/semver": "^7.5.8", "@types/semver": "^7.5.8",
"@typescript-eslint/eslint-plugin": "^8.48.0", "@typescript-eslint/eslint-plugin": "^8.35.1",
"@typescript-eslint/parser": "^8.61.1", "@typescript-eslint/parser": "^8.35.1",
"@vercel/ncc": "^0.44.0", "@vercel/ncc": "^0.38.1",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^29.0.1", "eslint-plugin-jest": "^29.0.1",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"jest": "^30.4.2", "jest": "^29.7.0",
"jest-circus": "^30.4.2", "jest-circus": "^29.7.0",
"prettier": "^3.6.2", "prettier": "^3.6.2",
"ts-jest": "^29.4.11", "ts-jest": "^29.3.0",
"typescript": "^5.3.3" "typescript": "^5.3.3"
}, },
"bugs": { "bugs": {

View File

@ -14,14 +14,10 @@ import {
} from '../base-models'; } from '../base-models';
import { import {
extractJdkFile, extractJdkFile,
getNextPageUrlFromLinkHeader,
getDownloadArchiveExtension, getDownloadArchiveExtension,
isVersionSatisfies, isVersionSatisfies,
renameWinArchive, renameWinArchive
MAX_PAGINATION_PAGES,
validatePaginationUrl
} from '../../util'; } from '../../util';
import {TemurinDistribution, TemurinImplementation} from '../temurin/installer';
export enum AdoptImplementation { export enum AdoptImplementation {
Hotspot = 'Hotspot', Hotspot = 'Hotspot',
@ -29,72 +25,15 @@ export enum AdoptImplementation {
} }
export class AdoptDistribution extends JavaBase { export class AdoptDistribution extends JavaBase {
private readonly temurinDistribution: TemurinDistribution | null;
constructor( constructor(
installerOptions: JavaInstallerOptions, installerOptions: JavaInstallerOptions,
private readonly jvmImpl: AdoptImplementation, private readonly jvmImpl: AdoptImplementation
temurinDistribution: TemurinDistribution | null = null
) { ) {
super(`Adopt-${jvmImpl}`, installerOptions); super(`Adopt-${jvmImpl}`, installerOptions);
if (
temurinDistribution !== null &&
jvmImpl !== AdoptImplementation.Hotspot
) {
throw new Error('Only Hotspot JVM is supported by Temurin.');
}
// Only use the temurin repo for Hotspot JVMs
this.temurinDistribution =
temurinDistribution ??
(jvmImpl === AdoptImplementation.Hotspot
? new TemurinDistribution(
installerOptions,
TemurinImplementation.Hotspot
)
: null);
} }
protected async findPackageForDownload( protected async findPackageForDownload(
version: string version: string
): Promise<JavaDownloadRelease> {
if (this.jvmImpl === AdoptImplementation.Hotspot) {
core.notice(
"AdoptOpenJDK has moved to Eclipse Temurin https://github.com/actions/setup-java#supported-distributions please consider changing to the 'temurin' distribution type in your setup-java configuration."
);
}
if (
this.jvmImpl === AdoptImplementation.Hotspot &&
this.temurinDistribution !== null
) {
try {
return await this.temurinDistribution.findPackageForDownload(version);
} catch (error) {
// Log the failure but always fall back to legacy AdoptOpenJDK for resilience
const errorMessage =
error instanceof Error ? error.message : String(error);
if (error instanceof Error && error.name === 'VersionNotFoundError') {
core.notice(
'The JVM you are looking for could not be found in the Temurin repository, this likely indicates ' +
'that you are using an out of date version of Java, consider updating and moving to using the Temurin distribution type in setup-java.'
);
} else {
// Log other errors for debugging but gracefully fall back
core.debug(
`Temurin lookup failed: ${errorMessage}. Falling back to AdoptOpenJDK API.`
);
}
}
}
// failed to find a Temurin version, so fall back to AdoptOpenJDK
return this.findPackageForDownloadOldAdoptOpenJdk(version);
}
private async findPackageForDownloadOldAdoptOpenJdk(
version: string
): Promise<JavaDownloadRelease> { ): Promise<JavaDownloadRelease> {
const availableVersionsRaw = await this.getAvailableVersions(); const availableVersionsRaw = await this.getAvailableVersions();
const availableVersionsWithBinaries = availableVersionsRaw const availableVersionsWithBinaries = availableVersionsRaw
@ -186,46 +125,30 @@ export class AdoptDistribution extends JavaBase {
`jvm_impl=${this.jvmImpl.toLowerCase()}` `jvm_impl=${this.jvmImpl.toLowerCase()}`
].join('&'); ].join('&');
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`; // need to iterate through all pages to retrieve the list of all versions
let availableVersionsUrl: string | null = // Adopt API doesn't provide way to retrieve the count of pages to iterate so infinity loop
`https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`; let page_index = 0;
const availableVersions: IAdoptAvailableVersions[] = []; const availableVersions: IAdoptAvailableVersions[] = [];
let pageCount = 0; while (true) {
if (core.isDebug()) { const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`;
core.debug(`Gathering available versions from '${availableVersionsUrl}'`); const availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
} if (core.isDebug() && page_index === 0) {
// url is identical except page_index so print it once for debug
while (availableVersionsUrl) { core.debug(
pageCount++; `Gathering available versions from '${availableVersionsUrl}'`
const response =
await this.http.getJson<IAdoptAvailableVersions[]>(
availableVersionsUrl
); );
const paginationPage = response.result;
const nextUrl = getNextPageUrlFromLinkHeader(response.headers);
if (
nextUrl &&
!validatePaginationUrl(nextUrl, 'https://api.adoptopenjdk.net')
) {
core.warning(
`Ignoring pagination link with unexpected origin: ${nextUrl}`
);
availableVersionsUrl = null;
} else {
availableVersionsUrl = nextUrl;
} }
const paginationPage = (
await this.http.getJson<IAdoptAvailableVersions[]>(availableVersionsUrl)
).result;
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

@ -292,9 +292,7 @@ export abstract class JavaBase {
} }
} }
const error = new Error(parts.join('\n')); return new Error(parts.join('\n'));
error.name = 'VersionNotFoundError';
return error;
} }
protected setJavaDefault(version: string, toolPath: string) { protected setJavaDefault(version: string, toolPath: string) {

View File

@ -7,12 +7,9 @@ import {
import semver from 'semver'; import semver from 'semver';
import { import {
extractJdkFile, extractJdkFile,
getNextPageUrlFromLinkHeader,
getDownloadArchiveExtension, getDownloadArchiveExtension,
isVersionSatisfies, isVersionSatisfies,
renameWinArchive, renameWinArchive
MAX_PAGINATION_PAGES,
validatePaginationUrl
} from '../../util'; } from '../../util';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as tc from '@actions/tool-cache'; import * as tc from '@actions/tool-cache';
@ -158,46 +155,32 @@ export class SemeruDistribution extends JavaBase {
`jvm_impl=openj9` `jvm_impl=openj9`
].join('&'); ].join('&');
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`; // need to iterate through all pages to retrieve the list of all versions
let availableVersionsUrl: string | null = // Adoptium API doesn't provide way to retrieve the count of pages to iterate so infinity loop
`https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`; let page_index = 0;
const availableVersions: ISemeruAvailableVersions[] = []; const availableVersions: ISemeruAvailableVersions[] = [];
let pageCount = 0; while (true) {
if (core.isDebug()) { const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`;
core.debug(`Gathering available versions from '${availableVersionsUrl}'`); const availableVersionsUrl = `https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
} 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}'`
);
}
while (availableVersionsUrl) { const paginationPage = (
pageCount++;
const response =
await this.http.getJson<ISemeruAvailableVersions[]>( await this.http.getJson<ISemeruAvailableVersions[]>(
availableVersionsUrl availableVersionsUrl
); )
const paginationPage = response.result; ).result;
const nextUrl = getNextPageUrlFromLinkHeader(response.headers);
if (
nextUrl &&
!validatePaginationUrl(nextUrl, 'https://api.adoptopenjdk.net')
) {
core.warning(
`Ignoring pagination link with unexpected origin: ${nextUrl}`
);
availableVersionsUrl = null;
} else {
availableVersionsUrl = nextUrl;
}
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,12 +14,9 @@ import {
} from '../base-models'; } from '../base-models';
import { import {
extractJdkFile, extractJdkFile,
getNextPageUrlFromLinkHeader,
getDownloadArchiveExtension, getDownloadArchiveExtension,
isVersionSatisfies, isVersionSatisfies,
renameWinArchive, renameWinArchive
MAX_PAGINATION_PAGES,
validatePaginationUrl
} from '../../util'; } from '../../util';
export enum TemurinImplementation { export enum TemurinImplementation {
@ -34,10 +31,7 @@ export class TemurinDistribution extends JavaBase {
super(`Temurin-${jvmImpl}`, installerOptions); super(`Temurin-${jvmImpl}`, installerOptions);
} }
/** protected async findPackageForDownload(
* @internal For cross-distribution reuse only. Not intended as a public API.
*/
public async findPackageForDownload(
version: string version: string
): Promise<JavaDownloadRelease> { ): Promise<JavaDownloadRelease> {
const availableVersionsRaw = await this.getAvailableVersions(); const availableVersionsRaw = await this.getAvailableVersions();
@ -129,47 +123,32 @@ export class TemurinDistribution extends JavaBase {
`jvm_impl=${this.jvmImpl.toLowerCase()}` `jvm_impl=${this.jvmImpl.toLowerCase()}`
].join('&'); ].join('&');
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`; // need to iterate through all pages to retrieve the list of all versions
let availableVersionsUrl: string | null = // Adoptium API doesn't provide way to retrieve the count of pages to iterate so infinity loop
`https://api.adoptium.net/v3/assets/version/${versionRange}?${requestArguments}`; let page_index = 0;
const availableVersions: ITemurinAvailableVersions[] = []; const availableVersions: ITemurinAvailableVersions[] = [];
let pageCount = 0; while (true) {
if (core.isDebug()) { const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`;
core.debug(`Gathering available versions from '${availableVersionsUrl}'`); const availableVersionsUrl = `https://api.adoptium.net/v3/assets/version/${versionRange}?${requestArguments}`;
} if (core.isDebug() && page_index === 0) {
// url is identical except page_index so print it once for debug
while (availableVersionsUrl) { core.debug(
pageCount++; `Gathering available versions from '${availableVersionsUrl}'`
const response =
await this.http.getJson<ITemurinAvailableVersions[]>(
availableVersionsUrl
); );
const paginationPage = response.result;
const nextUrl = getNextPageUrlFromLinkHeader(response.headers);
if (
nextUrl &&
!validatePaginationUrl(nextUrl, 'https://api.adoptium.net')
) {
core.warning(
`Ignoring pagination link with unexpected origin: ${nextUrl}`
);
availableVersionsUrl = null;
} else {
availableVersionsUrl = nextUrl;
} }
const paginationPage = (
await this.http.getJson<ITemurinAvailableVersions[]>(
availableVersionsUrl
)
).result;
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()) {
@ -192,11 +171,6 @@ export class TemurinDistribution extends JavaBase {
return 'mac'; return 'mac';
case 'win32': case 'win32':
return 'windows'; return 'windows';
case 'linux':
if (fs.existsSync('/etc/alpine-release')) {
return 'alpine-linux';
}
return 'linux';
default: default:
return process.platform; return process.platform;
} }

View File

@ -55,14 +55,6 @@ export function getDownloadArchiveExtension() {
} }
export function isVersionSatisfies(range: string, version: string): boolean { export function isVersionSatisfies(range: string, version: string): boolean {
// Some distributions (e.g. JetBrains Runtime) publish 4-segment versions
// like '17.0.8.1+1080.1' that semver rejects. If the candidate version
// isn't valid semver, it can't match — bail out rather than letting
// compareBuild / satisfies throw.
if (!semver.valid(version)) {
return false;
}
if (semver.valid(range)) { if (semver.valid(range)) {
// if full version with build digit is provided as a range (such as '1.2.3+4') // if full version with build digit is provided as a range (such as '1.2.3+4')
// we should check for exact equal via compareBuild // we should check for exact equal via compareBuild
@ -209,55 +201,6 @@ export function getGitHubHttpHeaders(): OutgoingHttpHeaders {
return headers; return headers;
} }
export const MAX_PAGINATION_PAGES = 1000;
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;
// Split into individual link-values and find the one with rel="next"
// RFC 8288 allows rel to appear anywhere among the parameters
const linkValues = normalizedLinkHeader.split(/,(?=\s*<)/);
for (const linkValue of linkValues) {
const urlMatch = linkValue.match(/<([^>]+)>/);
if (!urlMatch) continue;
const params = linkValue.slice(urlMatch[0].length);
// Use word boundary to match "next" as a standalone relation type
// RFC 8288 allows space-separated relation types like rel="next prev"
if (/;\s*rel="?[^"]*\bnext\b/i.test(params)) {
return urlMatch[1];
}
}
return null;
}
export function validatePaginationUrl(
url: string,
allowedOrigin: string
): boolean {
try {
const parsed = new URL(url);
const allowed = new URL(allowedOrigin);
return parsed.origin === allowed.origin;
} catch {
return false;
}
}
// 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.