Compare commits

..

9 Commits

Author SHA1 Message Date
Bruno Borges
dbe35f84c5 Add Maven plugin repository support
Generate plugin repositories alongside dependency repositories and share Central inclusion and ordering controls. Cover repository parsing, credentials, explicit Central overrides, plugin ordering, and effective Maven models; document the compatibility change and rebuild runtime bundles.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 52a8a8e2-7965-42d3-88d9-d66f64d0d60d
2026-10-14 15:26:18 -04:00
Yuna
2a69be27b0 feat: support Maven server credential origins (#1282)
* feat: support Maven server credential origins

* Refactor origin construction to include port if necessary

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

* fix: validate Maven server credential origins

* docs: note mvn-server-repository-origins requires Maven 3.10+

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

---------

Co-authored-by: Bruno Borges <bruno.borges@gmail.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Bruno Borges <brborges@microsoft.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-10-07 14:10:22 +02:00
dependabot[bot]
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
dependabot[bot]
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 Borges
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 Borges
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
Copilot
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
Jonas Konrad
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
Copilot
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
41 changed files with 3941 additions and 1374 deletions

View File

@@ -174,3 +174,115 @@ jobs:
if (-not (Test-Path $path)) {
throw "settings.xml file is not found in expected location"
}
test-maven-repositories:
name: Maven ${{ matrix.repositories }} repositories
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-latest, windows-latest, ubuntu-latest]
repositories: [dependencies, plugins, both]
steps:
- name: Checkout
uses: actions/checkout@v7
with:
persist-credentials: false
- name: Prepare Maven model inspection
run: |
$directory = Join-Path $env:RUNNER_TEMP "maven-repository-test"
New-Item -Path $directory -ItemType Directory -Force | Out-Null
$pomPath = Join-Path $directory "pom.xml"
$warmSettingsPath = Join-Path $directory "warm-settings.xml"
$cachePath = Join-Path $directory "repository"
$outputPath = Join-Path $directory "warm-effective-pom.xml"
# This project repository keeps the inspection plugin available offline
# after the generated settings disable inherited plugin Central.
@'
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>setup-java.test</groupId>
<artifactId>repository-settings</artifactId>
<version>1.0</version>
<pluginRepositories>
<pluginRepository>
<id>inspection</id>
<url>https://repo.maven.apache.org/maven2</url>
</pluginRepository>
</pluginRepositories>
</project>
'@ | Set-Content -Path $pomPath
'<settings />' | Set-Content -Path $warmSettingsPath
mvn --batch-mode --no-transfer-progress --settings $warmSettingsPath `
"-Dmaven.repo.local=$cachePath" --file $pomPath `
org.apache.maven.plugins:maven-help-plugin:3.5.1:effective-pom `
"-Doutput=$outputPath"
if ($LASTEXITCODE -ne 0) {
throw "Could not prepare the Maven Help Plugin"
}
- name: Configure dependency and plugin repositories
uses: ./
with:
distribution: 'temurin'
java-version: '21'
settings-path: ${{ runner.temp }}/maven-repository-test/settings
mvn-server-credentials: |
private:PRIVATE_USERNAME:PRIVATE_TOKEN
mvn-repositories: ${{ matrix.repositories != 'plugins' && 'private:https://repo.example.invalid/maven:false' || '' }}
mvn-plugin-repositories: ${{ matrix.repositories != 'dependencies' && 'private:https://repo.example.invalid/maven:false' || '' }}
mvn-repositories-include-central: false
- name: Validate generated settings and effective Maven repositories
env:
REPOSITORY_CONFIGURATION: ${{ matrix.repositories }}
run: |
$directory = Join-Path $env:RUNNER_TEMP "maven-repository-test"
$settingsPath = Join-Path $directory "settings/settings.xml"
[xml]$settings = Get-Content -Path $settingsPath -Raw
$profiles = @($settings.settings.profiles.profile)
if ($profiles.Count -ne 1 -or $profiles[0].id -ne "setup-java-repositories") {
throw "Expected one repository profile"
}
$activeProfiles = @($settings.settings.activeProfiles.activeProfile)
if ($activeProfiles.Count -ne 1 -or $activeProfiles[0] -ne "setup-java-repositories") {
throw "Expected one active repository profile"
}
$server = $settings.settings.servers.server
if ($server.id -ne "private" -or $server.username -ne '${env.PRIVATE_USERNAME}' -or $server.password -ne '${env.PRIVATE_TOKEN}') {
throw "Repository credentials were not preserved"
}
function Assert-Repositories($repositories, $expectPrivate, $context) {
$central = @($repositories | Where-Object { $_.id -eq "central" })
if ($central.Count -ne 1 -or $central[0].releases.enabled -ne "false" -or $central[0].snapshots.enabled -ne "false") {
throw "$context did not disable inherited Maven Central"
}
$private = @($repositories | Where-Object { $_.id -eq "private" })
$expectedCount = if ($expectPrivate) { 1 } else { 0 }
if ($private.Count -ne $expectedCount) {
throw "$context has incorrect private repositories"
}
if ($expectPrivate -and ($private[0].url -ne "https://repo.example.invalid/maven" -or $private[0].snapshots.enabled -ne "false")) {
throw "$context has incorrect private repository settings"
}
}
$expectDependencies = $env:REPOSITORY_CONFIGURATION -ne "plugins"
$expectPlugins = $env:REPOSITORY_CONFIGURATION -ne "dependencies"
Assert-Repositories @($profiles[0].repositories.repository) $expectDependencies "Settings dependency repositories"
Assert-Repositories @($profiles[0].pluginRepositories.pluginRepository) $expectPlugins "Settings plugin repositories"
$pomPath = Join-Path $directory "pom.xml"
$cachePath = Join-Path $directory "repository"
$outputPath = Join-Path $directory "effective-pom.xml"
mvn --batch-mode --no-transfer-progress --offline --settings $settingsPath `
"-Dmaven.repo.local=$cachePath" --file $pomPath `
org.apache.maven.plugins:maven-help-plugin:3.5.1:effective-pom `
"-Doutput=$outputPath"
if ($LASTEXITCODE -ne 0) {
throw "Could not inspect the effective Maven model"
}
[xml]$effectivePom = Get-Content -Path $outputPath -Raw
Assert-Repositories @($effectivePom.project.repositories.repository) $expectDependencies "Effective dependency repositories"
Assert-Repositories @($effectivePom.project.pluginRepositories.pluginRepository) $expectPlugins "Effective plugin repositories"

View File

@@ -1,6 +1,6 @@
---
name: brace-expansion
version: 5.0.9
version: 5.0.12
type: npm
summary: Brace expansion as known from sh/bash
homepage:

View File

@@ -1,6 +1,6 @@
---
name: fast-xml-parser
version: 5.11.0
version: 5.11.1
type: npm
summary: Validate XML, Parse XML, Build XML without C/C++ based libraries
homepage:

View File

@@ -1,6 +1,6 @@
---
name: undici
version: 6.28.0
version: 6.29.0
type: npm
summary: An HTTP/1.1 client, written from scratch for Node.js
homepage: https://undici.nodejs.org

View File

