From 1aa87b638dacaa70f1c903fa9ec9cbd91058e2ff Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 10 Sep 2026 23:15:52 -0400 Subject: [PATCH 1/9] Preserve compound Liberica build versions from .sdkmanrc (#1268) * Initial plan * Preserve compound Liberica build versions from release metadata Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- .../distributors/liberica-installer.test.ts | 63 +++++++++++++++++++ __tests__/util.test.ts | 1 + dist/setup/63.index.js | 3 +- src/distributions/liberica/installer.ts | 4 +- 4 files changed, 68 insertions(+), 3 deletions(-) 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__/util.test.ts b/__tests__/util.test.ts index 61143a596..97f932fbb 100644 --- a/__tests__/util.test.ts +++ b/__tests__/util.test.ts @@ -276,6 +276,7 @@ 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=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/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/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( '.' ); From 97c5a5e13aa773f277f72c1287dda3c789f1366c Mon Sep 17 00:00:00 2001 From: Jonas Konrad Date: Fri, 25 Sep 2026 15:04:21 +0200 Subject: [PATCH 2/9] Support Temurin on Linux RISC-V (#1269) * Document Temurin RISC-V compatibility Co-Authored-By: multicode * Expose RISC-V as a canonical architecture Co-Authored-By: multicode * Support Temurin on Linux RISC-V Co-Authored-By: multicode * Address RISC-V review feedback Co-Authored-By: multicode * Fix casing for RISC-V architecture in tests Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Update __tests__/java-platform-contract.test.ts --------- Co-authored-by: multicode Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- .../distributors/temurin-installer.test.ts | 3 ++- __tests__/java-platform-contract.test.ts | 8 ++++++++ action.yml | 2 +- dist/setup/index.js | 10 +++++++++- docs/advanced-usage.md | 2 +- src/distributions/platform-types.ts | 19 +++++++++++++++++-- 7 files changed, 39 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f76c65e73..a8f974113 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` | diff --git a/__tests__/distributors/temurin-installer.test.ts b/__tests__/distributors/temurin-installer.test.ts index 7606627e6..815ec26a2 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) => { 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/action.yml b/action.yml index 1f244a223..69f73ff40 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' diff --git a/dist/setup/index.js b/dist/setup/index.js index 8e019a02a..ee7cd382d 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -31022,7 +31022,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 +31171,7 @@ const CANONICAL_ARCHITECTURES = [ 'aarch64', 'ppc64le', 'ppc64', + 'riscv64', 's390x' ]; const PLATFORM_ALIASES = { diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 8561ab6f4..a00251d61 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` | | 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' ]; From a78ec39f1243adc9be41d35a49593a4c665a604c Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 28 Sep 2026 21:26:40 -0400 Subject: [PATCH 3/9] Stop GPG agent before removing signature-verification home (#1273) * Initial plan * Stop GPG agent before verification home cleanup Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: brunoborges <129743+brunoborges@users.noreply.github.com> --- __tests__/gpg.test.ts | 27 ++++++++++++++++++++++----- dist/cleanup/index.js | 8 ++++++-- dist/setup/220.index.js | 8 ++++++-- dist/setup/463.index.js | 8 ++++++-- dist/setup/81.index.js | 8 ++++++-- src/gpg.ts | 10 +++++++--- 6 files changed, 53 insertions(+), 16 deletions(-) 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/dist/cleanup/index.js b/dist/cleanup/index.js index 00ad7724f..d22ce7640 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -35832,13 +35832,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 +35887,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/463.index.js b/dist/setup/463.index.js index 02b5bfc93..226dd0248 100644 --- a/dist/setup/463.index.js +++ b/dist/setup/463.index.js @@ -375,13 +375,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 +430,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/81.index.js b/dist/setup/81.index.js index 3ac82d8fc..1aecd7fad 100644 --- a/dist/setup/81.index.js +++ b/dist/setup/81.index.js @@ -350,13 +350,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 +405,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/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); } From 74920aab4a9e7a33c5bd4fd3c8d5a7ba260ae795 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Fri, 2 Oct 2026 00:07:25 -0400 Subject: [PATCH 4/9] Prefer Zulu 4-segment hotfix builds when resolving version ranges (#1278) Azul reports hotfix releases like 25.0.4.1 as java_version=[25,0,4,1] with openjdk_build_number=1 (SDKMAN '25.0.4+1.1'). Sorting candidates via semver.compareBuild ranked 25.0.4+7 above 25.0.4+1.1, so ranges such as '25' resolved to the older build. Compare java_version, build number and distro_version numerically instead. Fixes: #1275 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- __tests__/distributors/zulu-installer.test.ts | 63 +++++++++++++++++++ __tests__/util.test.ts | 1 + dist/setup/978.index.js | 52 +++++++++------ src/distributions/zulu/installer.ts | 27 ++++++-- 4 files changed, 117 insertions(+), 26 deletions(-) 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__/util.test.ts b/__tests__/util.test.ts index 97f932fbb..5ebd53449 100644 --- a/__tests__/util.test.ts +++ b/__tests__/util.test.ts @@ -277,6 +277,7 @@ describe('getVersionFromFileContent', () => { ['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/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/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 => ({ From 47b36480ae3ac5671badb6bde2dec0c1898a1cc9 Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Fri, 2 Oct 2026 00:09:56 -0400 Subject: [PATCH 5/9] Support Temurin JEP 322 patch versions like 26.0.2.1+1 (#1270) (#1279) Accept 4-part versions with build metadata (X.Y.Z.P+B -> X.Y.Z+P.B) and match exact Temurin requests against OpenJDK-derived version keys, since the Adoptium API semver folds the patch into the build number (26.0.2+101) and adds LTS metadata (25.0.4+7.0.LTS). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- README.md | 2 +- __tests__/distributors/base-installer.test.ts | 2 + .../distributors/distribution-factory.test.ts | 4 +- .../distributors/temurin-installer.test.ts | 91 +++++++++++++++++++ __tests__/util.test.ts | 17 ++++ dist/cleanup/index.js | 16 +++- dist/setup/242.index.js | 9 +- dist/setup/463.index.js | 34 ++++++- dist/setup/index.js | 24 +++-- src/distributions/base-installer.ts | 13 +-- src/distributions/package-types.ts | 11 +-- src/distributions/temurin/installer.ts | 43 ++++++++- src/distributions/temurin/models.ts | 1 + src/util.ts | 15 +++ 14 files changed, 243 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index a8f974113..5fe1cca96 100644 --- a/README.md +++ b/README.md @@ -233,7 +233,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` | 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/temurin-installer.test.ts b/__tests__/distributors/temurin-installer.test.ts index 815ec26a2..2d424b33c 100644 --- a/__tests__/distributors/temurin-installer.test.ts +++ b/__tests__/distributors/temurin-installer.test.ts @@ -378,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__/util.test.ts b/__tests__/util.test.ts index 5ebd53449..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([ [ diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index d22ce7640..f57587610 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -30848,7 +30848,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 +31247,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/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 226dd0248..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; +} /***/ }), diff --git a/dist/setup/index.js b/dist/setup/index.js index ee7cd382d..1aede103f 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -30975,12 +30975,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; @@ -31311,7 +31306,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); @@ -31712,6 +31708,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/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/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/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/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 From b24925bc49b74c38e5a106894c14cfd876ac1ab5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 00:37:36 -0400 Subject: [PATCH 6/9] chore(deps): bump undici from 6.28.0 to 6.29.0 (#1274) * chore(deps): bump undici from 6.28.0 to 6.29.0 Bumps [undici](https://github.com/nodejs/undici) from 6.28.0 to 6.29.0. - [Release notes](https://github.com/nodejs/undici/releases) - [Commits](https://github.com/nodejs/undici/compare/v6.28.0...v6.29.0) --- updated-dependencies: - dependency-name: undici dependency-version: 6.29.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] * chore(deps): update generated undici artifacts Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * chore(deps): bump brace-expansion to 5.0.12 to fix high-severity audit Resolves GHSA-q2hr-2g5m-vwhr, GHSA-qhr7-859c-m2p7, GHSA-6j4f-fj2g-mc7p. Regenerates dist/ and updates licensed cache. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bruno Borges Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .licenses/npm/brace-expansion.dep.yml | 2 +- .licenses/npm/undici.dep.yml | 2 +- dist/cleanup/767.index.js | 94 +++- dist/cleanup/index.js | 640 ++++++++++++++++++-------- dist/setup/971.index.js | 94 +++- dist/setup/index.js | 640 ++++++++++++++++++-------- package-lock.json | 12 +- 7 files changed, 1052 insertions(+), 432 deletions(-) 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/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/dist/cleanup/767.index.js b/dist/cleanup/767.index.js index 35dd21b98..d0b9442d7 100644 --- a/dist/cleanup/767.index.js +++ b/dist/cleanup/767.index.js @@ -59498,6 +59498,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 +59535,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 +59590,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 +59685,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 +59703,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 +59730,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 +59751,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 +59777,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 f57587610..43b8c0acd 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 + } + + 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,12 +17231,151 @@ 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() } } @@ -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 } 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/index.js b/dist/setup/index.js index 1aede103f..18e223de8 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,12 +17231,151 @@ 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() } } @@ -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 } diff --git a/package-lock.json b/package-lock.json index bcc003721..9cf85dd85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2769,9 +2769,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://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -6020,9 +6020,9 @@ } }, "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" From d0e6e4dbf773b98efde945f446d2099b2c0d8fd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 2 Oct 2026 01:24:19 -0400 Subject: [PATCH 7/9] chore(deps): bump the monthly-npm-updates group with 13 updates (#1276) * chore(deps): bump the monthly-npm-updates group with 13 updates Bumps the monthly-npm-updates group with 13 updates: | Package | From | To | | --- | --- | --- | | [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) | `5.11.0` | `5.11.1` | | [@jest/globals](https://github.com/jestjs/jest/tree/HEAD/packages/jest-globals) | `30.4.1` | `30.5.2` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `26.2.0` | `26.6.2` | | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.67.0` | `8.70.1` | | [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.67.0` | `8.70.1` | | [eslint](https://github.com/eslint/eslint) | `10.8.1` | `10.11.0` | | [eslint-plugin-jest](https://github.com/jest-community/eslint-plugin-jest) | `29.16.1` | `29.16.6` | | [globals](https://github.com/sindresorhus/globals) | `17.11.0` | `17.12.0` | | [jest](https://github.com/jestjs/jest/tree/HEAD/packages/jest) | `30.4.2` | `30.5.2` | | [lint-staged](https://github.com/lint-staged/lint-staged) | `17.3.0` | `17.5.1` | | [prettier](https://github.com/prettier/prettier) | `3.9.6` | `3.9.9` | | [ts-jest](https://github.com/kulshekhar/ts-jest) | `29.4.12` | `29.4.13` | | [typescript](https://github.com/microsoft/TypeScript) | `6.0.3` | `7.0.2` | Updates `fast-xml-parser` from 5.11.0 to 5.11.1 - [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases) - [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.11.0...v5.11.1) Updates `@jest/globals` from 30.4.1 to 30.5.2 - [Release notes](https://github.com/jestjs/jest/releases) - [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/jestjs/jest/commits/v30.5.2/packages/jest-globals) Updates `@types/node` from 26.2.0 to 26.6.2 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@typescript-eslint/eslint-plugin` from 8.67.0 to 8.70.1 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.70.1/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.67.0 to 8.70.1 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.70.1/packages/parser) Updates `eslint` from 10.8.1 to 10.11.0 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/compare/v10.8.1...v10.11.0) Updates `eslint-plugin-jest` from 29.16.1 to 29.16.6 - [Release notes](https://github.com/jest-community/eslint-plugin-jest/releases) - [Changelog](https://github.com/jest-community/eslint-plugin-jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/jest-community/eslint-plugin-jest/compare/v29.16.1...v29.16.6) Updates `globals` from 17.11.0 to 17.12.0 - [Release notes](https://github.com/sindresorhus/globals/releases) - [Commits](https://github.com/sindresorhus/globals/compare/v17.11.0...v17.12.0) Updates `jest` from 30.4.2 to 30.5.2 - [Release notes](https://github.com/jestjs/jest/releases) - [Changelog](https://github.com/jestjs/jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/jestjs/jest/commits/v30.5.2/packages/jest) Updates `lint-staged` from 17.3.0 to 17.5.1 - [Release notes](https://github.com/lint-staged/lint-staged/releases) - [Changelog](https://github.com/lint-staged/lint-staged/blob/main/CHANGELOG.md) - [Commits](https://github.com/lint-staged/lint-staged/compare/v17.3.0...v17.5.1) Updates `prettier` from 3.9.6 to 3.9.9 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.9.6...3.9.9) Updates `ts-jest` from 29.4.12 to 29.4.13 - [Release notes](https://github.com/kulshekhar/ts-jest/releases) - [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.4.12...v29.4.13) Updates `typescript` from 6.0.3 to 7.0.2 - [Release notes](https://github.com/microsoft/TypeScript/releases) - [Commits](https://github.com/microsoft/TypeScript/compare/v6.0.3...v7.0.2) --- updated-dependencies: - dependency-name: fast-xml-parser dependency-version: 5.11.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: monthly-npm-updates - dependency-name: "@jest/globals" dependency-version: 30.5.2 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: monthly-npm-updates - dependency-name: "@types/node" dependency-version: 26.6.2 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: monthly-npm-updates - dependency-name: "@typescript-eslint/eslint-plugin" dependency-version: 8.70.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: monthly-npm-updates - dependency-name: "@typescript-eslint/parser" dependency-version: 8.70.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: monthly-npm-updates - dependency-name: eslint dependency-version: 10.11.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: monthly-npm-updates - dependency-name: eslint-plugin-jest dependency-version: 29.16.6 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: monthly-npm-updates - dependency-name: globals dependency-version: 17.12.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: monthly-npm-updates - dependency-name: jest dependency-version: 30.5.2 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: monthly-npm-updates - dependency-name: lint-staged dependency-version: 17.5.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: monthly-npm-updates - dependency-name: prettier dependency-version: 3.9.9 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: monthly-npm-updates - dependency-name: ts-jest dependency-version: 29.4.13 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: monthly-npm-updates - dependency-name: typescript dependency-version: 7.0.2 dependency-type: direct:development update-type: version-update:semver-major dependency-group: monthly-npm-updates ... Signed-off-by: dependabot[bot] * Fix monthly npm update checks Keep TypeScript on the compatible 6.x line for the current @typescript-eslint peer range, rebuild dist, and refresh the fast-xml-parser license cache. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * Fix monthly npm validation failures Update vulnerable transitive packages in the lockfile, rebuild dist, and correct the JetBrains test expectation for mocked Windows availability. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> * chore: update licensed cache for npm updates Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Bruno Borges Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .licenses/npm/fast-xml-parser.dep.yml | 2 +- .../distributors/jetbrains-installer.test.ts | 11 +- dist/cleanup/767.index.js | 102 +- dist/setup/824.index.js | 100 +- package-lock.json | 1619 ++++++++++------- package.json | 22 +- 6 files changed, 1194 insertions(+), 662 deletions(-) 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/__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/dist/cleanup/767.index.js b/dist/cleanup/767.index.js index d0b9442d7..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 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/package-lock.json b/package-lock.json index 9cf85dd85..a978598c3 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": { @@ -2770,7 +3143,7 @@ }, "node_modules/brace-expansion": { "version": "5.0.12", - "resolved": "https://ms-feed-25.pkgs.visualstudio.com/1es-public/_packaging/npm-public/npm/registry/brace-expansion/-/brace-expansion-5.0.12.tgz", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "license": "MIT", "dependencies": { @@ -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": { @@ -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": { @@ -6029,9 +6384,9 @@ } }, "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": { From 2a69be27b0baa8a7ec1c0ba665dc022b180664c5 Mon Sep 17 00:00:00 2001 From: Yuna Date: Wed, 7 Oct 2026 14:10:22 +0200 Subject: [PATCH 8/9] feat: support Maven server credential origins (#1282) * feat: support Maven server credential origins * Refactor origin construction to include port if necessary Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix: validate Maven server credential origins * docs: note mvn-server-repository-origins requires Maven 3.10+ Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Bruno Borges Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Bruno Borges Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- README.md | 1 + __tests__/auth.test.ts | 132 +++++++++++++++++++++++++++++++++++++-- action.yml | 3 + dist/cleanup/index.js | 3 +- dist/setup/81.index.js | 87 ++++++++++++++++++++++---- dist/setup/index.js | 2 + docs/advanced-usage.md | 14 +++++ src/auth.ts | 136 ++++++++++++++++++++++++++++++++++------- src/constants.ts | 2 + 9 files changed, 342 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 5fe1cca96..18e89f280 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ 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` | diff --git a/__tests__/auth.test.ts b/__tests__/auth.test.ts index a169451f4..ecb230a28 100644 --- a/__tests__/auth.test.ts +++ b/__tests__/auth.test.ts @@ -318,6 +318,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'; @@ -749,16 +764,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 +825,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/action.yml b/action.yml index 69f73ff40..554d4980b 100644 --- a/action.yml +++ b/action.yml @@ -66,6 +66,9 @@ 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 diff --git a/dist/cleanup/index.js b/dist/cleanup/index.js index 43b8c0acd..772499681 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -31040,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_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'; @@ -31058,6 +31058,7 @@ 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_REPOSITORIES_INCLUDE_CENTRAL = 'mvn-repositories-include-central'; const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL = 'mvn-repositories-prioritize-central'; diff --git a/dist/setup/81.index.js b/dist/setup/81.index.js index 1aecd7fad..9309d61de 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 */ .gR)); } // only exported for testing purposes function parseMavenServerCredentials(entries) { @@ -110,6 +112,61 @@ 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())) { @@ -187,7 +244,15 @@ 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) { diff --git a/dist/setup/index.js b/dist/setup/index.js index 18e223de8..1a178aaf7 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -31060,6 +31060,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_SERVER_REPOSITORY_ORIGINS), /* harmony export */ g_: () => (/* binding */ INPUT_DISTRIBUTION), /* harmony export */ gk: () => (/* binding */ INPUT_CACHE), /* harmony export */ hq: () => (/* binding */ MAVEN_REPOSITORIES_PROFILE_ID), @@ -31106,6 +31107,7 @@ 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_REPOSITORIES_INCLUDE_CENTRAL = 'mvn-repositories-include-central'; const INPUT_MVN_REPOSITORIES_PRIORITIZE_CENTRAL = 'mvn-repositories-prioritize-central'; diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index a00251d61..72a8ad963 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -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 + ``` diff --git a/src/auth.ts b/src/auth.ts index 3e20cb25b..096e1fb54 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 { @@ -28,6 +29,8 @@ export interface MavenRepositorySettings { 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 +98,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,6 +162,85 @@ 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 { @@ -286,9 +371,18 @@ 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(' '); diff --git a/src/constants.ts b/src/constants.ts index 9404e188d..6a213a592 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,6 +16,8 @@ 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_REPOSITORIES_INCLUDE_CENTRAL = 'mvn-repositories-include-central'; From b843ebf9b913146b38da2eaa7aeddf76074ae1ad Mon Sep 17 00:00:00 2001 From: Bruno Borges Date: Wed, 14 Oct 2026 16:11:40 -0400 Subject: [PATCH 9/9] Support Maven plugin repositories in generated settings.xml (#1286) * Add Maven plugin repository support Generate plugin repositories alongside dependency repositories and share Central inclusion and ordering controls. Cover repository parsing, credentials, explicit Central overrides, plugin ordering, and effective Maven models; document the compatibility change and rebuild runtime bundles. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 52a8a8e2-7965-42d3-88d9-d66f64d0d60d * Fix CI audit by updating Handlebars to 4.7.10 Update the transitive test dependency to the patched release without changing runtime dependencies or suppressing audit checks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 282b173f-7b1d-4fcd-bca4-9c8e2fa72a37 --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 52a8a8e2-7965-42d3-88d9-d66f64d0d60d Copilot-Session: 282b173f-7b1d-4fcd-bca4-9c8e2fa72a37 --- .github/workflows/e2e-publishing.yml | 112 ++++++ README.md | 23 +- __tests__/auth.test.ts | 555 ++++++++++++++++++++++++--- action.yml | 7 +- dist/cleanup/index.js | 3 +- dist/setup/81.index.js | 76 ++-- dist/setup/index.js | 4 +- docs/advanced-usage.md | 94 ++++- package-lock.json | 8 +- src/auth.ts | 127 +++--- src/constants.ts | 1 + 11 files changed, 846 insertions(+), 164 deletions(-) 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/README.md b/README.md index 18e89f280..58822e688 100644 --- a/README.md +++ b/README.md @@ -170,8 +170,9 @@ steps: | `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. | | @@ -482,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 ecb230a28..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; @@ -475,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( @@ -506,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', () => { @@ -543,6 +666,9 @@ describe('auth tests', () => { snapshots: {enabled: 'false'} } ] + }, + pluginRepositories: { + pluginRepository: disabledCentralRepositoryXml } }, { @@ -556,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&<>"'é`, @@ -601,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( @@ -721,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'], diff --git a/action.yml b/action.yml index 554d4980b..eb32ec42c 100644 --- a/action.yml +++ b/action.yml @@ -72,12 +72,15 @@ inputs: 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/index.js b/dist/cleanup/index.js index 772499681..03ab6fe1f 100644 --- a/dist/cleanup/index.js +++ b/dist/cleanup/index.js @@ -31040,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_SERVER_REPOSITORY_ORIGINS, 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'; @@ -31060,6 +31060,7 @@ const SIGNATURE_VERIFICATION_FAILURE_HELP = (/* unused pure expression or super 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'; diff --git a/dist/setup/81.index.js b/dist/setup/81.index.js index 9309d61de..39f98e377 100644 --- a/dist/setup/81.index.js +++ b/dist/setup/81.index.js @@ -85,7 +85,7 @@ function getMavenServerSettings() { 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 */ .gR)); + 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) { @@ -169,18 +169,21 @@ function addMavenServerRepositoryOrigins(servers, entries) { // 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) => { @@ -190,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(); @@ -199,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`); @@ -219,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) @@ -258,33 +292,7 @@ function generate(servers, gpgPassphraseEnvVar, repositorySettings) { 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)}`, ' ', ' '); diff --git a/dist/setup/index.js b/dist/setup/index.js index 1a178aaf7..5c0536646 100644 --- a/dist/setup/index.js +++ b/dist/setup/index.js @@ -31046,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), @@ -31060,7 +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_SERVER_REPOSITORY_ORIGINS), +/* 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), @@ -31109,6 +31110,7 @@ const SIGNATURE_VERIFICATION_FAILURE_HELP = `If this is a legitimate vendor sign 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'; diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md index 72a8ad963..dedca4ed4 100644 --- a/docs/advanced-usage.md +++ b/docs/advanced-usage.md @@ -1003,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 a978598c3..47cb35a8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4320,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" diff --git a/src/auth.ts b/src/auth.ts index 096e1fb54..91480f765 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -25,6 +25,7 @@ export interface MavenRepository { export interface MavenRepositorySettings { repositories: MavenRepository[]; + pluginRepositories?: MavenRepository[]; includeCentral: boolean; prioritizeCentral: boolean; } @@ -245,7 +246,13 @@ export function addMavenServerRepositoryOrigins( 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; } @@ -255,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, @@ -266,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(); @@ -280,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` ); } @@ -292,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( @@ -322,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, @@ -389,49 +450,21 @@ export function generate( 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 6a213a592..bdba92242 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -19,6 +19,7 @@ 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 =