Compare commits

...

16 Commits

Author SHA1 Message Date
Gerrit Grunwald
7b6be3ed76
Merge d3af3ec854 into baa1691374 2026-06-19 19:26:58 +00:00
Sean Proctor
baa1691374
fix: reject non-semver candidate versions in isVersionSatisfies (#1009)
Distributions like JetBrains Runtime publish 4-segment versions such as
'17.0.8.1+1080.1' that the semver package rejects. Both compareBuild and
satisfies throw on these, which surfaced to users as "Error: Invalid
Version: 17.0.8.1+1080.1" and aborted the whole install when any
available version was non-semver. Guard with an early semver.valid check
so unparseable versions are treated as a non-match.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-17 22:47:02 -05:00
George Adams
bc52a13212
fix CodeQL permissions (#1025) 2026-06-17 07:58:23 -07:00
Josh Soref
c9b6aee07e
Fix codeql workflow permissions (#993)
Signed-off-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2026-06-17 07:52:02 -07:00
dependabot[bot]
f300429fba
Bump @typescript-eslint/parser from 8.48.0 to 8.61.1 (#1021)
* Bump @typescript-eslint/parser from 8.48.0 to 8.61.1

Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 8.48.0 to 8.61.1.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.1/packages/parser)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.61.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* run licensed and update dist

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: George Adams <georgeadams1995@gmail.com>
2026-06-16 15:12:38 -07:00
dependabot[bot]
ad2b38190b
Bump @vercel/ncc from 0.38.1 to 0.44.0 (#1018)
* Bump @vercel/ncc from 0.38.1 to 0.44.0

Bumps [@vercel/ncc](https://github.com/vercel/ncc) from 0.38.1 to 0.44.0.
- [Release notes](https://github.com/vercel/ncc/releases)
- [Commits](https://github.com/vercel/ncc/compare/0.38.1...0.44.0)

---
updated-dependencies:
- dependency-name: "@vercel/ncc"
  dependency-version: 0.44.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* recompile dist

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: George Adams <georgeadams1995@gmail.com>
2026-06-16 09:37:47 +02:00
John
b24df5bba5
Make the Adoptopenjdk package type look at the Temurin repo first for latest assets (#522)
* Make the Adoptopenjdk package type look at the Temurin repo first for latest assets

* Address Copilot code review comments

- Use strict equality (===, !==) instead of loose equality (==, !=) for all comparisons
- Properly handle caught errors with instanceof type narrowing before accessing properties
- Only fall back to legacy AdoptOpenJDK for specific version-not-found errors
- Rethrow unexpected errors to avoid masking real issues (network failures, rate limits, etc.)
- Fix error message check to match actual error text ('No matching version found')
- Remove unnecessary undefined check since method return type is never undefined
- Add @internal JSDoc annotation to TemurinDistribution.findPackageForDownload()
- Update tests to properly mock Temurin lookup failures for fallback behavior testing
- Rebuild dist files

* Always fall back to legacy AdoptOpenJDK but log all Temurin failures

- Change error handling to gracefully fall back for all errors, not just version-not-found
- Log version-not-found errors as notices with migration guidance
- Log other Temurin failures as debug messages for troubleshooting
- Improves resilience: users always get a result even if Temurin API has issues
- Maintains visibility: failures are still logged for debugging

* Fixes from review

* Fixes from review

* Fixes from review

* Regenerate dist
2026-06-12 16:30:59 +01:00
John
43120bc3c3
Implement pagination with link headers for Adoptium based apis (#1014)
* Use Link headers for Adoptium pagination

* Fix nullable pagination URL types and rebuild dist

* Add 1000-page safeguard for JetBrains pagination

* Adjust plan for pagination safeguard scope

* Move pagination safeguard to non-JetBrains installers

* Add 1000-page safeguard to Adopt Temurin and Semeru pagination

* Fix Prettier formatting in adopt, semeru, and temurin installer files

* Fix CI audit failure by updating vulnerable transitive deps

* Address PR review: RFC-compliant Link parsing, SSRF validation, centralized constant

- Make getNextPageUrlFromLinkHeader RFC 8288 compliant by splitting
  link-values and checking for rel=next anywhere in the parameters,
  not just as the first parameter after the semicolon.
- Add validatePaginationUrl utility to reject pagination URLs that
  point to unexpected origins (SSRF mitigation).
- Centralize MAX_PAGINATION_PAGES in util.ts instead of duplicating
  across Adopt, Semeru, and Temurin installers.
- Add tests for rel not being the first parameter, and for URL
  origin validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address code review feedback on pagination implementation

- Tighten rel regex with word boundary to prevent false positives
  (e.g., rel="nextsomething" no longer matches).
- Use parsed.origin comparison in validatePaginationUrl to correctly
  handle explicit default ports (e.g., :443 for HTTPS).
- Fix pagination safeguard tests to use same-origin URLs so they
  actually exercise the 1000-page limit instead of being rejected
  by origin validation on the first request.
- Add test for rel="nextsomething" not matching.
- Add test for explicit default port acceptance.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix prettier formatting in util.test.ts

* Rebuild dist/ to fix check-dist CI failure

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-06-12 11:50:16 +01:00
dependabot[bot]
ad9d6a6320
Bump @types/node from 24.1.0 to 25.9.3 (#950)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 24.1.0 to 25.9.3.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node)

---
updated-dependencies:
- dependency-name: "@types/node"
  dependency-version: 24.9.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-12 10:21:27 +01:00
dependabot[bot]
039af37997
Bump picomatch, @types/jest, jest, jest-circus and ts-jest (#1016)
* Bump picomatch, @types/jest, jest, jest-circus and ts-jest

Bumps [picomatch](https://github.com/micromatch/picomatch) to 4.0.4 and updates ancestor dependencies [picomatch](https://github.com/micromatch/picomatch), [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest), [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest), [jest-circus](https://github.com/jestjs/jest/tree/HEAD/packages/jest-circus) and [ts-jest](https://github.com/kulshekhar/ts-jest). These dependencies need to be updated together.


Updates `picomatch` from 4.0.3 to 4.0.4
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4)

Updates `@types/jest` from 29.5.14 to 30.0.0
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

Updates `jest` from 29.7.0 to 30.4.2
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.4.2/packages/jest)

Updates `jest-circus` from 29.7.0 to 30.4.2
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.4.2/packages/jest-circus)

Updates `ts-jest` from 29.3.0 to 29.4.11
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.3.0...v29.4.11)

---
updated-dependencies:
- dependency-name: "@types/jest"
  dependency-version: 30.0.0
  dependency-type: direct:development
- dependency-name: jest
  dependency-version: 30.4.2
  dependency-type: direct:development
- dependency-name: jest-circus
  dependency-version: 30.4.2
  dependency-type: direct:development
- dependency-name: picomatch
  dependency-version: 4.0.4
  dependency-type: indirect
- dependency-name: ts-jest
  dependency-version: 29.4.11
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

* run licensed and update dist

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: George Adams <georgeadams1995@gmail.com>
2026-06-12 10:05:41 +01:00
dependabot[bot]
1756ab6acd
Bump eslint-config-prettier from 8.10.0 to 10.1.8 (#881)
Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 8.10.0 to 10.1.8.
- [Release notes](https://github.com/prettier/eslint-config-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-config-prettier/compare/v8.10.0...v10.1.8)

---
updated-dependencies:
- dependency-name: eslint-config-prettier
  dependency-version: 10.1.8
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-12 09:55:04 +01:00
dependabot[bot]
662bb59f48
Bump @typescript-eslint/eslint-plugin from 8.35.1 to 8.46.2 (#952)
Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 8.35.1 to 8.46.2.
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.46.2/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.46.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-12 09:52:03 +01:00
George Adams
1071fc12d6
fix: resolve npm audit vulnerabilities in fast-xml-builder and fast-xml-parser (#1015)
* fix: update dependency license records and resolve npm audit vulnerabilities

- Bump fast-xml-builder 1.1.4 → 1.2.0 (GHSA-5wm8-gmm8-39j9)
- Bump fast-xml-parser 5.5.10 → 5.8.0 (GHSA-gh4j-gqv2-49f6)
- Bump strnum 2.2.3 → 2.4.0
- Bump path-expression-matcher 1.4.0 → 1.5.0
- Add license records for new deps @nodable/entities and xml-naming

* fix: add anynum license record and rebuild dist

* re-run licensed
2026-06-12 09:49:51 +01:00
George Adams
576b821f29
Merge pull request #674 from gdams/alpine
temurin: add support for Alpine Linux
2026-06-12 09:18:56 +01:00
mahabaleshwars
307d3a25a0
update readme for ubuntu sudo java_home behavior (#1013) 2026-06-08 11:34:56 -05:00
Gerrit Grunwald
d3af3ec854
Added the option jdk+crac
Because with Azul Zulu one can also use jdk+crac in combination with version 17 and 21, this was added to the README.md and advanced-usage.md.
This PR contains no code changes, just documentation.
2024-02-02 16:59:47 +01:00
27 changed files with 9057 additions and 6719 deletions

View File

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

View File

@ -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

BIN
.licenses/npm/@nodable/entities.dep.yml generated Normal file

Binary file not shown.

BIN
.licenses/npm/anynum.dep.yml generated Normal file

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.

BIN
.licenses/npm/xml-naming.dep.yml generated Normal file

Binary file not shown.

View File

@ -40,7 +40,7 @@ For information about the latest releases, recent updates, and newly supported d
- `distribution`: _(required)_ Java [distribution](#supported-distributions).
- `java-package`: The packaging variant of the chosen distribution. Possible values: `jdk`, `jre`, `jdk+fx`, `jre+fx`. Default value: `jdk`.
- `java-package`: The packaging variant of the chosen distribution. Possible values: `jdk`, `jre`, `jdk+fx`, `jre+fx`, `jdk+crac`. Default value: `jdk`.
- `architecture`: The target architecture of the package. Possible values: `x86`, `x64`, `armv7`, `aarch64`, `ppc64le`. Default value: Derived from the runner machine.
@ -131,6 +131,8 @@ Currently, the following distributions are supported:
**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:

View File

@ -4,6 +4,7 @@ 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';
@ -14,6 +15,7 @@ import * as core from '@actions/core';
describe('getAvailableVersions', () => {
let spyHttpClient: jest.SpyInstance;
let spyCoreError: jest.SpyInstance;
let spyCoreWarning: jest.SpyInstance;
beforeEach(() => {
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
@ -26,6 +28,8 @@ describe('getAvailableVersions', () => {
// Mock core.error to suppress error logs
spyCoreError = jest.spyOn(core, 'error');
spyCoreError.mockImplementation(() => {});
spyCoreWarning = jest.spyOn(core, 'warning');
spyCoreWarning.mockImplementation(() => {});
});
afterEach(() => {
@ -136,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(
@ -166,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([
@ -228,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'],
@ -250,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);
@ -265,6 +331,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;
await expect(
distribution['findPackageForDownload']('9.0.8')
@ -281,6 +352,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;
await expect(distribution['findPackageForDownload']('7.x')).rejects.toThrow(
/No matching version found for SemVer */
@ -297,6 +373,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 () => [];
await expect(distribution['findPackageForDownload']('11')).rejects.toThrow(
/No matching version found for SemVer */

View File

@ -9,6 +9,7 @@ import * as core from '@actions/core';
describe('getAvailableVersions', () => {
let spyHttpClient: jest.SpyInstance;
let spyCoreError: jest.SpyInstance;
let spyCoreWarning: jest.SpyInstance;
beforeEach(() => {
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
@ -20,6 +21,8 @@ describe('getAvailableVersions', () => {
// Mock core.error to suppress error logs
spyCoreError = jest.spyOn(core, 'error');
spyCoreError.mockImplementation(() => {});
spyCoreWarning = jest.spyOn(core, 'warning');
spyCoreWarning.mockImplementation(() => {});
});
afterEach(() => {
@ -82,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({
@ -109,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([

View File

@ -12,6 +12,7 @@ import * as core from '@actions/core';
describe('getAvailableVersions', () => {
let spyHttpClient: jest.SpyInstance;
let spyCoreError: jest.SpyInstance;
let spyCoreWarning: jest.SpyInstance;
beforeEach(() => {
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
@ -23,6 +24,8 @@ describe('getAvailableVersions', () => {
// Mock core.error to suppress error logs
spyCoreError = jest.spyOn(core, 'error');
spyCoreError.mockImplementation(() => {});
spyCoreWarning = jest.spyOn(core, 'warning');
spyCoreWarning.mockImplementation(() => {});
});
afterEach(() => {
@ -93,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(
@ -123,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([

View File

@ -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');
@ -27,7 +29,11 @@ describe('isVersionSatisfies', () => {
['2.5.1+3', '2.5.1+3', true],
['2.5.1+3', '2.5.1+2', 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',
(inputRange: string, inputVersion: string, expected: boolean) => {
@ -85,6 +91,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([

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

View File

@ -61,7 +61,7 @@ steps:
with:
distribution: 'zulu'
java-version: '21'
java-package: jdk # optional (jdk, jre, jdk+fx or jre+fx) - defaults to jdk
java-package: jdk # optional (jdk, jre, jdk+fx or jre+fx, jdk+crac) - defaults to jdk
- run: java -cp java HelloWorldApp
```

3820
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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/parser": "^8.35.1",
"@vercel/ncc": "^0.38.1",
"@typescript-eslint/eslint-plugin": "^8.48.0",
"@typescript-eslint/parser": "^8.61.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": {

View File

@ -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
@ -125,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()) {

View File

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

View File

@ -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';
@ -155,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()) {

View File

@ -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();
@ -123,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()) {
@ -171,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;
}

View File

@ -55,6 +55,14 @@ export function getDownloadArchiveExtension() {
}
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 full version with build digit is provided as a range (such as '1.2.3+4')
// we should check for exact equal via compareBuild
@ -201,6 +209,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.