@@ -150,7 +150,7 @@ steps:
| `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. | |
| `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`. | |
| `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` |
@@ -168,9 +168,11 @@ steps:
| `server-username-env-var` | Environment variable name for Maven repository username. | `GITHUB_ACTOR` |
| `server-password-env-var` | Environment variable name for Maven repository password or token. | `GITHUB_TOKEN` |
| `mvn-server-credentials` | Multiline Maven server credentials in the format `server-id:USERNAME_ENV:PASSWORD_ENV`. Replaces the single server configured by the three inputs above when set. | |
| `mvn-server-repository-origins` | Multiline Maven credential origins in the format `server-id:repository-origin`. Each origin must belong to a configured server ID. Requires Maven 3.10+. | |
| `mvn-repositories` | Multiline Maven dependency repositories in the format `repository-id:repository-url:snapshots-enabled`. | |
| `mvn-repositories-include-central` | Include Maven Central in the generated dependency repositories profile. When `false`, Central is disabled unless an explicit `central` repository is declared. | `true` |
| `mvn-repositories-prioritize-central` | Place Maven Central before custom dependency repositories. Has no effect when Maven Central is excluded. | `true` |
| `mvn-plugin-repositories` | Multiline Maven plugin repositories in the format `repository-id:repository-url:snapshots-enabled`. Can be used independently of `mvn-repositories`. | |
| `mvn-repositories-include-central` | Include Maven Central for dependency and plugin repositories in the generated profile. When `false`, Central is disabled for both types unless that type explicitly declares `central`. Has no effect when neither repository list is configured. | `true` |
| `mvn-repositories-prioritize-central` | Place Maven Central before custom dependency and plugin repositories. Has no effect when Central is excluded or neither repository list is configured. | `true` |
| `settings-path` | Directory where `settings.xml` is written. | `~/.m2` |
| `overwrite-settings` | Overwrite an existing `settings.xml`. | `true` |
| `gpg-private-key` | GPG private key to import into an isolated temporary keyring. | |
@@ -233,7 +235,7 @@ Additional distribution notes:
| --- | --- |
| Major version | `8`, `11`, `17`, `21`, `25` |
| 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` |
| Latest stable GA release | `latest` |
@@ -481,10 +483,20 @@ steps:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
For dependencies hosted outside Maven Central, use `mvn-repositories` to add
resolution repositories to an active profile in the generated `settings.xml`.
Repository IDs can match `mvn-server-credentials` IDs when authentication is
required. See [Resolving Maven dependencies from custom repositories](docs/advanced-usage.md#resolving-maven-dependencies-from-custom-repositories).
For dependencies hosted outside Maven Central, use `mvn-repositories`; for build
plugins, use `mvn-plugin-repositories`. Either input adds its own repository type
to the same active profile in the generated `settings.xml`. Repository IDs can
match `mvn-server-credentials` IDs when authentication is required.
See [Resolving Maven dependencies from custom repositories](docs/advanced-usage.md#resolving-maven-dependencies-from-custom-repositories)
and [Resolving Maven plugins from custom repositories](docs/advanced-usage.md#resolving-maven-plugins-from-custom-repositories).
> [!WARNING]
> `mvn-repositories-include-central: false` now disables inherited Maven Central
> for **both dependencies and plugins**, even in existing workflows that only
> configure `mvn-repositories`. Builds relying on uncached plugins from Central
> must configure `mvn-plugin-repositories` with an accessible plugin repository
> or explicitly declare `central` there. Neither repository list configured
> means the Central controls remain inactive.
### GPG signing

View File

@@ -63,12 +63,51 @@ const credentials = (id: string, username: string, password: string) => [
const repositorySettings = (
repositories: auth.MavenRepository[],
includeCentral = true,
prioritizeCentral = true
prioritizeCentral = true,
pluginRepositories: auth.MavenRepository[] = []
): auth.MavenRepositorySettings => ({
repositories,
pluginRepositories,
includeCentral,
prioritizeCentral
});
const repositoryInputs = ['mvn-repositories', 'mvn-plugin-repositories'];
const privateRepository = {
id: 'private',
url: 'https://repo.example.com/maven',
snapshotsEnabled: true
};
const privateRepositoryXml = {
id: privateRepository.id,
url: privateRepository.url,
snapshots: {enabled: 'true'}
};
const centralRepositoryXml = {
id: 'central',
url: 'https://repo.maven.apache.org/maven2',
snapshots: {enabled: 'false'}
};
const disabledCentralRepositoryXml = {
...centralRepositoryXml,
releases: {enabled: 'false'}
};
const repositoryConfigurations = [
{
name: 'dependency-only',
repositories: [privateRepository],
pluginRepositories: []
},
{
name: 'plugin-only',
repositories: [],
pluginRepositories: [privateRepository]
},
{
name: 'combined',
repositories: [privateRepository],
pluginRepositories: [privateRepository]
}
];
describe('auth tests', () => {
let spyOSHomedir: any;
@@ -318,6 +357,21 @@ describe('auth tests', () => {
).toEqual(expectedSettings);
});
it('generates repository origins for a Maven server', () => {
const settings = auth.generate([
{
id: 'central',
usernameEnvVar: 'CENTRAL_USER',
passwordEnvVar: 'CENTRAL_PASS',
repositoryOrigins: ['https://central.sonatype.com']
}
]);
expect(settings).toContain(` <repositoryOrigins>
<repositoryOrigin>https://central.sonatype.com</repositoryOrigin>
</repositoryOrigins>`);
});
it('does not add a gpg profile when the passphrase env var is the maven-gpg-plugin default', () => {
const id = 'packages';
const username = 'USER';
@@ -460,6 +514,87 @@ describe('auth tests', () => {
).toEqual(['first', 'second', 'central']);
});
it.each([
{
name: 'Central first by default',
prioritizeCentral: undefined,
expectedRepositories: [
{
id: 'central',
url: 'https://repo.maven.apache.org/maven2',
snapshots: {enabled: 'false'}
},
{
id: 'first',
url: 'https://first.example.com/plugins',
snapshots: {enabled: 'false'}
},
{
id: 'second',
url: 'https://second.example.com/plugins',
snapshots: {enabled: 'true'}
}
]
},
{
name: 'custom plugins before Central',
prioritizeCentral: false,
expectedRepositories: [
{
id: 'first',
url: 'https://first.example.com/plugins',
snapshots: {enabled: 'false'}
},
{
id: 'second',
url: 'https://second.example.com/plugins',
snapshots: {enabled: 'true'}
},
{
id: 'central',
url: 'https://repo.maven.apache.org/maven2',
snapshots: {enabled: 'false'}
}
]
}
])(
'generates plugin-only settings with $name',
({prioritizeCentral, expectedRepositories}) => {
const xml = auth.generate(
[],
undefined,
repositorySettings([], true, prioritizeCentral, [
{
id: 'first',
url: 'https://first.example.com/plugins',
snapshotsEnabled: false
},
{
id: 'second',
url: 'https://second.example.com/plugins',
snapshotsEnabled: true
}
])
);
expect(parseXmlObject(xml)).toEqual(
expect.objectContaining({
settings: expect.objectContaining({
profiles: {
profile: {
id: 'setup-java-repositories',
pluginRepositories: {
pluginRepository: expectedRepositories
}
}
},
activeProfiles: {activeProfile: 'setup-java-repositories'}
})
})
);
}
);
it('excludes Central from the generated repository profile when configured', () => {
const parsed = parseXmlObject(
auth.generate(
@@ -491,6 +626,9 @@ describe('auth tests', () => {
snapshots: {enabled: 'false'}
}
]);
expect(
parsed.settings.profiles.profile.pluginRepositories.pluginRepository
).toEqual(disabledCentralRepositoryXml);
});
it('combines repository and GPG configuration in shared profile blocks', () => {
@@ -528,6 +666,9 @@ describe('auth tests', () => {
snapshots: {enabled: 'false'}
}
]
},
pluginRepositories: {
pluginRepository: disabledCentralRepositoryXml
}
},
{
@@ -541,6 +682,161 @@ describe('auth tests', () => {
]);
});
it.each(
repositoryConfigurations.flatMap(configuration =>
[
{includeCentral: true, prioritizeCentral: true},
{includeCentral: true, prioritizeCentral: false},
{includeCentral: false, prioritizeCentral: true},
{includeCentral: false, prioritizeCentral: false}
].map(controls => ({...configuration, ...controls}))
)
)(
'generates $name repositories with includeCentral=$includeCentral and prioritizeCentral=$prioritizeCentral',
({repositories, pluginRepositories, includeCentral, prioritizeCentral}) => {
const xml = auth.generate(
credentials('private', 'USERNAME', 'PASSWORD'),
undefined,
repositorySettings(
repositories,
includeCentral,
prioritizeCentral,
pluginRepositories
)
);
const expectedEntries = (configured: boolean) => {
const entries = includeCentral
? configured
? prioritizeCentral
? [centralRepositoryXml, privateRepositoryXml]
: [privateRepositoryXml, centralRepositoryXml]
: []
: configured
? [privateRepositoryXml, disabledCentralRepositoryXml]
: [disabledCentralRepositoryXml];
return entries.length === 1 ? entries[0] : entries;
};
const includeRepositories = repositories.length > 0 || !includeCentral;
const includePluginRepositories =
pluginRepositories.length > 0 || !includeCentral;
expect(parseXmlObject(xml)).toMatchObject({
settings: {
profiles: {
profile: {
id: 'setup-java-repositories',
...(includeRepositories
? {
repositories: {
repository: expectedEntries(repositories.length > 0)
}
}
: {}),
...(includePluginRepositories
? {
pluginRepositories: {
pluginRepository: expectedEntries(
pluginRepositories.length > 0
)
}
}
: {})
}
},
activeProfiles: {activeProfile: 'setup-java-repositories'}
}
});
if (!includeRepositories) {
expect(xml).not.toContain('<repositories>');
}
if (!includePluginRepositories) {
expect(xml).not.toContain('<pluginRepositories>');
}
}
);
it.each(repositoryConfigurations)(
'handles explicit Central independently for $name repositories',
({repositories, pluginRepositories}) => {
const central = {
id: 'central',
url: 'https://mirror.example.com/maven',
snapshotsEnabled: true
};
const explicitCentralXml = {
id: central.id,
url: central.url,
snapshots: {enabled: 'true'}
};
const xml = auth.generate(
[],
undefined,
repositorySettings(
repositories.length ? [central] : [],
false,
true,
pluginRepositories.length ? [central] : []
)
);
expect(parseXmlObject(xml)).toMatchObject({
settings: {
profiles: {
profile: {
repositories: {
repository: repositories.length
? explicitCentralXml
: disabledCentralRepositoryXml
},
pluginRepositories: {
pluginRepository: pluginRepositories.length
? explicitCentralXml
: disabledCentralRepositoryXml
}
}
}
}
});
expect(xml.match(/<id>central<\/id>/g)).toHaveLength(2);
}
);
it('escapes plugin repository values while preserving parsed semantics', () => {
const repository = {
id: `private&<>"'é`,
url: `https://repo.example.com:8443/a?x=1&y=<value>"'é`,
snapshotsEnabled: false
};
const xml = auth.generate(
credentials(repository.id, 'USERNAME', 'PASSWORD'),
undefined,
repositorySettings([], true, true, [repository])
);
expect(parseXmlObject(xml)).toMatchObject({
settings: {
servers: {server: {id: repository.id}},
profiles: {
profile: {
pluginRepositories: {
pluginRepository: [
centralRepositoryXml,
{
id: repository.id,
url: repository.url,
snapshots: {enabled: 'false'}
}
]
}
}
}
}
});
expect(xml).toContain('private&amp;&lt;&gt;');
expect(xml).toContain('x=1&amp;y=&lt;value&gt;');
expect(xml).not.toContain('<repositories>');
});
it('escapes repository values while preserving parsed semantics', () => {
const repository = {
id: `private&<>"'é`,
@@ -586,81 +882,111 @@ describe('auth tests', () => {
]);
});
it('parses Maven repositories while preserving colons in URLs', () => {
expect(
auth.parseMavenRepositories(
[
'',
' private : https://repo.example.com:8443/maven : true ',
'releases:https://repo.example.com/releases:false'
],
true
)
).toEqual([
it.each(repositoryInputs)(
'parses %s while preserving colons in URLs',
inputName => {
expect(
auth.parseMavenRepositories(
[
'',
' private : https://repo.example.com:8443/maven : TRUE ',
'releases:https://repo.example.com/releases:false'
],
true,
inputName
)
).toEqual([
{
id: 'private',
url: 'https://repo.example.com:8443/maven',
snapshotsEnabled: true
},
{
id: 'releases',
url: 'https://repo.example.com/releases',
snapshotsEnabled: false
}
]);
}
);
it.each(
[
{
id: 'private',
url: 'https://repo.example.com:8443/maven',
snapshotsEnabled: true
entries: ['private'],
error:
'Invalid mvn-repositories entry at line 1. Expected format: repository-id:repository-url:snapshots-enabled'
},
{
id: 'releases',
url: 'https://repo.example.com/releases',
snapshotsEnabled: false
entries: ['private:https://repo.example.com'],
error:
"Invalid snapshots-enabled value '//repo.example.com' in mvn-repositories entry at line 1. Expected true or false"
},
{
entries: ['private::true'],
error:
'Invalid mvn-repositories entry at line 1. repository-id, repository URL, and snapshots-enabled are required'
},
{
entries: ['private:https://repo.example.com:'],
error:
'Invalid mvn-repositories entry at line 1. repository-id, repository URL, and snapshots-enabled are required'
},
{
entries: ['', 'private:https://repo.example.com:true', ' :url:false'],
error:
'Invalid mvn-repositories entry at line 3. repository-id, repository URL, and snapshots-enabled are required'
},
{
entries: ['private:https://repo.example.com:sometimes'],
error:
"Invalid snapshots-enabled value 'sometimes' in mvn-repositories entry at line 1. Expected true or false"
}
]);
].flatMap(testCase =>
repositoryInputs.map(inputName => ({
...testCase,
inputName,
error: testCase.error.replace('mvn-repositories', inputName)
}))
)
)('rejects malformed $inputName: $entries', ({entries, error, inputName}) => {
expect(() => auth.parseMavenRepositories(entries, true, inputName)).toThrow(
error
);
});
it.each([
{
entries: ['private'],
error:
'Invalid mvn-repositories entry at line 1. Expected format: repository-id:repository-url:snapshots-enabled'
},
{
entries: ['private:https://repo.example.com'],
error:
"Invalid snapshots-enabled value '//repo.example.com' in mvn-repositories entry at line 1. Expected true or false"
},
{
entries: ['private::true'],
error:
'Invalid mvn-repositories entry at line 1. repository-id, repository URL, and snapshots-enabled are required'
},
{
entries: ['private:https://repo.example.com:sometimes'],
error:
"Invalid snapshots-enabled value 'sometimes' in mvn-repositories entry at line 1. Expected true or false"
}
])('rejects malformed Maven repositories: $entries', ({entries, error}) => {
expect(() => auth.parseMavenRepositories(entries, true)).toThrow(error);
});
it('rejects duplicate Maven repository ids', () => {
it.each(repositoryInputs)('rejects duplicate IDs in %s', inputName => {
expect(() =>
auth.parseMavenRepositories(
[
'private:https://first.example.com:false',
'private:https://second.example.com:true'
],
true
true,
inputName
)
).toThrow("Duplicate repository-id 'private' in mvn-repositories input");
).toThrow(`Duplicate repository-id 'private' in ${inputName} input`);
});
it('reserves the Central repository id only when automatic Central inclusion is enabled', () => {
const central = 'central:https://custom.example.com/maven:false';
it.each(repositoryInputs)(
'reserves the Central ID in %s only when automatic inclusion is enabled',
inputName => {
const central = 'central:https://custom.example.com/maven:false';
expect(() => auth.parseMavenRepositories([central], true)).toThrow(
"Repository-id 'central' is reserved when mvn-repositories-include-central is enabled"
);
expect(auth.parseMavenRepositories([central], false)).toEqual([
{
id: 'central',
url: 'https://custom.example.com/maven',
snapshotsEnabled: false
}
]);
});
expect(() =>
auth.parseMavenRepositories([central], true, inputName)
).toThrow(
"Repository-id 'central' is reserved when mvn-repositories-include-central is enabled"
);
expect(auth.parseMavenRepositories([central], false, inputName)).toEqual([
{
id: 'central',
url: 'https://custom.example.com/maven',
snapshotsEnabled: false
}
]);
}
);
it('uses an explicit Central repository instead of adding a disabled override', () => {
const parsed = parseXmlObject(
@@ -706,16 +1032,138 @@ describe('auth tests', () => {
snapshotsEnabled: true
}
],
pluginRepositories: [],
includeCentral: false,
prioritizeCentral: false
});
});
it('does not read repository controls when no repositories are configured', () => {
(
core.getInput as jest.MockedFunction<typeof core.getInput>
).mockReturnValue('false');
expect(auth.getMavenRepositorySettings()).toBeUndefined();
expect(core.getInput).not.toHaveBeenCalled();
});
it('ignores blank dependency and plugin repository inputs', () => {
(
core.getMultilineInput as jest.MockedFunction<
typeof core.getMultilineInput
>
).mockReturnValue(['', ' ', '\t']);
expect(auth.getMavenRepositorySettings()).toBeUndefined();
expect(core.getInput).not.toHaveBeenCalled();
});
it.each(repositoryConfigurations)(
'reads $name inputs with default Central controls and shared IDs',
({repositories, pluginRepositories}) => {
(
core.getInput as jest.MockedFunction<typeof core.getInput>
).mockReturnValue('');
(
core.getMultilineInput as jest.MockedFunction<
typeof core.getMultilineInput
>
).mockImplementation(name => {
const configured =
name === 'mvn-repositories' ? repositories : pluginRepositories;
return configured.map(
repository =>
`${repository.id}:${repository.url}:${repository.snapshotsEnabled}`
);
});
expect(auth.getMavenRepositorySettings()).toEqual({
repositories,
pluginRepositories,
includeCentral: true,
prioritizeCentral: true
});
}
);
it.each([true, false])(
'configures plugin repositories with credentials, GPG, a custom path, and overwrite=%s',
async overwriteSettings => {
const directory = path.join(m2Dir, 'custom');
const location = path.join(directory, MVN_SETTINGS_FILE);
fs.mkdirSync(directory, {recursive: true});
fs.writeFileSync(location, 'EXISTING SETTINGS');
(
core.getInput as jest.MockedFunction<typeof core.getInput>
).mockImplementation(name => {
const inputs: Record<string, string> = {
'settings-path': directory,
'overwrite-settings': String(overwriteSettings),
'gpg-passphrase-env-var': 'GPG_PASSPHRASE',
'mvn-repositories-include-central': 'false'
};
return inputs[name] ?? '';
});
(
core.getMultilineInput as jest.MockedFunction<
typeof core.getMultilineInput
>
).mockImplementation(name => {
const inputs: Record<string, string[]> = {
'mvn-server-credentials': ['private:USERNAME:PASSWORD'],
'mvn-server-repository-origins': ['private:https://repo.example.com'],
'mvn-plugin-repositories': [
'private:https://repo.example.com/maven:true'
]
};
return inputs[name] ?? [];
});
await auth.configureAuthentication();
const xml = fs.readFileSync(location, 'utf8');
if (!overwriteSettings) {
expect(xml).toBe('EXISTING SETTINGS');
return;
}
expect(parseXmlObject(xml)).toMatchObject({
settings: {
servers: {
server: {
id: 'private',
username: '${env.USERNAME}',
password: '${env.PASSWORD}',
repositoryOrigins: {
repositoryOrigin: 'https://repo.example.com'
}
}
},
profiles: {
profile: [
{
id: 'setup-java-repositories',
repositories: {repository: disabledCentralRepositoryXml},
pluginRepositories: {
pluginRepository: [
privateRepositoryXml,
disabledCentralRepositoryXml
]
}
},
{
id: 'setup-java-gpg',
properties: {'gpg.passphraseEnvName': 'GPG_PASSPHRASE'}
}
]
},
activeProfiles: {
activeProfile: ['setup-java-repositories', 'setup-java-gpg']
}
}
});
expect(fs.existsSync(settingsFile)).toBe(false);
}
);
it.each([
{
entries: ['releases:RELEASES_USERNAME'],
@@ -749,16 +1197,23 @@ describe('auth tests', () => {
});
it('uses multiline Maven server credentials instead of single-server inputs', () => {
(core.getMultilineInput as jest.Mock).mockReturnValue([
'releases:RELEASES_USERNAME:RELEASES_PASSWORD',
'snapshots:SNAPSHOTS_USERNAME:SNAPSHOTS_PASSWORD'
]);
(core.getMultilineInput as jest.Mock).mockImplementation((name: string) =>
name === 'mvn-server-credentials'
? [
'releases:RELEASES_USERNAME:RELEASES_PASSWORD',
'snapshots:SNAPSHOTS_USERNAME:SNAPSHOTS_PASSWORD'
]
: name === 'mvn-server-repository-origins'
? ['releases:https://repo.example.com']
: []
);
expect(auth.getMavenServerSettings()).toEqual([
{
id: 'releases',
usernameEnvVar: 'RELEASES_USERNAME',
passwordEnvVar: 'RELEASES_PASSWORD'
passwordEnvVar: 'RELEASES_PASSWORD',
repositoryOrigins: ['https://repo.example.com']
},
{
id: 'snapshots',
@@ -803,6 +1258,106 @@ describe('auth tests', () => {
expect(core.warning).toHaveBeenCalledTimes(2);
});
it('adds normalized repository origins to matching Maven servers', () => {
expect(
auth.addMavenServerRepositoryOrigins(
[
{
id: 'central',
usernameEnvVar: 'CENTRAL_USER',
passwordEnvVar: 'CENTRAL_PASS'
},
{
id: 'packages',
usernameEnvVar: 'GITHUB_ACTOR',
passwordEnvVar: 'GITHUB_TOKEN'
}
],
[
'central:https://central.sonatype.com/',
'central:https://central.sonatype.com',
'central:https://central.sonatype.com:443',
'packages:https://maven.pkg.github.com',
'packages:ssh://packages.example.com:22',
'packages:ftp://packages.example.com:21'
]
)
).toEqual([
{
id: 'central',
usernameEnvVar: 'CENTRAL_USER',
passwordEnvVar: 'CENTRAL_PASS',
repositoryOrigins: ['https://central.sonatype.com']
},
{
id: 'packages',
usernameEnvVar: 'GITHUB_ACTOR',
passwordEnvVar: 'GITHUB_TOKEN',
repositoryOrigins: [
'https://maven.pkg.github.com',
'ssh://packages.example.com:22',
'ftp://packages.example.com:21'
]
}
]);
});
it.each([
{
entries: ['central'],
error:
'Invalid mvn-server-repository-origins entry at line 1. Expected format: server-id:repository-origin'
},
{
entries: ['other:https://repo.example.com'],
error:
"Unknown server-id 'other' in mvn-server-repository-origins at line 1"
},
{
entries: ['central:https://repo.example.com/path'],
error:
"Invalid repository origin 'https://repo.example.com/path' in mvn-server-repository-origins at line 1"
},
{
entries: ['central:https:repo.example.com'],
error:
"Invalid repository origin 'https:repo.example.com' in mvn-server-repository-origins at line 1"
},
{
entries: ['central:https:/repo.example.com'],
error:
"Invalid repository origin 'https:/repo.example.com' in mvn-server-repository-origins at line 1"
},
{
entries: ['central:https://repo.example.com?'],
error:
"Invalid repository origin 'https://repo.example.com?' in mvn-server-repository-origins at line 1"
},
{
entries: ['central:https://repo.example.com#'],
error:
"Invalid repository origin 'https://repo.example.com#' in mvn-server-repository-origins at line 1"
},
{
entries: ['central:https://@repo.example.com'],
error:
"Invalid repository origin 'https://@repo.example.com' in mvn-server-repository-origins at line 1"
}
])('rejects invalid Maven server repository origins', ({entries, error}) => {
expect(() =>
auth.addMavenServerRepositoryOrigins(
[
{
id: 'central',
usernameEnvVar: 'CENTRAL_USER',
passwordEnvVar: 'CENTRAL_PASS'
}
],
entries
)
).toThrow(error);
});
function xmlElementText(xml: string, tagName: string): string {
const match = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`).exec(xml);
expect(match).not.toBeNull();

View File

@@ -1758,6 +1758,8 @@ describe('normalizeVersion', () => {
['11.0.9.1', {version: '11.0.9+1', stable: true, latest: false}],
['12.0.2.1.0', {version: '12.0.2+1.0', stable: true, latest: false}],
['18.0.1.1-ea', {version: '18.0.1+1', stable: false, latest: false}],
['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}]

View File

@@ -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",
async version => {
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",
async version => {
expect(

View File

@@ -14,7 +14,6 @@ import type {IncomingMessage} from 'http';
import {Readable} from 'stream';
import manifestData from '../data/jetbrains.json' with {type: 'json'};
import os from 'os';
// Mock @actions/core before importing source modules that depend on it
jest.unstable_mockModule('@actions/core', () => ({
@@ -81,6 +80,7 @@ describe('getAvailableVersions', () => {
jest.setTimeout(10_000);
let spyHttpClient: any;
let spyHttpClientHead: any;
let spyCoreError: any;
const originalGitHubToken = process.env.GITHUB_TOKEN;
@@ -93,6 +93,10 @@ describe('getAvailableVersions', () => {
headers: {},
result: []
});
spyHttpClientHead = jest.spyOn(HttpClient.prototype, 'head');
spyHttpClientHead.mockResolvedValue({
message: {statusCode: 200}
} as any);
// Mock core.error to suppress error logs
spyCoreError = core.error as jest.Mock;
@@ -133,9 +137,7 @@ describe('getAvailableVersions', () => {
const availableVersions = await distribution['getAvailableVersions']();
expect(availableVersions).not.toBeNull();
const length =
os.platform() === 'win32' ? manifestData.length : manifestData.length + 2;
expect(availableVersions.length).toBe(length);
expect(availableVersions.length).toBe(manifestData.length + 2);
}, 10_000);
it('continues a stable request after an all-prerelease page', async () => {
@@ -358,6 +360,7 @@ describe('getAvailableVersions', () => {
it('retries a GitHub rate limit using Retry-After', async () => {
spyHttpClient.mockRestore();
spyHttpClientHead.mockRestore();
const sleep = jest.fn(async () => undefined);
const requestRaw = jest
.spyOn(HttpClient.prototype, 'requestRaw')

View File

@@ -253,6 +253,39 @@ describe('findPackageForDownload', () => {
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 () => {
await expect(distribution['findPackageForDownload']('17')).rejects.toThrow(
/No matching version found for SemVer/
@@ -335,6 +368,36 @@ describe('convertVersionToSemver', () => {
buildVersion: 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) => {
const actual = distributions['convertVersionToSemver']({

View File

@@ -293,7 +293,8 @@ describe('getAvailableVersions', () => {
it.each([
['amd64', 'x64'],
['arm', 'arm'],
['arm64', 'aarch64']
['arm64', 'aarch64'],
['riscv64', 'riscv64']
])(
'defaults to os.arch(): %s mapped to distro arch: %s',
async (osArch: string, distroArch: string) => {
@@ -377,6 +378,97 @@ describe('findPackageForDownload', () => {
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 () => {
const distribution = new TemurinDistribution(
{

View File

@@ -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 () => {
const distribution = new ZuluDistribution({
version: '18',

View File

@@ -236,7 +236,12 @@ describe('gpg tests', () => {
process.env['RUNNER_TEMP'] = tempDir;
});
it.each(['success', 'import failure', 'verification failure'])(
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(
@@ -257,9 +262,16 @@ describe('gpg tests', () => {
fs.writeFileSync(signaturePath, 'signature');
(tc.downloadTool as jest.Mock<any>).mockResolvedValue(signaturePath);
(exec.exec as jest.Mock<any>).mockImplementation(
async (_command: string, args: string[]) => {
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'))
@@ -287,13 +299,18 @@ describe('gpg tests', () => {
'https://example.com/jdk.tar.gz.sig',
'public key'
);
if (outcome === 'success') {
if (outcome === 'success' || outcome === 'gpgconf unavailable') {
await verification;
} else {
await expect(verification).rejects.toThrow(outcome);
}
expect(exec.exec).toHaveBeenCalledTimes(
outcome === 'import failure' ? 1 : 2
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);
@@ -368,7 +385,7 @@ describe('gpg tests', () => {
],
expect.objectContaining({silent: true})
);
expect(exec.exec).toHaveBeenCalledTimes(2);
expect(exec.exec).toHaveBeenCalledTimes(3);
});
});
});

View File

@@ -25,6 +25,7 @@ describe('Java platform capabilities', () => {
['aarch64', 'aarch64'],
['arm64', 'aarch64'],
['ppc64le', 'ppc64le'],
['RiScV64', 'riscv64'],
['s390x', 's390x']
])('normalizes architecture %s to %s', (input, 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', () => {
expect(() =>
validateJavaPlatform('oracle', 'win32', 'arm64', '21')
@@ -115,6 +122,7 @@ describe('Java platform capabilities', () => {
'aarch64',
'ppc64le',
'ppc64',
'riscv64',
's390x'
]) {
expect(content).toContain(architecture);

View File

@@ -43,6 +43,7 @@ const core = await import('@actions/core');
const {
convertVersionToSemver,
normalizeJavaVersionToSemver,
getNextPageUrlFromLinkHeader,
getVersionFromFileContent,
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', () => {
it.each([
[
@@ -276,6 +293,8 @@ describe('getVersionFromFileContent', () => {
['java=21.0.5-graal', '21.0.5', 'graalvm'],
['java=17.0.9-graalce', '17.0.9', 'graalvm'],
['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=21.0.5-oracle', '21.0.5', 'oracle'],
['java=11.0.25-sapmchn', '11.0.25', 'sapmachine'],

View File

@@ -17,7 +17,7 @@ inputs:
required: false
default: 'jdk'
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
jdk-file:
description: 'Path to where the compressed JDK is located'
@@ -66,15 +66,21 @@ inputs:
mvn-server-credentials:
description: 'Multiline list of Maven server credentials in the format `server-id:USERNAME_ENV:PASSWORD_ENV`. When set, replaces the single server configured by server-id, server-username-env-var, and server-password-env-var.'
required: false
mvn-server-repository-origins:
description: 'Multiline list of Maven credential origins in the format `server-id:repository-origin`. Each origin must belong to a configured server ID. Requires Maven 3.10+.'
required: false
mvn-repositories:
description: 'Multiline list of Maven dependency repositories in the format `repository-id:repository-url:snapshots-enabled`.'
required: false
mvn-plugin-repositories:
description: 'Multiline list of Maven plugin repositories in the format `repository-id:repository-url:snapshots-enabled`.'
required: false
mvn-repositories-include-central:
description: 'Include Maven Central in the generated dependency repositories profile. When false, Central is disabled unless an explicit repository with ID `central` is declared.'
description: 'Include Maven Central for dependency and plugin repositories in the generated profile. When false, Central is disabled for both types unless that type explicitly declares ID `central`. Has no effect when neither repository list is configured.'
required: false
default: true
mvn-repositories-prioritize-central:
description: 'Place Maven Central before custom dependency repositories. Has no effect when Maven Central is excluded.'
description: 'Place Maven Central before custom dependency and plugin repositories. Has no effect when Maven Central is excluded or neither repository list is configured.'
required: false
default: true
settings-path:

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=""
@@ -18371,7 +18464,7 @@ function validateAttributeString(attrStr, options) {
//if(attrStr.trim().length === 0) return true; //empty string
const matches = getAllMatches(attrStr, validAttrStrRegxp);
const matches = scanAttributeTokens(attrStr);
const attrNames = {};
for (let i = 0; i < matches.length; i++) {
@@ -18473,7 +18566,6 @@ function getLineNumberForPosition(xmlData, index) {
function getPositionFromMatch(match) {
return match.startIndex + match[1].length;
}
;// CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/fxp.js
@@ -59498,6 +59590,24 @@ const EXPANSION_MAX = 100_000;
// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
// characters) so legitimate input is unaffected.
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) {
return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
}
@@ -59517,37 +59627,52 @@ function unescapeBraces(str) {
.replace(escCommaPattern, ',')
.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
* where we have nested braced sections, which should be
* treated as individual members, like {a,{b,c},d}
*/
function parseCommaParts(str) {
if (!str) {
return [''];
}
const parts = [];
const m = balanced('{', '}', str);
if (!m) {
return str.split(',');
// Walk the brace groups iteratively. Recursing on `post` once per group let a
// chain of them exhaust the stack - the parsing-side counterpart to
// 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 = {}) {
if (!str) {
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.
// Anything starting with {} will have the first two bytes preserved
// but *only* at the top level, so {},a}b will not expand to anything,
@@ -59557,7 +59682,7 @@ function expand(str, options = {}) {
if (str.slice(0, 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) {
return '{' + str + '}';
@@ -59652,7 +59777,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) {
}
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
// running set of combined prefixes (`acc`). Expanding the tail iteratively -
// rather than recursing on `m.post` once per group - keeps the native stack
@@ -59664,6 +59795,9 @@ function expand_(str, max, maxLength, isTop) {
// comma set - a sequence like `{a..\}` may legitimately yield ''. The drop
// is on the final strings, so it is applied to whichever `combine` produces
// 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 firstGroup = true;
for (;;) {
@@ -59688,7 +59822,8 @@ function expand_(str, max, maxLength, isTop) {
const isOptions = m.body.indexOf(',') >= 0;
if (!isSequence && !isOptions) {
// {a},b}
if (m.post.match(/,(?!,).*\}/)) {
if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) {
rewrites++;
str = m.pre + '{' + m.body + escClose + m.post;
isTop = true;
continue;
@@ -59708,7 +59843,7 @@ function expand_(str, max, maxLength, isTop) {
let n = parseCommaParts(m.body);
if (n.length === 1 && n[0] !== undefined) {
// 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.
/* c8 ignore start */
if (n.length === 1) {
@@ -59734,12 +59869,13 @@ function expand_(str, max, maxLength, isTop) {
values = [];
let valuesLength = 0;
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++) {
const v = expanded[k];
if (dropsEmpties && !v)
continue;
if (values.length >= max || valuesLength + v.length > maxLength) {
if (values.length >= max ||
valuesLength + v.length > maxLength) {
break outer;
}
values.push(v);

668
dist/cleanup/index.js vendored
View File

@@ -6009,11 +6009,77 @@ class Request {
}
}
onUpgrade (statusCode, headers, socket) {
/**
* @param {number|null} statusCode
* @param {Buffer[]|null} headers
* @param {import('node:stream').Duplex} socket
* @param {string} [statusText]
*/
onUpgrade (statusCode, headers, socket, statusText = '') {
this.onFinally()
assert(!this.aborted)
assert(!this.completed)
return this[kHandler].onUpgrade(statusCode, headers, socket)
if (statusCode !== null) {
this.#publishUpgradeHeaders(statusCode, headers, statusText)
}
const result = this[kHandler].onUpgrade(statusCode, headers, socket)
if (!this.aborted) {
this.completed = true
if (statusCode !== null) {
this.#publishUpgradeTrailers()
}
}
return result
}
/**
* @param {number} statusCode
* @param {import('node:http2').IncomingHttpHeaders} headers
* @param {(headers: import('node:http2').IncomingHttpHeaders) => Buffer[]} parseHeaders
* @param {string} [statusText]
*/
onUpgradeResponse (statusCode, headers, parseHeaders, statusText = '') {
assert(!this.aborted)
assert(this.completed)
if (channels.headers.hasSubscribers) {
this.#publishUpgradeHeaders(statusCode, parseHeaders(headers), statusText)
}
this.#publishUpgradeTrailers()
}
/**
* @param {Error} error
*/
onUpgradeError (error) {
assert(!this.aborted)
assert(this.completed)
if (channels.error.hasSubscribers) {
channels.error.publish({ request: this, error })
}
}
/**
* @param {number} statusCode
* @param {Buffer[]} headers
* @param {string} statusText
*/
#publishUpgradeHeaders (statusCode, headers, statusText) {
if (channels.headers.hasSubscribers) {
channels.headers.publish({ request: this, response: { statusCode, headers, statusText } })
}
}
#publishUpgradeTrailers () {
if (channels.trailers.hasSubscribers) {
channels.trailers.publish({ request: this, trailers: [] })
}
}
onComplete (trailers) {
@@ -7912,7 +7978,7 @@ class Parser {
}
onUpgrade (head) {
const { upgrade, client, socket, headers, statusCode } = this
const { upgrade, client, socket, headers, statusCode, statusText } = this
assert(upgrade)
assert(client[kSocket] === socket)
@@ -7947,9 +8013,10 @@ class Parser {
client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade'))
try {
request.onUpgrade(statusCode, headers, socket)
} catch (err) {
util.destroy(socket, err)
request.onUpgrade(statusCode, headers, socket, statusText)
} catch (error) {
util.errorRequest(client, request, error)
util.destroy(socket, error)
}
client[kResume]()
@@ -8356,7 +8423,7 @@ async function connectH1 (client, socket) {
function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
clearTimeout(socket[kIdleSocketValidationTimeout])
clearImmediate(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}
@@ -8365,15 +8432,23 @@ function clearIdleSocketValidation (socket) {
function scheduleIdleSocketValidation (client, socket) {
socket[kIdleSocketValidation] = 1
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
// Yield to the check phase (after poll) so unsolicited bytes / FIN / RST
// already pending on this idle keep-alive socket are processed before the
// next request is written (GHSA-35p6-xmwp-9g52).
//
// setTimeout(0) pays Node's ~1ms timer floor on every sequential reuse
// (#5493). setImmediate avoids that, but an *unref'd* Immediate lets poll
// block for ~500ms when the event loop is otherwise idle (#5600 / #5606).
// A ref'd Immediate both keeps the pending request alive and makes poll
// return immediately — the hybrid those issues asked for.
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
socket[kIdleSocketValidationTimeout] = null
socket[kIdleSocketValidation] = 2
if (client[kSocket] === socket && !socket.destroyed) {
client[kResume]()
}
}, 0)
socket[kIdleSocketValidationTimeout].unref?.()
})
}
/**
@@ -8522,12 +8597,22 @@ function writeH1 (client, request) {
const socket = client[kSocket]
clearIdleSocketValidation(socket)
const abort = (err) => {
if (request.aborted || request.completed) {
/**
* @param {Error} [error]
*/
const abort = (error) => {
if (request.aborted) {
return
}
util.errorRequest(client, request, err || new RequestAbortedError())
if (request.completed) {
if (request.upgrade || request.method === 'CONNECT') {
util.destroy(socket, new InformationalError('aborted'))
}
return
}
util.errorRequest(client, request, error || new RequestAbortedError())
util.destroy(body)
util.destroy(socket, new InformationalError('aborted'))
@@ -8984,6 +9069,7 @@ module.exports = connectH1
const assert = __nccwpck_require__(4589)
const { errorMonitor } = __nccwpck_require__(8474)
const { pipeline } = __nccwpck_require__(7075)
const util = __nccwpck_require__(3440)
const {
@@ -9060,6 +9146,15 @@ function parseH2Headers (headers) {
return result
}
/**
* @param {import('node:http2').IncomingHttpHeaders} headers
* @returns {Buffer[]}
*/
function parseH2ResponseHeaders (headers) {
const { [HTTP2_HEADER_STATUS]: _statusCode, ...realHeaders } = headers
return parseH2Headers(realHeaders)
}
async function connectH2 (client, socket) {
client[kSocket] = socket
@@ -9280,22 +9375,32 @@ function writeH2 (client, request) {
headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
headers[HTTP2_HEADER_METHOD] = method
const abort = (err) => {
if (request.aborted || request.completed) {
/**
* @param {Error} [error]
*/
const abort = (error) => {
if (request.aborted) {
return
}
err = err || new RequestAbortedError()
if (request.completed) {
if (method === 'CONNECT' && stream != null) {
util.destroy(stream, error || new RequestAbortedError())
}
return
}
util.errorRequest(client, request, err)
error = error || new RequestAbortedError()
util.errorRequest(client, request, error)
if (stream != null) {
util.destroy(stream, err)
util.destroy(stream, error)
}
// We do not destroy the socket as we can continue using the session
// the stream get's destroyed and the session remains to create new streams
util.destroy(body, err)
util.destroy(body, error)
client[kQueue][client[kRunningIdx]++] = null
client[kResume]()
}
@@ -9314,25 +9419,57 @@ function writeH2 (client, request) {
if (method === 'CONNECT') {
session.ref()
// We are already connected, streams are pending, first request
// will create a new stream. We trigger a request to create the stream and wait until
// `ready` event is triggered
// We disabled endStream to allow the user to write to the stream
stream = session.request(headers, { endStream: false, signal })
let upgradeResponseFinished = false
if (stream.id && !stream.pending) {
request.onUpgrade(null, null, stream)
++session[kOpenStreams]
client[kQueue][client[kRunningIdx]++] = null
} else {
stream.once('ready', () => {
request.onUpgrade(null, null, stream)
++session[kOpenStreams]
client[kQueue][client[kRunningIdx]++] = null
})
/**
* @param {import('node:http2').IncomingHttpHeaders} headers
*/
const onResponse = (headers) => {
upgradeResponseFinished = true
stream.off(errorMonitor, onUpgradeError)
request.onUpgradeResponse(Number(headers[HTTP2_HEADER_STATUS]), headers, parseH2ResponseHeaders)
}
/**
* @param {Error} error
*/
const onUpgradeError = (error) => {
upgradeResponseFinished = true
stream.off('response', onResponse)
request.onUpgradeError(error)
}
const onReady = () => {
try {
request.onUpgrade(null, null, stream)
} catch (error) {
stream.off('response', onResponse)
abort(error)
return
}
if (request.aborted) {
return
}
stream.off('error', abort)
stream.once(errorMonitor, onUpgradeError)
client[kQueue][client[kRunningIdx]++] = null
}
stream.once('response', onResponse)
stream.once('error', abort)
++session[kOpenStreams]
onReady()
stream.once('close', () => {
if (!upgradeResponseFinished && request.completed) {
stream.off('response', onResponse)
stream.off(errorMonitor, onUpgradeError)
request.onUpgradeError(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`))
}
session[kOpenStreams] -= 1
if (session[kOpenStreams] === 0) session.unref()
})
@@ -12031,6 +12168,7 @@ class RetryHandler {
this.end = null
this.etag = null
this.resume = null
this.headersSent = false
// Handle possible onConnect duplication
this.handler.onConnect(reason => {
@@ -12043,6 +12181,20 @@ class RetryHandler {
})
}
checkpointResponseEnd (headers, resume) {
if (this.end == null && this.opts.method !== 'HEAD') {
const contentLength = headers['content-length']
this.end = contentLength != null ? Number(contentLength) - 1 : null
assert(
this.end == null || Number.isFinite(this.end),
'invalid content-length'
)
}
this.resume = this.end != null ? resume : null
}
onRequestSent () {
if (this.handler.onRequestSent) {
this.handler.onRequestSent()
@@ -12131,7 +12283,12 @@ class RetryHandler {
this.retryCount += 1
if (statusCode >= 300) {
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
// Only expose a response if no earlier attempt has reached the caller.
// Otherwise abort this attempt so the error settles the existing body
// instead of replacing it with a new response.
if (!this.headersSent && this.retryOpts.statusCodes.includes(statusCode) === false) {
this.headersSent = true
this.checkpointResponseEnd(headers, resume)
return this.handler.onHeaders(
statusCode,
rawHeaders,
@@ -12200,8 +12357,15 @@ class RetryHandler {
const { start, size, end = size - 1 } = contentRange
assert(this.start === start, 'content-range mismatch')
assert(this.end == null || this.end === end, 'content-range mismatch')
if (this.start !== start || (this.end != null && this.end !== end)) {
this.abort(
new RequestRetryError('Content-Range mismatch', statusCode, {
headers,
data: { count: this.retryCount }
})
)
return false
}
this.resume = resume
return true
@@ -12213,6 +12377,7 @@ class RetryHandler {
const range = parseRangeHeader(headers['content-range'])
if (range == null) {
this.headersSent = true
return this.handler.onHeaders(
statusCode,
rawHeaders,
@@ -12251,6 +12416,7 @@ class RetryHandler {
)
this.resume = resume
this.headersSent = true
this.etag = headers.etag != null ? headers.etag : null
// Weak etags are not useful for comparison nor cache
@@ -12290,7 +12456,7 @@ class RetryHandler {
}
onError (err) {
if (this.aborted || isDisturbed(this.opts.body)) {
if (this.aborted || isDisturbed(this.opts.body) || (this.headersSent && this.resume == null)) {
return this.handler.onError(err)
}
@@ -16748,6 +16914,49 @@ const COLON = 0x3A
*/
const SPACE = 0x20
const DATA = Buffer.from('data')
const EVENT = Buffer.from('event')
const ID = Buffer.from('id')
const RETRY = Buffer.from('retry')
function isASCIINumberBytes (buffer, start) {
if (start >= buffer.length) {
return false
}
for (let i = start; i < buffer.length; i++) {
if (buffer[i] < 0x30 || buffer[i] > 0x39) {
return false
}
}
return true
}
function isValidLastEventIdBytes (buffer, start) {
for (let i = start; i < buffer.length; i++) {
if (buffer[i] === 0x00) {
return false
}
}
return true
}
function isFieldName (line, length, field) {
if (length !== field.length) {
return false
}
for (let i = 0; i < length; i++) {
if (line[i] !== field[i]) {
return false
}
}
return true
}
/**
* @typedef {object} EventSourceStreamEvent
* @type {object}
@@ -16788,11 +16997,14 @@ class EventSourceStream extends Transform {
eventEndCheck = false
/**
* @type {Buffer}
* @type {Buffer[]}
*/
buffer = null
chunks = []
chunkIndex = 0
pos = 0
lineChunkIndex = 0
linePos = 0
event = {
data: undefined,
@@ -16831,92 +17043,20 @@ class EventSourceStream extends Transform {
return
}
// Cache the chunk in the buffer, as the data might not be complete while
// processing it
// TODO: Investigate if there is a more performant way to handle
// incoming chunks
// see: https://github.com/nodejs/undici/issues/2630
if (this.buffer) {
this.buffer = Buffer.concat([this.buffer, chunk])
} else {
this.buffer = chunk
}
this.chunks.push(chunk)
// Strip leading byte-order-mark if we opened the stream and started
// the processing of the incoming data
if (this.checkBOM) {
switch (this.buffer.length) {
case 1:
// Check if the first byte is the same as the first byte of the BOM
if (this.buffer[0] === BOM[0]) {
// If it is, we need to wait for more data
callback()
return
}
// Set the checkBOM flag to false as we don't need to check for the
// BOM anymore
this.checkBOM = false
// The buffer only contains one byte so we need to wait for more data
callback()
return
case 2:
// Check if the first two bytes are the same as the first two bytes
// of the BOM
if (
this.buffer[0] === BOM[0] &&
this.buffer[1] === BOM[1]
) {
// If it is, we need to wait for more data, because the third byte
// is needed to determine if it is the BOM or not
callback()
return
}
// Set the checkBOM flag to false as we don't need to check for the
// BOM anymore
this.checkBOM = false
break
case 3:
// Check if the first three bytes are the same as the first three
// bytes of the BOM
if (
this.buffer[0] === BOM[0] &&
this.buffer[1] === BOM[1] &&
this.buffer[2] === BOM[2]
) {
// If it is, we can drop the buffered data, as it is only the BOM
this.buffer = Buffer.alloc(0)
// Set the checkBOM flag to false as we don't need to check for the
// BOM anymore
this.checkBOM = false
// Await more data
callback()
return
}
// If it is not the BOM, we can start processing the data
this.checkBOM = false
break
default:
// The buffer is longer than 3 bytes, so we can drop the BOM if it is
// present
if (
this.buffer[0] === BOM[0] &&
this.buffer[1] === BOM[1] &&
this.buffer[2] === BOM[2]
) {
// Remove the BOM from the buffer
this.buffer = this.buffer.subarray(3)
}
// Set the checkBOM flag to false as we don't need to check for the
this.checkBOM = false
break
if (this.handleBOM()) {
callback()
return
}
}
while (this.pos < this.buffer.length) {
while (this.hasCurrentByte()) {
const byte = this.currentByte()
// If the previous line ended with an end-of-line, we need to check
// if the next character is also an end-of-line.
if (this.eventEndCheck) {
@@ -16929,10 +17069,9 @@ class EventSourceStream extends Transform {
if (this.crlfCheck) {
// If the current character is a line feed, we can remove it
// from the buffer and reset the crlfCheck flag
if (this.buffer[this.pos] === LF) {
this.buffer = this.buffer.subarray(this.pos + 1)
this.pos = 0
if (byte === LF) {
this.crlfCheck = false
this.consumeCurrentByte()
// It is possible that the line feed is not the end of the
// event. We need to check if the next character is an
@@ -16948,19 +17087,17 @@ class EventSourceStream extends Transform {
this.crlfCheck = false
}
if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) {
if (byte === LF || byte === CR) {
// If the current character is a carriage return, we need to
// set the crlfCheck flag to true, as we need to check if the
// next character is a line feed so we can remove it from the
// buffer
if (this.buffer[this.pos] === CR) {
if (byte === CR) {
this.crlfCheck = true
}
this.buffer = this.buffer.subarray(this.pos + 1)
this.pos = 0
if (
this.event.data !== undefined || this.event.event || this.event.id || this.event.retry) {
this.consumeCurrentByte()
if (this.hasPendingEvent()) {
this.processEvent(this.event)
}
this.clearEvent()
@@ -16974,22 +17111,18 @@ class EventSourceStream extends Transform {
// If the current character is an end-of-line, we can process the
// line
if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) {
if (byte === LF || byte === CR) {
// If the current character is a carriage return, we need to
// set the crlfCheck flag to true, as we need to check if the
// next character is a line feed
if (this.buffer[this.pos] === CR) {
if (byte === CR) {
this.crlfCheck = true
}
// In any case, we can process the line as we reached an
// end-of-line character
this.parseLine(this.buffer.subarray(0, this.pos), this.event)
// Remove the processed line from the buffer
this.buffer = this.buffer.subarray(this.pos + 1)
// Reset the position as we removed the processed line from the buffer
this.pos = 0
this.parseLine(this.readLine(), this.event)
this.consumeCurrentByte()
// A line was processed and this could be the end of the event. We need
// to check if the next line is empty to determine if the event is
// finished.
@@ -16997,7 +17130,7 @@ class EventSourceStream extends Transform {
continue
}
this.pos++
this.advanceCursor()
}
callback()
@@ -17022,64 +17155,53 @@ class EventSourceStream extends Transform {
return
}
let field = ''
let value = ''
let fieldLength = line.length
let valueStart = line.length
// If the line contains a U+003A COLON character (:)
if (colonPosition !== -1) {
// Collect the characters on the line before the first U+003A COLON
// character (:), and let field be that string.
// TODO: Investigate if there is a more performant way to extract the
// field
// see: https://github.com/nodejs/undici/issues/2630
field = line.subarray(0, colonPosition).toString('utf8')
fieldLength = colonPosition
// Collect the characters on the line after the first U+003A COLON
// character (:), and let value be that string.
// If value starts with a U+0020 SPACE character, remove it from value.
let valueStart = colonPosition + 1
valueStart = colonPosition + 1
if (line[valueStart] === SPACE) {
++valueStart
}
// TODO: Investigate if there is a more performant way to extract the
// value
// see: https://github.com/nodejs/undici/issues/2630
value = line.subarray(valueStart).toString('utf8')
// Otherwise, the string is not empty but does not contain a U+003A COLON
// character (:)
} else {
// Process the field using the steps described below, using the whole
// line as the field name, and the empty string as the field value.
field = line.toString('utf8')
value = ''
}
// Modify the event with the field name and value. The value is also
// decoded as UTF-8
switch (field) {
case 'data':
if (event[field] === undefined) {
event[field] = value
} else {
event[field] += `\n${value}`
}
break
case 'retry':
if (isASCIINumber(value)) {
event[field] = value
}
break
case 'id':
if (isValidLastEventId(value)) {
event[field] = value
}
break
case 'event':
if (value.length > 0) {
event[field] = value
}
break
if (isFieldName(line, fieldLength, DATA)) {
const value = line.toString('utf8', valueStart)
if (event.data === undefined) {
event.data = value
} else {
event.data += `\n${value}`
}
return
}
if (isFieldName(line, fieldLength, RETRY)) {
if (isASCIINumberBytes(line, valueStart)) {
event.retry = line.toString('utf8', valueStart)
}
return
}
if (isFieldName(line, fieldLength, ID)) {
if (isValidLastEventIdBytes(line, valueStart)) {
event.id = line.toString('utf8', valueStart)
}
return
}
if (isFieldName(line, fieldLength, EVENT)) {
const value = line.toString('utf8', valueStart)
if (value.length > 0) {
event.event = value
}
}
}
@@ -17109,13 +17231,152 @@ class EventSourceStream extends Transform {
}
clearEvent () {
this.event = {
data: undefined,
event: undefined,
id: undefined,
retry: undefined
this.event.data = undefined
this.event.event = undefined
this.event.id = undefined
this.event.retry = undefined
}
hasPendingEvent () {
return this.event.data !== undefined ||
this.event.event !== undefined ||
this.event.id !== undefined ||
this.event.retry !== undefined
}
hasCurrentByte () {
return this.chunkIndex < this.chunks.length &&
this.pos < this.chunks[this.chunkIndex].length
}
currentByte () {
return this.chunks[this.chunkIndex][this.pos]
}
consumeCurrentByte () {
this.advanceCursor()
this.syncLineStartToCursor()
}
advanceCursor () {
this.pos++
while (this.chunkIndex < this.chunks.length && this.pos >= this.chunks[this.chunkIndex].length) {
this.chunkIndex++
this.pos = 0
}
}
syncLineStartToCursor () {
this.lineChunkIndex = this.chunkIndex
this.linePos = this.pos
this.dropConsumedChunks()
}
dropConsumedChunks () {
while (this.lineChunkIndex > 0) {
this.chunks.shift()
this.lineChunkIndex--
this.chunkIndex--
}
if (this.chunkIndex === this.chunks.length) {
this.chunks.length = 0
this.chunkIndex = 0
this.pos = 0
this.lineChunkIndex = 0
this.linePos = 0
}
}
readLine () {
if (this.lineChunkIndex === this.chunkIndex) {
return this.chunks[this.chunkIndex].subarray(this.linePos, this.pos)
}
const chunks = []
let length = 0
for (let i = this.lineChunkIndex; i <= this.chunkIndex; i++) {
const chunk = this.chunks[i]
const start = i === this.lineChunkIndex ? this.linePos : 0
const end = i === this.chunkIndex ? this.pos : chunk.length
const slice = chunk.subarray(start, end)
length += slice.length
chunks.push(slice)
}
return Buffer.concat(chunks, length)
}
peekBufferedByte (offset) {
let chunkIndex = this.lineChunkIndex
let pos = this.linePos
while (chunkIndex < this.chunks.length) {
const chunk = this.chunks[chunkIndex]
const remaining = chunk.length - pos
if (offset < remaining) {
return chunk[pos + offset]
}
offset -= remaining
chunkIndex++
pos = 0
}
}
discardLeadingBytes (count) {
while (count > 0 && this.lineChunkIndex < this.chunks.length) {
const chunk = this.chunks[this.lineChunkIndex]
const remaining = chunk.length - this.linePos
if (count < remaining) {
this.linePos += count
count = 0
} else {
count -= remaining
this.lineChunkIndex++
this.linePos = 0
}
}
this.chunkIndex = this.lineChunkIndex
this.pos = this.linePos
this.dropConsumedChunks()
}
handleBOM () {
const first = this.peekBufferedByte(0)
const second = this.peekBufferedByte(1)
const third = this.peekBufferedByte(2)
if (second === undefined) {
if (first === BOM[0]) {
return true
}
this.checkBOM = false
return true
}
if (third === undefined) {
if (first === BOM[0] && second === BOM[1]) {
return true
}
this.checkBOM = false
return false
}
if (first === BOM[0] && second === BOM[1] && third === BOM[2]) {
this.discardLeadingBytes(3)
}
this.checkBOM = false
return !this.hasCurrentByte()
}
}
module.exports = {
@@ -28383,7 +28644,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
// is specified, the server needs to include the same field and one of
// the selected subprotocol values in its response for the connection to
// be established.
if (!requestProtocols.includes(secProtocol)) {
if (requestProtocols === null || !requestProtocols.includes(secProtocol)) {
failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
return
}
@@ -29144,7 +29405,12 @@ class PerMessageDeflate {
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
callback(new MessageSizeExceededError())
// The inflater may still hold buffered input that can emit a late
// zlib error. Remove the data listener, then deterministically stop
// the stream so a subsequent 'error' cannot fire without a listener
// (which would terminate the process as an unhandled error event).
this.#inflate.removeAllListeners()
this.#inflate.destroy()
this.#inflate = null
return
}
@@ -30774,7 +31040,7 @@ module.exports = {
/* harmony export */ gk: () => (/* binding */ INPUT_CACHE),
/* harmony export */ wG: () => (/* binding */ INPUT_JOB_STATUS)
/* harmony export */ });
/* unused harmony exports MACOS_JAVA_CONTENT_POSTFIX, INPUT_JAVA_VERSION, INPUT_JAVA_VERSION_FILE, INPUT_ARCHITECTURE, INPUT_JAVA_PACKAGE, INPUT_DISTRIBUTION, INPUT_JDK_FILE, INPUT_JDK_FILE_DEPRECATED, INPUT_CHECK_LATEST, INPUT_FORCE_DOWNLOAD, INPUT_SET_DEFAULT, INPUT_PROBLEM_MATCHER, INPUT_VERIFY_SIGNATURE, INPUT_VERIFY_SIGNATURE_PUBLIC_KEY, SIGNATURE_VERIFICATION_DOCUMENTATION_URL, SIGNATURE_VERIFICATION_FAILURE_HELP, INPUT_MVN_SERVER_CREDENTIALS, INPUT_MVN_REPOSITORIES, INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL, INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL, INPUT_SERVER_ID, INPUT_SERVER_USERNAME_ENV_VAR, INPUT_SERVER_PASSWORD_ENV_VAR, INPUT_SERVER_USERNAME_DEPRECATED, INPUT_SERVER_PASSWORD_DEPRECATED, INPUT_SETTINGS_PATH, INPUT_OVERWRITE_SETTINGS, INPUT_GPG_PRIVATE_KEY, INPUT_GPG_PASSPHRASE_ENV_VAR, INPUT_GPG_PASSPHRASE_DEPRECATED, INPUT_DEFAULT_SERVER_USERNAME, INPUT_DEFAULT_SERVER_PASSWORD, INPUT_DEFAULT_GPG_PRIVATE_KEY, INPUT_DEFAULT_GPG_PASSPHRASE, MAVEN_GPG_PASSPHRASE_DEFAULT_ENV, GPG_PASSPHRASE_PROFILE_ID, MAVEN_REPOSITORIES_PROFILE_ID, MAVEN_CENTRAL_REPOSITORY_ID, MAVEN_CENTRAL_REPOSITORY_URL, INPUT_CACHE_DEPENDENCY_PATH, INPUT_CACHE_PATH, M2_DIR, MVN_SETTINGS_FILE, MVN_TOOLCHAINS_FILE, INPUT_MVN_TOOLCHAIN_ID, INPUT_MVN_TOOLCHAIN_VENDOR, INPUT_SHOW_DOWNLOAD_PROGRESS, MAVEN_ARGS_ENV, MAVEN_NO_TRANSFER_PROGRESS_FLAG, MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG, DISTRIBUTIONS_ONLY_MAJOR_VERSION */
/* unused harmony exports MACOS_JAVA_CONTENT_POSTFIX, INPUT_JAVA_VERSION, INPUT_JAVA_VERSION_FILE, INPUT_ARCHITECTURE, INPUT_JAVA_PACKAGE, INPUT_DISTRIBUTION, INPUT_JDK_FILE, INPUT_JDK_FILE_DEPRECATED, INPUT_CHECK_LATEST, INPUT_FORCE_DOWNLOAD, INPUT_SET_DEFAULT, INPUT_PROBLEM_MATCHER, INPUT_VERIFY_SIGNATURE, INPUT_VERIFY_SIGNATURE_PUBLIC_KEY, SIGNATURE_VERIFICATION_DOCUMENTATION_URL, SIGNATURE_VERIFICATION_FAILURE_HELP, INPUT_MVN_SERVER_CREDENTIALS, INPUT_MVN_SERVER_REPOSITORY_ORIGINS, INPUT_MVN_REPOSITORIES, INPUT_MVN_PLUGIN_REPOSITORIES, INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL, INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL, INPUT_SERVER_ID, INPUT_SERVER_USERNAME_ENV_VAR, INPUT_SERVER_PASSWORD_ENV_VAR, INPUT_SERVER_USERNAME_DEPRECATED, INPUT_SERVER_PASSWORD_DEPRECATED, INPUT_SETTINGS_PATH, INPUT_OVERWRITE_SETTINGS, INPUT_GPG_PRIVATE_KEY, INPUT_GPG_PASSPHRASE_ENV_VAR, INPUT_GPG_PASSPHRASE_DEPRECATED, INPUT_DEFAULT_SERVER_USERNAME, INPUT_DEFAULT_SERVER_PASSWORD, INPUT_DEFAULT_GPG_PRIVATE_KEY, INPUT_DEFAULT_GPG_PASSPHRASE, MAVEN_GPG_PASSPHRASE_DEFAULT_ENV, GPG_PASSPHRASE_PROFILE_ID, MAVEN_REPOSITORIES_PROFILE_ID, MAVEN_CENTRAL_REPOSITORY_ID, MAVEN_CENTRAL_REPOSITORY_URL, INPUT_CACHE_DEPENDENCY_PATH, INPUT_CACHE_PATH, M2_DIR, MVN_SETTINGS_FILE, MVN_TOOLCHAINS_FILE, INPUT_MVN_TOOLCHAIN_ID, INPUT_MVN_TOOLCHAIN_VENDOR, INPUT_SHOW_DOWNLOAD_PROGRESS, MAVEN_ARGS_ENV, MAVEN_NO_TRANSFER_PROGRESS_FLAG, MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG, DISTRIBUTIONS_ONLY_MAJOR_VERSION */
const MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home';
const INPUT_JAVA_VERSION = 'java-version';
const INPUT_JAVA_VERSION_FILE = 'java-version-file';
@@ -30792,7 +31058,9 @@ const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key';
const SIGNATURE_VERIFICATION_DOCUMENTATION_URL = 'https://github.com/actions/setup-java#download-integrity-and-signatures';
const SIGNATURE_VERIFICATION_FAILURE_HELP = (/* unused pure expression or super */ null && (`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.`));
const INPUT_MVN_SERVER_CREDENTIALS = 'mvn-server-credentials';
const INPUT_MVN_SERVER_REPOSITORY_ORIGINS = 'mvn-server-repository-origins';
const INPUT_MVN_REPOSITORIES = 'mvn-repositories';
const INPUT_MVN_PLUGIN_REPOSITORIES = 'mvn-plugin-repositories';
const INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL = 'mvn-repositories-include-central';
const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL = 'mvn-repositories-prioritize-central';
const INPUT_SERVER_ID = 'server-id';
@@ -30848,7 +31116,7 @@ const DISTRIBUTIONS_ONLY_MAJOR_VERSION = (/* unused pure expression or super */
/* harmony export */ Vt: () => (/* binding */ getBooleanInput),
/* harmony export */ lN: () => (/* binding */ isJdkCacheEnabled)
/* harmony export */ });
/* unused harmony exports getVersionFromToolcachePath, extractJdkFile, cacheJdkDir, getJavaVersionFromReleaseFile, getDownloadArchiveExtension, isVersionSatisfies, getToolcachePath, isGhes, getVersionFromFileContent, convertVersionToSemver, getArtifactFingerprint, getGitHubToken, getGitHubHttpHeaders, MAX_PAGINATION_PAGES, getNextPageUrlFromLinkHeader, validatePaginationUrl, renameWinArchive, getLatestMajorVersion */
/* unused harmony exports getVersionFromToolcachePath, extractJdkFile, cacheJdkDir, getJavaVersionFromReleaseFile, getDownloadArchiveExtension, isVersionSatisfies, getToolcachePath, isGhes, getVersionFromFileContent, convertVersionToSemver, normalizeJavaVersionToSemver, getArtifactFingerprint, getGitHubToken, getGitHubHttpHeaders, MAX_PAGINATION_PAGES, getNextPageUrlFromLinkHeader, validatePaginationUrl, renameWinArchive, getLatestMajorVersion */
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(857);
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nccwpck_require__.n(os__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(6928);
@@ -31247,6 +31515,20 @@ function convertVersionToSemver(version) {
}
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.
*/
function normalizeJavaVersionToSemver(version) {
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
* headers of a HEAD request. A vendor's `/latest/` URL never changes, so this
@@ -35832,13 +36114,16 @@ async function removeGpgHome(gpgHome) {
if (!external_fs_.existsSync(resolvedGpgHome)) {
return;
}
await stopGpgAgent(resolvedGpgHome);
await lib_io/* rmRF */.Yz(resolvedGpgHome);
}
async function stopGpgAgent(gpgHome) {
try {
await lib_exec/* exec */.m('gpgconf', ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true });
await lib_exec/* exec */.m('gpgconf', ['--homedir', toGpgPath(gpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true });
}
catch {
// gpgconf may be unavailable, but directory removal must still be attempted.
}
await lib_io/* rmRF */.Yz(resolvedGpgHome);
}
async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) {
const signaturePath = await tc.downloadTool(signatureUrl);
@@ -35884,6 +36169,7 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten
], options);
}
finally {
await stopGpgAgent(gpgHome);
await io.rmRF(signaturePath);
await io.rmRF(gpgHome);
}

View File

@@ -263,13 +263,16 @@ async function removeGpgHome(gpgHome) {
if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) {
return;
}
await stopGpgAgent(resolvedGpgHome);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome);
}
async function stopGpgAgent(gpgHome) {
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 {
// 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) {
const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl);
@@ -315,6 +318,7 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten
], options);
}
finally {
await stopGpgAgent(gpgHome);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome);
}

View File

@@ -730,12 +730,9 @@ class JavaBase {
}
// Java uses a versioning scheme (JEP 322) that can contain more numeric
// fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such
// exact versions to SemVer build notation ('18.0.1+1') so they are
// accepted. Ranges and versions that already carry build metadata are
// left untouched.
if (/^\d+(\.\d+){3,}$/.test(version)) {
version = (0,util/* convertVersionToSemver */.ZY)(version);
}
// exact versions to SemVer build notation ('18.0.1+1', or '26.0.2+1.1'
// for '26.0.2.1+1') so they are accepted. Ranges are left untouched.
version = (0,util/* normalizeJavaVersionToSemver */.zZ)(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`);
}

View File

@@ -109,7 +109,7 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
const formattedVersion = this.stable
? item.version_data.semver
: item.version_data.semver.replace('-beta+', '+');
return {
const release = {
version: formattedVersion,
url: item.binaries[0].package.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
}
};
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
.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) => {
return -semver_default().compareBuild(a.version, b.version);
});
const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
if (!resolvedFullVersion) {
const availableVersionStrings = availableVersionsWithBinaries.map(item => item.version);
const availableVersionStrings = availableVersionsWithBinaries.map(({ release }) => release.version);
throw this.createVersionNotFoundError(version, availableVersionStrings);
}
return resolvedFullVersion;
@@ -279,6 +293,20 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
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;
}
/***/ }),
@@ -375,13 +403,16 @@ async function removeGpgHome(gpgHome) {
if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) {
return;
}
await stopGpgAgent(resolvedGpgHome);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome);
}
async function stopGpgAgent(gpgHome) {
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 {
// 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) {
const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl);
@@ -427,6 +458,7 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten
], options);
}
finally {
await stopGpgAgent(gpgHome);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome);
}

View File

@@ -128,7 +128,8 @@ class LibericaDistributions extends _base_installer_js__WEBPACK_IMPORTED_MODULE_
}
}
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('.');
if (buildVersion != 0) {
return `${mainVersion}+${buildVersion}`;

169
dist/setup/81.index.js vendored
View File

@@ -7,6 +7,7 @@ export const modules = {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ addMavenServerRepositoryOrigins: () => (/* binding */ addMavenServerRepositoryOrigins),
/* harmony export */ configureAuthentication: () => (/* binding */ configureAuthentication),
/* harmony export */ createAuthenticationSettings: () => (/* binding */ createAuthenticationSettings),
/* harmony export */ generate: () => (/* binding */ generate),
@@ -37,6 +38,7 @@ __webpack_require__.r(__webpack_exports__);
const MAVEN_REPOSITORY_ORIGIN = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#@]+\/?$/;
async function configureAuthentication() {
const servers = getMavenServerSettings();
const repositorySettings = getMavenRepositorySettings();
@@ -74,16 +76,16 @@ function getInputWithDeprecatedAlias(inputName, deprecatedInputName, defaultValu
// only exported for testing purposes
function getMavenServerSettings() {
const entries = _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getMultilineInput */ .q3(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_SERVER_CREDENTIALS */ .MM);
if (entries.some(entry => entry.trim())) {
return parseMavenServerCredentials(entries);
}
return [
{
id: _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getInput */ .V4(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_ID */ .fd),
usernameEnvVar: getInputWithDeprecatedAlias(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_USERNAME_ENV_VAR */ .sc, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_USERNAME_DEPRECATED */ .sp, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_DEFAULT_SERVER_USERNAME */ .Wj),
passwordEnvVar: getInputWithDeprecatedAlias(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_PASSWORD_ENV_VAR */ .r4, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_PASSWORD_DEPRECATED */ .Vt, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_DEFAULT_SERVER_PASSWORD */ .xp)
}
];
const servers = entries.some(entry => entry.trim())
? parseMavenServerCredentials(entries)
: [
{
id: _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getInput */ .V4(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_ID */ .fd),
usernameEnvVar: getInputWithDeprecatedAlias(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_USERNAME_ENV_VAR */ .sc, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_USERNAME_DEPRECATED */ .sp, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_DEFAULT_SERVER_USERNAME */ .Wj),
passwordEnvVar: getInputWithDeprecatedAlias(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_PASSWORD_ENV_VAR */ .r4, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_PASSWORD_DEPRECATED */ .Vt, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_DEFAULT_SERVER_PASSWORD */ .xp)
}
];
return addMavenServerRepositoryOrigins(servers, _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getMultilineInput */ .q3(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_SERVER_REPOSITORY_ORIGINS */ .Pt));
}
// only exported for testing purposes
function parseMavenServerCredentials(entries) {
@@ -110,20 +112,78 @@ function parseMavenServerCredentials(entries) {
return servers;
}
// only exported for testing purposes
function addMavenServerRepositoryOrigins(servers, entries) {
const serverIds = new Set(servers.map(server => server.id));
const originsByServer = new Map();
entries.forEach((entry, index) => {
if (!entry.trim()) {
return;
}
const separator = entry.indexOf(':');
if (separator <= 0 || separator === entry.length - 1) {
throw new Error(`Invalid mvn-server-repository-origins entry at line ${index + 1}. Expected format: server-id:repository-origin`);
}
const id = entry.slice(0, separator).trim();
const value = entry.slice(separator + 1).trim();
if (!id || !value) {
throw new Error(`Invalid mvn-server-repository-origins entry at line ${index + 1}. Server ID and repository origin are required`);
}
if (!serverIds.has(id)) {
throw new Error(`Unknown server-id '${id}' in mvn-server-repository-origins at line ${index + 1}`);
}
let url;
try {
if (!MAVEN_REPOSITORY_ORIGIN.test(value)) {
throw new Error();
}
url = new URL(value);
}
catch {
throw new Error(`Invalid repository origin '${value}' in mvn-server-repository-origins at line ${index + 1}`);
}
if (!url.host ||
url.username ||
url.password ||
(url.pathname !== '' && url.pathname !== '/') ||
url.search ||
url.hash) {
throw new Error(`Invalid repository origin '${value}' in mvn-server-repository-origins at line ${index + 1}`);
}
const explicitPort = /:(\d+)\/?$/.exec(value)?.[1];
const port = explicitPort ? Number.parseInt(explicitPort, 10) : undefined;
const includePort = port !== undefined &&
!((url.protocol === 'http:' && port === 80) ||
(url.protocol === 'https:' && port === 443));
const origin = `${url.protocol}//${url.hostname}${includePort ? `:${port}` : ''}`;
const origins = originsByServer.get(id) || [];
if (!origins.includes(origin)) {
origins.push(origin);
originsByServer.set(id, origins);
}
});
return servers.map(server => {
const repositoryOrigins = originsByServer.get(server.id);
return repositoryOrigins ? { ...server, repositoryOrigins } : server;
});
}
// only exported for testing purposes
function getMavenRepositorySettings() {
const entries = _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getMultilineInput */ .q3(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_REPOSITORIES */ .W2);
if (!entries.some(entry => entry.trim())) {
const pluginEntries = _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getMultilineInput */ .q3(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_PLUGIN_REPOSITORIES */ .gR);
if (!entries.some(entry => entry.trim()) &&
!pluginEntries.some(entry => entry.trim())) {
return undefined;
}
const includeCentral = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getBooleanInput */ .Vt)(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL */ .H5, true);
return {
repositories: parseMavenRepositories(entries, includeCentral),
pluginRepositories: parseMavenRepositories(pluginEntries, includeCentral, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_PLUGIN_REPOSITORIES */ .gR),
includeCentral,
prioritizeCentral: (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getBooleanInput */ .Vt)(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL */ .OT, true)
};
}
// only exported for testing purposes
function parseMavenRepositories(entries, includeCentral) {
function parseMavenRepositories(entries, includeCentral, inputName = _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_REPOSITORIES */ .W2) {
const repositories = [];
const repositoryIds = new Set();
entries.forEach((entry, index) => {
@@ -133,7 +193,7 @@ function parseMavenRepositories(entries, includeCentral) {
const firstSeparator = entry.indexOf(':');
const lastSeparator = entry.lastIndexOf(':');
if (firstSeparator <= 0 || lastSeparator <= firstSeparator) {
throw new Error(`Invalid mvn-repositories entry at line ${index + 1}. Expected format: repository-id:repository-url:snapshots-enabled`);
throw new Error(`Invalid ${inputName} entry at line ${index + 1}. Expected format: repository-id:repository-url:snapshots-enabled`);
}
const id = entry.slice(0, firstSeparator).trim();
const url = entry.slice(firstSeparator + 1, lastSeparator).trim();
@@ -142,13 +202,13 @@ function parseMavenRepositories(entries, includeCentral) {
.trim()
.toLowerCase();
if (!id || !url || !snapshotsValue) {
throw new Error(`Invalid mvn-repositories entry at line ${index + 1}. repository-id, repository URL, and snapshots-enabled are required`);
throw new Error(`Invalid ${inputName} entry at line ${index + 1}. repository-id, repository URL, and snapshots-enabled are required`);
}
if (snapshotsValue !== 'true' && snapshotsValue !== 'false') {
throw new Error(`Invalid snapshots-enabled value '${snapshotsValue}' in mvn-repositories entry at line ${index + 1}. Expected true or false`);
throw new Error(`Invalid snapshots-enabled value '${snapshotsValue}' in ${inputName} entry at line ${index + 1}. Expected true or false`);
}
if (repositoryIds.has(id)) {
throw new Error(`Duplicate repository-id '${id}' in mvn-repositories input`);
throw new Error(`Duplicate repository-id '${id}' in ${inputName} input`);
}
if (includeCentral && id === _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_ID */ .xg) {
throw new Error(`Repository-id '${_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_ID */ .xg}' is reserved when ${_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL */ .H5} is enabled`);
@@ -162,6 +222,37 @@ function parseMavenRepositories(entries, includeCentral) {
});
return repositories;
}
function generateRepositorySection(customRepositories, settings, element) {
if (!customRepositories.length && settings.includeCentral) {
return [];
}
const centralRepository = {
id: _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_ID */ .xg,
url: _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_URL */ .jv,
snapshotsEnabled: false
};
const customCentralConfigured = customRepositories.some(repository => repository.id === _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_ID */ .xg);
const repositories = settings.includeCentral
? settings.prioritizeCentral
? [centralRepository, ...customRepositories]
: [...customRepositories, centralRepository]
: customCentralConfigured
? customRepositories
: [...customRepositories, { ...centralRepository, releasesEnabled: false }];
const container = element === 'repository' ? 'repositories' : 'pluginRepositories';
const lines = [` <${container}>`];
for (const repository of repositories) {
lines.push(` <${element}>`, ` <id>${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(repository.id)}</id>`, ` <url>${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(repository.url)}</url>`, ...(repository.releasesEnabled === undefined
? []
: [
' <releases>',
` <enabled>${repository.releasesEnabled}</enabled>`,
' </releases>'
]), ' <snapshots>', ` <enabled>${repository.snapshotsEnabled}</enabled>`, ' </snapshots>', ` </${element}>`);
}
lines.push(` </${container}>`);
return lines;
}
async function createAuthenticationSettings(servers, settingsDirectory, overwriteSettings, gpgPassphraseEnvVar = undefined, repositorySettings = undefined) {
_actions_core__WEBPACK_IMPORTED_MODULE_1__/* .info */ .pq(`Creating ${_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MVN_SETTINGS_FILE */ .vO} with server-id: ${servers.map(server => server.id).join(', ')}`);
// when an alternate m2 location is specified use only that location (no .m2 directory)
@@ -187,39 +278,21 @@ function generate(servers, gpgPassphraseEnvVar, repositorySettings) {
' <servers>'
];
for (const server of servers) {
lines.push(' <server>', ` <id>${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(server.id)}</id>`, ` <username>${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(`\${env.${server.usernameEnvVar}}`)}</username>`, ` <password>${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(`\${env.${server.passwordEnvVar}}`)}</password>`, ' </server>');
lines.push(' <server>', ` <id>${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(server.id)}</id>`, ` <username>${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(`\${env.${server.usernameEnvVar}}`)}</username>`, ` <password>${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(`\${env.${server.passwordEnvVar}}`)}</password>`);
if (server.repositoryOrigins) {
lines.push(' <repositoryOrigins>');
for (const origin of server.repositoryOrigins) {
lines.push(` <repositoryOrigin>${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(origin)}</repositoryOrigin>`);
}
lines.push(' </repositoryOrigins>');
}
lines.push(' </server>');
}
lines.push(' </servers>');
if (repositorySettings || includeGpgPassphraseProfile) {
lines.push(' <profiles>');
if (repositorySettings) {
const centralRepository = {
id: _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_ID */ .xg,
url: _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_URL */ .jv,
snapshotsEnabled: false
};
const customCentralConfigured = repositorySettings.repositories.some(repository => repository.id === _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_ID */ .xg);
const repositories = repositorySettings.includeCentral
? repositorySettings.prioritizeCentral
? [centralRepository, ...repositorySettings.repositories]
: [...repositorySettings.repositories, centralRepository]
: customCentralConfigured
? repositorySettings.repositories
: [
...repositorySettings.repositories,
{ ...centralRepository, releasesEnabled: false }
];
lines.push(' <profile>', ` <id>${_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_REPOSITORIES_PROFILE_ID */ .hq}</id>`, ' <repositories>');
for (const repository of repositories) {
lines.push(' <repository>', ` <id>${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(repository.id)}</id>`, ` <url>${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(repository.url)}</url>`, ...(repository.releasesEnabled === undefined
? []
: [
' <releases>',
` <enabled>${repository.releasesEnabled}</enabled>`,
' </releases>'
]), ' <snapshots>', ` <enabled>${repository.snapshotsEnabled}</enabled>`, ' </snapshots>', ' </repository>');
}
lines.push(' </repositories>', ' </profile>');
lines.push(' <profile>', ` <id>${_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_REPOSITORIES_PROFILE_ID */ .hq}</id>`, ...generateRepositorySection(repositorySettings.repositories, repositorySettings, 'repository'), ...generateRepositorySection(repositorySettings.pluginRepositories ?? [], repositorySettings, 'pluginRepository'), ' </profile>');
}
if (includeGpgPassphraseProfile) {
lines.push(' <profile>', ` <id>${_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .GPG_PASSPHRASE_PROFILE_ID */ .K$}</id>`, ' <properties>', ` <gpg.passphraseEnvName>${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(gpgPassphraseEnvVar)}</gpg.passphraseEnvName>`, ' </properties>', ' </profile>');
@@ -350,13 +423,16 @@ async function removeGpgHome(gpgHome) {
if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) {
return;
}
await stopGpgAgent(resolvedGpgHome);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome);
}
async function stopGpgAgent(gpgHome) {
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 {
// 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) {
const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl);
@@ -402,6 +478,7 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten
], options);
}
finally {
await stopGpgAgent(gpgHome);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath);
await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome);
}

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=""
@@ -429,7 +522,7 @@ function validateAttributeString(attrStr, options) {
//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 = {};
for (let i = 0; i < matches.length; i++) {
@@ -532,7 +625,6 @@ function getPositionFromMatch(match) {
return match.startIndex + match[1].length;
}
/***/ }),
/***/ 6009:

View File

@@ -52431,6 +52431,24 @@ const EXPANSION_MAX = 100_000;
// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
// characters) so legitimate input is unaffected.
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) {
return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
}
@@ -52450,37 +52468,52 @@ function unescapeBraces(str) {
.replace(escCommaPattern, ',')
.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
* where we have nested braced sections, which should be
* treated as individual members, like {a,{b,c},d}
*/
function parseCommaParts(str) {
if (!str) {
return [''];
}
const parts = [];
const m = balanced('{', '}', str);
if (!m) {
return str.split(',');
// Walk the brace groups iteratively. Recursing on `post` once per group let a
// chain of them exhaust the stack - the parsing-side counterpart to
// 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 = {}) {
if (!str) {
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.
// Anything starting with {} will have the first two bytes preserved
// 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) === '{}') {
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) {
return '{' + str + '}';
@@ -52585,7 +52618,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) {
}
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
// running set of combined prefixes (`acc`). Expanding the tail iteratively -
// 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
// is on the final strings, so it is applied to whichever `combine` produces
// 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 firstGroup = true;
for (;;) {
@@ -52621,7 +52663,8 @@ function expand_(str, max, maxLength, isTop) {
const isOptions = m.body.indexOf(',') >= 0;
if (!isSequence && !isOptions) {
// {a},b}
if (m.post.match(/,(?!,).*\}/)) {
if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) {
rewrites++;
str = m.pre + '{' + m.body + escClose + m.post;
isTop = true;
continue;
@@ -52641,7 +52684,7 @@ function expand_(str, max, maxLength, isTop) {
let n = parseCommaParts(m.body);
if (n.length === 1 && n[0] !== undefined) {
// 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.
/* c8 ignore start */
if (n.length === 1) {
@@ -52667,12 +52710,13 @@ function expand_(str, max, maxLength, isTop) {
values = [];
let valuesLength = 0;
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++) {
const v = expanded[k];
if (dropsEmpties && !v)
continue;
if (values.length >= max || valuesLength + v.length > maxLength) {
if (values.length >= max ||
valuesLength + v.length > maxLength) {
break outer;
}
values.push(v);

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 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 semver__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2088);
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6242);
/* 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);
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6242);
/* harmony import */ var _platform_types_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7444);
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4527);
class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* .JavaBase */ .O {
function compareNumberArrays(a, b) {
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) {
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;
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,
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
};
});
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) => {
// Azul provides two versions: java_version and distro_version
// we should sort by both fields by descending
return (-semver__WEBPACK_IMPORTED_MODULE_3___default().compareBuild(a.version, b.version) ||
-semver__WEBPACK_IMPORTED_MODULE_3___default().compareBuild(a.zuluVersion, b.zuluVersion));
// Compare numerically rather than via semver build metadata: Azul
// 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 (-compareNumberArrays(a.javaVersion, b.javaVersion) ||
b.buildNumber - a.buildNumber ||
-compareNumberArrays(a.distroVersion, b.distroVersion));
})
.map((item) => ({
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} ...`);
let javaArchivePath = await this.downloadAndVerify(javaRelease);
_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') {
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 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 };
}
async getAvailableVersions() {
const arch = this.getArchitectureOptions();
const [bundleType, features] = this.packageType.split('+');
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 crac = features?.includes('crac') ?? false;
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
// packages, so target the libc the runner actually has. A glibc JDK
// 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:
return process.platform;
}

678
dist/setup/index.js vendored
View File

@@ -6009,11 +6009,77 @@ class Request {
}
}
onUpgrade (statusCode, headers, socket) {
/**
* @param {number|null} statusCode
* @param {Buffer[]|null} headers
* @param {import('node:stream').Duplex} socket
* @param {string} [statusText]
*/
onUpgrade (statusCode, headers, socket, statusText = '') {
this.onFinally()
assert(!this.aborted)
assert(!this.completed)
return this[kHandler].onUpgrade(statusCode, headers, socket)
if (statusCode !== null) {
this.#publishUpgradeHeaders(statusCode, headers, statusText)
}
const result = this[kHandler].onUpgrade(statusCode, headers, socket)
if (!this.aborted) {
this.completed = true
if (statusCode !== null) {
this.#publishUpgradeTrailers()
}
}
return result
}
/**
* @param {number} statusCode
* @param {import('node:http2').IncomingHttpHeaders} headers
* @param {(headers: import('node:http2').IncomingHttpHeaders) => Buffer[]} parseHeaders
* @param {string} [statusText]
*/
onUpgradeResponse (statusCode, headers, parseHeaders, statusText = '') {
assert(!this.aborted)
assert(this.completed)
if (channels.headers.hasSubscribers) {
this.#publishUpgradeHeaders(statusCode, parseHeaders(headers), statusText)
}
this.#publishUpgradeTrailers()
}
/**
* @param {Error} error
*/
onUpgradeError (error) {
assert(!this.aborted)
assert(this.completed)
if (channels.error.hasSubscribers) {
channels.error.publish({ request: this, error })
}
}
/**
* @param {number} statusCode
* @param {Buffer[]} headers
* @param {string} statusText
*/
#publishUpgradeHeaders (statusCode, headers, statusText) {
if (channels.headers.hasSubscribers) {
channels.headers.publish({ request: this, response: { statusCode, headers, statusText } })
}
}
#publishUpgradeTrailers () {
if (channels.trailers.hasSubscribers) {
channels.trailers.publish({ request: this, trailers: [] })
}
}
onComplete (trailers) {
@@ -7912,7 +7978,7 @@ class Parser {
}
onUpgrade (head) {
const { upgrade, client, socket, headers, statusCode } = this
const { upgrade, client, socket, headers, statusCode, statusText } = this
assert(upgrade)
assert(client[kSocket] === socket)
@@ -7947,9 +8013,10 @@ class Parser {
client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade'))
try {
request.onUpgrade(statusCode, headers, socket)
} catch (err) {
util.destroy(socket, err)
request.onUpgrade(statusCode, headers, socket, statusText)
} catch (error) {
util.errorRequest(client, request, error)
util.destroy(socket, error)
}
client[kResume]()
@@ -8356,7 +8423,7 @@ async function connectH1 (client, socket) {
function clearIdleSocketValidation (socket) {
if (socket[kIdleSocketValidationTimeout]) {
clearTimeout(socket[kIdleSocketValidationTimeout])
clearImmediate(socket[kIdleSocketValidationTimeout])
socket[kIdleSocketValidationTimeout] = null
}
@@ -8365,15 +8432,23 @@ function clearIdleSocketValidation (socket) {
function scheduleIdleSocketValidation (client, socket) {
socket[kIdleSocketValidation] = 1
socket[kIdleSocketValidationTimeout] = setTimeout(() => {
// Yield to the check phase (after poll) so unsolicited bytes / FIN / RST
// already pending on this idle keep-alive socket are processed before the
// next request is written (GHSA-35p6-xmwp-9g52).
//
// setTimeout(0) pays Node's ~1ms timer floor on every sequential reuse
// (#5493). setImmediate avoids that, but an *unref'd* Immediate lets poll
// block for ~500ms when the event loop is otherwise idle (#5600 / #5606).
// A ref'd Immediate both keeps the pending request alive and makes poll
// return immediately — the hybrid those issues asked for.
socket[kIdleSocketValidationTimeout] = setImmediate(() => {
socket[kIdleSocketValidationTimeout] = null
socket[kIdleSocketValidation] = 2
if (client[kSocket] === socket && !socket.destroyed) {
client[kResume]()
}
}, 0)
socket[kIdleSocketValidationTimeout].unref?.()
})
}
/**
@@ -8522,12 +8597,22 @@ function writeH1 (client, request) {
const socket = client[kSocket]
clearIdleSocketValidation(socket)
const abort = (err) => {
if (request.aborted || request.completed) {
/**
* @param {Error} [error]
*/
const abort = (error) => {
if (request.aborted) {
return
}
util.errorRequest(client, request, err || new RequestAbortedError())
if (request.completed) {
if (request.upgrade || request.method === 'CONNECT') {
util.destroy(socket, new InformationalError('aborted'))
}
return
}
util.errorRequest(client, request, error || new RequestAbortedError())
util.destroy(body)
util.destroy(socket, new InformationalError('aborted'))
@@ -8984,6 +9069,7 @@ module.exports = connectH1
const assert = __nccwpck_require__(4589)
const { errorMonitor } = __nccwpck_require__(8474)
const { pipeline } = __nccwpck_require__(7075)
const util = __nccwpck_require__(3440)
const {
@@ -9060,6 +9146,15 @@ function parseH2Headers (headers) {
return result
}
/**
* @param {import('node:http2').IncomingHttpHeaders} headers
* @returns {Buffer[]}
*/
function parseH2ResponseHeaders (headers) {
const { [HTTP2_HEADER_STATUS]: _statusCode, ...realHeaders } = headers
return parseH2Headers(realHeaders)
}
async function connectH2 (client, socket) {
client[kSocket] = socket
@@ -9280,22 +9375,32 @@ function writeH2 (client, request) {
headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
headers[HTTP2_HEADER_METHOD] = method
const abort = (err) => {
if (request.aborted || request.completed) {
/**
* @param {Error} [error]
*/
const abort = (error) => {
if (request.aborted) {
return
}
err = err || new RequestAbortedError()
if (request.completed) {
if (method === 'CONNECT' && stream != null) {
util.destroy(stream, error || new RequestAbortedError())
}
return
}
util.errorRequest(client, request, err)
error = error || new RequestAbortedError()
util.errorRequest(client, request, error)
if (stream != null) {
util.destroy(stream, err)
util.destroy(stream, error)
}
// We do not destroy the socket as we can continue using the session
// the stream get's destroyed and the session remains to create new streams
util.destroy(body, err)
util.destroy(body, error)
client[kQueue][client[kRunningIdx]++] = null
client[kResume]()
}
@@ -9314,25 +9419,57 @@ function writeH2 (client, request) {
if (method === 'CONNECT') {
session.ref()
// We are already connected, streams are pending, first request
// will create a new stream. We trigger a request to create the stream and wait until
// `ready` event is triggered
// We disabled endStream to allow the user to write to the stream
stream = session.request(headers, { endStream: false, signal })
let upgradeResponseFinished = false
if (stream.id && !stream.pending) {
request.onUpgrade(null, null, stream)
++session[kOpenStreams]
client[kQueue][client[kRunningIdx]++] = null
} else {
stream.once('ready', () => {
request.onUpgrade(null, null, stream)
++session[kOpenStreams]
client[kQueue][client[kRunningIdx]++] = null
})
/**
* @param {import('node:http2').IncomingHttpHeaders} headers
*/
const onResponse = (headers) => {
upgradeResponseFinished = true
stream.off(errorMonitor, onUpgradeError)
request.onUpgradeResponse(Number(headers[HTTP2_HEADER_STATUS]), headers, parseH2ResponseHeaders)
}
/**
* @param {Error} error
*/
const onUpgradeError = (error) => {
upgradeResponseFinished = true
stream.off('response', onResponse)
request.onUpgradeError(error)
}
const onReady = () => {
try {
request.onUpgrade(null, null, stream)
} catch (error) {
stream.off('response', onResponse)
abort(error)
return
}
if (request.aborted) {
return
}
stream.off('error', abort)
stream.once(errorMonitor, onUpgradeError)
client[kQueue][client[kRunningIdx]++] = null
}
stream.once('response', onResponse)
stream.once('error', abort)
++session[kOpenStreams]
onReady()
stream.once('close', () => {
if (!upgradeResponseFinished && request.completed) {
stream.off('response', onResponse)
stream.off(errorMonitor, onUpgradeError)
request.onUpgradeError(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`))
}
session[kOpenStreams] -= 1
if (session[kOpenStreams] === 0) session.unref()
})
@@ -12031,6 +12168,7 @@ class RetryHandler {
this.end = null
this.etag = null
this.resume = null
this.headersSent = false
// Handle possible onConnect duplication
this.handler.onConnect(reason => {
@@ -12043,6 +12181,20 @@ class RetryHandler {
})
}
checkpointResponseEnd (headers, resume) {
if (this.end == null && this.opts.method !== 'HEAD') {
const contentLength = headers['content-length']
this.end = contentLength != null ? Number(contentLength) - 1 : null
assert(
this.end == null || Number.isFinite(this.end),
'invalid content-length'
)
}
this.resume = this.end != null ? resume : null
}
onRequestSent () {
if (this.handler.onRequestSent) {
this.handler.onRequestSent()
@@ -12131,7 +12283,12 @@ class RetryHandler {
this.retryCount += 1
if (statusCode >= 300) {
if (this.retryOpts.statusCodes.includes(statusCode) === false) {
// Only expose a response if no earlier attempt has reached the caller.
// Otherwise abort this attempt so the error settles the existing body
// instead of replacing it with a new response.
if (!this.headersSent && this.retryOpts.statusCodes.includes(statusCode) === false) {
this.headersSent = true
this.checkpointResponseEnd(headers, resume)
return this.handler.onHeaders(
statusCode,
rawHeaders,
@@ -12200,8 +12357,15 @@ class RetryHandler {
const { start, size, end = size - 1 } = contentRange
assert(this.start === start, 'content-range mismatch')
assert(this.end == null || this.end === end, 'content-range mismatch')
if (this.start !== start || (this.end != null && this.end !== end)) {
this.abort(
new RequestRetryError('Content-Range mismatch', statusCode, {
headers,
data: { count: this.retryCount }
})
)
return false
}
this.resume = resume
return true
@@ -12213,6 +12377,7 @@ class RetryHandler {
const range = parseRangeHeader(headers['content-range'])
if (range == null) {
this.headersSent = true
return this.handler.onHeaders(
statusCode,
rawHeaders,
@@ -12251,6 +12416,7 @@ class RetryHandler {
)
this.resume = resume
this.headersSent = true
this.etag = headers.etag != null ? headers.etag : null
// Weak etags are not useful for comparison nor cache
@@ -12290,7 +12456,7 @@ class RetryHandler {
}
onError (err) {
if (this.aborted || isDisturbed(this.opts.body)) {
if (this.aborted || isDisturbed(this.opts.body) || (this.headersSent && this.resume == null)) {
return this.handler.onError(err)
}
@@ -16748,6 +16914,49 @@ const COLON = 0x3A
*/
const SPACE = 0x20
const DATA = Buffer.from('data')
const EVENT = Buffer.from('event')
const ID = Buffer.from('id')
const RETRY = Buffer.from('retry')
function isASCIINumberBytes (buffer, start) {
if (start >= buffer.length) {
return false
}
for (let i = start; i < buffer.length; i++) {
if (buffer[i] < 0x30 || buffer[i] > 0x39) {
return false
}
}
return true
}
function isValidLastEventIdBytes (buffer, start) {
for (let i = start; i < buffer.length; i++) {
if (buffer[i] === 0x00) {
return false
}
}
return true
}
function isFieldName (line, length, field) {
if (length !== field.length) {
return false
}
for (let i = 0; i < length; i++) {
if (line[i] !== field[i]) {
return false
}
}
return true
}
/**
* @typedef {object} EventSourceStreamEvent
* @type {object}
@@ -16788,11 +16997,14 @@ class EventSourceStream extends Transform {
eventEndCheck = false
/**
* @type {Buffer}
* @type {Buffer[]}
*/
buffer = null
chunks = []
chunkIndex = 0
pos = 0
lineChunkIndex = 0
linePos = 0
event = {
data: undefined,
@@ -16831,92 +17043,20 @@ class EventSourceStream extends Transform {
return
}
// Cache the chunk in the buffer, as the data might not be complete while
// processing it
// TODO: Investigate if there is a more performant way to handle
// incoming chunks
// see: https://github.com/nodejs/undici/issues/2630
if (this.buffer) {
this.buffer = Buffer.concat([this.buffer, chunk])
} else {
this.buffer = chunk
}
this.chunks.push(chunk)
// Strip leading byte-order-mark if we opened the stream and started
// the processing of the incoming data
if (this.checkBOM) {
switch (this.buffer.length) {
case 1:
// Check if the first byte is the same as the first byte of the BOM
if (this.buffer[0] === BOM[0]) {
// If it is, we need to wait for more data
callback()
return
}
// Set the checkBOM flag to false as we don't need to check for the
// BOM anymore
this.checkBOM = false
// The buffer only contains one byte so we need to wait for more data
callback()
return
case 2:
// Check if the first two bytes are the same as the first two bytes
// of the BOM
if (
this.buffer[0] === BOM[0] &&
this.buffer[1] === BOM[1]
) {
// If it is, we need to wait for more data, because the third byte
// is needed to determine if it is the BOM or not
callback()
return
}
// Set the checkBOM flag to false as we don't need to check for the
// BOM anymore
this.checkBOM = false
break
case 3:
// Check if the first three bytes are the same as the first three
// bytes of the BOM
if (
this.buffer[0] === BOM[0] &&
this.buffer[1] === BOM[1] &&
this.buffer[2] === BOM[2]
) {
// If it is, we can drop the buffered data, as it is only the BOM
this.buffer = Buffer.alloc(0)
// Set the checkBOM flag to false as we don't need to check for the
// BOM anymore
this.checkBOM = false
// Await more data
callback()
return
}
// If it is not the BOM, we can start processing the data
this.checkBOM = false
break
default:
// The buffer is longer than 3 bytes, so we can drop the BOM if it is
// present
if (
this.buffer[0] === BOM[0] &&
this.buffer[1] === BOM[1] &&
this.buffer[2] === BOM[2]
) {
// Remove the BOM from the buffer
this.buffer = this.buffer.subarray(3)
}
// Set the checkBOM flag to false as we don't need to check for the
this.checkBOM = false
break
if (this.handleBOM()) {
callback()
return
}
}
while (this.pos < this.buffer.length) {
while (this.hasCurrentByte()) {
const byte = this.currentByte()
// If the previous line ended with an end-of-line, we need to check
// if the next character is also an end-of-line.
if (this.eventEndCheck) {
@@ -16929,10 +17069,9 @@ class EventSourceStream extends Transform {
if (this.crlfCheck) {
// If the current character is a line feed, we can remove it
// from the buffer and reset the crlfCheck flag
if (this.buffer[this.pos] === LF) {
this.buffer = this.buffer.subarray(this.pos + 1)
this.pos = 0
if (byte === LF) {
this.crlfCheck = false
this.consumeCurrentByte()
// It is possible that the line feed is not the end of the
// event. We need to check if the next character is an
@@ -16948,19 +17087,17 @@ class EventSourceStream extends Transform {
this.crlfCheck = false
}
if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) {
if (byte === LF || byte === CR) {
// If the current character is a carriage return, we need to
// set the crlfCheck flag to true, as we need to check if the
// next character is a line feed so we can remove it from the
// buffer
if (this.buffer[this.pos] === CR) {
if (byte === CR) {
this.crlfCheck = true
}
this.buffer = this.buffer.subarray(this.pos + 1)
this.pos = 0
if (
this.event.data !== undefined || this.event.event || this.event.id || this.event.retry) {
this.consumeCurrentByte()
if (this.hasPendingEvent()) {
this.processEvent(this.event)
}
this.clearEvent()
@@ -16974,22 +17111,18 @@ class EventSourceStream extends Transform {
// If the current character is an end-of-line, we can process the
// line
if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) {
if (byte === LF || byte === CR) {
// If the current character is a carriage return, we need to
// set the crlfCheck flag to true, as we need to check if the
// next character is a line feed
if (this.buffer[this.pos] === CR) {
if (byte === CR) {
this.crlfCheck = true
}
// In any case, we can process the line as we reached an
// end-of-line character
this.parseLine(this.buffer.subarray(0, this.pos), this.event)
// Remove the processed line from the buffer
this.buffer = this.buffer.subarray(this.pos + 1)
// Reset the position as we removed the processed line from the buffer
this.pos = 0
this.parseLine(this.readLine(), this.event)
this.consumeCurrentByte()
// A line was processed and this could be the end of the event. We need
// to check if the next line is empty to determine if the event is
// finished.
@@ -16997,7 +17130,7 @@ class EventSourceStream extends Transform {
continue
}
this.pos++
this.advanceCursor()
}
callback()
@@ -17022,64 +17155,53 @@ class EventSourceStream extends Transform {
return
}
let field = ''
let value = ''
let fieldLength = line.length
let valueStart = line.length
// If the line contains a U+003A COLON character (:)
if (colonPosition !== -1) {
// Collect the characters on the line before the first U+003A COLON
// character (:), and let field be that string.
// TODO: Investigate if there is a more performant way to extract the
// field
// see: https://github.com/nodejs/undici/issues/2630
field = line.subarray(0, colonPosition).toString('utf8')
fieldLength = colonPosition
// Collect the characters on the line after the first U+003A COLON
// character (:), and let value be that string.
// If value starts with a U+0020 SPACE character, remove it from value.
let valueStart = colonPosition + 1
valueStart = colonPosition + 1
if (line[valueStart] === SPACE) {
++valueStart
}
// TODO: Investigate if there is a more performant way to extract the
// value
// see: https://github.com/nodejs/undici/issues/2630
value = line.subarray(valueStart).toString('utf8')
// Otherwise, the string is not empty but does not contain a U+003A COLON
// character (:)
} else {
// Process the field using the steps described below, using the whole
// line as the field name, and the empty string as the field value.
field = line.toString('utf8')
value = ''
}
// Modify the event with the field name and value. The value is also
// decoded as UTF-8
switch (field) {
case 'data':
if (event[field] === undefined) {
event[field] = value
} else {
event[field] += `\n${value}`
}
break
case 'retry':
if (isASCIINumber(value)) {
event[field] = value
}
break
case 'id':
if (isValidLastEventId(value)) {
event[field] = value
}
break
case 'event':
if (value.length > 0) {
event[field] = value
}
break
if (isFieldName(line, fieldLength, DATA)) {
const value = line.toString('utf8', valueStart)
if (event.data === undefined) {
event.data = value
} else {
event.data += `\n${value}`
}
return
}
if (isFieldName(line, fieldLength, RETRY)) {
if (isASCIINumberBytes(line, valueStart)) {
event.retry = line.toString('utf8', valueStart)
}
return
}
if (isFieldName(line, fieldLength, ID)) {
if (isValidLastEventIdBytes(line, valueStart)) {
event.id = line.toString('utf8', valueStart)
}
return
}
if (isFieldName(line, fieldLength, EVENT)) {
const value = line.toString('utf8', valueStart)
if (value.length > 0) {
event.event = value
}
}
}
@@ -17109,13 +17231,152 @@ class EventSourceStream extends Transform {
}
clearEvent () {
this.event = {
data: undefined,
event: undefined,
id: undefined,
retry: undefined
this.event.data = undefined
this.event.event = undefined
this.event.id = undefined
this.event.retry = undefined
}
hasPendingEvent () {
return this.event.data !== undefined ||
this.event.event !== undefined ||
this.event.id !== undefined ||
this.event.retry !== undefined
}
hasCurrentByte () {
return this.chunkIndex < this.chunks.length &&
this.pos < this.chunks[this.chunkIndex].length
}
currentByte () {
return this.chunks[this.chunkIndex][this.pos]
}
consumeCurrentByte () {
this.advanceCursor()
this.syncLineStartToCursor()
}
advanceCursor () {
this.pos++
while (this.chunkIndex < this.chunks.length && this.pos >= this.chunks[this.chunkIndex].length) {
this.chunkIndex++
this.pos = 0
}
}
syncLineStartToCursor () {
this.lineChunkIndex = this.chunkIndex
this.linePos = this.pos
this.dropConsumedChunks()
}
dropConsumedChunks () {
while (this.lineChunkIndex > 0) {
this.chunks.shift()
this.lineChunkIndex--
this.chunkIndex--
}
if (this.chunkIndex === this.chunks.length) {
this.chunks.length = 0
this.chunkIndex = 0
this.pos = 0
this.lineChunkIndex = 0
this.linePos = 0
}
}
readLine () {
if (this.lineChunkIndex === this.chunkIndex) {
return this.chunks[this.chunkIndex].subarray(this.linePos, this.pos)
}
const chunks = []
let length = 0
for (let i = this.lineChunkIndex; i <= this.chunkIndex; i++) {
const chunk = this.chunks[i]
const start = i === this.lineChunkIndex ? this.linePos : 0
const end = i === this.chunkIndex ? this.pos : chunk.length
const slice = chunk.subarray(start, end)
length += slice.length
chunks.push(slice)
}
return Buffer.concat(chunks, length)
}
peekBufferedByte (offset) {
let chunkIndex = this.lineChunkIndex
let pos = this.linePos
while (chunkIndex < this.chunks.length) {
const chunk = this.chunks[chunkIndex]
const remaining = chunk.length - pos
if (offset < remaining) {
return chunk[pos + offset]
}
offset -= remaining
chunkIndex++
pos = 0
}
}
discardLeadingBytes (count) {
while (count > 0 && this.lineChunkIndex < this.chunks.length) {
const chunk = this.chunks[this.lineChunkIndex]
const remaining = chunk.length - this.linePos
if (count < remaining) {
this.linePos += count
count = 0
} else {
count -= remaining
this.lineChunkIndex++
this.linePos = 0
}
}
this.chunkIndex = this.lineChunkIndex
this.pos = this.linePos
this.dropConsumedChunks()
}
handleBOM () {
const first = this.peekBufferedByte(0)
const second = this.peekBufferedByte(1)
const third = this.peekBufferedByte(2)
if (second === undefined) {
if (first === BOM[0]) {
return true
}
this.checkBOM = false
return true
}
if (third === undefined) {
if (first === BOM[0] && second === BOM[1]) {
return true
}
this.checkBOM = false
return false
}
if (first === BOM[0] && second === BOM[1] && third === BOM[2]) {
this.discardLeadingBytes(3)
}
this.checkBOM = false
return !this.hasCurrentByte()
}
}
module.exports = {
@@ -28383,7 +28644,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
// is specified, the server needs to include the same field and one of
// the selected subprotocol values in its response for the connection to
// be established.
if (!requestProtocols.includes(secProtocol)) {
if (requestProtocols === null || !requestProtocols.includes(secProtocol)) {
failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
return
}
@@ -29144,7 +29405,12 @@ class PerMessageDeflate {
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
callback(new MessageSizeExceededError())
// The inflater may still hold buffered input that can emit a late
// zlib error. Remove the data listener, then deterministically stop
// the stream so a subsequent 'error' cannot fire without a listener
// (which would terminate the process as an unhandled error event).
this.#inflate.removeAllListeners()
this.#inflate.destroy()
this.#inflate = null
return
}
@@ -30780,6 +31046,7 @@ module.exports = {
/* harmony export */ OD: () => (/* binding */ INPUT_DEFAULT_GPG_PRIVATE_KEY),
/* harmony export */ OT: () => (/* binding */ INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL),
/* harmony export */ PG: () => (/* binding */ MACOS_JAVA_CONTENT_POSTFIX),
/* harmony export */ Pt: () => (/* binding */ INPUT_MVN_SERVER_REPOSITORY_ORIGINS),
/* harmony export */ QM: () => (/* binding */ INPUT_JAVA_VERSION),
/* harmony export */ RX: () => (/* binding */ INPUT_DEFAULT_GPG_PASSPHRASE),
/* harmony export */ TS: () => (/* binding */ INPUT_OVERWRITE_SETTINGS),
@@ -30794,6 +31061,7 @@ module.exports = {
/* harmony export */ af: () => (/* binding */ INPUT_JAVA_VERSION_FILE),
/* harmony export */ db: () => (/* binding */ INPUT_GPG_PASSPHRASE_ENV_VAR),
/* harmony export */ fd: () => (/* binding */ INPUT_SERVER_ID),
/* harmony export */ gR: () => (/* binding */ INPUT_MVN_PLUGIN_REPOSITORIES),
/* harmony export */ g_: () => (/* binding */ INPUT_DISTRIBUTION),
/* harmony export */ gk: () => (/* binding */ INPUT_CACHE),
/* harmony export */ hq: () => (/* binding */ MAVEN_REPOSITORIES_PROFILE_ID),
@@ -30840,7 +31108,9 @@ const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key';
const SIGNATURE_VERIFICATION_DOCUMENTATION_URL = 'https://github.com/actions/setup-java#download-integrity-and-signatures';
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.`;
const INPUT_MVN_SERVER_CREDENTIALS = 'mvn-server-credentials';
const INPUT_MVN_SERVER_REPOSITORY_ORIGINS = 'mvn-server-repository-origins';
const INPUT_MVN_REPOSITORIES = 'mvn-repositories';
const INPUT_MVN_PLUGIN_REPOSITORIES = 'mvn-plugin-repositories';
const INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL = 'mvn-repositories-include-central';
const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL = 'mvn-repositories-prioritize-central';
const INPUT_SERVER_ID = 'server-id';
@@ -30975,12 +31245,7 @@ function canResolveTemurinJmods(version) {
if (normalizedVersion === 'latest') {
return true;
}
let normalizedRange = normalizedVersion
.replace(/-ea$/, '')
.replace('-ea.', '+');
if (/^\d+(\.\d+){3,}$/.test(normalizedRange)) {
normalizedRange = (0,_util_js__WEBPACK_IMPORTED_MODULE_1__/* .convertVersionToSemver */ .ZY)(normalizedRange);
}
const normalizedRange = (0,_util_js__WEBPACK_IMPORTED_MODULE_1__/* .normalizeJavaVersionToSemver */ .zZ)(normalizedVersion.replace(/-ea$/, '').replace('-ea.', '+'));
if (!semver__WEBPACK_IMPORTED_MODULE_0___default().validRange(normalizedRange)) {
// JavaBase owns general version validation and its targeted error messages.
return true;
@@ -31022,7 +31287,14 @@ function createUnsupportedPackageError(distributionName, packageType, supportedP
const X64_ARM64 = ['x64', 'aarch64'];
const STANDARD_LINUX = ['x64', 'x86', 'aarch64', 'ppc64le', 's390x'];
const STANDARD_LINUX = [
'x64',
'x86',
'aarch64',
'ppc64le',
'riscv64',
's390x'
];
const JAVA_PLATFORM_CAPABILITIES = {
[_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.Temurin]: {
platforms: {
@@ -31164,6 +31436,7 @@ const CANONICAL_ARCHITECTURES = [
'aarch64',
'ppc64le',
'ppc64',
'riscv64',
's390x'
];
const PLATFORM_ALIASES = {
@@ -31303,7 +31576,8 @@ function validateToolchainIds(versions, versionFile, toolchainIds) {
/* harmony export */ rC: () => (/* binding */ getNextPageUrlFromLinkHeader),
/* harmony export */ ri: () => (/* binding */ getLatestMajorVersion),
/* harmony export */ y: () => (/* binding */ isVersionSatisfies),
/* harmony export */ yH: () => (/* binding */ getToolcachePath)
/* harmony export */ yH: () => (/* binding */ getToolcachePath),
/* harmony export */ zZ: () => (/* binding */ normalizeJavaVersionToSemver)
/* harmony export */ });
/* unused harmony exports getVersionFromToolcachePath, isJobStatusSuccess */
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(857);
@@ -31704,6 +31978,20 @@ function convertVersionToSemver(version) {
}
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.
*/
function normalizeJavaVersionToSemver(version) {
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
* headers of a HEAD request. A vendor's `/latest/` URL never changes, so this

View File

@@ -649,7 +649,7 @@ absent from a vendor catalog.
| 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` | |
| `liberica` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | Solaris: `x64`. |
| `liberica-nik` | `x64`, `aarch64` | `x64`, `aarch64` | `x64`, `aarch64` | |
@@ -885,6 +885,11 @@ Use `mvn-server-credentials` to add more than one credential entry to the genera
When this input is set, it replaces the single server configured by `server-id`, `server-username-env-var`, and `server-password-env-var`.
Use `mvn-server-repository-origins` when a Maven server credential must be allowed for an explicit repository origin. Each line has the format `server-id:repository-origin`; a server can have multiple origins.
> [!NOTE]
> `mvn-server-repository-origins` only works with Maven 3.10 and later. Earlier Maven versions ignore the generated `repositoryOrigins` element and log an `Unrecognised tag: 'repositoryOrigins'` warning. GitHub-hosted runners currently ship Maven 3.9.x, so use the [Maven Wrapper](https://maven.apache.org/tools/wrapper/) or install Maven 3.10+ yourself to use this feature.
```yaml
steps:
- uses: actions/checkout@v7
@@ -896,6 +901,9 @@ steps:
mvn-server-credentials: |
releases:RELEASES_USERNAME:RELEASES_PASSWORD
snapshots:SNAPSHOTS_USERNAME:SNAPSHOTS_PASSWORD
mvn-server-repository-origins: |
releases:https://central.sonatype.com
snapshots:https://central.sonatype.com
- name: Publish with Maven
run: mvn deploy
env:
@@ -913,11 +921,17 @@ This configuration produces the following server entries:
<id>releases</id>
<username>${env.RELEASES_USERNAME}</username>
<password>${env.RELEASES_PASSWORD}</password>
<repositoryOrigins>
<repositoryOrigin>https://central.sonatype.com</repositoryOrigin>
</repositoryOrigins>
</server>
<server>
<id>snapshots</id>
<username>${env.SNAPSHOTS_USERNAME}</username>
<password>${env.SNAPSHOTS_PASSWORD}</password>
<repositoryOrigins>
<repositoryOrigin>https://central.sonatype.com</repositoryOrigin>
</repositoryOrigins>
</server>
</servers>
```
@@ -989,14 +1003,92 @@ This configuration adds the following active profile to `settings.xml`:
</activeProfiles>
```
Maven Central is included first by default. Set
`mvn-repositories-prioritize-central: false` to place custom repositories
first, or set `mvn-repositories-include-central: false` to disable Central. The
generated profile overrides the Central repository inherited from Maven's Super
POM with releases and snapshots disabled. When automatic Central inclusion is
off, the ID `central` may instead be declared explicitly in `mvn-repositories`
to replace it with a user-specified repository; otherwise that ID is reserved
to prevent duplicate entries.
Maven Central is included first by default. The Central controls apply to both
dependency and plugin repositories. Set
`mvn-repositories-prioritize-central: false` to place custom repositories first
within each configured list, or set `mvn-repositories-include-central: false` to
disable Central for both types. The generated profile overrides the dependency
and plugin Central repositories inherited from Maven's Super POM with releases
and snapshots disabled, even if only one custom repository list is supplied.
These controls have no effect when neither list contains a nonblank entry.
When automatic Central inclusion is off, the ID `central` may instead be
declared explicitly in `mvn-repositories` or `mvn-plugin-repositories` to replace
it with a user-specified repository for that type. Declaring it in one list
does not enable Central in the other. Otherwise that ID is reserved to prevent
duplicate entries. Other repositories declared in your project's POM are not
disabled by these controls.
### Resolving Maven plugins from custom repositories
Maven resolves build plugins through `<pluginRepositories>`, separately from
dependency `<repositories>`. Use `mvn-plugin-repositories` to configure plugin
resolution with the same multiline `repository-id:repository-url:snapshots-enabled`
format. It can be supplied without `mvn-repositories`; dependency repositories
are not automatically used as plugin repositories.
For example, configure the same private repository for dependencies and plugins
and disable inherited Maven Central for both:
```yaml
steps:
- uses: actions/checkout@v7
- uses: actions/setup-java@v6
with:
distribution: 'temurin'
java-version: '21'
mvn-server-credentials: |
private:PRIVATE_REPOSITORY_USERNAME:PRIVATE_REPOSITORY_TOKEN
mvn-repositories: |
private:https://maven.example.com/releases:false
mvn-plugin-repositories: |
private:https://maven.example.com/releases:false
mvn-repositories-include-central: false
- run: mvn --batch-mode verify
env:
PRIVATE_REPOSITORY_USERNAME: ${{ secrets.PRIVATE_REPOSITORY_USERNAME }}
PRIVATE_REPOSITORY_TOKEN: ${{ secrets.PRIVATE_REPOSITORY_TOKEN }}
```
The matching ID `private` selects the same server credentials for both types.
IDs must be unique within each list, but can be shared across the two lists.
Public repositories do not require a matching credential entry.
Alongside dependency repositories, the active `setup-java-repositories` profile
contains:
```xml
<pluginRepositories>
<pluginRepository>
<id>private</id>
<url>https://maven.example.com/releases</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
```
Omitting a Central override would leave the Super POM's plugin repository
enabled. In particular, `mvn-repositories-include-central: false` now disables
inherited plugin Central even for existing workflows that only configure
`mvn-repositories`. To download plugins in such workflows, configure a plugin
repository or explicitly declare `central` in `mvn-plugin-repositories`.
With Central included, it is placed before custom plugin repositories by
default; `mvn-repositories-prioritize-central: false` places it after them.
All repository generation respects `settings-path` and `overwrite-settings`;
an existing settings file is left untouched when `overwrite-settings: false`.
### GPG

1629
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -49,27 +49,27 @@
"@actions/http-client": "^4.0.1",
"@actions/io": "^3.0.2",
"@actions/tool-cache": "^4.0.0",
"fast-xml-parser": "^5.11.0",
"fast-xml-parser": "^5.11.1",
"semver": "^7.8.5"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@jest/globals": "^30.4.1",
"@types/node": "^26.2.0",
"@jest/globals": "^30.5.2",
"@types/node": "^26.6.2",
"@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",
"@vercel/ncc": "^0.45.0",
"eslint": "^10.8.1",
"eslint": "^10.11.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-jest": "^29.16.1",
"eslint-plugin-jest": "^29.16.6",
"eslint-plugin-n": "^18.3.0",
"globals": "^17.11.0",
"globals": "^17.12.0",
"husky": "^9.1.7",
"jest": "^30.4.2",
"lint-staged": "^17.3.0",
"prettier": "^3.9.5",
"ts-jest": "^29.4.11",
"jest": "^30.5.2",
"lint-staged": "^17.5.1",
"prettier": "^3.9.9",
"ts-jest": "^29.4.13",
"typescript": "^6.0.3"
},
"bugs": {

View File

@@ -13,6 +13,7 @@ export interface MavenServerCredentials {
id: string;
usernameEnvVar: string;
passwordEnvVar: string;
repositoryOrigins?: string[];
}
export interface MavenRepository {
@@ -24,10 +25,13 @@ export interface MavenRepository {
export interface MavenRepositorySettings {
repositories: MavenRepository[];
pluginRepositories?: MavenRepository[];
includeCentral: boolean;
prioritizeCentral: boolean;
}
const MAVEN_REPOSITORY_ORIGIN = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#@]+\/?$/;
export async function configureAuthentication() {
const servers = getMavenServerSettings();
const repositorySettings = getMavenRepositorySettings();
@@ -95,25 +99,28 @@ export function getMavenServerSettings(): MavenServerCredentials[] {
constants.INPUT_MVN_SERVER_CREDENTIALS
);
if (entries.some(entry => entry.trim())) {
return parseMavenServerCredentials(entries);
}
const servers = entries.some(entry => entry.trim())
? parseMavenServerCredentials(entries)
: [
{
id: core.getInput(constants.INPUT_SERVER_ID),
usernameEnvVar: getInputWithDeprecatedAlias(
constants.INPUT_SERVER_USERNAME_ENV_VAR,
constants.INPUT_SERVER_USERNAME_DEPRECATED,
constants.INPUT_DEFAULT_SERVER_USERNAME
),
passwordEnvVar: getInputWithDeprecatedAlias(
constants.INPUT_SERVER_PASSWORD_ENV_VAR,
constants.INPUT_SERVER_PASSWORD_DEPRECATED,
constants.INPUT_DEFAULT_SERVER_PASSWORD
)
}
];
return [
{
id: core.getInput(constants.INPUT_SERVER_ID),
usernameEnvVar: getInputWithDeprecatedAlias(
constants.INPUT_SERVER_USERNAME_ENV_VAR,
constants.INPUT_SERVER_USERNAME_DEPRECATED,
constants.INPUT_DEFAULT_SERVER_USERNAME
),
passwordEnvVar: getInputWithDeprecatedAlias(
constants.INPUT_SERVER_PASSWORD_ENV_VAR,
constants.INPUT_SERVER_PASSWORD_DEPRECATED,
constants.INPUT_DEFAULT_SERVER_PASSWORD
)
}
];
return addMavenServerRepositoryOrigins(
servers,
core.getMultilineInput(constants.INPUT_MVN_SERVER_REPOSITORY_ORIGINS)
);
}
// only exported for testing purposes
@@ -156,11 +163,96 @@ export function parseMavenServerCredentials(
return servers;
}
// only exported for testing purposes
export function addMavenServerRepositoryOrigins(
servers: MavenServerCredentials[],
entries: string[]
): MavenServerCredentials[] {
const serverIds = new Set(servers.map(server => server.id));
const originsByServer = new Map<string, string[]>();
entries.forEach((entry, index) => {
if (!entry.trim()) {
return;
}
const separator = entry.indexOf(':');
if (separator <= 0 || separator === entry.length - 1) {
throw new Error(
`Invalid mvn-server-repository-origins entry at line ${index + 1}. Expected format: server-id:repository-origin`
);
}
const id = entry.slice(0, separator).trim();
const value = entry.slice(separator + 1).trim();
if (!id || !value) {
throw new Error(
`Invalid mvn-server-repository-origins entry at line ${index + 1}. Server ID and repository origin are required`
);
}
if (!serverIds.has(id)) {
throw new Error(
`Unknown server-id '${id}' in mvn-server-repository-origins at line ${index + 1}`
);
}
let url: URL;
try {
if (!MAVEN_REPOSITORY_ORIGIN.test(value)) {
throw new Error();
}
url = new URL(value);
} catch {
throw new Error(
`Invalid repository origin '${value}' in mvn-server-repository-origins at line ${index + 1}`
);
}
if (
!url.host ||
url.username ||
url.password ||
(url.pathname !== '' && url.pathname !== '/') ||
url.search ||
url.hash
) {
throw new Error(
`Invalid repository origin '${value}' in mvn-server-repository-origins at line ${index + 1}`
);
}
const explicitPort = /:(\d+)\/?$/.exec(value)?.[1];
const port = explicitPort ? Number.parseInt(explicitPort, 10) : undefined;
const includePort =
port !== undefined &&
!(
(url.protocol === 'http:' && port === 80) ||
(url.protocol === 'https:' && port === 443)
);
const origin = `${url.protocol}//${url.hostname}${includePort ? `:${port}` : ''}`;
const origins = originsByServer.get(id) || [];
if (!origins.includes(origin)) {
origins.push(origin);
originsByServer.set(id, origins);
}
});
return servers.map(server => {
const repositoryOrigins = originsByServer.get(server.id);
return repositoryOrigins ? {...server, repositoryOrigins} : server;
});
}
// only exported for testing purposes
export function getMavenRepositorySettings():
MavenRepositorySettings | undefined {
const entries = core.getMultilineInput(constants.INPUT_MVN_REPOSITORIES);
if (!entries.some(entry => entry.trim())) {
const pluginEntries = core.getMultilineInput(
constants.INPUT_MVN_PLUGIN_REPOSITORIES
);
if (
!entries.some(entry => entry.trim()) &&
!pluginEntries.some(entry => entry.trim())
) {
return undefined;
}
@@ -170,6 +262,11 @@ export function getMavenRepositorySettings():
);
return {
repositories: parseMavenRepositories(entries, includeCentral),
pluginRepositories: parseMavenRepositories(
pluginEntries,
includeCentral,
constants.INPUT_MVN_PLUGIN_REPOSITORIES
),
includeCentral,
prioritizeCentral: getBooleanInput(
constants.INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL,
@@ -181,7 +278,8 @@ export function getMavenRepositorySettings():
// only exported for testing purposes
export function parseMavenRepositories(
entries: string[],
includeCentral: boolean
includeCentral: boolean,
inputName = constants.INPUT_MVN_REPOSITORIES
): MavenRepository[] {
const repositories: MavenRepository[] = [];
const repositoryIds = new Set<string>();
@@ -195,7 +293,7 @@ export function parseMavenRepositories(
const lastSeparator = entry.lastIndexOf(':');
if (firstSeparator <= 0 || lastSeparator <= firstSeparator) {
throw new Error(
`Invalid mvn-repositories entry at line ${index + 1}. Expected format: repository-id:repository-url:snapshots-enabled`
`Invalid ${inputName} entry at line ${index + 1}. Expected format: repository-id:repository-url:snapshots-enabled`
);
}
@@ -207,18 +305,16 @@ export function parseMavenRepositories(
.toLowerCase();
if (!id || !url || !snapshotsValue) {
throw new Error(
`Invalid mvn-repositories entry at line ${index + 1}. repository-id, repository URL, and snapshots-enabled are required`
`Invalid ${inputName} entry at line ${index + 1}. repository-id, repository URL, and snapshots-enabled are required`
);
}
if (snapshotsValue !== 'true' && snapshotsValue !== 'false') {
throw new Error(
`Invalid snapshots-enabled value '${snapshotsValue}' in mvn-repositories entry at line ${index + 1}. Expected true or false`
`Invalid snapshots-enabled value '${snapshotsValue}' in ${inputName} entry at line ${index + 1}. Expected true or false`
);
}
if (repositoryIds.has(id)) {
throw new Error(
`Duplicate repository-id '${id}' in mvn-repositories input`
);
throw new Error(`Duplicate repository-id '${id}' in ${inputName} input`);
}
if (includeCentral && id === constants.MAVEN_CENTRAL_REPOSITORY_ID) {
throw new Error(
@@ -237,6 +333,56 @@ export function parseMavenRepositories(
return repositories;
}
function generateRepositorySection(
customRepositories: MavenRepository[],
settings: MavenRepositorySettings,
element: 'repository' | 'pluginRepository'
): string[] {
if (!customRepositories.length && settings.includeCentral) {
return [];
}
const centralRepository: MavenRepository = {
id: constants.MAVEN_CENTRAL_REPOSITORY_ID,
url: constants.MAVEN_CENTRAL_REPOSITORY_URL,
snapshotsEnabled: false
};
const customCentralConfigured = customRepositories.some(
repository => repository.id === constants.MAVEN_CENTRAL_REPOSITORY_ID
);
const repositories = settings.includeCentral
? settings.prioritizeCentral
? [centralRepository, ...customRepositories]
: [...customRepositories, centralRepository]
: customCentralConfigured
? customRepositories
: [...customRepositories, {...centralRepository, releasesEnabled: false}];
const container =
element === 'repository' ? 'repositories' : 'pluginRepositories';
const lines = [` <${container}>`];
for (const repository of repositories) {
lines.push(
` <${element}>`,
` <id>${escapeXmlText(repository.id)}</id>`,
` <url>${escapeXmlText(repository.url)}</url>`,
...(repository.releasesEnabled === undefined
? []
: [
' <releases>',
` <enabled>${repository.releasesEnabled}</enabled>`,
' </releases>'
]),
' <snapshots>',
` <enabled>${repository.snapshotsEnabled}</enabled>`,
' </snapshots>',
` </${element}>`
);
}
lines.push(` </${container}>`);
return lines;
}
export async function createAuthenticationSettings(
servers: MavenServerCredentials[],
settingsDirectory: string,
@@ -286,58 +432,39 @@ export function generate(
' <server>',
` <id>${escapeXmlText(server.id)}</id>`,
` <username>${escapeXmlText(`\${env.${server.usernameEnvVar}}`)}</username>`,
` <password>${escapeXmlText(`\${env.${server.passwordEnvVar}}`)}</password>`,
' </server>'
` <password>${escapeXmlText(`\${env.${server.passwordEnvVar}}`)}</password>`
);
if (server.repositoryOrigins) {
lines.push(' <repositoryOrigins>');
for (const origin of server.repositoryOrigins) {
lines.push(
` <repositoryOrigin>${escapeXmlText(origin)}</repositoryOrigin>`
);
}
lines.push(' </repositoryOrigins>');
}
lines.push(' </server>');
}
lines.push(' </servers>');
if (repositorySettings || includeGpgPassphraseProfile) {
lines.push(' <profiles>');
if (repositorySettings) {
const centralRepository: MavenRepository = {
id: constants.MAVEN_CENTRAL_REPOSITORY_ID,
url: constants.MAVEN_CENTRAL_REPOSITORY_URL,
snapshotsEnabled: false
};
const customCentralConfigured = repositorySettings.repositories.some(
repository => repository.id === constants.MAVEN_CENTRAL_REPOSITORY_ID
);
const repositories = repositorySettings.includeCentral
? repositorySettings.prioritizeCentral
? [centralRepository, ...repositorySettings.repositories]
: [...repositorySettings.repositories, centralRepository]
: customCentralConfigured
? repositorySettings.repositories
: [
...repositorySettings.repositories,
{...centralRepository, releasesEnabled: false}
];
lines.push(
' <profile>',
` <id>${constants.MAVEN_REPOSITORIES_PROFILE_ID}</id>`,
' <repositories>'
...generateRepositorySection(
repositorySettings.repositories,
repositorySettings,
'repository'
),
...generateRepositorySection(
repositorySettings.pluginRepositories ?? [],
repositorySettings,
'pluginRepository'
),
' </profile>'
);
for (const repository of repositories) {
lines.push(
' <repository>',
` <id>${escapeXmlText(repository.id)}</id>`,
` <url>${escapeXmlText(repository.url)}</url>`,
...(repository.releasesEnabled === undefined
? []
: [
' <releases>',
` <enabled>${repository.releasesEnabled}</enabled>`,
' </releases>'
]),
' <snapshots>',
` <enabled>${repository.snapshotsEnabled}</enabled>`,
' </snapshots>',
' </repository>'
);
}
lines.push(' </repositories>', ' </profile>');
}
if (includeGpgPassphraseProfile) {
lines.push(

View File

@@ -16,7 +16,10 @@ 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_REPOSITORY_ORIGINS =
'mvn-server-repository-origins';
export const INPUT_MVN_REPOSITORIES = 'mvn-repositories';
export const INPUT_MVN_PLUGIN_REPOSITORIES = 'mvn-plugin-repositories';
export const INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL =
'mvn-repositories-include-central';
export const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL =

View File

@@ -5,9 +5,9 @@ import semver from 'semver';
import path from 'path';
import * as httpm from '@actions/http-client';
import {
convertVersionToSemver,
getToolcachePath,
isVersionSatisfies
isVersionSatisfies,
normalizeJavaVersionToSemver
} from '../util.js';
import type {
ChecksumAlgorithm,
@@ -675,12 +675,9 @@ export abstract class JavaBase {
// Java uses a versioning scheme (JEP 322) that can contain more numeric
// fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such
// exact versions to SemVer build notation ('18.0.1+1') so they are
// accepted. Ranges and versions that already carry build metadata are
// left untouched.
if (/^\d+(\.\d+){3,}$/.test(version)) {
version = convertVersionToSemver(version);
}
// exact versions to SemVer build notation ('18.0.1+1', or '26.0.2+1.1'
// for '26.0.2.1+1') so they are accepted. Ranges are left untouched.
version = normalizeJavaVersionToSemver(version);
if (!semver.validRange(version)) {
throw new Error(

View File

@@ -166,8 +166,8 @@ export class LibericaDistributions extends JavaBase {
}
private convertVersionToSemver(version: LibericaVersion): string {
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(
'.'
);

View File

@@ -1,5 +1,5 @@
import semver from 'semver';
import {convertVersionToSemver} from '../util.js';
import {normalizeJavaVersionToSemver} from '../util.js';
export enum JavaDistribution {
Temurin = 'temurin',
@@ -98,12 +98,9 @@ function canResolveTemurinJmods(version: string): boolean {
return true;
}
let normalizedRange = normalizedVersion
.replace(/-ea$/, '')
.replace('-ea.', '+');
if (/^\d+(\.\d+){3,}$/.test(normalizedRange)) {
normalizedRange = convertVersionToSemver(normalizedRange);
}
const normalizedRange = normalizeJavaVersionToSemver(
normalizedVersion.replace(/-ea$/, '').replace('-ea.', '+')
);
if (!semver.validRange(normalizedRange)) {
// JavaBase owns general version validation and its targeted error messages.
return true;

View File

@@ -5,7 +5,14 @@ import {JavaDistribution} from './package-types.js';
export type JavaPlatform = 'linux' | 'macos' | 'windows' | 'solaris';
export type JavaArchitecture =
'x86' | 'x64' | 'armv7' | 'aarch64' | 'ppc64le' | 'ppc64' | 's390x';
| 'x86'
| 'x64'
| 'armv7'
| 'aarch64'
| 'ppc64le'
| 'ppc64'
| 'riscv64'
| 's390x';
interface VersionedArchitecture {
architecture: JavaArchitecture;
@@ -27,7 +34,14 @@ export type JavaPlatformCapability =
RestrictedPlatformCapability | UnrestrictedPlatformCapability;
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<
JavaDistribution,
@@ -174,6 +188,7 @@ const CANONICAL_ARCHITECTURES: readonly JavaArchitecture[] = [
'aarch64',
'ppc64le',
'ppc64',
'riscv64',
's390x'
];

View File

@@ -70,7 +70,7 @@ export class TemurinDistribution extends JavaBase {
const formattedVersion = this.stable
? item.version_data.semver
: item.version_data.semver.replace('-beta+', '+');
return {
const release: JavaDownloadRelease = {
version: formattedVersion,
url: item.binaries[0].package.link,
signatureUrl: item.binaries[0].package.signature_link,
@@ -79,11 +79,29 @@ export class TemurinDistribution extends JavaBase {
value: item.binaries[0].package.checksum,
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
.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) => {
return -semver.compareBuild(a.version, b.version);
});
@@ -92,7 +110,7 @@ export class TemurinDistribution extends JavaBase {
satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
if (!resolvedFullVersion) {
const availableVersionStrings = availableVersionsWithBinaries.map(
item => item.version
({release}) => release.version
);
throw this.createVersionNotFoundError(version, availableVersionStrings);
}
@@ -309,3 +327,20 @@ export class TemurinDistribution extends JavaBase {
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;
}

View File

@@ -34,6 +34,7 @@ export interface ITemurinAvailableVersions {
minor: number;
openjdk_version: string;
security: string;
patch?: number;
semver: string;
};
}

View File

@@ -2,7 +2,6 @@ import * as core from '@actions/core';
import path from 'path';
import fs from 'fs';
import semver from 'semver';
import {JavaBase} from '../base-installer.js';
import {IZuluPackageDetails, IZuluVersions} from './models.js';
@@ -30,6 +29,17 @@ interface ZuluResolvedRelease {
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 {
constructor(installerOptions: JavaInstallerOptions) {
super('Zulu', installerOptions);
@@ -50,7 +60,9 @@ export class ZuluDistribution extends JavaBase {
return {
version: convertVersionToSemver(javaVersion),
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
};
});
@@ -58,11 +70,14 @@ export class ZuluDistribution extends JavaBase {
const satisfiedVersions = availableVersions
.filter(item => isVersionSatisfies(version, item.version))
.sort((a, b) => {
// Azul provides two versions: java_version and distro_version
// we should sort by both fields by descending
// Compare numerically rather than via semver build metadata: Azul
// 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 (
-semver.compareBuild(a.version, b.version) ||
-semver.compareBuild(a.zuluVersion, b.zuluVersion)
-compareNumberArrays(a.javaVersion, b.javaVersion) ||
b.buildNumber - a.buildNumber ||
-compareNumberArrays(a.distroVersion, b.distroVersion)
);
})
.map((item): ZuluResolvedRelease => ({

View File

@@ -89,17 +89,20 @@ export async function removeGpgHome(gpgHome: string): Promise<void> {
return;
}
await stopGpgAgent(resolvedGpgHome);
await io.rmRF(resolvedGpgHome);
}
async function stopGpgAgent(gpgHome: string): Promise<void> {
try {
await exec.exec(
'gpgconf',
['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'],
['--homedir', toGpgPath(gpgHome), '--kill', 'gpg-agent'],
{silent: true, ignoreReturnCode: true}
);
} catch {
// gpgconf may be unavailable, but directory removal must still be attempted.
}
await io.rmRF(resolvedGpgHome);
}
export async function verifyPackageSignature(
@@ -160,6 +163,7 @@ export async function verifyPackageSignature(
options
);
} finally {
await stopGpgAgent(gpgHome);
await io.rmRF(signaturePath);
await io.rmRF(gpgHome);
}

View File

@@ -510,6 +510,21 @@ export function convertVersionToSemver(version: number[] | string) {
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
* headers of a HEAD request. A vendor's `/latest/` URL never changes, so this