mirror of
https://github.com/actions/setup-java.git
synced 2026-06-15 05:48:19 +01:00
Compare commits
13 Commits
copilot/up
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3780ef6e4f | ||
|
|
b24df5bba5 | ||
|
|
43120bc3c3 | ||
|
|
ad9d6a6320 | ||
|
|
039af37997 | ||
|
|
1756ab6acd | ||
|
|
662bb59f48 | ||
|
|
1071fc12d6 | ||
|
|
576b821f29 | ||
|
|
307d3a25a0 | ||
|
|
b622de1dfa | ||
|
|
c76542e033 | ||
|
|
0756542bc5 |
26
.github/workflows/e2e-versions.yml
vendored
26
.github/workflows/e2e-versions.yml
vendored
@@ -86,6 +86,32 @@ jobs:
|
||||
run: bash __tests__/verify-java.sh "${{ matrix.version }}" "${{ steps.setup-java.outputs.path }}"
|
||||
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:
|
||||
name: ${{ matrix.distribution }} ${{ matrix.version }} (jdk-x64) - ${{ matrix.os }}
|
||||
needs: setup-java-major-versions
|
||||
|
||||
11
.licenses/npm/@nodable/entities.dep.yml
generated
Normal file
11
.licenses/npm/@nodable/entities.dep.yml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: "@nodable/entities"
|
||||
version: 2.2.0
|
||||
type: npm
|
||||
summary: Entity parser for XML, HTML, External entites with security and NCR control
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: README.md
|
||||
text: MIT
|
||||
notices: []
|
||||
33
.licenses/npm/anynum.dep.yml
generated
Normal file
33
.licenses/npm/anynum.dep.yml
generated
Normal file
@@ -0,0 +1,33 @@
|
||||
---
|
||||
name: anynum
|
||||
version: 1.0.0
|
||||
type: npm
|
||||
summary: Normalize all Unicode decimal digits (Devanagari, Arabic, Thai, etc.) to
|
||||
ASCII numerals. Zero dependencies, performance-first.
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Natural Intelligence
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
notices: []
|
||||
2
.licenses/npm/fast-xml-builder.dep.yml
generated
2
.licenses/npm/fast-xml-builder.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: fast-xml-builder
|
||||
version: 1.1.4
|
||||
version: 1.2.0
|
||||
type: npm
|
||||
summary: Build XML from JSON without C/C++ based libraries
|
||||
homepage:
|
||||
|
||||
2
.licenses/npm/fast-xml-parser.dep.yml
generated
2
.licenses/npm/fast-xml-parser.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: fast-xml-parser
|
||||
version: 5.5.10
|
||||
version: 5.8.0
|
||||
type: npm
|
||||
summary: Validate XML, Parse XML, Build XML without C/C++ based libraries
|
||||
homepage:
|
||||
|
||||
2
.licenses/npm/path-expression-matcher.dep.yml
generated
2
.licenses/npm/path-expression-matcher.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: path-expression-matcher
|
||||
version: 1.4.0
|
||||
version: 1.5.0
|
||||
type: npm
|
||||
summary: Efficient path tracking and pattern matching for XML/JSON parsers
|
||||
homepage: https://github.com/NaturalIntelligence/path-expression-matcher#readme
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: semver
|
||||
version: 7.7.1
|
||||
version: 7.8.4
|
||||
type: npm
|
||||
summary: The semantic version parser used by npm.
|
||||
homepage:
|
||||
2
.licenses/npm/strnum.dep.yml
generated
2
.licenses/npm/strnum.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: strnum
|
||||
version: 2.2.3
|
||||
version: 2.4.0
|
||||
type: npm
|
||||
summary: Parse String to Number based on configuration
|
||||
homepage:
|
||||
|
||||
12
.licenses/npm/xml-naming.dep.yml
generated
Normal file
12
.licenses/npm/xml-naming.dep.yml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
name: xml-naming
|
||||
version: 0.1.0
|
||||
type: npm
|
||||
summary: Validates XML name productions — Name, NCName, QName, NMToken, NMTokens —
|
||||
for XML 1.0 and 1.1
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: README.md
|
||||
text: MIT
|
||||
notices: []
|
||||
@@ -129,6 +129,10 @@ Currently, the following distributions are supported:
|
||||
|
||||
**NOTE:** To comply with the GraalVM Free Terms and Conditions (GFTC) license, it is recommended to use GraalVM JDK 17 version 17.0.12, as this is the only version of GraalVM JDK 17 available under the GFTC license. Additionally, it is encouraged to consider upgrading to GraalVM JDK 21, which offers the latest features and improvements.
|
||||
|
||||
**NOTE:** Oracle JDK 17 licensing varies by patch level. As shown on the [JDK 17 Archive](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) (versions up to 17.0.12 are under the [NFTC](https://www.oracle.com/downloads/licenses/no-fee-license.html) license) and the [JDK 17.0.13+ Archive](https://www.oracle.com/java/technologies/javase/jdk17-0-13-later-archive-downloads.html) (versions 17.0.13 and later are under the [OTN](https://www.oracle.com/downloads/licenses/javase-license1.html) license). To stay on the free NFTC license, use `distribution: 'oracle'` with `java-version: '17.0.12'` (or earlier) instead of the floating `'17'`. Alternatively, upgrade to Oracle JDK 21+, which remains under the NFTC license.
|
||||
|
||||
**NOTE:** On Ubuntu runners, commands executed via `sudo` do not inherit the `JAVA_HOME` and `PATH` set by `setup-java` and will fall back to the runner image's system-default JDK.
|
||||
|
||||
### Caching packages dependencies
|
||||
The action has a built-in functionality for caching and restoring dependencies. It uses [toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under hood for caching dependencies but requires less configuration settings. Supported package managers are gradle, maven and sbt. The format of the used cache key is `setup-java-${{ platform }}-${{ packageManager }}-${{ fileHash }}`, where the hash is based on the following files:
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ describe('dependency cache', () => {
|
||||
let spyWarning: jest.SpyInstance<void, Parameters<typeof core.warning>>;
|
||||
let spyDebug: jest.SpyInstance<void, Parameters<typeof core.debug>>;
|
||||
let spySaveState: jest.SpyInstance<void, Parameters<typeof core.saveState>>;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
workspace = mkdtempSync(join(tmpdir(), 'setup-java-cache-'));
|
||||
@@ -51,6 +52,10 @@ describe('dependency cache', () => {
|
||||
|
||||
spySaveState = jest.spyOn(core, 'saveState');
|
||||
spySaveState.mockImplementation(() => null);
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -58,6 +63,10 @@ describe('dependency cache', () => {
|
||||
process.env['GITHUB_WORKSPACE'] = ORIGINAL_GITHUB_WORKSPACE;
|
||||
process.env['RUNNER_OS'] = ORIGINAL_RUNNER_OS;
|
||||
resetState();
|
||||
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe('restore', () => {
|
||||
|
||||
@@ -11,19 +11,32 @@ describe('cleanup', () => {
|
||||
Parameters<typeof cache.saveCache>
|
||||
>;
|
||||
let spyJobStatusSuccess: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyWarning = jest.spyOn(core, 'warning');
|
||||
spyWarning.mockImplementation(() => null);
|
||||
|
||||
spyInfo = jest.spyOn(core, 'info');
|
||||
spyInfo.mockImplementation(() => null);
|
||||
|
||||
spyCacheSave = jest.spyOn(cache, 'saveCache');
|
||||
|
||||
spyJobStatusSuccess = jest.spyOn(util, 'isJobStatusSuccess');
|
||||
spyJobStatusSuccess.mockReturnValue(true);
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
|
||||
createStateForSuccessfulRestore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetState();
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('does not fail nor warn even when the save process throws a ReserveCacheError', async () => {
|
||||
|
||||
@@ -4,14 +4,18 @@ import {
|
||||
AdoptDistribution,
|
||||
AdoptImplementation
|
||||
} from '../../src/distributions/adopt/installer';
|
||||
import {TemurinDistribution} from '../../src/distributions/temurin/installer';
|
||||
import {JavaInstallerOptions} from '../../src/distributions/base-models';
|
||||
|
||||
import os from 'os';
|
||||
|
||||
import manifestData from '../data/adopt.json';
|
||||
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 +24,12 @@ describe('getAvailableVersions', () => {
|
||||
headers: {},
|
||||
result: []
|
||||
});
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
spyCoreWarning = jest.spyOn(core, 'warning');
|
||||
spyCoreWarning.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -130,22 +140,19 @@ describe('getAvailableVersions', () => {
|
||||
);
|
||||
|
||||
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
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
headers: {link: `<${nextPageUrl}>; rel="next"`},
|
||||
result: manifestData as any
|
||||
})
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: manifestData as any
|
||||
})
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: []
|
||||
});
|
||||
|
||||
const distribution = new AdoptDistribution(
|
||||
@@ -160,6 +167,34 @@ describe('getAvailableVersions', () => {
|
||||
const availableVersions = await distribution['getAvailableVersions']();
|
||||
expect(availableVersions).not.toBeNull();
|
||||
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([
|
||||
@@ -222,6 +257,38 @@ describe('getAvailableVersions', () => {
|
||||
});
|
||||
|
||||
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([
|
||||
['9', '9.0.7+10'],
|
||||
['15', '15.0.2+7'],
|
||||
@@ -244,6 +311,11 @@ describe('findPackageForDownload', () => {
|
||||
},
|
||||
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;
|
||||
const resolvedVersion = await distribution['findPackageForDownload'](input);
|
||||
expect(resolvedVersion.version).toBe(expected);
|
||||
@@ -259,10 +331,15 @@ describe('findPackageForDownload', () => {
|
||||
},
|
||||
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;
|
||||
await expect(
|
||||
distribution['findPackageForDownload']('9.0.8')
|
||||
).rejects.toThrow(/Could not find satisfied version for SemVer */);
|
||||
).rejects.toThrow(/No matching version found for SemVer */);
|
||||
});
|
||||
|
||||
it('version is not found', async () => {
|
||||
@@ -275,9 +352,14 @@ describe('findPackageForDownload', () => {
|
||||
},
|
||||
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;
|
||||
await expect(distribution['findPackageForDownload']('7.x')).rejects.toThrow(
|
||||
/Could not find satisfied version for SemVer */
|
||||
/No matching version found for SemVer */
|
||||
);
|
||||
});
|
||||
|
||||
@@ -291,9 +373,14 @@ describe('findPackageForDownload', () => {
|
||||
},
|
||||
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 () => [];
|
||||
await expect(distribution['findPackageForDownload']('11')).rejects.toThrow(
|
||||
/Could not find satisfied version for SemVer */
|
||||
/No matching version found for SemVer */
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -38,7 +38,7 @@ class EmptyJavaBase extends JavaBase {
|
||||
): Promise<JavaDownloadRelease> {
|
||||
const availableVersion = '11.0.9';
|
||||
if (!semver.satisfies(availableVersion, range)) {
|
||||
throw new Error('Available version not found');
|
||||
throw this.createVersionNotFoundError(range, [availableVersion]);
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -248,6 +248,7 @@ describe('setupJava', () => {
|
||||
let spyCoreExportVariable: jest.SpyInstance;
|
||||
let spyCoreAddPath: jest.SpyInstance;
|
||||
let spyCoreSetOutput: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyGetToolcachePath = jest.spyOn(util, 'getToolcachePath');
|
||||
@@ -287,6 +288,10 @@ describe('setupJava', () => {
|
||||
spyCoreSetOutput = jest.spyOn(core, 'setOutput');
|
||||
spyCoreSetOutput.mockImplementation(() => undefined);
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => undefined);
|
||||
|
||||
jest.spyOn(os, 'arch').mockReturnValue('x86' as ReturnType<typeof os.arch>);
|
||||
});
|
||||
|
||||
@@ -530,19 +535,16 @@ describe('setupJava', () => {
|
||||
checkLatest: false
|
||||
}
|
||||
]
|
||||
])(
|
||||
'should throw an error for Available version not found for %s',
|
||||
async input => {
|
||||
mockJavaBase = new EmptyJavaBase(input);
|
||||
await expect(mockJavaBase.setupJava()).rejects.toThrow(
|
||||
'Available version not found'
|
||||
);
|
||||
expect(spyTcFindAllVersions).toHaveBeenCalled();
|
||||
expect(spyCoreAddPath).not.toHaveBeenCalled();
|
||||
expect(spyCoreExportVariable).not.toHaveBeenCalled();
|
||||
expect(spyCoreSetOutput).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
])('should throw an error for version not found for %s', async input => {
|
||||
mockJavaBase = new EmptyJavaBase(input);
|
||||
await expect(mockJavaBase.setupJava()).rejects.toThrow(
|
||||
`No matching version found for SemVer '${input.version}'`
|
||||
);
|
||||
expect(spyTcFindAllVersions).toHaveBeenCalled();
|
||||
expect(spyCoreAddPath).not.toHaveBeenCalled();
|
||||
expect(spyCoreExportVariable).not.toHaveBeenCalled();
|
||||
expect(spyCoreSetOutput).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('normalizeVersion', () => {
|
||||
@@ -570,6 +572,97 @@ describe('normalizeVersion', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('createVersionNotFoundError', () => {
|
||||
it('should include all required fields in error message without available versions', () => {
|
||||
const mockJavaBase = new EmptyJavaBase({
|
||||
version: '17.0.5',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk',
|
||||
checkLatest: false
|
||||
});
|
||||
|
||||
const error = (mockJavaBase as any).createVersionNotFoundError('17.0.5');
|
||||
|
||||
expect(error.message).toContain(
|
||||
"No matching version found for SemVer '17.0.5'"
|
||||
);
|
||||
expect(error.message).toContain('Distribution: Empty');
|
||||
expect(error.message).toContain('Package type: jdk');
|
||||
expect(error.message).toContain('Architecture: x64');
|
||||
});
|
||||
|
||||
it('should include available versions when provided', () => {
|
||||
const mockJavaBase = new EmptyJavaBase({
|
||||
version: '17.0.5',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk',
|
||||
checkLatest: false
|
||||
});
|
||||
|
||||
const availableVersions = ['11.0.1', '11.0.2', '17.0.1', '17.0.2'];
|
||||
const error = (mockJavaBase as any).createVersionNotFoundError(
|
||||
'17.0.5',
|
||||
availableVersions
|
||||
);
|
||||
|
||||
expect(error.message).toContain(
|
||||
"No matching version found for SemVer '17.0.5'"
|
||||
);
|
||||
expect(error.message).toContain('Distribution: Empty');
|
||||
expect(error.message).toContain('Package type: jdk');
|
||||
expect(error.message).toContain('Architecture: x64');
|
||||
expect(error.message).toContain(
|
||||
'Available versions: 11.0.1, 11.0.2, 17.0.1, 17.0.2'
|
||||
);
|
||||
});
|
||||
|
||||
it('should truncate available versions when there are many', () => {
|
||||
const mockJavaBase = new EmptyJavaBase({
|
||||
version: '17.0.5',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk',
|
||||
checkLatest: false
|
||||
});
|
||||
|
||||
// Create 60 versions to test truncation
|
||||
const availableVersions = Array.from({length: 60}, (_, i) => `11.0.${i}`);
|
||||
const error = (mockJavaBase as any).createVersionNotFoundError(
|
||||
'17.0.5',
|
||||
availableVersions
|
||||
);
|
||||
|
||||
expect(error.message).toContain('Available versions:');
|
||||
expect(error.message).toContain('...');
|
||||
expect(error.message).toContain('(showing first 50 of 60 versions');
|
||||
});
|
||||
|
||||
it('should include additional context when provided', () => {
|
||||
const mockJavaBase = new EmptyJavaBase({
|
||||
version: '17.0.5',
|
||||
architecture: 'x64',
|
||||
packageType: 'jdk',
|
||||
checkLatest: false
|
||||
});
|
||||
|
||||
const availableVersions = ['11.0.1', '11.0.2'];
|
||||
const additionalContext = 'Platform: linux';
|
||||
const error = (mockJavaBase as any).createVersionNotFoundError(
|
||||
'17.0.5',
|
||||
availableVersions,
|
||||
additionalContext
|
||||
);
|
||||
|
||||
expect(error.message).toContain(
|
||||
"No matching version found for SemVer '17.0.5'"
|
||||
);
|
||||
expect(error.message).toContain('Distribution: Empty');
|
||||
expect(error.message).toContain('Package type: jdk');
|
||||
expect(error.message).toContain('Architecture: x64');
|
||||
expect(error.message).toContain('Platform: linux');
|
||||
expect(error.message).toContain('Available versions: 11.0.1, 11.0.2');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getToolcacheVersionName', () => {
|
||||
const DummyJavaBase = JavaBase as any;
|
||||
|
||||
|
||||
@@ -4,13 +4,14 @@ import {JavaInstallerOptions} from '../../src/distributions/base-models';
|
||||
import {CorrettoDistribution} from '../../src/distributions/corretto/installer';
|
||||
import * as util from '../../src/util';
|
||||
import os from 'os';
|
||||
import {isGeneratorFunction} from 'util/types';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import manifestData from '../data/corretto.json';
|
||||
|
||||
describe('getAvailableVersions', () => {
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyGetDownloadArchiveExtension: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
@@ -23,6 +24,10 @@ describe('getAvailableVersions', () => {
|
||||
util,
|
||||
'getDownloadArchiveExtension'
|
||||
);
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -198,7 +203,7 @@ describe('getAvailableVersions', () => {
|
||||
|
||||
await expect(
|
||||
distribution['findPackageForDownload'](version)
|
||||
).rejects.toThrow("Could not find satisfied version for SemVer '4'");
|
||||
).rejects.toThrow("No matching version found for SemVer '4'");
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import {DragonwellDistribution} from '../../src/distributions/dragonwell/installer';
|
||||
import * as utils from '../../src/util';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import manifestData from '../data/dragonwell.json';
|
||||
|
||||
describe('getAvailableVersions', () => {
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyUtilGetDownloadArchiveExtension: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
@@ -21,6 +23,10 @@ describe('getAvailableVersions', () => {
|
||||
'getDownloadArchiveExtension'
|
||||
);
|
||||
spyUtilGetDownloadArchiveExtension.mockReturnValue('tar.gz');
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -232,7 +238,7 @@ describe('getAvailableVersions', () => {
|
||||
await expect(
|
||||
distribution['findPackageForDownload'](jdkVersion)
|
||||
).rejects.toThrow(
|
||||
`Couldn't find any satisfied version for the specified java-version: "${jdkVersion}" and architecture: "${arch}".`
|
||||
`No matching version found for SemVer '${jdkVersion}'`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -42,6 +42,7 @@ beforeAll(() => {
|
||||
describe('GraalVMDistribution', () => {
|
||||
let distribution: GraalVMDistribution;
|
||||
let mockHttpClient: jest.Mocked<http.HttpClient>;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
const defaultOptions: JavaInstallerOptions = {
|
||||
version: '17',
|
||||
@@ -59,6 +60,10 @@ describe('GraalVMDistribution', () => {
|
||||
(distribution as any).http = mockHttpClient;
|
||||
|
||||
(util.getDownloadArchiveExtension as jest.Mock).mockReturnValue('tar.gz');
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
@@ -348,11 +353,19 @@ describe('GraalVMDistribution', () => {
|
||||
} as http.HttpClientResponse;
|
||||
mockHttpClient.head.mockResolvedValue(mockResponse);
|
||||
|
||||
// Verify the error is thrown with the expected message
|
||||
await expect(
|
||||
(distribution as any).findPackageForDownload('17.0.99')
|
||||
).rejects.toThrow(
|
||||
'Could not find GraalVM for SemVer 17.0.99. Please check if this version is available at https://download.oracle.com/graalvm'
|
||||
);
|
||||
).rejects.toThrow("No matching version found for SemVer '17.0.99'");
|
||||
// Verify distribution info is included
|
||||
await expect(
|
||||
(distribution as any).findPackageForDownload('17.0.99')
|
||||
).rejects.toThrow('GraalVM');
|
||||
|
||||
// Verify the hint about checking the base URL is included
|
||||
await expect(
|
||||
(distribution as any).findPackageForDownload('17.0.99')
|
||||
).rejects.toThrow('https://www.graalvm.org/downloads/');
|
||||
});
|
||||
|
||||
it('should throw error for unauthorized access (401)', async () => {
|
||||
@@ -496,12 +509,19 @@ describe('GraalVMDistribution', () => {
|
||||
|
||||
await expect(
|
||||
(distribution as any).findPackageForDownload('23')
|
||||
).rejects.toThrow("Unable to find latest version for '23-ea'");
|
||||
).rejects.toThrow("No matching version found for SemVer '23-ea'");
|
||||
|
||||
// Verify error logging
|
||||
expect(core.error).toHaveBeenCalledWith(
|
||||
'Available versions: 23-ea-20240716'
|
||||
await expect(
|
||||
(distribution as any).findPackageForDownload('23')
|
||||
).rejects.toThrow(
|
||||
'Note: No EA build is marked as latest for this version.'
|
||||
);
|
||||
|
||||
await expect(
|
||||
(distribution as any).findPackageForDownload('23')
|
||||
).rejects.toThrow('23-ea-20240716');
|
||||
|
||||
// Verify error logging - removed as we now use the helper method which doesn't call core.error
|
||||
});
|
||||
|
||||
it('should throw error when no matching file for architecture in EA build', async () => {
|
||||
@@ -708,11 +728,19 @@ describe('GraalVMDistribution', () => {
|
||||
|
||||
await expect(
|
||||
(distribution as any).findEABuildDownloadUrl('23-ea')
|
||||
).rejects.toThrow("Unable to find latest version for '23-ea'");
|
||||
).rejects.toThrow("No matching version found for SemVer '23-ea'");
|
||||
|
||||
expect(core.error).toHaveBeenCalledWith(
|
||||
'Available versions: 23-ea-20240716, 23-ea-20240709'
|
||||
await expect(
|
||||
(distribution as any).findEABuildDownloadUrl('23-ea')
|
||||
).rejects.toThrow(
|
||||
'Note: No EA build is marked as latest for this version.'
|
||||
);
|
||||
|
||||
await expect(
|
||||
(distribution as any).findEABuildDownloadUrl('23-ea')
|
||||
).rejects.toThrow('23-ea-20240716');
|
||||
|
||||
// Verify error logging - removed as we now use the helper method which doesn't call core.error
|
||||
});
|
||||
|
||||
it('should throw error when no matching file for architecture', async () => {
|
||||
|
||||
@@ -4,9 +4,11 @@ import {JetBrainsDistribution} from '../../src/distributions/jetbrains/installer
|
||||
|
||||
import manifestData from '../data/jetbrains.json';
|
||||
import os from 'os';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
describe('getAvailableVersions', () => {
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
@@ -15,6 +17,10 @@ describe('getAvailableVersions', () => {
|
||||
headers: {},
|
||||
result: []
|
||||
});
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -98,7 +104,7 @@ describe('findPackageForDownload', () => {
|
||||
});
|
||||
distribution['getAvailableVersions'] = async () => manifestData as any;
|
||||
await expect(distribution['findPackageForDownload']('8.x')).rejects.toThrow(
|
||||
/Could not find satisfied version for SemVer */
|
||||
/No matching version found for SemVer */
|
||||
);
|
||||
});
|
||||
|
||||
@@ -111,7 +117,7 @@ describe('findPackageForDownload', () => {
|
||||
});
|
||||
distribution['getAvailableVersions'] = async () => [];
|
||||
await expect(distribution['findPackageForDownload']('8')).rejects.toThrow(
|
||||
/Could not find satisfied version for SemVer */
|
||||
/No matching version found for SemVer */
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,11 +5,13 @@ import {
|
||||
} from '../../src/distributions/liberica/models';
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import os from 'os';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import manifestData from '../data/liberica.json';
|
||||
|
||||
describe('getAvailableVersions', () => {
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
@@ -18,6 +20,10 @@ describe('getAvailableVersions', () => {
|
||||
headers: {},
|
||||
result: manifestData as LibericaVersion[]
|
||||
});
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -209,7 +215,7 @@ describe('findPackageForDownload', () => {
|
||||
|
||||
it('should throw an error', async () => {
|
||||
await expect(distribution['findPackageForDownload']('17')).rejects.toThrow(
|
||||
/Could not find satisfied version for semver */
|
||||
/No matching version found for SemVer/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,11 +5,13 @@ import {
|
||||
} from '../../src/distributions/liberica/models';
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import os from 'os';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import manifestData from '../data/liberica-linux.json';
|
||||
|
||||
describe('getAvailableVersions', () => {
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
@@ -18,6 +20,10 @@ describe('getAvailableVersions', () => {
|
||||
headers: {},
|
||||
result: manifestData as LibericaVersion[]
|
||||
});
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -209,7 +215,7 @@ describe('findPackageForDownload', () => {
|
||||
|
||||
it('should throw an error', async () => {
|
||||
await expect(distribution['findPackageForDownload']('18')).rejects.toThrow(
|
||||
/Could not find satisfied version for semver */
|
||||
/No matching version found for SemVer/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,11 +5,13 @@ import {
|
||||
} from '../../src/distributions/liberica/models';
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import os from 'os';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import manifestData from '../data/liberica-windows.json';
|
||||
|
||||
describe('getAvailableVersions', () => {
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
@@ -18,6 +20,9 @@ describe('getAvailableVersions', () => {
|
||||
headers: {},
|
||||
result: manifestData as LibericaVersion[]
|
||||
});
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -209,7 +214,7 @@ describe('findPackageForDownload', () => {
|
||||
|
||||
it('should throw an error', async () => {
|
||||
await expect(distribution['findPackageForDownload']('18')).rejects.toThrow(
|
||||
/Could not find satisfied version for semver */
|
||||
/No matching version found for SemVer/
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -27,6 +27,7 @@ describe('setupJava', () => {
|
||||
let spyFsReadDir: jest.SpyInstance;
|
||||
let spyUtilsExtractJdkFile: jest.SpyInstance;
|
||||
let spyPathResolve: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
const expectedJdkFile = 'JavaLocalJdkFile';
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -93,6 +94,10 @@ describe('setupJava', () => {
|
||||
// Spy on path methods
|
||||
spyPathResolve = jest.spyOn(path, 'resolve');
|
||||
spyPathResolve.mockImplementation((path: string) => path);
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -8,6 +8,7 @@ describe('findPackageForDownload', () => {
|
||||
let distribution: MicrosoftDistributions;
|
||||
let spyGetManifestFromRepo: jest.SpyInstance;
|
||||
let spyDebug: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
distribution = new MicrosoftDistributions({
|
||||
@@ -26,6 +27,10 @@ describe('findPackageForDownload', () => {
|
||||
|
||||
spyDebug = jest.spyOn(core, 'debug');
|
||||
spyDebug.mockImplementation(() => {});
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -174,7 +179,7 @@ describe('findPackageForDownload', () => {
|
||||
|
||||
it('should throw an error', async () => {
|
||||
await expect(distribution['findPackageForDownload']('8')).rejects.toThrow(
|
||||
/Could not find satisfied version for SemVer */
|
||||
/No matching version found for SemVer */
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ describe('findPackageForDownload', () => {
|
||||
let distribution: OracleDistribution;
|
||||
let spyDebug: jest.SpyInstance;
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
distribution = new OracleDistribution({
|
||||
@@ -19,6 +20,10 @@ describe('findPackageForDownload', () => {
|
||||
|
||||
spyDebug = jest.spyOn(core, 'debug');
|
||||
spyDebug.mockImplementation(() => {});
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import {HttpClient} from '@actions/http-client';
|
||||
import {SapMachineDistribution} from '../../src/distributions/sapmachine/installer';
|
||||
import * as utils from '../../src/util';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import manifestData from '../data/sapmachine.json';
|
||||
|
||||
describe('getAvailableVersions', () => {
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyUtilGetDownloadArchiveExtension: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
@@ -21,6 +23,10 @@ describe('getAvailableVersions', () => {
|
||||
'getDownloadArchiveExtension'
|
||||
);
|
||||
spyUtilGetDownloadArchiveExtension.mockReturnValue('tar.gz');
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -266,7 +272,7 @@ describe('getAvailableVersions', () => {
|
||||
await expect(
|
||||
distribution['findPackageForDownload'](normalizedVersion)
|
||||
).rejects.toThrow(
|
||||
`Couldn't find any satisfied version for the specified java-version: "${normalizedVersion}" and architecture: "${arch}".`
|
||||
`No matching version found for SemVer '${normalizedVersion}'`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -4,9 +4,12 @@ import {JavaInstallerOptions} from '../../src/distributions/base-models';
|
||||
import {SemeruDistribution} from '../../src/distributions/semeru/installer';
|
||||
|
||||
import manifestData from '../data/semeru.json';
|
||||
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');
|
||||
@@ -15,6 +18,11 @@ describe('getAvailableVersions', () => {
|
||||
headers: {},
|
||||
result: []
|
||||
});
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
spyCoreWarning = jest.spyOn(core, 'warning');
|
||||
spyCoreWarning.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -77,22 +85,19 @@ describe('getAvailableVersions', () => {
|
||||
);
|
||||
|
||||
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
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
headers: {link: `<${nextPageUrl}>; rel="next"`},
|
||||
result: manifestData as any
|
||||
})
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: manifestData as any
|
||||
})
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: []
|
||||
});
|
||||
|
||||
const distribution = new SemeruDistribution({
|
||||
@@ -104,6 +109,31 @@ describe('getAvailableVersions', () => {
|
||||
const availableVersions = await distribution['getAvailableVersions']();
|
||||
expect(availableVersions).not.toBeNull();
|
||||
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([
|
||||
@@ -152,7 +182,7 @@ describe('findPackageForDownload', () => {
|
||||
distribution['getAvailableVersions'] = async () => manifestData as any;
|
||||
await expect(
|
||||
distribution['findPackageForDownload']('9.0.8')
|
||||
).rejects.toThrow(/Could not find satisfied version for SemVer */);
|
||||
).rejects.toThrow(/No matching version found for SemVer */);
|
||||
});
|
||||
|
||||
it('version is not found', async () => {
|
||||
@@ -164,7 +194,7 @@ describe('findPackageForDownload', () => {
|
||||
});
|
||||
distribution['getAvailableVersions'] = async () => manifestData as any;
|
||||
await expect(distribution['findPackageForDownload']('7.x')).rejects.toThrow(
|
||||
/Could not find satisfied version for SemVer */
|
||||
/No matching version found for SemVer */
|
||||
);
|
||||
});
|
||||
|
||||
@@ -177,7 +207,7 @@ describe('findPackageForDownload', () => {
|
||||
});
|
||||
distribution['getAvailableVersions'] = async () => [];
|
||||
await expect(distribution['findPackageForDownload']('8')).rejects.toThrow(
|
||||
/Could not find satisfied version for SemVer */
|
||||
/No matching version found for SemVer */
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -7,9 +7,12 @@ import {
|
||||
import {JavaInstallerOptions} from '../../src/distributions/base-models';
|
||||
|
||||
import manifestData from '../data/temurin.json';
|
||||
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');
|
||||
@@ -18,6 +21,11 @@ describe('getAvailableVersions', () => {
|
||||
headers: {},
|
||||
result: []
|
||||
});
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
spyCoreWarning = jest.spyOn(core, 'warning');
|
||||
spyCoreWarning.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -88,22 +96,19 @@ describe('getAvailableVersions', () => {
|
||||
);
|
||||
|
||||
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
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
headers: {link: `<${nextPageUrl}>; rel="next"`},
|
||||
result: manifestData as any
|
||||
})
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: manifestData as any
|
||||
})
|
||||
.mockReturnValueOnce({
|
||||
statusCode: 200,
|
||||
headers: {},
|
||||
result: []
|
||||
});
|
||||
|
||||
const distribution = new TemurinDistribution(
|
||||
@@ -118,6 +123,34 @@ describe('getAvailableVersions', () => {
|
||||
const availableVersions = await distribution['getAvailableVersions']();
|
||||
expect(availableVersions).not.toBeNull();
|
||||
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([
|
||||
@@ -213,7 +246,7 @@ describe('findPackageForDownload', () => {
|
||||
distribution['getAvailableVersions'] = async () => manifestData as any;
|
||||
await expect(
|
||||
distribution['findPackageForDownload']('9.0.8')
|
||||
).rejects.toThrow(/Could not find satisfied version for SemVer */);
|
||||
).rejects.toThrow(/No matching version found for SemVer */);
|
||||
});
|
||||
|
||||
it('version is not found', async () => {
|
||||
@@ -228,7 +261,7 @@ describe('findPackageForDownload', () => {
|
||||
);
|
||||
distribution['getAvailableVersions'] = async () => manifestData as any;
|
||||
await expect(distribution['findPackageForDownload']('7.x')).rejects.toThrow(
|
||||
/Could not find satisfied version for SemVer */
|
||||
/No matching version found for SemVer */
|
||||
);
|
||||
});
|
||||
|
||||
@@ -244,7 +277,7 @@ describe('findPackageForDownload', () => {
|
||||
);
|
||||
distribution['getAvailableVersions'] = async () => [];
|
||||
await expect(distribution['findPackageForDownload']('8')).rejects.toThrow(
|
||||
/Could not find satisfied version for SemVer */
|
||||
/No matching version found for SemVer */
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,12 +3,14 @@ import {ZuluDistribution} from '../../src/distributions/zulu/installer';
|
||||
import {IZuluVersions} from '../../src/distributions/zulu/models';
|
||||
import * as utils from '../../src/util';
|
||||
import os from 'os';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import manifestData from '../data/zulu-releases-default.json';
|
||||
|
||||
describe('getAvailableVersions', () => {
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyUtilGetDownloadArchiveExtension: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
@@ -23,6 +25,10 @@ describe('getAvailableVersions', () => {
|
||||
'getDownloadArchiveExtension'
|
||||
);
|
||||
spyUtilGetDownloadArchiveExtension.mockReturnValue('tar.gz');
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -225,6 +231,6 @@ describe('findPackageForDownload', () => {
|
||||
distribution['getAvailableVersions'] = async () => manifestData;
|
||||
await expect(
|
||||
distribution['findPackageForDownload'](distribution['version'])
|
||||
).rejects.toThrow(/Could not find satisfied version for semver */);
|
||||
).rejects.toThrow(/No matching version found for SemVer/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,12 +4,14 @@ import {ZuluDistribution} from '../../src/distributions/zulu/installer';
|
||||
import {IZuluVersions} from '../../src/distributions/zulu/models';
|
||||
import * as utils from '../../src/util';
|
||||
import os from 'os';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import manifestData from '../data/zulu-linux.json';
|
||||
|
||||
describe('getAvailableVersions', () => {
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyUtilGetDownloadArchiveExtension: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
@@ -24,6 +26,10 @@ describe('getAvailableVersions', () => {
|
||||
'getDownloadArchiveExtension'
|
||||
);
|
||||
spyUtilGetDownloadArchiveExtension.mockReturnValue('zip');
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -228,6 +234,6 @@ describe('findPackageForDownload', () => {
|
||||
distribution['getAvailableVersions'] = async () => manifestData;
|
||||
await expect(
|
||||
distribution['findPackageForDownload'](distribution['version'])
|
||||
).rejects.toThrow(/Could not find satisfied version for semver */);
|
||||
).rejects.toThrow(/No matching version found for SemVer/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,12 +4,14 @@ import {ZuluDistribution} from '../../src/distributions/zulu/installer';
|
||||
import {IZuluVersions} from '../../src/distributions/zulu/models';
|
||||
import * as utils from '../../src/util';
|
||||
import os from 'os';
|
||||
import * as core from '@actions/core';
|
||||
|
||||
import manifestData from '../data/zulu-windows.json';
|
||||
|
||||
describe('getAvailableVersions', () => {
|
||||
let spyHttpClient: jest.SpyInstance;
|
||||
let spyUtilGetDownloadArchiveExtension: jest.SpyInstance;
|
||||
let spyCoreError: jest.SpyInstance;
|
||||
|
||||
beforeEach(() => {
|
||||
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
|
||||
@@ -24,6 +26,10 @@ describe('getAvailableVersions', () => {
|
||||
'getDownloadArchiveExtension'
|
||||
);
|
||||
spyUtilGetDownloadArchiveExtension.mockReturnValue('zip');
|
||||
|
||||
// Mock core.error to suppress error logs
|
||||
spyCoreError = jest.spyOn(core, 'error');
|
||||
spyCoreError.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -226,6 +232,6 @@ describe('findPackageForDownload', () => {
|
||||
distribution['getAvailableVersions'] = async () => manifestData;
|
||||
await expect(
|
||||
distribution['findPackageForDownload'](distribution['version'])
|
||||
).rejects.toThrow(/Could not find satisfied version for semver */);
|
||||
).rejects.toThrow(/No matching version found for SemVer/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,10 +4,12 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
convertVersionToSemver,
|
||||
getNextPageUrlFromLinkHeader,
|
||||
getVersionFromFileContent,
|
||||
isVersionSatisfies,
|
||||
isCacheFeatureAvailable,
|
||||
isGhes
|
||||
isGhes,
|
||||
validatePaginationUrl
|
||||
} from '../src/util';
|
||||
|
||||
jest.mock('@actions/cache');
|
||||
@@ -85,6 +87,78 @@ 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('.sdkmanrc', () => {
|
||||
it.each([
|
||||
|
||||
333
dist/cleanup/index.js
vendored
333
dist/cleanup/index.js
vendored
File diff suppressed because one or more lines are too long
595
dist/setup/index.js
vendored
595
dist/setup/index.js
vendored
File diff suppressed because one or more lines are too long
3620
package-lock.json
generated
3620
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -40,20 +40,20 @@
|
||||
"xmlbuilder2": "^4.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^24.1.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^25.9.3",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@typescript-eslint/eslint-plugin": "^8.35.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
||||
"@typescript-eslint/parser": "^8.35.1",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"@vercel/ncc": "^0.44.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-jest": "^29.0.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"jest": "^29.7.0",
|
||||
"jest-circus": "^29.7.0",
|
||||
"jest": "^30.4.2",
|
||||
"jest-circus": "^30.4.2",
|
||||
"prettier": "^3.6.2",
|
||||
"ts-jest": "^29.3.0",
|
||||
"ts-jest": "^29.4.11",
|
||||
"typescript": "^5.3.3"
|
||||
},
|
||||
"bugs": {
|
||||
|
||||
@@ -14,10 +14,14 @@ import {
|
||||
} from '../base-models';
|
||||
import {
|
||||
extractJdkFile,
|
||||
getNextPageUrlFromLinkHeader,
|
||||
getDownloadArchiveExtension,
|
||||
isVersionSatisfies,
|
||||
renameWinArchive
|
||||
renameWinArchive,
|
||||
MAX_PAGINATION_PAGES,
|
||||
validatePaginationUrl
|
||||
} from '../../util';
|
||||
import {TemurinDistribution, TemurinImplementation} from '../temurin/installer';
|
||||
|
||||
export enum AdoptImplementation {
|
||||
Hotspot = 'Hotspot',
|
||||
@@ -25,15 +29,72 @@ export enum AdoptImplementation {
|
||||
}
|
||||
|
||||
export class AdoptDistribution extends JavaBase {
|
||||
private readonly temurinDistribution: TemurinDistribution | null;
|
||||
|
||||
constructor(
|
||||
installerOptions: JavaInstallerOptions,
|
||||
private readonly jvmImpl: AdoptImplementation
|
||||
private readonly jvmImpl: AdoptImplementation,
|
||||
temurinDistribution: TemurinDistribution | null = null
|
||||
) {
|
||||
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(
|
||||
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> {
|
||||
const availableVersionsRaw = await this.getAvailableVersions();
|
||||
const availableVersionsWithBinaries = availableVersionsRaw
|
||||
@@ -54,15 +115,10 @@ export class AdoptDistribution extends JavaBase {
|
||||
const resolvedFullVersion =
|
||||
satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
|
||||
if (!resolvedFullVersion) {
|
||||
const availableOptions = availableVersionsWithBinaries
|
||||
.map(item => item.version)
|
||||
.join(', ');
|
||||
const availableOptionsMessage = availableOptions
|
||||
? `\nAvailable versions: ${availableOptions}`
|
||||
: '';
|
||||
throw new Error(
|
||||
`Could not find satisfied version for SemVer '${version}'. ${availableOptionsMessage}`
|
||||
const availableVersionStrings = availableVersionsWithBinaries.map(
|
||||
item => item.version
|
||||
);
|
||||
throw this.createVersionNotFoundError(version, availableVersionStrings);
|
||||
}
|
||||
|
||||
return resolvedFullVersion;
|
||||
@@ -130,30 +186,46 @@ export class AdoptDistribution extends JavaBase {
|
||||
`jvm_impl=${this.jvmImpl.toLowerCase()}`
|
||||
].join('&');
|
||||
|
||||
// need to iterate through all pages to retrieve the list of all versions
|
||||
// Adopt API doesn't provide way to retrieve the count of pages to iterate so infinity loop
|
||||
let page_index = 0;
|
||||
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
|
||||
let availableVersionsUrl: string | null =
|
||||
`https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
|
||||
const availableVersions: IAdoptAvailableVersions[] = [];
|
||||
while (true) {
|
||||
const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`;
|
||||
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}'`
|
||||
);
|
||||
}
|
||||
let pageCount = 0;
|
||||
if (core.isDebug()) {
|
||||
core.debug(`Gathering available versions from '${availableVersionsUrl}'`);
|
||||
}
|
||||
|
||||
const paginationPage = (
|
||||
await this.http.getJson<IAdoptAvailableVersions[]>(availableVersionsUrl)
|
||||
).result;
|
||||
while (availableVersionsUrl) {
|
||||
pageCount++;
|
||||
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;
|
||||
}
|
||||
if (paginationPage === null || paginationPage.length === 0) {
|
||||
// break infinity loop because we have reached end of pagination
|
||||
break;
|
||||
}
|
||||
|
||||
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()) {
|
||||
|
||||
@@ -259,6 +259,44 @@ export abstract class JavaBase {
|
||||
};
|
||||
}
|
||||
|
||||
protected createVersionNotFoundError(
|
||||
versionOrRange: string,
|
||||
availableVersions?: string[],
|
||||
additionalContext?: string
|
||||
): Error {
|
||||
const parts = [
|
||||
`No matching version found for SemVer '${versionOrRange}'.`,
|
||||
`Distribution: ${this.distribution}`,
|
||||
`Package type: ${this.packageType}`,
|
||||
`Architecture: ${this.architecture}`
|
||||
];
|
||||
|
||||
// Add additional context if provided (e.g., platform/OS info)
|
||||
if (additionalContext) {
|
||||
parts.push(additionalContext);
|
||||
}
|
||||
|
||||
if (availableVersions && availableVersions.length > 0) {
|
||||
const maxVersionsToShow = core.isDebug() ? availableVersions.length : 50;
|
||||
const versionsToShow = availableVersions.slice(0, maxVersionsToShow);
|
||||
const truncated = availableVersions.length > maxVersionsToShow;
|
||||
|
||||
parts.push(
|
||||
`Available versions: ${versionsToShow.join(', ')}${truncated ? ', ...' : ''}`
|
||||
);
|
||||
|
||||
if (truncated) {
|
||||
parts.push(
|
||||
`(showing first ${maxVersionsToShow} of ${availableVersions.length} versions, enable debug mode to see all)`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const error = new Error(parts.join('\n'));
|
||||
error.name = 'VersionNotFoundError';
|
||||
return error;
|
||||
}
|
||||
|
||||
protected setJavaDefault(version: string, toolPath: string) {
|
||||
const majorVersion = version.split('.')[0];
|
||||
core.exportVariable('JAVA_HOME', toolPath);
|
||||
|
||||
@@ -75,15 +75,10 @@ export class CorrettoDistribution extends JavaBase {
|
||||
const resolvedVersion =
|
||||
matchingVersions.length > 0 ? matchingVersions[0] : null;
|
||||
if (!resolvedVersion) {
|
||||
const availableOptions = availableVersions
|
||||
.map(item => item.version)
|
||||
.join(', ');
|
||||
const availableOptionsMessage = availableOptions
|
||||
? `\nAvailable versions: ${availableOptions}`
|
||||
: '';
|
||||
throw new Error(
|
||||
`Could not find satisfied version for SemVer '${version}'. ${availableOptionsMessage}`
|
||||
const availableVersionStrings = availableVersions.map(
|
||||
item => item.version
|
||||
);
|
||||
throw this.createVersionNotFoundError(version, availableVersionStrings);
|
||||
}
|
||||
return resolvedVersion;
|
||||
}
|
||||
|
||||
@@ -51,9 +51,10 @@ export class DragonwellDistribution extends JavaBase {
|
||||
});
|
||||
|
||||
if (!matchedVersions.length) {
|
||||
throw new Error(
|
||||
`Couldn't find any satisfied version for the specified java-version: "${version}" and architecture: "${this.architecture}".`
|
||||
const availableVersionStrings = availableVersions.map(
|
||||
item => item.jdk_version
|
||||
);
|
||||
throw this.createVersionNotFoundError(version, availableVersionStrings);
|
||||
}
|
||||
|
||||
const resolvedVersion = matchedVersions[0];
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from '../../util';
|
||||
|
||||
const GRAALVM_DL_BASE = 'https://download.oracle.com/graalvm';
|
||||
const GRAALVM_DOWNLOAD_URL = 'https://www.graalvm.org/downloads/';
|
||||
const IS_WINDOWS = process.platform === 'win32';
|
||||
const GRAALVM_PLATFORM = IS_WINDOWS ? 'windows' : process.platform;
|
||||
const GRAALVM_MIN_VERSION = 17;
|
||||
@@ -149,9 +150,10 @@ export class GraalVMDistribution extends JavaBase {
|
||||
const statusCode = response.message.statusCode;
|
||||
|
||||
if (statusCode === HttpCodes.NotFound) {
|
||||
throw new Error(
|
||||
`Could not find GraalVM for SemVer ${range}. Please check if this version is available at ${GRAALVM_DL_BASE}`
|
||||
);
|
||||
// Create the standard error with additional hint about checking the download URL
|
||||
const error = this.createVersionNotFoundError(range);
|
||||
error.message += `\nPlease check if this version is available at ${GRAALVM_DOWNLOAD_URL} . Pick a version from the list.`;
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -180,10 +182,12 @@ export class GraalVMDistribution extends JavaBase {
|
||||
|
||||
const latestVersion = versions.find(v => v.latest);
|
||||
if (!latestVersion) {
|
||||
core.error(
|
||||
`Available versions: ${versions.map(v => v.version).join(', ')}`
|
||||
const availableVersions = versions.map(v => v.version);
|
||||
throw this.createVersionNotFoundError(
|
||||
javaEaVersion,
|
||||
availableVersions,
|
||||
'Note: No EA build is marked as latest for this version.'
|
||||
);
|
||||
throw new Error(`Unable to find latest version for '${javaEaVersion}'`);
|
||||
}
|
||||
|
||||
core.debug(`Latest version found: ${latestVersion.version}`);
|
||||
|
||||
@@ -44,15 +44,10 @@ export class JetBrainsDistribution extends JavaBase {
|
||||
const resolvedFullVersion =
|
||||
satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
|
||||
if (!resolvedFullVersion) {
|
||||
const availableOptions = versionsRaw
|
||||
.map(item => `${item.tag_name} (${item.semver}+${item.build})`)
|
||||
.join(', ');
|
||||
const availableOptionsMessage = availableOptions
|
||||
? `\nAvailable versions: ${availableOptions}`
|
||||
: '';
|
||||
throw new Error(
|
||||
`Could not find satisfied version for SemVer '${range}'. ${availableOptionsMessage}`
|
||||
const availableVersionStrings = versionsRaw.map(
|
||||
item => `${item.tag_name} (${item.semver}+${item.build})`
|
||||
);
|
||||
throw this.createVersionNotFoundError(range, availableVersionStrings);
|
||||
}
|
||||
|
||||
return resolvedFullVersion;
|
||||
|
||||
@@ -69,15 +69,10 @@ export class LibericaDistributions extends JavaBase {
|
||||
.sort((a, b) => -semver.compareBuild(a.version, b.version))[0];
|
||||
|
||||
if (!satisfiedVersion) {
|
||||
const availableOptions = availableVersions
|
||||
.map(item => item.version)
|
||||
.join(', ');
|
||||
const availableOptionsMessage = availableOptions
|
||||
? `\nAvailable versions: ${availableOptions}`
|
||||
: '';
|
||||
throw new Error(
|
||||
`Could not find satisfied version for semver ${range}. ${availableOptionsMessage}`
|
||||
const availableVersionStrings = availableVersions.map(
|
||||
item => item.version
|
||||
);
|
||||
throw this.createVersionNotFoundError(range, availableVersionStrings);
|
||||
}
|
||||
|
||||
return satisfiedVersion;
|
||||
|
||||
@@ -76,11 +76,8 @@ export class MicrosoftDistributions extends JavaBase {
|
||||
const foundRelease = await tc.findFromManifest(range, true, manifest, arch);
|
||||
|
||||
if (!foundRelease) {
|
||||
throw new Error(
|
||||
`Could not find satisfied version for SemVer ${range}.\nAvailable versions: ${manifest
|
||||
.map(item => item.version)
|
||||
.join(', ')}`
|
||||
);
|
||||
const availableVersionStrings = manifest.map(item => item.version);
|
||||
throw this.createVersionNotFoundError(range, availableVersionStrings);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -112,7 +112,7 @@ export class OracleDistribution extends JavaBase {
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Could not find Oracle JDK for SemVer ${range}`);
|
||||
throw this.createVersionNotFoundError(range);
|
||||
}
|
||||
|
||||
public getPlatform(platform: NodeJS.Platform = process.platform): OsVersions {
|
||||
|
||||
@@ -49,9 +49,10 @@ export class SapMachineDistribution extends JavaBase {
|
||||
});
|
||||
|
||||
if (!matchedVersions.length) {
|
||||
throw new Error(
|
||||
`Couldn't find any satisfied version for the specified java-version: "${version}" and architecture: "${this.architecture}".`
|
||||
const availableVersionStrings = availableVersions.map(
|
||||
item => item.version
|
||||
);
|
||||
throw this.createVersionNotFoundError(version, availableVersionStrings);
|
||||
}
|
||||
|
||||
const resolvedVersion = matchedVersions[0];
|
||||
|
||||
@@ -7,9 +7,12 @@ import {
|
||||
import semver from 'semver';
|
||||
import {
|
||||
extractJdkFile,
|
||||
getNextPageUrlFromLinkHeader,
|
||||
getDownloadArchiveExtension,
|
||||
isVersionSatisfies,
|
||||
renameWinArchive
|
||||
renameWinArchive,
|
||||
MAX_PAGINATION_PAGES,
|
||||
validatePaginationUrl
|
||||
} from '../../util';
|
||||
import * as core from '@actions/core';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
@@ -79,14 +82,16 @@ export class SemeruDistribution extends JavaBase {
|
||||
const resolvedFullVersion =
|
||||
satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
|
||||
if (!resolvedFullVersion) {
|
||||
const availableOptions = availableVersionsWithBinaries
|
||||
.map(item => item.version)
|
||||
.join(', ');
|
||||
const availableOptionsMessage = availableOptions
|
||||
? `\nAvailable versions: ${availableOptions}`
|
||||
: '';
|
||||
throw new Error(
|
||||
`Could not find satisfied version for SemVer version '${version}' for your current OS version for ${this.architecture} architecture ${availableOptionsMessage}`
|
||||
const availableVersionStrings = availableVersionsWithBinaries.map(
|
||||
item => item.version
|
||||
);
|
||||
// Include platform context to help users understand OS-specific version availability
|
||||
// IBM Semeru builds are OS-specific, so platform info aids in troubleshooting
|
||||
const platformContext = `Platform: ${process.platform}`;
|
||||
throw this.createVersionNotFoundError(
|
||||
version,
|
||||
availableVersionStrings,
|
||||
platformContext
|
||||
);
|
||||
}
|
||||
|
||||
@@ -153,32 +158,46 @@ export class SemeruDistribution extends JavaBase {
|
||||
`jvm_impl=openj9`
|
||||
].join('&');
|
||||
|
||||
// need to iterate through all pages to retrieve the list of all versions
|
||||
// Adoptium API doesn't provide way to retrieve the count of pages to iterate so infinity loop
|
||||
let page_index = 0;
|
||||
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
|
||||
let availableVersionsUrl: string | null =
|
||||
`https://api.adoptopenjdk.net/v3/assets/version/${versionRange}?${requestArguments}`;
|
||||
const availableVersions: ISemeruAvailableVersions[] = [];
|
||||
while (true) {
|
||||
const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`;
|
||||
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}'`
|
||||
);
|
||||
}
|
||||
let pageCount = 0;
|
||||
if (core.isDebug()) {
|
||||
core.debug(`Gathering available versions from '${availableVersionsUrl}'`);
|
||||
}
|
||||
|
||||
const paginationPage = (
|
||||
while (availableVersionsUrl) {
|
||||
pageCount++;
|
||||
const response =
|
||||
await this.http.getJson<ISemeruAvailableVersions[]>(
|
||||
availableVersionsUrl
|
||||
)
|
||||
).result;
|
||||
);
|
||||
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;
|
||||
}
|
||||
if (paginationPage === null || paginationPage.length === 0) {
|
||||
// break infinity loop because we have reached end of pagination
|
||||
break;
|
||||
}
|
||||
|
||||
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()) {
|
||||
|
||||
@@ -14,9 +14,12 @@ import {
|
||||
} from '../base-models';
|
||||
import {
|
||||
extractJdkFile,
|
||||
getNextPageUrlFromLinkHeader,
|
||||
getDownloadArchiveExtension,
|
||||
isVersionSatisfies,
|
||||
renameWinArchive
|
||||
renameWinArchive,
|
||||
MAX_PAGINATION_PAGES,
|
||||
validatePaginationUrl
|
||||
} from '../../util';
|
||||
|
||||
export enum TemurinImplementation {
|
||||
@@ -31,7 +34,10 @@ export class TemurinDistribution extends JavaBase {
|
||||
super(`Temurin-${jvmImpl}`, installerOptions);
|
||||
}
|
||||
|
||||
protected async findPackageForDownload(
|
||||
/**
|
||||
* @internal For cross-distribution reuse only. Not intended as a public API.
|
||||
*/
|
||||
public async findPackageForDownload(
|
||||
version: string
|
||||
): Promise<JavaDownloadRelease> {
|
||||
const availableVersionsRaw = await this.getAvailableVersions();
|
||||
@@ -57,15 +63,10 @@ export class TemurinDistribution extends JavaBase {
|
||||
const resolvedFullVersion =
|
||||
satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
|
||||
if (!resolvedFullVersion) {
|
||||
const availableOptions = availableVersionsWithBinaries
|
||||
.map(item => item.version)
|
||||
.join(', ');
|
||||
const availableOptionsMessage = availableOptions
|
||||
? `\nAvailable versions: ${availableOptions}`
|
||||
: '';
|
||||
throw new Error(
|
||||
`Could not find satisfied version for SemVer '${version}'. ${availableOptionsMessage}`
|
||||
const availableVersionStrings = availableVersionsWithBinaries.map(
|
||||
item => item.version
|
||||
);
|
||||
throw this.createVersionNotFoundError(version, availableVersionStrings);
|
||||
}
|
||||
|
||||
return resolvedFullVersion;
|
||||
@@ -128,32 +129,47 @@ export class TemurinDistribution extends JavaBase {
|
||||
`jvm_impl=${this.jvmImpl.toLowerCase()}`
|
||||
].join('&');
|
||||
|
||||
// need to iterate through all pages to retrieve the list of all versions
|
||||
// Adoptium API doesn't provide way to retrieve the count of pages to iterate so infinity loop
|
||||
let page_index = 0;
|
||||
const requestArguments = `${baseRequestArguments}&page_size=20&page=0`;
|
||||
let availableVersionsUrl: string | null =
|
||||
`https://api.adoptium.net/v3/assets/version/${versionRange}?${requestArguments}`;
|
||||
const availableVersions: ITemurinAvailableVersions[] = [];
|
||||
while (true) {
|
||||
const requestArguments = `${baseRequestArguments}&page_size=20&page=${page_index}`;
|
||||
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
|
||||
core.debug(
|
||||
`Gathering available versions from '${availableVersionsUrl}'`
|
||||
);
|
||||
}
|
||||
let pageCount = 0;
|
||||
if (core.isDebug()) {
|
||||
core.debug(`Gathering available versions from '${availableVersionsUrl}'`);
|
||||
}
|
||||
|
||||
const paginationPage = (
|
||||
while (availableVersionsUrl) {
|
||||
pageCount++;
|
||||
const response =
|
||||
await this.http.getJson<ITemurinAvailableVersions[]>(
|
||||
availableVersionsUrl
|
||||
)
|
||||
).result;
|
||||
);
|
||||
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;
|
||||
}
|
||||
|
||||
if (paginationPage === null || paginationPage.length === 0) {
|
||||
// break infinity loop because we have reached end of pagination
|
||||
break;
|
||||
}
|
||||
|
||||
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()) {
|
||||
@@ -176,6 +192,11 @@ export class TemurinDistribution extends JavaBase {
|
||||
return 'mac';
|
||||
case 'win32':
|
||||
return 'windows';
|
||||
case 'linux':
|
||||
if (fs.existsSync('/etc/alpine-release')) {
|
||||
return 'alpine-linux';
|
||||
}
|
||||
return 'linux';
|
||||
default:
|
||||
return process.platform;
|
||||
}
|
||||
|
||||
@@ -57,15 +57,10 @@ export class ZuluDistribution extends JavaBase {
|
||||
const resolvedFullVersion =
|
||||
satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
|
||||
if (!resolvedFullVersion) {
|
||||
const availableOptions = availableVersions
|
||||
.map(item => item.version)
|
||||
.join(', ');
|
||||
const availableOptionsMessage = availableOptions
|
||||
? `\nAvailable versions: ${availableOptions}`
|
||||
: '';
|
||||
throw new Error(
|
||||
`Could not find satisfied version for semver ${version}. ${availableOptionsMessage}`
|
||||
const availableVersionStrings = availableVersions.map(
|
||||
item => item.version
|
||||
);
|
||||
throw this.createVersionNotFoundError(version, availableVersionStrings);
|
||||
}
|
||||
|
||||
return resolvedFullVersion;
|
||||
|
||||
49
src/util.ts
49
src/util.ts
@@ -201,6 +201,55 @@ export function getGitHubHttpHeaders(): OutgoingHttpHeaders {
|
||||
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
|
||||
// archive does not contain extension type and it leads to some issues
|
||||
// on Windows runners without PowerShell Core.
|
||||
|
||||
Reference in New Issue
Block a user