diff --git a/.github/workflows/e2e-publishing.yml b/.github/workflows/e2e-publishing.yml index 4dcab4117..35c1596c3 100644 --- a/.github/workflows/e2e-publishing.yml +++ b/.github/workflows/e2e-publishing.yml @@ -174,3 +174,115 @@ jobs: if (-not (Test-Path $path)) { throw "settings.xml file is not found in expected location" } + + test-maven-repositories: + name: Maven ${{ matrix.repositories }} repositories + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, windows-latest, ubuntu-latest] + repositories: [dependencies, plugins, both] + steps: + - name: Checkout + uses: actions/checkout@v7 + with: + persist-credentials: false + - name: Prepare Maven model inspection + run: | + $directory = Join-Path $env:RUNNER_TEMP "maven-repository-test" + New-Item -Path $directory -ItemType Directory -Force | Out-Null + $pomPath = Join-Path $directory "pom.xml" + $warmSettingsPath = Join-Path $directory "warm-settings.xml" + $cachePath = Join-Path $directory "repository" + $outputPath = Join-Path $directory "warm-effective-pom.xml" + + # This project repository keeps the inspection plugin available offline + # after the generated settings disable inherited plugin Central. + @' + + 4.0.0 + setup-java.test + repository-settings + 1.0 + + + inspection + https://repo.maven.apache.org/maven2 + + + + '@ | Set-Content -Path $pomPath + '' | 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" diff --git a/.licenses/npm/brace-expansion.dep.yml b/.licenses/npm/brace-expansion.dep.yml index 96e6211ba..8a9c0556d 100644 --- a/.licenses/npm/brace-expansion.dep.yml +++ b/.licenses/npm/brace-expansion.dep.yml @@ -1,6 +1,6 @@ --- name: brace-expansion -version: 5.0.9 +version: 5.0.12 type: npm summary: Brace expansion as known from sh/bash homepage: diff --git a/.licenses/npm/fast-xml-parser.dep.yml b/.licenses/npm/fast-xml-parser.dep.yml index 88bd3e262..84f4dd0b9 100644 --- a/.licenses/npm/fast-xml-parser.dep.yml +++ b/.licenses/npm/fast-xml-parser.dep.yml @@ -1,6 +1,6 @@ --- name: fast-xml-parser -version: 5.11.0 +version: 5.11.1 type: npm summary: Validate XML, Parse XML, Build XML without C/C++ based libraries homepage: diff --git a/.licenses/npm/undici.dep.yml b/.licenses/npm/undici.dep.yml index b339a4487..336811738 100644 --- a/.licenses/npm/undici.dep.yml +++ b/.licenses/npm/undici.dep.yml @@ -1,6 +1,6 @@ --- name: undici -version: 6.28.0 +version: 6.29.0 type: npm summary: An HTTP/1.1 client, written from scratch for Node.js homepage: https://undici.nodejs.org diff --git a/README.md b/README.md index f76c65e73..58822e688 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ steps: | `java-version-file` | Path to `.java-version`, `.tool-versions`, or `.sdkmanrc`. Used when `java-version` is not set. | | | `distribution` | Java distribution keyword. Values are case-sensitive and must match one of the supported keywords below. Required unless `java-version-file` points to `.sdkmanrc` with a recognized distribution suffix. | | | `java-package` | Package variant such as `jdk`, `jre`, `jdk+fx`, `jre+fx`, `jdk+crac`, `jre+crac`, `jdk+jmods`, `jdk+jcef`, `jre+jcef`, `jdk+ft`, or `jre+ft`. Support varies by distribution. | `jdk` | -| `architecture` | Package architecture. Canonical values are `x86`, `x64`, `armv7`, `aarch64`, `ppc64le`, `ppc64`, and `s390x`. Aliases `ia32`, `amd64`, `arm`, and `arm64` are normalized. | Runner architecture | +| `architecture` | Package architecture. Canonical values are `x86`, `x64`, `armv7`, `aarch64`, `ppc64le`, `ppc64`, `riscv64`, and `s390x`. Aliases `ia32`, `amd64`, `arm`, and `arm64` are normalized. | Runner architecture | | `jdk-file` | Local compressed JDK archive. Requires `distribution: jdkfile`. | | | `check-latest` | Check remote metadata for the latest version satisfying the version spec before using the runner tool cache. | `false` | | `force-download` | Always download Java and replace any matching version in the tool cache. | `false` | @@ -168,9 +168,11 @@ steps: | `server-username-env-var` | Environment variable name for Maven repository username. | `GITHUB_ACTOR` | | `server-password-env-var` | Environment variable name for Maven repository password or token. | `GITHUB_TOKEN` | | `mvn-server-credentials` | Multiline Maven server credentials in the format `server-id:USERNAME_ENV:PASSWORD_ENV`. Replaces the single server configured by the three inputs above when set. | | +| `mvn-server-repository-origins` | Multiline Maven credential origins in the format `server-id:repository-origin`. Each origin must belong to a configured server ID. Requires Maven 3.10+. | | | `mvn-repositories` | Multiline Maven dependency repositories in the format `repository-id:repository-url:snapshots-enabled`. | | -| `mvn-repositories-include-central` | Include Maven Central in the generated dependency repositories profile. When `false`, Central is disabled unless an explicit `central` repository is declared. | `true` | -| `mvn-repositories-prioritize-central` | Place Maven Central before custom dependency repositories. Has no effect when Maven Central is excluded. | `true` | +| `mvn-plugin-repositories` | Multiline Maven plugin repositories in the format `repository-id:repository-url:snapshots-enabled`. Can be used independently of `mvn-repositories`. | | +| `mvn-repositories-include-central` | Include Maven Central for dependency and plugin repositories in the generated profile. When `false`, Central is disabled for both types unless that type explicitly declares `central`. Has no effect when neither repository list is configured. | `true` | +| `mvn-repositories-prioritize-central` | Place Maven Central before custom dependency and plugin repositories. Has no effect when Central is excluded or neither repository list is configured. | `true` | | `settings-path` | Directory where `settings.xml` is written. | `~/.m2` | | `overwrite-settings` | Overwrite an existing `settings.xml`. | `true` | | `gpg-private-key` | GPG private key to import into an isolated temporary keyring. | | @@ -233,7 +235,7 @@ Additional distribution notes: | --- | --- | | Major version | `8`, `11`, `17`, `21`, `25` | | Specific feature or patch version | `11.0`, `11.0.4`, `17.0`, `8.0.282+8` | -| JEP 322 multi-field versions | `11.0.9.1`, `18.0.1.1` | +| JEP 322 multi-field versions | `11.0.9.1`, `18.0.1.1`, `26.0.2.1+1` | | Early access | `15-ea`, `15.0.0-ea`, `27-ea` | | Latest stable GA release | `latest` | @@ -481,10 +483,20 @@ steps: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ``` -For dependencies hosted outside Maven Central, use `mvn-repositories` to add -resolution repositories to an active profile in the generated `settings.xml`. -Repository IDs can match `mvn-server-credentials` IDs when authentication is -required. See [Resolving Maven dependencies from custom repositories](docs/advanced-usage.md#resolving-maven-dependencies-from-custom-repositories). +For dependencies hosted outside Maven Central, use `mvn-repositories`; for build +plugins, use `mvn-plugin-repositories`. Either input adds its own repository type +to the same active profile in the generated `settings.xml`. Repository IDs can +match `mvn-server-credentials` IDs when authentication is required. +See [Resolving Maven dependencies from custom repositories](docs/advanced-usage.md#resolving-maven-dependencies-from-custom-repositories) +and [Resolving Maven plugins from custom repositories](docs/advanced-usage.md#resolving-maven-plugins-from-custom-repositories). + +> [!WARNING] +> `mvn-repositories-include-central: false` now disables inherited Maven Central +> for **both dependencies and plugins**, even in existing workflows that only +> configure `mvn-repositories`. Builds relying on uncached plugins from Central +> must configure `mvn-plugin-repositories` with an accessible plugin repository +> or explicitly declare `central` there. Neither repository list configured +> means the Central controls remain inactive. ### GPG signing diff --git a/__tests__/auth.test.ts b/__tests__/auth.test.ts index a169451f4..7af82c8f8 100644 --- a/__tests__/auth.test.ts +++ b/__tests__/auth.test.ts @@ -63,12 +63,51 @@ const credentials = (id: string, username: string, password: string) => [ const repositorySettings = ( repositories: auth.MavenRepository[], includeCentral = true, - prioritizeCentral = true + prioritizeCentral = true, + pluginRepositories: auth.MavenRepository[] = [] ): auth.MavenRepositorySettings => ({ repositories, + pluginRepositories, includeCentral, prioritizeCentral }); +const repositoryInputs = ['mvn-repositories', 'mvn-plugin-repositories']; +const privateRepository = { + id: 'private', + url: 'https://repo.example.com/maven', + snapshotsEnabled: true +}; +const privateRepositoryXml = { + id: privateRepository.id, + url: privateRepository.url, + snapshots: {enabled: 'true'} +}; +const centralRepositoryXml = { + id: 'central', + url: 'https://repo.maven.apache.org/maven2', + snapshots: {enabled: 'false'} +}; +const disabledCentralRepositoryXml = { + ...centralRepositoryXml, + releases: {enabled: 'false'} +}; +const repositoryConfigurations = [ + { + name: 'dependency-only', + repositories: [privateRepository], + pluginRepositories: [] + }, + { + name: 'plugin-only', + repositories: [], + pluginRepositories: [privateRepository] + }, + { + name: 'combined', + repositories: [privateRepository], + pluginRepositories: [privateRepository] + } +]; describe('auth tests', () => { let spyOSHomedir: any; @@ -318,6 +357,21 @@ describe('auth tests', () => { ).toEqual(expectedSettings); }); + it('generates repository origins for a Maven server', () => { + const settings = auth.generate([ + { + id: 'central', + usernameEnvVar: 'CENTRAL_USER', + passwordEnvVar: 'CENTRAL_PASS', + repositoryOrigins: ['https://central.sonatype.com'] + } + ]); + + expect(settings).toContain(` + https://central.sonatype.com + `); + }); + it('does not add a gpg profile when the passphrase env var is the maven-gpg-plugin default', () => { const id = 'packages'; const username = 'USER'; @@ -460,6 +514,87 @@ describe('auth tests', () => { ).toEqual(['first', 'second', 'central']); }); + it.each([ + { + name: 'Central first by default', + prioritizeCentral: undefined, + expectedRepositories: [ + { + id: 'central', + url: 'https://repo.maven.apache.org/maven2', + snapshots: {enabled: 'false'} + }, + { + id: 'first', + url: 'https://first.example.com/plugins', + snapshots: {enabled: 'false'} + }, + { + id: 'second', + url: 'https://second.example.com/plugins', + snapshots: {enabled: 'true'} + } + ] + }, + { + name: 'custom plugins before Central', + prioritizeCentral: false, + expectedRepositories: [ + { + id: 'first', + url: 'https://first.example.com/plugins', + snapshots: {enabled: 'false'} + }, + { + id: 'second', + url: 'https://second.example.com/plugins', + snapshots: {enabled: 'true'} + }, + { + id: 'central', + url: 'https://repo.maven.apache.org/maven2', + snapshots: {enabled: 'false'} + } + ] + } + ])( + 'generates plugin-only settings with $name', + ({prioritizeCentral, expectedRepositories}) => { + const xml = auth.generate( + [], + undefined, + repositorySettings([], true, prioritizeCentral, [ + { + id: 'first', + url: 'https://first.example.com/plugins', + snapshotsEnabled: false + }, + { + id: 'second', + url: 'https://second.example.com/plugins', + snapshotsEnabled: true + } + ]) + ); + + expect(parseXmlObject(xml)).toEqual( + expect.objectContaining({ + settings: expect.objectContaining({ + profiles: { + profile: { + id: 'setup-java-repositories', + pluginRepositories: { + pluginRepository: expectedRepositories + } + } + }, + activeProfiles: {activeProfile: 'setup-java-repositories'} + }) + }) + ); + } + ); + it('excludes Central from the generated repository profile when configured', () => { const parsed = parseXmlObject( auth.generate( @@ -491,6 +626,9 @@ describe('auth tests', () => { snapshots: {enabled: 'false'} } ]); + expect( + parsed.settings.profiles.profile.pluginRepositories.pluginRepository + ).toEqual(disabledCentralRepositoryXml); }); it('combines repository and GPG configuration in shared profile blocks', () => { @@ -528,6 +666,9 @@ describe('auth tests', () => { snapshots: {enabled: 'false'} } ] + }, + pluginRepositories: { + pluginRepository: disabledCentralRepositoryXml } }, { @@ -541,6 +682,161 @@ describe('auth tests', () => { ]); }); + it.each( + repositoryConfigurations.flatMap(configuration => + [ + {includeCentral: true, prioritizeCentral: true}, + {includeCentral: true, prioritizeCentral: false}, + {includeCentral: false, prioritizeCentral: true}, + {includeCentral: false, prioritizeCentral: false} + ].map(controls => ({...configuration, ...controls})) + ) + )( + 'generates $name repositories with includeCentral=$includeCentral and prioritizeCentral=$prioritizeCentral', + ({repositories, pluginRepositories, includeCentral, prioritizeCentral}) => { + const xml = auth.generate( + credentials('private', 'USERNAME', 'PASSWORD'), + undefined, + repositorySettings( + repositories, + includeCentral, + prioritizeCentral, + pluginRepositories + ) + ); + const expectedEntries = (configured: boolean) => { + const entries = includeCentral + ? configured + ? prioritizeCentral + ? [centralRepositoryXml, privateRepositoryXml] + : [privateRepositoryXml, centralRepositoryXml] + : [] + : configured + ? [privateRepositoryXml, disabledCentralRepositoryXml] + : [disabledCentralRepositoryXml]; + return entries.length === 1 ? entries[0] : entries; + }; + const includeRepositories = repositories.length > 0 || !includeCentral; + const includePluginRepositories = + pluginRepositories.length > 0 || !includeCentral; + + expect(parseXmlObject(xml)).toMatchObject({ + settings: { + profiles: { + profile: { + id: 'setup-java-repositories', + ...(includeRepositories + ? { + repositories: { + repository: expectedEntries(repositories.length > 0) + } + } + : {}), + ...(includePluginRepositories + ? { + pluginRepositories: { + pluginRepository: expectedEntries( + pluginRepositories.length > 0 + ) + } + } + : {}) + } + }, + activeProfiles: {activeProfile: 'setup-java-repositories'} + } + }); + if (!includeRepositories) { + expect(xml).not.toContain(''); + } + if (!includePluginRepositories) { + expect(xml).not.toContain(''); + } + } + ); + + 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(/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="'é`, + 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(''); + }); + it('escapes repository values while preserving parsed semantics', () => { const repository = { id: `private&<>"'é`, @@ -586,81 +882,111 @@ describe('auth tests', () => { ]); }); - it('parses Maven repositories while preserving colons in URLs', () => { - expect( - auth.parseMavenRepositories( - [ - '', - ' private : https://repo.example.com:8443/maven : true ', - 'releases:https://repo.example.com/releases:false' - ], - true - ) - ).toEqual([ + it.each(repositoryInputs)( + 'parses %s while preserving colons in URLs', + inputName => { + expect( + auth.parseMavenRepositories( + [ + '', + ' private : https://repo.example.com:8443/maven : TRUE ', + 'releases:https://repo.example.com/releases:false' + ], + true, + inputName + ) + ).toEqual([ + { + id: 'private', + url: 'https://repo.example.com:8443/maven', + snapshotsEnabled: true + }, + { + id: 'releases', + url: 'https://repo.example.com/releases', + snapshotsEnabled: false + } + ]); + } + ); + + it.each( + [ { - id: 'private', - url: 'https://repo.example.com:8443/maven', - snapshotsEnabled: true + entries: ['private'], + error: + 'Invalid mvn-repositories entry at line 1. Expected format: repository-id:repository-url:snapshots-enabled' }, { - id: 'releases', - url: 'https://repo.example.com/releases', - snapshotsEnabled: false + entries: ['private:https://repo.example.com'], + error: + "Invalid snapshots-enabled value '//repo.example.com' in mvn-repositories entry at line 1. Expected true or false" + }, + { + entries: ['private::true'], + error: + 'Invalid mvn-repositories entry at line 1. repository-id, repository URL, and snapshots-enabled are required' + }, + { + entries: ['private:https://repo.example.com:'], + error: + 'Invalid mvn-repositories entry at line 1. repository-id, repository URL, and snapshots-enabled are required' + }, + { + entries: ['', 'private:https://repo.example.com:true', ' :url:false'], + error: + 'Invalid mvn-repositories entry at line 3. repository-id, repository URL, and snapshots-enabled are required' + }, + { + entries: ['private:https://repo.example.com:sometimes'], + error: + "Invalid snapshots-enabled value 'sometimes' in mvn-repositories entry at line 1. Expected true or false" } - ]); - }); - - it.each([ - { - entries: ['private'], - error: - 'Invalid mvn-repositories entry at line 1. Expected format: repository-id:repository-url:snapshots-enabled' - }, - { - entries: ['private:https://repo.example.com'], - error: - "Invalid snapshots-enabled value '//repo.example.com' in mvn-repositories entry at line 1. Expected true or false" - }, - { - entries: ['private::true'], - error: - 'Invalid mvn-repositories entry at line 1. repository-id, repository URL, and snapshots-enabled are required' - }, - { - entries: ['private:https://repo.example.com:sometimes'], - error: - "Invalid snapshots-enabled value 'sometimes' in mvn-repositories entry at line 1. Expected true or false" - } - ])('rejects malformed Maven repositories: $entries', ({entries, error}) => { - expect(() => auth.parseMavenRepositories(entries, true)).toThrow(error); + ].flatMap(testCase => + repositoryInputs.map(inputName => ({ + ...testCase, + inputName, + error: testCase.error.replace('mvn-repositories', inputName) + })) + ) + )('rejects malformed $inputName: $entries', ({entries, error, inputName}) => { + expect(() => auth.parseMavenRepositories(entries, true, inputName)).toThrow( + error + ); }); - it('rejects duplicate Maven repository ids', () => { + it.each(repositoryInputs)('rejects duplicate IDs in %s', inputName => { expect(() => auth.parseMavenRepositories( [ 'private:https://first.example.com:false', 'private:https://second.example.com:true' ], - true + true, + inputName ) - ).toThrow("Duplicate repository-id 'private' in mvn-repositories input"); + ).toThrow(`Duplicate repository-id 'private' in ${inputName} input`); }); - it('reserves the Central repository id only when automatic Central inclusion is enabled', () => { - const central = 'central:https://custom.example.com/maven:false'; - - expect(() => auth.parseMavenRepositories([central], true)).toThrow( - "Repository-id 'central' is reserved when mvn-repositories-include-central is enabled" - ); - expect(auth.parseMavenRepositories([central], false)).toEqual([ - { - id: 'central', - url: 'https://custom.example.com/maven', - snapshotsEnabled: false - } - ]); - }); + it.each(repositoryInputs)( + 'reserves the Central ID in %s only when automatic inclusion is enabled', + inputName => { + const central = 'central:https://custom.example.com/maven:false'; + + expect(() => + auth.parseMavenRepositories([central], true, inputName) + ).toThrow( + "Repository-id 'central' is reserved when mvn-repositories-include-central is enabled" + ); + expect(auth.parseMavenRepositories([central], false, inputName)).toEqual([ + { + id: 'central', + url: 'https://custom.example.com/maven', + snapshotsEnabled: false + } + ]); + } + ); it('uses an explicit Central repository instead of adding a disabled override', () => { const parsed = parseXmlObject( @@ -706,16 +1032,138 @@ describe('auth tests', () => { snapshotsEnabled: true } ], + pluginRepositories: [], includeCentral: false, prioritizeCentral: false }); }); it('does not read repository controls when no repositories are configured', () => { + ( + core.getInput as jest.MockedFunction + ).mockReturnValue('false'); + expect(auth.getMavenRepositorySettings()).toBeUndefined(); + expect(core.getInput).not.toHaveBeenCalled(); + }); + + it('ignores blank dependency and plugin repository inputs', () => { + ( + core.getMultilineInput as jest.MockedFunction< + typeof core.getMultilineInput + > + ).mockReturnValue(['', ' ', '\t']); + expect(auth.getMavenRepositorySettings()).toBeUndefined(); expect(core.getInput).not.toHaveBeenCalled(); }); + it.each(repositoryConfigurations)( + 'reads $name inputs with default Central controls and shared IDs', + ({repositories, pluginRepositories}) => { + ( + core.getInput as jest.MockedFunction + ).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 + ).mockImplementation(name => { + const inputs: Record = { + '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 = { + 'mvn-server-credentials': ['private:USERNAME:PASSWORD'], + 'mvn-server-repository-origins': ['private:https://repo.example.com'], + 'mvn-plugin-repositories': [ + 'private:https://repo.example.com/maven:true' + ] + }; + return inputs[name] ?? []; + }); + + await auth.configureAuthentication(); + + const xml = fs.readFileSync(location, 'utf8'); + if (!overwriteSettings) { + expect(xml).toBe('EXISTING SETTINGS'); + return; + } + expect(parseXmlObject(xml)).toMatchObject({ + settings: { + servers: { + server: { + id: 'private', + username: '${env.USERNAME}', + password: '${env.PASSWORD}', + repositoryOrigins: { + repositoryOrigin: 'https://repo.example.com' + } + } + }, + profiles: { + profile: [ + { + id: 'setup-java-repositories', + repositories: {repository: disabledCentralRepositoryXml}, + pluginRepositories: { + pluginRepository: [ + privateRepositoryXml, + disabledCentralRepositoryXml + ] + } + }, + { + id: 'setup-java-gpg', + properties: {'gpg.passphraseEnvName': 'GPG_PASSPHRASE'} + } + ] + }, + activeProfiles: { + activeProfile: ['setup-java-repositories', 'setup-java-gpg'] + } + } + }); + expect(fs.existsSync(settingsFile)).toBe(false); + } + ); + it.each([ { entries: ['releases:RELEASES_USERNAME'], @@ -749,16 +1197,23 @@ describe('auth tests', () => { }); it('uses multiline Maven server credentials instead of single-server inputs', () => { - (core.getMultilineInput as jest.Mock).mockReturnValue([ - 'releases:RELEASES_USERNAME:RELEASES_PASSWORD', - 'snapshots:SNAPSHOTS_USERNAME:SNAPSHOTS_PASSWORD' - ]); + (core.getMultilineInput as jest.Mock).mockImplementation((name: string) => + name === 'mvn-server-credentials' + ? [ + 'releases:RELEASES_USERNAME:RELEASES_PASSWORD', + 'snapshots:SNAPSHOTS_USERNAME:SNAPSHOTS_PASSWORD' + ] + : name === 'mvn-server-repository-origins' + ? ['releases:https://repo.example.com'] + : [] + ); expect(auth.getMavenServerSettings()).toEqual([ { id: 'releases', usernameEnvVar: 'RELEASES_USERNAME', - passwordEnvVar: 'RELEASES_PASSWORD' + passwordEnvVar: 'RELEASES_PASSWORD', + repositoryOrigins: ['https://repo.example.com'] }, { id: 'snapshots', @@ -803,6 +1258,106 @@ describe('auth tests', () => { expect(core.warning).toHaveBeenCalledTimes(2); }); + it('adds normalized repository origins to matching Maven servers', () => { + expect( + auth.addMavenServerRepositoryOrigins( + [ + { + id: 'central', + usernameEnvVar: 'CENTRAL_USER', + passwordEnvVar: 'CENTRAL_PASS' + }, + { + id: 'packages', + usernameEnvVar: 'GITHUB_ACTOR', + passwordEnvVar: 'GITHUB_TOKEN' + } + ], + [ + 'central:https://central.sonatype.com/', + 'central:https://central.sonatype.com', + 'central:https://central.sonatype.com:443', + 'packages:https://maven.pkg.github.com', + 'packages:ssh://packages.example.com:22', + 'packages:ftp://packages.example.com:21' + ] + ) + ).toEqual([ + { + id: 'central', + usernameEnvVar: 'CENTRAL_USER', + passwordEnvVar: 'CENTRAL_PASS', + repositoryOrigins: ['https://central.sonatype.com'] + }, + { + id: 'packages', + usernameEnvVar: 'GITHUB_ACTOR', + passwordEnvVar: 'GITHUB_TOKEN', + repositoryOrigins: [ + 'https://maven.pkg.github.com', + 'ssh://packages.example.com:22', + 'ftp://packages.example.com:21' + ] + } + ]); + }); + + it.each([ + { + entries: ['central'], + error: + 'Invalid mvn-server-repository-origins entry at line 1. Expected format: server-id:repository-origin' + }, + { + entries: ['other:https://repo.example.com'], + error: + "Unknown server-id 'other' in mvn-server-repository-origins at line 1" + }, + { + entries: ['central:https://repo.example.com/path'], + error: + "Invalid repository origin 'https://repo.example.com/path' in mvn-server-repository-origins at line 1" + }, + { + entries: ['central:https:repo.example.com'], + error: + "Invalid repository origin 'https:repo.example.com' in mvn-server-repository-origins at line 1" + }, + { + entries: ['central:https:/repo.example.com'], + error: + "Invalid repository origin 'https:/repo.example.com' in mvn-server-repository-origins at line 1" + }, + { + entries: ['central:https://repo.example.com?'], + error: + "Invalid repository origin 'https://repo.example.com?' in mvn-server-repository-origins at line 1" + }, + { + entries: ['central:https://repo.example.com#'], + error: + "Invalid repository origin 'https://repo.example.com#' in mvn-server-repository-origins at line 1" + }, + { + entries: ['central:https://@repo.example.com'], + error: + "Invalid repository origin 'https://@repo.example.com' in mvn-server-repository-origins at line 1" + } + ])('rejects invalid Maven server repository origins', ({entries, error}) => { + expect(() => + auth.addMavenServerRepositoryOrigins( + [ + { + id: 'central', + usernameEnvVar: 'CENTRAL_USER', + passwordEnvVar: 'CENTRAL_PASS' + } + ], + entries + ) + ).toThrow(error); + }); + function xmlElementText(xml: string, tagName: string): string { const match = new RegExp(`<${tagName}>([\\s\\S]*?)`).exec(xml); expect(match).not.toBeNull(); diff --git a/__tests__/distributors/base-installer.test.ts b/__tests__/distributors/base-installer.test.ts index 281f73e5e..1016a8fe6 100644 --- a/__tests__/distributors/base-installer.test.ts +++ b/__tests__/distributors/base-installer.test.ts @@ -1758,6 +1758,8 @@ describe('normalizeVersion', () => { ['11.0.9.1', {version: '11.0.9+1', stable: true, latest: false}], ['12.0.2.1.0', {version: '12.0.2+1.0', stable: true, latest: false}], ['18.0.1.1-ea', {version: '18.0.1+1', stable: false, latest: false}], + ['26.0.2.1+1', {version: '26.0.2+1.1', stable: true, latest: false}], + ['25.0.4.1+1', {version: '25.0.4+1.1', stable: true, latest: false}], ['latest', {version: 'x', stable: true, latest: true}], ['LATEST', {version: 'x', stable: true, latest: true}], [' Latest ', {version: 'x', stable: true, latest: true}] diff --git a/__tests__/distributors/distribution-factory.test.ts b/__tests__/distributors/distribution-factory.test.ts index c473373a4..c60e7f75b 100644 --- a/__tests__/distributors/distribution-factory.test.ts +++ b/__tests__/distributors/distribution-factory.test.ts @@ -82,7 +82,7 @@ describe('getJavaDistribution', () => { ); }); - it.each(['8', '23.x', '23.0.1.1', '<24'])( + it.each(['8', '23.x', '23.0.1.1', '23.0.1.1+1', '<24'])( "rejects Temurin java-package 'jdk+jmods' for version %s", async version => { await expect( @@ -96,7 +96,7 @@ describe('getJavaDistribution', () => { } ); - it.each(['24', '24.0.1.1', '25-ea', '>=21', 'latest'])( + it.each(['24', '24.0.1.1', '25.0.4.1+1', '25-ea', '>=21', 'latest'])( "accepts Temurin java-package 'jdk+jmods' for version %s", async version => { expect( diff --git a/__tests__/distributors/jetbrains-installer.test.ts b/__tests__/distributors/jetbrains-installer.test.ts index 944a5ce71..ebc393652 100644 --- a/__tests__/distributors/jetbrains-installer.test.ts +++ b/__tests__/distributors/jetbrains-installer.test.ts @@ -14,7 +14,6 @@ import type {IncomingMessage} from 'http'; import {Readable} from 'stream'; import manifestData from '../data/jetbrains.json' with {type: 'json'}; -import os from 'os'; // Mock @actions/core before importing source modules that depend on it jest.unstable_mockModule('@actions/core', () => ({ @@ -81,6 +80,7 @@ describe('getAvailableVersions', () => { jest.setTimeout(10_000); let spyHttpClient: any; + let spyHttpClientHead: any; let spyCoreError: any; const originalGitHubToken = process.env.GITHUB_TOKEN; @@ -93,6 +93,10 @@ describe('getAvailableVersions', () => { headers: {}, result: [] }); + spyHttpClientHead = jest.spyOn(HttpClient.prototype, 'head'); + spyHttpClientHead.mockResolvedValue({ + message: {statusCode: 200} + } as any); // Mock core.error to suppress error logs spyCoreError = core.error as jest.Mock; @@ -133,9 +137,7 @@ describe('getAvailableVersions', () => { const availableVersions = await distribution['getAvailableVersions'](); expect(availableVersions).not.toBeNull(); - const length = - os.platform() === 'win32' ? manifestData.length : manifestData.length + 2; - expect(availableVersions.length).toBe(length); + expect(availableVersions.length).toBe(manifestData.length + 2); }, 10_000); it('continues a stable request after an all-prerelease page', async () => { @@ -358,6 +360,7 @@ describe('getAvailableVersions', () => { it('retries a GitHub rate limit using Retry-After', async () => { spyHttpClient.mockRestore(); + spyHttpClientHead.mockRestore(); const sleep = jest.fn(async () => undefined); const requestRaw = jest .spyOn(HttpClient.prototype, 'requestRaw') diff --git a/__tests__/distributors/liberica-installer.test.ts b/__tests__/distributors/liberica-installer.test.ts index ab1b72b2d..2005f5343 100644 --- a/__tests__/distributors/liberica-installer.test.ts +++ b/__tests__/distributors/liberica-installer.test.ts @@ -253,6 +253,39 @@ describe('findPackageForDownload', () => { expect(result.version).toBe(expected); }); + describe('compound build versions', () => { + beforeEach(() => { + distribution['getAvailableVersions'] = async () => + ['1', '1.1', '1.2', '1.10'].map(build => ({ + featureVersion: 25, + interimVersion: 0, + updateVersion: 4, + buildVersion: 1, + version: `25.0.4+${build}`, + downloadUrl: `https://download.bell-sw.com/java/25.0.4+${build}/bellsoft-jdk25.0.4+${build}-macos-aarch64.tar.gz` + })); + }); + + it.each([ + ['25.0.4+1.1', '25.0.4+1.1'], + ['25.0.4+1', '25.0.4+1'], + ['25.0.4', '25.0.4+1.10'], + ['25', '25.0.4+1.10'] + ])('version is %s -> %s', async (input, expected) => { + const result = await distribution['findPackageForDownload'](input); + expect(result).toEqual({ + version: expected, + url: `https://download.bell-sw.com/java/${expected}/bellsoft-jdk${expected}-macos-aarch64.tar.gz` + }); + }); + + it('does not substitute a different compound build', async () => { + await expect( + distribution['findPackageForDownload']('25.0.4+1.3') + ).rejects.toThrow(/No matching version found for SemVer/); + }); + }); + it('should throw an error', async () => { await expect(distribution['findPackageForDownload']('17')).rejects.toThrow( /No matching version found for SemVer/ @@ -335,6 +368,36 @@ describe('convertVersionToSemver', () => { buildVersion: 13 }, '11.0.0+13' + ], + [ + { + version: '25.0.4+1.1', + featureVersion: 25, + interimVersion: 0, + updateVersion: 4, + buildVersion: 1 + }, + '25.0.4+1.1' + ], + [ + { + version: '25+36', + featureVersion: 25, + interimVersion: 0, + updateVersion: 0, + buildVersion: 36 + }, + '25.0.0+36' + ], + [ + { + version: '8u202', + featureVersion: 8, + interimVersion: 0, + updateVersion: 202, + buildVersion: 8 + }, + '8.0.202+8' ] ])('%s -> %s', (input, expected) => { const actual = distributions['convertVersionToSemver']({ diff --git a/__tests__/distributors/temurin-installer.test.ts b/__tests__/distributors/temurin-installer.test.ts index 7606627e6..2d424b33c 100644 --- a/__tests__/distributors/temurin-installer.test.ts +++ b/__tests__/distributors/temurin-installer.test.ts @@ -293,7 +293,8 @@ describe('getAvailableVersions', () => { it.each([ ['amd64', 'x64'], ['arm', 'arm'], - ['arm64', 'aarch64'] + ['arm64', 'aarch64'], + ['riscv64', 'riscv64'] ])( 'defaults to os.arch(): %s mapped to distro arch: %s', async (osArch: string, distroArch: string) => { @@ -377,6 +378,97 @@ describe('findPackageForDownload', () => { expect(resolvedVersion.version).toBe('16.0.2+7'); }); + describe('OpenJDK patch (respin) versions', () => { + const makeRelease = ( + semverVersion: string, + openjdkVersion: string, + versionData: Record + ) => ({ + binaries: [ + { + package: { + link: `https://example.com/${openjdkVersion}.tar.gz`, + checksum: `checksum-${openjdkVersion}`, + checksum_link: `https://example.com/${openjdkVersion}.sha256.txt` + } + } + ], + version_data: { + semver: semverVersion, + openjdk_version: openjdkVersion, + minor: 0, + ...versionData + } + }); + + const respinManifest = [ + makeRelease('26.0.2+101', '26.0.2.1+1', { + major: 26, + security: 2, + patch: 1, + build: 1 + }), + makeRelease('26.0.2+10', '26.0.2+10', { + major: 26, + security: 2, + build: 10 + }), + makeRelease('25.0.4+101.0.LTS', '25.0.4.1+1-LTS', { + major: 25, + security: 4, + patch: 1, + build: 1 + }), + makeRelease('25.0.4+7.0.LTS', '25.0.4+7-LTS', { + major: 25, + security: 4, + build: 7 + }) + ]; + + it.each([ + ['26.0.2.1+1', '26.0.2+101'], + ['26.0.2+10', '26.0.2+10'], + ['26', '26.0.2+101'], + ['26.0.2', '26.0.2+101'], + ['25.0.4.1+1', '25.0.4+101.0.LTS'], + ['25.0.4+7', '25.0.4+7.0.LTS'], + ['25.0.4', '25.0.4+101.0.LTS'] + ])('%s resolves to %s', async (input, expected) => { + const distribution = new TemurinDistribution( + { + version: input, + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }, + TemurinImplementation.Hotspot + ); + distribution['getAvailableVersions'] = async () => respinManifest as any; + const resolvedVersion = await distribution['findPackageForDownload']( + distribution['version'] + ); + expect(resolvedVersion.version).toBe(expected); + expect(resolvedVersion).not.toHaveProperty('openjdkVersion'); + }); + + it('does not match a non-existent respin', async () => { + const distribution = new TemurinDistribution( + { + version: '26.0.2.2+1', + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }, + TemurinImplementation.Hotspot + ); + distribution['getAvailableVersions'] = async () => respinManifest as any; + await expect( + distribution['findPackageForDownload'](distribution['version']) + ).rejects.toThrow(/No matching version found for SemVer '26.0.2\+2.1'/); + }); + }); + it('version is found but binaries list is empty', async () => { const distribution = new TemurinDistribution( { diff --git a/__tests__/distributors/zulu-installer.test.ts b/__tests__/distributors/zulu-installer.test.ts index 20939cb9a..7b7b1856d 100644 --- a/__tests__/distributors/zulu-installer.test.ts +++ b/__tests__/distributors/zulu-installer.test.ts @@ -334,6 +334,69 @@ describe('findPackageForDownload', () => { ); }); + describe('hotfix builds with a 4-segment java_version', () => { + // Mirrors the Azul Metadata API: 25.0.4.1 hotfix is reported as + // java_version=[25,0,4,1], openjdk_build_number=1 (SDKMAN '25.0.4+1.1'). + const hotfixManifest = [ + { + package_uuid: 'uuid-25.0.4+7', + name: 'zulu25.36.15-ca-jdk25.0.4-linux_x64.tar.gz', + download_url: + 'https://cdn.azul.com/zulu/bin/zulu25.36.15-ca-jdk25.0.4-linux_x64.tar.gz', + java_version: [25, 0, 4], + openjdk_build_number: 7, + distro_version: [25, 36, 15, 0], + latest: false, + availability_type: 'ca' + }, + { + package_uuid: 'uuid-25.0.4+1.1', + name: 'zulu25.36.205-ca-jdk25.0.4.1-linux_x64.tar.gz', + download_url: + 'https://cdn.azul.com/zulu/bin/zulu25.36.205-ca-jdk25.0.4.1-linux_x64.tar.gz', + java_version: [25, 0, 4, 1], + openjdk_build_number: 1, + distro_version: [25, 36, 205, 0], + latest: true, + availability_type: 'ca' + }, + { + package_uuid: 'uuid-25.0.3+9', + name: 'zulu25.34.17-ca-jdk25.0.3-linux_x64.tar.gz', + download_url: + 'https://cdn.azul.com/zulu/bin/zulu25.34.17-ca-jdk25.0.3-linux_x64.tar.gz', + java_version: [25, 0, 3], + openjdk_build_number: 9, + distro_version: [25, 34, 17, 0], + latest: false, + availability_type: 'ca' + } + ] as IZuluVersions[]; + + it.each([ + ['25.0.4+1.1', '25.0.4+1.1', 'uuid-25.0.4+1.1'], + ['25', '25.0.4+1.1', 'uuid-25.0.4+1.1'], + ['25.0.4', '25.0.4+1.1', 'uuid-25.0.4+1.1'], + ['25.0.4+7', '25.0.4+7', 'uuid-25.0.4+7'], + ['25.0.3', '25.0.3+9', 'uuid-25.0.3+9'] + ])('version is %s -> %s', async (input, expected, uuid) => { + const distribution = new ZuluDistribution({ + version: input, + architecture: 'x64', + packageType: 'jdk', + checkLatest: false + }); + distribution['getAvailableVersions'] = async () => hotfixManifest; + const result = await distribution['findPackageForDownload']( + distribution['version'] + ); + expect(result.version).toBe(expected); + expect(result.url).toBe( + hotfixManifest.find(item => item.package_uuid === uuid)!.download_url + ); + }); + }); + it('should throw an error', async () => { const distribution = new ZuluDistribution({ version: '18', diff --git a/__tests__/gpg.test.ts b/__tests__/gpg.test.ts index f9a0ca3cc..93c4593a6 100644 --- a/__tests__/gpg.test.ts +++ b/__tests__/gpg.test.ts @@ -236,7 +236,12 @@ describe('gpg tests', () => { process.env['RUNNER_TEMP'] = tempDir; }); - it.each(['success', 'import failure', 'verification failure'])( + it.each([ + 'success', + 'import failure', + 'verification failure', + 'gpgconf unavailable' + ])( 'uses a short macOS home or RUNNER_TEMP elsewhere and cleans up after %s', async outcome => { const longRunnerTemp = path.join( @@ -257,9 +262,16 @@ describe('gpg tests', () => { fs.writeFileSync(signaturePath, 'signature'); (tc.downloadTool as jest.Mock).mockResolvedValue(signaturePath); (exec.exec as jest.Mock).mockImplementation( - async (_command: string, args: string[]) => { + async (command: string, args: string[]) => { gpgHome = path.join(expectedParent, path.posix.basename(args[1])); expect(args[1]).toBe(gpg.toGpgPath(gpgHome)); + if (command === 'gpgconf') { + expect(fs.existsSync(gpgHome)).toBe(true); + if (outcome === 'gpgconf unavailable') { + throw new Error('gpgconf unavailable'); + } + return 0; + } if (process.platform === 'darwin') { expect( Buffer.byteLength(path.join(gpgHome, 'S.gpg-agent.browser')) @@ -287,13 +299,18 @@ describe('gpg tests', () => { 'https://example.com/jdk.tar.gz.sig', 'public key' ); - if (outcome === 'success') { + if (outcome === 'success' || outcome === 'gpgconf unavailable') { await verification; } else { await expect(verification).rejects.toThrow(outcome); } expect(exec.exec).toHaveBeenCalledTimes( - outcome === 'import failure' ? 1 : 2 + outcome === 'import failure' ? 2 : 3 + ); + expect(exec.exec).toHaveBeenLastCalledWith( + 'gpgconf', + ['--homedir', gpg.toGpgPath(gpgHome), '--kill', 'gpg-agent'], + {silent: true, ignoreReturnCode: true} ); expect(fs.existsSync(gpgHome)).toBe(false); expect(fs.existsSync(signaturePath)).toBe(false); @@ -368,7 +385,7 @@ describe('gpg tests', () => { ], expect.objectContaining({silent: true}) ); - expect(exec.exec).toHaveBeenCalledTimes(2); + expect(exec.exec).toHaveBeenCalledTimes(3); }); }); }); diff --git a/__tests__/java-platform-contract.test.ts b/__tests__/java-platform-contract.test.ts index 187a54a48..57a0df659 100644 --- a/__tests__/java-platform-contract.test.ts +++ b/__tests__/java-platform-contract.test.ts @@ -25,6 +25,7 @@ describe('Java platform capabilities', () => { ['aarch64', 'aarch64'], ['arm64', 'aarch64'], ['ppc64le', 'ppc64le'], + ['RiScV64', 'riscv64'], ['s390x', 's390x'] ])('normalizes architecture %s to %s', (input, expected) => { expect(normalizeArchitecture(input)).toBe(expected); @@ -68,6 +69,12 @@ describe('Java platform capabilities', () => { ); }); + it('allows Temurin on Linux riscv64', () => { + expect(validateJavaPlatform('temurin', 'linux', 'riscv64', '25')).toBe( + 'riscv64' + ); + }); + it('rejects OS-specific restrictions with a consistent diagnostic', () => { expect(() => validateJavaPlatform('oracle', 'win32', 'arm64', '21') @@ -115,6 +122,7 @@ describe('Java platform capabilities', () => { 'aarch64', 'ppc64le', 'ppc64', + 'riscv64', 's390x' ]) { expect(content).toContain(architecture); diff --git a/__tests__/util.test.ts b/__tests__/util.test.ts index 61143a596..87b10b74f 100644 --- a/__tests__/util.test.ts +++ b/__tests__/util.test.ts @@ -43,6 +43,7 @@ const core = await import('@actions/core'); const { convertVersionToSemver, + normalizeJavaVersionToSemver, getNextPageUrlFromLinkHeader, getVersionFromFileContent, isVersionSatisfies, @@ -188,6 +189,22 @@ describe('convertVersionToSemver', () => { }); }); +describe('normalizeJavaVersionToSemver', () => { + it.each([ + ['17', '17'], + ['17.0.8', '17.0.8'], + ['17.0.8+7', '17.0.8+7'], + ['11.0.9.1', '11.0.9+1'], + ['12.0.2.1.0', '12.0.2+1.0'], + ['26.0.2.1+1', '26.0.2+1.1'], + ['17.0.8.1+1080.1', '17.0.8+1.1080.1'], + ['>=11.0.9.1', '>=11.0.9.1'], + ['17.x', '17.x'] + ])('%s -> %s', (input: string, expected: string) => { + expect(normalizeJavaVersionToSemver(input)).toBe(expected); + }); +}); + describe('getNextPageUrlFromLinkHeader', () => { it.each([ [ @@ -276,6 +293,8 @@ describe('getVersionFromFileContent', () => { ['java=21.0.5-graal', '21.0.5', 'graalvm'], ['java=17.0.9-graalce', '17.0.9', 'graalvm'], ['java=11.0.25-librca', '11.0.25', 'liberica'], + ['java=25.0.4+1.1-librca', '25.0.4+1.1', 'liberica'], + ['java=25.0.4+1.1-zulu', '25.0.4+1.1', 'zulu'], ['java=11.0.25-ms', '11.0.25', 'microsoft'], ['java=21.0.5-oracle', '21.0.5', 'oracle'], ['java=11.0.25-sapmchn', '11.0.25', 'sapmachine'], diff --git a/action.yml b/action.yml index 1f244a223..eb32ec42c 100644 --- a/action.yml +++ b/action.yml @@ -17,7 +17,7 @@ inputs: required: false default: 'jdk' architecture: - description: "The architecture of the package (`x86`, `x64`, `armv7`, `aarch64`, `ppc64le`, `ppc64`, or `s390x`). Aliases `ia32`, `amd64`, `arm`, and `arm64` are normalized to `x86`, `x64`, `armv7`, and `aarch64`. Supported values vary by distribution and operating system. Defaults to the action runner's architecture." + description: "The architecture of the package (`x86`, `x64`, `armv7`, `aarch64`, `ppc64le`, `ppc64`, `riscv64`, or `s390x`). Aliases `ia32`, `amd64`, `arm`, and `arm64` are normalized to `x86`, `x64`, `armv7`, and `aarch64`. Supported values vary by distribution and operating system. Defaults to the action runner's architecture." required: false jdk-file: description: 'Path to where the compressed JDK is located' @@ -66,15 +66,21 @@ inputs: mvn-server-credentials: description: 'Multiline list of Maven server credentials in the format `server-id:USERNAME_ENV:PASSWORD_ENV`. When set, replaces the single server configured by server-id, server-username-env-var, and server-password-env-var.' required: false + mvn-server-repository-origins: + description: 'Multiline list of Maven credential origins in the format `server-id:repository-origin`. Each origin must belong to a configured server ID. Requires Maven 3.10+.' + required: false mvn-repositories: description: 'Multiline list of Maven dependency repositories in the format `repository-id:repository-url:snapshots-enabled`.' required: false + mvn-plugin-repositories: + description: 'Multiline list of Maven plugin repositories in the format `repository-id:repository-url:snapshots-enabled`.' + required: false mvn-repositories-include-central: - description: 'Include Maven Central in the generated dependency repositories profile. When false, Central is disabled unless an explicit repository with ID `central` is declared.' + description: 'Include Maven Central for dependency and plugin repositories in the generated profile. When false, Central is disabled for both types unless that type explicitly declares ID `central`. Has no effect when neither repository list is configured.' required: false default: true mvn-repositories-prioritize-central: - description: 'Place Maven Central before custom dependency repositories. Has no effect when Maven Central is excluded.' + description: 'Place Maven Central before custom dependency and plugin repositories. Has no effect when Maven Central is excluded or neither repository list is configured.' required: false default: true settings-path: diff --git a/dist/cleanup/767.index.js b/dist/cleanup/767.index.js index 35dd21b98..2fe79f8a6 100644 --- a/dist/cleanup/767.index.js +++ b/dist/cleanup/767.index.js @@ -18360,9 +18360,102 @@ function readAttributeStr(xmlData, i) { } /** - * Select all the attributes whether valid or invalid. - */ -const validAttrStrRegxp = new RegExp('(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s\\S])*?)\\5)?', 'g'); + * 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. + */ +function scanAttributeTokens(attrStr) { + const tokens = []; + const len = attrStr.length; + let i = 0; + + while (i < len) { + const tokenStart = i; + + // Leading whitespace before the name. + while (i < len && isWhiteSpace(attrStr[i])) i++; + if (i >= len) break; // trailing whitespace only — nothing left to read + + if (attrStr[i] === '=') { + // No name before this '=' — not a valid attribute start. Move past + // just this one character and try again from the next position. + i = tokenStart + 1; + continue; + } + + const leadingWs = attrStr.slice(tokenStart, i); + + // Attribute name — everything up to the next whitespace or '='. + const nameStart = i; + while (i < len && !isWhiteSpace(attrStr[i]) && attrStr[i] !== '=') i++; + const name = attrStr.slice(nameStart, i); + + // Optional whitespace + '='. + let equalsGroup; // whitespace + '=' text, or undefined if absent + let j = i; + while (j < len && isWhiteSpace(attrStr[j])) j++; + if (j < len && attrStr[j] === '=') { + equalsGroup = attrStr.slice(i, j + 1); + i = j + 1; + } + + // Optional whitespace + quoted value. + let quoteChar; + let value; + let k = i; + while (k < len && isWhiteSpace(attrStr[k])) k++; + if (k < len && (attrStr[k] === '"' || attrStr[k] === "'")) { + const valueStart = k + 1; + const closeIdx = attrStr.indexOf(attrStr[k], valueStart); + if (closeIdx !== -1) { + quoteChar = attrStr[k]; + value = attrStr.slice(valueStart, closeIdx); + i = closeIdx + 1; + } + // No closing quote found anywhere in the rest of the string — leave + // quoteChar/value undefined, same as the old regex's group failing + // to match a backreference-less run. + } + + const token = { startIndex: tokenStart }; + token[1] = leadingWs; + token[2] = name; + token[3] = equalsGroup; + token[4] = quoteChar !== undefined ? true : undefined; + token[5] = quoteChar; + token[6] = value; + tokens.push(token); + } + + return tokens; +} //attr, ="sd", a="amit's", a="sd"b="saf", ab cd="" @@ -18371,7 +18464,7 @@ function validateAttributeString(attrStr, options) { //if(attrStr.trim().length === 0) return true; //empty string - const matches = getAllMatches(attrStr, validAttrStrRegxp); + const matches = scanAttributeTokens(attrStr); const attrNames = {}; for (let i = 0; i < matches.length; i++) { @@ -18473,7 +18566,6 @@ function getLineNumberForPosition(xmlData, index) { function getPositionFromMatch(match) { return match.startIndex + match[1].length; } - ;// CONCATENATED MODULE: ./node_modules/fast-xml-parser/src/fxp.js @@ -59498,6 +59590,24 @@ const EXPANSION_MAX = 100_000; // realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M // characters) so legitimate input is unaffected. const EXPANSION_MAX_LENGTH = 4_000_000; +// `expand_` recurses once per level of brace *nesting* - both when expanding a +// set's comma members and when re-wrapping a set whose body is a single part. +// The CVE-2026-14257 fix made the *tail* iterative (recursion on `m.post`, one +// level per chained group), which left nesting depth unbounded: about 3,100 +// levels of `{{{...a,b...}}}` - only ~6KB of input - exhausted the native stack +// and crashed the process. `EXPANSION_MAX_DEPTH` bounds how deep the parser +// will follow nesting. It sits far above any realistic pattern and well below +// the depth at which the stack runs out. +const EXPANSION_MAX_DEPTH = 1_000; +// Bash keeps a quirk where a brace group followed by a comma set still expands +// (`{a},b}`). The parser implements it by rewriting the string and restarting +// the scan, absorbing one `}` per pass. `n` trailing braces therefore cost `n` +// full passes over a string that itself grows by one `escClose` sentinel each +// time - quadratic in `n`, with a ~26x constant from the sentinel's length. +// 128KB of `'{a}' + '}'.repeat(n) + ',z}'` blocked the event loop for 27 +// seconds to produce two results. `EXPANSION_MAX_REWRITES` bounds how many +// times the scan may restart. Real `{a},b}` input needs a handful. +const EXPANSION_MAX_REWRITES = 1_000; function numeric(str) { return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); } @@ -59517,37 +59627,52 @@ function unescapeBraces(str) { .replace(escCommaPattern, ',') .replace(escPeriodPattern, '.'); } +// Like `target.push(...items)` but doesn't overflow the stack +function pushAll(target, items) { + for (let i = 0; i < items.length; i++) { + target.push(items[i]); + } +} /** * Basically just str.split(","), but handling cases * where we have nested braced sections, which should be * treated as individual members, like {a,{b,c},d} */ function parseCommaParts(str) { - if (!str) { - return ['']; - } const parts = []; - const m = balanced('{', '}', str); - if (!m) { - return str.split(','); - } - const { pre, body, post } = m; - const p = pre.split(','); - p[p.length - 1] += '{' + body + '}'; - const postParts = parseCommaParts(post); - if (post.length) { - ; - p[p.length - 1] += postParts.shift(); - p.push.apply(p, postParts); - } - parts.push.apply(parts, p); - return parts; + // Walk the brace groups iteratively. Recursing on `post` once per group let a + // chain of them exhaust the stack - the parsing-side counterpart to + // the `expand_` overflow fixed for CVE-2026-14257, and not something `max` or + // `maxLength` can bound, since it happens before expansion. + // + // The part the next chunk continues + let carry = ''; + for (;;) { + const m = balanced('{', '}', str); + if (!m) { + const tail = str.split(','); + tail[0] = carry + tail[0]; + pushAll(parts, tail); + return parts; + } + const { pre, body, post } = m; + const p = pre.split(','); + p[0] = carry + p[0]; + p[p.length - 1] += '{' + body + '}'; + if (!post.length) { + pushAll(parts, p); + return parts; + } + carry = p.pop(); + pushAll(parts, p); + str = post; + } } function expand(str, options = {}) { if (!str) { return []; } - const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH } = options; + const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH, maxDepth = EXPANSION_MAX_DEPTH, maxRewrites = EXPANSION_MAX_REWRITES, } = options; // I don't know why Bash 4.3 does this, but it does. // Anything starting with {} will have the first two bytes preserved // but *only* at the top level, so {},a}b will not expand to anything, @@ -59557,7 +59682,7 @@ function expand(str, options = {}) { if (str.slice(0, 2) === '{}') { str = '\\{\\}' + str.slice(2); } - return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces); + return expand_(escapeBraces(str), max, maxLength, maxDepth, 0, maxRewrites, true).map(unescapeBraces); } function embrace(str) { return '{' + str + '}'; @@ -59652,7 +59777,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) { } return N; } -function expand_(str, max, maxLength, isTop) { +function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) { + // Too deeply nested to keep following: treat the rest as literal, the same + // way a group that cannot expand is already handled. Truncating rather than + // throwing keeps `expand` total, matching `max` and `maxLength`. + if (depth > maxDepth) { + return [str]; + } // Consume the string's top-level brace groups left to right, threading a // running set of combined prefixes (`acc`). Expanding the tail iteratively - // rather than recursing on `m.post` once per group - keeps the native stack @@ -59664,6 +59795,9 @@ function expand_(str, max, maxLength, isTop) { // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop // is on the final strings, so it is applied to whichever `combine` produces // them (the one with no brace set left in the tail). + // How many times the `{a},b}` rewrite below has restarted the scan. Each pass + // re-reads the whole string, so leaving this unbounded is quadratic. + let rewrites = 0; let dropEmpties = false; let firstGroup = true; for (;;) { @@ -59688,7 +59822,8 @@ function expand_(str, max, maxLength, isTop) { const isOptions = m.body.indexOf(',') >= 0; if (!isSequence && !isOptions) { // {a},b} - if (m.post.match(/,(?!,).*\}/)) { + if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) { + rewrites++; str = m.pre + '{' + m.body + escClose + m.post; isTop = true; continue; @@ -59708,7 +59843,7 @@ function expand_(str, max, maxLength, isTop) { let n = parseCommaParts(m.body); if (n.length === 1 && n[0] !== undefined) { // x{{a,b}}y ==> x{a}y x{b}y - n = expand_(n[0], max, maxLength, false).map(embrace); + n = expand_(n[0], max, maxLength, maxDepth, depth + 1, maxRewrites, false).map(embrace); //XXX is this necessary? Can't seem to hit it in tests. /* c8 ignore start */ if (n.length === 1) { @@ -59734,12 +59869,13 @@ function expand_(str, max, maxLength, isTop) { values = []; let valuesLength = 0; outer: for (let j = 0; j < n.length; j++) { - const expanded = expand_(n[j], max, maxLength, false); + const expanded = expand_(n[j], max, maxLength, maxDepth, depth + 1, maxRewrites, false); for (let k = 0; k < expanded.length; k++) { const v = expanded[k]; if (dropsEmpties && !v) continue; - if (values.length >= max || valuesLength + v.length > maxLength) { + if (values.length >= max || + valuesLength + v.length > maxLength) { break outer; } values.push(v); diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index 00ad7724f..03ab6fe1f 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -6009,11 +6009,77 @@ class Request { } } - onUpgrade (statusCode, headers, socket) { + /** + * @param {number|null} statusCode + * @param {Buffer[]|null} headers + * @param {import('node:stream').Duplex} socket + * @param {string} [statusText] + */ + onUpgrade (statusCode, headers, socket, statusText = '') { + this.onFinally() + assert(!this.aborted) assert(!this.completed) - return this[kHandler].onUpgrade(statusCode, headers, socket) + if (statusCode !== null) { + this.#publishUpgradeHeaders(statusCode, headers, statusText) + } + + const result = this[kHandler].onUpgrade(statusCode, headers, socket) + + if (!this.aborted) { + this.completed = true + if (statusCode !== null) { + this.#publishUpgradeTrailers() + } + } + + return result + } + + /** + * @param {number} statusCode + * @param {import('node:http2').IncomingHttpHeaders} headers + * @param {(headers: import('node:http2').IncomingHttpHeaders) => Buffer[]} parseHeaders + * @param {string} [statusText] + */ + onUpgradeResponse (statusCode, headers, parseHeaders, statusText = '') { + assert(!this.aborted) + assert(this.completed) + + if (channels.headers.hasSubscribers) { + this.#publishUpgradeHeaders(statusCode, parseHeaders(headers), statusText) + } + this.#publishUpgradeTrailers() + } + + /** + * @param {Error} error + */ + onUpgradeError (error) { + assert(!this.aborted) + assert(this.completed) + + if (channels.error.hasSubscribers) { + channels.error.publish({ request: this, error }) + } + } + + /** + * @param {number} statusCode + * @param {Buffer[]} headers + * @param {string} statusText + */ + #publishUpgradeHeaders (statusCode, headers, statusText) { + if (channels.headers.hasSubscribers) { + channels.headers.publish({ request: this, response: { statusCode, headers, statusText } }) + } + } + + #publishUpgradeTrailers () { + if (channels.trailers.hasSubscribers) { + channels.trailers.publish({ request: this, trailers: [] }) + } } onComplete (trailers) { @@ -7912,7 +7978,7 @@ class Parser { } onUpgrade (head) { - const { upgrade, client, socket, headers, statusCode } = this + const { upgrade, client, socket, headers, statusCode, statusText } = this assert(upgrade) assert(client[kSocket] === socket) @@ -7947,9 +8013,10 @@ class Parser { client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade')) try { - request.onUpgrade(statusCode, headers, socket) - } catch (err) { - util.destroy(socket, err) + request.onUpgrade(statusCode, headers, socket, statusText) + } catch (error) { + util.errorRequest(client, request, error) + util.destroy(socket, error) } client[kResume]() @@ -8356,7 +8423,7 @@ async function connectH1 (client, socket) { function clearIdleSocketValidation (socket) { if (socket[kIdleSocketValidationTimeout]) { - clearTimeout(socket[kIdleSocketValidationTimeout]) + clearImmediate(socket[kIdleSocketValidationTimeout]) socket[kIdleSocketValidationTimeout] = null } @@ -8365,15 +8432,23 @@ function clearIdleSocketValidation (socket) { function scheduleIdleSocketValidation (client, socket) { socket[kIdleSocketValidation] = 1 - socket[kIdleSocketValidationTimeout] = setTimeout(() => { + // Yield to the check phase (after poll) so unsolicited bytes / FIN / RST + // already pending on this idle keep-alive socket are processed before the + // next request is written (GHSA-35p6-xmwp-9g52). + // + // setTimeout(0) pays Node's ~1ms timer floor on every sequential reuse + // (#5493). setImmediate avoids that, but an *unref'd* Immediate lets poll + // block for ~500ms when the event loop is otherwise idle (#5600 / #5606). + // A ref'd Immediate both keeps the pending request alive and makes poll + // return immediately — the hybrid those issues asked for. + socket[kIdleSocketValidationTimeout] = setImmediate(() => { socket[kIdleSocketValidationTimeout] = null socket[kIdleSocketValidation] = 2 if (client[kSocket] === socket && !socket.destroyed) { client[kResume]() } - }, 0) - socket[kIdleSocketValidationTimeout].unref?.() + }) } /** @@ -8522,12 +8597,22 @@ function writeH1 (client, request) { const socket = client[kSocket] clearIdleSocketValidation(socket) - const abort = (err) => { - if (request.aborted || request.completed) { + /** + * @param {Error} [error] + */ + const abort = (error) => { + if (request.aborted) { return } - util.errorRequest(client, request, err || new RequestAbortedError()) + if (request.completed) { + if (request.upgrade || request.method === 'CONNECT') { + util.destroy(socket, new InformationalError('aborted')) + } + return + } + + util.errorRequest(client, request, error || new RequestAbortedError()) util.destroy(body) util.destroy(socket, new InformationalError('aborted')) @@ -8984,6 +9069,7 @@ module.exports = connectH1 const assert = __nccwpck_require__(4589) +const { errorMonitor } = __nccwpck_require__(8474) const { pipeline } = __nccwpck_require__(7075) const util = __nccwpck_require__(3440) const { @@ -9060,6 +9146,15 @@ function parseH2Headers (headers) { return result } +/** + * @param {import('node:http2').IncomingHttpHeaders} headers + * @returns {Buffer[]} + */ +function parseH2ResponseHeaders (headers) { + const { [HTTP2_HEADER_STATUS]: _statusCode, ...realHeaders } = headers + return parseH2Headers(realHeaders) +} + async function connectH2 (client, socket) { client[kSocket] = socket @@ -9280,22 +9375,32 @@ function writeH2 (client, request) { headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}` headers[HTTP2_HEADER_METHOD] = method - const abort = (err) => { - if (request.aborted || request.completed) { + /** + * @param {Error} [error] + */ + const abort = (error) => { + if (request.aborted) { return } - err = err || new RequestAbortedError() + if (request.completed) { + if (method === 'CONNECT' && stream != null) { + util.destroy(stream, error || new RequestAbortedError()) + } + return + } - util.errorRequest(client, request, err) + error = error || new RequestAbortedError() + + util.errorRequest(client, request, error) if (stream != null) { - util.destroy(stream, err) + util.destroy(stream, error) } // We do not destroy the socket as we can continue using the session // the stream get's destroyed and the session remains to create new streams - util.destroy(body, err) + util.destroy(body, error) client[kQueue][client[kRunningIdx]++] = null client[kResume]() } @@ -9314,25 +9419,57 @@ function writeH2 (client, request) { if (method === 'CONNECT') { session.ref() - // We are already connected, streams are pending, first request - // will create a new stream. We trigger a request to create the stream and wait until - // `ready` event is triggered // We disabled endStream to allow the user to write to the stream stream = session.request(headers, { endStream: false, signal }) + let upgradeResponseFinished = false - if (stream.id && !stream.pending) { - request.onUpgrade(null, null, stream) - ++session[kOpenStreams] - client[kQueue][client[kRunningIdx]++] = null - } else { - stream.once('ready', () => { + /** + * @param {import('node:http2').IncomingHttpHeaders} headers + */ + const onResponse = (headers) => { + upgradeResponseFinished = true + stream.off(errorMonitor, onUpgradeError) + request.onUpgradeResponse(Number(headers[HTTP2_HEADER_STATUS]), headers, parseH2ResponseHeaders) + } + + /** + * @param {Error} error + */ + const onUpgradeError = (error) => { + upgradeResponseFinished = true + stream.off('response', onResponse) + request.onUpgradeError(error) + } + + const onReady = () => { + try { request.onUpgrade(null, null, stream) - ++session[kOpenStreams] - client[kQueue][client[kRunningIdx]++] = null - }) + } catch (error) { + stream.off('response', onResponse) + abort(error) + return + } + + if (request.aborted) { + return + } + + stream.off('error', abort) + stream.once(errorMonitor, onUpgradeError) + client[kQueue][client[kRunningIdx]++] = null } + stream.once('response', onResponse) + stream.once('error', abort) + ++session[kOpenStreams] + onReady() + stream.once('close', () => { + if (!upgradeResponseFinished && request.completed) { + stream.off('response', onResponse) + stream.off(errorMonitor, onUpgradeError) + request.onUpgradeError(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`)) + } session[kOpenStreams] -= 1 if (session[kOpenStreams] === 0) session.unref() }) @@ -12031,6 +12168,7 @@ class RetryHandler { this.end = null this.etag = null this.resume = null + this.headersSent = false // Handle possible onConnect duplication this.handler.onConnect(reason => { @@ -12043,6 +12181,20 @@ class RetryHandler { }) } + checkpointResponseEnd (headers, resume) { + if (this.end == null && this.opts.method !== 'HEAD') { + const contentLength = headers['content-length'] + this.end = contentLength != null ? Number(contentLength) - 1 : null + + assert( + this.end == null || Number.isFinite(this.end), + 'invalid content-length' + ) + } + + this.resume = this.end != null ? resume : null + } + onRequestSent () { if (this.handler.onRequestSent) { this.handler.onRequestSent() @@ -12131,7 +12283,12 @@ class RetryHandler { this.retryCount += 1 if (statusCode >= 300) { - if (this.retryOpts.statusCodes.includes(statusCode) === false) { + // Only expose a response if no earlier attempt has reached the caller. + // Otherwise abort this attempt so the error settles the existing body + // instead of replacing it with a new response. + if (!this.headersSent && this.retryOpts.statusCodes.includes(statusCode) === false) { + this.headersSent = true + this.checkpointResponseEnd(headers, resume) return this.handler.onHeaders( statusCode, rawHeaders, @@ -12200,8 +12357,15 @@ class RetryHandler { const { start, size, end = size - 1 } = contentRange - assert(this.start === start, 'content-range mismatch') - assert(this.end == null || this.end === end, 'content-range mismatch') + if (this.start !== start || (this.end != null && this.end !== end)) { + this.abort( + new RequestRetryError('Content-Range mismatch', statusCode, { + headers, + data: { count: this.retryCount } + }) + ) + return false + } this.resume = resume return true @@ -12213,6 +12377,7 @@ class RetryHandler { const range = parseRangeHeader(headers['content-range']) if (range == null) { + this.headersSent = true return this.handler.onHeaders( statusCode, rawHeaders, @@ -12251,6 +12416,7 @@ class RetryHandler { ) this.resume = resume + this.headersSent = true this.etag = headers.etag != null ? headers.etag : null // Weak etags are not useful for comparison nor cache @@ -12290,7 +12456,7 @@ class RetryHandler { } onError (err) { - if (this.aborted || isDisturbed(this.opts.body)) { + if (this.aborted || isDisturbed(this.opts.body) || (this.headersSent && this.resume == null)) { return this.handler.onError(err) } @@ -16748,6 +16914,49 @@ const COLON = 0x3A */ const SPACE = 0x20 +const DATA = Buffer.from('data') +const EVENT = Buffer.from('event') +const ID = Buffer.from('id') +const RETRY = Buffer.from('retry') + +function isASCIINumberBytes (buffer, start) { + if (start >= buffer.length) { + return false + } + + for (let i = start; i < buffer.length; i++) { + if (buffer[i] < 0x30 || buffer[i] > 0x39) { + return false + } + } + + return true +} + +function isValidLastEventIdBytes (buffer, start) { + for (let i = start; i < buffer.length; i++) { + if (buffer[i] === 0x00) { + return false + } + } + + return true +} + +function isFieldName (line, length, field) { + if (length !== field.length) { + return false + } + + for (let i = 0; i < length; i++) { + if (line[i] !== field[i]) { + return false + } + } + + return true +} + /** * @typedef {object} EventSourceStreamEvent * @type {object} @@ -16788,11 +16997,14 @@ class EventSourceStream extends Transform { eventEndCheck = false /** - * @type {Buffer} + * @type {Buffer[]} */ - buffer = null + chunks = [] + chunkIndex = 0 pos = 0 + lineChunkIndex = 0 + linePos = 0 event = { data: undefined, @@ -16831,92 +17043,20 @@ class EventSourceStream extends Transform { return } - // Cache the chunk in the buffer, as the data might not be complete while - // processing it - // TODO: Investigate if there is a more performant way to handle - // incoming chunks - // see: https://github.com/nodejs/undici/issues/2630 - if (this.buffer) { - this.buffer = Buffer.concat([this.buffer, chunk]) - } else { - this.buffer = chunk - } + this.chunks.push(chunk) // Strip leading byte-order-mark if we opened the stream and started // the processing of the incoming data if (this.checkBOM) { - switch (this.buffer.length) { - case 1: - // Check if the first byte is the same as the first byte of the BOM - if (this.buffer[0] === BOM[0]) { - // If it is, we need to wait for more data - callback() - return - } - // Set the checkBOM flag to false as we don't need to check for the - // BOM anymore - this.checkBOM = false - - // The buffer only contains one byte so we need to wait for more data - callback() - return - case 2: - // Check if the first two bytes are the same as the first two bytes - // of the BOM - if ( - this.buffer[0] === BOM[0] && - this.buffer[1] === BOM[1] - ) { - // If it is, we need to wait for more data, because the third byte - // is needed to determine if it is the BOM or not - callback() - return - } - - // Set the checkBOM flag to false as we don't need to check for the - // BOM anymore - this.checkBOM = false - break - case 3: - // Check if the first three bytes are the same as the first three - // bytes of the BOM - if ( - this.buffer[0] === BOM[0] && - this.buffer[1] === BOM[1] && - this.buffer[2] === BOM[2] - ) { - // If it is, we can drop the buffered data, as it is only the BOM - this.buffer = Buffer.alloc(0) - // Set the checkBOM flag to false as we don't need to check for the - // BOM anymore - this.checkBOM = false - - // Await more data - callback() - return - } - // If it is not the BOM, we can start processing the data - this.checkBOM = false - break - default: - // The buffer is longer than 3 bytes, so we can drop the BOM if it is - // present - if ( - this.buffer[0] === BOM[0] && - this.buffer[1] === BOM[1] && - this.buffer[2] === BOM[2] - ) { - // Remove the BOM from the buffer - this.buffer = this.buffer.subarray(3) - } - - // Set the checkBOM flag to false as we don't need to check for the - this.checkBOM = false - break + if (this.handleBOM()) { + callback() + return } } - while (this.pos < this.buffer.length) { + while (this.hasCurrentByte()) { + const byte = this.currentByte() + // If the previous line ended with an end-of-line, we need to check // if the next character is also an end-of-line. if (this.eventEndCheck) { @@ -16929,10 +17069,9 @@ class EventSourceStream extends Transform { if (this.crlfCheck) { // If the current character is a line feed, we can remove it // from the buffer and reset the crlfCheck flag - if (this.buffer[this.pos] === LF) { - this.buffer = this.buffer.subarray(this.pos + 1) - this.pos = 0 + if (byte === LF) { this.crlfCheck = false + this.consumeCurrentByte() // It is possible that the line feed is not the end of the // event. We need to check if the next character is an @@ -16948,19 +17087,17 @@ class EventSourceStream extends Transform { this.crlfCheck = false } - if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) { + if (byte === LF || byte === CR) { // If the current character is a carriage return, we need to // set the crlfCheck flag to true, as we need to check if the // next character is a line feed so we can remove it from the // buffer - if (this.buffer[this.pos] === CR) { + if (byte === CR) { this.crlfCheck = true } - this.buffer = this.buffer.subarray(this.pos + 1) - this.pos = 0 - if ( - this.event.data !== undefined || this.event.event || this.event.id || this.event.retry) { + this.consumeCurrentByte() + if (this.hasPendingEvent()) { this.processEvent(this.event) } this.clearEvent() @@ -16974,22 +17111,18 @@ class EventSourceStream extends Transform { // If the current character is an end-of-line, we can process the // line - if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) { + if (byte === LF || byte === CR) { // If the current character is a carriage return, we need to // set the crlfCheck flag to true, as we need to check if the // next character is a line feed - if (this.buffer[this.pos] === CR) { + if (byte === CR) { this.crlfCheck = true } // In any case, we can process the line as we reached an // end-of-line character - this.parseLine(this.buffer.subarray(0, this.pos), this.event) - - // Remove the processed line from the buffer - this.buffer = this.buffer.subarray(this.pos + 1) - // Reset the position as we removed the processed line from the buffer - this.pos = 0 + this.parseLine(this.readLine(), this.event) + this.consumeCurrentByte() // A line was processed and this could be the end of the event. We need // to check if the next line is empty to determine if the event is // finished. @@ -16997,7 +17130,7 @@ class EventSourceStream extends Transform { continue } - this.pos++ + this.advanceCursor() } callback() @@ -17022,64 +17155,53 @@ class EventSourceStream extends Transform { return } - let field = '' - let value = '' + let fieldLength = line.length + let valueStart = line.length // If the line contains a U+003A COLON character (:) if (colonPosition !== -1) { - // Collect the characters on the line before the first U+003A COLON - // character (:), and let field be that string. - // TODO: Investigate if there is a more performant way to extract the - // field - // see: https://github.com/nodejs/undici/issues/2630 - field = line.subarray(0, colonPosition).toString('utf8') + fieldLength = colonPosition // Collect the characters on the line after the first U+003A COLON // character (:), and let value be that string. // If value starts with a U+0020 SPACE character, remove it from value. - let valueStart = colonPosition + 1 + valueStart = colonPosition + 1 if (line[valueStart] === SPACE) { ++valueStart } - // TODO: Investigate if there is a more performant way to extract the - // value - // see: https://github.com/nodejs/undici/issues/2630 - value = line.subarray(valueStart).toString('utf8') + } - // Otherwise, the string is not empty but does not contain a U+003A COLON - // character (:) - } else { - // Process the field using the steps described below, using the whole - // line as the field name, and the empty string as the field value. - field = line.toString('utf8') - value = '' - } - - // Modify the event with the field name and value. The value is also - // decoded as UTF-8 - switch (field) { - case 'data': - if (event[field] === undefined) { - event[field] = value - } else { - event[field] += `\n${value}` - } - break - case 'retry': - if (isASCIINumber(value)) { - event[field] = value - } - break - case 'id': - if (isValidLastEventId(value)) { - event[field] = value - } - break - case 'event': - if (value.length > 0) { - event[field] = value - } - break + if (isFieldName(line, fieldLength, DATA)) { + const value = line.toString('utf8', valueStart) + + if (event.data === undefined) { + event.data = value + } else { + event.data += `\n${value}` + } + return + } + + if (isFieldName(line, fieldLength, RETRY)) { + if (isASCIINumberBytes(line, valueStart)) { + event.retry = line.toString('utf8', valueStart) + } + return + } + + if (isFieldName(line, fieldLength, ID)) { + if (isValidLastEventIdBytes(line, valueStart)) { + event.id = line.toString('utf8', valueStart) + } + return + } + + if (isFieldName(line, fieldLength, EVENT)) { + const value = line.toString('utf8', valueStart) + + if (value.length > 0) { + event.event = value + } } } @@ -17109,13 +17231,152 @@ class EventSourceStream extends Transform { } clearEvent () { - this.event = { - data: undefined, - event: undefined, - id: undefined, - retry: undefined + this.event.data = undefined + this.event.event = undefined + this.event.id = undefined + this.event.retry = undefined + } + + hasPendingEvent () { + return this.event.data !== undefined || + this.event.event !== undefined || + this.event.id !== undefined || + this.event.retry !== undefined + } + + hasCurrentByte () { + return this.chunkIndex < this.chunks.length && + this.pos < this.chunks[this.chunkIndex].length + } + + currentByte () { + return this.chunks[this.chunkIndex][this.pos] + } + + consumeCurrentByte () { + this.advanceCursor() + this.syncLineStartToCursor() + } + + advanceCursor () { + this.pos++ + + while (this.chunkIndex < this.chunks.length && this.pos >= this.chunks[this.chunkIndex].length) { + this.chunkIndex++ + this.pos = 0 + } + } + + syncLineStartToCursor () { + this.lineChunkIndex = this.chunkIndex + this.linePos = this.pos + this.dropConsumedChunks() + } + + dropConsumedChunks () { + while (this.lineChunkIndex > 0) { + this.chunks.shift() + this.lineChunkIndex-- + this.chunkIndex-- + } + + if (this.chunkIndex === this.chunks.length) { + this.chunks.length = 0 + this.chunkIndex = 0 + this.pos = 0 + this.lineChunkIndex = 0 + this.linePos = 0 + } + } + + readLine () { + if (this.lineChunkIndex === this.chunkIndex) { + return this.chunks[this.chunkIndex].subarray(this.linePos, this.pos) + } + + const chunks = [] + let length = 0 + + for (let i = this.lineChunkIndex; i <= this.chunkIndex; i++) { + const chunk = this.chunks[i] + const start = i === this.lineChunkIndex ? this.linePos : 0 + const end = i === this.chunkIndex ? this.pos : chunk.length + const slice = chunk.subarray(start, end) + length += slice.length + chunks.push(slice) + } + + return Buffer.concat(chunks, length) + } + + peekBufferedByte (offset) { + let chunkIndex = this.lineChunkIndex + let pos = this.linePos + + while (chunkIndex < this.chunks.length) { + const chunk = this.chunks[chunkIndex] + const remaining = chunk.length - pos + + if (offset < remaining) { + return chunk[pos + offset] + } + + offset -= remaining + chunkIndex++ + pos = 0 } } + + discardLeadingBytes (count) { + while (count > 0 && this.lineChunkIndex < this.chunks.length) { + const chunk = this.chunks[this.lineChunkIndex] + const remaining = chunk.length - this.linePos + + if (count < remaining) { + this.linePos += count + count = 0 + } else { + count -= remaining + this.lineChunkIndex++ + this.linePos = 0 + } + } + + this.chunkIndex = this.lineChunkIndex + this.pos = this.linePos + this.dropConsumedChunks() + } + + handleBOM () { + const first = this.peekBufferedByte(0) + const second = this.peekBufferedByte(1) + const third = this.peekBufferedByte(2) + + if (second === undefined) { + if (first === BOM[0]) { + return true + } + + this.checkBOM = false + return true + } + + if (third === undefined) { + if (first === BOM[0] && second === BOM[1]) { + return true + } + + this.checkBOM = false + return false + } + + if (first === BOM[0] && second === BOM[1] && third === BOM[2]) { + this.discardLeadingBytes(3) + } + + this.checkBOM = false + return !this.hasCurrentByte() + } } module.exports = { @@ -28383,7 +28644,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish, // is specified, the server needs to include the same field and one of // the selected subprotocol values in its response for the connection to // be established. - if (!requestProtocols.includes(secProtocol)) { + if (requestProtocols === null || !requestProtocols.includes(secProtocol)) { failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.') return } @@ -29144,7 +29405,12 @@ class PerMessageDeflate { if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) { callback(new MessageSizeExceededError()) + // The inflater may still hold buffered input that can emit a late + // zlib error. Remove the data listener, then deterministically stop + // the stream so a subsequent 'error' cannot fire without a listener + // (which would terminate the process as an unhandled error event). this.#inflate.removeAllListeners() + this.#inflate.destroy() this.#inflate = null return } @@ -30774,7 +31040,7 @@ module.exports = { /* harmony export */ gk: () => (/* binding */ INPUT_CACHE), /* harmony export */ wG: () => (/* binding */ INPUT_JOB_STATUS) /* harmony export */ }); -/* unused harmony exports MACOS_JAVA_CONTENT_POSTFIX, INPUT_JAVA_VERSION, INPUT_JAVA_VERSION_FILE, INPUT_ARCHITECTURE, INPUT_JAVA_PACKAGE, INPUT_DISTRIBUTION, INPUT_JDK_FILE, INPUT_JDK_FILE_DEPRECATED, INPUT_CHECK_LATEST, INPUT_FORCE_DOWNLOAD, INPUT_SET_DEFAULT, INPUT_PROBLEM_MATCHER, INPUT_VERIFY_SIGNATURE, INPUT_VERIFY_SIGNATURE_PUBLIC_KEY, SIGNATURE_VERIFICATION_DOCUMENTATION_URL, SIGNATURE_VERIFICATION_FAILURE_HELP, INPUT_MVN_SERVER_CREDENTIALS, INPUT_MVN_REPOSITORIES, INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL, INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL, INPUT_SERVER_ID, INPUT_SERVER_USERNAME_ENV_VAR, INPUT_SERVER_PASSWORD_ENV_VAR, INPUT_SERVER_USERNAME_DEPRECATED, INPUT_SERVER_PASSWORD_DEPRECATED, INPUT_SETTINGS_PATH, INPUT_OVERWRITE_SETTINGS, INPUT_GPG_PRIVATE_KEY, INPUT_GPG_PASSPHRASE_ENV_VAR, INPUT_GPG_PASSPHRASE_DEPRECATED, INPUT_DEFAULT_SERVER_USERNAME, INPUT_DEFAULT_SERVER_PASSWORD, INPUT_DEFAULT_GPG_PRIVATE_KEY, INPUT_DEFAULT_GPG_PASSPHRASE, MAVEN_GPG_PASSPHRASE_DEFAULT_ENV, GPG_PASSPHRASE_PROFILE_ID, MAVEN_REPOSITORIES_PROFILE_ID, MAVEN_CENTRAL_REPOSITORY_ID, MAVEN_CENTRAL_REPOSITORY_URL, INPUT_CACHE_DEPENDENCY_PATH, INPUT_CACHE_PATH, M2_DIR, MVN_SETTINGS_FILE, MVN_TOOLCHAINS_FILE, INPUT_MVN_TOOLCHAIN_ID, INPUT_MVN_TOOLCHAIN_VENDOR, INPUT_SHOW_DOWNLOAD_PROGRESS, MAVEN_ARGS_ENV, MAVEN_NO_TRANSFER_PROGRESS_FLAG, MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG, DISTRIBUTIONS_ONLY_MAJOR_VERSION */ +/* unused harmony exports MACOS_JAVA_CONTENT_POSTFIX, INPUT_JAVA_VERSION, INPUT_JAVA_VERSION_FILE, INPUT_ARCHITECTURE, INPUT_JAVA_PACKAGE, INPUT_DISTRIBUTION, INPUT_JDK_FILE, INPUT_JDK_FILE_DEPRECATED, INPUT_CHECK_LATEST, INPUT_FORCE_DOWNLOAD, INPUT_SET_DEFAULT, INPUT_PROBLEM_MATCHER, INPUT_VERIFY_SIGNATURE, INPUT_VERIFY_SIGNATURE_PUBLIC_KEY, SIGNATURE_VERIFICATION_DOCUMENTATION_URL, SIGNATURE_VERIFICATION_FAILURE_HELP, INPUT_MVN_SERVER_CREDENTIALS, INPUT_MVN_SERVER_REPOSITORY_ORIGINS, INPUT_MVN_REPOSITORIES, INPUT_MVN_PLUGIN_REPOSITORIES, INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL, INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL, INPUT_SERVER_ID, INPUT_SERVER_USERNAME_ENV_VAR, INPUT_SERVER_PASSWORD_ENV_VAR, INPUT_SERVER_USERNAME_DEPRECATED, INPUT_SERVER_PASSWORD_DEPRECATED, INPUT_SETTINGS_PATH, INPUT_OVERWRITE_SETTINGS, INPUT_GPG_PRIVATE_KEY, INPUT_GPG_PASSPHRASE_ENV_VAR, INPUT_GPG_PASSPHRASE_DEPRECATED, INPUT_DEFAULT_SERVER_USERNAME, INPUT_DEFAULT_SERVER_PASSWORD, INPUT_DEFAULT_GPG_PRIVATE_KEY, INPUT_DEFAULT_GPG_PASSPHRASE, MAVEN_GPG_PASSPHRASE_DEFAULT_ENV, GPG_PASSPHRASE_PROFILE_ID, MAVEN_REPOSITORIES_PROFILE_ID, MAVEN_CENTRAL_REPOSITORY_ID, MAVEN_CENTRAL_REPOSITORY_URL, INPUT_CACHE_DEPENDENCY_PATH, INPUT_CACHE_PATH, M2_DIR, MVN_SETTINGS_FILE, MVN_TOOLCHAINS_FILE, INPUT_MVN_TOOLCHAIN_ID, INPUT_MVN_TOOLCHAIN_VENDOR, INPUT_SHOW_DOWNLOAD_PROGRESS, MAVEN_ARGS_ENV, MAVEN_NO_TRANSFER_PROGRESS_FLAG, MAVEN_NO_TRANSFER_PROGRESS_LONG_FLAG, DISTRIBUTIONS_ONLY_MAJOR_VERSION */ const MACOS_JAVA_CONTENT_POSTFIX = 'Contents/Home'; const INPUT_JAVA_VERSION = 'java-version'; const INPUT_JAVA_VERSION_FILE = 'java-version-file'; @@ -30792,7 +31058,9 @@ const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key'; const SIGNATURE_VERIFICATION_DOCUMENTATION_URL = 'https://github.com/actions/setup-java#download-integrity-and-signatures'; const SIGNATURE_VERIFICATION_FAILURE_HELP = (/* unused pure expression or super */ null && (`If this is a legitimate vendor signing-key rotation, see ${SIGNATURE_VERIFICATION_DOCUMENTATION_URL} for instructions to configure the updated public key or temporarily disable signature verification.`)); const INPUT_MVN_SERVER_CREDENTIALS = 'mvn-server-credentials'; +const INPUT_MVN_SERVER_REPOSITORY_ORIGINS = 'mvn-server-repository-origins'; const INPUT_MVN_REPOSITORIES = 'mvn-repositories'; +const INPUT_MVN_PLUGIN_REPOSITORIES = 'mvn-plugin-repositories'; const INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL = 'mvn-repositories-include-central'; const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL = 'mvn-repositories-prioritize-central'; const INPUT_SERVER_ID = 'server-id'; @@ -30848,7 +31116,7 @@ const DISTRIBUTIONS_ONLY_MAJOR_VERSION = (/* unused pure expression or super */ /* harmony export */ Vt: () => (/* binding */ getBooleanInput), /* harmony export */ lN: () => (/* binding */ isJdkCacheEnabled) /* harmony export */ }); -/* unused harmony exports getVersionFromToolcachePath, extractJdkFile, cacheJdkDir, getJavaVersionFromReleaseFile, getDownloadArchiveExtension, isVersionSatisfies, getToolcachePath, isGhes, getVersionFromFileContent, convertVersionToSemver, getArtifactFingerprint, getGitHubToken, getGitHubHttpHeaders, MAX_PAGINATION_PAGES, getNextPageUrlFromLinkHeader, validatePaginationUrl, renameWinArchive, getLatestMajorVersion */ +/* unused harmony exports getVersionFromToolcachePath, extractJdkFile, cacheJdkDir, getJavaVersionFromReleaseFile, getDownloadArchiveExtension, isVersionSatisfies, getToolcachePath, isGhes, getVersionFromFileContent, convertVersionToSemver, normalizeJavaVersionToSemver, getArtifactFingerprint, getGitHubToken, getGitHubHttpHeaders, MAX_PAGINATION_PAGES, getNextPageUrlFromLinkHeader, validatePaginationUrl, renameWinArchive, getLatestMajorVersion */ /* harmony import */ var os__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(857); /* harmony import */ var os__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__nccwpck_require__.n(os__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __nccwpck_require__(6928); @@ -31247,6 +31515,20 @@ function convertVersionToSemver(version) { } return mainVersion; } +/** + * Java versions (JEP 322) can contain more numeric fields than SemVer allows, + * e.g. '11.0.9.1' or Temurin respins such as '26.0.2.1+1'. Move the extra + * fields into SemVer build metadata ('11.0.9+1', '26.0.2+1.1'). Any other + * input (ranges, regular SemVer versions) is returned unchanged. + */ +function normalizeJavaVersionToSemver(version) { + const match = /^(\d+(?:\.\d+){3,})(?:\+([0-9A-Za-z.-]+))?$/.exec(version); + if (!match) { + return version; + } + const converted = convertVersionToSemver(match[1]); + return match[2] ? `${converted}.${match[2]}` : converted; +} /** * Builds a validator for the bytes currently served by a URL from the response * headers of a HEAD request. A vendor's `/latest/` URL never changes, so this @@ -35832,13 +36114,16 @@ async function removeGpgHome(gpgHome) { if (!external_fs_.existsSync(resolvedGpgHome)) { return; } + await stopGpgAgent(resolvedGpgHome); + await lib_io/* rmRF */.Yz(resolvedGpgHome); +} +async function stopGpgAgent(gpgHome) { try { - await lib_exec/* exec */.m('gpgconf', ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); + await lib_exec/* exec */.m('gpgconf', ['--homedir', toGpgPath(gpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); } catch { // gpgconf may be unavailable, but directory removal must still be attempted. } - await lib_io/* rmRF */.Yz(resolvedGpgHome); } async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { const signaturePath = await tc.downloadTool(signatureUrl); @@ -35884,6 +36169,7 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten ], options); } finally { + await stopGpgAgent(gpgHome); await io.rmRF(signaturePath); await io.rmRF(gpgHome); } diff --git a/dist/setup/220.index.js b/dist/setup/220.index.js index f5cfebc79..d1ae361fb 100644 --- a/dist/setup/220.index.js +++ b/dist/setup/220.index.js @@ -263,13 +263,16 @@ async function removeGpgHome(gpgHome) { if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) { return; } + await stopGpgAgent(resolvedGpgHome); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome); +} +async function stopGpgAgent(gpgHome) { try { - await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(gpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); } catch { // gpgconf may be unavailable, but directory removal must still be attempted. } - await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome); } async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl); @@ -315,6 +318,7 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten ], options); } finally { + await stopGpgAgent(gpgHome); await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath); await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); } diff --git a/dist/setup/242.index.js b/dist/setup/242.index.js index 12e3d457d..1b0369a65 100644 --- a/dist/setup/242.index.js +++ b/dist/setup/242.index.js @@ -730,12 +730,9 @@ class JavaBase { } // Java uses a versioning scheme (JEP 322) that can contain more numeric // fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such - // exact versions to SemVer build notation ('18.0.1+1') so they are - // accepted. Ranges and versions that already carry build metadata are - // left untouched. - if (/^\d+(\.\d+){3,}$/.test(version)) { - version = (0,util/* convertVersionToSemver */.ZY)(version); - } + // exact versions to SemVer build notation ('18.0.1+1', or '26.0.2+1.1' + // for '26.0.2.1+1') so they are accepted. Ranges are left untouched. + version = (0,util/* normalizeJavaVersionToSemver */.zZ)(version); if (!semver_default().validRange(version)) { throw new Error(`The string '${version}' is not valid SemVer notation for a Java version. Please check README file for code snippets and more detailed information`); } diff --git a/dist/setup/463.index.js b/dist/setup/463.index.js index 02b5bfc93..5267d4c81 100644 --- a/dist/setup/463.index.js +++ b/dist/setup/463.index.js @@ -109,7 +109,7 @@ class TemurinDistribution extends base_installer/* JavaBase */.O { const formattedVersion = this.stable ? item.version_data.semver : item.version_data.semver.replace('-beta+', '+'); - return { + const release = { version: formattedVersion, url: item.binaries[0].package.link, signatureUrl: item.binaries[0].package.signature_link, @@ -119,15 +119,29 @@ class TemurinDistribution extends base_installer/* JavaBase */.O { source: item.binaries[0].package.checksum_link } }; + return { + release, + openjdkVersion: getOpenJdkSemverVersion(item.version_data) + }; }); + // The Adoptium API `semver` folds the JEP 322 patch field into the build + // number ('26.0.2.1+1' -> '26.0.2+101') and appends extra metadata for LTS + // releases ('25.0.4+7' -> '25.0.4+7.0.LTS'). Exact versions requested by + // users follow the OpenJDK notation instead, so also match them against a + // key derived from the OpenJDK version fields ('26.0.2+1.1', '25.0.4+7'). + const isExactBuildRequest = (semver_default().parse(version)?.build.length ?? 0) > 0; const satisfiedVersions = availableVersionsWithBinaries - .filter(item => (0,util/* isVersionSatisfies */.y)(version, item.version)) + .filter(({ release, openjdkVersion }) => (0,util/* isVersionSatisfies */.y)(version, release.version) || + (isExactBuildRequest && + openjdkVersion !== null && + semver_default().compareBuild(version, openjdkVersion) === 0)) + .map(({ release }) => release) .sort((a, b) => { return -semver_default().compareBuild(a.version, b.version); }); const resolvedFullVersion = satisfiedVersions.length > 0 ? satisfiedVersions[0] : null; if (!resolvedFullVersion) { - const availableVersionStrings = availableVersionsWithBinaries.map(item => item.version); + const availableVersionStrings = availableVersionsWithBinaries.map(({ release }) => release.version); throw this.createVersionNotFoundError(version, availableVersionStrings); } return resolvedFullVersion; @@ -279,6 +293,20 @@ class TemurinDistribution extends base_installer/* JavaBase */.O { return architecture === 'armv7' ? 'arm' : architecture; } } +/** + * Builds a SemVer version from the OpenJDK version fields reported by the + * Adoptium API, e.g. '26.0.2.1+1' -> '26.0.2+1.1' and '25.0.4+7-LTS' -> + * '25.0.4+7'. Returns null if the fields cannot form a valid SemVer version. + */ +function getOpenJdkSemverVersion(versionData) { + const { major, minor, security, patch, build } = versionData; + if (build === undefined || build === null) { + return null; + } + const buildMetadata = patch ? `${patch}.${build}` : `${build}`; + const version = `${major}.${minor}.${security}+${buildMetadata}`; + return semver_default().valid(version) ? version : null; +} /***/ }), @@ -375,13 +403,16 @@ async function removeGpgHome(gpgHome) { if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) { return; } + await stopGpgAgent(resolvedGpgHome); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome); +} +async function stopGpgAgent(gpgHome) { try { - await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(gpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); } catch { // gpgconf may be unavailable, but directory removal must still be attempted. } - await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome); } async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl); @@ -427,6 +458,7 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten ], options); } finally { + await stopGpgAgent(gpgHome); await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath); await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); } diff --git a/dist/setup/63.index.js b/dist/setup/63.index.js index b441e4523..3b8ac9758 100644 --- a/dist/setup/63.index.js +++ b/dist/setup/63.index.js @@ -128,7 +128,8 @@ class LibericaDistributions extends _base_installer_js__WEBPACK_IMPORTED_MODULE_ } } convertVersionToSemver(version) { - const { buildVersion, featureVersion, interimVersion, updateVersion } = version; + const { featureVersion, interimVersion, updateVersion } = version; + const buildVersion = version.version.split('+')[1] || version.buildVersion; const mainVersion = [featureVersion, interimVersion, updateVersion].join('.'); if (buildVersion != 0) { return `${mainVersion}+${buildVersion}`; diff --git a/dist/setup/81.index.js b/dist/setup/81.index.js index 3ac82d8fc..39f98e377 100644 --- a/dist/setup/81.index.js +++ b/dist/setup/81.index.js @@ -7,6 +7,7 @@ export const modules = { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { +/* harmony export */ addMavenServerRepositoryOrigins: () => (/* binding */ addMavenServerRepositoryOrigins), /* harmony export */ configureAuthentication: () => (/* binding */ configureAuthentication), /* harmony export */ createAuthenticationSettings: () => (/* binding */ createAuthenticationSettings), /* harmony export */ generate: () => (/* binding */ generate), @@ -37,6 +38,7 @@ __webpack_require__.r(__webpack_exports__); +const MAVEN_REPOSITORY_ORIGIN = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#@]+\/?$/; async function configureAuthentication() { const servers = getMavenServerSettings(); const repositorySettings = getMavenRepositorySettings(); @@ -74,16 +76,16 @@ function getInputWithDeprecatedAlias(inputName, deprecatedInputName, defaultValu // only exported for testing purposes function getMavenServerSettings() { const entries = _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getMultilineInput */ .q3(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_SERVER_CREDENTIALS */ .MM); - if (entries.some(entry => entry.trim())) { - return parseMavenServerCredentials(entries); - } - return [ - { - id: _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getInput */ .V4(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_ID */ .fd), - usernameEnvVar: getInputWithDeprecatedAlias(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_USERNAME_ENV_VAR */ .sc, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_USERNAME_DEPRECATED */ .sp, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_DEFAULT_SERVER_USERNAME */ .Wj), - passwordEnvVar: getInputWithDeprecatedAlias(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_PASSWORD_ENV_VAR */ .r4, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_PASSWORD_DEPRECATED */ .Vt, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_DEFAULT_SERVER_PASSWORD */ .xp) - } - ]; + const servers = entries.some(entry => entry.trim()) + ? parseMavenServerCredentials(entries) + : [ + { + id: _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getInput */ .V4(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_ID */ .fd), + usernameEnvVar: getInputWithDeprecatedAlias(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_USERNAME_ENV_VAR */ .sc, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_USERNAME_DEPRECATED */ .sp, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_DEFAULT_SERVER_USERNAME */ .Wj), + passwordEnvVar: getInputWithDeprecatedAlias(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_PASSWORD_ENV_VAR */ .r4, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_SERVER_PASSWORD_DEPRECATED */ .Vt, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_DEFAULT_SERVER_PASSWORD */ .xp) + } + ]; + return addMavenServerRepositoryOrigins(servers, _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getMultilineInput */ .q3(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_SERVER_REPOSITORY_ORIGINS */ .Pt)); } // only exported for testing purposes function parseMavenServerCredentials(entries) { @@ -110,20 +112,78 @@ function parseMavenServerCredentials(entries) { return servers; } // only exported for testing purposes +function addMavenServerRepositoryOrigins(servers, entries) { + const serverIds = new Set(servers.map(server => server.id)); + const originsByServer = new Map(); + entries.forEach((entry, index) => { + if (!entry.trim()) { + return; + } + const separator = entry.indexOf(':'); + if (separator <= 0 || separator === entry.length - 1) { + throw new Error(`Invalid mvn-server-repository-origins entry at line ${index + 1}. Expected format: server-id:repository-origin`); + } + const id = entry.slice(0, separator).trim(); + const value = entry.slice(separator + 1).trim(); + if (!id || !value) { + throw new Error(`Invalid mvn-server-repository-origins entry at line ${index + 1}. Server ID and repository origin are required`); + } + if (!serverIds.has(id)) { + throw new Error(`Unknown server-id '${id}' in mvn-server-repository-origins at line ${index + 1}`); + } + let url; + try { + if (!MAVEN_REPOSITORY_ORIGIN.test(value)) { + throw new Error(); + } + url = new URL(value); + } + catch { + throw new Error(`Invalid repository origin '${value}' in mvn-server-repository-origins at line ${index + 1}`); + } + if (!url.host || + url.username || + url.password || + (url.pathname !== '' && url.pathname !== '/') || + url.search || + url.hash) { + throw new Error(`Invalid repository origin '${value}' in mvn-server-repository-origins at line ${index + 1}`); + } + const explicitPort = /:(\d+)\/?$/.exec(value)?.[1]; + const port = explicitPort ? Number.parseInt(explicitPort, 10) : undefined; + const includePort = port !== undefined && + !((url.protocol === 'http:' && port === 80) || + (url.protocol === 'https:' && port === 443)); + const origin = `${url.protocol}//${url.hostname}${includePort ? `:${port}` : ''}`; + const origins = originsByServer.get(id) || []; + if (!origins.includes(origin)) { + origins.push(origin); + originsByServer.set(id, origins); + } + }); + return servers.map(server => { + const repositoryOrigins = originsByServer.get(server.id); + return repositoryOrigins ? { ...server, repositoryOrigins } : server; + }); +} +// only exported for testing purposes function getMavenRepositorySettings() { const entries = _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getMultilineInput */ .q3(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_REPOSITORIES */ .W2); - if (!entries.some(entry => entry.trim())) { + const pluginEntries = _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .getMultilineInput */ .q3(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_PLUGIN_REPOSITORIES */ .gR); + if (!entries.some(entry => entry.trim()) && + !pluginEntries.some(entry => entry.trim())) { return undefined; } const includeCentral = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getBooleanInput */ .Vt)(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL */ .H5, true); return { repositories: parseMavenRepositories(entries, includeCentral), + pluginRepositories: parseMavenRepositories(pluginEntries, includeCentral, _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_PLUGIN_REPOSITORIES */ .gR), includeCentral, prioritizeCentral: (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getBooleanInput */ .Vt)(_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL */ .OT, true) }; } // only exported for testing purposes -function parseMavenRepositories(entries, includeCentral) { +function parseMavenRepositories(entries, includeCentral, inputName = _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_REPOSITORIES */ .W2) { const repositories = []; const repositoryIds = new Set(); entries.forEach((entry, index) => { @@ -133,7 +193,7 @@ function parseMavenRepositories(entries, includeCentral) { const firstSeparator = entry.indexOf(':'); const lastSeparator = entry.lastIndexOf(':'); if (firstSeparator <= 0 || lastSeparator <= firstSeparator) { - throw new Error(`Invalid mvn-repositories entry at line ${index + 1}. Expected format: repository-id:repository-url:snapshots-enabled`); + throw new Error(`Invalid ${inputName} entry at line ${index + 1}. Expected format: repository-id:repository-url:snapshots-enabled`); } const id = entry.slice(0, firstSeparator).trim(); const url = entry.slice(firstSeparator + 1, lastSeparator).trim(); @@ -142,13 +202,13 @@ function parseMavenRepositories(entries, includeCentral) { .trim() .toLowerCase(); if (!id || !url || !snapshotsValue) { - throw new Error(`Invalid mvn-repositories entry at line ${index + 1}. repository-id, repository URL, and snapshots-enabled are required`); + throw new Error(`Invalid ${inputName} entry at line ${index + 1}. repository-id, repository URL, and snapshots-enabled are required`); } if (snapshotsValue !== 'true' && snapshotsValue !== 'false') { - throw new Error(`Invalid snapshots-enabled value '${snapshotsValue}' in mvn-repositories entry at line ${index + 1}. Expected true or false`); + throw new Error(`Invalid snapshots-enabled value '${snapshotsValue}' in ${inputName} entry at line ${index + 1}. Expected true or false`); } if (repositoryIds.has(id)) { - throw new Error(`Duplicate repository-id '${id}' in mvn-repositories input`); + throw new Error(`Duplicate repository-id '${id}' in ${inputName} input`); } if (includeCentral && id === _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_ID */ .xg) { throw new Error(`Repository-id '${_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_ID */ .xg}' is reserved when ${_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL */ .H5} is enabled`); @@ -162,6 +222,37 @@ function parseMavenRepositories(entries, includeCentral) { }); return repositories; } +function generateRepositorySection(customRepositories, settings, element) { + if (!customRepositories.length && settings.includeCentral) { + return []; + } + const centralRepository = { + id: _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_ID */ .xg, + url: _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_URL */ .jv, + snapshotsEnabled: false + }; + const customCentralConfigured = customRepositories.some(repository => repository.id === _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_ID */ .xg); + const repositories = settings.includeCentral + ? settings.prioritizeCentral + ? [centralRepository, ...customRepositories] + : [...customRepositories, centralRepository] + : customCentralConfigured + ? customRepositories + : [...customRepositories, { ...centralRepository, releasesEnabled: false }]; + const container = element === 'repository' ? 'repositories' : 'pluginRepositories'; + const lines = [` <${container}>`]; + for (const repository of repositories) { + lines.push(` <${element}>`, ` ${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(repository.id)}`, ` ${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(repository.url)}`, ...(repository.releasesEnabled === undefined + ? [] + : [ + ' ', + ` ${repository.releasesEnabled}`, + ' ' + ]), ' ', ` ${repository.snapshotsEnabled}`, ' ', ` `); + } + lines.push(` `); + return lines; +} async function createAuthenticationSettings(servers, settingsDirectory, overwriteSettings, gpgPassphraseEnvVar = undefined, repositorySettings = undefined) { _actions_core__WEBPACK_IMPORTED_MODULE_1__/* .info */ .pq(`Creating ${_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MVN_SETTINGS_FILE */ .vO} with server-id: ${servers.map(server => server.id).join(', ')}`); // when an alternate m2 location is specified use only that location (no .m2 directory) @@ -187,39 +278,21 @@ function generate(servers, gpgPassphraseEnvVar, repositorySettings) { ' ' ]; for (const server of servers) { - lines.push(' ', ` ${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(server.id)}`, ` ${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(`\${env.${server.usernameEnvVar}}`)}`, ` ${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(`\${env.${server.passwordEnvVar}}`)}`, ' '); + lines.push(' ', ` ${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(server.id)}`, ` ${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(`\${env.${server.usernameEnvVar}}`)}`, ` ${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(`\${env.${server.passwordEnvVar}}`)}`); + if (server.repositoryOrigins) { + lines.push(' '); + for (const origin of server.repositoryOrigins) { + lines.push(` ${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(origin)}`); + } + lines.push(' '); + } + lines.push(' '); } lines.push(' '); if (repositorySettings || includeGpgPassphraseProfile) { lines.push(' '); if (repositorySettings) { - const centralRepository = { - id: _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_ID */ .xg, - url: _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_URL */ .jv, - snapshotsEnabled: false - }; - const customCentralConfigured = repositorySettings.repositories.some(repository => repository.id === _constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_CENTRAL_REPOSITORY_ID */ .xg); - const repositories = repositorySettings.includeCentral - ? repositorySettings.prioritizeCentral - ? [centralRepository, ...repositorySettings.repositories] - : [...repositorySettings.repositories, centralRepository] - : customCentralConfigured - ? repositorySettings.repositories - : [ - ...repositorySettings.repositories, - { ...centralRepository, releasesEnabled: false } - ]; - lines.push(' ', ` ${_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_REPOSITORIES_PROFILE_ID */ .hq}`, ' '); - for (const repository of repositories) { - lines.push(' ', ` ${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(repository.id)}`, ` ${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(repository.url)}`, ...(repository.releasesEnabled === undefined - ? [] - : [ - ' ', - ` ${repository.releasesEnabled}`, - ' ' - ]), ' ', ` ${repository.snapshotsEnabled}`, ' ', ' '); - } - lines.push(' ', ' '); + lines.push(' ', ` ${_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .MAVEN_REPOSITORIES_PROFILE_ID */ .hq}`, ...generateRepositorySection(repositorySettings.repositories, repositorySettings, 'repository'), ...generateRepositorySection(repositorySettings.pluginRepositories ?? [], repositorySettings, 'pluginRepository'), ' '); } if (includeGpgPassphraseProfile) { lines.push(' ', ` ${_constants_js__WEBPACK_IMPORTED_MODULE_7__/* .GPG_PASSPHRASE_PROFILE_ID */ .K$}`, ' ', ` ${(0,_xml_js__WEBPACK_IMPORTED_MODULE_8__/* .escapeXmlText */ .I)(gpgPassphraseEnvVar)}`, ' ', ' '); @@ -350,13 +423,16 @@ async function removeGpgHome(gpgHome) { if (!fs__WEBPACK_IMPORTED_MODULE_0__.existsSync(resolvedGpgHome)) { return; } + await stopGpgAgent(resolvedGpgHome); + await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome); +} +async function stopGpgAgent(gpgHome) { try { - await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); + await _actions_exec__WEBPACK_IMPORTED_MODULE_4__/* .exec */ .m('gpgconf', ['--homedir', toGpgPath(gpgHome), '--kill', 'gpg-agent'], { silent: true, ignoreReturnCode: true }); } catch { // gpgconf may be unavailable, but directory removal must still be attempted. } - await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(resolvedGpgHome); } async function verifyPackageSignature(archivePath, signatureUrl, publicKeyContent) { const signaturePath = await _actions_tool_cache__WEBPACK_IMPORTED_MODULE_5__/* .downloadTool */ .bq(signatureUrl); @@ -402,6 +478,7 @@ async function verifyPackageSignature(archivePath, signatureUrl, publicKeyConten ], options); } finally { + await stopGpgAgent(gpgHome); await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(signaturePath); await _actions_io__WEBPACK_IMPORTED_MODULE_3__/* .rmRF */ .Yz(gpgHome); } diff --git a/dist/setup/824.index.js b/dist/setup/824.index.js index 8e59e5d18..05b97c697 100644 --- a/dist/setup/824.index.js +++ b/dist/setup/824.index.js @@ -418,9 +418,102 @@ function readAttributeStr(xmlData, i) { } /** - * Select all the attributes whether valid or invalid. + * Walk `attrStr` once, left to right, splitting it into attribute tokens. + * + * This replaces a regex that used to do the same job + * (`(\s*)([^\s=]+)(\s*=)?(\s*(['"])(([\s\S])*?)\5)?`). That regex led with an + * optional whitespace group followed by a required "non-whitespace" group. + * On a long run of whitespace that never resolves into an attribute name + * (e.g. a tag with thousands of trailing spaces before `>`), the engine + * backtracks the whitespace group one character at a time before giving up + * and moving to the next starting position — one full backtrack per + * position, which is quadratic in the length of the run. + * + * A single forward-only scan can never backtrack, so it can't be made slow + * this way no matter how much whitespace the input contains — it's always + * proportional to the length of the string, once. + * + * Each returned token mirrors the shape the old regex match array had, so + * the validation logic below (which reads token[1]..token[6]) didn't need + * to change: + * token.startIndex - where this token begins in attrStr + * token[1] - leading whitespace before the name + * token[2] - the attribute name + * token[3] - whitespace + '=' if present, else undefined + * token[4] - marker (any defined value) if a quoted value was found + * token[5] - the quote character used ('"' or "'") + * token[6] - the value's text, without the surrounding quotes + * + * A malformed leading character (e.g. a stray '=' with no name before it) + * is simply skipped over, one character at a time — the same outcome the + * old regex produced by failing to match at that position and retrying at + * the next one. */ -const validAttrStrRegxp = new RegExp('(\\s*)([^\\s=]+)(\\s*=)?(\\s*([\'"])(([\\s\\S])*?)\\5)?', 'g'); +function scanAttributeTokens(attrStr) { + const tokens = []; + const len = attrStr.length; + let i = 0; + + while (i < len) { + const tokenStart = i; + + // Leading whitespace before the name. + while (i < len && isWhiteSpace(attrStr[i])) i++; + if (i >= len) break; // trailing whitespace only — nothing left to read + + if (attrStr[i] === '=') { + // No name before this '=' — not a valid attribute start. Move past + // just this one character and try again from the next position. + i = tokenStart + 1; + continue; + } + + const leadingWs = attrStr.slice(tokenStart, i); + + // Attribute name — everything up to the next whitespace or '='. + const nameStart = i; + while (i < len && !isWhiteSpace(attrStr[i]) && attrStr[i] !== '=') i++; + const name = attrStr.slice(nameStart, i); + + // Optional whitespace + '='. + let equalsGroup; // whitespace + '=' text, or undefined if absent + let j = i; + while (j < len && isWhiteSpace(attrStr[j])) j++; + if (j < len && attrStr[j] === '=') { + equalsGroup = attrStr.slice(i, j + 1); + i = j + 1; + } + + // Optional whitespace + quoted value. + let quoteChar; + let value; + let k = i; + while (k < len && isWhiteSpace(attrStr[k])) k++; + if (k < len && (attrStr[k] === '"' || attrStr[k] === "'")) { + const valueStart = k + 1; + const closeIdx = attrStr.indexOf(attrStr[k], valueStart); + if (closeIdx !== -1) { + quoteChar = attrStr[k]; + value = attrStr.slice(valueStart, closeIdx); + i = closeIdx + 1; + } + // No closing quote found anywhere in the rest of the string — leave + // quoteChar/value undefined, same as the old regex's group failing + // to match a backreference-less run. + } + + const token = { startIndex: tokenStart }; + token[1] = leadingWs; + token[2] = name; + token[3] = equalsGroup; + token[4] = quoteChar !== undefined ? true : undefined; + token[5] = quoteChar; + token[6] = value; + tokens.push(token); + } + + return tokens; +} //attr, ="sd", a="amit's", a="sd"b="saf", ab cd="" @@ -429,7 +522,7 @@ function validateAttributeString(attrStr, options) { //if(attrStr.trim().length === 0) return true; //empty string - const matches = (0,_util_js__WEBPACK_IMPORTED_MODULE_0__/* .getAllMatches */ .Xe)(attrStr, validAttrStrRegxp); + const matches = scanAttributeTokens(attrStr); const attrNames = {}; for (let i = 0; i < matches.length; i++) { @@ -532,7 +625,6 @@ function getPositionFromMatch(match) { return match.startIndex + match[1].length; } - /***/ }), /***/ 6009: diff --git a/dist/setup/971.index.js b/dist/setup/971.index.js index f83b7ac85..3a70aef00 100644 --- a/dist/setup/971.index.js +++ b/dist/setup/971.index.js @@ -52431,6 +52431,24 @@ const EXPANSION_MAX = 100_000; // realistic expansion (100k results hitting `EXPANSION_MAX` measure ~1M // characters) so legitimate input is unaffected. const EXPANSION_MAX_LENGTH = 4_000_000; +// `expand_` recurses once per level of brace *nesting* - both when expanding a +// set's comma members and when re-wrapping a set whose body is a single part. +// The CVE-2026-14257 fix made the *tail* iterative (recursion on `m.post`, one +// level per chained group), which left nesting depth unbounded: about 3,100 +// levels of `{{{...a,b...}}}` - only ~6KB of input - exhausted the native stack +// and crashed the process. `EXPANSION_MAX_DEPTH` bounds how deep the parser +// will follow nesting. It sits far above any realistic pattern and well below +// the depth at which the stack runs out. +const EXPANSION_MAX_DEPTH = 1_000; +// Bash keeps a quirk where a brace group followed by a comma set still expands +// (`{a},b}`). The parser implements it by rewriting the string and restarting +// the scan, absorbing one `}` per pass. `n` trailing braces therefore cost `n` +// full passes over a string that itself grows by one `escClose` sentinel each +// time - quadratic in `n`, with a ~26x constant from the sentinel's length. +// 128KB of `'{a}' + '}'.repeat(n) + ',z}'` blocked the event loop for 27 +// seconds to produce two results. `EXPANSION_MAX_REWRITES` bounds how many +// times the scan may restart. Real `{a},b}` input needs a handful. +const EXPANSION_MAX_REWRITES = 1_000; function numeric(str) { return !isNaN(str) ? parseInt(str, 10) : str.charCodeAt(0); } @@ -52450,37 +52468,52 @@ function unescapeBraces(str) { .replace(escCommaPattern, ',') .replace(escPeriodPattern, '.'); } +// Like `target.push(...items)` but doesn't overflow the stack +function pushAll(target, items) { + for (let i = 0; i < items.length; i++) { + target.push(items[i]); + } +} /** * Basically just str.split(","), but handling cases * where we have nested braced sections, which should be * treated as individual members, like {a,{b,c},d} */ function parseCommaParts(str) { - if (!str) { - return ['']; - } const parts = []; - const m = balanced('{', '}', str); - if (!m) { - return str.split(','); - } - const { pre, body, post } = m; - const p = pre.split(','); - p[p.length - 1] += '{' + body + '}'; - const postParts = parseCommaParts(post); - if (post.length) { - ; - p[p.length - 1] += postParts.shift(); - p.push.apply(p, postParts); - } - parts.push.apply(parts, p); - return parts; + // Walk the brace groups iteratively. Recursing on `post` once per group let a + // chain of them exhaust the stack - the parsing-side counterpart to + // the `expand_` overflow fixed for CVE-2026-14257, and not something `max` or + // `maxLength` can bound, since it happens before expansion. + // + // The part the next chunk continues + let carry = ''; + for (;;) { + const m = balanced('{', '}', str); + if (!m) { + const tail = str.split(','); + tail[0] = carry + tail[0]; + pushAll(parts, tail); + return parts; + } + const { pre, body, post } = m; + const p = pre.split(','); + p[0] = carry + p[0]; + p[p.length - 1] += '{' + body + '}'; + if (!post.length) { + pushAll(parts, p); + return parts; + } + carry = p.pop(); + pushAll(parts, p); + str = post; + } } function expand(str, options = {}) { if (!str) { return []; } - const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH } = options; + const { max = EXPANSION_MAX, maxLength = EXPANSION_MAX_LENGTH, maxDepth = EXPANSION_MAX_DEPTH, maxRewrites = EXPANSION_MAX_REWRITES, } = options; // I don't know why Bash 4.3 does this, but it does. // Anything starting with {} will have the first two bytes preserved // but *only* at the top level, so {},a}b will not expand to anything, @@ -52490,7 +52523,7 @@ function expand(str, options = {}) { if (str.slice(0, 2) === '{}') { str = '\\{\\}' + str.slice(2); } - return expand_(escapeBraces(str), max, maxLength, true).map(unescapeBraces); + return expand_(escapeBraces(str), max, maxLength, maxDepth, 0, maxRewrites, true).map(unescapeBraces); } function embrace(str) { return '{' + str + '}'; @@ -52585,7 +52618,13 @@ function expandSequence(body, isAlphaSequence, max, maxLength) { } return N; } -function expand_(str, max, maxLength, isTop) { +function expand_(str, max, maxLength, maxDepth, depth, maxRewrites, isTop) { + // Too deeply nested to keep following: treat the rest as literal, the same + // way a group that cannot expand is already handled. Truncating rather than + // throwing keeps `expand` total, matching `max` and `maxLength`. + if (depth > maxDepth) { + return [str]; + } // Consume the string's top-level brace groups left to right, threading a // running set of combined prefixes (`acc`). Expanding the tail iteratively - // rather than recursing on `m.post` once per group - keeps the native stack @@ -52597,6 +52636,9 @@ function expand_(str, max, maxLength, isTop) { // comma set - a sequence like `{a..\}` may legitimately yield ''. The drop // is on the final strings, so it is applied to whichever `combine` produces // them (the one with no brace set left in the tail). + // How many times the `{a},b}` rewrite below has restarted the scan. Each pass + // re-reads the whole string, so leaving this unbounded is quadratic. + let rewrites = 0; let dropEmpties = false; let firstGroup = true; for (;;) { @@ -52621,7 +52663,8 @@ function expand_(str, max, maxLength, isTop) { const isOptions = m.body.indexOf(',') >= 0; if (!isSequence && !isOptions) { // {a},b} - if (m.post.match(/,(?!,).*\}/)) { + if (rewrites < maxRewrites && m.post.match(/,(?!,).*\}/)) { + rewrites++; str = m.pre + '{' + m.body + escClose + m.post; isTop = true; continue; @@ -52641,7 +52684,7 @@ function expand_(str, max, maxLength, isTop) { let n = parseCommaParts(m.body); if (n.length === 1 && n[0] !== undefined) { // x{{a,b}}y ==> x{a}y x{b}y - n = expand_(n[0], max, maxLength, false).map(embrace); + n = expand_(n[0], max, maxLength, maxDepth, depth + 1, maxRewrites, false).map(embrace); //XXX is this necessary? Can't seem to hit it in tests. /* c8 ignore start */ if (n.length === 1) { @@ -52667,12 +52710,13 @@ function expand_(str, max, maxLength, isTop) { values = []; let valuesLength = 0; outer: for (let j = 0; j < n.length; j++) { - const expanded = expand_(n[j], max, maxLength, false); + const expanded = expand_(n[j], max, maxLength, maxDepth, depth + 1, maxRewrites, false); for (let k = 0; k < expanded.length; k++) { const v = expanded[k]; if (dropsEmpties && !v) continue; - if (values.length >= max || valuesLength + v.length > maxLength) { + if (values.length >= max || + valuesLength + v.length > maxLength) { break outer; } values.push(v); diff --git a/dist/setup/978.index.js b/dist/setup/978.index.js index 23b363d34..fe57a00e2 100644 --- a/dist/setup/978.index.js +++ b/dist/setup/978.index.js @@ -13,19 +13,26 @@ export const modules = { /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9896); /* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(2088); -/* harmony import */ var semver__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(semver__WEBPACK_IMPORTED_MODULE_3__); -/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(6242); -/* harmony import */ var _platform_types_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(7444); -/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(4527); +/* harmony import */ var _base_installer_js__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(6242); +/* harmony import */ var _platform_types_js__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(7444); +/* harmony import */ var _util_js__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(4527); - -class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* .JavaBase */ .O { +function compareNumberArrays(a, b) { + const length = Math.max(a.length, b.length); + for (let i = 0; i < length; i++) { + const diff = (a[i] ?? 0) - (b[i] ?? 0); + if (diff !== 0) { + return diff; + } + } + return 0; +} +class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_3__/* .JavaBase */ .O { constructor(installerOptions) { super('Zulu', installerOptions); } @@ -39,19 +46,24 @@ class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* ? [...item.java_version, item.openjdk_build_number] : item.java_version; return { - version: (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .convertVersionToSemver */ .ZY)(javaVersion), + version: (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .convertVersionToSemver */ .ZY)(javaVersion), url: item.download_url, - zuluVersion: (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .convertVersionToSemver */ .ZY)(item.distro_version), + javaVersion: item.java_version, + buildNumber: item.openjdk_build_number ?? 0, + distroVersion: item.distro_version, packageUuid: item.package_uuid }; }); const satisfiedVersions = availableVersions - .filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .isVersionSatisfies */ .y)(version, item.version)) + .filter(item => (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .isVersionSatisfies */ .y)(version, item.version)) .sort((a, b) => { - // Azul provides two versions: java_version and distro_version - // we should sort by both fields by descending - return (-semver__WEBPACK_IMPORTED_MODULE_3___default().compareBuild(a.version, b.version) || - -semver__WEBPACK_IMPORTED_MODULE_3___default().compareBuild(a.zuluVersion, b.zuluVersion)); + // Compare numerically rather than via semver build metadata: Azul + // hotfix releases carry a 4th java_version segment (e.g. 25.0.4.1+1, + // rendered as '25.0.4+1.1') that must rank above 25.0.4+7, whereas + // semver.compareBuild would order the build identifiers '7' > '1'. + return (-compareNumberArrays(a.javaVersion, b.javaVersion) || + b.buildNumber - a.buildNumber || + -compareNumberArrays(a.distroVersion, b.distroVersion)); }) .map((item) => ({ version: item.version, @@ -85,21 +97,21 @@ class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* _actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Downloading Java ${javaRelease.version} (${this.distribution}) from ${javaRelease.url} ...`); let javaArchivePath = await this.downloadAndVerify(javaRelease); _actions_core__WEBPACK_IMPORTED_MODULE_0__/* .info */ .pq(`Extracting Java archive...`); - const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getDownloadArchiveExtension */ .ag)(); + const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getDownloadArchiveExtension */ .ag)(); if (process.platform === 'win32') { - javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .renameWinArchive */ .n2)(javaArchivePath); + javaArchivePath = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .renameWinArchive */ .n2)(javaArchivePath); } - const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .extractJdkFile */ .PE)(javaArchivePath, extension); + const extractedJavaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .extractJdkFile */ .PE)(javaArchivePath, extension); const archiveName = fs__WEBPACK_IMPORTED_MODULE_2___default().readdirSync(extractedJavaPath)[0]; const archivePath = path__WEBPACK_IMPORTED_MODULE_1___default().join(extractedJavaPath, archiveName); - const javaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .cacheJdkDir */ .Vj)(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture); + const javaPath = await (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .cacheJdkDir */ .Vj)(archivePath, this.toolcacheFolderName, this.getToolcacheVersionName(javaRelease.version), this.architecture); return { version: javaRelease.version, path: javaPath }; } async getAvailableVersions() { const arch = this.getArchitectureOptions(); const [bundleType, features] = this.packageType.split('+'); const platform = this.getPlatformOption(); - const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_6__/* .getDownloadArchiveExtension */ .ag)(); + const extension = (0,_util_js__WEBPACK_IMPORTED_MODULE_5__/* .getDownloadArchiveExtension */ .ag)(); const javafx = features?.includes('fx') ?? false; const crac = features?.includes('crac') ?? false; const releaseStatus = this.stable ? 'ga' : 'ea'; @@ -185,7 +197,7 @@ class ZuluDistribution extends _base_installer_js__WEBPACK_IMPORTED_MODULE_4__/* // The new Metadata API's "linux" value returns both glibc and musl // packages, so target the libc the runner actually has. A glibc JDK // cannot run on Alpine. - return (0,_platform_types_js__WEBPACK_IMPORTED_MODULE_5__/* .isAlpineLinux */ .G6)() ? 'linux_musl' : 'linux_glibc'; + return (0,_platform_types_js__WEBPACK_IMPORTED_MODULE_4__/* .isAlpineLinux */ .G6)() ? 'linux_musl' : 'linux_glibc'; default: return process.platform; } diff --git a/dist/setup/index.js b/dist/setup/index.js index 8e019a02a..5c0536646 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -6009,11 +6009,77 @@ class Request { } } - onUpgrade (statusCode, headers, socket) { + /** + * @param {number|null} statusCode + * @param {Buffer[]|null} headers + * @param {import('node:stream').Duplex} socket + * @param {string} [statusText] + */ + onUpgrade (statusCode, headers, socket, statusText = '') { + this.onFinally() + assert(!this.aborted) assert(!this.completed) - return this[kHandler].onUpgrade(statusCode, headers, socket) + if (statusCode !== null) { + this.#publishUpgradeHeaders(statusCode, headers, statusText) + } + + const result = this[kHandler].onUpgrade(statusCode, headers, socket) + + if (!this.aborted) { + this.completed = true + if (statusCode !== null) { + this.#publishUpgradeTrailers() + } + } + + return result + } + + /** + * @param {number} statusCode + * @param {import('node:http2').IncomingHttpHeaders} headers + * @param {(headers: import('node:http2').IncomingHttpHeaders) => Buffer[]} parseHeaders + * @param {string} [statusText] + */ + onUpgradeResponse (statusCode, headers, parseHeaders, statusText = '') { + assert(!this.aborted) + assert(this.completed) + + if (channels.headers.hasSubscribers) { + this.#publishUpgradeHeaders(statusCode, parseHeaders(headers), statusText) + } + this.#publishUpgradeTrailers() + } + + /** + * @param {Error} error + */ + onUpgradeError (error) { + assert(!this.aborted) + assert(this.completed) + + if (channels.error.hasSubscribers) { + channels.error.publish({ request: this, error }) + } + } + + /** + * @param {number} statusCode + * @param {Buffer[]} headers + * @param {string} statusText + */ + #publishUpgradeHeaders (statusCode, headers, statusText) { + if (channels.headers.hasSubscribers) { + channels.headers.publish({ request: this, response: { statusCode, headers, statusText } }) + } + } + + #publishUpgradeTrailers () { + if (channels.trailers.hasSubscribers) { + channels.trailers.publish({ request: this, trailers: [] }) + } } onComplete (trailers) { @@ -7912,7 +7978,7 @@ class Parser { } onUpgrade (head) { - const { upgrade, client, socket, headers, statusCode } = this + const { upgrade, client, socket, headers, statusCode, statusText } = this assert(upgrade) assert(client[kSocket] === socket) @@ -7947,9 +8013,10 @@ class Parser { client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade')) try { - request.onUpgrade(statusCode, headers, socket) - } catch (err) { - util.destroy(socket, err) + request.onUpgrade(statusCode, headers, socket, statusText) + } catch (error) { + util.errorRequest(client, request, error) + util.destroy(socket, error) } client[kResume]() @@ -8356,7 +8423,7 @@ async function connectH1 (client, socket) { function clearIdleSocketValidation (socket) { if (socket[kIdleSocketValidationTimeout]) { - clearTimeout(socket[kIdleSocketValidationTimeout]) + clearImmediate(socket[kIdleSocketValidationTimeout]) socket[kIdleSocketValidationTimeout] = null } @@ -8365,15 +8432,23 @@ function clearIdleSocketValidation (socket) { function scheduleIdleSocketValidation (client, socket) { socket[kIdleSocketValidation] = 1 - socket[kIdleSocketValidationTimeout] = setTimeout(() => { + // Yield to the check phase (after poll) so unsolicited bytes / FIN / RST + // already pending on this idle keep-alive socket are processed before the + // next request is written (GHSA-35p6-xmwp-9g52). + // + // setTimeout(0) pays Node's ~1ms timer floor on every sequential reuse + // (#5493). setImmediate avoids that, but an *unref'd* Immediate lets poll + // block for ~500ms when the event loop is otherwise idle (#5600 / #5606). + // A ref'd Immediate both keeps the pending request alive and makes poll + // return immediately — the hybrid those issues asked for. + socket[kIdleSocketValidationTimeout] = setImmediate(() => { socket[kIdleSocketValidationTimeout] = null socket[kIdleSocketValidation] = 2 if (client[kSocket] === socket && !socket.destroyed) { client[kResume]() } - }, 0) - socket[kIdleSocketValidationTimeout].unref?.() + }) } /** @@ -8522,12 +8597,22 @@ function writeH1 (client, request) { const socket = client[kSocket] clearIdleSocketValidation(socket) - const abort = (err) => { - if (request.aborted || request.completed) { + /** + * @param {Error} [error] + */ + const abort = (error) => { + if (request.aborted) { + return + } + + if (request.completed) { + if (request.upgrade || request.method === 'CONNECT') { + util.destroy(socket, new InformationalError('aborted')) + } return } - util.errorRequest(client, request, err || new RequestAbortedError()) + util.errorRequest(client, request, error || new RequestAbortedError()) util.destroy(body) util.destroy(socket, new InformationalError('aborted')) @@ -8984,6 +9069,7 @@ module.exports = connectH1 const assert = __nccwpck_require__(4589) +const { errorMonitor } = __nccwpck_require__(8474) const { pipeline } = __nccwpck_require__(7075) const util = __nccwpck_require__(3440) const { @@ -9060,6 +9146,15 @@ function parseH2Headers (headers) { return result } +/** + * @param {import('node:http2').IncomingHttpHeaders} headers + * @returns {Buffer[]} + */ +function parseH2ResponseHeaders (headers) { + const { [HTTP2_HEADER_STATUS]: _statusCode, ...realHeaders } = headers + return parseH2Headers(realHeaders) +} + async function connectH2 (client, socket) { client[kSocket] = socket @@ -9280,22 +9375,32 @@ function writeH2 (client, request) { headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}` headers[HTTP2_HEADER_METHOD] = method - const abort = (err) => { - if (request.aborted || request.completed) { + /** + * @param {Error} [error] + */ + const abort = (error) => { + if (request.aborted) { + return + } + + if (request.completed) { + if (method === 'CONNECT' && stream != null) { + util.destroy(stream, error || new RequestAbortedError()) + } return } - err = err || new RequestAbortedError() + error = error || new RequestAbortedError() - util.errorRequest(client, request, err) + util.errorRequest(client, request, error) if (stream != null) { - util.destroy(stream, err) + util.destroy(stream, error) } // We do not destroy the socket as we can continue using the session // the stream get's destroyed and the session remains to create new streams - util.destroy(body, err) + util.destroy(body, error) client[kQueue][client[kRunningIdx]++] = null client[kResume]() } @@ -9314,25 +9419,57 @@ function writeH2 (client, request) { if (method === 'CONNECT') { session.ref() - // We are already connected, streams are pending, first request - // will create a new stream. We trigger a request to create the stream and wait until - // `ready` event is triggered // We disabled endStream to allow the user to write to the stream stream = session.request(headers, { endStream: false, signal }) + let upgradeResponseFinished = false - if (stream.id && !stream.pending) { - request.onUpgrade(null, null, stream) - ++session[kOpenStreams] - client[kQueue][client[kRunningIdx]++] = null - } else { - stream.once('ready', () => { + /** + * @param {import('node:http2').IncomingHttpHeaders} headers + */ + const onResponse = (headers) => { + upgradeResponseFinished = true + stream.off(errorMonitor, onUpgradeError) + request.onUpgradeResponse(Number(headers[HTTP2_HEADER_STATUS]), headers, parseH2ResponseHeaders) + } + + /** + * @param {Error} error + */ + const onUpgradeError = (error) => { + upgradeResponseFinished = true + stream.off('response', onResponse) + request.onUpgradeError(error) + } + + const onReady = () => { + try { request.onUpgrade(null, null, stream) - ++session[kOpenStreams] - client[kQueue][client[kRunningIdx]++] = null - }) + } catch (error) { + stream.off('response', onResponse) + abort(error) + return + } + + if (request.aborted) { + return + } + + stream.off('error', abort) + stream.once(errorMonitor, onUpgradeError) + client[kQueue][client[kRunningIdx]++] = null } + stream.once('response', onResponse) + stream.once('error', abort) + ++session[kOpenStreams] + onReady() + stream.once('close', () => { + if (!upgradeResponseFinished && request.completed) { + stream.off('response', onResponse) + stream.off(errorMonitor, onUpgradeError) + request.onUpgradeError(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`)) + } session[kOpenStreams] -= 1 if (session[kOpenStreams] === 0) session.unref() }) @@ -12031,6 +12168,7 @@ class RetryHandler { this.end = null this.etag = null this.resume = null + this.headersSent = false // Handle possible onConnect duplication this.handler.onConnect(reason => { @@ -12043,6 +12181,20 @@ class RetryHandler { }) } + checkpointResponseEnd (headers, resume) { + if (this.end == null && this.opts.method !== 'HEAD') { + const contentLength = headers['content-length'] + this.end = contentLength != null ? Number(contentLength) - 1 : null + + assert( + this.end == null || Number.isFinite(this.end), + 'invalid content-length' + ) + } + + this.resume = this.end != null ? resume : null + } + onRequestSent () { if (this.handler.onRequestSent) { this.handler.onRequestSent() @@ -12131,7 +12283,12 @@ class RetryHandler { this.retryCount += 1 if (statusCode >= 300) { - if (this.retryOpts.statusCodes.includes(statusCode) === false) { + // Only expose a response if no earlier attempt has reached the caller. + // Otherwise abort this attempt so the error settles the existing body + // instead of replacing it with a new response. + if (!this.headersSent && this.retryOpts.statusCodes.includes(statusCode) === false) { + this.headersSent = true + this.checkpointResponseEnd(headers, resume) return this.handler.onHeaders( statusCode, rawHeaders, @@ -12200,8 +12357,15 @@ class RetryHandler { const { start, size, end = size - 1 } = contentRange - assert(this.start === start, 'content-range mismatch') - assert(this.end == null || this.end === end, 'content-range mismatch') + if (this.start !== start || (this.end != null && this.end !== end)) { + this.abort( + new RequestRetryError('Content-Range mismatch', statusCode, { + headers, + data: { count: this.retryCount } + }) + ) + return false + } this.resume = resume return true @@ -12213,6 +12377,7 @@ class RetryHandler { const range = parseRangeHeader(headers['content-range']) if (range == null) { + this.headersSent = true return this.handler.onHeaders( statusCode, rawHeaders, @@ -12251,6 +12416,7 @@ class RetryHandler { ) this.resume = resume + this.headersSent = true this.etag = headers.etag != null ? headers.etag : null // Weak etags are not useful for comparison nor cache @@ -12290,7 +12456,7 @@ class RetryHandler { } onError (err) { - if (this.aborted || isDisturbed(this.opts.body)) { + if (this.aborted || isDisturbed(this.opts.body) || (this.headersSent && this.resume == null)) { return this.handler.onError(err) } @@ -16748,6 +16914,49 @@ const COLON = 0x3A */ const SPACE = 0x20 +const DATA = Buffer.from('data') +const EVENT = Buffer.from('event') +const ID = Buffer.from('id') +const RETRY = Buffer.from('retry') + +function isASCIINumberBytes (buffer, start) { + if (start >= buffer.length) { + return false + } + + for (let i = start; i < buffer.length; i++) { + if (buffer[i] < 0x30 || buffer[i] > 0x39) { + return false + } + } + + return true +} + +function isValidLastEventIdBytes (buffer, start) { + for (let i = start; i < buffer.length; i++) { + if (buffer[i] === 0x00) { + return false + } + } + + return true +} + +function isFieldName (line, length, field) { + if (length !== field.length) { + return false + } + + for (let i = 0; i < length; i++) { + if (line[i] !== field[i]) { + return false + } + } + + return true +} + /** * @typedef {object} EventSourceStreamEvent * @type {object} @@ -16788,11 +16997,14 @@ class EventSourceStream extends Transform { eventEndCheck = false /** - * @type {Buffer} + * @type {Buffer[]} */ - buffer = null + chunks = [] + chunkIndex = 0 pos = 0 + lineChunkIndex = 0 + linePos = 0 event = { data: undefined, @@ -16831,92 +17043,20 @@ class EventSourceStream extends Transform { return } - // Cache the chunk in the buffer, as the data might not be complete while - // processing it - // TODO: Investigate if there is a more performant way to handle - // incoming chunks - // see: https://github.com/nodejs/undici/issues/2630 - if (this.buffer) { - this.buffer = Buffer.concat([this.buffer, chunk]) - } else { - this.buffer = chunk - } + this.chunks.push(chunk) // Strip leading byte-order-mark if we opened the stream and started // the processing of the incoming data if (this.checkBOM) { - switch (this.buffer.length) { - case 1: - // Check if the first byte is the same as the first byte of the BOM - if (this.buffer[0] === BOM[0]) { - // If it is, we need to wait for more data - callback() - return - } - // Set the checkBOM flag to false as we don't need to check for the - // BOM anymore - this.checkBOM = false - - // The buffer only contains one byte so we need to wait for more data - callback() - return - case 2: - // Check if the first two bytes are the same as the first two bytes - // of the BOM - if ( - this.buffer[0] === BOM[0] && - this.buffer[1] === BOM[1] - ) { - // If it is, we need to wait for more data, because the third byte - // is needed to determine if it is the BOM or not - callback() - return - } - - // Set the checkBOM flag to false as we don't need to check for the - // BOM anymore - this.checkBOM = false - break - case 3: - // Check if the first three bytes are the same as the first three - // bytes of the BOM - if ( - this.buffer[0] === BOM[0] && - this.buffer[1] === BOM[1] && - this.buffer[2] === BOM[2] - ) { - // If it is, we can drop the buffered data, as it is only the BOM - this.buffer = Buffer.alloc(0) - // Set the checkBOM flag to false as we don't need to check for the - // BOM anymore - this.checkBOM = false - - // Await more data - callback() - return - } - // If it is not the BOM, we can start processing the data - this.checkBOM = false - break - default: - // The buffer is longer than 3 bytes, so we can drop the BOM if it is - // present - if ( - this.buffer[0] === BOM[0] && - this.buffer[1] === BOM[1] && - this.buffer[2] === BOM[2] - ) { - // Remove the BOM from the buffer - this.buffer = this.buffer.subarray(3) - } - - // Set the checkBOM flag to false as we don't need to check for the - this.checkBOM = false - break + if (this.handleBOM()) { + callback() + return } } - while (this.pos < this.buffer.length) { + while (this.hasCurrentByte()) { + const byte = this.currentByte() + // If the previous line ended with an end-of-line, we need to check // if the next character is also an end-of-line. if (this.eventEndCheck) { @@ -16929,10 +17069,9 @@ class EventSourceStream extends Transform { if (this.crlfCheck) { // If the current character is a line feed, we can remove it // from the buffer and reset the crlfCheck flag - if (this.buffer[this.pos] === LF) { - this.buffer = this.buffer.subarray(this.pos + 1) - this.pos = 0 + if (byte === LF) { this.crlfCheck = false + this.consumeCurrentByte() // It is possible that the line feed is not the end of the // event. We need to check if the next character is an @@ -16948,19 +17087,17 @@ class EventSourceStream extends Transform { this.crlfCheck = false } - if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) { + if (byte === LF || byte === CR) { // If the current character is a carriage return, we need to // set the crlfCheck flag to true, as we need to check if the // next character is a line feed so we can remove it from the // buffer - if (this.buffer[this.pos] === CR) { + if (byte === CR) { this.crlfCheck = true } - this.buffer = this.buffer.subarray(this.pos + 1) - this.pos = 0 - if ( - this.event.data !== undefined || this.event.event || this.event.id || this.event.retry) { + this.consumeCurrentByte() + if (this.hasPendingEvent()) { this.processEvent(this.event) } this.clearEvent() @@ -16974,22 +17111,18 @@ class EventSourceStream extends Transform { // If the current character is an end-of-line, we can process the // line - if (this.buffer[this.pos] === LF || this.buffer[this.pos] === CR) { + if (byte === LF || byte === CR) { // If the current character is a carriage return, we need to // set the crlfCheck flag to true, as we need to check if the // next character is a line feed - if (this.buffer[this.pos] === CR) { + if (byte === CR) { this.crlfCheck = true } // In any case, we can process the line as we reached an // end-of-line character - this.parseLine(this.buffer.subarray(0, this.pos), this.event) - - // Remove the processed line from the buffer - this.buffer = this.buffer.subarray(this.pos + 1) - // Reset the position as we removed the processed line from the buffer - this.pos = 0 + this.parseLine(this.readLine(), this.event) + this.consumeCurrentByte() // A line was processed and this could be the end of the event. We need // to check if the next line is empty to determine if the event is // finished. @@ -16997,7 +17130,7 @@ class EventSourceStream extends Transform { continue } - this.pos++ + this.advanceCursor() } callback() @@ -17022,64 +17155,53 @@ class EventSourceStream extends Transform { return } - let field = '' - let value = '' + let fieldLength = line.length + let valueStart = line.length // If the line contains a U+003A COLON character (:) if (colonPosition !== -1) { - // Collect the characters on the line before the first U+003A COLON - // character (:), and let field be that string. - // TODO: Investigate if there is a more performant way to extract the - // field - // see: https://github.com/nodejs/undici/issues/2630 - field = line.subarray(0, colonPosition).toString('utf8') + fieldLength = colonPosition // Collect the characters on the line after the first U+003A COLON // character (:), and let value be that string. // If value starts with a U+0020 SPACE character, remove it from value. - let valueStart = colonPosition + 1 + valueStart = colonPosition + 1 if (line[valueStart] === SPACE) { ++valueStart } - // TODO: Investigate if there is a more performant way to extract the - // value - // see: https://github.com/nodejs/undici/issues/2630 - value = line.subarray(valueStart).toString('utf8') + } - // Otherwise, the string is not empty but does not contain a U+003A COLON - // character (:) - } else { - // Process the field using the steps described below, using the whole - // line as the field name, and the empty string as the field value. - field = line.toString('utf8') - value = '' - } - - // Modify the event with the field name and value. The value is also - // decoded as UTF-8 - switch (field) { - case 'data': - if (event[field] === undefined) { - event[field] = value - } else { - event[field] += `\n${value}` - } - break - case 'retry': - if (isASCIINumber(value)) { - event[field] = value - } - break - case 'id': - if (isValidLastEventId(value)) { - event[field] = value - } - break - case 'event': - if (value.length > 0) { - event[field] = value - } - break + if (isFieldName(line, fieldLength, DATA)) { + const value = line.toString('utf8', valueStart) + + if (event.data === undefined) { + event.data = value + } else { + event.data += `\n${value}` + } + return + } + + if (isFieldName(line, fieldLength, RETRY)) { + if (isASCIINumberBytes(line, valueStart)) { + event.retry = line.toString('utf8', valueStart) + } + return + } + + if (isFieldName(line, fieldLength, ID)) { + if (isValidLastEventIdBytes(line, valueStart)) { + event.id = line.toString('utf8', valueStart) + } + return + } + + if (isFieldName(line, fieldLength, EVENT)) { + const value = line.toString('utf8', valueStart) + + if (value.length > 0) { + event.event = value + } } } @@ -17109,13 +17231,152 @@ class EventSourceStream extends Transform { } clearEvent () { - this.event = { - data: undefined, - event: undefined, - id: undefined, - retry: undefined + this.event.data = undefined + this.event.event = undefined + this.event.id = undefined + this.event.retry = undefined + } + + hasPendingEvent () { + return this.event.data !== undefined || + this.event.event !== undefined || + this.event.id !== undefined || + this.event.retry !== undefined + } + + hasCurrentByte () { + return this.chunkIndex < this.chunks.length && + this.pos < this.chunks[this.chunkIndex].length + } + + currentByte () { + return this.chunks[this.chunkIndex][this.pos] + } + + consumeCurrentByte () { + this.advanceCursor() + this.syncLineStartToCursor() + } + + advanceCursor () { + this.pos++ + + while (this.chunkIndex < this.chunks.length && this.pos >= this.chunks[this.chunkIndex].length) { + this.chunkIndex++ + this.pos = 0 + } + } + + syncLineStartToCursor () { + this.lineChunkIndex = this.chunkIndex + this.linePos = this.pos + this.dropConsumedChunks() + } + + dropConsumedChunks () { + while (this.lineChunkIndex > 0) { + this.chunks.shift() + this.lineChunkIndex-- + this.chunkIndex-- + } + + if (this.chunkIndex === this.chunks.length) { + this.chunks.length = 0 + this.chunkIndex = 0 + this.pos = 0 + this.lineChunkIndex = 0 + this.linePos = 0 + } + } + + readLine () { + if (this.lineChunkIndex === this.chunkIndex) { + return this.chunks[this.chunkIndex].subarray(this.linePos, this.pos) + } + + const chunks = [] + let length = 0 + + for (let i = this.lineChunkIndex; i <= this.chunkIndex; i++) { + const chunk = this.chunks[i] + const start = i === this.lineChunkIndex ? this.linePos : 0 + const end = i === this.chunkIndex ? this.pos : chunk.length + const slice = chunk.subarray(start, end) + length += slice.length + chunks.push(slice) + } + + return Buffer.concat(chunks, length) + } + + peekBufferedByte (offset) { + let chunkIndex = this.lineChunkIndex + let pos = this.linePos + + while (chunkIndex < this.chunks.length) { + const chunk = this.chunks[chunkIndex] + const remaining = chunk.length - pos + + if (offset < remaining) { + return chunk[pos + offset] + } + + offset -= remaining + chunkIndex++ + pos = 0 } } + + discardLeadingBytes (count) { + while (count > 0 && this.lineChunkIndex < this.chunks.length) { + const chunk = this.chunks[this.lineChunkIndex] + const remaining = chunk.length - this.linePos + + if (count < remaining) { + this.linePos += count + count = 0 + } else { + count -= remaining + this.lineChunkIndex++ + this.linePos = 0 + } + } + + this.chunkIndex = this.lineChunkIndex + this.pos = this.linePos + this.dropConsumedChunks() + } + + handleBOM () { + const first = this.peekBufferedByte(0) + const second = this.peekBufferedByte(1) + const third = this.peekBufferedByte(2) + + if (second === undefined) { + if (first === BOM[0]) { + return true + } + + this.checkBOM = false + return true + } + + if (third === undefined) { + if (first === BOM[0] && second === BOM[1]) { + return true + } + + this.checkBOM = false + return false + } + + if (first === BOM[0] && second === BOM[1] && third === BOM[2]) { + this.discardLeadingBytes(3) + } + + this.checkBOM = false + return !this.hasCurrentByte() + } } module.exports = { @@ -28383,7 +28644,7 @@ function establishWebSocketConnection (url, protocols, client, ws, onEstablish, // is specified, the server needs to include the same field and one of // the selected subprotocol values in its response for the connection to // be established. - if (!requestProtocols.includes(secProtocol)) { + if (requestProtocols === null || !requestProtocols.includes(secProtocol)) { failWebsocketConnection(ws, 'Protocol was not set in the opening handshake.') return } @@ -29144,7 +29405,12 @@ class PerMessageDeflate { if (this.#maxPayloadSize > 0 && this.#inflate[kLength] > this.#maxPayloadSize) { callback(new MessageSizeExceededError()) + // The inflater may still hold buffered input that can emit a late + // zlib error. Remove the data listener, then deterministically stop + // the stream so a subsequent 'error' cannot fire without a listener + // (which would terminate the process as an unhandled error event). this.#inflate.removeAllListeners() + this.#inflate.destroy() this.#inflate = null return } @@ -30780,6 +31046,7 @@ module.exports = { /* harmony export */ OD: () => (/* binding */ INPUT_DEFAULT_GPG_PRIVATE_KEY), /* harmony export */ OT: () => (/* binding */ INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL), /* harmony export */ PG: () => (/* binding */ MACOS_JAVA_CONTENT_POSTFIX), +/* harmony export */ Pt: () => (/* binding */ INPUT_MVN_SERVER_REPOSITORY_ORIGINS), /* harmony export */ QM: () => (/* binding */ INPUT_JAVA_VERSION), /* harmony export */ RX: () => (/* binding */ INPUT_DEFAULT_GPG_PASSPHRASE), /* harmony export */ TS: () => (/* binding */ INPUT_OVERWRITE_SETTINGS), @@ -30794,6 +31061,7 @@ module.exports = { /* harmony export */ af: () => (/* binding */ INPUT_JAVA_VERSION_FILE), /* harmony export */ db: () => (/* binding */ INPUT_GPG_PASSPHRASE_ENV_VAR), /* harmony export */ fd: () => (/* binding */ INPUT_SERVER_ID), +/* harmony export */ gR: () => (/* binding */ INPUT_MVN_PLUGIN_REPOSITORIES), /* harmony export */ g_: () => (/* binding */ INPUT_DISTRIBUTION), /* harmony export */ gk: () => (/* binding */ INPUT_CACHE), /* harmony export */ hq: () => (/* binding */ MAVEN_REPOSITORIES_PROFILE_ID), @@ -30840,7 +31108,9 @@ const INPUT_VERIFY_SIGNATURE_PUBLIC_KEY = 'verify-signature-public-key'; const SIGNATURE_VERIFICATION_DOCUMENTATION_URL = 'https://github.com/actions/setup-java#download-integrity-and-signatures'; const SIGNATURE_VERIFICATION_FAILURE_HELP = `If this is a legitimate vendor signing-key rotation, see ${SIGNATURE_VERIFICATION_DOCUMENTATION_URL} for instructions to configure the updated public key or temporarily disable signature verification.`; const INPUT_MVN_SERVER_CREDENTIALS = 'mvn-server-credentials'; +const INPUT_MVN_SERVER_REPOSITORY_ORIGINS = 'mvn-server-repository-origins'; const INPUT_MVN_REPOSITORIES = 'mvn-repositories'; +const INPUT_MVN_PLUGIN_REPOSITORIES = 'mvn-plugin-repositories'; const INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL = 'mvn-repositories-include-central'; const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL = 'mvn-repositories-prioritize-central'; const INPUT_SERVER_ID = 'server-id'; @@ -30975,12 +31245,7 @@ function canResolveTemurinJmods(version) { if (normalizedVersion === 'latest') { return true; } - let normalizedRange = normalizedVersion - .replace(/-ea$/, '') - .replace('-ea.', '+'); - if (/^\d+(\.\d+){3,}$/.test(normalizedRange)) { - normalizedRange = (0,_util_js__WEBPACK_IMPORTED_MODULE_1__/* .convertVersionToSemver */ .ZY)(normalizedRange); - } + const normalizedRange = (0,_util_js__WEBPACK_IMPORTED_MODULE_1__/* .normalizeJavaVersionToSemver */ .zZ)(normalizedVersion.replace(/-ea$/, '').replace('-ea.', '+')); if (!semver__WEBPACK_IMPORTED_MODULE_0___default().validRange(normalizedRange)) { // JavaBase owns general version validation and its targeted error messages. return true; @@ -31022,7 +31287,14 @@ function createUnsupportedPackageError(distributionName, packageType, supportedP const X64_ARM64 = ['x64', 'aarch64']; -const STANDARD_LINUX = ['x64', 'x86', 'aarch64', 'ppc64le', 's390x']; +const STANDARD_LINUX = [ + 'x64', + 'x86', + 'aarch64', + 'ppc64le', + 'riscv64', + 's390x' +]; const JAVA_PLATFORM_CAPABILITIES = { [_package_types_js__WEBPACK_IMPORTED_MODULE_2__/* .JavaDistribution */ .zS.Temurin]: { platforms: { @@ -31164,6 +31436,7 @@ const CANONICAL_ARCHITECTURES = [ 'aarch64', 'ppc64le', 'ppc64', + 'riscv64', 's390x' ]; const PLATFORM_ALIASES = { @@ -31303,7 +31576,8 @@ function validateToolchainIds(versions, versionFile, toolchainIds) { /* harmony export */ rC: () => (/* binding */ getNextPageUrlFromLinkHeader), /* harmony export */ ri: () => (/* binding */ getLatestMajorVersion), /* harmony export */ y: () => (/* binding */ isVersionSatisfies), -/* harmony export */ yH: () => (/* binding */ getToolcachePath) +/* harmony export */ yH: () => (/* binding */ getToolcachePath), +/* harmony export */ zZ: () => (/* binding */ normalizeJavaVersionToSemver) /* harmony export */ }); /* unused harmony exports getVersionFromToolcachePath, isJobStatusSuccess */ /* harmony import */ var os__WEBPACK_IMPORTED_MODULE_0__ = __nccwpck_require__(857); @@ -31704,6 +31978,20 @@ function convertVersionToSemver(version) { } return mainVersion; } +/** + * Java versions (JEP 322) can contain more numeric fields than SemVer allows, + * e.g. '11.0.9.1' or Temurin respins such as '26.0.2.1+1'. Move the extra + * fields into SemVer build metadata ('11.0.9+1', '26.0.2+1.1'). Any other + * input (ranges, regular SemVer versions) is returned unchanged. + */ +function normalizeJavaVersionToSemver(version) { + const match = /^(\d+(?:\.\d+){3,})(?:\+([0-9A-Za-z.-]+))?$/.exec(version); + if (!match) { + return version; + } + const converted = convertVersionToSemver(match[1]); + return match[2] ? `${converted}.${match[2]}` : converted; +} /** * Builds a validator for the bytes currently served by a URL from the response * headers of a HEAD request. A vendor's `/latest/` URL never changes, so this diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 8561ab6f4..dedca4ed4 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -649,7 +649,7 @@ absent from a vendor catalog. | Distribution | Linux | macOS | Windows | Other / version restrictions | | --- | --- | --- | --- | --- | -| `temurin` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le`, `s390x` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | Linux `armv7` is available through Java 17. | +| `temurin` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le`, `riscv64`, `s390x` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | Linux `armv7` is available through Java 17. | | `zulu` | `x64`, `x86`, `armv7`, `aarch64` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | | | `liberica` | `x64`, `x86`, `armv7`, `aarch64`, `ppc64le` | `x64`, `aarch64` | `x64`, `x86`, `aarch64` | Solaris: `x64`. | | `liberica-nik` | `x64`, `aarch64` | `x64`, `aarch64` | `x64`, `aarch64` | | @@ -885,6 +885,11 @@ Use `mvn-server-credentials` to add more than one credential entry to the genera When this input is set, it replaces the single server configured by `server-id`, `server-username-env-var`, and `server-password-env-var`. +Use `mvn-server-repository-origins` when a Maven server credential must be allowed for an explicit repository origin. Each line has the format `server-id:repository-origin`; a server can have multiple origins. + +> [!NOTE] +> `mvn-server-repository-origins` only works with Maven 3.10 and later. Earlier Maven versions ignore the generated `repositoryOrigins` element and log an `Unrecognised tag: 'repositoryOrigins'` warning. GitHub-hosted runners currently ship Maven 3.9.x, so use the [Maven Wrapper](https://maven.apache.org/tools/wrapper/) or install Maven 3.10+ yourself to use this feature. + ```yaml steps: - uses: actions/checkout@v7 @@ -896,6 +901,9 @@ steps: mvn-server-credentials: | releases:RELEASES_USERNAME:RELEASES_PASSWORD snapshots:SNAPSHOTS_USERNAME:SNAPSHOTS_PASSWORD + mvn-server-repository-origins: | + releases:https://central.sonatype.com + snapshots:https://central.sonatype.com - name: Publish with Maven run: mvn deploy env: @@ -913,11 +921,17 @@ This configuration produces the following server entries: releases ${env.RELEASES_USERNAME} ${env.RELEASES_PASSWORD} + + https://central.sonatype.com + snapshots ${env.SNAPSHOTS_USERNAME} ${env.SNAPSHOTS_PASSWORD} + + https://central.sonatype.com + ``` @@ -989,14 +1003,92 @@ This configuration adds the following active profile to `settings.xml`: ``` -Maven Central is included first by default. Set -`mvn-repositories-prioritize-central: false` to place custom repositories -first, or set `mvn-repositories-include-central: false` to disable Central. The -generated profile overrides the Central repository inherited from Maven's Super -POM with releases and snapshots disabled. When automatic Central inclusion is -off, the ID `central` may instead be declared explicitly in `mvn-repositories` -to replace it with a user-specified repository; otherwise that ID is reserved -to prevent duplicate entries. +Maven Central is included first by default. The Central controls apply to both +dependency and plugin repositories. Set +`mvn-repositories-prioritize-central: false` to place custom repositories first +within each configured list, or set `mvn-repositories-include-central: false` to +disable Central for both types. The generated profile overrides the dependency +and plugin Central repositories inherited from Maven's Super POM with releases +and snapshots disabled, even if only one custom repository list is supplied. +These controls have no effect when neither list contains a nonblank entry. + +When automatic Central inclusion is off, the ID `central` may instead be +declared explicitly in `mvn-repositories` or `mvn-plugin-repositories` to replace +it with a user-specified repository for that type. Declaring it in one list +does not enable Central in the other. Otherwise that ID is reserved to prevent +duplicate entries. Other repositories declared in your project's POM are not +disabled by these controls. + +### Resolving Maven plugins from custom repositories + +Maven resolves build plugins through ``, separately from +dependency ``. 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 + + + private + https://maven.example.com/releases + + false + + + + central + https://repo.maven.apache.org/maven2 + + false + + + false + + + +``` + +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 diff --git a/package-lock.json b/package-lock.json index bcc003721..47cb35a8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,27 +16,27 @@ "@actions/http-client": "^4.0.1", "@actions/io": "^3.0.2", "@actions/tool-cache": "^4.0.0", - "fast-xml-parser": "^5.11.0", + "fast-xml-parser": "^5.11.1", "semver": "^7.8.5" }, "devDependencies": { "@eslint/js": "^10.0.1", - "@jest/globals": "^30.4.1", - "@types/node": "^26.2.0", + "@jest/globals": "^30.5.2", + "@types/node": "^26.6.2", "@types/semver": "^7.8.0", - "@typescript-eslint/eslint-plugin": "^8.67.0", + "@typescript-eslint/eslint-plugin": "^8.70.1", "@typescript-eslint/parser": "^8.65.0", "@vercel/ncc": "^0.45.0", - "eslint": "^10.8.1", + "eslint": "^10.11.0", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-jest": "^29.16.1", + "eslint-plugin-jest": "^29.16.6", "eslint-plugin-n": "^18.3.0", - "globals": "^17.11.0", + "globals": "^17.12.0", "husky": "^9.1.7", - "jest": "^30.4.2", - "lint-staged": "^17.3.0", - "prettier": "^3.9.5", - "ts-jest": "^29.4.11", + "jest": "^30.5.2", + "lint-staged": "^17.5.1", + "prettier": "^3.9.9", + "ts-jest": "^29.4.13", "typescript": "^6.0.3" }, "engines": { @@ -836,6 +836,30 @@ "dev": true, "license": "MIT" }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, "node_modules/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", @@ -972,9 +996,9 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", - "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1145,17 +1169,17 @@ } }, "node_modules/@jest/console": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.4.1.tgz", - "integrity": "sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.5.2.tgz", + "integrity": "sha512-e8sQC4pCMHPBrPX7Hk8TJr+DM5dkUnImSTNag8SEWFAFAF6xIz4AJvCKapnrdHnZxep4bxw4PhG+ZbimYBMyKw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", "slash": "^3.0.0" }, "engines": { @@ -1163,18 +1187,18 @@ } }, "node_modules/@jest/core": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.4.2.tgz", - "integrity": "sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.5.2.tgz", + "integrity": "sha512-/jqvFWoJF7LV1a6AUpYMbMm+2yIYsHJfR474H0SCzr9k8tnO/jl9wRg6zSG6lxgf6EozNlCG4amFzWsbZSvvZA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.4.1", - "@jest/pattern": "30.4.0", - "@jest/reporters": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/console": "30.5.2", + "@jest/pattern": "30.5.0", + "@jest/reporters": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", @@ -1182,20 +1206,20 @@ "exit-x": "^0.2.2", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-changed-files": "30.4.1", - "jest-config": "30.4.2", - "jest-haste-map": "30.4.1", - "jest-message-util": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-resolve-dependencies": "30.4.2", - "jest-runner": "30.4.2", - "jest-runtime": "30.4.2", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", - "jest-watcher": "30.4.1", - "pretty-format": "30.4.1", + "jest-changed-files": "30.5.1", + "jest-config": "30.5.2", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-resolve-dependencies": "30.5.2", + "jest-runner": "30.5.2", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", + "jest-watcher": "30.5.2", + "pretty-format": "30.5.1", "slash": "^3.0.0" }, "engines": { @@ -1211,9 +1235,9 @@ } }, "node_modules/@jest/diff-sequences": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.4.0.tgz", - "integrity": "sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.5.0.tgz", + "integrity": "sha512-OsqBjHXCn8cadasoAZBP6nWYvMsRhpMzGXTpxJ5aO04NlbdhIz+FVe3q49l0AwVhsz/cEmIpBes6gAFl1/dWQg==", "dev": true, "license": "MIT", "engines": { @@ -1221,70 +1245,70 @@ } }, "node_modules/@jest/environment": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.4.1.tgz", - "integrity": "sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.5.2.tgz", + "integrity": "sha512-nwFPOUMbmsjNNRCpnJpB9HbSZu4C07wDDds++5j+j1MezzPLxMPDdbdUVjr2o4RULDOValnDd70taPFI1EtxAg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", - "jest-mock": "30.4.1" + "jest-mock": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.4.1.tgz", - "integrity": "sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.5.2.tgz", + "integrity": "sha512-2E+1Pg6jJxbRjLNLpciC71iri/3qsKJL222/aXJEFwC0VdUSvfS+JZQQLEVjUYYc8t8pQu2MGE3cF2+bNTYRsg==", "dev": true, "license": "MIT", "dependencies": { - "expect": "30.4.1", - "jest-snapshot": "30.4.1" + "expect": "30.5.2", + "jest-snapshot": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.4.1.tgz", - "integrity": "sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.5.2.tgz", + "integrity": "sha512-d/HOLSrJUlBIl6n3LzOa7cjW3xdePm5H5gQw2lixZ/0h157l9sw3mLblzFqnPcTbhDzhRQdBOX3A3KcG73nm5Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0" + "@jest/get-type": "30.5.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.4.1.tgz", - "integrity": "sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.5.2.tgz", + "integrity": "sha512-GKk0h0tKT5SpDPxDhPg3r9mm5s8/YCBzVNMOGdRiQXwUiMsjU10mVPlMaQstH6/cu50vOb8N9oQUMETHuwwxKw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@sinonjs/fake-timers": "^15.4.0", "@types/node": "*", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/get-type": { - "version": "30.1.0", - "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", - "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.5.0.tgz", + "integrity": "sha512-9/2VUPitAjmBzbvDvqrxmvB7BzWsBW0WmkkojX1ODuxX1NLGxx9gfaZpHB0z8DtJ9uhGNmZG/VXBhf8uO0OV8Q==", "dev": true, "license": "MIT", "engines": { @@ -1292,62 +1316,78 @@ } }, "node_modules/@jest/globals": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.4.1.tgz", - "integrity": "sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.5.2.tgz", + "integrity": "sha512-62zfS+dL+M5aTNHXCNLyqD+j4oqxTIzDYrZbpqMP8Yn+gvlAGyzC6BSPNb1ZmFOakV1RtFYX/O3FCQXSWmQdgg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/expect": "30.4.1", - "@jest/types": "30.4.1", - "jest-mock": "30.4.1" + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", + "@jest/types": "30.5.1", + "jest-mock": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/pattern": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.4.0.tgz", - "integrity": "sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.5.0.tgz", + "integrity": "sha512-HdNQYSdRTEBNrginaqzQtTjG0HRMfrra/z6Ok7uL3S87vSlarIVohEsJsSj5edu3MiHoHjAkvPROz5ZjoKai+w==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-regex-util": "30.4.0" + "jest-regex-util": "30.5.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, + "node_modules/@jest/react-is-18": { + "name": "react-is", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jest/react-is-19": { + "name": "react-is", + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.3.0.tgz", + "integrity": "sha512-UpMYezM4v5/18F28aC66AEsjXIgE02kyEMH6yLdgLXu/UTfa1Ntwck/nNLrbqJsEXW7gPb0coNO9FQse9WTovA==", + "dev": true, + "license": "MIT" + }, "node_modules/@jest/reporters": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.4.1.tgz", - "integrity": "sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.5.2.tgz", + "integrity": "sha512-ev6E9iG73twQcfAfoTnviuYkr3yOrYbzAtMFCimv2fxHXPIp9PBt2u1wNMt0aYWgl8FPwIcS8oqRJg6alnht4Q==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", - "@jridgewell/trace-mapping": "^0.3.25", + "@jest/console": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", "@types/node": "*", "chalk": "^4.1.2", "collect-v8-coverage": "^1.0.2", "exit-x": "^0.2.2", - "glob": "^10.5.0", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "jest-worker": "30.4.1", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", "slash": "^3.0.0", "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" @@ -1365,9 +1405,9 @@ } }, "node_modules/@jest/schemas": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.4.1.tgz", - "integrity": "sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.5.0.tgz", + "integrity": "sha512-/hunigyNpc4RCjC0VaW3f5RCUZVM2+WQ65qP7z083Gmvac7or2LI50XVNOtE4YPgBpV0yxYiAgorAPGniCoJmg==", "dev": true, "license": "MIT", "dependencies": { @@ -1378,13 +1418,13 @@ } }, "node_modules/@jest/snapshot-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.4.1.tgz", - "integrity": "sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.5.1.tgz", + "integrity": "sha512-V3wnxNtiVmw5PPVg433Cn3VdXnsOeu/ofLw3KC04Bn2y1wIlU5kozXQn48rhrgj/02LrCVtntTEF1yBha0XSUw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "natural-compare": "^1.4.0" @@ -1394,14 +1434,15 @@ } }, "node_modules/@jest/source-map": { - "version": "30.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", - "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.5.2.tgz", + "integrity": "sha512-c5CehvkbfHX6yyNg7bMn2ylqlp9AHXuZ/3hPH/Lh5bxD/vefd/lV9HBjMqXef9yQNqkdwyZvkLSVSwB38hy8Qw==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", + "@jridgewell/trace-mapping": "^0.3.31", "callsites": "^3.1.0", + "convert-source-map": "^2.0.0", "graceful-fs": "^4.2.11" }, "engines": { @@ -1409,14 +1450,14 @@ } }, "node_modules/@jest/test-result": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.4.1.tgz", - "integrity": "sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.5.2.tgz", + "integrity": "sha512-eO6YU5rsLfs60ne694e0IAmRgeBnoA8mu0nlxXDbl/1j5km0o2vO9/VbbUIKIH+WRKKEI+ELgwraRGn2Lhep8g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.4.1", - "@jest/types": "30.4.1", + "@jest/console": "30.5.2", + "@jest/types": "30.5.1", "@types/istanbul-lib-coverage": "^2.0.6", "collect-v8-coverage": "^1.0.2" }, @@ -1425,15 +1466,15 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.4.1.tgz", - "integrity": "sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.5.2.tgz", + "integrity": "sha512-bmij8jZyYAC47ExT2GeP7PcrlwSNS+Bp3KeRuBH88gpcxqDv6fljN8PemS4J/lLZ79xb6mpaennQTTq0zUAVVg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.4.1", + "@jest/test-result": "30.5.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", + "jest-haste-map": "30.5.1", "slash": "^3.0.0" }, "engines": { @@ -1441,23 +1482,23 @@ } }, "node_modules/@jest/transform": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.4.1.tgz", - "integrity": "sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.5.2.tgz", + "integrity": "sha512-LBGwmaE886C41jDZ65eqR1YbXM7642MK3ZhTowZd5+xDlrDLuf4ePuDdDmVSvcr8NTVJ3MqxLkMbUCzuigUaoQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/types": "30.4.1", - "@jridgewell/trace-mapping": "^0.3.25", - "babel-plugin-istanbul": "^7.0.1", + "@jest/types": "30.5.1", + "@jridgewell/trace-mapping": "^0.3.31", + "babel-plugin-istanbul": "^8.0.0", "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-util": "30.4.1", + "jest-haste-map": "30.5.1", + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", "pirates": "^4.0.7", "slash": "^3.0.0", "write-file-atomic": "^5.0.1" @@ -1467,14 +1508,14 @@ } }, "node_modules/@jest/types": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.4.1.tgz", - "integrity": "sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.5.1.tgz", + "integrity": "sha512-LvVYn83nnXPl+Rg98nvcFgjx6nRMTArhSn6RAX/w3ELn54S8A42TYZvsCMdGUqTM8S0wyXbtlQdU6Hi6dykj9g==", "dev": true, "license": "MIT", "dependencies": { - "@jest/pattern": "30.4.0", - "@jest/schemas": "30.4.1", + "@jest/pattern": "30.5.0", + "@jest/schemas": "30.5.0", "@types/istanbul-lib-coverage": "^2.0.6", "@types/istanbul-reports": "^3.0.4", "@types/node": "*", @@ -1535,23 +1576,50 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.4.tgz", + "integrity": "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g==", "dev": true, "license": "MIT", "optional": true, "dependencies": { "@tybys/wasm-util": "^0.10.3" }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, "funding": { "type": "github", "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "node_modules/@nodable/entities": { @@ -1566,6 +1634,311 @@ ], "license": "MIT" }, + "node_modules/@parcel/watcher": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.6.0.tgz", + "integrity": "sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.6.0", + "@parcel/watcher-darwin-arm64": "2.6.0", + "@parcel/watcher-darwin-x64": "2.6.0", + "@parcel/watcher-freebsd-x64": "2.6.0", + "@parcel/watcher-linux-arm-glibc": "2.6.0", + "@parcel/watcher-linux-arm-musl": "2.6.0", + "@parcel/watcher-linux-arm64-glibc": "2.6.0", + "@parcel/watcher-linux-arm64-musl": "2.6.0", + "@parcel/watcher-linux-x64-glibc": "2.6.0", + "@parcel/watcher-linux-x64-musl": "2.6.0", + "@parcel/watcher-win32-arm64": "2.6.0", + "@parcel/watcher-win32-x64": "2.6.0" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz", + "integrity": "sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz", + "integrity": "sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz", + "integrity": "sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz", + "integrity": "sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz", + "integrity": "sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz", + "integrity": "sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz", + "integrity": "sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz", + "integrity": "sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz", + "integrity": "sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz", + "integrity": "sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz", + "integrity": "sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz", + "integrity": "sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@pkgr/core": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.3.6.tgz", @@ -1622,9 +1995,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.4.tgz", + "integrity": "sha512-W3c4gRigFS0T/Ma4qIYF3GDAc5AQdHb1yL5znJT1Zv1YaD9Kitx656wBjvr19qbiosmZT8lWDM5BEMynUqX65A==", "dev": true, "license": "MIT", "optional": true, @@ -1726,13 +2099,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "26.2.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.2.0.tgz", - "integrity": "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg==", + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.2.tgz", + "integrity": "sha512-X1P21scMv4zGKLYqjdGjaKa7COa0RKVYYZZN/NfvLQ1JegxFhdhpZG/Lyn8AXx6CDUavKAd11v6BvfpkDByK8g==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~8.3.0" + "undici-types": "~8.9.0" } }, "node_modules/@types/semver": { @@ -1767,17 +2140,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz", - "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.1.tgz", + "integrity": "sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/type-utils": "8.67.0", - "@typescript-eslint/utils": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/type-utils": "8.70.1", + "@typescript-eslint/utils": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1790,21 +2163,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.67.0", + "@typescript-eslint/parser": "^8.70.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz", - "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.70.1.tgz", + "integrity": "sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/utils": "8.67.0", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1", + "@typescript-eslint/utils": "8.70.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1821,16 +2194,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", - "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.1.tgz", + "integrity": "sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.67.0", - "@typescript-eslint/tsconfig-utils": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", + "@typescript-eslint/project-service": "8.70.1", + "@typescript-eslint/tsconfig-utils": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1849,14 +2222,14 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", - "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.1.tgz", + "integrity": "sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.67.0", - "@typescript-eslint/types": "^8.67.0", + "@typescript-eslint/tsconfig-utils": "^8.70.1", + "@typescript-eslint/types": "^8.70.1", "debug": "^4.4.3" }, "engines": { @@ -1871,9 +2244,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", - "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.1.tgz", + "integrity": "sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==", "dev": true, "license": "MIT", "engines": { @@ -1888,16 +2261,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", - "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.70.1.tgz", + "integrity": "sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0" + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1912,16 +2285,16 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", - "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.1.tgz", + "integrity": "sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.67.0", - "@typescript-eslint/tsconfig-utils": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", + "@typescript-eslint/project-service": "8.70.1", + "@typescript-eslint/tsconfig-utils": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1940,14 +2313,14 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", - "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.1.tgz", + "integrity": "sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.67.0", - "@typescript-eslint/types": "^8.67.0", + "@typescript-eslint/tsconfig-utils": "^8.70.1", + "@typescript-eslint/types": "^8.70.1", "debug": "^4.4.3" }, "engines": { @@ -1962,9 +2335,9 @@ } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", - "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.1.tgz", + "integrity": "sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==", "dev": true, "license": "MIT", "engines": { @@ -1979,16 +2352,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz", - "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.70.1.tgz", + "integrity": "sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "debug": "^4.4.3" }, "engines": { @@ -2004,16 +2377,16 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", - "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.1.tgz", + "integrity": "sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.67.0", - "@typescript-eslint/tsconfig-utils": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", + "@typescript-eslint/project-service": "8.70.1", + "@typescript-eslint/tsconfig-utils": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2032,14 +2405,14 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", - "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.1.tgz", + "integrity": "sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.67.0", - "@typescript-eslint/types": "^8.67.0", + "@typescript-eslint/tsconfig-utils": "^8.70.1", + "@typescript-eslint/types": "^8.70.1", "debug": "^4.4.3" }, "engines": { @@ -2054,9 +2427,9 @@ } }, "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", - "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.1.tgz", + "integrity": "sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==", "dev": true, "license": "MIT", "engines": { @@ -2071,14 +2444,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz", - "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.70.1.tgz", + "integrity": "sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0" + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2089,9 +2462,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz", - "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.70.1.tgz", + "integrity": "sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==", "dev": true, "license": "MIT", "engines": { @@ -2103,13 +2476,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz", - "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.1.tgz", + "integrity": "sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.67.0", + "@typescript-eslint/types": "8.70.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2648,16 +3021,16 @@ } }, "node_modules/babel-jest": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.4.1.tgz", - "integrity": "sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.5.2.tgz", + "integrity": "sha512-ES8WeJ2fNWPEDunyAtUfJ12nHyaWUloCHdkfK2oW5uSoQSUmjNKdWiGaXJITJwvFEWoD/evHEg/QoCXAvLaQqQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "30.4.1", + "@jest/transform": "30.5.2", "@types/babel__core": "^7.20.5", - "babel-plugin-istanbul": "^7.0.1", - "babel-preset-jest": "30.4.0", + "babel-plugin-istanbul": "^8.0.0", + "babel-preset-jest": "30.5.0", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", "slash": "^3.0.0" @@ -2670,9 +3043,9 @@ } }, "node_modules/babel-plugin-istanbul": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", - "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-8.0.0.tgz", + "integrity": "sha512-18wCskrN3DgbuBmp1gr7LBGT8xdz5xhQQqFvFhVxbkl8VBCrMKQ2YtqBWtUal1Zrc1HTuX0011+Brjw78TCFkg==", "dev": true, "license": "BSD-3-Clause", "workspaces": [ @@ -2683,16 +3056,16 @@ "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-instrument": "^6.0.2", - "test-exclude": "^6.0.0" + "test-exclude": "^7.0.1" }, "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.4.0.tgz", - "integrity": "sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.5.0.tgz", + "integrity": "sha512-gtGo1B+u14jrZQv6TdSWIWkTqclboo7Qn+dFAGUOIuXLFuJKAdg3U5MIWzZnW4vfUb4dX9skmAMiby86e/SF4A==", "dev": true, "license": "MIT", "dependencies": { @@ -2730,20 +3103,20 @@ } }, "node_modules/babel-preset-jest": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.4.0.tgz", - "integrity": "sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.5.0.tgz", + "integrity": "sha512-ZGPn5ClP4lBDpuOK8W1yQIOy359HmbnZv3suucAlIe+SEE9yDsxV4S2PjSbH2Vc97U+WmzYD7vw3kr8NsQ/i6w==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "30.4.0", + "babel-plugin-jest-hoist": "30.5.0", "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.11.0 || ^8.0.0-beta.1" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1 || ^8.0.0" } }, "node_modules/balanced-match": { @@ -2769,9 +3142,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -2837,12 +3210,19 @@ "node-int64": "^0.4.0" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } }, "node_modules/callsites": { "version": "3.1.0", @@ -2929,9 +3309,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", - "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.1.tgz", + "integrity": "sha512-Ca8swihM+/4yKecYHY52kgJd300hi2lADU/a1RxNTRe+RJ9jvqQlESpbz9DnG9mowez8qwXHB8qYdIUw9e+F5Q==", "dev": true, "license": "MIT" }, @@ -3059,6 +3439,16 @@ "node": ">=0.10.0" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3120,6 +3510,13 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -3144,9 +3541,9 @@ } }, "node_modules/eslint": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.1.tgz", - "integrity": "sha512-wqA7W2jbsC/BnV9Iv1UZpKVFkO1AdNoSmYW8NWG4HNOBbkAMvIqDZ27pI2f07dqn583NcIC44ckjAcOXDL1QbQ==", + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.11.0.tgz", + "integrity": "sha512-P7a6UEEqb9G95MYAtqkmsTbVXIYyzIfl6NGOIJk162PaahFxFyeGcrlXYFSiagECg4sEm8IseJdZBKR3rx6MsQ==", "dev": true, "license": "MIT", "workspaces": [ @@ -3158,7 +3555,7 @@ "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.2", + "@eslint/plugin-kit": "^0.7.3", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", @@ -3173,7 +3570,7 @@ "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", + "file-entry-cache": "11.1.5 || >11.1.6 <12", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", @@ -3257,9 +3654,9 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "29.16.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.16.1.tgz", - "integrity": "sha512-tfxOIsjzaBud+f74aLbBMRcnrztt5eCIgnAdeoGdnzMAQ4IdAa/s/p8Ls55mk9MC79N3j2jbv4Qetz6Hclbcfw==", + "version": "29.16.6", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.16.6.tgz", + "integrity": "sha512-q8TVr0rlNvUD0XnafGWkwtPeX+tTl2llP86EDl3sJtwWrQA/JT9SIu2hLLZbhhX6fT5SDE4O0gIKyZUujKnkYA==", "dev": true, "license": "MIT", "dependencies": { @@ -3287,16 +3684,16 @@ } }, "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz", - "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.70.1.tgz", + "integrity": "sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/typescript-estree": "8.67.0" + "@typescript-eslint/scope-manager": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/typescript-estree": "8.70.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3311,16 +3708,16 @@ } }, "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz", - "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.1.tgz", + "integrity": "sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.67.0", - "@typescript-eslint/tsconfig-utils": "8.67.0", - "@typescript-eslint/types": "8.67.0", - "@typescript-eslint/visitor-keys": "8.67.0", + "@typescript-eslint/project-service": "8.70.1", + "@typescript-eslint/tsconfig-utils": "8.70.1", + "@typescript-eslint/types": "8.70.1", + "@typescript-eslint/visitor-keys": "8.70.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3339,14 +3736,14 @@ } }, "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz", - "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.1.tgz", + "integrity": "sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.67.0", - "@typescript-eslint/types": "^8.67.0", + "@typescript-eslint/tsconfig-utils": "^8.70.1", + "@typescript-eslint/types": "^8.70.1", "debug": "^4.4.3" }, "engines": { @@ -3361,9 +3758,9 @@ } }, "node_modules/eslint-plugin-jest/node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.67.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz", - "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==", + "version": "8.70.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.1.tgz", + "integrity": "sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==", "dev": true, "license": "MIT", "engines": { @@ -3633,18 +4030,18 @@ } }, "node_modules/expect": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-30.4.1.tgz", - "integrity": "sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.5.2.tgz", + "integrity": "sha512-0LNuMvs8/5mRYhnCR4k+lGV7fqPMs6Bnksda4S3zsCUmdxBpAn4zU4NNzXdq1pMKe/H4W5t/zVIDSJ/H3e0vDA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "30.4.1", - "@jest/get-type": "30.1.0", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-util": "30.4.1" + "@jest/expect-utils": "30.5.2", + "@jest/get-type": "30.5.0", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -3688,9 +4085,9 @@ } }, "node_modules/fast-xml-parser": { - "version": "5.11.0", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.0.tgz", - "integrity": "sha512-9IGxMqvqLOnqP+Egi1nqDHKv5k8aZ7r9n558enxcucmyVGEBNPAU+MOg/8jPIS7rO7sSq4gFm1/nHtiaubMruw==", + "version": "5.11.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.11.1.tgz", + "integrity": "sha512-TBw6K/fxoQGGjCmZDw9w/ZwP3uDcnTM4YH/g+PFRWr8sbe5idXtxNN6vITh4+1ruCZaho6uBFurElsA7F0zzgw==", "funding": [ { "type": "github", @@ -3739,16 +4136,13 @@ } }, "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" + "flat-cache": "^6.1.23" } }, "node_modules/find-up": { @@ -3769,23 +4163,21 @@ } }, "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", "dev": true, "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" } }, "node_modules/flatted": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.3.tgz", - "integrity": "sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==", + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", "dev": true, "license": "ISC" }, @@ -3806,21 +4198,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3916,9 +4293,9 @@ } }, "node_modules/globals": { - "version": "17.11.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.11.0.tgz", - "integrity": "sha512-Z2I8hM+PbJDXQDq3Icgpzv+mPdwr68iZUU9d5WW4FuXfDUQfkZaZuvjMv42/5crNyw154+9+VWXbYrUgDXbxNw==", + "version": "17.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz", + "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", "dev": true, "license": "MIT", "engines": { @@ -3943,13 +4320,13 @@ "license": "ISC" }, "node_modules/handlebars": { - "version": "4.7.9", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", - "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "version": "4.7.10", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.10.tgz", + "integrity": "sha512-P5VJMVM7qgBn6vjXMw8WG9uVI+ncf2pi72j4de4yz5ZULLj2RGqLYaKOYGsgyrViQ0tePOVlN1tDCCXXtFqXKg==", "dev": true, "license": "MIT", "dependencies": { - "minimist": "^1.2.5", + "minimist": "^1.2.8", "neo-async": "^2.6.2", "source-map": "^0.6.1", "wordwrap": "^1.0.0" @@ -3974,6 +4351,26 @@ "node": ">=8" } }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -4243,16 +4640,16 @@ } }, "node_modules/jest": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest/-/jest-30.4.2.tgz", - "integrity": "sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.5.2.tgz", + "integrity": "sha512-3gnpdBIza8ijCl3iMV9ChE3hSt1+46865qqod863gQtJr1DixOkAU5DoGbi3u+XlRcw++BhZQMu/y4xBdHBvSQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.4.2", - "@jest/types": "30.4.1", + "@jest/core": "30.5.2", + "@jest/types": "30.5.1", "import-local": "^3.2.0", - "jest-cli": "30.4.2" + "jest-cli": "30.5.2" }, "bin": { "jest": "bin/jest.js" @@ -4270,14 +4667,14 @@ } }, "node_modules/jest-changed-files": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.4.1.tgz", - "integrity": "sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.5.1.tgz", + "integrity": "sha512-0+bvMM/ENhDI29Z8q1r4HxiDIi4G5tnBmSw4esfPQoj9q8Nik7KIyuxHkTVnnniJQf05SxpGaKDQ+4h28ynGPg==", "dev": true, "license": "MIT", "dependencies": { "execa": "^5.1.1", - "jest-util": "30.4.1", + "jest-util": "30.5.1", "p-limit": "^3.1.0" }, "engines": { @@ -4285,29 +4682,29 @@ } }, "node_modules/jest-circus": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.4.2.tgz", - "integrity": "sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.5.2.tgz", + "integrity": "sha512-vffFgjs5JSJ9q4ds1vNW3klKYPJfYfIY61LT/zaZgvTeASJOWZUt8LBfHgIcV4rwxlPBLl/ePBGccHEclS4PlQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/expect": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", + "@jest/environment": "30.5.2", + "@jest/expect": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "co": "^4.6.0", "dedent": "^1.6.0", "is-generator-fn": "^2.1.0", - "jest-each": "30.4.1", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-runtime": "30.4.2", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", + "jest-each": "30.5.2", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-runtime": "30.5.2", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", "p-limit": "^3.1.0", - "pretty-format": "30.4.1", + "pretty-format": "30.5.1", "pure-rand": "^7.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.6" @@ -4317,21 +4714,21 @@ } }, "node_modules/jest-cli": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.4.2.tgz", - "integrity": "sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.5.2.tgz", + "integrity": "sha512-YYYxUUVjFgPvgEleKNKMaYGIFgR3l3832Cl0kRL/oCiDZ03v7wImUtquHl2OOBCCNgprjZgBN5bczS/JklPBDg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "30.4.2", - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", + "@jest/core": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", "chalk": "^4.1.2", "exit-x": "^0.2.2", "import-local": "^3.2.0", - "jest-config": "30.4.2", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", + "jest-config": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", "yargs": "^17.7.2" }, "bin": { @@ -4350,33 +4747,33 @@ } }, "node_modules/jest-config": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.4.2.tgz", - "integrity": "sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.5.2.tgz", + "integrity": "sha512-U6221OZekabePvGcGPf4raIBKMvYQWFHvhobDJRojOYaFMj2HaGytAdknjDhYc6JTz2fdHW9+6k0wWUDaNeOFQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.27.4", - "@jest/get-type": "30.1.0", - "@jest/pattern": "30.4.0", - "@jest/test-sequencer": "30.4.1", - "@jest/types": "30.4.1", - "babel-jest": "30.4.1", + "@jest/get-type": "30.5.0", + "@jest/pattern": "30.5.0", + "@jest/test-sequencer": "30.5.2", + "@jest/types": "30.5.1", + "babel-jest": "30.5.2", "chalk": "^4.1.2", "ci-info": "^4.2.0", "deepmerge": "^4.3.1", - "glob": "^10.5.0", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", - "jest-circus": "30.4.2", - "jest-docblock": "30.4.0", - "jest-environment-node": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-runner": "30.4.2", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", + "jest-circus": "30.5.2", + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.2", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-runner": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", "parse-json": "^5.2.0", - "pretty-format": "30.4.1", + "pretty-format": "30.5.1", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -4401,25 +4798,25 @@ } }, "node_modules/jest-diff": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.4.1.tgz", - "integrity": "sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.5.2.tgz", + "integrity": "sha512-rBtHnI6BWTiWj3lM7fOLVfChx7R9B0u9VV3PRHawKs79x6ZjW1QoSrKenaigNGWlvhtoi2BqvDbABDie+Os3xQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/diff-sequences": "30.4.0", - "@jest/get-type": "30.1.0", + "@jest/diff-sequences": "30.5.0", + "@jest/get-type": "30.5.0", "chalk": "^4.1.2", - "pretty-format": "30.4.1" + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.4.0.tgz", - "integrity": "sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.5.0.tgz", + "integrity": "sha512-NwDqcxtoZi33RhuW+zJS/RVA3rmheQ8BnwpYZuc/Eruaz6seQb7+aoCeDZu/3X7W2XmD8DbSo9Pn72DbKsBFYw==", "dev": true, "license": "MIT", "dependencies": { @@ -4430,111 +4827,110 @@ } }, "node_modules/jest-each": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.4.1.tgz", - "integrity": "sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.5.2.tgz", + "integrity": "sha512-GRrW+7fmmgmkvyfEBqc1QFdWnF1Ex5yG6Y6AWL4TTxnkWlKIT98dsKh3P3UuWm6v4XsQwVE6DEzXIPMzWJ3ZbA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.4.1", + "@jest/get-type": "30.5.0", + "@jest/types": "30.5.1", "chalk": "^4.1.2", - "jest-util": "30.4.1", - "pretty-format": "30.4.1" + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.4.1.tgz", - "integrity": "sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.5.2.tgz", + "integrity": "sha512-Ciz4KgTGIbojxSKpuFImgWJj5EyqfE8xMq9NxWmOXnQywHHS03H9Mv+vaCB29J5pr3JBrY27WbRV18rBdjh8Dg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/types": "30.4.1", + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", - "jest-mock": "30.4.1", - "jest-util": "30.4.1", - "jest-validate": "30.4.1" + "jest-mock": "30.5.2", + "jest-util": "30.5.1", + "jest-validate": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.4.1.tgz", - "integrity": "sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.5.1.tgz", + "integrity": "sha512-VIFgt67jW480YDxKfEv9IYQKrFpYt7bOCMn3VsnjK7AK9qo7p6acpnA1DHwsVOULE3dTaYew/6JaTD/d0VDHoQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", + "@parcel/watcher": "^2.6.0", "@types/node": "*", "anymatch": "^3.1.3", "fb-watchman": "^2.0.2", + "fdir": "^6.5.0", "graceful-fs": "^4.2.11", - "jest-regex-util": "30.4.0", - "jest-util": "30.4.1", - "jest-worker": "30.4.1", - "picomatch": "^4.0.3", - "walker": "^1.0.8" + "jest-regex-util": "30.5.0", + "jest-util": "30.5.1", + "jest-worker": "30.5.1", + "picomatch": "^4.0.3" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.3" } }, "node_modules/jest-leak-detector": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.4.1.tgz", - "integrity": "sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.5.1.tgz", + "integrity": "sha512-gt4GT2aWgEoCNTcBe4rqS74xTIUJxi+UD9SNSu9aOk5LmTEd6fS5KSTmAKAfitCCktQHNN+upUuL6EkBfFbMDQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "pretty-format": "30.4.1" + "@jest/get-type": "30.5.0", + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.4.1.tgz", - "integrity": "sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.5.2.tgz", + "integrity": "sha512-lFkB365PTIHOhPzwrb9T6gvlDnPpOZ7/c3ewOKKB7bzztqjVrtlyRL2MkyfJXpyY2u2SKw001JKrTx14fBUFcQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", + "@jest/get-type": "30.5.0", "chalk": "^4.1.2", - "jest-diff": "30.4.1", - "pretty-format": "30.4.1" + "jest-diff": "30.5.2", + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.4.1.tgz", - "integrity": "sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.5.1.tgz", + "integrity": "sha512-UdQlLdd9wL/Ys7xRErckqwD6wPlSZYueosSWuHc1r2ztGLwlgPvtSJq2+BPEgaEY13WLvfFbmhTj8pba0Sd1jg==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@types/stack-utils": "^2.0.3", "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-util": "30.4.1", + "jest-util": "30.5.1", "picomatch": "^4.0.3", - "pretty-format": "30.4.1", + "pretty-format": "30.5.1", "slash": "^3.0.0", "stack-utils": "^2.0.6" }, @@ -4543,42 +4939,25 @@ } }, "node_modules/jest-mock": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.4.1.tgz", - "integrity": "sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.5.2.tgz", + "integrity": "sha512-KTHb37Fhj8xY8qPFvTFaFJHcdsumQp+ExsUlgoX232Agmc706gagBs53+CYvulV7MVQo9AgXOUxsqzzVBoJlrA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/expect-utils": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", - "jest-util": "30.4.1" + "jest-util": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, "node_modules/jest-regex-util": { - "version": "30.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.4.0.tgz", - "integrity": "sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==", + "version": "30.5.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.5.0.tgz", + "integrity": "sha512-Mg0WK7A6xRHLSA1udJ8y9f3lM0uUhFTBnLKzwPmqB9AylvpleJ6BLemR8K9dK27DY+cesDryoA7yLZCAHsPG1A==", "dev": true, "license": "MIT", "engines": { @@ -4586,100 +4965,100 @@ } }, "node_modules/jest-resolve": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.4.1.tgz", - "integrity": "sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.5.1.tgz", + "integrity": "sha512-wprhLejRtwN6h8ZgaqC0eYjGJ1uMdGPT/+b3eFncbG4NWuuP9QL+vWwRinu9waw7hkOLcpMJGVJAMgBwsPssMQ==", "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.2", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-pnp-resolver": "^1.2.3", - "jest-util": "30.4.1", - "jest-validate": "30.4.1", + "jest-haste-map": "30.5.1", + "jest-util": "30.5.1", + "jest-validate": "30.5.1", "slash": "^3.0.0", - "unrs-resolver": "^1.7.11" + "unrs-resolver": "^1.12.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.4.2.tgz", - "integrity": "sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.5.2.tgz", + "integrity": "sha512-GMI0DP5enX9Z2qW2zFy1bOkrCfWAOPRJQ7qHt0pdfrxBPsgWxglMPrRSK2TX/wRWYxcPRSLFg3Z88qPOzHx7FQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "30.4.0", - "jest-snapshot": "30.4.1" + "jest-regex-util": "30.5.0", + "jest-snapshot": "30.5.2" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.4.2.tgz", - "integrity": "sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.5.2.tgz", + "integrity": "sha512-/B6px7hiiNhVSRwuv9AGn0nawYvwsHeQZ3ItTd5Gp2diFd0EOKaWVeWqcbw3L88vx1GfpCKjQkxWItZCTl11Rw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "30.4.1", - "@jest/environment": "30.4.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/console": "30.5.2", + "@jest/environment": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "emittery": "^0.13.1", "exit-x": "^0.2.2", "graceful-fs": "^4.2.11", - "jest-docblock": "30.4.0", - "jest-environment-node": "30.4.1", - "jest-haste-map": "30.4.1", - "jest-leak-detector": "30.4.1", - "jest-message-util": "30.4.1", - "jest-resolve": "30.4.1", - "jest-runtime": "30.4.2", - "jest-util": "30.4.1", - "jest-watcher": "30.4.1", - "jest-worker": "30.4.1", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" + "jest-docblock": "30.5.0", + "jest-environment-node": "30.5.2", + "jest-haste-map": "30.5.1", + "jest-leak-detector": "30.5.1", + "jest-message-util": "30.5.1", + "jest-resolve": "30.5.1", + "jest-runtime": "30.5.2", + "jest-util": "30.5.1", + "jest-watcher": "30.5.2", + "jest-worker": "30.5.1", + "p-limit": "^3.1.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runtime": { - "version": "30.4.2", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.4.2.tgz", - "integrity": "sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.5.2.tgz", + "integrity": "sha512-0paUAHBlheai10A3XSy9Xh+YBG0IROy+/qzcDPzQ/nbbXrznQzeWy5qniICWK6d3W3scaRQCVAbXdcK0ibBq4w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "30.4.1", - "@jest/fake-timers": "30.4.1", - "@jest/globals": "30.4.1", - "@jest/source-map": "30.0.1", - "@jest/test-result": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/environment": "30.5.2", + "@jest/fake-timers": "30.5.2", + "@jest/globals": "30.5.2", + "@jest/source-map": "30.5.2", + "@jest/test-result": "30.5.2", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", - "cjs-module-lexer": "^2.1.0", + "cjs-module-lexer": "^2.2.0", "collect-v8-coverage": "^1.0.2", - "glob": "^10.5.0", + "es-module-lexer": "^2.1.0", + "glob": "^13.0.6", "graceful-fs": "^4.2.11", - "jest-haste-map": "30.4.1", - "jest-message-util": "30.4.1", - "jest-mock": "30.4.1", - "jest-regex-util": "30.4.0", - "jest-resolve": "30.4.1", - "jest-snapshot": "30.4.1", - "jest-util": "30.4.1", + "jest-haste-map": "30.5.1", + "jest-message-util": "30.5.1", + "jest-mock": "30.5.2", + "jest-regex-util": "30.5.0", + "jest-resolve": "30.5.1", + "jest-snapshot": "30.5.2", + "jest-util": "30.5.1", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -4688,9 +5067,9 @@ } }, "node_modules/jest-snapshot": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.4.1.tgz", - "integrity": "sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.5.2.tgz", + "integrity": "sha512-jKIdes25AnAH73dOCi2qE6J6vHJ6L2vyBS11M89kT50DVJcLTf2AOjvcc+2vVaN8lUbY5v2R83aqXo3Ro9fqYA==", "dev": true, "license": "MIT", "dependencies": { @@ -4699,20 +5078,20 @@ "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1", "@babel/types": "^7.27.3", - "@jest/expect-utils": "30.4.1", - "@jest/get-type": "30.1.0", - "@jest/snapshot-utils": "30.4.1", - "@jest/transform": "30.4.1", - "@jest/types": "30.4.1", + "@jest/expect-utils": "30.5.2", + "@jest/get-type": "30.5.0", + "@jest/snapshot-utils": "30.5.1", + "@jest/transform": "30.5.2", + "@jest/types": "30.5.1", "babel-preset-current-node-syntax": "^1.2.0", "chalk": "^4.1.2", - "expect": "30.4.1", + "expect": "30.5.2", "graceful-fs": "^4.2.11", - "jest-diff": "30.4.1", - "jest-matcher-utils": "30.4.1", - "jest-message-util": "30.4.1", - "jest-util": "30.4.1", - "pretty-format": "30.4.1", + "jest-diff": "30.5.2", + "jest-matcher-utils": "30.5.2", + "jest-message-util": "30.5.1", + "jest-util": "30.5.1", + "pretty-format": "30.5.1", "semver": "^7.7.2", "synckit": "^0.11.8" }, @@ -4721,13 +5100,13 @@ } }, "node_modules/jest-util": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.4.1.tgz", - "integrity": "sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.5.1.tgz", + "integrity": "sha512-yKuxmNy2rSbTXw+3SIPanJo+nV4/BS1p26v44IYBFMsswSQySfMMcPHErnOncda7i9HEz0q605rIhSTBVgrZTg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "30.4.1", + "@jest/types": "30.5.1", "@types/node": "*", "chalk": "^4.1.2", "ci-info": "^4.2.0", @@ -4739,18 +5118,18 @@ } }, "node_modules/jest-validate": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.4.1.tgz", - "integrity": "sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.5.1.tgz", + "integrity": "sha512-i/buJ56wTpxihE93hQYNMfdOThS87+HGvLZzZJmU4xggPcdTYQq051iwALLCHp3q+SkCGH+EFejQZz5EbBSkkg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/get-type": "30.1.0", - "@jest/types": "30.4.1", + "@jest/get-type": "30.5.0", + "@jest/types": "30.5.1", "camelcase": "^6.3.0", "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "30.4.1" + "pretty-format": "30.5.1" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -4770,19 +5149,19 @@ } }, "node_modules/jest-watcher": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.4.1.tgz", - "integrity": "sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==", + "version": "30.5.2", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.5.2.tgz", + "integrity": "sha512-rqRgg4R11GA9m1UsEZwQeixHsgZReCn6TOHAHtIH33yXrIvoX0OGPA20Yzzjkz7X40fZvcRSxEpQppYPcWzmcQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "30.4.1", - "@jest/types": "30.4.1", + "@jest/test-result": "30.5.2", + "@jest/types": "30.5.1", "@types/node": "*", "ansi-escapes": "^4.3.2", "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "30.4.1", + "jest-util": "30.5.1", "string-length": "^4.0.2" }, "engines": { @@ -4790,15 +5169,15 @@ } }, "node_modules/jest-worker": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.4.1.tgz", - "integrity": "sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.5.1.tgz", + "integrity": "sha512-Cbxh5v7AoLuFRmFJSM4/aHdQ68rjXvUWr716EE0Dh3I7T+T/3FgFKhOERGXHcU2Meftq9+9zxPM3TSNyI9D+HA==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", "@ungap/structured-clone": "^1.3.0", - "jest-util": "30.4.1", + "jest-util": "30.5.1", "merge-stream": "^2.0.0", "supports-color": "^8.1.1" }, @@ -4856,13 +5235,6 @@ "node": ">=6" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -4898,13 +5270,13 @@ } }, "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", "dev": true, "license": "MIT", "dependencies": { - "json-buffer": "3.0.1" + "@keyv/serialize": "^1.1.1" } }, "node_modules/leven": { @@ -4939,15 +5311,15 @@ "license": "MIT" }, "node_modules/lint-staged": { - "version": "17.3.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.3.0.tgz", - "integrity": "sha512-woZS3vNe3UKqBaLPvbLOtKRY4tLANpWQhom12MGWqC8Mh1lCOO+WgSwmX2amjJAqTY9BkXYW87fCUH5H9Ph6xw==", + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.5.1.tgz", + "integrity": "sha512-7EDuco1xnBMeVpvAbeMq1U5KXwJGLmY8q6l+Ye78r36C4mPc+Vg1Z2SK8gHyRTyvPro90A0q5/FAZ+Au23d6QQ==", "dev": true, "license": "MIT", "dependencies": { - "picomatch": "^4.0.5", + "picomatch": "^4.0.7", "string-argv": "^0.3.2", - "tinyexec": "^1.2.4" + "tinyexec": "^1.3.1" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -5018,16 +5390,6 @@ "dev": true, "license": "ISC" }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -5116,6 +5478,13 @@ "dev": true, "license": "MIT" }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5328,9 +5697,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -5430,9 +5799,9 @@ } }, "node_modules/prettier": { - "version": "3.9.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", - "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.9.tgz", + "integrity": "sha512-Z/CJHIkdujO/OtN7nXUii0Rf3VT5SRuhjBA82Xvu2XhBUgX3nhP67T0LHceBdQLex7OOFGTox+Q5Yg8Jk2Qivg==", "dev": true, "license": "MIT", "bin": { @@ -5446,16 +5815,16 @@ } }, "node_modules/pretty-format": { - "version": "30.4.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.4.1.tgz", - "integrity": "sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==", + "version": "30.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.5.1.tgz", + "integrity": "sha512-byhRAPguVKMQIj4kjJwJ5lAskVhfuiSdiYl/aLTWpgkGEmic2jYhJh1yE9ih8Ox44Xg9ccCT11/S6QrzSJuNrg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "30.4.1", - "ansi-styles": "^5.2.0", - "react-is-18": "npm:react-is@^18.3.1", - "react-is-19": "npm:react-is@^19.2.5" + "@jest/react-is-18": "npm:react-is@^18.3.1", + "@jest/react-is-19": "npm:react-is@^19.2.5", + "@jest/schemas": "30.5.0", + "ansi-styles": "^5.2.0" }, "engines": { "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" @@ -5501,19 +5870,23 @@ ], "license": "MIT" }, - "node_modules/react-is-18": { - "name": "react-is", - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } }, - "node_modules/react-is-19": { - "name": "react-is", - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.8.tgz", - "integrity": "sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==", + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", "dev": true, "license": "MIT" }, @@ -5628,17 +6001,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -5828,9 +6190,9 @@ } }, "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "dev": true, "license": "MIT", "engines": { @@ -5854,13 +6216,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, "node_modules/ts-api-utils": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", @@ -5875,9 +6230,9 @@ } }, "node_modules/ts-jest": { - "version": "29.4.12", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.12.tgz", - "integrity": "sha512-Ov6ClY53Fflh6BGAnY2DlTq1hYDrTycz2PVTXBWFW2CU+9zrEqAp9fWdGXl42EXO5RLSFAcAZ2JFKbP+zBTFfw==", + "version": "29.4.13", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.13.tgz", + "integrity": "sha512-rUQG+Ar95cNQf//KvsSjvabLpCTbwk+vKGWkAgiKFugqVwmOpGR7n7xa3S68/AB21Wn0KNhIAVxmxrUsz0L1AQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5898,7 +6253,7 @@ "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" }, "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", + "@babel/core": ">=7.0.0-beta.0 <9", "@jest/transform": "^29.0.0 || ^30.0.0", "@jest/types": "^29.0.0 || ^30.0.0", "babel-jest": "^29.0.0 || ^30.0.0", @@ -5993,8 +6348,8 @@ }, "node_modules/typescript": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha1-kCUdwAeRbpcnhsuU100VsYVXfSE=", "dev": true, "license": "Apache-2.0", "bin": { @@ -6020,18 +6375,18 @@ } }, "node_modules/undici": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", - "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", + "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", "license": "MIT", "engines": { "node": ">=18.17" } }, "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", "dev": true, "license": "MIT" }, @@ -6129,16 +6484,6 @@ "node": ">=10.12.0" } }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index b6d0bc576..500787181 100644 --- a/package.json +++ b/package.json @@ -49,27 +49,27 @@ "@actions/http-client": "^4.0.1", "@actions/io": "^3.0.2", "@actions/tool-cache": "^4.0.0", - "fast-xml-parser": "^5.11.0", + "fast-xml-parser": "^5.11.1", "semver": "^7.8.5" }, "devDependencies": { "@eslint/js": "^10.0.1", - "@jest/globals": "^30.4.1", - "@types/node": "^26.2.0", + "@jest/globals": "^30.5.2", + "@types/node": "^26.6.2", "@types/semver": "^7.8.0", - "@typescript-eslint/eslint-plugin": "^8.67.0", + "@typescript-eslint/eslint-plugin": "^8.70.1", "@typescript-eslint/parser": "^8.65.0", "@vercel/ncc": "^0.45.0", - "eslint": "^10.8.1", + "eslint": "^10.11.0", "eslint-config-prettier": "^10.1.8", - "eslint-plugin-jest": "^29.16.1", + "eslint-plugin-jest": "^29.16.6", "eslint-plugin-n": "^18.3.0", - "globals": "^17.11.0", + "globals": "^17.12.0", "husky": "^9.1.7", - "jest": "^30.4.2", - "lint-staged": "^17.3.0", - "prettier": "^3.9.5", - "ts-jest": "^29.4.11", + "jest": "^30.5.2", + "lint-staged": "^17.5.1", + "prettier": "^3.9.9", + "ts-jest": "^29.4.13", "typescript": "^6.0.3" }, "bugs": { diff --git a/src/auth.ts b/src/auth.ts index 3e20cb25b..91480f765 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -13,6 +13,7 @@ export interface MavenServerCredentials { id: string; usernameEnvVar: string; passwordEnvVar: string; + repositoryOrigins?: string[]; } export interface MavenRepository { @@ -24,10 +25,13 @@ export interface MavenRepository { export interface MavenRepositorySettings { repositories: MavenRepository[]; + pluginRepositories?: MavenRepository[]; includeCentral: boolean; prioritizeCentral: boolean; } +const MAVEN_REPOSITORY_ORIGIN = /^[A-Za-z][A-Za-z0-9+.-]*:\/\/[^/?#@]+\/?$/; + export async function configureAuthentication() { const servers = getMavenServerSettings(); const repositorySettings = getMavenRepositorySettings(); @@ -95,25 +99,28 @@ export function getMavenServerSettings(): MavenServerCredentials[] { constants.INPUT_MVN_SERVER_CREDENTIALS ); - if (entries.some(entry => entry.trim())) { - return parseMavenServerCredentials(entries); - } - - return [ - { - id: core.getInput(constants.INPUT_SERVER_ID), - usernameEnvVar: getInputWithDeprecatedAlias( - constants.INPUT_SERVER_USERNAME_ENV_VAR, - constants.INPUT_SERVER_USERNAME_DEPRECATED, - constants.INPUT_DEFAULT_SERVER_USERNAME - ), - passwordEnvVar: getInputWithDeprecatedAlias( - constants.INPUT_SERVER_PASSWORD_ENV_VAR, - constants.INPUT_SERVER_PASSWORD_DEPRECATED, - constants.INPUT_DEFAULT_SERVER_PASSWORD - ) - } - ]; + const servers = entries.some(entry => entry.trim()) + ? parseMavenServerCredentials(entries) + : [ + { + id: core.getInput(constants.INPUT_SERVER_ID), + usernameEnvVar: getInputWithDeprecatedAlias( + constants.INPUT_SERVER_USERNAME_ENV_VAR, + constants.INPUT_SERVER_USERNAME_DEPRECATED, + constants.INPUT_DEFAULT_SERVER_USERNAME + ), + passwordEnvVar: getInputWithDeprecatedAlias( + constants.INPUT_SERVER_PASSWORD_ENV_VAR, + constants.INPUT_SERVER_PASSWORD_DEPRECATED, + constants.INPUT_DEFAULT_SERVER_PASSWORD + ) + } + ]; + + return addMavenServerRepositoryOrigins( + servers, + core.getMultilineInput(constants.INPUT_MVN_SERVER_REPOSITORY_ORIGINS) + ); } // only exported for testing purposes @@ -156,11 +163,96 @@ export function parseMavenServerCredentials( return servers; } +// only exported for testing purposes +export function addMavenServerRepositoryOrigins( + servers: MavenServerCredentials[], + entries: string[] +): MavenServerCredentials[] { + const serverIds = new Set(servers.map(server => server.id)); + const originsByServer = new Map(); + + entries.forEach((entry, index) => { + if (!entry.trim()) { + return; + } + + const separator = entry.indexOf(':'); + if (separator <= 0 || separator === entry.length - 1) { + throw new Error( + `Invalid mvn-server-repository-origins entry at line ${index + 1}. Expected format: server-id:repository-origin` + ); + } + + const id = entry.slice(0, separator).trim(); + const value = entry.slice(separator + 1).trim(); + if (!id || !value) { + throw new Error( + `Invalid mvn-server-repository-origins entry at line ${index + 1}. Server ID and repository origin are required` + ); + } + if (!serverIds.has(id)) { + throw new Error( + `Unknown server-id '${id}' in mvn-server-repository-origins at line ${index + 1}` + ); + } + + let url: URL; + try { + if (!MAVEN_REPOSITORY_ORIGIN.test(value)) { + throw new Error(); + } + url = new URL(value); + } catch { + throw new Error( + `Invalid repository origin '${value}' in mvn-server-repository-origins at line ${index + 1}` + ); + } + if ( + !url.host || + url.username || + url.password || + (url.pathname !== '' && url.pathname !== '/') || + url.search || + url.hash + ) { + throw new Error( + `Invalid repository origin '${value}' in mvn-server-repository-origins at line ${index + 1}` + ); + } + + const explicitPort = /:(\d+)\/?$/.exec(value)?.[1]; + const port = explicitPort ? Number.parseInt(explicitPort, 10) : undefined; + const includePort = + port !== undefined && + !( + (url.protocol === 'http:' && port === 80) || + (url.protocol === 'https:' && port === 443) + ); + const origin = `${url.protocol}//${url.hostname}${includePort ? `:${port}` : ''}`; + const origins = originsByServer.get(id) || []; + if (!origins.includes(origin)) { + origins.push(origin); + originsByServer.set(id, origins); + } + }); + + return servers.map(server => { + const repositoryOrigins = originsByServer.get(server.id); + return repositoryOrigins ? {...server, repositoryOrigins} : server; + }); +} + // only exported for testing purposes export function getMavenRepositorySettings(): MavenRepositorySettings | undefined { const entries = core.getMultilineInput(constants.INPUT_MVN_REPOSITORIES); - if (!entries.some(entry => entry.trim())) { + const pluginEntries = core.getMultilineInput( + constants.INPUT_MVN_PLUGIN_REPOSITORIES + ); + if ( + !entries.some(entry => entry.trim()) && + !pluginEntries.some(entry => entry.trim()) + ) { return undefined; } @@ -170,6 +262,11 @@ export function getMavenRepositorySettings(): ); return { repositories: parseMavenRepositories(entries, includeCentral), + pluginRepositories: parseMavenRepositories( + pluginEntries, + includeCentral, + constants.INPUT_MVN_PLUGIN_REPOSITORIES + ), includeCentral, prioritizeCentral: getBooleanInput( constants.INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL, @@ -181,7 +278,8 @@ export function getMavenRepositorySettings(): // only exported for testing purposes export function parseMavenRepositories( entries: string[], - includeCentral: boolean + includeCentral: boolean, + inputName = constants.INPUT_MVN_REPOSITORIES ): MavenRepository[] { const repositories: MavenRepository[] = []; const repositoryIds = new Set(); @@ -195,7 +293,7 @@ export function parseMavenRepositories( const lastSeparator = entry.lastIndexOf(':'); if (firstSeparator <= 0 || lastSeparator <= firstSeparator) { throw new Error( - `Invalid mvn-repositories entry at line ${index + 1}. Expected format: repository-id:repository-url:snapshots-enabled` + `Invalid ${inputName} entry at line ${index + 1}. Expected format: repository-id:repository-url:snapshots-enabled` ); } @@ -207,18 +305,16 @@ export function parseMavenRepositories( .toLowerCase(); if (!id || !url || !snapshotsValue) { throw new Error( - `Invalid mvn-repositories entry at line ${index + 1}. repository-id, repository URL, and snapshots-enabled are required` + `Invalid ${inputName} entry at line ${index + 1}. repository-id, repository URL, and snapshots-enabled are required` ); } if (snapshotsValue !== 'true' && snapshotsValue !== 'false') { throw new Error( - `Invalid snapshots-enabled value '${snapshotsValue}' in mvn-repositories entry at line ${index + 1}. Expected true or false` + `Invalid snapshots-enabled value '${snapshotsValue}' in ${inputName} entry at line ${index + 1}. Expected true or false` ); } if (repositoryIds.has(id)) { - throw new Error( - `Duplicate repository-id '${id}' in mvn-repositories input` - ); + throw new Error(`Duplicate repository-id '${id}' in ${inputName} input`); } if (includeCentral && id === constants.MAVEN_CENTRAL_REPOSITORY_ID) { throw new Error( @@ -237,6 +333,56 @@ export function parseMavenRepositories( return repositories; } +function generateRepositorySection( + customRepositories: MavenRepository[], + settings: MavenRepositorySettings, + element: 'repository' | 'pluginRepository' +): string[] { + if (!customRepositories.length && settings.includeCentral) { + return []; + } + + const centralRepository: MavenRepository = { + id: constants.MAVEN_CENTRAL_REPOSITORY_ID, + url: constants.MAVEN_CENTRAL_REPOSITORY_URL, + snapshotsEnabled: false + }; + const customCentralConfigured = customRepositories.some( + repository => repository.id === constants.MAVEN_CENTRAL_REPOSITORY_ID + ); + const repositories = settings.includeCentral + ? settings.prioritizeCentral + ? [centralRepository, ...customRepositories] + : [...customRepositories, centralRepository] + : customCentralConfigured + ? customRepositories + : [...customRepositories, {...centralRepository, releasesEnabled: false}]; + const container = + element === 'repository' ? 'repositories' : 'pluginRepositories'; + const lines = [` <${container}>`]; + + for (const repository of repositories) { + lines.push( + ` <${element}>`, + ` ${escapeXmlText(repository.id)}`, + ` ${escapeXmlText(repository.url)}`, + ...(repository.releasesEnabled === undefined + ? [] + : [ + ' ', + ` ${repository.releasesEnabled}`, + ' ' + ]), + ' ', + ` ${repository.snapshotsEnabled}`, + ' ', + ` ` + ); + } + lines.push(` `); + return lines; +} + export async function createAuthenticationSettings( servers: MavenServerCredentials[], settingsDirectory: string, @@ -286,58 +432,39 @@ export function generate( ' ', ` ${escapeXmlText(server.id)}`, ` ${escapeXmlText(`\${env.${server.usernameEnvVar}}`)}`, - ` ${escapeXmlText(`\${env.${server.passwordEnvVar}}`)}`, - ' ' + ` ${escapeXmlText(`\${env.${server.passwordEnvVar}}`)}` ); + if (server.repositoryOrigins) { + lines.push(' '); + for (const origin of server.repositoryOrigins) { + lines.push( + ` ${escapeXmlText(origin)}` + ); + } + lines.push(' '); + } + lines.push(' '); } lines.push(' '); if (repositorySettings || includeGpgPassphraseProfile) { lines.push(' '); 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( ' ', ` ${constants.MAVEN_REPOSITORIES_PROFILE_ID}`, - ' ' + ...generateRepositorySection( + repositorySettings.repositories, + repositorySettings, + 'repository' + ), + ...generateRepositorySection( + repositorySettings.pluginRepositories ?? [], + repositorySettings, + 'pluginRepository' + ), + ' ' ); - for (const repository of repositories) { - lines.push( - ' ', - ` ${escapeXmlText(repository.id)}`, - ` ${escapeXmlText(repository.url)}`, - ...(repository.releasesEnabled === undefined - ? [] - : [ - ' ', - ` ${repository.releasesEnabled}`, - ' ' - ]), - ' ', - ` ${repository.snapshotsEnabled}`, - ' ', - ' ' - ); - } - lines.push(' ', ' '); } if (includeGpgPassphraseProfile) { lines.push( diff --git a/src/constants.ts b/src/constants.ts index 9404e188d..bdba92242 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,7 +16,10 @@ export const SIGNATURE_VERIFICATION_DOCUMENTATION_URL = 'https://github.com/actions/setup-java#download-integrity-and-signatures'; export const SIGNATURE_VERIFICATION_FAILURE_HELP = `If this is a legitimate vendor signing-key rotation, see ${SIGNATURE_VERIFICATION_DOCUMENTATION_URL} for instructions to configure the updated public key or temporarily disable signature verification.`; export const INPUT_MVN_SERVER_CREDENTIALS = 'mvn-server-credentials'; +export const INPUT_MVN_SERVER_REPOSITORY_ORIGINS = + 'mvn-server-repository-origins'; export const INPUT_MVN_REPOSITORIES = 'mvn-repositories'; +export const INPUT_MVN_PLUGIN_REPOSITORIES = 'mvn-plugin-repositories'; export const INPUT_MVN_REPOSITORIES_INCLUDE_CENTRAL = 'mvn-repositories-include-central'; export const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL = diff --git a/src/distributions/base-installer.ts b/src/distributions/base-installer.ts index f2d0c1414..32c7455c1 100644 --- a/src/distributions/base-installer.ts +++ b/src/distributions/base-installer.ts @@ -5,9 +5,9 @@ import semver from 'semver'; import path from 'path'; import * as httpm from '@actions/http-client'; import { - convertVersionToSemver, getToolcachePath, - isVersionSatisfies + isVersionSatisfies, + normalizeJavaVersionToSemver } from '../util.js'; import type { ChecksumAlgorithm, @@ -675,12 +675,9 @@ export abstract class JavaBase { // Java uses a versioning scheme (JEP 322) that can contain more numeric // fields than SemVer allows, e.g. '18.0.1.1' or '11.0.9.1'. Convert such - // exact versions to SemVer build notation ('18.0.1+1') so they are - // accepted. Ranges and versions that already carry build metadata are - // left untouched. - if (/^\d+(\.\d+){3,}$/.test(version)) { - version = convertVersionToSemver(version); - } + // exact versions to SemVer build notation ('18.0.1+1', or '26.0.2+1.1' + // for '26.0.2.1+1') so they are accepted. Ranges are left untouched. + version = normalizeJavaVersionToSemver(version); if (!semver.validRange(version)) { throw new Error( diff --git a/src/distributions/liberica/installer.ts b/src/distributions/liberica/installer.ts index 27a09f3e7..b4320d66a 100644 --- a/src/distributions/liberica/installer.ts +++ b/src/distributions/liberica/installer.ts @@ -166,8 +166,8 @@ export class LibericaDistributions extends JavaBase { } private convertVersionToSemver(version: LibericaVersion): string { - const {buildVersion, featureVersion, interimVersion, updateVersion} = - version; + const {featureVersion, interimVersion, updateVersion} = version; + const buildVersion = version.version.split('+')[1] || version.buildVersion; const mainVersion = [featureVersion, interimVersion, updateVersion].join( '.' ); diff --git a/src/distributions/package-types.ts b/src/distributions/package-types.ts index 9b0d23bd6..81ab37849 100644 --- a/src/distributions/package-types.ts +++ b/src/distributions/package-types.ts @@ -1,5 +1,5 @@ import semver from 'semver'; -import {convertVersionToSemver} from '../util.js'; +import {normalizeJavaVersionToSemver} from '../util.js'; export enum JavaDistribution { Temurin = 'temurin', @@ -98,12 +98,9 @@ function canResolveTemurinJmods(version: string): boolean { return true; } - let normalizedRange = normalizedVersion - .replace(/-ea$/, '') - .replace('-ea.', '+'); - if (/^\d+(\.\d+){3,}$/.test(normalizedRange)) { - normalizedRange = convertVersionToSemver(normalizedRange); - } + const normalizedRange = normalizeJavaVersionToSemver( + normalizedVersion.replace(/-ea$/, '').replace('-ea.', '+') + ); if (!semver.validRange(normalizedRange)) { // JavaBase owns general version validation and its targeted error messages. return true; diff --git a/src/distributions/platform-types.ts b/src/distributions/platform-types.ts index a48b4595e..5e65c7262 100644 --- a/src/distributions/platform-types.ts +++ b/src/distributions/platform-types.ts @@ -5,7 +5,14 @@ import {JavaDistribution} from './package-types.js'; export type JavaPlatform = 'linux' | 'macos' | 'windows' | 'solaris'; export type JavaArchitecture = - 'x86' | 'x64' | 'armv7' | 'aarch64' | 'ppc64le' | 'ppc64' | 's390x'; + | 'x86' + | 'x64' + | 'armv7' + | 'aarch64' + | 'ppc64le' + | 'ppc64' + | 'riscv64' + | 's390x'; interface VersionedArchitecture { architecture: JavaArchitecture; @@ -27,7 +34,14 @@ export type JavaPlatformCapability = RestrictedPlatformCapability | UnrestrictedPlatformCapability; const X64_ARM64 = ['x64', 'aarch64'] as const; -const STANDARD_LINUX = ['x64', 'x86', 'aarch64', 'ppc64le', 's390x'] as const; +const STANDARD_LINUX = [ + 'x64', + 'x86', + 'aarch64', + 'ppc64le', + 'riscv64', + 's390x' +] as const; export const JAVA_PLATFORM_CAPABILITIES: Record< JavaDistribution, @@ -174,6 +188,7 @@ const CANONICAL_ARCHITECTURES: readonly JavaArchitecture[] = [ 'aarch64', 'ppc64le', 'ppc64', + 'riscv64', 's390x' ]; diff --git a/src/distributions/temurin/installer.ts b/src/distributions/temurin/installer.ts index e01535e91..2b81d7725 100644 --- a/src/distributions/temurin/installer.ts +++ b/src/distributions/temurin/installer.ts @@ -70,7 +70,7 @@ export class TemurinDistribution extends JavaBase { const formattedVersion = this.stable ? item.version_data.semver : item.version_data.semver.replace('-beta+', '+'); - return { + const release: JavaDownloadRelease = { version: formattedVersion, url: item.binaries[0].package.link, signatureUrl: item.binaries[0].package.signature_link, @@ -79,11 +79,29 @@ export class TemurinDistribution extends JavaBase { value: item.binaries[0].package.checksum, source: item.binaries[0].package.checksum_link } - } as JavaDownloadRelease; + }; + return { + release, + openjdkVersion: getOpenJdkSemverVersion(item.version_data) + }; }); + // The Adoptium API `semver` folds the JEP 322 patch field into the build + // number ('26.0.2.1+1' -> '26.0.2+101') and appends extra metadata for LTS + // releases ('25.0.4+7' -> '25.0.4+7.0.LTS'). Exact versions requested by + // users follow the OpenJDK notation instead, so also match them against a + // key derived from the OpenJDK version fields ('26.0.2+1.1', '25.0.4+7'). + const isExactBuildRequest = (semver.parse(version)?.build.length ?? 0) > 0; + const satisfiedVersions = availableVersionsWithBinaries - .filter(item => isVersionSatisfies(version, item.version)) + .filter( + ({release, openjdkVersion}) => + isVersionSatisfies(version, release.version) || + (isExactBuildRequest && + openjdkVersion !== null && + semver.compareBuild(version, openjdkVersion) === 0) + ) + .map(({release}) => release) .sort((a, b) => { return -semver.compareBuild(a.version, b.version); }); @@ -92,7 +110,7 @@ export class TemurinDistribution extends JavaBase { satisfiedVersions.length > 0 ? satisfiedVersions[0] : null; if (!resolvedFullVersion) { const availableVersionStrings = availableVersionsWithBinaries.map( - item => item.version + ({release}) => release.version ); throw this.createVersionNotFoundError(version, availableVersionStrings); } @@ -309,3 +327,20 @@ export class TemurinDistribution extends JavaBase { return architecture === 'armv7' ? 'arm' : architecture; } } + +/** + * Builds a SemVer version from the OpenJDK version fields reported by the + * Adoptium API, e.g. '26.0.2.1+1' -> '26.0.2+1.1' and '25.0.4+7-LTS' -> + * '25.0.4+7'. Returns null if the fields cannot form a valid SemVer version. + */ +function getOpenJdkSemverVersion( + versionData: ITemurinAvailableVersions['version_data'] +): string | null { + const {major, minor, security, patch, build} = versionData; + if (build === undefined || build === null) { + return null; + } + const buildMetadata = patch ? `${patch}.${build}` : `${build}`; + const version = `${major}.${minor}.${security}+${buildMetadata}`; + return semver.valid(version) ? version : null; +} diff --git a/src/distributions/temurin/models.ts b/src/distributions/temurin/models.ts index c5bff9020..443d3bb25 100644 --- a/src/distributions/temurin/models.ts +++ b/src/distributions/temurin/models.ts @@ -34,6 +34,7 @@ export interface ITemurinAvailableVersions { minor: number; openjdk_version: string; security: string; + patch?: number; semver: string; }; } diff --git a/src/distributions/zulu/installer.ts b/src/distributions/zulu/installer.ts index 2c05567c8..0107dd249 100644 --- a/src/distributions/zulu/installer.ts +++ b/src/distributions/zulu/installer.ts @@ -2,7 +2,6 @@ import * as core from '@actions/core'; import path from 'path'; import fs from 'fs'; -import semver from 'semver'; import {JavaBase} from '../base-installer.js'; import {IZuluPackageDetails, IZuluVersions} from './models.js'; @@ -30,6 +29,17 @@ interface ZuluResolvedRelease { packageUuid: string; } +function compareNumberArrays(a: number[], b: number[]): number { + const length = Math.max(a.length, b.length); + for (let i = 0; i < length; i++) { + const diff = (a[i] ?? 0) - (b[i] ?? 0); + if (diff !== 0) { + return diff; + } + } + return 0; +} + export class ZuluDistribution extends JavaBase { constructor(installerOptions: JavaInstallerOptions) { super('Zulu', installerOptions); @@ -50,7 +60,9 @@ export class ZuluDistribution extends JavaBase { return { version: convertVersionToSemver(javaVersion), url: item.download_url, - zuluVersion: convertVersionToSemver(item.distro_version), + javaVersion: item.java_version, + buildNumber: item.openjdk_build_number ?? 0, + distroVersion: item.distro_version, packageUuid: item.package_uuid }; }); @@ -58,11 +70,14 @@ export class ZuluDistribution extends JavaBase { const satisfiedVersions = availableVersions .filter(item => isVersionSatisfies(version, item.version)) .sort((a, b) => { - // Azul provides two versions: java_version and distro_version - // we should sort by both fields by descending + // Compare numerically rather than via semver build metadata: Azul + // hotfix releases carry a 4th java_version segment (e.g. 25.0.4.1+1, + // rendered as '25.0.4+1.1') that must rank above 25.0.4+7, whereas + // semver.compareBuild would order the build identifiers '7' > '1'. return ( - -semver.compareBuild(a.version, b.version) || - -semver.compareBuild(a.zuluVersion, b.zuluVersion) + -compareNumberArrays(a.javaVersion, b.javaVersion) || + b.buildNumber - a.buildNumber || + -compareNumberArrays(a.distroVersion, b.distroVersion) ); }) .map((item): ZuluResolvedRelease => ({ diff --git a/src/gpg.ts b/src/gpg.ts index b522d126c..4ea4044d7 100644 --- a/src/gpg.ts +++ b/src/gpg.ts @@ -89,17 +89,20 @@ export async function removeGpgHome(gpgHome: string): Promise { return; } + await stopGpgAgent(resolvedGpgHome); + await io.rmRF(resolvedGpgHome); +} + +async function stopGpgAgent(gpgHome: string): Promise { try { await exec.exec( 'gpgconf', - ['--homedir', toGpgPath(resolvedGpgHome), '--kill', 'gpg-agent'], + ['--homedir', toGpgPath(gpgHome), '--kill', 'gpg-agent'], {silent: true, ignoreReturnCode: true} ); } catch { // gpgconf may be unavailable, but directory removal must still be attempted. } - - await io.rmRF(resolvedGpgHome); } export async function verifyPackageSignature( @@ -160,6 +163,7 @@ export async function verifyPackageSignature( options ); } finally { + await stopGpgAgent(gpgHome); await io.rmRF(signaturePath); await io.rmRF(gpgHome); } diff --git a/src/util.ts b/src/util.ts index e2583849d..b71a73629 100644 --- a/src/util.ts +++ b/src/util.ts @@ -510,6 +510,21 @@ export function convertVersionToSemver(version: number[] | string) { return mainVersion; } +/** + * Java versions (JEP 322) can contain more numeric fields than SemVer allows, + * e.g. '11.0.9.1' or Temurin respins such as '26.0.2.1+1'. Move the extra + * fields into SemVer build metadata ('11.0.9+1', '26.0.2+1.1'). Any other + * input (ranges, regular SemVer versions) is returned unchanged. + */ +export function normalizeJavaVersionToSemver(version: string): string { + const match = /^(\d+(?:\.\d+){3,})(?:\+([0-9A-Za-z.-]+))?$/.exec(version); + if (!match) { + return version; + } + const converted = convertVersionToSemver(match[1]); + return match[2] ? `${converted}.${match[2]}` : converted; +} + /** * Builds a validator for the bytes currently served by a URL from the response * headers of a HEAD request. A vendor's `/latest/` URL never changes, so this