Compare commits

..
Author SHA1 Message Date
d0e6e4dbf7 chore(deps): bump the monthly-npm-updates group with 13 updates (#1276)
* chore(deps): bump the monthly-npm-updates group with 13 updates

Bumps the monthly-npm-updates group with 13 updates:

| Package | From | To |
| --- | --- | --- |
| [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) | `5.11.0` | `5.11.1` |
| [@jest/globals](https://github.com/jestjs/jest/tree/HEAD/packages/jest-globals) | `30.4.1` | `30.5.2` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.2.0` | `26.6.2` |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.67.0` | `8.70.1` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.67.0` | `8.70.1` |
| [eslint](https://github.com/eslint/eslint) | `10.8.1` | `10.11.0` |
| [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) | `29.16.1` | `29.16.6` |
| [globals](https://github.com/sindresorhus/globals) | `17.11.0` | `17.12.0` |
| [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) | `30.4.2` | `30.5.2` |
| [lint-staged](https://github.com/lint-staged/lint-staged) | `17.3.0` | `17.5.1` |
| [prettier](https://github.com/prettier/prettier) | `3.9.6` | `3.9.9` |
| [ts-jest](https://github.com/kulshekhar/ts-jest) | `29.4.12` | `29.4.13` |
| [typescript](https://github.com/microsoft/TypeScript) | `6.0.3` | `7.0.2` |


Updates `fast-xml-parser` from 5.11.0 to 5.11.1
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.11.0...v5.11.1)

Updates `@jest/globals` from 30.4.1 to 30.5.2
- [Release notes](https://github.com/jestjs/jest/releases)
- [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jestjs/jest/commits/v30.5.2/packages/jest-globals)

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

Updates `@typescript-eslint/eslint-plugin` from 8.67.0 to 8.70.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.70.1/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.67.0 to 8.70.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.70.1/packages/parser)

Updates `eslint` from 10.8.1 to 10.11.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.8.1...v10.11.0)

Updates `eslint-plugin-jest` from 29.16.1 to 29.16.6
- [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases)
- [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v29.16.1...v29.16.6)

Updates `globals` from 17.11.0 to 17.12.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.11.0...v17.12.0)

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

Updates `lint-staged` from 17.3.0 to 17.5.1
- [Release notes](https://github.com/lint-staged/lint-staged/releases)
- [Changelog](https://github.com/lint-staged/lint-staged/blob/main/CHANGELOG.md)
- [Commits](https://github.com/lint-staged/lint-staged/compare/v17.3.0...v17.5.1)

Updates `prettier` from 3.9.6 to 3.9.9
- [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.6...3.9.9)

Updates `ts-jest` from 29.4.12 to 29.4.13
- [Release notes](https://github.com/kulshekhar/ts-jest/releases)
- [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md)
- [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.4.12...v29.4.13)

Updates `typescript` from 6.0.3 to 7.0.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/compare/v6.0.3...v7.0.2)

---
updated-dependencies:
- dependency-name: fast-xml-parser
  dependency-version: 5.11.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: monthly-npm-updates
- dependency-name: "@jest/globals"
  dependency-version: 30.5.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: monthly-npm-updates
- dependency-name: "@types/node"
  dependency-version: 26.6.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: monthly-npm-updates
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.70.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: monthly-npm-updates
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.70.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: monthly-npm-updates
- dependency-name: eslint
  dependency-version: 10.11.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: monthly-npm-updates
- dependency-name: eslint-plugin-jest
  dependency-version: 29.16.6
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: monthly-npm-updates
- dependency-name: globals
  dependency-version: 17.12.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: monthly-npm-updates
- dependency-name: jest
  dependency-version: 30.5.2
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: monthly-npm-updates
- dependency-name: lint-staged
  dependency-version: 17.5.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: monthly-npm-updates
- dependency-name: prettier
  dependency-version: 3.9.9
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: monthly-npm-updates
- dependency-name: ts-jest
  dependency-version: 29.4.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: monthly-npm-updates
- dependency-name: typescript
  dependency-version: 7.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: monthly-npm-updates
...

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

* Fix monthly npm update checks

Keep TypeScript on the compatible 6.x line for the current @typescript-eslint peer range, rebuild dist, and refresh the fast-xml-parser license cache.

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

* Fix monthly npm validation failures

Update vulnerable transitive packages in the lockfile, rebuild dist, and correct the JetBrains test expectation for mocked Windows availability.

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

* chore: update licensed cache for npm updates

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

---------

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>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-02 01:24:19 -04:00
b24925bc49 chore(deps): bump undici from 6.28.0 to 6.29.0 (#1274)
* chore(deps): bump undici from 6.28.0 to 6.29.0

Bumps [undici](https://github.com/nodejs/undici) from 6.28.0 to 6.29.0.
- [Release notes](https://github.com/nodejs/undici/releases)
- [Commits](https://github.com/nodejs/undici/compare/v6.28.0...v6.29.0)

---
updated-dependencies:
- dependency-name: undici
  dependency-version: 6.29.0
  dependency-type: indirect
...

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

* chore(deps): update generated undici artifacts

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

* chore(deps): bump brace-expansion to 5.0.12 to fix high-severity audit

Resolves GHSA-q2hr-2g5m-vwhr, GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p.
Regenerates dist/ and updates licensed cache.

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

---------

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>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-02 00:37:36 -04:00
Bruno BorgesandCopilot App 47b36480ae Support Temurin JEP 322 patch versions like 26.0.2.1+1 (#1270) (#1279)
Accept 4-part versions with build metadata (X.Y.Z.P+B -> X.Y.Z+P.B) and
match exact Temurin requests against OpenJDK-derived version keys, since the
Adoptium API semver folds the patch into the build number (26.0.2+101) and
adds LTS metadata (25.0.4+7.0.LTS).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-02 00:09:56 -04:00
Bruno BorgesandCopilot App 74920aab4a Prefer Zulu 4-segment hotfix builds when resolving version ranges (#1278)
Azul reports hotfix releases like 25.0.4.1 as java_version=[25,0,4,1]
with openjdk_build_number=1 (SDKMAN '25.0.4+1.1'). Sorting candidates via
semver.compareBuild ranked 25.0.4+7 above 25.0.4+1.1, so ranges such as
'25' resolved to the older build. Compare java_version, build number and
distro_version numerically instead.

Fixes: #1275

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-02 00:07:25 -04:00
Copilotandbrunoborges a78ec39f12 Stop GPG agent before removing signature-verification home (#1273)
* Initial plan

* Stop GPG agent before verification home cleanup

Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>
2026-09-28 21:26:40 -04:00
97c5a5e13a Support Temurin on Linux RISC-V (#1269)
* Document Temurin RISC-V compatibility

Co-Authored-By: multicode <multicode@yawk.at>

* Expose RISC-V as a canonical architecture

Co-Authored-By: multicode <multicode@yawk.at>

* Support Temurin on Linux RISC-V

Co-Authored-By: multicode <multicode@yawk.at>

* Address RISC-V review feedback

Co-Authored-By: multicode <multicode@yawk.at>

* Fix casing for RISC-V architecture in tests

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

* Update __tests__/java-platform-contract.test.ts

---------

Co-authored-by: multicode <multicode@yawk.at>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-09-25 10:04:21 -03:00
Copilotandbrunoborges 1aa87b638d Preserve compound Liberica build versions from .sdkmanrc (#1268)
* Initial plan

* Preserve compound Liberica build versions from release metadata

Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>
2026-09-10 23:15:52 -04:00
de7274f081 Avoid macOS GPG socket overflow on long runner paths (#1266)
* Initial plan

* Fix signature verification GPG homes on long runner paths

* Keep macOS GPG verification homes within socket limits

Use /tmp for signature verification on macOS while preserving runner temp behavior elsewhere. Cover long and canonical OS temp paths and regenerate action bundles.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 32d31c8d-ddbc-4e57-a5c3-f70588fef3f3

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Bruno Borges <brborges@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 32d31c8d-ddbc-4e57-a5c3-f70588fef3f3
2026-09-09 08:08:57 -04:00
134912a529 Fix import-safe checks when scripts are run from a path with symlinks (#1265)
* Fix import-safe checks when scripts are run from a path with symlinks

In v6, setup-java was made "import-safe" to facilitate testing. This
prevents setup-java & cleanup-java from doing anything when their
sources get imported.

This works fine in the general case, but actually invoking the script
(`node setup-java/index.js`) when the path to the script contains
symlinks led to the script incorrectly believing it was imported, and
refuse to actually run.

To fix this, we pass `process.argv[1]` through `fs.realpathSync`, which
resolves symlinks in the path.

Fixes #1264

* Preserve import safety when resolving symlink entrypoints

Share entrypoint detection between setup and cleanup, handle non-file entrypoints safely, and normalize both paths for preserved symlinks. Add real-process regression coverage and rebuild action bundles.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 831c32f2-a275-45bd-a92b-c387372a1554

* Update js-yaml to fix merge-source denial of service

Bump the transitive development dependency from 3.15.1 to 3.15.2 to address GHSA-2883-xcg3-v3hh without changing dependency ranges or CI checks.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 831c32f2-a275-45bd-a92b-c387372a1554

---------

Co-authored-by: Bruno Borges <brborges@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 831c32f2-a275-45bd-a92b-c387372a1554
2026-09-09 02:20:21 -04:00
Bruno BorgesandJohn 0781fc6af3 Fix alpine failures by switching default back to only warn on verification failures. To prevent build failures due to missing GPG or rotated vendor keys. (#1262)
Also allow multiple GPG keys to be provided.

Co-authored-by: John <1615532+johnoliver@users.noreply.github.com>
2026-09-03 13:27:24 -04:00
4889c4aff5 Fix Temurin EA E2E signature verification (#1260)
* Configure Temurin EA E2E signature verification

Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>

* Disable signature verification for Temurin EA E2E

Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>

* Fix Temurin EA signature verification scope

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

Copilot-Session: 7ac2d888-2383-49c7-9178-da5455a10034

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com>
Co-authored-by: Bruno Borges <brborges@microsoft.com>
Copilot-Session: 7ac2d888-2383-49c7-9178-da5455a10034
2026-09-03 10:34:10 -04:00
Copilot 8fd3240085 [WIP] Fix failing GitHub Actions job for temurin 17 (#1259)
* Initial plan

* Install GnuPG in Alpine E2E job

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-09-03 07:39:25 -04:00
2732291815 chore(deps-dev): update eslint and globals (#1256)
* chore(deps-dev): bump the monthly-npm-updates group with 3 updates

Bumps the monthly-npm-updates group with 3 updates: [eslint](https://github.com/eslint/eslint), [globals](https://github.com/sindresorhus/globals) and [typescript](https://github.com/microsoft/TypeScript).


Updates `eslint` from 10.8.0 to 10.8.1
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v10.8.0...v10.8.1)

Updates `globals` from 17.9.0 to 17.11.0
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v17.9.0...v17.11.0)

Updates `typescript` from 6.0.3 to 7.0.2
- [Release notes](https://github.com/microsoft/TypeScript/releases)
- [Commits](https://github.com/microsoft/TypeScript/compare/v6.0.3...v7.0.2)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.8.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: monthly-npm-updates
- dependency-name: globals
  dependency-version: 17.11.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: monthly-npm-updates
- dependency-name: typescript
  dependency-version: 7.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
  dependency-group: monthly-npm-updates
...

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

* fix(deps): keep TypeScript on supported version

TypeScript 7 is outside the peer dependency ranges supported by typescript-eslint and ts-jest, causing npm ci to fail before dist can be rebuilt.

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

Copilot-Session: f1ca1f37-990b-4feb-9e3d-708cfdccbb6e

---------

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>
Copilot-Session: f1ca1f37-990b-4feb-9e3d-708cfdccbb6e
2026-08-24 13:26:11 -04:00
Bruno BorgesandCopilot App 1a8f22b7f7 chore: streamline Dependabot updates (#1255)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 916e0cf3-84f7-43fa-9822-3dac5cf8c5e6
2026-08-24 12:32:37 -04:00
Bruno BorgesandCopilot App 85030b78b5 docs: complete v6 release highlights (#1254)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: bb8de955-60c9-4a56-a7a2-d0be5d32dbc4
2026-08-24 12:03:01 -04:00
Bruno BorgesandCopilot App dd06d9cba3 Prepare documentation for v6 release (#1253)
Update public examples and release messaging to make v6 the recommended stable version, include late-cycle v6 features, and increase the JetBrains test timeout to avoid cold-run flakes.

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

Copilot-Session: 24e3d384-3bda-4ca3-b1fc-f49e340c174f
2026-08-24 11:49:23 -04:00
Bruno BorgesandCopilot App 59b3450628 chore(deps): combine open Dependabot npm updates (#1252)
* Combine Dependabot npm updates

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

* Rebuild action bundles

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

Copilot-Session: a5d254ad-e29c-4477-ae66-cba6e36d7e03

* Update Licensed cache for XML dependencies

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

Copilot-Session: a5d254ad-e29c-4477-ae66-cba6e36d7e03

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a5d254ad-e29c-4477-ae66-cba6e36d7e03
2026-08-24 10:36:54 -04:00
John b96213d9d2 Set default signature verification for supported distributions (#1246)
* Default signature verification for supported distributions

* Delegate signature defaults to installers
2026-08-24 10:08:03 -04:00
Bruno BorgesandCopilot App 1dbac3c9e1 docs: expose contributing guide to GitHub (#1245)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 684fec44-989a-4d0e-a42b-79efbda28933
2026-08-18 14:16:28 -04:00
Philip Gai 11741d6cfa ci: constrain cache e2e job modes (#1244)
* ci: constrain cache e2e job modes

* ci: constrain cache benchmark job modes
2026-08-18 10:56:27 -04:00
Bruno BorgesandCopilot App ff99aa1c87 Fix Oracle macOS E2E version (#1243)
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d4f1997e-98a5-4dec-b5fc-2f0fcf1abd27
2026-08-17 21:23:19 -04:00
62 changed files with 4203 additions and 1463 deletions
+16 -13
View File
@@ -1,22 +1,25 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2 version: 2
updates: updates:
# Enable version updates for npm
- package-ecosystem: 'npm' - package-ecosystem: 'npm'
# Look for `package.json` and `lock` files in the `root` directory
directory: '/' directory: '/'
# Check the npm registry for updates every day (weekdays)
schedule: schedule:
interval: 'weekly' interval: 'monthly'
cooldown:
default-days: 7
groups:
monthly-npm-updates:
applies-to: 'version-updates'
patterns:
- '*'
# Enable version updates for GitHub Actions
- package-ecosystem: 'github-actions' - package-ecosystem: 'github-actions'
# Workflow files stored in the default location of `.github/workflows`
# You don't need to specify `/.github/workflows` for `directory`. You can use `directory: "/"`.
directory: '/' directory: '/'
schedule: schedule:
interval: 'weekly' interval: 'monthly'
cooldown:
default-days: 7
groups:
monthly-actions-updates:
applies-to: 'version-updates'
patterns:
- '*'
@@ -24,6 +24,7 @@ jobs:
warm-caches: warm-caches:
name: Warm ${{ matrix.tool }} ${{ matrix.profile }} caches (${{ matrix.os }}) name: Warm ${{ matrix.tool }} ${{ matrix.profile }} caches (${{ matrix.os }})
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: write-only
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -65,6 +66,7 @@ jobs:
name: Benchmark ${{ matrix.tool }} ${{ matrix.profile }} (${{ matrix.os }}) name: Benchmark ${{ matrix.tool }} ${{ matrix.profile }} (${{ matrix.os }})
needs: warm-caches needs: warm-caches
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: read
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
+17
View File
@@ -21,6 +21,7 @@ defaults:
jobs: jobs:
gradle-save: gradle-save:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: write-only
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -48,6 +49,7 @@ jobs:
bash __tests__/check-dir.sh "$HOME/.gradle/wrapper/dists" bash __tests__/check-dir.sh "$HOME/.gradle/wrapper/dists"
gradle-restore: gradle-restore:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: read
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -72,6 +74,7 @@ jobs:
run: bash __tests__/check-dir.sh "$HOME/.gradle/wrapper/dists" run: bash __tests__/check-dir.sh "$HOME/.gradle/wrapper/dists"
maven-save: maven-save:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: write-only
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -97,6 +100,7 @@ jobs:
bash __tests__/check-dir.sh "$HOME/.m2/wrapper/dists" bash __tests__/check-dir.sh "$HOME/.m2/wrapper/dists"
maven-restore: maven-restore:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: read
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -121,6 +125,7 @@ jobs:
run: bash __tests__/check-dir.sh "$HOME/.m2/wrapper/dists" run: bash __tests__/check-dir.sh "$HOME/.m2/wrapper/dists"
sbt-save: sbt-save:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: write-only
defaults: defaults:
run: run:
shell: bash shell: bash
@@ -160,6 +165,7 @@ jobs:
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier" run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier"
sbt-restore: sbt-restore:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: read
defaults: defaults:
run: run:
shell: bash shell: bash
@@ -194,6 +200,7 @@ jobs:
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier" run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier"
gradle1-save: gradle1-save:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: write-only
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -222,6 +229,7 @@ jobs:
bash __tests__/check-dir.sh "$HOME/.gradle/wrapper/dists" bash __tests__/check-dir.sh "$HOME/.gradle/wrapper/dists"
gradle1-restore: gradle1-restore:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: read
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -246,6 +254,7 @@ jobs:
run: bash __tests__/check-dir.sh "$HOME/.gradle/wrapper/dists" run: bash __tests__/check-dir.sh "$HOME/.gradle/wrapper/dists"
gradle2-restore: gradle2-restore:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: read
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -268,6 +277,7 @@ jobs:
run: bash __tests__/check-dir.sh "$HOME/.gradle/caches" absent run: bash __tests__/check-dir.sh "$HOME/.gradle/caches" absent
maven1-save: maven1-save:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: write-only
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -294,6 +304,7 @@ jobs:
bash __tests__/check-dir.sh "$HOME/.m2/wrapper/dists" bash __tests__/check-dir.sh "$HOME/.m2/wrapper/dists"
maven1-restore: maven1-restore:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: read
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -318,6 +329,7 @@ jobs:
run: bash __tests__/check-dir.sh "$HOME/.m2/wrapper/dists" run: bash __tests__/check-dir.sh "$HOME/.m2/wrapper/dists"
maven2-restore: maven2-restore:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: read
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@@ -342,6 +354,7 @@ jobs:
run: bash __tests__/check-dir.sh "$HOME/.m2/repository" absent run: bash __tests__/check-dir.sh "$HOME/.m2/repository" absent
sbt1-save: sbt1-save:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: write-only
defaults: defaults:
run: run:
shell: bash shell: bash
@@ -382,6 +395,7 @@ jobs:
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier" run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier"
sbt1-restore: sbt1-restore:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: read
defaults: defaults:
run: run:
shell: bash shell: bash
@@ -416,6 +430,7 @@ jobs:
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier" run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier"
sbt2-restore: sbt2-restore:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
cache-mode: read
defaults: defaults:
run: run:
shell: bash shell: bash
@@ -450,6 +465,7 @@ jobs:
run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier" absent run: bash "$GITHUB_WORKSPACE/__tests__/check-dir.sh" "$HOME/.cache/coursier" absent
custom-maven-path-save: custom-maven-path-save:
runs-on: ubuntu-latest runs-on: ubuntu-latest
cache-mode: write-only
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v7 uses: actions/checkout@v7
@@ -474,6 +490,7 @@ jobs:
bash __tests__/check-dir.sh "$RUNNER_TEMP/setup-java-custom-maven-repository" bash __tests__/check-dir.sh "$RUNNER_TEMP/setup-java-custom-maven-repository"
custom-maven-path-restore: custom-maven-path-restore:
runs-on: ubuntu-latest runs-on: ubuntu-latest
cache-mode: read
needs: custom-maven-path-save needs: custom-maven-path-save
steps: steps:
- name: Checkout - name: Checkout
+4 -3
View File
@@ -71,7 +71,7 @@ jobs:
version: 25 version: 25
- distribution: oracle - distribution: oracle
os: macos-15-intel os: macos-15-intel
version: 17 version: 21
- distribution: oracle - distribution: oracle
os: windows-latest os: windows-latest
version: 21 version: 21
@@ -154,8 +154,8 @@ jobs:
version: ['21', '17'] version: ['21', '17']
steps: steps:
- *checkout_step - *checkout_step
- name: Install bash - name: Install bash and GnuPG
run: apk add --no-cache bash run: apk add --no-cache bash gnupg
- name: setup-java - name: setup-java
uses: ./ uses: ./
id: setup-java id: setup-java
@@ -340,6 +340,7 @@ jobs:
with: with:
java-version: ${{ matrix.version }} java-version: ${{ matrix.version }}
distribution: ${{ matrix.distribution }} distribution: ${{ matrix.distribution }}
verify-signature: ${{ matrix.distribution == 'temurin' && contains(matrix.version, '-ea') && 'false' || '' }}
- name: Verify Java - name: Verify Java
env: env:
JAVA_VERSION: ${{ matrix.version }} JAVA_VERSION: ${{ matrix.version }}
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: brace-expansion name: brace-expansion
version: 5.0.9 version: 5.0.12
type: npm type: npm
summary: Brace expansion as known from sh/bash summary: Brace expansion as known from sh/bash
homepage: homepage:
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: fast-xml-parser name: fast-xml-parser
version: 5.10.1 version: 5.11.1
type: npm type: npm
summary: Validate XML, Parse XML, Build XML without C/C++ based libraries summary: Validate XML, Parse XML, Build XML without C/C++ based libraries
homepage: homepage:
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: strnum name: strnum
version: 2.4.1 version: 2.4.2
type: npm type: npm
summary: Parse String to Number based on configuration summary: Parse String to Number based on configuration
homepage: homepage:
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: undici name: undici
version: 6.28.0 version: 6.29.0
type: npm type: npm
summary: An HTTP/1.1 client, written from scratch for Node.js summary: An HTTP/1.1 client, written from scratch for Node.js
homepage: https://undici.nodejs.org homepage: https://undici.nodejs.org
+59 -35
View File
@@ -9,22 +9,13 @@ Set up Java for GitHub Actions workflows. `setup-java` installs a requested Java
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v7 - 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'
- run: java --version - run: java --version
``` ```
> [!NOTE]
> V6 is still in development on the `main` branch and is not yet recommended for production workflows. To use it, you must explicitly reference the `main` branch in your workflow, as in
>
> ```yaml
> - uses: actions/setup-java@main
> ```
>
> For production workflows, it is recommended to use the latest stable release `v5`.
## Contents ## Contents
- [What it does](#what-it-does) - [What it does](#what-it-does)
@@ -54,15 +45,20 @@ steps:
## What's new ## What's new
### V6 (in development) ### V6
- Migrated the action implementation to ESM to support the latest `@actions/*` packages. - Migrated the action implementation to ESM to support the latest `@actions/*` packages.
- Added the `oracle-openjdk` distribution for OpenJDK builds from Oracle. - Added Oracle OpenJDK (`oracle-openjdk`), Red Hat Build of OpenJDK (`redhat`), and Liberica Native Image Kit (`liberica-nik`), and expanded Tencent Kona support through JDK 25.
- Added `java-version: latest` to resolve the newest stable GA release from the distribution's remote metadata. - Added `java-version: latest` to resolve the newest stable GA release from the distribution's remote metadata.
- JDK downloads now automatically verify authoritative checksums for [supported distributions](#download-integrity-and-signatures). - Expanded install compatibility with JEP 322 multi-field versions such as `18.0.1.1`, Temurin `jdk+jmods` packages, and native musl artifacts on Alpine for Dragonwell, Corretto, Zulu, and Liberica.
- JDK downloads now automatically verify authoritative checksums. Package signature verification is supported for Temurin and Microsoft builds, with configurable strict enforcement.
- Added `force-download: true` to bypass the tool cache and perform a reproducible fresh install. - Added `force-download: true` to bypass the tool cache and perform a reproducible fresh install.
- Dependency caching now supports custom paths with `cache-path` and restore-only operation with `cache-read-only: true`. - Dependency caching now supports custom paths with `cache-path` and restore-only operation with `cache-read-only: true`.
- Dependency cache keys now include `.mvn/extensions.xml` and `gradle.properties`, preventing stale restores when Maven extensions or Gradle dependency properties change.
- Downloaded JDKs are now [cached](#caching-jdk-installations) automatically when `cache` is set; use `cache-jdk` to enable or disable it independently. - Downloaded JDKs are now [cached](#caching-jdk-installations) automatically when `cache` is set; use `cache-jdk` to enable or disable it independently.
- Warm JDK-cached jobs can reuse cached release metadata, avoiding vendor API calls while retaining a stale-metadata fallback for vendor outages and rate limits.
- Maven configuration now supports multiple server credentials and custom dependency-resolution repositories.
- Maven signing keys are imported into an isolated temporary GPG home instead of the runner's default keyring.
- Set `problem-matcher: false` to disable Java compiler and uncaught-exception annotations. - Set `problem-matcher: false` to disable Java compiler and uncaught-exception annotations.
- GraalVM distributions now set `GRAALVM_HOME` in addition to `JAVA_HOME`. - GraalVM distributions now set `GRAALVM_HOME` in addition to `JAVA_HOME`.
- Invalid boolean values, unsupported distribution/package/platform combinations, and mismatched Maven toolchain ID counts now fail with targeted errors. - Invalid boolean values, unsupported distribution/package/platform combinations, and mismatched Maven toolchain ID counts now fail with targeted errors.
@@ -73,6 +69,7 @@ steps:
- Deprecated aliases still work, but emit warnings. - Deprecated aliases still work, but emit warnings.
- Maven GPG passphrases are now passed through `gpg.passphraseEnvName` instead of a deprecated `gpg.passphrase` server entry in `settings.xml`. This requires `maven-gpg-plugin` 3.2.0 or newer. See [GPG](docs/advanced-usage.md#gpg). - Maven GPG passphrases are now passed through `gpg.passphraseEnvName` instead of a deprecated `gpg.passphrase` server entry in `settings.xml`. This requires `maven-gpg-plugin` 3.2.0 or newer. See [GPG](docs/advanced-usage.md#gpg).
- Legacy AdoptOpenJDK distributions were removed. Use `temurin` instead of `adopt` or `adopt-hotspot`, and `semeru` instead of `adopt-openj9`. - Legacy AdoptOpenJDK distributions were removed. Use `temurin` instead of `adopt` or `adopt-hotspot`, and `semeru` instead of `adopt-openj9`.
- See the [complete V6 release notes](https://github.com/actions/setup-java/releases/tag/v6.0.0) for all enhancements and fixes.
### V5 ### V5
@@ -89,7 +86,7 @@ steps:
### Older versions ### Older versions
> [!WARNING] > [!WARNING]
> `actions/setup-java` versions `v1` through `v4` are deprecated. Upgrade workflows to `actions/setup-java@v5`, the latest stable release. > `actions/setup-java` versions `v1` through `v4` are deprecated. Upgrade workflows to `actions/setup-java@v6`, the latest stable release.
## Usage ## Usage
@@ -98,7 +95,7 @@ steps:
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v7 - 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'
@@ -110,7 +107,7 @@ steps:
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: microsoft distribution: microsoft
java-version: '25' java-version: '25'
@@ -122,7 +119,7 @@ steps:
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: temurin distribution: temurin
java-version-file: .java-version java-version-file: .java-version
@@ -136,7 +133,7 @@ Supported version files are `.java-version`, `.tool-versions`, and `.sdkmanrc`.
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: temurin distribution: temurin
java-version: latest java-version: latest
@@ -153,14 +150,14 @@ steps:
| `java-version-file` | Path to `.java-version`, `.tool-versions`, or `.sdkmanrc`. Used when `java-version` is not set. | | | `java-version-file` | Path to `.java-version`, `.tool-versions`, or `.sdkmanrc`. Used when `java-version` is not set. | |
| `distribution` | Java distribution keyword. Values are case-sensitive and must match one of the supported keywords below. Required unless `java-version-file` points to `.sdkmanrc` with a recognized distribution suffix. | | | `distribution` | Java distribution keyword. Values are case-sensitive and must match one of the supported keywords below. Required unless `java-version-file` points to `.sdkmanrc` with a recognized distribution suffix. | |
| `java-package` | Package variant such as `jdk`, `jre`, `jdk+fx`, `jre+fx`, `jdk+crac`, `jre+crac`, `jdk+jmods`, `jdk+jcef`, `jre+jcef`, `jdk+ft`, or `jre+ft`. Support varies by distribution. | `jdk` | | `java-package` | Package variant such as `jdk`, `jre`, `jdk+fx`, `jre+fx`, `jdk+crac`, `jre+crac`, `jdk+jmods`, `jdk+jcef`, `jre+jcef`, `jdk+ft`, or `jre+ft`. Support varies by distribution. | `jdk` |
| `architecture` | Package architecture. Canonical values are `x86`, `x64`, `armv7`, `aarch64`, `ppc64le`, `ppc64`, and `s390x`. Aliases `ia32`, `amd64`, `arm`, and `arm64` are normalized. | Runner architecture | | `architecture` | Package architecture. Canonical values are `x86`, `x64`, `armv7`, `aarch64`, `ppc64le`, `ppc64`, `riscv64`, and `s390x`. Aliases `ia32`, `amd64`, `arm`, and `arm64` are normalized. | Runner architecture |
| `jdk-file` | Local compressed JDK archive. Requires `distribution: jdkfile`. | | | `jdk-file` | Local compressed JDK archive. Requires `distribution: jdkfile`. | |
| `check-latest` | Check remote metadata for the latest version satisfying the version spec before using the runner tool cache. | `false` | | `check-latest` | Check remote metadata for the latest version satisfying the version spec before using the runner tool cache. | `false` |
| `force-download` | Always download Java and replace any matching version in the tool cache. | `false` | | `force-download` | Always download Java and replace any matching version in the tool cache. | `false` |
| `set-default` | Add Java to `PATH` and set `JAVA_HOME`. When `false`, only version-specific `JAVA_HOME_<major>_<arch>` variables are set. | `true` | | `set-default` | Add Java to `PATH` and set `JAVA_HOME`. When `false`, only version-specific `JAVA_HOME_<major>_<arch>` variables are set. | `true` |
| `problem-matcher` | Register Java compiler and uncaught exception problem matchers. | `true` | | `problem-matcher` | Register Java compiler and uncaught exception problem matchers. | `true` |
| `verify-signature` | Verify downloaded Java package signatures when supported. Currently supported for `temurin` and `microsoft`. | `false` | | `verify-signature` | Verify downloaded Java package signatures when supported. Explicitly setting this to `true` makes verification failures fatal. | Distribution-dependent; see [Download integrity and signatures](#download-integrity-and-signatures) |
| `verify-signature-public-key` | ASCII-armored GPG public key to use for signature verification. Overrides the bundled key. | | | `verify-signature-public-key` | One or more ASCII-armored GPG public keys used for signature verification. Concatenate multiple armored key blocks. Custom keys replace the bundled distribution keys. | |
| `token` | Token for fetching GitHub.com-hosted version manifests, useful on GitHub Enterprise Server when unauthenticated requests are rate-limited. | `${{ github.token }}` on GitHub.com; empty string on GHES | | `token` | Token for fetching GitHub.com-hosted version manifests, useful on GitHub Enterprise Server when unauthenticated requests are rate-limited. | `${{ github.token }}` on GitHub.com; empty string on GHES |
| `cache` | Enable dependency caching for `maven`, `gradle`, or `sbt`. | | | `cache` | Enable dependency caching for `maven`, `gradle`, or `sbt`. | |
| `cache-jdk` | Cache downloaded JDK installations between jobs. When omitted, JDK caching is enabled only if `cache` is set. Set explicitly to `true` or `false` to override. | Enabled when `cache` is set | | `cache-jdk` | Cache downloaded JDK installations between jobs. When omitted, JDK caching is enabled only if `cache` is set. Set explicitly to `true` or `false` to override. | Enabled when `cache` is set |
@@ -236,7 +233,7 @@ Additional distribution notes:
| --- | --- | | --- | --- |
| Major version | `8`, `11`, `17`, `21`, `25` | | Major version | `8`, `11`, `17`, `21`, `25` |
| Specific feature or patch version | `11.0`, `11.0.4`, `17.0`, `8.0.282+8` | | Specific feature or patch version | `11.0`, `11.0.4`, `17.0`, `8.0.282+8` |
| JEP 322 multi-field versions | `11.0.9.1`, `18.0.1.1` | | JEP 322 multi-field versions | `11.0.9.1`, `18.0.1.1`, `26.0.2.1+1` |
| Early access | `15-ea`, `15.0.0-ea`, `27-ea` | | Early access | `15-ea`, `15.0.0-ea`, `27-ea` |
| Latest stable GA release | `latest` | | Latest stable GA release | `latest` |
@@ -250,7 +247,34 @@ GitHub-hosted runners primarily pre-cache Eclipse Temurin JDKs. See the installe
Distributions or individual releases without an authoritative checksum continue to install normally, with the omission reported in debug logs. Installations resolved directly from the runner tool cache — including JDKs preinstalled on the runner image and JDKs installed by an earlier step of the same job — are not downloaded again and are not reverified, even when `verify-signature: true` is set. Use `force-download: true` to always download and verify the archive. Distributions or individual releases without an authoritative checksum continue to install normally, with the omission reported in debug logs. Installations resolved directly from the runner tool cache — including JDKs preinstalled on the runner image and JDKs installed by an earlier step of the same job — are not downloaded again and are not reverified, even when `verify-signature: true` is set. Use `force-download: true` to always download and verify the archive.
Use `verify-signature: true` to verify package signatures for distributions that support it. Currently supported distributions are `temurin` and `microsoft`; setting it for an unsupported distribution fails the workflow. Package signature verification is supported for `temurin` and `microsoft`. When `verify-signature` is omitted, the action checks the signature and warns if GPG is unavailable or verification fails, but does not enforce the result. Explicitly setting `verify-signature: true` enforces verification and makes these failures fatal. Setting `verify-signature: true` for an unsupported distribution also fails the workflow.
> [!WARNING]
> Requesting explicit signature verification with verify-signature can fail a build after an unexpected but legitimate vendor signing-key rotation, because the action's bundled keys may not yet include the new key. Confirm a new key through the vendor's trusted documentation before using it.
After confirming a legitimate rotation, configure the updated key with `verify-signature-public-key`. The input accepts one or more ASCII-armored public keys; concatenate complete armored key blocks when both old and new vendor keys are needed during a transition. Custom keys replace, rather than extend, the keys bundled with the selected distribution.
```yaml
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '25'
verify-signature: true
verify-signature-public-key: |
-----BEGIN PGP PUBLIC KEY BLOCK-----
...vendor key material...
-----END PGP PUBLIC KEY BLOCK-----
```
As a temporary fallback while a legitimate rotation is being investigated, set `verify-signature: false`. This disables package signature verification, although authoritative checksum verification still applies when the vendor publishes a checksum.
```yaml
- uses: actions/setup-java@v6
with:
distribution: temurin
java-version: '25'
verify-signature: false
```
## Caching ## Caching
@@ -267,7 +291,7 @@ Set `cache` to `maven`, `gradle`, or `sbt` to cache dependencies with minimal co
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v7 - 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'
@@ -286,7 +310,7 @@ The primary dependency cache key is `setup-java-<runner-os>-<node-arch>-<package
Use `cache-dependency-path` to override the files used for key hashing, especially in monorepos: Use `cache-dependency-path` to override the files used for key hashing, especially in monorepos:
```yaml ```yaml
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: temurin distribution: temurin
java-version: '25' java-version: '25'
@@ -299,7 +323,7 @@ Use `cache-dependency-path` to override the files used for key hashing, especial
Use `cache-path` when the build tool stores dependencies outside the default location: Use `cache-path` when the build tool stores dependencies outside the default location:
```yaml ```yaml
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: temurin distribution: temurin
java-version: '25' java-version: '25'
@@ -338,7 +362,7 @@ The JDK cache stores the downloaded JDK installation so later runs skip the down
Set `cache-read-only: true` to restore dependency, wrapper, and JDK caches without saving changes in the post action. This is useful for pull requests, merge queues, short-lived branches, and matrix fan-out jobs that should only consume caches produced elsewhere. Set `cache-read-only: true` to restore dependency, wrapper, and JDK caches without saving changes in the post action. This is useful for pull requests, merge queues, short-lived branches, and matrix fan-out jobs that should only consume caches produced elsewhere.
```yaml ```yaml
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: temurin distribution: temurin
java-version: '25' java-version: '25'
@@ -354,7 +378,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v7 - 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'
@@ -369,7 +393,7 @@ jobs:
goal: [test, verify, package] goal: [test, verify, package]
steps: steps:
- uses: actions/checkout@v7 - 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'
@@ -387,7 +411,7 @@ env:
SEGMENT_DOWNLOAD_TIMEOUT_MINS: '5' SEGMENT_DOWNLOAD_TIMEOUT_MINS: '5'
steps: steps:
- uses: actions/checkout@v7 - 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'
@@ -401,7 +425,7 @@ Install multiple Java versions by providing a multiline `java-version` value. Al
```yaml ```yaml
steps: steps:
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: temurin distribution: temurin
java-version: | java-version: |
@@ -428,7 +452,7 @@ jobs:
name: Java ${{ matrix.java }} name: Java ${{ matrix.java }}
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
- uses: actions/setup-java@v5 - uses: actions/setup-java@v6
with: with:
distribution: temurin distribution: temurin
java-version: ${{ matrix.java }} java-version: ${{ matrix.java }}
@@ -445,7 +469,7 @@ jobs:
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v7 - 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'
@@ -467,7 +491,7 @@ required. See [Resolving Maven dependencies from custom repositories](docs/advan
```yaml ```yaml
steps: steps:
- uses: actions/checkout@v7 - 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'
@@ -522,7 +546,7 @@ The scripts and documentation in this project are released under the [MIT Licens
## Contributions ## Contributions
Contributions are welcome. See our [Contributor's Guide](docs/contributors.md). Contributions are welcome. See our [Contributor's Guide](docs/CONTRIBUTING.md).
## Code of Conduct ## Code of Conduct
+1 -1
View File
@@ -393,7 +393,7 @@ function createRegisteredJdk(version = '21.0.8+9') {
architecture: 'x64', architecture: 'x64',
version, version,
source: `sha256:${path.basename(root)}`, source: `sha256:${path.basename(root)}`,
verification: 'unverified', verification: 'disabled',
path: jdkPath path: jdkPath
}; };
registerJdk(jdk); registerJdk(jdk);
+14 -7
View File
@@ -71,8 +71,11 @@ jest.unstable_mockModule('@actions/tool-cache', () => ({
})); }));
jest.unstable_mockModule('../../src/jdk-cache.js', () => ({ jest.unstable_mockModule('../../src/jdk-cache.js', () => ({
getJdkVerificationIdentity: jest.fn((verified: boolean, key?: string) => getJdkVerificationIdentity: jest.fn(
verified ? (key ? 'verified:custom' : 'verified:bundled') : 'unverified' (verified: boolean, enforced: boolean, key?: string) =>
verified
? `${enforced ? 'enforced' : 'check-and-warn'}:${key ? 'custom' : 'bundled'}`
: 'disabled'
), ),
registerJdk: jest.fn(), registerJdk: jest.fn(),
restoreJdk: jest.fn() restoreJdk: jest.fn()
@@ -395,8 +398,10 @@ describe('setupJava', () => {
beforeEach(() => { beforeEach(() => {
(jdkCache.getJdkVerificationIdentity as jest.Mock).mockImplementation( (jdkCache.getJdkVerificationIdentity as jest.Mock).mockImplementation(
(verified: boolean, key?: string) => (verified: boolean, enforced: boolean, key?: string) =>
verified ? (key ? 'verified:custom' : 'verified:bundled') : 'unverified' verified
? `${enforced ? 'enforced' : 'check-and-warn'}:${key ? 'custom' : 'bundled'}`
: 'disabled'
); );
spyGetToolcachePath = util.getToolcachePath as jest.Mock; spyGetToolcachePath = util.getToolcachePath as jest.Mock;
spyGetToolcachePath.mockImplementation( spyGetToolcachePath.mockImplementation(
@@ -826,7 +831,7 @@ describe('setupJava', () => {
expect(jdkCache.registerJdk).toHaveBeenCalledWith( expect(jdkCache.registerJdk).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
version: actualJavaVersion, version: actualJavaVersion,
verification: 'unverified' verification: 'disabled'
}) })
); );
}); });
@@ -886,7 +891,7 @@ describe('setupJava', () => {
architecture: 'x86', architecture: 'x86',
version: actualJavaVersion, version: actualJavaVersion,
source: `some/random_url/java/${actualJavaVersion}`, source: `some/random_url/java/${actualJavaVersion}`,
verification: 'unverified', verification: 'disabled',
path: path.join(toolCachePath, 'Java_Empty_jdk', actualJavaVersion) path: path.join(toolCachePath, 'Java_Empty_jdk', actualJavaVersion)
}); });
expect(downloadTool).not.toHaveBeenCalled(); expect(downloadTool).not.toHaveBeenCalled();
@@ -919,7 +924,7 @@ describe('setupJava', () => {
architecture: 'x86', architecture: 'x86',
version: actualJavaVersion, version: actualJavaVersion,
source: `some/random_url/java/${actualJavaVersion}`, source: `some/random_url/java/${actualJavaVersion}`,
verification: 'unverified', verification: 'disabled',
path: path.join(toolCachePath, 'Java_Empty_jdk', actualJavaVersion) path: path.join(toolCachePath, 'Java_Empty_jdk', actualJavaVersion)
}; };
expect(jdkCache.restoreJdk).toHaveBeenCalledWith(expectedIdentity); expect(jdkCache.restoreJdk).toHaveBeenCalledWith(expectedIdentity);
@@ -1753,6 +1758,8 @@ describe('normalizeVersion', () => {
['11.0.9.1', {version: '11.0.9+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}], ['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}], ['18.0.1.1-ea', {version: '18.0.1+1', stable: false, latest: false}],
['26.0.2.1+1', {version: '26.0.2+1.1', stable: true, latest: false}],
['25.0.4.1+1', {version: '25.0.4+1.1', stable: true, latest: false}],
['latest', {version: 'x', stable: true, latest: true}], ['latest', {version: 'x', stable: true, latest: true}],
['LATEST', {version: 'x', stable: true, latest: true}], ['LATEST', {version: 'x', stable: true, latest: true}],
[' Latest ', {version: 'x', stable: true, latest: true}] [' Latest ', {version: 'x', stable: true, latest: true}]
@@ -82,7 +82,7 @@ describe('getJavaDistribution', () => {
); );
}); });
it.each(['8', '23.x', '23.0.1.1', '<24'])( it.each(['8', '23.x', '23.0.1.1', '23.0.1.1+1', '<24'])(
"rejects Temurin java-package 'jdk+jmods' for version %s", "rejects Temurin java-package 'jdk+jmods' for version %s",
async version => { async version => {
await expect( await expect(
@@ -96,7 +96,7 @@ describe('getJavaDistribution', () => {
} }
); );
it.each(['24', '24.0.1.1', '25-ea', '>=21', 'latest'])( it.each(['24', '24.0.1.1', '25.0.4.1+1', '25-ea', '>=21', 'latest'])(
"accepts Temurin java-package 'jdk+jmods' for version %s", "accepts Temurin java-package 'jdk+jmods' for version %s",
async version => { async version => {
expect( expect(
@@ -14,7 +14,6 @@ import type {IncomingMessage} from 'http';
import {Readable} from 'stream'; import {Readable} from 'stream';
import manifestData from '../data/jetbrains.json' with {type: 'json'}; import manifestData from '../data/jetbrains.json' with {type: 'json'};
import os from 'os';
// Mock @actions/core before importing source modules that depend on it // Mock @actions/core before importing source modules that depend on it
jest.unstable_mockModule('@actions/core', () => ({ jest.unstable_mockModule('@actions/core', () => ({
@@ -78,7 +77,10 @@ function response(
} }
describe('getAvailableVersions', () => { describe('getAvailableVersions', () => {
jest.setTimeout(10_000);
let spyHttpClient: any; let spyHttpClient: any;
let spyHttpClientHead: any;
let spyCoreError: any; let spyCoreError: any;
const originalGitHubToken = process.env.GITHUB_TOKEN; const originalGitHubToken = process.env.GITHUB_TOKEN;
@@ -91,6 +93,10 @@ describe('getAvailableVersions', () => {
headers: {}, headers: {},
result: [] result: []
}); });
spyHttpClientHead = jest.spyOn(HttpClient.prototype, 'head');
spyHttpClientHead.mockResolvedValue({
message: {statusCode: 200}
} as any);
// Mock core.error to suppress error logs // Mock core.error to suppress error logs
spyCoreError = core.error as jest.Mock; spyCoreError = core.error as jest.Mock;
@@ -131,9 +137,7 @@ describe('getAvailableVersions', () => {
const availableVersions = await distribution['getAvailableVersions'](); const availableVersions = await distribution['getAvailableVersions']();
expect(availableVersions).not.toBeNull(); expect(availableVersions).not.toBeNull();
const length = expect(availableVersions.length).toBe(manifestData.length + 2);
os.platform() === 'win32' ? manifestData.length : manifestData.length + 2;
expect(availableVersions.length).toBe(length);
}, 10_000); }, 10_000);
it('continues a stable request after an all-prerelease page', async () => { it('continues a stable request after an all-prerelease page', async () => {
@@ -356,6 +360,7 @@ describe('getAvailableVersions', () => {
it('retries a GitHub rate limit using Retry-After', async () => { it('retries a GitHub rate limit using Retry-After', async () => {
spyHttpClient.mockRestore(); spyHttpClient.mockRestore();
spyHttpClientHead.mockRestore();
const sleep = jest.fn(async () => undefined); const sleep = jest.fn(async () => undefined);
const requestRaw = jest const requestRaw = jest
.spyOn(HttpClient.prototype, 'requestRaw') .spyOn(HttpClient.prototype, 'requestRaw')
@@ -253,6 +253,39 @@ describe('findPackageForDownload', () => {
expect(result.version).toBe(expected); expect(result.version).toBe(expected);
}); });
describe('compound build versions', () => {
beforeEach(() => {
distribution['getAvailableVersions'] = async () =>
['1', '1.1', '1.2', '1.10'].map(build => ({
featureVersion: 25,
interimVersion: 0,
updateVersion: 4,
buildVersion: 1,
version: `25.0.4+${build}`,
downloadUrl: `https://download.bell-sw.com/java/25.0.4+${build}/bellsoft-jdk25.0.4+${build}-macos-aarch64.tar.gz`
}));
});
it.each([
['25.0.4+1.1', '25.0.4+1.1'],
['25.0.4+1', '25.0.4+1'],
['25.0.4', '25.0.4+1.10'],
['25', '25.0.4+1.10']
])('version is %s -> %s', async (input, expected) => {
const result = await distribution['findPackageForDownload'](input);
expect(result).toEqual({
version: expected,
url: `https://download.bell-sw.com/java/${expected}/bellsoft-jdk${expected}-macos-aarch64.tar.gz`
});
});
it('does not substitute a different compound build', async () => {
await expect(
distribution['findPackageForDownload']('25.0.4+1.3')
).rejects.toThrow(/No matching version found for SemVer/);
});
});
it('should throw an error', async () => { it('should throw an error', async () => {
await expect(distribution['findPackageForDownload']('17')).rejects.toThrow( await expect(distribution['findPackageForDownload']('17')).rejects.toThrow(
/No matching version found for SemVer/ /No matching version found for SemVer/
@@ -335,6 +368,36 @@ describe('convertVersionToSemver', () => {
buildVersion: 13 buildVersion: 13
}, },
'11.0.0+13' '11.0.0+13'
],
[
{
version: '25.0.4+1.1',
featureVersion: 25,
interimVersion: 0,
updateVersion: 4,
buildVersion: 1
},
'25.0.4+1.1'
],
[
{
version: '25+36',
featureVersion: 25,
interimVersion: 0,
updateVersion: 0,
buildVersion: 36
},
'25.0.0+36'
],
[
{
version: '8u202',
featureVersion: 8,
interimVersion: 0,
updateVersion: 202,
buildVersion: 8
},
'8.0.202+8'
] ]
])('%s -> %s', (input, expected) => { ])('%s -> %s', (input, expected) => {
const actual = distributions['convertVersionToSemver']({ const actual = distributions['convertVersionToSemver']({
@@ -58,7 +58,7 @@ jest.unstable_mockModule('@actions/tool-cache', () => ({
})); }));
jest.unstable_mockModule('../../src/jdk-cache.js', () => ({ jest.unstable_mockModule('../../src/jdk-cache.js', () => ({
getJdkVerificationIdentity: jest.fn(() => 'unverified'), getJdkVerificationIdentity: jest.fn(() => 'disabled'),
registerJdk: jest.fn(), registerJdk: jest.fn(),
restoreJdk: jest.fn() restoreJdk: jest.fn()
})); }));
@@ -106,7 +106,7 @@ describe('setupJava', () => {
beforeEach(() => { beforeEach(() => {
(jdkCache.getJdkVerificationIdentity as jest.Mock).mockReturnValue( (jdkCache.getJdkVerificationIdentity as jest.Mock).mockReturnValue(
'unverified' 'disabled'
); );
spyGetToolcachePath = util.getToolcachePath as jest.Mock; spyGetToolcachePath = util.getToolcachePath as jest.Mock;
spyGetToolcachePath.mockImplementation( spyGetToolcachePath.mockImplementation(
@@ -283,7 +283,7 @@ describe('setupJava', () => {
expect.objectContaining({ expect.objectContaining({
distribution: 'jdkfile', distribution: 'jdkfile',
version: actualJavaVersion, version: actualJavaVersion,
verification: 'unverified' verification: 'disabled'
}) })
); );
} finally { } finally {
@@ -398,13 +398,12 @@ describe('downloadTool', () => {
jest.restoreAllMocks(); jest.restoreAllMocks();
}); });
it('verifies signature when enabled', async () => { it('verifies signatures by default', async () => {
const signedDistribution = new MicrosoftDistributions({ const signedDistribution = new MicrosoftDistributions({
version: '17', version: '17',
architecture: 'x64', architecture: 'x64',
packageType: 'jdk', packageType: 'jdk',
checkLatest: false, checkLatest: false
verifySignature: true
}); });
await signedDistribution['downloadTool']({ await signedDistribution['downloadTool']({
@@ -445,6 +444,64 @@ describe('downloadTool', () => {
); );
}); });
it('warns with key rotation recovery guidance when default verification fails', async () => {
spyVerifySignature.mockRejectedValue(new Error('bad signature'));
const signedDistribution = new MicrosoftDistributions({
version: '17',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
await signedDistribution['downloadTool']({
version: '17.0.14+7',
url: 'https://example.com/jdk.tar.gz',
signatureUrl: 'https://example.com/jdk.tar.gz.sig'
});
expect(core.warning).toHaveBeenCalledWith(
expect.stringMatching(
/bad signature.*https:\/\/github\.com\/actions\/setup-java#download-integrity-and-signatures/
)
);
expect(spyExtractJdkFile).toHaveBeenCalled();
});
it('fails with recovery guidance when verification is explicitly enabled', async () => {
spyVerifySignature.mockRejectedValue(new Error('bad signature'));
const signedDistribution = new MicrosoftDistributions({
version: '17',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false,
verifySignature: true
});
await expect(
signedDistribution['downloadTool']({
version: '17.0.14+7',
url: 'https://example.com/jdk.tar.gz',
signatureUrl: 'https://example.com/jdk.tar.gz.sig'
})
).rejects.toThrow(
/bad signature.*https:\/\/github\.com\/actions\/setup-java#download-integrity-and-signatures/
);
expect(spyExtractJdkFile).not.toHaveBeenCalled();
});
it('warns when the signature is missing during default verification', async () => {
await distribution['downloadTool']({
version: '17.0.14+7',
url: 'https://example.com/jdk.tar.gz'
});
expect(core.warning).toHaveBeenCalledWith(
"Input 'verify-signature' is enabled, but no signature URL was found for Microsoft Build of OpenJDK version 17.0.14+7."
);
expect(spyVerifySignature).not.toHaveBeenCalled();
expect(spyExtractJdkFile).toHaveBeenCalled();
});
it('fails when signature is missing and verification is enabled', async () => { it('fails when signature is missing and verification is enabled', async () => {
const signedDistribution = new MicrosoftDistributions({ const signedDistribution = new MicrosoftDistributions({
version: '17', version: '17',
@@ -73,6 +73,7 @@ jest.unstable_mockModule('../../src/util.js', () => ({
jest.unstable_mockModule('../../src/gpg.js', () => ({ jest.unstable_mockModule('../../src/gpg.js', () => ({
importKey: jest.fn(), importKey: jest.fn(),
removeGpgHome: jest.fn(), removeGpgHome: jest.fn(),
isGpgAvailable: jest.fn(),
verifyPackageSignature: jest.fn() verifyPackageSignature: jest.fn()
})); }));
@@ -292,7 +293,8 @@ describe('getAvailableVersions', () => {
it.each([ it.each([
['amd64', 'x64'], ['amd64', 'x64'],
['arm', 'arm'], ['arm', 'arm'],
['arm64', 'aarch64'] ['arm64', 'aarch64'],
['riscv64', 'riscv64']
])( ])(
'defaults to os.arch(): %s mapped to distro arch: %s', 'defaults to os.arch(): %s mapped to distro arch: %s',
async (osArch: string, distroArch: string) => { async (osArch: string, distroArch: string) => {
@@ -376,6 +378,97 @@ describe('findPackageForDownload', () => {
expect(resolvedVersion.version).toBe('16.0.2+7'); expect(resolvedVersion.version).toBe('16.0.2+7');
}); });
describe('OpenJDK patch (respin) versions', () => {
const makeRelease = (
semverVersion: string,
openjdkVersion: string,
versionData: Record<string, number>
) => ({
binaries: [
{
package: {
link: `https://example.com/${openjdkVersion}.tar.gz`,
checksum: `checksum-${openjdkVersion}`,
checksum_link: `https://example.com/${openjdkVersion}.sha256.txt`
}
}
],
version_data: {
semver: semverVersion,
openjdk_version: openjdkVersion,
minor: 0,
...versionData
}
});
const respinManifest = [
makeRelease('26.0.2+101', '26.0.2.1+1', {
major: 26,
security: 2,
patch: 1,
build: 1
}),
makeRelease('26.0.2+10', '26.0.2+10', {
major: 26,
security: 2,
build: 10
}),
makeRelease('25.0.4+101.0.LTS', '25.0.4.1+1-LTS', {
major: 25,
security: 4,
patch: 1,
build: 1
}),
makeRelease('25.0.4+7.0.LTS', '25.0.4+7-LTS', {
major: 25,
security: 4,
build: 7
})
];
it.each([
['26.0.2.1+1', '26.0.2+101'],
['26.0.2+10', '26.0.2+10'],
['26', '26.0.2+101'],
['26.0.2', '26.0.2+101'],
['25.0.4.1+1', '25.0.4+101.0.LTS'],
['25.0.4+7', '25.0.4+7.0.LTS'],
['25.0.4', '25.0.4+101.0.LTS']
])('%s resolves to %s', async (input, expected) => {
const distribution = new TemurinDistribution(
{
version: input,
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
},
TemurinImplementation.Hotspot
);
distribution['getAvailableVersions'] = async () => respinManifest as any;
const resolvedVersion = await distribution['findPackageForDownload'](
distribution['version']
);
expect(resolvedVersion.version).toBe(expected);
expect(resolvedVersion).not.toHaveProperty('openjdkVersion');
});
it('does not match a non-existent respin', async () => {
const distribution = new TemurinDistribution(
{
version: '26.0.2.2+1',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
},
TemurinImplementation.Hotspot
);
distribution['getAvailableVersions'] = async () => respinManifest as any;
await expect(
distribution['findPackageForDownload'](distribution['version'])
).rejects.toThrow(/No matching version found for SemVer '26.0.2\+2.1'/);
});
});
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(
{ {
@@ -437,6 +530,7 @@ describe('downloadTool', () => {
beforeEach(() => { beforeEach(() => {
spyDownloadTool = tc.downloadTool as jest.Mock; spyDownloadTool = tc.downloadTool as jest.Mock;
spyDownloadTool.mockResolvedValue('/tmp/jdk.tar.gz'); spyDownloadTool.mockResolvedValue('/tmp/jdk.tar.gz');
(gpg.isGpgAvailable as jest.Mock).mockResolvedValue(true);
spyVerifySignature = gpg.verifyPackageSignature as jest.Mock; spyVerifySignature = gpg.verifyPackageSignature as jest.Mock;
spyVerifySignature.mockResolvedValue(undefined); spyVerifySignature.mockResolvedValue(undefined);
spyExtractJdkFile = util.extractJdkFile as jest.Mock; spyExtractJdkFile = util.extractJdkFile as jest.Mock;
@@ -457,14 +551,13 @@ describe('downloadTool', () => {
jest.restoreAllMocks(); jest.restoreAllMocks();
}); });
it('verifies signature when enabled', async () => { it('verifies signatures by default', async () => {
const distribution = new TemurinDistribution( const distribution = new TemurinDistribution(
{ {
version: '17', version: '17',
architecture: 'x64', architecture: 'x64',
packageType: 'jdk', packageType: 'jdk',
checkLatest: false, checkLatest: false
verifySignature: true
}, },
TemurinImplementation.Hotspot TemurinImplementation.Hotspot
); );
@@ -482,6 +575,154 @@ describe('downloadTool', () => {
); );
}); });
it('does not verify signatures when explicitly disabled', async () => {
const distribution = new TemurinDistribution(
{
version: '17',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false,
verifySignature: false
},
TemurinImplementation.Hotspot
);
await distribution['downloadTool']({
version: '17.0.14+7',
url: 'https://example.com/jdk.tar.gz',
signatureUrl: 'https://example.com/jdk.tar.gz.sig'
});
expect(spyVerifySignature).not.toHaveBeenCalled();
});
it('skips implicit signature verification when gpg is unavailable', async () => {
(gpg.isGpgAvailable as jest.Mock).mockResolvedValue(false);
const distribution = new TemurinDistribution(
{
version: '17',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
},
TemurinImplementation.Hotspot
);
await expect(
distribution['downloadTool']({
version: '17.0.14+7',
url: 'https://example.com/jdk.tar.gz',
signatureUrl: 'https://example.com/jdk.tar.gz.sig'
})
).resolves.toEqual({version: '17.0.14+7', path: '/tmp/toolcache'});
expect(spyVerifySignature).not.toHaveBeenCalled();
expect(core.warning).toHaveBeenCalledWith(
"Input 'verify-signature' is enabled, but gpg is not available."
);
});
it('fails when signature verification is explicitly enabled without gpg', async () => {
(gpg.isGpgAvailable as jest.Mock).mockResolvedValue(false);
const distribution = new TemurinDistribution(
{
version: '17',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false,
verifySignature: true
},
TemurinImplementation.Hotspot
);
await expect(
distribution['downloadTool']({
version: '17.0.14+7',
url: 'https://example.com/jdk.tar.gz',
signatureUrl: 'https://example.com/jdk.tar.gz.sig'
})
).rejects.toThrow(
"Input 'verify-signature' is enabled, but gpg is not available."
);
expect(spyVerifySignature).not.toHaveBeenCalled();
});
it('warns when implicit signature verification fails', async () => {
spyVerifySignature.mockRejectedValue(new Error('bad signature'));
const distribution = new TemurinDistribution(
{
version: '17',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
},
TemurinImplementation.Hotspot
);
await expect(
distribution['downloadTool']({
version: '17.0.14+7',
url: 'https://example.com/jdk.tar.gz',
signatureUrl: 'https://example.com/jdk.tar.gz.sig'
})
).resolves.toEqual({version: '17.0.14+7', path: '/tmp/toolcache'});
expect(core.warning).toHaveBeenCalledWith(
expect.stringContaining(
'https://github.com/actions/setup-java#download-integrity-and-signatures'
)
);
});
it('fails when explicitly requested signature verification fails', async () => {
spyVerifySignature.mockRejectedValue(new Error('bad signature'));
const distribution = new TemurinDistribution(
{
version: '17',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false,
verifySignature: true
},
TemurinImplementation.Hotspot
);
await expect(
distribution['downloadTool']({
version: '17.0.14+7',
url: 'https://example.com/jdk.tar.gz',
signatureUrl: 'https://example.com/jdk.tar.gz.sig'
})
).rejects.toThrow(
/Failed to verify signature for Temurin version 17\.0\.14\+7.*bad signature.*https:\/\/github\.com\/actions\/setup-java#download-integrity-and-signatures/
);
});
it('warns when a signature is missing and verification is implicit', async () => {
const distribution = new TemurinDistribution(
{
version: '17',
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
},
TemurinImplementation.Hotspot
);
await expect(
distribution['downloadTool']({
version: '17.0.14+7',
url: 'https://example.com/jdk.tar.gz'
})
).resolves.toEqual({version: '17.0.14+7', path: '/tmp/toolcache'});
expect(core.warning).toHaveBeenCalledWith(
"Input 'verify-signature' is enabled, but no signature URL was found for Temurin version 17.0.14+7."
);
expect(spyVerifySignature).not.toHaveBeenCalled();
});
it('downloads and adds matching JMODs to the JDK', async () => { it('downloads and adds matching JMODs to the JDK', async () => {
spyDownloadTool spyDownloadTool
.mockResolvedValueOnce('/tmp/jdk.tar.gz') .mockResolvedValueOnce('/tmp/jdk.tar.gz')
@@ -499,7 +740,8 @@ describe('downloadTool', () => {
version: '25', version: '25',
architecture: 'x64', architecture: 'x64',
packageType: 'jdk+jmods', packageType: 'jdk+jmods',
checkLatest: false checkLatest: false,
verifySignature: false
}, },
TemurinImplementation.Hotspot TemurinImplementation.Hotspot
); );
@@ -334,6 +334,69 @@ describe('findPackageForDownload', () => {
); );
}); });
describe('hotfix builds with a 4-segment java_version', () => {
// Mirrors the Azul Metadata API: 25.0.4.1 hotfix is reported as
// java_version=[25,0,4,1], openjdk_build_number=1 (SDKMAN '25.0.4+1.1').
const hotfixManifest = [
{
package_uuid: 'uuid-25.0.4+7',
name: 'zulu25.36.15-ca-jdk25.0.4-linux_x64.tar.gz',
download_url:
'https://cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-linux_x64.tar.gz',
java_version: [25, 0, 4],
openjdk_build_number: 7,
distro_version: [25, 36, 15, 0],
latest: false,
availability_type: 'ca'
},
{
package_uuid: 'uuid-25.0.4+1.1',
name: 'zulu25.36.205-ca-jdk25.0.4.1-linux_x64.tar.gz',
download_url:
'https://cdn.azul.com/zulu/bin/zulu25.36.205-ca-jdk25.0.4.1-linux_x64.tar.gz',
java_version: [25, 0, 4, 1],
openjdk_build_number: 1,
distro_version: [25, 36, 205, 0],
latest: true,
availability_type: 'ca'
},
{
package_uuid: 'uuid-25.0.3+9',
name: 'zulu25.34.17-ca-jdk25.0.3-linux_x64.tar.gz',
download_url:
'https://cdn.azul.com/zulu/bin/zulu25.34.17-ca-jdk25.0.3-linux_x64.tar.gz',
java_version: [25, 0, 3],
openjdk_build_number: 9,
distro_version: [25, 34, 17, 0],
latest: false,
availability_type: 'ca'
}
] as IZuluVersions[];
it.each([
['25.0.4+1.1', '25.0.4+1.1', 'uuid-25.0.4+1.1'],
['25', '25.0.4+1.1', 'uuid-25.0.4+1.1'],
['25.0.4', '25.0.4+1.1', 'uuid-25.0.4+1.1'],
['25.0.4+7', '25.0.4+7', 'uuid-25.0.4+7'],
['25.0.3', '25.0.3+9', 'uuid-25.0.3+9']
])('version is %s -> %s', async (input, expected, uuid) => {
const distribution = new ZuluDistribution({
version: input,
architecture: 'x64',
packageType: 'jdk',
checkLatest: false
});
distribution['getAvailableVersions'] = async () => hotfixManifest;
const result = await distribution['findPackageForDownload'](
distribution['version']
);
expect(result.version).toBe(expected);
expect(result.url).toBe(
hotfixManifest.find(item => item.package_uuid === uuid)!.download_url
);
});
});
it('should throw an error', async () => { it('should throw an error', async () => {
const distribution = new ZuluDistribution({ const distribution = new ZuluDistribution({
version: '18', version: '18',
+87
View File
@@ -0,0 +1,87 @@
import {afterAll, beforeAll, describe, expect, it} from '@jest/globals';
import {spawnSync} from 'child_process';
import fs from 'fs';
import os from 'os';
import path from 'path';
import {fileURLToPath, pathToFileURL} from 'url';
const dist = fileURLToPath(new URL('../dist/', import.meta.url));
let tempDir: string;
let linkedDist: string;
beforeAll(() => {
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'setup-java-entrypoints-'));
linkedDist = path.join(tempDir, 'linked # dist');
fs.symlinkSync(dist, linkedDist, 'junction');
});
afterAll(() => {
fs.rmSync(tempDir, {recursive: true, force: true});
});
function execute(args: string[], input?: string) {
return spawnSync(process.execPath, args, {
encoding: 'utf8',
input,
timeout: 10000,
env: {
PATH: process.env.PATH,
SystemRoot: process.env.SystemRoot
}
});
}
describe.each([
['setup', 1, 'java-version or java-version-file input expected'],
['cleanup', 0, '']
] as const)('%s entrypoint', (name, exitCode, output) => {
it.each(['direct', 'symlink', 'preserved symlink'])(
'executes through a %s path',
mode => {
const entry = path.join(
mode === 'direct' ? dist : linkedDist,
name,
'index.js'
);
const args =
mode === 'preserved symlink'
? ['--preserve-symlinks-main', entry]
: [entry];
const result = execute(args);
expect(result.error).toBeUndefined();
expect(result.status).toBe(exitCode);
expect(result.stderr).toBe('');
expect(result.stdout).not.toContain('skipping the execution');
if (output) {
expect(result.stdout).toContain(output);
} else {
expect(result.stdout).toBe('');
}
}
);
it.each(['eval', 'stdin', 'file'])(
'does not execute when imported from %s',
mode => {
const moduleUrl = pathToFileURL(path.join(dist, name, 'index.js')).href;
const source = `const {run} = await import(${JSON.stringify(moduleUrl)}); console.log(typeof run);`;
const importer = path.join(tempDir, `${name}-importer.mjs`);
fs.writeFileSync(importer, source);
const args =
mode === 'file'
? [importer]
: mode === 'eval'
? ['--input-type=module', '-e', source]
: ['--input-type=module', '-'];
const result = execute(args, mode === 'stdin' ? source : undefined);
expect(result.error).toBeUndefined();
expect(result.status).toBe(0);
expect(result.stderr).toBe('');
expect(result.stdout).toContain('skipping the execution');
expect(result.stdout).toContain('function');
expect(result.stdout).not.toContain('::error::');
}
);
});
+125 -1
View File
@@ -9,10 +9,18 @@ import {
} from '@jest/globals'; } from '@jest/globals';
import {fileURLToPath} from 'url'; import {fileURLToPath} from 'url';
import * as fs from 'fs'; import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as io from '@actions/io'; import * as io from '@actions/io';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const mockTmpDir = jest.fn(os.tmpdir);
jest.unstable_mockModule('os', () => ({
...os,
default: {...os, tmpdir: mockTmpDir},
tmpdir: mockTmpDir
}));
jest.unstable_mockModule('@actions/exec', () => ({ jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn() exec: jest.fn()
@@ -34,6 +42,7 @@ describe('gpg tests', () => {
await io.rmRF(tempDir); await io.rmRF(tempDir);
await io.mkdirP(tempDir); await io.mkdirP(tempDir);
jest.clearAllMocks(); jest.clearAllMocks();
mockTmpDir.mockImplementation(os.tmpdir);
(exec.exec as jest.Mock<any>).mockResolvedValue(0); (exec.exec as jest.Mock<any>).mockResolvedValue(0);
}); });
@@ -222,6 +231,94 @@ describe('gpg tests', () => {
}); });
describe('verifyPackageSignature', () => { describe('verifyPackageSignature', () => {
describe.each(['long', 'canonical macOS'])('%s TMPDIR', tempDirKind => {
afterEach(() => {
process.env['RUNNER_TEMP'] = tempDir;
});
it.each([
'success',
'import failure',
'verification failure',
'gpgconf unavailable'
])(
'uses a short macOS home or RUNNER_TEMP elsewhere and cleans up after %s',
async outcome => {
const longRunnerTemp = path.join(
tempDir,
'long-runner-path-'.repeat(8)
);
const signaturePath = path.join(tempDir, 'jdk.tar.gz.sig');
const expectedParent =
process.platform === 'darwin' ? '/tmp' : longRunnerTemp;
let gpgHome = '';
process.env['RUNNER_TEMP'] = longRunnerTemp;
mockTmpDir.mockReturnValue(
tempDirKind === 'long'
? longRunnerTemp
: `/private/var/folders/ab/${'c'.repeat(31)}/T`
);
fs.mkdirSync(longRunnerTemp, {recursive: true});
fs.writeFileSync(signaturePath, 'signature');
(tc.downloadTool as jest.Mock<any>).mockResolvedValue(signaturePath);
(exec.exec as jest.Mock<any>).mockImplementation(
async (command: string, args: string[]) => {
gpgHome = path.join(expectedParent, path.posix.basename(args[1]));
expect(args[1]).toBe(gpg.toGpgPath(gpgHome));
if (command === 'gpgconf') {
expect(fs.existsSync(gpgHome)).toBe(true);
if (outcome === 'gpgconf unavailable') {
throw new Error('gpgconf unavailable');
}
return 0;
}
if (process.platform === 'darwin') {
expect(
Buffer.byteLength(path.join(gpgHome, 'S.gpg-agent.browser'))
).toBeLessThan(104);
}
expect(
fs.readFileSync(path.join(gpgHome, 'public-key-0.asc'), 'utf8')
).toBe('public key');
if (process.platform !== 'win32') {
expect(fs.statSync(gpgHome).mode & 0o777).toBe(0o700);
}
if (
(outcome === 'import failure' && args.includes('--import')) ||
(outcome === 'verification failure' &&
args.includes('--verify'))
) {
throw new Error(outcome);
}
return 0;
}
);
const verification = gpg.verifyPackageSignature(
path.join(tempDir, 'jdk.tar.gz'),
'https://example.com/jdk.tar.gz.sig',
'public key'
);
if (outcome === 'success' || outcome === 'gpgconf unavailable') {
await verification;
} else {
await expect(verification).rejects.toThrow(outcome);
}
expect(exec.exec).toHaveBeenCalledTimes(
outcome === 'import failure' ? 2 : 3
);
expect(exec.exec).toHaveBeenLastCalledWith(
'gpgconf',
['--homedir', gpg.toGpgPath(gpgHome), '--kill', 'gpg-agent'],
{silent: true, ignoreReturnCode: true}
);
expect(fs.existsSync(gpgHome)).toBe(false);
expect(fs.existsSync(signaturePath)).toBe(false);
expect(fs.readdirSync(longRunnerTemp)).toEqual([]);
}
);
});
it('imports bundled key and verifies package', async () => { it('imports bundled key and verifies package', async () => {
const publicKeyContent = const publicKeyContent =
'-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest\n-----END PGP PUBLIC KEY BLOCK-----'; '-----BEGIN PGP PUBLIC KEY BLOCK-----\ntest\n-----END PGP PUBLIC KEY BLOCK-----';
@@ -245,7 +342,7 @@ describe('gpg tests', () => {
expect.any(String), expect.any(String),
'--batch', '--batch',
'--import', '--import',
expect.stringContaining('public-key.asc') expect.stringContaining('public-key-0.asc')
], ],
expect.objectContaining({silent: true}) expect.objectContaining({silent: true})
); );
@@ -263,5 +360,32 @@ describe('gpg tests', () => {
expect.objectContaining({silent: true}) expect.objectContaining({silent: true})
); );
}); });
it('imports multiple bundled keys before verifying the package', async () => {
(tc.downloadTool as jest.Mock<any>).mockResolvedValue(
'/tmp/jdk.tar.gz.sig'
);
await gpg.verifyPackageSignature(
'/tmp/jdk.tar.gz',
'https://example.com/jdk.tar.gz.sig',
['public-key-a', 'public-key-b']
);
expect(exec.exec).toHaveBeenNthCalledWith(
1,
'gpg',
[
'--homedir',
expect.any(String),
'--batch',
'--import',
expect.stringContaining('public-key-0.asc'),
expect.stringContaining('public-key-1.asc')
],
expect.objectContaining({silent: true})
);
expect(exec.exec).toHaveBeenCalledTimes(3);
});
}); });
}); });
+52
View File
@@ -0,0 +1,52 @@
import {afterEach, beforeEach, describe, expect, it, jest} from '@jest/globals';
import fs from 'fs';
import {isMainModule} from '../src/is-main-module.js';
describe('main module detection', () => {
const originalArgv = process.argv;
beforeEach(() => {
process.argv = [process.execPath, 'entrypoint.js'];
});
afterEach(() => {
process.argv = originalArgv;
jest.restoreAllMocks();
});
it.each([undefined, '-'])(
'skips filesystem access when argv[1] is %s',
entrypoint => {
process.argv =
entrypoint === undefined
? [process.execPath]
: [process.execPath, entrypoint];
const realpath = jest.spyOn(fs, 'realpathSync');
expect(isMainModule(import.meta.url)).toBe(false);
expect(realpath).not.toHaveBeenCalled();
}
);
it.each(['ENOENT', 'ENOTDIR'])(
'treats a non-file entrypoint returning %s as an import',
code => {
jest.spyOn(fs, 'realpathSync').mockImplementation(() => {
throw Object.assign(new Error('No file-based entrypoint'), {code});
});
expect(isMainModule(import.meta.url)).toBe(false);
}
);
it('propagates unexpected filesystem errors', () => {
const error = Object.assign(new Error('Permission denied'), {
code: 'EACCES'
});
jest.spyOn(fs, 'realpathSync').mockImplementation(() => {
throw error;
});
expect(() => isMainModule(import.meta.url)).toThrow(error);
});
});
+8
View File
@@ -25,6 +25,7 @@ describe('Java platform capabilities', () => {
['aarch64', 'aarch64'], ['aarch64', 'aarch64'],
['arm64', 'aarch64'], ['arm64', 'aarch64'],
['ppc64le', 'ppc64le'], ['ppc64le', 'ppc64le'],
['RiScV64', 'riscv64'],
['s390x', 's390x'] ['s390x', 's390x']
])('normalizes architecture %s to %s', (input, expected) => { ])('normalizes architecture %s to %s', (input, expected) => {
expect(normalizeArchitecture(input)).toBe(expected); expect(normalizeArchitecture(input)).toBe(expected);
@@ -68,6 +69,12 @@ describe('Java platform capabilities', () => {
); );
}); });
it('allows Temurin on Linux riscv64', () => {
expect(validateJavaPlatform('temurin', 'linux', 'riscv64', '25')).toBe(
'riscv64'
);
});
it('rejects OS-specific restrictions with a consistent diagnostic', () => { it('rejects OS-specific restrictions with a consistent diagnostic', () => {
expect(() => expect(() =>
validateJavaPlatform('oracle', 'win32', 'arm64', '21') validateJavaPlatform('oracle', 'win32', 'arm64', '21')
@@ -115,6 +122,7 @@ describe('Java platform capabilities', () => {
'aarch64', 'aarch64',
'ppc64le', 'ppc64le',
'ppc64', 'ppc64',
'riscv64',
's390x' 's390x'
]) { ]) {
expect(content).toContain(architecture); expect(content).toContain(architecture);
+45 -18
View File
@@ -43,7 +43,7 @@ const jdk = {
architecture: 'x64', architecture: 'x64',
version: '21.0.8+9', version: '21.0.8+9',
source: 'sha256:abc123', source: 'sha256:abc123',
verification: 'unverified', verification: 'disabled',
path: '/toolcache/Java_temurin_jdk/21.0.8-9' path: '/toolcache/Java_temurin_jdk/21.0.8-9'
}; };
@@ -117,32 +117,59 @@ describe('JDK cache', () => {
); );
}); });
it('separates unverified, bundled-key, and custom-key caches', () => { it('separates verification policies and keys', () => {
const unverified = getJdkVerificationIdentity(false); const disabled = getJdkVerificationIdentity(false, false);
const bundled = getJdkVerificationIdentity(true); const checkAndWarnBundled = getJdkVerificationIdentity(true, false);
const enforcedBundled = getJdkVerificationIdentity(true, true);
const customA = getJdkVerificationIdentity( const customA = getJdkVerificationIdentity(
true,
true, true,
'-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nkey-a\r\n-----END PGP PUBLIC KEY BLOCK-----\r\n' '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\nkey-a\r\n-----END PGP PUBLIC KEY BLOCK-----\r\n'
); );
const customANormalized = getJdkVerificationIdentity( const customANormalized = getJdkVerificationIdentity(
true,
true, true,
'-----BEGIN PGP PUBLIC KEY BLOCK-----\nkey-a\n-----END PGP PUBLIC KEY BLOCK-----' '-----BEGIN PGP PUBLIC KEY BLOCK-----\nkey-a\n-----END PGP PUBLIC KEY BLOCK-----'
); );
const customB = getJdkVerificationIdentity(true, 'different-key'); const customB = getJdkVerificationIdentity(true, true, 'different-key');
const checkAndWarnCustomA = getJdkVerificationIdentity(
expect(new Set([unverified, bundled, customA, customB])).toHaveProperty( true,
'size', false,
4 '-----BEGIN PGP PUBLIC KEY BLOCK-----\nkey-a\n-----END PGP PUBLIC KEY BLOCK-----'
); );
const customList = getJdkVerificationIdentity(true, true, [
'key-a',
'key-b'
]);
const customListWithDifferentBoundary = getJdkVerificationIdentity(
true,
true,
['key-ak', 'ey-b']
);
expect(
new Set([
disabled,
checkAndWarnBundled,
enforcedBundled,
customA,
customB
])
).toHaveProperty('size', 5);
expect(disabled).toBe('disabled');
expect(checkAndWarnBundled).toBe('check-and-warn:bundled');
expect(enforcedBundled).toBe('enforced:bundled');
expect(checkAndWarnCustomA).not.toBe(customA);
expect(customA).toBe(customANormalized); expect(customA).toBe(customANormalized);
expect(customA).not.toContain('key-a'); expect(customA).not.toContain('key-a');
expect(customList).not.toBe(customListWithDifferentBoundary);
expect( expect(
new Set( new Set(
[unverified, bundled, customA, customB].map(verification => [disabled, checkAndWarnBundled, enforcedBundled, customA, customB].map(
buildJdkCacheKey({...jdk, verification}) verification => buildJdkCacheKey({...jdk, verification})
) )
) )
).toHaveProperty('size', 4); ).toHaveProperty('size', 5);
}); });
it('restores and records an exact JDK cache hit', async () => { it('restores and records an exact JDK cache hit', async () => {
@@ -210,12 +237,12 @@ describe('JDK cache', () => {
it('saves only the key matching the installation that occupies the path', async () => { it('saves only the key matching the installation that occupies the path', async () => {
const jdkPath = createInstallation(); const jdkPath = createInstallation();
const verified = {...jdk, path: jdkPath, verification: 'verified:bundled'}; const enforced = {...jdk, path: jdkPath, verification: 'enforced:bundled'};
const unverified = {...jdk, path: jdkPath}; const disabled = {...jdk, path: jdkPath};
registerJdk(verified); registerJdk(enforced);
writeInstallation(jdkPath, 'force-downloaded-without-verification'); writeInstallation(jdkPath, 'force-downloaded-without-verification');
registerJdk(unverified); registerJdk(disabled);
(core.getState as jest.Mock).mockReturnValue(lastState()); (core.getState as jest.Mock).mockReturnValue(lastState());
(cache.saveCache as jest.Mock).mockResolvedValue(1); (cache.saveCache as jest.Mock).mockResolvedValue(1);
@@ -223,11 +250,11 @@ describe('JDK cache', () => {
expect(cache.saveCache).not.toHaveBeenCalledWith( expect(cache.saveCache).not.toHaveBeenCalledWith(
[jdkPath], [jdkPath],
buildJdkCacheKey(verified) buildJdkCacheKey(enforced)
); );
expect(cache.saveCache).toHaveBeenCalledWith( expect(cache.saveCache).toHaveBeenCalledWith(
[jdkPath], [jdkPath],
buildJdkCacheKey(unverified) buildJdkCacheKey(disabled)
); );
}); });
@@ -27,6 +27,7 @@ jest.unstable_mockModule('@actions/core', () => ({
jest.unstable_mockModule('fs', () => ({ jest.unstable_mockModule('fs', () => ({
default: { default: {
...jest.requireActual<typeof import('fs')>('fs'),
readFileSync: jest.fn() readFileSync: jest.fn()
} }
})); }));
+33 -2
View File
@@ -27,6 +27,7 @@ jest.unstable_mockModule('@actions/core', () => ({
jest.unstable_mockModule('fs', () => ({ jest.unstable_mockModule('fs', () => ({
default: { default: {
...jest.requireActual<typeof import('fs')>('fs'),
readFileSync: jest.fn() readFileSync: jest.fn()
} }
})); }));
@@ -161,6 +162,37 @@ describe('setup action orchestration', () => {
expect(factory.getJavaDistribution).not.toHaveBeenCalled(); expect(factory.getJavaDistribution).not.toHaveBeenCalled();
}); });
it.each([
['temurin', undefined, undefined],
['zulu', undefined, undefined],
['temurin', false, false],
['zulu', true, true]
])(
'passes signature verification input for %s with explicit value %s as %s',
async (distribution, explicitValue, expectedValue) => {
inputs.set('distribution', distribution);
multilineInputs.set('java-version', ['21']);
if (explicitValue !== undefined) {
inputs.set('verify-signature', String(explicitValue));
booleanInputs.set('verify-signature', explicitValue);
}
(factory.getJavaDistribution as jest.Mock).mockReturnValue({
setupJava: jest.fn(async () => ({
version: '21.0.4+7',
path: '/opt/java/21'
}))
});
await run();
expect(factory.getJavaDistribution).toHaveBeenCalledWith(
distribution,
expect.objectContaining({verifySignature: expectedValue}),
''
);
}
);
it('requires distribution when it cannot be inferred from the version file', async () => { it('requires distribution when it cannot be inferred from the version file', async () => {
inputs.set('java-version-file', '.java-version'); inputs.set('java-version-file', '.java-version');
(fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from('21')); (fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from('21'));
@@ -200,7 +232,6 @@ describe('setup action orchestration', () => {
booleanInputs.set('check-latest', true); booleanInputs.set('check-latest', true);
booleanInputs.set('force-download', true); booleanInputs.set('force-download', true);
booleanInputs.set('set-default', false); booleanInputs.set('set-default', false);
booleanInputs.set('verify-signature', true);
inputs.set('verify-signature-public-key', 'public-key'); inputs.set('verify-signature-public-key', 'public-key');
(fs.readFileSync as jest.Mock).mockReturnValue( (fs.readFileSync as jest.Mock).mockReturnValue(
Buffer.from('java=21.0.5-tem') Buffer.from('java=21.0.5-tem')
@@ -232,7 +263,7 @@ describe('setup action orchestration', () => {
forceDownload: true, forceDownload: true,
cacheJdk: false, cacheJdk: false,
setDefault: false, setDefault: false,
verifySignature: true, verifySignature: undefined,
verifySignaturePublicKey: 'public-key' verifySignaturePublicKey: 'public-key'
}, },
'/tmp/java.tar.gz' '/tmp/java.tar.gz'
+19
View File
@@ -43,6 +43,7 @@ const core = await import('@actions/core');
const { const {
convertVersionToSemver, convertVersionToSemver,
normalizeJavaVersionToSemver,
getNextPageUrlFromLinkHeader, getNextPageUrlFromLinkHeader,
getVersionFromFileContent, getVersionFromFileContent,
isVersionSatisfies, isVersionSatisfies,
@@ -188,6 +189,22 @@ describe('convertVersionToSemver', () => {
}); });
}); });
describe('normalizeJavaVersionToSemver', () => {
it.each([
['17', '17'],
['17.0.8', '17.0.8'],
['17.0.8+7', '17.0.8+7'],
['11.0.9.1', '11.0.9+1'],
['12.0.2.1.0', '12.0.2+1.0'],
['26.0.2.1+1', '26.0.2+1.1'],
['17.0.8.1+1080.1', '17.0.8+1.1080.1'],
['>=11.0.9.1', '>=11.0.9.1'],
['17.x', '17.x']
])('%s -> %s', (input: string, expected: string) => {
expect(normalizeJavaVersionToSemver(input)).toBe(expected);
});
});
describe('getNextPageUrlFromLinkHeader', () => { describe('getNextPageUrlFromLinkHeader', () => {
it.each([ it.each([
[ [
@@ -276,6 +293,8 @@ describe('getVersionFromFileContent', () => {
['java=21.0.5-graal', '21.0.5', 'graalvm'], ['java=21.0.5-graal', '21.0.5', 'graalvm'],
['java=17.0.9-graalce', '17.0.9', 'graalvm'], ['java=17.0.9-graalce', '17.0.9', 'graalvm'],
['java=11.0.25-librca', '11.0.25', 'liberica'], ['java=11.0.25-librca', '11.0.25', 'liberica'],
['java=25.0.4+1.1-librca', '25.0.4+1.1', 'liberica'],
['java=25.0.4+1.1-zulu', '25.0.4+1.1', 'zulu'],
['java=11.0.25-ms', '11.0.25', 'microsoft'], ['java=11.0.25-ms', '11.0.25', 'microsoft'],
['java=21.0.5-oracle', '21.0.5', 'oracle'], ['java=21.0.5-oracle', '21.0.5', 'oracle'],
['java=11.0.25-sapmchn', '11.0.25', 'sapmachine'], ['java=11.0.25-sapmchn', '11.0.25', 'sapmachine'],
+3 -4
View File
@@ -17,7 +17,7 @@ inputs:
required: false required: false
default: 'jdk' default: 'jdk'
architecture: architecture:
description: "The architecture of the package (`x86`, `x64`, `armv7`, `aarch64`, `ppc64le`, `ppc64`, or `s390x`). Aliases `ia32`, `amd64`, `arm`, and `arm64` are normalized to `x86`, `x64`, `armv7`, and `aarch64`. Supported values vary by distribution and operating system. Defaults to the action runner's architecture." description: "The architecture of the package (`x86`, `x64`, `armv7`, `aarch64`, `ppc64le`, `ppc64`, `riscv64`, or `s390x`). Aliases `ia32`, `amd64`, `arm`, and `arm64` are normalized to `x86`, `x64`, `armv7`, and `aarch64`. Supported values vary by distribution and operating system. Defaults to the action runner's architecture."
required: false required: false
jdk-file: jdk-file:
description: 'Path to where the compressed JDK is located' description: 'Path to where the compressed JDK is located'
@@ -39,11 +39,10 @@ inputs:
required: false required: false
default: true default: true
verify-signature: verify-signature:
description: 'Verify downloaded Java package signatures when supported by the selected distribution' description: 'Check downloaded Java package signatures when supported by the selected distribution. When omitted, failures produce warnings. Explicitly setting this to true enforces verification and makes failures fatal, including failures caused by an unexpected vendor signing-key rotation.'
required: false required: false
default: false
verify-signature-public-key: verify-signature-public-key:
description: 'ASCII-armored GPG public key used to verify the downloaded package signature. Overrides the default bundled key for the selected distribution.' description: 'One or more ASCII-armored GPG public keys used to verify downloaded package signatures. Concatenate multiple armored key blocks. Custom keys replace the bundled keys for the selected distribution.'
required: false required: false
server-id: server-id:
description: 'ID of the distributionManagement repository in the pom.xml description: 'ID of the distributionManagement repository in the pom.xml
+15 -6
View File
@@ -122,16 +122,25 @@ function getInstallationIdentity(jdkPath, architecture) {
return undefined; return undefined;
} }
} }
function getJdkVerificationIdentity(verifySignature, publicKey) { function getJdkVerificationIdentity(verifySignature, enforceSignatureVerification, publicKey) {
if (!verifySignature) { if (!verifySignature) {
return 'unverified'; return 'disabled';
} }
const verificationPolicy = enforceSignatureVerification
? 'enforced'
: 'check-and-warn';
if (!publicKey) { if (!publicKey) {
return 'verified:bundled'; return `${verificationPolicy}:bundled`;
} }
const normalizedKey = publicKey.replace(/\r\n?/g, '\n').trim(); const publicKeys = Array.isArray(publicKey) ? publicKey : [publicKey];
const fingerprint = createHash('sha256').update(normalizedKey).digest('hex'); const normalizedKeys = publicKeys.map(key => key.replace(/\r\n?/g, '\n').trim());
return `verified:custom:sha256:${fingerprint}`; const fingerprintSource = Array.isArray(publicKey)
? normalizedKeys.map(key => `${Buffer.byteLength(key)}:${key}`).join('')
: normalizedKeys[0];
const fingerprint = createHash('sha256')
.update(fingerprintSource)
.digest('hex');
return `${verificationPolicy}:custom:sha256:${fingerprint}`;
} }
async function saveJdkCaches() { async function saveJdkCaches() {
const state = lib_core/* getState */.Gu(STATE_JDK_CACHES); const state = lib_core/* getState */.Gu(STATE_JDK_CACHES);
+222 -31
View File
@@ -18360,9 +18360,102 @@ function readAttributeStr(xmlData, i) {
} }
/** /**
* Select all the attributes whether valid or invalid. * Walk `attrStr` once, left to right, splitting it into attribute tokens.
*
* This replaces a regex that used to do the same job
* (`(\s*)([^\s=]+)(\s*=)?(\s*(['"])(([\s\S])*?)\5)?`). That regex led with an
* optional whitespace group followed by a required "non-whitespace" group.
* On a long run of whitespace that never resolves into an attribute name
* (e.g. a tag with thousands of trailing spaces before `>`), the engine
* backtracks the whitespace group one character at a time before giving up
* and moving to the next starting position — one full backtrack per
* position, which is quadratic in the length of the run.
*
* A single forward-only scan can never backtrack, so it can't be made slow
* this way no matter how much whitespace the input contains — it's always
* proportional to the length of the string, once.
*
* Each returned token mirrors the shape the old regex match array had, so
* the validation logic below (which reads token[1]..token[6]) didn't need
* to change:
* token.startIndex - where this token begins in attrStr
* token[1] - leading whitespace before the name
* token[2] - the attribute name
* token[3] - whitespace + '=' if present, else undefined
* token[4] - marker (any defined value) if a quoted value was found
* token[5] - the quote character used ('"' or "'")
* token[6] - the value's text, without the surrounding quotes
*
* A malformed leading character (e.g. a stray '=' with no name before it)
* is simply skipped over, one character at a time — the same outcome the
* old regex produced by failing to match at that position and retrying at
* the next one.
*/ */
const validAttrStrRegxp = new RegExp('(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s\\S])*?)\\5)?', 'g'); function scanAttributeTokens(attrStr) {
const tokens = [];
const len = attrStr.length;
let i = 0;
while (i < len) {
const tokenStart = i;
// Leading whitespace before the name.
while (i < len && isWhiteSpace(attrStr[i])) i++;
if (i >= len) break; // trailing whitespace only — nothing left to read
if (attrStr[i] === '=') {
// No name before this '=' — not a valid attribute start. Move past
// just this one character and try again from the next position.
i = tokenStart + 1;
continue;
}
const leadingWs = attrStr.slice(tokenStart, i);
// Attribute name — everything up to the next whitespace or '='.
const nameStart = i;
while (i < len && !isWhiteSpace(attrStr[i]) && attrStr[i] !== '=') i++;
const name = attrStr.slice(nameStart, i);
// Optional whitespace + '='.
let equalsGroup; // whitespace + '=' text, or undefined if absent
let j = i;
while (j < len && isWhiteSpace(attrStr[j])) j++;
if (j < len && attrStr[j] === '=') {
equalsGroup = attrStr.slice(i, j + 1);
i = j + 1;
}
// Optional whitespace + quoted value.
let quoteChar;
let value;
let k = i;
while (k < len && isWhiteSpace(attrStr[k])) k++;
if (k < len && (attrStr[k] === '"' || attrStr[k] === "'")) {
const valueStart = k + 1;
const closeIdx = attrStr.indexOf(attrStr[k], valueStart);
if (closeIdx !== -1) {
quoteChar = attrStr[k];
value = attrStr.slice(valueStart, closeIdx);
i = closeIdx + 1;
}
// No closing quote found anywhere in the rest of the string — leave
// quoteChar/value undefined, same as the old regex's group failing
// to match a backreference-less run.
}
const token = { startIndex: tokenStart };
token[1] = leadingWs;
token[2] = name;
token[3] = equalsGroup;
token[4] = quoteChar !== undefined ? true : undefined;
token[5] = quoteChar;
token[6] = value;
tokens.push(token);
}
return tokens;
}
//attr, ="sd", a="amit's", a="sd"b="saf", ab cd="" //attr, ="sd", a="amit's", a="sd"b="saf", ab cd=""
@@ -18371,7 +18464,7 @@ function validateAttributeString(attrStr, options) {
//if(attrStr.trim().length === 0) return true; //empty string //if(attrStr.trim().length === 0) return true; //empty string
const matches = getAllMatches(attrStr, validAttrStrRegxp); const matches = scanAttributeTokens(attrStr);
const attrNames = {}; const attrNames = {};
for (let i = 0; i < matches.length; i++) { for (let i = 0; i < matches.length; i++) {
@@ -18473,7 +18566,6 @@ function getLineNumberForPosition(xmlData, index) {
function getPositionFromMatch(match) { function getPositionFromMatch(match) {
return match.startIndex + match[1].length; return match.startIndex + match[1].length;
} }
;// CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/fxp.js ;// CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/fxp.js
@@ -18680,12 +18772,26 @@ class XmlNode {
this.child.push({ [node.tagname]: node.child }); this.child.push({ [node.tagname]: node.child });
} }
// if requested, add the startIndex // if requested, add the startIndex
this.addStartIndex(startIndex);
}
addStartIndex(startIndex) {
if (startIndex !== undefined) { if (startIndex !== undefined) {
// Note: for now we just overwrite the metadata. If we had more complex metadata, // Note: for now we just overwrite the metadata. If we had more complex metadata,
// we might need to do an object append here: metadata = { ...metadata, startIndex } // we might need to do an object append here: metadata = { ...metadata, startIndex }
this.child[this.child.length - 1][METADATA_SYMBOL] = { startIndex }; this.child[this.child.length - 1][METADATA_SYMBOL] = { startIndex };
} }
} }
addEndIndex(endIndex) {
const lastChild = this.child[this.child.length - 1];
// endIndex is write-once: when updateTag drops a node, the last child is a
// previously completed sibling whose endIndex must not be overwritten
if (lastChild !== undefined && lastChild[METADATA_SYMBOL] !== undefined
&& lastChild[METADATA_SYMBOL].endIndex === undefined) {
lastChild[METADATA_SYMBOL].endIndex = endIndex;
}
}
/** symbol used for metadata */ /** symbol used for metadata */
static getMetaDataSymbol() { static getMetaDataSymbol() {
return METADATA_SYMBOL; return METADATA_SYMBOL;
@@ -18718,8 +18824,23 @@ class DocTypeReader {
i = i + 9; i = i + 9;
let angleBracketsCount = 1; let angleBracketsCount = 1;
let hasBody = false, comment = false; let hasBody = false, comment = false;
let quoteChar = null; // tracks an open SYSTEM/PUBLIC literal before the '[' body
let exp = ""; let exp = "";
for (; i < xmlData.length; i++) { for (; i < xmlData.length; i++) {
// Inside a quoted external-identifier literal — XML allows '<'
// and '>' as plain data here, so they must not be interpreted
// as DOCTYPE structure until the matching quote closes.
if (quoteChar !== null) {
if (xmlData[i] === quoteChar) quoteChar = null;
exp += xmlData[i];
continue;
}
if (!hasBody && !comment && (xmlData[i] === '"' || xmlData[i] === "'")) {
quoteChar = xmlData[i];
exp += xmlData[i];
continue;
}
if (xmlData[i] === '<' && !comment) { //Determine the tag type if (xmlData[i] === '<' && !comment) { //Determine the tag type
if (hasBody && hasSeq(xmlData, "!ENTITY", i)) { if (hasBody && hasSeq(xmlData, "!ENTITY", i)) {
i += 7; i += 7;
@@ -18774,7 +18895,7 @@ class DocTypeReader {
exp += xmlData[i]; exp += xmlData[i];
} }
} }
if (angleBracketsCount !== 0) { if (quoteChar !== null || angleBracketsCount !== 0) {
throw new Error(`Unclosed DOCTYPE`); throw new Error(`Unclosed DOCTYPE`);
} }
} else { } else {
@@ -19489,7 +19610,11 @@ function resolveEnotation(str, trimmedStr, options) {
*/ */
function trimZeros(numStr) { function trimZeros(numStr) {
if (numStr && numStr.indexOf(".") !== -1) {//float if (numStr && numStr.indexOf(".") !== -1) {//float
numStr = numStr.replace(/0+$/, ""); //remove ending zeros //remove ending zeros without the O(n^2) backtracking that /0+$/ hits
//when the string doesn't end in 0 but has a long internal zero-run
let end = numStr.length;
while (end > 0 && numStr.charCodeAt(end - 1) === 48 /* '0' */) end--;
numStr = numStr.slice(0, end);
if (numStr === ".") numStr = "0"; if (numStr === ".") numStr = "0";
else if (numStr[0] === ".") numStr = "0" + numStr; else if (numStr[0] === ".") numStr = "0" + numStr;
else if (numStr[numStr.length - 1] === ".") numStr = numStr.substring(0, numStr.length - 1); else if (numStr[numStr.length - 1] === ".") numStr = numStr.substring(0, numStr.length - 1);
@@ -22965,7 +23090,12 @@ const parseXml = function (xmlData) {
this.matcher.pop(); this.matcher.pop();
this.isCurrentNodeStopNode = false; // Reset flag when closing tag this.isCurrentNodeStopNode = false; // Reset flag when closing tag
currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope //a closing tag with no matching opening tag leaves the stack empty
currentNode = this.tagsNodeStack.pop() || xmlObj;//avoid recursion, set the parent tag scope
if (options.captureMetaData && currentNode) {
currentNode.addEndIndex(closeIndex + 1);
}
textData = ""; textData = "";
i = closeIndex; i = closeIndex;
} else if (c1 === 63) { //'?' } else if (c1 === 63) { //'?'
@@ -22991,6 +23121,11 @@ const parseXml = function (xmlData) {
childNode[":@"] = attsMap childNode[":@"] = attsMap
} }
this.addChild(currentNode, childNode, this.readonlyMatcher, i); this.addChild(currentNode, childNode, this.readonlyMatcher, i);
if (options.captureMetaData) {
// closeIndex points at '?' of the closing '?>'
currentNode.addEndIndex(tagData.closeIndex + 2);
}
} }
@@ -23155,6 +23290,10 @@ const parseXml = function (xmlData) {
this.isCurrentNodeStopNode = false; // Reset flag this.isCurrentNodeStopNode = false; // Reset flag
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex); this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
if (options.captureMetaData) {
currentNode.addEndIndex(i + 1);
}
} else { } else {
//selfClosing tag //selfClosing tag
if (isSelfClosing) { if (isSelfClosing) {
@@ -23165,6 +23304,10 @@ const parseXml = function (xmlData) {
childNode[":@"] = prefixedAttrs; childNode[":@"] = prefixedAttrs;
} }
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex); this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
if (options.captureMetaData) {
currentNode.addEndIndex(closeIndex + 1);
}
this.matcher.pop(); // Pop self-closing tag this.matcher.pop(); // Pop self-closing tag
this.isCurrentNodeStopNode = false; // Reset flag this.isCurrentNodeStopNode = false; // Reset flag
} }
@@ -23174,6 +23317,10 @@ const parseXml = function (xmlData) {
childNode[":@"] = prefixedAttrs; childNode[":@"] = prefixedAttrs;
} }
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex); this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
if (options.captureMetaData) {
currentNode.addEndIndex(result.closeIndex + 1);
}
this.matcher.pop(); // Pop unpaired tag this.matcher.pop(); // Pop unpaired tag
this.isCurrentNodeStopNode = false; // Reset flag this.isCurrentNodeStopNode = false; // Reset flag
i = result.closeIndex; i = result.closeIndex;
@@ -59443,6 +59590,24 @@ const EXPANSION_MAX = 100_000;
// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M // realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
// characters) so legitimate input is unaffected. // characters) so legitimate input is unaffected.
const EXPANSION_MAX_LENGTH = 4_000_000; const EXPANSION_MAX_LENGTH = 4_000_000;
// `expand_` recurses once per level of brace *nesting* - both when expanding a
// set's comma members and when re-wrapping a set whose body is a single part.
// The CVE-2026-14257 fix made the *tail* iterative (recursion on `m.post`, one
// level per chained group), which left nesting depth unbounded: about 3,100
// levels of `{{{...a,b...}}}` - only ~6KB of input - exhausted the native stack
// and crashed the process. `EXPANSION_MAX_DEPTH` bounds how deep the parser
// will follow nesting. It sits far above any realistic pattern and well below
// the depth at which the stack runs out.
const EXPANSION_MAX_DEPTH = 1_000;
// Bash keeps a quirk where a brace group followed by a comma set still expands
// (`{a},b}`). The parser implements it by rewriting the string and restarting
// the scan, absorbing one `}` per pass. `n` trailing braces therefore cost `n`
// full passes over a string that itself grows by one `escClose` sentinel each
// time - quadratic in `n`, with a ~26x constant from the sentinel's length.
// 128KB of `'{a}' + '}'.repeat(n) + ',z}'` blocked the event loop for 27
// seconds to produce two results. `EXPANSION_MAX_REWRITES` bounds how many
// times the scan may restart. Real `{a},b}` input needs a handful.
const EXPANSION_MAX_REWRITES = 1_000;
function numeric(str) { function numeric(str) {
return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
} }
@@ -59462,37 +59627,52 @@ function unescapeBraces(str) {
.replace(escCommaPattern, ',') .replace(escCommaPattern, ',')
.replace(escPeriodPattern, '.'); .replace(escPeriodPattern, '.');
} }
// Like `target.push(...items)` but doesn't overflow the stack
function pushAll(target, items) {
for (let i = 0; i < items.length; i++) {
target.push(items[i]);
}
}
/** /**
* Basically just str.split(","), but handling cases * Basically just str.split(","), but handling cases
* where we have nested braced sections, which should be * where we have nested braced sections, which should be
* treated as individual members, like {a,{b,c},d} * treated as individual members, like {a,{b,c},d}
*/ */
function parseCommaParts(str) { function parseCommaParts(str) {
if (!str) {
return [''];
}
const parts = []; const parts = [];
const m = balanced('{', '}', str); // Walk the brace groups iteratively. Recursing on `post` once per group let a
if (!m) { // chain of them exhaust the stack - the parsing-side counterpart to
return str.split(','); // the `expand_` overflow fixed for CVE-2026-14257, and not something `max` or
// `maxLength` can bound, since it happens before expansion.
//
// The part the next chunk continues
let carry = '';
for (;;) {
const m = balanced('{', '}', str);
if (!m) {
const tail = str.split(',');
tail[0] = carry + tail[0];
pushAll(parts, tail);
return parts;
}
const { pre, body, post } = m;
const p = pre.split(',');
p[0] = carry + p[0];
p[p.length - 1] += '{' + body + '}';
if (!post.length) {
pushAll(parts, p);
return parts;
}
carry = p.pop();
pushAll(parts, p);
str = post;
} }
const { pre, body, post } = m;
const p = pre.split(',');
p[p.length - 1] += '{' + body + '}';
const postParts = parseCommaParts(post);
if (post.length) {
;
p[p.length - 1] += postParts.shift();
p.push.apply(p, postParts);
}
parts.push.apply(parts, p);
return parts;
} }
function expand(str, options = {}) { function expand(str, options = {}) {
if (!str) { if (!str) {
return []; return [];
} }
const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH } = options; const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH, maxDepth = EXPANSION_MAX_DEPTH, maxRewrites = EXPANSION_MAX_REWRITES, } = options;
// I don't know why Bash 4.3 does this, but it does. // I don't know why Bash 4.3 does this, but it does.
// Anything starting with {} will have the first two bytes preserved // Anything starting with {} will have the first two bytes preserved
// but *only* at the top level, so {},a}b will not expand to anything, // but *only* at the top level, so {},a}b will not expand to anything,
@@ -59502,7 +59682,7 @@ function expand(str, options = {}) {
if (str.slice(0, 2) === '{}') { if (str.slice(0, 2) === '{}') {
str = '\\{\\}' + str.slice(2); str = '\\{\\}' + str.slice(2);
} }
return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces); return expand_(escapeBraces(str), max, maxLength, maxDepth, 0, maxRewrites, true).map(unescapeBraces);
} }
function embrace(str) { function embrace(str) {
return '{' + str + '}'; return '{' + str + '}';
@@ -59597,7 +59777,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) {
} }
return N; return N;
} }
function expand_(str, max, maxLength, isTop) { function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) {
// Too deeply nested to keep following: treat the rest as literal, the same
// way a group that cannot expand is already handled. Truncating rather than
// throwing keeps `expand` total, matching `max` and `maxLength`.
if (depth > maxDepth) {
return [str];
}
// Consume the string's top-level brace groups left to right, threading a // Consume the string's top-level brace groups left to right, threading a
// running set of combined prefixes (`acc`). Expanding the tail iteratively - // running set of combined prefixes (`acc`). Expanding the tail iteratively -
// rather than recursing on `m.post` once per group - keeps the native stack // rather than recursing on `m.post` once per group - keeps the native stack
@@ -59609,6 +59795,9 @@ function expand_(str, max, maxLength, isTop) {
// comma set - a sequence like `{a..\}` may legitimately yield ''. The drop // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop
// is on the final strings, so it is applied to whichever `combine` produces // is on the final strings, so it is applied to whichever `combine` produces
// them (the one with no brace set left in the tail). // them (the one with no brace set left in the tail).
// How many times the `{a},b}` rewrite below has restarted the scan. Each pass
// re-reads the whole string, so leaving this unbounded is quadratic.
let rewrites = 0;
let dropEmpties = false; let dropEmpties = false;
let firstGroup = true; let firstGroup = true;
for (;;) { for (;;) {
@@ -59633,7 +59822,8 @@ function expand_(str, max, maxLength, isTop) {
const isOptions = m.body.indexOf(',') >= 0; const isOptions = m.body.indexOf(',') >= 0;
if (!isSequence && !isOptions) { if (!isSequence && !isOptions) {
// {a},b} // {a},b}
if (m.post.match(/,(?!,).*\}/)) { if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) {
rewrites++;
str = m.pre + '{' + m.body + escClose + m.post; str = m.pre + '{' + m.body + escClose + m.post;
isTop = true; isTop = true;
continue; continue;
@@ -59653,7 +59843,7 @@ function expand_(str, max, maxLength, isTop) {
let n = parseCommaParts(m.body); let n = parseCommaParts(m.body);
if (n.length === 1 && n[0] !== undefined) { if (n.length === 1 && n[0] !== undefined) {
// x{{a,b}}y ==> x{a}y x{b}y // x{{a,b}}y ==> x{a}y x{b}y
n = expand_(n[0], max, maxLength, false).map(embrace); n = expand_(n[0], max, maxLength, maxDepth, depth + 1, maxRewrites, false).map(embrace);
//XXX is this necessary? Can't seem to hit it in tests. //XXX is this necessary? Can't seem to hit it in tests.
/* c8 ignore start */ /* c8 ignore start */
if (n.length === 1) { if (n.length === 1) {
@@ -59679,12 +59869,13 @@ function expand_(str, max, maxLength, isTop) {
values = []; values = [];
let valuesLength = 0; let valuesLength = 0;
outer: for (let j = 0; j < n.length; j++) { outer: for (let j = 0; j < n.length; j++) {
const expanded = expand_(n[j], max, maxLength, false); const expanded = expand_(n[j], max, maxLength, maxDepth, depth + 1, maxRewrites, false);
for (let k = 0; k < expanded.length; k++) { for (let k = 0; k < expanded.length; k++) {
const v = expanded[k]; const v = expanded[k];
if (dropsEmpties && !v) if (dropsEmpties && !v)
continue; continue;
if (values.length >= max || valuesLength + v.length > maxLength) { if (values.length >= max ||
valuesLength + v.length > maxLength) {
break outer; break outer;
} }
values.push(v); values.push(v);
+523 -202
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -65,7 +65,7 @@ class LocalDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_3__/
architecture: this.architecture, architecture: this.architecture,
version: this.version, version: this.version,
source, source,
verification: getJdkVerificationIdentity(false), verification: getJdkVerificationIdentity(false, false),
path: this.getJdkCachePath(this.version) path: this.getJdkCachePath(this.version)
}; };
} }
+42 -15
View File
@@ -42,6 +42,8 @@ Fa133tP85xzJEq1XeXm8WeLFo2wV
=rHCS =rHCS
-----END PGP PUBLIC KEY BLOCK-----`; -----END PGP PUBLIC KEY BLOCK-----`;
// EXTERNAL MODULE: ./src/constants.ts
var constants = __webpack_require__(7242);
// EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js + 7 modules // EXTERNAL MODULE: ./node_modules/@actions/core/lib/core.js + 7 modules
var core = __webpack_require__(3838); var core = __webpack_require__(3838);
// EXTERNAL MODULE: ./node_modules/@actions/tool-cache/lib/tool-cache.js + 2 modules // EXTERNAL MODULE: ./node_modules/@actions/tool-cache/lib/tool-cache.js + 2 modules
@@ -62,6 +64,7 @@ var external_path_default = /*#__PURE__*/__webpack_require__.n(external_path_);
class MicrosoftDistributions extends base_installer/* JavaBase */.O { class MicrosoftDistributions extends base_installer/* JavaBase */.O {
constructor(installerOptions) { constructor(installerOptions) {
super('Microsoft', installerOptions); super('Microsoft', installerOptions);
@@ -70,15 +73,23 @@ class MicrosoftDistributions extends base_installer/* JavaBase */.O {
core/* info */.pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`); core/* info */.pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease); let javaArchivePath = await this.downloadAndVerify(javaRelease);
if (this.verifySignature) { if (this.verifySignature) {
if (!javaRelease.signatureUrl) {
throw new Error(`Input 'verify-signature' is enabled, but no signature URL was found for Microsoft Build of OpenJDK version ${javaRelease.version}.`);
}
core/* info */.pq(`Verifying Java package signature...`);
try { try {
await gpg/* verifyPackageSignature */.Yi(javaArchivePath, javaRelease.signatureUrl, this.verifySignaturePublicKey ?? MICROSOFT_PUBLIC_KEY); if (!javaRelease.signatureUrl) {
throw new Error(`Input 'verify-signature' is enabled, but no signature URL was found for Microsoft Build of OpenJDK version ${javaRelease.version}.`);
}
core/* info */.pq(`Verifying Java package signature...`);
try {
await gpg/* verifyPackageSignature */.Yi(javaArchivePath, javaRelease.signatureUrl, this.verifySignaturePublicKey ?? MICROSOFT_PUBLIC_KEY);
}
catch (error) {
throw new Error(`Failed to verify signature for Microsoft Build of OpenJDK version ${javaRelease.version}. Signature URL: ${javaRelease.signatureUrl}. Error: ${error.message} ${constants/* SIGNATURE_VERIFICATION_FAILURE_HELP */.kQ}`, { cause: error });
}
} }
catch (error) { catch (error) {
throw new Error(`Failed to verify signature for Microsoft Build of OpenJDK version ${javaRelease.version}. Signature URL: ${javaRelease.signatureUrl}. Error: ${error.message}`, { cause: error }); if (this.verifySignatureExplicitlyRequested) {
throw error;
}
core/* warning */.$e(error instanceof Error ? error.message : `Unknown error: ${error}`);
} }
} }
core/* info */.pq(`Extracting Java archive...`); core/* info */.pq(`Extracting Java archive...`);
@@ -167,7 +178,8 @@ class MicrosoftDistributions extends base_installer/* JavaBase */.O {
/* harmony export */ Fh: () => (/* binding */ importKey), /* harmony export */ Fh: () => (/* binding */ importKey),
/* harmony export */ Yi: () => (/* binding */ verifyPackageSignature), /* harmony export */ Yi: () => (/* binding */ verifyPackageSignature),
/* harmony export */ mS: () => (/* binding */ removeGpgHome), /* harmony export */ mS: () => (/* binding */ removeGpgHome),
/* harmony export */ nY: () => (/* binding */ toGpgPath) /* harmony export */ nY: () => (/* binding */ toGpgPath),
/* harmony export */ o6: () => (/* binding */ isGpgAvailable)
/* harmony export */ }); /* harmony export */ });
/* unused harmony export GPG_HOME_PREFIX */ /* unused harmony export GPG_HOME_PREFIX */
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896);
@@ -189,6 +201,9 @@ class MicrosoftDistributions extends base_installer/* JavaBase */.O {
const GPG_HOME_PREFIX = 'setup-java-gpg-'; const GPG_HOME_PREFIX = 'setup-java-gpg-';
const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-';
async function isGpgAvailable() {
return Boolean(await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .which */ .K7('gpg', false));
}
// Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...). // Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...).
// The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions // The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions
// internally. Passing Windows paths with backslashes can cause fatal GPG errors // internally. Passing Windows paths with backslashes can cause fatal GPG errors
@@ -200,8 +215,8 @@ function toGpgPath(p) {
.replace(/\\/g, '/') .replace(/\\/g, '/')
.replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`); .replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`);
} }
function createGpgHome(prefix) { function createGpgHome(prefix, tempDir = _util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4()) {
const gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4(), prefix)); const gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(tempDir, prefix));
if (process.platform !== 'win32') { if (process.platform !== 'win32') {
fs__WEBPACK_IMPORTED_MODULE_0__.chmodSync(gpgHome, 0o700); fs__WEBPACK_IMPORTED_MODULE_0__.chmodSync(gpgHome, 0o700);
} }
@@ -248,19 +263,24 @@ async function removeGpgHome(gpgHome) {
if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) { if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) {
return; return;
} }
await stopGpgAgent(resolvedGpgHome);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome);
}
async function stopGpgAgent(gpgHome) {
try { try {
await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(gpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true });
} }
catch { catch {
// gpgconf may be unavailable, but directory removal must still be attempted. // gpgconf may be unavailable, but directory removal must still be attempted.
} }
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome);
} }
async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) {
const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl); const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl);
let gpgHome; let gpgHome;
try { try {
gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); // Both RUNNER_TEMP and TMPDIR can exceed macOS's 104-byte agent socket limit.
const tempDir = process.platform === 'darwin' ? '/tmp' : _util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4();
gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX, tempDir);
} }
catch (error) { catch (error) {
try { try {
@@ -272,15 +292,21 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten
throw new Error(`Failed to create temporary GPG home directory for signature verification: ${error.message}`, { cause: error }); throw new Error(`Failed to create temporary GPG home directory for signature verification: ${error.message}`, { cause: error });
} }
try { try {
const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, 'public-key.asc'); const publicKeys = Array.isArray(publicKeyContent)
fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKeyContent, { encoding: 'utf-8' }); ? publicKeyContent
: [publicKeyContent];
const publicKeyFiles = publicKeys.map((publicKey, index) => {
const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, `public-key-${index}.asc`);
fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKey, { encoding: 'utf-8' });
return toGpgPath(publicKeyFile);
});
const options = { silent: true }; const options = { silent: true };
await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [
'--homedir', '--homedir',
toGpgPath(gpgHome), toGpgPath(gpgHome),
'--batch', '--batch',
'--import', '--import',
toGpgPath(publicKeyFile) ...publicKeyFiles
], options); ], options);
await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [
'--homedir', '--homedir',
@@ -292,6 +318,7 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten
], options); ], options);
} }
finally { finally {
await stopGpgAgent(gpgHome);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath); await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome);
} }
+9 -8
View File
@@ -226,6 +226,7 @@ class JavaBase {
floatingVersionVerified = false; floatingVersionVerified = false;
setDefault; setDefault;
verifySignature; verifySignature;
verifySignatureExplicitlyRequested;
verifySignaturePublicKey; verifySignaturePublicKey;
constructor(distribution, installerOptions) { constructor(distribution, installerOptions) {
this.distribution = distribution; this.distribution = distribution;
@@ -244,7 +245,10 @@ class JavaBase {
installerOptions.setDefault !== undefined installerOptions.setDefault !== undefined
? installerOptions.setDefault ? installerOptions.setDefault
: true; : true;
this.verifySignature = installerOptions.verifySignature ?? false; this.verifySignature =
installerOptions.verifySignature ?? this.supportsSignatureVerification();
this.verifySignatureExplicitlyRequested =
installerOptions.verifySignature === true;
this.verifySignaturePublicKey = installerOptions.verifySignaturePublicKey; this.verifySignaturePublicKey = installerOptions.verifySignaturePublicKey;
} }
async downloadAndVerify(javaRelease) { async downloadAndVerify(javaRelease) {
@@ -480,7 +484,7 @@ class JavaBase {
architecture: this.architecture, architecture: this.architecture,
version: javaRelease.version, version: javaRelease.version,
source: this.getJdkReleaseIdentity(javaRelease), source: this.getJdkReleaseIdentity(javaRelease),
verification: getJdkVerificationIdentity(this.verifySignature, this.verifySignaturePublicKey), verification: getJdkVerificationIdentity(this.verifySignature, this.verifySignatureExplicitlyRequested, this.verifySignaturePublicKey),
path: this.getJdkCachePath(javaRelease.version) path: this.getJdkCachePath(javaRelease.version)
}; };
} }
@@ -726,12 +730,9 @@ class JavaBase {
} }
// Java uses a versioning scheme (JEP 322) that can contain more numeric // 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 // 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 // exact versions to SemVer build notation ('18.0.1+1', or '26.0.2+1.1'
// accepted. Ranges and versions that already carry build metadata are // for '26.0.2.1+1') so they are accepted. Ranges are left untouched.
// left untouched. version = (0,util/* normalizeJavaVersionToSemver */.zZ)(version);
if (/^\d+(\.\d+){3,}$/.test(version)) {
version = (0,util/* convertVersionToSemver */.ZY)(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`);
} }
+79 -18
View File
@@ -109,7 +109,7 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
const formattedVersion = this.stable const formattedVersion = this.stable
? item.version_data.semver ? item.version_data.semver
: item.version_data.semver.replace('-beta+', '+'); : item.version_data.semver.replace('-beta+', '+');
return { const release = {
version: formattedVersion, version: formattedVersion,
url: item.binaries[0].package.link, url: item.binaries[0].package.link,
signatureUrl: item.binaries[0].package.signature_link, signatureUrl: item.binaries[0].package.signature_link,
@@ -119,15 +119,29 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
source: item.binaries[0].package.checksum_link source: item.binaries[0].package.checksum_link
} }
}; };
return {
release,
openjdkVersion: getOpenJdkSemverVersion(item.version_data)
};
}); });
// The Adoptium API `semver` folds the JEP 322 patch field into the build
// number ('26.0.2.1+1' -> '26.0.2+101') and appends extra metadata for LTS
// releases ('25.0.4+7' -> '25.0.4+7.0.LTS'). Exact versions requested by
// users follow the OpenJDK notation instead, so also match them against a
// key derived from the OpenJDK version fields ('26.0.2+1.1', '25.0.4+7').
const isExactBuildRequest = (semver_default().parse(version)?.build.length ?? 0) > 0;
const satisfiedVersions = availableVersionsWithBinaries const satisfiedVersions = availableVersionsWithBinaries
.filter(item => (0,util/* isVersionSatisfies */.y)(version, item.version)) .filter(({ release, openjdkVersion }) => (0,util/* isVersionSatisfies */.y)(version, release.version) ||
(isExactBuildRequest &&
openjdkVersion !== null &&
semver_default().compareBuild(version, openjdkVersion) === 0))
.map(({ release }) => release)
.sort((a, b) => { .sort((a, b) => {
return -semver_default().compareBuild(a.version, b.version); return -semver_default().compareBuild(a.version, b.version);
}); });
const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null; const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
if (!resolvedFullVersion) { if (!resolvedFullVersion) {
const availableVersionStrings = availableVersionsWithBinaries.map(item => item.version); const availableVersionStrings = availableVersionsWithBinaries.map(({ release }) => release.version);
throw this.createVersionNotFoundError(version, availableVersionStrings); throw this.createVersionNotFoundError(version, availableVersionStrings);
} }
return resolvedFullVersion; return resolvedFullVersion;
@@ -159,15 +173,32 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
async downloadPackage(release) { async downloadPackage(release) {
const archivePath = await this.downloadAndVerify(release); const archivePath = await this.downloadAndVerify(release);
if (this.verifySignature) { if (this.verifySignature) {
if (!release.signatureUrl) {
throw new Error(`Input 'verify-signature' is enabled, but no signature URL was found for Temurin version ${release.version}.`);
}
core/* info */.pq(`Verifying Java package signature...`);
try { try {
await gpg/* verifyPackageSignature */.Yi(archivePath, release.signatureUrl, this.verifySignaturePublicKey ?? ADOPTIUM_PUBLIC_KEY); if (!(await gpg/* isGpgAvailable */.o6())) {
throw new Error("Input 'verify-signature' is enabled, but gpg is not available.");
}
if (!release.signatureUrl) {
throw new Error(`Input 'verify-signature' is enabled, but no signature URL was found for Temurin version ${release.version}.`);
}
core/* info */.pq(`Verifying Java package signature...`);
try {
await gpg/* verifyPackageSignature */.Yi(archivePath, release.signatureUrl, this.verifySignaturePublicKey ?? ADOPTIUM_PUBLIC_KEY);
}
catch (error) {
const verificationError = new Error(`Failed to verify signature for Temurin version ${release.version} from ${release.signatureUrl}: ${error.message} ${constants/* SIGNATURE_VERIFICATION_FAILURE_HELP */.kQ}`, { cause: error });
if (this.verifySignatureExplicitlyRequested) {
throw verificationError;
}
else {
core/* warning */.$e(verificationError.message);
}
}
} }
catch (error) { catch (error) {
throw new Error(`Failed to verify signature for Temurin version ${release.version} from ${release.signatureUrl}: ${error.message}`, { cause: error }); if (this.verifySignatureExplicitlyRequested) {
throw error;
}
core/* warning */.$e(error instanceof Error ? error.message : `Unknown error: ${error}`);
} }
} }
return archivePath; return archivePath;
@@ -262,6 +293,20 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
return architecture === 'armv7' ? 'arm' : architecture; return architecture === 'armv7' ? 'arm' : architecture;
} }
} }
/**
* Builds a SemVer version from the OpenJDK version fields reported by the
* Adoptium API, e.g. '26.0.2.1+1' -> '26.0.2+1.1' and '25.0.4+7-LTS' ->
* '25.0.4+7'. Returns null if the fields cannot form a valid SemVer version.
*/
function getOpenJdkSemverVersion(versionData) {
const { major, minor, security, patch, build } = versionData;
if (build === undefined || build === null) {
return null;
}
const buildMetadata = patch ? `${patch}.${build}` : `${build}`;
const version = `${major}.${minor}.${security}+${buildMetadata}`;
return semver_default().valid(version) ? version : null;
}
/***/ }), /***/ }),
@@ -273,7 +318,8 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
/* harmony export */ Fh: () => (/* binding */ importKey), /* harmony export */ Fh: () => (/* binding */ importKey),
/* harmony export */ Yi: () => (/* binding */ verifyPackageSignature), /* harmony export */ Yi: () => (/* binding */ verifyPackageSignature),
/* harmony export */ mS: () => (/* binding */ removeGpgHome), /* harmony export */ mS: () => (/* binding */ removeGpgHome),
/* harmony export */ nY: () => (/* binding */ toGpgPath) /* harmony export */ nY: () => (/* binding */ toGpgPath),
/* harmony export */ o6: () => (/* binding */ isGpgAvailable)
/* harmony export */ }); /* harmony export */ });
/* unused harmony export GPG_HOME_PREFIX */ /* unused harmony export GPG_HOME_PREFIX */
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896);
@@ -295,6 +341,9 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
const GPG_HOME_PREFIX = 'setup-java-gpg-'; const GPG_HOME_PREFIX = 'setup-java-gpg-';
const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-';
async function isGpgAvailable() {
return Boolean(await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .which */ .K7('gpg', false));
}
// Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...). // Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...).
// The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions // The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions
// internally. Passing Windows paths with backslashes can cause fatal GPG errors // internally. Passing Windows paths with backslashes can cause fatal GPG errors
@@ -306,8 +355,8 @@ function toGpgPath(p) {
.replace(/\\/g, '/') .replace(/\\/g, '/')
.replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`); .replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`);
} }
function createGpgHome(prefix) { function createGpgHome(prefix, tempDir = _util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4()) {
const gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4(), prefix)); const gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(tempDir, prefix));
if (process.platform !== 'win32') { if (process.platform !== 'win32') {
fs__WEBPACK_IMPORTED_MODULE_0__.chmodSync(gpgHome, 0o700); fs__WEBPACK_IMPORTED_MODULE_0__.chmodSync(gpgHome, 0o700);
} }
@@ -354,19 +403,24 @@ async function removeGpgHome(gpgHome) {
if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) { if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) {
return; return;
} }
await stopGpgAgent(resolvedGpgHome);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome);
}
async function stopGpgAgent(gpgHome) {
try { try {
await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(gpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true });
} }
catch { catch {
// gpgconf may be unavailable, but directory removal must still be attempted. // gpgconf may be unavailable, but directory removal must still be attempted.
} }
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome);
} }
async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) {
const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl); const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl);
let gpgHome; let gpgHome;
try { try {
gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); // Both RUNNER_TEMP and TMPDIR can exceed macOS's 104-byte agent socket limit.
const tempDir = process.platform === 'darwin' ? '/tmp' : _util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4();
gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX, tempDir);
} }
catch (error) { catch (error) {
try { try {
@@ -378,15 +432,21 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten
throw new Error(`Failed to create temporary GPG home directory for signature verification: ${error.message}`, { cause: error }); throw new Error(`Failed to create temporary GPG home directory for signature verification: ${error.message}`, { cause: error });
} }
try { try {
const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, 'public-key.asc'); const publicKeys = Array.isArray(publicKeyContent)
fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKeyContent, { encoding: 'utf-8' }); ? publicKeyContent
: [publicKeyContent];
const publicKeyFiles = publicKeys.map((publicKey, index) => {
const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, `public-key-${index}.asc`);
fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKey, { encoding: 'utf-8' });
return toGpgPath(publicKeyFile);
});
const options = { silent: true }; const options = { silent: true };
await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [
'--homedir', '--homedir',
toGpgPath(gpgHome), toGpgPath(gpgHome),
'--batch', '--batch',
'--import', '--import',
toGpgPath(publicKeyFile) ...publicKeyFiles
], options); ], options);
await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [
'--homedir', '--homedir',
@@ -398,6 +458,7 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten
], options); ], options);
} }
finally { finally {
await stopGpgAgent(gpgHome);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath); await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome);
} }
+2 -1
View File
@@ -128,7 +128,8 @@ class LibericaDistributions extends _base_installer_js__WEBPACK_IMPORTED_MODULE_
} }
} }
convertVersionToSemver(version) { convertVersionToSemver(version) {
const { buildVersion, featureVersion, interimVersion, updateVersion } = version; const { featureVersion, interimVersion, updateVersion } = version;
const buildVersion = version.version.split('+')[1] || version.buildVersion;
const mainVersion = [featureVersion, interimVersion, updateVersion].join('.'); const mainVersion = [featureVersion, interimVersion, updateVersion].join('.');
if (buildVersion != 0) { if (buildVersion != 0) {
return `${mainVersion}+${buildVersion}`; return `${mainVersion}+${buildVersion}`;
+15 -6
View File
@@ -127,16 +127,25 @@ function getInstallationIdentity(jdkPath, architecture) {
return undefined; return undefined;
} }
} }
function getJdkVerificationIdentity(verifySignature, publicKey) { function getJdkVerificationIdentity(verifySignature, enforceSignatureVerification, publicKey) {
if (!verifySignature) { if (!verifySignature) {
return 'unverified'; return 'disabled';
} }
const verificationPolicy = enforceSignatureVerification
? 'enforced'
: 'check-and-warn';
if (!publicKey) { if (!publicKey) {
return 'verified:bundled'; return `${verificationPolicy}:bundled`;
} }
const normalizedKey = publicKey.replace(/\r\n?/g, '\n').trim(); const publicKeys = Array.isArray(publicKey) ? publicKey : [publicKey];
const fingerprint = (0,crypto__WEBPACK_IMPORTED_MODULE_0__.createHash)('sha256').update(normalizedKey).digest('hex'); const normalizedKeys = publicKeys.map(key => key.replace(/\r\n?/g, '\n').trim());
return `verified:custom:sha256:${fingerprint}`; const fingerprintSource = Array.isArray(publicKey)
? normalizedKeys.map(key => `${Buffer.byteLength(key)}:${key}`).join('')
: normalizedKeys[0];
const fingerprint = (0,crypto__WEBPACK_IMPORTED_MODULE_0__.createHash)('sha256')
.update(fingerprintSource)
.digest('hex');
return `${verificationPolicy}:custom:sha256:${fingerprint}`;
} }
async function saveJdkCaches() { async function saveJdkCaches() {
const state = _actions_core__WEBPACK_IMPORTED_MODULE_4__/* .getState */ .Gu(STATE_JDK_CACHES); const state = _actions_core__WEBPACK_IMPORTED_MODULE_4__/* .getState */ .Gu(STATE_JDK_CACHES);
+25 -9
View File
@@ -265,7 +265,8 @@ async function write(directory, settings, overwriteSettings) {
/* harmony export */ Fh: () => (/* binding */ importKey), /* harmony export */ Fh: () => (/* binding */ importKey),
/* harmony export */ Yi: () => (/* binding */ verifyPackageSignature), /* harmony export */ Yi: () => (/* binding */ verifyPackageSignature),
/* harmony export */ mS: () => (/* binding */ removeGpgHome), /* harmony export */ mS: () => (/* binding */ removeGpgHome),
/* harmony export */ nY: () => (/* binding */ toGpgPath) /* harmony export */ nY: () => (/* binding */ toGpgPath),
/* harmony export */ o6: () => (/* binding */ isGpgAvailable)
/* harmony export */ }); /* harmony export */ });
/* unused harmony export GPG_HOME_PREFIX */ /* unused harmony export GPG_HOME_PREFIX */
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(9896);
@@ -287,6 +288,9 @@ async function write(directory, settings, overwriteSettings) {
const GPG_HOME_PREFIX = 'setup-java-gpg-'; const GPG_HOME_PREFIX = 'setup-java-gpg-';
const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-';
async function isGpgAvailable() {
return Boolean(await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .which */ .K7('gpg', false));
}
// Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...). // Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...).
// The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions // The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions
// internally. Passing Windows paths with backslashes can cause fatal GPG errors // internally. Passing Windows paths with backslashes can cause fatal GPG errors
@@ -298,8 +302,8 @@ function toGpgPath(p) {
.replace(/\\/g, '/') .replace(/\\/g, '/')
.replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`); .replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`);
} }
function createGpgHome(prefix) { function createGpgHome(prefix, tempDir = _util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4()) {
const gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4(), prefix)); const gpgHome = fs__WEBPACK_IMPORTED_MODULE_0__.mkdtempSync(path__WEBPACK_IMPORTED_MODULE_1__.join(tempDir, prefix));
if (process.platform !== 'win32') { if (process.platform !== 'win32') {
fs__WEBPACK_IMPORTED_MODULE_0__.chmodSync(gpgHome, 0o700); fs__WEBPACK_IMPORTED_MODULE_0__.chmodSync(gpgHome, 0o700);
} }
@@ -346,19 +350,24 @@ async function removeGpgHome(gpgHome) {
if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) { if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) {
return; return;
} }
await stopGpgAgent(resolvedGpgHome);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome);
}
async function stopGpgAgent(gpgHome) {
try { try {
await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(gpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true });
} }
catch { catch {
// gpgconf may be unavailable, but directory removal must still be attempted. // gpgconf may be unavailable, but directory removal must still be attempted.
} }
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome);
} }
async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) {
const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl); const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl);
let gpgHome; let gpgHome;
try { try {
gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); // Both RUNNER_TEMP and TMPDIR can exceed macOS's 104-byte agent socket limit.
const tempDir = process.platform === 'darwin' ? '/tmp' : _util_js__WEBPACK_IMPORTED_MODULE_6__/* .getTempDir */ .G4();
gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX, tempDir);
} }
catch (error) { catch (error) {
try { try {
@@ -370,15 +379,21 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten
throw new Error(`Failed to create temporary GPG home directory for signature verification: ${error.message}`, { cause: error }); throw new Error(`Failed to create temporary GPG home directory for signature verification: ${error.message}`, { cause: error });
} }
try { try {
const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, 'public-key.asc'); const publicKeys = Array.isArray(publicKeyContent)
fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKeyContent, { encoding: 'utf-8' }); ? publicKeyContent
: [publicKeyContent];
const publicKeyFiles = publicKeys.map((publicKey, index) => {
const publicKeyFile = path__WEBPACK_IMPORTED_MODULE_1__.join(gpgHome, `public-key-${index}.asc`);
fs__WEBPACK_IMPORTED_MODULE_0__.writeFileSync(publicKeyFile, publicKey, { encoding: 'utf-8' });
return toGpgPath(publicKeyFile);
});
const options = { silent: true }; const options = { silent: true };
await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [
'--homedir', '--homedir',
toGpgPath(gpgHome), toGpgPath(gpgHome),
'--batch', '--batch',
'--import', '--import',
toGpgPath(publicKeyFile) ...publicKeyFiles
], options); ], options);
await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [ await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpg', [
'--homedir', '--homedir',
@@ -390,6 +405,7 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten
], options); ], options);
} }
finally { finally {
await stopGpgAgent(gpgHome);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath); await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome);
} }
+154 -7
View File
@@ -418,9 +418,102 @@ function readAttributeStr(xmlData, i) {
} }
/** /**
* Select all the attributes whether valid or invalid. * Walk `attrStr` once, left to right, splitting it into attribute tokens.
*
* This replaces a regex that used to do the same job
* (`(\s*)([^\s=]+)(\s*=)?(\s*(['"])(([\s\S])*?)\5)?`). That regex led with an
* optional whitespace group followed by a required "non-whitespace" group.
* On a long run of whitespace that never resolves into an attribute name
* (e.g. a tag with thousands of trailing spaces before `>`), the engine
* backtracks the whitespace group one character at a time before giving up
* and moving to the next starting position — one full backtrack per
* position, which is quadratic in the length of the run.
*
* A single forward-only scan can never backtrack, so it can't be made slow
* this way no matter how much whitespace the input contains — it's always
* proportional to the length of the string, once.
*
* Each returned token mirrors the shape the old regex match array had, so
* the validation logic below (which reads token[1]..token[6]) didn't need
* to change:
* token.startIndex - where this token begins in attrStr
* token[1] - leading whitespace before the name
* token[2] - the attribute name
* token[3] - whitespace + '=' if present, else undefined
* token[4] - marker (any defined value) if a quoted value was found
* token[5] - the quote character used ('"' or "'")
* token[6] - the value's text, without the surrounding quotes
*
* A malformed leading character (e.g. a stray '=' with no name before it)
* is simply skipped over, one character at a time — the same outcome the
* old regex produced by failing to match at that position and retrying at
* the next one.
*/ */
const validAttrStrRegxp = new RegExp('(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s\\S])*?)\\5)?', 'g'); function scanAttributeTokens(attrStr) {
const tokens = [];
const len = attrStr.length;
let i = 0;
while (i < len) {
const tokenStart = i;
// Leading whitespace before the name.
while (i < len && isWhiteSpace(attrStr[i])) i++;
if (i >= len) break; // trailing whitespace only — nothing left to read
if (attrStr[i] === '=') {
// No name before this '=' — not a valid attribute start. Move past
// just this one character and try again from the next position.
i = tokenStart + 1;
continue;
}
const leadingWs = attrStr.slice(tokenStart, i);
// Attribute name — everything up to the next whitespace or '='.
const nameStart = i;
while (i < len && !isWhiteSpace(attrStr[i]) && attrStr[i] !== '=') i++;
const name = attrStr.slice(nameStart, i);
// Optional whitespace + '='.
let equalsGroup; // whitespace + '=' text, or undefined if absent
let j = i;
while (j < len && isWhiteSpace(attrStr[j])) j++;
if (j < len && attrStr[j] === '=') {
equalsGroup = attrStr.slice(i, j + 1);
i = j + 1;
}
// Optional whitespace + quoted value.
let quoteChar;
let value;
let k = i;
while (k < len && isWhiteSpace(attrStr[k])) k++;
if (k < len && (attrStr[k] === '"' || attrStr[k] === "'")) {
const valueStart = k + 1;
const closeIdx = attrStr.indexOf(attrStr[k], valueStart);
if (closeIdx !== -1) {
quoteChar = attrStr[k];
value = attrStr.slice(valueStart, closeIdx);
i = closeIdx + 1;
}
// No closing quote found anywhere in the rest of the string — leave
// quoteChar/value undefined, same as the old regex's group failing
// to match a backreference-less run.
}
const token = { startIndex: tokenStart };
token[1] = leadingWs;
token[2] = name;
token[3] = equalsGroup;
token[4] = quoteChar !== undefined ? true : undefined;
token[5] = quoteChar;
token[6] = value;
tokens.push(token);
}
return tokens;
}
//attr, ="sd", a="amit's", a="sd"b="saf", ab cd="" //attr, ="sd", a="amit's", a="sd"b="saf", ab cd=""
@@ -429,7 +522,7 @@ function validateAttributeString(attrStr, options) {
//if(attrStr.trim().length === 0) return true; //empty string //if(attrStr.trim().length === 0) return true; //empty string
const matches = (0,_util_js__WEBPACK_IMPORTED_MODULE_0__/* .getAllMatches */ .Xe)(attrStr, validAttrStrRegxp); const matches = scanAttributeTokens(attrStr);
const attrNames = {}; const attrNames = {};
for (let i = 0; i < matches.length; i++) { for (let i = 0; i < matches.length; i++) {
@@ -532,7 +625,6 @@ function getPositionFromMatch(match) {
return match.startIndex + match[1].length; return match.startIndex + match[1].length;
} }
/***/ }), /***/ }),
/***/ 6009: /***/ 6009:
@@ -741,12 +833,26 @@ class XmlNode {
this.child.push({ [node.tagname]: node.child }); this.child.push({ [node.tagname]: node.child });
} }
// if requested, add the startIndex // if requested, add the startIndex
this.addStartIndex(startIndex);
}
addStartIndex(startIndex) {
if (startIndex !== undefined) { if (startIndex !== undefined) {
// Note: for now we just overwrite the metadata. If we had more complex metadata, // Note: for now we just overwrite the metadata. If we had more complex metadata,
// we might need to do an object append here: metadata = { ...metadata, startIndex } // we might need to do an object append here: metadata = { ...metadata, startIndex }
this.child[this.child.length - 1][METADATA_SYMBOL] = { startIndex }; this.child[this.child.length - 1][METADATA_SYMBOL] = { startIndex };
} }
} }
addEndIndex(endIndex) {
const lastChild = this.child[this.child.length - 1];
// endIndex is write-once: when updateTag drops a node, the last child is a
// previously completed sibling whose endIndex must not be overwritten
if (lastChild !== undefined && lastChild[METADATA_SYMBOL] !== undefined
&& lastChild[METADATA_SYMBOL].endIndex === undefined) {
lastChild[METADATA_SYMBOL].endIndex = endIndex;
}
}
/** symbol used for metadata */ /** symbol used for metadata */
static getMetaDataSymbol() { static getMetaDataSymbol() {
return METADATA_SYMBOL; return METADATA_SYMBOL;
@@ -781,8 +887,23 @@ class DocTypeReader {
i = i + 9; i = i + 9;
let angleBracketsCount = 1; let angleBracketsCount = 1;
let hasBody = false, comment = false; let hasBody = false, comment = false;
let quoteChar = null; // tracks an open SYSTEM/PUBLIC literal before the '[' body
let exp = ""; let exp = "";
for (; i < xmlData.length; i++) { for (; i < xmlData.length; i++) {
// Inside a quoted external-identifier literal — XML allows '<'
// and '>' as plain data here, so they must not be interpreted
// as DOCTYPE structure until the matching quote closes.
if (quoteChar !== null) {
if (xmlData[i] === quoteChar) quoteChar = null;
exp += xmlData[i];
continue;
}
if (!hasBody && !comment && (xmlData[i] === '"' || xmlData[i] === "'")) {
quoteChar = xmlData[i];
exp += xmlData[i];
continue;
}
if (xmlData[i] === '<' && !comment) { //Determine the tag type if (xmlData[i] === '<' && !comment) { //Determine the tag type
if (hasBody && hasSeq(xmlData, "!ENTITY", i)) { if (hasBody && hasSeq(xmlData, "!ENTITY", i)) {
i += 7; i += 7;
@@ -837,7 +958,7 @@ class DocTypeReader {
exp += xmlData[i]; exp += xmlData[i];
} }
} }
if (angleBracketsCount !== 0) { if (quoteChar !== null || angleBracketsCount !== 0) {
throw new Error(`Unclosed DOCTYPE`); throw new Error(`Unclosed DOCTYPE`);
} }
} else { } else {
@@ -1552,7 +1673,11 @@ function resolveEnotation(str, trimmedStr, options) {
*/ */
function trimZeros(numStr) { function trimZeros(numStr) {
if (numStr && numStr.indexOf(".") !== -1) {//float if (numStr && numStr.indexOf(".") !== -1) {//float
numStr = numStr.replace(/0+$/, ""); //remove ending zeros //remove ending zeros without the O(n^2) backtracking that /0+$/ hits
//when the string doesn't end in 0 but has a long internal zero-run
let end = numStr.length;
while (end > 0 && numStr.charCodeAt(end - 1) === 48 /* '0' */) end--;
numStr = numStr.slice(0, end);
if (numStr === ".") numStr = "0"; if (numStr === ".") numStr = "0";
else if (numStr[0] === ".") numStr = "0" + numStr; else if (numStr[0] === ".") numStr = "0" + numStr;
else if (numStr[numStr.length - 1] === ".") numStr = numStr.substring(0, numStr.length - 1); else if (numStr[numStr.length - 1] === ".") numStr = numStr.substring(0, numStr.length - 1);
@@ -5032,7 +5157,12 @@ const parseXml = function (xmlData) {
this.matcher.pop(); this.matcher.pop();
this.isCurrentNodeStopNode = false; // Reset flag when closing tag this.isCurrentNodeStopNode = false; // Reset flag when closing tag
currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope //a closing tag with no matching opening tag leaves the stack empty
currentNode = this.tagsNodeStack.pop() || xmlObj;//avoid recursion, set the parent tag scope
if (options.captureMetaData && currentNode) {
currentNode.addEndIndex(closeIndex + 1);
}
textData = ""; textData = "";
i = closeIndex; i = closeIndex;
} else if (c1 === 63) { //'?' } else if (c1 === 63) { //'?'
@@ -5058,6 +5188,11 @@ const parseXml = function (xmlData) {
childNode[":@"] = attsMap childNode[":@"] = attsMap
} }
this.addChild(currentNode, childNode, this.readonlyMatcher, i); this.addChild(currentNode, childNode, this.readonlyMatcher, i);
if (options.captureMetaData) {
// closeIndex points at '?' of the closing '?>'
currentNode.addEndIndex(tagData.closeIndex + 2);
}
} }
@@ -5222,6 +5357,10 @@ const parseXml = function (xmlData) {
this.isCurrentNodeStopNode = false; // Reset flag this.isCurrentNodeStopNode = false; // Reset flag
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex); this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
if (options.captureMetaData) {
currentNode.addEndIndex(i + 1);
}
} else { } else {
//selfClosing tag //selfClosing tag
if (isSelfClosing) { if (isSelfClosing) {
@@ -5232,6 +5371,10 @@ const parseXml = function (xmlData) {
childNode[":@"] = prefixedAttrs; childNode[":@"] = prefixedAttrs;
} }
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex); this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
if (options.captureMetaData) {
currentNode.addEndIndex(closeIndex + 1);
}
this.matcher.pop(); // Pop self-closing tag this.matcher.pop(); // Pop self-closing tag
this.isCurrentNodeStopNode = false; // Reset flag this.isCurrentNodeStopNode = false; // Reset flag
} }
@@ -5241,6 +5384,10 @@ const parseXml = function (xmlData) {
childNode[":@"] = prefixedAttrs; childNode[":@"] = prefixedAttrs;
} }
this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex); this.addChild(currentNode, childNode, this.readonlyMatcher, startIndex);
if (options.captureMetaData) {
currentNode.addEndIndex(result.closeIndex + 1);
}
this.matcher.pop(); // Pop unpaired tag this.matcher.pop(); // Pop unpaired tag
this.isCurrentNodeStopNode = false; // Reset flag this.isCurrentNodeStopNode = false; // Reset flag
i = result.closeIndex; i = result.closeIndex;
+68 -24
View File
@@ -52431,6 +52431,24 @@ const EXPANSION_MAX = 100_000;
// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M // realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
// characters) so legitimate input is unaffected. // characters) so legitimate input is unaffected.
const EXPANSION_MAX_LENGTH = 4_000_000; const EXPANSION_MAX_LENGTH = 4_000_000;
// `expand_` recurses once per level of brace *nesting* - both when expanding a
// set's comma members and when re-wrapping a set whose body is a single part.
// The CVE-2026-14257 fix made the *tail* iterative (recursion on `m.post`, one
// level per chained group), which left nesting depth unbounded: about 3,100
// levels of `{{{...a,b...}}}` - only ~6KB of input - exhausted the native stack
// and crashed the process. `EXPANSION_MAX_DEPTH` bounds how deep the parser
// will follow nesting. It sits far above any realistic pattern and well below
// the depth at which the stack runs out.
const EXPANSION_MAX_DEPTH = 1_000;
// Bash keeps a quirk where a brace group followed by a comma set still expands
// (`{a},b}`). The parser implements it by rewriting the string and restarting
// the scan, absorbing one `}` per pass. `n` trailing braces therefore cost `n`
// full passes over a string that itself grows by one `escClose` sentinel each
// time - quadratic in `n`, with a ~26x constant from the sentinel's length.
// 128KB of `'{a}' + '}'.repeat(n) + ',z}'` blocked the event loop for 27
// seconds to produce two results. `EXPANSION_MAX_REWRITES` bounds how many
// times the scan may restart. Real `{a},b}` input needs a handful.
const EXPANSION_MAX_REWRITES = 1_000;
function numeric(str) { function numeric(str) {
return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
} }
@@ -52450,37 +52468,52 @@ function unescapeBraces(str) {
.replace(escCommaPattern, ',') .replace(escCommaPattern, ',')
.replace(escPeriodPattern, '.'); .replace(escPeriodPattern, '.');
} }
// Like `target.push(...items)` but doesn't overflow the stack
function pushAll(target, items) {
for (let i = 0; i < items.length; i++) {
target.push(items[i]);
}
}
/** /**
* Basically just str.split(","), but handling cases * Basically just str.split(","), but handling cases
* where we have nested braced sections, which should be * where we have nested braced sections, which should be
* treated as individual members, like {a,{b,c},d} * treated as individual members, like {a,{b,c},d}
*/ */
function parseCommaParts(str) { function parseCommaParts(str) {
if (!str) {
return [''];
}
const parts = []; const parts = [];
const m = balanced('{', '}', str); // Walk the brace groups iteratively. Recursing on `post` once per group let a
if (!m) { // chain of them exhaust the stack - the parsing-side counterpart to
return str.split(','); // the `expand_` overflow fixed for CVE-2026-14257, and not something `max` or
// `maxLength` can bound, since it happens before expansion.
//
// The part the next chunk continues
let carry = '';
for (;;) {
const m = balanced('{', '}', str);
if (!m) {
const tail = str.split(',');
tail[0] = carry + tail[0];
pushAll(parts, tail);
return parts;
}
const { pre, body, post } = m;
const p = pre.split(',');
p[0] = carry + p[0];
p[p.length - 1] += '{' + body + '}';
if (!post.length) {
pushAll(parts, p);
return parts;
}
carry = p.pop();
pushAll(parts, p);
str = post;
} }
const { pre, body, post } = m;
const p = pre.split(',');
p[p.length - 1] += '{' + body + '}';
const postParts = parseCommaParts(post);
if (post.length) {
;
p[p.length - 1] += postParts.shift();
p.push.apply(p, postParts);
}
parts.push.apply(parts, p);
return parts;
} }
function expand(str, options = {}) { function expand(str, options = {}) {
if (!str) { if (!str) {
return []; return [];
} }
const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH } = options; const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH, maxDepth = EXPANSION_MAX_DEPTH, maxRewrites = EXPANSION_MAX_REWRITES, } = options;
// I don't know why Bash 4.3 does this, but it does. // I don't know why Bash 4.3 does this, but it does.
// Anything starting with {} will have the first two bytes preserved // Anything starting with {} will have the first two bytes preserved
// but *only* at the top level, so {},a}b will not expand to anything, // but *only* at the top level, so {},a}b will not expand to anything,
@@ -52490,7 +52523,7 @@ function expand(str, options = {}) {
if (str.slice(0, 2) === '{}') { if (str.slice(0, 2) === '{}') {
str = '\\{\\}' + str.slice(2); str = '\\{\\}' + str.slice(2);
} }
return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces); return expand_(escapeBraces(str), max, maxLength, maxDepth, 0, maxRewrites, true).map(unescapeBraces);
} }
function embrace(str) { function embrace(str) {
return '{' + str + '}'; return '{' + str + '}';
@@ -52585,7 +52618,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) {
} }
return N; return N;
} }
function expand_(str, max, maxLength, isTop) { function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) {
// Too deeply nested to keep following: treat the rest as literal, the same
// way a group that cannot expand is already handled. Truncating rather than
// throwing keeps `expand` total, matching `max` and `maxLength`.
if (depth > maxDepth) {
return [str];
}
// Consume the string's top-level brace groups left to right, threading a // Consume the string's top-level brace groups left to right, threading a
// running set of combined prefixes (`acc`). Expanding the tail iteratively - // running set of combined prefixes (`acc`). Expanding the tail iteratively -
// rather than recursing on `m.post` once per group - keeps the native stack // rather than recursing on `m.post` once per group - keeps the native stack
@@ -52597,6 +52636,9 @@ function expand_(str, max, maxLength, isTop) {
// comma set - a sequence like `{a..\}` may legitimately yield ''. The drop // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop
// is on the final strings, so it is applied to whichever `combine` produces // is on the final strings, so it is applied to whichever `combine` produces
// them (the one with no brace set left in the tail). // them (the one with no brace set left in the tail).
// How many times the `{a},b}` rewrite below has restarted the scan. Each pass
// re-reads the whole string, so leaving this unbounded is quadratic.
let rewrites = 0;
let dropEmpties = false; let dropEmpties = false;
let firstGroup = true; let firstGroup = true;
for (;;) { for (;;) {
@@ -52621,7 +52663,8 @@ function expand_(str, max, maxLength, isTop) {
const isOptions = m.body.indexOf(',') >= 0; const isOptions = m.body.indexOf(',') >= 0;
if (!isSequence && !isOptions) { if (!isSequence && !isOptions) {
// {a},b} // {a},b}
if (m.post.match(/,(?!,).*\}/)) { if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) {
rewrites++;
str = m.pre + '{' + m.body + escClose + m.post; str = m.pre + '{' + m.body + escClose + m.post;
isTop = true; isTop = true;
continue; continue;
@@ -52641,7 +52684,7 @@ function expand_(str, max, maxLength, isTop) {
let n = parseCommaParts(m.body); let n = parseCommaParts(m.body);
if (n.length === 1 && n[0] !== undefined) { if (n.length === 1 && n[0] !== undefined) {
// x{{a,b}}y ==> x{a}y x{b}y // x{{a,b}}y ==> x{a}y x{b}y
n = expand_(n[0], max, maxLength, false).map(embrace); n = expand_(n[0], max, maxLength, maxDepth, depth + 1, maxRewrites, false).map(embrace);
//XXX is this necessary? Can't seem to hit it in tests. //XXX is this necessary? Can't seem to hit it in tests.
/* c8 ignore start */ /* c8 ignore start */
if (n.length === 1) { if (n.length === 1) {
@@ -52667,12 +52710,13 @@ function expand_(str, max, maxLength, isTop) {
values = []; values = [];
let valuesLength = 0; let valuesLength = 0;
outer: for (let j = 0; j < n.length; j++) { outer: for (let j = 0; j < n.length; j++) {
const expanded = expand_(n[j], max, maxLength, false); const expanded = expand_(n[j], max, maxLength, maxDepth, depth + 1, maxRewrites, false);
for (let k = 0; k < expanded.length; k++) { for (let k = 0; k < expanded.length; k++) {
const v = expanded[k]; const v = expanded[k];
if (dropsEmpties && !v) if (dropsEmpties && !v)
continue; continue;
if (values.length >= max || valuesLength + v.length > maxLength) { if (values.length >= max ||
valuesLength + v.length > maxLength) {
break outer; break outer;
} }
values.push(v); values.push(v);
+32 -20
View File
@@ -13,19 +13,26 @@ export const modules = {
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9896); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9896);
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2088); /* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6242);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _platform_types_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7444);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6242); /* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4527);
/* harmony import */ var _platform_types_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(7444);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4527);
function compareNumberArrays(a, b) {
class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* .JavaBase */ .O { const length = Math.max(a.length, b.length);
for (let i = 0; i < length; i++) {
const diff = (a[i] ?? 0) - (b[i] ?? 0);
if (diff !== 0) {
return diff;
}
}
return 0;
}
class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaBase */ .O {
constructor(installerOptions) { constructor(installerOptions) {
super('Zulu', installerOptions); super('Zulu', installerOptions);
} }
@@ -39,19 +46,24 @@ class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/*
? [...item.java_version, item.openjdk_build_number] ? [...item.java_version, item.openjdk_build_number]
: item.java_version; : item.java_version;
return { return {
version: (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .convertVersionToSemver */ .ZY)(javaVersion), version: (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .convertVersionToSemver */ .ZY)(javaVersion),
url: item.download_url, url: item.download_url,
zuluVersion: (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .convertVersionToSemver */ .ZY)(item.distro_version), javaVersion: item.java_version,
buildNumber: item.openjdk_build_number ?? 0,
distroVersion: item.distro_version,
packageUuid: item.package_uuid packageUuid: item.package_uuid
}; };
}); });
const satisfiedVersions = availableVersions const satisfiedVersions = availableVersions
.filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .isVersionSatisfies */ .y)(version, item.version)) .filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .isVersionSatisfies */ .y)(version, item.version))
.sort((a, b) => { .sort((a, b) => {
// Azul provides two versions: java_version and distro_version // Compare numerically rather than via semver build metadata: Azul
// we should sort by both fields by descending // hotfix releases carry a 4th java_version segment (e.g. 25.0.4.1+1,
return (-semver__WEBPACK_IMPORTED_MODULE_3___default().compareBuild(a.version, b.version) || // rendered as '25.0.4+1.1') that must rank above 25.0.4+7, whereas
-semver__WEBPACK_IMPORTED_MODULE_3___default().compareBuild(a.zuluVersion, b.zuluVersion)); // semver.compareBuild would order the build identifiers '7' > '1'.
return (-compareNumberArrays(a.javaVersion, b.javaVersion) ||
b.buildNumber - a.buildNumber ||
-compareNumberArrays(a.distroVersion, b.distroVersion));
}) })
.map((item) => ({ .map((item) => ({
version: item.version, version: item.version,
@@ -85,21 +97,21 @@ class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/*
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`); _actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease); let javaArchivePath = await this.downloadAndVerify(javaRelease);
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`); _actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`);
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getDownloadArchiveExtension */ .ag)(); const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getDownloadArchiveExtension */ .ag)();
if (process.platform === 'win32') { if (process.platform === 'win32') {
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .renameWinArchive */ .n2)(javaArchivePath); javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .renameWinArchive */ .n2)(javaArchivePath);
} }
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .extractJdkFile */ .PE)(javaArchivePath, extension); const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
const archiveName = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath)[0]; const archiveName = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath)[0];
const archivePath = path__WEBPACK_IMPORTED_MODULE_1___default().join(extractedJavaPath, archiveName); const archivePath = path__WEBPACK_IMPORTED_MODULE_1___default().join(extractedJavaPath, archiveName);
const javaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .cacheJdkDir */ .Vj)(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture); const javaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .cacheJdkDir */ .Vj)(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture);
return { version: javaRelease.version, path: javaPath }; return { version: javaRelease.version, path: javaPath };
} }
async getAvailableVersions() { async getAvailableVersions() {
const arch = this.getArchitectureOptions(); const arch = this.getArchitectureOptions();
const [bundleType, features] = this.packageType.split('+'); const [bundleType, features] = this.packageType.split('+');
const platform = this.getPlatformOption(); const platform = this.getPlatformOption();
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getDownloadArchiveExtension */ .ag)(); const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getDownloadArchiveExtension */ .ag)();
const javafx = features?.includes('fx') ?? false; const javafx = features?.includes('fx') ?? false;
const crac = features?.includes('crac') ?? false; const crac = features?.includes('crac') ?? false;
const releaseStatus = this.stable ? 'ga' : 'ea'; const releaseStatus = this.stable ? 'ga' : 'ea';
@@ -185,7 +197,7 @@ class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/*
// The new Metadata API's "linux" value returns both glibc and musl // The new Metadata API's "linux" value returns both glibc and musl
// packages, so target the libc the runner actually has. A glibc JDK // packages, so target the libc the runner actually has. A glibc JDK
// cannot run on Alpine. // cannot run on Alpine.
return (0,_platform_types_js__WEBPACK_IMPORTED_MODULE_5__/* .isAlpineLinux */ .G6)() ? 'linux_musl' : 'linux_glibc'; return (0,_platform_types_js__WEBPACK_IMPORTED_MODULE_4__/* .isAlpineLinux */ .G6)() ? 'linux_musl' : 'linux_glibc';
default: default:
return process.platform; return process.platform;
} }
+519 -202
View File
File diff suppressed because it is too large Load Diff
@@ -1,4 +1,4 @@
# Contributors # Contributing
Thank you for contributing! Thank you for contributing!
+20 -20
View File
@@ -39,15 +39,6 @@
See [action.yml](../action.yml) for more details on task inputs. See [action.yml](../action.yml) for more details on task inputs.
> [!NOTE]
> The examples on this page reference `actions/setup-java@v6`, which is still in
> development on the `main` branch and is not yet published as a release tag. To
> try the V6 features documented here (`cache-jdk`, `force-download`,
> `problem-matcher`, `cache-path`, `cache-read-only`, `java-version: latest`,
> `oracle-openjdk`, and the `*-env-var` input names), reference
> `actions/setup-java@main`. For production workflows use the latest stable
> release, `actions/setup-java@v5`, as shown in the [README](../README.md).
## Selecting a Java distribution ## Selecting a Java distribution
`java-version` and `distribution` select what gets installed. `java-version` may be replaced by `java-version-file`, and `distribution` is optional only when `java-version-file` points to a `.sdkmanrc` or `.tool-versions` file that carries a recognized vendor identifier. In every other case both inputs must be provided. See [Supported distributions](../README.md#Supported-distributions) for a list of available options. `java-version` and `distribution` select what gets installed. `java-version` may be replaced by `java-version-file`, and `distribution` is optional only when `java-version-file` points to a `.sdkmanrc` or `.tool-versions` file that carries a recognized vendor identifier. In every other case both inputs must be provided. See [Supported distributions](../README.md#Supported-distributions) for a list of available options.
@@ -548,18 +539,27 @@ tool-cache installation short-circuits setup, so a changed `jdk-file` is not
re-extracted for a version that is already installed. Use re-extracted for a version that is already installed. Use
`force-download: true` when the archive contents change but the version does not. `force-download: true` when the archive contents change but the version does not.
The verification identity separates unverified downloads from packages verified The verification identity separates requests that disable signature verification,
with the distribution's bundled signing key and from packages verified with each check and warn without enforcement, or explicitly enforce verification. Disabled
custom key. Custom public keys are represented by a SHA-256 fingerprint of and check-and-warn requests have the same non-enforcement guarantee, but they are
normalized key material; the key itself is not placed in the cache key, the logs, kept separate so an entry downloaded with verification disabled cannot prevent a
or action state. A verified exact-key hit reuses content that was later check-and-warn request from attempting verification. The identity also
signature-verified when it was downloaded by the run that saved the entry, separates the distribution's bundled signing keys from custom keys. Custom
instead of downloading and verifying it again. public-key sets are represented by a SHA-256 fingerprint of normalized,
boundary-delimited key material; the keys themselves are not placed in the cache
key, the logs, or action state. Enforced requests only restore entries created by
an enforced request whose signature verification succeeded. Check-and-warn entries
may have been saved after verification succeeded or after a verification failure
was reported as a warning.
For signature-verification defaults, enforced failure behavior, and recovery from
a legitimate vendor signing-key rotation, see
[Download integrity and signatures](../README.md#download-integrity-and-signatures).
> [!IMPORTANT] > [!IMPORTANT]
> The JDK cache **key** is what isolates verification modes and release > The JDK cache **key** isolates disabled, check-and-warn, and enforced verification
> identity: a JDK cache entry created by an unverified download can never be > modes as well as release identity. A check-and-warn entry can never be restored
> restored for a request that sets `verify-signature: true`, and vice versa. > for a request that sets `verify-signature: true`, and vice versa.
> `cache-jdk` does not change how the runner tool cache is used. setup-java > `cache-jdk` does not change how the runner tool cache is used. setup-java
> first looks for an installation in the runner tool cache — a preinstalled > first looks for an installation in the runner tool cache — a preinstalled
> JDK, or one installed by an earlier step of the same job — and uses it as-is. Such an installation is not downloaded again, and its checksum > JDK, or one installed by an earlier step of the same job — and uses it as-is. Such an installation is not downloaded again, and its checksum
@@ -649,7 +649,7 @@ absent from a vendor catalog.
| Distribution | Linux | macOS | Windows | Other / version restrictions | | Distribution | Linux | macOS | Windows | Other / version restrictions |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| `temurin` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le`, `s390x` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | Linux `armv7` is available through Java 17. | | `temurin` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le`, `riscv64`, `s390x` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | Linux `armv7` is available through Java 17. |
| `zulu` | `x64`, `x86`, `armv7`, `aarch64` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | | | `zulu` | `x64`, `x86`, `armv7`, `aarch64` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | |
| `liberica` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | Solaris: `x64`. | | `liberica` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | Solaris: `x64`. |
| `liberica-nik` | `x64`, `aarch64` | `x64`, `aarch64` | `x64`, `aarch64` | | | `liberica-nik` | `x64`, `aarch64` | `x64`, `aarch64` | `x64`, `aarch64` | |
+1253 -683
View File
File diff suppressed because it is too large Load Diff
+13 -13
View File
@@ -49,27 +49,27 @@
"@actions/http-client": "^4.0.1", "@actions/http-client": "^4.0.1",
"@actions/io": "^3.0.2", "@actions/io": "^3.0.2",
"@actions/tool-cache": "^4.0.0", "@actions/tool-cache": "^4.0.0",
"fast-xml-parser": "^5.10.1", "fast-xml-parser": "^5.11.1",
"semver": "^7.8.5" "semver": "^7.8.5"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@jest/globals": "^30.4.1", "@jest/globals": "^30.5.2",
"@types/node": "^26.1.1", "@types/node": "^26.6.2",
"@types/semver": "^7.8.0", "@types/semver": "^7.8.0",
"@typescript-eslint/eslint-plugin": "^8.67.0", "@typescript-eslint/eslint-plugin": "^8.70.1",
"@typescript-eslint/parser": "^8.65.0", "@typescript-eslint/parser": "^8.65.0",
"@vercel/ncc": "^0.44.0", "@vercel/ncc": "^0.45.0",
"eslint": "^10.7.0", "eslint": "^10.11.0",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-jest": "^29.15.4", "eslint-plugin-jest": "^29.16.6",
"eslint-plugin-n": "^18.2.2", "eslint-plugin-n": "^18.3.0",
"globals": "^17.9.0", "globals": "^17.12.0",
"husky": "^9.1.7", "husky": "^9.1.7",
"jest": "^30.4.2", "jest": "^30.5.2",
"lint-staged": "^17.3.0", "lint-staged": "^17.5.1",
"prettier": "^3.9.5", "prettier": "^3.9.9",
"ts-jest": "^29.4.11", "ts-jest": "^29.4.13",
"typescript": "^6.0.3" "typescript": "^6.0.3"
}, },
"bugs": { "bugs": {
+2 -2
View File
@@ -6,7 +6,7 @@ import {
isJdkCacheEnabled, isJdkCacheEnabled,
isJobStatusSuccess isJobStatusSuccess
} from './util.js'; } from './util.js';
import {fileURLToPath} from 'url'; import {isMainModule} from './is-main-module.js';
async function removeGpgHome() { async function removeGpgHome() {
const gpgHome = core.getState(constants.STATE_GPG_HOME); const gpgHome = core.getState(constants.STATE_GPG_HOME);
@@ -77,7 +77,7 @@ export async function run() {
await ignoreError(saveCaches()); await ignoreError(saveCaches());
} }
if (process.argv[1] === fileURLToPath(import.meta.url)) { if (isMainModule(import.meta.url)) {
run(); run();
} else { } else {
// https://nodejs.org/api/modules.html#modules_accessing_the_main_module // https://nodejs.org/api/modules.html#modules_accessing_the_main_module
+3
View File
@@ -12,6 +12,9 @@ export const INPUT_SET_DEFAULT = 'set-default';
export const INPUT_PROBLEM_MATCHER = 'problem-matcher'; 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 SIGNATURE_VERIFICATION_DOCUMENTATION_URL =
'https://github.com/actions/setup-java#download-integrity-and-signatures';
export const SIGNATURE_VERIFICATION_FAILURE_HELP = `If this is a legitimate vendor signing-key rotation, see ${SIGNATURE_VERIFICATION_DOCUMENTATION_URL} for instructions to configure the updated public key or temporarily disable signature verification.`;
export const INPUT_MVN_SERVER_CREDENTIALS = 'mvn-server-credentials'; export const INPUT_MVN_SERVER_CREDENTIALS = 'mvn-server-credentials';
export const INPUT_MVN_REPOSITORIES = 'mvn-repositories'; export const INPUT_MVN_REPOSITORIES = 'mvn-repositories';
export const INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL = export const INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL =
+15 -12
View File
@@ -5,16 +5,17 @@ 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 { import {
convertVersionToSemver,
getToolcachePath, getToolcachePath,
isVersionSatisfies isVersionSatisfies,
normalizeJavaVersionToSemver
} from '../util.js'; } from '../util.js';
import { import type {
ChecksumAlgorithm, ChecksumAlgorithm,
ChecksumMetadata, ChecksumMetadata,
JavaDownloadRelease, JavaDownloadRelease,
JavaInstallerOptions, JavaInstallerOptions,
JavaInstallerResults JavaInstallerResults,
SignatureVerificationKey
} from './base-models.js'; } from './base-models.js';
import {MACOS_JAVA_CONTENT_POSTFIX} from '../constants.js'; import {MACOS_JAVA_CONTENT_POSTFIX} from '../constants.js';
import {RetryingHttpClient} from '../retrying-http-client.js'; import {RetryingHttpClient} from '../retrying-http-client.js';
@@ -44,7 +45,8 @@ export abstract class JavaBase {
private floatingVersionVerified = false; private floatingVersionVerified = false;
protected setDefault: boolean; protected setDefault: boolean;
protected verifySignature: boolean; protected verifySignature: boolean;
protected verifySignaturePublicKey: string | undefined; protected verifySignatureExplicitlyRequested: boolean;
protected verifySignaturePublicKey: SignatureVerificationKey | undefined;
constructor( constructor(
protected distribution: string, protected distribution: string,
@@ -68,7 +70,10 @@ export abstract class JavaBase {
installerOptions.setDefault !== undefined installerOptions.setDefault !== undefined
? installerOptions.setDefault ? installerOptions.setDefault
: true; : true;
this.verifySignature = installerOptions.verifySignature ?? false; this.verifySignature =
installerOptions.verifySignature ?? this.supportsSignatureVerification();
this.verifySignatureExplicitlyRequested =
installerOptions.verifySignature === true;
this.verifySignaturePublicKey = installerOptions.verifySignaturePublicKey; this.verifySignaturePublicKey = installerOptions.verifySignaturePublicKey;
} }
@@ -368,6 +373,7 @@ export abstract class JavaBase {
source: this.getJdkReleaseIdentity(javaRelease), source: this.getJdkReleaseIdentity(javaRelease),
verification: getJdkVerificationIdentity( verification: getJdkVerificationIdentity(
this.verifySignature, this.verifySignature,
this.verifySignatureExplicitlyRequested,
this.verifySignaturePublicKey this.verifySignaturePublicKey
), ),
path: this.getJdkCachePath(javaRelease.version) path: this.getJdkCachePath(javaRelease.version)
@@ -669,12 +675,9 @@ export abstract class JavaBase {
// Java uses a versioning scheme (JEP 322) that can contain more numeric // 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 // 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 // exact versions to SemVer build notation ('18.0.1+1', or '26.0.2+1.1'
// accepted. Ranges and versions that already carry build metadata are // for '26.0.2.1+1') so they are accepted. Ranges are left untouched.
// left untouched. version = normalizeJavaVersionToSemver(version);
if (/^\d+(\.\d+){3,}$/.test(version)) {
version = convertVersionToSemver(version);
}
if (!semver.validRange(version)) { if (!semver.validRange(version)) {
throw new Error( throw new Error(
+3 -1
View File
@@ -1,3 +1,5 @@
export type SignatureVerificationKey = string | readonly string[];
export interface JavaInstallerOptions { export interface JavaInstallerOptions {
version: string; version: string;
architecture: string; architecture: string;
@@ -7,7 +9,7 @@ export interface JavaInstallerOptions {
cacheJdk?: boolean; cacheJdk?: boolean;
setDefault?: boolean; setDefault?: boolean;
verifySignature?: boolean; verifySignature?: boolean;
verifySignaturePublicKey?: string; verifySignaturePublicKey?: SignatureVerificationKey;
} }
export interface JavaInstallerResults { export interface JavaInstallerResults {
+2 -2
View File
@@ -166,8 +166,8 @@ export class LibericaDistributions extends JavaBase {
} }
private convertVersionToSemver(version: LibericaVersion): string { private convertVersionToSemver(version: LibericaVersion): string {
const {buildVersion, featureVersion, interimVersion, updateVersion} = const {featureVersion, interimVersion, updateVersion} = version;
version; const buildVersion = version.version.split('+')[1] || version.buildVersion;
const mainVersion = [featureVersion, interimVersion, updateVersion].join( const mainVersion = [featureVersion, interimVersion, updateVersion].join(
'.' '.'
); );
+1 -1
View File
@@ -65,7 +65,7 @@ export class LocalDistribution extends JavaBase {
architecture: this.architecture, architecture: this.architecture,
version: this.version, version: this.version,
source, source,
verification: getJdkVerificationIdentity(false), verification: getJdkVerificationIdentity(false, false),
path: this.getJdkCachePath(this.version) path: this.getJdkCachePath(this.version)
}; };
} }
+24 -14
View File
@@ -12,6 +12,7 @@ import {
} from '../../util.js'; } from '../../util.js';
import * as gpg from '../../gpg.js'; import * as gpg from '../../gpg.js';
import {MICROSOFT_PUBLIC_KEY} from './microsoft-key.js'; import {MICROSOFT_PUBLIC_KEY} from './microsoft-key.js';
import {SIGNATURE_VERIFICATION_FAILURE_HELP} from '../../constants.js';
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 fs from 'fs'; import fs from 'fs';
@@ -34,22 +35,31 @@ export class MicrosoftDistributions extends JavaBase {
let javaArchivePath = await this.downloadAndVerify(javaRelease); let javaArchivePath = await this.downloadAndVerify(javaRelease);
if (this.verifySignature) { if (this.verifySignature) {
if (!javaRelease.signatureUrl) {
throw new Error(
`Input 'verify-signature' is enabled, but no signature URL was found for Microsoft Build of OpenJDK version ${javaRelease.version}.`
);
}
core.info(`Verifying Java package signature...`);
try { try {
await gpg.verifyPackageSignature( if (!javaRelease.signatureUrl) {
javaArchivePath, throw new Error(
javaRelease.signatureUrl, `Input 'verify-signature' is enabled, but no signature URL was found for Microsoft Build of OpenJDK version ${javaRelease.version}.`
this.verifySignaturePublicKey ?? MICROSOFT_PUBLIC_KEY );
); }
core.info(`Verifying Java package signature...`);
try {
await gpg.verifyPackageSignature(
javaArchivePath,
javaRelease.signatureUrl,
this.verifySignaturePublicKey ?? MICROSOFT_PUBLIC_KEY
);
} catch (error) {
throw new Error(
`Failed to verify signature for Microsoft Build of OpenJDK version ${javaRelease.version}. Signature URL: ${javaRelease.signatureUrl}. Error: ${(error as Error).message} ${SIGNATURE_VERIFICATION_FAILURE_HELP}`,
{cause: error}
);
}
} catch (error) { } catch (error) {
throw new Error( if (this.verifySignatureExplicitlyRequested) {
`Failed to verify signature for Microsoft Build of OpenJDK version ${javaRelease.version}. Signature URL: ${javaRelease.signatureUrl}. Error: ${(error as Error).message}`, throw error;
{cause: error} }
core.warning(
error instanceof Error ? error.message : `Unknown error: ${error}`
); );
} }
} }
+4 -7
View File
@@ -1,5 +1,5 @@
import semver from 'semver'; import semver from 'semver';
import {convertVersionToSemver} from '../util.js'; import {normalizeJavaVersionToSemver} from '../util.js';
export enum JavaDistribution { export enum JavaDistribution {
Temurin = 'temurin', Temurin = 'temurin',
@@ -98,12 +98,9 @@ function canResolveTemurinJmods(version: string): boolean {
return true; return true;
} }
let normalizedRange = normalizedVersion const normalizedRange = normalizeJavaVersionToSemver(
.replace(/-ea$/, '') normalizedVersion.replace(/-ea$/, '').replace('-ea.', '+')
.replace('-ea.', '+'); );
if (/^\d+(\.\d+){3,}$/.test(normalizedRange)) {
normalizedRange = convertVersionToSemver(normalizedRange);
}
if (!semver.validRange(normalizedRange)) { if (!semver.validRange(normalizedRange)) {
// JavaBase owns general version validation and its targeted error messages. // JavaBase owns general version validation and its targeted error messages.
return true; return true;
+17 -2
View File
@@ -5,7 +5,14 @@ import {JavaDistribution} from './package-types.js';
export type JavaPlatform = 'linux' | 'macos' | 'windows' | 'solaris'; export type JavaPlatform = 'linux' | 'macos' | 'windows' | 'solaris';
export type JavaArchitecture = export type JavaArchitecture =
'x86' | 'x64' | 'armv7' | 'aarch64' | 'ppc64le' | 'ppc64' | 's390x'; | 'x86'
| 'x64'
| 'armv7'
| 'aarch64'
| 'ppc64le'
| 'ppc64'
| 'riscv64'
| 's390x';
interface VersionedArchitecture { interface VersionedArchitecture {
architecture: JavaArchitecture; architecture: JavaArchitecture;
@@ -27,7 +34,14 @@ export type JavaPlatformCapability =
RestrictedPlatformCapability | UnrestrictedPlatformCapability; RestrictedPlatformCapability | UnrestrictedPlatformCapability;
const X64_ARM64 = ['x64', 'aarch64'] as const; const X64_ARM64 = ['x64', 'aarch64'] as const;
const STANDARD_LINUX = ['x64', 'x86', 'aarch64', 'ppc64le', 's390x'] as const; const STANDARD_LINUX = [
'x64',
'x86',
'aarch64',
'ppc64le',
'riscv64',
's390x'
] as const;
export const JAVA_PLATFORM_CAPABILITIES: Record< export const JAVA_PLATFORM_CAPABILITIES: Record<
JavaDistribution, JavaDistribution,
@@ -174,6 +188,7 @@ const CANONICAL_ARCHITECTURES: readonly JavaArchitecture[] = [
'aarch64', 'aarch64',
'ppc64le', 'ppc64le',
'ppc64', 'ppc64',
'riscv64',
's390x' 's390x'
]; ];
+76 -21
View File
@@ -8,7 +8,10 @@ import * as gpg from '../../gpg.js';
import {ADOPTIUM_PUBLIC_KEY} from './adoptium-key.js'; import {ADOPTIUM_PUBLIC_KEY} from './adoptium-key.js';
import {JavaBase} from '../base-installer.js'; import {JavaBase} from '../base-installer.js';
import {ITemurinAvailableVersions} from './models.js'; import {ITemurinAvailableVersions} from './models.js';
import {MACOS_JAVA_CONTENT_POSTFIX} from '../../constants.js'; import {
MACOS_JAVA_CONTENT_POSTFIX,
SIGNATURE_VERIFICATION_FAILURE_HELP
} from '../../constants.js';
import { import {
JavaDownloadRelease, JavaDownloadRelease,
JavaInstallerOptions, JavaInstallerOptions,
@@ -67,7 +70,7 @@ export class TemurinDistribution extends JavaBase {
const formattedVersion = this.stable const formattedVersion = this.stable
? item.version_data.semver ? item.version_data.semver
: item.version_data.semver.replace('-beta+', '+'); : item.version_data.semver.replace('-beta+', '+');
return { const release: JavaDownloadRelease = {
version: formattedVersion, version: formattedVersion,
url: item.binaries[0].package.link, url: item.binaries[0].package.link,
signatureUrl: item.binaries[0].package.signature_link, signatureUrl: item.binaries[0].package.signature_link,
@@ -76,11 +79,29 @@ export class TemurinDistribution extends JavaBase {
value: item.binaries[0].package.checksum, value: item.binaries[0].package.checksum,
source: item.binaries[0].package.checksum_link source: item.binaries[0].package.checksum_link
} }
} as JavaDownloadRelease; };
return {
release,
openjdkVersion: getOpenJdkSemverVersion(item.version_data)
};
}); });
// The Adoptium API `semver` folds the JEP 322 patch field into the build
// number ('26.0.2.1+1' -> '26.0.2+101') and appends extra metadata for LTS
// releases ('25.0.4+7' -> '25.0.4+7.0.LTS'). Exact versions requested by
// users follow the OpenJDK notation instead, so also match them against a
// key derived from the OpenJDK version fields ('26.0.2+1.1', '25.0.4+7').
const isExactBuildRequest = (semver.parse(version)?.build.length ?? 0) > 0;
const satisfiedVersions = availableVersionsWithBinaries const satisfiedVersions = availableVersionsWithBinaries
.filter(item => isVersionSatisfies(version, item.version)) .filter(
({release, openjdkVersion}) =>
isVersionSatisfies(version, release.version) ||
(isExactBuildRequest &&
openjdkVersion !== null &&
semver.compareBuild(version, openjdkVersion) === 0)
)
.map(({release}) => release)
.sort((a, b) => { .sort((a, b) => {
return -semver.compareBuild(a.version, b.version); return -semver.compareBuild(a.version, b.version);
}); });
@@ -89,7 +110,7 @@ export class TemurinDistribution extends JavaBase {
satisfiedVersions.length > 0 ? satisfiedVersions[0] : null; satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
if (!resolvedFullVersion) { if (!resolvedFullVersion) {
const availableVersionStrings = availableVersionsWithBinaries.map( const availableVersionStrings = availableVersionsWithBinaries.map(
item => item.version ({release}) => release.version
); );
throw this.createVersionNotFoundError(version, availableVersionStrings); throw this.createVersionNotFoundError(version, availableVersionStrings);
} }
@@ -141,24 +162,41 @@ export class TemurinDistribution extends JavaBase {
const archivePath = await this.downloadAndVerify(release); const archivePath = await this.downloadAndVerify(release);
if (this.verifySignature) { if (this.verifySignature) {
if (!release.signatureUrl) {
throw new Error(
`Input 'verify-signature' is enabled, but no signature URL was found for Temurin version ${release.version}.`
);
}
core.info(`Verifying Java package signature...`);
try { try {
await gpg.verifyPackageSignature( if (!(await gpg.isGpgAvailable())) {
archivePath, throw new Error(
release.signatureUrl, "Input 'verify-signature' is enabled, but gpg is not available."
this.verifySignaturePublicKey ?? ADOPTIUM_PUBLIC_KEY );
); }
if (!release.signatureUrl) {
throw new Error(
`Input 'verify-signature' is enabled, but no signature URL was found for Temurin version ${release.version}.`
);
}
core.info(`Verifying Java package signature...`);
try {
await gpg.verifyPackageSignature(
archivePath,
release.signatureUrl,
this.verifySignaturePublicKey ?? ADOPTIUM_PUBLIC_KEY
);
} catch (error) {
const verificationError = new Error(
`Failed to verify signature for Temurin version ${release.version} from ${release.signatureUrl}: ${(error as Error).message} ${SIGNATURE_VERIFICATION_FAILURE_HELP}`,
{cause: error}
);
if (this.verifySignatureExplicitlyRequested) {
throw verificationError;
} else {
core.warning(verificationError.message);
}
}
} catch (error) { } catch (error) {
throw new Error( if (this.verifySignatureExplicitlyRequested) {
`Failed to verify signature for Temurin version ${release.version} from ${release.signatureUrl}: ${ throw error;
(error as Error).message }
}`, core.warning(
{cause: error} error instanceof Error ? error.message : `Unknown error: ${error}`
); );
} }
} }
@@ -289,3 +327,20 @@ export class TemurinDistribution extends JavaBase {
return architecture === 'armv7' ? 'arm' : architecture; return architecture === 'armv7' ? 'arm' : architecture;
} }
} }
/**
* Builds a SemVer version from the OpenJDK version fields reported by the
* Adoptium API, e.g. '26.0.2.1+1' -> '26.0.2+1.1' and '25.0.4+7-LTS' ->
* '25.0.4+7'. Returns null if the fields cannot form a valid SemVer version.
*/
function getOpenJdkSemverVersion(
versionData: ITemurinAvailableVersions['version_data']
): string | null {
const {major, minor, security, patch, build} = versionData;
if (build === undefined || build === null) {
return null;
}
const buildMetadata = patch ? `${patch}.${build}` : `${build}`;
const version = `${major}.${minor}.${security}+${buildMetadata}`;
return semver.valid(version) ? version : null;
}
+1
View File
@@ -34,6 +34,7 @@ export interface ITemurinAvailableVersions {
minor: number; minor: number;
openjdk_version: string; openjdk_version: string;
security: string; security: string;
patch?: number;
semver: string; semver: string;
}; };
} }
+21 -6
View File
@@ -2,7 +2,6 @@ import * as core from '@actions/core';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import semver from 'semver';
import {JavaBase} from '../base-installer.js'; import {JavaBase} from '../base-installer.js';
import {IZuluPackageDetails, IZuluVersions} from './models.js'; import {IZuluPackageDetails, IZuluVersions} from './models.js';
@@ -30,6 +29,17 @@ interface ZuluResolvedRelease {
packageUuid: string; packageUuid: string;
} }
function compareNumberArrays(a: number[], b: number[]): number {
const length = Math.max(a.length, b.length);
for (let i = 0; i < length; i++) {
const diff = (a[i] ?? 0) - (b[i] ?? 0);
if (diff !== 0) {
return diff;
}
}
return 0;
}
export class ZuluDistribution extends JavaBase { export class ZuluDistribution extends JavaBase {
constructor(installerOptions: JavaInstallerOptions) { constructor(installerOptions: JavaInstallerOptions) {
super('Zulu', installerOptions); super('Zulu', installerOptions);
@@ -50,7 +60,9 @@ export class ZuluDistribution extends JavaBase {
return { return {
version: convertVersionToSemver(javaVersion), version: convertVersionToSemver(javaVersion),
url: item.download_url, url: item.download_url,
zuluVersion: convertVersionToSemver(item.distro_version), javaVersion: item.java_version,
buildNumber: item.openjdk_build_number ?? 0,
distroVersion: item.distro_version,
packageUuid: item.package_uuid packageUuid: item.package_uuid
}; };
}); });
@@ -58,11 +70,14 @@ export class ZuluDistribution extends JavaBase {
const satisfiedVersions = availableVersions const satisfiedVersions = availableVersions
.filter(item => isVersionSatisfies(version, item.version)) .filter(item => isVersionSatisfies(version, item.version))
.sort((a, b) => { .sort((a, b) => {
// Azul provides two versions: java_version and distro_version // Compare numerically rather than via semver build metadata: Azul
// we should sort by both fields by descending // hotfix releases carry a 4th java_version segment (e.g. 25.0.4.1+1,
// rendered as '25.0.4+1.1') that must rank above 25.0.4+7, whereas
// semver.compareBuild would order the build identifiers '7' > '1'.
return ( return (
-semver.compareBuild(a.version, b.version) || -compareNumberArrays(a.javaVersion, b.javaVersion) ||
-semver.compareBuild(a.zuluVersion, b.zuluVersion) b.buildNumber - a.buildNumber ||
-compareNumberArrays(a.distroVersion, b.distroVersion)
); );
}) })
.map((item): ZuluResolvedRelease => ({ .map((item): ZuluResolvedRelease => ({
+31 -11
View File
@@ -5,11 +5,16 @@ import * as io from '@actions/io';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
import * as tc from '@actions/tool-cache'; import * as tc from '@actions/tool-cache';
import * as util from './util.js'; import * as util from './util.js';
import {ExecOptions} from '@actions/exec'; import type {ExecOptions} from '@actions/exec';
import type {SignatureVerificationKey} from './distributions/base-models.js';
export const GPG_HOME_PREFIX = 'setup-java-gpg-'; export const GPG_HOME_PREFIX = 'setup-java-gpg-';
const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-'; const VERIFY_GPG_HOME_PREFIX = 'verify-signature-gpg-home-';
export async function isGpgAvailable(): Promise<boolean> {
return Boolean(await io.which('gpg', false));
}
// Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...). // Convert a Windows path (D:\a\_temp\...) to a POSIX path (/d/a/_temp/...).
// The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions // The Git-bundled GPG on Windows (MSYS2-based) uses POSIX path conventions
// internally. Passing Windows paths with backslashes can cause fatal GPG errors // internally. Passing Windows paths with backslashes can cause fatal GPG errors
@@ -21,8 +26,11 @@ export function toGpgPath(p: string): string {
.replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`); .replace(/^([A-Za-z]):\//, (_, drive) => `/${drive.toLowerCase()}/`);
} }
function createGpgHome(prefix: string): string { function createGpgHome(
const gpgHome = fs.mkdtempSync(path.join(util.getTempDir(), prefix)); prefix: string,
tempDir: string = util.getTempDir()
): string {
const gpgHome = fs.mkdtempSync(path.join(tempDir, prefix));
if (process.platform !== 'win32') { if (process.platform !== 'win32') {
fs.chmodSync(gpgHome, 0o700); fs.chmodSync(gpgHome, 0o700);
} }
@@ -81,28 +89,33 @@ export async function removeGpgHome(gpgHome: string): Promise<void> {
return; return;
} }
await stopGpgAgent(resolvedGpgHome);
await io.rmRF(resolvedGpgHome);
}
async function stopGpgAgent(gpgHome: string): Promise<void> {
try { try {
await exec.exec( await exec.exec(
'gpgconf', 'gpgconf',
['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], ['--homedir', toGpgPath(gpgHome), '--kill', 'gpg-agent'],
{silent: true, ignoreReturnCode: true} {silent: true, ignoreReturnCode: true}
); );
} catch { } catch {
// gpgconf may be unavailable, but directory removal must still be attempted. // gpgconf may be unavailable, but directory removal must still be attempted.
} }
await io.rmRF(resolvedGpgHome);
} }
export async function verifyPackageSignature( export async function verifyPackageSignature(
archivePath: string, archivePath: string,
signatureUrl: string, signatureUrl: string,
publicKeyContent: string publicKeyContent: SignatureVerificationKey
) { ) {
const signaturePath = await tc.downloadTool(signatureUrl); const signaturePath = await tc.downloadTool(signatureUrl);
let gpgHome: string; let gpgHome: string;
try { try {
gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX); // Both RUNNER_TEMP and TMPDIR can exceed macOS's 104-byte agent socket limit.
const tempDir = process.platform === 'darwin' ? '/tmp' : util.getTempDir();
gpgHome = createGpgHome(VERIFY_GPG_HOME_PREFIX, tempDir);
} catch (error) { } catch (error) {
try { try {
await io.rmRF(signaturePath); await io.rmRF(signaturePath);
@@ -117,8 +130,14 @@ export async function verifyPackageSignature(
); );
} }
try { try {
const publicKeyFile = path.join(gpgHome, 'public-key.asc'); const publicKeys = Array.isArray(publicKeyContent)
fs.writeFileSync(publicKeyFile, publicKeyContent, {encoding: 'utf-8'}); ? publicKeyContent
: [publicKeyContent];
const publicKeyFiles = publicKeys.map((publicKey, index) => {
const publicKeyFile = path.join(gpgHome, `public-key-${index}.asc`);
fs.writeFileSync(publicKeyFile, publicKey, {encoding: 'utf-8'});
return toGpgPath(publicKeyFile);
});
const options: ExecOptions = {silent: true}; const options: ExecOptions = {silent: true};
await exec.exec( await exec.exec(
'gpg', 'gpg',
@@ -127,7 +146,7 @@ export async function verifyPackageSignature(
toGpgPath(gpgHome), toGpgPath(gpgHome),
'--batch', '--batch',
'--import', '--import',
toGpgPath(publicKeyFile) ...publicKeyFiles
], ],
options options
); );
@@ -144,6 +163,7 @@ export async function verifyPackageSignature(
options options
); );
} finally { } finally {
await stopGpgAgent(gpgHome);
await io.rmRF(signaturePath); await io.rmRF(signaturePath);
await io.rmRF(gpgHome); await io.rmRF(gpgHome);
} }
+26
View File
@@ -0,0 +1,26 @@
import fs from 'fs';
import {fileURLToPath} from 'url';
export function isMainModule(moduleUrl: string): boolean {
const entrypoint = process.argv[1];
if (!entrypoint || entrypoint === '-') {
return false;
}
let entrypointPath: string;
try {
entrypointPath = fs.realpathSync(entrypoint);
} catch (error) {
if (
error instanceof Error &&
'code' in error &&
(error.code === 'ENOENT' || error.code === 'ENOTDIR')
) {
return false;
}
throw error;
}
// Resolve both paths for runtimes using --preserve-symlinks-main.
return entrypointPath === fs.realpathSync(fileURLToPath(moduleUrl));
}
+19 -6
View File
@@ -4,6 +4,7 @@ import path from 'path';
import * as cache from '@actions/cache'; import * as cache from '@actions/cache';
import * as core from '@actions/core'; import * as core from '@actions/core';
import {isCacheFeatureAvailable} from './cache-feature.js'; import {isCacheFeatureAvailable} from './cache-feature.js';
import type {SignatureVerificationKey} from './distributions/base-models.js';
const STATE_JDK_CACHES = 'jdk-caches'; const STATE_JDK_CACHES = 'jdk-caches';
const JDK_CACHE_KEY_VERSION = 1; const JDK_CACHE_KEY_VERSION = 1;
@@ -117,18 +118,30 @@ function getInstallationIdentity(
export function getJdkVerificationIdentity( export function getJdkVerificationIdentity(
verifySignature: boolean, verifySignature: boolean,
publicKey?: string enforceSignatureVerification: boolean,
publicKey?: SignatureVerificationKey
): string { ): string {
if (!verifySignature) { if (!verifySignature) {
return 'unverified'; return 'disabled';
} }
const verificationPolicy = enforceSignatureVerification
? 'enforced'
: 'check-and-warn';
if (!publicKey) { if (!publicKey) {
return 'verified:bundled'; return `${verificationPolicy}:bundled`;
} }
const normalizedKey = publicKey.replace(/\r\n?/g, '\n').trim(); const publicKeys = Array.isArray(publicKey) ? publicKey : [publicKey];
const fingerprint = createHash('sha256').update(normalizedKey).digest('hex'); const normalizedKeys = publicKeys.map(key =>
return `verified:custom:sha256:${fingerprint}`; key.replace(/\r\n?/g, '\n').trim()
);
const fingerprintSource = Array.isArray(publicKey)
? normalizedKeys.map(key => `${Buffer.byteLength(key)}:${key}`).join('')
: normalizedKeys[0];
const fingerprint = createHash('sha256')
.update(fingerprintSource)
.digest('hex');
return `${verificationPolicy}:custom:sha256:${fingerprint}`;
} }
export async function saveJdkCaches(): Promise<void> { export async function saveJdkCaches(): Promise<void> {
+13 -6
View File
@@ -12,6 +12,7 @@ import {getJavaDistribution} from './distributions/distribution-factory.js';
import {JavaInstallerOptions} from './distributions/base-models.js'; import {JavaInstallerOptions} from './distributions/base-models.js';
import {configureProblemMatcher} from './problem-matcher.js'; import {configureProblemMatcher} from './problem-matcher.js';
import {validateToolchainIds} from './toolchain-ids.js'; import {validateToolchainIds} from './toolchain-ids.js';
import {isMainModule} from './is-main-module.js';
export async function run() { export async function run() {
const versions = core.getMultilineInput(constants.INPUT_JAVA_VERSION); const versions = core.getMultilineInput(constants.INPUT_JAVA_VERSION);
@@ -29,10 +30,6 @@ export async function run() {
const checkLatest = getBooleanInput(constants.INPUT_CHECK_LATEST, false); const checkLatest = getBooleanInput(constants.INPUT_CHECK_LATEST, false);
const forceDownload = getBooleanInput(constants.INPUT_FORCE_DOWNLOAD, false); const forceDownload = getBooleanInput(constants.INPUT_FORCE_DOWNLOAD, false);
const setDefault = getBooleanInput(constants.INPUT_SET_DEFAULT, true); const setDefault = getBooleanInput(constants.INPUT_SET_DEFAULT, true);
const verifySignature = getBooleanInput(
constants.INPUT_VERIFY_SIGNATURE,
false
);
const verifySignaturePublicKey = const verifySignaturePublicKey =
core.getInput(constants.INPUT_VERIFY_SIGNATURE_PUBLIC_KEY) || undefined; core.getInput(constants.INPUT_VERIFY_SIGNATURE_PUBLIC_KEY) || undefined;
const toolchainIds = core.getMultilineInput(constants.INPUT_MVN_TOOLCHAIN_ID); const toolchainIds = core.getMultilineInput(constants.INPUT_MVN_TOOLCHAIN_ID);
@@ -80,6 +77,8 @@ export async function run() {
); );
} }
const verifySignature = getVerifySignatureInput();
const installerInputsOptions: installerInputsOptions = { const installerInputsOptions: installerInputsOptions = {
architecture, architecture,
packageType, packageType,
@@ -107,6 +106,8 @@ export async function run() {
throw new Error('distribution input is required'); throw new Error('distribution input is required');
} }
const verifySignature = getVerifySignatureInput();
const installerInputsOptions: installerInputsOptions = { const installerInputsOptions: installerInputsOptions = {
architecture, architecture,
packageType, packageType,
@@ -172,7 +173,7 @@ function settle<T>(promise: Promise<T>): Promise<PromiseSettledResult<T>> {
); );
} }
if (process.argv[1] === fileURLToPath(import.meta.url)) { if (isMainModule(import.meta.url)) {
run(); run();
} else { } else {
// https://nodejs.org/api/modules.html#modules_accessing_the_main_module // https://nodejs.org/api/modules.html#modules_accessing_the_main_module
@@ -192,6 +193,12 @@ function getJdkFileInput(): string {
return jdkFile || deprecatedJdkFile; return jdkFile || deprecatedJdkFile;
} }
function getVerifySignatureInput(): boolean | undefined {
return core.getInput(constants.INPUT_VERIFY_SIGNATURE).trim()
? getBooleanInput(constants.INPUT_VERIFY_SIGNATURE)
: undefined;
}
async function installVersion( async function installVersion(
version: string, version: string,
options: installerInputsOptions, options: installerInputsOptions,
@@ -263,7 +270,7 @@ interface installerInputsOptions {
forceDownload: boolean; forceDownload: boolean;
cacheJdk: boolean; cacheJdk: boolean;
setDefault: boolean; setDefault: boolean;
verifySignature: boolean; verifySignature: boolean | undefined;
verifySignaturePublicKey: string | undefined; verifySignaturePublicKey: string | undefined;
distributionName: string; distributionName: string;
jdkFile: string; jdkFile: string;
+15
View File
@@ -510,6 +510,21 @@ export function convertVersionToSemver(version: number[] | string) {
return mainVersion; return mainVersion;
} }
/**
* Java versions (JEP 322) can contain more numeric fields than SemVer allows,
* e.g. '11.0.9.1' or Temurin respins such as '26.0.2.1+1'. Move the extra
* fields into SemVer build metadata ('11.0.9+1', '26.0.2+1.1'). Any other
* input (ranges, regular SemVer versions) is returned unchanged.
*/
export function normalizeJavaVersionToSemver(version: string): string {
const match = /^(\d+(?:\.\d+){3,})(?:\+([0-9A-Za-z.-]+))?$/.exec(version);
if (!match) {
return version;
}
const converted = convertVersionToSemver(match[1]);
return match[2] ? `${converted}.${match[2]}` : converted;
}
/** /**
* Builds a validator for the bytes currently served by a URL from the response * Builds a validator for the bytes currently served by a URL from the response
* headers of a HEAD request. A vendor's `/latest/` URL never changes, so this * headers of a HEAD request. A vendor's `/latest/` URL never changes, so this