mirror of
https://github.com/actions/setup-java.git
synced 2026-10-15 10:53:06 +01:00
Compare commits
6 Commits
a78ec39f12
...
brborges-m
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbe35f84c5 | ||
|
|
2a69be27b0 | ||
|
|
d0e6e4dbf7 | ||
|
|
b24925bc49 | ||
|
|
47b36480ae | ||
|
|
74920aab4a |
112
.github/workflows/e2e-publishing.yml
vendored
112
.github/workflows/e2e-publishing.yml
vendored
@@ -174,3 +174,115 @@ jobs:
|
|||||||
if (-not (Test-Path $path)) {
|
if (-not (Test-Path $path)) {
|
||||||
throw "settings.xml file is not found in expected location"
|
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"
|
||||||
|
|||||||
2
.licenses/npm/brace-expansion.dep.yml
generated
2
.licenses/npm/brace-expansion.dep.yml
generated
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: brace-expansion
|
name: brace-expansion
|
||||||
version: 5.0.9
|
version: 5.0.12
|
||||||
type: npm
|
type: npm
|
||||||
summary: Brace expansion as known from sh/bash
|
summary: Brace expansion as known from sh/bash
|
||||||
homepage:
|
homepage:
|
||||||
|
|||||||
2
.licenses/npm/fast-xml-parser.dep.yml
generated
2
.licenses/npm/fast-xml-parser.dep.yml
generated
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: fast-xml-parser
|
name: fast-xml-parser
|
||||||
version: 5.11.0
|
version: 5.11.1
|
||||||
type: npm
|
type: npm
|
||||||
summary: Validate XML, Parse XML, Build XML without C/C++ based libraries
|
summary: Validate XML, Parse XML, Build XML without C/C++ based libraries
|
||||||
homepage:
|
homepage:
|
||||||
|
|||||||
2
.licenses/npm/undici.dep.yml
generated
2
.licenses/npm/undici.dep.yml
generated
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: undici
|
name: undici
|
||||||
version: 6.28.0
|
version: 6.29.0
|
||||||
type: npm
|
type: npm
|
||||||
summary: An HTTP/1.1 client, written from scratch for Node.js
|
summary: An HTTP/1.1 client, written from scratch for Node.js
|
||||||
homepage: https://undici.nodejs.org
|
homepage: https://undici.nodejs.org
|
||||||
|
|||||||
26
README.md
26
README.md
@@ -168,9 +168,11 @@ steps:
|
|||||||
| `server-username-env-var` | Environment variable name for Maven repository username. | `GITHUB_ACTOR` |
|
| `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` |
|
| `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-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` | 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-plugin-repositories` | Multiline Maven plugin repositories in the format `repository-id:repository-url:snapshots-enabled`. Can be used independently of `mvn-repositories`. | |
|
||||||
| `mvn-repositories-prioritize-central` | Place Maven Central before custom dependency repositories. Has no effect when Maven Central is excluded. | `true` |
|
| `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` |
|
| `settings-path` | Directory where `settings.xml` is written. | `~/.m2` |
|
||||||
| `overwrite-settings` | Overwrite an existing `settings.xml`. | `true` |
|
| `overwrite-settings` | Overwrite an existing `settings.xml`. | `true` |
|
||||||
| `gpg-private-key` | GPG private key to import into an isolated temporary keyring. | |
|
| `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` |
|
| Major version | `8`, `11`, `17`, `21`, `25` |
|
||||||
| Specific feature or patch version | `11.0`, `11.0.4`, `17.0`, `8.0.282+8` |
|
| Specific feature or patch version | `11.0`, `11.0.4`, `17.0`, `8.0.282+8` |
|
||||||
| JEP 322 multi-field versions | `11.0.9.1`, `18.0.1.1` |
|
| JEP 322 multi-field versions | `11.0.9.1`, `18.0.1.1`, `26.0.2.1+1` |
|
||||||
| Early access | `15-ea`, `15.0.0-ea`, `27-ea` |
|
| Early access | `15-ea`, `15.0.0-ea`, `27-ea` |
|
||||||
| Latest stable GA release | `latest` |
|
| Latest stable GA release | `latest` |
|
||||||
|
|
||||||
@@ -481,10 +483,20 @@ steps:
|
|||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
```
|
```
|
||||||
|
|
||||||
For dependencies hosted outside Maven Central, use `mvn-repositories` to add
|
For dependencies hosted outside Maven Central, use `mvn-repositories`; for build
|
||||||
resolution repositories to an active profile in the generated `settings.xml`.
|
plugins, use `mvn-plugin-repositories`. Either input adds its own repository type
|
||||||
Repository IDs can match `mvn-server-credentials` IDs when authentication is
|
to the same active profile in the generated `settings.xml`. Repository IDs can
|
||||||
required. See [Resolving Maven dependencies from custom repositories](docs/advanced-usage.md#resolving-maven-dependencies-from-custom-repositories).
|
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
|
### GPG signing
|
||||||
|
|
||||||
|
|||||||
@@ -63,12 +63,51 @@ const credentials = (id: string, username: string, password: string) => [
|
|||||||
const repositorySettings = (
|
const repositorySettings = (
|
||||||
repositories: auth.MavenRepository[],
|
repositories: auth.MavenRepository[],
|
||||||
includeCentral = true,
|
includeCentral = true,
|
||||||
prioritizeCentral = true
|
prioritizeCentral = true,
|
||||||
|
pluginRepositories: auth.MavenRepository[] = []
|
||||||
): auth.MavenRepositorySettings => ({
|
): auth.MavenRepositorySettings => ({
|
||||||
repositories,
|
repositories,
|
||||||
|
pluginRepositories,
|
||||||
includeCentral,
|
includeCentral,
|
||||||
prioritizeCentral
|
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', () => {
|
describe('auth tests', () => {
|
||||||
let spyOSHomedir: any;
|
let spyOSHomedir: any;
|
||||||
@@ -318,6 +357,21 @@ describe('auth tests', () => {
|
|||||||
).toEqual(expectedSettings);
|
).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', () => {
|
it('does not add a gpg profile when the passphrase env var is the maven-gpg-plugin default', () => {
|
||||||
const id = 'packages';
|
const id = 'packages';
|
||||||
const username = 'USER';
|
const username = 'USER';
|
||||||
@@ -460,6 +514,87 @@ describe('auth tests', () => {
|
|||||||
).toEqual(['first', 'second', 'central']);
|
).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', () => {
|
it('excludes Central from the generated repository profile when configured', () => {
|
||||||
const parsed = parseXmlObject(
|
const parsed = parseXmlObject(
|
||||||
auth.generate(
|
auth.generate(
|
||||||
@@ -491,6 +626,9 @@ describe('auth tests', () => {
|
|||||||
snapshots: {enabled: 'false'}
|
snapshots: {enabled: 'false'}
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
|
expect(
|
||||||
|
parsed.settings.profiles.profile.pluginRepositories.pluginRepository
|
||||||
|
).toEqual(disabledCentralRepositoryXml);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('combines repository and GPG configuration in shared profile blocks', () => {
|
it('combines repository and GPG configuration in shared profile blocks', () => {
|
||||||
@@ -528,6 +666,9 @@ describe('auth tests', () => {
|
|||||||
snapshots: {enabled: 'false'}
|
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&<>');
|
||||||
|
expect(xml).toContain('x=1&y=<value>');
|
||||||
|
expect(xml).not.toContain('<repositories>');
|
||||||
|
});
|
||||||
|
|
||||||
it('escapes repository values while preserving parsed semantics', () => {
|
it('escapes repository values while preserving parsed semantics', () => {
|
||||||
const repository = {
|
const repository = {
|
||||||
id: `private&<>"'é`,
|
id: `private&<>"'é`,
|
||||||
@@ -586,15 +882,18 @@ describe('auth tests', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('parses Maven repositories while preserving colons in URLs', () => {
|
it.each(repositoryInputs)(
|
||||||
|
'parses %s while preserving colons in URLs',
|
||||||
|
inputName => {
|
||||||
expect(
|
expect(
|
||||||
auth.parseMavenRepositories(
|
auth.parseMavenRepositories(
|
||||||
[
|
[
|
||||||
'',
|
'',
|
||||||
' private : https://repo.example.com:8443/maven : true ',
|
' private : https://repo.example.com:8443/maven : TRUE ',
|
||||||
'releases:https://repo.example.com/releases:false'
|
'releases:https://repo.example.com/releases:false'
|
||||||
],
|
],
|
||||||
true
|
true,
|
||||||
|
inputName
|
||||||
)
|
)
|
||||||
).toEqual([
|
).toEqual([
|
||||||
{
|
{
|
||||||
@@ -608,9 +907,11 @@ describe('auth tests', () => {
|
|||||||
snapshotsEnabled: false
|
snapshotsEnabled: false
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
it.each([
|
it.each(
|
||||||
|
[
|
||||||
{
|
{
|
||||||
entries: ['private'],
|
entries: ['private'],
|
||||||
error:
|
error:
|
||||||
@@ -626,41 +927,66 @@ describe('auth tests', () => {
|
|||||||
error:
|
error:
|
||||||
'Invalid mvn-repositories entry at line 1. repository-id, repository URL, and snapshots-enabled are required'
|
'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'],
|
entries: ['private:https://repo.example.com:sometimes'],
|
||||||
error:
|
error:
|
||||||
"Invalid snapshots-enabled value 'sometimes' in mvn-repositories entry at line 1. Expected true or false"
|
"Invalid snapshots-enabled value 'sometimes' in mvn-repositories entry at line 1. Expected true or false"
|
||||||
}
|
}
|
||||||
])('rejects malformed Maven repositories: $entries', ({entries, error}) => {
|
].flatMap(testCase =>
|
||||||
expect(() => auth.parseMavenRepositories(entries, true)).toThrow(error);
|
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('rejects duplicate Maven repository ids', () => {
|
it.each(repositoryInputs)('rejects duplicate IDs in %s', inputName => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
auth.parseMavenRepositories(
|
auth.parseMavenRepositories(
|
||||||
[
|
[
|
||||||
'private:https://first.example.com:false',
|
'private:https://first.example.com:false',
|
||||||
'private:https://second.example.com:true'
|
'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', () => {
|
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';
|
const central = 'central:https://custom.example.com/maven:false';
|
||||||
|
|
||||||
expect(() => auth.parseMavenRepositories([central], true)).toThrow(
|
expect(() =>
|
||||||
|
auth.parseMavenRepositories([central], true, inputName)
|
||||||
|
).toThrow(
|
||||||
"Repository-id 'central' is reserved when mvn-repositories-include-central is enabled"
|
"Repository-id 'central' is reserved when mvn-repositories-include-central is enabled"
|
||||||
);
|
);
|
||||||
expect(auth.parseMavenRepositories([central], false)).toEqual([
|
expect(auth.parseMavenRepositories([central], false, inputName)).toEqual([
|
||||||
{
|
{
|
||||||
id: 'central',
|
id: 'central',
|
||||||
url: 'https://custom.example.com/maven',
|
url: 'https://custom.example.com/maven',
|
||||||
snapshotsEnabled: false
|
snapshotsEnabled: false
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
it('uses an explicit Central repository instead of adding a disabled override', () => {
|
it('uses an explicit Central repository instead of adding a disabled override', () => {
|
||||||
const parsed = parseXmlObject(
|
const parsed = parseXmlObject(
|
||||||
@@ -706,16 +1032,138 @@ describe('auth tests', () => {
|
|||||||
snapshotsEnabled: true
|
snapshotsEnabled: true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
pluginRepositories: [],
|
||||||
includeCentral: false,
|
includeCentral: false,
|
||||||
prioritizeCentral: false
|
prioritizeCentral: false
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('does not read repository controls when no repositories are configured', () => {
|
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(auth.getMavenRepositorySettings()).toBeUndefined();
|
||||||
expect(core.getInput).not.toHaveBeenCalled();
|
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([
|
it.each([
|
||||||
{
|
{
|
||||||
entries: ['releases:RELEASES_USERNAME'],
|
entries: ['releases:RELEASES_USERNAME'],
|
||||||
@@ -749,16 +1197,23 @@ describe('auth tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('uses multiline Maven server credentials instead of single-server inputs', () => {
|
it('uses multiline Maven server credentials instead of single-server inputs', () => {
|
||||||
(core.getMultilineInput as jest.Mock).mockReturnValue([
|
(core.getMultilineInput as jest.Mock).mockImplementation((name: string) =>
|
||||||
|
name === 'mvn-server-credentials'
|
||||||
|
? [
|
||||||
'releases:RELEASES_USERNAME:RELEASES_PASSWORD',
|
'releases:RELEASES_USERNAME:RELEASES_PASSWORD',
|
||||||
'snapshots:SNAPSHOTS_USERNAME:SNAPSHOTS_PASSWORD'
|
'snapshots:SNAPSHOTS_USERNAME:SNAPSHOTS_PASSWORD'
|
||||||
]);
|
]
|
||||||
|
: name === 'mvn-server-repository-origins'
|
||||||
|
? ['releases:https://repo.example.com']
|
||||||
|
: []
|
||||||
|
);
|
||||||
|
|
||||||
expect(auth.getMavenServerSettings()).toEqual([
|
expect(auth.getMavenServerSettings()).toEqual([
|
||||||
{
|
{
|
||||||
id: 'releases',
|
id: 'releases',
|
||||||
usernameEnvVar: 'RELEASES_USERNAME',
|
usernameEnvVar: 'RELEASES_USERNAME',
|
||||||
passwordEnvVar: 'RELEASES_PASSWORD'
|
passwordEnvVar: 'RELEASES_PASSWORD',
|
||||||
|
repositoryOrigins: ['https://repo.example.com']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'snapshots',
|
id: 'snapshots',
|
||||||
@@ -803,6 +1258,106 @@ describe('auth tests', () => {
|
|||||||
expect(core.warning).toHaveBeenCalledTimes(2);
|
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 {
|
function xmlElementText(xml: string, tagName: string): string {
|
||||||
const match = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`).exec(xml);
|
const match = new RegExp(`<${tagName}>([\\s\\S]*?)</${tagName}>`).exec(xml);
|
||||||
expect(match).not.toBeNull();
|
expect(match).not.toBeNull();
|
||||||
|
|||||||
@@ -1758,6 +1758,8 @@ describe('normalizeVersion', () => {
|
|||||||
['11.0.9.1', {version: '11.0.9+1', stable: true, latest: false}],
|
['11.0.9.1', {version: '11.0.9+1', stable: true, latest: false}],
|
||||||
['12.0.2.1.0', {version: '12.0.2+1.0', stable: true, latest: false}],
|
['12.0.2.1.0', {version: '12.0.2+1.0', stable: true, latest: false}],
|
||||||
['18.0.1.1-ea', {version: '18.0.1+1', stable: false, latest: false}],
|
['18.0.1.1-ea', {version: '18.0.1+1', stable: false, latest: false}],
|
||||||
|
['26.0.2.1+1', {version: '26.0.2+1.1', stable: true, latest: false}],
|
||||||
|
['25.0.4.1+1', {version: '25.0.4+1.1', stable: true, latest: false}],
|
||||||
['latest', {version: 'x', stable: true, latest: true}],
|
['latest', {version: 'x', stable: true, latest: true}],
|
||||||
['LATEST', {version: 'x', stable: true, latest: true}],
|
['LATEST', {version: 'x', stable: true, latest: true}],
|
||||||
[' Latest ', {version: 'x', stable: true, latest: true}]
|
[' Latest ', {version: 'x', stable: true, latest: true}]
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ describe('getJavaDistribution', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it.each(['8', '23.x', '23.0.1.1', '<24'])(
|
it.each(['8', '23.x', '23.0.1.1', '23.0.1.1+1', '<24'])(
|
||||||
"rejects Temurin java-package 'jdk+jmods' for version %s",
|
"rejects Temurin java-package 'jdk+jmods' for version %s",
|
||||||
async version => {
|
async version => {
|
||||||
await expect(
|
await expect(
|
||||||
@@ -96,7 +96,7 @@ describe('getJavaDistribution', () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
it.each(['24', '24.0.1.1', '25-ea', '>=21', 'latest'])(
|
it.each(['24', '24.0.1.1', '25.0.4.1+1', '25-ea', '>=21', 'latest'])(
|
||||||
"accepts Temurin java-package 'jdk+jmods' for version %s",
|
"accepts Temurin java-package 'jdk+jmods' for version %s",
|
||||||
async version => {
|
async version => {
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import type {IncomingMessage} from 'http';
|
|||||||
import {Readable} from 'stream';
|
import {Readable} from 'stream';
|
||||||
|
|
||||||
import manifestData from '../data/jetbrains.json' with {type: 'json'};
|
import manifestData from '../data/jetbrains.json' with {type: 'json'};
|
||||||
import os from 'os';
|
|
||||||
|
|
||||||
// Mock @actions/core before importing source modules that depend on it
|
// Mock @actions/core before importing source modules that depend on it
|
||||||
jest.unstable_mockModule('@actions/core', () => ({
|
jest.unstable_mockModule('@actions/core', () => ({
|
||||||
@@ -81,6 +80,7 @@ describe('getAvailableVersions', () => {
|
|||||||
jest.setTimeout(10_000);
|
jest.setTimeout(10_000);
|
||||||
|
|
||||||
let spyHttpClient: any;
|
let spyHttpClient: any;
|
||||||
|
let spyHttpClientHead: any;
|
||||||
let spyCoreError: any;
|
let spyCoreError: any;
|
||||||
const originalGitHubToken = process.env.GITHUB_TOKEN;
|
const originalGitHubToken = process.env.GITHUB_TOKEN;
|
||||||
|
|
||||||
@@ -93,6 +93,10 @@ describe('getAvailableVersions', () => {
|
|||||||
headers: {},
|
headers: {},
|
||||||
result: []
|
result: []
|
||||||
});
|
});
|
||||||
|
spyHttpClientHead = jest.spyOn(HttpClient.prototype, 'head');
|
||||||
|
spyHttpClientHead.mockResolvedValue({
|
||||||
|
message: {statusCode: 200}
|
||||||
|
} as any);
|
||||||
|
|
||||||
// Mock core.error to suppress error logs
|
// Mock core.error to suppress error logs
|
||||||
spyCoreError = core.error as jest.Mock;
|
spyCoreError = core.error as jest.Mock;
|
||||||
@@ -133,9 +137,7 @@ describe('getAvailableVersions', () => {
|
|||||||
const availableVersions = await distribution['getAvailableVersions']();
|
const availableVersions = await distribution['getAvailableVersions']();
|
||||||
expect(availableVersions).not.toBeNull();
|
expect(availableVersions).not.toBeNull();
|
||||||
|
|
||||||
const length =
|
expect(availableVersions.length).toBe(manifestData.length + 2);
|
||||||
os.platform() === 'win32' ? manifestData.length : manifestData.length + 2;
|
|
||||||
expect(availableVersions.length).toBe(length);
|
|
||||||
}, 10_000);
|
}, 10_000);
|
||||||
|
|
||||||
it('continues a stable request after an all-prerelease page', async () => {
|
it('continues a stable request after an all-prerelease page', async () => {
|
||||||
@@ -358,6 +360,7 @@ describe('getAvailableVersions', () => {
|
|||||||
|
|
||||||
it('retries a GitHub rate limit using Retry-After', async () => {
|
it('retries a GitHub rate limit using Retry-After', async () => {
|
||||||
spyHttpClient.mockRestore();
|
spyHttpClient.mockRestore();
|
||||||
|
spyHttpClientHead.mockRestore();
|
||||||
const sleep = jest.fn(async () => undefined);
|
const sleep = jest.fn(async () => undefined);
|
||||||
const requestRaw = jest
|
const requestRaw = jest
|
||||||
.spyOn(HttpClient.prototype, 'requestRaw')
|
.spyOn(HttpClient.prototype, 'requestRaw')
|
||||||
|
|||||||
@@ -378,6 +378,97 @@ describe('findPackageForDownload', () => {
|
|||||||
expect(resolvedVersion.version).toBe('16.0.2+7');
|
expect(resolvedVersion.version).toBe('16.0.2+7');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('OpenJDK patch (respin) versions', () => {
|
||||||
|
const makeRelease = (
|
||||||
|
semverVersion: string,
|
||||||
|
openjdkVersion: string,
|
||||||
|
versionData: Record<string, number>
|
||||||
|
) => ({
|
||||||
|
binaries: [
|
||||||
|
{
|
||||||
|
package: {
|
||||||
|
link: `https://example.com/${openjdkVersion}.tar.gz`,
|
||||||
|
checksum: `checksum-${openjdkVersion}`,
|
||||||
|
checksum_link: `https://example.com/${openjdkVersion}.sha256.txt`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
version_data: {
|
||||||
|
semver: semverVersion,
|
||||||
|
openjdk_version: openjdkVersion,
|
||||||
|
minor: 0,
|
||||||
|
...versionData
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const respinManifest = [
|
||||||
|
makeRelease('26.0.2+101', '26.0.2.1+1', {
|
||||||
|
major: 26,
|
||||||
|
security: 2,
|
||||||
|
patch: 1,
|
||||||
|
build: 1
|
||||||
|
}),
|
||||||
|
makeRelease('26.0.2+10', '26.0.2+10', {
|
||||||
|
major: 26,
|
||||||
|
security: 2,
|
||||||
|
build: 10
|
||||||
|
}),
|
||||||
|
makeRelease('25.0.4+101.0.LTS', '25.0.4.1+1-LTS', {
|
||||||
|
major: 25,
|
||||||
|
security: 4,
|
||||||
|
patch: 1,
|
||||||
|
build: 1
|
||||||
|
}),
|
||||||
|
makeRelease('25.0.4+7.0.LTS', '25.0.4+7-LTS', {
|
||||||
|
major: 25,
|
||||||
|
security: 4,
|
||||||
|
build: 7
|
||||||
|
})
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['26.0.2.1+1', '26.0.2+101'],
|
||||||
|
['26.0.2+10', '26.0.2+10'],
|
||||||
|
['26', '26.0.2+101'],
|
||||||
|
['26.0.2', '26.0.2+101'],
|
||||||
|
['25.0.4.1+1', '25.0.4+101.0.LTS'],
|
||||||
|
['25.0.4+7', '25.0.4+7.0.LTS'],
|
||||||
|
['25.0.4', '25.0.4+101.0.LTS']
|
||||||
|
])('%s resolves to %s', async (input, expected) => {
|
||||||
|
const distribution = new TemurinDistribution(
|
||||||
|
{
|
||||||
|
version: input,
|
||||||
|
architecture: 'x64',
|
||||||
|
packageType: 'jdk',
|
||||||
|
checkLatest: false
|
||||||
|
},
|
||||||
|
TemurinImplementation.Hotspot
|
||||||
|
);
|
||||||
|
distribution['getAvailableVersions'] = async () => respinManifest as any;
|
||||||
|
const resolvedVersion = await distribution['findPackageForDownload'](
|
||||||
|
distribution['version']
|
||||||
|
);
|
||||||
|
expect(resolvedVersion.version).toBe(expected);
|
||||||
|
expect(resolvedVersion).not.toHaveProperty('openjdkVersion');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not match a non-existent respin', async () => {
|
||||||
|
const distribution = new TemurinDistribution(
|
||||||
|
{
|
||||||
|
version: '26.0.2.2+1',
|
||||||
|
architecture: 'x64',
|
||||||
|
packageType: 'jdk',
|
||||||
|
checkLatest: false
|
||||||
|
},
|
||||||
|
TemurinImplementation.Hotspot
|
||||||
|
);
|
||||||
|
distribution['getAvailableVersions'] = async () => respinManifest as any;
|
||||||
|
await expect(
|
||||||
|
distribution['findPackageForDownload'](distribution['version'])
|
||||||
|
).rejects.toThrow(/No matching version found for SemVer '26.0.2\+2.1'/);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('version is found but binaries list is empty', async () => {
|
it('version is found but binaries list is empty', async () => {
|
||||||
const distribution = new TemurinDistribution(
|
const distribution = new TemurinDistribution(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -334,6 +334,69 @@ describe('findPackageForDownload', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('hotfix builds with a 4-segment java_version', () => {
|
||||||
|
// Mirrors the Azul Metadata API: 25.0.4.1 hotfix is reported as
|
||||||
|
// java_version=[25,0,4,1], openjdk_build_number=1 (SDKMAN '25.0.4+1.1').
|
||||||
|
const hotfixManifest = [
|
||||||
|
{
|
||||||
|
package_uuid: 'uuid-25.0.4+7',
|
||||||
|
name: 'zulu25.36.15-ca-jdk25.0.4-linux_x64.tar.gz',
|
||||||
|
download_url:
|
||||||
|
'https://cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-linux_x64.tar.gz',
|
||||||
|
java_version: [25, 0, 4],
|
||||||
|
openjdk_build_number: 7,
|
||||||
|
distro_version: [25, 36, 15, 0],
|
||||||
|
latest: false,
|
||||||
|
availability_type: 'ca'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
package_uuid: 'uuid-25.0.4+1.1',
|
||||||
|
name: 'zulu25.36.205-ca-jdk25.0.4.1-linux_x64.tar.gz',
|
||||||
|
download_url:
|
||||||
|
'https://cdn.azul.com/zulu/bin/zulu25.36.205-ca-jdk25.0.4.1-linux_x64.tar.gz',
|
||||||
|
java_version: [25, 0, 4, 1],
|
||||||
|
openjdk_build_number: 1,
|
||||||
|
distro_version: [25, 36, 205, 0],
|
||||||
|
latest: true,
|
||||||
|
availability_type: 'ca'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
package_uuid: 'uuid-25.0.3+9',
|
||||||
|
name: 'zulu25.34.17-ca-jdk25.0.3-linux_x64.tar.gz',
|
||||||
|
download_url:
|
||||||
|
'https://cdn.azul.com/zulu/bin/zulu25.34.17-ca-jdk25.0.3-linux_x64.tar.gz',
|
||||||
|
java_version: [25, 0, 3],
|
||||||
|
openjdk_build_number: 9,
|
||||||
|
distro_version: [25, 34, 17, 0],
|
||||||
|
latest: false,
|
||||||
|
availability_type: 'ca'
|
||||||
|
}
|
||||||
|
] as IZuluVersions[];
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
['25.0.4+1.1', '25.0.4+1.1', 'uuid-25.0.4+1.1'],
|
||||||
|
['25', '25.0.4+1.1', 'uuid-25.0.4+1.1'],
|
||||||
|
['25.0.4', '25.0.4+1.1', 'uuid-25.0.4+1.1'],
|
||||||
|
['25.0.4+7', '25.0.4+7', 'uuid-25.0.4+7'],
|
||||||
|
['25.0.3', '25.0.3+9', 'uuid-25.0.3+9']
|
||||||
|
])('version is %s -> %s', async (input, expected, uuid) => {
|
||||||
|
const distribution = new ZuluDistribution({
|
||||||
|
version: input,
|
||||||
|
architecture: 'x64',
|
||||||
|
packageType: 'jdk',
|
||||||
|
checkLatest: false
|
||||||
|
});
|
||||||
|
distribution['getAvailableVersions'] = async () => hotfixManifest;
|
||||||
|
const result = await distribution['findPackageForDownload'](
|
||||||
|
distribution['version']
|
||||||
|
);
|
||||||
|
expect(result.version).toBe(expected);
|
||||||
|
expect(result.url).toBe(
|
||||||
|
hotfixManifest.find(item => item.package_uuid === uuid)!.download_url
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw an error', async () => {
|
it('should throw an error', async () => {
|
||||||
const distribution = new ZuluDistribution({
|
const distribution = new ZuluDistribution({
|
||||||
version: '18',
|
version: '18',
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const core = await import('@actions/core');
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
convertVersionToSemver,
|
convertVersionToSemver,
|
||||||
|
normalizeJavaVersionToSemver,
|
||||||
getNextPageUrlFromLinkHeader,
|
getNextPageUrlFromLinkHeader,
|
||||||
getVersionFromFileContent,
|
getVersionFromFileContent,
|
||||||
isVersionSatisfies,
|
isVersionSatisfies,
|
||||||
@@ -188,6 +189,22 @@ describe('convertVersionToSemver', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('normalizeJavaVersionToSemver', () => {
|
||||||
|
it.each([
|
||||||
|
['17', '17'],
|
||||||
|
['17.0.8', '17.0.8'],
|
||||||
|
['17.0.8+7', '17.0.8+7'],
|
||||||
|
['11.0.9.1', '11.0.9+1'],
|
||||||
|
['12.0.2.1.0', '12.0.2+1.0'],
|
||||||
|
['26.0.2.1+1', '26.0.2+1.1'],
|
||||||
|
['17.0.8.1+1080.1', '17.0.8+1.1080.1'],
|
||||||
|
['>=11.0.9.1', '>=11.0.9.1'],
|
||||||
|
['17.x', '17.x']
|
||||||
|
])('%s -> %s', (input: string, expected: string) => {
|
||||||
|
expect(normalizeJavaVersionToSemver(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getNextPageUrlFromLinkHeader', () => {
|
describe('getNextPageUrlFromLinkHeader', () => {
|
||||||
it.each([
|
it.each([
|
||||||
[
|
[
|
||||||
@@ -277,6 +294,7 @@ describe('getVersionFromFileContent', () => {
|
|||||||
['java=17.0.9-graalce', '17.0.9', 'graalvm'],
|
['java=17.0.9-graalce', '17.0.9', 'graalvm'],
|
||||||
['java=11.0.25-librca', '11.0.25', 'liberica'],
|
['java=11.0.25-librca', '11.0.25', 'liberica'],
|
||||||
['java=25.0.4+1.1-librca', '25.0.4+1.1', 'liberica'],
|
['java=25.0.4+1.1-librca', '25.0.4+1.1', 'liberica'],
|
||||||
|
['java=25.0.4+1.1-zulu', '25.0.4+1.1', 'zulu'],
|
||||||
['java=11.0.25-ms', '11.0.25', 'microsoft'],
|
['java=11.0.25-ms', '11.0.25', 'microsoft'],
|
||||||
['java=21.0.5-oracle', '21.0.5', 'oracle'],
|
['java=21.0.5-oracle', '21.0.5', 'oracle'],
|
||||||
['java=11.0.25-sapmchn', '11.0.25', 'sapmachine'],
|
['java=11.0.25-sapmchn', '11.0.25', 'sapmachine'],
|
||||||
|
|||||||
10
action.yml
10
action.yml
@@ -66,15 +66,21 @@ inputs:
|
|||||||
mvn-server-credentials:
|
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.'
|
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
|
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:
|
mvn-repositories:
|
||||||
description: 'Multiline list of Maven dependency repositories in the format `repository-id:repository-url:snapshots-enabled`.'
|
description: 'Multiline list of Maven dependency repositories in the format `repository-id:repository-url:snapshots-enabled`.'
|
||||||
required: false
|
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:
|
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
|
required: false
|
||||||
default: true
|
default: true
|
||||||
mvn-repositories-prioritize-central:
|
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
|
required: false
|
||||||
default: true
|
default: true
|
||||||
settings-path:
|
settings-path:
|
||||||
|
|||||||
180
dist/cleanup/767.index.js
vendored
180
dist/cleanup/767.index.js
vendored
@@ -18360,9 +18360,102 @@ function readAttributeStr(xmlData, i) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select all the attributes whether valid or invalid.
|
* Walk `attrStr` once, left to right, splitting it into attribute tokens.
|
||||||
|
*
|
||||||
|
* This replaces a regex that used to do the same job
|
||||||
|
* (`(\s*)([^\s=]+)(\s*=)?(\s*(['"])(([\s\S])*?)\5)?`). That regex led with an
|
||||||
|
* optional whitespace group followed by a required "non-whitespace" group.
|
||||||
|
* On a long run of whitespace that never resolves into an attribute name
|
||||||
|
* (e.g. a tag with thousands of trailing spaces before `>`), the engine
|
||||||
|
* backtracks the whitespace group one character at a time before giving up
|
||||||
|
* and moving to the next starting position — one full backtrack per
|
||||||
|
* position, which is quadratic in the length of the run.
|
||||||
|
*
|
||||||
|
* A single forward-only scan can never backtrack, so it can't be made slow
|
||||||
|
* this way no matter how much whitespace the input contains — it's always
|
||||||
|
* proportional to the length of the string, once.
|
||||||
|
*
|
||||||
|
* Each returned token mirrors the shape the old regex match array had, so
|
||||||
|
* the validation logic below (which reads token[1]..token[6]) didn't need
|
||||||
|
* to change:
|
||||||
|
* token.startIndex - where this token begins in attrStr
|
||||||
|
* token[1] - leading whitespace before the name
|
||||||
|
* token[2] - the attribute name
|
||||||
|
* token[3] - whitespace + '=' if present, else undefined
|
||||||
|
* token[4] - marker (any defined value) if a quoted value was found
|
||||||
|
* token[5] - the quote character used ('"' or "'")
|
||||||
|
* token[6] - the value's text, without the surrounding quotes
|
||||||
|
*
|
||||||
|
* A malformed leading character (e.g. a stray '=' with no name before it)
|
||||||
|
* is simply skipped over, one character at a time — the same outcome the
|
||||||
|
* old regex produced by failing to match at that position and retrying at
|
||||||
|
* the next one.
|
||||||
*/
|
*/
|
||||||
const validAttrStrRegxp = new RegExp('(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s\\S])*?)\\5)?', 'g');
|
function scanAttributeTokens(attrStr) {
|
||||||
|
const tokens = [];
|
||||||
|
const len = attrStr.length;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
const tokenStart = i;
|
||||||
|
|
||||||
|
// Leading whitespace before the name.
|
||||||
|
while (i < len && isWhiteSpace(attrStr[i])) i++;
|
||||||
|
if (i >= len) break; // trailing whitespace only — nothing left to read
|
||||||
|
|
||||||
|
if (attrStr[i] === '=') {
|
||||||
|
// No name before this '=' — not a valid attribute start. Move past
|
||||||
|
// just this one character and try again from the next position.
|
||||||
|
i = tokenStart + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const leadingWs = attrStr.slice(tokenStart, i);
|
||||||
|
|
||||||
|
// Attribute name — everything up to the next whitespace or '='.
|
||||||
|
const nameStart = i;
|
||||||
|
while (i < len && !isWhiteSpace(attrStr[i]) && attrStr[i] !== '=') i++;
|
||||||
|
const name = attrStr.slice(nameStart, i);
|
||||||
|
|
||||||
|
// Optional whitespace + '='.
|
||||||
|
let equalsGroup; // whitespace + '=' text, or undefined if absent
|
||||||
|
let j = i;
|
||||||
|
while (j < len && isWhiteSpace(attrStr[j])) j++;
|
||||||
|
if (j < len && attrStr[j] === '=') {
|
||||||
|
equalsGroup = attrStr.slice(i, j + 1);
|
||||||
|
i = j + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional whitespace + quoted value.
|
||||||
|
let quoteChar;
|
||||||
|
let value;
|
||||||
|
let k = i;
|
||||||
|
while (k < len && isWhiteSpace(attrStr[k])) k++;
|
||||||
|
if (k < len && (attrStr[k] === '"' || attrStr[k] === "'")) {
|
||||||
|
const valueStart = k + 1;
|
||||||
|
const closeIdx = attrStr.indexOf(attrStr[k], valueStart);
|
||||||
|
if (closeIdx !== -1) {
|
||||||
|
quoteChar = attrStr[k];
|
||||||
|
value = attrStr.slice(valueStart, closeIdx);
|
||||||
|
i = closeIdx + 1;
|
||||||
|
}
|
||||||
|
// No closing quote found anywhere in the rest of the string — leave
|
||||||
|
// quoteChar/value undefined, same as the old regex's group failing
|
||||||
|
// to match a backreference-less run.
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = { startIndex: tokenStart };
|
||||||
|
token[1] = leadingWs;
|
||||||
|
token[2] = name;
|
||||||
|
token[3] = equalsGroup;
|
||||||
|
token[4] = quoteChar !== undefined ? true : undefined;
|
||||||
|
token[5] = quoteChar;
|
||||||
|
token[6] = value;
|
||||||
|
tokens.push(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
//attr, ="sd", a="amit's", a="sd"b="saf", ab cd=""
|
//attr, ="sd", a="amit's", a="sd"b="saf", ab cd=""
|
||||||
|
|
||||||
@@ -18371,7 +18464,7 @@ function validateAttributeString(attrStr, options) {
|
|||||||
|
|
||||||
//if(attrStr.trim().length === 0) return true; //empty string
|
//if(attrStr.trim().length === 0) return true; //empty string
|
||||||
|
|
||||||
const matches = getAllMatches(attrStr, validAttrStrRegxp);
|
const matches = scanAttributeTokens(attrStr);
|
||||||
const attrNames = {};
|
const attrNames = {};
|
||||||
|
|
||||||
for (let i = 0; i < matches.length; i++) {
|
for (let i = 0; i < matches.length; i++) {
|
||||||
@@ -18473,7 +18566,6 @@ function getLineNumberForPosition(xmlData, index) {
|
|||||||
function getPositionFromMatch(match) {
|
function getPositionFromMatch(match) {
|
||||||
return match.startIndex + match[1].length;
|
return match.startIndex + match[1].length;
|
||||||
}
|
}
|
||||||
|
|
||||||
;// CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/fxp.js
|
;// CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/fxp.js
|
||||||
|
|
||||||
|
|
||||||
@@ -59498,6 +59590,24 @@ const EXPANSION_MAX = 100_000;
|
|||||||
// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
|
// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
|
||||||
// characters) so legitimate input is unaffected.
|
// characters) so legitimate input is unaffected.
|
||||||
const EXPANSION_MAX_LENGTH = 4_000_000;
|
const EXPANSION_MAX_LENGTH = 4_000_000;
|
||||||
|
// `expand_` recurses once per level of brace *nesting* - both when expanding a
|
||||||
|
// set's comma members and when re-wrapping a set whose body is a single part.
|
||||||
|
// The CVE-2026-14257 fix made the *tail* iterative (recursion on `m.post`, one
|
||||||
|
// level per chained group), which left nesting depth unbounded: about 3,100
|
||||||
|
// levels of `{{{...a,b...}}}` - only ~6KB of input - exhausted the native stack
|
||||||
|
// and crashed the process. `EXPANSION_MAX_DEPTH` bounds how deep the parser
|
||||||
|
// will follow nesting. It sits far above any realistic pattern and well below
|
||||||
|
// the depth at which the stack runs out.
|
||||||
|
const EXPANSION_MAX_DEPTH = 1_000;
|
||||||
|
// Bash keeps a quirk where a brace group followed by a comma set still expands
|
||||||
|
// (`{a},b}`). The parser implements it by rewriting the string and restarting
|
||||||
|
// the scan, absorbing one `}` per pass. `n` trailing braces therefore cost `n`
|
||||||
|
// full passes over a string that itself grows by one `escClose` sentinel each
|
||||||
|
// time - quadratic in `n`, with a ~26x constant from the sentinel's length.
|
||||||
|
// 128KB of `'{a}' + '}'.repeat(n) + ',z}'` blocked the event loop for 27
|
||||||
|
// seconds to produce two results. `EXPANSION_MAX_REWRITES` bounds how many
|
||||||
|
// times the scan may restart. Real `{a},b}` input needs a handful.
|
||||||
|
const EXPANSION_MAX_REWRITES = 1_000;
|
||||||
function numeric(str) {
|
function numeric(str) {
|
||||||
return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
|
return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
|
||||||
}
|
}
|
||||||
@@ -59517,37 +59627,52 @@ function unescapeBraces(str) {
|
|||||||
.replace(escCommaPattern, ',')
|
.replace(escCommaPattern, ',')
|
||||||
.replace(escPeriodPattern, '.');
|
.replace(escPeriodPattern, '.');
|
||||||
}
|
}
|
||||||
|
// Like `target.push(...items)` but doesn't overflow the stack
|
||||||
|
function pushAll(target, items) {
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
target.push(items[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Basically just str.split(","), but handling cases
|
* Basically just str.split(","), but handling cases
|
||||||
* where we have nested braced sections, which should be
|
* where we have nested braced sections, which should be
|
||||||
* treated as individual members, like {a,{b,c},d}
|
* treated as individual members, like {a,{b,c},d}
|
||||||
*/
|
*/
|
||||||
function parseCommaParts(str) {
|
function parseCommaParts(str) {
|
||||||
if (!str) {
|
|
||||||
return [''];
|
|
||||||
}
|
|
||||||
const parts = [];
|
const parts = [];
|
||||||
|
// 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);
|
const m = balanced('{', '}', str);
|
||||||
if (!m) {
|
if (!m) {
|
||||||
return str.split(',');
|
const tail = str.split(',');
|
||||||
|
tail[0] = carry + tail[0];
|
||||||
|
pushAll(parts, tail);
|
||||||
|
return parts;
|
||||||
}
|
}
|
||||||
const { pre, body, post } = m;
|
const { pre, body, post } = m;
|
||||||
const p = pre.split(',');
|
const p = pre.split(',');
|
||||||
|
p[0] = carry + p[0];
|
||||||
p[p.length - 1] += '{' + body + '}';
|
p[p.length - 1] += '{' + body + '}';
|
||||||
const postParts = parseCommaParts(post);
|
if (!post.length) {
|
||||||
if (post.length) {
|
pushAll(parts, p);
|
||||||
;
|
|
||||||
p[p.length - 1] += postParts.shift();
|
|
||||||
p.push.apply(p, postParts);
|
|
||||||
}
|
|
||||||
parts.push.apply(parts, p);
|
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
carry = p.pop();
|
||||||
|
pushAll(parts, p);
|
||||||
|
str = post;
|
||||||
|
}
|
||||||
|
}
|
||||||
function expand(str, options = {}) {
|
function expand(str, options = {}) {
|
||||||
if (!str) {
|
if (!str) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH } = options;
|
const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH, maxDepth = EXPANSION_MAX_DEPTH, maxRewrites = EXPANSION_MAX_REWRITES, } = options;
|
||||||
// I don't know why Bash 4.3 does this, but it does.
|
// I don't know why Bash 4.3 does this, but it does.
|
||||||
// Anything starting with {} will have the first two bytes preserved
|
// Anything starting with {} will have the first two bytes preserved
|
||||||
// but *only* at the top level, so {},a}b will not expand to anything,
|
// but *only* at the top level, so {},a}b will not expand to anything,
|
||||||
@@ -59557,7 +59682,7 @@ function expand(str, options = {}) {
|
|||||||
if (str.slice(0, 2) === '{}') {
|
if (str.slice(0, 2) === '{}') {
|
||||||
str = '\\{\\}' + str.slice(2);
|
str = '\\{\\}' + str.slice(2);
|
||||||
}
|
}
|
||||||
return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces);
|
return expand_(escapeBraces(str), max, maxLength, maxDepth, 0, maxRewrites, true).map(unescapeBraces);
|
||||||
}
|
}
|
||||||
function embrace(str) {
|
function embrace(str) {
|
||||||
return '{' + str + '}';
|
return '{' + str + '}';
|
||||||
@@ -59652,7 +59777,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) {
|
|||||||
}
|
}
|
||||||
return N;
|
return N;
|
||||||
}
|
}
|
||||||
function expand_(str, max, maxLength, isTop) {
|
function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) {
|
||||||
|
// Too deeply nested to keep following: treat the rest as literal, the same
|
||||||
|
// way a group that cannot expand is already handled. Truncating rather than
|
||||||
|
// throwing keeps `expand` total, matching `max` and `maxLength`.
|
||||||
|
if (depth > maxDepth) {
|
||||||
|
return [str];
|
||||||
|
}
|
||||||
// Consume the string's top-level brace groups left to right, threading a
|
// Consume the string's top-level brace groups left to right, threading a
|
||||||
// running set of combined prefixes (`acc`). Expanding the tail iteratively -
|
// running set of combined prefixes (`acc`). Expanding the tail iteratively -
|
||||||
// rather than recursing on `m.post` once per group - keeps the native stack
|
// rather than recursing on `m.post` once per group - keeps the native stack
|
||||||
@@ -59664,6 +59795,9 @@ function expand_(str, max, maxLength, isTop) {
|
|||||||
// comma set - a sequence like `{a..\}` may legitimately yield ''. The drop
|
// comma set - a sequence like `{a..\}` may legitimately yield ''. The drop
|
||||||
// is on the final strings, so it is applied to whichever `combine` produces
|
// is on the final strings, so it is applied to whichever `combine` produces
|
||||||
// them (the one with no brace set left in the tail).
|
// them (the one with no brace set left in the tail).
|
||||||
|
// How many times the `{a},b}` rewrite below has restarted the scan. Each pass
|
||||||
|
// re-reads the whole string, so leaving this unbounded is quadratic.
|
||||||
|
let rewrites = 0;
|
||||||
let dropEmpties = false;
|
let dropEmpties = false;
|
||||||
let firstGroup = true;
|
let firstGroup = true;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@@ -59688,7 +59822,8 @@ function expand_(str, max, maxLength, isTop) {
|
|||||||
const isOptions = m.body.indexOf(',') >= 0;
|
const isOptions = m.body.indexOf(',') >= 0;
|
||||||
if (!isSequence && !isOptions) {
|
if (!isSequence && !isOptions) {
|
||||||
// {a},b}
|
// {a},b}
|
||||||
if (m.post.match(/,(?!,).*\}/)) {
|
if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) {
|
||||||
|
rewrites++;
|
||||||
str = m.pre + '{' + m.body + escClose + m.post;
|
str = m.pre + '{' + m.body + escClose + m.post;
|
||||||
isTop = true;
|
isTop = true;
|
||||||
continue;
|
continue;
|
||||||
@@ -59708,7 +59843,7 @@ function expand_(str, max, maxLength, isTop) {
|
|||||||
let n = parseCommaParts(m.body);
|
let n = parseCommaParts(m.body);
|
||||||
if (n.length === 1 && n[0] !== undefined) {
|
if (n.length === 1 && n[0] !== undefined) {
|
||||||
// x{{a,b}}y ==> x{a}y x{b}y
|
// x{{a,b}}y ==> x{a}y x{b}y
|
||||||
n = expand_(n[0], max, maxLength, false).map(embrace);
|
n = expand_(n[0], max, maxLength, maxDepth, depth + 1, maxRewrites, false).map(embrace);
|
||||||
//XXX is this necessary? Can't seem to hit it in tests.
|
//XXX is this necessary? Can't seem to hit it in tests.
|
||||||
/* c8 ignore start */
|
/* c8 ignore start */
|
||||||
if (n.length === 1) {
|
if (n.length === 1) {
|
||||||
@@ -59734,12 +59869,13 @@ function expand_(str, max, maxLength, isTop) {
|
|||||||
values = [];
|
values = [];
|
||||||
let valuesLength = 0;
|
let valuesLength = 0;
|
||||||
outer: for (let j = 0; j < n.length; j++) {
|
outer: for (let j = 0; j < n.length; j++) {
|
||||||
const expanded = expand_(n[j], max, maxLength, false);
|
const expanded = expand_(n[j], max, maxLength, maxDepth, depth + 1, maxRewrites, false);
|
||||||
for (let k = 0; k < expanded.length; k++) {
|
for (let k = 0; k < expanded.length; k++) {
|
||||||
const v = expanded[k];
|
const v = expanded[k];
|
||||||
if (dropsEmpties && !v)
|
if (dropsEmpties && !v)
|
||||||
continue;
|
continue;
|
||||||
if (values.length >= max || valuesLength + v.length > maxLength) {
|
if (values.length >= max ||
|
||||||
|
valuesLength + v.length > maxLength) {
|
||||||
break outer;
|
break outer;
|
||||||
}
|
}
|
||||||
values.push(v);
|
values.push(v);
|
||||||
|
|||||||
642
dist/cleanup/index.js
vendored
642
dist/cleanup/index.js
vendored
@@ -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.aborted)
|
||||||
assert(!this.completed)
|
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) {
|
onComplete (trailers) {
|
||||||
@@ -7912,7 +7978,7 @@ class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onUpgrade (head) {
|
onUpgrade (head) {
|
||||||
const { upgrade, client, socket, headers, statusCode } = this
|
const { upgrade, client, socket, headers, statusCode, statusText } = this
|
||||||
|
|
||||||
assert(upgrade)
|
assert(upgrade)
|
||||||
assert(client[kSocket] === socket)
|
assert(client[kSocket] === socket)
|
||||||
@@ -7947,9 +8013,10 @@ class Parser {
|
|||||||
client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade'))
|
client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade'))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
request.onUpgrade(statusCode, headers, socket)
|
request.onUpgrade(statusCode, headers, socket, statusText)
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
util.destroy(socket, err)
|
util.errorRequest(client, request, error)
|
||||||
|
util.destroy(socket, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
client[kResume]()
|
client[kResume]()
|
||||||
@@ -8356,7 +8423,7 @@ async function connectH1 (client, socket) {
|
|||||||
|
|
||||||
function clearIdleSocketValidation (socket) {
|
function clearIdleSocketValidation (socket) {
|
||||||
if (socket[kIdleSocketValidationTimeout]) {
|
if (socket[kIdleSocketValidationTimeout]) {
|
||||||
clearTimeout(socket[kIdleSocketValidationTimeout])
|
clearImmediate(socket[kIdleSocketValidationTimeout])
|
||||||
socket[kIdleSocketValidationTimeout] = null
|
socket[kIdleSocketValidationTimeout] = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8365,15 +8432,23 @@ function clearIdleSocketValidation (socket) {
|
|||||||
|
|
||||||
function scheduleIdleSocketValidation (client, socket) {
|
function scheduleIdleSocketValidation (client, socket) {
|
||||||
socket[kIdleSocketValidation] = 1
|
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[kIdleSocketValidationTimeout] = null
|
||||||
socket[kIdleSocketValidation] = 2
|
socket[kIdleSocketValidation] = 2
|
||||||
|
|
||||||
if (client[kSocket] === socket && !socket.destroyed) {
|
if (client[kSocket] === socket && !socket.destroyed) {
|
||||||
client[kResume]()
|
client[kResume]()
|
||||||
}
|
}
|
||||||
}, 0)
|
})
|
||||||
socket[kIdleSocketValidationTimeout].unref?.()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -8522,12 +8597,22 @@ function writeH1 (client, request) {
|
|||||||
const socket = client[kSocket]
|
const socket = client[kSocket]
|
||||||
clearIdleSocketValidation(socket)
|
clearIdleSocketValidation(socket)
|
||||||
|
|
||||||
const abort = (err) => {
|
/**
|
||||||
if (request.aborted || request.completed) {
|
* @param {Error} [error]
|
||||||
|
*/
|
||||||
|
const abort = (error) => {
|
||||||
|
if (request.aborted) {
|
||||||
return
|
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(body)
|
||||||
util.destroy(socket, new InformationalError('aborted'))
|
util.destroy(socket, new InformationalError('aborted'))
|
||||||
@@ -8984,6 +9069,7 @@ module.exports = connectH1
|
|||||||
|
|
||||||
|
|
||||||
const assert = __nccwpck_require__(4589)
|
const assert = __nccwpck_require__(4589)
|
||||||
|
const { errorMonitor } = __nccwpck_require__(8474)
|
||||||
const { pipeline } = __nccwpck_require__(7075)
|
const { pipeline } = __nccwpck_require__(7075)
|
||||||
const util = __nccwpck_require__(3440)
|
const util = __nccwpck_require__(3440)
|
||||||
const {
|
const {
|
||||||
@@ -9060,6 +9146,15 @@ function parseH2Headers (headers) {
|
|||||||
return result
|
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) {
|
async function connectH2 (client, socket) {
|
||||||
client[kSocket] = socket
|
client[kSocket] = socket
|
||||||
|
|
||||||
@@ -9280,22 +9375,32 @@ function writeH2 (client, request) {
|
|||||||
headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
|
headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
|
||||||
headers[HTTP2_HEADER_METHOD] = method
|
headers[HTTP2_HEADER_METHOD] = method
|
||||||
|
|
||||||
const abort = (err) => {
|
/**
|
||||||
if (request.aborted || request.completed) {
|
* @param {Error} [error]
|
||||||
|
*/
|
||||||
|
const abort = (error) => {
|
||||||
|
if (request.aborted) {
|
||||||
return
|
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) {
|
if (stream != null) {
|
||||||
util.destroy(stream, err)
|
util.destroy(stream, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We do not destroy the socket as we can continue using the session
|
// 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
|
// 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[kQueue][client[kRunningIdx]++] = null
|
||||||
client[kResume]()
|
client[kResume]()
|
||||||
}
|
}
|
||||||
@@ -9314,25 +9419,57 @@ function writeH2 (client, request) {
|
|||||||
|
|
||||||
if (method === 'CONNECT') {
|
if (method === 'CONNECT') {
|
||||||
session.ref()
|
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
|
// We disabled endStream to allow the user to write to the stream
|
||||||
stream = session.request(headers, { endStream: false, signal })
|
stream = session.request(headers, { endStream: false, signal })
|
||||||
|
let upgradeResponseFinished = false
|
||||||
|
|
||||||
if (stream.id && !stream.pending) {
|
/**
|
||||||
request.onUpgrade(null, null, stream)
|
* @param {import('node:http2').IncomingHttpHeaders} headers
|
||||||
++session[kOpenStreams]
|
*/
|
||||||
client[kQueue][client[kRunningIdx]++] = null
|
const onResponse = (headers) => {
|
||||||
} else {
|
upgradeResponseFinished = true
|
||||||
stream.once('ready', () => {
|
stream.off(errorMonitor, onUpgradeError)
|
||||||
request.onUpgrade(null, null, stream)
|
request.onUpgradeResponse(Number(headers[HTTP2_HEADER_STATUS]), headers, parseH2ResponseHeaders)
|
||||||
++session[kOpenStreams]
|
|
||||||
client[kQueue][client[kRunningIdx]++] = null
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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', () => {
|
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
|
session[kOpenStreams] -= 1
|
||||||
if (session[kOpenStreams] === 0) session.unref()
|
if (session[kOpenStreams] === 0) session.unref()
|
||||||
})
|
})
|
||||||
@@ -12031,6 +12168,7 @@ class RetryHandler {
|
|||||||
this.end = null
|
this.end = null
|
||||||
this.etag = null
|
this.etag = null
|
||||||
this.resume = null
|
this.resume = null
|
||||||
|
this.headersSent = false
|
||||||
|
|
||||||
// Handle possible onConnect duplication
|
// Handle possible onConnect duplication
|
||||||
this.handler.onConnect(reason => {
|
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 () {
|
onRequestSent () {
|
||||||
if (this.handler.onRequestSent) {
|
if (this.handler.onRequestSent) {
|
||||||
this.handler.onRequestSent()
|
this.handler.onRequestSent()
|
||||||
@@ -12131,7 +12283,12 @@ class RetryHandler {
|
|||||||
this.retryCount += 1
|
this.retryCount += 1
|
||||||
|
|
||||||
if (statusCode >= 300) {
|
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(
|
return this.handler.onHeaders(
|
||||||
statusCode,
|
statusCode,
|
||||||
rawHeaders,
|
rawHeaders,
|
||||||
@@ -12200,8 +12357,15 @@ class RetryHandler {
|
|||||||
|
|
||||||
const { start, size, end = size - 1 } = contentRange
|
const { start, size, end = size - 1 } = contentRange
|
||||||
|
|
||||||
assert(this.start === start, 'content-range mismatch')
|
if (this.start !== start || (this.end != null && this.end !== end)) {
|
||||||
assert(this.end == null || this.end === end, 'content-range mismatch')
|
this.abort(
|
||||||
|
new RequestRetryError('Content-Range mismatch', statusCode, {
|
||||||
|
headers,
|
||||||
|
data: { count: this.retryCount }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
this.resume = resume
|
this.resume = resume
|
||||||
return true
|
return true
|
||||||
@@ -12213,6 +12377,7 @@ class RetryHandler {
|
|||||||
const range = parseRangeHeader(headers['content-range'])
|
const range = parseRangeHeader(headers['content-range'])
|
||||||
|
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
|
this.headersSent = true
|
||||||
return this.handler.onHeaders(
|
return this.handler.onHeaders(
|
||||||
statusCode,
|
statusCode,
|
||||||
rawHeaders,
|
rawHeaders,
|
||||||
@@ -12251,6 +12416,7 @@ class RetryHandler {
|
|||||||
)
|
)
|
||||||
|
|
||||||
this.resume = resume
|
this.resume = resume
|
||||||
|
this.headersSent = true
|
||||||
this.etag = headers.etag != null ? headers.etag : null
|
this.etag = headers.etag != null ? headers.etag : null
|
||||||
|
|
||||||
// Weak etags are not useful for comparison nor cache
|
// Weak etags are not useful for comparison nor cache
|
||||||
@@ -12290,7 +12456,7 @@ class RetryHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onError (err) {
|
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)
|
return this.handler.onError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16748,6 +16914,49 @@ const COLON = 0x3A
|
|||||||
*/
|
*/
|
||||||
const SPACE = 0x20
|
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
|
* @typedef {object} EventSourceStreamEvent
|
||||||
* @type {object}
|
* @type {object}
|
||||||
@@ -16788,11 +16997,14 @@ class EventSourceStream extends Transform {
|
|||||||
eventEndCheck = false
|
eventEndCheck = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Buffer}
|
* @type {Buffer[]}
|
||||||
*/
|
*/
|
||||||
buffer = null
|
chunks = []
|
||||||
|
|
||||||
|
chunkIndex = 0
|
||||||
pos = 0
|
pos = 0
|
||||||
|
lineChunkIndex = 0
|
||||||
|
linePos = 0
|
||||||
|
|
||||||
event = {
|
event = {
|
||||||
data: undefined,
|
data: undefined,
|
||||||
@@ -16831,92 +17043,20 @@ class EventSourceStream extends Transform {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the chunk in the buffer, as the data might not be complete while
|
this.chunks.push(chunk)
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip leading byte-order-mark if we opened the stream and started
|
// Strip leading byte-order-mark if we opened the stream and started
|
||||||
// the processing of the incoming data
|
// the processing of the incoming data
|
||||||
if (this.checkBOM) {
|
if (this.checkBOM) {
|
||||||
switch (this.buffer.length) {
|
if (this.handleBOM()) {
|
||||||
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()
|
callback()
|
||||||
return
|
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
|
while (this.hasCurrentByte()) {
|
||||||
// BOM anymore
|
const byte = this.currentByte()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (this.pos < this.buffer.length) {
|
|
||||||
// If the previous line ended with an end-of-line, we need to check
|
// 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 the next character is also an end-of-line.
|
||||||
if (this.eventEndCheck) {
|
if (this.eventEndCheck) {
|
||||||
@@ -16929,10 +17069,9 @@ class EventSourceStream extends Transform {
|
|||||||
if (this.crlfCheck) {
|
if (this.crlfCheck) {
|
||||||
// If the current character is a line feed, we can remove it
|
// If the current character is a line feed, we can remove it
|
||||||
// from the buffer and reset the crlfCheck flag
|
// from the buffer and reset the crlfCheck flag
|
||||||
if (this.buffer[this.pos] === LF) {
|
if (byte === LF) {
|
||||||
this.buffer = this.buffer.subarray(this.pos + 1)
|
|
||||||
this.pos = 0
|
|
||||||
this.crlfCheck = false
|
this.crlfCheck = false
|
||||||
|
this.consumeCurrentByte()
|
||||||
|
|
||||||
// It is possible that the line feed is not the end of the
|
// It is possible that the line feed is not the end of the
|
||||||
// event. We need to check if the next character is an
|
// event. We need to check if the next character is an
|
||||||
@@ -16948,19 +17087,17 @@ class EventSourceStream extends Transform {
|
|||||||
this.crlfCheck = false
|
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
|
// If the current character is a carriage return, we need to
|
||||||
// set the crlfCheck flag to true, as we need to check if the
|
// 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
|
// next character is a line feed so we can remove it from the
|
||||||
// buffer
|
// buffer
|
||||||
if (this.buffer[this.pos] === CR) {
|
if (byte === CR) {
|
||||||
this.crlfCheck = true
|
this.crlfCheck = true
|
||||||
}
|
}
|
||||||
|
|
||||||
this.buffer = this.buffer.subarray(this.pos + 1)
|
this.consumeCurrentByte()
|
||||||
this.pos = 0
|
if (this.hasPendingEvent()) {
|
||||||
if (
|
|
||||||
this.event.data !== undefined || this.event.event || this.event.id || this.event.retry) {
|
|
||||||
this.processEvent(this.event)
|
this.processEvent(this.event)
|
||||||
}
|
}
|
||||||
this.clearEvent()
|
this.clearEvent()
|
||||||
@@ -16974,22 +17111,18 @@ class EventSourceStream extends Transform {
|
|||||||
|
|
||||||
// If the current character is an end-of-line, we can process the
|
// If the current character is an end-of-line, we can process the
|
||||||
// line
|
// 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
|
// If the current character is a carriage return, we need to
|
||||||
// set the crlfCheck flag to true, as we need to check if the
|
// set the crlfCheck flag to true, as we need to check if the
|
||||||
// next character is a line feed
|
// next character is a line feed
|
||||||
if (this.buffer[this.pos] === CR) {
|
if (byte === CR) {
|
||||||
this.crlfCheck = true
|
this.crlfCheck = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// In any case, we can process the line as we reached an
|
// In any case, we can process the line as we reached an
|
||||||
// end-of-line character
|
// end-of-line character
|
||||||
this.parseLine(this.buffer.subarray(0, this.pos), this.event)
|
this.parseLine(this.readLine(), this.event)
|
||||||
|
this.consumeCurrentByte()
|
||||||
// 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
|
|
||||||
// A line was processed and this could be the end of the event. We need
|
// 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
|
// to check if the next line is empty to determine if the event is
|
||||||
// finished.
|
// finished.
|
||||||
@@ -16997,7 +17130,7 @@ class EventSourceStream extends Transform {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pos++
|
this.advanceCursor()
|
||||||
}
|
}
|
||||||
|
|
||||||
callback()
|
callback()
|
||||||
@@ -17022,64 +17155,53 @@ class EventSourceStream extends Transform {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let field = ''
|
let fieldLength = line.length
|
||||||
let value = ''
|
let valueStart = line.length
|
||||||
|
|
||||||
// If the line contains a U+003A COLON character (:)
|
// If the line contains a U+003A COLON character (:)
|
||||||
if (colonPosition !== -1) {
|
if (colonPosition !== -1) {
|
||||||
// Collect the characters on the line before the first U+003A COLON
|
fieldLength = colonPosition
|
||||||
// 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')
|
|
||||||
|
|
||||||
// Collect the characters on the line after the first U+003A COLON
|
// Collect the characters on the line after the first U+003A COLON
|
||||||
// character (:), and let value be that string.
|
// character (:), and let value be that string.
|
||||||
// If value starts with a U+0020 SPACE character, remove it from value.
|
// If value starts with a U+0020 SPACE character, remove it from value.
|
||||||
let valueStart = colonPosition + 1
|
valueStart = colonPosition + 1
|
||||||
if (line[valueStart] === SPACE) {
|
if (line[valueStart] === SPACE) {
|
||||||
++valueStart
|
++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
|
if (isFieldName(line, fieldLength, DATA)) {
|
||||||
// decoded as UTF-8
|
const value = line.toString('utf8', valueStart)
|
||||||
switch (field) {
|
|
||||||
case 'data':
|
if (event.data === undefined) {
|
||||||
if (event[field] === undefined) {
|
event.data = value
|
||||||
event[field] = value
|
|
||||||
} else {
|
} else {
|
||||||
event[field] += `\n${value}`
|
event.data += `\n${value}`
|
||||||
}
|
}
|
||||||
break
|
return
|
||||||
case 'retry':
|
|
||||||
if (isASCIINumber(value)) {
|
|
||||||
event[field] = value
|
|
||||||
}
|
}
|
||||||
break
|
|
||||||
case 'id':
|
if (isFieldName(line, fieldLength, RETRY)) {
|
||||||
if (isValidLastEventId(value)) {
|
if (isASCIINumberBytes(line, valueStart)) {
|
||||||
event[field] = value
|
event.retry = line.toString('utf8', valueStart)
|
||||||
}
|
}
|
||||||
break
|
return
|
||||||
case 'event':
|
}
|
||||||
|
|
||||||
|
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) {
|
if (value.length > 0) {
|
||||||
event[field] = value
|
event.event = value
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17109,12 +17231,151 @@ class EventSourceStream extends Transform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearEvent () {
|
clearEvent () {
|
||||||
this.event = {
|
this.event.data = undefined
|
||||||
data: undefined,
|
this.event.event = undefined
|
||||||
event: undefined,
|
this.event.id = undefined
|
||||||
id: undefined,
|
this.event.retry = undefined
|
||||||
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28383,7 +28644,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|||||||
// is specified, the server needs to include the same field and one of
|
// is specified, the server needs to include the same field and one of
|
||||||
// the selected subprotocol values in its response for the connection to
|
// the selected subprotocol values in its response for the connection to
|
||||||
// be established.
|
// be established.
|
||||||
if (!requestProtocols.includes(secProtocol)) {
|
if (requestProtocols === null || !requestProtocols.includes(secProtocol)) {
|
||||||
failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
|
failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -29144,7 +29405,12 @@ class PerMessageDeflate {
|
|||||||
|
|
||||||
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
|
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
|
||||||
callback(new MessageSizeExceededError())
|
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.removeAllListeners()
|
||||||
|
this.#inflate.destroy()
|
||||||
this.#inflate = null
|
this.#inflate = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -30774,7 +31040,7 @@ module.exports = {
|
|||||||
/* harmony export */ gk: () => (/* binding */ INPUT_CACHE),
|
/* harmony export */ gk: () => (/* binding */ INPUT_CACHE),
|
||||||
/* harmony export */ wG: () => (/* binding */ INPUT_JOB_STATUS)
|
/* harmony export */ wG: () => (/* binding */ INPUT_JOB_STATUS)
|
||||||
/* harmony export */ });
|
/* 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 MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home';
|
||||||
const INPUT_JAVA_VERSION = 'java-version';
|
const INPUT_JAVA_VERSION = 'java-version';
|
||||||
const INPUT_JAVA_VERSION_FILE = 'java-version-file';
|
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_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 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_CREDENTIALS = 'mvn-server-credentials';
|
||||||
|
const INPUT_MVN_SERVER_REPOSITORY_ORIGINS = 'mvn-server-repository-origins';
|
||||||
const INPUT_MVN_REPOSITORIES = 'mvn-repositories';
|
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_INCLUDE_CENTRAL = 'mvn-repositories-include-central';
|
||||||
const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL = 'mvn-repositories-prioritize-central';
|
const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL = 'mvn-repositories-prioritize-central';
|
||||||
const INPUT_SERVER_ID = 'server-id';
|
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 */ Vt: () => (/* binding */ getBooleanInput),
|
||||||
/* harmony export */ lN: () => (/* binding */ isJdkCacheEnabled)
|
/* harmony export */ lN: () => (/* binding */ isJdkCacheEnabled)
|
||||||
/* harmony export */ });
|
/* 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__ = __nccwpck_require__(857);
|
||||||
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nccwpck_require__.n(os__WEBPACK_IMPORTED_MODULE_0__);
|
/* 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);
|
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(6928);
|
||||||
@@ -31247,6 +31515,20 @@ function convertVersionToSemver(version) {
|
|||||||
}
|
}
|
||||||
return mainVersion;
|
return mainVersion;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Java versions (JEP 322) can contain more numeric fields than SemVer allows,
|
||||||
|
* e.g. '11.0.9.1' or Temurin respins such as '26.0.2.1+1'. Move the extra
|
||||||
|
* fields into SemVer build metadata ('11.0.9+1', '26.0.2+1.1'). Any other
|
||||||
|
* input (ranges, regular SemVer versions) is returned unchanged.
|
||||||
|
*/
|
||||||
|
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
|
* Builds a validator for the bytes currently served by a URL from the response
|
||||||
* headers of a HEAD request. A vendor's `/latest/` URL never changes, so this
|
* headers of a HEAD request. A vendor's `/latest/` URL never changes, so this
|
||||||
|
|||||||
9
dist/setup/242.index.js
vendored
9
dist/setup/242.index.js
vendored
@@ -730,12 +730,9 @@ class JavaBase {
|
|||||||
}
|
}
|
||||||
// Java uses a versioning scheme (JEP 322) that can contain more numeric
|
// Java uses a versioning scheme (JEP 322) that can contain more numeric
|
||||||
// fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such
|
// fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such
|
||||||
// exact versions to SemVer build notation ('18.0.1+1') so they are
|
// exact versions to SemVer build notation ('18.0.1+1', or '26.0.2+1.1'
|
||||||
// accepted. Ranges and versions that already carry build metadata are
|
// for '26.0.2.1+1') so they are accepted. Ranges are left untouched.
|
||||||
// left untouched.
|
version = (0,util/* normalizeJavaVersionToSemver */.zZ)(version);
|
||||||
if (/^\d+(\.\d+){3,}$/.test(version)) {
|
|
||||||
version = (0,util/* convertVersionToSemver */.ZY)(version);
|
|
||||||
}
|
|
||||||
if (!semver_default().validRange(version)) {
|
if (!semver_default().validRange(version)) {
|
||||||
throw new Error(`The string '${version}' is not valid SemVer notation for a Java version. Please check README file for code snippets and more detailed information`);
|
throw new Error(`The string '${version}' is not valid SemVer notation for a Java version. Please check README file for code snippets and more detailed information`);
|
||||||
}
|
}
|
||||||
|
|||||||
34
dist/setup/463.index.js
vendored
34
dist/setup/463.index.js
vendored
@@ -109,7 +109,7 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
|
|||||||
const formattedVersion = this.stable
|
const formattedVersion = this.stable
|
||||||
? item.version_data.semver
|
? item.version_data.semver
|
||||||
: item.version_data.semver.replace('-beta+', '+');
|
: item.version_data.semver.replace('-beta+', '+');
|
||||||
return {
|
const release = {
|
||||||
version: formattedVersion,
|
version: formattedVersion,
|
||||||
url: item.binaries[0].package.link,
|
url: item.binaries[0].package.link,
|
||||||
signatureUrl: item.binaries[0].package.signature_link,
|
signatureUrl: item.binaries[0].package.signature_link,
|
||||||
@@ -119,15 +119,29 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
|
|||||||
source: item.binaries[0].package.checksum_link
|
source: item.binaries[0].package.checksum_link
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
return {
|
||||||
|
release,
|
||||||
|
openjdkVersion: getOpenJdkSemverVersion(item.version_data)
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
// The Adoptium API `semver` folds the JEP 322 patch field into the build
|
||||||
|
// number ('26.0.2.1+1' -> '26.0.2+101') and appends extra metadata for LTS
|
||||||
|
// releases ('25.0.4+7' -> '25.0.4+7.0.LTS'). Exact versions requested by
|
||||||
|
// users follow the OpenJDK notation instead, so also match them against a
|
||||||
|
// key derived from the OpenJDK version fields ('26.0.2+1.1', '25.0.4+7').
|
||||||
|
const isExactBuildRequest = (semver_default().parse(version)?.build.length ?? 0) > 0;
|
||||||
const satisfiedVersions = availableVersionsWithBinaries
|
const satisfiedVersions = availableVersionsWithBinaries
|
||||||
.filter(item => (0,util/* isVersionSatisfies */.y)(version, item.version))
|
.filter(({ release, openjdkVersion }) => (0,util/* isVersionSatisfies */.y)(version, release.version) ||
|
||||||
|
(isExactBuildRequest &&
|
||||||
|
openjdkVersion !== null &&
|
||||||
|
semver_default().compareBuild(version, openjdkVersion) === 0))
|
||||||
|
.map(({ release }) => release)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
return -semver_default().compareBuild(a.version, b.version);
|
return -semver_default().compareBuild(a.version, b.version);
|
||||||
});
|
});
|
||||||
const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
|
const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
|
||||||
if (!resolvedFullVersion) {
|
if (!resolvedFullVersion) {
|
||||||
const availableVersionStrings = availableVersionsWithBinaries.map(item => item.version);
|
const availableVersionStrings = availableVersionsWithBinaries.map(({ release }) => release.version);
|
||||||
throw this.createVersionNotFoundError(version, availableVersionStrings);
|
throw this.createVersionNotFoundError(version, availableVersionStrings);
|
||||||
}
|
}
|
||||||
return resolvedFullVersion;
|
return resolvedFullVersion;
|
||||||
@@ -279,6 +293,20 @@ class TemurinDistribution extends base_installer/* JavaBase */.O {
|
|||||||
return architecture === 'armv7' ? 'arm' : architecture;
|
return architecture === 'armv7' ? 'arm' : architecture;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Builds a SemVer version from the OpenJDK version fields reported by the
|
||||||
|
* Adoptium API, e.g. '26.0.2.1+1' -> '26.0.2+1.1' and '25.0.4+7-LTS' ->
|
||||||
|
* '25.0.4+7'. Returns null if the fields cannot form a valid SemVer version.
|
||||||
|
*/
|
||||||
|
function getOpenJdkSemverVersion(versionData) {
|
||||||
|
const { major, minor, security, patch, build } = versionData;
|
||||||
|
if (build === undefined || build === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const buildMetadata = patch ? `${patch}.${build}` : `${build}`;
|
||||||
|
const version = `${major}.${minor}.${security}+${buildMetadata}`;
|
||||||
|
return semver_default().valid(version) ? version : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|||||||
149
dist/setup/81.index.js
vendored
149
dist/setup/81.index.js
vendored
@@ -7,6 +7,7 @@ export const modules = {
|
|||||||
|
|
||||||
__webpack_require__.r(__webpack_exports__);
|
__webpack_require__.r(__webpack_exports__);
|
||||||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
||||||
|
/* harmony export */ addMavenServerRepositoryOrigins: () => (/* binding */ addMavenServerRepositoryOrigins),
|
||||||
/* harmony export */ configureAuthentication: () => (/* binding */ configureAuthentication),
|
/* harmony export */ configureAuthentication: () => (/* binding */ configureAuthentication),
|
||||||
/* harmony export */ createAuthenticationSettings: () => (/* binding */ createAuthenticationSettings),
|
/* harmony export */ createAuthenticationSettings: () => (/* binding */ createAuthenticationSettings),
|
||||||
/* harmony export */ generate: () => (/* binding */ generate),
|
/* 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() {
|
async function configureAuthentication() {
|
||||||
const servers = getMavenServerSettings();
|
const servers = getMavenServerSettings();
|
||||||
const repositorySettings = getMavenRepositorySettings();
|
const repositorySettings = getMavenRepositorySettings();
|
||||||
@@ -74,16 +76,16 @@ function getInputWithDeprecatedAlias(inputName, deprecatedInputName, defaultValu
|
|||||||
// only exported for testing purposes
|
// only exported for testing purposes
|
||||||
function getMavenServerSettings() {
|
function getMavenServerSettings() {
|
||||||
const entries = _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getMultilineInput */ .q3(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_SERVER_CREDENTIALS */ .MM);
|
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())) {
|
const servers = entries.some(entry => entry.trim())
|
||||||
return parseMavenServerCredentials(entries);
|
? parseMavenServerCredentials(entries)
|
||||||
}
|
: [
|
||||||
return [
|
|
||||||
{
|
{
|
||||||
id: _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getInput */ .V4(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_ID */ .fd),
|
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),
|
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)
|
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
|
// only exported for testing purposes
|
||||||
function parseMavenServerCredentials(entries) {
|
function parseMavenServerCredentials(entries) {
|
||||||
@@ -110,20 +112,78 @@ function parseMavenServerCredentials(entries) {
|
|||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
// only exported for testing purposes
|
// 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() {
|
function getMavenRepositorySettings() {
|
||||||
const entries = _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getMultilineInput */ .q3(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_REPOSITORIES */ .W2);
|
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;
|
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);
|
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 {
|
return {
|
||||||
repositories: parseMavenRepositories(entries, includeCentral),
|
repositories: parseMavenRepositories(entries, includeCentral),
|
||||||
|
pluginRepositories: parseMavenRepositories(pluginEntries, includeCentral, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_PLUGIN_REPOSITORIES */ .gR),
|
||||||
includeCentral,
|
includeCentral,
|
||||||
prioritizeCentral: (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getBooleanInput */ .Vt)(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL */ .OT, true)
|
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
|
// 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 repositories = [];
|
||||||
const repositoryIds = new Set();
|
const repositoryIds = new Set();
|
||||||
entries.forEach((entry, index) => {
|
entries.forEach((entry, index) => {
|
||||||
@@ -133,7 +193,7 @@ function parseMavenRepositories(entries, includeCentral) {
|
|||||||
const firstSeparator = entry.indexOf(':');
|
const firstSeparator = entry.indexOf(':');
|
||||||
const lastSeparator = entry.lastIndexOf(':');
|
const lastSeparator = entry.lastIndexOf(':');
|
||||||
if (firstSeparator <= 0 || lastSeparator <= firstSeparator) {
|
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 id = entry.slice(0, firstSeparator).trim();
|
||||||
const url = entry.slice(firstSeparator + 1, lastSeparator).trim();
|
const url = entry.slice(firstSeparator + 1, lastSeparator).trim();
|
||||||
@@ -142,13 +202,13 @@ function parseMavenRepositories(entries, includeCentral) {
|
|||||||
.trim()
|
.trim()
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
if (!id || !url || !snapshotsValue) {
|
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') {
|
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)) {
|
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) {
|
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`);
|
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;
|
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) {
|
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(', ')}`);
|
_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)
|
// when an alternate m2 location is specified use only that location (no .m2 directory)
|
||||||
@@ -187,39 +278,21 @@ function generate(servers, gpgPassphraseEnvVar, repositorySettings) {
|
|||||||
' <servers>'
|
' <servers>'
|
||||||
];
|
];
|
||||||
for (const server of 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>');
|
lines.push(' </servers>');
|
||||||
if (repositorySettings || includeGpgPassphraseProfile) {
|
if (repositorySettings || includeGpgPassphraseProfile) {
|
||||||
lines.push(' <profiles>');
|
lines.push(' <profiles>');
|
||||||
if (repositorySettings) {
|
if (repositorySettings) {
|
||||||
const centralRepository = {
|
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>');
|
||||||
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>');
|
|
||||||
}
|
}
|
||||||
if (includeGpgPassphraseProfile) {
|
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>');
|
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>');
|
||||||
|
|||||||
100
dist/setup/824.index.js
vendored
100
dist/setup/824.index.js
vendored
@@ -418,9 +418,102 @@ function readAttributeStr(xmlData, i) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select all the attributes whether valid or invalid.
|
* Walk `attrStr` once, left to right, splitting it into attribute tokens.
|
||||||
|
*
|
||||||
|
* This replaces a regex that used to do the same job
|
||||||
|
* (`(\s*)([^\s=]+)(\s*=)?(\s*(['"])(([\s\S])*?)\5)?`). That regex led with an
|
||||||
|
* optional whitespace group followed by a required "non-whitespace" group.
|
||||||
|
* On a long run of whitespace that never resolves into an attribute name
|
||||||
|
* (e.g. a tag with thousands of trailing spaces before `>`), the engine
|
||||||
|
* backtracks the whitespace group one character at a time before giving up
|
||||||
|
* and moving to the next starting position — one full backtrack per
|
||||||
|
* position, which is quadratic in the length of the run.
|
||||||
|
*
|
||||||
|
* A single forward-only scan can never backtrack, so it can't be made slow
|
||||||
|
* this way no matter how much whitespace the input contains — it's always
|
||||||
|
* proportional to the length of the string, once.
|
||||||
|
*
|
||||||
|
* Each returned token mirrors the shape the old regex match array had, so
|
||||||
|
* the validation logic below (which reads token[1]..token[6]) didn't need
|
||||||
|
* to change:
|
||||||
|
* token.startIndex - where this token begins in attrStr
|
||||||
|
* token[1] - leading whitespace before the name
|
||||||
|
* token[2] - the attribute name
|
||||||
|
* token[3] - whitespace + '=' if present, else undefined
|
||||||
|
* token[4] - marker (any defined value) if a quoted value was found
|
||||||
|
* token[5] - the quote character used ('"' or "'")
|
||||||
|
* token[6] - the value's text, without the surrounding quotes
|
||||||
|
*
|
||||||
|
* A malformed leading character (e.g. a stray '=' with no name before it)
|
||||||
|
* is simply skipped over, one character at a time — the same outcome the
|
||||||
|
* old regex produced by failing to match at that position and retrying at
|
||||||
|
* the next one.
|
||||||
*/
|
*/
|
||||||
const validAttrStrRegxp = new RegExp('(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s\\S])*?)\\5)?', 'g');
|
function scanAttributeTokens(attrStr) {
|
||||||
|
const tokens = [];
|
||||||
|
const len = attrStr.length;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < len) {
|
||||||
|
const tokenStart = i;
|
||||||
|
|
||||||
|
// Leading whitespace before the name.
|
||||||
|
while (i < len && isWhiteSpace(attrStr[i])) i++;
|
||||||
|
if (i >= len) break; // trailing whitespace only — nothing left to read
|
||||||
|
|
||||||
|
if (attrStr[i] === '=') {
|
||||||
|
// No name before this '=' — not a valid attribute start. Move past
|
||||||
|
// just this one character and try again from the next position.
|
||||||
|
i = tokenStart + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const leadingWs = attrStr.slice(tokenStart, i);
|
||||||
|
|
||||||
|
// Attribute name — everything up to the next whitespace or '='.
|
||||||
|
const nameStart = i;
|
||||||
|
while (i < len && !isWhiteSpace(attrStr[i]) && attrStr[i] !== '=') i++;
|
||||||
|
const name = attrStr.slice(nameStart, i);
|
||||||
|
|
||||||
|
// Optional whitespace + '='.
|
||||||
|
let equalsGroup; // whitespace + '=' text, or undefined if absent
|
||||||
|
let j = i;
|
||||||
|
while (j < len && isWhiteSpace(attrStr[j])) j++;
|
||||||
|
if (j < len && attrStr[j] === '=') {
|
||||||
|
equalsGroup = attrStr.slice(i, j + 1);
|
||||||
|
i = j + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional whitespace + quoted value.
|
||||||
|
let quoteChar;
|
||||||
|
let value;
|
||||||
|
let k = i;
|
||||||
|
while (k < len && isWhiteSpace(attrStr[k])) k++;
|
||||||
|
if (k < len && (attrStr[k] === '"' || attrStr[k] === "'")) {
|
||||||
|
const valueStart = k + 1;
|
||||||
|
const closeIdx = attrStr.indexOf(attrStr[k], valueStart);
|
||||||
|
if (closeIdx !== -1) {
|
||||||
|
quoteChar = attrStr[k];
|
||||||
|
value = attrStr.slice(valueStart, closeIdx);
|
||||||
|
i = closeIdx + 1;
|
||||||
|
}
|
||||||
|
// No closing quote found anywhere in the rest of the string — leave
|
||||||
|
// quoteChar/value undefined, same as the old regex's group failing
|
||||||
|
// to match a backreference-less run.
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = { startIndex: tokenStart };
|
||||||
|
token[1] = leadingWs;
|
||||||
|
token[2] = name;
|
||||||
|
token[3] = equalsGroup;
|
||||||
|
token[4] = quoteChar !== undefined ? true : undefined;
|
||||||
|
token[5] = quoteChar;
|
||||||
|
token[6] = value;
|
||||||
|
tokens.push(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
//attr, ="sd", a="amit's", a="sd"b="saf", ab cd=""
|
//attr, ="sd", a="amit's", a="sd"b="saf", ab cd=""
|
||||||
|
|
||||||
@@ -429,7 +522,7 @@ function validateAttributeString(attrStr, options) {
|
|||||||
|
|
||||||
//if(attrStr.trim().length === 0) return true; //empty string
|
//if(attrStr.trim().length === 0) return true; //empty string
|
||||||
|
|
||||||
const matches = (0,_util_js__WEBPACK_IMPORTED_MODULE_0__/* .getAllMatches */ .Xe)(attrStr, validAttrStrRegxp);
|
const matches = scanAttributeTokens(attrStr);
|
||||||
const attrNames = {};
|
const attrNames = {};
|
||||||
|
|
||||||
for (let i = 0; i < matches.length; i++) {
|
for (let i = 0; i < matches.length; i++) {
|
||||||
@@ -532,7 +625,6 @@ function getPositionFromMatch(match) {
|
|||||||
return match.startIndex + match[1].length;
|
return match.startIndex + match[1].length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 6009:
|
/***/ 6009:
|
||||||
|
|||||||
80
dist/setup/971.index.js
vendored
80
dist/setup/971.index.js
vendored
@@ -52431,6 +52431,24 @@ const EXPANSION_MAX = 100_000;
|
|||||||
// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
|
// realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M
|
||||||
// characters) so legitimate input is unaffected.
|
// characters) so legitimate input is unaffected.
|
||||||
const EXPANSION_MAX_LENGTH = 4_000_000;
|
const EXPANSION_MAX_LENGTH = 4_000_000;
|
||||||
|
// `expand_` recurses once per level of brace *nesting* - both when expanding a
|
||||||
|
// set's comma members and when re-wrapping a set whose body is a single part.
|
||||||
|
// The CVE-2026-14257 fix made the *tail* iterative (recursion on `m.post`, one
|
||||||
|
// level per chained group), which left nesting depth unbounded: about 3,100
|
||||||
|
// levels of `{{{...a,b...}}}` - only ~6KB of input - exhausted the native stack
|
||||||
|
// and crashed the process. `EXPANSION_MAX_DEPTH` bounds how deep the parser
|
||||||
|
// will follow nesting. It sits far above any realistic pattern and well below
|
||||||
|
// the depth at which the stack runs out.
|
||||||
|
const EXPANSION_MAX_DEPTH = 1_000;
|
||||||
|
// Bash keeps a quirk where a brace group followed by a comma set still expands
|
||||||
|
// (`{a},b}`). The parser implements it by rewriting the string and restarting
|
||||||
|
// the scan, absorbing one `}` per pass. `n` trailing braces therefore cost `n`
|
||||||
|
// full passes over a string that itself grows by one `escClose` sentinel each
|
||||||
|
// time - quadratic in `n`, with a ~26x constant from the sentinel's length.
|
||||||
|
// 128KB of `'{a}' + '}'.repeat(n) + ',z}'` blocked the event loop for 27
|
||||||
|
// seconds to produce two results. `EXPANSION_MAX_REWRITES` bounds how many
|
||||||
|
// times the scan may restart. Real `{a},b}` input needs a handful.
|
||||||
|
const EXPANSION_MAX_REWRITES = 1_000;
|
||||||
function numeric(str) {
|
function numeric(str) {
|
||||||
return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
|
return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0);
|
||||||
}
|
}
|
||||||
@@ -52450,37 +52468,52 @@ function unescapeBraces(str) {
|
|||||||
.replace(escCommaPattern, ',')
|
.replace(escCommaPattern, ',')
|
||||||
.replace(escPeriodPattern, '.');
|
.replace(escPeriodPattern, '.');
|
||||||
}
|
}
|
||||||
|
// Like `target.push(...items)` but doesn't overflow the stack
|
||||||
|
function pushAll(target, items) {
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
target.push(items[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Basically just str.split(","), but handling cases
|
* Basically just str.split(","), but handling cases
|
||||||
* where we have nested braced sections, which should be
|
* where we have nested braced sections, which should be
|
||||||
* treated as individual members, like {a,{b,c},d}
|
* treated as individual members, like {a,{b,c},d}
|
||||||
*/
|
*/
|
||||||
function parseCommaParts(str) {
|
function parseCommaParts(str) {
|
||||||
if (!str) {
|
|
||||||
return [''];
|
|
||||||
}
|
|
||||||
const parts = [];
|
const parts = [];
|
||||||
|
// 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);
|
const m = balanced('{', '}', str);
|
||||||
if (!m) {
|
if (!m) {
|
||||||
return str.split(',');
|
const tail = str.split(',');
|
||||||
|
tail[0] = carry + tail[0];
|
||||||
|
pushAll(parts, tail);
|
||||||
|
return parts;
|
||||||
}
|
}
|
||||||
const { pre, body, post } = m;
|
const { pre, body, post } = m;
|
||||||
const p = pre.split(',');
|
const p = pre.split(',');
|
||||||
|
p[0] = carry + p[0];
|
||||||
p[p.length - 1] += '{' + body + '}';
|
p[p.length - 1] += '{' + body + '}';
|
||||||
const postParts = parseCommaParts(post);
|
if (!post.length) {
|
||||||
if (post.length) {
|
pushAll(parts, p);
|
||||||
;
|
|
||||||
p[p.length - 1] += postParts.shift();
|
|
||||||
p.push.apply(p, postParts);
|
|
||||||
}
|
|
||||||
parts.push.apply(parts, p);
|
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
|
carry = p.pop();
|
||||||
|
pushAll(parts, p);
|
||||||
|
str = post;
|
||||||
|
}
|
||||||
|
}
|
||||||
function expand(str, options = {}) {
|
function expand(str, options = {}) {
|
||||||
if (!str) {
|
if (!str) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH } = options;
|
const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH, maxDepth = EXPANSION_MAX_DEPTH, maxRewrites = EXPANSION_MAX_REWRITES, } = options;
|
||||||
// I don't know why Bash 4.3 does this, but it does.
|
// I don't know why Bash 4.3 does this, but it does.
|
||||||
// Anything starting with {} will have the first two bytes preserved
|
// Anything starting with {} will have the first two bytes preserved
|
||||||
// but *only* at the top level, so {},a}b will not expand to anything,
|
// but *only* at the top level, so {},a}b will not expand to anything,
|
||||||
@@ -52490,7 +52523,7 @@ function expand(str, options = {}) {
|
|||||||
if (str.slice(0, 2) === '{}') {
|
if (str.slice(0, 2) === '{}') {
|
||||||
str = '\\{\\}' + str.slice(2);
|
str = '\\{\\}' + str.slice(2);
|
||||||
}
|
}
|
||||||
return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces);
|
return expand_(escapeBraces(str), max, maxLength, maxDepth, 0, maxRewrites, true).map(unescapeBraces);
|
||||||
}
|
}
|
||||||
function embrace(str) {
|
function embrace(str) {
|
||||||
return '{' + str + '}';
|
return '{' + str + '}';
|
||||||
@@ -52585,7 +52618,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) {
|
|||||||
}
|
}
|
||||||
return N;
|
return N;
|
||||||
}
|
}
|
||||||
function expand_(str, max, maxLength, isTop) {
|
function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) {
|
||||||
|
// Too deeply nested to keep following: treat the rest as literal, the same
|
||||||
|
// way a group that cannot expand is already handled. Truncating rather than
|
||||||
|
// throwing keeps `expand` total, matching `max` and `maxLength`.
|
||||||
|
if (depth > maxDepth) {
|
||||||
|
return [str];
|
||||||
|
}
|
||||||
// Consume the string's top-level brace groups left to right, threading a
|
// Consume the string's top-level brace groups left to right, threading a
|
||||||
// running set of combined prefixes (`acc`). Expanding the tail iteratively -
|
// running set of combined prefixes (`acc`). Expanding the tail iteratively -
|
||||||
// rather than recursing on `m.post` once per group - keeps the native stack
|
// rather than recursing on `m.post` once per group - keeps the native stack
|
||||||
@@ -52597,6 +52636,9 @@ function expand_(str, max, maxLength, isTop) {
|
|||||||
// comma set - a sequence like `{a..\}` may legitimately yield ''. The drop
|
// comma set - a sequence like `{a..\}` may legitimately yield ''. The drop
|
||||||
// is on the final strings, so it is applied to whichever `combine` produces
|
// is on the final strings, so it is applied to whichever `combine` produces
|
||||||
// them (the one with no brace set left in the tail).
|
// them (the one with no brace set left in the tail).
|
||||||
|
// How many times the `{a},b}` rewrite below has restarted the scan. Each pass
|
||||||
|
// re-reads the whole string, so leaving this unbounded is quadratic.
|
||||||
|
let rewrites = 0;
|
||||||
let dropEmpties = false;
|
let dropEmpties = false;
|
||||||
let firstGroup = true;
|
let firstGroup = true;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
@@ -52621,7 +52663,8 @@ function expand_(str, max, maxLength, isTop) {
|
|||||||
const isOptions = m.body.indexOf(',') >= 0;
|
const isOptions = m.body.indexOf(',') >= 0;
|
||||||
if (!isSequence && !isOptions) {
|
if (!isSequence && !isOptions) {
|
||||||
// {a},b}
|
// {a},b}
|
||||||
if (m.post.match(/,(?!,).*\}/)) {
|
if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) {
|
||||||
|
rewrites++;
|
||||||
str = m.pre + '{' + m.body + escClose + m.post;
|
str = m.pre + '{' + m.body + escClose + m.post;
|
||||||
isTop = true;
|
isTop = true;
|
||||||
continue;
|
continue;
|
||||||
@@ -52641,7 +52684,7 @@ function expand_(str, max, maxLength, isTop) {
|
|||||||
let n = parseCommaParts(m.body);
|
let n = parseCommaParts(m.body);
|
||||||
if (n.length === 1 && n[0] !== undefined) {
|
if (n.length === 1 && n[0] !== undefined) {
|
||||||
// x{{a,b}}y ==> x{a}y x{b}y
|
// x{{a,b}}y ==> x{a}y x{b}y
|
||||||
n = expand_(n[0], max, maxLength, false).map(embrace);
|
n = expand_(n[0], max, maxLength, maxDepth, depth + 1, maxRewrites, false).map(embrace);
|
||||||
//XXX is this necessary? Can't seem to hit it in tests.
|
//XXX is this necessary? Can't seem to hit it in tests.
|
||||||
/* c8 ignore start */
|
/* c8 ignore start */
|
||||||
if (n.length === 1) {
|
if (n.length === 1) {
|
||||||
@@ -52667,12 +52710,13 @@ function expand_(str, max, maxLength, isTop) {
|
|||||||
values = [];
|
values = [];
|
||||||
let valuesLength = 0;
|
let valuesLength = 0;
|
||||||
outer: for (let j = 0; j < n.length; j++) {
|
outer: for (let j = 0; j < n.length; j++) {
|
||||||
const expanded = expand_(n[j], max, maxLength, false);
|
const expanded = expand_(n[j], max, maxLength, maxDepth, depth + 1, maxRewrites, false);
|
||||||
for (let k = 0; k < expanded.length; k++) {
|
for (let k = 0; k < expanded.length; k++) {
|
||||||
const v = expanded[k];
|
const v = expanded[k];
|
||||||
if (dropsEmpties && !v)
|
if (dropsEmpties && !v)
|
||||||
continue;
|
continue;
|
||||||
if (values.length >= max || valuesLength + v.length > maxLength) {
|
if (values.length >= max ||
|
||||||
|
valuesLength + v.length > maxLength) {
|
||||||
break outer;
|
break outer;
|
||||||
}
|
}
|
||||||
values.push(v);
|
values.push(v);
|
||||||
|
|||||||
52
dist/setup/978.index.js
vendored
52
dist/setup/978.index.js
vendored
@@ -13,19 +13,26 @@ export const modules = {
|
|||||||
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__);
|
/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__);
|
||||||
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9896);
|
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9896);
|
||||||
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__);
|
/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__);
|
||||||
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2088);
|
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6242);
|
||||||
/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_3__);
|
/* harmony import */ var _platform_types_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7444);
|
||||||
/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6242);
|
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4527);
|
||||||
/* harmony import */ var _platform_types_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(7444);
|
|
||||||
/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4527);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function compareNumberArrays(a, b) {
|
||||||
class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* .JavaBase */ .O {
|
const length = Math.max(a.length, b.length);
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const diff = (a[i] ?? 0) - (b[i] ?? 0);
|
||||||
|
if (diff !== 0) {
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaBase */ .O {
|
||||||
constructor(installerOptions) {
|
constructor(installerOptions) {
|
||||||
super('Zulu', installerOptions);
|
super('Zulu', installerOptions);
|
||||||
}
|
}
|
||||||
@@ -39,19 +46,24 @@ class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/*
|
|||||||
? [...item.java_version, item.openjdk_build_number]
|
? [...item.java_version, item.openjdk_build_number]
|
||||||
: item.java_version;
|
: item.java_version;
|
||||||
return {
|
return {
|
||||||
version: (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .convertVersionToSemver */ .ZY)(javaVersion),
|
version: (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .convertVersionToSemver */ .ZY)(javaVersion),
|
||||||
url: item.download_url,
|
url: item.download_url,
|
||||||
zuluVersion: (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .convertVersionToSemver */ .ZY)(item.distro_version),
|
javaVersion: item.java_version,
|
||||||
|
buildNumber: item.openjdk_build_number ?? 0,
|
||||||
|
distroVersion: item.distro_version,
|
||||||
packageUuid: item.package_uuid
|
packageUuid: item.package_uuid
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
const satisfiedVersions = availableVersions
|
const satisfiedVersions = availableVersions
|
||||||
.filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .isVersionSatisfies */ .y)(version, item.version))
|
.filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .isVersionSatisfies */ .y)(version, item.version))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
// Azul provides two versions: java_version and distro_version
|
// Compare numerically rather than via semver build metadata: Azul
|
||||||
// we should sort by both fields by descending
|
// hotfix releases carry a 4th java_version segment (e.g. 25.0.4.1+1,
|
||||||
return (-semver__WEBPACK_IMPORTED_MODULE_3___default().compareBuild(a.version, b.version) ||
|
// rendered as '25.0.4+1.1') that must rank above 25.0.4+7, whereas
|
||||||
-semver__WEBPACK_IMPORTED_MODULE_3___default().compareBuild(a.zuluVersion, b.zuluVersion));
|
// semver.compareBuild would order the build identifiers '7' > '1'.
|
||||||
|
return (-compareNumberArrays(a.javaVersion, b.javaVersion) ||
|
||||||
|
b.buildNumber - a.buildNumber ||
|
||||||
|
-compareNumberArrays(a.distroVersion, b.distroVersion));
|
||||||
})
|
})
|
||||||
.map((item) => ({
|
.map((item) => ({
|
||||||
version: item.version,
|
version: item.version,
|
||||||
@@ -85,21 +97,21 @@ class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/*
|
|||||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
|
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`);
|
||||||
let javaArchivePath = await this.downloadAndVerify(javaRelease);
|
let javaArchivePath = await this.downloadAndVerify(javaRelease);
|
||||||
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`);
|
_actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`);
|
||||||
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getDownloadArchiveExtension */ .ag)();
|
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getDownloadArchiveExtension */ .ag)();
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .renameWinArchive */ .n2)(javaArchivePath);
|
javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .renameWinArchive */ .n2)(javaArchivePath);
|
||||||
}
|
}
|
||||||
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
|
const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .extractJdkFile */ .PE)(javaArchivePath, extension);
|
||||||
const archiveName = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath)[0];
|
const archiveName = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath)[0];
|
||||||
const archivePath = path__WEBPACK_IMPORTED_MODULE_1___default().join(extractedJavaPath, archiveName);
|
const archivePath = path__WEBPACK_IMPORTED_MODULE_1___default().join(extractedJavaPath, archiveName);
|
||||||
const javaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .cacheJdkDir */ .Vj)(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture);
|
const javaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .cacheJdkDir */ .Vj)(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture);
|
||||||
return { version: javaRelease.version, path: javaPath };
|
return { version: javaRelease.version, path: javaPath };
|
||||||
}
|
}
|
||||||
async getAvailableVersions() {
|
async getAvailableVersions() {
|
||||||
const arch = this.getArchitectureOptions();
|
const arch = this.getArchitectureOptions();
|
||||||
const [bundleType, features] = this.packageType.split('+');
|
const [bundleType, features] = this.packageType.split('+');
|
||||||
const platform = this.getPlatformOption();
|
const platform = this.getPlatformOption();
|
||||||
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getDownloadArchiveExtension */ .ag)();
|
const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getDownloadArchiveExtension */ .ag)();
|
||||||
const javafx = features?.includes('fx') ?? false;
|
const javafx = features?.includes('fx') ?? false;
|
||||||
const crac = features?.includes('crac') ?? false;
|
const crac = features?.includes('crac') ?? false;
|
||||||
const releaseStatus = this.stable ? 'ga' : 'ea';
|
const releaseStatus = this.stable ? 'ga' : 'ea';
|
||||||
@@ -185,7 +197,7 @@ class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/*
|
|||||||
// The new Metadata API's "linux" value returns both glibc and musl
|
// The new Metadata API's "linux" value returns both glibc and musl
|
||||||
// packages, so target the libc the runner actually has. A glibc JDK
|
// packages, so target the libc the runner actually has. A glibc JDK
|
||||||
// cannot run on Alpine.
|
// cannot run on Alpine.
|
||||||
return (0,_platform_types_js__WEBPACK_IMPORTED_MODULE_5__/* .isAlpineLinux */ .G6)() ? 'linux_musl' : 'linux_glibc';
|
return (0,_platform_types_js__WEBPACK_IMPORTED_MODULE_4__/* .isAlpineLinux */ .G6)() ? 'linux_musl' : 'linux_glibc';
|
||||||
default:
|
default:
|
||||||
return process.platform;
|
return process.platform;
|
||||||
}
|
}
|
||||||
|
|||||||
650
dist/setup/index.js
vendored
650
dist/setup/index.js
vendored
@@ -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.aborted)
|
||||||
assert(!this.completed)
|
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) {
|
onComplete (trailers) {
|
||||||
@@ -7912,7 +7978,7 @@ class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onUpgrade (head) {
|
onUpgrade (head) {
|
||||||
const { upgrade, client, socket, headers, statusCode } = this
|
const { upgrade, client, socket, headers, statusCode, statusText } = this
|
||||||
|
|
||||||
assert(upgrade)
|
assert(upgrade)
|
||||||
assert(client[kSocket] === socket)
|
assert(client[kSocket] === socket)
|
||||||
@@ -7947,9 +8013,10 @@ class Parser {
|
|||||||
client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade'))
|
client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade'))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
request.onUpgrade(statusCode, headers, socket)
|
request.onUpgrade(statusCode, headers, socket, statusText)
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
util.destroy(socket, err)
|
util.errorRequest(client, request, error)
|
||||||
|
util.destroy(socket, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
client[kResume]()
|
client[kResume]()
|
||||||
@@ -8356,7 +8423,7 @@ async function connectH1 (client, socket) {
|
|||||||
|
|
||||||
function clearIdleSocketValidation (socket) {
|
function clearIdleSocketValidation (socket) {
|
||||||
if (socket[kIdleSocketValidationTimeout]) {
|
if (socket[kIdleSocketValidationTimeout]) {
|
||||||
clearTimeout(socket[kIdleSocketValidationTimeout])
|
clearImmediate(socket[kIdleSocketValidationTimeout])
|
||||||
socket[kIdleSocketValidationTimeout] = null
|
socket[kIdleSocketValidationTimeout] = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8365,15 +8432,23 @@ function clearIdleSocketValidation (socket) {
|
|||||||
|
|
||||||
function scheduleIdleSocketValidation (client, socket) {
|
function scheduleIdleSocketValidation (client, socket) {
|
||||||
socket[kIdleSocketValidation] = 1
|
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[kIdleSocketValidationTimeout] = null
|
||||||
socket[kIdleSocketValidation] = 2
|
socket[kIdleSocketValidation] = 2
|
||||||
|
|
||||||
if (client[kSocket] === socket && !socket.destroyed) {
|
if (client[kSocket] === socket && !socket.destroyed) {
|
||||||
client[kResume]()
|
client[kResume]()
|
||||||
}
|
}
|
||||||
}, 0)
|
})
|
||||||
socket[kIdleSocketValidationTimeout].unref?.()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -8522,12 +8597,22 @@ function writeH1 (client, request) {
|
|||||||
const socket = client[kSocket]
|
const socket = client[kSocket]
|
||||||
clearIdleSocketValidation(socket)
|
clearIdleSocketValidation(socket)
|
||||||
|
|
||||||
const abort = (err) => {
|
/**
|
||||||
if (request.aborted || request.completed) {
|
* @param {Error} [error]
|
||||||
|
*/
|
||||||
|
const abort = (error) => {
|
||||||
|
if (request.aborted) {
|
||||||
return
|
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(body)
|
||||||
util.destroy(socket, new InformationalError('aborted'))
|
util.destroy(socket, new InformationalError('aborted'))
|
||||||
@@ -8984,6 +9069,7 @@ module.exports = connectH1
|
|||||||
|
|
||||||
|
|
||||||
const assert = __nccwpck_require__(4589)
|
const assert = __nccwpck_require__(4589)
|
||||||
|
const { errorMonitor } = __nccwpck_require__(8474)
|
||||||
const { pipeline } = __nccwpck_require__(7075)
|
const { pipeline } = __nccwpck_require__(7075)
|
||||||
const util = __nccwpck_require__(3440)
|
const util = __nccwpck_require__(3440)
|
||||||
const {
|
const {
|
||||||
@@ -9060,6 +9146,15 @@ function parseH2Headers (headers) {
|
|||||||
return result
|
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) {
|
async function connectH2 (client, socket) {
|
||||||
client[kSocket] = socket
|
client[kSocket] = socket
|
||||||
|
|
||||||
@@ -9280,22 +9375,32 @@ function writeH2 (client, request) {
|
|||||||
headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
|
headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}`
|
||||||
headers[HTTP2_HEADER_METHOD] = method
|
headers[HTTP2_HEADER_METHOD] = method
|
||||||
|
|
||||||
const abort = (err) => {
|
/**
|
||||||
if (request.aborted || request.completed) {
|
* @param {Error} [error]
|
||||||
|
*/
|
||||||
|
const abort = (error) => {
|
||||||
|
if (request.aborted) {
|
||||||
return
|
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) {
|
if (stream != null) {
|
||||||
util.destroy(stream, err)
|
util.destroy(stream, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We do not destroy the socket as we can continue using the session
|
// 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
|
// 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[kQueue][client[kRunningIdx]++] = null
|
||||||
client[kResume]()
|
client[kResume]()
|
||||||
}
|
}
|
||||||
@@ -9314,25 +9419,57 @@ function writeH2 (client, request) {
|
|||||||
|
|
||||||
if (method === 'CONNECT') {
|
if (method === 'CONNECT') {
|
||||||
session.ref()
|
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
|
// We disabled endStream to allow the user to write to the stream
|
||||||
stream = session.request(headers, { endStream: false, signal })
|
stream = session.request(headers, { endStream: false, signal })
|
||||||
|
let upgradeResponseFinished = false
|
||||||
|
|
||||||
if (stream.id && !stream.pending) {
|
/**
|
||||||
request.onUpgrade(null, null, stream)
|
* @param {import('node:http2').IncomingHttpHeaders} headers
|
||||||
++session[kOpenStreams]
|
*/
|
||||||
client[kQueue][client[kRunningIdx]++] = null
|
const onResponse = (headers) => {
|
||||||
} else {
|
upgradeResponseFinished = true
|
||||||
stream.once('ready', () => {
|
stream.off(errorMonitor, onUpgradeError)
|
||||||
request.onUpgrade(null, null, stream)
|
request.onUpgradeResponse(Number(headers[HTTP2_HEADER_STATUS]), headers, parseH2ResponseHeaders)
|
||||||
++session[kOpenStreams]
|
|
||||||
client[kQueue][client[kRunningIdx]++] = null
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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', () => {
|
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
|
session[kOpenStreams] -= 1
|
||||||
if (session[kOpenStreams] === 0) session.unref()
|
if (session[kOpenStreams] === 0) session.unref()
|
||||||
})
|
})
|
||||||
@@ -12031,6 +12168,7 @@ class RetryHandler {
|
|||||||
this.end = null
|
this.end = null
|
||||||
this.etag = null
|
this.etag = null
|
||||||
this.resume = null
|
this.resume = null
|
||||||
|
this.headersSent = false
|
||||||
|
|
||||||
// Handle possible onConnect duplication
|
// Handle possible onConnect duplication
|
||||||
this.handler.onConnect(reason => {
|
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 () {
|
onRequestSent () {
|
||||||
if (this.handler.onRequestSent) {
|
if (this.handler.onRequestSent) {
|
||||||
this.handler.onRequestSent()
|
this.handler.onRequestSent()
|
||||||
@@ -12131,7 +12283,12 @@ class RetryHandler {
|
|||||||
this.retryCount += 1
|
this.retryCount += 1
|
||||||
|
|
||||||
if (statusCode >= 300) {
|
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(
|
return this.handler.onHeaders(
|
||||||
statusCode,
|
statusCode,
|
||||||
rawHeaders,
|
rawHeaders,
|
||||||
@@ -12200,8 +12357,15 @@ class RetryHandler {
|
|||||||
|
|
||||||
const { start, size, end = size - 1 } = contentRange
|
const { start, size, end = size - 1 } = contentRange
|
||||||
|
|
||||||
assert(this.start === start, 'content-range mismatch')
|
if (this.start !== start || (this.end != null && this.end !== end)) {
|
||||||
assert(this.end == null || this.end === end, 'content-range mismatch')
|
this.abort(
|
||||||
|
new RequestRetryError('Content-Range mismatch', statusCode, {
|
||||||
|
headers,
|
||||||
|
data: { count: this.retryCount }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
this.resume = resume
|
this.resume = resume
|
||||||
return true
|
return true
|
||||||
@@ -12213,6 +12377,7 @@ class RetryHandler {
|
|||||||
const range = parseRangeHeader(headers['content-range'])
|
const range = parseRangeHeader(headers['content-range'])
|
||||||
|
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
|
this.headersSent = true
|
||||||
return this.handler.onHeaders(
|
return this.handler.onHeaders(
|
||||||
statusCode,
|
statusCode,
|
||||||
rawHeaders,
|
rawHeaders,
|
||||||
@@ -12251,6 +12416,7 @@ class RetryHandler {
|
|||||||
)
|
)
|
||||||
|
|
||||||
this.resume = resume
|
this.resume = resume
|
||||||
|
this.headersSent = true
|
||||||
this.etag = headers.etag != null ? headers.etag : null
|
this.etag = headers.etag != null ? headers.etag : null
|
||||||
|
|
||||||
// Weak etags are not useful for comparison nor cache
|
// Weak etags are not useful for comparison nor cache
|
||||||
@@ -12290,7 +12456,7 @@ class RetryHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onError (err) {
|
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)
|
return this.handler.onError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16748,6 +16914,49 @@ const COLON = 0x3A
|
|||||||
*/
|
*/
|
||||||
const SPACE = 0x20
|
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
|
* @typedef {object} EventSourceStreamEvent
|
||||||
* @type {object}
|
* @type {object}
|
||||||
@@ -16788,11 +16997,14 @@ class EventSourceStream extends Transform {
|
|||||||
eventEndCheck = false
|
eventEndCheck = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Buffer}
|
* @type {Buffer[]}
|
||||||
*/
|
*/
|
||||||
buffer = null
|
chunks = []
|
||||||
|
|
||||||
|
chunkIndex = 0
|
||||||
pos = 0
|
pos = 0
|
||||||
|
lineChunkIndex = 0
|
||||||
|
linePos = 0
|
||||||
|
|
||||||
event = {
|
event = {
|
||||||
data: undefined,
|
data: undefined,
|
||||||
@@ -16831,92 +17043,20 @@ class EventSourceStream extends Transform {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the chunk in the buffer, as the data might not be complete while
|
this.chunks.push(chunk)
|
||||||
// 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Strip leading byte-order-mark if we opened the stream and started
|
// Strip leading byte-order-mark if we opened the stream and started
|
||||||
// the processing of the incoming data
|
// the processing of the incoming data
|
||||||
if (this.checkBOM) {
|
if (this.checkBOM) {
|
||||||
switch (this.buffer.length) {
|
if (this.handleBOM()) {
|
||||||
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()
|
callback()
|
||||||
return
|
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
|
while (this.hasCurrentByte()) {
|
||||||
// BOM anymore
|
const byte = this.currentByte()
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (this.pos < this.buffer.length) {
|
|
||||||
// If the previous line ended with an end-of-line, we need to check
|
// 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 the next character is also an end-of-line.
|
||||||
if (this.eventEndCheck) {
|
if (this.eventEndCheck) {
|
||||||
@@ -16929,10 +17069,9 @@ class EventSourceStream extends Transform {
|
|||||||
if (this.crlfCheck) {
|
if (this.crlfCheck) {
|
||||||
// If the current character is a line feed, we can remove it
|
// If the current character is a line feed, we can remove it
|
||||||
// from the buffer and reset the crlfCheck flag
|
// from the buffer and reset the crlfCheck flag
|
||||||
if (this.buffer[this.pos] === LF) {
|
if (byte === LF) {
|
||||||
this.buffer = this.buffer.subarray(this.pos + 1)
|
|
||||||
this.pos = 0
|
|
||||||
this.crlfCheck = false
|
this.crlfCheck = false
|
||||||
|
this.consumeCurrentByte()
|
||||||
|
|
||||||
// It is possible that the line feed is not the end of the
|
// It is possible that the line feed is not the end of the
|
||||||
// event. We need to check if the next character is an
|
// event. We need to check if the next character is an
|
||||||
@@ -16948,19 +17087,17 @@ class EventSourceStream extends Transform {
|
|||||||
this.crlfCheck = false
|
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
|
// If the current character is a carriage return, we need to
|
||||||
// set the crlfCheck flag to true, as we need to check if the
|
// 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
|
// next character is a line feed so we can remove it from the
|
||||||
// buffer
|
// buffer
|
||||||
if (this.buffer[this.pos] === CR) {
|
if (byte === CR) {
|
||||||
this.crlfCheck = true
|
this.crlfCheck = true
|
||||||
}
|
}
|
||||||
|
|
||||||
this.buffer = this.buffer.subarray(this.pos + 1)
|
this.consumeCurrentByte()
|
||||||
this.pos = 0
|
if (this.hasPendingEvent()) {
|
||||||
if (
|
|
||||||
this.event.data !== undefined || this.event.event || this.event.id || this.event.retry) {
|
|
||||||
this.processEvent(this.event)
|
this.processEvent(this.event)
|
||||||
}
|
}
|
||||||
this.clearEvent()
|
this.clearEvent()
|
||||||
@@ -16974,22 +17111,18 @@ class EventSourceStream extends Transform {
|
|||||||
|
|
||||||
// If the current character is an end-of-line, we can process the
|
// If the current character is an end-of-line, we can process the
|
||||||
// line
|
// 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
|
// If the current character is a carriage return, we need to
|
||||||
// set the crlfCheck flag to true, as we need to check if the
|
// set the crlfCheck flag to true, as we need to check if the
|
||||||
// next character is a line feed
|
// next character is a line feed
|
||||||
if (this.buffer[this.pos] === CR) {
|
if (byte === CR) {
|
||||||
this.crlfCheck = true
|
this.crlfCheck = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// In any case, we can process the line as we reached an
|
// In any case, we can process the line as we reached an
|
||||||
// end-of-line character
|
// end-of-line character
|
||||||
this.parseLine(this.buffer.subarray(0, this.pos), this.event)
|
this.parseLine(this.readLine(), this.event)
|
||||||
|
this.consumeCurrentByte()
|
||||||
// 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
|
|
||||||
// A line was processed and this could be the end of the event. We need
|
// 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
|
// to check if the next line is empty to determine if the event is
|
||||||
// finished.
|
// finished.
|
||||||
@@ -16997,7 +17130,7 @@ class EventSourceStream extends Transform {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pos++
|
this.advanceCursor()
|
||||||
}
|
}
|
||||||
|
|
||||||
callback()
|
callback()
|
||||||
@@ -17022,64 +17155,53 @@ class EventSourceStream extends Transform {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let field = ''
|
let fieldLength = line.length
|
||||||
let value = ''
|
let valueStart = line.length
|
||||||
|
|
||||||
// If the line contains a U+003A COLON character (:)
|
// If the line contains a U+003A COLON character (:)
|
||||||
if (colonPosition !== -1) {
|
if (colonPosition !== -1) {
|
||||||
// Collect the characters on the line before the first U+003A COLON
|
fieldLength = colonPosition
|
||||||
// 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')
|
|
||||||
|
|
||||||
// Collect the characters on the line after the first U+003A COLON
|
// Collect the characters on the line after the first U+003A COLON
|
||||||
// character (:), and let value be that string.
|
// character (:), and let value be that string.
|
||||||
// If value starts with a U+0020 SPACE character, remove it from value.
|
// If value starts with a U+0020 SPACE character, remove it from value.
|
||||||
let valueStart = colonPosition + 1
|
valueStart = colonPosition + 1
|
||||||
if (line[valueStart] === SPACE) {
|
if (line[valueStart] === SPACE) {
|
||||||
++valueStart
|
++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
|
if (isFieldName(line, fieldLength, DATA)) {
|
||||||
// decoded as UTF-8
|
const value = line.toString('utf8', valueStart)
|
||||||
switch (field) {
|
|
||||||
case 'data':
|
if (event.data === undefined) {
|
||||||
if (event[field] === undefined) {
|
event.data = value
|
||||||
event[field] = value
|
|
||||||
} else {
|
} else {
|
||||||
event[field] += `\n${value}`
|
event.data += `\n${value}`
|
||||||
}
|
}
|
||||||
break
|
return
|
||||||
case 'retry':
|
|
||||||
if (isASCIINumber(value)) {
|
|
||||||
event[field] = value
|
|
||||||
}
|
}
|
||||||
break
|
|
||||||
case 'id':
|
if (isFieldName(line, fieldLength, RETRY)) {
|
||||||
if (isValidLastEventId(value)) {
|
if (isASCIINumberBytes(line, valueStart)) {
|
||||||
event[field] = value
|
event.retry = line.toString('utf8', valueStart)
|
||||||
}
|
}
|
||||||
break
|
return
|
||||||
case 'event':
|
}
|
||||||
|
|
||||||
|
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) {
|
if (value.length > 0) {
|
||||||
event[field] = value
|
event.event = value
|
||||||
}
|
}
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17109,12 +17231,151 @@ class EventSourceStream extends Transform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clearEvent () {
|
clearEvent () {
|
||||||
this.event = {
|
this.event.data = undefined
|
||||||
data: undefined,
|
this.event.event = undefined
|
||||||
event: undefined,
|
this.event.id = undefined
|
||||||
id: undefined,
|
this.event.retry = undefined
|
||||||
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28383,7 +28644,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish,
|
|||||||
// is specified, the server needs to include the same field and one of
|
// is specified, the server needs to include the same field and one of
|
||||||
// the selected subprotocol values in its response for the connection to
|
// the selected subprotocol values in its response for the connection to
|
||||||
// be established.
|
// be established.
|
||||||
if (!requestProtocols.includes(secProtocol)) {
|
if (requestProtocols === null || !requestProtocols.includes(secProtocol)) {
|
||||||
failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
|
failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -29144,7 +29405,12 @@ class PerMessageDeflate {
|
|||||||
|
|
||||||
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
|
if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) {
|
||||||
callback(new MessageSizeExceededError())
|
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.removeAllListeners()
|
||||||
|
this.#inflate.destroy()
|
||||||
this.#inflate = null
|
this.#inflate = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -30780,6 +31046,7 @@ module.exports = {
|
|||||||
/* harmony export */ OD: () => (/* binding */ INPUT_DEFAULT_GPG_PRIVATE_KEY),
|
/* harmony export */ OD: () => (/* binding */ INPUT_DEFAULT_GPG_PRIVATE_KEY),
|
||||||
/* harmony export */ OT: () => (/* binding */ INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL),
|
/* harmony export */ OT: () => (/* binding */ INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL),
|
||||||
/* harmony export */ PG: () => (/* binding */ MACOS_JAVA_CONTENT_POSTFIX),
|
/* 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 */ QM: () => (/* binding */ INPUT_JAVA_VERSION),
|
||||||
/* harmony export */ RX: () => (/* binding */ INPUT_DEFAULT_GPG_PASSPHRASE),
|
/* harmony export */ RX: () => (/* binding */ INPUT_DEFAULT_GPG_PASSPHRASE),
|
||||||
/* harmony export */ TS: () => (/* binding */ INPUT_OVERWRITE_SETTINGS),
|
/* harmony export */ TS: () => (/* binding */ INPUT_OVERWRITE_SETTINGS),
|
||||||
@@ -30794,6 +31061,7 @@ module.exports = {
|
|||||||
/* harmony export */ af: () => (/* binding */ INPUT_JAVA_VERSION_FILE),
|
/* harmony export */ af: () => (/* binding */ INPUT_JAVA_VERSION_FILE),
|
||||||
/* harmony export */ db: () => (/* binding */ INPUT_GPG_PASSPHRASE_ENV_VAR),
|
/* harmony export */ db: () => (/* binding */ INPUT_GPG_PASSPHRASE_ENV_VAR),
|
||||||
/* harmony export */ fd: () => (/* binding */ INPUT_SERVER_ID),
|
/* harmony export */ fd: () => (/* binding */ INPUT_SERVER_ID),
|
||||||
|
/* harmony export */ gR: () => (/* binding */ INPUT_MVN_PLUGIN_REPOSITORIES),
|
||||||
/* harmony export */ g_: () => (/* binding */ INPUT_DISTRIBUTION),
|
/* harmony export */ g_: () => (/* binding */ INPUT_DISTRIBUTION),
|
||||||
/* harmony export */ gk: () => (/* binding */ INPUT_CACHE),
|
/* harmony export */ gk: () => (/* binding */ INPUT_CACHE),
|
||||||
/* harmony export */ hq: () => (/* binding */ MAVEN_REPOSITORIES_PROFILE_ID),
|
/* 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_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 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_CREDENTIALS = 'mvn-server-credentials';
|
||||||
|
const INPUT_MVN_SERVER_REPOSITORY_ORIGINS = 'mvn-server-repository-origins';
|
||||||
const INPUT_MVN_REPOSITORIES = 'mvn-repositories';
|
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_INCLUDE_CENTRAL = 'mvn-repositories-include-central';
|
||||||
const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL = 'mvn-repositories-prioritize-central';
|
const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL = 'mvn-repositories-prioritize-central';
|
||||||
const INPUT_SERVER_ID = 'server-id';
|
const INPUT_SERVER_ID = 'server-id';
|
||||||
@@ -30975,12 +31245,7 @@ function canResolveTemurinJmods(version) {
|
|||||||
if (normalizedVersion === 'latest') {
|
if (normalizedVersion === 'latest') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
let normalizedRange = normalizedVersion
|
const normalizedRange = (0,_util_js__WEBPACK_IMPORTED_MODULE_1__/* .normalizeJavaVersionToSemver */ .zZ)(normalizedVersion.replace(/-ea$/, '').replace('-ea.', '+'));
|
||||||
.replace(/-ea$/, '')
|
|
||||||
.replace('-ea.', '+');
|
|
||||||
if (/^\d+(\.\d+){3,}$/.test(normalizedRange)) {
|
|
||||||
normalizedRange = (0,_util_js__WEBPACK_IMPORTED_MODULE_1__/* .convertVersionToSemver */ .ZY)(normalizedRange);
|
|
||||||
}
|
|
||||||
if (!semver__WEBPACK_IMPORTED_MODULE_0___default().validRange(normalizedRange)) {
|
if (!semver__WEBPACK_IMPORTED_MODULE_0___default().validRange(normalizedRange)) {
|
||||||
// JavaBase owns general version validation and its targeted error messages.
|
// JavaBase owns general version validation and its targeted error messages.
|
||||||
return true;
|
return true;
|
||||||
@@ -31311,7 +31576,8 @@ function validateToolchainIds(versions, versionFile, toolchainIds) {
|
|||||||
/* harmony export */ rC: () => (/* binding */ getNextPageUrlFromLinkHeader),
|
/* harmony export */ rC: () => (/* binding */ getNextPageUrlFromLinkHeader),
|
||||||
/* harmony export */ ri: () => (/* binding */ getLatestMajorVersion),
|
/* harmony export */ ri: () => (/* binding */ getLatestMajorVersion),
|
||||||
/* harmony export */ y: () => (/* binding */ isVersionSatisfies),
|
/* harmony export */ y: () => (/* binding */ isVersionSatisfies),
|
||||||
/* harmony export */ yH: () => (/* binding */ getToolcachePath)
|
/* harmony export */ yH: () => (/* binding */ getToolcachePath),
|
||||||
|
/* harmony export */ zZ: () => (/* binding */ normalizeJavaVersionToSemver)
|
||||||
/* harmony export */ });
|
/* harmony export */ });
|
||||||
/* unused harmony exports getVersionFromToolcachePath, isJobStatusSuccess */
|
/* unused harmony exports getVersionFromToolcachePath, isJobStatusSuccess */
|
||||||
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(857);
|
/* harmony import */ var os__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(857);
|
||||||
@@ -31712,6 +31978,20 @@ function convertVersionToSemver(version) {
|
|||||||
}
|
}
|
||||||
return mainVersion;
|
return mainVersion;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Java versions (JEP 322) can contain more numeric fields than SemVer allows,
|
||||||
|
* e.g. '11.0.9.1' or Temurin respins such as '26.0.2.1+1'. Move the extra
|
||||||
|
* fields into SemVer build metadata ('11.0.9+1', '26.0.2+1.1'). Any other
|
||||||
|
* input (ranges, regular SemVer versions) is returned unchanged.
|
||||||
|
*/
|
||||||
|
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
|
* Builds a validator for the bytes currently served by a URL from the response
|
||||||
* headers of a HEAD request. A vendor's `/latest/` URL never changes, so this
|
* headers of a HEAD request. A vendor's `/latest/` URL never changes, so this
|
||||||
|
|||||||
@@ -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`.
|
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
|
```yaml
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v7
|
- uses: actions/checkout@v7
|
||||||
@@ -896,6 +901,9 @@ steps:
|
|||||||
mvn-server-credentials: |
|
mvn-server-credentials: |
|
||||||
releases:RELEASES_USERNAME:RELEASES_PASSWORD
|
releases:RELEASES_USERNAME:RELEASES_PASSWORD
|
||||||
snapshots:SNAPSHOTS_USERNAME:SNAPSHOTS_PASSWORD
|
snapshots:SNAPSHOTS_USERNAME:SNAPSHOTS_PASSWORD
|
||||||
|
mvn-server-repository-origins: |
|
||||||
|
releases:https://central.sonatype.com
|
||||||
|
snapshots:https://central.sonatype.com
|
||||||
- name: Publish with Maven
|
- name: Publish with Maven
|
||||||
run: mvn deploy
|
run: mvn deploy
|
||||||
env:
|
env:
|
||||||
@@ -913,11 +921,17 @@ This configuration produces the following server entries:
|
|||||||
<id>releases</id>
|
<id>releases</id>
|
||||||
<username>${env.RELEASES_USERNAME}</username>
|
<username>${env.RELEASES_USERNAME}</username>
|
||||||
<password>${env.RELEASES_PASSWORD}</password>
|
<password>${env.RELEASES_PASSWORD}</password>
|
||||||
|
<repositoryOrigins>
|
||||||
|
<repositoryOrigin>https://central.sonatype.com</repositoryOrigin>
|
||||||
|
</repositoryOrigins>
|
||||||
</server>
|
</server>
|
||||||
<server>
|
<server>
|
||||||
<id>snapshots</id>
|
<id>snapshots</id>
|
||||||
<username>${env.SNAPSHOTS_USERNAME}</username>
|
<username>${env.SNAPSHOTS_USERNAME}</username>
|
||||||
<password>${env.SNAPSHOTS_PASSWORD}</password>
|
<password>${env.SNAPSHOTS_PASSWORD}</password>
|
||||||
|
<repositoryOrigins>
|
||||||
|
<repositoryOrigin>https://central.sonatype.com</repositoryOrigin>
|
||||||
|
</repositoryOrigins>
|
||||||
</server>
|
</server>
|
||||||
</servers>
|
</servers>
|
||||||
```
|
```
|
||||||
@@ -989,14 +1003,92 @@ This configuration adds the following active profile to `settings.xml`:
|
|||||||
</activeProfiles>
|
</activeProfiles>
|
||||||
```
|
```
|
||||||
|
|
||||||
Maven Central is included first by default. Set
|
Maven Central is included first by default. The Central controls apply to both
|
||||||
`mvn-repositories-prioritize-central: false` to place custom repositories
|
dependency and plugin repositories. Set
|
||||||
first, or set `mvn-repositories-include-central: false` to disable Central. The
|
`mvn-repositories-prioritize-central: false` to place custom repositories first
|
||||||
generated profile overrides the Central repository inherited from Maven's Super
|
within each configured list, or set `mvn-repositories-include-central: false` to
|
||||||
POM with releases and snapshots disabled. When automatic Central inclusion is
|
disable Central for both types. The generated profile overrides the dependency
|
||||||
off, the ID `central` may instead be declared explicitly in `mvn-repositories`
|
and plugin Central repositories inherited from Maven's Super POM with releases
|
||||||
to replace it with a user-specified repository; otherwise that ID is reserved
|
and snapshots disabled, even if only one custom repository list is supplied.
|
||||||
to prevent duplicate entries.
|
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
|
### GPG
|
||||||
|
|
||||||
|
|||||||
1629
package-lock.json
generated
1629
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@@ -49,27 +49,27 @@
|
|||||||
"@actions/http-client": "^4.0.1",
|
"@actions/http-client": "^4.0.1",
|
||||||
"@actions/io": "^3.0.2",
|
"@actions/io": "^3.0.2",
|
||||||
"@actions/tool-cache": "^4.0.0",
|
"@actions/tool-cache": "^4.0.0",
|
||||||
"fast-xml-parser": "^5.11.0",
|
"fast-xml-parser": "^5.11.1",
|
||||||
"semver": "^7.8.5"
|
"semver": "^7.8.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@jest/globals": "^30.4.1",
|
"@jest/globals": "^30.5.2",
|
||||||
"@types/node": "^26.2.0",
|
"@types/node": "^26.6.2",
|
||||||
"@types/semver": "^7.8.0",
|
"@types/semver": "^7.8.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.67.0",
|
"@typescript-eslint/eslint-plugin": "^8.70.1",
|
||||||
"@typescript-eslint/parser": "^8.65.0",
|
"@typescript-eslint/parser": "^8.65.0",
|
||||||
"@vercel/ncc": "^0.45.0",
|
"@vercel/ncc": "^0.45.0",
|
||||||
"eslint": "^10.8.1",
|
"eslint": "^10.11.0",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"eslint-plugin-jest": "^29.16.1",
|
"eslint-plugin-jest": "^29.16.6",
|
||||||
"eslint-plugin-n": "^18.3.0",
|
"eslint-plugin-n": "^18.3.0",
|
||||||
"globals": "^17.11.0",
|
"globals": "^17.12.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jest": "^30.4.2",
|
"jest": "^30.5.2",
|
||||||
"lint-staged": "^17.3.0",
|
"lint-staged": "^17.5.1",
|
||||||
"prettier": "^3.9.5",
|
"prettier": "^3.9.9",
|
||||||
"ts-jest": "^29.4.11",
|
"ts-jest": "^29.4.13",
|
||||||
"typescript": "^6.0.3"
|
"typescript": "^6.0.3"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
|||||||
235
src/auth.ts
235
src/auth.ts
@@ -13,6 +13,7 @@ export interface MavenServerCredentials {
|
|||||||
id: string;
|
id: string;
|
||||||
usernameEnvVar: string;
|
usernameEnvVar: string;
|
||||||
passwordEnvVar: string;
|
passwordEnvVar: string;
|
||||||
|
repositoryOrigins?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MavenRepository {
|
export interface MavenRepository {
|
||||||
@@ -24,10 +25,13 @@ export interface MavenRepository {
|
|||||||
|
|
||||||
export interface MavenRepositorySettings {
|
export interface MavenRepositorySettings {
|
||||||
repositories: MavenRepository[];
|
repositories: MavenRepository[];
|
||||||
|
pluginRepositories?: MavenRepository[];
|
||||||
includeCentral: boolean;
|
includeCentral: boolean;
|
||||||
prioritizeCentral: boolean;
|
prioritizeCentral: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAVEN_REPOSITORY_ORIGIN = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#@]+\/?$/;
|
||||||
|
|
||||||
export async function configureAuthentication() {
|
export async function configureAuthentication() {
|
||||||
const servers = getMavenServerSettings();
|
const servers = getMavenServerSettings();
|
||||||
const repositorySettings = getMavenRepositorySettings();
|
const repositorySettings = getMavenRepositorySettings();
|
||||||
@@ -95,11 +99,9 @@ export function getMavenServerSettings(): MavenServerCredentials[] {
|
|||||||
constants.INPUT_MVN_SERVER_CREDENTIALS
|
constants.INPUT_MVN_SERVER_CREDENTIALS
|
||||||
);
|
);
|
||||||
|
|
||||||
if (entries.some(entry => entry.trim())) {
|
const servers = entries.some(entry => entry.trim())
|
||||||
return parseMavenServerCredentials(entries);
|
? parseMavenServerCredentials(entries)
|
||||||
}
|
: [
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
{
|
||||||
id: core.getInput(constants.INPUT_SERVER_ID),
|
id: core.getInput(constants.INPUT_SERVER_ID),
|
||||||
usernameEnvVar: getInputWithDeprecatedAlias(
|
usernameEnvVar: getInputWithDeprecatedAlias(
|
||||||
@@ -114,6 +116,11 @@ export function getMavenServerSettings(): MavenServerCredentials[] {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
return addMavenServerRepositoryOrigins(
|
||||||
|
servers,
|
||||||
|
core.getMultilineInput(constants.INPUT_MVN_SERVER_REPOSITORY_ORIGINS)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// only exported for testing purposes
|
// only exported for testing purposes
|
||||||
@@ -156,11 +163,96 @@ export function parseMavenServerCredentials(
|
|||||||
return servers;
|
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
|
// only exported for testing purposes
|
||||||
export function getMavenRepositorySettings():
|
export function getMavenRepositorySettings():
|
||||||
MavenRepositorySettings | undefined {
|
MavenRepositorySettings | undefined {
|
||||||
const entries = core.getMultilineInput(constants.INPUT_MVN_REPOSITORIES);
|
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;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,6 +262,11 @@ export function getMavenRepositorySettings():
|
|||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
repositories: parseMavenRepositories(entries, includeCentral),
|
repositories: parseMavenRepositories(entries, includeCentral),
|
||||||
|
pluginRepositories: parseMavenRepositories(
|
||||||
|
pluginEntries,
|
||||||
|
includeCentral,
|
||||||
|
constants.INPUT_MVN_PLUGIN_REPOSITORIES
|
||||||
|
),
|
||||||
includeCentral,
|
includeCentral,
|
||||||
prioritizeCentral: getBooleanInput(
|
prioritizeCentral: getBooleanInput(
|
||||||
constants.INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL,
|
constants.INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL,
|
||||||
@@ -181,7 +278,8 @@ export function getMavenRepositorySettings():
|
|||||||
// only exported for testing purposes
|
// only exported for testing purposes
|
||||||
export function parseMavenRepositories(
|
export function parseMavenRepositories(
|
||||||
entries: string[],
|
entries: string[],
|
||||||
includeCentral: boolean
|
includeCentral: boolean,
|
||||||
|
inputName = constants.INPUT_MVN_REPOSITORIES
|
||||||
): MavenRepository[] {
|
): MavenRepository[] {
|
||||||
const repositories: MavenRepository[] = [];
|
const repositories: MavenRepository[] = [];
|
||||||
const repositoryIds = new Set<string>();
|
const repositoryIds = new Set<string>();
|
||||||
@@ -195,7 +293,7 @@ export function parseMavenRepositories(
|
|||||||
const lastSeparator = entry.lastIndexOf(':');
|
const lastSeparator = entry.lastIndexOf(':');
|
||||||
if (firstSeparator <= 0 || lastSeparator <= firstSeparator) {
|
if (firstSeparator <= 0 || lastSeparator <= firstSeparator) {
|
||||||
throw new Error(
|
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();
|
.toLowerCase();
|
||||||
if (!id || !url || !snapshotsValue) {
|
if (!id || !url || !snapshotsValue) {
|
||||||
throw new Error(
|
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') {
|
if (snapshotsValue !== 'true' && snapshotsValue !== 'false') {
|
||||||
throw new Error(
|
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)) {
|
if (repositoryIds.has(id)) {
|
||||||
throw new Error(
|
throw new Error(`Duplicate repository-id '${id}' in ${inputName} input`);
|
||||||
`Duplicate repository-id '${id}' in mvn-repositories input`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (includeCentral && id === constants.MAVEN_CENTRAL_REPOSITORY_ID) {
|
if (includeCentral && id === constants.MAVEN_CENTRAL_REPOSITORY_ID) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -237,6 +333,56 @@ export function parseMavenRepositories(
|
|||||||
return repositories;
|
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(
|
export async function createAuthenticationSettings(
|
||||||
servers: MavenServerCredentials[],
|
servers: MavenServerCredentials[],
|
||||||
settingsDirectory: string,
|
settingsDirectory: string,
|
||||||
@@ -286,58 +432,39 @@ export function generate(
|
|||||||
' <server>',
|
' <server>',
|
||||||
` <id>${escapeXmlText(server.id)}</id>`,
|
` <id>${escapeXmlText(server.id)}</id>`,
|
||||||
` <username>${escapeXmlText(`\${env.${server.usernameEnvVar}}`)}</username>`,
|
` <username>${escapeXmlText(`\${env.${server.usernameEnvVar}}`)}</username>`,
|
||||||
` <password>${escapeXmlText(`\${env.${server.passwordEnvVar}}`)}</password>`,
|
` <password>${escapeXmlText(`\${env.${server.passwordEnvVar}}`)}</password>`
|
||||||
' </server>'
|
|
||||||
);
|
);
|
||||||
|
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>');
|
lines.push(' </servers>');
|
||||||
|
|
||||||
if (repositorySettings || includeGpgPassphraseProfile) {
|
if (repositorySettings || includeGpgPassphraseProfile) {
|
||||||
lines.push(' <profiles>');
|
lines.push(' <profiles>');
|
||||||
if (repositorySettings) {
|
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(
|
lines.push(
|
||||||
' <profile>',
|
' <profile>',
|
||||||
` <id>${constants.MAVEN_REPOSITORIES_PROFILE_ID}</id>`,
|
` <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) {
|
if (includeGpgPassphraseProfile) {
|
||||||
lines.push(
|
lines.push(
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ export const SIGNATURE_VERIFICATION_DOCUMENTATION_URL =
|
|||||||
'https://github.com/actions/setup-java#download-integrity-and-signatures';
|
'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 SIGNATURE_VERIFICATION_FAILURE_HELP = `If this is a legitimate vendor signing-key rotation, see ${SIGNATURE_VERIFICATION_DOCUMENTATION_URL} for instructions to configure the updated public key or temporarily disable signature verification.`;
|
||||||
export const INPUT_MVN_SERVER_CREDENTIALS = 'mvn-server-credentials';
|
export const INPUT_MVN_SERVER_CREDENTIALS = 'mvn-server-credentials';
|
||||||
|
export const INPUT_MVN_SERVER_REPOSITORY_ORIGINS =
|
||||||
|
'mvn-server-repository-origins';
|
||||||
export const INPUT_MVN_REPOSITORIES = 'mvn-repositories';
|
export const INPUT_MVN_REPOSITORIES = 'mvn-repositories';
|
||||||
|
export const INPUT_MVN_PLUGIN_REPOSITORIES = 'mvn-plugin-repositories';
|
||||||
export const INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL =
|
export const INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL =
|
||||||
'mvn-repositories-include-central';
|
'mvn-repositories-include-central';
|
||||||
export const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL =
|
export const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL =
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import semver from 'semver';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import * as httpm from '@actions/http-client';
|
import * as httpm from '@actions/http-client';
|
||||||
import {
|
import {
|
||||||
convertVersionToSemver,
|
|
||||||
getToolcachePath,
|
getToolcachePath,
|
||||||
isVersionSatisfies
|
isVersionSatisfies,
|
||||||
|
normalizeJavaVersionToSemver
|
||||||
} from '../util.js';
|
} from '../util.js';
|
||||||
import type {
|
import type {
|
||||||
ChecksumAlgorithm,
|
ChecksumAlgorithm,
|
||||||
@@ -675,12 +675,9 @@ export abstract class JavaBase {
|
|||||||
|
|
||||||
// Java uses a versioning scheme (JEP 322) that can contain more numeric
|
// Java uses a versioning scheme (JEP 322) that can contain more numeric
|
||||||
// fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such
|
// fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such
|
||||||
// exact versions to SemVer build notation ('18.0.1+1') so they are
|
// exact versions to SemVer build notation ('18.0.1+1', or '26.0.2+1.1'
|
||||||
// accepted. Ranges and versions that already carry build metadata are
|
// for '26.0.2.1+1') so they are accepted. Ranges are left untouched.
|
||||||
// left untouched.
|
version = normalizeJavaVersionToSemver(version);
|
||||||
if (/^\d+(\.\d+){3,}$/.test(version)) {
|
|
||||||
version = convertVersionToSemver(version);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!semver.validRange(version)) {
|
if (!semver.validRange(version)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
import {convertVersionToSemver} from '../util.js';
|
import {normalizeJavaVersionToSemver} from '../util.js';
|
||||||
|
|
||||||
export enum JavaDistribution {
|
export enum JavaDistribution {
|
||||||
Temurin = 'temurin',
|
Temurin = 'temurin',
|
||||||
@@ -98,12 +98,9 @@ function canResolveTemurinJmods(version: string): boolean {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
let normalizedRange = normalizedVersion
|
const normalizedRange = normalizeJavaVersionToSemver(
|
||||||
.replace(/-ea$/, '')
|
normalizedVersion.replace(/-ea$/, '').replace('-ea.', '+')
|
||||||
.replace('-ea.', '+');
|
);
|
||||||
if (/^\d+(\.\d+){3,}$/.test(normalizedRange)) {
|
|
||||||
normalizedRange = convertVersionToSemver(normalizedRange);
|
|
||||||
}
|
|
||||||
if (!semver.validRange(normalizedRange)) {
|
if (!semver.validRange(normalizedRange)) {
|
||||||
// JavaBase owns general version validation and its targeted error messages.
|
// JavaBase owns general version validation and its targeted error messages.
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export class TemurinDistribution extends JavaBase {
|
|||||||
const formattedVersion = this.stable
|
const formattedVersion = this.stable
|
||||||
? item.version_data.semver
|
? item.version_data.semver
|
||||||
: item.version_data.semver.replace('-beta+', '+');
|
: item.version_data.semver.replace('-beta+', '+');
|
||||||
return {
|
const release: JavaDownloadRelease = {
|
||||||
version: formattedVersion,
|
version: formattedVersion,
|
||||||
url: item.binaries[0].package.link,
|
url: item.binaries[0].package.link,
|
||||||
signatureUrl: item.binaries[0].package.signature_link,
|
signatureUrl: item.binaries[0].package.signature_link,
|
||||||
@@ -79,11 +79,29 @@ export class TemurinDistribution extends JavaBase {
|
|||||||
value: item.binaries[0].package.checksum,
|
value: item.binaries[0].package.checksum,
|
||||||
source: item.binaries[0].package.checksum_link
|
source: item.binaries[0].package.checksum_link
|
||||||
}
|
}
|
||||||
} as JavaDownloadRelease;
|
};
|
||||||
|
return {
|
||||||
|
release,
|
||||||
|
openjdkVersion: getOpenJdkSemverVersion(item.version_data)
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// The Adoptium API `semver` folds the JEP 322 patch field into the build
|
||||||
|
// number ('26.0.2.1+1' -> '26.0.2+101') and appends extra metadata for LTS
|
||||||
|
// releases ('25.0.4+7' -> '25.0.4+7.0.LTS'). Exact versions requested by
|
||||||
|
// users follow the OpenJDK notation instead, so also match them against a
|
||||||
|
// key derived from the OpenJDK version fields ('26.0.2+1.1', '25.0.4+7').
|
||||||
|
const isExactBuildRequest = (semver.parse(version)?.build.length ?? 0) > 0;
|
||||||
|
|
||||||
const satisfiedVersions = availableVersionsWithBinaries
|
const satisfiedVersions = availableVersionsWithBinaries
|
||||||
.filter(item => isVersionSatisfies(version, item.version))
|
.filter(
|
||||||
|
({release, openjdkVersion}) =>
|
||||||
|
isVersionSatisfies(version, release.version) ||
|
||||||
|
(isExactBuildRequest &&
|
||||||
|
openjdkVersion !== null &&
|
||||||
|
semver.compareBuild(version, openjdkVersion) === 0)
|
||||||
|
)
|
||||||
|
.map(({release}) => release)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
return -semver.compareBuild(a.version, b.version);
|
return -semver.compareBuild(a.version, b.version);
|
||||||
});
|
});
|
||||||
@@ -92,7 +110,7 @@ export class TemurinDistribution extends JavaBase {
|
|||||||
satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
|
satisfiedVersions.length > 0 ? satisfiedVersions[0] : null;
|
||||||
if (!resolvedFullVersion) {
|
if (!resolvedFullVersion) {
|
||||||
const availableVersionStrings = availableVersionsWithBinaries.map(
|
const availableVersionStrings = availableVersionsWithBinaries.map(
|
||||||
item => item.version
|
({release}) => release.version
|
||||||
);
|
);
|
||||||
throw this.createVersionNotFoundError(version, availableVersionStrings);
|
throw this.createVersionNotFoundError(version, availableVersionStrings);
|
||||||
}
|
}
|
||||||
@@ -309,3 +327,20 @@ export class TemurinDistribution extends JavaBase {
|
|||||||
return architecture === 'armv7' ? 'arm' : architecture;
|
return architecture === 'armv7' ? 'arm' : architecture;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a SemVer version from the OpenJDK version fields reported by the
|
||||||
|
* Adoptium API, e.g. '26.0.2.1+1' -> '26.0.2+1.1' and '25.0.4+7-LTS' ->
|
||||||
|
* '25.0.4+7'. Returns null if the fields cannot form a valid SemVer version.
|
||||||
|
*/
|
||||||
|
function getOpenJdkSemverVersion(
|
||||||
|
versionData: ITemurinAvailableVersions['version_data']
|
||||||
|
): string | null {
|
||||||
|
const {major, minor, security, patch, build} = versionData;
|
||||||
|
if (build === undefined || build === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const buildMetadata = patch ? `${patch}.${build}` : `${build}`;
|
||||||
|
const version = `${major}.${minor}.${security}+${buildMetadata}`;
|
||||||
|
return semver.valid(version) ? version : null;
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export interface ITemurinAvailableVersions {
|
|||||||
minor: number;
|
minor: number;
|
||||||
openjdk_version: string;
|
openjdk_version: string;
|
||||||
security: string;
|
security: string;
|
||||||
|
patch?: number;
|
||||||
semver: string;
|
semver: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import * as core from '@actions/core';
|
|||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import semver from 'semver';
|
|
||||||
|
|
||||||
import {JavaBase} from '../base-installer.js';
|
import {JavaBase} from '../base-installer.js';
|
||||||
import {IZuluPackageDetails, IZuluVersions} from './models.js';
|
import {IZuluPackageDetails, IZuluVersions} from './models.js';
|
||||||
@@ -30,6 +29,17 @@ interface ZuluResolvedRelease {
|
|||||||
packageUuid: string;
|
packageUuid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compareNumberArrays(a: number[], b: number[]): number {
|
||||||
|
const length = Math.max(a.length, b.length);
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const diff = (a[i] ?? 0) - (b[i] ?? 0);
|
||||||
|
if (diff !== 0) {
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
export class ZuluDistribution extends JavaBase {
|
export class ZuluDistribution extends JavaBase {
|
||||||
constructor(installerOptions: JavaInstallerOptions) {
|
constructor(installerOptions: JavaInstallerOptions) {
|
||||||
super('Zulu', installerOptions);
|
super('Zulu', installerOptions);
|
||||||
@@ -50,7 +60,9 @@ export class ZuluDistribution extends JavaBase {
|
|||||||
return {
|
return {
|
||||||
version: convertVersionToSemver(javaVersion),
|
version: convertVersionToSemver(javaVersion),
|
||||||
url: item.download_url,
|
url: item.download_url,
|
||||||
zuluVersion: convertVersionToSemver(item.distro_version),
|
javaVersion: item.java_version,
|
||||||
|
buildNumber: item.openjdk_build_number ?? 0,
|
||||||
|
distroVersion: item.distro_version,
|
||||||
packageUuid: item.package_uuid
|
packageUuid: item.package_uuid
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -58,11 +70,14 @@ export class ZuluDistribution extends JavaBase {
|
|||||||
const satisfiedVersions = availableVersions
|
const satisfiedVersions = availableVersions
|
||||||
.filter(item => isVersionSatisfies(version, item.version))
|
.filter(item => isVersionSatisfies(version, item.version))
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
// Azul provides two versions: java_version and distro_version
|
// Compare numerically rather than via semver build metadata: Azul
|
||||||
// we should sort by both fields by descending
|
// hotfix releases carry a 4th java_version segment (e.g. 25.0.4.1+1,
|
||||||
|
// rendered as '25.0.4+1.1') that must rank above 25.0.4+7, whereas
|
||||||
|
// semver.compareBuild would order the build identifiers '7' > '1'.
|
||||||
return (
|
return (
|
||||||
-semver.compareBuild(a.version, b.version) ||
|
-compareNumberArrays(a.javaVersion, b.javaVersion) ||
|
||||||
-semver.compareBuild(a.zuluVersion, b.zuluVersion)
|
b.buildNumber - a.buildNumber ||
|
||||||
|
-compareNumberArrays(a.distroVersion, b.distroVersion)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map((item): ZuluResolvedRelease => ({
|
.map((item): ZuluResolvedRelease => ({
|
||||||
|
|||||||
15
src/util.ts
15
src/util.ts
@@ -510,6 +510,21 @@ export function convertVersionToSemver(version: number[] | string) {
|
|||||||
return mainVersion;
|
return mainVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java versions (JEP 322) can contain more numeric fields than SemVer allows,
|
||||||
|
* e.g. '11.0.9.1' or Temurin respins such as '26.0.2.1+1'. Move the extra
|
||||||
|
* fields into SemVer build metadata ('11.0.9+1', '26.0.2+1.1'). Any other
|
||||||
|
* input (ranges, regular SemVer versions) is returned unchanged.
|
||||||
|
*/
|
||||||
|
export function normalizeJavaVersionToSemver(version: string): string {
|
||||||
|
const match = /^(\d+(?:\.\d+){3,})(?:\+([0-9A-Za-z.-]+))?$/.exec(version);
|
||||||
|
if (!match) {
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
const converted = convertVersionToSemver(match[1]);
|
||||||
|
return match[2] ? `${converted}.${match[2]}` : converted;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds a validator for the bytes currently served by a URL from the response
|
* Builds a validator for the bytes currently served by a URL from the response
|
||||||
* headers of a HEAD request. A vendor's `/latest/` URL never changes, so this
|
* headers of a HEAD request. A vendor's `/latest/` URL never changes, so this
|
||||||
|
|||||||
Reference in New Issue
Block a user