Compare commits

..

28 Commits

Author SHA1 Message Date
Haritha 46f6294045 docs: update setup-java examples (#1131)
* docs: update setup-java examples

* docs: fix YAML example indentation

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

Copilot-Session: 190c65c4-f5aa-4595-8e2e-26266fbcb37f

* docs: note v6 development status

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

Copilot-Session: 190c65c4-f5aa-4595-8e2e-26266fbcb37f

---------

Co-authored-by: Bruno Borges <brborges@microsoft.com>
2026-07-16 14:25:45 -04:00
Bruno Borges e40a8e0642 Add an option to disable Java problem matchers (#1133)
* Add problem matcher opt-out

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

Copilot-Session: 38eb7886-7ea3-4e4d-8d5f-e3f975135053

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-16 11:28:10 -04:00
Philip Gai 6e2e32e729 chore(deps): bump @actions/cache to 6.2.0 (#1128)
* chore(deps): bump @actions/cache to 6.2.0

Bump @actions/cache from ^6.1.0 to ^6.2.0 and rebuild the vendored
dist/ bundles. 6.2.0 honors ACTIONS_CACHE_MODE to skip restore/save
when the effective cache-mode disallows it, and surfaces a
core.warning (instead of failing the run) when the cache service
denies a read/write due to token scopes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0046a2b8-0238-4ac6-93f6-70c4e60b5630

* chore: update licensed record for @actions/cache 6.2.0

Refresh the cached licensed dependency record so the version matches
the bumped @actions/cache 6.2.0. License text, summary, and homepage
are unchanged; only the version field was stale, which failed the
"Check licenses" CI job.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0046a2b8-0238-4ac6-93f6-70c4e60b5630

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Bruno Borges <brborges@microsoft.com>
2026-07-15 17:16:17 -04:00
Bruno Borges 38fa86f9e4 Document missing action inputs in README (#1130)
* Document missing action inputs

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

Copilot-Session: 4b9fa143-418b-44da-aff0-7a7938148e39

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-15 16:33:32 -04:00
jmjaffe37 ecd5f809c2 Updated msft json for now (#1129) 2026-07-15 16:33:24 -04:00
Bruno Borges 4a24efdcc9 Use YAML anchors to reduce boilerplate in e2e-versions workflow (#1126)
* Use YAML anchors to reduce boilerplate in e2e-versions workflow

No-behavior-change refactor. Introduces a `&checkout_step` anchor for the
identical Checkout step (aliased via `*checkout_step` in the other 16 jobs)
and a `&default_os` anchor for the common
`[macos-latest, windows-latest, ubuntu-latest]` matrix list (aliased via
`*default_os` in the 11 other jobs that use that exact list). Lists that
differ (macos-15-intel, ubuntu-latest only, windows-latest+ubuntu-22.04)
are left untouched. Once anchors are expanded the document is byte-for-byte
equivalent to the original.

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

* Fix merge conflicts: adopt consolidated jobs from main, preserve YAML anchors

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-15 11:49:48 -04:00
Bruno Borges cc8cdedf67 Consolidate duplicate jobs in e2e-versions workflow (#1125)
Merge the three EA jobs (zulu, temurin, sapmachine) into a single
setup-java-ea-versions job driven by a matrix include list, and merge
the two signature-verification jobs (temurin, microsoft) into a single
setup-java-signature-verification job with a distribution matrix
dimension. Test coverage is unchanged: all 15 EA combos and all 12
signature combos still run.


Copilot-Session: 9924b163-0ad2-4f8c-b038-0f00220a3157

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-15 11:30:35 -04:00
Bruno Borges 4b0728bd0d Extract repeated directory-check assertions into check-dir.sh helper (#1127)
* Extract repeated directory-check assertions into check-dir.sh helper

The e2e-cache.yml workflow repeated the same inline shell block many times
to assert a cache directory exists (and list it), plus inverse checks that a
directory does NOT exist (the gradle2/maven2/sbt2 cache-miss jobs).

Add `__tests__/check-dir.sh` (POSIX sh, executable) with a
`check-dir.sh <dir> [present|absent]` interface and replace every inline
check with a call to it, passing already-expanded $HOME paths to avoid
tilde-expansion pitfalls. The sbt jobs override working-directory, so they
call the helper via $GITHUB_WORKSPACE. Per-OS Coursier conditionals and all
build steps are left unchanged.

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

* Address review: argc guard in check-dir.sh, fix sbt ubuntu matrix conditionals

- check-dir.sh: with set -u, invoking without a <dir> argument failed with an
  opaque "parameter not set" error. Add an explicit argc check that prints a
  usage message and exits 2 (distinct from the assertion failure code 1).
- e2e-cache.yml: the sbt-save and sbt-restore jobs run on an ubuntu-22.04
  matrix entry, but their coursier-cache steps were guarded by
  'if: matrix.os == "ubuntu-latest"', so those checks never executed on
  Ubuntu. Align the conditionals with the matrix (ubuntu-22.04), matching the
  newer sbt1/sbt2 jobs.

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

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-15 11:14:40 -04:00
Bruno Borges 7d485884af Use gpg.passphraseEnvName instead of the deprecated gpg.passphrase server (#1123)
* Use gpg.passphraseEnvName instead of gpg.passphrase server

The maven-gpg-plugin's `gpg.passphrase`/`passphraseServerId` mechanism is
deprecated and fails when the plugin's `bestPractices` mode is enabled.
Stop writing the `gpg.passphrase` server to settings.xml and instead set
`gpg.passphraseEnvName` via an active profile when the configured passphrase
env var name differs from the plugin default (MAVEN_GPG_PASSPHRASE).

The default `gpg-passphrase` input value (GPG_PASSPHRASE) is unchanged, so the
plugin reads the same environment variable as before.

Fixes #760

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

* Fix settings.xml publishing validation

Update the publishing e2e check to assert the settings.xml generated when gpg-passphrase is MAVEN_GPG_PASSPHRASE. In that default case the action no longer writes a gpg.passphrase server entry.

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

Copilot-Session: 7d0a510e-aebf-4ec4-a667-efeb3e4edeb1

* Default gpg-passphrase to MAVEN_GPG_PASSPHRASE

Align the default `gpg-passphrase` input value with the maven-gpg-plugin
default environment variable name (MAVEN_GPG_PASSPHRASE). With this default,
setup-java writes no extra GPG configuration to settings.xml and the plugin
reads the passphrase from MAVEN_GPG_PASSPHRASE out of the box.

Also document that reading the passphrase from an environment variable via
`gpg.passphraseEnvName` requires maven-gpg-plugin 3.2.0 or newer.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7d0a510e-aebf-4ec4-a667-efeb3e4edeb1

* Document gpg-passphrase breaking change in V6

Add a "Breaking changes in V6" section to the README covering the switch to
gpg.passphraseEnvName, the new MAVEN_GPG_PASSPHRASE default, and the
maven-gpg-plugin 3.2.0+ requirement.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7d0a510e-aebf-4ec4-a667-efeb3e4edeb1

* Keep GPG_PASSPHRASE default for v5 compatibility

Revert the gpg-passphrase input default back to GPG_PASSPHRASE so existing v5
workflows that set the GPG_PASSPHRASE environment variable keep working without
changes. setup-java writes gpg.passphraseEnvName=GPG_PASSPHRASE into an active
profile, so the maven-gpg-plugin reads the same variable as before.

The only remaining compatibility requirement is maven-gpg-plugin 3.2.0+, which
is documented in the README and advanced usage guide.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7d0a510e-aebf-4ec4-a667-efeb3e4edeb1

* Clarify GPG passphrase profile compatibility

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7d0a510e-aebf-4ec4-a667-efeb3e4edeb1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-15 10:33:09 -04:00
Bruno Borges c3568cc9d9 Consolidate cache-dependency-path e2e workflow and add maven/sbt coverage (#1124)
* Merge cache-dependency-path e2e workflow into e2e-cache

Fold the standalone 'Validate cache with cache-dependency-path option'
workflow into e2e-cache.yml and delete the separate file. The three
gradle1-save/gradle1-restore/gradle2-restore jobs are copied verbatim,
preserving their matrix, cache-dependency-path inputs, and needs
relationships. No change in test coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 635b89fa-5c35-49e1-a257-dc9001ebb8db

* Add cache-dependency-path e2e coverage for maven and sbt

Previously only gradle exercised the cache-dependency-path input. Mirror
the gradle save/restore(hit)/restore(miss) pattern for maven and sbt:

- maven1-save/maven1-restore/maven2-restore
- sbt1-save/sbt1-restore/sbt2-restore

Each save+restore pair uses the same cache-dependency-path so the restore
is a hit, while the second restore points at a new maven2/sbt2 fixture
whose different dependencies produce a different hash, so the cache is a
miss (directory not created). New fixtures added under __tests__/cache.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 635b89fa-5c35-49e1-a257-dc9001ebb8db

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-15 10:28:27 -04:00
Bruno Borges 81c13a41f9 Cache Maven and Gradle wrapper distributions separately from the dependency cache (#1097)
* Cache Maven wrapper distribution separately from the local repository

The Maven wrapper distribution (~/.m2/wrapper/dists) was cached in the same
entry as the local Maven repository (~/.m2/repository), keyed on a hash of
**/pom.xml (plus wrapper properties and extensions). Because pom.xml changes
frequently and no restoreKeys are used (by design, #269), almost every change
produces a full cache miss and the wrapper distribution is re-downloaded via
mvnw — which intermittently fails due to upstream rate limiting.

The wrapper distribution only depends on maven-wrapper.properties, which
changes very rarely. Give it its own cache entry keyed solely on
**/.mvn/wrapper/maven-wrapper.properties so it survives the frequent pom.xml
changes that rotate the main dependency cache key.

- Add a generic additionalCaches concept to PackageManager, restored and saved
  independently with name-scoped state keys.
- Move ~/.m2/wrapper/dists out of the main maven path into a maven-wrapper
  additional cache; skip silently when the project does not use mvnw.
- Keep cache-hit / cache-primary-key outputs driven by the main cache.
- Update tests, docs, and rebuild dist bundles.

Fixes #1095

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

* Cache Gradle wrapper distribution separately from the dependency cache (#1098)

The Gradle wrapper distribution (~/.gradle/wrapper) only depends on
gradle-wrapper.properties, which changes rarely, but it was previously
cached in the same entry as ~/.gradle/caches, keyed on volatile
**/*.gradle* files with no restoreKeys (issue #269). Every dependency
change therefore re-downloaded the wrapper.

Move ~/.gradle/wrapper into a dedicated `gradle-wrapper` additional
cache keyed only on **/gradle-wrapper.properties, reusing the
additionalCaches infrastructure introduced for the Maven wrapper fix.

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

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Fix broken try/catch in saveAdditionalCache and rebuild dist

The autofix commits dropped the `} catch (error) {` line in
saveAdditionalCache, leaving a `try` block without a catch and a dangling
`error` reference, which broke compilation and Prettier. Restore the catch
clause, reformat, and regenerate the dist bundles.

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

* Address cache.ts review feedback

- saveAdditionalCache: handle @actions/cache ValidationError (thrown when the
  cache paths do not resolve, e.g. the wrapper distribution was never
  downloaded) by skipping instead of failing the post step. Add a test.
- Point the Gradle wrapper cache comment at issue #269 (Gradle wrapper cache
  churn) instead of the Maven-specific #1095.

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

* Document Gradle Wrapper distribution caching in README

Add a parallel "Gradle Wrapper" note alongside the Maven Wrapper note, so the
new behavior for cache: 'gradle' (caching ~/.gradle/wrapper in a separate entry
keyed on **/gradle-wrapper.properties) is documented.

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

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-14 17:14:23 -04:00
Antonin Delpeuch 3c85b14378 feat: Update recommended configuration for GPG signing (#608)
* Update recommended configuration for GPG signing

This attempts to document the new recommended configuration to sign artifacts with the maven-gpg-plugin as part of the deploy process.

It imitates this PR from the maintainer of the maven-gpg-plugin:
https://github.com/xerial/sqlite-jdbc/pull/1082/files

Notes that this requires the maven-gpg-plugin version 3.2.0 or above, not sure if this is worth adding to the documentation as I expect this guide will mostly be followed by people setting up a new project (hopefully using the latest version of the plugin by default).

@cstamas I hope I got it right, feel free to suggest any improvements

* Remove unnecessary comment

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* docs: tidy GPG signing section for bc signer

- Remove leftover gpg.passphrase server from the GitHub Packages settings.xml example
- Clarify that the bc signer needs no gpg binary, keychain import, or pinentry loopback
- Document the legacy gpg-private-key/gpg-passphrase input path alongside it
- Note the MAVEN_GPG_KEY must be an ASCII-armored (TSK) secret key

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 50be9bf4-3414-43f2-8454-03c2d9e61973

* docs: add legacy GPG signing example alongside bc signer

Restore a complete, clearly-labeled legacy path (setup-java gpg-private-key/
gpg-passphrase inputs) for maven-gpg-plugin < 3.2.0 or the gpg executable:
full workflow YAML, the generated gpg.passphrase server, and the
--pinentry-mode loopback pom.xml snippet.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 50be9bf4-3414-43f2-8454-03c2d9e61973

---------

Co-authored-by: Bruno Borges <bruno.borges@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Bruno Borges <brborges@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-14 16:45:40 -04:00
Copilot 80c368b720 Disable persisted checkout credentials in e2e workflow (#1115)
* Initial plan

* Disable persisted checkout credentials in e2e set-default job (alert #124)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Bruno Borges <brborges@microsoft.com>
2026-07-14 16:11:17 -04:00
Copilot cd57b95697 Fix template injection in e2e-versions.yml (zizmor alert #122) (#1120)
* Initial plan

* Fix template injection in e2e-versions.yml (zizmor alert #122)

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-14 15:55:49 -04:00
Copilot 7a475d86d1 Fix template injection (zizmor alert #118) in e2e-versions.yml (#1114)
* Initial plan

* Fix template injection alert #118 in e2e-versions.yml

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-07-14 15:36:28 -04:00
Pasha Finkelshteyn 92f9362dd0 dist: Support Liberica NIK (#878) (#1112)
Add the `liberica-nik` distribution (Liberica Native Image Kit), a
GraalVM-based build from BELL Software resolved via the Bell-SW
`/v1/nik/releases` API.

- `java-version` matches the embedded JDK version (from the release's
  `liberica` component), consistent with every other distribution.
- `java-package: jdk` installs the `standard` bundle; `jdk+fx` installs
  the `full` bundle with JavaFX/Swing support.
- Supported on Linux, macOS and Windows for x64 and aarch64.

Signed-off-by: asm0dey <pavel.finkelshtein@gmail.com>
Co-authored-by: Bruno Borges <brborges@microsoft.com>
2026-07-14 14:49:31 -04:00
Bruno Borges 71cfe8e0ed Preserve Maven toolchains across repeated setup-java runs (#1099) (#1111)
* Preserve Maven toolchains across repeated setup-java runs (#1099)

Toolchain generation was gated behind the `overwrite-settings` input,
which is documented to control only regeneration of `settings.xml`.
Because `generateToolchainDefinition` already performs a non-destructive
merge (existing JDK, custom, and user-managed toolchains are preserved,
and only an entry with the same `type` + `provides.id` is replaced),
skipping the write when `overwrite-settings: false` caused later
setup-java executions to drop toolchain entries registered by earlier
runs.

Decouple toolchains generation from `overwrite-settings`: the toolchains
file is now always written, so consecutive runs accumulate every JDK.
`settings.xml` behavior (auth.ts) is unchanged.

- src/toolchains.ts: drop overwriteSettings from configureToolchains /
  createToolchainsSettings / writeToolchainsFileToDisk; always write.
- __tests__/toolchains.test.ts: update call sites, rewrite the
  "does not overwrite" test to assert non-destructive extension, and add
  a regression test for consecutive configureToolchains executions.
- docs/advanced-usage.md: clarify merge is non-destructive and
  independent of overwrite-settings.
- dist/setup/index.js: rebuilt.

Fixes #1099

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 0bcec457-b86b-4902-b6e6-6dfd6b2570f7

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-14 07:54:11 -04:00
dependabot[bot] eb6157cd13 chore(deps-dev): bump typescript from 6.0.3 to 7.0.2 (#1102)
* chore(deps-dev): bump typescript from 6.0.3 to 7.0.2

Bumps [typescript](https://github.com/microsoft/TypeScript) from 6.0.3 to 7.0.2.
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/commits)

---
updated-dependencies:
- dependency-name: typescript
  dependency-version: 7.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
...

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

* fix: restore TypeScript 6 compatibility with eslint stack

@typescript-eslint currently requires typescript <6.1.0, so TypeScript 7 breaks npm ci in CI jobs.

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

Copilot-Session: 267dc9e4-4d46-424a-9f4e-6c2b2e6e2e57

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bruno Borges <brborges@microsoft.com>
2026-07-13 14:16:51 -04:00
John Jiang 2adeb10550 dist: Cover Tencent Kona JDK 25 (#1108)
- Update Tencent Kona documentation to list JDK 25 as supported
- Add Kona 25 to the e2e verification matrix (ubuntu, windows, macos)
- Extend Kona test fixture with real Kona 25.0.3 release entries
- Add unit tests covering Kona 25 selection across all platforms

Signed-off-by: John Jiang <johnsjiang@tencent.com>
Co-authored-by: Bruno Borges <brborges@microsoft.com>
2026-07-13 14:08:24 -04:00
dependabot[bot] 23f8c419c6 chore(deps-dev): bump @types/node from 26.1.0 to 26.1.1 (#1104)
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 26.1.0 to 26.1.1.
- [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: 26.1.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bruno Borges <brborges@microsoft.com>
2026-07-13 13:55:40 -04:00
dependabot[bot] 6f7d7a6eb1 chore(deps): bump actions/checkout from 6 to 7 (#1106)
Bumps [actions/checkout](https://github.com/actions/checkout) from 6 to 7.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  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>
Co-authored-by: Bruno Borges <brborges@microsoft.com>
2026-07-13 13:50:12 -04:00
dependabot[bot] 2301a55b6d chore(deps-dev): bump prettier from 3.9.4 to 3.9.5 (#1105)
Bumps [prettier](https://github.com/prettier/prettier) from 3.9.4 to 3.9.5.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.9.4...3.9.5)

---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.9.5
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bruno Borges <brborges@microsoft.com>
2026-07-13 13:43:44 -04:00
dependabot[bot] 2789aa9170 chore(deps-dev): bump eslint-plugin-n from 18.2.1 to 18.2.2 (#1103)
Bumps [eslint-plugin-n](https://github.com/eslint-community/eslint-plugin-n) from 18.2.1 to 18.2.2.
- [Release notes](https://github.com/eslint-community/eslint-plugin-n/releases)
- [Changelog](https://github.com/eslint-community/eslint-plugin-n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/eslint-community/eslint-plugin-n/compare/v18.2.1...v18.2.2)

---
updated-dependencies:
- dependency-name: eslint-plugin-n
  dependency-version: 18.2.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bruno Borges <brborges@microsoft.com>
2026-07-13 13:40:19 -04:00
dependabot[bot] 28fb8cb06b chore(deps-dev): bump eslint from 10.6.0 to 10.7.0 (#1101)
Bumps [eslint](https://github.com/eslint/eslint) from 10.6.0 to 10.7.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.6.0...v10.7.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.7.0
  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-07-13 13:29:17 -04:00
Bruno Borges 174d4b5609 Support pinning java-version as "latest" (#1093)
* Support pinning java-version as "latest"

Add a `latest` alias for the `java-version` input that floats to the newest
available stable (GA) release. It is normalized to the SemVer wildcard at the
base-installer layer and always resolves from remote (like `check-latest: true`).

List-based distributions resolve it automatically via the existing newest-first
matching. Corretto selects its newest available major; Oracle and GraalVM look up
the newest GA major via the Adoptium API and request it, failing with an actionable
error if that major isn't published yet. The jdkfile distribution rejects `latest`.

Closes #832

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

* Clarify latest note for oracle/graalvm version resolution vs download

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

* graalvm-community: float latest to its own newest GA release

GraalVM Community publishes its releases on GitHub, so the `latest`
alias now matches against that real release list using the SemVer
wildcard instead of asking the Adoptium API for the newest GA major.
This prevents `latest` from hard-failing when GraalVM lags behind a
freshly released Java major (e.g. Adoptium reports 26 before GraalVM
ships it). Oracle GraalVM has no listing endpoint, so it keeps deriving
the newest major from Adoptium and errors clearly if that major is not
yet published.

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

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Give latest+qualifier inputs (e.g. latest-ea) a targeted error

Inputs like 'latest-ea' had their '-ea' suffix stripped and fell through
to the generic SemVer validation, failing with a confusing "'latest' is
not valid SemVer" message even though 'latest' is supported. Add an
explicit guard so any 'latest*' value other than exactly 'latest' throws
a targeted error explaining that 'latest' resolves GA releases only and
cannot be combined with '-ea' or other qualifiers.

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

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-09 20:11:03 -04:00
Bruno Borges 86ce8e6cc8 docs: clarify Maven cache paths and key hash inputs (#1096)
Address post-merge review feedback on #1094: the intro paragraph
described the cache too narrowly. setup-java caches both
~/.m2/repository and ~/.m2/wrapper/dists, and the default key hashes
**/pom.xml plus .mvn/wrapper/maven-wrapper.properties and
.mvn/extensions.xml, so changing wrapper/extensions files also
invalidates the cache.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-09 17:14:05 -04:00
Bruno Borges 90be2728bc docs: document seeding the Maven cache for plugin dependencies (#1094)
* docs: document Maven cache seeding for plugin dependencies (#990)

Explain that setup-java's maven cache only stores what a run downloads and
is not re-saved on a hit, so plugin dependencies resolved lazily (e.g. by
maven-shade-plugin) can be missing from the cache. Document a dependency
resolution 'seed' step (dependency:go-offline + dependency:resolve-plugins)
in README and a fuller advanced-usage section with a goal-comparison table,
single-job and separate-seed-job examples, and caveats.

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

* docs: note gradle cache has the same limitation, point to setup-gradle (#990)

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

* docs: shorten README maven cache note, link to advanced docs (#990)

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

* docs: cover the parallel multi-job cache race in the seed-job example (#705)

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

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-09 16:03:22 -04:00
Bruno Borges 3157986b3f Support multi-field Java versions like 18.0.1.1 (#1092)
Java's version scheme (JEP 322) can contain more than the three numeric
fields SemVer allows, e.g. 18.0.1.1 or 11.0.9.1. normalizeVersion()
rejected these inputs. Convert exact multi-field versions to SemVer build
notation (18.0.1.1 -> 18.0.1+1) before validation, reusing the existing
convertVersionToSemver() helper. Ranges, EA tags, and inputs that already
carry build metadata are left untouched.

Fixes: #326

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-07-09 15:24:02 -04:00
49 changed files with 6359 additions and 1160 deletions
@@ -1,102 +0,0 @@
name: Validate cache with cache-dependency-path option
on:
push:
branches:
- main
- releases/*
paths-ignore:
- '**.md'
pull_request:
paths-ignore:
- '**.md'
permissions:
contents: read
defaults:
run:
shell: bash
jobs:
gradle1-save:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with the cache for gradle
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '17'
cache: gradle
cache-dependency-path: __tests__/cache/gradle1/*.gradle*
- name: Create files to cache
# Need to avoid using Gradle daemon to stabilize the save process on Windows
# https://github.com/actions/cache/issues/454#issuecomment-840493935
run: |
gradle downloadDependencies --no-daemon -p __tests__/cache/gradle1
if [ ! -d ~/.gradle/caches ]; then
echo "::error::The ~/.gradle/caches directory does not exist unexpectedly"
exit 1
fi
gradle1-restore:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
needs: gradle1-save
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with the cache for gradle
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: gradle
cache-dependency-path: __tests__/cache/gradle1/*.gradle*
- name: Confirm that ~/.gradle/caches directory has been made
run: |
if [ ! -d ~/.gradle/caches ]; then
echo "::error::The ~/.gradle/caches directory does not exist unexpectedly"
exit 1
fi
ls ~/.gradle/caches/
gradle2-restore:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
needs: gradle1-save
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with the cache for gradle
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: gradle
cache-dependency-path: __tests__/cache/gradle2/*.gradle*
- name: Confirm that ~/.gradle/caches directory has not been made
run: |
if [ -d ~/.gradle/caches ]; then
echo "::error::The ~/.gradle/caches directory exists unexpectedly"
exit 1
fi
+255 -54
View File
@@ -42,10 +42,7 @@ jobs:
# https://github.com/actions/cache/issues/454#issuecomment-840493935 # https://github.com/actions/cache/issues/454#issuecomment-840493935
run: | run: |
gradle downloadDependencies --no-daemon -p __tests__/cache/gradle1 gradle downloadDependencies --no-daemon -p __tests__/cache/gradle1
if [ ! -d ~/.gradle/caches ]; then bash __tests__/check-dir.sh "$HOME/.gradle/caches"
echo "::error::The ~/.gradle/caches directory does not exist unexpectedly"
exit 1
fi
gradle-restore: gradle-restore:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
@@ -66,12 +63,7 @@ jobs:
java-version: '11' java-version: '11'
cache: gradle cache: gradle
- name: Confirm that ~/.gradle/caches directory has been made - name: Confirm that ~/.gradle/caches directory has been made
run: | run: bash __tests__/check-dir.sh "$HOME/.gradle/caches"
if [ ! -d ~/.gradle/caches ]; then
echo "::error::The ~/.gradle/caches directory does not exist unexpectedly"
exit 1
fi
ls ~/.gradle/caches/
maven-save: maven-save:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
@@ -93,10 +85,7 @@ jobs:
- name: Create files to cache - name: Create files to cache
run: | run: |
mvn verify -f __tests__/cache/maven/pom.xml mvn verify -f __tests__/cache/maven/pom.xml
if [ ! -d ~/.m2/repository ]; then bash __tests__/check-dir.sh "$HOME/.m2/repository"
echo "::error::The ~/.m2/repository directory does not exist unexpectedly"
exit 1
fi
maven-restore: maven-restore:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
@@ -117,12 +106,7 @@ jobs:
java-version: '11' java-version: '11'
cache: maven cache: maven
- name: Confirm that ~/.m2/repository directory has been made - name: Confirm that ~/.m2/repository directory has been made
run: | run: bash __tests__/check-dir.sh "$HOME/.m2/repository"
if [ ! -d ~/.m2/repository ]; then
echo "::error::The ~/.m2/repository directory does not exist unexpectedly"
exit 1
fi
ls ~/.m2/repository
sbt-save: sbt-save:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
defaults: defaults:
@@ -155,25 +139,13 @@ jobs:
- name: Check files to cache on macos-latest - name: Check files to cache on macos-latest
if: matrix.os == 'macos-15-intel' if: matrix.os == 'macos-15-intel'
run: | run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/Library/Caches/Coursier"
if [ ! -d ~/Library/Caches/Coursier ]; then
echo "::error::The ~/Library/Caches/Coursier directory does not exist unexpectedly"
exit 1
fi
- name: Check files to cache on windows-latest - name: Check files to cache on windows-latest
if: matrix.os == 'windows-latest' if: matrix.os == 'windows-latest'
run: | run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/AppData/Local/Coursier/Cache"
if [ ! -d ~/AppData/Local/Coursier/Cache ]; then
echo "::error::The ~/AppData/Local/Coursier/Cache directory does not exist unexpectedly"
exit 1
fi
- name: Check files to cache on ubuntu-latest - name: Check files to cache on ubuntu-latest
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-22.04'
run: | run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier"
if [ ! -d ~/.cache/coursier ]; then
echo "::error::The ~/.cache/coursier directory does not exist unexpectedly"
exit 1
fi
sbt-restore: sbt-restore:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
defaults: defaults:
@@ -200,25 +172,254 @@ jobs:
- name: Confirm that ~/Library/Caches/Coursier directory has been made - name: Confirm that ~/Library/Caches/Coursier directory has been made
if: matrix.os == 'macos-15-intel' if: matrix.os == 'macos-15-intel'
run: | run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/Library/Caches/Coursier"
if [ ! -d ~/Library/Caches/Coursier ]; then
echo "::error::The ~/Library/Caches/Coursier directory does not exist unexpectedly"
exit 1
fi
ls ~/Library/Caches/Coursier
- name: Confirm that ~/AppData/Local/Coursier/Cache directory has been made - name: Confirm that ~/AppData/Local/Coursier/Cache directory has been made
if: matrix.os == 'windows-latest' if: matrix.os == 'windows-latest'
run: | run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/AppData/Local/Coursier/Cache"
if [ ! -d ~/AppData/Local/Coursier/Cache ]; then
echo "::error::The ~/AppData/Local/Coursier/Cache directory does not exist unexpectedly"
exit 1
fi
ls ~/AppData/Local/Coursier/Cache
- name: Confirm that ~/.cache/coursier directory has been made - name: Confirm that ~/.cache/coursier directory has been made
if: matrix.os == 'ubuntu-latest' if: matrix.os == 'ubuntu-22.04'
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier"
gradle1-save:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with the cache for gradle
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '17'
cache: gradle
cache-dependency-path: __tests__/cache/gradle1/*.gradle*
- name: Create files to cache
# Need to avoid using Gradle daemon to stabilize the save process on Windows
# https://github.com/actions/cache/issues/454#issuecomment-840493935
run: | run: |
if [ ! -d ~/.cache/coursier ]; then gradle downloadDependencies --no-daemon -p __tests__/cache/gradle1
echo "::error::The ~/.cache/coursier directory does not exist unexpectedly" bash __tests__/check-dir.sh "$HOME/.gradle/caches"
exit 1 gradle1-restore:
fi runs-on: ${{ matrix.os }}
ls ~/.cache/coursier strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
needs: gradle1-save
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with the cache for gradle
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: gradle
cache-dependency-path: __tests__/cache/gradle1/*.gradle*
- name: Confirm that ~/.gradle/caches directory has been made
run: bash __tests__/check-dir.sh "$HOME/.gradle/caches"
gradle2-restore:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
needs: gradle1-save
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with the cache for gradle
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: gradle
cache-dependency-path: __tests__/cache/gradle2/*.gradle*
- name: Confirm that ~/.gradle/caches directory has not been made
run: bash __tests__/check-dir.sh "$HOME/.gradle/caches" absent
maven1-save:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-15-intel, windows-latest, ubuntu-latest]
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with the cache for maven
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: maven
cache-dependency-path: __tests__/cache/maven/pom.xml
- name: Create files to cache
run: |
mvn verify -f __tests__/cache/maven/pom.xml
bash __tests__/check-dir.sh "$HOME/.m2/repository"
maven1-restore:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-15-intel, windows-latest, ubuntu-latest]
needs: maven1-save
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with the cache for maven
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: maven
cache-dependency-path: __tests__/cache/maven/pom.xml
- name: Confirm that ~/.m2/repository directory has been made
run: bash __tests__/check-dir.sh "$HOME/.m2/repository"
maven2-restore:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-15-intel, windows-latest, ubuntu-latest]
needs: maven1-save
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with the cache for maven
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: maven
cache-dependency-path: __tests__/cache/maven2/pom.xml
- name: Confirm that ~/.m2/repository directory has not been made
run: bash __tests__/check-dir.sh "$HOME/.m2/repository" absent
sbt1-save:
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
working-directory: __tests__/cache/sbt
strategy:
fail-fast: false
matrix:
os: [macos-15-intel, windows-latest, ubuntu-22.04]
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with the cache for sbt
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: sbt
cache-dependency-path: __tests__/cache/sbt/*.sbt
- name: Setup SBT
if: matrix.os == 'macos-15-intel'
run: |
echo ""Installing SBT...""
brew install sbt
- name: Create files to cache
run: sbt update
- name: Check files to cache on macos-latest
if: matrix.os == 'macos-15-intel'
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/Library/Caches/Coursier"
- name: Check files to cache on windows-latest
if: matrix.os == 'windows-latest'
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/AppData/Local/Coursier/Cache"
- name: Check files to cache on ubuntu-latest
if: matrix.os == 'ubuntu-22.04'
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier"
sbt1-restore:
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
working-directory: __tests__/cache/sbt
strategy:
fail-fast: false
matrix:
os: [macos-15-intel, windows-latest, ubuntu-22.04]
needs: sbt1-save
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with the cache for sbt
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: sbt
cache-dependency-path: __tests__/cache/sbt/*.sbt
- name: Confirm that ~/Library/Caches/Coursier directory has been made
if: matrix.os == 'macos-15-intel'
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/Library/Caches/Coursier"
- name: Confirm that ~/AppData/Local/Coursier/Cache directory has been made
if: matrix.os == 'windows-latest'
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/AppData/Local/Coursier/Cache"
- name: Confirm that ~/.cache/coursier directory has been made
if: matrix.os == 'ubuntu-22.04'
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier"
sbt2-restore:
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
working-directory: __tests__/cache/sbt2
strategy:
fail-fast: false
matrix:
os: [macos-15-intel, windows-latest, ubuntu-22.04]
needs: sbt1-save
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Run setup-java with the cache for sbt
uses: ./
id: setup-java
with:
distribution: 'adopt'
java-version: '11'
cache: sbt
cache-dependency-path: __tests__/cache/sbt2/*.sbt
- name: Confirm that ~/Library/Caches/Coursier directory has not been made
if: matrix.os == 'macos-15-intel'
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/Library/Caches/Coursier" absent
- name: Confirm that ~/AppData/Local/Coursier/Cache directory has not been made
if: matrix.os == 'windows-latest'
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/AppData/Local/Coursier/Cache" absent
- name: Confirm that ~/.cache/coursier directory has not been made
if: matrix.os == 'ubuntu-22.04'
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier" absent
+18 -6
View File
@@ -46,13 +46,25 @@ jobs:
$xmlPath = Join-Path $HOME ".m2" "settings.xml" $xmlPath = Join-Path $HOME ".m2" "settings.xml"
Get-Content $xmlPath | ForEach-Object { Write-Host $_ } Get-Content $xmlPath | ForEach-Object { Write-Host $_ }
[xml]$xml = Get-Content $xmlPath $content = [System.IO.File]::ReadAllText($xmlPath)
$servers = $xml.settings.servers.server $expected = @(
if (($servers[0].id -ne 'maven') -or ($servers[0].username -ne '${env.MAVEN_USERNAME}') -or ($servers[0].password -ne '${env.MAVEN_CENTRAL_TOKEN}')) { '<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"'
throw "Generated XML file is incorrect" ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
} ' xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">'
' <interactiveMode>false</interactiveMode>'
' <servers>'
' <server>'
' <id>maven</id>'
' <username>${env.MAVEN_USERNAME}</username>'
' <password>${env.MAVEN_CENTRAL_TOKEN}</password>'
' </server>'
' </servers>'
'</settings>'
) -join "`n"
if (($servers[1].id -ne 'gpg.passphrase') -or ($servers[1].passphrase -ne '${env.MAVEN_GPG_PASSPHRASE}')) { if ($content -ne $expected) {
Write-Host "Expected settings.xml:"
$expected -split "`n" | ForEach-Object { Write-Host $_ }
throw "Generated XML file is incorrect" throw "Generated XML file is incorrect"
} }
+82 -157
View File
@@ -37,7 +37,8 @@ jobs:
'dragonwell', 'dragonwell',
'sapmachine', 'sapmachine',
'jetbrains', 'jetbrains',
'kona' 'kona',
'liberica-nik'
] # internally 'adopt-hotspot' is the same as 'adopt' ] # internally 'adopt-hotspot' is the same as 'adopt'
version: ['21', '11', '17'] version: ['21', '11', '17']
exclude: exclude:
@@ -55,6 +56,24 @@ jobs:
- distribution: microsoft - distribution: microsoft
os: macos-latest os: macos-latest
version: 25 version: 25
- distribution: kona
os: windows-latest
version: 25
- distribution: kona
os: ubuntu-latest
version: 25
- distribution: kona
os: macos-latest
version: 25
- distribution: liberica-nik
os: windows-latest
version: 25
- distribution: liberica-nik
os: ubuntu-latest
version: 25
- distribution: liberica-nik
os: macos-latest
version: 25
- distribution: oracle - distribution: oracle
os: macos-15-intel os: macos-15-intel
version: 17 version: 17
@@ -77,7 +96,8 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
version: '24-ea' version: '24-ea'
steps: steps:
- name: Checkout - &checkout_step
name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v7
with: with:
persist-credentials: false persist-credentials: false
@@ -108,10 +128,7 @@ jobs:
distribution: ['temurin', 'sapmachine'] distribution: ['temurin', 'sapmachine']
version: ['21', '17'] version: ['21', '17']
steps: steps:
- name: Checkout - *checkout_step
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Install bash - name: Install bash
run: apk add --no-cache bash run: apk add --no-cache bash
- name: setup-java - name: setup-java
@@ -134,7 +151,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-latest] os: &default_os [macos-latest, windows-latest, ubuntu-latest]
distribution: ['temurin', 'zulu', 'liberica'] distribution: ['temurin', 'zulu', 'liberica']
version: version:
- '11.0' - '11.0'
@@ -163,10 +180,7 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
version: '17.0.7' version: '17.0.7'
steps: steps:
- name: Checkout - *checkout_step
uses: actions/checkout@v7
with:
persist-credentials: false
- name: setup-java - name: setup-java
uses: ./ uses: ./
id: setup-java id: setup-java
@@ -188,7 +202,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-latest] os: *default_os
distribution: distribution:
[ [
'temurin', 'temurin',
@@ -202,10 +216,7 @@ jobs:
- distribution: dragonwell - distribution: dragonwell
os: macos-latest os: macos-latest
steps: steps:
- name: Checkout - *checkout_step
uses: actions/checkout@v7
with:
persist-credentials: false
- name: setup-java - name: setup-java
uses: ./ uses: ./
id: setup-java id: setup-java
@@ -228,7 +239,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-latest] os: *default_os
distribution: distribution:
[ [
'temurin', 'temurin',
@@ -242,10 +253,7 @@ jobs:
- distribution: dragonwell - distribution: dragonwell
os: macos-latest os: macos-latest
steps: steps:
- name: Checkout - *checkout_step
uses: actions/checkout@v7
with:
persist-credentials: false
- name: setup-java - name: setup-java
uses: ./ uses: ./
id: setup-java id: setup-java
@@ -275,26 +283,37 @@ jobs:
run: bash __tests__/verify-java.sh "17" "$JAVA_PATH" run: bash __tests__/verify-java.sh "17" "$JAVA_PATH"
shell: bash shell: bash
setup-java-ea-versions-zulu: setup-java-ea-versions:
name: zulu ${{ matrix.version }} (jdk-${{ contains(matrix.os, 'macos') && !contains(matrix.os, 'intel') && 'arm64' || 'x64' }}) - ${{ matrix.os }} name: ${{ matrix.distribution }} ${{ matrix.version }} (jdk-${{ contains(matrix.os, 'macos') && !contains(matrix.os, 'intel') && 'arm64' || 'x64' }}) - ${{ matrix.os }}
needs: setup-java-major-minor-versions needs: setup-java-major-minor-versions
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-15-intel, windows-latest, ubuntu-latest] include:
version: ['17-ea', '15.0.0-ea.14'] - {os: macos-15-intel, version: '17-ea', distribution: zulu}
- {os: windows-latest, version: '17-ea', distribution: zulu}
- {os: ubuntu-latest, version: '17-ea', distribution: zulu}
- {os: macos-15-intel, version: '15.0.0-ea.14', distribution: zulu}
- {os: windows-latest, version: '15.0.0-ea.14', distribution: zulu}
- {os: ubuntu-latest, version: '15.0.0-ea.14', distribution: zulu}
- {os: macos-latest, version: '17-ea', distribution: temurin}
- {os: windows-latest, version: '17-ea', distribution: temurin}
- {os: ubuntu-latest, version: '17-ea', distribution: temurin}
- {os: macos-latest, version: '17-ea', distribution: sapmachine}
- {os: windows-latest, version: '17-ea', distribution: sapmachine}
- {os: ubuntu-latest, version: '17-ea', distribution: sapmachine}
- {os: macos-latest, version: '21-ea', distribution: sapmachine}
- {os: windows-latest, version: '21-ea', distribution: sapmachine}
- {os: ubuntu-latest, version: '21-ea', distribution: sapmachine}
steps: steps:
- name: Checkout - *checkout_step
uses: actions/checkout@v7
with:
persist-credentials: false
- name: setup-java - name: setup-java
uses: ./ uses: ./
id: setup-java id: setup-java
with: with:
java-version: ${{ matrix.version }} java-version: ${{ matrix.version }}
distribution: zulu distribution: ${{ matrix.distribution }}
- name: Verify Java - name: Verify Java
env: env:
JAVA_VERSION: ${{ matrix.version }} JAVA_VERSION: ${{ matrix.version }}
@@ -302,53 +321,24 @@ jobs:
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH" run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
shell: bash shell: bash
setup-java-ea-versions-temurin: setup-java-signature-verification:
name: temurin ${{ matrix.version }} (jdk-${{ contains(matrix.os, 'macos') && !contains(matrix.os, 'intel') && 'arm64' || 'x64' }}) - ${{ matrix.os }} name: ${{ matrix.distribution }} ${{ matrix.version }} signature verification - ${{ matrix.os }}
needs: setup-java-major-minor-versions needs: setup-java-major-minor-versions
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-latest] os: *default_os
version: ['17-ea']
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: setup-java
uses: ./
id: setup-java
with:
java-version: ${{ matrix.version }}
distribution: temurin
- name: Verify Java
env:
JAVA_VERSION: ${{ matrix.version }}
JAVA_PATH: ${{ steps.setup-java.outputs.path }}
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
shell: bash
setup-java-temurin-signature-verification:
name: temurin ${{ matrix.version }} signature verification - ${{ matrix.os }}
needs: setup-java-major-minor-versions
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
version: ['21', '17'] version: ['21', '17']
distribution: [temurin, microsoft]
steps: steps:
- name: Checkout - *checkout_step
uses: actions/checkout@v7
with:
persist-credentials: false
- name: setup-java with signature verification - name: setup-java with signature verification
uses: ./ uses: ./
id: setup-java id: setup-java
with: with:
java-version: ${{ matrix.version }} java-version: ${{ matrix.version }}
distribution: temurin distribution: ${{ matrix.distribution }}
verify-signature: true verify-signature: true
- name: Verify Java - name: Verify Java
env: env:
@@ -357,61 +347,6 @@ jobs:
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH" run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
shell: bash shell: bash
setup-java-microsoft-signature-verification:
name: microsoft ${{ matrix.version }} signature verification - ${{ matrix.os }}
needs: setup-java-major-minor-versions
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
version: ['21', '17']
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: setup-java with signature verification
uses: ./
id: setup-java
with:
java-version: ${{ matrix.version }}
distribution: microsoft
verify-signature: true
- name: Verify Java
env:
JAVA_VERSION: ${{ matrix.version }}
JAVA_PATH: ${{ steps.setup-java.outputs.path }}
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
shell: bash
setup-java-ea-versions-sapmachine:
name: sapmachine ${{ matrix.version }} (jdk-${{ contains(matrix.os, 'macos') && !contains(matrix.os, 'intel') && 'arm64' || 'x64' }}) - ${{ matrix.os }}
needs: setup-java-major-minor-versions
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
version: ['17-ea', '21-ea']
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: setup-java
uses: ./
id: setup-java
with:
java-version: ${{ matrix.version }}
distribution: sapmachine
- name: Verify Java
env:
JAVA_VERSION: ${{ matrix.version }}
JAVA_PATH: ${{ steps.setup-java.outputs.path }}
run: bash __tests__/verify-java.sh "$JAVA_VERSION" "$JAVA_PATH"
shell: bash
setup-java-custom-package-type: setup-java-custom-package-type:
name: ${{ matrix.distribution }} ${{ matrix.version }} (${{ matrix.java-package }}-x64) - ${{ matrix.os }} name: ${{ matrix.distribution }} ${{ matrix.version }} (${{ matrix.java-package }}-x64) - ${{ matrix.os }}
needs: setup-java-major-minor-versions needs: setup-java-major-minor-versions
@@ -441,6 +376,10 @@ jobs:
java-package: jre+fx java-package: jre+fx
version: '11' version: '11'
os: ubuntu-latest os: ubuntu-latest
- distribution: 'liberica-nik'
java-package: jdk+fx
version: '21'
os: ubuntu-latest
- distribution: 'corretto' - distribution: 'corretto'
java-package: jre java-package: jre
version: '8' version: '8'
@@ -487,10 +426,7 @@ jobs:
os: ubuntu-latest os: ubuntu-latest
steps: steps:
- name: Checkout - *checkout_step
uses: actions/checkout@v7
with:
persist-credentials: false
- name: setup-java - name: setup-java
uses: ./ uses: ./
id: setup-java id: setup-java
@@ -520,10 +456,7 @@ jobs:
distribution: ['liberica', 'zulu', 'corretto'] distribution: ['liberica', 'zulu', 'corretto']
version: ['11'] version: ['11']
steps: steps:
- name: Checkout - *checkout_step
uses: actions/checkout@v7
with:
persist-credentials: false
- name: setup-java - name: setup-java
uses: ./ uses: ./
id: setup-java id: setup-java
@@ -544,14 +477,11 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-latest] os: *default_os
distribution: ['temurin', 'microsoft', 'corretto'] distribution: ['temurin', 'microsoft', 'corretto']
java-version-file: ['.java-version', '.tool-versions'] java-version-file: ['.java-version', '.tool-versions']
steps: steps:
- name: Checkout - *checkout_step
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Create .java-version file - name: Create .java-version file
shell: bash shell: bash
run: echo "17" > .java-version run: echo "17" > .java-version
@@ -577,14 +507,11 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-latest] os: *default_os
distribution: ['temurin', 'zulu', 'liberica', 'microsoft', 'corretto'] distribution: ['temurin', 'zulu', 'liberica', 'microsoft', 'corretto']
java-version-file: ['.java-version', '.tool-versions'] java-version-file: ['.java-version', '.tool-versions']
steps: steps:
- name: Checkout - *checkout_step
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Create .java-version file - name: Create .java-version file
shell: bash shell: bash
run: echo "11" > .java-version run: echo "11" > .java-version
@@ -609,14 +536,11 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-latest] os: *default_os
distribution: ['adopt', 'adopt-openj9', 'zulu'] distribution: ['adopt', 'adopt-openj9', 'zulu']
java-version-file: ['.java-version', '.tool-versions'] java-version-file: ['.java-version', '.tool-versions']
steps: steps:
- name: Checkout - *checkout_step
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Create .java-version file - name: Create .java-version file
shell: bash shell: bash
run: echo "17.0.10" > .java-version run: echo "17.0.10" > .java-version
@@ -641,14 +565,11 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-latest] os: *default_os
distribution: ['adopt', 'zulu', 'liberica'] distribution: ['adopt', 'zulu', 'liberica']
java-version-file: ['.java-version', '.tool-versions', '.sdkmanrc'] java-version-file: ['.java-version', '.tool-versions', '.sdkmanrc']
steps: steps:
- name: Checkout - *checkout_step
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Create .java-version file - name: Create .java-version file
shell: bash shell: bash
run: echo "openjdk64-17.0.10" > .java-version run: echo "openjdk64-17.0.10" > .java-version
@@ -677,10 +598,9 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [macos-latest, windows-latest, ubuntu-latest] os: *default_os
steps: steps:
- name: Checkout - *checkout_step
uses: actions/checkout@v6
- name: Setup Java 17 as default - name: Setup Java 17 as default
uses: ./ uses: ./
id: setup-java-17 id: setup-java-17
@@ -699,10 +619,12 @@ jobs:
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Verify JAVA_HOME still points to Java 17 - name: Verify JAVA_HOME still points to Java 17
env:
JAVA_17_PATH: ${{ steps.setup-java-17.outputs.path }}
run: | run: |
echo "JAVA_HOME=$JAVA_HOME" echo "JAVA_HOME=$JAVA_HOME"
echo "Java 17 path=${{ steps.setup-java-17.outputs.path }}" echo "Java 17 path=$JAVA_17_PATH"
if [ "$JAVA_HOME" != "${{ steps.setup-java-17.outputs.path }}" ]; then if [ "$JAVA_HOME" != "$JAVA_17_PATH" ]; then
echo "JAVA_HOME should still point to Java 17" echo "JAVA_HOME should still point to Java 17"
exit 1 exit 1
fi fi
@@ -731,14 +653,17 @@ jobs:
Write-Host "$envName=$JavaVersionPath" Write-Host "$envName=$JavaVersionPath"
shell: pwsh shell: pwsh
- name: Verify Java 21 outputs are set - name: Verify Java 21 outputs are set
env:
JAVA_21_PATH: ${{ steps.setup-java-21.outputs.path }}
JAVA_21_VERSION: ${{ steps.setup-java-21.outputs.version }}
run: | run: |
echo "Java 21 path=${{ steps.setup-java-21.outputs.path }}" echo "Java 21 path=$JAVA_21_PATH"
echo "Java 21 version=${{ steps.setup-java-21.outputs.version }}" echo "Java 21 version=$JAVA_21_VERSION"
if [ -z "${{ steps.setup-java-21.outputs.path }}" ]; then if [ -z "$JAVA_21_PATH" ]; then
echo "Java 21 path output should be set" echo "Java 21 path output should be set"
exit 1 exit 1
fi fi
if [ -z "${{ steps.setup-java-21.outputs.version }}" ]; then if [ -z "$JAVA_21_VERSION" ]; then
echo "Java 21 version output should be set" echo "Java 21 version output should be set"
exit 1 exit 1
fi fi
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: "@actions/cache" name: "@actions/cache"
version: 6.1.0 version: 6.2.0
type: npm type: npm
summary: Actions cache lib summary: Actions cache lib
homepage: https://github.com/actions/toolkit/tree/main/packages/cache homepage: https://github.com/actions/toolkit/tree/main/packages/cache
+131 -104
View File
@@ -20,8 +20,17 @@ This action allows you to work with Java and Scala projects.
## What's new in V6 ## What's new in V6
> [!NOTE]
> V6 is still in development (`main` branch) and is not yet recommended for production workflows.
- **Migrated to ESM** to enable support for the latest `@actions/*` package versions. This is an internal implementation change only. No changes are required to your workflow configuration, and the action's behavior is unchanged. Existing workflows continue to work as before. - **Migrated to ESM** to enable support for the latest `@actions/*` package versions. This is an internal implementation change only. No changes are required to your workflow configuration, and the action's behavior is unchanged. Existing workflows continue to work as before.
## Breaking changes in V6
- **The GPG passphrase is now passed to the Maven GPG Plugin through an environment variable (`gpg.passphraseEnvName`) instead of the deprecated `gpg.passphrase` server in `settings.xml`.** The `gpg-passphrase` input and its default (`GPG_PASSPHRASE`) are unchanged, so if you already set that environment variable in your build step your workflow keeps working. However, this now requires `maven-gpg-plugin` **3.2.0 or newer**; older versions do not honor `gpg.passphraseEnvName` and, because the `gpg.passphrase` server is no longer written, will not pick up the passphrase. Upgrade the plugin to 3.2.0+.
See [GPG](docs/advanced-usage.md#gpg) for details.
## Breaking changes in V5 ## Breaking changes in V5
- Upgraded action from node20 to node24 - Upgraded action from node20 to node24
@@ -31,80 +40,95 @@ For more details, see the full release notes on the [releases page](https://git
## Usage ## Usage
- `java-version`: The Java version that is going to be set up. Takes a whole or [semver](#supported-version-syntax) Java version. If not specified, the action will expect `java-version-file` input to be specified. - `java-version`: The Java version that is going to be set up. Takes a whole or [semver](#supported-version-syntax) Java version. If not specified, the action will expect `java-version-file` input to be specified.
- `java-version-file`: The path to a file containing java version. Supported file types are `.java-version`, `.tool-versions`, and `.sdkmanrc`. See more details in [about .java-version-file](docs/advanced-usage.md#Java-version-file). - `java-version-file`: The path to a file containing java version. Supported file types are `.java-version`, `.tool-versions`, and `.sdkmanrc`. See more details in [about .java-version-file](docs/advanced-usage.md#Java-version-file).
- `distribution`: Java [distribution](#supported-distributions). Required unless `java-version-file` points to `.sdkmanrc` with a recognized distribution suffix (for example `java=21.0.5-tem`). - `distribution`: Java [distribution](#supported-distributions). Required unless `java-version-file` points to `.sdkmanrc` with a recognized distribution suffix (for example `java=21.0.5-tem`).
- `java-package`: The packaging variant of the chosen distribution. Possible values: `jdk`, `jre`, `jdk+fx`, `jre+fx`. For Azul Zulu, `jdk+crac` and `jre+crac` are also supported. Default value: `jdk`. - `java-package`: The packaging variant of the chosen distribution. Possible values: `jdk`, `jre`, `jdk+fx`, `jre+fx`. For Azul Zulu, `jdk+crac` and `jre+crac` are also supported. Default value: `jdk`.
- `architecture`: The target architecture of the package. Possible values: `x86`, `x64`, `armv7`, `aarch64`, `ppc64le`. Default value: Derived from the runner machine. - `architecture`: The target architecture of the package. Possible values: `x86`, `x64`, `armv7`, `aarch64`, `ppc64le`. Default value: Derived from the runner machine.
- `jdk-file`: If a use-case requires a custom distribution setup-java uses the compressed JDK from the location pointed by this input and will take care of the installation and caching on the VM. Note: `distribution` must be set to 'jdkfile' (case-sensitive; all lowercase) when using this option. (The camelCase `jdkFile` input is still accepted as a deprecated alias and may be removed in a future release.) - `jdk-file`: If a use-case requires a custom distribution setup-java uses the compressed JDK from the location pointed by this input and will take care of the installation and caching on the VM. Note: `distribution` must be set to 'jdkfile' (case-sensitive; all lowercase) when using this option. (The camelCase `jdkFile` input is still accepted as a deprecated alias and may be removed in a future release.)
- `check-latest`: Setting this option makes the action to check for the latest available version for the version spec. - `check-latest`: Setting this option makes the action to check for the latest available version for the version spec.
- `set-default`: Set to `false` to install a JDK without making it the default. When `false`, `JAVA_HOME` and `PATH` are not updated, but `JAVA_HOME_<major>_<arch>` is still set so the JDK remains discoverable. Default value: `true`. See [Installing JDK without setting as default](docs/advanced-usage.md#Installing-JDK-without-setting-as-default) for more details. - `set-default`: Set to `false` to install a JDK without making it the default. When `false`, `JAVA_HOME` and `PATH` are not updated, but `JAVA_HOME_<major>_<arch>` is still set so the JDK remains discoverable. Default value: `true`. See [Installing JDK without setting as default](docs/advanced-usage.md#Installing-JDK-without-setting-as-default) for more details.
- `verify-signature`: Verifies downloaded Java package signatures when supported by the selected distribution. Currently supported for `temurin` and `microsoft`. If set to `true` for unsupported distributions, the action fails. - `problem-matcher`: Set to `false` to disable Java problem matcher annotations (compiler diagnostics and uncaught exceptions). Default value: `true`. See [Java problem matcher](docs/advanced-usage.md#java-problem-matcher-compiler-annotations) for details and annotation limits.
- `verify-signature-public-key`: ASCII-armored GPG public key used to verify the downloaded package signature. Overrides the default bundled key for the selected distribution. - `verify-signature`: Verifies downloaded Java package signatures when supported by the selected distribution. Currently supported for `temurin` and `microsoft`. If set to `true` for unsupported distributions, the action fails.
- `cache`: Quick [setup caching](#caching-packages-dependencies) for the dependencies managed through one of the predefined package managers. It can be one of "maven", "gradle" or "sbt". - `verify-signature-public-key`: ASCII-armored GPG public key used to verify the downloaded package signature. Overrides the default bundled key for the selected distribution.
- `cache-dependency-path`: The path to a dependency file: pom.xml, build.gradle, build.sbt, etc. This option can be used with the `cache` option. If this option is omitted, the action searches for the dependency file in the entire repository. This option supports wildcards and a list of file names for caching multiple dependencies. - `token`: The token used to authenticate when fetching version manifests hosted on GitHub.com. Defaults to `${{ github.token }}` when running on GitHub.com; defaults to an empty string on GitHub Enterprise Server. On GHES, provide a GitHub.com personal access token if manifest requests are rate-limited. See [Using Microsoft distribution on GHES](docs/advanced-usage.md#using-microsoft-distribution-on-ghes) for more details.
- `cache`: Quick [setup caching](#caching-packages-dependencies) for the dependencies managed through one of the predefined package managers. It can be one of "maven", "gradle" or "sbt".
- `cache-dependency-path`: The path to a dependency file: pom.xml, build.gradle, build.sbt, etc. This option can be used with the `cache` option. If this option is omitted, the action searches for the dependency file in the entire repository. This option supports wildcards and a list of file names for caching multiple dependencies.
#### Maven options #### Maven options
The action has a bunch of inputs to generate maven's [settings.xml](https://maven.apache.org/settings.html) on the fly and pass the values to Apache Maven GPG Plugin as well as Apache Maven Toolchains. See [advanced usage](docs/advanced-usage.md) for more. The action has a bunch of inputs to generate maven's [settings.xml](https://maven.apache.org/settings.html) on the fly and pass the values to Apache Maven GPG Plugin as well as Apache Maven Toolchains. See [advanced usage](docs/advanced-usage.md) for more.
- `overwrite-settings`: By default action overwrites the settings.xml. In order to skip generation of file if it exists, set this to `false`. - `overwrite-settings`: By default action overwrites the settings.xml. In order to skip generation of file if it exists, set this to `false`.
- `server-id`: ID of the distributionManagement repository in the pom.xml file. Default is `github`. - `server-id`: ID of the distributionManagement repository in the pom.xml file. Default is `github`.
- `server-username`: Environment variable name for the username for authentication to the Apache Maven repository. Default is GITHUB_ACTOR. - `server-username`: Environment variable name for the username for authentication to the Apache Maven repository. Default is GITHUB\_ACTOR.
- `server-password`: Environment variable name for password or token for authentication to the Apache Maven repository. Default is GITHUB_TOKEN. - `server-password`: Environment variable name for password or token for authentication to the Apache Maven repository. Default is GITHUB\_TOKEN.
- `settings-path`: Maven related setting to point to the directory where the settings.xml file will be written. Default is ~/.m2. - `settings-path`: Maven related setting to point to the directory where the settings.xml file will be written. Default is \~/.m2.
- `gpg-private-key`: GPG private key to import. Default is empty string. - `gpg-private-key`: GPG private key to import. Default is empty string.
- `gpg-passphrase`: Environment variable name for the GPG private key passphrase. Default is GPG_PASSPHRASE. - `gpg-passphrase`: Environment variable name for the GPG private key passphrase. Default is GPG\_PASSPHRASE.
- `mvn-toolchain-id`: Name of Maven Toolchain ID if the default name of `${distribution}_${java-version}` is not wanted. - `mvn-toolchain-id`: Name of Maven Toolchain ID if the default name of `${distribution}_${java-version}` is not wanted.
- `mvn-toolchain-vendor`: Name of Maven Toolchain Vendor if the default name of `${distribution}` is not wanted. - `mvn-toolchain-vendor`: Name of Maven Toolchain Vendor if the default name of `${distribution}` is not wanted.
- `show-download-progress`: Set to `true` to keep Maven artifact download and transfer progress in build logs. Default value: `false`. By default, the action adds `-ntp` (`--no-transfer-progress`) to `MAVEN_ARGS`. This input has no effect on non-Maven builds. See [Maven transfer progress](docs/advanced-usage.md#maven-transfer-progress-download-logs) for more details.
### Basic Configuration ### Basic Configuration
#### Eclipse Temurin #### Eclipse Temurin
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: 'temurin' # See 'Supported distributions' for available options distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '25' java-version: '25'
- run: java --version - run: java --version
``` ```
#### Azul Zulu OpenJDK #### Azul Zulu OpenJDK
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: 'zulu' # See 'Supported distributions' for available options distribution: 'zulu' # See 'Supported distributions' for available options
java-version: '25' java-version: '25'
- run: java --version - run: java --version
``` ```
#### Supported version syntax #### Supported version syntax
The `java-version` input supports an exact version or a version range using [SemVer](https://semver.org/) notation. The values below are examples, not an exhaustive list: The `java-version` input supports an exact version or a version range using [SemVer](https://semver.org/) notation. The values below are examples, not an exhaustive list:
- major versions, such as: `8`, `11`, `16`, `17`, `21`, `25` - major versions, such as: `8`, `11`, `16`, `17`, `21`, `25`
- more specific versions: `8.0.282+8`, `8.0.232`, `11.0`, `11.0.4`, `17.0` - more specific versions: `8.0.282+8`, `8.0.232`, `11.0`, `11.0.4`, `17.0`
- multi-field Java versions (JEP 322), such as: `11.0.9.1`, `18.0.1.1`
- early access (EA) versions: `15-ea`, `15.0.0-ea` - early access (EA) versions: `15-ea`, `15.0.0-ea`
- the `latest` alias, which floats to the newest available stable (GA) release
> [!NOTE]
> - `latest` always resolves the newest version from the distribution's remote metadata (it behaves like `check-latest: true`), so it ignores any older version already present in the runner tool cache. This has the same performance trade-off described in [Check latest](#check-latest).
> - `latest` is only supported through the `java-version` input, not through `java-version-file`, and it resolves stable (GA) releases only — it cannot be combined with `-ea`.
> - The `jdkfile` distribution does not support `latest`, as it installs from a local file.
> - For `oracle` and `graalvm` (Oracle GraalVM), `latest` uses the Adoptium API only to determine the newest GA **major version number** — the JDK binary itself is still downloaded from the Oracle / GraalVM servers for that major. Because these distributions have no endpoint to list their own releases, if their servers haven't published the resolved major yet, the action fails and asks you to specify a concrete version. Note the Oracle JDK license caveat below still applies to a floating `latest`.
> - For `graalvm-community`, `latest` floats to the newest GA release published on GitHub, so it never depends on the Adoptium API and always resolves to the newest major that GraalVM Community actually ships.
#### Supported distributions #### Supported distributions
Currently, the following distributions are supported: Currently, the following distributions are supported:
@@ -115,6 +139,7 @@ Currently, the following distributions are supported:
| `adopt` or `adopt-hotspot` | [AdoptOpenJDK Hotspot](https://adoptopenjdk.net/) | [`adopt-hotspot` license](https://adoptopenjdk.net/about.html) | | `adopt` or `adopt-hotspot` | [AdoptOpenJDK Hotspot](https://adoptopenjdk.net/) | [`adopt-hotspot` license](https://adoptopenjdk.net/about.html) |
| `adopt-openj9` | [AdoptOpenJDK OpenJ9](https://adoptopenjdk.net/) | [`adopt-openj9` license](https://adoptopenjdk.net/about.html) | | `adopt-openj9` | [AdoptOpenJDK OpenJ9](https://adoptopenjdk.net/) | [`adopt-openj9` license](https://adoptopenjdk.net/about.html) |
| `liberica` | [Liberica JDK](https://bell-sw.com/) | [`liberica` license](https://bell-sw.com/liberica_eula/) | | `liberica` | [Liberica JDK](https://bell-sw.com/) | [`liberica` license](https://bell-sw.com/liberica_eula/) |
| `liberica-nik` | [Liberica Native Image Kit](https://bell-sw.com/pages/downloads/native-image-kit/) | [`liberica-nik` license](https://bell-sw.com/liberica_nik_eula/) |
| `microsoft` | [Microsoft Build of OpenJDK](https://www.microsoft.com/openjdk) | [`microsoft` license](https://docs.microsoft.com/java/openjdk/faq) | `microsoft` | [Microsoft Build of OpenJDK](https://www.microsoft.com/openjdk) | [`microsoft` license](https://docs.microsoft.com/java/openjdk/faq)
| `corretto` | [Amazon Corretto Build of OpenJDK](https://aws.amazon.com/corretto/) | [`corretto` license](https://aws.amazon.com/corretto/faqs/) | `corretto` | [Amazon Corretto Build of OpenJDK](https://aws.amazon.com/corretto/) | [`corretto` license](https://aws.amazon.com/corretto/faqs/)
| `semeru` | [IBM Semeru Runtime Open Edition](https://developer.ibm.com/languages/java/semeru-runtimes/downloads/) | [`semeru` license](https://openjdk.java.net/legal/gplv2+ce.html) | | `semeru` | [IBM Semeru Runtime Open Edition](https://developer.ibm.com/languages/java/semeru-runtimes/downloads/) | [`semeru` license](https://openjdk.java.net/legal/gplv2+ce.html) |
@@ -153,24 +178,26 @@ The workflow output `cache-primary-key` exposes the primary cache key computed b
The cache input is optional, and caching is turned off by default. The cache input is optional, and caching is turned off by default.
**Maven Wrapper:** when `cache: 'maven'` is enabled, the action also caches and restores the Maven Wrapper distribution downloaded to `~/.m2/wrapper/dists` (in addition to the local repository), so wrapper-based (`./mvnw`) builds don't re-download the wrapper on every run. This is keyed on `**/.mvn/wrapper/maven-wrapper.properties` as shown above. **Maven Wrapper:** when `cache: 'maven'` is enabled, the action also caches and restores the Maven Wrapper distribution downloaded to `~/.m2/wrapper/dists` (in addition to the local repository), so wrapper-based (`./mvnw`) builds don't re-download the Maven distribution. The wrapper distribution is stored in a **separate** cache entry keyed only on `**/.mvn/wrapper/maven-wrapper.properties`, so it stays cached across the frequent `pom.xml` changes that rotate the main dependency cache key.
#### Caching gradle dependencies #### Caching gradle dependencies
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '25' java-version: '25'
cache: 'gradle' cache: 'gradle'
cache-dependency-path: | # optional cache-dependency-path: | # optional
sub-project/*.gradle* sub-project/*.gradle*
sub-project/**/gradle-wrapper.properties sub-project/**/gradle-wrapper.properties
- run: ./gradlew build --no-daemon - run: ./gradlew build --no-daemon
``` ```
Using the `cache: gradle` provides a simple and effective way to cache Gradle dependencies with minimal configuration. Using the `cache: gradle` provides a simple and effective way to cache Gradle dependencies with minimal configuration.
**Gradle Wrapper:** when `cache: 'gradle'` is enabled, the action also caches and restores the Gradle Wrapper distribution downloaded to `~/.gradle/wrapper` (in addition to the Gradle caches), so wrapper-based (`./gradlew`) builds don't re-download the Gradle distribution. The wrapper distribution is stored in a **separate** cache entry keyed only on `**/gradle-wrapper.properties`, so it stays cached across the frequent `*.gradle*` changes that rotate the main dependency cache key.
For projects that require more advanced `Gradle` caching features, such as caching build outputs, support for Gradle configuration cache, encrypted cache storage, fine-grained cache control (including options to enable or disable the cache, set it to read-only or write-only, perform automated cleanup, and define custom cache rules), or optimized performance for complex CI workflows, consider using [`gradle/actions/setup-gradle`](https://github.com/gradle/actions/tree/main/setup-gradle). For projects that require more advanced `Gradle` caching features, such as caching build outputs, support for Gradle configuration cache, encrypted cache storage, fine-grained cache control (including options to enable or disable the cache, set it to read-only or write-only, perform automated cleanup, and define custom cache rules), or optimized performance for complex CI workflows, consider using [`gradle/actions/setup-gradle`](https://github.com/gradle/actions/tree/main/setup-gradle).
For setup details and a comprehensive overview of all available features, visit the [setup-gradle documentation](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md). For setup details and a comprehensive overview of all available features, visit the [setup-gradle documentation](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md).
@@ -178,31 +205,38 @@ For setup details and a comprehensive overview of all available features, visit
#### Caching maven dependencies #### Caching maven dependencies
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '25' java-version: '25'
cache: 'maven' cache: 'maven'
cache-dependency-path: 'sub-project/pom.xml' # optional cache-dependency-path: 'sub-project/pom.xml' # optional
- name: Build with Maven - name: Build with Maven
run: mvn -B package --file pom.xml run: mvn package --file pom.xml
``` ```
> [!NOTE]
> Maven resolves plugin dependencies lazily, so a cache created by a "thin" goal
> (e.g. `mvn compile`) can be missing plugin dependencies that later
> `test`/`verify`/`package` jobs then re-download on every run. See
> [Ensuring the Maven cache is complete](docs/advanced-usage.md#ensuring-the-maven-cache-is-complete-plugin-dependencies)
> for how to seed a complete cache.
#### Caching sbt dependencies #### Caching sbt dependencies
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '25' java-version: '25'
cache: 'sbt' cache: 'sbt'
cache-dependency-path: | # optional cache-dependency-path: | # optional
sub-project/build.sbt sub-project/build.sbt
sub-project/project/build.properties sub-project/project/build.properties
- name: Build with SBT - name: Build with SBT
run: sbt package run: sbt package
``` ```
#### Cache segment restore timeout #### Cache segment restore timeout
@@ -212,13 +246,13 @@ Usually, cache gets downloaded in multiple segments of fixed sizes. Sometimes, a
env: env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: '5' SEGMENT_DOWNLOAD_TIMEOUT_MINS: '5'
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '25' java-version: '25'
cache: 'gradle' cache: 'gradle'
- run: ./gradlew build --no-daemon - run: ./gradlew build --no-daemon
``` ```
### Check latest ### Check latest
@@ -232,13 +266,13 @@ For Java distributions that are not cached on Hosted images, `check-latest` alwa
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '25' java-version: '25'
check-latest: true check-latest: true
- run: java --version - run: java --version
``` ```
### Testing against different Java versions ### Testing against different Java versions
@@ -251,9 +285,9 @@ jobs:
java: [ '8', '11', '17', '21', '25' ] java: [ '8', '11', '17', '21', '25' ]
name: Java ${{ matrix.Java }} sample name: Java ${{ matrix.Java }} sample
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v7
- name: Setup java - name: Setup java
uses: actions/setup-java@v5 uses: actions/setup-java@v6
with: with:
distribution: '<distribution>' distribution: '<distribution>'
java-version: ${{ matrix.java }} java-version: ${{ matrix.java }}
@@ -262,11 +296,11 @@ jobs:
### Install multiple JDKs ### Install multiple JDKs
All configured Java versions are added to the PATH. The last one added to the PATH (i.e., the last JDK set up by this action) will be used as the default and available globally. Other Java versions can be accessed through environment variables such as 'JAVA_HOME_{{ MAJOR_VERSION }}_{{ ARCHITECTURE }}'. To use a specific Java version, set the JAVA_HOME environment variable accordingly and prepend its bin directory to the PATH to ensure it takes priority during execution. All configured Java versions are added to the PATH. The last one added to the PATH (i.e., the last JDK set up by this action) will be used as the default and available globally. Other Java versions can be accessed through environment variables such as 'JAVA\_HOME\_{{ MAJOR\_VERSION }}\_{{ ARCHITECTURE }}'. To use a specific Java version, set the JAVA\_HOME environment variable accordingly and prepend its bin directory to the PATH to ensure it takes priority during execution.
```yaml ```yaml
steps: steps:
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: '<distribution>' distribution: '<distribution>'
java-version: | java-version: |
@@ -281,38 +315,31 @@ In the example above multiple JDKs are installed for the same job. The result af
### Advanced Configuration ### Advanced Configuration
- [Selecting a Java distribution](docs/advanced-usage.md#Selecting-a-Java-distribution) - [Selecting a Java distribution](docs/advanced-usage.md#Selecting-a-Java-distribution)
- [Eclipse Temurin](docs/advanced-usage.md#Eclipse-Temurin) - [Eclipse Temurin](docs/advanced-usage.md#Eclipse-Temurin)
- [Adopt](docs/advanced-usage.md#Adopt) - [Adopt](docs/advanced-usage.md#Adopt)
- [Zulu](docs/advanced-usage.md#Zulu) - [Zulu](docs/advanced-usage.md#Zulu)
- [Liberica](docs/advanced-usage.md#Liberica) - [Liberica](docs/advanced-usage.md#Liberica)
- [Microsoft](docs/advanced-usage.md#Microsoft) - [Liberica Native Image Kit](docs/advanced-usage.md#Liberica-Native-Image-Kit)
- [Amazon Corretto](docs/advanced-usage.md#Amazon-Corretto) - [Microsoft](docs/advanced-usage.md#Microsoft)
- [Oracle](docs/advanced-usage.md#Oracle) - [Amazon Corretto](docs/advanced-usage.md#Amazon-Corretto)
- [Alibaba Dragonwell](docs/advanced-usage.md#Alibaba-Dragonwell) - [Oracle](docs/advanced-usage.md#Oracle)
- [SapMachine](docs/advanced-usage.md#SapMachine) - [Alibaba Dragonwell](docs/advanced-usage.md#Alibaba-Dragonwell)
- [GraalVM](docs/advanced-usage.md#GraalVM) - [SapMachine](docs/advanced-usage.md#SapMachine)
- [JetBrains](docs/advanced-usage.md#JetBrains) - [GraalVM](docs/advanced-usage.md#GraalVM)
- [Tencent Kona](docs/advanced-usage.md#Tencent-Kona) - [JetBrains](docs/advanced-usage.md#JetBrains)
- [Tencent Kona](docs/advanced-usage.md#Tencent-Kona)
- [Installing custom Java package type](docs/advanced-usage.md#Installing-custom-Java-package-type) - [Installing custom Java package type](docs/advanced-usage.md#Installing-custom-Java-package-type)
- [Installing custom Java architecture](docs/advanced-usage.md#Installing-custom-Java-architecture) - [Installing custom Java architecture](docs/advanced-usage.md#Installing-custom-Java-architecture)
- [Installing custom Java distribution from local file](docs/advanced-usage.md#Installing-Java-from-local-file) - [Installing custom Java distribution from local file](docs/advanced-usage.md#Installing-Java-from-local-file)
- [Testing against different Java distributions](docs/advanced-usage.md#Testing-against-different-Java-distributions) - [Testing against different Java distributions](docs/advanced-usage.md#Testing-against-different-Java-distributions)
- [Testing against different platforms](docs/advanced-usage.md#Testing-against-different-platforms) - [Testing against different platforms](docs/advanced-usage.md#Testing-against-different-platforms)
- [Publishing using Apache Maven](docs/advanced-usage.md#Publishing-using-Apache-Maven) - [Publishing using Apache Maven](docs/advanced-usage.md#Publishing-using-Apache-Maven)
- [Maven transfer progress (download logs)](docs/advanced-usage.md#maven-transfer-progress-download-logs)
- [Publishing using Gradle](docs/advanced-usage.md#Publishing-using-Gradle) - [Publishing using Gradle](docs/advanced-usage.md#Publishing-using-Gradle)
- [Hosted Tool Cache](docs/advanced-usage.md#Hosted-Tool-Cache) - [Hosted Tool Cache](docs/advanced-usage.md#Hosted-Tool-Cache)
- [Modifying Maven Toolchains](docs/advanced-usage.md#Modifying-Maven-Toolchains) - [Modifying Maven Toolchains](docs/advanced-usage.md#Modifying-Maven-Toolchains)
- [Java Version File](docs/advanced-usage.md#Java-version-file) - [Java Version File](docs/advanced-usage.md#Java-version-file)
## V2 vs V1
Examples in this README use `actions/setup-java@v5`, but the main migration note from V1 still applies to all later major versions (`v2`, `v3`, `v4`, and `v5`):
- Starting with V2, the action supports custom distributions. V1 supports only Azul Zulu OpenJDK.
- Starting with V2, you must specify distribution along with the version. V1 defaults to Azul Zulu OpenJDK, so only version input is required. Follow [the migration guide](docs/switching-to-v2.md) to switch from V1 to V2.
For information about the latest releases, recent updates, and newly supported distributions, please refer to the `setup-java` [Releases](https://github.com/actions/setup-java/releases).
## Recommended permissions ## Recommended permissions
When using the `setup-java` action in your GitHub Actions workflow, it is recommended to set the following permissions to ensure proper functionality: When using the `setup-java` action in your GitHub Actions workflow, it is recommended to set the following permissions to ensure proper functionality:
+33 -2
View File
@@ -228,9 +228,40 @@ describe('auth tests', () => {
<username>\${env.${username}}</username> <username>\${env.${username}}</username>
<password>\${env.&amp;&lt;&gt;"''"&gt;&lt;&amp;}</password> <password>\${env.&amp;&lt;&gt;"''"&gt;&lt;&amp;}</password>
</server> </server>
</servers>
<profiles>
<profile>
<id>setup-java-gpg</id>
<properties>
<gpg.passphraseEnvName>${gpgPassphrase}</gpg.passphraseEnvName>
</properties>
</profile>
</profiles>
<activeProfiles>
<activeProfile>setup-java-gpg</activeProfile>
</activeProfiles>
</settings>`;
expect(auth.generate(id, username, password, gpgPassphrase)).toEqual(
expectedSettings
);
});
it('does not add a gpg profile when the passphrase env var is the maven-gpg-plugin default', () => {
const id = 'packages';
const username = 'USER';
const password = '&<>"\'\'"><&';
const gpgPassphrase = 'MAVEN_GPG_PASSPHRASE';
const expectedSettings = `<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
<interactiveMode>false</interactiveMode>
<servers>
<server> <server>
<id>gpg.passphrase</id> <id>${id}</id>
<passphrase>\${env.${gpgPassphrase}}</passphrase> <username>\${env.${username}}</username>
<password>\${env.&amp;&lt;&gt;"''"&gt;&lt;&amp;}</password>
</server> </server>
</servers> </servers>
</settings>`; </settings>`;
+196 -12
View File
@@ -166,10 +166,7 @@ describe('dependency cache', () => {
await restore('maven', ''); await restore('maven', '');
expect(spyCacheRestore).toHaveBeenCalledWith( expect(spyCacheRestore).toHaveBeenCalledWith(
[ [join(os.homedir(), '.m2', 'repository')],
join(os.homedir(), '.m2', 'repository'),
join(os.homedir(), '.m2', 'wrapper', 'dists')
],
expect.any(String) expect.any(String)
); );
expect(spyGlobHashFiles).toHaveBeenCalledWith( expect(spyGlobHashFiles).toHaveBeenCalledWith(
@@ -196,10 +193,7 @@ describe('dependency cache', () => {
await restore('maven', ''); await restore('maven', '');
expect(spyCacheRestore).toHaveBeenCalledWith( expect(spyCacheRestore).toHaveBeenCalledWith(
[ [join(os.homedir(), '.m2', 'repository')],
join(os.homedir(), '.m2', 'repository'),
join(os.homedir(), '.m2', 'wrapper', 'dists')
],
expect.any(String) expect.any(String)
); );
expect(spyGlobHashFiles).toHaveBeenCalledWith( expect(spyGlobHashFiles).toHaveBeenCalledWith(
@@ -214,10 +208,7 @@ describe('dependency cache', () => {
await restore('maven', ''); await restore('maven', '');
expect(spyCacheRestore).toHaveBeenCalledWith( expect(spyCacheRestore).toHaveBeenCalledWith(
[ [join(os.homedir(), '.m2', 'repository')],
join(os.homedir(), '.m2', 'repository'),
join(os.homedir(), '.m2', 'wrapper', 'dists')
],
expect.any(String) expect.any(String)
); );
expect(spyGlobHashFiles).toHaveBeenCalledWith( expect(spyGlobHashFiles).toHaveBeenCalledWith(
@@ -226,6 +217,47 @@ describe('dependency cache', () => {
expect(spyWarning).not.toHaveBeenCalled(); expect(spyWarning).not.toHaveBeenCalled();
expect(spyInfo).toHaveBeenCalledWith('maven cache is not found'); expect(spyInfo).toHaveBeenCalledWith('maven cache is not found');
}); });
it('restores the maven wrapper distribution cache independently of the main cache', async () => {
createDirectory(join(workspace, '.mvn'));
createDirectory(join(workspace, '.mvn', 'wrapper'));
createFile(
join(workspace, '.mvn', 'wrapper', 'maven-wrapper.properties')
);
await restore('maven', '');
// Main dependency cache no longer carries the wrapper dists path.
expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'repository')],
expect.any(String)
);
expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'wrapper', 'dists')],
expect.stringContaining('maven-wrapper')
);
expect(spyGlobHashFiles).toHaveBeenCalledWith(
'**/.mvn/wrapper/maven-wrapper.properties'
);
});
it('skips the maven wrapper cache when no wrapper properties exist', async () => {
createFile(join(workspace, 'pom.xml'));
spyGlobHashFiles.mockImplementation((pattern: string) =>
Promise.resolve(
pattern === '**/.mvn/wrapper/maven-wrapper.properties'
? ''
: 'hash-stub'
)
);
await restore('maven', '');
// Only the main dependency cache is restored; the wrapper cache path is
// never touched because the project does not use mvnw.
expect(spyCacheRestore).toHaveBeenCalledTimes(1);
expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'repository')],
expect.any(String)
);
expect(spyWarning).not.toHaveBeenCalled();
});
}); });
describe('for gradle', () => { describe('for gradle', () => {
it('throws error if no build.gradle found', async () => { it('throws error if no build.gradle found', async () => {
@@ -282,6 +314,43 @@ describe('dependency cache', () => {
expect(spyWarning).not.toHaveBeenCalled(); expect(spyWarning).not.toHaveBeenCalled();
expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found'); expect(spyInfo).toHaveBeenCalledWith('gradle cache is not found');
}); });
it('restores the gradle wrapper distribution cache independently of the main cache', async () => {
createFile(join(workspace, 'build.gradle'));
await restore('gradle', '');
// Main dependency cache no longer carries the wrapper path.
expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.gradle', 'caches')],
expect.any(String)
);
// Wrapper distribution is restored on its own, keyed only on the
// wrapper properties file.
expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.gradle', 'wrapper')],
expect.stringContaining('setup-java-')
);
expect(spyGlobHashFiles).toHaveBeenCalledWith(
'**/gradle-wrapper.properties'
);
});
it('skips the gradle wrapper cache when no wrapper properties exist', async () => {
createFile(join(workspace, 'build.gradle'));
spyGlobHashFiles.mockImplementation((pattern: string) =>
Promise.resolve(
pattern === '**/gradle-wrapper.properties' ? '' : 'hash-stub'
)
);
await restore('gradle', '');
// Only the main dependency cache is restored; the wrapper cache path is
// never touched because the project does not use the gradle wrapper.
expect(spyCacheRestore).toHaveBeenCalledTimes(1);
expect(spyCacheRestore).toHaveBeenCalledWith(
[join(os.homedir(), '.gradle', 'caches')],
expect.any(String)
);
expect(spyWarning).not.toHaveBeenCalled();
});
}); });
describe('for sbt', () => { describe('for sbt', () => {
it('throws error if no build.sbt found', async () => { it('throws error if no build.sbt found', async () => {
@@ -457,6 +526,77 @@ describe('dependency cache', () => {
expect.stringMatching(/^Cache saved with the key:.*/) expect.stringMatching(/^Cache saved with the key:.*/)
); );
}); });
it('saves the maven wrapper distribution cache under its own key', async () => {
createFile(join(workspace, 'pom.xml'));
(core.getState as jest.Mock<any>).mockImplementation((name: any) => {
switch (name) {
case 'cache-primary-key':
return 'setup-java-cache-primary-key';
case 'cache-matched-key':
return 'setup-java-cache-matched-key';
case 'cache-primary-key-maven-wrapper':
return 'setup-java-maven-wrapper-key';
default:
return '';
}
});
await save('maven');
expect(spyCacheSave).toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'wrapper', 'dists')],
'setup-java-maven-wrapper-key'
);
expect(spyWarning).not.toHaveBeenCalled();
});
it('does not save the maven wrapper cache on an exact wrapper hit', async () => {
createFile(join(workspace, 'pom.xml'));
(core.getState as jest.Mock<any>).mockImplementation((name: any) => {
switch (name) {
case 'cache-primary-key':
return 'setup-java-cache-primary-key';
case 'cache-matched-key':
return 'setup-java-cache-matched-key';
case 'cache-primary-key-maven-wrapper':
case 'cache-matched-key-maven-wrapper':
return 'setup-java-maven-wrapper-key';
default:
return '';
}
});
await save('maven');
expect(spyCacheSave).not.toHaveBeenCalledWith(
[join(os.homedir(), '.m2', 'wrapper', 'dists')],
expect.any(String)
);
});
it('does not fail the post step when the wrapper distribution path is missing', async () => {
createFile(join(workspace, 'pom.xml'));
(core.getState as jest.Mock<any>).mockImplementation((name: any) => {
switch (name) {
case 'cache-primary-key':
return 'setup-java-cache-primary-key';
case 'cache-matched-key':
return 'setup-java-cache-matched-key';
case 'cache-primary-key-maven-wrapper':
return 'setup-java-maven-wrapper-key';
default:
return '';
}
});
spyCacheSave.mockImplementation((paths: string[]) =>
paths.includes(join(os.homedir(), '.m2', 'wrapper', 'dists'))
? Promise.reject(
new cache.ValidationError(
'Path Validation Error: Path(s) specified in the action for caching do(es) not exist'
)
)
: Promise.resolve(0)
);
await expect(save('maven')).resolves.toBeUndefined();
expect(spyWarning).not.toHaveBeenCalled();
});
}); });
describe('for gradle', () => { describe('for gradle', () => {
it('uploads cache even if no build.gradle found', async () => { it('uploads cache even if no build.gradle found', async () => {
@@ -509,6 +649,50 @@ describe('dependency cache', () => {
expect.stringMatching(/^Cache saved with the key:.*/) expect.stringMatching(/^Cache saved with the key:.*/)
); );
}); });
it('saves the gradle wrapper distribution cache under its own key', async () => {
createFile(join(workspace, 'build.gradle'));
(core.getState as jest.Mock<any>).mockImplementation((name: any) => {
switch (name) {
case 'cache-primary-key':
return 'setup-java-cache-primary-key';
case 'cache-matched-key':
return 'setup-java-cache-matched-key';
case 'cache-primary-key-gradle-wrapper':
return 'setup-java-gradle-wrapper-key';
default:
return '';
}
});
await save('gradle');
expect(spyCacheSave).toHaveBeenCalledWith(
[join(os.homedir(), '.gradle', 'wrapper')],
'setup-java-gradle-wrapper-key'
);
expect(spyWarning).not.toHaveBeenCalled();
});
it('does not save the gradle wrapper cache on an exact wrapper hit', async () => {
createFile(join(workspace, 'build.gradle'));
(core.getState as jest.Mock<any>).mockImplementation((name: any) => {
switch (name) {
case 'cache-primary-key':
return 'setup-java-cache-primary-key';
case 'cache-matched-key':
return 'setup-java-cache-matched-key';
case 'cache-primary-key-gradle-wrapper':
case 'cache-matched-key-gradle-wrapper':
return 'setup-java-gradle-wrapper-key';
default:
return '';
}
});
await save('gradle');
expect(spyCacheSave).not.toHaveBeenCalledWith(
[join(os.homedir(), '.gradle', 'wrapper')],
expect.any(String)
);
});
}); });
describe('for sbt', () => { describe('for sbt', () => {
it('uploads cache even if no build.sbt found', async () => { it('uploads cache even if no build.sbt found', async () => {
+11
View File
@@ -0,0 +1,11 @@
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
pom.xml.next
release.properties
dependency-reduced-pom.xml
buildNumber.properties
.mvn/timing.properties
# https://github.com/takari/maven-wrapper#usage-without-binary-jar
.mvn/wrapper/maven-wrapper.jar
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>io.github.actions</groupId>
<artifactId>setup-java-maven2-example</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
</dependencies>
</project>
+1
View File
@@ -0,0 +1 @@
target/
+3
View File
@@ -0,0 +1,3 @@
ThisBuild / scalaVersion := "2.12.15"
libraryDependencies += "org.scala-lang.modules" %% "scala-parser-combinators" % "2.1.1"
+39
View File
@@ -0,0 +1,39 @@
#!/bin/sh
# Assert whether a directory exists, for use in the e2e cache workflows.
#
# Usage: check-dir.sh <dir> [present|absent]
#
# present (default): fail if <dir> does NOT exist, otherwise list its contents.
# absent: fail if <dir> DOES exist.
#
# Call with already-expanded paths (e.g. "$HOME/.gradle/caches") to avoid
# tilde-expansion pitfalls.
set -eu
if [ "$#" -lt 1 ]; then
echo "Usage: check-dir.sh <dir> [present|absent]" >&2
exit 2
fi
dir=$1
mode=${2:-present}
case "$mode" in
present)
if [ ! -d "$dir" ]; then
echo "::error::The $dir directory does not exist unexpectedly"
exit 1
fi
ls "$dir"
;;
absent)
if [ -d "$dir" ]; then
echo "::error::The $dir directory exists unexpectedly"
exit 1
fi
;;
*)
echo "::error::Unknown mode '$mode' (expected 'present' or 'absent')"
exit 1
;;
esac
+40
View File
@@ -158,5 +158,45 @@
} }
] ]
} }
],
"25": [
{
"version": "25.0.3",
"jdkVersion": "25.0.3",
"latest": true,
"baseUrl": "https://github.com/Tencent/TencentKona-25/releases/download/TencentKona-25.0.3/",
"files": [
{
"os": "linux",
"arch": "aarch64",
"filename": "TencentKona-25.0.3.b1-jdk_linux-aarch64.tar.gz",
"checksum": "2fb77e3ba9c00045ca497ea22210f18dae4bf7df4c87029f5abadd42a5daf8c7"
},
{
"os": "linux",
"arch": "x86_64",
"filename": "TencentKona-25.0.3.b1-jdk_linux-x86_64.tar.gz",
"checksum": "47445e6fad020e834055a705bb48fa3cd0727a2c57ad6f1c5206a45f4efb2d67"
},
{
"os": "macos",
"arch": "aarch64",
"filename": "TencentKona-25.0.3.b1_jdk_macosx-aarch64_notarized.tar.gz",
"checksum": "e29405eff95da412ed7ecc890e6ef5ebbfcd9ab334005b3d32d1700bbe4204d2"
},
{
"os": "macos",
"arch": "x86_64",
"filename": "TencentKona-25.0.3.b1_jdk_macosx-x86_64_notarized.tar.gz",
"checksum": "d512db5c079db16bd3a9c86d9cb979808994e90e4de29e17d27e5219fe488659"
},
{
"os": "windows",
"arch": "x86_64",
"filename": "TencentKona-25.0.3.b1_jdk_windows-x86_64_signed.zip",
"checksum": "865bce92ccbbca75115076e618a98baa1d0f30fcccdce954c0a22bee33d6ae83"
}
]
}
] ]
} }
+739
View File
@@ -0,0 +1,739 @@
[
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/25.0.1+3-25.0.1+16/bellsoft-liberica-vm-openjdk25.0.1+16-25.0.1+3-linux-amd64.tar.gz",
"version": "25.0.1+3",
"components": [
{
"component": "liberica",
"version": "25.0.1+16",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/24.1.0+1-23+38/bellsoft-liberica-vm-openjdk23+38-24.1.0+1-linux-amd64.tar.gz",
"version": "24.1.0+1",
"components": [
{
"component": "liberica",
"version": "23+38",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/21.3.2/bellsoft-liberica-vm-openjdk11.0.15.1+2-21.3.2+2-linux-amd64.tar.gz",
"version": "21.3.2+2",
"components": [
{
"component": "liberica",
"version": "11.0.15.1+2",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/22.3.0/bellsoft-liberica-vm-openjdk17.0.5+8-22.3.0+2-linux-amd64.tar.gz",
"version": "22.3.0+2",
"components": [
{
"component": "liberica",
"version": "17.0.5+8",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.0.9+1-17.0.16+13/bellsoft-liberica-vm-openjdk17.0.16+13-23.0.9+1-linux-amd64.tar.gz",
"version": "23.0.9+1",
"components": [
{
"component": "liberica",
"version": "17.0.16+13",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/23.0.0/bellsoft-liberica-vm-openjdk17.0.7+7-23.0.0+1-src.tar.gz",
"version": "23.0.0+1",
"components": [
{
"component": "liberica",
"version": "17.0.7+7",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/25.0.2+1-25.0.2+13/bellsoft-liberica-vm-openjdk25.0.2+13-25.0.2+1-linux-amd64.tar.gz",
"version": "25.0.2+1",
"components": [
{
"component": "liberica",
"version": "25.0.2+13",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/24.1.1+1-23.0.1+13/bellsoft-liberica-vm-openjdk23.0.1+13-24.1.1+1-linux-amd64.tar.gz",
"version": "24.1.1+1",
"components": [
{
"component": "liberica",
"version": "23.0.1+13",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.0.12+1-17.0.19+12/bellsoft-liberica-vm-openjdk17.0.19+12-23.0.12+1-linux-amd64.tar.gz",
"version": "23.0.12+1",
"components": [
{
"component": "liberica",
"version": "17.0.19+12",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.0.3+1-17.0.10+13/bellsoft-liberica-vm-openjdk17.0.10+13-23.0.3+1-linux-amd64.tar.gz",
"version": "23.0.3+1",
"components": [
{
"component": "liberica",
"version": "17.0.10+13",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/21.3.2/bellsoft-liberica-vm-openjdk11-21.3.2-src.tar.gz",
"version": "21.3.2+1",
"components": [
{
"component": "liberica",
"version": "11.0.15+10",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/22.3.3/bellsoft-liberica-vm-openjdk11.0.20+8-22.3.3+1-src.tar.gz",
"version": "22.3.3+1",
"components": [
{
"component": "liberica",
"version": "11.0.20+8",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.0.8+1-17.0.15+10/bellsoft-liberica-vm-openjdk17.0.15+10-23.0.8+1-linux-amd64.tar.gz",
"version": "23.0.8+1",
"components": [
{
"component": "liberica",
"version": "17.0.15+10",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/21.3.3/bellsoft-liberica-vm-openjdk17.0.4-21.3.3-src.tar.gz",
"version": "21.3.3+1",
"components": [
{
"component": "liberica",
"version": "17.0.4+8",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.1.7+1-21.0.7+9/bellsoft-liberica-vm-openjdk21.0.7+9-23.1.7+1-linux-amd64.tar.gz",
"version": "23.1.7+1",
"components": [
{
"component": "liberica",
"version": "21.0.7+9",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.1.8+1-21.0.8+13/bellsoft-liberica-vm-openjdk21.0.8+13-23.1.8+1-linux-amd64.tar.gz",
"version": "23.1.8+1",
"components": [
{
"component": "liberica",
"version": "21.0.8+13",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/21.0.0.2/bellsoft-liberica-vm-openjdk11-21.0.0.2-src.tar.gz",
"version": "21.0.0.2",
"components": [
{
"component": "liberica",
"version": "11.0.10+9",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/23.0.1/bellsoft-liberica-vm-openjdk17.0.8+7-23.0.1+1-linux-amd64.tar.gz",
"version": "23.0.1+1",
"components": [
{
"component": "liberica",
"version": "17.0.8+7",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/24.0.0+1-22+37/bellsoft-liberica-vm-openjdk22+37-24.0.0+1-linux-amd64.tar.gz",
"version": "24.0.0+1",
"components": [
{
"component": "liberica",
"version": "22+37",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/22.1.0/bellsoft-liberica-vm-openjdk17-22.1.0-src.tar.gz",
"version": "22.1.0+1",
"components": [
{
"component": "liberica",
"version": "17.0.3+7",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/24.0.2+1-22.0.2+11/bellsoft-liberica-vm-openjdk22.0.2+11-24.0.2+1-linux-amd64.tar.gz",
"version": "24.0.2+1",
"components": [
{
"component": "liberica",
"version": "22.0.2+11",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/23.0.1/bellsoft-liberica-vm-openjdk20.0.2+10-23.0.1+1-src.tar.gz",
"version": "23.0.1+1",
"components": [
{
"component": "liberica",
"version": "20.0.2+10",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/23.1.0/bellsoft-liberica-vm-openjdk21+37-23.1.0+1-linux-amd64.tar.gz",
"version": "23.1.0+1",
"components": [
{
"component": "liberica",
"version": "21+37",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.1.4+3-21.0.4+9/bellsoft-liberica-vm-openjdk21.0.4+9-23.1.4+3-linux-amd64.tar.gz",
"version": "23.1.4+3",
"components": [
{
"component": "liberica",
"version": "21.0.4+9",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/22.3.3/bellsoft-liberica-vm-openjdk11.0.20.1+1-22.3.3+2-linux-amd64.tar.gz",
"version": "22.3.3+2",
"components": [
{
"component": "liberica",
"version": "11.0.20.1+1",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.0.4+1-17.0.11+10/bellsoft-liberica-vm-openjdk17.0.11+10-23.0.4+1-linux-amd64.tar.gz",
"version": "23.0.4+1",
"components": [
{
"component": "liberica",
"version": "17.0.11+10",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/24.1.2+1-23.0.2+9/bellsoft-liberica-vm-openjdk23.0.2+9-24.1.2+1-linux-amd64.tar.gz",
"version": "24.1.2+1",
"components": [
{
"component": "liberica",
"version": "23.0.2+9",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.0.10+1-17.0.17+12/bellsoft-liberica-vm-openjdk17.0.17+12-23.0.10+1-linux-amd64.tar.gz",
"version": "23.0.10+1",
"components": [
{
"component": "liberica",
"version": "17.0.17+12",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/22.3.1/bellsoft-liberica-vm-openjdk11.0.18+10-22.3.1+1-src.tar.gz",
"version": "22.3.1+1",
"components": [
{
"component": "liberica",
"version": "11.0.18+10",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/22.0.0.2/bellsoft-liberica-vm-openjdk17-22.0.0.2-src.tar.gz",
"version": "22.0.0.2",
"components": [
{
"component": "liberica",
"version": "17.0.2+9",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/22.0.0.2/bellsoft-liberica-vm-openjdk11-22.0.0.2-linux-amd64.tar.gz",
"version": "22.0.0.2",
"components": [
{
"component": "liberica",
"version": "11.0.14.1+1",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/25.0.1+2-25.0.1+14/bellsoft-liberica-vm-openjdk25.0.1+14-25.0.1+2-linux-amd64.tar.gz",
"version": "25.0.1+2",
"components": [
{
"component": "liberica",
"version": "25.0.1+14",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/21.3.3/bellsoft-liberica-vm-openjdk11.0.16-21.3.3-src.tar.gz",
"version": "21.3.3+1",
"components": [
{
"component": "liberica",
"version": "11.0.16+8",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.1.5+1-21.0.5+11/bellsoft-liberica-vm-openjdk21.0.5+11-23.1.5+1-linux-amd64.tar.gz",
"version": "23.1.5+1",
"components": [
{
"component": "liberica",
"version": "21.0.5+11",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/22.3.4/bellsoft-liberica-vm-openjdk17.0.9+11-22.3.4+1-linux-amd64.tar.gz",
"version": "22.3.4+1",
"components": [
{
"component": "liberica",
"version": "17.0.9+11",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/25.0.0+1-25+37/bellsoft-liberica-vm-openjdk25+37-25.0.0+1-linux-amd64.tar.gz",
"version": "25.0.0+1",
"components": [
{
"component": "liberica",
"version": "25+37",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/22.3.3/bellsoft-liberica-vm-openjdk17.0.8.1+1-22.3.3+2-linux-amd64.tar.gz",
"version": "22.3.3+2",
"components": [
{
"component": "liberica",
"version": "17.0.8.1+1",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.1.3+1-21.0.3+10/bellsoft-liberica-vm-openjdk21.0.3+10-23.1.3+1-linux-amd64.tar.gz",
"version": "23.1.3+1",
"components": [
{
"component": "liberica",
"version": "21.0.3+10",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/24.2.0+1-24+37/bellsoft-liberica-vm-openjdk24+37-24.2.0+1-linux-amd64.tar.gz",
"version": "24.2.0+1",
"components": [
{
"component": "liberica",
"version": "24+37",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/23.1.1/bellsoft-liberica-vm-openjdk21.0.1+12-23.1.1+1-linux-amd64.tar.gz",
"version": "23.1.1+1",
"components": [
{
"component": "liberica",
"version": "21.0.1+12",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/21.3.3/bellsoft-liberica-vm-openjdk17.0.4.1-21.3.3-src.tar.gz",
"version": "21.3.3+2",
"components": [
{
"component": "liberica",
"version": "17.0.4.1+1",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.1.11+2-21.0.11+12/bellsoft-liberica-vm-openjdk21.0.11+12-23.1.11+2-linux-amd64.tar.gz",
"version": "23.1.11+2",
"components": [
{
"component": "liberica",
"version": "21.0.11+12",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/22.3.2/bellsoft-liberica-vm-openjdk11.0.19+7-22.3.2+1-src.tar.gz",
"version": "22.3.2+1",
"components": [
{
"component": "liberica",
"version": "11.0.19+7",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/22.3.4/bellsoft-liberica-vm-openjdk11.0.21+10-22.3.4+1-src.tar.gz",
"version": "22.3.4+1",
"components": [
{
"component": "liberica",
"version": "11.0.21+10",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/25.0.3+2-25.0.3+12/bellsoft-liberica-vm-openjdk25.0.3+12-25.0.3+2-linux-amd64.tar.gz",
"version": "25.0.3+2",
"components": [
{
"component": "liberica",
"version": "25.0.3+12",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/21.3.2/bellsoft-liberica-vm-openjdk17.0.3.1+2-21.3.2+2-linux-amd64.tar.gz",
"version": "21.3.2+2",
"components": [
{
"component": "liberica",
"version": "17.0.3.1+2",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/24.2.1+1-24.0.1+11/bellsoft-liberica-vm-openjdk24.0.1+11-24.2.1+1-linux-amd64.tar.gz",
"version": "24.2.1+1",
"components": [
{
"component": "liberica",
"version": "24.0.1+11",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/22.2.0/bellsoft-liberica-vm-openjdk11.0.16.1+1-22.2.0+3-linux-amd64.tar.gz",
"version": "22.2.0+3",
"components": [
{
"component": "liberica",
"version": "11.0.16.1+1",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.0.6+1-17.0.13+12/bellsoft-liberica-vm-openjdk17.0.13+12-23.0.6+1-linux-amd64.tar.gz",
"version": "23.0.6+1",
"components": [
{
"component": "liberica",
"version": "17.0.13+12",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/23.0.0/bellsoft-liberica-vm-openjdk20.0.1+10-23.0.0+1-linux-amd64.tar.gz",
"version": "23.0.0+1",
"components": [
{
"component": "liberica",
"version": "20.0.1+10",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.1.10+1-21.0.10+11/bellsoft-liberica-vm-openjdk21.0.10+11-23.1.10+1-linux-amd64.tar.gz",
"version": "23.1.10+1",
"components": [
{
"component": "liberica",
"version": "21.0.10+11",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/22.3.1/bellsoft-liberica-vm-openjdk17.0.6+10-22.3.1+1-src.tar.gz",
"version": "22.3.1+1",
"components": [
{
"component": "liberica",
"version": "17.0.6+10",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/21.3.0/bellsoft-liberica-vm-openjdk17-21.3.0-src.tar.gz",
"version": "21.3.0",
"components": [
{
"component": "liberica",
"version": "17.0.1+12",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/21.3.0/bellsoft-liberica-vm-openjdk11-21.3.0-src.tar.gz",
"version": "21.3.0",
"components": [
{
"component": "liberica",
"version": "11.0.13+8",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/24.0.1+1-22.0.1+10/bellsoft-liberica-vm-openjdk22.0.1+10-24.0.1+1-linux-amd64.tar.gz",
"version": "24.0.1+1",
"components": [
{
"component": "liberica",
"version": "22.0.1+10",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.0.7+1-17.0.14+10/bellsoft-liberica-vm-openjdk17.0.14+10-23.0.7+1-linux-amd64.tar.gz",
"version": "23.0.7+1",
"components": [
{
"component": "liberica",
"version": "17.0.14+10",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/21.1.0/bellsoft-liberica-vm-openjdk11-21.1.0-src.tar.gz",
"version": "21.1.0",
"components": [
{
"component": "liberica",
"version": "11.0.11+9",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.1.6+1-21.0.6+10/bellsoft-liberica-vm-openjdk21.0.6+10-23.1.6+1-linux-amd64.tar.gz",
"version": "23.1.6+1",
"components": [
{
"component": "liberica",
"version": "21.0.6+10",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/25.0.1+1-25.0.1+12/bellsoft-liberica-vm-openjdk25.0.1+12-25.0.1+1-linux-amd64.tar.gz",
"version": "25.0.1+1",
"components": [
{
"component": "liberica",
"version": "25.0.1+12",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.1.9+1-21.0.9+12/bellsoft-liberica-vm-openjdk21.0.9+12-23.1.9+1-linux-amd64.tar.gz",
"version": "23.1.9+1",
"components": [
{
"component": "liberica",
"version": "21.0.9+12",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/22.3.5+1-11.0.22+12/bellsoft-liberica-vm-openjdk11.0.22+12-22.3.5+1-linux-amd64.tar.gz",
"version": "22.3.5+1",
"components": [
{
"component": "liberica",
"version": "11.0.22+12",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.0.5+1-17.0.12+10/bellsoft-liberica-vm-openjdk17.0.12+10-23.0.5+1-linux-amd64.tar.gz",
"version": "23.0.5+1",
"components": [
{
"component": "liberica",
"version": "17.0.12+10",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/21.3.3.1/bellsoft-liberica-vm-openjdk11.0.17+7-21.3.3.1+1-linux-amd64.tar.gz",
"version": "21.3.3.1+1",
"components": [
{
"component": "liberica",
"version": "11.0.17+7",
"embedded": true
}
]
},
{
"downloadUrl": "https://download.bell-sw.com/vm/21.2.0/bellsoft-liberica-vm-openjdk11-21.2.0-linux-amd64.tar.gz",
"version": "21.2.0",
"components": [
{
"component": "liberica",
"version": "11.0.12+7",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.0.11+1-17.0.18+11/bellsoft-liberica-vm-openjdk17.0.18+11-23.0.11+1-linux-amd64.tar.gz",
"version": "23.0.11+1",
"components": [
{
"component": "liberica",
"version": "17.0.18+11",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/23.1.2+1-21.0.2+14/bellsoft-liberica-vm-openjdk21.0.2+14-23.1.2+1-linux-amd64.tar.gz",
"version": "23.1.2+1",
"components": [
{
"component": "liberica",
"version": "21.0.2+14",
"embedded": true
}
]
},
{
"downloadUrl": "https://github.com/bell-sw/LibericaNIK/releases/download/24.2.2+1-24.0.2+13/bellsoft-liberica-vm-openjdk24.0.2+13-24.2.2+1-linux-amd64.tar.gz",
"version": "24.2.2+1",
"components": [
{
"component": "liberica",
"version": "24.0.2+13",
"embedded": true
}
]
}
]
+46 -5
View File
@@ -420,6 +420,29 @@ describe('setupJava', () => {
expect(spyCoreInfo).not.toHaveBeenCalledWith('Trying to download...'); expect(spyCoreInfo).not.toHaveBeenCalledWith('Trying to download...');
}); });
it('should resolve the latest version from remote when java-version is "latest", even if a version is cached', async () => {
mockJavaBase = new EmptyJavaBase({
version: 'latest',
architecture: 'x86',
packageType: 'jdk',
checkLatest: false
});
await expect(mockJavaBase.setupJava()).resolves.toEqual({
version: actualJavaVersion,
path: javaPathInstalled
});
// `latest` must bypass the tool-cache short-circuit and always resolve remotely
expect(spyCoreInfo).toHaveBeenCalledWith(
'Trying to resolve the latest version from remote'
);
expect(spyCoreInfo).toHaveBeenCalledWith('Trying to download...');
expect(spyCoreInfo).not.toHaveBeenCalledWith(
`Resolved Java ${installedJavaVersion} from tool-cache`
);
});
it.each([ it.each([
[ [
{ {
@@ -744,11 +767,18 @@ describe('normalizeVersion', () => {
const DummyJavaBase = JavaBase as any; const DummyJavaBase = JavaBase as any;
it.each([ it.each([
['11', {version: '11', stable: true}], ['11', {version: '11', stable: true, latest: false}],
['11.0', {version: '11.0', stable: true}], ['11.0', {version: '11.0', stable: true, latest: false}],
['11.0.10', {version: '11.0.10', stable: true}], ['11.0.10', {version: '11.0.10', stable: true, latest: false}],
['11-ea', {version: '11', stable: false}], ['11-ea', {version: '11', stable: false, latest: false}],
['11.0.2-ea', {version: '11.0.2', stable: false}] ['11.0.2-ea', {version: '11.0.2', stable: false, latest: false}],
['18.0.1.1', {version: '18.0.1+1', stable: true, latest: false}],
['11.0.9.1', {version: '11.0.9+1', stable: true, latest: false}],
['12.0.2.1.0', {version: '12.0.2+1.0', stable: true, latest: false}],
['18.0.1.1-ea', {version: '18.0.1+1', stable: false, latest: false}],
['latest', {version: 'x', stable: true, latest: true}],
['LATEST', {version: 'x', stable: true, latest: true}],
[' Latest ', {version: 'x', stable: true, latest: true}]
])('normalizeVersion from %s to %s', (input, expected) => { ])('normalizeVersion from %s to %s', (input, expected) => {
expect(DummyJavaBase.prototype.normalizeVersion.call(null, input)).toEqual( expect(DummyJavaBase.prototype.normalizeVersion.call(null, input)).toEqual(
expected expected
@@ -763,6 +793,17 @@ describe('normalizeVersion', () => {
`The string '${version}' is not valid SemVer notation for a Java version. Please check README file for code snippets and more detailed information` `The string '${version}' is not valid SemVer notation for a Java version. Please check README file for code snippets and more detailed information`
); );
}); });
it.each(['latest-ea', 'latest.1', 'LATEST-EA', ' latest-ea '])(
'normalizeVersion should throw a targeted error for latest combined with a qualifier (%s)',
version => {
expect(
DummyJavaBase.prototype.normalizeVersion.bind(null, version)
).toThrow(
`The 'latest' alias resolves stable (GA) releases only and cannot be combined with '-ea' or other qualifiers (received '${version}'). Use 'latest' on its own, or specify a concrete version.`
);
}
);
}); });
describe('createVersionNotFoundError', () => { describe('createVersionNotFoundError', () => {
@@ -204,6 +204,24 @@ describe('getAvailableVersions', () => {
expect(availableVersion.url).toBe(expectedLink); expect(availableVersion.url).toBe(expectedLink);
}); });
it('with latest resolves to the newest available major version', async () => {
const distribution = new CorrettoDistribution({
version: 'latest',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
mockPlatform(distribution, 'linux');
const availableVersion =
await distribution['findPackageForDownload']('x');
expect(availableVersion).not.toBeNull();
// 18 is the newest major present in the mocked Corretto index
expect(availableVersion.url).toBe(
'https://corretto.aws/downloads/resources/18.0.0.37.1/amazon-corretto-18.0.0.37.1-linux-x64.tar.gz'
);
});
it('with unstable version expect to throw not supported error', async () => { it('with unstable version expect to throw not supported error', async () => {
const version = '18.0.1-ea'; const version = '18.0.1-ea';
const distribution = new CorrettoDistribution({ const distribution = new CorrettoDistribution({
@@ -417,6 +417,61 @@ describe('GraalVMDistribution', () => {
); );
}); });
describe('latest alias', () => {
it('resolves the newest major version from the Adoptium API', async () => {
const latestDistribution = new GraalVMDistribution({
...defaultOptions,
version: 'latest'
});
(latestDistribution as any).http = mockHttpClient;
jest
.spyOn(latestDistribution, 'getPlatform')
.mockReturnValue('linux');
mockHttpClient.getJson.mockResolvedValue({
statusCode: 200,
result: {most_recent_feature_release: 25},
headers: {}
});
mockHttpClient.head.mockResolvedValue({
message: {statusCode: 200}
});
const result = await (
latestDistribution as any
).findPackageForDownload('x');
expect(result).toEqual({
url: 'https://download.oracle.com/graalvm/25/latest/graalvm-jdk-25_linux-x64_bin.tar.gz',
version: '25'
});
});
it('throws an actionable error when the latest major is not yet available', async () => {
const latestDistribution = new GraalVMDistribution({
...defaultOptions,
version: 'latest'
});
(latestDistribution as any).http = mockHttpClient;
jest
.spyOn(latestDistribution, 'getPlatform')
.mockReturnValue('linux');
mockHttpClient.getJson.mockResolvedValue({
statusCode: 200,
result: {most_recent_feature_release: 25},
headers: {}
});
mockHttpClient.head.mockResolvedValue({
message: {statusCode: 404}
});
await expect(
(latestDistribution as any).findPackageForDownload('x')
).rejects.toThrow(
/is not yet available for the GraalVM distribution/
);
});
});
it('should throw error for JDK versions less than 17', async () => { it('should throw error for JDK versions less than 17', async () => {
await expect( await expect(
(distribution as any).findPackageForDownload('11') (distribution as any).findPackageForDownload('11')
@@ -1115,6 +1170,60 @@ describe('GraalVMDistribution', () => {
}); });
}); });
it('resolves latest to the newest GA across all Community majors without calling Adoptium', async () => {
const latestCommunity = new GraalVMCommunityDistribution({
...defaultOptions,
version: 'latest'
});
(latestCommunity as any).http = mockHttpClient;
jest.spyOn(latestCommunity, 'getPlatform').mockReturnValue('linux');
mockHttpClient.getJson.mockResolvedValue({
result: [
{
draft: false,
prerelease: false,
assets: [
{
name: 'graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz',
browser_download_url:
'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-21.0.2/graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz'
}
]
},
{
draft: false,
prerelease: false,
assets: [
{
name: 'graalvm-community-jdk-24.0.1_linux-x64_bin.tar.gz',
browser_download_url:
'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-24.0.1/graalvm-community-jdk-24.0.1_linux-x64_bin.tar.gz'
}
]
}
],
statusCode: 200,
headers: {}
});
const result = await (latestCommunity as any).findPackageForDownload(
'x'
);
expect(result).toEqual({
url: 'https://github.com/graalvm/graalvm-ce-builds/releases/download/jdk-24.0.1/graalvm-community-jdk-24.0.1_linux-x64_bin.tar.gz',
version: '24.0.1'
});
// The Community release list is authoritative, so the Adoptium
// most_recent_feature_release endpoint must not be consulted.
expect(mockHttpClient.getJson).toHaveBeenCalledTimes(1);
expect(mockHttpClient.getJson).toHaveBeenCalledWith(
expect.stringContaining('graalvm-ce-builds/releases'),
expect.anything()
);
});
it('should reject GraalVM Community early access requests', async () => { it('should reject GraalVM Community early access requests', async () => {
(communityDistribution as any).stable = false; (communityDistribution as any).stable = false;
+35 -2
View File
@@ -28,7 +28,9 @@ describe('Check getAvailableReleases', () => {
['11', 'linux', 'x86_64', 'linux-x86_64'], ['11', 'linux', 'x86_64', 'linux-x86_64'],
['11.0.25', 'macos', 'aarch64', 'macosx-aarch64'], ['11.0.25', 'macos', 'aarch64', 'macosx-aarch64'],
['17.0.13', 'windows', 'x86_64', 'windows-x86_64'], ['17.0.13', 'windows', 'x86_64', 'windows-x86_64'],
['21.0.5', 'linux', 'x86_64', 'linux-x86_64'] ['21.0.5', 'linux', 'x86_64', 'linux-x86_64'],
['25', 'linux', 'aarch64', 'linux-aarch64'],
['25.0.3', 'macos', 'x86_64', 'macosx-x86_64']
])( ])(
'should get releases with the specified version "%s", OS "%s" and arch "%s"', 'should get releases with the specified version "%s", OS "%s" and arch "%s"',
async ( async (
@@ -41,7 +43,7 @@ describe('Check getAvailableReleases', () => {
const releases = await distribution['getAvailableReleases'](); const releases = await distribution['getAvailableReleases']();
expect(releases).not.toBeNull(); expect(releases).not.toBeNull();
expect(releases.length).toBe(4); expect(releases.length).toBe(5);
releases.forEach(release => releases.forEach(release =>
expect(release.downloadUrl).toContain(expectedPattern) expect(release.downloadUrl).toContain(expectedPattern)
); );
@@ -173,6 +175,37 @@ describe('Check findPackageForDownload', () => {
'windows', 'windows',
'x86_64', 'x86_64',
'https://github.com/Tencent/TencentKona-21/releases/download/TencentKona-21.0.5/TencentKona-21.0.5.b1_jdk_windows-x86_64_signed.zip' 'https://github.com/Tencent/TencentKona-21/releases/download/TencentKona-21.0.5/TencentKona-21.0.5.b1_jdk_windows-x86_64_signed.zip'
],
[
'25',
'linux',
'aarch64',
'https://github.com/Tencent/TencentKona-25/releases/download/TencentKona-25.0.3/TencentKona-25.0.3.b1-jdk_linux-aarch64.tar.gz'
],
[
'25.0.3',
'linux',
'x86_64',
'https://github.com/Tencent/TencentKona-25/releases/download/TencentKona-25.0.3/TencentKona-25.0.3.b1-jdk_linux-x86_64.tar.gz'
],
[
'25.0.3',
'macos',
'aarch64',
'https://github.com/Tencent/TencentKona-25/releases/download/TencentKona-25.0.3/TencentKona-25.0.3.b1_jdk_macosx-aarch64_notarized.tar.gz'
],
[
'25.0.3',
'macos',
'x86_64',
'https://github.com/Tencent/TencentKona-25/releases/download/TencentKona-25.0.3/TencentKona-25.0.3.b1_jdk_macosx-x86_64_notarized.tar.gz'
],
[
'25.0.3',
'windows',
'x86_64',
'https://github.com/Tencent/TencentKona-25/releases/download/TencentKona-25.0.3/TencentKona-25.0.3.b1_jdk_windows-x86_64_signed.zip'
] ]
])( ])(
'should return the download URL with the specified version "%s", OS "%s" and arch "%s"', 'should return the download URL with the specified version "%s", OS "%s" and arch "%s"',
@@ -0,0 +1,219 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import type {
ArchitectureOptions,
NikVersion
} from '../../src/distributions/liberica-nik/models.js';
import {HttpClient} from '@actions/http-client';
import manifestData from '../data/liberica-nik.json' with {type: 'json'};
// Mock @actions/core before importing source modules that depend on it
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
// Dynamic imports after mocking
const {LibericaNikDistributions} =
await import('../../src/distributions/liberica-nik/installer.js');
const ADDITIONAL_PARAMS =
'&installation-type=archive&fields=downloadUrl%2Cversion%2Ccomponents%2Ccomponent%2Cembedded';
describe('getAvailableVersions', () => {
let spyHttpClient: any;
beforeEach(() => {
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
spyHttpClient.mockReturnValue({
statusCode: 200,
headers: {},
result: manifestData as NikVersion[]
});
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
jest.restoreAllMocks();
});
it.each([
[
{version: '21', architecture: 'x64', packageType: 'jdk'},
'bundle-type=standard&bitness=64&arch=x86&build-type=all'
],
[
{version: '21-ea', architecture: 'x64', packageType: 'jdk'},
'bundle-type=standard&bitness=64&arch=x86&build-type=ea'
],
[
{version: '21', architecture: 'aarch64', packageType: 'jdk'},
'bundle-type=standard&bitness=64&arch=arm&build-type=all'
],
[
{version: '21', architecture: 'x64', packageType: 'jdk+fx'},
'bundle-type=full&bitness=64&arch=x86&build-type=all'
]
])('build correct url for %s -> %s', async (input, urlParams) => {
const distribution = new LibericaNikDistributions({
...input,
checkLatest: false
});
distribution['getPlatformOption'] = () => 'linux';
const buildUrl = `https://api.bell-sw.com/v1/nik/releases?os=linux&${urlParams}${ADDITIONAL_PARAMS}`;
await distribution['getAvailableVersions']();
expect(spyHttpClient.mock.calls).toHaveLength(1);
expect(spyHttpClient.mock.calls[0][0]).toBe(buildUrl);
});
it('load available versions', async () => {
const distribution = new LibericaNikDistributions({
version: '21',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
const availableVersions = await distribution['getAvailableVersions']();
expect(availableVersions).toEqual(manifestData);
});
});
describe('getArchitectureOptions', () => {
it.each([
['x64', {bitness: '64', arch: 'x86'}],
['aarch64', {bitness: '64', arch: 'arm'}]
] as [string, ArchitectureOptions][])(
'parse architecture %s -> %s',
(input, expected) => {
const distributions = new LibericaNikDistributions({
architecture: input,
checkLatest: false,
packageType: 'jdk',
version: '21'
});
expect(distributions['getArchitectureOptions']()).toEqual(expected);
}
);
it.each(['x86', 'armv7', 's390x'])('not support architecture %s', input => {
const distributions = new LibericaNikDistributions({
architecture: input,
checkLatest: false,
packageType: 'jdk',
version: '21'
});
expect(() => distributions['getArchitectureOptions']()).toThrow(
/Architecture '\w+' is not supported\. Supported architectures: .*/
);
});
});
describe('findPackageForDownload', () => {
let distribution: InstanceType<typeof LibericaNikDistributions>;
beforeEach(() => {
distribution = new LibericaNikDistributions({
version: '',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
distribution['getAvailableVersions'] = async () => manifestData;
});
// The user's java-version resolves against the embedded JDK version, not
// NIK's own GraalVM version.
it.each([
['21', '21.0.11+12'],
['17', '17.0.19+12'],
['25', '25.0.3+12'],
['11', '11.0.22+12'],
['21.0.2', '21.0.2+14'],
['23', '23.0.2+9'],
['20.x', '20.0.2+10'],
['25.0.1', '25.0.1+16']
])('version is %s -> %s', async (input, expected) => {
const result = await distribution['findPackageForDownload'](input);
expect(result.version).toBe(expected);
});
it('should throw an error', async () => {
await expect(distribution['findPackageForDownload']('7')).rejects.toThrow(
/No matching version found for SemVer/
);
});
});
describe('getPlatformOption', () => {
const distributions = new LibericaNikDistributions({
architecture: 'x64',
version: '21',
packageType: 'jdk',
checkLatest: false
});
it.each([
['linux', 'linux'],
['darwin', 'macos'],
['win32', 'windows'],
['cygwin', 'windows']
])('os version %s -> %s', (input, expected) => {
const actual = distributions['getPlatformOption'](input as NodeJS.Platform);
expect(actual).toEqual(expected);
});
it.each(['sunos', 'aix', 'android', 'freebsd'])(
'not support os version %s',
input => {
expect(() =>
distributions['getPlatformOption'](input as NodeJS.Platform)
).toThrow(/Platform '\w+' is not supported\. Supported platforms: .+/);
}
);
});
describe('convertVersionToSemver', () => {
const distributions = new LibericaNikDistributions({
architecture: 'x64',
version: '21',
packageType: 'jdk',
checkLatest: false
});
it.each([
['25.0.1+16', '25.0.1+16'],
['21+37', '21.0.0+37'],
['23+38', '23.0.0+38'],
['11.0.15.1+2', '11.0.15+1.2'],
['17.0.5', '17.0.5']
])('%s -> %s', (input, expected) => {
const actual = distributions['convertVersionToSemver'](input);
expect(actual).toEqual(expected);
});
});
@@ -170,6 +170,20 @@ describe('setupJava', () => {
jest.restoreAllMocks(); jest.restoreAllMocks();
}); });
it('throws for the latest alias since jdkfile has no version list', async () => {
const inputs = {
version: 'latest',
architecture: 'x86',
packageType: 'jdk',
checkLatest: false
};
mockJavaBase = new LocalDistribution(inputs, expectedJdkFile);
await expect(mockJavaBase.setupJava()).rejects.toThrow(
"The 'latest' version alias is not supported for the 'jdkfile' distribution. Please specify a concrete version."
);
});
it('java is resolved from toolcache, jdkfile is untouched', async () => { it('java is resolved from toolcache, jdkfile is untouched', async () => {
const inputs = { const inputs = {
version: actualJavaVersion, version: actualJavaVersion,
@@ -174,3 +174,59 @@ describe('findPackageForDownload', () => {
); );
}); });
}); });
describe('findPackageForDownload with latest', () => {
let spyHttpClientHead: any;
let spyHttpClientGetJson: any;
beforeEach(() => {
(core.debug as jest.Mock).mockImplementation(() => {});
(core.error as jest.Mock).mockImplementation(() => {});
spyHttpClientGetJson = jest.spyOn(HttpClient.prototype, 'getJson');
spyHttpClientGetJson.mockResolvedValue({
statusCode: 200,
result: {most_recent_feature_release: 25},
headers: {}
});
});
afterEach(() => {
jest.restoreAllMocks();
});
it('resolves the newest major version from the Adoptium API', async () => {
spyHttpClientHead = jest.spyOn(HttpClient.prototype, 'head');
spyHttpClientHead.mockResolvedValue({message: {statusCode: 200}});
const distribution = new OracleDistribution({
version: 'latest',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
const result = await distribution['findPackageForDownload']('x');
const osType = distribution.getPlatform();
const archiveType = getDownloadArchiveExtension();
expect(result.version).toBe('25');
expect(result.url).toBe(
`https://download.oracle.com/java/25/latest/jdk-25_${osType}-x64_bin.${archiveType}`
);
});
it('throws an actionable error when the latest major is not yet available', async () => {
spyHttpClientHead = jest.spyOn(HttpClient.prototype, 'head');
spyHttpClientHead.mockResolvedValue({message: {statusCode: 404}});
const distribution = new OracleDistribution({
version: 'latest',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
await expect(distribution['findPackageForDownload']('x')).rejects.toThrow(
/is not yet available for the Oracle JDK distribution/
);
});
});
@@ -312,6 +312,24 @@ describe('findPackageForDownload', () => {
expect(resolvedVersion.signatureUrl).toBeDefined(); expect(resolvedVersion.signatureUrl).toBeDefined();
}); });
it('version "latest" is normalized to the newest available version', async () => {
const distribution = new TemurinDistribution(
{
version: 'latest',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
},
TemurinImplementation.Hotspot
);
distribution['getAvailableVersions'] = async () => manifestData as any;
// normalizeVersion turns `latest` into the wildcard carried on `this.version`
const resolvedVersion = await distribution['findPackageForDownload'](
distribution['version']
);
expect(resolvedVersion.version).toBe('16.0.2+7');
});
it('version is found but binaries list is empty', async () => { it('version is found but binaries list is empty', async () => {
const distribution = new TemurinDistribution( const distribution = new TemurinDistribution(
{ {
+44
View File
@@ -0,0 +1,44 @@
import {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals';
const mockGetInput = jest.fn<(...args: any[]) => any>();
const mockInfo = jest.fn<(...args: any[]) => any>();
const mockDebug = jest.fn<(...args: any[]) => any>();
jest.unstable_mockModule('@actions/core', () => ({
getInput: mockGetInput,
info: mockInfo,
debug: mockDebug,
warning: jest.fn(),
setSecret: jest.fn()
}));
const {configureProblemMatcher} = await import('../src/problem-matcher.js');
const {INPUT_PROBLEM_MATCHER} = await import('../src/constants.js');
describe('configureProblemMatcher', () => {
let inputs: Record<string, string>;
beforeEach(() => {
inputs = {};
mockGetInput.mockImplementation((name: string) => inputs[name] ?? '');
});
afterEach(() => {
jest.resetAllMocks();
});
it('registers the Java problem matcher by default', () => {
configureProblemMatcher('/matchers/java.json');
expect(mockInfo).toHaveBeenCalledWith('##[add-matcher]/matchers/java.json');
});
it('does not register the Java problem matcher when disabled', () => {
inputs[INPUT_PROBLEM_MATCHER] = 'false';
configureProblemMatcher('/matchers/java.json');
expect(mockInfo).not.toHaveBeenCalled();
expect(mockDebug).toHaveBeenCalledWith('Java problem matcher is disabled');
});
});
+73 -26
View File
@@ -86,8 +86,7 @@ describe('toolchains tests', () => {
await toolchains.createToolchainsSettings({ await toolchains.createToolchainsSettings({
jdkInfo, jdkInfo,
settingsDirectory: altHome, settingsDirectory: altHome
overwriteSettings: true
}); });
expect(fs.existsSync(m2Dir)).toBe(false); expect(fs.existsSync(m2Dir)).toBe(false);
@@ -135,8 +134,7 @@ describe('toolchains tests', () => {
await toolchains.createToolchainsSettings({ await toolchains.createToolchainsSettings({
jdkInfo, jdkInfo,
settingsDirectory: m2Dir, settingsDirectory: m2Dir
overwriteSettings: true
}); });
expect(fs.existsSync(m2Dir)).toBe(true); expect(fs.existsSync(m2Dir)).toBe(true);
@@ -217,8 +215,7 @@ describe('toolchains tests', () => {
await toolchains.createToolchainsSettings({ await toolchains.createToolchainsSettings({
jdkInfo, jdkInfo,
settingsDirectory: m2Dir, settingsDirectory: m2Dir
overwriteSettings: true
}); });
expect(fs.existsSync(m2Dir)).toBe(true); expect(fs.existsSync(m2Dir)).toBe(true);
@@ -303,8 +300,7 @@ describe('toolchains tests', () => {
await toolchains.createToolchainsSettings({ await toolchains.createToolchainsSettings({
jdkInfo, jdkInfo,
settingsDirectory: m2Dir, settingsDirectory: m2Dir
overwriteSettings: true
}); });
expect(fs.existsSync(m2Dir)).toBe(true); expect(fs.existsSync(m2Dir)).toBe(true);
@@ -381,8 +377,7 @@ describe('toolchains tests', () => {
await toolchains.createToolchainsSettings({ await toolchains.createToolchainsSettings({
jdkInfo, jdkInfo,
settingsDirectory: m2Dir, settingsDirectory: m2Dir
overwriteSettings: true
}); });
expect(fs.existsSync(m2Dir)).toBe(true); expect(fs.existsSync(m2Dir)).toBe(true);
@@ -452,8 +447,7 @@ describe('toolchains tests', () => {
await toolchains.createToolchainsSettings({ await toolchains.createToolchainsSettings({
jdkInfo, jdkInfo,
settingsDirectory: m2Dir, settingsDirectory: m2Dir
overwriteSettings: true
}); });
expect(fs.existsSync(m2Dir)).toBe(true); expect(fs.existsSync(m2Dir)).toBe(true);
@@ -545,8 +539,7 @@ describe('toolchains tests', () => {
await toolchains.createToolchainsSettings({ await toolchains.createToolchainsSettings({
jdkInfo, jdkInfo,
settingsDirectory: m2Dir, settingsDirectory: m2Dir
overwriteSettings: true
}); });
expect(fs.existsSync(m2Dir)).toBe(true); expect(fs.existsSync(m2Dir)).toBe(true);
@@ -605,8 +598,7 @@ describe('toolchains tests', () => {
await toolchains.createToolchainsSettings({ await toolchains.createToolchainsSettings({
jdkInfo, jdkInfo,
settingsDirectory: m2Dir, settingsDirectory: m2Dir
overwriteSettings: true
}); });
expect(fs.existsSync(m2Dir)).toBe(true); expect(fs.existsSync(m2Dir)).toBe(true);
@@ -664,8 +656,7 @@ describe('toolchains tests', () => {
await toolchains.createToolchainsSettings({ await toolchains.createToolchainsSettings({
jdkInfo, jdkInfo,
settingsDirectory: m2Dir, settingsDirectory: m2Dir
overwriteSettings: true
}); });
expect(fs.existsSync(m2Dir)).toBe(true); expect(fs.existsSync(m2Dir)).toBe(true);
@@ -748,8 +739,7 @@ describe('toolchains tests', () => {
await toolchains.createToolchainsSettings({ await toolchains.createToolchainsSettings({
jdkInfo, jdkInfo,
settingsDirectory: m2Dir, settingsDirectory: m2Dir
overwriteSettings: true
}); });
expect(fs.existsSync(m2Dir)).toBe(true); expect(fs.existsSync(m2Dir)).toBe(true);
@@ -828,8 +818,7 @@ describe('toolchains tests', () => {
await toolchains.createToolchainsSettings({ await toolchains.createToolchainsSettings({
jdkInfo, jdkInfo,
settingsDirectory: m2Dir, settingsDirectory: m2Dir
overwriteSettings: true
}); });
expect(fs.existsSync(m2Dir)).toBe(true); expect(fs.existsSync(m2Dir)).toBe(true);
@@ -854,7 +843,7 @@ describe('toolchains tests', () => {
).toEqual(result); ).toEqual(result);
}, 100000); }, 100000);
it('does not overwrite existing toolchains.xml files', async () => { it('extends existing toolchains.xml files instead of overwriting them', async () => {
const jdkInfo = { const jdkInfo = {
version: '17', version: '17',
vendor: 'Eclipse Temurin', vendor: 'Eclipse Temurin',
@@ -883,13 +872,20 @@ describe('toolchains tests', () => {
await toolchains.createToolchainsSettings({ await toolchains.createToolchainsSettings({
jdkInfo, jdkInfo,
settingsDirectory: m2Dir, settingsDirectory: m2Dir
overwriteSettings: false
}); });
expect(fs.existsSync(m2Dir)).toBe(true); expect(fs.existsSync(m2Dir)).toBe(true);
expect(fs.existsSync(toolchainsFile)).toBe(true); expect(fs.existsSync(toolchainsFile)).toBe(true);
expect(fs.readFileSync(toolchainsFile, 'utf-8')).toEqual(originalFile);
const updated = fs.readFileSync(toolchainsFile, 'utf-8');
// The pre-existing (Sun 1.6) toolchain must be preserved ...
expect(updated).toContain('<id>sun_1.6</id>');
expect(updated).toContain('<jdkHome>/opt/jdk/sun/1.6</jdkHome>');
// ... and the newly installed JDK must be appended.
expect(updated).toContain('<id>temurin_17</id>');
expect(updated).toContain('<vendor>Eclipse Temurin</vendor>');
expect(updated).toContain(`<jdkHome>${jdkInfo.jdkHome}</jdkHome>`);
}, 100000); }, 100000);
it('generates valid toolchains.xml with minimal configuration', () => { it('generates valid toolchains.xml with minimal configuration', () => {
@@ -959,4 +955,55 @@ describe('toolchains tests', () => {
) )
); );
}, 100000); }, 100000);
it('preserves toolchains from previous executions across multiple setup-java runs', async () => {
// Regression test for https://github.com/actions/setup-java/issues/1099
// Running setup-java several times in the same job (e.g. multiple steps / multiple
// java-version entries) must accumulate every JDK in toolchains.xml rather
// than replacing previously registered entries.
(core.getInput as jest.Mock<any>).mockImplementation((name: string) => {
if (name === 'settings-path') return m2Dir;
return '';
});
const runs = [
{
version: '8',
distributionName: 'temurin',
id: 'temurin_8',
jdkHome: '/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/8.0.1-12/x64'
},
{
version: '11',
distributionName: 'temurin',
id: 'temurin_11',
jdkHome: '/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/11.0.1-12/x64'
},
{
version: '17',
distributionName: 'temurin',
id: 'temurin_17',
jdkHome: '/opt/hostedtoolcache/Java_Temurin-Hotspot_jdk/17.0.1-12/x64'
}
];
for (const run of runs) {
await toolchains.configureToolchains(
run.version,
run.distributionName,
run.jdkHome,
undefined
);
}
expect(fs.existsSync(toolchainsFile)).toBe(true);
const contents = fs.readFileSync(toolchainsFile, 'utf-8');
for (const run of runs) {
expect(contents).toContain(`<id>${run.id}</id>`);
expect(contents).toContain(`<jdkHome>${run.jdkHome}</jdkHome>`);
}
// Exactly one <toolchain> entry per run no duplicates, none dropped.
expect((contents.match(/<toolchain>/g) || []).length).toBe(runs.length);
}, 100000);
}); });
+32 -1
View File
@@ -56,7 +56,8 @@ const {
isVersionSatisfies, isVersionSatisfies,
isCacheFeatureAvailable, isCacheFeatureAvailable,
isGhes, isGhes,
validatePaginationUrl validatePaginationUrl,
getLatestMajorVersion
} = await import('../src/util.js'); } = await import('../src/util.js');
describe('isVersionSatisfies', () => { describe('isVersionSatisfies', () => {
@@ -400,3 +401,33 @@ describe('isGhes', () => {
expect(isGhes()).toBeTruthy(); expect(isGhes()).toBeTruthy();
}); });
}); });
describe('getLatestMajorVersion', () => {
const makeHttp = (getJson: jest.Mock) =>
({getJson}) as unknown as import('@actions/http-client').HttpClient;
it('returns most_recent_feature_release from the Adoptium API', async () => {
const getJson = jest.fn(async () => ({
statusCode: 200,
result: {most_recent_feature_release: 25},
headers: {}
}));
await expect(getLatestMajorVersion(makeHttp(getJson))).resolves.toBe(25);
expect(getJson).toHaveBeenCalledWith(
'https://api.adoptium.net/v3/info/available_releases'
);
});
it('throws when the response does not contain a usable value', async () => {
const getJson = jest.fn(async () => ({
statusCode: 200,
result: {},
headers: {}
}));
await expect(getLatestMajorVersion(makeHttp(getJson))).rejects.toThrow(
'Could not determine the latest available Java major version'
);
});
});
+5 -1
View File
@@ -4,7 +4,7 @@ description: 'Set up a specific version of the Java JDK and add the
author: 'GitHub' author: 'GitHub'
inputs: inputs:
java-version: java-version:
description: 'The Java version to set up. Takes a whole or semver Java version. See examples of supported syntax in README file' description: 'The Java version to set up. Takes a whole or semver Java version, or the "latest" alias to use the newest available stable release. See examples of supported syntax in README file'
required: false required: false
java-version-file: java-version-file:
description: 'The path to a file containing the Java version to set up (.java-version, .tool-versions, .sdkmanrc). Used when java-version is not set. See examples of supported syntax in README file' description: 'The path to a file containing the Java version to set up (.java-version, .tool-versions, .sdkmanrc). Used when java-version is not set. See examples of supported syntax in README file'
@@ -94,6 +94,10 @@ inputs:
description: 'Whether Maven should print artifact download/transfer progress to the build log. When "false" (default) the action sets "-ntp" (--no-transfer-progress) in MAVEN_ARGS to produce cleaner logs. Set to "true" to keep the progress output. Has no effect on non-Maven builds.' description: 'Whether Maven should print artifact download/transfer progress to the build log. When "false" (default) the action sets "-ntp" (--no-transfer-progress) in MAVEN_ARGS to produce cleaner logs. Set to "true" to keep the progress output. Has no effect on non-Maven builds.'
required: false required: false
default: false default: false
problem-matcher:
description: 'Whether to register the Java problem matcher (compiler errors/warnings and uncaught exceptions). Set to "false" to disable annotations.'
required: false
default: true
outputs: outputs:
distribution: distribution:
description: 'Distribution of Java that has been installed' description: 'Distribution of Java that has been installed'
+258 -38
View File
@@ -39023,7 +39023,7 @@ module.exports = { version: packageJson.version }
/***/ 4012: /***/ 4012:
/***/ ((module) => { /***/ ((module) => {
module.exports = /*#__PURE__*/JSON.parse('{"name":"@actions/cache","version":"6.1.0","description":"Actions cache lib","keywords":["github","actions","cache"],"homepage":"https://github.com/actions/toolkit/tree/main/packages/cache","license":"MIT","type":"module","main":"lib/cache.js","types":"lib/cache.d.ts","exports":{".":{"types":"./lib/cache.d.ts","import":"./lib/cache.js"}},"directories":{"lib":"lib","test":"__tests__"},"files":["lib","!.DS_Store"],"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/actions/toolkit.git","directory":"packages/cache"},"scripts":{"audit-moderate":"npm install && npm audit --json --audit-level=moderate > audit.json","test":"echo \\"Error: run tests from root\\" && exit 1","tsc":"tsc && cp src/internal/shared/package-version.cjs lib/internal/shared/"},"bugs":{"url":"https://github.com/actions/toolkit/issues"},"dependencies":{"@actions/core":"^3.0.1","@actions/exec":"^3.0.0","@actions/glob":"^0.6.1","@actions/http-client":"^4.0.1","@actions/io":"^3.0.2","@azure/core-rest-pipeline":"^1.23.0","@azure/storage-blob":"^12.31.0","@protobuf-ts/runtime-rpc":"^2.11.1","semver":"^7.7.4"},"devDependencies":{"@protobuf-ts/plugin":"^2.11.1","@types/node":"^25.6.0","@types/semver":"^7.7.1","typescript":"^5.9.3"},"overrides":{"uri-js":"npm:uri-js-replace@^1.0.1","node-fetch":"^3.3.2"}}'); module.exports = /*#__PURE__*/JSON.parse('{"name":"@actions/cache","version":"6.2.0","description":"Actions cache lib","keywords":["github","actions","cache"],"homepage":"https://github.com/actions/toolkit/tree/main/packages/cache","license":"MIT","type":"module","main":"lib/cache.js","types":"lib/cache.d.ts","exports":{".":{"types":"./lib/cache.d.ts","import":"./lib/cache.js"}},"directories":{"lib":"lib","test":"__tests__"},"files":["lib","!.DS_Store"],"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/actions/toolkit.git","directory":"packages/cache"},"scripts":{"audit-moderate":"npm install && npm audit --json --audit-level=moderate > audit.json","test":"echo \\"Error: run tests from root\\" && exit 1","tsc":"tsc && cp src/internal/shared/package-version.cjs lib/internal/shared/"},"bugs":{"url":"https://github.com/actions/toolkit/issues"},"dependencies":{"@actions/core":"^3.0.1","@actions/exec":"^3.0.0","@actions/glob":"^0.6.1","@actions/http-client":"^4.0.1","@actions/io":"^3.0.2","@azure/core-rest-pipeline":"^1.23.0","@azure/storage-blob":"^12.31.0","@protobuf-ts/runtime-rpc":"^2.11.1","semver":"^7.7.4"},"devDependencies":{"@protobuf-ts/plugin":"^2.11.1","@types/node":"^25.6.0","@types/semver":"^7.7.1","typescript":"^5.9.3"},"overrides":{"uri-js":"npm:uri-js-replace@^1.0.1","node-fetch":"^3.3.2"}}');
/***/ }) /***/ })
@@ -44068,6 +44068,10 @@ const SystemTarPathOnWindows = `${process.env['SYSTEMDRIVE']}\\Windows\\System32
const TarFilename = 'cache.tar'; const TarFilename = 'cache.tar';
const ManifestFilename = 'manifest.txt'; const ManifestFilename = 'manifest.txt';
const CacheFileSizeLimit = 10 * Math.pow(1024, 3); // 10GiB per repository const CacheFileSizeLimit = 10 * Math.pow(1024, 3); // 10GiB per repository
// Prefix the cache backend embeds in a read-denial message (v2 twirp
// GetCacheEntryDownloadURL error or the GHES v1 `_apis/artifactcache` 403 body).
// Shared so cache.ts and cacheHttpClient.ts match the same contract value.
const constants_CacheReadDeniedMessagePrefix = 'cache read denied:';
//# sourceMappingURL=constants.js.map //# sourceMappingURL=constants.js.map
;// CONCATENATED MODULE: ./node_modules/@actions/cache/lib/internal/cacheUtils.js ;// CONCATENATED MODULE: ./node_modules/@actions/cache/lib/internal/cacheUtils.js
var cacheUtils_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { var cacheUtils_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
@@ -93948,6 +93952,24 @@ function config_getCacheServiceVersion() {
return 'v1'; return 'v1';
return process.env['ACTIONS_CACHE_SERVICE_V2'] ? 'v2' : 'v1'; return process.env['ACTIONS_CACHE_SERVICE_V2'] ? 'v2' : 'v1';
} }
// The cache-mode lattice: readable = {read, write}, writable = {write,
// write-only}, none = neither.
const KNOWN_CACHE_MODES = ['none', 'read', 'write', 'write-only'];
// The effective cache-mode exported by the runner, or '' when not set.
function config_getCacheMode() {
return (process.env['ACTIONS_CACHE_MODE'] || '').trim().toLowerCase();
}
// Unset or unrecognized modes are permissive so behavior matches today.
function config_isCacheReadable(mode) {
if (!KNOWN_CACHE_MODES.includes(mode))
return true;
return mode === 'read' || mode === 'write';
}
function isCacheWritable(mode) {
if (!KNOWN_CACHE_MODES.includes(mode))
return true;
return mode === 'write' || mode === 'write-only';
}
function getCacheServiceURL() { function getCacheServiceURL() {
const version = config_getCacheServiceVersion(); const version = config_getCacheServiceVersion();
// Based on the version of the cache service, we will determine which // Based on the version of the cache service, we will determine which
@@ -93997,6 +94019,7 @@ var cacheHttpClient_awaiter = (undefined && undefined.__awaiter) || function (th
function getCacheApiUrl(resource) { function getCacheApiUrl(resource) {
const baseUrl = getCacheServiceURL(); const baseUrl = getCacheServiceURL();
if (!baseUrl) { if (!baseUrl) {
@@ -94024,6 +94047,7 @@ function createHttpClient() {
} }
function getCacheEntry(keys, paths, options) { function getCacheEntry(keys, paths, options) {
return cacheHttpClient_awaiter(this, void 0, void 0, function* () { return cacheHttpClient_awaiter(this, void 0, void 0, function* () {
var _a;
const httpClient = createHttpClient(); const httpClient = createHttpClient();
const version = utils.getCacheVersion(paths, options === null || options === void 0 ? void 0 : options.compressionMethod, options === null || options === void 0 ? void 0 : options.enableCrossOsArchive); const version = utils.getCacheVersion(paths, options === null || options === void 0 ? void 0 : options.compressionMethod, options === null || options === void 0 ? void 0 : options.enableCrossOsArchive);
const resource = `cache?keys=${encodeURIComponent(keys.join(','))}&version=${version}`; const resource = `cache?keys=${encodeURIComponent(keys.join(','))}&version=${version}`;
@@ -94037,6 +94061,12 @@ function getCacheEntry(keys, paths, options) {
return null; return null;
} }
if (!isSuccessStatusCode(response.statusCode)) { if (!isSuccessStatusCode(response.statusCode)) {
// Only surface the receiver's body for a `cache read denied:` policy denial
// so callers can dispatch on it; keep the generic message otherwise.
const errorMessage = (_a = response.error) === null || _a === void 0 ? void 0 : _a.message;
if (errorMessage === null || errorMessage === void 0 ? void 0 : errorMessage.includes(CacheReadDeniedMessagePrefix)) {
throw new Error(errorMessage);
}
throw new Error(`Cache service responded with ${response.statusCode}`); throw new Error(`Cache service responded with ${response.statusCode}`);
} }
const cacheResult = response.result; const cacheResult = response.result;
@@ -95297,6 +95327,7 @@ var cache_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _ar
class ValidationError extends Error { class ValidationError extends Error {
constructor(message) { constructor(message) {
super(message); super(message);
@@ -95312,19 +95343,20 @@ class ReserveCacheError extends Error {
} }
} }
/** /**
* Stable prefix the receiver writes into the cache reservation response when * Stable prefix the cache service writes into the cache reservation response
* the issuer downgraded the cache token to read-only (for example, because * when the issuer downgraded the cache token to read-only (for example, because
* the run was triggered by an untrusted event). saveCacheV1 / saveCacheV2 * the run was triggered by an untrusted event). saveCacheV1 / saveCacheV2
* dispatch on this prefix to re-classify the failure as a * dispatch on this prefix to re-classify the failure as a CacheWriteDeniedError
* CacheWriteDeniedError so consumers (and the outer catch arm) can * so consumers and tests can distinguish a policy denial from other reservation
* distinguish a policy denial from other reservation failures. * failures. Internally it is logged as a non-fatal warning like other
* best-effort save failures.
*/ */
const CACHE_WRITE_DENIED_PREFIX = 'cache write denied:'; const CACHE_WRITE_DENIED_PREFIX = 'cache write denied:';
/** /**
* Raised when the cache backend refuses to reserve a writable cache entry * Raised when the cache backend refuses to reserve a writable cache entry
* because the JWT issued for this run was scoped read-only (for example, the * because the JWT issued for this run was scoped read-only (for example, the
* run was triggered by an event the repository administrator classified as * run was triggered by an event the repository administrator classified as
* untrusted). The receiver-supplied detail message always begins with * untrusted). The service-supplied detail message always begins with
* `cache write denied:` (the full error message includes additional context * `cache write denied:` (the full error message includes additional context
* like the cache key). * like the cache key).
* *
@@ -95340,6 +95372,19 @@ class CacheWriteDeniedError extends ReserveCacheError {
Object.setPrototypeOf(this, CacheWriteDeniedError.prototype); Object.setPrototypeOf(this, CacheWriteDeniedError.prototype);
} }
} }
// Re-exported from constants so consumers keep referencing it here; the shared
// value also drives detection in cacheHttpClient without duplicating the string.
const CACHE_READ_DENIED_PREFIX = (/* unused pure expression or super */ null && (CacheReadDeniedMessagePrefix));
// Raised when the cache backend denies a download URL because the run's token
// has no readable cache scopes. Caching is best-effort, so restoreCache logs a
// warning and reports a cache miss rather than rethrowing this.
class CacheReadDeniedError extends Error {
constructor(message) {
super(message);
this.name = 'CacheReadDeniedError';
Object.setPrototypeOf(this, CacheReadDeniedError.prototype);
}
}
class FinalizeCacheError extends Error { class FinalizeCacheError extends Error {
constructor(message) { constructor(message) {
super(message); super(message);
@@ -95394,6 +95439,12 @@ function restoreCache(paths_1, primaryKey_1, restoreKeys_1, options_1) {
const cacheServiceVersion = getCacheServiceVersion(); const cacheServiceVersion = getCacheServiceVersion();
core.debug(`Cache service version: ${cacheServiceVersion}`); core.debug(`Cache service version: ${cacheServiceVersion}`);
checkPaths(paths); checkPaths(paths);
const cacheMode = getCacheMode();
if (!isCacheReadable(cacheMode)) {
core.info(`Cache restore skipped: the effective cache-mode '${cacheMode}' does not permit reads.`);
core.debug(`Skipped restore for paths [${paths.join(', ')}] with primary key '${primaryKey}'.`);
return undefined;
}
switch (cacheServiceVersion) { switch (cacheServiceVersion) {
case 'v2': case 'v2':
return yield restoreCacheV2(paths, primaryKey, restoreKeys, options, enableCrossOsArchive); return yield restoreCacheV2(paths, primaryKey, restoreKeys, options, enableCrossOsArchive);
@@ -95415,6 +95466,7 @@ function restoreCache(paths_1, primaryKey_1, restoreKeys_1, options_1) {
*/ */
function restoreCacheV1(paths_1, primaryKey_1, restoreKeys_1, options_1) { function restoreCacheV1(paths_1, primaryKey_1, restoreKeys_1, options_1) {
return cache_awaiter(this, arguments, void 0, function* (paths, primaryKey, restoreKeys, options, enableCrossOsArchive = false) { return cache_awaiter(this, arguments, void 0, function* (paths, primaryKey, restoreKeys, options, enableCrossOsArchive = false) {
var _a;
restoreKeys = restoreKeys || []; restoreKeys = restoreKeys || [];
const keys = [primaryKey, ...restoreKeys]; const keys = [primaryKey, ...restoreKeys];
core.debug('Resolved Keys:'); core.debug('Resolved Keys:');
@@ -95429,10 +95481,26 @@ function restoreCacheV1(paths_1, primaryKey_1, restoreKeys_1, options_1) {
let archivePath = ''; let archivePath = '';
try { try {
// path are needed to compute version // path are needed to compute version
const cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, { let cacheEntry;
compressionMethod, try {
enableCrossOsArchive cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, {
}); compressionMethod,
enableCrossOsArchive
});
}
catch (error) {
// The v1 artifact cache service returns HTTP 403 with a
// `cache read denied:` body when the run's token has no readable cache
// scopes. getCacheEntry lives in a dependency-free internal module and
// cannot import CacheReadDeniedError without a circular dependency, so it
// only surfaces the raw denial message; we classify it into the typed
// error here so the outer catch and consumers can dispatch on it.
const errorMessage = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : '';
if (errorMessage.includes(CACHE_READ_DENIED_PREFIX)) {
throw new CacheReadDeniedError(errorMessage);
}
throw error;
}
if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) { if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) {
// Cache not found // Cache not found
return undefined; return undefined;
@@ -95461,7 +95529,9 @@ function restoreCacheV1(paths_1, primaryKey_1, restoreKeys_1, options_1) {
} }
else { else {
// warn on cache restore failure and continue build // warn on cache restore failure and continue build
// Log server errors (5xx) as errors, all other errors as warnings // Log server errors (5xx) as errors, all other errors as warnings.
// A read denied by policy (CacheReadDeniedError) is not an HttpClientError
// so it falls here and is warned, treated as a cache miss.
if (typedError instanceof HttpClientError && if (typedError instanceof HttpClientError &&
typeof typedError.statusCode === 'number' && typeof typedError.statusCode === 'number' &&
typedError.statusCode >= 500) { typedError.statusCode >= 500) {
@@ -95496,6 +95566,7 @@ function restoreCacheV1(paths_1, primaryKey_1, restoreKeys_1, options_1) {
*/ */
function restoreCacheV2(paths_1, primaryKey_1, restoreKeys_1, options_1) { function restoreCacheV2(paths_1, primaryKey_1, restoreKeys_1, options_1) {
return cache_awaiter(this, arguments, void 0, function* (paths, primaryKey, restoreKeys, options, enableCrossOsArchive = false) { return cache_awaiter(this, arguments, void 0, function* (paths, primaryKey, restoreKeys, options, enableCrossOsArchive = false) {
var _a;
// Override UploadOptions to force the use of Azure // Override UploadOptions to force the use of Azure
options = Object.assign(Object.assign({}, options), { useAzureSdk: true }); options = Object.assign(Object.assign({}, options), { useAzureSdk: true });
restoreKeys = restoreKeys || []; restoreKeys = restoreKeys || [];
@@ -95517,7 +95588,20 @@ function restoreCacheV2(paths_1, primaryKey_1, restoreKeys_1, options_1) {
restoreKeys, restoreKeys,
version: utils.getCacheVersion(paths, compressionMethod, enableCrossOsArchive) version: utils.getCacheVersion(paths, compressionMethod, enableCrossOsArchive)
}; };
const response = yield twirpClient.GetCacheEntryDownloadURL(request); let response;
try {
response = yield twirpClient.GetCacheEntryDownloadURL(request);
}
catch (error) {
// The receiver returns twirp PermissionDenied (403) when the run's token
// has no readable cache scopes. The client wraps that 403, so the stable
// prefix is embedded in the message rather than leading it.
const errorMessage = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : '';
if (errorMessage.includes(CACHE_READ_DENIED_PREFIX)) {
throw new CacheReadDeniedError(errorMessage);
}
throw error;
}
if (!response.ok) { if (!response.ok) {
core.debug(`Cache not found for version ${request.version} of keys: ${keys.join(', ')}`); core.debug(`Cache not found for version ${request.version} of keys: ${keys.join(', ')}`);
return undefined; return undefined;
@@ -95552,8 +95636,10 @@ function restoreCacheV2(paths_1, primaryKey_1, restoreKeys_1, options_1) {
throw error; throw error;
} }
else { else {
// Supress all non-validation cache related errors because caching should be optional // Suppress all non-validation cache related errors because caching should be optional
// Log server errors (5xx) as errors, all other errors as warnings // Log server errors (5xx) as errors, all other errors as warnings.
// A read denied by policy (CacheReadDeniedError) is not an HttpClientError
// so it falls here and is warned, treated as a cache miss.
if (typedError instanceof HttpClientError && if (typedError instanceof HttpClientError &&
typeof typedError.statusCode === 'number' && typeof typedError.statusCode === 'number' &&
typedError.statusCode >= 500) { typedError.statusCode >= 500) {
@@ -95592,6 +95678,12 @@ function cache_saveCache(paths_1, key_1, options_1) {
core_debug(`Cache service version: ${cacheServiceVersion}`); core_debug(`Cache service version: ${cacheServiceVersion}`);
checkPaths(paths); checkPaths(paths);
checkKey(key); checkKey(key);
const cacheMode = config_getCacheMode();
if (!isCacheWritable(cacheMode)) {
info(`Cache save skipped: the effective cache-mode '${cacheMode}' does not permit writes.`);
core_debug(`Skipped save for paths [${paths.join(', ')}] with key '${key}'.`);
return -1;
}
switch (cacheServiceVersion) { switch (cacheServiceVersion) {
case 'v2': case 'v2':
return yield saveCacheV2(paths, key, options, enableCrossOsArchive); return yield saveCacheV2(paths, key, options, enableCrossOsArchive);
@@ -95669,17 +95761,14 @@ function saveCacheV1(paths_1, key_1, options_1) {
if (typedError.name === ValidationError.name) { if (typedError.name === ValidationError.name) {
throw error; throw error;
} }
else if (typedError.name === CacheWriteDeniedError.name) {
// Cache write was denied by policy (read-only token). Surface to the
// customer at warning level so it is visible in the workflow log
// without failing the run.
warning(`Failed to save: ${typedError.message}`);
}
else if (typedError.name === ReserveCacheError.name) { else if (typedError.name === ReserveCacheError.name) {
info(`Failed to save: ${typedError.message}`); info(`Failed to save: ${typedError.message}`);
} }
else { else {
// Log server errors (5xx) as errors, all other errors as warnings // Log server errors (5xx) as errors, all other errors as warnings.
// A write denied by policy (CacheWriteDeniedError) is not an
// HttpClientError and its name does not match the ReserveCacheError arm,
// so it falls here and is warned without failing the run.
if (typedError instanceof lib_HttpClientError && if (typedError instanceof lib_HttpClientError &&
typeof typedError.statusCode === 'number' && typeof typedError.statusCode === 'number' &&
typedError.statusCode >= 500) { typedError.statusCode >= 500) {
@@ -95790,12 +95879,6 @@ function saveCacheV2(paths_1, key_1, options_1) {
if (typedError.name === ValidationError.name) { if (typedError.name === ValidationError.name) {
throw error; throw error;
} }
else if (typedError.name === CacheWriteDeniedError.name) {
// Cache write was denied by policy (read-only token). Surface to the
// customer at warning level so it is visible in the workflow log
// without failing the run.
warning(`Failed to save: ${typedError.message}`);
}
else if (typedError.name === ReserveCacheError.name) { else if (typedError.name === ReserveCacheError.name) {
info(`Failed to save: ${typedError.message}`); info(`Failed to save: ${typedError.message}`);
} }
@@ -95803,7 +95886,10 @@ function saveCacheV2(paths_1, key_1, options_1) {
warning(typedError.message); warning(typedError.message);
} }
else { else {
// Log server errors (5xx) as errors, all other errors as warnings // Log server errors (5xx) as errors, all other errors as warnings.
// A write denied by policy (CacheWriteDeniedError) is not an
// HttpClientError and its name does not match the ReserveCacheError arm,
// so it falls here and is warned without failing the run.
if (typedError instanceof lib_HttpClientError && if (typedError instanceof lib_HttpClientError &&
typeof typedError.statusCode === 'number' && typeof typedError.statusCode === 'number' &&
typedError.statusCode >= 500) { typedError.statusCode >= 500) {
@@ -95838,6 +95924,7 @@ const INPUT_JDK_FILE = 'jdk-file';
const INPUT_JDK_FILE_DEPRECATED = 'jdkFile'; const INPUT_JDK_FILE_DEPRECATED = 'jdkFile';
const INPUT_CHECK_LATEST = 'check-latest'; const INPUT_CHECK_LATEST = 'check-latest';
const INPUT_SET_DEFAULT = 'set-default'; const INPUT_SET_DEFAULT = 'set-default';
const INPUT_PROBLEM_MATCHER = 'problem-matcher';
const INPUT_VERIFY_SIGNATURE = 'verify-signature'; const INPUT_VERIFY_SIGNATURE = 'verify-signature';
const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key'; const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key';
const INPUT_SERVER_ID = 'server-id'; const INPUT_SERVER_ID = 'server-id';
@@ -95849,6 +95936,12 @@ const INPUT_GPG_PRIVATE_KEY = 'gpg-private-key';
const INPUT_GPG_PASSPHRASE = 'gpg-passphrase'; const INPUT_GPG_PASSPHRASE = 'gpg-passphrase';
const INPUT_DEFAULT_GPG_PRIVATE_KEY = (/* unused pure expression or super */ null && (undefined)); const INPUT_DEFAULT_GPG_PRIVATE_KEY = (/* unused pure expression or super */ null && (undefined));
const INPUT_DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE'; const INPUT_DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE';
// The default name of the environment variable the maven-gpg-plugin reads the
// passphrase from (property `gpg.passphraseEnvName`). When the configured
// passphrase env var name matches this, no extra configuration is required.
const MAVEN_GPG_PASSPHRASE_DEFAULT_ENV = 'MAVEN_GPG_PASSPHRASE';
// Id of the settings.xml profile used to set `gpg.passphraseEnvName`.
const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg';
const INPUT_CACHE = 'cache'; const INPUT_CACHE = 'cache';
const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path'; const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
const INPUT_JOB_STATUS = 'job-status'; const INPUT_JOB_STATUS = 'job-status';
@@ -96154,6 +96247,22 @@ function renameWinArchive(javaArchivePath) {
fs.renameSync(javaArchivePath, javaArchivePathRenamed); fs.renameSync(javaArchivePath, javaArchivePathRenamed);
return javaArchivePathRenamed; return javaArchivePathRenamed;
} }
// Resolve the newest available stable/GA feature (major) release.
//
// Some distributions (e.g. Oracle, GraalVM) construct their download URLs from a
// concrete major version and don't expose an endpoint to list every available
// release, so a bare `latest` alias can't be resolved from their own metadata.
// The Adoptium (Temurin) API is used as a proxy for "what is the newest GA major
// version out there", which those distributions typically publish at the same time.
async function getLatestMajorVersion(http) {
const availableReleasesUrl = 'https://api.adoptium.net/v3/info/available_releases';
const response = await http.getJson(availableReleasesUrl);
const mostRecent = response.result?.most_recent_feature_release;
if (!mostRecent || Number.isNaN(Number(mostRecent))) {
throw new Error(`Could not determine the latest available Java major version from ${availableReleasesUrl}`);
}
return Number(mostRecent);
}
;// CONCATENATED MODULE: ./src/gpg.ts ;// CONCATENATED MODULE: ./src/gpg.ts
@@ -99650,23 +99759,28 @@ const CACHE_KEY_PREFIX = 'setup-java';
const supportedPackageManager = [ const supportedPackageManager = [
{ {
id: 'maven', id: 'maven',
path: [ path: [(0,external_path_.join)(external_os_default().homedir(), '.m2', 'repository')],
(0,external_path_.join)(external_os_default().homedir(), '.m2', 'repository'),
(0,external_path_.join)(external_os_default().homedir(), '.m2', 'wrapper', 'dists')
],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven
pattern: [ pattern: [
'**/pom.xml', '**/pom.xml',
'**/.mvn/wrapper/maven-wrapper.properties', '**/.mvn/wrapper/maven-wrapper.properties',
'**/.mvn/extensions.xml' '**/.mvn/extensions.xml'
],
// The Maven wrapper distribution only depends on the wrapper properties,
// which change very rarely, so it is cached separately from the local
// repository. This keeps it available across the frequent pom.xml changes
// that rotate the main cache key. See issue #1095.
additionalCaches: [
{
name: 'maven-wrapper',
path: [(0,external_path_.join)(external_os_default().homedir(), '.m2', 'wrapper', 'dists')],
pattern: ['**/.mvn/wrapper/maven-wrapper.properties']
}
] ]
}, },
{ {
id: 'gradle', id: 'gradle',
path: [ path: [(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'caches')],
(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'caches'),
(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'wrapper')
],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle
pattern: [ pattern: [
'**/*.gradle*', '**/*.gradle*',
@@ -99675,6 +99789,17 @@ const supportedPackageManager = [
'buildSrc/**/Dependencies.kt', 'buildSrc/**/Dependencies.kt',
'gradle/*.versions.toml', 'gradle/*.versions.toml',
'**/versions.properties' '**/versions.properties'
],
// The Gradle wrapper distribution only depends on the wrapper properties,
// which change very rarely, so it is cached separately from the Gradle
// caches. This keeps it available across the frequent *.gradle* changes
// that rotate the main cache key. See issue #269.
additionalCaches: [
{
name: 'gradle-wrapper',
path: [(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'wrapper')],
pattern: ['**/gradle-wrapper.properties']
}
] ]
}, },
{ {
@@ -99710,6 +99835,19 @@ function findPackageManager(id) {
} }
return packageManager; return packageManager;
} }
/**
* State keys used to carry an additional cache's restore-time information over
* to the post (save) action, scoped by the additional cache name.
*/
function additionalCachePrimaryKeyState(name) {
return `${STATE_CACHE_PRIMARY_KEY}-${name}`;
}
function additionalCacheMatchedKeyState(name) {
return `${CACHE_MATCHED_KEY}-${name}`;
}
function buildCacheKey(id, fileHash) {
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${id}-${fileHash}`;
}
/** /**
* A function that generates a cache key to use. * A function that generates a cache key to use.
* Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"". * Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"".
@@ -99723,7 +99861,19 @@ async function computeCacheKey(packageManager, cacheDependencyPath) {
if (!fileHash) { if (!fileHash) {
throw new Error(`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`); throw new Error(`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`);
} }
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${packageManager.id}-${fileHash}`; return buildCacheKey(packageManager.id, fileHash);
}
/**
* Computes the cache key for an additional cache. Unlike {@link computeCacheKey}
* this returns undefined (instead of throwing) when no file matches the pattern,
* because additional caches are optional features that many projects do not use.
*/
async function computeAdditionalCacheKey(additionalCache) {
const fileHash = await glob.hashFiles(additionalCache.pattern.join('\n'));
if (!fileHash) {
return undefined;
}
return buildCacheKey(additionalCache.name, fileHash);
} }
/** /**
* Restore the dependency cache * Restore the dependency cache
@@ -99747,6 +99897,29 @@ async function restore(id, cacheDependencyPath) {
core.setOutput('cache-hit', false); core.setOutput('cache-hit', false);
core.info(`${packageManager.id} cache is not found`); core.info(`${packageManager.id} cache is not found`);
} }
for (const additionalCache of packageManager.additionalCaches ?? []) {
await restoreAdditionalCache(additionalCache);
}
}
/**
* Restore an additional cache (e.g. a build-tool wrapper distribution) that is
* keyed independently of the main dependency cache so that it survives changes
* to volatile dependency files. Skips silently when the project does not use
* the corresponding feature.
*/
async function restoreAdditionalCache(additionalCache) {
const primaryKey = await computeAdditionalCacheKey(additionalCache);
if (!primaryKey) {
core.debug(`No file matched [${additionalCache.pattern}] for the ${additionalCache.name} cache, skipping.`);
return;
}
core.debug(`${additionalCache.name} primary key is ${primaryKey}`);
core.saveState(additionalCachePrimaryKeyState(additionalCache.name), primaryKey);
const matchedKey = await cache.restoreCache(additionalCache.path, primaryKey);
if (matchedKey) {
core.saveState(additionalCacheMatchedKeyState(additionalCache.name), matchedKey);
core.info(`${additionalCache.name} cache restored from key: ${matchedKey}`);
}
} }
/** /**
* Save the dependency cache * Save the dependency cache
@@ -99757,6 +99930,9 @@ async function save(id) {
const matchedKey = getState(CACHE_MATCHED_KEY); const matchedKey = getState(CACHE_MATCHED_KEY);
// Inputs are re-evaluated before the post action, so we want the original key used for restore // Inputs are re-evaluated before the post action, so we want the original key used for restore
const primaryKey = getState(STATE_CACHE_PRIMARY_KEY); const primaryKey = getState(STATE_CACHE_PRIMARY_KEY);
for (const additionalCache of packageManager.additionalCaches ?? []) {
await saveAdditionalCache(packageManager, additionalCache);
}
if (!primaryKey) { if (!primaryKey) {
warning('Error retrieving key from state.'); warning('Error retrieving key from state.');
return; return;
@@ -99791,6 +99967,50 @@ async function save(id) {
} }
} }
} }
/**
* Save an additional cache under its own key. Skips when no key was recorded at
* restore time (feature unused) or when the exact key was already restored.
*/
async function saveAdditionalCache(packageManager, additionalCache) {
const primaryKey = getState(additionalCachePrimaryKeyState(additionalCache.name));
const matchedKey = getState(additionalCacheMatchedKeyState(additionalCache.name));
if (!primaryKey) {
// The feature is not used by this project, nothing to save.
core_debug(`No primary key for the ${additionalCache.name} cache, not saving cache.`);
return;
}
else if (matchedKey === primaryKey) {
info(`Cache hit occurred on the ${additionalCache.name} primary key ${primaryKey}, not saving cache.`);
return;
}
try {
const cacheId = await cache_saveCache(additionalCache.path, primaryKey);
if (cacheId === -1) {
core_debug(`${additionalCache.name} cache was not saved for the key: ${primaryKey}`);
return;
}
info(`${additionalCache.name} cache saved with the key: ${primaryKey}`);
}
catch (error) {
const err = error;
if (err.name === ValidationError.name) {
// The cache paths did not resolve, e.g. the wrapper distribution was
// never downloaded because a system build tool was used or the download
// failed. Optional wrapper caches must not fail the post step, so skip.
core_debug(`${additionalCache.name} cache paths do not exist, not saving cache: ${err.message}`);
return;
}
if (err.name === ReserveCacheError.name) {
info(err.message);
}
else {
if (isProbablyGradleDaemonProblem(packageManager, err)) {
warning('Failed to save Gradle cache on Windows. If tar.exe reported "Permission denied", try to run Gradle with `--no-daemon` option. Refer to https://github.com/actions/cache/issues/454 for details.');
}
throw error;
}
}
}
/** /**
* @param packageManager the specified package manager by user * @param packageManager the specified package manager by user
* @param error the error thrown by the saveCache * @param error the error thrown by the saveCache
+533 -69
View File
@@ -38608,7 +38608,7 @@ let h2ExperimentalWarned = false
/** @type {import('http2')} */ /** @type {import('http2')} */
let http2 let http2
try { try {
http2 = __nccwpck_require__(4848) http2 = __nccwpck_require__(2467)
} catch { } catch {
// @ts-ignore // @ts-ignore
http2 = { constants: {} } http2 = { constants: {} }
@@ -64403,7 +64403,7 @@ module.exports = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("node:http");
/***/ }), /***/ }),
/***/ 4848: /***/ 2467:
/***/ ((module) => { /***/ ((module) => {
module.exports = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("node:http2"); module.exports = __WEBPACK_EXTERNAL_createRequire(import.meta.url)("node:http2");
@@ -70021,7 +70021,7 @@ module.exports = { version: packageJson.version }
/***/ 4012: /***/ 4012:
/***/ ((module) => { /***/ ((module) => {
module.exports = /*#__PURE__*/JSON.parse('{"name":"@actions/cache","version":"6.1.0","description":"Actions cache lib","keywords":["github","actions","cache"],"homepage":"https://github.com/actions/toolkit/tree/main/packages/cache","license":"MIT","type":"module","main":"lib/cache.js","types":"lib/cache.d.ts","exports":{".":{"types":"./lib/cache.d.ts","import":"./lib/cache.js"}},"directories":{"lib":"lib","test":"__tests__"},"files":["lib","!.DS_Store"],"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/actions/toolkit.git","directory":"packages/cache"},"scripts":{"audit-moderate":"npm install && npm audit --json --audit-level=moderate > audit.json","test":"echo \\"Error: run tests from root\\" && exit 1","tsc":"tsc && cp src/internal/shared/package-version.cjs lib/internal/shared/"},"bugs":{"url":"https://github.com/actions/toolkit/issues"},"dependencies":{"@actions/core":"^3.0.1","@actions/exec":"^3.0.0","@actions/glob":"^0.6.1","@actions/http-client":"^4.0.1","@actions/io":"^3.0.2","@azure/core-rest-pipeline":"^1.23.0","@azure/storage-blob":"^12.31.0","@protobuf-ts/runtime-rpc":"^2.11.1","semver":"^7.7.4"},"devDependencies":{"@protobuf-ts/plugin":"^2.11.1","@types/node":"^25.6.0","@types/semver":"^7.7.1","typescript":"^5.9.3"},"overrides":{"uri-js":"npm:uri-js-replace@^1.0.1","node-fetch":"^3.3.2"}}'); module.exports = /*#__PURE__*/JSON.parse('{"name":"@actions/cache","version":"6.2.0","description":"Actions cache lib","keywords":["github","actions","cache"],"homepage":"https://github.com/actions/toolkit/tree/main/packages/cache","license":"MIT","type":"module","main":"lib/cache.js","types":"lib/cache.d.ts","exports":{".":{"types":"./lib/cache.d.ts","import":"./lib/cache.js"}},"directories":{"lib":"lib","test":"__tests__"},"files":["lib","!.DS_Store"],"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/actions/toolkit.git","directory":"packages/cache"},"scripts":{"audit-moderate":"npm install && npm audit --json --audit-level=moderate > audit.json","test":"echo \\"Error: run tests from root\\" && exit 1","tsc":"tsc && cp src/internal/shared/package-version.cjs lib/internal/shared/"},"bugs":{"url":"https://github.com/actions/toolkit/issues"},"dependencies":{"@actions/core":"^3.0.1","@actions/exec":"^3.0.0","@actions/glob":"^0.6.1","@actions/http-client":"^4.0.1","@actions/io":"^3.0.2","@azure/core-rest-pipeline":"^1.23.0","@azure/storage-blob":"^12.31.0","@protobuf-ts/runtime-rpc":"^2.11.1","semver":"^7.7.4"},"devDependencies":{"@protobuf-ts/plugin":"^2.11.1","@types/node":"^25.6.0","@types/semver":"^7.7.1","typescript":"^5.9.3"},"overrides":{"uri-js":"npm:uri-js-replace@^1.0.1","node-fetch":"^3.3.2"}}');
/***/ }) /***/ })
@@ -73283,6 +73283,7 @@ const INPUT_JDK_FILE = 'jdk-file';
const INPUT_JDK_FILE_DEPRECATED = 'jdkFile'; const INPUT_JDK_FILE_DEPRECATED = 'jdkFile';
const INPUT_CHECK_LATEST = 'check-latest'; const INPUT_CHECK_LATEST = 'check-latest';
const INPUT_SET_DEFAULT = 'set-default'; const INPUT_SET_DEFAULT = 'set-default';
const INPUT_PROBLEM_MATCHER = 'problem-matcher';
const INPUT_VERIFY_SIGNATURE = 'verify-signature'; const INPUT_VERIFY_SIGNATURE = 'verify-signature';
const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key'; const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key';
const INPUT_SERVER_ID = 'server-id'; const INPUT_SERVER_ID = 'server-id';
@@ -73294,6 +73295,12 @@ const INPUT_GPG_PRIVATE_KEY = 'gpg-private-key';
const INPUT_GPG_PASSPHRASE = 'gpg-passphrase'; const INPUT_GPG_PASSPHRASE = 'gpg-passphrase';
const INPUT_DEFAULT_GPG_PRIVATE_KEY = undefined; const INPUT_DEFAULT_GPG_PRIVATE_KEY = undefined;
const INPUT_DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE'; const INPUT_DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE';
// The default name of the environment variable the maven-gpg-plugin reads the
// passphrase from (property `gpg.passphraseEnvName`). When the configured
// passphrase env var name matches this, no extra configuration is required.
const MAVEN_GPG_PASSPHRASE_DEFAULT_ENV = 'MAVEN_GPG_PASSPHRASE';
// Id of the settings.xml profile used to set `gpg.passphraseEnvName`.
const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg';
const INPUT_CACHE = 'cache'; const INPUT_CACHE = 'cache';
const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path'; const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
const constants_INPUT_JOB_STATUS = 'job-status'; const constants_INPUT_JOB_STATUS = 'job-status';
@@ -75095,6 +75102,10 @@ const SystemTarPathOnWindows = `${process.env['SYSTEMDRIVE']}\\Windows\\System32
const TarFilename = 'cache.tar'; const TarFilename = 'cache.tar';
const constants_ManifestFilename = 'manifest.txt'; const constants_ManifestFilename = 'manifest.txt';
const CacheFileSizeLimit = 10 * Math.pow(1024, 3); // 10GiB per repository const CacheFileSizeLimit = 10 * Math.pow(1024, 3); // 10GiB per repository
// Prefix the cache backend embeds in a read-denial message (v2 twirp
// GetCacheEntryDownloadURL error or the GHES v1 `_apis/artifactcache` 403 body).
// Shared so cache.ts and cacheHttpClient.ts match the same contract value.
const CacheReadDeniedMessagePrefix = 'cache read denied:';
//# sourceMappingURL=constants.js.map //# sourceMappingURL=constants.js.map
;// CONCATENATED MODULE: ./node_modules/@actions/cache/lib/internal/cacheUtils.js ;// CONCATENATED MODULE: ./node_modules/@actions/cache/lib/internal/cacheUtils.js
var cacheUtils_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) { var cacheUtils_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _arguments, P, generator) {
@@ -124976,6 +124987,24 @@ function config_getCacheServiceVersion() {
return 'v1'; return 'v1';
return process.env['ACTIONS_CACHE_SERVICE_V2'] ? 'v2' : 'v1'; return process.env['ACTIONS_CACHE_SERVICE_V2'] ? 'v2' : 'v1';
} }
// The cache-mode lattice: readable = {read, write}, writable = {write,
// write-only}, none = neither.
const KNOWN_CACHE_MODES = ['none', 'read', 'write', 'write-only'];
// The effective cache-mode exported by the runner, or '' when not set.
function config_getCacheMode() {
return (process.env['ACTIONS_CACHE_MODE'] || '').trim().toLowerCase();
}
// Unset or unrecognized modes are permissive so behavior matches today.
function isCacheReadable(mode) {
if (!KNOWN_CACHE_MODES.includes(mode))
return true;
return mode === 'read' || mode === 'write';
}
function config_isCacheWritable(mode) {
if (!KNOWN_CACHE_MODES.includes(mode))
return true;
return mode === 'write' || mode === 'write-only';
}
function getCacheServiceURL() { function getCacheServiceURL() {
const version = config_getCacheServiceVersion(); const version = config_getCacheServiceVersion();
// Based on the version of the cache service, we will determine which // Based on the version of the cache service, we will determine which
@@ -125025,6 +125054,7 @@ var cacheHttpClient_awaiter = (undefined && undefined.__awaiter) || function (th
function getCacheApiUrl(resource) { function getCacheApiUrl(resource) {
const baseUrl = getCacheServiceURL(); const baseUrl = getCacheServiceURL();
if (!baseUrl) { if (!baseUrl) {
@@ -125052,6 +125082,7 @@ function createHttpClient() {
} }
function getCacheEntry(keys, paths, options) { function getCacheEntry(keys, paths, options) {
return cacheHttpClient_awaiter(this, void 0, void 0, function* () { return cacheHttpClient_awaiter(this, void 0, void 0, function* () {
var _a;
const httpClient = createHttpClient(); const httpClient = createHttpClient();
const version = getCacheVersion(paths, options === null || options === void 0 ? void 0 : options.compressionMethod, options === null || options === void 0 ? void 0 : options.enableCrossOsArchive); const version = getCacheVersion(paths, options === null || options === void 0 ? void 0 : options.compressionMethod, options === null || options === void 0 ? void 0 : options.enableCrossOsArchive);
const resource = `cache?keys=${encodeURIComponent(keys.join(','))}&version=${version}`; const resource = `cache?keys=${encodeURIComponent(keys.join(','))}&version=${version}`;
@@ -125065,6 +125096,12 @@ function getCacheEntry(keys, paths, options) {
return null; return null;
} }
if (!requestUtils_isSuccessStatusCode(response.statusCode)) { if (!requestUtils_isSuccessStatusCode(response.statusCode)) {
// Only surface the receiver's body for a `cache read denied:` policy denial
// so callers can dispatch on it; keep the generic message otherwise.
const errorMessage = (_a = response.error) === null || _a === void 0 ? void 0 : _a.message;
if (errorMessage === null || errorMessage === void 0 ? void 0 : errorMessage.includes(CacheReadDeniedMessagePrefix)) {
throw new Error(errorMessage);
}
throw new Error(`Cache service responded with ${response.statusCode}`); throw new Error(`Cache service responded with ${response.statusCode}`);
} }
const cacheResult = response.result; const cacheResult = response.result;
@@ -126326,6 +126363,7 @@ var cache_awaiter = (undefined && undefined.__awaiter) || function (thisArg, _ar
class ValidationError extends Error { class ValidationError extends Error {
constructor(message) { constructor(message) {
super(message); super(message);
@@ -126341,19 +126379,20 @@ class ReserveCacheError extends Error {
} }
} }
/** /**
* Stable prefix the receiver writes into the cache reservation response when * Stable prefix the cache service writes into the cache reservation response
* the issuer downgraded the cache token to read-only (for example, because * when the issuer downgraded the cache token to read-only (for example, because
* the run was triggered by an untrusted event). saveCacheV1 / saveCacheV2 * the run was triggered by an untrusted event). saveCacheV1 / saveCacheV2
* dispatch on this prefix to re-classify the failure as a * dispatch on this prefix to re-classify the failure as a CacheWriteDeniedError
* CacheWriteDeniedError so consumers (and the outer catch arm) can * so consumers and tests can distinguish a policy denial from other reservation
* distinguish a policy denial from other reservation failures. * failures. Internally it is logged as a non-fatal warning like other
* best-effort save failures.
*/ */
const CACHE_WRITE_DENIED_PREFIX = 'cache write denied:'; const CACHE_WRITE_DENIED_PREFIX = 'cache write denied:';
/** /**
* Raised when the cache backend refuses to reserve a writable cache entry * Raised when the cache backend refuses to reserve a writable cache entry
* because the JWT issued for this run was scoped read-only (for example, the * because the JWT issued for this run was scoped read-only (for example, the
* run was triggered by an event the repository administrator classified as * run was triggered by an event the repository administrator classified as
* untrusted). The receiver-supplied detail message always begins with * untrusted). The service-supplied detail message always begins with
* `cache write denied:` (the full error message includes additional context * `cache write denied:` (the full error message includes additional context
* like the cache key). * like the cache key).
* *
@@ -126369,6 +126408,19 @@ class CacheWriteDeniedError extends ReserveCacheError {
Object.setPrototypeOf(this, CacheWriteDeniedError.prototype); Object.setPrototypeOf(this, CacheWriteDeniedError.prototype);
} }
} }
// Re-exported from constants so consumers keep referencing it here; the shared
// value also drives detection in cacheHttpClient without duplicating the string.
const CACHE_READ_DENIED_PREFIX = CacheReadDeniedMessagePrefix;
// Raised when the cache backend denies a download URL because the run's token
// has no readable cache scopes. Caching is best-effort, so restoreCache logs a
// warning and reports a cache miss rather than rethrowing this.
class CacheReadDeniedError extends Error {
constructor(message) {
super(message);
this.name = 'CacheReadDeniedError';
Object.setPrototypeOf(this, CacheReadDeniedError.prototype);
}
}
class FinalizeCacheError extends Error { class FinalizeCacheError extends Error {
constructor(message) { constructor(message) {
super(message); super(message);
@@ -126423,6 +126475,12 @@ function restoreCache(paths_1, primaryKey_1, restoreKeys_1, options_1) {
const cacheServiceVersion = config_getCacheServiceVersion(); const cacheServiceVersion = config_getCacheServiceVersion();
core_debug(`Cache service version: ${cacheServiceVersion}`); core_debug(`Cache service version: ${cacheServiceVersion}`);
checkPaths(paths); checkPaths(paths);
const cacheMode = config_getCacheMode();
if (!isCacheReadable(cacheMode)) {
info(`Cache restore skipped: the effective cache-mode '${cacheMode}' does not permit reads.`);
core_debug(`Skipped restore for paths [${paths.join(', ')}] with primary key '${primaryKey}'.`);
return undefined;
}
switch (cacheServiceVersion) { switch (cacheServiceVersion) {
case 'v2': case 'v2':
return yield restoreCacheV2(paths, primaryKey, restoreKeys, options, enableCrossOsArchive); return yield restoreCacheV2(paths, primaryKey, restoreKeys, options, enableCrossOsArchive);
@@ -126444,6 +126502,7 @@ function restoreCache(paths_1, primaryKey_1, restoreKeys_1, options_1) {
*/ */
function restoreCacheV1(paths_1, primaryKey_1, restoreKeys_1, options_1) { function restoreCacheV1(paths_1, primaryKey_1, restoreKeys_1, options_1) {
return cache_awaiter(this, arguments, void 0, function* (paths, primaryKey, restoreKeys, options, enableCrossOsArchive = false) { return cache_awaiter(this, arguments, void 0, function* (paths, primaryKey, restoreKeys, options, enableCrossOsArchive = false) {
var _a;
restoreKeys = restoreKeys || []; restoreKeys = restoreKeys || [];
const keys = [primaryKey, ...restoreKeys]; const keys = [primaryKey, ...restoreKeys];
core_debug('Resolved Keys:'); core_debug('Resolved Keys:');
@@ -126458,10 +126517,26 @@ function restoreCacheV1(paths_1, primaryKey_1, restoreKeys_1, options_1) {
let archivePath = ''; let archivePath = '';
try { try {
// path are needed to compute version // path are needed to compute version
const cacheEntry = yield getCacheEntry(keys, paths, { let cacheEntry;
compressionMethod, try {
enableCrossOsArchive cacheEntry = yield getCacheEntry(keys, paths, {
}); compressionMethod,
enableCrossOsArchive
});
}
catch (error) {
// The v1 artifact cache service returns HTTP 403 with a
// `cache read denied:` body when the run's token has no readable cache
// scopes. getCacheEntry lives in a dependency-free internal module and
// cannot import CacheReadDeniedError without a circular dependency, so it
// only surfaces the raw denial message; we classify it into the typed
// error here so the outer catch and consumers can dispatch on it.
const errorMessage = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : '';
if (errorMessage.includes(CACHE_READ_DENIED_PREFIX)) {
throw new CacheReadDeniedError(errorMessage);
}
throw error;
}
if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) { if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) {
// Cache not found // Cache not found
return undefined; return undefined;
@@ -126490,7 +126565,9 @@ function restoreCacheV1(paths_1, primaryKey_1, restoreKeys_1, options_1) {
} }
else { else {
// warn on cache restore failure and continue build // warn on cache restore failure and continue build
// Log server errors (5xx) as errors, all other errors as warnings // Log server errors (5xx) as errors, all other errors as warnings.
// A read denied by policy (CacheReadDeniedError) is not an HttpClientError
// so it falls here and is warned, treated as a cache miss.
if (typedError instanceof lib_HttpClientError && if (typedError instanceof lib_HttpClientError &&
typeof typedError.statusCode === 'number' && typeof typedError.statusCode === 'number' &&
typedError.statusCode >= 500) { typedError.statusCode >= 500) {
@@ -126525,6 +126602,7 @@ function restoreCacheV1(paths_1, primaryKey_1, restoreKeys_1, options_1) {
*/ */
function restoreCacheV2(paths_1, primaryKey_1, restoreKeys_1, options_1) { function restoreCacheV2(paths_1, primaryKey_1, restoreKeys_1, options_1) {
return cache_awaiter(this, arguments, void 0, function* (paths, primaryKey, restoreKeys, options, enableCrossOsArchive = false) { return cache_awaiter(this, arguments, void 0, function* (paths, primaryKey, restoreKeys, options, enableCrossOsArchive = false) {
var _a;
// Override UploadOptions to force the use of Azure // Override UploadOptions to force the use of Azure
options = Object.assign(Object.assign({}, options), { useAzureSdk: true }); options = Object.assign(Object.assign({}, options), { useAzureSdk: true });
restoreKeys = restoreKeys || []; restoreKeys = restoreKeys || [];
@@ -126546,7 +126624,20 @@ function restoreCacheV2(paths_1, primaryKey_1, restoreKeys_1, options_1) {
restoreKeys, restoreKeys,
version: getCacheVersion(paths, compressionMethod, enableCrossOsArchive) version: getCacheVersion(paths, compressionMethod, enableCrossOsArchive)
}; };
const response = yield twirpClient.GetCacheEntryDownloadURL(request); let response;
try {
response = yield twirpClient.GetCacheEntryDownloadURL(request);
}
catch (error) {
// The receiver returns twirp PermissionDenied (403) when the run's token
// has no readable cache scopes. The client wraps that 403, so the stable
// prefix is embedded in the message rather than leading it.
const errorMessage = (_a = error === null || error === void 0 ? void 0 : error.message) !== null && _a !== void 0 ? _a : '';
if (errorMessage.includes(CACHE_READ_DENIED_PREFIX)) {
throw new CacheReadDeniedError(errorMessage);
}
throw error;
}
if (!response.ok) { if (!response.ok) {
core_debug(`Cache not found for version ${request.version} of keys: ${keys.join(', ')}`); core_debug(`Cache not found for version ${request.version} of keys: ${keys.join(', ')}`);
return undefined; return undefined;
@@ -126581,8 +126672,10 @@ function restoreCacheV2(paths_1, primaryKey_1, restoreKeys_1, options_1) {
throw error; throw error;
} }
else { else {
// Supress all non-validation cache related errors because caching should be optional // Suppress all non-validation cache related errors because caching should be optional
// Log server errors (5xx) as errors, all other errors as warnings // Log server errors (5xx) as errors, all other errors as warnings.
// A read denied by policy (CacheReadDeniedError) is not an HttpClientError
// so it falls here and is warned, treated as a cache miss.
if (typedError instanceof lib_HttpClientError && if (typedError instanceof lib_HttpClientError &&
typeof typedError.statusCode === 'number' && typeof typedError.statusCode === 'number' &&
typedError.statusCode >= 500) { typedError.statusCode >= 500) {
@@ -126621,6 +126714,12 @@ function cache_saveCache(paths_1, key_1, options_1) {
core.debug(`Cache service version: ${cacheServiceVersion}`); core.debug(`Cache service version: ${cacheServiceVersion}`);
checkPaths(paths); checkPaths(paths);
checkKey(key); checkKey(key);
const cacheMode = getCacheMode();
if (!isCacheWritable(cacheMode)) {
core.info(`Cache save skipped: the effective cache-mode '${cacheMode}' does not permit writes.`);
core.debug(`Skipped save for paths [${paths.join(', ')}] with key '${key}'.`);
return -1;
}
switch (cacheServiceVersion) { switch (cacheServiceVersion) {
case 'v2': case 'v2':
return yield saveCacheV2(paths, key, options, enableCrossOsArchive); return yield saveCacheV2(paths, key, options, enableCrossOsArchive);
@@ -126698,17 +126797,14 @@ function saveCacheV1(paths_1, key_1, options_1) {
if (typedError.name === ValidationError.name) { if (typedError.name === ValidationError.name) {
throw error; throw error;
} }
else if (typedError.name === CacheWriteDeniedError.name) {
// Cache write was denied by policy (read-only token). Surface to the
// customer at warning level so it is visible in the workflow log
// without failing the run.
core.warning(`Failed to save: ${typedError.message}`);
}
else if (typedError.name === ReserveCacheError.name) { else if (typedError.name === ReserveCacheError.name) {
core.info(`Failed to save: ${typedError.message}`); core.info(`Failed to save: ${typedError.message}`);
} }
else { else {
// Log server errors (5xx) as errors, all other errors as warnings // Log server errors (5xx) as errors, all other errors as warnings.
// A write denied by policy (CacheWriteDeniedError) is not an
// HttpClientError and its name does not match the ReserveCacheError arm,
// so it falls here and is warned without failing the run.
if (typedError instanceof HttpClientError && if (typedError instanceof HttpClientError &&
typeof typedError.statusCode === 'number' && typeof typedError.statusCode === 'number' &&
typedError.statusCode >= 500) { typedError.statusCode >= 500) {
@@ -126819,12 +126915,6 @@ function saveCacheV2(paths_1, key_1, options_1) {
if (typedError.name === ValidationError.name) { if (typedError.name === ValidationError.name) {
throw error; throw error;
} }
else if (typedError.name === CacheWriteDeniedError.name) {
// Cache write was denied by policy (read-only token). Surface to the
// customer at warning level so it is visible in the workflow log
// without failing the run.
core.warning(`Failed to save: ${typedError.message}`);
}
else if (typedError.name === ReserveCacheError.name) { else if (typedError.name === ReserveCacheError.name) {
core.info(`Failed to save: ${typedError.message}`); core.info(`Failed to save: ${typedError.message}`);
} }
@@ -126832,7 +126922,10 @@ function saveCacheV2(paths_1, key_1, options_1) {
core.warning(typedError.message); core.warning(typedError.message);
} }
else { else {
// Log server errors (5xx) as errors, all other errors as warnings // Log server errors (5xx) as errors, all other errors as warnings.
// A write denied by policy (CacheWriteDeniedError) is not an
// HttpClientError and its name does not match the ReserveCacheError arm,
// so it falls here and is warned without failing the run.
if (typedError instanceof HttpClientError && if (typedError instanceof HttpClientError &&
typeof typedError.statusCode === 'number' && typeof typedError.statusCode === 'number' &&
typedError.statusCode >= 500) { typedError.statusCode >= 500) {
@@ -127146,6 +127239,22 @@ function renameWinArchive(javaArchivePath) {
external_fs_namespaceObject.renameSync(javaArchivePath, javaArchivePathRenamed); external_fs_namespaceObject.renameSync(javaArchivePath, javaArchivePathRenamed);
return javaArchivePathRenamed; return javaArchivePathRenamed;
} }
// Resolve the newest available stable/GA feature (major) release.
//
// Some distributions (e.g. Oracle, GraalVM) construct their download URLs from a
// concrete major version and don't expose an endpoint to list every available
// release, so a bare `latest` alias can't be resolved from their own metadata.
// The Adoptium (Temurin) API is used as a proxy for "what is the newest GA major
// version out there", which those distributions typically publish at the same time.
async function getLatestMajorVersion(http) {
const availableReleasesUrl = 'https://api.adoptium.net/v3/info/available_releases';
const response = await http.getJson(availableReleasesUrl);
const mostRecent = response.result?.most_recent_feature_release;
if (!mostRecent || Number.isNaN(Number(mostRecent))) {
throw new Error(`Could not determine the latest available Java major version from ${availableReleasesUrl}`);
}
return Number(mostRecent);
}
;// CONCATENATED MODULE: ./src/gpg.ts ;// CONCATENATED MODULE: ./src/gpg.ts
@@ -127295,12 +127404,25 @@ function generate(id, username, password, gpgPassphrase) {
} }
} }
}; };
if (gpgPassphrase) { // The maven-gpg-plugin reads the passphrase from the environment variable
const gpgServer = { // named by the `gpg.passphraseEnvName` property (default MAVEN_GPG_PASSPHRASE).
id: 'gpg.passphrase', // Only configure it when the requested env var name differs from that default;
passphrase: `\${env.${gpgPassphrase}}` // otherwise the plugin already reads the right variable and no extra settings
// are needed. Writing `gpg.passphrase` to settings.xml is deprecated and fails
// when the plugin's `bestPractices` mode is enabled.
if (gpgPassphrase &&
gpgPassphrase !== MAVEN_GPG_PASSPHRASE_DEFAULT_ENV) {
xmlObj.settings.profiles = {
profile: {
id: GPG_PASSPHRASE_PROFILE_ID,
properties: {
'gpg.passphraseEnvName': gpgPassphrase
}
}
};
xmlObj.settings.activeProfiles = {
activeProfile: GPG_PASSPHRASE_PROFILE_ID
}; };
xmlObj.settings.servers.server.push(gpgServer);
} }
return (0,lib/* create */.vt)(xmlObj).end({ return (0,lib/* create */.vt)(xmlObj).end({
headless: true, headless: true,
@@ -127335,13 +127457,11 @@ async function write(directory, settings, overwriteSettings) {
async function configureToolchains(version, distributionName, jdkHome, toolchainId) { async function configureToolchains(version, distributionName, jdkHome, toolchainId) {
const vendor = getInput(INPUT_MVN_TOOLCHAIN_VENDOR) || distributionName; const vendor = getInput(INPUT_MVN_TOOLCHAIN_VENDOR) || distributionName;
const id = toolchainId || `${vendor}_${version}`; const id = toolchainId || `${vendor}_${version}`;
const settingsDirectory = getInput(INPUT_SETTINGS_PATH) || const settingsDirectory = getInput(INPUT_SETTINGS_PATH) ||
external_path_.join(external_os_.homedir(), M2_DIR); external_path_.join(external_os_.homedir(), M2_DIR);
const overwriteSettings = util_getBooleanInput(INPUT_OVERWRITE_SETTINGS, true);
await createToolchainsSettings({ await createToolchainsSettings({
jdkInfo: { jdkInfo: {
version, version,
@@ -127349,18 +127469,17 @@ async function configureToolchains(version, distributionName, jdkHome, toolchain
id, id,
jdkHome jdkHome
}, },
settingsDirectory, settingsDirectory
overwriteSettings
}); });
} }
async function createToolchainsSettings({ jdkInfo, settingsDirectory, overwriteSettings }) { async function createToolchainsSettings({ jdkInfo, settingsDirectory }) {
info(`Creating ${MVN_TOOLCHAINS_FILE} for JDK version ${jdkInfo.version} from ${jdkInfo.vendor}`); info(`Creating ${MVN_TOOLCHAINS_FILE} for JDK version ${jdkInfo.version} from ${jdkInfo.vendor}`);
// when an alternate m2 location is specified use only that location (no .m2 directory) // when an alternate m2 location is specified use only that location (no .m2 directory)
// otherwise use the home/.m2/ path // otherwise use the home/.m2/ path
await mkdirP(settingsDirectory); await mkdirP(settingsDirectory);
const originalToolchains = await readExistingToolchainsFile(settingsDirectory); const originalToolchains = await readExistingToolchainsFile(settingsDirectory);
const updatedToolchains = generateToolchainDefinition(originalToolchains, jdkInfo.version, jdkInfo.vendor, jdkInfo.id, jdkInfo.jdkHome); const updatedToolchains = generateToolchainDefinition(originalToolchains, jdkInfo.version, jdkInfo.vendor, jdkInfo.id, jdkInfo.jdkHome);
await writeToolchainsFileToDisk(settingsDirectory, updatedToolchains, overwriteSettings); await writeToolchainsFileToDisk(settingsDirectory, updatedToolchains);
} }
// only exported for testing purposes // only exported for testing purposes
function generateToolchainDefinition(original, version, vendor, id, jdkHome) { function generateToolchainDefinition(original, version, vendor, id, jdkHome) {
@@ -127442,18 +127561,20 @@ async function readExistingToolchainsFile(directory) {
} }
return ''; return '';
} }
async function writeToolchainsFileToDisk(directory, settings, overwriteSettings) { async function writeToolchainsFileToDisk(directory, settings) {
const location = external_path_.join(directory, MVN_TOOLCHAINS_FILE); const location = external_path_.join(directory, MVN_TOOLCHAINS_FILE);
const settingsExists = external_fs_namespaceObject.existsSync(location); const settingsExists = external_fs_namespaceObject.existsSync(location);
if (settingsExists && overwriteSettings) { // The toolchains file is produced by a non-destructive merge (existing JDK,
info(`Overwriting existing file ${location}`); // custom, and non-jdk toolchains are preserved see generateToolchainDefinition),
} // so it is always safe to write it. Unlike settings.xml, it is therefore not
else if (!settingsExists) { // gated behind the `overwrite-settings` input; that would prevent subsequent
info(`Writing to ${location}`); // setup-java runs from registering additional JDKs and silently drop the
// toolchain entries created by earlier runs.
if (settingsExists) {
info(`Updating existing file ${location}`);
} }
else { else {
info(`Skipping generation of ${location} because file already exists and overwriting is not enabled`); info(`Writing to ${location}`);
return;
} }
return external_fs_namespaceObject.writeFileSync(location, settings, { return external_fs_namespaceObject.writeFileSync(location, settings, {
encoding: 'utf-8', encoding: 'utf-8',
@@ -130872,23 +130993,28 @@ const CACHE_KEY_PREFIX = 'setup-java';
const supportedPackageManager = [ const supportedPackageManager = [
{ {
id: 'maven', id: 'maven',
path: [ path: [(0,external_path_.join)(external_os_default().homedir(), '.m2', 'repository')],
(0,external_path_.join)(external_os_default().homedir(), '.m2', 'repository'),
(0,external_path_.join)(external_os_default().homedir(), '.m2', 'wrapper', 'dists')
],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven
pattern: [ pattern: [
'**/pom.xml', '**/pom.xml',
'**/.mvn/wrapper/maven-wrapper.properties', '**/.mvn/wrapper/maven-wrapper.properties',
'**/.mvn/extensions.xml' '**/.mvn/extensions.xml'
],
// The Maven wrapper distribution only depends on the wrapper properties,
// which change very rarely, so it is cached separately from the local
// repository. This keeps it available across the frequent pom.xml changes
// that rotate the main cache key. See issue #1095.
additionalCaches: [
{
name: 'maven-wrapper',
path: [(0,external_path_.join)(external_os_default().homedir(), '.m2', 'wrapper', 'dists')],
pattern: ['**/.mvn/wrapper/maven-wrapper.properties']
}
] ]
}, },
{ {
id: 'gradle', id: 'gradle',
path: [ path: [(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'caches')],
(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'caches'),
(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'wrapper')
],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle
pattern: [ pattern: [
'**/*.gradle*', '**/*.gradle*',
@@ -130897,6 +131023,17 @@ const supportedPackageManager = [
'buildSrc/**/Dependencies.kt', 'buildSrc/**/Dependencies.kt',
'gradle/*.versions.toml', 'gradle/*.versions.toml',
'**/versions.properties' '**/versions.properties'
],
// The Gradle wrapper distribution only depends on the wrapper properties,
// which change very rarely, so it is cached separately from the Gradle
// caches. This keeps it available across the frequent *.gradle* changes
// that rotate the main cache key. See issue #269.
additionalCaches: [
{
name: 'gradle-wrapper',
path: [(0,external_path_.join)(external_os_default().homedir(), '.gradle', 'wrapper')],
pattern: ['**/gradle-wrapper.properties']
}
] ]
}, },
{ {
@@ -130932,6 +131069,19 @@ function findPackageManager(id) {
} }
return packageManager; return packageManager;
} }
/**
* State keys used to carry an additional cache's restore-time information over
* to the post (save) action, scoped by the additional cache name.
*/
function additionalCachePrimaryKeyState(name) {
return `${STATE_CACHE_PRIMARY_KEY}-${name}`;
}
function additionalCacheMatchedKeyState(name) {
return `${CACHE_MATCHED_KEY}-${name}`;
}
function buildCacheKey(id, fileHash) {
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${id}-${fileHash}`;
}
/** /**
* A function that generates a cache key to use. * A function that generates a cache key to use.
* Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"". * Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"".
@@ -130945,7 +131095,19 @@ async function computeCacheKey(packageManager, cacheDependencyPath) {
if (!fileHash) { if (!fileHash) {
throw new Error(`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`); throw new Error(`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`);
} }
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${packageManager.id}-${fileHash}`; return buildCacheKey(packageManager.id, fileHash);
}
/**
* Computes the cache key for an additional cache. Unlike {@link computeCacheKey}
* this returns undefined (instead of throwing) when no file matches the pattern,
* because additional caches are optional features that many projects do not use.
*/
async function computeAdditionalCacheKey(additionalCache) {
const fileHash = await lib_glob_hashFiles(additionalCache.pattern.join('\n'));
if (!fileHash) {
return undefined;
}
return buildCacheKey(additionalCache.name, fileHash);
} }
/** /**
* Restore the dependency cache * Restore the dependency cache
@@ -130969,6 +131131,29 @@ async function restore(id, cacheDependencyPath) {
setOutput('cache-hit', false); setOutput('cache-hit', false);
info(`${packageManager.id} cache is not found`); info(`${packageManager.id} cache is not found`);
} }
for (const additionalCache of packageManager.additionalCaches ?? []) {
await restoreAdditionalCache(additionalCache);
}
}
/**
* Restore an additional cache (e.g. a build-tool wrapper distribution) that is
* keyed independently of the main dependency cache so that it survives changes
* to volatile dependency files. Skips silently when the project does not use
* the corresponding feature.
*/
async function restoreAdditionalCache(additionalCache) {
const primaryKey = await computeAdditionalCacheKey(additionalCache);
if (!primaryKey) {
core_debug(`No file matched [${additionalCache.pattern}] for the ${additionalCache.name} cache, skipping.`);
return;
}
core_debug(`${additionalCache.name} primary key is ${primaryKey}`);
saveState(additionalCachePrimaryKeyState(additionalCache.name), primaryKey);
const matchedKey = await restoreCache(additionalCache.path, primaryKey);
if (matchedKey) {
saveState(additionalCacheMatchedKeyState(additionalCache.name), matchedKey);
info(`${additionalCache.name} cache restored from key: ${matchedKey}`);
}
} }
/** /**
* Save the dependency cache * Save the dependency cache
@@ -130979,6 +131164,9 @@ async function save(id) {
const matchedKey = core.getState(CACHE_MATCHED_KEY); const matchedKey = core.getState(CACHE_MATCHED_KEY);
// Inputs are re-evaluated before the post action, so we want the original key used for restore // Inputs are re-evaluated before the post action, so we want the original key used for restore
const primaryKey = core.getState(STATE_CACHE_PRIMARY_KEY); const primaryKey = core.getState(STATE_CACHE_PRIMARY_KEY);
for (const additionalCache of packageManager.additionalCaches ?? []) {
await saveAdditionalCache(packageManager, additionalCache);
}
if (!primaryKey) { if (!primaryKey) {
core.warning('Error retrieving key from state.'); core.warning('Error retrieving key from state.');
return; return;
@@ -131013,6 +131201,50 @@ async function save(id) {
} }
} }
} }
/**
* Save an additional cache under its own key. Skips when no key was recorded at
* restore time (feature unused) or when the exact key was already restored.
*/
async function saveAdditionalCache(packageManager, additionalCache) {
const primaryKey = core.getState(additionalCachePrimaryKeyState(additionalCache.name));
const matchedKey = core.getState(additionalCacheMatchedKeyState(additionalCache.name));
if (!primaryKey) {
// The feature is not used by this project, nothing to save.
core.debug(`No primary key for the ${additionalCache.name} cache, not saving cache.`);
return;
}
else if (matchedKey === primaryKey) {
core.info(`Cache hit occurred on the ${additionalCache.name} primary key ${primaryKey}, not saving cache.`);
return;
}
try {
const cacheId = await cache.saveCache(additionalCache.path, primaryKey);
if (cacheId === -1) {
core.debug(`${additionalCache.name} cache was not saved for the key: ${primaryKey}`);
return;
}
core.info(`${additionalCache.name} cache saved with the key: ${primaryKey}`);
}
catch (error) {
const err = error;
if (err.name === cache.ValidationError.name) {
// The cache paths did not resolve, e.g. the wrapper distribution was
// never downloaded because a system build tool was used or the download
// failed. Optional wrapper caches must not fail the post step, so skip.
core.debug(`${additionalCache.name} cache paths do not exist, not saving cache: ${err.message}`);
return;
}
if (err.name === cache.ReserveCacheError.name) {
core.info(err.message);
}
else {
if (isProbablyGradleDaemonProblem(packageManager, err)) {
core.warning('Failed to save Gradle cache on Windows. If tar.exe reported "Permission denied", try to run Gradle with `--no-daemon` option. Refer to https://github.com/actions/cache/issues/454 for details.');
}
throw error;
}
}
}
/** /**
* @param packageManager the specified package manager by user * @param packageManager the specified package manager by user
* @param error the error thrown by the saveCache * @param error the error thrown by the saveCache
@@ -131045,6 +131277,7 @@ class JavaBase {
architecture; architecture;
packageType; packageType;
stable; stable;
latest;
checkLatest; checkLatest;
setDefault; setDefault;
verifySignature; verifySignature;
@@ -131055,7 +131288,11 @@ class JavaBase {
allowRetries: true, allowRetries: true,
maxRetries: 3 maxRetries: 3
}); });
({ version: this.version, stable: this.stable } = this.normalizeVersion(installerOptions.version)); ({
version: this.version,
stable: this.stable,
latest: this.latest
} = this.normalizeVersion(installerOptions.version));
this.architecture = installerOptions.architecture || external_os_default().arch(); this.architecture = installerOptions.architecture || external_os_default().arch();
this.packageType = installerOptions.packageType; this.packageType = installerOptions.packageType;
this.checkLatest = installerOptions.checkLatest; this.checkLatest = installerOptions.checkLatest;
@@ -131071,7 +131308,7 @@ class JavaBase {
throw new Error(`Input 'verify-signature' is not supported for distribution '${this.distribution}'.`); throw new Error(`Input 'verify-signature' is not supported for distribution '${this.distribution}'.`);
} }
let foundJava = this.findInToolcache(); let foundJava = this.findInToolcache();
if (foundJava && !this.checkLatest) { if (foundJava && !this.checkLatest && !this.latest) {
info(`Resolved Java ${foundJava.version} from tool-cache`); info(`Resolved Java ${foundJava.version} from tool-cache`);
} }
else { else {
@@ -131249,6 +131486,26 @@ class JavaBase {
} }
normalizeVersion(version) { normalizeVersion(version) {
let stable = true; let stable = true;
const latest = false;
// Support the `latest` alias (case-insensitive), which floats to the newest
// available stable/GA release. It is translated to the SemVer wildcard `x`
// so the existing "newest satisfying version wins" resolution applies.
const normalized = version.trim().toLowerCase();
if (normalized === 'latest') {
return {
version: 'x',
stable: true,
latest: true
};
}
// Reject `latest` combined with any qualifier (e.g. `latest-ea`). Such inputs
// would otherwise have their `-ea` suffix stripped and fall through to the
// generic SemVer check, which fails with a confusing "'latest' is not valid
// SemVer" message even though `latest` is a supported value. Fail early with a
// targeted explanation instead.
if (normalized.startsWith('latest')) {
throw new Error(`The 'latest' alias resolves stable (GA) releases only and cannot be combined with '-ea' or other qualifiers (received '${version}'). Use 'latest' on its own, or specify a concrete version.`);
}
if (version.endsWith('-ea')) { if (version.endsWith('-ea')) {
version = version.replace(/-ea$/, ''); version = version.replace(/-ea$/, '');
stable = false; stable = false;
@@ -131258,12 +131515,21 @@ class JavaBase {
version = version.replace('-ea.', '+'); version = version.replace('-ea.', '+');
stable = false; stable = false;
} }
// Java uses a versioning scheme (JEP 322) that can contain more numeric
// fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such
// exact versions to SemVer build notation ('18.0.1+1') so they are
// accepted. Ranges and versions that already carry build metadata are
// left untouched.
if (/^\d+(\.\d+){3,}$/.test(version)) {
version = convertVersionToSemver(version);
}
if (!semver_default().validRange(version)) { if (!semver_default().validRange(version)) {
throw new Error(`The string '${version}' is not valid SemVer notation for a Java version. Please check README file for code snippets and more detailed information`); throw new Error(`The string '${version}' is not valid SemVer notation for a Java version. Please check README file for code snippets and more detailed information`);
} }
return { return {
version, version,
stable stable,
latest
}; };
} }
createVersionNotFoundError(versionOrRange, availableVersions, additionalContext) { createVersionNotFoundError(versionOrRange, availableVersions, additionalContext) {
@@ -131337,6 +131603,9 @@ class LocalDistribution extends JavaBase {
this.jdkFile = jdkFile; this.jdkFile = jdkFile;
} }
async setupJava() { async setupJava() {
if (this.latest) {
throw new Error("The 'latest' version alias is not supported for the 'jdkfile' distribution. Please specify a concrete version.");
}
let foundJava = this.findInToolcache(); let foundJava = this.findInToolcache();
if (foundJava) { if (foundJava) {
info(`Resolved Java ${foundJava.version} from tool-cache`); info(`Resolved Java ${foundJava.version} from tool-cache`);
@@ -132041,6 +132310,129 @@ class LibericaDistributions extends JavaBase {
} }
} }
;// CONCATENATED MODULE: ./src/distributions/liberica-nik/installer.ts
const installer_supportedPlatform = `'linux', 'macos', 'windows'`;
const installer_supportedArchitectures = `'x64', 'aarch64'`;
class LibericaNikDistributions extends JavaBase {
constructor(installerOptions) {
super('Liberica_NIK', installerOptions);
}
async downloadTool(javaRelease) {
info(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await downloadTool(javaRelease.url);
info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
if (process.platform === 'win32') {
javaArchivePath = renameWinArchive(javaArchivePath);
}
const extractedJavaPath = await extractJdkFile(javaArchivePath, extension);
const archiveName = external_fs_default().readdirSync(extractedJavaPath)[0];
const archivePath = external_path_default().join(extractedJavaPath, archiveName);
const javaPath = await cacheDir(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture);
return { version: javaRelease.version, path: javaPath };
}
async findPackageForDownload(range) {
const availableVersionsRaw = await this.getAvailableVersions();
const availableVersions = availableVersionsRaw
.map(item => {
const jdkVersion = this.getJdkVersion(item);
return jdkVersion ? { url: item.downloadUrl, version: jdkVersion } : null;
})
.filter((item) => item !== null);
const satisfiedVersion = availableVersions
.filter(item => isVersionSatisfies(range, item.version))
.sort((a, b) => -semver_default().compareBuild(a.version, b.version))[0];
if (!satisfiedVersion) {
const availableVersionStrings = availableVersions.map(item => item.version);
throw this.createVersionNotFoundError(range, availableVersionStrings);
}
return satisfiedVersion;
}
async getAvailableVersions() {
if (isDebug()) {
console.time('Retrieving available versions for Liberica NIK took'); // eslint-disable-line no-console
}
const url = this.prepareAvailableVersionsUrl();
core_debug(`Gathering available versions from '${url}'`);
const availableVersions = (await this.http.getJson(url)).result ?? [];
if (isDebug()) {
startGroup('Print information about available versions');
console.timeEnd('Retrieving available versions for Liberica NIK took'); // eslint-disable-line no-console
core_debug(`Available versions: [${availableVersions.length}]`);
core_debug(availableVersions.map(item => item.version).join(', '));
endGroup();
}
return availableVersions;
}
prepareAvailableVersionsUrl() {
const urlOptions = {
os: this.getPlatformOption(),
'bundle-type': this.getBundleType(),
...this.getArchitectureOptions(),
'build-type': this.stable ? 'all' : 'ea',
'installation-type': 'archive',
fields: 'downloadUrl,version,components,component,embedded'
};
const searchParams = new URLSearchParams(urlOptions).toString();
return `https://api.bell-sw.com/v1/nik/releases?${searchParams}`;
}
// NIK's top-level `version` is the GraalVM/NIK version; the JDK version that
// users select on lives in the embedded `liberica` component.
getJdkVersion(release) {
const liberica = release.components?.find(component => component.component === 'liberica');
return liberica ? this.convertVersionToSemver(liberica.version) : null;
}
// The `full` bundle adds JavaFX/Swing GUI support; otherwise use `standard`.
getBundleType() {
const [, feature] = this.packageType.split('+');
return feature?.includes('fx') ? 'full' : 'standard';
}
getArchitectureOptions() {
const arch = this.distributionArchitecture();
switch (arch) {
case 'x64':
return { bitness: '64', arch: 'x86' };
case 'aarch64':
return { bitness: '64', arch: 'arm' };
default:
throw new Error(`Architecture '${this.architecture}' is not supported. Supported architectures: ${installer_supportedArchitectures}`);
}
}
getPlatformOption(platform = process.platform) {
switch (platform) {
case 'darwin':
return 'macos';
case 'win32':
case 'cygwin':
return 'windows';
case 'linux':
return 'linux';
default:
throw new Error(`Platform '${platform}' is not supported. Supported platforms: ${installer_supportedPlatform}`);
}
}
// JDK versions come as strings like '25.0.1+16', '23+38' or '11.0.15.1+2'.
// Normalize them to valid SemVer while preserving build metadata so newer
// NIK builds of the same JDK sort ahead of older ones.
convertVersionToSemver(jdkVersion) {
const [main, build] = jdkVersion.split('+');
const parts = main.split('.');
while (parts.length < 3) {
parts.push('0');
}
const base = parts.slice(0, 3).join('.');
const buildMeta = [...parts.slice(3), ...(build ? [build] : [])];
return buildMeta.length ? `${base}+${buildMeta.join('.')}` : base;
}
}
;// CONCATENATED MODULE: ./src/distributions/microsoft/microsoft-key.ts ;// CONCATENATED MODULE: ./src/distributions/microsoft/microsoft-key.ts
// Microsoft Build of OpenJDK GPG signing key // Microsoft Build of OpenJDK GPG signing key
// Retrieved from: https://download.visualstudio.microsoft.com/download/pr/b90071e2-e0cf-4411-98be-dbeb09d67bf0/8622862bcd54206e158c5abca0582c9b/464279_464280_aoc_20210208.asc // Retrieved from: https://download.visualstudio.microsoft.com/download/pr/b90071e2-e0cf-4411-98be-dbeb09d67bf0/8622862bcd54206e158c5abca0582c9b/464279_464280_aoc_20210208.asc
@@ -132181,7 +132573,7 @@ class MicrosoftDistributions extends JavaBase {
const installer_supportedArchitectures = [ const semeru_installer_supportedArchitectures = [
'x64', 'x64',
'x86', 'x86',
'ppc64le', 'ppc64le',
@@ -132195,8 +132587,8 @@ class SemeruDistribution extends JavaBase {
} }
async findPackageForDownload(version) { async findPackageForDownload(version) {
const arch = this.distributionArchitecture(); const arch = this.distributionArchitecture();
if (!installer_supportedArchitectures.includes(arch)) { if (!semeru_installer_supportedArchitectures.includes(arch)) {
throw new Error(`Unsupported architecture for IBM Semeru: ${this.architecture} for your current OS version, the following are supported: ${installer_supportedArchitectures.join(', ')}`); throw new Error(`Unsupported architecture for IBM Semeru: ${this.architecture} for your current OS version, the following are supported: ${semeru_installer_supportedArchitectures.join(', ')}`);
} }
if (!this.stable) { if (!this.stable) {
throw new Error('IBM Semeru does not provide builds for early access versions'); throw new Error('IBM Semeru does not provide builds for early access versions');
@@ -132352,10 +132744,22 @@ class CorrettoDistribution extends JavaBase {
if (!this.stable) { if (!this.stable) {
throw new Error('Early access versions are not supported'); throw new Error('Early access versions are not supported');
} }
const availableVersions = await this.getAvailableVersions();
// The `latest` alias is normalized to the SemVer wildcard, but Corretto
// matches on an exact major version, so resolve it to the newest available
// major from Corretto's own list.
if (this.latest) {
const majors = availableVersions
.map(item => parseInt(item.version, 10))
.filter(major => Number.isFinite(major) && major > 0);
if (majors.length === 0) {
throw new Error('Could not determine the latest available Corretto major version from remote metadata');
}
version = Math.max(...majors).toString();
}
if (version.includes('.')) { if (version.includes('.')) {
throw new Error('Only major versions are supported'); throw new Error('Only major versions are supported');
} }
const availableVersions = await this.getAvailableVersions();
const matchingVersions = availableVersions const matchingVersions = availableVersions
.filter(item => item.version == version) .filter(item => item.version == version)
.map(item => { .map(item => {
@@ -132483,6 +132887,13 @@ class OracleDistribution extends JavaBase {
} }
const platform = this.getPlatform(); const platform = this.getPlatform();
const extension = getDownloadArchiveExtension(); const extension = getDownloadArchiveExtension();
// The `latest` alias is normalized to the SemVer wildcard. Oracle builds its
// download URLs from a concrete major and has no endpoint to list releases,
// so resolve the newest available GA major from the Adoptium API and use it.
if (this.latest) {
const latestMajor = await getLatestMajorVersion(this.http);
range = latestMajor.toString();
}
const isOnlyMajorProvided = !range.includes('.'); const isOnlyMajorProvided = !range.includes('.');
const major = isOnlyMajorProvided ? range : range.split('.')[0]; const major = isOnlyMajorProvided ? range : range.split('.')[0];
const possibleUrls = []; const possibleUrls = [];
@@ -132509,6 +132920,11 @@ class OracleDistribution extends JavaBase {
throw new Error(`Http request for Oracle JDK failed with status code: ${response.message.statusCode}`); throw new Error(`Http request for Oracle JDK failed with status code: ${response.message.statusCode}`);
} }
} }
if (this.latest) {
const error = this.createVersionNotFoundError(range);
error.message += `\nThe latest Java major version (${range}) is not yet available for the Oracle JDK distribution. Please specify a concrete version instead of 'latest'.`;
throw error;
}
throw this.createVersionNotFoundError(range); throw this.createVersionNotFoundError(range);
} }
getPlatform(platform = process.platform) { getPlatform(platform = process.platform) {
@@ -132907,6 +133323,12 @@ class GraalVMDistribution extends JavaBase {
if (!this.stable) { if (!this.stable) {
return this.findEABuildDownloadUrl(`${range}-ea`); return this.findEABuildDownloadUrl(`${range}-ea`);
} }
// The `latest` alias is normalized to the SemVer wildcard. Oracle GraalVM
// builds its download URLs from a concrete major and has no endpoint to list
// releases, so resolve the newest available GA major from the Adoptium API.
if (this.latest) {
range = (await getLatestMajorVersion(this.http)).toString();
}
const { platform, extension, major } = this.validateStableBuildRequest(range); const { platform, extension, major } = this.validateStableBuildRequest(range);
const fileUrl = this.constructFileUrl(range, major, platform, arch, extension); const fileUrl = this.constructFileUrl(range, major, platform, arch, extension);
const response = await this.http.head(fileUrl); const response = await this.http.head(fileUrl);
@@ -132955,6 +133377,9 @@ class GraalVMDistribution extends JavaBase {
if (statusCode === HttpCodes.NotFound) { if (statusCode === HttpCodes.NotFound) {
// Create the standard error with additional hint about checking the download URL // Create the standard error with additional hint about checking the download URL
const error = this.createVersionNotFoundError(range); const error = this.createVersionNotFoundError(range);
if (this.latest) {
error.message += `\nThe latest Java major version (${range}) is not yet available for the ${this.distribution} distribution. Please specify a concrete version instead of 'latest'.`;
}
error.message += `\nPlease check if this version is available at ${GRAALVM_DOWNLOAD_URL} . Pick a version from the list.`; error.message += `\nPlease check if this version is available at ${GRAALVM_DOWNLOAD_URL} . Pick a version from the list.`;
throw error; throw error;
} }
@@ -133042,7 +133467,24 @@ class GraalVMCommunityDistribution extends GraalVMDistribution {
throw new Error('GraalVM Community does not provide early access builds'); throw new Error('GraalVM Community does not provide early access builds');
} }
const arch = this.getSupportedArchitecture(); const arch = this.getSupportedArchitecture();
const { platform, extension } = this.validateStableBuildRequest(range); // GraalVM Community publishes its releases on GitHub, so the `latest` alias
// (normalized to the SemVer wildcard `x`) can float to the newest GA it
// actually ships. Unlike Oracle GraalVM (which has no listing endpoint and
// must derive the newest major from the Adoptium API), we match against the
// real release list here, so `latest` never fails when GraalVM lags behind a
// brand-new Java major.
let platform;
let extension;
if (this.latest) {
if (this.packageType !== 'jdk') {
throw new Error(`${this.distribution} provides only the \`jdk\` package type`);
}
platform = this.getPlatform();
extension = getDownloadArchiveExtension();
}
else {
({ platform, extension } = this.validateStableBuildRequest(range));
}
// GraalVM Community asset names embed the platform, architecture and // GraalVM Community asset names embed the platform, architecture and
// archive type, e.g. `graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz`. // archive type, e.g. `graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz`.
const assetSuffix = `_${platform}-${arch}_bin.${extension}`; const assetSuffix = `_${platform}-${arch}_bin.${extension}`;
@@ -133436,6 +133878,7 @@ class KonaDistribution extends JavaBase {
var JavaDistribution; var JavaDistribution;
(function (JavaDistribution) { (function (JavaDistribution) {
JavaDistribution["Adopt"] = "adopt"; JavaDistribution["Adopt"] = "adopt";
@@ -133444,6 +133887,7 @@ var JavaDistribution;
JavaDistribution["Temurin"] = "temurin"; JavaDistribution["Temurin"] = "temurin";
JavaDistribution["Zulu"] = "zulu"; JavaDistribution["Zulu"] = "zulu";
JavaDistribution["Liberica"] = "liberica"; JavaDistribution["Liberica"] = "liberica";
JavaDistribution["LibericaNik"] = "liberica-nik";
JavaDistribution["JdkFile"] = "jdkfile"; JavaDistribution["JdkFile"] = "jdkfile";
JavaDistribution["Microsoft"] = "microsoft"; JavaDistribution["Microsoft"] = "microsoft";
JavaDistribution["Semeru"] = "semeru"; JavaDistribution["Semeru"] = "semeru";
@@ -133471,6 +133915,8 @@ function getJavaDistribution(distributionName, installerOptions, jdkFile) {
return new ZuluDistribution(installerOptions); return new ZuluDistribution(installerOptions);
case JavaDistribution.Liberica: case JavaDistribution.Liberica:
return new LibericaDistributions(installerOptions); return new LibericaDistributions(installerOptions);
case JavaDistribution.LibericaNik:
return new LibericaNikDistributions(installerOptions);
case JavaDistribution.Microsoft: case JavaDistribution.Microsoft:
return new MicrosoftDistributions(installerOptions); return new MicrosoftDistributions(installerOptions);
case JavaDistribution.Semeru: case JavaDistribution.Semeru:
@@ -133543,6 +133989,19 @@ function configureMavenArgs() {
`Set '${INPUT_SHOW_DOWNLOAD_PROGRESS}: true' to keep the download progress output.`); `Set '${INPUT_SHOW_DOWNLOAD_PROGRESS}: true' to keep the download progress output.`);
} }
;// CONCATENATED MODULE: ./src/problem-matcher.ts
function configureProblemMatcher(matcherPath) {
const problemMatcherEnabled = util_getBooleanInput(INPUT_PROBLEM_MATCHER, true);
if (!problemMatcherEnabled) {
core_debug('Java problem matcher is disabled');
return;
}
info(`##[add-matcher]${matcherPath}`);
}
;// CONCATENATED MODULE: ./src/setup-java.ts ;// CONCATENATED MODULE: ./src/setup-java.ts
@@ -133555,6 +134014,7 @@ function configureMavenArgs() {
async function run() { async function run() {
try { try {
const versions = getMultilineInput(INPUT_JAVA_VERSION); const versions = getMultilineInput(INPUT_JAVA_VERSION);
@@ -133628,7 +134088,7 @@ async function run() {
} }
endGroup(); endGroup();
const matchersPath = external_path_.join(external_path_.dirname((0,external_url_.fileURLToPath)(import.meta.url)), '..', '..', '.github'); const matchersPath = external_path_.join(external_path_.dirname((0,external_url_.fileURLToPath)(import.meta.url)), '..', '..', '.github');
info(`##[add-matcher]${external_path_.join(matchersPath, 'java.json')}`); configureProblemMatcher(external_path_.join(matchersPath, 'java.json'));
await configureAuthentication(); await configureAuthentication();
configureMavenArgs(); configureMavenArgs();
if (cache && isCacheFeatureAvailable()) { if (cache && isCacheFeatureAvailable()) {
@@ -133664,7 +134124,11 @@ async function installVersion(version, options, toolchainId = 0) {
throw new Error(`No supported distribution was found for input ${distributionName}`); throw new Error(`No supported distribution was found for input ${distributionName}`);
} }
const result = await distribution.setupJava(); const result = await distribution.setupJava();
await configureToolchains(version, distributionName, result.path, toolchainIds[toolchainId]); // When the `latest` alias is used, the literal input isn't a real version, so
// pass the resolved version to the toolchains configuration instead.
const isLatest = version.trim().toLowerCase() === 'latest';
const toolchainVersion = isLatest ? result.version : version;
await configureToolchains(toolchainVersion, distributionName, result.path, toolchainIds[toolchainId]);
info(''); info('');
info('Java configuration:'); info('Java configuration:');
info(` Distribution: ${distributionName}`); info(` Distribution: ${distributionName}`);
+493 -319
View File
File diff suppressed because it is too large Load Diff
+16 -16
View File
@@ -4,13 +4,13 @@ The major breaking change in V2 is the new mandatory `distribution` input. This
Use the `zulu` keyword if you would like to continue using the same distribution as in V1. Use the `zulu` keyword if you would like to continue using the same distribution as in V1.
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/setup-java@v2 - uses: actions/setup-java@v2
with: with:
distribution: 'zulu' distribution: 'zulu'
java-version: '11' java-version: '11'
java-package: jdk # optional (jdk or jre) - defaults to jdk java-package: jdk # optional (jdk or jre) - defaults to jdk
- run: java -cp java HelloWorldApp - run: java -cp java HelloWorldApp
``` ```
**General recommendation** — configure CI with the same distribution that is used on your local dev machine. **General recommendation** — configure CI with the same distribution that is used on your local dev machine.
@@ -19,15 +19,15 @@ steps:
Since the `distribution` input is required in V2, you should specify it using `jdkfile` to continue installing Java from a local file on the runner Since the `distribution` input is required in V2, you should specify it using `jdkfile` to continue installing Java from a local file on the runner
```yaml ```yaml
steps: steps:
- run: | - run: |
download_url="https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-jdk_x64_linux_hotspot_11.0.10_9.tar.gz" download_url="https://github.com/AdoptOpenJDK/openjdk11-binaries/releases/download/jdk-11.0.10%2B9/OpenJDK11U-jdk_x64_linux_hotspot_11.0.10_9.tar.gz"
wget -O $RUNNER_TEMP/java_package.tar.gz $download_url wget -O $RUNNER_TEMP/java_package.tar.gz $download_url
- uses: actions/setup-java@v2 - uses: actions/setup-java@v2
with: with:
distribution: 'jdkfile' distribution: 'jdkfile'
jdkFile: ${{ runner.temp }}/java_package.tar.gz jdkFile: ${{ runner.temp }}/java_package.tar.gz
java-version: '11.0.0' java-version: '11.0.0'
architecture: x64 architecture: x64
``` ```
## Dropping legacy Java version syntax 1.x ## Dropping legacy Java version syntax 1.x
+403 -145
View File
@@ -9,7 +9,7 @@
"version": "6.0.0", "version": "6.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/cache": "^6.1.0", "@actions/cache": "^6.2.0",
"@actions/core": "^3.0.1", "@actions/core": "^3.0.1",
"@actions/exec": "^3.0.0", "@actions/exec": "^3.0.0",
"@actions/glob": "^0.7.0", "@actions/glob": "^0.7.0",
@@ -22,20 +22,20 @@
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@jest/globals": "^30.4.1", "@jest/globals": "^30.4.1",
"@types/node": "^26.0.0", "@types/node": "^26.1.1",
"@types/semver": "^7.7.0", "@types/semver": "^7.7.0",
"@typescript-eslint/eslint-plugin": "^8.62.0", "@typescript-eslint/eslint-plugin": "^8.62.0",
"@typescript-eslint/parser": "^8.62.0", "@typescript-eslint/parser": "^8.62.0",
"@vercel/ncc": "^0.44.0", "@vercel/ncc": "^0.44.0",
"eslint": "^10.5.0", "eslint": "^10.7.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-jest": "^29.15.4", "eslint-plugin-jest": "^29.15.4",
"eslint-plugin-n": "^18.1.0", "eslint-plugin-n": "^18.2.2",
"globals": "^17.7.0", "globals": "^17.7.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"jest": "^30.4.2", "jest": "^30.4.2",
"lint-staged": "^17.0.8", "lint-staged": "^17.0.8",
"prettier": "^3.9.1", "prettier": "^3.9.5",
"ts-jest": "^29.4.11", "ts-jest": "^29.4.11",
"typescript": "^6.0.3" "typescript": "^6.0.3"
}, },
@@ -44,9 +44,9 @@
} }
}, },
"node_modules/@actions/cache": { "node_modules/@actions/cache": {
"version": "6.1.0", "version": "6.2.0",
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-6.1.0.tgz", "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-6.2.0.tgz",
"integrity": "sha512-LVqybSbzhBp2uAETOQ3HnVjXA4AcjavgMH+LCr+cjgO+PZfciv/1QAgoW+esXBaAhvDid+vXeV70GGJpAh4V5Q==", "integrity": "sha512-Nv0xWRmbxfDbAn/70flO/F6tj2Nv4XTYMAsQHiDFSojCDfso/Zni+fRKa14ToI9hnmOW/rQcY1WYb6wsM7Pgwg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^3.0.1", "@actions/core": "^3.0.1",
@@ -1831,9 +1831,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "26.1.0", "version": "26.1.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.1.tgz",
"integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", "integrity": "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -1900,6 +1900,189 @@
"typescript": ">=4.8.4 <6.1.0" "typescript": ">=4.8.4 <6.1.0"
} }
}, },
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz",
"integrity": "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.63.0",
"@typescript-eslint/typescript-estree": "8.63.0",
"@typescript-eslint/utils": "8.63.0",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz",
"integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.63.0",
"@typescript-eslint/tsconfig-utils": "8.63.0",
"@typescript-eslint/types": "8.63.0",
"@typescript-eslint/visitor-keys": "8.63.0",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz",
"integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.63.0",
"@typescript-eslint/types": "^8.63.0",
"debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz",
"integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz",
"integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.63.0",
"@typescript-eslint/types": "8.63.0",
"@typescript-eslint/typescript-estree": "8.63.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz",
"integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.63.0",
"@typescript-eslint/tsconfig-utils": "8.63.0",
"@typescript-eslint/types": "8.63.0",
"@typescript-eslint/visitor-keys": "8.63.0",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz",
"integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.63.0",
"@typescript-eslint/types": "^8.63.0",
"debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz",
"integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.63.0", "version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz",
@@ -1925,7 +2108,35 @@
"typescript": ">=4.8.4 <6.1.0" "typescript": ">=4.8.4 <6.1.0"
} }
}, },
"node_modules/@typescript-eslint/project-service": { "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz",
"integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.63.0",
"@typescript-eslint/tsconfig-utils": "8.63.0",
"@typescript-eslint/types": "8.63.0",
"@typescript-eslint/visitor-keys": "8.63.0",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": {
"version": "8.63.0", "version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz",
"integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==", "integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==",
@@ -1947,6 +2158,23 @@
"typescript": ">=4.8.4 <6.1.0" "typescript": ">=4.8.4 <6.1.0"
} }
}, },
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz",
"integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.63.0", "version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz",
@@ -1965,48 +2193,6 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz",
"integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz",
"integrity": "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.63.0",
"@typescript-eslint/typescript-estree": "8.63.0",
"@typescript-eslint/utils": "8.63.0",
"debug": "^4.4.3",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.63.0", "version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz",
@@ -2021,58 +2207,6 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz",
"integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.63.0",
"@typescript-eslint/tsconfig-utils": "8.63.0",
"@typescript-eslint/types": "8.63.0",
"@typescript-eslint/visitor-keys": "8.63.0",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz",
"integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.63.0",
"@typescript-eslint/types": "8.63.0",
"@typescript-eslint/typescript-estree": "8.63.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.63.0", "version": "8.63.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz",
@@ -2231,9 +2365,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2248,9 +2379,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2265,9 +2393,6 @@
"loong64" "loong64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2282,9 +2407,6 @@
"loong64" "loong64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2299,9 +2421,6 @@
"ppc64" "ppc64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2316,9 +2435,6 @@
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2333,9 +2449,6 @@
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2350,9 +2463,6 @@
"s390x" "s390x"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2367,9 +2477,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2384,9 +2491,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -3267,9 +3371,9 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "10.6.0", "version": "10.7.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.7.0.tgz",
"integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", "integrity": "sha512-GVTD7s1vdIl6UYvAfriOPeY1Df8LIZjfofLvHwde+erDHGGuHyuM6xoxRxmHiebhYuD2p1vN4wWh0XzPARSGDQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": [
@@ -3409,10 +3513,164 @@
} }
} }
}, },
"node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/scope-manager": {
"version": "8.64.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz",
"integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.64.0",
"@typescript-eslint/visitor-keys": "8.64.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/types": {
"version": "8.64.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz",
"integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils": {
"version": "8.64.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz",
"integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.64.0",
"@typescript-eslint/types": "8.64.0",
"@typescript-eslint/typescript-estree": "8.64.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": {
"version": "8.64.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz",
"integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.64.0",
"@typescript-eslint/tsconfig-utils": "8.64.0",
"@typescript-eslint/types": "8.64.0",
"@typescript-eslint/visitor-keys": "8.64.0",
"debug": "^4.4.3",
"minimatch": "^10.2.2",
"semver": "^7.7.3",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": {
"version": "8.64.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz",
"integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.64.0",
"@typescript-eslint/types": "^8.64.0",
"debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.64.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz",
"integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==",
"dev": true,
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/visitor-keys": {
"version": "8.64.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz",
"integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.64.0",
"eslint-visitor-keys": "^5.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/eslint-plugin-jest/node_modules/eslint-visitor-keys": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
"integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/eslint-plugin-n": { "node_modules/eslint-plugin-n": {
"version": "18.2.1", "version": "18.2.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-18.2.1.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-n/-/eslint-plugin-n-18.2.2.tgz",
"integrity": "sha512-aO3C9//yq8JIvYOi/T+jPvcZ9hZzpwzbR8esrYpFtgE9vpbyM8kn42AQOtIqYspVmpaSWr8X+nrlQuAJYxXAaw==", "integrity": "sha512-gOO0lIqwEjZ750kv9/SptCWArUoAZXJoBr0vYWTO2dCBxctHUXlBIigiC8xuxxr/NKqgIT6Ehz1xRcilj8a5cA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -5749,9 +6007,9 @@
} }
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "3.9.4", "version": "3.9.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.4.tgz", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.5.tgz",
"integrity": "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==", "integrity": "sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"bin": { "bin": {
+5 -5
View File
@@ -42,7 +42,7 @@
"author": "GitHub", "author": "GitHub",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/cache": "^6.1.0", "@actions/cache": "^6.2.0",
"@actions/core": "^3.0.1", "@actions/core": "^3.0.1",
"@actions/exec": "^3.0.0", "@actions/exec": "^3.0.0",
"@actions/glob": "^0.7.0", "@actions/glob": "^0.7.0",
@@ -55,20 +55,20 @@
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@jest/globals": "^30.4.1", "@jest/globals": "^30.4.1",
"@types/node": "^26.0.0", "@types/node": "^26.1.1",
"@types/semver": "^7.7.0", "@types/semver": "^7.7.0",
"@typescript-eslint/eslint-plugin": "^8.62.0", "@typescript-eslint/eslint-plugin": "^8.62.0",
"@typescript-eslint/parser": "^8.62.0", "@typescript-eslint/parser": "^8.62.0",
"@vercel/ncc": "^0.44.0", "@vercel/ncc": "^0.44.0",
"eslint": "^10.5.0", "eslint": "^10.7.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-jest": "^29.15.4", "eslint-plugin-jest": "^29.15.4",
"eslint-plugin-n": "^18.1.0", "eslint-plugin-n": "^18.2.2",
"globals": "^17.7.0", "globals": "^17.7.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"jest": "^30.4.2", "jest": "^30.4.2",
"lint-staged": "^17.0.8", "lint-staged": "^17.0.8",
"prettier": "^3.9.1", "prettier": "^3.9.5",
"ts-jest": "^29.4.11", "ts-jest": "^29.4.11",
"typescript": "^6.0.3" "typescript": "^6.0.3"
}, },
+20 -5
View File
@@ -93,12 +93,27 @@ export function generate(
} }
}; };
if (gpgPassphrase) { // The maven-gpg-plugin reads the passphrase from the environment variable
const gpgServer = { // named by the `gpg.passphraseEnvName` property (default MAVEN_GPG_PASSPHRASE).
id: 'gpg.passphrase', // Only configure it when the requested env var name differs from that default;
passphrase: `\${env.${gpgPassphrase}}` // otherwise the plugin already reads the right variable and no extra settings
// are needed. Writing `gpg.passphrase` to settings.xml is deprecated and fails
// when the plugin's `bestPractices` mode is enabled.
if (
gpgPassphrase &&
gpgPassphrase !== constants.MAVEN_GPG_PASSPHRASE_DEFAULT_ENV
) {
xmlObj.settings.profiles = {
profile: {
id: constants.GPG_PASSPHRASE_PROFILE_ID,
properties: {
'gpg.passphraseEnvName': gpgPassphrase
}
}
};
xmlObj.settings.activeProfiles = {
activeProfile: constants.GPG_PASSPHRASE_PROFILE_ID
}; };
xmlObj.settings.servers.server.push(gpgServer);
} }
return xmlCreate(xmlObj).end({ return xmlCreate(xmlObj).end({
+185 -9
View File
@@ -12,6 +12,30 @@ const STATE_CACHE_PRIMARY_KEY = 'cache-primary-key';
const CACHE_MATCHED_KEY = 'cache-matched-key'; const CACHE_MATCHED_KEY = 'cache-matched-key';
const CACHE_KEY_PREFIX = 'setup-java'; const CACHE_KEY_PREFIX = 'setup-java';
/**
* An additional cache entry that is restored and saved independently of the
* main dependency cache. Used for build-tool wrapper distributions that rarely
* change (e.g. the Maven wrapper distribution) so that they are not evicted
* every time a volatile dependency file such as pom.xml changes. See
* https://github.com/actions/setup-java/issues/1095.
*/
interface AdditionalCache {
/**
* Short identifier for the cache, used to build its cache key and to scope
* the state keys that carry information from restore to save.
*/
name: string;
/**
* Paths that make up this cache entry.
*/
path: string[];
/**
* Glob patterns whose hash forms the cache key. If no file matches, the
* cache is skipped silently (the project simply does not use this feature).
*/
pattern: string[];
}
interface PackageManager { interface PackageManager {
id: 'maven' | 'gradle' | 'sbt'; id: 'maven' | 'gradle' | 'sbt';
/** /**
@@ -19,27 +43,36 @@ interface PackageManager {
*/ */
path: string[]; path: string[];
pattern: string[]; pattern: string[];
/**
* Additional caches keyed independently of the main dependency cache.
*/
additionalCaches?: AdditionalCache[];
} }
const supportedPackageManager: PackageManager[] = [ const supportedPackageManager: PackageManager[] = [
{ {
id: 'maven', id: 'maven',
path: [ path: [join(os.homedir(), '.m2', 'repository')],
join(os.homedir(), '.m2', 'repository'),
join(os.homedir(), '.m2', 'wrapper', 'dists')
],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---maven
pattern: [ pattern: [
'**/pom.xml', '**/pom.xml',
'**/.mvn/wrapper/maven-wrapper.properties', '**/.mvn/wrapper/maven-wrapper.properties',
'**/.mvn/extensions.xml' '**/.mvn/extensions.xml'
],
// The Maven wrapper distribution only depends on the wrapper properties,
// which change very rarely, so it is cached separately from the local
// repository. This keeps it available across the frequent pom.xml changes
// that rotate the main cache key. See issue #1095.
additionalCaches: [
{
name: 'maven-wrapper',
path: [join(os.homedir(), '.m2', 'wrapper', 'dists')],
pattern: ['**/.mvn/wrapper/maven-wrapper.properties']
}
] ]
}, },
{ {
id: 'gradle', id: 'gradle',
path: [ path: [join(os.homedir(), '.gradle', 'caches')],
join(os.homedir(), '.gradle', 'caches'),
join(os.homedir(), '.gradle', 'wrapper')
],
// https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle // https://github.com/actions/cache/blob/0638051e9af2c23d10bb70fa9beffcad6cff9ce3/examples.md#java---gradle
pattern: [ pattern: [
'**/*.gradle*', '**/*.gradle*',
@@ -48,6 +81,17 @@ const supportedPackageManager: PackageManager[] = [
'buildSrc/**/Dependencies.kt', 'buildSrc/**/Dependencies.kt',
'gradle/*.versions.toml', 'gradle/*.versions.toml',
'**/versions.properties' '**/versions.properties'
],
// The Gradle wrapper distribution only depends on the wrapper properties,
// which change very rarely, so it is cached separately from the Gradle
// caches. This keeps it available across the frequent *.gradle* changes
// that rotate the main cache key. See issue #269.
additionalCaches: [
{
name: 'gradle-wrapper',
path: [join(os.homedir(), '.gradle', 'wrapper')],
pattern: ['**/gradle-wrapper.properties']
}
] ]
}, },
{ {
@@ -87,6 +131,21 @@ function findPackageManager(id: string): PackageManager {
return packageManager; return packageManager;
} }
/**
* State keys used to carry an additional cache's restore-time information over
* to the post (save) action, scoped by the additional cache name.
*/
function additionalCachePrimaryKeyState(name: string): string {
return `${STATE_CACHE_PRIMARY_KEY}-${name}`;
}
function additionalCacheMatchedKeyState(name: string): string {
return `${CACHE_MATCHED_KEY}-${name}`;
}
function buildCacheKey(id: string, fileHash: string): string {
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${id}-${fileHash}`;
}
/** /**
* A function that generates a cache key to use. * A function that generates a cache key to use.
* Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"". * Format of the generated key will be "${{ platform }}-${{ id }}-${{ fileHash }}"".
@@ -105,7 +164,22 @@ async function computeCacheKey(
`No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository` `No file in ${process.cwd()} matched to [${pattern}], make sure you have checked out the target repository`
); );
} }
return `${CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${packageManager.id}-${fileHash}`; return buildCacheKey(packageManager.id, fileHash);
}
/**
* Computes the cache key for an additional cache. Unlike {@link computeCacheKey}
* this returns undefined (instead of throwing) when no file matches the pattern,
* because additional caches are optional features that many projects do not use.
*/
async function computeAdditionalCacheKey(
additionalCache: AdditionalCache
): Promise<string | undefined> {
const fileHash = await glob.hashFiles(additionalCache.pattern.join('\n'));
if (!fileHash) {
return undefined;
}
return buildCacheKey(additionalCache.name, fileHash);
} }
/** /**
@@ -130,6 +204,40 @@ export async function restore(id: string, cacheDependencyPath: string) {
core.setOutput('cache-hit', false); core.setOutput('cache-hit', false);
core.info(`${packageManager.id} cache is not found`); core.info(`${packageManager.id} cache is not found`);
} }
for (const additionalCache of packageManager.additionalCaches ?? []) {
await restoreAdditionalCache(additionalCache);
}
}
/**
* Restore an additional cache (e.g. a build-tool wrapper distribution) that is
* keyed independently of the main dependency cache so that it survives changes
* to volatile dependency files. Skips silently when the project does not use
* the corresponding feature.
*/
async function restoreAdditionalCache(additionalCache: AdditionalCache) {
const primaryKey = await computeAdditionalCacheKey(additionalCache);
if (!primaryKey) {
core.debug(
`No file matched [${additionalCache.pattern}] for the ${additionalCache.name} cache, skipping.`
);
return;
}
core.debug(`${additionalCache.name} primary key is ${primaryKey}`);
core.saveState(
additionalCachePrimaryKeyState(additionalCache.name),
primaryKey
);
const matchedKey = await cache.restoreCache(additionalCache.path, primaryKey);
if (matchedKey) {
core.saveState(
additionalCacheMatchedKeyState(additionalCache.name),
matchedKey
);
core.info(`${additionalCache.name} cache restored from key: ${matchedKey}`);
}
} }
/** /**
@@ -143,6 +251,10 @@ export async function save(id: string) {
// Inputs are re-evaluated before the post action, so we want the original key used for restore // Inputs are re-evaluated before the post action, so we want the original key used for restore
const primaryKey = core.getState(STATE_CACHE_PRIMARY_KEY); const primaryKey = core.getState(STATE_CACHE_PRIMARY_KEY);
for (const additionalCache of packageManager.additionalCaches ?? []) {
await saveAdditionalCache(packageManager, additionalCache);
}
if (!primaryKey) { if (!primaryKey) {
core.warning('Error retrieving key from state.'); core.warning('Error retrieving key from state.');
return; return;
@@ -180,6 +292,70 @@ export async function save(id: string) {
} }
} }
/**
* Save an additional cache under its own key. Skips when no key was recorded at
* restore time (feature unused) or when the exact key was already restored.
*/
async function saveAdditionalCache(
packageManager: PackageManager,
additionalCache: AdditionalCache
) {
const primaryKey = core.getState(
additionalCachePrimaryKeyState(additionalCache.name)
);
const matchedKey = core.getState(
additionalCacheMatchedKeyState(additionalCache.name)
);
if (!primaryKey) {
// The feature is not used by this project, nothing to save.
core.debug(
`No primary key for the ${additionalCache.name} cache, not saving cache.`
);
return;
} else if (matchedKey === primaryKey) {
core.info(
`Cache hit occurred on the ${additionalCache.name} primary key ${primaryKey}, not saving cache.`
);
return;
}
try {
const cacheId = await cache.saveCache(additionalCache.path, primaryKey);
if (cacheId === -1) {
core.debug(
`${additionalCache.name} cache was not saved for the key: ${primaryKey}`
);
return;
}
core.info(
`${additionalCache.name} cache saved with the key: ${primaryKey}`
);
} catch (error) {
const err = error as Error;
if (err.name === cache.ValidationError.name) {
// The cache paths did not resolve, e.g. the wrapper distribution was
// never downloaded because a system build tool was used or the download
// failed. Optional wrapper caches must not fail the post step, so skip.
core.debug(
`${additionalCache.name} cache paths do not exist, not saving cache: ${err.message}`
);
return;
}
if (err.name === cache.ReserveCacheError.name) {
core.info(err.message);
} else {
if (isProbablyGradleDaemonProblem(packageManager, err)) {
core.warning(
'Failed to save Gradle cache on Windows. If tar.exe reported "Permission denied", try to run Gradle with `--no-daemon` option. Refer to https://github.com/actions/cache/issues/454 for details.'
);
}
throw error;
}
}
}
/** /**
* @param packageManager the specified package manager by user * @param packageManager the specified package manager by user
* @param error the error thrown by the saveCache * @param error the error thrown by the saveCache
+9
View File
@@ -8,6 +8,7 @@ export const INPUT_JDK_FILE = 'jdk-file';
export const INPUT_JDK_FILE_DEPRECATED = 'jdkFile'; export const INPUT_JDK_FILE_DEPRECATED = 'jdkFile';
export const INPUT_CHECK_LATEST = 'check-latest'; export const INPUT_CHECK_LATEST = 'check-latest';
export const INPUT_SET_DEFAULT = 'set-default'; export const INPUT_SET_DEFAULT = 'set-default';
export const INPUT_PROBLEM_MATCHER = 'problem-matcher';
export const INPUT_VERIFY_SIGNATURE = 'verify-signature'; export const INPUT_VERIFY_SIGNATURE = 'verify-signature';
export const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key'; export const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key';
export const INPUT_SERVER_ID = 'server-id'; export const INPUT_SERVER_ID = 'server-id';
@@ -21,6 +22,14 @@ export const INPUT_GPG_PASSPHRASE = 'gpg-passphrase';
export const INPUT_DEFAULT_GPG_PRIVATE_KEY = undefined; export const INPUT_DEFAULT_GPG_PRIVATE_KEY = undefined;
export const INPUT_DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE'; export const INPUT_DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE';
// The default name of the environment variable the maven-gpg-plugin reads the
// passphrase from (property `gpg.passphraseEnvName`). When the configured
// passphrase env var name matches this, no extra configuration is required.
export const MAVEN_GPG_PASSPHRASE_DEFAULT_ENV = 'MAVEN_GPG_PASSPHRASE';
// Id of the settings.xml profile used to set `gpg.passphraseEnvName`.
export const GPG_PASSPHRASE_PROFILE_ID = 'setup-java-gpg';
export const INPUT_CACHE = 'cache'; export const INPUT_CACHE = 'cache';
export const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path'; export const INPUT_CACHE_DEPENDENCY_PATH = 'cache-dependency-path';
export const INPUT_JOB_STATUS = 'job-status'; export const INPUT_JOB_STATUS = 'job-status';
+47 -6
View File
@@ -4,7 +4,11 @@ import * as fs from 'fs';
import semver from 'semver'; import semver from 'semver';
import path from 'path'; import path from 'path';
import * as httpm from '@actions/http-client'; import * as httpm from '@actions/http-client';
import {getToolcachePath, isVersionSatisfies} from '../util.js'; import {
convertVersionToSemver,
getToolcachePath,
isVersionSatisfies
} from '../util.js';
import { import {
JavaDownloadRelease, JavaDownloadRelease,
JavaInstallerOptions, JavaInstallerOptions,
@@ -19,6 +23,7 @@ export abstract class JavaBase {
protected architecture: string; protected architecture: string;
protected packageType: string; protected packageType: string;
protected stable: boolean; protected stable: boolean;
protected latest: boolean;
protected checkLatest: boolean; protected checkLatest: boolean;
protected setDefault: boolean; protected setDefault: boolean;
protected verifySignature: boolean; protected verifySignature: boolean;
@@ -33,9 +38,11 @@ export abstract class JavaBase {
maxRetries: 3 maxRetries: 3
}); });
({version: this.version, stable: this.stable} = this.normalizeVersion( ({
installerOptions.version version: this.version,
)); stable: this.stable,
latest: this.latest
} = this.normalizeVersion(installerOptions.version));
this.architecture = installerOptions.architecture || os.arch(); this.architecture = installerOptions.architecture || os.arch();
this.packageType = installerOptions.packageType; this.packageType = installerOptions.packageType;
this.checkLatest = installerOptions.checkLatest; this.checkLatest = installerOptions.checkLatest;
@@ -62,7 +69,7 @@ export abstract class JavaBase {
} }
let foundJava = this.findInToolcache(); let foundJava = this.findInToolcache();
if (foundJava && !this.checkLatest) { if (foundJava && !this.checkLatest && !this.latest) {
core.info(`Resolved Java ${foundJava.version} from tool-cache`); core.info(`Resolved Java ${foundJava.version} from tool-cache`);
} else { } else {
core.info('Trying to resolve the latest version from remote'); core.info('Trying to resolve the latest version from remote');
@@ -263,6 +270,30 @@ export abstract class JavaBase {
protected normalizeVersion(version: string) { protected normalizeVersion(version: string) {
let stable = true; let stable = true;
const latest = false;
// Support the `latest` alias (case-insensitive), which floats to the newest
// available stable/GA release. It is translated to the SemVer wildcard `x`
// so the existing "newest satisfying version wins" resolution applies.
const normalized = version.trim().toLowerCase();
if (normalized === 'latest') {
return {
version: 'x',
stable: true,
latest: true
};
}
// Reject `latest` combined with any qualifier (e.g. `latest-ea`). Such inputs
// would otherwise have their `-ea` suffix stripped and fall through to the
// generic SemVer check, which fails with a confusing "'latest' is not valid
// SemVer" message even though `latest` is a supported value. Fail early with a
// targeted explanation instead.
if (normalized.startsWith('latest')) {
throw new Error(
`The 'latest' alias resolves stable (GA) releases only and cannot be combined with '-ea' or other qualifiers (received '${version}'). Use 'latest' on its own, or specify a concrete version.`
);
}
if (version.endsWith('-ea')) { if (version.endsWith('-ea')) {
version = version.replace(/-ea$/, ''); version = version.replace(/-ea$/, '');
@@ -273,6 +304,15 @@ export abstract class JavaBase {
stable = false; stable = false;
} }
// Java uses a versioning scheme (JEP 322) that can contain more numeric
// fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such
// exact versions to SemVer build notation ('18.0.1+1') so they are
// accepted. Ranges and versions that already carry build metadata are
// left untouched.
if (/^\d+(\.\d+){3,}$/.test(version)) {
version = convertVersionToSemver(version);
}
if (!semver.validRange(version)) { if (!semver.validRange(version)) {
throw new Error( throw new Error(
`The string '${version}' is not valid SemVer notation for a Java version. Please check README file for code snippets and more detailed information` `The string '${version}' is not valid SemVer notation for a Java version. Please check README file for code snippets and more detailed information`
@@ -281,7 +321,8 @@ export abstract class JavaBase {
return { return {
version, version,
stable stable,
latest
}; };
} }
+19 -1
View File
@@ -59,10 +59,28 @@ export class CorrettoDistribution extends JavaBase {
if (!this.stable) { if (!this.stable) {
throw new Error('Early access versions are not supported'); throw new Error('Early access versions are not supported');
} }
const availableVersions = await this.getAvailableVersions();
// The `latest` alias is normalized to the SemVer wildcard, but Corretto
// matches on an exact major version, so resolve it to the newest available
// major from Corretto's own list.
if (this.latest) {
const majors = availableVersions
.map(item => parseInt(item.version, 10))
.filter(major => Number.isFinite(major) && major > 0);
if (majors.length === 0) {
throw new Error(
'Could not determine the latest available Corretto major version from remote metadata'
);
}
version = Math.max(...majors).toString();
}
if (version.includes('.')) { if (version.includes('.')) {
throw new Error('Only major versions are supported'); throw new Error('Only major versions are supported');
} }
const availableVersions = await this.getAvailableVersions();
const matchingVersions = availableVersions const matchingVersions = availableVersions
.filter(item => item.version == version) .filter(item => item.version == version)
.map(item => { .map(item => {
@@ -8,6 +8,7 @@ import {
TemurinImplementation TemurinImplementation
} from './temurin/installer.js'; } from './temurin/installer.js';
import {LibericaDistributions} from './liberica/installer.js'; import {LibericaDistributions} from './liberica/installer.js';
import {LibericaNikDistributions} from './liberica-nik/installer.js';
import {MicrosoftDistributions} from './microsoft/installer.js'; import {MicrosoftDistributions} from './microsoft/installer.js';
import {SemeruDistribution} from './semeru/installer.js'; import {SemeruDistribution} from './semeru/installer.js';
import {CorrettoDistribution} from './corretto/installer.js'; import {CorrettoDistribution} from './corretto/installer.js';
@@ -28,6 +29,7 @@ enum JavaDistribution {
Temurin = 'temurin', Temurin = 'temurin',
Zulu = 'zulu', Zulu = 'zulu',
Liberica = 'liberica', Liberica = 'liberica',
LibericaNik = 'liberica-nik',
JdkFile = 'jdkfile', JdkFile = 'jdkfile',
Microsoft = 'microsoft', Microsoft = 'microsoft',
Semeru = 'semeru', Semeru = 'semeru',
@@ -69,6 +71,8 @@ export function getJavaDistribution(
return new ZuluDistribution(installerOptions); return new ZuluDistribution(installerOptions);
case JavaDistribution.Liberica: case JavaDistribution.Liberica:
return new LibericaDistributions(installerOptions); return new LibericaDistributions(installerOptions);
case JavaDistribution.LibericaNik:
return new LibericaNikDistributions(installerOptions);
case JavaDistribution.Microsoft: case JavaDistribution.Microsoft:
return new MicrosoftDistributions(installerOptions); return new MicrosoftDistributions(installerOptions);
case JavaDistribution.Semeru: case JavaDistribution.Semeru:
+32 -1
View File
@@ -16,6 +16,7 @@ import {
extractJdkFile, extractJdkFile,
getDownloadArchiveExtension, getDownloadArchiveExtension,
getGitHubHttpHeaders, getGitHubHttpHeaders,
getLatestMajorVersion,
getNextPageUrlFromLinkHeader, getNextPageUrlFromLinkHeader,
isVersionSatisfies, isVersionSatisfies,
MAX_PAGINATION_PAGES, MAX_PAGINATION_PAGES,
@@ -119,6 +120,13 @@ export class GraalVMDistribution extends JavaBase {
return this.findEABuildDownloadUrl(`${range}-ea`); return this.findEABuildDownloadUrl(`${range}-ea`);
} }
// The `latest` alias is normalized to the SemVer wildcard. Oracle GraalVM
// builds its download URLs from a concrete major and has no endpoint to list
// releases, so resolve the newest available GA major from the Adoptium API.
if (this.latest) {
range = (await getLatestMajorVersion(this.http)).toString();
}
const {platform, extension, major} = this.validateStableBuildRequest(range); const {platform, extension, major} = this.validateStableBuildRequest(range);
const fileUrl = this.constructFileUrl( const fileUrl = this.constructFileUrl(
@@ -203,6 +211,9 @@ export class GraalVMDistribution extends JavaBase {
if (statusCode === HttpCodes.NotFound) { if (statusCode === HttpCodes.NotFound) {
// Create the standard error with additional hint about checking the download URL // Create the standard error with additional hint about checking the download URL
const error = this.createVersionNotFoundError(range); const error = this.createVersionNotFoundError(range);
if (this.latest) {
error.message += `\nThe latest Java major version (${range}) is not yet available for the ${this.distribution} distribution. Please specify a concrete version instead of 'latest'.`;
}
error.message += `\nPlease check if this version is available at ${GRAALVM_DOWNLOAD_URL} . Pick a version from the list.`; error.message += `\nPlease check if this version is available at ${GRAALVM_DOWNLOAD_URL} . Pick a version from the list.`;
throw error; throw error;
} }
@@ -354,7 +365,27 @@ export class GraalVMCommunityDistribution extends GraalVMDistribution {
} }
const arch = this.getSupportedArchitecture(); const arch = this.getSupportedArchitecture();
const {platform, extension} = this.validateStableBuildRequest(range);
// GraalVM Community publishes its releases on GitHub, so the `latest` alias
// (normalized to the SemVer wildcard `x`) can float to the newest GA it
// actually ships. Unlike Oracle GraalVM (which has no listing endpoint and
// must derive the newest major from the Adoptium API), we match against the
// real release list here, so `latest` never fails when GraalVM lags behind a
// brand-new Java major.
let platform: OsVersions;
let extension: string;
if (this.latest) {
if (this.packageType !== 'jdk') {
throw new Error(
`${this.distribution} provides only the \`jdk\` package type`
);
}
platform = this.getPlatform();
extension = getDownloadArchiveExtension();
} else {
({platform, extension} = this.validateStableBuildRequest(range));
}
// GraalVM Community asset names embed the platform, architecture and // GraalVM Community asset names embed the platform, architecture and
// archive type, e.g. `graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz`. // archive type, e.g. `graalvm-community-jdk-21.0.2_linux-x64_bin.tar.gz`.
const assetSuffix = `_${platform}-${arch}_bin.${extension}`; const assetSuffix = `_${platform}-${arch}_bin.${extension}`;
+180
View File
@@ -0,0 +1,180 @@
import {JavaBase} from '../base-installer.js';
import {
JavaDownloadRelease,
JavaInstallerOptions,
JavaInstallerResults
} from '../base-models.js';
import semver from 'semver';
import {
extractJdkFile,
getDownloadArchiveExtension,
isVersionSatisfies,
renameWinArchive
} from '../../util.js';
import * as core from '@actions/core';
import {ArchitectureOptions, NikVersion, OsVersions} from './models.js';
import * as tc from '@actions/tool-cache';
import fs from 'fs';
import path from 'path';
const supportedPlatform = `'linux', 'macos', 'windows'`;
const supportedArchitectures = `'x64', 'aarch64'`;
export class LibericaNikDistributions extends JavaBase {
constructor(installerOptions: JavaInstallerOptions) {
super('Liberica_NIK', installerOptions);
}
protected async downloadTool(
javaRelease: JavaDownloadRelease
): Promise<JavaInstallerResults> {
core.info(
`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`
);
let javaArchivePath = await tc.downloadTool(javaRelease.url);
core.info(`Extracting Java archive...`);
const extension = getDownloadArchiveExtension();
if (process.platform === 'win32') {
javaArchivePath = renameWinArchive(javaArchivePath);
}
const extractedJavaPath = await extractJdkFile(javaArchivePath, extension);
const archiveName = fs.readdirSync(extractedJavaPath)[0];
const archivePath = path.join(extractedJavaPath, archiveName);
const javaPath = await tc.cacheDir(
archivePath,
this.toolcacheFolderName,
this.getToolcacheVersionName(javaRelease.version),
this.architecture
);
return {version: javaRelease.version, path: javaPath};
}
protected async findPackageForDownload(
range: string
): Promise<JavaDownloadRelease> {
const availableVersionsRaw = await this.getAvailableVersions();
const availableVersions = availableVersionsRaw
.map(item => {
const jdkVersion = this.getJdkVersion(item);
return jdkVersion ? {url: item.downloadUrl, version: jdkVersion} : null;
})
.filter((item): item is {url: string; version: string} => item !== null);
const satisfiedVersion = availableVersions
.filter(item => isVersionSatisfies(range, item.version))
.sort((a, b) => -semver.compareBuild(a.version, b.version))[0];
if (!satisfiedVersion) {
const availableVersionStrings = availableVersions.map(
item => item.version
);
throw this.createVersionNotFoundError(range, availableVersionStrings);
}
return satisfiedVersion;
}
private async getAvailableVersions(): Promise<NikVersion[]> {
if (core.isDebug()) {
console.time('Retrieving available versions for Liberica NIK took'); // eslint-disable-line no-console
}
const url = this.prepareAvailableVersionsUrl();
core.debug(`Gathering available versions from '${url}'`);
const availableVersions =
(await this.http.getJson<NikVersion[]>(url)).result ?? [];
if (core.isDebug()) {
core.startGroup('Print information about available versions');
console.timeEnd('Retrieving available versions for Liberica NIK took'); // eslint-disable-line no-console
core.debug(`Available versions: [${availableVersions.length}]`);
core.debug(availableVersions.map(item => item.version).join(', '));
core.endGroup();
}
return availableVersions;
}
private prepareAvailableVersionsUrl() {
const urlOptions = {
os: this.getPlatformOption(),
'bundle-type': this.getBundleType(),
...this.getArchitectureOptions(),
'build-type': this.stable ? 'all' : 'ea',
'installation-type': 'archive',
fields: 'downloadUrl,version,components,component,embedded'
};
const searchParams = new URLSearchParams(urlOptions).toString();
return `https://api.bell-sw.com/v1/nik/releases?${searchParams}`;
}
// NIK's top-level `version` is the GraalVM/NIK version; the JDK version that
// users select on lives in the embedded `liberica` component.
private getJdkVersion(release: NikVersion): string | null {
const liberica = release.components?.find(
component => component.component === 'liberica'
);
return liberica ? this.convertVersionToSemver(liberica.version) : null;
}
// The `full` bundle adds JavaFX/Swing GUI support; otherwise use `standard`.
private getBundleType(): string {
const [, feature] = this.packageType.split('+');
return feature?.includes('fx') ? 'full' : 'standard';
}
private getArchitectureOptions(): ArchitectureOptions {
const arch = this.distributionArchitecture();
switch (arch) {
case 'x64':
return {bitness: '64', arch: 'x86'};
case 'aarch64':
return {bitness: '64', arch: 'arm'};
default:
throw new Error(
`Architecture '${this.architecture}' is not supported. Supported architectures: ${supportedArchitectures}`
);
}
}
private getPlatformOption(
platform: NodeJS.Platform = process.platform
): OsVersions {
switch (platform) {
case 'darwin':
return 'macos';
case 'win32':
case 'cygwin':
return 'windows';
case 'linux':
return 'linux';
default:
throw new Error(
`Platform '${platform}' is not supported. Supported platforms: ${supportedPlatform}`
);
}
}
// JDK versions come as strings like '25.0.1+16', '23+38' or '11.0.15.1+2'.
// Normalize them to valid SemVer while preserving build metadata so newer
// NIK builds of the same JDK sort ahead of older ones.
private convertVersionToSemver(jdkVersion: string): string {
const [main, build] = jdkVersion.split('+');
const parts = main.split('.');
while (parts.length < 3) {
parts.push('0');
}
const base = parts.slice(0, 3).join('.');
const buildMeta = [...parts.slice(3), ...(build ? [build] : [])];
return buildMeta.length ? `${base}+${buildMeta.join('.')}` : base;
}
}
+26
View File
@@ -0,0 +1,26 @@
// Models from https://api.bell-sw.com/api.html (NIK product)
export type Bitness = '32' | '64';
export type ArchType = 'arm' | 'ppc' | 'sparc' | 'x86';
export type OsVersions = 'linux' | 'linux-musl' | 'macos' | 'windows';
export interface ArchitectureOptions {
bitness: Bitness;
arch: ArchType;
}
export interface NikComponent {
component: string;
version: string;
embedded?: boolean;
}
export interface NikVersion {
// The main Liberica NIK VM bundle download URL.
downloadUrl: string;
// NIK/GraalVM version (e.g. '24.1.0+1'), kept for logging only.
version: string;
// The embedded `liberica` component carries the actual JDK version.
components: NikComponent[];
}
+6
View File
@@ -22,6 +22,12 @@ export class LocalDistribution extends JavaBase {
} }
public async setupJava(): Promise<JavaInstallerResults> { public async setupJava(): Promise<JavaInstallerResults> {
if (this.latest) {
throw new Error(
"The 'latest' version alias is not supported for the 'jdkfile' distribution. Please specify a concrete version."
);
}
let foundJava = this.findInToolcache(); let foundJava = this.findInToolcache();
if (foundJava) { if (foundJava) {
File diff suppressed because it is too large Load Diff
+15
View File
@@ -13,6 +13,7 @@ import {
import { import {
extractJdkFile, extractJdkFile,
getDownloadArchiveExtension, getDownloadArchiveExtension,
getLatestMajorVersion,
renameWinArchive renameWinArchive
} from '../../util.js'; } from '../../util.js';
import {HttpCodes} from '@actions/http-client'; import {HttpCodes} from '@actions/http-client';
@@ -73,6 +74,14 @@ export class OracleDistribution extends JavaBase {
const platform = this.getPlatform(); const platform = this.getPlatform();
const extension = getDownloadArchiveExtension(); const extension = getDownloadArchiveExtension();
// The `latest` alias is normalized to the SemVer wildcard. Oracle builds its
// download URLs from a concrete major and has no endpoint to list releases,
// so resolve the newest available GA major from the Adoptium API and use it.
if (this.latest) {
const latestMajor = await getLatestMajorVersion(this.http);
range = latestMajor.toString();
}
const isOnlyMajorProvided = !range.includes('.'); const isOnlyMajorProvided = !range.includes('.');
const major = isOnlyMajorProvided ? range : range.split('.')[0]; const major = isOnlyMajorProvided ? range : range.split('.')[0];
@@ -113,6 +122,12 @@ export class OracleDistribution extends JavaBase {
} }
} }
if (this.latest) {
const error = this.createVersionNotFoundError(range);
error.message += `\nThe latest Java major version (${range}) is not yet available for the Oracle JDK distribution. Please specify a concrete version instead of 'latest'.`;
throw error;
}
throw this.createVersionNotFoundError(range); throw this.createVersionNotFoundError(range);
} }
+14
View File
@@ -0,0 +1,14 @@
import * as core from '@actions/core';
import {INPUT_PROBLEM_MATCHER} from './constants.js';
import {getBooleanInput} from './util.js';
export function configureProblemMatcher(matcherPath: string): void {
const problemMatcherEnabled = getBooleanInput(INPUT_PROBLEM_MATCHER, true);
if (!problemMatcherEnabled) {
core.debug('Java problem matcher is disabled');
return;
}
core.info(`##[add-matcher]${matcherPath}`);
}
+9 -2
View File
@@ -14,6 +14,7 @@ import {fileURLToPath} from 'url';
import {getJavaDistribution} from './distributions/distribution-factory.js'; import {getJavaDistribution} from './distributions/distribution-factory.js';
import {JavaInstallerOptions} from './distributions/base-models.js'; import {JavaInstallerOptions} from './distributions/base-models.js';
import {configureMavenArgs} from './maven-args.js'; import {configureMavenArgs} from './maven-args.js';
import {configureProblemMatcher} from './problem-matcher.js';
async function run() { async function run() {
try { try {
@@ -120,7 +121,7 @@ async function run() {
'..', '..',
'.github' '.github'
); );
core.info(`##[add-matcher]${path.join(matchersPath, 'java.json')}`); configureProblemMatcher(path.join(matchersPath, 'java.json'));
await auth.configureAuthentication(); await auth.configureAuthentication();
configureMavenArgs(); configureMavenArgs();
@@ -186,8 +187,14 @@ async function installVersion(
} }
const result = await distribution.setupJava(); const result = await distribution.setupJava();
// When the `latest` alias is used, the literal input isn't a real version, so
// pass the resolved version to the toolchains configuration instead.
const isLatest = version.trim().toLowerCase() === 'latest';
const toolchainVersion = isLatest ? result.version : version;
await toolchains.configureToolchains( await toolchains.configureToolchains(
version, toolchainVersion,
distributionName, distributionName,
result.path, result.path,
toolchainIds[toolchainId] toolchainIds[toolchainId]
+13 -28
View File
@@ -5,7 +5,6 @@ import * as core from '@actions/core';
import * as io from '@actions/io'; import * as io from '@actions/io';
import * as constants from './constants.js'; import * as constants from './constants.js';
import {getBooleanInput} from './util.js';
import {create as xmlCreate} from 'xmlbuilder2'; import {create as xmlCreate} from 'xmlbuilder2';
interface JdkInfo { interface JdkInfo {
@@ -27,10 +26,6 @@ export async function configureToolchains(
const settingsDirectory = const settingsDirectory =
core.getInput(constants.INPUT_SETTINGS_PATH) || core.getInput(constants.INPUT_SETTINGS_PATH) ||
path.join(os.homedir(), constants.M2_DIR); path.join(os.homedir(), constants.M2_DIR);
const overwriteSettings = getBooleanInput(
constants.INPUT_OVERWRITE_SETTINGS,
true
);
await createToolchainsSettings({ await createToolchainsSettings({
jdkInfo: { jdkInfo: {
@@ -39,19 +34,16 @@ export async function configureToolchains(
id, id,
jdkHome jdkHome
}, },
settingsDirectory, settingsDirectory
overwriteSettings
}); });
} }
export async function createToolchainsSettings({ export async function createToolchainsSettings({
jdkInfo, jdkInfo,
settingsDirectory, settingsDirectory
overwriteSettings
}: { }: {
jdkInfo: JdkInfo; jdkInfo: JdkInfo;
settingsDirectory: string; settingsDirectory: string;
overwriteSettings: boolean;
}) { }) {
core.info( core.info(
`Creating ${constants.MVN_TOOLCHAINS_FILE} for JDK version ${jdkInfo.version} from ${jdkInfo.vendor}` `Creating ${constants.MVN_TOOLCHAINS_FILE} for JDK version ${jdkInfo.version} from ${jdkInfo.vendor}`
@@ -68,11 +60,7 @@ export async function createToolchainsSettings({
jdkInfo.id, jdkInfo.id,
jdkInfo.jdkHome jdkInfo.jdkHome
); );
await writeToolchainsFileToDisk( await writeToolchainsFileToDisk(settingsDirectory, updatedToolchains);
settingsDirectory,
updatedToolchains,
overwriteSettings
);
} }
// only exported for testing purposes // only exported for testing purposes
@@ -172,22 +160,19 @@ async function readExistingToolchainsFile(directory: string) {
return ''; return '';
} }
async function writeToolchainsFileToDisk( async function writeToolchainsFileToDisk(directory: string, settings: string) {
directory: string,
settings: string,
overwriteSettings: boolean
) {
const location = path.join(directory, constants.MVN_TOOLCHAINS_FILE); const location = path.join(directory, constants.MVN_TOOLCHAINS_FILE);
const settingsExists = fs.existsSync(location); const settingsExists = fs.existsSync(location);
if (settingsExists && overwriteSettings) { // The toolchains file is produced by a non-destructive merge (existing JDK,
core.info(`Overwriting existing file ${location}`); // custom, and non-jdk toolchains are preserved see generateToolchainDefinition),
} else if (!settingsExists) { // so it is always safe to write it. Unlike settings.xml, it is therefore not
core.info(`Writing to ${location}`); // gated behind the `overwrite-settings` input; that would prevent subsequent
// setup-java runs from registering additional JDKs and silently drop the
// toolchain entries created by earlier runs.
if (settingsExists) {
core.info(`Updating existing file ${location}`);
} else { } else {
core.info( core.info(`Writing to ${location}`);
`Skipping generation of ${location} because file already exists and overwriting is not enabled`
);
return;
} }
return fs.writeFileSync(location, settings, { return fs.writeFileSync(location, settings, {
+31
View File
@@ -6,6 +6,7 @@ import * as cache from '@actions/cache';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as tc from '@actions/tool-cache'; import * as tc from '@actions/tool-cache';
import * as httpm from '@actions/http-client';
import { import {
INPUT_JOB_STATUS, INPUT_JOB_STATUS,
DISTRIBUTIONS_ONLY_MAJOR_VERSION DISTRIBUTIONS_ONLY_MAJOR_VERSION
@@ -382,3 +383,33 @@ export function renameWinArchive(javaArchivePath: string): string {
fs.renameSync(javaArchivePath, javaArchivePathRenamed); fs.renameSync(javaArchivePath, javaArchivePathRenamed);
return javaArchivePathRenamed; return javaArchivePathRenamed;
} }
interface IAdoptiumAvailableReleases {
most_recent_feature_release: number;
}
// Resolve the newest available stable/GA feature (major) release.
//
// Some distributions (e.g. Oracle, GraalVM) construct their download URLs from a
// concrete major version and don't expose an endpoint to list every available
// release, so a bare `latest` alias can't be resolved from their own metadata.
// The Adoptium (Temurin) API is used as a proxy for "what is the newest GA major
// version out there", which those distributions typically publish at the same time.
export async function getLatestMajorVersion(
http: httpm.HttpClient
): Promise<number> {
const availableReleasesUrl =
'https://api.adoptium.net/v3/info/available_releases';
const response =
await http.getJson<IAdoptiumAvailableReleases>(availableReleasesUrl);
const mostRecent = response.result?.most_recent_feature_release;
if (!mostRecent || Number.isNaN(Number(mostRecent))) {
throw new Error(
`Could not determine the latest available Java major version from ${availableReleasesUrl}`
);
}
return Number(mostRecent);
}