diff --git a/.github/actions/test-cc/action.yml b/.github/actions/test-cc/action.yml deleted file mode 100644 index 77920fde..00000000 --- a/.github/actions/test-cc/action.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Test CC -description: Test C compiler compatibility -inputs: - compiler: - description: "Toolchain or compiler to install" - required: true - version: - description: "Version of toolchain or compiler" - required: true -runs: - using: "composite" - steps: - - - name: Check compiler version - shell: bash - run: | - # check $CC == $FPM_CC - [[ "${{ env.CC }}" == "${{ env.FPM_CC }}" ]] && (echo "CC and FPM_CC match") || (echo "CC and FPM_CC don't match: ${{ env.CC }} != ${{ env.FPM_CC}}"; exit 1) - - # check compiler version - if ([ "$RUNNER_OS" == "Windows" ] && [[ "${{ inputs.compiler }}" =~ "intel" ]]); then - # only last line of output captured by command substitution, write to temp file instead - ${{ env.CC }} //QV > "$RUNNER_TEMP/${{ env.CC }}.ver" 2>&1 - ccv=$(cat "$RUNNER_TEMP/${{ env.CC }}.ver" | head -n 1) - ccv=${ccv#*Version } - ccv=${ccv%% Build*} - elif ([ "$RUNNER_OS" == "Linux" ] && [[ "${{ inputs.compiler }}" == "nvidia-hpc" ]]); then - # Get the compiler version and extract the version number - ccv=$(${{ env.CC }} --version 2>&1 | awk '/nvc/ {print $2}' | cut -d'-' -f1) - elif ([[ "${{ inputs.compiler }}" == "lfortran" ]]); then - exit 0 # uses preinstalled gcc, skip version check - elif ([[ "${{ inputs.compiler }}" != "nvidia-hpc" ]]); then - ccv=$(${{ env.CC }} --version | head -n 1) - ccv=$(echo "$ccv" | grep -woE '[0123456789.]+' | head -n 1) - fi - [[ "$ccv" == ${{ inputs.version }}* ]] && (echo "found ${{ env.CC }} version: $ccv") || (echo "unexpected ${{ env.CC }} version: $ccv"; exit 1) - - - name: Test compile (bash) - working-directory: test - shell: bash - run: | - ${{ env.CC }} -o hw hw.c - output=$(./hw '2>&1') - [[ "$output" == *"hello world"* ]] && echo "$output" || (echo "Unexpected C program output: $output"; exit 1) - rm hw - \ No newline at end of file diff --git a/.github/actions/test-cxx/action.yml b/.github/actions/test-cxx/action.yml deleted file mode 100644 index 5793c6c2..00000000 --- a/.github/actions/test-cxx/action.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Test CXX -description: Test CXX compiler compatibility -inputs: - compiler: - description: "Toolchain or compiler to install" - required: true - version: - description: "Version of toolchain or compiler" - required: true -runs: - using: "composite" - steps: - - - name: Check compiler version - shell: bash - run: | - # check $CXX == $FPM_CXX - [[ "${{ env.CXX }}" == "${{ env.FPM_CXX }}" ]] && (echo "CXX and FPM_CXX match") || (echo "CXX and FPM_CXX don't match: ${{ env.CXX }} != ${{ env.FPM_CXX}}"; exit 1) - - # check compiler version - if ([ "$RUNNER_OS" == "Windows" ] && [[ "${{ matrix.toolchain.compiler }}" =~ "intel" ]]); then - # only last line of output captured by command substitution, write to temp file instead - ${{ env.CXX }} //QV > "$RUNNER_TEMP/${{ env.CXX }}.ver" 2>&1 - cxxv=$(cat "$RUNNER_TEMP/${{ env.CXX }}.ver" | head -n 1) - cxxv=${cxxv#*Version } - cxxv=${cxxv%% Build*} - elif ([ "$RUNNER_OS" == "Linux" ] && [[ "${{ matrix.toolchain.compiler}}" == "nvidia-hpc" ]]); then - # Get the compiler version and extract the version number - cxxv=$(${{ env.CXX }} --version 2>&1 | awk '/nvc++/ {print $2}' | cut -d'-' -f1) - elif ([[ "${{ inputs.compiler }}" == "lfortran" ]]); then - exit 0 # uses preinstalled gcc, skip version check - elif ([[ "${{ matrix.toolchain.compiler}}" != "nvidia-hpc" ]]); then - cxxv=$(${{ env.CXX }} --version | head -n 1) - cxxv=$(echo "$cxxv" | grep -woE '[0123456789.]+' | head -n 1) - fi - [[ "$cxxv" == ${{ matrix.toolchain.version }}* ]] && (echo "found ${{ env.CXX }} version: $cxxv") || (echo "unexpected ${{ env.CXX }} version: $cxxv"; exit 1) - - - name: Test compile (bash) - working-directory: test - shell: bash - run: | - ${{ env.CXX }} -o hw hw.cpp - output=$(./hw '2>&1') - [[ "$output" == *"hello world"* ]] && echo "$output" || (echo "Unexpected C++ program output: $output"; exit 1) - rm hw - \ No newline at end of file diff --git a/.github/actions/test-fc/action.yml b/.github/actions/test-fc/action.yml deleted file mode 100644 index 2095ca59..00000000 --- a/.github/actions/test-fc/action.yml +++ /dev/null @@ -1,96 +0,0 @@ -name: Test FC -description: Test Fortran compiler compatibility -inputs: - compiler: - description: "Toolchain or compiler to install" - required: true - version: - description: "Version of toolchain or compiler" - required: true -runs: - using: "composite" - steps: - - - name: Check compiler version - shell: bash - run: | - # check $FC == $FPM_FC - [[ "${{ env.FC }}" == "${{ env.FPM_FC }}" ]] && (echo "FC and FPM_FC match") || (echo "FC and FPM_FC don't match: ${{ env.FC }} != ${{ env.FPM_FC}}"; exit 1) - - # check compiler version - if ([ "$RUNNER_OS" == "Windows" ] && [[ "${{ inputs.compiler }}" =~ "intel" ]]); then - # only last line of output captured by command substitution, write to temp file instead - ${{ env.FC }} //QV > "$RUNNER_TEMP/${{ env.FC }}.ver" 2>&1 - fcv=$(cat "$RUNNER_TEMP/${{ env.FC }}.ver" | head -n 1) - fcv=${fcv#*Version } - fcv=${fcv%% Build*} - elif ([ "$RUNNER_OS" == "Linux" ] && [[ "${{ inputs.compiler }}" == "nvidia-hpc" ]]); then - # Get the compiler version and extract the version number - fcv=$(${{ env.FC }} --version 2>&1 | awk '/nvfortran/ {print $2}' | cut -d'-' -f1) - elif ([[ "${{ inputs.compiler }}" == "lfortran" ]]); then - fcv=$(${{ env.FC }} --version | head -n 1 | grep -woE '[0123456789.]+') - elif ([[ "${{ inputs.compiler }}" == "aocc" ]]); then - # AOCC flang reports LLVM/clang version first; extract AOCC version (e.g. AOCC_4.0.0 or AOCC 5.1.0) - fcv=$(${{ env.FC }} --version 2>&1 | grep -oE 'AOCC[_ ][0-9]+\.[0-9]+\.[0-9]+' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1) - elif ([[ "${{ inputs.compiler }}" != "nvidia-hpc" ]]); then - fcv=$(${{ env.FC }} --version | head -n 1) - fcv=$(echo "$fcv" | grep -woE '[0123456789.]+' | head -n 1) - fi - [[ "$fcv" == ${{ inputs.version }}* ]] && (echo "found ${{ env.FC }} version: $fcv") || (echo "unexpected ${{ env.FC }} version: $fcv"; exit 1) - - - name: Test compile (bash) - working-directory: test - shell: bash - run: | - # hello world program - ${{ env.FC }} -o hw hw.f90 - output=$(./hw '2>&1') - [[ "$output" == *"hello world"* ]] && echo "$output" || (echo "Unexpected Fortran program 'hw' output: $output"; exit 1) - rm hw - - - name: Test compile Fortran (pwsh) - working-directory: test - # todo: debug lfortran discovery issues (same for powershell and cmd below) - if: ${{ (success() || failure()) && runner.os == 'Windows' && inputs.compiler != 'lfortran' }} - shell: pwsh - run: | - ${{ env.FC }} -o hw.exe hw.f90 - $output=$(& ".\hw.exe") - if ($output -match "hello world") { - write-output $output - } else { - write-output "unexpected output: $output" - exit 1 - } - rm hw.exe - - - name: Test compile Fortran (powershell) - working-directory: test - if: ${{ (success() || failure()) && runner.os == 'Windows' && inputs.compiler != 'lfortran' }} - shell: powershell - run: | - ${{ env.FC }} -o hw.exe hw.f90 - $output=$(& ".\hw.exe") - if ($output -match "hello world") { - write-output $output - } else { - write-output "unexpected output: $output" - exit 1 - } - rm hw.exe - - - name: Test compile Fortran (cmd) - working-directory: test - if: ${{ (success() || failure()) && runner.os == 'Windows' && inputs.compiler != 'lfortran' }} - shell: cmd - run: | - ${{ env.FC }} -o hw.exe hw.f90 - for /f "tokens=* usebackq" %%f in (`.\hw.exe`) do @set "OUTPUT=%%f" - if "%OUTPUT%"=="hello world" ( - echo %OUTPUT% - ) else ( - echo unexpected output: %OUTPUT% - exit 1 - ) - del hw.exe - \ No newline at end of file diff --git a/.github/actions/verify-windows-shells/action.yml b/.github/actions/verify-windows-shells/action.yml new file mode 100644 index 00000000..71320904 --- /dev/null +++ b/.github/actions/verify-windows-shells/action.yml @@ -0,0 +1,123 @@ +name: Verify Windows Shell Invocation +description: | + Verifies that FC, CC, CXX, F77, and F90 environment variables are correctly + set and that all compiler executables are callable from PowerShell, CMD, + and Bash. Uses --version with a /? fallback for MSVC-style compilers + (e.g. icl). +inputs: + fc: + description: Full path to the Fortran compiler ($FC) + required: true + cc: + description: Full path to the C companion compiler ($CC) + required: true + cxx: + description: Full path to the C++ companion compiler ($CXX) + required: true +runs: + using: composite + steps: + - name: Verify compiler invocation under pwsh (Windows only) + shell: pwsh + env: + OUTPUT_FC: ${{ inputs.fc }} + OUTPUT_CC: ${{ inputs.cc }} + OUTPUT_CXX: ${{ inputs.cxx }} + run: | + $fc = $Env:OUTPUT_FC + $cc = $Env:OUTPUT_CC + $cxx = $Env:OUTPUT_CXX + Write-Host "FC: $fc" + Write-Host "CC: $cc" + Write-Host "CXX: $cxx" + + # Verify F77 and F90 legacy aliases are set and match FC + $f77 = $Env:F77 + $f90 = $Env:F90 + Write-Host "F77: $f77" + Write-Host "F90: $f90" + if ($f77 -ne $fc) { + Write-Error "F77='$f77' does not match FC='$fc'" + exit 1 + } + if ($f90 -ne $fc) { + Write-Error "F90='$f90' does not match FC='$fc'" + exit 1 + } + + # Verify executables are callable under PowerShell. + # Try --version; fall back to /? for MSVC-style compilers (e.g. icl, + # ifort, cl), which reject --version on Windows. + & "$fc" --version 2>$null + if ($LASTEXITCODE -ne 0) { & "$fc" /? 2>$null } + & "$cc" --version 2>$null + if ($LASTEXITCODE -ne 0) { & "$cc" /? 2>$null } + & "$cxx" --version 2>$null + if ($LASTEXITCODE -ne 0) { & "$cxx" /? 2>$null } + & "$f77" --version 2>$null + if ($LASTEXITCODE -ne 0) { & "$f77" /? 2>$null } + & "$f90" --version 2>$null + if ($LASTEXITCODE -ne 0) { & "$f90" /? 2>$null } + + - name: Verify compiler invocation under cmd (Windows only) + shell: cmd + env: + OUTPUT_FC: ${{ inputs.fc }} + OUTPUT_CC: ${{ inputs.cc }} + OUTPUT_CXX: ${{ inputs.cxx }} + run: | + echo FC: %OUTPUT_FC% + echo CC: %OUTPUT_CC% + echo CXX: %OUTPUT_CXX% + echo F77: %F77% + echo F90: %F90% + if not "%F77%" == "%OUTPUT_FC%" ( + echo "::error::F77='%F77%' does not match FC='%OUTPUT_FC%'" + exit /b 1 + ) + if not "%F90%" == "%OUTPUT_FC%" ( + echo "::error::F90='%F90%' does not match FC='%OUTPUT_FC%'" + exit /b 1 + ) + REM Try --version; fall back to /? for MSVC-style compilers (e.g. icl, + REM ifort, cl), which reject --version on Windows. + "%OUTPUT_FC%" --version 2>nul + if errorlevel 1 "%OUTPUT_FC%" /? 2>nul + "%OUTPUT_CC%" --version 2>nul + if errorlevel 1 "%OUTPUT_CC%" /? 2>nul + "%OUTPUT_CXX%" --version 2>nul + if errorlevel 1 "%OUTPUT_CXX%" /? 2>nul + "%F77%" --version 2>nul + if errorlevel 1 "%F77%" /? 2>nul + "%F90%" --version 2>nul + if errorlevel 1 "%F90%" /? 2>nul + + - name: Verify compiler invocation under bash (Windows only) + shell: bash + env: + OUTPUT_FC: ${{ inputs.fc }} + OUTPUT_CC: ${{ inputs.cc }} + OUTPUT_CXX: ${{ inputs.cxx }} + run: | + fc="$OUTPUT_FC" + cc="$OUTPUT_CC" + cxx="$OUTPUT_CXX" + echo "FC: $fc" + echo "CC: $cc" + echo "CXX: $cxx" + + # F77/F90 legacy aliases must match FC. + if [ "$F77" != "$fc" ] || [ "$F90" != "$fc" ]; then + echo "::error::F77='$F77' or F90='$F90' does not match FC='$fc'" + exit 1 + fi + + # Try --version; fall back to /? for MSVC-style compilers (e.g. icl, + # ifort, cl), which reject --version on Windows. This step also proves + # the toolchain directories are reachable from Git Bash, where /usr/bin + # precedes every GITHUB_PATH entry. + "$fc" --version >/dev/null 2>&1 || "$fc" /? >/dev/null 2>&1 + "$cc" --version >/dev/null 2>&1 || "$cc" /? >/dev/null 2>&1 + "$cxx" --version >/dev/null 2>&1 || "$cxx" /? >/dev/null 2>&1 + "$F77" --version >/dev/null 2>&1 || "$F77" /? >/dev/null 2>&1 + "$F90" --version >/dev/null 2>&1 || "$F90" /? >/dev/null 2>&1 diff --git a/.github/compat/compat.csv b/.github/compat/compat.csv deleted file mode 100644 index a26bcb67..00000000 --- a/.github/compat/compat.csv +++ /dev/null @@ -1,13 +0,0 @@ -compiler,aocc,aocc,gcc,gcc,gcc,intel,intel,intel,intel,intel,intel,intel,intel,intel,intel,intel,intel,intel,intel,intel,intel,intel,intel,intel-classic,intel-classic,intel-classic,intel-classic,intel-classic,intel-classic,intel-classic,intel-classic,intel-classic,intel-classic,intel-classic,intel-classic,intel-classic,lfortran,lfortran,nvidia-hpc,nvidia-hpc,nvidia-hpc,nvidia-hpc,nvidia-hpc,nvidia-hpc,nvidia-hpc,nvidia-hpc,nvidia-hpc,nvidia-hpc,nvidia-hpc,nvidia-hpc,nvidia-hpc,nvidia-hpc -version,5.1.0,5.2.0,13,14,15,2021.1,2021.1.2,2021.2,2021.4,2022.0,2022.1,2022.2,2022.2.1,2023.0,2023.1,2023.2,2024.0,2024.1,2025.0,2025.2,2025.3,2026.0,2026.1,2021.1,2021.1.2,2021.2,2021.3,2021.4,2021.5,2021.6,2021.7.1,2021.8,2021.9,2021.10,2021.11,2021.12,0.57.0,0.58.0,23.3,23.5,23.7,23.9,23.11,24.1,24.3,24.5,25.1,25.3,25.5,25.7,25.9,26.1 -runner,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -macos-14,,,✓,✓,✓,,,,,,,,,,,,,,,,,,,✓,,✓,✓,,✓,✓,,✓,✓,✓,,,✓,✓,,,,,,,,,,,,,, -macos-15,,,✓,✓,✓,,,,,,,,,,,,,,,,,,,✓,,✓,✓,,✓,✓,,✓,✓,✓,,,✓,✓,,,,,,,,,,,,,, -macos-15-intel,,,✓,✓,✓,✓,,✓,,,,,,,,,,,,,,,,✓,,✓,✓,,✓,✓,,✓,✓,✓,,,✓,✓,,,,,,,,,,,,,, -macos-26,,,✓,✓,✓,,,,,,,,,,,,,,,,,,,✓,,✓,✓,,✓,✓,,✓,✓,✓,,,✓,✓,,,,,,,,,,,,,, -macos-26-intel,,,✓,✓,✓,✓,,✓,,,,,,,,,,,,,,,,✓,,✓,✓,,✓,✓,,✓,✓,✓,,,✓,✓,,,,,,,,,,,,,, -ubuntu-22.04,,,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓ -ubuntu-24.04,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,✓,,,,,,,,✓,✓,✓,✓,✓,✓,✓ -windows-2022,,,✓,✓,✓,,,,,,✓,✓,,,✓,✓,✓,✓,✓,✓,✓,✓,✓,,,,,,,✓,,,✓,✓,✓,✓,✓,✓,,,,,,,,,,,,,, -windows-2025,,,✓,✓,✓,,,,,,,,,,,,,,,,✓,✓,✓,,,,,,,,,,,,,,✓,✓,,,,,,,,,,,,,, -windows-2025-vs2026,,,✓,✓,✓,,,,,,,,,,,,,,,,✓,✓,✓,,,,,,,,,,,,,,✓,✓,,,,,,,,,,,,,, diff --git a/.github/compat/latest-versions.sh b/.github/compat/latest-versions.sh deleted file mode 100644 index e1f96bff..00000000 --- a/.github/compat/latest-versions.sh +++ /dev/null @@ -1,52 +0,0 @@ -# Auto-generated from compat.csv -# DO NOT EDIT MANUALLY - Run .github/compat/update_latest_versions.py to regenerate - -# Latest supported aocc versions by runner -LATEST_aocc_ubuntu_24_04="5.2.0" - -# Latest supported gcc versions by runner -LATEST_gcc_macos_14="15" -LATEST_gcc_macos_15="15" -LATEST_gcc_macos_15_intel="15" -LATEST_gcc_macos_26="15" -LATEST_gcc_macos_26_intel="15" -LATEST_gcc_ubuntu_22_04="15" -LATEST_gcc_ubuntu_24_04="15" -LATEST_gcc_windows_2022="15" -LATEST_gcc_windows_2025="15" -LATEST_gcc_windows_2025_vs2026="15" - -# Latest supported intel versions by runner -LATEST_intel_macos_15_intel="2021.2" -LATEST_intel_macos_26_intel="2021.2" -LATEST_intel_ubuntu_22_04="2026.1" -LATEST_intel_ubuntu_24_04="2026.1" -LATEST_intel_windows_2022="2026.1" -LATEST_intel_windows_2025="2026.1" -LATEST_intel_windows_2025_vs2026="2026.1" - -# Latest supported intel-classic versions by runner -LATEST_intel_classic_macos_14="2021.10" -LATEST_intel_classic_macos_15="2021.10" -LATEST_intel_classic_macos_15_intel="2021.10" -LATEST_intel_classic_macos_26="2021.10" -LATEST_intel_classic_macos_26_intel="2021.10" -LATEST_intel_classic_ubuntu_22_04="2021.12" -LATEST_intel_classic_ubuntu_24_04="2021.12" -LATEST_intel_classic_windows_2022="2021.12" - -# Latest supported lfortran versions by runner -LATEST_lfortran_macos_14="0.58.0" -LATEST_lfortran_macos_15="0.58.0" -LATEST_lfortran_macos_15_intel="0.58.0" -LATEST_lfortran_macos_26="0.58.0" -LATEST_lfortran_macos_26_intel="0.58.0" -LATEST_lfortran_ubuntu_22_04="0.58.0" -LATEST_lfortran_ubuntu_24_04="0.58.0" -LATEST_lfortran_windows_2022="0.58.0" -LATEST_lfortran_windows_2025="0.58.0" -LATEST_lfortran_windows_2025_vs2026="0.58.0" - -# Latest supported nvidia-hpc versions by runner -LATEST_nvidia_hpc_ubuntu_22_04="26.1" -LATEST_nvidia_hpc_ubuntu_24_04="26.1" diff --git a/.github/compat/long_compat.csv b/.github/compat/long_compat.csv deleted file mode 100644 index 0fbc1947..00000000 --- a/.github/compat/long_compat.csv +++ /dev/null @@ -1,315 +0,0 @@ -runner,compiler,version,support -macos-14,gcc,13,✓ -macos-14,gcc,14,✓ -macos-14,gcc,15,✓ -macos-14,intel-classic,2021.1.2, -macos-14,intel-classic,2021.1,✓ -macos-14,intel-classic,2021.10,✓ -macos-14,intel-classic,2021.11, -macos-14,intel-classic,2021.12, -macos-14,intel-classic,2021.2,✓ -macos-14,intel-classic,2021.3,✓ -macos-14,intel-classic,2021.4, -macos-14,intel-classic,2021.5,✓ -macos-14,intel-classic,2021.6,✓ -macos-14,intel-classic,2021.7.1, -macos-14,intel-classic,2021.8,✓ -macos-14,intel-classic,2021.9,✓ -macos-14,lfortran,0.57.0,✓ -macos-14,lfortran,0.58.0,✓ -macos-15-intel,gcc,13,✓ -macos-15-intel,gcc,14,✓ -macos-15-intel,gcc,15,✓ -macos-15-intel,intel-classic,2021.1.2, -macos-15-intel,intel-classic,2021.1,✓ -macos-15-intel,intel-classic,2021.10,✓ -macos-15-intel,intel-classic,2021.11, -macos-15-intel,intel-classic,2021.12, -macos-15-intel,intel-classic,2021.2,✓ -macos-15-intel,intel-classic,2021.3,✓ -macos-15-intel,intel-classic,2021.4, -macos-15-intel,intel-classic,2021.5,✓ -macos-15-intel,intel-classic,2021.6,✓ -macos-15-intel,intel-classic,2021.7.1, -macos-15-intel,intel-classic,2021.8,✓ -macos-15-intel,intel-classic,2021.9,✓ -macos-15-intel,intel,2021.1.2, -macos-15-intel,intel,2021.1,✓ -macos-15-intel,intel,2021.2,✓ -macos-15-intel,intel,2021.4, -macos-15-intel,intel,2022.0, -macos-15-intel,intel,2022.1, -macos-15-intel,intel,2022.2.1, -macos-15-intel,intel,2022.2, -macos-15-intel,intel,2023.0, -macos-15-intel,intel,2023.1, -macos-15-intel,intel,2023.2, -macos-15-intel,intel,2024.0, -macos-15-intel,intel,2024.1, -macos-15-intel,intel,2025.0, -macos-15-intel,intel,2025.2, -macos-15-intel,intel,2025.3, -macos-15-intel,intel,2026.0, -macos-15-intel,intel,2026.1, -macos-15-intel,lfortran,0.57.0,✓ -macos-15-intel,lfortran,0.58.0,✓ -macos-15,gcc,13,✓ -macos-15,gcc,14,✓ -macos-15,gcc,15,✓ -macos-15,intel-classic,2021.1.2, -macos-15,intel-classic,2021.1,✓ -macos-15,intel-classic,2021.10,✓ -macos-15,intel-classic,2021.11, -macos-15,intel-classic,2021.12, -macos-15,intel-classic,2021.2,✓ -macos-15,intel-classic,2021.3,✓ -macos-15,intel-classic,2021.4, -macos-15,intel-classic,2021.5,✓ -macos-15,intel-classic,2021.6,✓ -macos-15,intel-classic,2021.7.1, -macos-15,intel-classic,2021.8,✓ -macos-15,intel-classic,2021.9,✓ -macos-15,lfortran,0.57.0,✓ -macos-15,lfortran,0.58.0,✓ -macos-26-intel,gcc,13,✓ -macos-26-intel,gcc,14,✓ -macos-26-intel,gcc,15,✓ -macos-26-intel,intel-classic,2021.1.2, -macos-26-intel,intel-classic,2021.1,✓ -macos-26-intel,intel-classic,2021.10,✓ -macos-26-intel,intel-classic,2021.11, -macos-26-intel,intel-classic,2021.12, -macos-26-intel,intel-classic,2021.2,✓ -macos-26-intel,intel-classic,2021.3,✓ -macos-26-intel,intel-classic,2021.4, -macos-26-intel,intel-classic,2021.5,✓ -macos-26-intel,intel-classic,2021.6,✓ -macos-26-intel,intel-classic,2021.7.1, -macos-26-intel,intel-classic,2021.8,✓ -macos-26-intel,intel-classic,2021.9,✓ -macos-26-intel,intel,2021.1.2, -macos-26-intel,intel,2021.1,✓ -macos-26-intel,intel,2021.2,✓ -macos-26-intel,intel,2021.4, -macos-26-intel,intel,2022.0, -macos-26-intel,intel,2022.1, -macos-26-intel,intel,2022.2.1, -macos-26-intel,intel,2022.2, -macos-26-intel,intel,2023.0, -macos-26-intel,intel,2023.1, -macos-26-intel,intel,2023.2, -macos-26-intel,intel,2024.0, -macos-26-intel,intel,2024.1, -macos-26-intel,intel,2025.0, -macos-26-intel,intel,2025.2, -macos-26-intel,intel,2025.3, -macos-26-intel,intel,2026.0, -macos-26-intel,intel,2026.1, -macos-26-intel,lfortran,0.57.0,✓ -macos-26-intel,lfortran,0.58.0,✓ -macos-26,gcc,13,✓ -macos-26,gcc,14,✓ -macos-26,gcc,15,✓ -macos-26,intel-classic,2021.1.2, -macos-26,intel-classic,2021.1,✓ -macos-26,intel-classic,2021.10,✓ -macos-26,intel-classic,2021.11, -macos-26,intel-classic,2021.12, -macos-26,intel-classic,2021.2,✓ -macos-26,intel-classic,2021.3,✓ -macos-26,intel-classic,2021.4, -macos-26,intel-classic,2021.5,✓ -macos-26,intel-classic,2021.6,✓ -macos-26,intel-classic,2021.7.1, -macos-26,intel-classic,2021.8,✓ -macos-26,intel-classic,2021.9,✓ -macos-26,lfortran,0.57.0,✓ -macos-26,lfortran,0.58.0,✓ -ubuntu-22.04,gcc,13,✓ -ubuntu-22.04,gcc,14,✓ -ubuntu-22.04,gcc,15,✓ -ubuntu-22.04,intel-classic,2021.1.2,✓ -ubuntu-22.04,intel-classic,2021.1,✓ -ubuntu-22.04,intel-classic,2021.10,✓ -ubuntu-22.04,intel-classic,2021.11,✓ -ubuntu-22.04,intel-classic,2021.12,✓ -ubuntu-22.04,intel-classic,2021.2,✓ -ubuntu-22.04,intel-classic,2021.3, -ubuntu-22.04,intel-classic,2021.4,✓ -ubuntu-22.04,intel-classic,2021.5,✓ -ubuntu-22.04,intel-classic,2021.6,✓ -ubuntu-22.04,intel-classic,2021.7.1,✓ -ubuntu-22.04,intel-classic,2021.8,✓ -ubuntu-22.04,intel-classic,2021.9,✓ -ubuntu-22.04,intel,2021.1.2,✓ -ubuntu-22.04,intel,2021.1,✓ -ubuntu-22.04,intel,2021.2,✓ -ubuntu-22.04,intel,2021.4,✓ -ubuntu-22.04,intel,2022.0,✓ -ubuntu-22.04,intel,2022.1,✓ -ubuntu-22.04,intel,2022.2.1,✓ -ubuntu-22.04,intel,2022.2,✓ -ubuntu-22.04,intel,2023.0,✓ -ubuntu-22.04,intel,2023.1,✓ -ubuntu-22.04,intel,2023.2,✓ -ubuntu-22.04,intel,2024.0,✓ -ubuntu-22.04,intel,2024.1,✓ -ubuntu-22.04,intel,2025.0,✓ -ubuntu-22.04,intel,2025.2,✓ -ubuntu-22.04,intel,2025.3,✓ -ubuntu-22.04,intel,2026.0,✓ -ubuntu-22.04,intel,2026.1,✓ -ubuntu-22.04,lfortran,0.57.0,✓ -ubuntu-22.04,lfortran,0.58.0,✓ -ubuntu-22.04,nvidia-hpc,23.11,✓ -ubuntu-22.04,nvidia-hpc,23.3,✓ -ubuntu-22.04,nvidia-hpc,23.5,✓ -ubuntu-22.04,nvidia-hpc,23.7,✓ -ubuntu-22.04,nvidia-hpc,23.9,✓ -ubuntu-22.04,nvidia-hpc,24.1,✓ -ubuntu-22.04,nvidia-hpc,24.3,✓ -ubuntu-22.04,nvidia-hpc,24.5,✓ -ubuntu-22.04,nvidia-hpc,25.1,✓ -ubuntu-22.04,nvidia-hpc,25.3,✓ -ubuntu-22.04,nvidia-hpc,25.5,✓ -ubuntu-22.04,nvidia-hpc,25.7,✓ -ubuntu-22.04,nvidia-hpc,25.9,✓ -ubuntu-22.04,nvidia-hpc,26.1,✓ -ubuntu-24.04,aocc,5.1.0,✓ -ubuntu-24.04,aocc,5.2.0,✓ -ubuntu-24.04,gcc,13,✓ -ubuntu-24.04,gcc,14,✓ -ubuntu-24.04,gcc,15,✓ -ubuntu-24.04,intel-classic,2021.1.2,✓ -ubuntu-24.04,intel-classic,2021.1,✓ -ubuntu-24.04,intel-classic,2021.10,✓ -ubuntu-24.04,intel-classic,2021.11,✓ -ubuntu-24.04,intel-classic,2021.12,✓ -ubuntu-24.04,intel-classic,2021.2,✓ -ubuntu-24.04,intel-classic,2021.3, -ubuntu-24.04,intel-classic,2021.4,✓ -ubuntu-24.04,intel-classic,2021.5,✓ -ubuntu-24.04,intel-classic,2021.6,✓ -ubuntu-24.04,intel-classic,2021.7.1,✓ -ubuntu-24.04,intel-classic,2021.8,✓ -ubuntu-24.04,intel-classic,2021.9,✓ -ubuntu-24.04,intel,2021.1.2,✓ -ubuntu-24.04,intel,2021.1,✓ -ubuntu-24.04,intel,2021.2,✓ -ubuntu-24.04,intel,2021.4,✓ -ubuntu-24.04,intel,2022.0,✓ -ubuntu-24.04,intel,2022.1,✓ -ubuntu-24.04,intel,2022.2.1,✓ -ubuntu-24.04,intel,2022.2,✓ -ubuntu-24.04,intel,2023.0,✓ -ubuntu-24.04,intel,2023.1,✓ -ubuntu-24.04,intel,2023.2,✓ -ubuntu-24.04,intel,2024.0,✓ -ubuntu-24.04,intel,2024.1,✓ -ubuntu-24.04,intel,2025.0,✓ -ubuntu-24.04,intel,2025.2,✓ -ubuntu-24.04,intel,2025.3,✓ -ubuntu-24.04,intel,2026.0,✓ -ubuntu-24.04,intel,2026.1,✓ -ubuntu-24.04,lfortran,0.57.0,✓ -ubuntu-24.04,lfortran,0.58.0,✓ -ubuntu-24.04,nvidia-hpc,23.1, -ubuntu-24.04,nvidia-hpc,23.11, -ubuntu-24.04,nvidia-hpc,23.3, -ubuntu-24.04,nvidia-hpc,23.5, -ubuntu-24.04,nvidia-hpc,23.7, -ubuntu-24.04,nvidia-hpc,23.9, -ubuntu-24.04,nvidia-hpc,24.1, -ubuntu-24.04,nvidia-hpc,24.3, -ubuntu-24.04,nvidia-hpc,24.5,✓ -ubuntu-24.04,nvidia-hpc,25.1,✓ -ubuntu-24.04,nvidia-hpc,25.3,✓ -ubuntu-24.04,nvidia-hpc,25.5,✓ -ubuntu-24.04,nvidia-hpc,25.7,✓ -ubuntu-24.04,nvidia-hpc,25.9,✓ -ubuntu-24.04,nvidia-hpc,26.1,✓ -windows-11-arm,gcc,13, -windows-11-arm,gcc,14, -windows-11-arm,gcc,15, -windows-2022,gcc,13,✓ -windows-2022,gcc,14,✓ -windows-2022,gcc,15,✓ -windows-2022,intel-classic,2021.1.2, -windows-2022,intel-classic,2021.1, -windows-2022,intel-classic,2021.10,✓ -windows-2022,intel-classic,2021.11,✓ -windows-2022,intel-classic,2021.12,✓ -windows-2022,intel-classic,2021.2, -windows-2022,intel-classic,2021.3, -windows-2022,intel-classic,2021.4, -windows-2022,intel-classic,2021.5, -windows-2022,intel-classic,2021.6,✓ -windows-2022,intel-classic,2021.7.1, -windows-2022,intel-classic,2021.8, -windows-2022,intel-classic,2021.9,✓ -windows-2022,intel,2021.1.2, -windows-2022,intel,2021.1, -windows-2022,intel,2021.2, -windows-2022,intel,2021.4, -windows-2022,intel,2022.0, -windows-2022,intel,2022.1,✓ -windows-2022,intel,2022.2.1, -windows-2022,intel,2022.2,✓ -windows-2022,intel,2023.0, -windows-2022,intel,2023.1,✓ -windows-2022,intel,2023.2,✓ -windows-2022,intel,2024.0,✓ -windows-2022,intel,2024.1,✓ -windows-2022,intel,2025.0,✓ -windows-2022,intel,2025.2,✓ -windows-2022,intel,2025.3,✓ -windows-2022,intel,2026.0,✓ -windows-2022,intel,2026.1,✓ -windows-2022,lfortran,0.57.0,✓ -windows-2022,lfortran,0.58.0,✓ -windows-2025-vs2026,gcc,13,✓ -windows-2025-vs2026,gcc,14,✓ -windows-2025-vs2026,gcc,15,✓ -windows-2025-vs2026,intel-classic,2021.1.2, -windows-2025-vs2026,intel-classic,2021.1, -windows-2025-vs2026,intel-classic,2021.2, -windows-2025-vs2026,intel-classic,2021.3, -windows-2025-vs2026,intel-classic,2021.4, -windows-2025-vs2026,intel-classic,2021.5, -windows-2025-vs2026,intel-classic,2021.7.1, -windows-2025-vs2026,intel-classic,2021.8, -windows-2025-vs2026,intel,2021.1.2, -windows-2025-vs2026,intel,2021.1, -windows-2025-vs2026,intel,2021.2, -windows-2025-vs2026,intel,2021.4, -windows-2025-vs2026,intel,2022.0, -windows-2025-vs2026,intel,2022.2.1, -windows-2025-vs2026,intel,2023.0, -windows-2025-vs2026,intel,2025.3,✓ -windows-2025-vs2026,intel,2026.0,✓ -windows-2025-vs2026,intel,2026.1,✓ -windows-2025-vs2026,lfortran,0.57.0,✓ -windows-2025-vs2026,lfortran,0.58.0,✓ -windows-2025,gcc,13,✓ -windows-2025,gcc,14,✓ -windows-2025,gcc,15,✓ -windows-2025,intel-classic,2021.1.2, -windows-2025,intel-classic,2021.1, -windows-2025,intel-classic,2021.2, -windows-2025,intel-classic,2021.3, -windows-2025,intel-classic,2021.4, -windows-2025,intel-classic,2021.5, -windows-2025,intel-classic,2021.7.1, -windows-2025,intel-classic,2021.8, -windows-2025,intel,2021.1.2, -windows-2025,intel,2021.1, -windows-2025,intel,2021.2, -windows-2025,intel,2021.4, -windows-2025,intel,2022.0, -windows-2025,intel,2022.2.1, -windows-2025,intel,2023.0, -windows-2025,intel,2025.3,✓ -windows-2025,intel,2026.0,✓ -windows-2025,intel,2026.1,✓ -windows-2025,lfortran,0.57.0,✓ -windows-2025,lfortran,0.58.0,✓ diff --git a/.github/compat/matrix.yml b/.github/compat/matrix.yml deleted file mode 100644 index 595a4955..00000000 --- a/.github/compat/matrix.yml +++ /dev/null @@ -1,197 +0,0 @@ -os: - - ubuntu-24.04 - - ubuntu-22.04 - - macos-26-intel - - macos-26 - - windows-2025 - - windows-2025-vs2026 - - windows-2022 - - windows-11-arm -toolchain: - - {compiler: gcc, version: 15} - - {compiler: gcc, version: 14} - - {compiler: gcc, version: 13} - - {compiler: intel, version: '2026.1'} - - {compiler: intel, version: '2026.0'} - - {compiler: intel, version: '2025.3'} - - {compiler: intel, version: '2025.2'} - - {compiler: intel, version: '2025.0'} - - {compiler: intel, version: '2024.1'} - - {compiler: intel, version: '2024.0'} - - {compiler: intel, version: '2023.2'} - - {compiler: intel, version: '2023.1'} - - {compiler: intel, version: '2023.0'} - - {compiler: intel, version: '2022.2.1'} - - {compiler: intel, version: '2022.2'} - - {compiler: intel, version: '2022.1'} - - {compiler: intel, version: '2022.0'} - - {compiler: intel, version: '2021.4'} - - {compiler: intel-classic, version: '2021.12'} - - {compiler: intel-classic, version: '2021.11'} - - {compiler: intel-classic, version: '2021.10'} - - {compiler: intel-classic, version: '2021.9'} - - {compiler: intel-classic, version: '2021.8'} - - {compiler: intel-classic, version: '2021.7.1'} - - {compiler: intel-classic, version: '2021.6'} - - {compiler: intel-classic, version: '2021.5'} - - {compiler: intel-classic, version: '2021.4'} - - {compiler: intel-classic, version: '2021.3'} - - {compiler: intel-classic, version: '2021.2'} - - {compiler: intel-classic, version: '2021.1.2'} - - {compiler: intel-classic, version: '2021.1'} - - {compiler: lfortran, version: '0.58.0'} - - {compiler: lfortran, version: '0.57.0'} - - {compiler: nvidia-hpc, version: '26.1'} - - {compiler: nvidia-hpc, version: '25.9'} - - {compiler: nvidia-hpc, version: '25.7'} - - {compiler: nvidia-hpc, version: '25.5'} - - {compiler: nvidia-hpc, version: '25.3'} - - {compiler: nvidia-hpc, version: '25.1'} - - {compiler: nvidia-hpc, version: '24.5'} - - {compiler: nvidia-hpc, version: '24.3'} - - {compiler: nvidia-hpc, version: '24.1'} - - {compiler: nvidia-hpc, version: '23.11'} - - {compiler: nvidia-hpc, version: '23.9'} - - {compiler: nvidia-hpc, version: '23.7'} - - {compiler: nvidia-hpc, version: '23.5'} - - {compiler: nvidia-hpc, version: '23.3'} - - {compiler: nvidia-hpc, version: '23.1'} - - {compiler: aocc, version: '5.2.0'} - - {compiler: aocc, version: '5.1.0'} -exclude: - # ifx not available on ARM macs - - os: macos-26 - toolchain: {compiler: intel} - # nvidia-hpc not available on mac - - os: macos-26-intel - toolchain: {compiler: nvidia-hpc} - - os: macos-26 - toolchain: {compiler: nvidia-hpc} - # nvidia-hpc not available on windows - - os: windows-2025 - toolchain: {compiler: nvidia-hpc} - - os: windows-2025-vs2026 - toolchain: {compiler: nvidia-hpc} - - os: windows-2022 - toolchain: {compiler: nvidia-hpc} - - os: windows-11-arm - toolchain: {compiler: nvidia-hpc} - # intel/intel-classic not available on windows-11-arm - - os: windows-11-arm - toolchain: {compiler: intel} - - os: windows-11-arm - toolchain: {compiler: intel-classic} - # lfortran not available on windows-11-arm (no conda/miniconda on that runner) - - os: windows-11-arm - toolchain: {compiler: lfortran} - # aocc supported only on Ubuntu 24.04 - - os: ubuntu-22.04 - toolchain: {compiler: aocc} - - os: macos-26-intel - toolchain: {compiler: aocc} - - os: macos-26 - toolchain: {compiler: aocc} - - os: windows-2025 - toolchain: {compiler: aocc} - - os: windows-2025-vs2026 - toolchain: {compiler: aocc} - - os: windows-2022 - toolchain: {compiler: aocc} - - os: windows-11-arm - toolchain: {compiler: aocc} -include: - # intel 2021.1/2021.1.2/2021.2: kept out of the main cross-product to stay - # under the 256-job limit, but still tested explicitly here - - {os: ubuntu-24.04, toolchain: {compiler: intel, version: '2021.2'}} - - {os: ubuntu-24.04, toolchain: {compiler: intel, version: '2021.1.2'}} - - {os: ubuntu-24.04, toolchain: {compiler: intel, version: '2021.1'}} - - {os: ubuntu-22.04, toolchain: {compiler: intel, version: '2021.2'}} - - {os: ubuntu-22.04, toolchain: {compiler: intel, version: '2021.1.2'}} - - {os: ubuntu-22.04, toolchain: {compiler: intel, version: '2021.1'}} - - {os: macos-26-intel, toolchain: {compiler: intel, version: '2021.2'}} - - {os: macos-26-intel, toolchain: {compiler: intel, version: '2021.1.2'}} - - {os: macos-26-intel, toolchain: {compiler: intel, version: '2021.1'}} - - {os: windows-2022, toolchain: {compiler: intel, version: '2021.2'}} - - {os: windows-2022, toolchain: {compiler: intel, version: '2021.1.2'}} - - {os: windows-2022, toolchain: {compiler: intel, version: '2021.1'}} - - {os: windows-2025, toolchain: {compiler: intel, version: '2021.2'}} - - {os: windows-2025, toolchain: {compiler: intel, version: '2021.1.2'}} - - {os: windows-2025, toolchain: {compiler: intel, version: '2021.1'}} - - {os: windows-2025-vs2026, toolchain: {compiler: intel, version: '2021.2'}} - - {os: windows-2025-vs2026, toolchain: {compiler: intel, version: '2021.1.2'}} - - {os: windows-2025-vs2026, toolchain: {compiler: intel, version: '2021.1'}} - # macos-14 (ARM): gcc, intel-classic full range, lfortran - - {os: macos-14, toolchain: {compiler: gcc, version: 15}} - - {os: macos-14, toolchain: {compiler: gcc, version: 14}} - - {os: macos-14, toolchain: {compiler: gcc, version: 13}} - - {os: macos-14, toolchain: {compiler: intel-classic, version: '2021.12'}} - - {os: macos-14, toolchain: {compiler: intel-classic, version: '2021.11'}} - - {os: macos-14, toolchain: {compiler: intel-classic, version: '2021.10'}} - - {os: macos-14, toolchain: {compiler: intel-classic, version: '2021.9'}} - - {os: macos-14, toolchain: {compiler: intel-classic, version: '2021.8'}} - - {os: macos-14, toolchain: {compiler: intel-classic, version: '2021.7.1'}} - - {os: macos-14, toolchain: {compiler: intel-classic, version: '2021.6'}} - - {os: macos-14, toolchain: {compiler: intel-classic, version: '2021.5'}} - - {os: macos-14, toolchain: {compiler: intel-classic, version: '2021.4'}} - - {os: macos-14, toolchain: {compiler: intel-classic, version: '2021.3'}} - - {os: macos-14, toolchain: {compiler: intel-classic, version: '2021.2'}} - - {os: macos-14, toolchain: {compiler: intel-classic, version: '2021.1.2'}} - - {os: macos-14, toolchain: {compiler: intel-classic, version: '2021.1'}} - - {os: macos-14, toolchain: {compiler: lfortran, version: '0.58.0'}} - - {os: macos-14, toolchain: {compiler: lfortran, version: '0.57.0'}} - # macos-15 (ARM): gcc, intel-classic full range, lfortran - - {os: macos-15, toolchain: {compiler: gcc, version: 15}} - - {os: macos-15, toolchain: {compiler: gcc, version: 14}} - - {os: macos-15, toolchain: {compiler: gcc, version: 13}} - - {os: macos-15, toolchain: {compiler: intel-classic, version: '2021.12'}} - - {os: macos-15, toolchain: {compiler: intel-classic, version: '2021.11'}} - - {os: macos-15, toolchain: {compiler: intel-classic, version: '2021.10'}} - - {os: macos-15, toolchain: {compiler: intel-classic, version: '2021.9'}} - - {os: macos-15, toolchain: {compiler: intel-classic, version: '2021.8'}} - - {os: macos-15, toolchain: {compiler: intel-classic, version: '2021.7.1'}} - - {os: macos-15, toolchain: {compiler: intel-classic, version: '2021.6'}} - - {os: macos-15, toolchain: {compiler: intel-classic, version: '2021.5'}} - - {os: macos-15, toolchain: {compiler: intel-classic, version: '2021.4'}} - - {os: macos-15, toolchain: {compiler: intel-classic, version: '2021.3'}} - - {os: macos-15, toolchain: {compiler: intel-classic, version: '2021.2'}} - - {os: macos-15, toolchain: {compiler: intel-classic, version: '2021.1.2'}} - - {os: macos-15, toolchain: {compiler: intel-classic, version: '2021.1'}} - - {os: macos-15, toolchain: {compiler: lfortran, version: '0.58.0'}} - - {os: macos-15, toolchain: {compiler: lfortran, version: '0.57.0'}} - # macos-15-intel: gcc, intel-classic full range, intel full range, lfortran - - {os: macos-15-intel, toolchain: {compiler: gcc, version: 15}} - - {os: macos-15-intel, toolchain: {compiler: gcc, version: 14}} - - {os: macos-15-intel, toolchain: {compiler: gcc, version: 13}} - - {os: macos-15-intel, toolchain: {compiler: intel-classic, version: '2021.12'}} - - {os: macos-15-intel, toolchain: {compiler: intel-classic, version: '2021.11'}} - - {os: macos-15-intel, toolchain: {compiler: intel-classic, version: '2021.10'}} - - {os: macos-15-intel, toolchain: {compiler: intel-classic, version: '2021.9'}} - - {os: macos-15-intel, toolchain: {compiler: intel-classic, version: '2021.8'}} - - {os: macos-15-intel, toolchain: {compiler: intel-classic, version: '2021.7.1'}} - - {os: macos-15-intel, toolchain: {compiler: intel-classic, version: '2021.6'}} - - {os: macos-15-intel, toolchain: {compiler: intel-classic, version: '2021.5'}} - - {os: macos-15-intel, toolchain: {compiler: intel-classic, version: '2021.4'}} - - {os: macos-15-intel, toolchain: {compiler: intel-classic, version: '2021.3'}} - - {os: macos-15-intel, toolchain: {compiler: intel-classic, version: '2021.2'}} - - {os: macos-15-intel, toolchain: {compiler: intel-classic, version: '2021.1.2'}} - - {os: macos-15-intel, toolchain: {compiler: intel-classic, version: '2021.1'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2026.1'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2026.0'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2025.3'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2025.2'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2025.0'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2024.1'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2024.0'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2023.2'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2023.1'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2023.0'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2022.2.1'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2022.2'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2022.1'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2022.0'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2021.4'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2021.2'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2021.1.2'}} - - {os: macos-15-intel, toolchain: {compiler: intel, version: '2021.1'}} - - {os: macos-15-intel, toolchain: {compiler: lfortran, version: '0.58.0'}} - - {os: macos-15-intel, toolchain: {compiler: lfortran, version: '0.57.0'}} diff --git a/.github/compat/matrix_json_from_csv.py b/.github/compat/matrix_json_from_csv.py deleted file mode 100644 index da1485c8..00000000 --- a/.github/compat/matrix_json_from_csv.py +++ /dev/null @@ -1,24 +0,0 @@ -import csv -import json -import sys -from pathlib import Path - -csv_path = Path(sys.argv[1]) # path to CSV file -jsn_path = Path(sys.argv[2]) # path to JSON file - -include = [] - -with open(csv_path, "r") as csv_file: - reader = csv.DictReader(csv_file) - for row in reader: - if not any(row["support"].strip()): - continue - include.append( - { - "os": row["runner"], - "toolchain": {"compiler": row["compiler"], "version": row["version"]}, - } - ) - -with open(jsn_path, "w") as jsn_file: - json.dump({"include": include}, jsn_file) diff --git a/.github/compat/update_compat_table.py b/.github/compat/update_compat_table.py deleted file mode 100644 index 3b1a07b6..00000000 --- a/.github/compat/update_compat_table.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Inserts Markdown compatibility tables -between tags in target Markdown file. -""" - -import re -import sys -from pathlib import Path - -compat_path = Path(sys.argv[1]) # path to Markdown table -update_path = Path(sys.argv[2]) # path to file to update - -assert compat_path.is_file() -assert update_path.is_file() - -with open(compat_path, "r") as compat: - table = "".join(compat.readlines()) - r = re.compile( - r".*", - re.DOTALL, - ) - ct = "{}".format("\n{}\n".format(table)) - readme = update_path.open().read() - update_path.open("w").write(r.sub(ct, readme)) diff --git a/.github/compat/update_latest_versions.py b/.github/compat/update_latest_versions.py deleted file mode 100644 index 1f780ba8..00000000 --- a/.github/compat/update_latest_versions.py +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env python3 -""" -Read compat.csv and write a lookup table -of the latest working versions to a .sh -file to source at run time. -""" - -import csv -import sys -from pathlib import Path - - -def version_sort_key(version: str): - """Convert version string to sortable tuple of integers.""" - # Handle versions like "13", "14", "2023.2", "0.57.0", etc. - parts = str(version).replace("'", "").split('.') - return tuple(int(p) if p.isdigit() else 0 for p in parts) - - -def parse_compat_csv(csv_path): - """ - Parse compat.csv and return latest working version for each OS/compiler combo. - - CSV format: - Row 1: compiler,gcc,gcc,gcc,intel,intel,... - Row 2: version,13,14,15,2021.1,2021.2,... - Row 3: runner,,,,,,... - Row 4+: os-name,✓,,✓,... - - Returns: dict[compiler][os] = latest_version - """ - with open(csv_path, 'r', encoding='utf-8') as f: - reader = csv.reader(f) - rows = list(reader) - - if len(rows) < 4: - raise ValueError(f"CSV must have at least 4 rows, got {len(rows)}") - - # Parse header rows - compiler_row = rows[0] - version_row = rows[1] - # Skip row 2 (runner header) - - # Build list of (compiler, version, column_index) - columns = [] - for i in range(1, len(compiler_row)): # Skip first column (label) - compiler = compiler_row[i] - version = version_row[i] - if compiler and version: # Skip empty columns - columns.append((compiler, version, i)) - - # Parse OS rows and find latest version for each OS/compiler - latest_versions = {} - - for row_idx in range(3, len(rows)): # Start after runner header - row = rows[row_idx] - if not row or not row[0]: # Skip empty rows - continue - - os_name = row[0] - - # Check each column for this OS - for compiler, version, col_idx in columns: - if col_idx < len(row) and row[col_idx] == '✓': - # This OS/compiler/version combo works - if compiler not in latest_versions: - latest_versions[compiler] = {} - - # Update if this is the first or highest version for this OS/compiler - if os_name not in latest_versions[compiler]: - latest_versions[compiler][os_name] = version - else: - current = latest_versions[compiler][os_name] - if version_sort_key(version) > version_sort_key(current): - latest_versions[compiler][os_name] = version - - return latest_versions - - -def to_lines(latest_versions) -> list[str]: - """ - Variable names follow pattern: LATEST__ - where both compiler and os have . and - replaced with _ - """ - - lines = [] - for compiler, os_versions in sorted(latest_versions.items()): - compiler_safe = compiler.replace('-', '_').replace('.', '_') - lines.append(f"# Latest supported {compiler} versions by runner") - for os_name, version in sorted(os_versions.items()): - os_safe = os_name.replace('-', '_').replace('.', '_') - var_name = f"LATEST_{compiler_safe}_{os_safe}" - lines.append(f'{var_name}="{version}"') - lines.append("") - - return lines - - -def main(): - if len(sys.argv) != 3: - print("Usage: update_latest_versions.py ") - sys.exit(1) - - csv_path = Path(sys.argv[1]) - output_path = Path(sys.argv[2]) - - if not csv_path.is_file(): - print(f"Error: {csv_path} not found") - sys.exit(1) - - latest_versions = parse_compat_csv(csv_path) - variable_lines = to_lines(latest_versions) - - with open(output_path, 'w') as f: - prefix_lines = [ - f"# Auto-generated from {csv_path.name}", - "# DO NOT EDIT MANUALLY - Run .github/compat/update_latest_versions.py to regenerate", - "", - ] - f.write("\n".join(prefix_lines + variable_lines)) - - print(f"Wrote {output_path}") - - -if __name__ == "__main__": - main() diff --git a/.github/compat/wide_compat_reports.py b/.github/compat/wide_compat_reports.py deleted file mode 100644 index 90e536e3..00000000 --- a/.github/compat/wide_compat_reports.py +++ /dev/null @@ -1,57 +0,0 @@ -""" -Converts compatibility reports from long to wide format -and makes a Markdown table from the wide format report. -""" - -from pathlib import Path -import pandas as pd -import sys - -ip = Path(sys.argv[1]) # input file path -op = Path(sys.argv[2]) # output file path -assert ip.is_file() -assert ip.suffix == ".csv" -assert op.suffix == ".csv" - -# read long CSV -df = pd.read_csv(ip) - -# pivot and sort by runners -df = pd.pivot_table( - df, - index="runner", - columns=["compiler", "version"], - values="support", - sort=True, - aggfunc="first", -).sort_values(by=["runner"]) - - -# group by compiler and sort by versions -grouped_versions = {} -for compiler in df.columns.get_level_values("compiler").unique(): - versions = df.loc[:, (compiler,)].columns.get_level_values("version").unique() - versions = sorted(versions, key=lambda version: tuple(map(int, version.split(".")))) - grouped_versions[compiler] = versions - -sorted_columns = [] -for compiler in grouped_versions: - sorted_columns.extend( - [(compiler, version) for version in grouped_versions[compiler]] - ) - -df = df[sorted_columns] - -# write wide CSV -df.to_csv(op) - -# write wide markdown table -with open(op.with_suffix(".md"), "w") as file: - file.write( - df.to_markdown() - .replace("nan", "") - .replace("(", "") - .replace(")", "") - .replace(",", "") - .replace("'", "") - ) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 82618bb3..8b2c4c07 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,7 +1,11 @@ -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - - package-ecosystem: "github-actions" - directory: "/" # Location of package manifests + - package-ecosystem: github-actions + directory: / schedule: - interval: "daily" + interval: weekly + + - package-ecosystem: npm + directory: / + schedule: + interval: weekly diff --git a/.github/workflows/ci-aocc.yml b/.github/workflows/ci-aocc.yml new file mode 100644 index 00000000..32bdabc8 --- /dev/null +++ b/.github/workflows/ci-aocc.yml @@ -0,0 +1,74 @@ +name: aocc + +on: + workflow_call: + +permissions: + actions: read + contents: read + +jobs: + integration-tests: + name: ${{ matrix.toolchain.version }} ${{ matrix.image }} ${{ + matrix.toolchain.msystem }} + if: github.actor != 'dependabot[bot]' + runs-on: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + image: [ubuntu-24.04, ubuntu-22.04] + + toolchain: + - { compiler: aocc, version: "5.2" } + - { compiler: aocc, version: "5.1" } + - { compiler: aocc, version: "5.0" } + - { compiler: aocc, version: "4.2" } + - { compiler: aocc, version: "4.1" } + + steps: + - uses: actions/checkout@v7 + + - name: Restore dist-verify + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-verify --dir dist-verify + env: + GH_TOKEN: ${{ github.token }} + + - name: Restore dist-tests + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-tests --dir dist-tests + env: + GH_TOKEN: ${{ github.token }} + + - name: Install ${{ matrix.toolchain.compiler }} ${{ matrix.toolchain.version }} + id: setup + uses: ./ + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + + - name: Verify installation + env: + OUTPUT_VERSION: ${{ steps.setup.outputs.version }} + OUTPUT_FC: ${{ steps.setup.outputs.fc }} + OUTPUT_CC: ${{ steps.setup.outputs.cc }} + OUTPUT_CXX: ${{ steps.setup.outputs.cxx }} + run: | + npm run verify-installation + echo "Fortran Compiler Setup:" + echo " Version: ${{ steps.setup.outputs.version }}" + echo " FC: ${{ steps.setup.outputs.fc }}" + echo " CC: ${{ steps.setup.outputs.cc }}" + echo " CXX: ${{ steps.setup.outputs.cxx }}" + + - name: Run Integration Tests + run: npm run integration-test + timeout-minutes: 5 diff --git a/.github/workflows/ci-armflang.yml b/.github/workflows/ci-armflang.yml new file mode 100644 index 00000000..b1b7b758 --- /dev/null +++ b/.github/workflows/ci-armflang.yml @@ -0,0 +1,65 @@ +name: armflang + +on: + workflow_call: + +permissions: + actions: read + contents: read + +jobs: + integration-tests: + name: ${{ matrix.toolchain.version }} ${{ matrix.image }} + if: github.actor != 'dependabot[bot]' + runs-on: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + image: [ubuntu-24.04-arm, ubuntu-22.04-arm] + toolchain: + - { compiler: armflang, version: "23.1" } + - { compiler: armflang, version: "22.1" } + - { compiler: armflang, version: "21.1" } + - { compiler: armflang, version: "20.1" } + + steps: + - uses: actions/checkout@v7 + + - name: Restore dist-verify + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-verify --dir dist-verify + env: + GH_TOKEN: ${{ github.token }} + + - name: Restore dist-tests + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-tests --dir dist-tests + env: + GH_TOKEN: ${{ github.token }} + + - name: Install ArmFlang ${{ matrix.toolchain.version }} + id: setup + uses: ./ + with: + compiler: armflang + version: ${{ matrix.toolchain.version }} + + - name: Verify installation + env: + OUTPUT_VERSION: ${{ steps.setup.outputs.version }} + OUTPUT_FC: ${{ steps.setup.outputs.fc }} + OUTPUT_CC: ${{ steps.setup.outputs.cc }} + OUTPUT_CXX: ${{ steps.setup.outputs.cxx }} + run: npm run verify-installation + + - name: Run integration tests + run: npm run integration-test + timeout-minutes: 5 diff --git a/.github/workflows/ci-flang.yml b/.github/workflows/ci-flang.yml new file mode 100644 index 00000000..c9332cc1 --- /dev/null +++ b/.github/workflows/ci-flang.yml @@ -0,0 +1,145 @@ +name: flang + +on: + workflow_call: + +permissions: + actions: read + contents: read + +jobs: + integration-tests: + name: ${{ matrix.toolchain.version }} ${{ matrix.image }} ${{ + matrix.toolchain.msystem }} + if: github.actor != 'dependabot[bot]' + runs-on: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + image: [ubuntu-24.04, ubuntu-22.04, ubuntu-24.04-arm, ubuntu-22.04-arm] + toolchain: + - { compiler: flang, version: "23" } + - { compiler: flang, version: "22" } + - { compiler: flang, version: "21" } + - { compiler: flang, version: "20" } + - { compiler: flang, version: "19" } + - { compiler: flang, version: "18" } + - { compiler: flang, version: "17" } + exclude: + - image: ubuntu-22.04 + toolchain: { compiler: flang, version: "23" } + - image: ubuntu-22.04-arm + toolchain: { compiler: flang, version: "23" } + include: + - image: ubuntu-22.04 + toolchain: { compiler: flang, version: "16" } + - image: macos-26 + toolchain: { compiler: flang } # only latest version available from brew + - image: macos-26-intel + toolchain: { compiler: flang } # only latest version available from brew + - image: macos-15 + toolchain: { compiler: flang } # only latest version available from brew + - image: macos-15-intel + toolchain: { compiler: flang } # only latest version available from brew + - image: macos-14 + toolchain: { compiler: flang } # only latest version available from brew + - image: macos-26 + toolchain: { compiler: flang, version: "21" } + - image: macos-26 + toolchain: { compiler: flang, version: "21.1.6" } # test that patch versions are accepted and resolved correctly + - image: macos-26 + toolchain: { compiler: flang, version: "20" } + - image: macos-26 + toolchain: { compiler: flang, version: "19" } + - image: macos-15 + toolchain: { compiler: flang, version: "21" } + - image: macos-15 + toolchain: { compiler: flang, version: "20" } + - image: macos-15 + toolchain: { compiler: flang, version: "19" } + - image: macos-26-intel + toolchain: { compiler: flang, version: "19" } + - image: macos-15-intel + toolchain: { compiler: flang, version: "19" } + - image: windows-2025 + toolchain: { compiler: flang, version: "23" } + - image: windows-2025 + toolchain: { compiler: flang, version: "23.1.0" } # test that patch versions are accepted and resolved correctly + - image: windows-2025 + toolchain: { compiler: flang, version: "22" } + - image: windows-2022 + toolchain: { compiler: flang, version: "22" } + - image: windows-11-arm + toolchain: { compiler: flang, version: "23" } + - image: windows-11-arm + toolchain: { compiler: flang, version: "22" } + - image: windows-11-arm + toolchain: { compiler: flang, version: "21" } + - image: windows-11-arm + toolchain: { compiler: flang, version: "20" } + - image: windows-2025 + toolchain: { compiler: flang, msystem: ucrt64 } # only latest version available from Pacman + - image: windows-2022 + toolchain: { compiler: flang, msystem: ucrt64 } # only latest version available from Pacman + - image: windows-2025 + toolchain: { compiler: flang, msystem: clang64 } # only latest version available from Pacman + - image: windows-2022 + toolchain: { compiler: flang, msystem: clang64 } # only latest version available from Pacman + steps: + - uses: actions/checkout@v7 + + - name: Restore dist-verify + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-verify --dir dist-verify + env: + GH_TOKEN: ${{ github.token }} + + - name: Restore dist-tests + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-tests --dir dist-tests + env: + GH_TOKEN: ${{ github.token }} + + - name: Install ${{ matrix.toolchain.compiler }} ${{ matrix.toolchain.version }} + id: setup + uses: ./ + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + msystem: ${{ matrix.toolchain.msystem }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify installation + shell: bash + env: + OUTPUT_VERSION: ${{ steps.setup.outputs.version }} + OUTPUT_FC: ${{ steps.setup.outputs.fc }} + OUTPUT_CC: ${{ steps.setup.outputs.cc }} + OUTPUT_CXX: ${{ steps.setup.outputs.cxx }} + run: | + npm run verify-installation + echo "Fortran Compiler Setup:" + echo " Version: ${{ steps.setup.outputs.version }}" + echo " FC: ${{ steps.setup.outputs.fc }}" + echo " CC: ${{ steps.setup.outputs.cc }}" + echo " CXX: ${{ steps.setup.outputs.cxx }}" + + - name: Verify compiler invocation under pwsh/cmd (Windows only) + if: runner.os == 'Windows' + uses: ./.github/actions/verify-windows-shells + with: + fc: ${{ steps.setup.outputs.fc }} + cc: ${{ steps.setup.outputs.cc }} + cxx: ${{ steps.setup.outputs.cxx }} + - name: Run Integration Tests + run: npm run integration-test + timeout-minutes: 5 diff --git a/.github/workflows/ci-gfortran.yml b/.github/workflows/ci-gfortran.yml new file mode 100644 index 00000000..840a574a --- /dev/null +++ b/.github/workflows/ci-gfortran.yml @@ -0,0 +1,107 @@ +name: gfortran + +on: + workflow_call: + +permissions: + actions: read + contents: read + +jobs: + integration-tests: + name: ${{ matrix.toolchain.version }} ${{ matrix.image }} ${{ + matrix.toolchain.msystem }} + if: github.actor != 'dependabot[bot]' + runs-on: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + image: + [ + ubuntu-24.04, + ubuntu-22.04, + ubuntu-24.04-arm, + ubuntu-22.04-arm, + macos-26, + macos-26-intel, + macos-15, + macos-15-intel, + macos-14, + windows-2025, + windows-2022, + ] + toolchain: + - { compiler: gfortran, version: "16" } + - { compiler: gfortran, version: "15" } + - { compiler: gfortran, version: "14" } + - { compiler: gfortran, version: "13" } + - { compiler: gfortran, version: "12" } + - { compiler: gfortran, version: "11" } + include: + - image: ubuntu-latest # no compiler and version specified, defaults to the latest gfortran version + - image: ubuntu-26.04 # no compiler and version specified, defaults to the latest gfortran version + - image: macos-latest # no compiler and version specified, defaults to the latest gfortran version + - image: windows-latest # no compiler and version specified, defaults to the latest gfortran version + - image: ubuntu-24.04 + toolchain: { compiler: gcc } # test compatibility alias + - image: windows-2025 + toolchain: { compiler: gfortran, msystem: ucrt64 } # only latest version available from Pacman + - image: windows-2022 + toolchain: { compiler: gfortran, msystem: ucrt64 } # only latest version available from Pacman + + steps: + - uses: actions/checkout@v7 + + - name: Restore dist-verify + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-verify --dir dist-verify + env: + GH_TOKEN: ${{ github.token }} + + - name: Restore dist-tests + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-tests --dir dist-tests + env: + GH_TOKEN: ${{ github.token }} + + - name: Install ${{ matrix.toolchain.compiler }} ${{ matrix.toolchain.version }} + id: setup + uses: ./ + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + msystem: ${{ matrix.toolchain.msystem }} + timeout-minutes: 30 + + - name: Verify installation + env: + OUTPUT_VERSION: ${{ steps.setup.outputs.version }} + OUTPUT_FC: ${{ steps.setup.outputs.fc }} + OUTPUT_CC: ${{ steps.setup.outputs.cc }} + OUTPUT_CXX: ${{ steps.setup.outputs.cxx }} + run: | + npm run verify-installation + echo "Fortran Compiler Setup:" + echo " Version: ${{ steps.setup.outputs.version }}" + echo " FC: ${{ steps.setup.outputs.fc }}" + echo " CC: ${{ steps.setup.outputs.cc }}" + echo " CXX: ${{ steps.setup.outputs.cxx }}" + + - name: Verify compiler invocation under pwsh/cmd (Windows only) + if: runner.os == 'Windows' + uses: ./.github/actions/verify-windows-shells + with: + fc: ${{ steps.setup.outputs.fc }} + cc: ${{ steps.setup.outputs.cc }} + cxx: ${{ steps.setup.outputs.cxx }} + - name: Run Integration Tests + run: npm run integration-test + timeout-minutes: 5 diff --git a/.github/workflows/ci-ifort.yml b/.github/workflows/ci-ifort.yml new file mode 100644 index 00000000..5e3bc1ff --- /dev/null +++ b/.github/workflows/ci-ifort.yml @@ -0,0 +1,191 @@ +name: ifort + +on: + workflow_call: + +permissions: + actions: read + contents: read + +jobs: + integration-tests: + name: ${{ matrix.toolchain.version }} ${{ matrix.image }} ${{ + matrix.toolchain.msystem }} + if: github.actor != 'dependabot[bot]' + runs-on: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + image: [ubuntu-24.04, ubuntu-22.04] + toolchain: + - { compiler: ifort, version: "2021.13" } + - { compiler: ifort, version: "2021.12" } + - { compiler: ifort, version: "2021.11" } + - { compiler: ifort, version: "2021.10" } + - { compiler: ifort, version: "2021.9" } + - { compiler: ifort, version: "2021.8" } + - { compiler: ifort, version: "2021.7.1" } + - { compiler: ifort, version: "2021.7" } + - { compiler: ifort, version: "2021.6" } + - { compiler: ifort, version: "2021.5" } + - { compiler: ifort, version: "2021.4" } + - { compiler: ifort, version: "2021.3" } + - { compiler: ifort, version: "2021.2" } + - { compiler: ifort, version: "2021.1.2" } + - { compiler: ifort, version: "2021.1" } + include: + - image: macos-26-intel + toolchain: { compiler: intel-classic, version: "2021.10" } # test compatibility alias + - image: macos-26-intel + toolchain: { compiler: ifort, version: "2021.10" } + - image: macos-15-intel + toolchain: { compiler: ifort, version: "2021.10" } + - image: macos-26 + toolchain: { compiler: intel-classic, version: "2021.10" } # test compatibility alias + - image: macos-26 + toolchain: { compiler: ifort, version: "2021.10" } + - image: macos-15 + toolchain: { compiler: ifort, version: "2021.10" } + - image: macos-14 + toolchain: { compiler: ifort, version: "2021.10" } + - image: macos-26 + toolchain: { compiler: ifort, version: "2021.9" } + - image: macos-15 + toolchain: { compiler: ifort, version: "2021.9" } + - image: macos-26 + toolchain: { compiler: ifort, version: "2021.8" } + - image: macos-15 + toolchain: { compiler: ifort, version: "2021.8" } + - image: macos-26 + toolchain: { compiler: ifort, version: "2021.6" } + - image: macos-15 + toolchain: { compiler: ifort, version: "2021.6" } + - image: macos-26 + toolchain: { compiler: ifort, version: "2021.5" } + - image: macos-15 + toolchain: { compiler: ifort, version: "2021.5" } + - image: macos-26 + toolchain: { compiler: ifort, version: "2021.3" } + - image: macos-15 + toolchain: { compiler: ifort, version: "2021.3" } + - image: macos-26 + toolchain: { compiler: ifort, version: "2021.2" } + - image: macos-15 + toolchain: { compiler: ifort, version: "2021.2" } + - image: macos-26 + toolchain: { compiler: ifort, version: "2021.1" } + - image: macos-15 + toolchain: { compiler: ifort, version: "2021.1" } + - image: macos-26-intel + toolchain: { compiler: ifort, version: "2021.9" } + - image: macos-15-intel + toolchain: { compiler: ifort, version: "2021.9" } + - image: macos-26-intel + toolchain: { compiler: ifort, version: "2021.8" } + - image: macos-15-intel + toolchain: { compiler: ifort, version: "2021.8" } + - image: macos-26-intel + toolchain: { compiler: ifort, version: "2021.6" } + - image: macos-15-intel + toolchain: { compiler: ifort, version: "2021.6" } + - image: macos-26-intel + toolchain: { compiler: ifort, version: "2021.5" } + - image: macos-15-intel + toolchain: { compiler: ifort, version: "2021.5" } + - image: macos-26-intel + toolchain: { compiler: ifort, version: "2021.3" } + - image: macos-15-intel + toolchain: { compiler: ifort, version: "2021.3" } + - image: macos-26-intel + toolchain: { compiler: ifort, version: "2021.2" } + - image: macos-15-intel + toolchain: { compiler: ifort, version: "2021.2" } + - image: macos-26-intel + toolchain: { compiler: ifort, version: "2021.1" } + - image: macos-15-intel + toolchain: { compiler: ifort, version: "2021.1" } + - image: windows-2025 + toolchain: { compiler: ifort, version: "2021.13" } + - image: windows-2022 + toolchain: { compiler: ifort, version: "2021.13" } + - image: windows-2025 + toolchain: { compiler: ifort, version: "2021.12" } + - image: windows-2022 + toolchain: { compiler: ifort, version: "2021.12" } + - image: windows-2025 + toolchain: { compiler: ifort, version: "2021.11" } + - image: windows-2022 + toolchain: { compiler: ifort, version: "2021.11" } + - image: windows-2025 + toolchain: { compiler: ifort, version: "2021.10" } + - image: windows-2022 + toolchain: { compiler: ifort, version: "2021.10" } + - image: windows-2025 + toolchain: { compiler: ifort, version: "2021.9" } + - image: windows-2022 + toolchain: { compiler: ifort, version: "2021.9" } + - image: windows-2025 + toolchain: { compiler: ifort, version: "2021.7" } + - image: windows-2022 + toolchain: { compiler: ifort, version: "2021.7" } + - image: windows-2025 + toolchain: { compiler: ifort, version: "2021.6" } + - image: windows-2022 + toolchain: { compiler: ifort, version: "2021.6" } + + steps: + - uses: actions/checkout@v7 + + - name: Restore dist-verify + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-verify --dir dist-verify + env: + GH_TOKEN: ${{ github.token }} + + - name: Restore dist-tests + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-tests --dir dist-tests + env: + GH_TOKEN: ${{ github.token }} + + - name: Install ${{ matrix.toolchain.compiler }} ${{ matrix.toolchain.version }} + id: setup + uses: ./ + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + timeout-minutes: 45 + + - name: Verify installation + shell: bash + env: + OUTPUT_VERSION: ${{ steps.setup.outputs.version }} + OUTPUT_FC: ${{ steps.setup.outputs.fc }} + OUTPUT_CC: ${{ steps.setup.outputs.cc }} + OUTPUT_CXX: ${{ steps.setup.outputs.cxx }} + run: | + npm run verify-installation + echo "Fortran Compiler Setup:" + echo " Version: ${{ steps.setup.outputs.version }}" + echo " FC: ${{ steps.setup.outputs.fc }}" + echo " CC: ${{ steps.setup.outputs.cc }}" + echo " CXX: ${{ steps.setup.outputs.cxx }}" + + - name: Verify compiler invocation under pwsh/cmd (Windows only) + if: runner.os == 'Windows' + uses: ./.github/actions/verify-windows-shells + with: + fc: ${{ steps.setup.outputs.fc }} + cc: ${{ steps.setup.outputs.cc }} + cxx: ${{ steps.setup.outputs.cxx }} + - name: Run Integration Tests + run: npm run integration-test + timeout-minutes: 5 diff --git a/.github/workflows/ci-ifx.yml b/.github/workflows/ci-ifx.yml new file mode 100644 index 00000000..c3cf2734 --- /dev/null +++ b/.github/workflows/ci-ifx.yml @@ -0,0 +1,243 @@ +name: ifx + +on: + workflow_call: + +permissions: + actions: read + contents: read + +jobs: + integration-tests: + name: ${{ matrix.toolchain.version }} ${{ matrix.image }} ${{ + matrix.toolchain.msystem }} + if: github.actor != 'dependabot[bot]' + timeout-minutes: 60 + runs-on: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + image: [ubuntu-24.04, ubuntu-22.04] + toolchain: + - { compiler: ifx, version: "2026.1" } + - { compiler: ifx, version: "2026.0" } + - { compiler: ifx, version: "2025.3" } + - { compiler: ifx, version: "2025.2" } + - { compiler: ifx, version: "2025.1" } + - { compiler: ifx, version: "2025.0" } + - { compiler: ifx, version: "2024.2" } + - { compiler: ifx, version: "2024.1" } + - { compiler: ifx, version: "2024.0" } + - { compiler: ifx, version: "2023.2.4" } + - { compiler: ifx, version: "2023.2.3" } + - { compiler: ifx, version: "2023.2.2" } + - { compiler: ifx, version: "2023.2.1" } + - { compiler: ifx, version: "2023.2.0" } + - { compiler: ifx, version: "2023.1.0" } + - { compiler: ifx, version: "2023.0.0" } + - { compiler: ifx, version: "2022.2.1" } + - { compiler: ifx, version: "2022.2.0" } + - { compiler: ifx, version: "2022.1.0" } + - { compiler: ifx, version: "2022.0.2" } + - { compiler: ifx, version: "2022.0.1" } + - { compiler: ifx, version: "2021.4.0" } + - { compiler: ifx, version: "2021.3.0" } + - { compiler: ifx, version: "2021.2.0" } + - { compiler: ifx, version: "2021.1.2" } + - { compiler: ifx, version: "2021.1.1" } + include: + - image: windows-2022 + toolchain: { compiler: intel } # test compatibility alias + - image: ubuntu-24.04 + toolchain: { compiler: ifx, version: "2023.2" } # make sure that minor is resolved to latest patch + - image: windows-2025 + toolchain: { compiler: ifx, version: "2025.3" } # make sure that minor is resolved to latest patch + - image: windows-2025 + toolchain: { compiler: ifx, version: "2026.1.1" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2026.1.1" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2026.1.0" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2026.1.0" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2026.0.0" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2026.0.0" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2025.3.3" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2025.3.3" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2025.3.2" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2025.3.2" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2025.3.1" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2025.3.1" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2025.3.0" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2025.3.0" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2025.2.1" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2025.2.1" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2025.2.0" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2025.2.0" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2025.1.0" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2025.1.0" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2025.0.4" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2025.0.4" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2025.0.3" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2025.0.3" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2025.0.1" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2025.0.1" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2025.0.0" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2025.0.0" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2024.2.1" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2024.2.1" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2024.2.0" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2024.2.0" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2024.1.0" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2024.1.0" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2024.0.2" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2024.0.2" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2024.0.1" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2024.0.1" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2023.2.1" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2023.2.1" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2023.2.0" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2023.2.0" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2023.1.0" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2023.1.0" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2022.3.0" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2022.3.0" } + - image: windows-2025 + toolchain: { compiler: ifx, version: "2022.2.0" } + - image: windows-2022 + toolchain: { compiler: ifx, version: "2022.2.0" } + + steps: + - uses: actions/checkout@v7 + + - name: Restore dist-verify + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-verify --dir dist-verify + env: + GH_TOKEN: ${{ github.token }} + + - name: Restore dist-tests + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-tests --dir dist-tests + env: + GH_TOKEN: ${{ github.token }} + + - name: Install ${{ matrix.toolchain.compiler }} ${{ matrix.toolchain.version }} + id: setup + uses: ./ + timeout-minutes: 45 + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + + - name: Verify installation + shell: bash + env: + OUTPUT_VERSION: ${{ steps.setup.outputs.version }} + OUTPUT_FC: ${{ steps.setup.outputs.fc }} + OUTPUT_CC: ${{ steps.setup.outputs.cc }} + OUTPUT_CXX: ${{ steps.setup.outputs.cxx }} + run: | + npm run verify-installation + echo "Fortran Compiler Setup:" + echo " Version: ${{ steps.setup.outputs.version }}" + echo " FC: ${{ steps.setup.outputs.fc }}" + echo " CC: ${{ steps.setup.outputs.cc }}" + echo " CXX: ${{ steps.setup.outputs.cxx }}" + + - name: Verify compiler invocation under pwsh/cmd (Windows only) + if: runner.os == 'Windows' + uses: ./.github/actions/verify-windows-shells + with: + fc: ${{ steps.setup.outputs.fc }} + cc: ${{ steps.setup.outputs.cc }} + cxx: ${{ steps.setup.outputs.cxx }} + - name: Run Integration Tests + run: npm run integration-test + timeout-minutes: 5 + + negative-macos-fallback: + name: intel on macOS fails (no silent ifort fallback) + runs-on: macos-26-intel + steps: + - uses: actions/checkout@v7 + + - name: Install intel on macOS (must fail) + id: setup + uses: ./ + with: + compiler: intel + continue-on-error: true + timeout-minutes: 20 + + - name: Assert expected failure and no substituted outputs + if: always() + env: + OUTCOME: ${{ steps.setup.outcome }} + FC_OUT: ${{ steps.setup.outputs.fc }} + CC_OUT: ${{ steps.setup.outputs.cc }} + CXX_OUT: ${{ steps.setup.outputs.cxx }} + VERSION_OUT: ${{ steps.setup.outputs.version }} + run: | + echo "Asserting: outcome=$OUTCOME fc=$FC_OUT cc=$CC_OUT cxx=$CXX_OUT version=$VERSION_OUT" + if [ "$OUTCOME" != "failure" ]; then + echo "::error::Expected 'intel' on macOS to fail, but outcome=$OUTCOME" + exit 1 + fi + for value in "$FC_OUT" "$CC_OUT" "$CXX_OUT" "$VERSION_OUT"; do + if [ -n "$value" ]; then + echo "::error::Expected empty output after failure, got: $value" + exit 1 + fi + done + echo "intel on macOS failed as expected with no silently substituted compiler" diff --git a/.github/workflows/ci-lfortran.yml b/.github/workflows/ci-lfortran.yml new file mode 100644 index 00000000..3ed44b4a --- /dev/null +++ b/.github/workflows/ci-lfortran.yml @@ -0,0 +1,133 @@ +name: lfortran + +on: + workflow_call: + +permissions: + actions: read + contents: read + +jobs: + integration-tests: + name: ${{ matrix.toolchain.version }} ${{ matrix.image }} ${{ + matrix.toolchain.msystem }} + if: github.actor != 'dependabot[bot]' + runs-on: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + image: + [ + ubuntu-24.04, + ubuntu-22.04, + macos-26, + macos-26-intel, + macos-15, + macos-15-intel, + macos-14, + windows-2025, + windows-2022, + ] + toolchain: + - { compiler: lfortran, version: "0.67.0" } + - { compiler: lfortran, version: "0.66.0" } + - { compiler: lfortran, version: "0.65.0" } + - { compiler: lfortran, version: "0.64.0" } + - { compiler: lfortran, version: "0.63.0" } + - { compiler: lfortran, version: "0.62.0" } + - { compiler: lfortran, version: "0.61.0" } + - { compiler: lfortran, version: "0.60.0" } + - { compiler: lfortran, version: "0.59.0" } + - { compiler: lfortran, version: "0.58.0" } + - { compiler: lfortran, version: "0.57.0" } + include: + - image: ubuntu-24.04-arm + toolchain: { compiler: lfortran, version: "0.67.0" } + - image: ubuntu-22.04-arm + toolchain: { compiler: lfortran, version: "0.67.0" } + - image: ubuntu-24.04-arm + toolchain: { compiler: lfortran, version: "0.66.0" } + - image: ubuntu-22.04-arm + toolchain: { compiler: lfortran, version: "0.66.0" } + - image: ubuntu-24.04-arm + toolchain: { compiler: lfortran, version: "0.65.0" } + - image: ubuntu-22.04-arm + toolchain: { compiler: lfortran, version: "0.65.0" } + - image: ubuntu-24.04-arm + toolchain: { compiler: lfortran, version: "0.64.0" } + - image: ubuntu-22.04-arm + toolchain: { compiler: lfortran, version: "0.64.0" } + - image: windows-2025 + toolchain: { compiler: lfortran, msystem: ucrt64 } # only latest version available from Pacman + - image: windows-2022 + toolchain: { compiler: lfortran, msystem: ucrt64 } # only latest version available from Pacman + - image: windows-2025 + toolchain: { compiler: lfortran, msystem: clang64 } # only latest version available from Pacman + - image: windows-2022 + toolchain: { compiler: lfortran, msystem: clang64 } # only latest version available from Pacman + steps: + - uses: actions/checkout@v7 + + - name: Restore dist-verify + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-verify --dir dist-verify + env: + GH_TOKEN: ${{ github.token }} + + - name: Restore dist-tests + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-tests --dir dist-tests + env: + GH_TOKEN: ${{ github.token }} + + - name: Install ${{ matrix.toolchain.compiler }} ${{ matrix.toolchain.version }} + id: setup + uses: ./ + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + msystem: ${{ matrix.toolchain.msystem }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify installation + env: + OUTPUT_VERSION: ${{ steps.setup.outputs.version }} + OUTPUT_FC: ${{ steps.setup.outputs.fc }} + OUTPUT_CC: ${{ steps.setup.outputs.cc }} + OUTPUT_CXX: ${{ steps.setup.outputs.cxx }} + run: | + npm run verify-installation + echo "Fortran Compiler Setup:" + echo " Version: ${{ steps.setup.outputs.version }}" + echo " FC: ${{ steps.setup.outputs.fc }}" + echo " CC: ${{ steps.setup.outputs.cc }}" + echo " CXX: ${{ steps.setup.outputs.cxx }}" + + - name: Verify compiler invocation under pwsh/cmd (Windows only) + if: runner.os == 'Windows' + uses: ./.github/actions/verify-windows-shells + with: + fc: ${{ steps.setup.outputs.fc }} + cc: ${{ steps.setup.outputs.cc }} + cxx: ${{ steps.setup.outputs.cxx }} + + - name: Verify compile and link under bash (Windows native only) + if: runner.os == 'Windows' && matrix.toolchain.msystem == '' + shell: bash + run: | + printf 'program smoke\n print *, "linked from bash"\nend program smoke\n' > smoke.f90 + "$FC" smoke.f90 -o smoke.exe + ./smoke.exe + + - name: Run Integration Tests + run: npm run integration-test + timeout-minutes: 5 diff --git a/.github/workflows/ci-nvfortran.yml b/.github/workflows/ci-nvfortran.yml new file mode 100644 index 00000000..acde9fed --- /dev/null +++ b/.github/workflows/ci-nvfortran.yml @@ -0,0 +1,112 @@ +name: nvfortran + +on: + workflow_call: + +permissions: + actions: read + contents: read + +jobs: + integration-tests: + name: ${{ matrix.toolchain.version }} ${{ matrix.image }} ${{ + matrix.toolchain.msystem }} + if: github.actor != 'dependabot[bot]' + runs-on: ${{ matrix.image }} + strategy: + fail-fast: false + matrix: + image: [ubuntu-24.04, ubuntu-22.04, ubuntu-24.04-arm, ubuntu-22.04-arm] + toolchain: + - { compiler: nvfortran, version: "26.9" } + - { compiler: nvfortran, version: "26.5" } + - { compiler: nvfortran, version: "26.3" } + - { compiler: nvfortran, version: "26.1" } + - { compiler: nvfortran, version: "25.11" } + - { compiler: nvfortran, version: "25.9" } + - { compiler: nvfortran, version: "25.7" } + - { compiler: nvfortran, version: "25.5" } + - { compiler: nvfortran, version: "25.3" } + - { compiler: nvfortran, version: "25.1" } + - { compiler: nvfortran, version: "24.11" } + - { compiler: nvfortran, version: "24.9" } + - { compiler: nvfortran, version: "24.7" } + - { compiler: nvfortran, version: "24.5" } + - { compiler: nvfortran, version: "24.3" } + - { compiler: nvfortran, version: "24.1" } + - { compiler: nvfortran, version: "23.11" } + - { compiler: nvfortran, version: "23.9" } + - { compiler: nvfortran, version: "23.7" } + - { compiler: nvfortran, version: "23.5" } + - { compiler: nvfortran, version: "23.3" } + - { compiler: nvfortran, version: "23.1" } + - { compiler: nvfortran, version: "22.11" } + - { compiler: nvfortran, version: "22.9" } + - { compiler: nvfortran, version: "22.7" } + - { compiler: nvfortran, version: "22.5" } + - { compiler: nvfortran, version: "22.3" } + - { compiler: nvfortran, version: "22.2" } + - { compiler: nvfortran, version: "22.1" } + - { compiler: nvfortran, version: "21.11" } + - { compiler: nvfortran, version: "21.9" } + - { compiler: nvfortran, version: "21.7" } + - { compiler: nvfortran, version: "21.5" } + - { compiler: nvfortran, version: "21.3" } + - { compiler: nvfortran, version: "21.2" } + - { compiler: nvfortran, version: "21.1" } + - { compiler: nvfortran, version: "20.11" } + - { compiler: nvfortran, version: "20.9" } + - { compiler: nvfortran, version: "20.7" } + include: + - image: ubuntu-24.04-arm + toolchain: { compiler: nvidia-hpc } # test compatibility alias + + steps: + - uses: actions/checkout@v7 + + - name: Restore dist-verify + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-verify --dir dist-verify + env: + GH_TOKEN: ${{ github.token }} + + - name: Restore dist-tests + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-tests --dir dist-tests + env: + GH_TOKEN: ${{ github.token }} + + - name: Install ${{ matrix.toolchain.compiler }} ${{ matrix.toolchain.version }} + id: setup + uses: ./ + with: + compiler: ${{ matrix.toolchain.compiler }} + version: ${{ matrix.toolchain.version }} + cleanup-disk: true + timeout-minutes: 45 + + - name: Verify installation + env: + OUTPUT_VERSION: ${{ steps.setup.outputs.version }} + OUTPUT_FC: ${{ steps.setup.outputs.fc }} + OUTPUT_CC: ${{ steps.setup.outputs.cc }} + OUTPUT_CXX: ${{ steps.setup.outputs.cxx }} + run: | + npm run verify-installation + echo "Fortran Compiler Setup:" + echo " Version: ${{ steps.setup.outputs.version }}" + echo " FC: ${{ steps.setup.outputs.fc }}" + echo " CC: ${{ steps.setup.outputs.cc }}" + echo " CXX: ${{ steps.setup.outputs.cxx }}" + + - name: Run Integration Tests + run: npm run integration-test + timeout-minutes: 5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..ef7f71b3 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,346 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + schedule: + - cron: "0 0 * * 5" + workflow_dispatch: + +concurrency: + group: >- + ${{ github.workflow }}-${{ + (github.event_name == 'schedule' && github.run_id) || + github.event.pull_request.number || + github.ref }} + cancel-in-progress: true + +permissions: + actions: read + contents: read + +jobs: + validation: + if: >- + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name != github.repository || + (github.event.pull_request.head.ref != 'main' && github.event.pull_request.head.ref != 'develop') + uses: ./.github/workflows/validation.yml + + aocc: + needs: [validation] + uses: ./.github/workflows/ci-aocc.yml + + armflang: + needs: [validation] + uses: ./.github/workflows/ci-armflang.yml + + gfortran: + needs: [validation] + uses: ./.github/workflows/ci-gfortran.yml + + flang: + needs: [validation] + uses: ./.github/workflows/ci-flang.yml + + ifort: + needs: [validation] + uses: ./.github/workflows/ci-ifort.yml + + ifx: + needs: [validation] + uses: ./.github/workflows/ci-ifx.yml + + lfortran: + needs: [validation] + uses: ./.github/workflows/ci-lfortran.yml + + nvfortran: + needs: [validation] + uses: ./.github/workflows/ci-nvfortran.yml + + two-invocations: + name: sequential installations (${{ matrix.image }}, ${{ + matrix.first.compiler }} -> ${{ matrix.second.compiler }}) + needs: [validation] + runs-on: ${{ matrix.image }} + if: github.actor != 'dependabot[bot]' + strategy: + fail-fast: false + matrix: + include: + - image: ubuntu-latest + first: { compiler: gfortran, version: "14" } + second: { compiler: gfortran, version: "15" } + - image: windows-latest + first: { compiler: gfortran, version: "14" } + second: { compiler: gfortran, version: "15" } + - image: windows-latest + first: { compiler: gfortran, version: "14" } + second: { compiler: ifx } + - image: windows-latest + first: { compiler: ifx } + second: { compiler: gfortran, version: "14" } + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install Dependencies + run: npm ci + + - name: Restore dist-verify + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-verify --dir dist-verify + env: + GH_TOKEN: ${{ github.token }} + + - name: Restore dist-tests + uses: nick-fields/retry@v4 + with: + timeout_minutes: 5 + max_attempts: 3 + retry_wait_seconds: 10 + command: gh run download ${{ github.run_id }} --name generated-dist-tests --dir dist-tests + env: + GH_TOKEN: ${{ github.token }} + + - name: Install ${{ matrix.first.compiler }} ${{ matrix.first.version }} + id: first + uses: ./ + with: + compiler: ${{ matrix.first.compiler }} + version: ${{ matrix.first.version }} + timeout-minutes: 30 + + - name: Verify first installation env vars (baseline) + shell: bash + env: + FIRST_VERSION: ${{ steps.first.outputs.version }} + FIRST_FC: ${{ steps.first.outputs.fc }} + FIRST_CC: ${{ steps.first.outputs.cc }} + FIRST_CXX: ${{ steps.first.outputs.cxx }} + run: | + echo "First installation: version=$FIRST_VERSION FC=$FIRST_FC CC=$FIRST_CC CXX=$FIRST_CXX F77=$F77 F90=$F90" + + # Env vars must be set and match the step outputs + if [ "$FC" != "$FIRST_FC" ]; then + echo "::error::FC env var (\$FC='$FC') does not match first output ('$FIRST_FC')" + exit 1 + fi + if [ "$CC" != "$FIRST_CC" ]; then + echo "::error::CC env var (\$CC='$CC') does not match first output ('$FIRST_CC')" + exit 1 + fi + if [ "$CXX" != "$FIRST_CXX" ]; then + echo "::error::CXX env var (\$CXX='$CXX') does not match first output ('$FIRST_CXX')" + exit 1 + fi + # F77 and F90 aliases should also match FC + if [ "$F77" != "$FIRST_FC" ]; then + echo "::error::F77 env var (\$F77) does not match first FC ('$FIRST_FC')" + exit 1 + fi + if [ "$F90" != "$FIRST_FC" ]; then + echo "::error::F90 env var (\$F90) does not match first FC ('$FIRST_FC')" + exit 1 + fi + echo "First installation env vars: OK (FC, CC, CXX, F77, F90)" + + # PATH ordering: first compiler's bin dir must be present + # Windows paths are backslash-separated, Git Bash's $PATH is POSIX + to_posix_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -u "$1" + else + echo "$1" + fi + } + + # ifx/ifort on Windows report a bare name, so resolve it first + if ! resolved_first_fc=$(command -v "$FIRST_FC"); then + echo "::error::First compiler ($FIRST_FC) could not be resolved on PATH" + exit 1 + fi + first_bin=$(dirname "$(to_posix_path "$resolved_first_fc")") + path_first=$(echo "$PATH" | tr ':' '\n' | grep -n -F -x "$first_bin" | head -1 | cut -d: -f1 || true) + if [ -z "$path_first" ]; then + echo "::error::First compiler bin dir ($first_bin) not found on PATH" + exit 1 + fi + echo "First installation PATH: OK (bin at position $path_first)" + + - name: Install ${{ matrix.second.compiler }} ${{ matrix.second.version }} (different compiler/version) + id: second + uses: ./ + with: + compiler: ${{ matrix.second.compiler }} + version: ${{ matrix.second.version }} + timeout-minutes: 30 + + - name: Verify second installation took precedence + shell: bash + env: + FIRST_VERSION: ${{ steps.first.outputs.version }} + SECOND_VERSION: ${{ steps.second.outputs.version }} + FIRST_FC: ${{ steps.first.outputs.fc }} + SECOND_FC: ${{ steps.second.outputs.fc }} + FIRST_CC: ${{ steps.first.outputs.cc }} + SECOND_CC: ${{ steps.second.outputs.cc }} + FIRST_CXX: ${{ steps.first.outputs.cxx }} + SECOND_CXX: ${{ steps.second.outputs.cxx }} + run: | + echo "First installation: version=$FIRST_VERSION FC=$FIRST_FC CC=$FIRST_CC CXX=$FIRST_CXX F77=$F77 F90=$F90" + echo "Second installation: version=$SECOND_VERSION FC=$SECOND_FC CC=$SECOND_CC CXX=$SECOND_CXX F77=$F77 F90=$F90" + + if [ "$FIRST_VERSION" = "$SECOND_VERSION" ]; then + echo "::error::Two different versions resolved to the same version." + exit 1 + fi + + # 1. Env-var overwrite: $FC, $CC, $CXX, $F77, $F90 must point to the SECOND installation + if [ "$FC" != "$SECOND_FC" ]; then + echo "::error::FC env var (\$FC='$FC') does not match second output ('$SECOND_FC')" + exit 1 + fi + if [ "$CC" != "$SECOND_CC" ]; then + echo "::error::CC env var (\$CC='$CC') does not match second output ('$SECOND_CC')" + exit 1 + fi + if [ "$CXX" != "$SECOND_CXX" ]; then + echo "::error::CXX env var (\$CXX='$CXX') does not match second output ('$SECOND_CXX')" + exit 1 + fi + # F77 and F90 aliases should also point to the second installation + if [ "$F77" != "$SECOND_FC" ]; then + echo "::error::F77 env var (\$F77) does not match second FC ('$SECOND_FC')" + exit 1 + fi + if [ "$F90" != "$SECOND_FC" ]; then + echo "::error::F90 env var (\$F90) does not match second FC ('$SECOND_FC')" + exit 1 + fi + # Also verify they are NOT still pointing to the first installation + if [ "$FC" = "$FIRST_FC" ] || [ "$CC" = "$FIRST_CC" ] || [ "$CXX" = "$FIRST_CXX" ] || [ "$F77" = "$FIRST_FC" ] || [ "$F90" = "$FIRST_FC" ]; then + echo "::error::One or more env vars still point to the FIRST installation" + exit 1 + fi + echo "Env-var overwrite: OK (FC, CC, CXX, F77, F90 all replaced)" + + # 2. PATH ordering: second compiler's bin dir must precede first's. + to_posix_path() { + if command -v cygpath >/dev/null 2>&1; then + cygpath -u "$1" + else + echo "$1" + fi + } + + if ! resolved_second_fc=$(command -v "$SECOND_FC"); then + echo "::error::Second compiler ($SECOND_FC) could not be resolved on PATH" + exit 1 + fi + second_bin=$(dirname "$(to_posix_path "$resolved_second_fc")") + first_bin="" + if resolved_first_fc=$(command -v "$FIRST_FC" 2>/dev/null); then + first_bin=$(dirname "$(to_posix_path "$resolved_first_fc")") + fi + path_first="" + if [ -n "$first_bin" ]; then + path_first=$(echo "$PATH" | tr ':' '\n' | grep -n -F -x "$first_bin" | head -1 | cut -d: -f1 || true) + fi + path_second=$(echo "$PATH" | tr ':' '\n' | grep -n -F -x "$second_bin" | head -1 | cut -d: -f1 || true) + if [ -z "$path_second" ]; then + echo "::error::Second compiler bin dir ($second_bin) not found on PATH" + exit 1 + fi + if [ -n "$path_first" ] && [ "$path_first" -lt "$path_second" ]; then + echo "::error::First compiler bin ($first_bin) precedes second ($second_bin) on PATH" + exit 1 + fi + echo "PATH ordering: OK (second compiler precedes first, or first is no longer on PATH)" + + echo "Sequential installation test passed: second installation took precedence." + + - name: Run integration tests for final state + run: npm run integration-test + timeout-minutes: 5 + + canary-report: + name: Weekly Canary Report + needs: + [ + validation, + aocc, + armflang, + gfortran, + flang, + ifort, + ifx, + lfortran, + nvfortran, + two-invocations, + ] + if: always() && github.event_name == 'schedule' + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + steps: + - name: Create issue for failed integration jobs + uses: actions/github-script@v9 + env: + AOCC: ${{ needs.aocc.result }} + ARMFLANG: ${{ needs.armflang.result }} + GFORTRAN: ${{ needs.gfortran.result }} + FLANG: ${{ needs.flang.result }} + IFORT: ${{ needs.ifort.result }} + IFX: ${{ needs.ifx.result }} + LFORTRAN: ${{ needs.lfortran.result }} + NVFORTRAN: ${{ needs.nvfortran.result }} + TWO_INVOCATIONS: ${{ needs.two-invocations.result }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + with: + script: | + const jobs = { + 'AOCC': process.env.AOCC, + 'ARM Flang': process.env.ARMFLANG, + 'GFortran': process.env.GFORTRAN, + 'Flang': process.env.FLANG, + 'Ifort (Classic)': process.env.IFORT, + 'Ifx': process.env.IFX, + 'LFortran': process.env.LFORTRAN, + 'NVFORTRAN': process.env.NVFORTRAN, + 'Sequential installations': process.env.TWO_INVOCATIONS, + }; + const failed = Object.entries(jobs) + .filter(([, result]) => result === 'failure') + .map(([name]) => `- ${name}`); + if (failed.length === 0) { + console.log('All canary jobs passed.'); + return; + } + const runUrl = process.env.RUN_URL; + await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `Weekly Canary: ${failed.length} integration job(s) failed`, + body: [ + '## Weekly Canary Failure Report', + '', + 'The weekly CI canary run detected failures in the following integration jobs:', + '', + ...failed, + '', + 'These failures typically indicate upstream breakage — installer URLs, package repositories, Homebrew formulae, hosted runner images, or `latest` resolution may have changed without a code modification.', + '', + `[View the workflow run](${runUrl}) for details on the failed jobs.`, + ].join('\n'), + }); diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 0b3524d8..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,408 +0,0 @@ -name: Test -# Workflow has two modes: assert and report. -# Push and pull request run in assert mode. -# Schedule and dispatch run in report mode. -# -# In report mode, the test matrix is split across two jobs to stay under -# GitHub's 256-job-per-matrix limit: -# - 'test': cross-product of current runners x full toolchain list -# - 'test-include': explicit combinations from matrix.yml's include section -# (currently legacy macOS runners, but general-purpose: -# any runner/toolchain combo that doesn't fit cleanly in -# the cross-product belongs here) -# Adding runners or toolchains to the include section requires no workflow -# changes — test-include picks them up automatically. -on: - push: - paths-ignore: - - '**.md' - tags-ignore: - - v** - pull_request: - branches: - - main - - develop* - paths-ignore: - - '**.md' - schedule: - - cron: '0 0 * * 0' - workflow_dispatch: -jobs: - options: - name: Set options - runs-on: ubuntu-latest - outputs: - mode: ${{ steps.mode.outputs.mode }} - matrix: ${{ steps.matrix.outputs.matrix }} - include_matrix: ${{ steps.matrix.outputs.include_matrix }} - steps: - - name: Checkout repository - uses: actions/checkout@v7 - - - name: Set mode - id: mode - run: | - trigger=${{ github.event_name }} - if [[ "$trigger" == "schedule" ]] || [[ "$trigger" == "workflow_dispatch" ]]; then - echo "mode=report" >> "$GITHUB_OUTPUT" - else - echo "mode=assert" >> "$GITHUB_OUTPUT" - fi - - - name: Set matrix - working-directory: .github/compat - id: matrix - run: | - mode=${{ steps.mode.outputs.mode }} - if [[ "$mode" == "report" ]]; then - yq -o json 'del(.include)' matrix.yml > matrix.json - yq -o json '.include' matrix.yml | jq '{include: .}' > include_matrix.json - else - python matrix_json_from_csv.py "long_compat.csv" "matrix.json" - echo '{"include":[]}' > include_matrix.json - fi - matrix=$(cat matrix.json | jq -r tostring) - include_matrix=$(cat include_matrix.json | jq -r tostring) - echo "matrix=$matrix" >> "$GITHUB_OUTPUT" - echo "include_matrix=$include_matrix" >> "$GITHUB_OUTPUT" - - # debugging - - name: Show matrix - run: echo "${{ toJSON(fromJSON(steps.matrix.outputs.matrix)) }}" - - - name: Show include matrix - run: echo "${{ toJSON(fromJSON(steps.matrix.outputs.include_matrix)) }}" - - test: - name: Test - needs: options - runs-on: ${{ matrix.os }} - continue-on-error: ${{ needs.options.outputs.mode == 'report' }} - strategy: - fail-fast: false - matrix: ${{ fromJSON(needs.options.outputs.matrix) }} - steps: - - name: Checkout repository - uses: actions/checkout@v7 - - - name: Setup Fortran - id: setup-fortran - continue-on-error: ${{ needs.options.outputs.mode == 'report' }} - uses: ./ - with: - compiler: ${{ matrix.toolchain.compiler }} - version: ${{ matrix.toolchain.version }} - - # - name: Debug with tmate - # uses: mxschmitt/action-tmate@v3 - - - name: Test Fortran compiler - if: steps.setup-fortran.outcome == 'success' - uses: ./.github/actions/test-fc - with: - compiler: ${{ matrix.toolchain.compiler }} - version: ${{ matrix.toolchain.version }} - - - name: Test C compiler - continue-on-error: true - if: needs.options.outputs.mode == 'report' && steps.setup-fortran.outcome == 'success' - uses: ./.github/actions/test-cc - with: - compiler: ${{ matrix.toolchain.compiler }} - version: ${{ matrix.toolchain.version }} - - - name: Test C++ compiler - continue-on-error: true - if: needs.options.outputs.mode == 'report' && steps.setup-fortran.outcome == 'success' - uses: ./.github/actions/test-cxx - with: - compiler: ${{ matrix.toolchain.compiler }} - version: ${{ matrix.toolchain.version }} - - - name: Create compatibility report - if: needs.options.outputs.mode == 'report' - shell: bash - run: | - mkdir -p compat - support=$([ "${{ steps.setup-fortran.outcome }}" == "success" ] && echo "✓" || echo "") - prefix="${{ matrix.os }},${{ matrix.toolchain.compiler }},${{ matrix.toolchain.version }}" - report="compat/${prefix//,/_}.csv" - echo "$prefix,$support" >> "$report" - cat "$report" - - - name: Upload compatibility report - if: needs.options.outputs.mode == 'report' - uses: actions/upload-artifact@v7 - with: - name: compat-${{ matrix.os }}-${{ matrix.toolchain.compiler }}-${{ matrix.toolchain.version }} - path: compat/*.csv - - test-include: - name: Test (include matrix) - needs: options - if: needs.options.outputs.mode == 'report' - runs-on: ${{ matrix.os }} - continue-on-error: true - strategy: - fail-fast: false - matrix: ${{ fromJSON(needs.options.outputs.include_matrix) }} - steps: - - name: Checkout repository - uses: actions/checkout@v7 - - - name: Setup Fortran - id: setup-fortran - continue-on-error: true - uses: ./ - with: - compiler: ${{ matrix.toolchain.compiler }} - version: ${{ matrix.toolchain.version }} - - - name: Test Fortran compiler - if: steps.setup-fortran.outcome == 'success' - uses: ./.github/actions/test-fc - with: - compiler: ${{ matrix.toolchain.compiler }} - version: ${{ matrix.toolchain.version }} - - - name: Test C compiler - continue-on-error: true - if: steps.setup-fortran.outcome == 'success' - uses: ./.github/actions/test-cc - with: - compiler: ${{ matrix.toolchain.compiler }} - version: ${{ matrix.toolchain.version }} - - - name: Test C++ compiler - continue-on-error: true - if: steps.setup-fortran.outcome == 'success' - uses: ./.github/actions/test-cxx - with: - compiler: ${{ matrix.toolchain.compiler }} - version: ${{ matrix.toolchain.version }} - - - name: Create compatibility report - shell: bash - run: | - mkdir -p compat - support=$([ "${{ steps.setup-fortran.outcome }}" == "success" ] && echo "✓" || echo "") - prefix="${{ matrix.os }},${{ matrix.toolchain.compiler }},${{ matrix.toolchain.version }}" - report="compat/${prefix//,/_}.csv" - echo "$prefix,$support" >> "$report" - cat "$report" - - - name: Upload compatibility report - uses: actions/upload-artifact@v7 - with: - name: compat-${{ matrix.os }}-${{ matrix.toolchain.compiler }}-${{ matrix.toolchain.version }} - path: compat/*.csv - - test-latest: - name: Test version latest resolution - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-24.04 - compiler: gcc - - os: ubuntu-22.04 - compiler: gcc - - os: windows-2025 - compiler: gcc - - os: macos-26 - compiler: gcc - - os: macos-15 - compiler: gcc - - os: ubuntu-24.04 - compiler: lfortran - - os: ubuntu-24.04 - compiler: intel - - os: ubuntu-22.04 - compiler: nvidia-hpc - - os: ubuntu-24.04 - compiler: nvidia-hpc - - os: ubuntu-24.04 - compiler: aocc - steps: - - name: Checkout repository - uses: actions/checkout@v7 - - - name: Setup Fortran with version latest - id: setup-fortran - uses: ./ - with: - compiler: ${{ matrix.compiler }} - version: latest - - - name: Verify installed version matches expected - shell: bash - run: | - source ./setup-fortran.sh - - # Detect platform (same as main.sh) - platform=$(uname -s | tr '[:upper:]' '[:lower:]') - - # Get actual installed version - case "${{ matrix.compiler }}" in - gcc) - actual_version=$(gfortran --version | head -n1 | grep -oE '[0-9]+\.[0-9]+' | head -n1 | cut -d. -f1) - ;; - lfortran) - actual_version=$(lfortran --version | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1) - ;; - aocc) - actual_version=$($FC --version 2>&1 | grep -oE 'AOCC[_ ][0-9]+\.[0-9]+\.[0-9]+' | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1) - ;; - intel|intel-classic) - actual_version=$($FC --version | head -n1 | grep -woE '[0-9]+\.[0-9]+\.[0-9]+' | head -n1) - ;; - nvidia-hpc) - actual_version=$(nvfortran --version 2>&1 | awk '/nvfortran/ {print $2}' | cut -d'-' -f1) - ;; - *) - echo "Unknown compiler: ${{ matrix.compiler }}" - exit 1 - ;; - esac - echo "Actual version: $actual_version" - - # For compilers that resolve 'latest' to a pinned version via latest-versions.sh, - # verify the installed version matches. For compilers that install an unversioned - # package (intel, intel-classic, nvidia-hpc), just confirm the compiler is functional. - case "${{ matrix.compiler }}" in - gcc|lfortran|aocc) - expected_version=$(resolve_latest_version "${{ matrix.compiler }}" "$platform") - echo "Expected version: $expected_version" - if [[ "$actual_version" == "$expected_version" ]]; then - echo "Version matches expected" - else - echo "Version mismatch: expected $expected_version, got $actual_version" - exit 1 - fi - ;; - *) - if [[ -n "$actual_version" ]]; then - echo "Compiler is functional (version: $actual_version)" - else - echo "Could not determine installed version" - exit 1 - fi - ;; - esac - - - name: Test compiler works (compile hello world) - shell: bash - run: | - cat > hello.f90 < .github/compat/long_compat.csv - cat compat/*.csv >> .github/compat/long_compat.csv - - - name: Make wide reports - working-directory: .github/compat - run: | - python wide_compat_reports.py "long_compat.csv" "compat.csv" - cat compat.md - - - name: Upload artifacts - uses: actions/upload-artifact@v7 - with: - name: compat - path: | - .github/compat/long_compat.csv - .github/compat/compat.csv - .github/compat/compat.md - - - name: Check for changes - working-directory: .github/compat - id: diff - run: | - if ! [ -f compat.csv ]; then - echo "diff=false" >> $GITHUB_OUTPUT - exit 0 - fi - - diff=$(git diff compat.csv) - if [[ $diff == "" ]]; then - echo "No changes found" - echo "diff=false" >> $GITHUB_OUTPUT - else - echo "Changes found:" - echo "$diff" - echo "diff=true" >> $GITHUB_OUTPUT - fi - - - name: Update README - if: ${{ steps.diff.outputs.diff == 'true' }} - run: python .github/compat/update_compat_table.py ".github/compat/compat.md" "README.md" - - - name: Generate latest versions lookup table - if: ${{ steps.diff.outputs.diff == 'true' }} - run: python .github/compat/update_latest_versions.py ".github/compat/compat.csv" ".github/compat/latest-versions.sh" - - - name: Print README diff - if: ${{ steps.diff.outputs.diff == 'true' }} - run: git diff README.md - - - name: Print latest-versions.sh diff - if: ${{ steps.diff.outputs.diff == 'true' }} - run: git diff .github/compat/latest-versions.sh - - - name: Create pull request - if: ${{ steps.diff.outputs.diff == 'true' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - now=$(date +'%Y-%m-%dT%H-%M-%S') - updated_branch="compat_$now" - default_branch="${{ github.event.repository.default_branch }}" - - git switch -c "$updated_branch" - git add .github/compat/ README.md - git commit -m "Update compatibility matrix" - git push -u origin "$updated_branch" - gh pr create -B "$default_branch" -H "$updated_branch" --title "Update compatibility matrix" --body-file .github/compat/compat.md diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml new file mode 100644 index 00000000..d2f32b11 --- /dev/null +++ b/.github/workflows/validation.yml @@ -0,0 +1,137 @@ +name: validation + +on: + workflow_call: + +permissions: + contents: read + +jobs: + format-lint: + name: format, lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install Dependencies + run: npm ci + + - name: Check Formatting + run: npm run format:check + + - name: Lint + run: npm run lint + + - name: Audit production dependencies + run: npm audit --omit=dev + + unit-tests: + name: unit tests (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Run unit tests + run: npm run test + + build: + name: build (${{ matrix.os }}) + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + needs: [format-lint, unit-tests] + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v7 + with: + node-version-file: .nvmrc + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build dist + run: npm run build + + - name: Smoke test bundled action + run: npm run test:bundle + + - name: Save dist + uses: actions/upload-artifact@v7 + with: + name: generated-dist-${{ matrix.os }} + path: dist + + - name: Build dist-verify + if: matrix.os == 'ubuntu-latest' + run: npm run build:verify-installation + + - name: Save dist-verify + if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v7 + with: + name: generated-dist-verify + path: dist-verify + + - name: Build dist-tests + if: matrix.os == 'ubuntu-latest' + run: npm run build:integration-test + + - name: Save dist-tests + if: matrix.os == 'ubuntu-latest' + uses: actions/upload-artifact@v7 + with: + name: generated-dist-tests + path: dist-tests + + verify-dist: + name: verify dist + needs: [build] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - name: Download ubuntu dist + uses: actions/download-artifact@v7 + with: + name: generated-dist-ubuntu-latest + path: /tmp/dist-ubuntu + + - name: Download windows dist + uses: actions/download-artifact@v7 + with: + name: generated-dist-windows-latest + path: /tmp/dist-windows + + - name: Verify committed dist matches ubuntu build + run: | + if ! diff -r dist /tmp/dist-ubuntu > /tmp/dist-stale.diff; then + echo "::error::dist/ is out of date. Please run 'npm run build' and commit the changes. If your dist/ is already up to date, but you are using another operating system, please check for a dependency or bundler change that caused the difference." + cat /tmp/dist-stale.diff + exit 1 + fi + + - name: Verify committed dist matches windows build + run: | + if ! diff -r dist /tmp/dist-windows > /tmp/dist-stale-windows.diff; then + echo "::error::dist/ is out of date. Please run 'npm run build' and commit the changes. If your dist/ is already up to date, but you are using another operating system, please check for a dependency or bundler change that caused the difference." + cat /tmp/dist-stale-windows.diff + exit 1 + fi diff --git a/.gitignore b/.gitignore index 182f1004..ebb88c2a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,15 @@ -.github/compat/*.json -.github/compat/*.md -**/.DS_Store -venv/ +node_modules/ +lib/ +dist-tests +dist-verify +test_build +coverage/ +*.tsbuildinfo +*.mod +*.out +.vscode/settings.json +.idea/ +.env +.env.* +.DS_Store +Thumbs.db diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..a45fd52c --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/LICENSE b/LICENSE index d6456956..f506b7bd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,3 @@ - Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -187,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2026 fortran-lang Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index c77f6bbb..aa6c4441 100644 --- a/README.md +++ b/README.md @@ -1,147 +1,384 @@ -# Setup Fortran +[![GitHub Marketplace](https://img.shields.io/badge/Marketplace-setup--fortran-blueviolet?logo=github)](https://github.com/marketplace/actions/setup-fortran-compilers) +[![GitHub release](https://img.shields.io/github/v/release/fortran-lang/setup-fortran?color=orange)](https://github.com/fortran-lang/setup-fortran/releases) +[![CI](https://github.com/fortran-lang/setup-fortran/actions/workflows/ci.yml/badge.svg)](https://github.com/fortran-lang/setup-fortran/actions/workflows/ci.yml) +[![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) -[![Test](https://github.com/fortran-lang/setup-fortran/actions/workflows/test.yml/badge.svg)](https://github.com/fortran-lang/setup-fortran/actions/workflows/test.yml) -[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) -[![GitHub tag](https://img.shields.io/github/tag/fortran-lang/setup-fortran.svg)](https://github.com/fortran-lang/setup-fortran/tags/latest) +# setup-fortran +Set up Fortran compiler toolchains for GitHub Actions. +Supports GNU, Intel, LLVM, NVIDIA, AMD, Arm, and LFortran compilers across +Linux, macOS, and Windows. -Set up a Fortran compiler on Ubuntu, macOS and Windows runners. +## Migration Guide - - +Migrating from `v1` requires only a few changes: -- [Usage](#usage) -- [Options](#options) -- [Outputs](#outputs) -- [Environment variables](#environment-variables) -- [Runner compatibility](#runner-compatibility) -- [License](#license) +- With Intel compilers, this action sets up the oneAPI environment. Do not re-run environment setup scripts such as `setvars.bat` or `vcvarsall.bat` after this action runs. Doing so can cause the `PATH` variable to exceed character limits on Windows. +- `ifx` configurations on macOS were previously redirected to `ifort`. This behavior is no longer supported; `ifx` on macOS will fail. Remove these configurations from your workflow matrices. +- In 2022, oneAPI release numbers differed from compiler version numbers. For example, oneAPI `2022.1` on Windows installed `ifx` version `2022.2.0`. Compiler versions are now used consistently, so `2022.1` is no longer listed as a supported version. Use `2022.2.0` instead. +- Intel's Fortran-only installer for Windows does not come with a working `icx` backend, so the preferred C/C++ companion compiler has been changed to `cl`. If you have hardcoded `CC=icx`/`CXX=icx` anywhere in your own workflow, remove these settings and let the action provide the appropriate values. +- The legacy compiler names `gcc`, `intel`, `intel-classic`, and `nvidia-hpc` remain supported as compatibility aliases. Migrating to the canonical names is recommended. - +## Usage +```yaml +- uses: fortran-lang/setup-fortran@v2 + with: + compiler: + version: +``` -## Usage +## Inputs + +| Input | Description | Default | +| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | +| `compiler` | Compiler to install (`gfortran`, `ifx`, `ifort`, `nvfortran`, `aocc`, `lfortran`, `flang`, `armflang`) | `gfortran` | +| `version` | Compiler version to install. Enclose the version string in quotation marks (e.g. `"2026.1.1"` or `"0.64.0"`) | `latest` | +| `msystem` | Windows toolchain environment (`native`, `ucrt64`, `clang64`) | `native` | +| `cleanup-disk` | Free up disk space by removing large pre-installed toolkits during `nvfortran` setup (`true`, `false`) | `false` | +| `update-environment` | Whether to export toolchain environment variables (`FC`, `CC`, `CXX`, `FPM_*`, `F77`, `F90`, `FORTRAN_COMPILER`) into the runner environment (`true`, `false`) | `true` | + +For compatibility with `fortran-lang/setup-fortran`, the legacy names `gcc`, +`intel`, `intel-classic`, and `nvidia-hpc` are accepted as aliases for +`gfortran`, `ifx`, `ifort`, and `nvfortran`, respectively. Aliases do not +change based on platform; for example, `intel` always resolves to `ifx`. +Use of the canonical names is recommended. + +## Compiler Support + +### `gfortran` + +| Version | ubuntu-24.04 | ubuntu-22.04 | ubuntu-24.04-arm | ubuntu-22.04-arm | macos-26 | macos-26-intel | macos-15 | macos-15-intel | macos-14 | windows-2025 | windows-2022 | windows-2025 (ucrt64) | windows-2022 (ucrt64) | +| ------- | ------------ | ------------ | ---------------- | ---------------- | -------- | -------------- | -------- | -------------- | -------- | ------------ | ------------ | --------------------- | --------------------- | +| latest | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| 16 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | +| 15 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | +| 14 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | +| 13 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | +| 12 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | +| 11 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | + +--- + +### `ifx` + +| Version | ubuntu-24.04 | ubuntu-22.04 | windows-2025 | windows-2022 | +| -------- | ------------ | ------------ | ------------ | ------------ | +| latest | ✓ | ✓ | ✓ | ✓ | +| 2026.1.1 | | | ✓ | ✓ | +| 2026.1.0 | | | ✓ | ✓ | +| 2026.1 | ✓ | ✓ | ✓ | ✓ | +| 2026.0 | ✓ | ✓ | ✓ | ✓ | +| 2025.3.3 | | | ✓ | ✓ | +| 2025.3.2 | | | ✓ | ✓ | +| 2025.3.1 | | | ✓ | ✓ | +| 2025.3.0 | | | ✓ | ✓ | +| 2025.3 | ✓ | ✓ | ✓ | ✓ | +| 2025.2.1 | | | ✓ | ✓ | +| 2025.2.0 | | | ✓ | ✓ | +| 2025.2 | ✓ | ✓ | ✓ | ✓ | +| 2025.1.0 | | | ✓ | ✓ | +| 2025.1 | ✓ | ✓ | ✓ | ✓ | +| 2025.0.0 | | | ✓ | ✓ | +| 2025.0 | ✓ | ✓ | ✓ | ✓ | +| 2024.2.1 | | | ✓ | ✓ | +| 2024.2.0 | | | ✓ | ✓ | +| 2024.2 | ✓ | ✓ | ✓ | ✓ | +| 2024.1.0 | | | ✓ | ✓ | +| 2024.1 | ✓ | ✓ | ✓ | ✓ | +| 2024.0.2 | | | ✓ | ✓ | +| 2024.0.1 | | | ✓ | ✓ | +| 2024.0 | ✓ | ✓ | ✓ | ✓ | +| 2023.2.4 | ✓ | ✓ | | | +| 2023.2.3 | ✓ | ✓ | | | +| 2023.2.2 | ✓ | ✓ | | | +| 2023.2.1 | ✓ | ✓ | ✓ | ✓ | +| 2023.2.0 | ✓ | ✓ | ✓ | ✓ | +| 2023.2 | ✓ | ✓ | ✓ | ✓ | +| 2023.1.0 | ✓ | ✓ | ✓ | ✓ | +| 2023.1 | ✓ | ✓ | ✓ | ✓ | +| 2023.0.0 | ✓ | ✓ | | | +| 2023.0 | ✓ | ✓ | | | +| 2022.3.0 | | | ✓ | ✓ | +| 2022.3 | | | ✓ | ✓ | +| 2022.2.1 | ✓ | ✓ | | | +| 2022.2.0 | ✓ | ✓ | ✓ | ✓ | +| 2022.2 | ✓ | ✓ | ✓ | ✓ | +| 2022.1.0 | ✓ | ✓ | | | +| 2022.1 | ✓ | ✓ | | | +| 2022.0.2 | ✓ | ✓ | | | +| 2022.0.1 | ✓ | ✓ | | | +| 2022.0 | ✓ | ✓ | | | +| 2021.4.0 | ✓ | ✓ | | | +| 2021.4 | ✓ | ✓ | | | +| 2021.3.0 | ✓ | ✓ | | | +| 2021.3 | ✓ | ✓ | | | +| 2021.2.0 | ✓ | ✓ | | | +| 2021.2 | ✓ | ✓ | | | +| 2021.1.2 | ✓ | ✓ | | | +| 2021.1.1 | ✓ | ✓ | | | +| 2021.1 | ✓ | ✓ | | | + + +Notes: + +- In 2022, `ifx` compiler version numbers differed from oneAPI release numbers. Specify the compiler version here, not the oneAPI release number. + +- `ifx` versions ≤2023.1.0 can take significantly longer to install on `windows-2022`. If possible, use `2023.2.0` or newer. + +--- + +### `ifort` + +| Version | ubuntu-24.04 | ubuntu-22.04 | macos-26 | macos-15 | macos-14 | macos-26-intel | macos-15-intel | windows-2025 | windows-2022 | +| -------- | ------------ | ------------ | -------- | -------- | -------- | -------------- | -------------- | ------------ | ------------ | +| latest | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| 2021.13 | ✓ | ✓ | | | | | | ✓ | ✓ | +| 2021.12 | ✓ | ✓ | | | | | | ✓ | ✓ | +| 2021.11 | ✓ | ✓ | | | | | | ✓ | ✓ | +| 2021.10 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| 2021.9 | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | +| 2021.8 | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | | | +| 2021.7.1 | ✓ | ✓ | | | | | | | | +| 2021.7 | ✓ | ✓ | | | | | | ✓ | ✓ | +| 2021.6 | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | +| 2021.5 | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | | | +| 2021.4 | ✓ | ✓ | | | | | | | | +| 2021.3 | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | | | +| 2021.2 | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | | | +| 2021.1.2 | ✓ | ✓ | | | | | | | | +| 2021.1 | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | | | + +Notes: + +- Intel discontinued `ifort` with the oneAPI 2025 release and recommends `ifx` instead. No new versions will be added; the versions listed above remain installable. + +- Intel ships `ifort` for macOS as x86_64 binaries. On ARM64 runners they run under Rosetta 2, which the action verifies (and installs if missing) before installation. To keep companion C/C++ objects linkable there, the action exports `CFLAGS`, `CXXFLAGS`, and `LDFLAGS` with `-arch x86_64`. + +- `ifort` versions ≤2021.9 can take significantly longer to install on `windows-2022`. If possible, use `2021.10` or newer. + +--- + +### `nvfortran` + +| Version | ubuntu-24.04 | ubuntu-22.04 | ubuntu-24.04-arm | ubuntu-22.04-arm | +| ------- | ------------ | ------------ | ---------------- | ---------------- | +| latest | ✓ | ✓ | ✓ | ✓ | +| 26.9 | ✓ | ✓ | ✓ | ✓ | +| 26.5 | ✓ | ✓ | ✓ | ✓ | +| 26.3 | ✓ | ✓ | ✓ | ✓ | +| 26.1 | ✓ | ✓ | ✓ | ✓ | +| 25.11 | ✓ | ✓ | ✓ | ✓ | +| 25.9 | ✓ | ✓ | ✓ | ✓ | +| 25.7 | ✓ | ✓ | ✓ | ✓ | +| 25.5 | ✓ | ✓ | ✓ | ✓ | +| 25.3 | ✓ | ✓ | ✓ | ✓ | +| 25.1 | ✓ | ✓ | ✓ | ✓ | +| 24.11 | ✓ | ✓ | ✓ | ✓ | +| 24.9 | ✓ | ✓ | ✓ | ✓ | +| 24.7 | ✓ | ✓ | ✓ | ✓ | +| 24.5 | ✓ | ✓ | ✓ | ✓ | +| 24.3 | ✓ | ✓ | ✓ | ✓ | +| 24.1 | ✓ | ✓ | ✓ | ✓ | +| 23.11 | ✓ | ✓ | ✓ | ✓ | +| 23.9 | ✓ | ✓ | ✓ | ✓ | +| 23.7 | ✓ | ✓ | ✓ | ✓ | +| 23.5 | ✓ | ✓ | ✓ | ✓ | +| 23.3 | ✓ | ✓ | ✓ | ✓ | +| 23.1 | ✓ | ✓ | ✓ | ✓ | +| 22.11 | ✓ | ✓ | ✓ | ✓ | +| 22.9 | ✓ | ✓ | ✓ | ✓ | +| 22.7 | ✓ | ✓ | ✓ | ✓ | +| 22.5 | ✓ | ✓ | ✓ | ✓ | +| 22.3 | ✓ | ✓ | ✓ | ✓ | +| 22.2 | ✓ | ✓ | ✓ | ✓ | +| 22.1 | ✓ | ✓ | ✓ | ✓ | +| 21.11 | ✓ | ✓ | ✓ | ✓ | +| 21.9 | ✓ | ✓ | ✓ | ✓ | +| 21.7 | ✓ | ✓ | ✓ | ✓ | +| 21.5 | ✓ | ✓ | ✓ | ✓ | +| 21.3 | ✓ | ✓ | ✓ | ✓ | +| 21.2 | ✓ | ✓ | ✓ | ✓ | +| 21.1 | ✓ | ✓ | ✓ | ✓ | +| 20.11 | ✓ | ✓ | ✓ | ✓ | +| 20.9 | ✓ | ✓ | ✓ | ✓ | +| 20.7 | ✓ | ✓ | ✓ | ✓ | + +--- + +### `aocc` + +| Version | ubuntu-24.04 | ubuntu-22.04 | +| ------- | ------------ | ------------ | +| latest | ✓ | ✓ | +| 5.2 | ✓ | ✓ | +| 5.1 | ✓ | ✓ | +| 5.0 | ✓ | ✓ | +| 4.2 | ✓ | ✓ | +| 4.1 | ✓ | ✓ | + +--- + +### `lfortran` + +| Version | ubuntu-24.04 | ubuntu-22.04 | ubuntu-24.04-arm | ubuntu-22.04-arm | macos-26 | macos-26-intel | macos-15 | macos-15-intel | macos-14 | windows-2025 | windows-2022 | windows-2025 (ucrt64) | windows-2022 (ucrt64) | windows-2025 (clang64) | windows-2022 (clang64) | +| ------- | ------------ | ------------ | ---------------- | ---------------- | -------- | -------------- | -------- | -------------- | -------- | ------------ | ------------ | --------------------- | --------------------- | ---------------------- | ---------------------- | +| latest | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| 0.67.0 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | +| 0.66.0 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | +| 0.65.0 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | +| 0.64.0 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | +| 0.63.0 | ✓ | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | +| 0.62.0 | ✓ | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | +| 0.61.0 | ✓ | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | +| 0.60.0 | ✓ | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | +| 0.59.0 | ✓ | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | +| 0.58.0 | ✓ | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | +| 0.57.0 | ✓ | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | + +--- + +### `flang` (LLVM Flang) + +| Version | ubuntu-24.04 | ubuntu-22.04 | ubuntu-24.04-arm | ubuntu-22.04-arm | macos-26 | macos-26-intel | macos-15 | macos-15-intel | macos-14 | windows-2025 | windows-2022 | windows-11-arm | windows-2025 (ucrt64) | windows-2022 (ucrt64) | windows-2025 (clang64) | windows-2022 (clang64) | +| ------- | ------------ | ------------ | ---------------- | ---------------- | -------- | -------------- | -------- | -------------- | -------- | ------------ | ------------ | -------------- | --------------------- | --------------------- | ---------------------- | ---------------------- | +| latest | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| 23 | ✓ | | ✓ | | | | | | | ✓ | ✓ | ✓ | | | | | +| 22 | ✓ | ✓ | ✓ | ✓ | | | | | | ✓ | ✓ | ✓ | | | | | +| 21 | ✓ | ✓ | ✓ | ✓ | ✓ | | ✓ | | | | | ✓ | | | | | +| 20 | ✓ | ✓ | ✓ | ✓ | ✓ | | ✓ | | | | | ✓ | | | | | +| 19 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | | | | | +| 18 | ✓ | ✓ | ✓ | ✓ | | | | | | | | | | | | | +| 17 | ✓ | ✓ | ✓ | ✓ | | | | | | | | | | | | | +| 16 | | ✓ | | | | | | | | | | | | | | | + +> Specific patch versions (e.g. `21.1.6`) are supported on macOS and native +> Windows runners and validated against available GitHub releases. Patch +> versions are not individually tested. + +--- + +### `armflang` (Arm Toolchain for Linux) + +| Version | ubuntu-24.04-arm | ubuntu-22.04-arm | +| ------- | ---------------- | ---------------- | +| latest | ✓ | ✓ | +| 23.1 | ✓ | ✓ | +| 22.1 | ✓ | ✓ | +| 21.1 | ✓ | ✓ | +| 20.1 | ✓ | ✓ | + +--- + +## Examples + +### Basic Usage ```yaml +steps: + - uses: actions/checkout@v7 + - uses: fortran-lang/setup-fortran@v2 + - run: ${{ env.FC }} hello.f90 +``` + +If omitted, `compiler` defaults to `gfortran` and `version` to the latest +supported version for the platform. + +### Specific Version + +```yaml +- uses: fortran-lang/setup-fortran@v2 + with: + compiler: lfortran + version: "0.67.0" +``` + +### Matrix Build + +```yaml +strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + toolchain: + - { compiler: gfortran, version: "15" } + - { compiler: ifx, version: "2026.1" } + - { compiler: lfortran, version: "0.67.0" } + exclude: + - os: macos-latest + toolchain: { compiler: ifx, version: "2026.1" } + include: + - os: windows-11-arm + toolchain: { compiler: flang, version: "22" } jobs: test: runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - toolchain: - - {compiler: gcc, version: 'latest'} - - {compiler: intel, version: '2025.0'} - - {compiler: intel-classic, version: '2021.10'} - - {compiler: nvidia-hpc, version: 'latest'} - - {compiler: lfortran, version: '0.45.0'} - - {compiler: aocc, version: '5.1.0'} - exclude: - - os: macos-latest - toolchain: {compiler: intel} - - os: macos-latest - toolchain: {compiler: intel-classic} - - os: macos-latest - toolchain: {compiler: nvidia-hpc} - - os: windows-latest - toolchain: {compiler: nvidia-hpc} - - os: macos-latest - toolchain: {compiler: aocc} - - os: windows-latest - toolchain: {compiler: aocc} - steps: - - uses: fortran-lang/setup-fortran@v1 - id: setup-fortran + - uses: actions/checkout@v7 + - uses: fortran-lang/setup-fortran@v2 with: compiler: ${{ matrix.toolchain.compiler }} version: ${{ matrix.toolchain.version }} - - - run: | - ${{ env.FC }} ... # environment vars FC, CC, and CXX are set - ${{ steps.setup-fortran.outputs.fc }} ... # outputs work too + - run: ${{ env.FC }} hello.f90 ``` +### Windows with MSYS2 -## Options - -- *compiler*: Compiler toolchain to setup, available options are - - *gcc* for `gfortran` - - *intel* for `ifx` - - *intel-classic* for `ifort` - - *lfortran* for `lfortran` - - *nvidia-hpc* for `nvfortran` - - *aocc* for `flang` (AMD Optimizing C/C++ and Fortran Compilers) -- *version*: Version of the compiler toolchain. See [runner compatibility](#runner-compatibility) charts below. -- *update-environment*: Whether to set environment variables (`FC` etc, defaults true). - +```yaml +- uses: fortran-lang/setup-fortran@v2 + with: + compiler: lfortran + msystem: ucrt64 +``` ## Outputs -The action sets the following outputs: - -- `fc`: Fortran compiler executable, e.g. `gfortran` -- `cc`: C compiler executable, e.g. `gcc` -- `cxx`: C++ compiler executable, e.g. `g++` - -C/C++ compilers of the same toolchain/version are provided where available. If a standalone Fortran compiler is selected, the action will attempt to configure compatible C/C++ compilers (typically GCC, or MSVC on Windows), but this is not guaranteed — use at your own risk. +| Output | Description | +| --------- | ------------------------------------------ | +| `version` | Resolved version of the installed compiler | +| `fc` | Command or path to the Fortran compiler | +| `cc` | Command or path to the C compiler | +| `cxx` | Command or path to the C++ compiler | +## Environment Variables -## Environment variables +| Variable | Description | +| --------- | -------------------------------------------------------- | +| `FC` | Command or path to the Fortran compiler | +| `CC` | Command or path to the C compiler | +| `CXX` | Command or path to the C++ compiler | +| `FPM_FC` | Command or path to the Fortran compiler for fpm | +| `FPM_CC` | Command or path to the C compiler for fpm | +| `FPM_CXX` | Command or path to the C++ compiler for fpm | +| `F77` | Command or path to the Fortran compiler (alias for `FC`) | +| `F90` | Command or path to the Fortran compiler (alias for `FC`) | -The same values are also set as environment variables: +## Development -- `FC` -- `CC` -- `CXX` +Requires Node.js 24 or newer (`.nvmrc` pins the version CI uses). -Corresponding FPM environment variables are also set: +Install dependencies with `npm ci`. -- `FPM_FC` -- `FPM_CC` -- `FPM_CXX` +Run `npm run all` to format and lint the source, run unit tests, bundle the +action into `dist`, and run the smoke tests. -These are made available to subsequent workflow steps via the [`GITHUB_ENV` environment file mechanism](https://docs.github.com/en/actions/learn-github-actions/environment-variables#passing-values-between-steps-and-jobs-in-a-workflow). +Commit changes to `dist` together with the source changes, as GitHub Actions +executes the bundled code from this directory. +## Maintenance -## Runner compatibility +### Adding a New Version -Toolchain support varies across GitHub-hosted runner images. +1. Add the version to `SUPPORTED_VERSIONS` in the respective installation script (latest first). +2. Add the version to the CI matrix for that compiler. +3. Update the [README](README.md). +4. Run `npm run all` — it will fail if the version isn't tested or documented. - -| runner | aocc 5.1.0 | aocc 5.2.0 | gcc 13 | gcc 14 | gcc 15 | intel 2021.1 | intel 2021.1.2 | intel 2021.2 | intel 2021.4 | intel 2022.0 | intel 2022.1 | intel 2022.2 | intel 2022.2.1 | intel 2023.0 | intel 2023.1 | intel 2023.2 | intel 2024.0 | intel 2024.1 | intel 2025.0 | intel 2025.2 | intel 2025.3 | intel 2026.0 | intel 2026.1 | intel-classic 2021.1 | intel-classic 2021.1.2 | intel-classic 2021.2 | intel-classic 2021.3 | intel-classic 2021.4 | intel-classic 2021.5 | intel-classic 2021.6 | intel-classic 2021.7.1 | intel-classic 2021.8 | intel-classic 2021.9 | intel-classic 2021.10 | intel-classic 2021.11 | intel-classic 2021.12 | lfortran 0.57.0 | lfortran 0.58.0 | nvidia-hpc 23.3 | nvidia-hpc 23.5 | nvidia-hpc 23.7 | nvidia-hpc 23.9 | nvidia-hpc 23.11 | nvidia-hpc 24.1 | nvidia-hpc 24.3 | nvidia-hpc 24.5 | nvidia-hpc 25.1 | nvidia-hpc 25.3 | nvidia-hpc 25.5 | nvidia-hpc 25.7 | nvidia-hpc 25.9 | nvidia-hpc 26.1 | -|:--------------------|:--------------------|:--------------------|:----------------|:----------------|:----------------|:----------------------|:------------------------|:----------------------|:----------------------|:----------------------|:----------------------|:----------------------|:------------------------|:----------------------|:----------------------|:----------------------|:----------------------|:----------------------|:----------------------|:----------------------|:----------------------|:----------------------|:----------------------|:------------------------------|:--------------------------------|:------------------------------|:------------------------------|:------------------------------|:------------------------------|:------------------------------|:--------------------------------|:------------------------------|:------------------------------|:-------------------------------|:-------------------------------|:-------------------------------|:-------------------------|:-------------------------|:-------------------------|:-------------------------|:-------------------------|:-------------------------|:--------------------------|:-------------------------|:-------------------------|:-------------------------|:-------------------------|:-------------------------|:-------------------------|:-------------------------|:-------------------------|:-------------------------| -| macos-14 | | | ✓ | ✓ | ✓ | | | | | | | | | | | | | | | | | | | ✓ | | ✓ | ✓ | | ✓ | ✓ | | ✓ | ✓ | ✓ | | | ✓ | ✓ | | | | | | | | | | | | | | | -| macos-15 | | | ✓ | ✓ | ✓ | | | | | | | | | | | | | | | | | | | ✓ | | ✓ | ✓ | | ✓ | ✓ | | ✓ | ✓ | ✓ | | | ✓ | ✓ | | | | | | | | | | | | | | | -| macos-15-intel | | | ✓ | ✓ | ✓ | ✓ | | ✓ | | | | | | | | | | | | | | | | ✓ | | ✓ | ✓ | | ✓ | ✓ | | ✓ | ✓ | ✓ | | | ✓ | ✓ | | | | | | | | | | | | | | | -| macos-26 | | | ✓ | ✓ | ✓ | | | | | | | | | | | | | | | | | | | ✓ | | ✓ | ✓ | | ✓ | ✓ | | ✓ | ✓ | ✓ | | | ✓ | ✓ | | | | | | | | | | | | | | | -| macos-26-intel | | | ✓ | ✓ | ✓ | ✓ | | ✓ | | | | | | | | | | | | | | | | ✓ | | ✓ | ✓ | | ✓ | ✓ | | ✓ | ✓ | ✓ | | | ✓ | ✓ | | | | | | | | | | | | | | | -| ubuntu-22.04 | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| ubuntu-24.04 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | -| windows-2022 | | | ✓ | ✓ | ✓ | | | | | | ✓ | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | | | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | | | | | | | | | | | | | -| windows-2025 | | | ✓ | ✓ | ✓ | | | | | | | | | | | | | | | | ✓ | ✓ | ✓ | | | | | | | | | | | | | | ✓ | ✓ | | | | | | | | | | | | | | | -| windows-2025-vs2026 | | | ✓ | ✓ | ✓ | | | | | | | | | | | | | | | | ✓ | ✓ | ✓ | | | | | | | | | | | | | | ✓ | ✓ | | | | | | | | | | | | | | | - +Compilers have separate installation scripts for each platform they support. -**Note:** Intel's `ifx` compiler is not supported on macOS, so the `intel` option redirects to `intel-classic` (`ifort`). +## Reporting Issues -**Note:** LFortran is currently only discoverable by name with `bash` on Windows, see [here for context](https://github.com/fortran-lang/setup-fortran/pull/57#issuecomment-2021605094). +Report bugs and feature requests in the [issue tracker](https://github.com/fortran-lang/setup-fortran/issues). ## License -Licensed under the Apache License, Version 2.0 (the “License”); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an *“as is” basis*, -*without warranties or conditions of any kind*, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - -Unless you explicitly state otherwise, any contribution intentionally -submitted for inclusion in this project by you, as defined in the -Apache-2.0 license, shall be licensed as above, without any additional -terms or conditions. +[Apache-2.0](LICENSE) diff --git a/__mocks__/@actions/cache.ts b/__mocks__/@actions/cache.ts new file mode 100644 index 00000000..12d39336 --- /dev/null +++ b/__mocks__/@actions/cache.ts @@ -0,0 +1,4 @@ +/// +export const isFeatureAvailable = jest.fn(); +export const restoreCache = jest.fn(); +export const saveCache = jest.fn(); diff --git a/__mocks__/@actions/core.ts b/__mocks__/@actions/core.ts new file mode 100644 index 00000000..51d1394f --- /dev/null +++ b/__mocks__/@actions/core.ts @@ -0,0 +1,22 @@ +/// +export const exportVariable = jest.fn() +export const setSecret = jest.fn() +export const addPath = jest.fn() +export const getInput = jest.fn() +export const getMultilineInput = jest.fn() +export const getBooleanInput = jest.fn() +export const setOutput = jest.fn() +export const setCommandEcho = jest.fn() +export const setFailed = jest.fn() +export const isDebug = jest.fn() +export const debug = jest.fn() +export const error = jest.fn() +export const warning = jest.fn() +export const notice = jest.fn() +export const info = jest.fn() +export const startGroup = jest.fn() +export const endGroup = jest.fn() +export const group = jest.fn() +export const saveState = jest.fn() +export const getState = jest.fn() +export const getIDToken = jest.fn() diff --git a/__mocks__/@actions/exec.ts b/__mocks__/@actions/exec.ts new file mode 100644 index 00000000..7c249534 --- /dev/null +++ b/__mocks__/@actions/exec.ts @@ -0,0 +1,3 @@ +/// +export const exec = jest.fn() +export const getExecOutput = jest.fn() diff --git a/__mocks__/@actions/tool-cache.ts b/__mocks__/@actions/tool-cache.ts new file mode 100644 index 00000000..6133a988 --- /dev/null +++ b/__mocks__/@actions/tool-cache.ts @@ -0,0 +1,14 @@ +/// +export const find = jest.fn(); +export const findAllVersions = jest.fn(); +export const downloadTool = jest.fn(); +export const cacheDir = jest.fn(); +export const cacheFile = jest.fn(); +export const extractZip = jest.fn(); +export const extractTar = jest.fn(); +export const extract7z = jest.fn(); +export const extractXar = jest.fn(); +export const getManifestFromRepo = jest.fn(); +export const findFromManifest = jest.fn(); +export const isExplicitVersion = jest.fn(); +export const evaluateVersions = jest.fn(); diff --git a/__tests__/action_metadata.test.ts b/__tests__/action_metadata.test.ts new file mode 100644 index 00000000..f7e1d570 --- /dev/null +++ b/__tests__/action_metadata.test.ts @@ -0,0 +1,133 @@ +import * as fs from "fs"; +import * as path from "path"; +import yaml from "js-yaml"; +import type { Inputs } from "../src/types"; + +const repoRoot = path.resolve(__dirname, ".."); + +interface ActionYml { + name: string; + description: string; + author?: string; + inputs: Record< + string, + { description?: string; required?: boolean; default?: string } + >; + outputs: Record; + runs: { using: string; main: string }; +} + +function loadActionYml(): ActionYml { + const filePath = path.join(repoRoot, "action.yml"); + const content = fs.readFileSync(filePath, "utf-8"); + return yaml.load(content) as ActionYml; +} + +describe("action.yml metadata contract", () => { + const actionYml = loadActionYml(); + + describe("basic metadata", () => { + it("has a name", () => { + expect(actionYml.name).toBe("Setup Fortran Compilers"); + }); + + it("has a non-empty description", () => { + expect(actionYml.description).toBeTruthy(); + expect(actionYml.description.length).toBeGreaterThan(0); + }); + + it("has an author", () => { + expect(actionYml.author).toBeTruthy(); + }); + }); + + describe("inputs", () => { + it("declares the expected input names", () => { + expect(Object.keys(actionYml.inputs).sort()).toEqual( + [ + "cleanup-disk", + "compiler", + "msystem", + "update-environment", + "version", + ].sort(), + ); + }); + + it("makes compiler optional with default gfortran", () => { + const input = actionYml.inputs["compiler"]; + expect(input.required).toBe(false); + expect(input.default).toBe("gfortran"); + }); + + it("makes version optional without a default (resolved in code)", () => { + const input = actionYml.inputs["version"]; + expect(input.required).toBe(false); + expect(input.default).toBeUndefined(); + }); + + it("makes msystem optional with default native", () => { + const input = actionYml.inputs["msystem"]; + expect(input.required).toBe(false); + expect(input.default).toBe("native"); + }); + + it("makes cleanup-disk optional with default false", () => { + const input = actionYml.inputs["cleanup-disk"]; + expect(input.required).toBe(false); + expect(input.default).toBe("false"); + }); + + it("makes update-environment optional with default true", () => { + const input = actionYml.inputs["update-environment"]; + expect(input.required).toBe(false); + expect(input.default).toBe("true"); + }); + }); + + describe("outputs", () => { + it("declares the expected output names", () => { + expect(Object.keys(actionYml.outputs).sort()).toEqual( + ["cc", "cxx", "fc", "version"].sort(), + ); + }); + }); + + describe("runs", () => { + it("uses node24 runtime", () => { + expect(actionYml.runs.using).toBe("node24"); + }); + + it("points to dist/index.js", () => { + expect(actionYml.runs.main).toBe("dist/index.js"); + }); + }); +}); + +describe("action.yml input default values match code DEFAULTS", () => { + const actionYml = loadActionYml(); + + it("compiler default matches DEFAULTS.compiler (gfortran)", () => { + expect(actionYml.inputs["compiler"].default).toBe("gfortran"); + }); + + it("msystem default matches DEFAULTS.msystem (native)", () => { + expect(actionYml.inputs["msystem"].default).toBe("native"); + }); + + it("cleanup-disk default matches DEFAULTS.cleanupDisk (false)", () => { + expect(actionYml.inputs["cleanup-disk"].default).toBe("false"); + }); + + it("update-environment default matches DEFAULTS.updateEnvironment (true)", () => { + expect(actionYml.inputs["update-environment"].default).toBe("true"); + }); + + it("version default in action.yml matches code resolution for unspecified version", () => { + // version has no default in action.yml; the code falls back to LATEST = "latest" + expect(actionYml.inputs["version"].default).toBeUndefined(); + // The code's DEFAULTS.version is LATEST + const expectedDefault: Inputs["version"] = "latest"; + expect(expectedDefault).toBe("latest"); + }); +}); diff --git a/__tests__/bash_env.test.ts b/__tests__/bash_env.test.ts new file mode 100644 index 00000000..031fed07 --- /dev/null +++ b/__tests__/bash_env.test.ts @@ -0,0 +1,83 @@ +import * as core from "@actions/core"; +import * as fs from "fs"; +import * as path from "path"; +import { persistBinDirForBash, toMsysPath } from "../src/bash_env"; + +jest.mock("@actions/core"); +jest.mock("fs"); + +describe("persistBinDirForBash", () => { + const runnerTemp = "D:\\a\\_temp"; + // Built the same way persistBinDirForBash builds it (plain path.join), + // so this matches regardless of whether the test runs on Linux CI or a + // native Windows machine: both sides track the same host OS. + const lfortranBashEnvPath = path.join( + runnerTemp, + "setup-fortran-lfortran-bash-env.sh", + ); + + beforeEach(() => { + jest.clearAllMocks(); + process.env.RUNNER_TEMP = runnerTemp; + delete process.env.BASH_ENV; + }); + + afterAll(() => { + delete process.env.RUNNER_TEMP; + delete process.env.BASH_ENV; + }); + + it("writes a per-installer Bash environment and exports BASH_ENV", () => { + const bashEnv = persistBinDirForBash( + "C:\\hostedtoolcache\\setup-fortran\\lfortran\\win32\\x64\\0.64.0\\env\\Library\\bin", + "lfortran", + ); + + expect(bashEnv).toBe(lfortranBashEnvPath); + expect(fs.writeFileSync).toHaveBeenCalledWith( + lfortranBashEnvPath, + `export PATH='/c/hostedtoolcache/setup-fortran/lfortran/win32/x64/0.64.0/env/Library/bin':"$PATH"\n`, + { mode: 0o600 }, + ); + expect(core.exportVariable).toHaveBeenCalledWith( + "BASH_ENV", + toMsysPath(lfortranBashEnvPath), + ); + }); + + it("chains a Bash environment written by another installer", () => { + const previousBashEnv = path.join( + runnerTemp, + "setup-fortran-msvc-bash-env.sh", + ); + process.env.BASH_ENV = previousBashEnv; + + persistBinDirForBash("C:\\MSVC\\bin", "lfortran"); + + expect(fs.writeFileSync).toHaveBeenCalledWith( + expect.any(String), + `. '${toMsysPath(previousBashEnv)}'\nexport PATH='/c/MSVC/bin':"$PATH"\n`, + { mode: 0o600 }, + ); + }); + + it("does not source itself when the same installer runs twice", () => { + process.env.BASH_ENV = toMsysPath(lfortranBashEnvPath); + + persistBinDirForBash("C:\\MSVC\\bin", "lfortran"); + + expect(fs.writeFileSync).toHaveBeenCalledWith( + lfortranBashEnvPath, + `export PATH='/c/MSVC/bin':"$PATH"\n`, + { mode: 0o600 }, + ); + }); +}); + +describe("toMsysPath", () => { + it("converts Windows drive paths for Git Bash", () => { + expect(toMsysPath("C:\\Program Files\\LLVM\\bin")).toBe( + "/c/Program Files/LLVM/bin", + ); + }); +}); diff --git a/__tests__/cache_validation.test.ts b/__tests__/cache_validation.test.ts new file mode 100644 index 00000000..62af3080 --- /dev/null +++ b/__tests__/cache_validation.test.ts @@ -0,0 +1,74 @@ +import * as cache from "@actions/cache"; +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as fs from "fs"; +import { + saveCompilerCache, + validateRestoredCompilerCache, +} from "../src/cache_validation"; + +jest.mock("@actions/cache"); +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + existsSync: jest.fn(), +})); + +describe("compiler cache validation", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedExists = fs.existsSync as jest.MockedFunction< + typeof fs.existsSync + >; + + beforeEach(() => { + jest.clearAllMocks(); + mockedExists.mockReturnValue(true); + mockedExec.mockResolvedValue(0); + }); + + it("accepts a complete cache whose compiler runs", async () => { + await expect( + validateRestoredCompilerCache("compiler", ["/setup"], "tool", [ + "--version", + ]), + ).resolves.toBe(true); + }); + + it("rejects a cache with missing required files without running it", async () => { + mockedExists.mockReturnValue(false); + + await expect( + validateRestoredCompilerCache("compiler", ["/setup"], "tool", [ + "--version", + ]), + ).resolves.toBe(false); + expect(mockedExec).not.toHaveBeenCalled(); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("missing: /setup"), + ); + }); + + it("rejects a cache whose compiler validation fails", async () => { + mockedExec.mockResolvedValue(1); + + await expect( + validateRestoredCompilerCache("compiler", ["/setup"], "tool", [ + "--version", + ]), + ).resolves.toBe(false); + }); + + it("does not fail an installation when cache saving is unavailable", async () => { + ( + cache.saveCache as jest.MockedFunction + ).mockRejectedValue(new Error("immutable cache already exists")); + + await expect( + saveCompilerCache(["/compiler"], "key"), + ).resolves.toBeUndefined(); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("immutable cache already exists"), + ); + }); +}); diff --git a/__tests__/download_installer.test.ts b/__tests__/download_installer.test.ts new file mode 100644 index 00000000..633064fb --- /dev/null +++ b/__tests__/download_installer.test.ts @@ -0,0 +1,421 @@ +import * as exec from "@actions/exec"; +import * as tc from "@actions/tool-cache"; +import { downloadInstaller } from "../src/download_installer"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("@actions/tool-cache"); + +const URL = + "https://registrationcenter-download.intel.com/akdlm/IRC_NAS/test-id/m_HPCKit_p_test.dmg"; +const DEST = "/tmp/test-installer.dmg"; +const HOST = "registrationcenter-download.intel.com"; +const REAL_PLATFORM = process.platform; + +// Mirrors the shape of a dns.google/resolve answer for the Akamai chain: +// the A records belong to a1928.d.akamai.net, not to the queried name. +const DOH_BODY = JSON.stringify({ + Status: 0, + Question: [{ name: HOST, type: 1 }], + Answer: [ + { name: HOST, type: 5, TTL: 51, data: "a1928.d.akamai.net." }, + { name: "a1928.d.akamai.net.", type: 1, TTL: 20, data: "192.0.2.1" }, + { name: "a1928.d.akamai.net.", type: 1, TTL: 20, data: "192.0.2.2" }, + ], +}); + +const DOH_GOOGLE = "https://dns.google/resolve"; +const DOH_CLOUDFLARE = "https://cloudflare-dns.com/dns-query"; + +const mockedExec = exec.exec as jest.MockedFunction; +const mockedDownloadTool = tc.downloadTool as jest.MockedFunction< + typeof tc.downloadTool +>; + +function isDohCall(command: string, args?: readonly string[]): boolean { + return ( + command === "curl" && + (args ?? []).some( + (arg) => + arg.startsWith(`${DOH_GOOGLE}?`) || + arg.startsWith(`${DOH_CLOUDFLARE}?`), + ) + ); +} + +function isPinnedDownload(command: string, args?: readonly string[]): boolean { + return command === "curl" && (args ?? []).includes("--resolve"); +} + +// DoH answers; every unpinned download attempt fails to resolve the host, so +// the code never escapes the fallback chain. +function mockResolverFailure(): void { + mockedExec.mockImplementation(async (command, args, options) => { + if (isDohCall(command, args)) { + options?.listeners?.stdout?.(Buffer.from(DOH_BODY)); + return 0; + } + if (command === "curl" && !isPinnedDownload(command, args)) { + throw new Error("Could not resolve host"); + } + return 0; + }); +} + +// Every DoH endpoint is unreachable, so the fallback has to come back and +// keep retrying the resolver path. +function mockDohUnavailable(): void { + mockedExec.mockImplementation(async (command, args) => { + if (isDohCall(command, args)) { + return 6; // curl: could not resolve host + } + return 0; + }); +} + +function setPlatform(value: NodeJS.Platform): void { + Object.defineProperty(process, "platform", { value }); +} + +// Runs pending setTimeout backoffs (the retry delays) immediately. +function immediateTimers(): () => void { + const spy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback: Parameters[0]) => { + if (typeof callback === "function") callback(); + return 0 as unknown as NodeJS.Timeout; + }); + return () => spy.mockRestore(); +} + +beforeEach(() => { + jest.clearAllMocks(); + setPlatform(REAL_PLATFORM); + mockedExec.mockResolvedValue(0); +}); + +afterEach(() => { + setPlatform(REAL_PLATFORM); +}); + +describe("downloadInstaller", () => { + it("downloads via tool-cache on the first attempt", async () => { + mockedDownloadTool.mockResolvedValue(DEST); + + await expect(downloadInstaller(URL, DEST)).resolves.toBe(DEST); + + expect(mockedDownloadTool).toHaveBeenCalledTimes(1); + expect(mockedExec).not.toHaveBeenCalled(); + }); + + it("flushes the system DNS cache between retries on macOS", async () => { + setPlatform("darwin"); + mockedDownloadTool + .mockRejectedValueOnce(new Error("getaddrinfo ENOTFOUND")) + .mockResolvedValue(DEST); + mockDohUnavailable(); + const restoreTimers = immediateTimers(); + + try { + await expect(downloadInstaller(URL, DEST)).resolves.toBe(DEST); + } finally { + restoreTimers(); + } + + expect(mockedDownloadTool).toHaveBeenCalledTimes(2); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + ["dscacheutil", "-flushcache"], + expect.objectContaining({ ignoreReturnCode: true }), + ); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + ["killall", "-HUP", "mDNSResponder"], + expect.objectContaining({ ignoreReturnCode: true }), + ); + }); + + it("skips the DNS flush on other platforms", async () => { + setPlatform("linux"); + mockedDownloadTool + .mockRejectedValueOnce(new Error("getaddrinfo ENOTFOUND")) + .mockResolvedValue(DEST); + mockDohUnavailable(); + const restoreTimers = immediateTimers(); + + try { + await expect(downloadInstaller(URL, DEST)).resolves.toBe(DEST); + } finally { + restoreTimers(); + } + + expect(mockedDownloadTool).toHaveBeenCalledTimes(2); + expect(mockedExec).not.toHaveBeenCalledWith( + "sudo", + expect.arrayContaining(["dscacheutil"]), + ); + }); + + it("falls back to curl with resilient flags after tool-cache fails", async () => { + mockedDownloadTool.mockRejectedValue(new Error("socket hang up")); + const restoreTimers = immediateTimers(); + + try { + await expect(downloadInstaller(URL, DEST)).resolves.toBe(DEST); + } finally { + restoreTimers(); + } + + expect(mockedDownloadTool).toHaveBeenCalledTimes(3); + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining([ + "--fail", + "-4", + "--retry-all-errors", + "-o", + DEST, + URL, + ]), + ); + }); + + it("pins DNS-over-HTTPS addresses via --resolve when the runner resolver fails", async () => { + mockedDownloadTool.mockRejectedValue(new Error("getaddrinfo ENOTFOUND")); + mockResolverFailure(); + const restoreTimers = immediateTimers(); + + try { + await expect(downloadInstaller(URL, DEST)).resolves.toBe(DEST); + } finally { + restoreTimers(); + } + + // Both Akamai A records from the DoH answer are pinned. + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining(["--resolve", `${HOST}:443:192.0.2.1,192.0.2.2`]), + ); + + // --fail, so an HTTP error is reported as such rather than as a + // "non-JSON body". + const dohArgs = mockedExec.mock.calls.find(([cmd, args]) => + isDohCall(cmd, args), + )?.[1]; + expect(dohArgs).toEqual(expect.arrayContaining(["--fail"])); + expect(dohArgs).toContainEqual(expect.stringContaining(`${DOH_GOOGLE}?`)); + }); + + it("re-resolves just-in-time rather than pinning stale addresses", async () => { + mockedDownloadTool.mockRejectedValue(new Error("getaddrinfo ENOTFOUND")); + mockedExec.mockImplementation(async (command, args, options) => { + if (isDohCall(command, args)) { + options?.listeners?.stdout?.(Buffer.from(DOH_BODY)); + return 0; + } + // Even the pinned download fails, so the resolve/pin pair retries. + if (command === "curl") throw new Error("Could not resolve host"); + return 0; + }); + const restoreTimers = immediateTimers(); + + try { + await expect(downloadInstaller(URL, DEST)).rejects.toThrow(); + } finally { + restoreTimers(); + } + + // DoH is queried again per attempt instead of reusing one answer. + const dohCalls = mockedExec.mock.calls.filter(([cmd, args]) => + isDohCall(cmd, args), + ); + expect(dohCalls.length).toBeGreaterThanOrEqual(3); + }); + + it("skips the curl retry when resolution is known to be broken", async () => { + mockedDownloadTool.mockRejectedValue( + new Error("getaddrinfo ENOTFOUND registrationcenter-download.intel.com"), + ); + mockResolverFailure(); + const restoreTimers = immediateTimers(); + + try { + await expect(downloadInstaller(URL, DEST)).resolves.toBe(DEST); + } finally { + restoreTimers(); + } + + // tool-cache is abandoned after one resolution failure instead of three, + // and plain curl is never attempted. + expect(mockedDownloadTool).toHaveBeenCalledTimes(1); + const plainCurlCalls = mockedExec.mock.calls.filter( + ([cmd, args]) => + cmd === "curl" && !isPinnedDownload(cmd, args) && !isDohCall(cmd, args), + ); + expect(plainCurlCalls).toHaveLength(0); + }); + + it("keeps retrying the resolver path when DoH also fails", async () => { + setPlatform("darwin"); + mockedDownloadTool + .mockRejectedValueOnce(new Error("getaddrinfo ENOTFOUND")) + .mockResolvedValue(DEST); + mockDohUnavailable(); + const restoreTimers = immediateTimers(); + + try { + await expect(downloadInstaller(URL, DEST)).resolves.toBe(DEST); + } finally { + restoreTimers(); + } + + // The DoH intervention is a shortcut, not a replacement: when it fails + // the tool-cache retries still run. + expect(mockedDownloadTool).toHaveBeenCalledTimes(2); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + ["dscacheutil", "-flushcache"], + expect.objectContaining({ ignoreReturnCode: true }), + ); + }); + + it("keeps retrying when the failure is transport, not resolution", async () => { + mockedDownloadTool.mockRejectedValue(new Error("socket hang up")); + mockedExec.mockImplementation(async (command, args, options) => { + if (isDohCall(command, args)) { + options?.listeners?.stdout?.(Buffer.from(DOH_BODY)); + } + return 0; + }); + const restoreTimers = immediateTimers(); + + try { + await expect(downloadInstaller(URL, DEST)).resolves.toBe(DEST); + } finally { + restoreTimers(); + } + + // Full three tool-cache attempts plus the plain curl fallback, as before. + expect(mockedDownloadTool).toHaveBeenCalledTimes(3); + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining(["--retry-all-errors", "-o", DEST, URL]), + ); + }); + + it("rotates to the next DoH provider when the first one fails", async () => { + mockedDownloadTool.mockRejectedValue(new Error("getaddrinfo ENOTFOUND")); + const endpointsSeen: string[] = []; + mockedExec.mockImplementation(async (command, args, options) => { + if (isDohCall(command, args)) { + const [base] = ( + (args ?? []).find( + (a) => + a.startsWith(`${DOH_GOOGLE}?`) || + a.startsWith(`${DOH_CLOUDFLARE}?`), + ) ?? "" + ).split("?"); + endpointsSeen.push(base); + if (base === DOH_GOOGLE) return 2; + options?.listeners?.stdout?.(Buffer.from(DOH_BODY)); + return 0; + } + if (command === "curl" && !isPinnedDownload(command, args)) { + throw new Error("Could not resolve host"); + } + return 0; + }); + const restoreTimers = immediateTimers(); + + try { + await expect(downloadInstaller(URL, DEST)).resolves.toBe(DEST); + } finally { + restoreTimers(); + } + + expect(endpointsSeen[0]).toBe(DOH_GOOGLE); + expect(endpointsSeen[1]).toBe(DOH_CLOUDFLARE); + }); + + it("throws a descriptive error when every DoH provider fails", async () => { + mockedDownloadTool.mockRejectedValue(new Error("getaddrinfo ENOTFOUND")); + mockedExec.mockImplementation(async (command, args, options) => { + if (isDohCall(command, args)) { + const query = + (args ?? []).find( + (a) => + a.startsWith(`${DOH_GOOGLE}?`) || + a.startsWith(`${DOH_CLOUDFLARE}?`), + ) ?? ""; + if (query.startsWith(DOH_GOOGLE)) return 22; + if (query.startsWith(DOH_CLOUDFLARE)) { + options?.listeners?.stdout?.( + Buffer.from(JSON.stringify({ Status: 3 })), + ); + return 0; + } + } + if (command === "curl" && !isPinnedDownload(command, args)) { + throw new Error("Could not resolve host"); + } + return 0; + }); + const restoreTimers = immediateTimers(); + + try { + await expect(downloadInstaller(URL, DEST)).rejects.toThrow( + /DNS-over-HTTPS/, + ); + } finally { + restoreTimers(); + } + }); + + it("reports a non-JSON DoH body descriptively", async () => { + mockedDownloadTool.mockRejectedValue(new Error("getaddrinfo ENOTFOUND")); + mockedExec.mockImplementation(async (command, args, options) => { + if (isDohCall(command, args)) { + options?.listeners?.stdout?.(Buffer.from("not json")); + return 0; + } + if (command === "curl" && !isPinnedDownload(command, args)) { + throw new Error("Could not resolve host"); + } + return 0; + }); + const restoreTimers = immediateTimers(); + + try { + await expect(downloadInstaller(URL, DEST)).rejects.toThrow( + /non-JSON body/, + ); + } finally { + restoreTimers(); + } + }); + + it("reports a DoH answer with no A records descriptively", async () => { + mockedDownloadTool.mockRejectedValue(new Error("getaddrinfo ENOTFOUND")); + mockedExec.mockImplementation(async (command, args, options) => { + if (isDohCall(command, args)) { + options?.listeners?.stdout?.( + Buffer.from(JSON.stringify({ Status: 0 })), + ); + return 0; + } + if (command === "curl" && !isPinnedDownload(command, args)) { + throw new Error("Could not resolve host"); + } + return 0; + }); + const restoreTimers = immediateTimers(); + + try { + await expect(downloadInstaller(URL, DEST)).rejects.toThrow( + /no A records/, + ); + } finally { + restoreTimers(); + } + }); +}); diff --git a/__tests__/index.test.ts b/__tests__/index.test.ts new file mode 100644 index 00000000..38f28e84 --- /dev/null +++ b/__tests__/index.test.ts @@ -0,0 +1,293 @@ +import * as core from "@actions/core"; +import type { Inputs, InstallationResult } from "../src/types"; +import { Arch, Compiler, Msystem, OS } from "../src/types"; + +jest.mock("@actions/core"); +jest.mock("../src/parse_inputs", () => ({ parseInputs: jest.fn() })); +jest.mock("../src/installers/gfortran", () => ({ + installGFortran: jest.fn(), +})); +jest.mock("../src/installers/ifx", () => ({ installIFX: jest.fn() })); +jest.mock("../src/installers/ifort", () => ({ installIFort: jest.fn() })); +jest.mock("../src/installers/nvfortran", () => ({ + installNVFortran: jest.fn(), +})); +jest.mock("../src/installers/aocc", () => ({ installAOCC: jest.fn() })); +jest.mock("../src/installers/flang", () => ({ installFlang: jest.fn() })); +jest.mock("../src/installers/lfortran", () => ({ + installLFortran: jest.fn(), +})); +jest.mock("../src/installers/armflang", () => ({ + installArmFlang: jest.fn(), +})); +jest.mock("../src/installation_result", () => ({ + setInstallationOutputs: jest.fn(), + exportInstallationVariables: jest.fn(), +})); + +describe("action invocation", () => { + const previousSmokeTest = process.env.SETUP_FORTRAN_BUNDLE_SMOKE_TEST; + + beforeAll(() => { + process.env.SETUP_FORTRAN_BUNDLE_SMOKE_TEST = "1"; + }); + + afterAll(() => { + if (previousSmokeTest === undefined) { + delete process.env.SETUP_FORTRAN_BUNDLE_SMOKE_TEST; + } else { + process.env.SETUP_FORTRAN_BUNDLE_SMOKE_TEST = previousSmokeTest; + } + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it.each([ + [Compiler.GFortran, "../src/installers/gfortran", "installGFortran"], + [Compiler.IFX, "../src/installers/ifx", "installIFX"], + [Compiler.IFort, "../src/installers/ifort", "installIFort"], + [Compiler.NVFortran, "../src/installers/nvfortran", "installNVFortran"], + ] as const)( + "dispatches normalized %s inputs to %s", + async (compiler, installerModule, installerName) => { + const { parseInputs } = jest.requireMock("../src/parse_inputs") as { + parseInputs: jest.Mock; + }; + const installer = ( + jest.requireMock(installerModule) as Record< + string, + jest.Mock> + > + )[installerName]; + const inputs: Inputs = { + compiler, + version: "latest", + os: OS.Linux, + osVersion: "24.04", + arch: Arch.X64, + msystem: Msystem.Native, + cleanupDisk: false, + updateEnvironment: true, + }; + const result: InstallationResult = { + version: "1.2.3", + fc: "fc", + cc: "cc", + cxx: "cxx", + }; + parseInputs.mockReturnValue(inputs); + installer.mockResolvedValue(result); + + const { run } = await import("../src/index"); + await run(); + + expect(installer).toHaveBeenCalledTimes(1); + expect(installer).toHaveBeenCalledWith(inputs); + expect(core.setFailed).not.toHaveBeenCalled(); + }, + ); + + it("can run twice in one process without retaining invocation state", async () => { + const { parseInputs } = jest.requireMock("../src/parse_inputs") as { + parseInputs: jest.Mock; + }; + const { installGFortran } = jest.requireMock( + "../src/installers/gfortran", + ) as { installGFortran: jest.Mock> }; + const { setInstallationOutputs, exportInstallationVariables } = + jest.requireMock("../src/installation_result") as { + setInstallationOutputs: jest.Mock; + exportInstallationVariables: jest.Mock; + }; + const inputs: Inputs = { + compiler: Compiler.GFortran, + version: "14", + os: OS.Linux, + osVersion: "24.04", + arch: Arch.X64, + msystem: Msystem.Native, + cleanupDisk: false, + updateEnvironment: true, + }; + const result: InstallationResult = { + version: "14.2.0", + fc: "gfortran-14", + cc: "gcc-14", + cxx: "g++-14", + }; + parseInputs.mockReturnValue(inputs); + installGFortran.mockResolvedValue(result); + + const { run } = await import("../src/index"); + await run(); + await run(); + + expect(parseInputs).toHaveBeenCalledTimes(2); + expect(installGFortran).toHaveBeenCalledTimes(2); + expect(setInstallationOutputs).toHaveBeenNthCalledWith(1, result); + expect(setInstallationOutputs).toHaveBeenNthCalledWith(2, result); + expect(exportInstallationVariables).toHaveBeenCalledTimes(2); + expect(core.exportVariable).toHaveBeenCalledWith( + "FORTRAN_COMPILER", + Compiler.GFortran, + ); + }); + + it("always sets outputs even when update-environment is false", async () => { + const { parseInputs } = jest.requireMock("../src/parse_inputs") as { + parseInputs: jest.Mock; + }; + const { installGFortran } = jest.requireMock( + "../src/installers/gfortran", + ) as { installGFortran: jest.Mock> }; + const { setInstallationOutputs, exportInstallationVariables } = + jest.requireMock("../src/installation_result") as { + setInstallationOutputs: jest.Mock; + exportInstallationVariables: jest.Mock; + }; + const inputs: Inputs = { + compiler: Compiler.GFortran, + version: "14", + os: OS.Linux, + osVersion: "24.04", + arch: Arch.X64, + msystem: Msystem.Native, + cleanupDisk: false, + updateEnvironment: false, + }; + const result: InstallationResult = { + version: "14.2.0", + fc: "gfortran-14", + cc: "gcc-14", + cxx: "g++-14", + }; + parseInputs.mockReturnValue(inputs); + installGFortran.mockResolvedValue(result); + + const { run } = await import("../src/index"); + await run(); + + expect(installGFortran).toHaveBeenCalledWith(inputs); + expect(setInstallationOutputs).toHaveBeenCalledWith(result); + expect(exportInstallationVariables).not.toHaveBeenCalled(); + expect(core.exportVariable).not.toHaveBeenCalled(); + }); + + // --- Negative: no silent fallback when installers fail --- + it("does not silently fall back to ifort when ifx fails on macOS", async () => { + const { parseInputs } = jest.requireMock("../src/parse_inputs") as { + parseInputs: jest.Mock; + }; + const { installIFX } = jest.requireMock("../src/installers/ifx") as { + installIFX: jest.Mock>; + }; + const { installIFort } = jest.requireMock("../src/installers/ifort") as { + installIFort: jest.Mock>; + }; + const { setInstallationOutputs, exportInstallationVariables } = + jest.requireMock("../src/installation_result") as { + setInstallationOutputs: jest.Mock; + exportInstallationVariables: jest.Mock; + }; + + const inputs: Inputs = { + compiler: Compiler.IFX, + version: "latest", + os: OS.MacOS, + osVersion: "14", + arch: Arch.X64, + msystem: Msystem.Native, + cleanupDisk: false, + updateEnvironment: true, + }; + parseInputs.mockReturnValue(inputs); + installIFX.mockRejectedValue( + new Error( + "ifx is not supported on macOS. Use ifort, or exclude {compiler: ifx, os: macos} from your build matrix.", + ), + ); + + const { run } = await import("../src/index"); + await run(); + + expect(installIFX).toHaveBeenCalledTimes(1); + expect(installIFX).toHaveBeenCalledWith(inputs); + expect(installIFort).not.toHaveBeenCalled(); + expect(core.setFailed).toHaveBeenCalledTimes(1); + expect(setInstallationOutputs).not.toHaveBeenCalled(); + expect(exportInstallationVariables).not.toHaveBeenCalled(); + expect(core.exportVariable).not.toHaveBeenCalledWith( + "FORTRAN_COMPILER", + expect.anything(), + ); + expect(core.setOutput).not.toHaveBeenCalled(); + }); + + // Each failure leaves NO substituted compiler output — no silent fallback + // to another compiler, no env exports, no action outputs. + it.each([ + [Compiler.IFX, OS.MacOS, "ifx is not supported on macOS"], + [Compiler.NVFortran, OS.MacOS, "nvfortran is only supported on Linux"], + [Compiler.AOCC, OS.MacOS, "aocc is only supported on Linux"], + [Compiler.ArmFlang, OS.MacOS, "armflang is only supported on Linux"], + ])( + "%s on %s leaves no substituted compiler output", + async (compiler, os, expectedMessage) => { + const { parseInputs } = jest.requireMock("../src/parse_inputs") as { + parseInputs: jest.Mock; + }; + const installerNameMap: Record = { + [Compiler.IFX]: "installIFX", + [Compiler.NVFortran]: "installNVFortran", + [Compiler.AOCC]: "installAOCC", + [Compiler.ArmFlang]: "installArmFlang", + }; + const installerModuleMap: Record = { + [Compiler.IFX]: "../src/installers/ifx", + [Compiler.NVFortran]: "../src/installers/nvfortran", + [Compiler.AOCC]: "../src/installers/aocc", + [Compiler.ArmFlang]: "../src/installers/armflang", + }; + const installer = ( + jest.requireMock(installerModuleMap[compiler]) as Record< + string, + jest.Mock> + > + )[installerNameMap[compiler]]; + const { setInstallationOutputs, exportInstallationVariables } = + jest.requireMock("../src/installation_result") as { + setInstallationOutputs: jest.Mock; + exportInstallationVariables: jest.Mock; + }; + + const inputs: Inputs = { + compiler, + version: "latest", + os, + osVersion: os === OS.MacOS ? "14" : "24.04", + arch: Arch.X64, + msystem: Msystem.Native, + cleanupDisk: false, + updateEnvironment: true, + }; + parseInputs.mockReturnValue(inputs); + installer.mockRejectedValue(new Error(expectedMessage)); + + const { run } = await import("../src/index"); + await run(); + + expect(installer).toHaveBeenCalledTimes(1); + expect(installer).toHaveBeenCalledWith(inputs); + expect(core.setFailed).toHaveBeenCalledTimes(1); + expect(core.setFailed).toHaveBeenCalledWith( + expect.stringContaining(expectedMessage), + ); + expect(setInstallationOutputs).not.toHaveBeenCalled(); + expect(exportInstallationVariables).not.toHaveBeenCalled(); + expect(core.setOutput).not.toHaveBeenCalled(); + expect(core.exportVariable).not.toHaveBeenCalled(); + }, + ); +}); diff --git a/__tests__/installation_result.test.ts b/__tests__/installation_result.test.ts new file mode 100644 index 00000000..e6d1b6a0 --- /dev/null +++ b/__tests__/installation_result.test.ts @@ -0,0 +1,92 @@ +import * as core from "@actions/core"; +import { + exportInstallationVariables, + normalizeVersionOutput, + setInstallationOutputs, +} from "../src/installation_result"; +import type { InstallationResult } from "../src/types"; + +jest.mock("@actions/core"); + +describe("installation result helpers", () => { + const result: InstallationResult = { + version: "GNU Fortran (Ubuntu 14.2.0-1ubuntu2) 14.2.0\nCopyright GCC", + fc: "fortran", + cc: "c", + cxx: "cxx", + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("sets action outputs from the installation result", () => { + setInstallationOutputs(result); + + expect(core.setOutput).toHaveBeenCalledWith("version", "14.2.0"); + expect(core.setOutput).toHaveBeenCalledWith("fc", "fortran"); + expect(core.setOutput).toHaveBeenCalledWith("cc", "c"); + expect(core.setOutput).toHaveBeenCalledWith("cxx", "cxx"); + }); + + it.each([ + ["14", "14"], + ["ifort (IFORT) 2021.10.0 20230609", "2021.10.0"], + ["flang-new version 19.1.7\nTarget: x86_64-linux-gnu", "19.1.7"], + ["LFortran version: 0.64.0-12-gabcdef", "0.64.0"], + ["AMD clang version 17.0.0 (AOCC_5.0.0-Build#123)", "5.0.0"], + ])("normalizes compiler version output %p", (output, expected) => { + expect(normalizeVersionOutput(output)).toBe(expected); + }); + + it("rejects a banner without a numeric version", () => { + expect(() => normalizeVersionOutput("unknown compiler")).toThrow( + "Could not determine compiler version", + ); + }); + + it("exports compiler, fpm, and alias variables from the installation result", () => { + exportInstallationVariables(result); + + expect(core.exportVariable).toHaveBeenCalledWith("FC", "fortran"); + expect(core.exportVariable).toHaveBeenCalledWith("CC", "c"); + expect(core.exportVariable).toHaveBeenCalledWith("CXX", "cxx"); + expect(core.exportVariable).toHaveBeenCalledWith("FPM_FC", "fortran"); + expect(core.exportVariable).toHaveBeenCalledWith("FPM_CC", "c"); + expect(core.exportVariable).toHaveBeenCalledWith("FPM_CXX", "cxx"); + expect(core.exportVariable).toHaveBeenCalledWith("F77", "fortran"); + expect(core.exportVariable).toHaveBeenCalledWith("F90", "fortran"); + }); + + it("ensures environment variables and action outputs are in sync", () => { + setInstallationOutputs(result); + exportInstallationVariables(result); + + const outputs = (core.setOutput as jest.Mock).mock.calls.reduce( + (acc: Record, [key, val]) => { + acc[key] = val; + return acc; + }, + {}, + ); + + const envVars = (core.exportVariable as jest.Mock).mock.calls.reduce( + (acc: Record, [key, val]) => { + acc[key] = val; + return acc; + }, + {}, + ); + + expect(envVars["FC"]).toBe(outputs["fc"]); + expect(envVars["CC"]).toBe(outputs["cc"]); + expect(envVars["CXX"]).toBe(outputs["cxx"]); + + // Also check aliases + expect(envVars["FPM_FC"]).toBe(outputs["fc"]); + expect(envVars["F77"]).toBe(outputs["fc"]); + expect(envVars["F90"]).toBe(outputs["fc"]); + expect(envVars["FPM_CC"]).toBe(outputs["cc"]); + expect(envVars["FPM_CXX"]).toBe(outputs["cxx"]); + }); +}); diff --git a/__tests__/installers/aocc/debian.test.ts b/__tests__/installers/aocc/debian.test.ts new file mode 100644 index 00000000..c59cae13 --- /dev/null +++ b/__tests__/installers/aocc/debian.test.ts @@ -0,0 +1,307 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as cache from "@actions/cache"; +import * as tc from "@actions/tool-cache"; +import * as os from "os"; +import * as path from "path"; +import * as fs from "fs"; +import { installDebian } from "../../../src/installers/aocc/debian"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("@actions/cache"); +jest.mock("@actions/tool-cache"); +jest.mock("os", () => ({ + ...jest.requireActual("os"), + homedir: jest.fn().mockReturnValue("/home/user"), + tmpdir: jest.fn().mockReturnValue("/tmp"), + userInfo: jest.fn().mockReturnValue({ username: "user" }), +})); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + existsSync: jest.fn(), + rmSync: jest.fn(), +})); + +describe("installDebian (AOCC)", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedFs = fs as jest.Mocked; + const mockedCache = cache as jest.Mocked; + const mockedDownloadTool = tc.downloadTool as jest.MockedFunction< + typeof tc.downloadTool + >; + const mockedExportVariable = core.exportVariable as jest.MockedFunction< + typeof core.exportVariable + >; + + const baseInputs: Inputs = { + compiler: Compiler.AOCC, + version: "5.1", + os: OS.Linux, + osVersion: "22.04", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + const tempInstallDir = "/home/user/.aocc-cache"; + + beforeEach(() => { + jest.clearAllMocks(); + mockedFs.existsSync.mockReturnValue(false); // Assume not installed + mockedCache.restoreCache.mockResolvedValue(undefined); // Cache miss + mockedDownloadTool.mockResolvedValue( + "/tmp/aocc-compiler-5.1.0_1_amd64.deb", + ); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if ( + commandLine === "bash" && + args?.[1] === + 'source "/opt/AMD/aocc-compiler-5.1.0/setenv_AOCC.sh" && env' + ) { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from( + "PATH=/opt/AMD/aocc/bin:/usr/bin\nLD_LIBRARY_PATH=/opt/AMD/aocc/lib\nAOCC_DIR=/opt/AMD/aocc\n", + ), + ); + } + } + if ( + commandLine === "/opt/AMD/aocc-compiler-5.1.0/bin/flang" && + args?.[0] === "--version" + ) { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("AOCC flang version 5.1.0")); + } + } + return 0; + }); + }); + + it("downloads and installs on cache miss", async () => { + await installDebian(baseInputs); + + expect(mockedCache.restoreCache).toHaveBeenCalledWith( + [tempInstallDir], + expect.stringContaining("aocc-5.1-x64-22.04"), + ); + expect(mockedDownloadTool).toHaveBeenCalledWith( + expect.stringContaining("aocc-5"), + expect.stringContaining("aocc-compiler-5.1.0_1_amd64.deb"), + undefined, + { "User-Agent": "Mozilla/5.0" }, + ); + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "dpkg", + "-i", + expect.stringContaining("aocc-compiler-5.1.0_1_amd64.deb"), + ]); + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "timeout", + "--signal=TERM", + "--kill-after=30s", + "15m", + "apt-get", + "install", + "-f", + "-y", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + ]); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "cp", + "-rT", + "/opt/AMD/aocc-compiler-5.1.0", + tempInstallDir, + ]); + expect(mockedCache.saveCache).toHaveBeenCalledWith( + [tempInstallDir], + expect.stringContaining("aocc-5.1-x64-22.04"), + ); + }); + + it("retries apt-get install -f on failure and eventually succeeds", async () => { + let attempts = 0; + mockedExec.mockImplementation(async (cmd, args, options) => { + if ( + cmd === "sudo" && + args?.includes("apt-get") && + args?.includes("install") && + args?.includes("-f") + ) { + attempts++; + if (attempts === 1) throw new Error("apt-get failure"); + return 0; + } + if ( + cmd === "bash" && + args?.[1] === + 'source "/opt/AMD/aocc-compiler-5.1.0/setenv_AOCC.sh" && env' + ) { + options?.listeners?.stdout?.(Buffer.from("PATH=/opt/AMD/aocc/bin\n")); + } + return 0; + }); + + jest.useFakeTimers(); + const installPromise = installDebian(baseInputs); + + // Flush microtasks to allow the first attempt to fail and reach setTimeout. + for (let i = 0; i < 10; i++) await Promise.resolve(); + + // Fast-forward past the 10s delay for the first retry. + jest.advanceTimersByTime(10000); + + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await installPromise; + jest.useRealTimers(); + + expect(attempts).toBe(2); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("apt-get install -f failed (attempt 1/3)"), + ); + }); + + it("skips download and restores from cache on cache hit", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + await installDebian(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "mv", + tempInstallDir, + "/opt/AMD/aocc-compiler-5.1.0", + ]); + expect(mockedExec).not.toHaveBeenCalledWith("curl", expect.anything()); + expect(mockedCache.saveCache).not.toHaveBeenCalled(); + }); + + it("skips download when already installed on disk but cache miss", async () => { + mockedFs.existsSync.mockReturnValue(true); + await installDebian(baseInputs); + + expect(mockedExec).not.toHaveBeenCalledWith("curl", expect.anything()); + expect(mockedExec).not.toHaveBeenCalledWith("sudo", [ + "dpkg", + "-i", + expect.anything(), + ]); + expect(mockedCache.saveCache).not.toHaveBeenCalled(); + }); + + it("sources setenv script and exports variables", async () => { + await installDebian(baseInputs); + + expect(mockedExportVariable).toHaveBeenCalledWith( + "PATH", + "/opt/AMD/aocc/bin:/usr/bin", + ); + expect(mockedExportVariable).toHaveBeenCalledWith( + "LD_LIBRARY_PATH", + "/opt/AMD/aocc/lib", + ); + }); + + it("resolves and returns the installed version", async () => { + const result = await installDebian(baseInputs); + expect(result).toEqual({ + version: "AOCC flang version 5.1.0", + fc: "flang", + cc: "clang", + cxx: "clang++", + }); + }); + + it("normalizes 5.1.0 input to 5.1 release entry", async () => { + const inputs: Inputs = { ...baseInputs, version: "5.1.0" }; + const result = await installDebian(inputs); + expect(result).toEqual({ + version: "AOCC flang version 5.1.0", + fc: "flang", + cc: "clang", + cxx: "clang++", + }); + expect(mockedDownloadTool).toHaveBeenCalledWith( + expect.stringContaining("aocc-compiler-5.1.0_1_amd64.deb"), + expect.stringContaining("aocc-compiler-5.1.0_1_amd64.deb"), + undefined, + { "User-Agent": "Mozilla/5.0" }, + ); + }); + + it("rejects non-zero patch versions like 5.1.1", async () => { + const inputs: Inputs = { ...baseInputs, version: "5.1.1" }; + await expect(installDebian(inputs)).rejects.toThrow( + "aocc 5.1.1 is not supported on linux (x64). Supported versions: 5.2, 5.1, 5.0, 4.2, 4.1", + ); + }); + + describe("download retry", () => { + afterEach(() => { + jest.useRealTimers(); + }); + + it("retries a failed .deb download, removes the partial file, and succeeds", async () => { + mockedDownloadTool + .mockRejectedValueOnce(new Error("connection reset")) + .mockResolvedValue("/tmp/aocc-compiler-5.1.0_1_amd64.deb"); + + jest.useFakeTimers(); + const installPromise = installDebian(baseInputs); + + for (let i = 0; i < 10; i++) await Promise.resolve(); + expect(mockedDownloadTool).toHaveBeenCalledTimes(1); + + // Advance past the 20s backoff after the first failure. + jest.advanceTimersByTime(20_000); + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await installPromise; + + expect(mockedDownloadTool).toHaveBeenCalledTimes(2); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("Download failed (attempt 1/3)"), + ); + expect(mockedFs.rmSync).toHaveBeenCalledWith( + expect.stringContaining("aocc-compiler-5.1.0_1_amd64.deb"), + { force: true }, + ); + expect(mockedCache.saveCache).toHaveBeenCalled(); + }); + + it("gives up after three attempts and propagates the last error", async () => { + mockedDownloadTool.mockRejectedValue(new Error("network down")); + + jest.useFakeTimers(); + const installPromise = installDebian(baseInputs); + + for (let i = 0; i < 10; i++) await Promise.resolve(); + jest.advanceTimersByTime(20_000); // backoff after attempt 1 + for (let i = 0; i < 10; i++) await Promise.resolve(); + jest.advanceTimersByTime(40_000); // backoff after attempt 2 + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await expect(installPromise).rejects.toThrow("network down"); + + expect(mockedDownloadTool).toHaveBeenCalledTimes(3); + expect(mockedFs.rmSync).toHaveBeenCalledTimes(3); + expect(mockedExec).not.toHaveBeenCalledWith( + "sudo", + expect.arrayContaining(["dpkg"]), + ); + expect(mockedCache.saveCache).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/__tests__/installers/armflang/debian.test.ts b/__tests__/installers/armflang/debian.test.ts new file mode 100644 index 00000000..981e9435 --- /dev/null +++ b/__tests__/installers/armflang/debian.test.ts @@ -0,0 +1,398 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as cache from "@actions/cache"; +import * as crypto from "crypto"; +import * as fs from "fs"; +import { installDebian } from "../../../src/installers/armflang/debian"; +import { Arch, Compiler, Msystem, OS, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("@actions/cache"); +jest.mock("os", () => ({ + ...jest.requireActual("os"), + homedir: jest.fn().mockReturnValue("/home/user"), + tmpdir: jest.fn().mockReturnValue("/tmp"), + userInfo: jest.fn().mockReturnValue({ username: "user" }), +})); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + existsSync: jest.fn(), + readFileSync: jest.fn(), + rmSync: jest.fn(), +})); + +describe("installDebian (ArmFlang)", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedGetExecOutput = exec.getExecOutput as jest.MockedFunction< + typeof exec.getExecOutput + >; + const mockedFs = fs as jest.Mocked; + const mockedCache = cache as jest.Mocked; + + const inputs: Inputs = { + compiler: Compiler.ArmFlang, + version: "22.1", + os: OS.Linux, + osVersion: "ubuntu24", + arch: Arch.ARM64, + msystem: Msystem.Native, + cleanupDisk: false, + updateEnvironment: true, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockedFs.existsSync.mockReturnValue(true); + mockedCache.restoreCache.mockResolvedValue(undefined); + const packageContents = Buffer.from("repository package"); + const packageChecksum = crypto + .createHash("sha256") + .update(packageContents) + .digest("hex"); + (mockedFs.readFileSync as jest.Mock).mockImplementation( + (_filePath: string, encoding?: string) => + encoding === "utf8" + ? "Package: arm-toolchains-repository\n" + + "Version: 2-2~noble\n" + + "Filename: pool/arm-toolchains-repository_2-2~noble_all.deb\n" + + `SHA256: ${packageChecksum}\n` + : packageContents, + ); + mockedGetExecOutput.mockImplementation(async (command) => { + if (command === "sha256sum") { + return { + stdout: `${"a".repeat(64)} repository.deb\n`, + stderr: "", + exitCode: 0, + }; + } + return { + stdout: + " arm-toolchain-for-linux | 22.1.0-123 | https://developer.arm.com\n", + stderr: "", + exitCode: 0, + }; + }); + mockedExec.mockImplementation(async (command, args, options) => { + if (command.endsWith("/armflang") && args?.[0] === "--version") { + options?.listeners?.stdout?.(Buffer.from("ArmFlang 22.1.0")); + } + return 0; + }); + }); + + it("configures the current repository and installs ArmFlang 22.1", async () => { + await installDebian(inputs); + + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining([ + expect.stringContaining("arm-toolchains-repository_2-2~noble_all.deb"), + ]), + ); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + expect.arrayContaining([ + "apt-get", + "install", + "arm-toolchain-for-linux=22.1.0-123", + "Acquire::Retries=5", + ]), + expect.objectContaining({ ignoreReturnCode: true }), + ); + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining(["--retry", "5", "--connect-timeout", "30"]), + ); + }); + + it("configures the current repository and installs ArmFlang 23.1", async () => { + mockedGetExecOutput.mockImplementation(async (command) => { + if (command === "sha256sum") { + return { + stdout: `${"a".repeat(64)} repository.deb\n`, + stderr: "", + exitCode: 0, + }; + } + return { + stdout: + " arm-toolchain-for-linux | 23.1.0-12 | https://developer.arm.com\n", + stderr: "", + exitCode: 0, + }; + }); + mockedExec.mockImplementation(async (command, args, options) => { + if (command.endsWith("/armflang") && args?.[0] === "--version") { + options?.listeners?.stdout?.(Buffer.from("ArmFlang 23.1.0")); + } + return 0; + }); + + const result = await installDebian({ ...inputs, version: "23.1" }); + + // 23.1 ships through the current repository, like 22.1 — not the legacy + // OBS Release.key flow. + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining([ + expect.stringContaining("arm-toolchains-repository_2-2~noble_all.deb"), + ]), + ); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + expect.arrayContaining([ + "apt-get", + "install", + "arm-toolchain-for-linux=23.1.0-12", + "Acquire::Retries=5", + ]), + expect.objectContaining({ ignoreReturnCode: true }), + ); + expect(result.version).toBe("ArmFlang 23.1.0"); + }); + + it("uses the legacy repository for ArmFlang 21.1", async () => { + mockedGetExecOutput.mockResolvedValue({ + stdout: " arm-toolchain-for-linux | 21.1-81 | repo\n", + stderr: "", + exitCode: 0, + }); + + await installDebian({ ...inputs, version: "21.1" }); + + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining([ + "https://developer.arm.com/packages/arm-toolchains:ubuntu-24/noble/Release.key", + ]), + ); + }); + + it("stages and saves the installed toolchain on a cache miss", async () => { + await installDebian(inputs); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "cp", + "-a", + "/opt/arm/.", + "/home/user/.armflang-cache", + ]); + expect(mockedCache.saveCache).toHaveBeenCalledWith( + ["/home/user/.armflang-cache"], + "armflang-22.1-arm64-ubuntu24", + ); + }); + + it("restores the toolchain and skips all network work on a cache hit", async () => { + mockedCache.restoreCache.mockResolvedValue("cache-key"); + + await installDebian(inputs); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "cp", + "-a", + "/home/user/.armflang-cache/.", + "/opt/arm", + ]); + expect(mockedExec).not.toHaveBeenCalledWith("curl", expect.anything()); + expect(mockedExec).not.toHaveBeenCalledWith( + "sudo", + expect.arrayContaining(["apt-get"]), + expect.anything(), + ); + expect(mockedCache.saveCache).not.toHaveBeenCalled(); + }); + + it("reinstalls when a restored cache is incomplete", async () => { + mockedCache.restoreCache.mockResolvedValue("cache-key"); + let installationReady = false; + mockedFs.existsSync.mockImplementation((filePath) => { + const value = String(filePath); + if ( + value.includes("/bin/armflang") || + value.includes("/bin/armclang") || + value.includes("libamath") + ) { + return installationReady; + } + return true; + }); + mockedExec.mockImplementation(async (command, args, options) => { + if ( + command === "sudo" && + args?.[0] === "apt-get" && + args.includes("install") && + args.some((arg) => arg.startsWith("arm-toolchain-for-linux=")) + ) { + installationReady = true; + } + if (command.endsWith("/armflang") && args?.[0] === "--version") { + options?.listeners?.stdout?.(Buffer.from("ArmFlang 22.1.0")); + } + return 0; + }); + + await installDebian(inputs); + + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("binaries were incomplete"), + ); + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining([ + expect.stringContaining("arm-toolchains-repository"), + ]), + ); + }); + + it("tolerates unrelated update failures and retries Arm repo failures", async () => { + const timeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback: Parameters[0]) => { + callback(); + return 0 as unknown as NodeJS.Timeout; + }); + let updateAttempts = 0; + mockedExec.mockImplementation(async (command, args, options) => { + if (command.endsWith("/armflang") && args?.[0] === "--version") { + options?.listeners?.stdout?.(Buffer.from("ArmFlang 22.1.0")); + } + if ( + command === "sudo" && + args?.[0] === "apt-get" && + args.includes("update") + ) { + updateAttempts++; + if (updateAttempts === 1) { + // Best-effort update (before the Arm repo is configured) fails on + // an unrelated, pre-baked repository: tolerated without retry. + options?.listeners?.stderr?.( + Buffer.from( + "E: Failed to fetch https://packages.microsoft.com/ubuntu/24.04/prod/dists/noble/InRelease 403 Forbidden\n", + ), + ); + return 100; + } + if (updateAttempts === 2) { + // Arm-repository update fails transiently: must be retried. + options?.listeners?.stderr?.( + Buffer.from( + "E: Failed to fetch https://developer.arm.com/packages/arm-toolchains/ubuntu/dists/noble/InRelease Connection timed out\n", + ), + ); + return 100; + } + } + return 0; + }); + + try { + await installDebian(inputs); + } finally { + timeoutSpy.mockRestore(); + } + + // 1 tolerated best-effort attempt + 1 failed Arm attempt + 1 successful retry. + expect(updateAttempts).toBe(3); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("Retrying in 10 seconds"), + ); + }); + + it("fails when the Arm repository stays unreachable", async () => { + const timeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback: Parameters[0]) => { + callback(); + return 0 as unknown as NodeJS.Timeout; + }); + mockedExec.mockImplementation(async (command, args, options) => { + if (command.endsWith("/armflang") && args?.[0] === "--version") { + options?.listeners?.stdout?.(Buffer.from("ArmFlang 22.1.0")); + } + if ( + command === "sudo" && + args?.[0] === "apt-get" && + args.includes("update") + ) { + options?.listeners?.stderr?.( + Buffer.from( + "E: Failed to fetch https://developer.arm.com/packages/arm-toolchains/ubuntu/dists/noble/InRelease Connection timed out\n", + ), + ); + return 100; + } + return 0; + }); + + try { + await expect(installDebian(inputs)).rejects.toThrow( + /apt-get update failed after 3 attempts/, + ); + } finally { + timeoutSpy.mockRestore(); + } + }); + + it("exports absolute compiler paths", async () => { + const result = await installDebian(inputs); + + expect(core.addPath).toHaveBeenCalledWith( + "/opt/arm/arm-toolchain-for-linux/bin", + ); + expect(result).toEqual({ + version: "ArmFlang 22.1.0", + fc: "/opt/arm/arm-toolchain-for-linux/bin/armflang", + cc: "/opt/arm/arm-toolchain-for-linux/bin/armclang", + cxx: "/opt/arm/arm-toolchain-for-linux/bin/armclang++", + }); + }); + + it("rejects x64", async () => { + await expect(installDebian({ ...inputs, arch: Arch.X64 })).rejects.toThrow( + "No supported versions found for armflang on linux (x64)", + ); + }); + + it("fails when the requested package version is absent", async () => { + mockedGetExecOutput.mockImplementation(async (command) => { + if (command === "sha256sum") { + return { + stdout: `${"a".repeat(64)} repository.deb\n`, + stderr: "", + exitCode: 0, + }; + } + return { + stdout: " arm-toolchain-for-linux | 21.1-81 | repo\n", + stderr: "", + exitCode: 0, + }; + }); + + await expect(installDebian(inputs)).rejects.toThrow( + "ArmFlang 22.1 is not available", + ); + }); + + it("rejects a repository package with an invalid checksum", async () => { + (mockedFs.readFileSync as jest.Mock).mockImplementation( + (_filePath: string, encoding?: string) => + encoding === "utf8" + ? "Package: arm-toolchains-repository\n" + + "Filename: pool/arm-toolchains-repository_2-2~noble_all.deb\n" + + `SHA256: ${"b".repeat(64)}\n` + : Buffer.from("repository package"), + ); + + await expect(installDebian(inputs)).rejects.toThrow( + "Checksum verification failed", + ); + expect(mockedExec).not.toHaveBeenCalledWith( + "sudo", + expect.arrayContaining(["dpkg", "-i"]), + ); + }); +}); diff --git a/__tests__/installers/flang/darwin.test.ts b/__tests__/installers/flang/darwin.test.ts new file mode 100644 index 00000000..9f10ca58 --- /dev/null +++ b/__tests__/installers/flang/darwin.test.ts @@ -0,0 +1,304 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as tc from "@actions/tool-cache"; +import * as fs from "fs"; +import { installDarwin } from "../../../src/installers/flang/darwin"; +import { + Arch, + Compiler, + OS, + Msystem, + type Inputs, + LATEST, +} from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("../../../src/verify_download"); +jest.mock("@actions/tool-cache", () => ({ + find: jest.fn(), + downloadTool: jest.fn(), + extractTar: jest.fn(), + extractZip: jest.fn(), + cacheDir: jest.fn(), +})); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + existsSync: jest.fn(), + symlinkSync: jest.fn(), +})); + +describe("installDarwin (Flang)", () => { + beforeAll(() => { + global.fetch = jest.fn().mockImplementation(async (input: string | URL) => { + const url = String(input); + if (url.includes("/releases?")) { + const page = new URL(url).searchParams.get("page") ?? "1"; + return { + ok: true, + status: 200, + json: async () => + page === "1" + ? [{ tag_name: "llvmorg-19.1.7", prerelease: false }] + : [], + }; + } + return { + ok: true, + status: 200, + json: async () => ({ + assets: [ + { + name: "LLVM-19.1.7-macOS-X64.tar.xz", + digest: `sha256:${"a".repeat(64)}`, + }, + ], + }), + }; + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + const mockedExec = exec.exec as jest.MockedFunction; + const mockedTc = tc as jest.Mocked; + const mockedFs = fs as jest.Mocked; + const mockedExportVariable = core.exportVariable as jest.MockedFunction< + typeof core.exportVariable + >; + + const baseInputs: Inputs = { + compiler: Compiler.Flang, + version: LATEST, + os: OS.MacOS, + osVersion: "13", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockedFs.existsSync.mockReturnValue(true); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine.includes("flang") && args?.[0] === "--version") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("flang version 18.1.0")); + } + } + if (commandLine === "brew" && args?.[0] === "--prefix") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("/usr/local")); + } + } + if (commandLine === "xcrun" && args?.[0] === "--show-sdk-path") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("/path/to/SDK")); + } + } + return 0; + }); + }); + + it("installs via Homebrew when version is LATEST", async () => { + await installDarwin(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith( + "brew", + ["install", "--skip-post-install", "flang"], + expect.objectContaining({ + ignoreReturnCode: true, + env: expect.objectContaining({ HOMEBREW_NO_AUTO_UPDATE: "1" }), + }), + ); + }); + + it("rejects concrete LLVM 23 on macOS (upstream publishes no macOS release binaries)", async () => { + const inputs = { ...baseInputs, version: "23" }; + + await expect(installDarwin(inputs)).rejects.toThrow( + /flang 23 is not supported on darwin/, + ); + }); + + it("downloads from GitHub when version is specified", async () => { + const inputs = { ...baseInputs, version: "19" }; + mockedTc.find.mockReturnValue(""); + mockedTc.downloadTool.mockResolvedValue("/tmp/llvm.tar.xz"); + mockedTc.extractTar.mockResolvedValue("/tmp/llvm-extracted"); + mockedTc.cacheDir.mockResolvedValue("/cache/llvm"); + + await installDarwin(inputs); + + expect(mockedTc.downloadTool).toHaveBeenCalledWith( + expect.stringContaining("github.com/llvm/llvm-project/releases/download"), + expect.stringContaining("LLVM-19.1.7"), + ); + expect(mockedTc.extractTar).toHaveBeenCalled(); + expect(core.addPath).toHaveBeenCalledWith(expect.stringContaining("bin")); + }); + + it("creates an unversioned flang symlink when only flang-new is present (LLVM < 20)", async () => { + const inputs = { ...baseInputs, version: "19" }; + mockedTc.find.mockReturnValue(""); + mockedTc.downloadTool.mockResolvedValue("/tmp/llvm19.tar.xz"); + mockedTc.extractTar.mockResolvedValue("/tmp/llvm19-extracted"); + mockedTc.cacheDir.mockResolvedValue("/cache/llvm19"); + + // Simulate an LLVM 19 archive that ships only `flang-new` (no bare `flang`). + const binDir = "/cache/llvm19/bin"; + mockedFs.existsSync.mockImplementation((p: fs.PathLike) => { + if (String(p) === `${binDir}/flang`) return false; + return true; // flang-new, clang, clang++ all present + }); + + await installDarwin(inputs); + + const flangNew = `${binDir}/flang-new`; + expect(fs.symlinkSync).toHaveBeenCalledWith(flangNew, `${binDir}/flang`); + }); + + it("exports environment variables", async () => { + await installDarwin(baseInputs); + + expect(mockedExportVariable).toHaveBeenCalledWith( + "SDKROOT", + "/path/to/SDK", + ); + }); + + it("resolves and returns the installed version", async () => { + const result = await installDarwin(baseInputs); + expect(result).toEqual({ + version: "flang version 18.1.0", + fc: "/usr/local/opt/flang/bin/flang", + cc: "/usr/local/opt/llvm/bin/clang", + cxx: "/usr/local/opt/llvm/bin/clang++", + }); + }); + + describe("GitHub download retry", () => { + afterEach(() => { + jest.useRealTimers(); + }); + + it("retries a failed download and succeeds", async () => { + const inputs = { ...baseInputs, version: "19" }; + mockedTc.find.mockReturnValue(""); + mockedTc.downloadTool + .mockRejectedValueOnce(new Error("connection reset")) + .mockResolvedValue("/tmp/llvm.tar.xz"); + mockedTc.extractTar.mockResolvedValue("/tmp/llvm-extracted"); + mockedTc.cacheDir.mockResolvedValue("/cache/llvm"); + + jest.useFakeTimers(); + const installPromise = installDarwin(inputs); + + for (let i = 0; i < 10; i++) await Promise.resolve(); + expect(mockedTc.downloadTool).toHaveBeenCalledTimes(1); + + // Advance past the 20s backoff after the first failure. + jest.advanceTimersByTime(20_000); + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await installPromise; + + expect(mockedTc.downloadTool).toHaveBeenCalledTimes(2); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("Download failed (attempt 1/3)"), + ); + expect(mockedTc.extractTar).toHaveBeenCalled(); + }); + + it("gives up after three attempts and propagates the last error", async () => { + const inputs = { ...baseInputs, version: "19" }; + mockedTc.find.mockReturnValue(""); + mockedTc.downloadTool.mockRejectedValue(new Error("network down")); + + jest.useFakeTimers(); + const installPromise = installDarwin(inputs); + + for (let i = 0; i < 10; i++) await Promise.resolve(); + jest.advanceTimersByTime(20_000); // backoff after attempt 1 + for (let i = 0; i < 10; i++) await Promise.resolve(); + jest.advanceTimersByTime(40_000); // backoff after attempt 2 + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await expect(installPromise).rejects.toThrow("network down"); + + expect(mockedTc.downloadTool).toHaveBeenCalledTimes(3); + expect(mockedTc.extractTar).not.toHaveBeenCalled(); + }); + }); + + describe("brew install retry", () => { + afterEach(() => { + jest.useRealTimers(); + }); + + it("retries a failed brew install and succeeds", async () => { + let brewAttempts = 0; + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "brew" && args?.[0] === "install") { + brewAttempts++; + return brewAttempts === 1 ? 1 : 0; + } + if (commandLine.includes("flang") && args?.[0] === "--version") { + options?.listeners?.stdout?.(Buffer.from("flang version 18.1.0")); + } + if (commandLine === "brew" && args?.[0] === "--prefix") { + options?.listeners?.stdout?.(Buffer.from("/usr/local")); + } + if (commandLine === "xcrun" && args?.[0] === "--show-sdk-path") { + options?.listeners?.stdout?.(Buffer.from("/path/to/SDK")); + } + return 0; + }); + + jest.useFakeTimers(); + const installPromise = installDarwin(baseInputs); + + for (let i = 0; i < 10; i++) await Promise.resolve(); + expect(brewAttempts).toBe(1); + + // Advance past the 15s backoff after the first failure. + jest.advanceTimersByTime(15_000); + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await installPromise; + + expect(brewAttempts).toBe(2); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("brew install flang failed (attempt 1/3)"), + ); + }); + + it("gives up after three failed brew install attempts", async () => { + mockedExec.mockImplementation(async (commandLine) => { + if (commandLine === "brew") return 1; + return 0; + }); + + jest.useFakeTimers(); + const installPromise = installDarwin(baseInputs); + + for (let i = 0; i < 10; i++) await Promise.resolve(); + jest.advanceTimersByTime(15_000); // backoff after attempt 1 + for (let i = 0; i < 10; i++) await Promise.resolve(); + jest.advanceTimersByTime(30_000); // backoff after attempt 2 + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await expect(installPromise).rejects.toThrow( + "brew install flang failed after 3 attempts.", + ); + }); + }); +}); diff --git a/__tests__/installers/flang/debian.test.ts b/__tests__/installers/flang/debian.test.ts new file mode 100644 index 00000000..bda2ea9f --- /dev/null +++ b/__tests__/installers/flang/debian.test.ts @@ -0,0 +1,403 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as fs from "fs"; +import { installDebian } from "../../../src/installers/flang/debian"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core", () => ({ + info: jest.fn(), + addPath: jest.fn(), + exportVariable: jest.fn((name, value) => { + process.env[name] = value; + }), + warning: jest.fn(), +})); +jest.mock("@actions/exec"); +jest.mock("../../../src/verify_download"); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + existsSync: jest.fn(), +})); + +describe("installDebian (Flang)", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedFs = fs as jest.Mocked; + const mockedExportVariable = core.exportVariable as jest.MockedFunction< + typeof core.exportVariable + >; + + const baseInputs: Inputs = { + compiler: Compiler.Flang, + version: "18", + os: OS.Linux, + osVersion: "22.04", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + const originalEnv = process.env; + + beforeEach(() => { + jest.clearAllMocks(); + process.env = { ...originalEnv }; + mockedFs.existsSync.mockReturnValue(true); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if ( + (commandLine.startsWith("flang-new") || + commandLine.startsWith("flang")) && + args?.[0] === "--version" + ) { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("flang version 18.1.0")); + } + } + return 0; + }); + }); + + afterAll(() => { + process.env = originalEnv; + }); + + it("configures the explicit LLVM repository without executing llvm.sh", async () => { + await installDebian(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining([ + "-o", + expect.stringContaining("llvm-snapshot.gpg.key"), + "https://apt.llvm.org/llvm-snapshot.gpg.key", + ]), + ); + expect(mockedExec).not.toHaveBeenCalledWith( + "bash", + expect.arrayContaining([expect.stringContaining("llvm.sh")]), + ); + }); + + it("installs the correct flang package", async () => { + await installDebian(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + [ + "timeout", + "--signal=TERM", + "--kill-after=30s", + "15m", + "apt-get", + "install", + "-y", + "-o", + "Acquire::ForceIPv4=true", + "-o", + "Acquire::Retries=0", + "-o", + "Acquire::http::Timeout=10", + "-o", + "Acquire::https::Timeout=10", + "clang-18", + "flang-18", + "libomp-18-dev", + "libclang-rt-18-dev", + ], + expect.objectContaining({ ignoreReturnCode: true }), + ); + }); + + it("installs flang-23 from the noble repository with the flang binary name", async () => { + const inputs = { ...baseInputs, version: "23", osVersion: "24.04" }; + mockedExec.mockImplementation(async (commandLine, args, options) => { + if ( + commandLine === "flang-23" && + args?.[0] === "--version" && + options?.listeners?.stdout + ) { + options.listeners.stdout(Buffer.from("flang version 23.1.0")); + } + return 0; + }); + + const result = await installDebian(inputs); + + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + [ + "timeout", + "--signal=TERM", + "--kill-after=30s", + "15m", + "apt-get", + "install", + "-y", + "-o", + "Acquire::ForceIPv4=true", + "-o", + "Acquire::Retries=0", + "-o", + "Acquire::http::Timeout=10", + "-o", + "Acquire::https::Timeout=10", + "clang-23", + "flang-23", + "libomp-23-dev", + "libclang-rt-23-dev", + ], + expect.objectContaining({ ignoreReturnCode: true }), + ); + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "update-alternatives", + "--install", + "/usr/bin/flang", + "flang", + "/usr/lib/llvm-23/bin/flang", + "100", + ]); + expect(result.fc).toBe("flang-23"); + }); + + it("retries apt-get install after a transient failure", async () => { + // Avoid the real backoff sleep inside aptGetInstallWithRetry. + const timeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback) => { + if (typeof callback === "function") callback(); + return 0 as unknown as NodeJS.Timeout; + }); + + let installAttempts = 0; + mockedExec.mockImplementation(async (commandLine, args, options) => { + if ( + (commandLine.startsWith("flang-new") || + commandLine.startsWith("flang")) && + args?.[0] === "--version" + ) { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("flang version 18.1.0")); + } + } + if ( + commandLine === "sudo" && + args?.includes("apt-get") && + args?.includes("install") && + !args?.includes("--fix-broken") + ) { + installAttempts++; + // Fail the first install attempt like a dropped mirror connection. + if (installAttempts === 1) return 100; + } + return 0; + }); + + try { + const result = await installDebian(baseInputs); + expect(result.fc).toBe("flang-new-18"); + } finally { + timeoutSpy.mockRestore(); + } + + expect(installAttempts).toBe(2); // failed once, succeeded on retry + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("apt-get install failed (attempt 1/3)"), + ); + }); + + it("rejects LLVM 23+ on Ubuntu 22.04 with an actionable error", async () => { + const inputs = { ...baseInputs, version: "23", osVersion: "22.04" }; + + await expect(installDebian(inputs)).rejects.toThrow( + /no longer publishes LLVM 23\+ packages for jammy/, + ); + // The guard fires before any repository configuration or download. + expect(mockedExec).not.toHaveBeenCalled(); + }); + + it("retries apt-get update after a transient failure", async () => { + // Avoid the real backoff sleep inside aptGetUpdateWithRetry. + const timeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback) => { + if (typeof callback === "function") callback(); + return 0 as unknown as NodeJS.Timeout; + }); + + let updateAttempts = 0; + // flang's update retry is exit-code driven (ignoreReturnCode: true + + // exitCode === 0 check), so the mock returns a non-zero code to fail, + // rather than throwing (which is the gfortran/aptGetUpdateWithRetry style). + mockedExec.mockImplementation(async (commandLine, args, options) => { + if ( + (commandLine.startsWith("flang-new") || + commandLine.startsWith("flang")) && + args?.[0] === "--version" + ) { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("flang version 18.1.0")); + } + } + if ( + commandLine === "sudo" && + args?.includes("apt-get") && + args?.includes("update") + ) { + updateAttempts++; + if (updateAttempts === 1) return 1; // fail the first update attempt + } + return 0; + }); + + try { + const result = await installDebian(baseInputs); + expect(result.fc).toBe("flang-new-18"); + } finally { + timeoutSpy.mockRestore(); + } + + expect(updateAttempts).toBe(2); // failed once, succeeded on retry + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("apt-get update failed (attempt 1/3)"), + ); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + [ + "timeout", + "--signal=TERM", + "--kill-after=10s", + "5m", + "apt-get", + "update", + "-y", + // Scoped to the LLVM source list; unrelated repositories in the + // runner image cannot fail the install. + "-o", + "Dir::Etc::SourceList=sources.list.d/llvm.list", + "-o", + "Dir::Etc::SourceParts=-", + "-o", + "Acquire::ForceIPv4=true", + "-o", + "Acquire::Retries=0", + "-o", + "Acquire::http::Timeout=10", + "-o", + "Acquire::https::Timeout=10", + ], + { ignoreReturnCode: true }, + ); + }); + + it("does not write global APT settings or rewrite Ubuntu mirrors", async () => { + await installDebian(baseInputs); + + expect(mockedExec).not.toHaveBeenCalledWith( + "sudo", + expect.arrayContaining([ + expect.stringMatching(/apt\.conf\.d|ubuntu\.sources|apt-mirrors/), + ]), + ); + }); + + it("configures update-alternatives for flang-new (15 <= major < 20)", async () => { + const inputs = { ...baseInputs, version: "17" }; + await installDebian(inputs); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "update-alternatives", + "--install", + "/usr/bin/flang", + "flang", + "/usr/lib/llvm-17/bin/flang-new", + "100", + ]); + }); + + it("configures update-alternatives for flang (major >= 20)", async () => { + const inputs = { ...baseInputs, version: "20" }; + await installDebian(inputs); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "update-alternatives", + "--install", + "/usr/bin/flang", + "flang", + "/usr/lib/llvm-20/bin/flang", + "100", + ]); + }); + + it("exports environment variables and adds to PATH", async () => { + await installDebian(baseInputs); + + expect(core.addPath).toHaveBeenCalledWith("/usr/lib/llvm-18/bin"); + expect(mockedExportVariable).toHaveBeenCalledWith( + "LIBRARY_PATH", + "/usr/lib/llvm-18/lib", + ); + }); + + it("exports flang-20 for FC when version is 20", async () => { + const inputs = { ...baseInputs, version: "20" }; + await installDebian(inputs); + }); + + it("resolves and returns the installed version", async () => { + const result = await installDebian(baseInputs); + expect(result).toEqual({ + version: "flang version 18.1.0", + fc: "flang-new-18", + cc: "/usr/lib/llvm-18/bin/clang", + cxx: "/usr/lib/llvm-18/bin/clang++", + }); + }); + + it("falls back to versioned symlink if primary binary is missing", async () => { + mockedFs.existsSync.mockImplementation((path) => { + if (path === "/usr/lib/llvm-18/bin/flang-new") return false; + if (path === "/usr/bin/flang-new-18") return true; + return true; + }); + + await installDebian(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "update-alternatives", + "--install", + "/usr/bin/flang", + "flang", + "/usr/bin/flang-new-18", + "100", + ]); + }); + + it("skips update-alternatives if binary is already /usr/bin/flang", async () => { + mockedFs.existsSync.mockImplementation((path) => { + if (path === "/usr/lib/llvm-18/bin/flang-new") return false; + if (path === "/usr/bin/flang-new-18") return false; + if (path === "/usr/bin/flang-new-18") return false; + if (path === "/usr/bin/flang") return true; + return true; + }); + + await installDebian(baseInputs); + + expect(mockedExec).not.toHaveBeenCalledWith("sudo", [ + "update-alternatives", + "--install", + "/usr/bin/flang", + "flang", + "/usr/bin/flang", + "100", + ]); + }); + + it("throws error if no flang binary is found", async () => { + mockedFs.existsSync.mockReturnValue(false); + + await expect(installDebian(baseInputs)).rejects.toThrow( + /Flang binary not found/, + ); + }); +}); diff --git a/__tests__/installers/flang/win32.test.ts b/__tests__/installers/flang/win32.test.ts new file mode 100644 index 00000000..b14daf8d --- /dev/null +++ b/__tests__/installers/flang/win32.test.ts @@ -0,0 +1,359 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as tc from "@actions/tool-cache"; +import * as fs from "fs"; +import { installWin32 } from "../../../src/installers/flang/win32"; +import { setupMSYS2 } from "../../../src/setup_msys2"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("@actions/tool-cache"); +jest.mock("../../../src/setup_msys2"); +jest.mock("../../../src/verify_download"); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + existsSync: jest.fn(), + mkdirSync: jest.fn(), + readdirSync: jest.fn(), +})); + +describe("installWin32 (Flang)", () => { + beforeAll(() => { + global.fetch = jest.fn().mockImplementation(async (input: string | URL) => { + const url = String(input); + if (url.includes("/releases?")) { + // Paginated list: only page 1 carries data; later pages are empty + // so asset-aware resolution terminates instead of re-scanning. + const page = new URL(url).searchParams.get("page") ?? "1"; + return { + ok: true, + status: 200, + json: async () => + page === "1" + ? [ + { tag_name: "llvmorg-23.1.0", prerelease: false }, + { tag_name: "llvmorg-22.1.0", prerelease: false }, + ] + : [], + }; + } + return { + ok: true, + status: 200, + json: async () => + url.includes("llvmorg-23.1.0") + ? { + assets: [ + { + name: "LLVM-23.1.0-win64.msi", + digest: `sha256:${"a".repeat(64)}`, + }, + { + name: "LLVM-23.1.0-woa64.msi", + digest: `sha256:${"a".repeat(64)}`, + }, + ], + } + : { + assets: [ + { + name: "LLVM-22.1.0-win64.exe", + digest: `sha256:${"a".repeat(64)}`, + }, + { + name: "LLVM-22.1.0-woa64.exe", + digest: `sha256:${"a".repeat(64)}`, + }, + ], + }, + }; + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + const mockedExec = exec.exec as jest.MockedFunction; + const mockedTc = tc as jest.Mocked; + const mockedFs = fs as jest.Mocked; + const mockedSetupMSYS2 = setupMSYS2 as jest.MockedFunction; + const mockedExportVariable = core.exportVariable as jest.MockedFunction< + typeof core.exportVariable + >; + + const baseInputs: Inputs = { + compiler: Compiler.Flang, + version: "22", + os: OS.Windows, + osVersion: "2022", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockedFs.existsSync.mockReturnValue(true); + mockedFs.readdirSync.mockReturnValue(["14.38.33130" as any]); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine.includes("flang") && args?.[0] === "--version") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("flang version 22.0.0")); + } + } + if (commandLine.includes("vswhere.exe")) { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("C:\\VS")); + } + } + return 0; + }); + }); + + describe("Native", () => { + it("downloads and extracts LLVM installer", async () => { + mockedTc.find.mockReturnValue(""); + mockedTc.downloadTool.mockResolvedValue("C:\\Temp\\llvm.exe"); + mockedTc.extractZip.mockResolvedValue("C:\\Temp\\extracted"); + mockedTc.cacheDir.mockResolvedValue("C:\\Cache\\flang"); + + const result = await installWin32(baseInputs); + + expect(result.fc).toEqual(expect.stringContaining("flang.exe")); + expect(result.cc).toEqual(expect.stringContaining("clang.exe")); + expect(result.cxx).toEqual(expect.stringContaining("clang++.exe")); + expect(mockedTc.downloadTool).toHaveBeenCalled(); + expect(mockedExec).toHaveBeenCalledWith( + expect.stringContaining("7z.exe"), + expect.arrayContaining(["x", "C:\\Temp\\llvm.exe"]), + ); + expect(mockedTc.cacheDir).toHaveBeenCalled(); + }); + + it("retries a failed download and succeeds", async () => { + mockedTc.find.mockReturnValue(""); + mockedTc.downloadTool + .mockRejectedValueOnce(new Error("Unexpected HTTP response: 504")) + .mockResolvedValue("C:\\Temp\\llvm.exe"); + mockedTc.extractZip.mockResolvedValue("C:\\Temp\\extracted"); + mockedTc.cacheDir.mockResolvedValue("C:\\Cache\\flang"); + + jest.useFakeTimers(); + try { + const installPromise = installWin32(baseInputs); + + for (let i = 0; i < 10; i++) await Promise.resolve(); + expect(mockedTc.downloadTool).toHaveBeenCalledTimes(1); + + // Advance past the 20s backoff after the first failure. + jest.advanceTimersByTime(20_000); + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await installPromise; + } finally { + jest.useRealTimers(); + } + + expect(mockedTc.downloadTool).toHaveBeenCalledTimes(2); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("Download failed (attempt 1/3)"), + ); + }); + + it("extracts the LLVM 23 MSI with an msiexec administrative install", async () => { + const inputs = { ...baseInputs, version: "23" }; + mockedTc.find.mockReturnValue(""); + // tc.downloadTool saves to an extensionless GUID path when no + // destination is passed — extraction must not dispatch on the extension. + mockedTc.downloadTool.mockResolvedValue( + "D:\\a\\_temp\\e50d54fe-ad02-446d-9587-48dee931e0c6", + ); + mockedTc.cacheDir.mockResolvedValue("C:\\Cache\\flang23"); + + const result = await installWin32(inputs); + + // The installer is downloaded under its real (extension-bearing) name. + expect(mockedTc.downloadTool).toHaveBeenCalledWith( + expect.stringContaining("LLVM-23.1.0-win64.msi"), + expect.stringContaining("LLVM-23.1.0-win64.msi"), + ); + expect(mockedExec).toHaveBeenCalledWith("msiexec", [ + "/a", + "D:\\a\\_temp\\e50d54fe-ad02-446d-9587-48dee931e0c6", + "/qn", + expect.stringContaining("TARGETDIR="), + ]); + // The WiX layout nests the install tree under a single LLVM directory, + // which is what gets cached so bin/ sits at the tool cache root. + expect(mockedTc.cacheDir).toHaveBeenCalledWith( + expect.stringMatching(/LLVM$/), + "flang-verified", + "23.1.0", + Arch.X64, + ); + expect(result.fc).toEqual(expect.stringContaining("flang.exe")); + }); + + it("fails loudly when the msiexec extraction misses the bin directory", async () => { + const inputs = { ...baseInputs, version: "23" }; + mockedTc.find.mockReturnValue(""); + mockedTc.downloadTool.mockResolvedValue("C:\\Temp\\llvm.msi"); + mockedFs.existsSync.mockImplementation( + (p) => !/LLVM[/\\]bin$/.test(String(p)), + ); + + await expect(installWin32(inputs)).rejects.toThrow(/expected layout/); + expect(mockedTc.cacheDir).not.toHaveBeenCalled(); + }); + + it("sets up MSVC libs and exports variables", async () => { + mockedTc.find.mockReturnValue("C:\\Cache\\flang"); + + await installWin32(baseInputs); + + expect(mockedExportVariable).toHaveBeenCalledWith( + "LIB", + expect.stringContaining("Cache"), + ); + }); + + it("resolves the woa64 asset suffix and Arch.ARM64 tool-cache key on Windows on Arm", async () => { + const inputs = { ...baseInputs, arch: Arch.ARM64, version: "22" }; + mockedTc.find.mockReturnValue(""); + mockedTc.downloadTool.mockResolvedValue("C:\\Temp\\llvm-woa64.exe"); + mockedTc.cacheDir.mockResolvedValue("C:\\Cache\\flang-arm64"); + + const result = await installWin32(inputs); + + expect(mockedTc.downloadTool).toHaveBeenCalledWith( + expect.stringContaining("LLVM-22.1.0-woa64.exe"), + expect.stringContaining("LLVM-22.1.0-woa64.exe"), + ); + expect(mockedTc.cacheDir).toHaveBeenCalledWith( + expect.any(String), + "flang-verified", + "22.1.0", + Arch.ARM64, + ); + expect(result.fc).toEqual(expect.stringContaining("flang.exe")); + }); + + it("falls back to an older patch when the newest lacks the woa64 asset", async () => { + // Regression test: llvmorg-23.1.2 ships win64.msi but no woa64.msi, + // while 23.1.1 ships both. A bare "23" on ARM64 must resolve to 23.1.1. + const inputs = { ...baseInputs, arch: Arch.ARM64, version: "23" }; + const fetchMock = global.fetch as jest.Mock; + const originalImpl = fetchMock.getMockImplementation(); + const digest = `sha256:${"b".repeat(64)}`; + fetchMock.mockImplementation(async (input: string | URL) => { + const url = String(input); + if (url.includes("/releases?")) { + return { + ok: true, + status: 200, + json: async () => [ + { + tag_name: "llvmorg-23.1.2", + prerelease: false, + assets: [{ name: "LLVM-23.1.2-win64.msi" }], + }, + { + tag_name: "llvmorg-23.1.1", + prerelease: false, + assets: [ + { name: "LLVM-23.1.1-win64.msi" }, + { name: "LLVM-23.1.1-woa64.msi" }, + ], + }, + ], + }; + } + if (url.includes("llvmorg-23.1.1")) { + return { + ok: true, + status: 200, + json: async () => ({ + assets: [ + { name: "LLVM-23.1.1-win64.msi", digest }, + { name: "LLVM-23.1.1-woa64.msi", digest }, + ], + }), + }; + } + return { + ok: true, + status: 200, + json: async () => ({ + assets: [{ name: "LLVM-23.1.2-win64.msi", digest }], + }), + }; + }); + + try { + mockedTc.find.mockReturnValue(""); + mockedTc.downloadTool.mockResolvedValue("C:\\Temp\\llvm-woa64.msi"); + mockedTc.cacheDir.mockResolvedValue("C:\\Cache\\flang-arm64"); + + const result = await installWin32(inputs); + + expect(mockedTc.downloadTool).toHaveBeenCalledWith( + expect.stringContaining("LLVM-23.1.1-woa64.msi"), + expect.stringContaining("LLVM-23.1.1-woa64.msi"), + ); + expect(mockedTc.cacheDir).toHaveBeenCalledWith( + expect.any(String), + "flang-verified", + "23.1.1", + Arch.ARM64, + ); + expect(result.fc).toEqual(expect.stringContaining("flang.exe")); + } finally { + fetchMock.mockImplementation(originalImpl); + } + }); + }); + + describe("MSYS2", () => { + it("calls setupMSYS2 and exports variables", async () => { + const inputs = { + ...baseInputs, + version: "latest", + msystem: Msystem.UCRT64, + }; + await installWin32(inputs); + + // llvm-openmp is required for -fopenmp; the flang package only lists it + // as an optional dependency. + expect(mockedSetupMSYS2).toHaveBeenCalledWith(Msystem.UCRT64, [ + "flang", + "llvm-openmp", + ]); + }); + + it("calls setupMSYS2 with Clang64 and exports variables", async () => { + const inputs = { + ...baseInputs, + version: "latest", + msystem: Msystem.Clang64, + }; + await installWin32(inputs); + + expect(mockedSetupMSYS2).toHaveBeenCalledWith(Msystem.Clang64, [ + "flang", + "llvm-openmp", + ]); + expect(core.exportVariable).toHaveBeenCalledWith( + "WINDOWS_ENV", + Msystem.Clang64, + ); + }); + }); +}); diff --git a/__tests__/installers/gfortran/darwin.test.ts b/__tests__/installers/gfortran/darwin.test.ts new file mode 100644 index 00000000..0eb3009c --- /dev/null +++ b/__tests__/installers/gfortran/darwin.test.ts @@ -0,0 +1,148 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import { installDarwin } from "../../../src/installers/gfortran/darwin"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); + +describe("installDarwin (gfortran)", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedExportVariable = core.exportVariable as jest.MockedFunction< + typeof core.exportVariable + >; + + const baseInputs: Inputs = { + compiler: Compiler.GFortran, + version: "14", + os: OS.MacOS, + osVersion: "13", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine.includes("gfortran-14") && args?.[0] === "--version") { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from("GNU Fortran (Homebrew GCC 14.1.0) 14.1.0"), + ); + } + } + if (commandLine === "brew" && args?.[0] === "list") { + return 1; // Not installed + } + if (commandLine === "brew" && args?.[0] === "--prefix") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("/usr/local")); + } + } + if (commandLine === "xcrun" && args?.[0] === "--show-sdk-path") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("/path/to/SDK")); + } + } + if (commandLine === "bash" && args?.[0] === "-c") { + options?.listeners?.stdout?.( + Buffer.from("/usr/local/Cellar/gcc@14/14.1.0/lib/gcc/14"), + ); + } + return 0; + }); + }); + + it("installs gcc via Homebrew if missing", async () => { + await installDarwin(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith( + "brew", + ["install", "--skip-post-install", "gcc@14"], + expect.objectContaining({ + ignoreReturnCode: true, + env: expect.objectContaining({ HOMEBREW_NO_AUTO_UPDATE: "1" }), + }), + ); + // Versioned Homebrew formulae don't create unversioned drivers; the + // action must symlink gfortran/gcc/g++ -> for downstream use. + expect(mockedExec).toHaveBeenCalledWith("ln", [ + "-sf", + "/usr/local/bin/gfortran-14", + "/usr/local/bin/gfortran", + ]); + expect(mockedExec).toHaveBeenCalledWith("ln", [ + "-sf", + "/usr/local/bin/gcc-14", + "/usr/local/bin/gcc", + ]); + expect(mockedExec).toHaveBeenCalledWith("ln", [ + "-sf", + "/usr/local/bin/g++-14", + "/usr/local/bin/g++", + ]); + expect(mockedExec).not.toHaveBeenCalledWith("bash", [ + "-c", + expect.stringContaining("/usr/local/lib"), + ]); + }); + + it("skips install if already present", async () => { + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "brew" && args?.[0] === "list") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("14.1.0")); + } + return 0; + } + if (commandLine === "brew" && args?.[0] === "--prefix") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("/usr/local")); + } + } + if (commandLine === "bash" && args?.[0] === "-c") { + options?.listeners?.stdout?.( + Buffer.from("/usr/local/Cellar/gcc@14/14.1.0/lib/gcc/14"), + ); + } + if (commandLine.includes("gfortran-14") && args?.[0] === "--version") { + options?.listeners?.stdout?.( + Buffer.from("GNU Fortran (Homebrew GCC 14.1.0) 14.1.0"), + ); + } + return 0; + }); + + await installDarwin(baseInputs); + expect(mockedExec).not.toHaveBeenCalledWith("brew", ["install", "gcc@14"]); + }); + + it("exports environment variables and SDKROOT", async () => { + await installDarwin(baseInputs); + + expect(mockedExportVariable).toHaveBeenCalledWith( + "SDKROOT", + "/path/to/SDK", + ); + expect(mockedExportVariable).toHaveBeenCalledWith( + "DYLD_FALLBACK_LIBRARY_PATH", + expect.stringContaining("/lib/gcc/14"), + ); + expect(mockedExportVariable).toHaveBeenCalledWith( + "LIBRARY_PATH", + "/path/to/SDK/usr/lib", + ); + }); + + it("resolves and returns the installed version", async () => { + const result = await installDarwin(baseInputs); + expect(result).toMatchObject({ + fc: expect.stringContaining("gfortran-14"), + cc: expect.stringContaining("gcc-14"), + cxx: expect.stringContaining("g++-14"), + }); + expect(result.version).toContain("14.1.0"); + }); +}); diff --git a/__tests__/installers/gfortran/debian.test.ts b/__tests__/installers/gfortran/debian.test.ts new file mode 100644 index 00000000..e29e1014 --- /dev/null +++ b/__tests__/installers/gfortran/debian.test.ts @@ -0,0 +1,631 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as cache from "@actions/cache"; +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; +import { + installDebian, + needsPpa, +} from "../../../src/installers/gfortran/debian"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("@actions/cache"); + +describe("GFortran Debian Installer", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedCache = cache as jest.Mocked; + + const baseInputs: Inputs = { + compiler: Compiler.GFortran, + version: "14", + os: OS.Linux, + osVersion: "20.04.6", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + const testRunnerTemp = path.join(os.tmpdir(), "setup-fortran-gfortran-tests"); + const cacheDir = path.join( + testRunnerTemp, + "setup-fortran", + "apt", + "gfortran", + "20.04.6", + "x64", + "14", + ); + const cacheKey = "apt-gfortran-v2-20.04.6-x64-14"; + + beforeEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + process.env.RUNNER_TEMP = testRunnerTemp; + mockedCache.restoreCache.mockResolvedValue(undefined); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "gfortran" && args?.[0] === "--version") { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from("GNU Fortran (Ubuntu 14.2.0-1ubuntu2~22.04) 14.2.0"), + ); + } + } + return 0; + }); + }); + + afterAll(() => { + fs.rmSync(testRunnerTemp, { recursive: true, force: true }); + }); + + describe("needsPpa", () => { + it("returns true for version >= 15 on Ubuntu 24.04", () => { + expect(needsPpa("15", "24.04")).toBe(true); + expect(needsPpa("16", "24.04")).toBe(true); + }); + + it("returns false for version < 15 on Ubuntu 24.04", () => { + expect(needsPpa("14", "24.04")).toBe(false); + expect(needsPpa("13", "24.04")).toBe(false); + }); + + it("returns true for version >= 13 on Ubuntu 22.04", () => { + expect(needsPpa("13", "22.04")).toBe(true); + expect(needsPpa("14", "22.04")).toBe(true); + }); + + it("returns false for version < 13 on Ubuntu 22.04", () => { + expect(needsPpa("12", "22.04")).toBe(false); + expect(needsPpa("11", "22.04")).toBe(false); + }); + + it("returns true for other OS versions regardless of compiler version", () => { + expect(needsPpa("11", "20.04")).toBe(true); + expect(needsPpa("16", "20.04")).toBe(true); + expect(needsPpa("14", "debian-12")).toBe(true); + }); + }); + + describe("installDebian", () => { + it("adds PPA when needsPpa returns true", async () => { + const inputs = { ...baseInputs, version: "15", osVersion: "24.04" }; + await installDebian(inputs); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "add-apt-repository", + "--yes", + "--no-update", + "ppa:ubuntu-toolchain-r/test", + ]); + }); + + it("does not add PPA when needsPpa returns false", async () => { + const inputs = { ...baseInputs, version: "14", osVersion: "24.04" }; + await installDebian(inputs); + + expect(mockedExec).not.toHaveBeenCalledWith("sudo", [ + "add-apt-repository", + "--yes", + "--no-update", + "ppa:ubuntu-toolchain-r/test", + ]); + }); + + it("always updates apt and installs gfortran on cache miss", async () => { + const result = await installDebian(baseInputs); + + expect(result.fc).toBe("gfortran-14"); + expect(result.cc).toBe("gcc-14"); + expect(result.cxx).toBe("g++-14"); + expect(mockedCache.restoreCache).toHaveBeenCalledWith( + [cacheDir], + cacheKey, + ); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + [ + "timeout", + "--signal=TERM", + "--kill-after=10s", + "5m", + "apt-get", + "update", + "-y", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + ], + expect.objectContaining({ ignoreReturnCode: true }), + ); + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "timeout", + "--signal=TERM", + "--kill-after=30s", + "15m", + "apt-get", + "install", + "-y", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + "-o", + `Dir::Cache::archives=${cacheDir}`, + "gcc-14", + "g++-14", + "gfortran-14", + ]); + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "chown", + "-R", + os.userInfo().username, + cacheDir, + ]); + expect(mockedCache.saveCache).toHaveBeenCalledWith([cacheDir], cacheKey); + expect(fs.existsSync(path.join(cacheDir, "partial"))).toBe(false); + }); + + it("tolerates apt-get update failures in unrelated repositories", async () => { + // Simulates the packages.microsoft.com 403 outages seen on GitHub + // runner images: the toolchain repositories fetch fine, but an + // unrelated pre-baked repository fails the overall update. + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "gfortran" && args?.[0] === "--version") { + options?.listeners?.stdout?.( + Buffer.from("GNU Fortran (Ubuntu) 14.2.0"), + ); + } + if ( + commandLine === "sudo" && + args?.includes("apt-get") && + args?.includes("update") + ) { + options?.listeners?.stderr?.( + Buffer.from( + "E: Failed to fetch https://packages.microsoft.com/ubuntu/24.04/prod/dists/noble/InRelease 403 Forbidden\n", + ), + ); + return 100; + } + return 0; + }); + + const result = await installDebian(baseInputs); + + expect(result.fc).toBe("gfortran-14"); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("continuing with cached/stale package index"), + ); + }); + + it("retries apt-get update when the toolchain PPA cannot be fetched", async () => { + const timeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback) => { + if (typeof callback === "function") callback(); + return 0 as unknown as NodeJS.Timeout; + }); + + let updateAttempts = 0; + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "gfortran" && args?.[0] === "--version") { + options?.listeners?.stdout?.( + Buffer.from("GNU Fortran (Ubuntu) 14.2.0"), + ); + } + if ( + commandLine === "sudo" && + args?.includes("apt-get") && + args?.includes("update") + ) { + updateAttempts++; + if (updateAttempts === 1) { + options?.listeners?.stderr?.( + Buffer.from( + "E: Failed to fetch https://ppa.launchpadcontent.net/ubuntu-toolchain-r/test/ubuntu/dists/noble/InRelease 503 Service Unavailable\n", + ), + ); + return 100; + } + } + return 0; + }); + + try { + await installDebian(baseInputs); + } finally { + timeoutSpy.mockRestore(); + } + + expect(updateAttempts).toBe(2); // failed once, succeeded on retry + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("apt-get update failed (attempt 1/3)"), + ); + }); + + it("retries apt-get update when the toolchain PPA fails even on cache hit", async () => { + // Regression test for https://github.com/fortran-lang/setup-fortran/pull/245#issuecomment-5761437288: + // a transient 503 from ppa.launchpadcontent.net left the package index + // without gcc-16, and the old code skipped the update retry because of + // the cache hit, so both the cached (--no-download) and online installs + // failed with "Unable to locate package". + mockedCache.restoreCache.mockResolvedValue("hit"); + const timeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback) => { + if (typeof callback === "function") callback(); + return 0 as unknown as NodeJS.Timeout; + }); + + let updateAttempts = 0; + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "gfortran" && args?.[0] === "--version") { + options?.listeners?.stdout?.( + Buffer.from("GNU Fortran (Ubuntu) 16.0.0"), + ); + } + if ( + commandLine === "sudo" && + args?.includes("apt-get") && + args?.includes("update") + ) { + updateAttempts++; + if (updateAttempts === 1) { + options?.listeners?.stderr?.( + Buffer.from( + "W: Failed to fetch https://ppa.launchpadcontent.net/ubuntu-toolchain-r/test/ubuntu/dists/noble/main/binary-amd64/Packages 503 Service Unavailable\n", + ), + ); + return 100; + } + } + return 0; + }); + + try { + const inputs = { ...baseInputs, version: "16", osVersion: "24.04" }; + const result = await installDebian(inputs); + + expect(updateAttempts).toBe(2); // failed once, succeeded on retry + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("apt-get update failed (attempt 1/3)"), + ); + // The refreshed index lets the cached .debs resolve. + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "apt-get", + "install", + "-y", + "--no-download", + "-o", + expect.stringContaining("Dir::Cache::archives="), + "gcc-16", + "g++-16", + "gfortran-16", + ]); + expect(result.fc).toBe("gfortran-16"); + } finally { + timeoutSpy.mockRestore(); + } + }); + + it("retries update when the PPA warning occurs despite exit code 0", async () => { + const timeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback) => { + if (typeof callback === "function") callback(); + return 0 as unknown as NodeJS.Timeout; + }); + + let updateAttempts = 0; + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "gfortran" && args?.[0] === "--version") { + options?.listeners?.stdout?.( + Buffer.from("GNU Fortran (Ubuntu) 16.0.0"), + ); + } + if ( + commandLine === "sudo" && + args?.includes("apt-get") && + args?.includes("update") + ) { + updateAttempts++; + if (updateAttempts === 1) { + options?.listeners?.stderr?.( + Buffer.from( + "W: Failed to fetch https://ppa.launchpadcontent.net/ubuntu-toolchain-r/test/ubuntu/dists/noble/InRelease 503 Service Unavailable\n", + ), + ); + return 0; + } + } + return 0; + }); + + try { + const inputs = { ...baseInputs, version: "16", osVersion: "24.04" }; + const result = await installDebian(inputs); + + expect(updateAttempts).toBe(2); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("apt-get update failed (attempt 1/3)"), + ); + expect(result.fc).toBe("gfortran-16"); + } finally { + timeoutSpy.mockRestore(); + } + }); + + it("does not retry a warning-only failure in an unrelated repository", async () => { + let updateAttempts = 0; + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "gfortran" && args?.[0] === "--version") { + options?.listeners?.stdout?.( + Buffer.from("GNU Fortran (Ubuntu) 14.2.0"), + ); + } + if ( + commandLine === "sudo" && + args?.includes("apt-get") && + args?.includes("update") + ) { + updateAttempts++; + options?.listeners?.stderr?.( + Buffer.from( + "W: Failed to fetch https://packages.microsoft.com/ubuntu/24.04/prod/dists/noble/InRelease 403 Forbidden\n", + ), + ); + return 0; + } + return 0; + }); + + await installDebian(baseInputs); + + expect(updateAttempts).toBe(1); + expect(core.info).not.toHaveBeenCalledWith( + expect.stringContaining("apt-get update failed"), + ); + }); + + it("separates x64 and ARM64 caches", async () => { + await installDebian({ ...baseInputs, arch: Arch.ARM64 }); + + expect(mockedCache.restoreCache).toHaveBeenCalledWith( + [expect.stringContaining(path.join("arm64", "14"))], + "apt-gfortran-v2-20.04.6-arm64-14", + ); + }); + + it("installs from cache on cache hit", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + await installDebian(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "apt-get", + "install", + "-y", + "--no-download", + "-o", + `Dir::Cache::archives=${cacheDir}`, + "gcc-14", + "g++-14", + "gfortran-14", + ]); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + [ + "timeout", + "--signal=TERM", + "--kill-after=10s", + "5m", + "apt-get", + "update", + "-y", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + ], + expect.objectContaining({ ignoreReturnCode: true }), + ); + expect(mockedCache.saveCache).not.toHaveBeenCalled(); + }); + + it("falls back to an online install when cached packages are incomplete", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "sudo" && args?.includes("--no-download")) { + throw new Error("Cached package is missing"); + } + if (commandLine === "gfortran" && args?.[0] === "--version") { + options?.listeners?.stdout?.( + Buffer.from("GNU Fortran (Ubuntu) 14.2.0"), + ); + } + return 0; + }); + + await installDebian(baseInputs); + + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("falling back to an online installation"), + ); + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "timeout", + "--signal=TERM", + "--kill-after=30s", + "15m", + "apt-get", + "install", + "-y", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + "-o", + `Dir::Cache::archives=${cacheDir}`, + "gcc-14", + "g++-14", + "gfortran-14", + ]); + expect(mockedCache.saveCache).not.toHaveBeenCalled(); + }); + + it("falls back when cached tools fail validation", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + let validationAttempts = 0; + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "gcc-14" && args?.[0] === "--version") { + validationAttempts++; + if (validationAttempts === 1) { + throw new Error("Invalid cached compiler"); + } + } + if (commandLine === "gfortran" && args?.[0] === "--version") { + options?.listeners?.stdout?.( + Buffer.from("GNU Fortran (Ubuntu) 14.2.0"), + ); + } + return 0; + }); + + await installDebian(baseInputs); + + expect(validationAttempts).toBe(2); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("incomplete or invalid"), + ); + }); + + it("continues when cache restore fails", async () => { + mockedCache.restoreCache.mockRejectedValue( + new Error("Cache unavailable"), + ); + + await installDebian(baseInputs); + + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("proceeding without it"), + ); + expect(mockedCache.saveCache).toHaveBeenCalled(); + }); + + it("retries apt-get install on failure and eventually succeeds", async () => { + let attempts = 0; + mockedExec.mockImplementation(async (cmd, args) => { + if ( + cmd === "sudo" && + args?.includes("apt-get") && + args?.includes("install") && + !args?.includes("--no-download") + ) { + attempts++; + if (attempts === 1) throw new Error("Apt failure"); + } + return 0; + }); + + jest.useFakeTimers(); + const installPromise = installDebian(baseInputs); + + // Flush microtasks to allow the first attempt to fail and reach the setTimeout + for (let i = 0; i < 10; i++) await Promise.resolve(); + + // Fast-forward past the 10s delay for the first retry + jest.advanceTimersByTime(10000); + + // Flush microtasks to allow the second attempt to run + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await installPromise; + jest.useRealTimers(); + + expect(attempts).toBe(2); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("apt-get install failed (attempt 1/3)"), + ); + }); + + it("retries add-apt-repository on failure and eventually succeeds", async () => { + const inputs = { ...baseInputs, version: "15", osVersion: "24.04" }; + let attempts = 0; + mockedExec.mockImplementation(async (cmd, args) => { + if (cmd === "sudo" && args?.[0] === "add-apt-repository") { + attempts++; + if (attempts === 1) throw new Error("PPA failure"); + } + return 0; + }); + + jest.useFakeTimers(); + const installPromise = installDebian(inputs); + + // Flush microtasks + for (let i = 0; i < 10; i++) await Promise.resolve(); + + // Fast-forward past the 5s delay for the first retry + jest.advanceTimersByTime(5000); + + // Flush microtasks + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await installPromise; + jest.useRealTimers(); + + expect(attempts).toBe(2); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("add-apt-repository failed (attempt 1/3)"), + ); + }); + + it("configures update-alternatives", async () => { + await installDebian(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "update-alternatives", + "--install", + "/usr/bin/gcc", + "gcc", + "/usr/bin/gcc-14", + "100", + "--slave", + "/usr/bin/gfortran", + "gfortran", + "/usr/bin/gfortran-14", + ]); + }); + + it("exports environment variables", async () => { + await installDebian(baseInputs); + }); + }); +}); diff --git a/__tests__/installers/gfortran/win32.test.ts b/__tests__/installers/gfortran/win32.test.ts new file mode 100644 index 00000000..c5f5a629 --- /dev/null +++ b/__tests__/installers/gfortran/win32.test.ts @@ -0,0 +1,165 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as tc from "@actions/tool-cache"; +import * as path from "path"; +import { installWin32 } from "../../../src/installers/gfortran/win32"; +import { msys2PackageAvailable, setupMSYS2 } from "../../../src/setup_msys2"; +import { verifySha256 } from "../../../src/verify_download"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("@actions/tool-cache"); +jest.mock("../../../src/setup_msys2"); +jest.mock("../../../src/verify_download"); + +describe("installWin32 (gfortran)", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedTc = tc as jest.Mocked; + const mockedSetupMSYS2 = setupMSYS2 as jest.MockedFunction; + const mockedMsys2PackageAvailable = + msys2PackageAvailable as jest.MockedFunction; + const mockedVerifySha256 = verifySha256 as jest.MockedFunction< + typeof verifySha256 + >; + const mockedExportVariable = core.exportVariable as jest.MockedFunction< + typeof core.exportVariable + >; + + const baseInputs: Inputs = { + compiler: Compiler.GFortran, + version: "14", + os: OS.Windows, + osVersion: "2022", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockedVerifySha256.mockResolvedValue(); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "gfortran" && args?.[0] === "-dumpversion") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("14.1.0")); + } + } + return 0; + }); + }); + + describe("Native", () => { + it("downloads and extracts GFortran", async () => { + const cacheDir = "C:\\Cache\\gfortran"; + const downloadedZip = "C:\\Temp\\gcc.zip"; + const extractedDir = "C:\\Temp\\extracted"; + mockedTc.find.mockReturnValue(""); + mockedTc.downloadTool.mockResolvedValue(downloadedZip); + mockedTc.extractZip.mockResolvedValue(extractedDir); + mockedTc.cacheDir.mockResolvedValue(cacheDir); + + const result = await installWin32(baseInputs); + + // src uses path.win32.join so these are real Windows paths on any host OS. + const binDir = path.win32.join(cacheDir, "bin"); + expect(result.fc).toBe(path.win32.join(binDir, "gfortran.exe")); + expect(result.cc).toBe(path.win32.join(binDir, "gcc.exe")); + expect(result.cxx).toBe(path.win32.join(binDir, "g++.exe")); + expect(mockedTc.downloadTool).toHaveBeenCalled(); + expect(mockedTc.extractZip).toHaveBeenCalledWith(downloadedZip); + // Verifies the "mingw64" subdirectory installWin32 appends before + // caching, not just that cacheDir was called at all. + expect(mockedTc.cacheDir).toHaveBeenCalledWith( + path.win32.join(extractedDir, "mingw64"), + `gfortran-verified-${baseInputs.msystem}`, + baseInputs.version, + baseInputs.arch, + ); + expect(core.addPath).toHaveBeenCalledWith(expect.stringContaining("bin")); + }); + + it("aborts before extraction when checksum verification fails", async () => { + mockedTc.find.mockReturnValue(""); + mockedTc.downloadTool.mockResolvedValue("C:\\Temp\\gcc.zip"); + mockedVerifySha256.mockRejectedValue( + new Error("SHA-256 verification failed"), + ); + + await expect(installWin32(baseInputs)).rejects.toThrow( + "SHA-256 verification failed", + ); + + expect(mockedTc.extractZip).not.toHaveBeenCalled(); + expect(mockedTc.cacheDir).not.toHaveBeenCalled(); + }); + + it("retries a failed download and succeeds", async () => { + mockedTc.find.mockReturnValue(""); + mockedTc.downloadTool + .mockRejectedValueOnce(new Error("Unexpected HTTP response: 504")) + .mockResolvedValue("C:\\Temp\\gcc.zip"); + mockedTc.extractZip.mockResolvedValue("C:\\Temp\\extracted"); + mockedTc.cacheDir.mockResolvedValue("C:\\Cache\\gfortran"); + + jest.useFakeTimers(); + try { + const installPromise = installWin32(baseInputs); + + for (let i = 0; i < 10; i++) await Promise.resolve(); + expect(mockedTc.downloadTool).toHaveBeenCalledTimes(1); + + // Advance past the 20s backoff after the first failure. + jest.advanceTimersByTime(20_000); + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await installPromise; + } finally { + jest.useRealTimers(); + } + + expect(mockedTc.downloadTool).toHaveBeenCalledTimes(2); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("Download failed (attempt 1/3)"), + ); + }); + + it("exports environment variables", async () => { + mockedTc.find.mockReturnValue("C:\\Cache\\gfortran"); + + await installWin32(baseInputs); + }); + }); + + describe("MSYS2", () => { + const msys2Inputs = { + ...baseInputs, + version: "latest", + msystem: Msystem.UCRT64, + }; + + it("installs libgomp when the package database provides it", async () => { + mockedMsys2PackageAvailable.mockResolvedValue(true); + + await installWin32(msys2Inputs); + + // libgomp is required for -fopenmp; gcc-fortran only lists it + // as an optional dependency since 16.2.0-4. + expect(mockedSetupMSYS2).toHaveBeenCalledWith(Msystem.UCRT64, [ + "gcc-fortran", + "libgomp", + ]); + }); + + it("omits libgomp when the package database predates the split", async () => { + mockedMsys2PackageAvailable.mockResolvedValue(false); + + await installWin32(msys2Inputs); + + expect(mockedSetupMSYS2).toHaveBeenCalledWith(Msystem.UCRT64, [ + "gcc-fortran", + ]); + }); + }); +}); diff --git a/__tests__/installers/ifort/darwin.test.ts b/__tests__/installers/ifort/darwin.test.ts new file mode 100644 index 00000000..77126536 --- /dev/null +++ b/__tests__/installers/ifort/darwin.test.ts @@ -0,0 +1,292 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as cache from "@actions/cache"; +import * as tc from "@actions/tool-cache"; +import * as fs from "fs"; +import { installDarwin } from "../../../src/installers/ifort/darwin"; +import { verifySha256 } from "../../../src/verify_download"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("@actions/cache"); +jest.mock("@actions/tool-cache"); +jest.mock("../../../src/verify_download"); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + existsSync: jest.fn(), + mkdirSync: jest.fn(), +})); + +describe("installDarwin (ifort)", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedCache = cache as jest.Mocked; + const mockedTc = tc as jest.Mocked; + const mockedFs = fs as jest.Mocked; + const mockedVerifySha256 = verifySha256 as jest.MockedFunction< + typeof verifySha256 + >; + const mockedExportVariable = core.exportVariable as jest.MockedFunction< + typeof core.exportVariable + >; + + const baseInputs: Inputs = { + compiler: Compiler.IFort, + version: "2021.10", + os: OS.MacOS, + osVersion: "13", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + beforeEach(() => { + jest.clearAllMocks(); + // installDarwin appends -arch flags to these and mutates process.env + for (const name of ["CFLAGS", "CXXFLAGS", "LDFLAGS"]) { + delete process.env[name]; + } + mockedFs.existsSync.mockReturnValue(true); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "ifort" && args?.[0] === "--version") { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from("ifort (IFORT) 2021.10.0 20230609"), + ); + } + } + if (commandLine === "bash" && args?.[1]?.includes("setvars.sh")) { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from( + "PATH=/opt/intel/oneapi/compiler/latest/bin\nONEAPI_ROOT=/opt/intel/oneapi", + ), + ); + } + } + return 0; + }); + }); + + it("restores from cache if available", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + + await installDarwin(baseInputs); + + expect(mockedCache.restoreCache).toHaveBeenCalled(); + expect(mockedTc.downloadTool).not.toHaveBeenCalled(); + }); + + it("downloads and installs if cache is missing", async () => { + mockedCache.restoreCache.mockResolvedValue(undefined); + mockedTc.downloadTool.mockResolvedValue("/tmp/ifort.dmg"); + + await installDarwin(baseInputs); + + expect(mockedTc.downloadTool).toHaveBeenCalled(); + expect(mockedExec).toHaveBeenCalledWith("hdiutil", [ + "attach", + "/tmp/ifort.dmg", + "-mountpoint", + "/Volumes/Intel_oneAPI_Installer", + "-quiet", + "-nobrowse", + ]); + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + expect.stringContaining("bootstrapper"), + "-s", + "--action", + "install", + "--eula", + "accept", + "--ignore-errors", + "--components", + "intel.oneapi.mac.ifort-compiler", + ]); + expect(mockedCache.saveCache).toHaveBeenCalled(); + }); + + it("verifies the downloaded DMG's checksum before mounting it", async () => { + mockedCache.restoreCache.mockResolvedValue(undefined); + mockedTc.downloadTool.mockResolvedValue("/tmp/ifort.dmg"); + + await installDarwin(baseInputs); + + expect(mockedVerifySha256).toHaveBeenCalledWith( + "/tmp/ifort.dmg", + "a17790161712632605f50c37fc8462112ec9947993f9124d0aad53bc055d8fb2", + ); + const verifyOrder = mockedVerifySha256.mock.invocationCallOrder[0]; + const hdiutilVerifyCall = mockedExec.mock.calls.findIndex( + ([commandLine, args]) => + commandLine === "hdiutil" && args?.[0] === "verify", + ); + expect( + mockedExec.mock.invocationCallOrder[hdiutilVerifyCall], + ).toBeGreaterThan(verifyOrder); + }); + + it("does not verify or mount the DMG when its checksum is rejected", async () => { + mockedCache.restoreCache.mockResolvedValue(undefined); + mockedTc.downloadTool.mockResolvedValue("/tmp/ifort.dmg"); + mockedVerifySha256.mockRejectedValueOnce( + new Error("SHA-256 verification failed for /tmp/ifort.dmg."), + ); + + await expect(installDarwin(baseInputs)).rejects.toThrow( + /SHA-256 verification failed/, + ); + + expect(mockedExec).not.toHaveBeenCalledWith( + "hdiutil", + expect.arrayContaining(["verify"]), + ); + expect(mockedExec).not.toHaveBeenCalledWith( + "hdiutil", + expect.arrayContaining(["attach"]), + ); + expect(mockedCache.saveCache).not.toHaveBeenCalled(); + }); + + it("retries a transient bootstrapper failure", async () => { + mockedCache.restoreCache.mockResolvedValue(undefined); + mockedTc.downloadTool.mockResolvedValue("/tmp/ifort.dmg"); + const timeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback) => { + callback(); + return 0 as unknown as NodeJS.Timeout; + }); + let installerAttempts = 0; + mockedExec.mockImplementation(async (commandLine, args, options) => { + if ( + commandLine === "sudo" && + typeof args?.[0] === "string" && + args[0].includes("bootstrapper") + ) { + installerAttempts++; + if (installerAttempts === 1) { + throw new Error("Cannot establish Internet connection"); + } + } + if (commandLine === "ifort" && args?.[0] === "--version") { + options?.listeners?.stdout?.( + Buffer.from("ifort (IFORT) 2021.10.0 20230609"), + ); + } + if (commandLine === "bash" && args?.[1]?.includes("setvars.sh")) { + options?.listeners?.stdout?.( + Buffer.from( + "PATH=/opt/intel/oneapi/compiler/latest/bin\nONEAPI_ROOT=/opt/intel/oneapi", + ), + ); + } + return 0; + }); + + try { + await installDarwin(baseInputs); + } finally { + timeoutSpy.mockRestore(); + } + + expect(installerAttempts).toBe(2); + expect(mockedCache.saveCache).toHaveBeenCalled(); + }); + + it("installs on ARM64 under Rosetta 2", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + + await installDarwin({ ...baseInputs, arch: Arch.ARM64 }); + + expect(mockedExec).toHaveBeenCalledWith( + "arch", + ["-x86_64", "/usr/bin/true"], + expect.objectContaining({ ignoreReturnCode: true }), + ); + expect(mockedExec).not.toHaveBeenCalledWith( + "sudo", + expect.arrayContaining(["--install-rosetta"]), + ); + }); + + it("exports x86_64 C/C++ flags on ARM64 so companions match ifort", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + + await installDarwin({ ...baseInputs, arch: Arch.ARM64 }); + + expect(mockedExportVariable).toHaveBeenCalledWith("CFLAGS", "-arch x86_64"); + expect(mockedExportVariable).toHaveBeenCalledWith( + "CXXFLAGS", + "-arch x86_64", + ); + expect(mockedExportVariable).toHaveBeenCalledWith( + "LDFLAGS", + "-arch x86_64", + ); + }); + + it("leaves C flags untouched on x64", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + + await installDarwin(baseInputs); + + expect(mockedExportVariable).not.toHaveBeenCalledWith( + "CFLAGS", + expect.anything(), + ); + }); + + it("installs Rosetta 2 on ARM64 when it is missing", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "arch") { + return 71; + } + if (commandLine === "ifort" && args?.[0] === "--version") { + options?.listeners?.stdout?.( + Buffer.from("ifort (IFORT) 2021.10.0 20230609"), + ); + } + if (commandLine === "bash" && args?.[1]?.includes("setvars.sh")) { + options?.listeners?.stdout?.( + Buffer.from( + "PATH=/opt/intel/oneapi/compiler/latest/bin\nONEAPI_ROOT=/opt/intel/oneapi", + ), + ); + } + return 0; + }); + + await installDarwin({ ...baseInputs, arch: Arch.ARM64 }); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "softwareupdate", + "--install-rosetta", + "--agree-to-license", + ]); + }); + + it("exports environment variables", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + + await installDarwin(baseInputs); + + expect(mockedExportVariable).toHaveBeenCalledWith( + "ONEAPI_ROOT", + "/opt/intel/oneapi", + ); + }); + + it("resolves and returns the installed version", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + const result = await installDarwin(baseInputs); + expect(result).toEqual({ + version: "ifort (IFORT) 2021.10.0 20230609", + fc: "ifort", + cc: "clang", + cxx: "clang++", + }); + }); +}); diff --git a/__tests__/installers/ifort/debian.test.ts b/__tests__/installers/ifort/debian.test.ts new file mode 100644 index 00000000..39517606 --- /dev/null +++ b/__tests__/installers/ifort/debian.test.ts @@ -0,0 +1,309 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as cache from "@actions/cache"; +import * as fs from "fs"; +import { installDebian } from "../../../src/installers/ifort/debian"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("@actions/cache"); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + existsSync: jest.fn(), + mkdirSync: jest.fn(), +})); + +describe("installDebian (ifort)", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedCache = cache as jest.Mocked; + const mockedFs = fs as jest.Mocked; + const mockedExportVariable = core.exportVariable as jest.MockedFunction< + typeof core.exportVariable + >; + + const baseInputs: Inputs = { + compiler: Compiler.IFort, + version: "2021.10", + os: OS.Linux, + osVersion: "22.04", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockedFs.existsSync.mockReturnValue(true); + mockedCache.restoreCache.mockResolvedValue(undefined); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "ifort" && args?.[0] === "--version") { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from("ifort (IFORT) 2021.10.0 20230609"), + ); + } + } + if (commandLine === "bash" && args?.[1]?.includes("setvars.sh")) { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from( + "PATH=/opt/intel/oneapi/compiler/latest/bin\nONEAPI_ROOT=/opt/intel/oneapi", + ), + ); + } + } + return 0; + }); + }); + + it("adds the Intel repository on cache miss", async () => { + await installDebian(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "timeout", + "--signal=TERM", + "--kill-after=10s", + "5m", + "apt-get", + "update", + "-y", + // Scoped to the Intel oneAPI source list; unrelated repositories in + // the runner image cannot fail the install. + "-o", + "Dir::Etc::SourceList=sources.list.d/oneAPI.list", + "-o", + "Dir::Etc::SourceParts=-", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + ]); + expect(mockedExec).toHaveBeenCalledWith("bash", [ + "-c", + expect.stringContaining( + "https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB", + ), + ]); + expect(mockedExec).toHaveBeenCalledWith("bash", [ + "-c", + expect.stringContaining("https://apt.repos.intel.com/oneapi all main"), + ]); + }); + + it("installs the correct packages and saves to cache on miss", async () => { + await installDebian(baseInputs); + + expect(mockedCache.restoreCache).toHaveBeenCalledWith( + ["/opt/intel/oneapi"], + "oneapi-ifort-validated-v1-x64-2023.2.4", + ); + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "timeout", + "--signal=TERM", + "--kill-after=30s", + "15m", + "apt-get", + "install", + "-y", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + "--no-install-recommends", + "intel-oneapi-compiler-fortran-2023.2.4", + "intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2023.2.4", + ]); + expect(mockedCache.saveCache).toHaveBeenCalledWith( + ["/opt/intel/oneapi"], + "oneapi-ifort-validated-v1-x64-2023.2.4", + ); + }); + + it("retries apt-get install after a transient failure", async () => { + // Avoid the real backoff sleep inside aptGetInstallWithRetry. + const timeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback) => { + if (typeof callback === "function") callback(); + return 0 as unknown as NodeJS.Timeout; + }); + + let installAttempts = 0; + // ifort's installer relies on exec.exec throwing on non-zero exit (no + // ignoreReturnCode), so the mock must throw to simulate a transient failure. + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "ifort" && args?.[0] === "--version") { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from("ifort (IFORT) 2021.10.0 20230609"), + ); + } + } + if (commandLine === "bash" && args?.[1]?.includes("setvars.sh")) { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from( + "PATH=/opt/intel/oneapi/compiler/latest/bin\nONEAPI_ROOT=/opt/intel/oneapi", + ), + ); + } + } + if ( + commandLine === "sudo" && + args?.includes("apt-get") && + args?.includes("install") + ) { + installAttempts++; + if (installAttempts === 1) throw new Error("apt-get install failed"); + } + return 0; + }); + + try { + const result = await installDebian(baseInputs); + expect(result.fc).toBe("ifort"); + } finally { + timeoutSpy.mockRestore(); + } + + expect(installAttempts).toBe(2); // failed once, succeeded on retry + }); + + it("skips installation and restores from cache on hit", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + await installDebian(baseInputs); + + expect(mockedExec).not.toHaveBeenCalledWith("sudo", [ + "apt-get", + "install", + expect.anything(), + expect.anything(), + expect.anything(), + expect.anything(), + ]); + expect(mockedCache.saveCache).not.toHaveBeenCalled(); + // But still sources setvars.sh + expect(mockedExec).toHaveBeenCalledWith( + "bash", + ["-c", expect.stringContaining('source "/opt/intel/oneapi/setvars.sh"')], + expect.anything(), + ); + }); + + it("exports environment variables", async () => { + await installDebian(baseInputs); + + expect(mockedExportVariable).toHaveBeenCalledWith( + "ONEAPI_ROOT", + "/opt/intel/oneapi", + ); + }); + + it("applies OpenMP workaround for 2024.1 bundle", async () => { + // 2021.12 ifort corresponds to 2024.1 bundle + const inputs = { ...baseInputs, version: "2021.12" }; + await installDebian(inputs); + + expect(mockedExportVariable).toHaveBeenCalledWith( + "FFLAGS", + expect.stringContaining("intel64"), + ); + }); + + it("resolves and returns the installed version", async () => { + const result = await installDebian(baseInputs); + expect(result).toEqual({ + version: "ifort (IFORT) 2021.10.0 20230609", + fc: "ifort", + cc: "icc", + cxx: "icpc", + }); + }); + + it("advertises LLVM-based companion drivers for 2024+ bundles", async () => { + // 2021.13 ifort corresponds to the 2024.2 bundle. Intel oneAPI 2024+ + // discontinued classic icc/icpc in favour of the LLVM icx/icpx drivers. + const result = await installDebian({ ...baseInputs, version: "2021.13" }); + expect(result.fc).toBe("ifort"); + expect(result.cc).toBe("icx"); + expect(result.cxx).toBe("icpx"); + }); + + it("installs the 2021.1.2 legacy spelling as oneAPI 2021.1.2 packages", async () => { + const inputs = { ...baseInputs, version: "2021.1.2" }; + await installDebian(inputs); + + expect(mockedCache.restoreCache).toHaveBeenCalledWith( + ["/opt/intel/oneapi"], + "oneapi-ifort-validated-v1-x64-2021.1.2", + ); + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "timeout", + "--signal=TERM", + "--kill-after=30s", + "15m", + "apt-get", + "install", + "-y", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + "--no-install-recommends", + "intel-oneapi-compiler-fortran-2021.1.2", + "intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2021.1.2", + ]); + }); + + it("installs the 2021.7.1 legacy spelling as oneAPI 2022.2.1 packages", async () => { + const inputs = { ...baseInputs, version: "2021.7.1" }; + await installDebian(inputs); + + expect(mockedCache.restoreCache).toHaveBeenCalledWith( + ["/opt/intel/oneapi"], + "oneapi-ifort-validated-v1-x64-2022.2.1", + ); + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "timeout", + "--signal=TERM", + "--kill-after=30s", + "15m", + "apt-get", + "install", + "-y", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + "--no-install-recommends", + "intel-oneapi-compiler-fortran-2022.2.1", + "intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2022.2.1", + ]); + }); +}); diff --git a/__tests__/installers/ifort/win32.test.ts b/__tests__/installers/ifort/win32.test.ts new file mode 100644 index 00000000..97fae72c --- /dev/null +++ b/__tests__/installers/ifort/win32.test.ts @@ -0,0 +1,213 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as cache from "@actions/cache"; +import * as tc from "@actions/tool-cache"; +import * as fs from "fs"; +import { installWin32 } from "../../../src/installers/ifort/win32"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("@actions/cache"); +jest.mock("@actions/tool-cache"); +jest.mock("../../../src/verify_download"); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + writeFileSync: jest.fn(), + existsSync: jest.fn(), + mkdirSync: jest.fn(), + rmSync: jest.fn(), +})); + +describe("installWin32 (ifort)", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedCache = cache as jest.Mocked; + const mockedTc = tc as jest.Mocked; + const mockedFs = fs as jest.Mocked; + const mockedExportVariable = core.exportVariable as jest.MockedFunction< + typeof core.exportVariable + >; + + const baseInputs: Inputs = { + compiler: Compiler.IFort, + version: "2021.10", + os: OS.Windows, + osVersion: "2022", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockedFs.existsSync.mockReturnValue(true); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "ifort" && args?.[0] === "/what") { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from( + "Intel(R) Fortran Intel(R) 64 Compiler Classic for applications running on Intel(R) 64, Version 2021.10.0 Build 20230609", + ), + ); + } + } + if ( + commandLine === "cmd" && + args?.[1]?.includes("setvars_ifort_dump.bat") + ) { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from( + "PATH=C:\\Program Files (x86)\\Intel\\oneAPI\\compiler\\latest\\windows\\bin\nONEAPI_ROOT=C:\\Program Files (x86)\\Intel\\oneAPI", + ), + ); + } + } + return 0; + }); + }); + + it("restores from cache if available", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + + await installWin32(baseInputs); + + expect(mockedCache.restoreCache).toHaveBeenCalled(); + expect(mockedTc.downloadTool).not.toHaveBeenCalled(); + }); + + it("downloads and installs if cache is missing", async () => { + mockedCache.restoreCache.mockResolvedValue(undefined); + mockedTc.downloadTool.mockResolvedValue("C:\\Temp\\ifort.exe"); + + await installWin32(baseInputs); + + expect(mockedTc.downloadTool).toHaveBeenCalled(); + expect(mockedExec).toHaveBeenCalledWith('"C:\\Temp\\ifort.exe"', [ + "-s", + "-a", + "--silent", + "--eula", + "accept", + "-p=NEED_VS2019_INTEGRATION=0", + "-p=NEED_VS2022_INTEGRATION=0", + ]); + expect(mockedCache.saveCache).toHaveBeenCalled(); + }); + + it("exports environment variables", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + + await installWin32(baseInputs); + + expect(mockedExportVariable).toHaveBeenCalledWith( + "ONEAPI_ROOT", + "C:\\Program Files (x86)\\Intel\\oneAPI", + ); + expect(mockedExportVariable).toHaveBeenCalledWith( + "PATH", + "C:\\Program Files (x86)\\Intel\\oneAPI\\compiler\\latest\\windows\\bin", + ); + }); + + it("resolves and returns the installed version", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + const result = await installWin32(baseInputs); + expect(result).toMatchObject({ + fc: "ifort", + cc: "cl", + cxx: "cl", + }); + expect(result.version).toContain( + "Intel(R) Fortran Intel(R) 64 Compiler Classic", + ); + }); + + it("dedupes PATH entries case-insensitively before exporting", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if ( + commandLine === "cmd" && + args?.[1]?.includes("setvars_ifort_dump.bat") + ) { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from( + "PATH=C:\\Intel\\bin;C:\\MSVC\\bin;C:\\intel\\BIN;;C:\\MSVC\\bin", + ), + ); + } + } + return 0; + }); + + await installWin32(baseInputs); + + expect(mockedExportVariable).toHaveBeenCalledWith( + "PATH", + "C:\\Intel\\bin;C:\\MSVC\\bin", + ); + }); + + describe("download retry", () => { + afterEach(() => { + jest.useRealTimers(); + }); + + it("retries a failed download, removes the partial file, and succeeds", async () => { + mockedCache.restoreCache.mockResolvedValue(undefined); + mockedTc.downloadTool + .mockRejectedValueOnce(new Error("connection reset")) + .mockResolvedValue("C:\\Temp\\ifort.exe"); + + jest.useFakeTimers(); + const installPromise = installWin32(baseInputs); + + for (let i = 0; i < 10; i++) await Promise.resolve(); + expect(mockedTc.downloadTool).toHaveBeenCalledTimes(1); + + // Advance past the 20s backoff after the first failure. + jest.advanceTimersByTime(20_000); + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await installPromise; + + expect(mockedTc.downloadTool).toHaveBeenCalledTimes(2); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("Download failed (attempt 1/3)"), + ); + expect(mockedFs.rmSync).toHaveBeenCalledWith( + expect.stringContaining("ifort-2021.10.exe"), + { force: true }, + ); + expect(mockedCache.saveCache).toHaveBeenCalled(); + }); + + it("gives up after three attempts and propagates the last error", async () => { + mockedCache.restoreCache.mockResolvedValue(undefined); + mockedTc.downloadTool.mockRejectedValue(new Error("network down")); + + jest.useFakeTimers(); + const installPromise = installWin32(baseInputs); + + for (let i = 0; i < 10; i++) await Promise.resolve(); + jest.advanceTimersByTime(20_000); // backoff after attempt 1 + for (let i = 0; i < 10; i++) await Promise.resolve(); + jest.advanceTimersByTime(40_000); // backoff after attempt 2 + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await expect(installPromise).rejects.toThrow("network down"); + + expect(mockedTc.downloadTool).toHaveBeenCalledTimes(3); + expect(mockedFs.rmSync).toHaveBeenCalledTimes(3); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("Download failed (attempt 1/3)"), + ); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("Download failed (attempt 2/3)"), + ); + expect(mockedCache.saveCache).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/__tests__/installers/ifx/debian.test.ts b/__tests__/installers/ifx/debian.test.ts new file mode 100644 index 00000000..aca358d1 --- /dev/null +++ b/__tests__/installers/ifx/debian.test.ts @@ -0,0 +1,323 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as cache from "@actions/cache"; +import * as fs from "fs"; +import { installDebian } from "../../../src/installers/ifx/debian"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("@actions/cache"); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + existsSync: jest.fn(), + mkdirSync: jest.fn(), +})); + +describe("installDebian ifx", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedCache = cache as jest.Mocked; + const mockedFs = fs as jest.Mocked; + + const baseInputs: Inputs = { + compiler: Compiler.IFX, + version: "2023.2.4", + os: OS.Linux, + osVersion: "22.04", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockedFs.existsSync.mockReturnValue(true); + mockedCache.restoreCache.mockResolvedValue(undefined); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "ifx" && args?.[0] === "--version") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("ifx (IFX) 2023.2.4 20230101")); + } + } + if (commandLine === "bash" && args?.[1]?.includes("setvars.sh")) { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from( + "PATH=/opt/intel/oneapi/compiler/latest/bin\nONEAPI_ROOT=/opt/intel/oneapi", + ), + ); + } + } + return 0; + }); + }); + + it("installs the correct versioned packages and saves to cache on miss", async () => { + const inputs = { ...baseInputs, version: "2023.2.0" }; + const result = await installDebian(inputs); + + expect(result.fc).toBe("ifx"); + expect(result.cc).toBe("icx"); + expect(result.cxx).toBe("icpx"); + + expect(mockedCache.restoreCache).toHaveBeenCalledWith( + ["/opt/intel/oneapi"], + "oneapi-ifx-validated-v1-x64-2023.2.0", + ); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + [ + "timeout", + "--signal=TERM", + "--kill-after=30s", + "15m", + "apt-get", + "install", + "-y", + "--no-install-recommends", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + "intel-oneapi-compiler-fortran-2023.2.0", + "intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2023.2.0", + ], + { ignoreReturnCode: true }, + ); + expect(mockedCache.saveCache).toHaveBeenCalledWith( + ["/opt/intel/oneapi"], + "oneapi-ifx-validated-v1-x64-2023.2.0", + ); + }); + + it("skips installation and restores from cache on hit", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + const inputs = { ...baseInputs, version: "2023.2.0" }; + await installDebian(inputs); + + expect(mockedExec).not.toHaveBeenCalledWith("sudo", [ + "apt-get", + "install", + expect.anything(), + expect.anything(), + expect.anything(), + expect.anything(), + ]); + expect(mockedCache.saveCache).not.toHaveBeenCalled(); + // But still sources setvars.sh + expect(mockedExec).toHaveBeenCalledWith( + "bash", + ["-c", expect.stringContaining('source "/opt/intel/oneapi/setvars.sh"')], + expect.anything(), + ); + }); + + it("reinstalls when the restored cache is incomplete", async () => { + mockedCache.restoreCache.mockResolvedValue("hit"); + mockedFs.existsSync.mockReturnValue(false); + + await installDebian({ ...baseInputs, version: "2023.2.0" }); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "rm", + "-rf", + "/opt/intel/oneapi", + ]); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + expect.arrayContaining([ + "apt-get", + "intel-oneapi-compiler-fortran-2023.2.0", + ]), + expect.anything(), + ); + }); + + it("repairs dependencies with apt-get --fix-broken install after a failed install attempt", async () => { + // Avoid the real 15s retry backoff inside aptInstallWithRetry. + const timeoutSpy = jest + .spyOn(global, "setTimeout") + .mockImplementation((callback) => { + if (typeof callback === "function") callback(); + return 0 as unknown as NodeJS.Timeout; + }); + + let installAttempts = 0; + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "ifx" && args?.[0] === "--version") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("ifx (IFX) 2023.2.4 20230101")); + } + } + if (commandLine === "bash" && args?.[1]?.includes("setvars.sh")) { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from( + "PATH=/opt/intel/oneapi/compiler/latest/bin\nONEAPI_ROOT=/opt/intel/oneapi", + ), + ); + } + } + // The real `apt-get install` attempt (not the --fix-broken repair). + if ( + commandLine === "sudo" && + args?.includes("apt-get") && + args?.includes("install") && + !args?.includes("--fix-broken") + ) { + installAttempts++; + if (installAttempts === 1) return 1; // fail the first install attempt + } + return 0; + }); + + try { + const result = await installDebian(baseInputs); + expect(result.fc).toBe("ifx"); + } finally { + timeoutSpy.mockRestore(); + } + + // Install was retried once: failed first, succeeded on the second attempt. + expect(installAttempts).toBe(2); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + [ + "timeout", + "--signal=TERM", + "--kill-after=30s", + "10m", + "apt-get", + "--fix-broken", + "install", + "-y", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + ], + { ignoreReturnCode: true }, + ); + }); + + it("maps 2-digit version 2025.2 to 2025.2", async () => { + const inputs = { ...baseInputs, version: "2025.2" }; + await installDebian(inputs); + + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + [ + "timeout", + "--signal=TERM", + "--kill-after=30s", + "15m", + "apt-get", + "install", + "-y", + "--no-install-recommends", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + "intel-oneapi-compiler-fortran-2025.2", + "intel-oneapi-compiler-dpcpp-cpp-2025.2", + ], + { ignoreReturnCode: true }, + ); + }); + + it("resolves 2023.2 to the latest patch 2023.2.4 using resolveMinorToLatestPatch", async () => { + const inputs = { ...baseInputs, version: "2023.2" }; + await installDebian(inputs); + + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + [ + "timeout", + "--signal=TERM", + "--kill-after=30s", + "15m", + "apt-get", + "install", + "-y", + "--no-install-recommends", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + "intel-oneapi-compiler-fortran-2023.2.4", + "intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2023.2.4", + ], + { ignoreReturnCode: true }, + ); + }); + + it("adds the Intel repository on cache miss", async () => { + await installDebian(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith("sudo", [ + "timeout", + "--signal=TERM", + "--kill-after=10s", + "5m", + "apt-get", + "update", + "-y", + // The update is scoped to the Intel oneAPI source list so unrelated + // repositories baked into the runner image cannot fail the install. + "-o", + "Dir::Etc::SourceList=sources.list.d/oneAPI.list", + "-o", + "Dir::Etc::SourceParts=-", + "-o", + "Acquire::http::Timeout=30", + "-o", + "Acquire::http::ConnectTimeout=20", + "-o", + "Acquire::https::Timeout=30", + "-o", + "Acquire::https::ConnectTimeout=20", + "-o", + "Acquire::Retries=0", + ]); + expect(mockedExec).toHaveBeenCalledWith("bash", [ + "-c", + expect.stringContaining( + "https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB", + ), + ]); + expect(mockedExec).toHaveBeenCalledWith("bash", [ + "-c", + expect.stringContaining("https://apt.repos.intel.com/oneapi all main"), + ]); + }); + + it("exports environment variables including FPM flags", async () => { + await installDebian(baseInputs); + }); +}); diff --git a/__tests__/installers/ifx/win32.test.ts b/__tests__/installers/ifx/win32.test.ts new file mode 100644 index 00000000..ace21a1c --- /dev/null +++ b/__tests__/installers/ifx/win32.test.ts @@ -0,0 +1,418 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as cache from "@actions/cache"; +import * as tc from "@actions/tool-cache"; +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; +import { installWin32 } from "../../../src/installers/ifx/win32"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("@actions/cache"); +jest.mock("@actions/tool-cache"); +jest.mock("../../../src/verify_download"); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + writeFileSync: jest.fn(), + existsSync: jest.fn(), + mkdirSync: jest.fn(), + rmSync: jest.fn(), +})); +jest.mock("os"); + +describe("installWin32 (ifx)", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedRestoreCache = cache.restoreCache as jest.MockedFunction< + typeof cache.restoreCache + >; + const mockedDownloadTool = tc.downloadTool as jest.MockedFunction< + typeof tc.downloadTool + >; + const mockedFs = fs as jest.Mocked; + const mockedOs = os as jest.Mocked; + + const baseInputs: Inputs = { + compiler: Compiler.IFX, + version: "2026.0.0", + os: OS.Windows, + osVersion: "10.0.19045", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockedFs.existsSync.mockReturnValue(true); + mockedOs.tmpdir.mockReturnValue("C:\\Temp"); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "ifx") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("ifx version 2026.0.0")); + } + } else if (commandLine === "cmd" && args?.[0] === "/C") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("PATH=C:\\bin\nINTEL_VAR=foo")); + } + } + return 0; + }); + }); + + it("restores from cache if available", async () => { + mockedRestoreCache.mockResolvedValue("cache-hit"); + + const result = await installWin32(baseInputs); + + expect(result.fc).toBe("ifx"); + expect(result.cc).toBe("cl"); + expect(result.cxx).toBe("cl"); + + expect(mockedRestoreCache).toHaveBeenCalled(); + expect(mockedDownloadTool).not.toHaveBeenCalled(); + }); + + it("downloads and installs if not in cache", async () => { + mockedRestoreCache.mockResolvedValue(undefined); + mockedDownloadTool.mockResolvedValue("C:\\Temp\\installer.exe"); + + await installWin32(baseInputs); + + expect(mockedDownloadTool).toHaveBeenCalled(); + // Standalone (non-HPC-kit) installers run the silent install without any + // --components selection... + expect(mockedExec).toHaveBeenCalledWith( + '"C:\\Temp\\installer.exe"', + [ + "-s", + "-a", + "--silent", + "--eula", + "accept", + "-p=NEED_VS2019_INTEGRATION=0", + "-p=NEED_VS2022_INTEGRATION=0", + ], + { ignoreReturnCode: true }, + ); + // ...and must not probe the component list. + expect(mockedExec).not.toHaveBeenCalledWith( + '"C:\\Temp\\installer.exe"', + expect.arrayContaining(["--list-components"]), + ); + expect(cache.saveCache).toHaveBeenCalled(); + }); + + describe("full HPC kit installers", () => { + const silentArgs = [ + "-s", + "-a", + "--silent", + "--eula", + "accept", + "-p=NEED_VS2019_INTEGRATION=0", + "-p=NEED_VS2022_INTEGRATION=0", + ]; + + const fullKitInputs = { + ...baseInputs, + version: "2025.3.1", // intel-oneapi-hpc-toolkit offline installer + }; + + const mockFullKitInstallerFlow = (listOutput: string) => { + mockedExec.mockImplementation(async (commandLine, args, options) => { + if ( + commandLine === '"C:\\Temp\\installer.exe"' && + args?.includes("--list-components") + ) { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from(listOutput)); + } + return 0; + } + if (commandLine === '"C:\\Temp\\installer.exe"') return 0; + if (commandLine === "ifx") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("ifx version 2025.3.1")); + } + return 0; + } + if (commandLine === "cmd" && args?.[0] === "/C") { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from("PATH=C:\\bin\nINTEL_VAR=foo"), + ); + } + return 0; + } + return 0; + }); + }; + + beforeEach(() => { + mockedRestoreCache.mockResolvedValue(undefined); + mockedDownloadTool.mockResolvedValue("C:\\Temp\\installer.exe"); + }); + + it("lists components and installs only the Fortran compiler component", async () => { + mockFullKitInstallerFlow( + [ + "intel.oneapi.win.mkl.devel 2025.3.1 Intel oneAPI Math Kernel Library", + "intel.oneapi.win.ifort-compiler 2025.3.1 Intel(R) Fortran Compiler", + "intel.oneapi.win.tbb.devel 2025.3.1 Intel oneAPI Threading Building Blocks", + ].join("\n"), + ); + + await installWin32(fullKitInputs); + + expect(mockedExec).toHaveBeenCalledWith( + '"C:\\Temp\\installer.exe"', + ["-s", "-a", "--list-components"], + expect.objectContaining({ listeners: expect.any(Object) }), + ); + expect(mockedExec).toHaveBeenCalledWith( + '"C:\\Temp\\installer.exe"', + [...silentArgs, "--components=intel.oneapi.win.ifort-compiler"], + { ignoreReturnCode: true }, + ); + }); + + it("detects the w_HPCKit-prefixed installers as full kits", async () => { + mockFullKitInstallerFlow( + "intel.oneapi.win.ifort-compiler 2024.1.0 Intel(R) Fortran Compiler", + ); + + await installWin32({ ...baseInputs, version: "2024.1.0" }); + + expect(mockedExec).toHaveBeenCalledWith( + '"C:\\Temp\\installer.exe"', + expect.arrayContaining([ + "--components=intel.oneapi.win.ifort-compiler", + ]), + { ignoreReturnCode: true }, + ); + }); + + it("falls back to a full kit install when no component is listed", async () => { + mockFullKitInstallerFlow( + [ + "intel.oneapi.win.mkl.devel 2025.3.1 Intel oneAPI Math Kernel Library", + "intel.oneapi.win.tbb.devel 2025.3.1 Intel oneAPI Threading Building Blocks", + ].join("\n"), + ); + + await installWin32(fullKitInputs); + + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining( + "This installer doesn't expose the Fortran compiler as a separately " + + "installable component; installing the full kit instead.", + ), + ); + // Degrades gracefully: installs the full kit without any --components. + expect(mockedExec).toHaveBeenCalledWith( + '"C:\\Temp\\installer.exe"', + silentArgs, + { ignoreReturnCode: true }, + ); + expect(cache.saveCache).toHaveBeenCalled(); + }); + + it("does not probe the component list for legacy HPC kits", async () => { + // 2023.1.0 is one of the older w_HPCKit bootstrappers that reportedly + // chokes on --components; legacy kits install in full. + mockFullKitInstallerFlow(""); + + await installWin32({ ...baseInputs, version: "2023.1.0" }); + + expect(mockedExec).not.toHaveBeenCalledWith( + '"C:\\Temp\\installer.exe"', + expect.arrayContaining(["--list-components"]), + ); + expect(mockedExec).toHaveBeenCalledWith( + '"C:\\Temp\\installer.exe"', + silentArgs, + { ignoreReturnCode: true }, + ); + }); + }); + + it("retries installation on crash and eventually succeeds", async () => { + mockedRestoreCache.mockResolvedValue(undefined); + mockedDownloadTool.mockResolvedValue("C:\\Temp\\installer.exe"); + + let attempts = 0; + mockedExec.mockImplementation(async (cmd) => { + if (cmd === '"C:\\Temp\\installer.exe"') { + attempts++; + if (attempts === 1) return 1; + } + if (cmd === "ifx") return 0; + if (cmd === "cmd") return 0; + return 0; + }); + + jest.useFakeTimers(); + const installPromise = installWin32(baseInputs); + + // Flush microtasks + for (let i = 0; i < 10; i++) await Promise.resolve(); + + // Advance past 15s delay + jest.advanceTimersByTime(15000); + + // Flush microtasks + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await installPromise; + jest.useRealTimers(); + + expect(attempts).toBe(2); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining( + "Installer crashed with exit code 1 (attempt 1/3)", + ), + ); + }); + + describe("download retry", () => { + afterEach(() => { + jest.useRealTimers(); + }); + + it("retries a failed download, removes the partial file, and succeeds", async () => { + mockedRestoreCache.mockResolvedValue(undefined); + mockedDownloadTool + .mockRejectedValueOnce(new Error("connection reset")) + .mockResolvedValue("C:\\Temp\\installer.exe"); + + jest.useFakeTimers(); + const installPromise = installWin32(baseInputs); + + for (let i = 0; i < 10; i++) await Promise.resolve(); + expect(mockedDownloadTool).toHaveBeenCalledTimes(1); + + // Advance past the 20s backoff after the first failure. + jest.advanceTimersByTime(20_000); + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await installPromise; + + expect(mockedDownloadTool).toHaveBeenCalledTimes(2); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("Download failed (attempt 1/3)"), + ); + // The partial download must not be treated as a complete installer. + expect(mockedFs.rmSync).toHaveBeenCalledWith( + expect.stringContaining("ifx-2026.0.0.exe"), + { force: true }, + ); + expect(core.info).toHaveBeenCalledWith("Verifying installer..."); + expect(cache.saveCache).toHaveBeenCalled(); + }); + + it("gives up after three attempts and propagates the last error", async () => { + mockedRestoreCache.mockResolvedValue(undefined); + mockedDownloadTool.mockRejectedValue(new Error("network down")); + + jest.useFakeTimers(); + const installPromise = installWin32(baseInputs); + + for (let i = 0; i < 10; i++) await Promise.resolve(); + jest.advanceTimersByTime(20_000); // backoff after attempt 1 + for (let i = 0; i < 10; i++) await Promise.resolve(); + jest.advanceTimersByTime(40_000); // backoff after attempt 2 + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await expect(installPromise).rejects.toThrow("network down"); + + expect(mockedDownloadTool).toHaveBeenCalledTimes(3); + expect(mockedFs.rmSync).toHaveBeenCalledTimes(3); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("Download failed (attempt 1/3)"), + ); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("Download failed (attempt 2/3)"), + ); + expect(core.info).not.toHaveBeenCalledWith("Verifying installer..."); + }); + }); + + it("skips installation if exit code is 1001 (already installed)", async () => { + mockedRestoreCache.mockResolvedValue(undefined); + mockedDownloadTool.mockResolvedValue("C:\\Temp\\installer.exe"); + + let installerCalls = 0; + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === '"C:\\Temp\\installer.exe"') { + installerCalls++; + return 1001; + } else if (commandLine === "ifx") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("ifx version 2026.0.0")); + } + } else if (commandLine === "cmd" && args?.[0] === "/C") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("PATH=C:\\bin\nINTEL_VAR=foo")); + } + } + return 0; + }); + + await installWin32(baseInputs); + + expect(installerCalls).toBe(1); + expect(core.info).toHaveBeenCalledWith( + "Intel oneAPI is already installed, skipping.", + ); + }); + + it("resolves 2025.3 to 2025.3.3 using resolveMinorToLatestPatch", async () => { + mockedRestoreCache.mockResolvedValue("cache-hit"); + const inputs = { ...baseInputs, version: "2025.3" }; + await installWin32(inputs); + + expect(mockedRestoreCache).toHaveBeenCalledWith( + expect.anything(), + expect.stringContaining("ifx-win32-validated-v1-x64-2025.3.3"), + ); + }); + + it("exports environment variables from setvars", async () => { + mockedRestoreCache.mockResolvedValue("cache-hit"); + + await installWin32(baseInputs); + + expect(mockedFs.writeFileSync).toHaveBeenCalledWith( + expect.stringContaining("setvars_and_dump.bat"), + expect.stringContaining("setvars.bat"), + ); + expect(core.exportVariable).toHaveBeenCalledWith("PATH", "C:\\bin"); + expect(core.exportVariable).toHaveBeenCalledWith("INTEL_VAR", "foo"); + }); + + it("dedupes PATH entries case-insensitively before exporting", async () => { + mockedRestoreCache.mockResolvedValue("cache-hit"); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "cmd" && args?.[0] === "/C") { + if (options?.listeners?.stdout) { + options.listeners.stdout( + Buffer.from( + "PATH=C:\\Intel\\bin;C:\\MSVC\\bin;C:\\intel\\BIN;;C:\\MSVC\\bin", + ), + ); + } + } + return 0; + }); + + await installWin32(baseInputs); + + expect(core.exportVariable).toHaveBeenCalledWith( + "PATH", + "C:\\Intel\\bin;C:\\MSVC\\bin", + ); + }); +}); diff --git a/__tests__/installers/lfortran/darwin.test.ts b/__tests__/installers/lfortran/darwin.test.ts new file mode 100644 index 00000000..cebacb42 --- /dev/null +++ b/__tests__/installers/lfortran/darwin.test.ts @@ -0,0 +1,125 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as fs from "fs"; +import { installDarwin } from "../../../src/installers/lfortran/darwin"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("../../../src/verify_download"); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + existsSync: jest.fn(), +})); + +describe("installDarwin (LFortran)", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedFs = fs as jest.Mocked; + const mockedExportVariable = core.exportVariable as jest.MockedFunction< + typeof core.exportVariable + >; + let environmentCreated: boolean; + + const baseInputs: Inputs = { + compiler: Compiler.LFortran, + version: "0.63.0", + os: OS.MacOS, + osVersion: "13", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + beforeEach(() => { + jest.clearAllMocks(); + environmentCreated = false; + mockedFs.existsSync.mockImplementation(() => environmentCreated); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (args?.[0] === "create") environmentCreated = true; + if ( + commandLine.includes("conda") && + args?.[0] === "run" && + args?.[3] === "lfortran" && + args?.[4] === "--version" + ) { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("LFortran version 0.63.0")); + } + } + if (commandLine === "xcrun" && args?.[0] === "--show-sdk-path") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("/path/to/SDK")); + } + } + return 0; + }); + }); + + it("downloads and installs Miniforge", async () => { + await installDarwin(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith("curl", [ + "-fsSL", + "--retry", + "3", + "--retry-all-errors", + "--retry-delay", + "15", + "-o", + expect.stringContaining("miniforge.sh"), + expect.stringContaining("Miniforge3-26.3.2-2-MacOSX-x86_64.sh"), + ]); + expect(mockedExec).toHaveBeenCalledWith("bash", [ + expect.stringContaining("miniforge.sh"), + "-b", + "-p", + expect.stringContaining("miniforge"), + ]); + }); + + it("installs lfortran via conda", async () => { + await installDarwin(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith( + expect.stringContaining("conda"), + expect.arrayContaining([ + "create", + "-y", + "-p", + expect.stringContaining("0.63.0"), + "lfortran==0.63.0", + ]), + expect.objectContaining({ ignoreReturnCode: true }), + ); + }); + + it("reuses a valid environment on a second invocation", async () => { + await installDarwin(baseInputs); + await installDarwin(baseInputs); + + expect( + mockedExec.mock.calls.filter(([command]) => command === "curl"), + ).toHaveLength(1); + }); + + it("exports environment variables and SDKROOT", async () => { + await installDarwin(baseInputs); + + expect(core.addPath).toHaveBeenCalledWith(expect.stringContaining("bin")); + expect(mockedExportVariable).toHaveBeenCalledWith( + "SDKROOT", + "/path/to/SDK", + ); + }); + + it("resolves and returns the installed version", async () => { + const result = await installDarwin(baseInputs); + expect(result).toEqual({ + version: "LFortran version 0.63.0", + fc: expect.stringContaining("lfortran"), + cc: "clang", + cxx: "clang++", + }); + }); +}); diff --git a/__tests__/installers/lfortran/debian.test.ts b/__tests__/installers/lfortran/debian.test.ts new file mode 100644 index 00000000..740355d9 --- /dev/null +++ b/__tests__/installers/lfortran/debian.test.ts @@ -0,0 +1,175 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as fs from "fs"; +import { installDebian } from "../../../src/installers/lfortran/debian"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("../../../src/verify_download"); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + existsSync: jest.fn(), + rmSync: jest.fn(), +})); + +describe("installDebian (LFortran)", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedFs = fs as jest.Mocked; + const mockedExportVariable = core.exportVariable as jest.MockedFunction< + typeof core.exportVariable + >; + let environmentCreated: boolean; + + const baseInputs: Inputs = { + compiler: Compiler.LFortran, + version: "0.63.0", + os: OS.Linux, + osVersion: "22.04", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + beforeEach(() => { + jest.clearAllMocks(); + environmentCreated = false; + mockedFs.existsSync.mockImplementation(() => environmentCreated); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (args?.[0] === "create") environmentCreated = true; + if (args?.[0] === "run" && args.includes("lfortran")) { + options?.listeners?.stdout?.(Buffer.from("LFortran version 0.63.0")); + } + if (commandLine.includes("lfortran") && args?.[0] === "--version") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("LFortran version 0.63.0")); + } + } + return 0; + }); + }); + + it("downloads and installs Miniforge", async () => { + await installDebian(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith("curl", [ + "-fsSL", + "--retry", + "3", + "--retry-all-errors", + "--retry-delay", + "15", + "-o", + expect.stringContaining("miniforge.sh"), + expect.stringContaining("Miniforge3-26.3.2-2-Linux-x86_64.sh"), + ]); + expect(mockedExec).toHaveBeenCalledWith("bash", [ + expect.stringContaining("miniforge.sh"), + "-b", + "-p", + expect.stringContaining("miniforge"), + ]); + }); + + it("installs lfortran via conda", async () => { + await installDebian(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith( + expect.stringContaining("conda"), + expect.arrayContaining([ + "create", + "-y", + "-p", + expect.stringContaining("0.63.0"), + "lfortran==0.63.0", + ]), + expect.objectContaining({ ignoreReturnCode: true }), + ); + }); + + it("reuses a valid environment on a second invocation", async () => { + await installDebian(baseInputs); + await installDebian(baseInputs); + + expect( + mockedExec.mock.calls.filter(([command]) => command === "curl"), + ).toHaveLength(1); + expect(core.info).toHaveBeenCalledWith( + expect.stringContaining("Reusing LFortran 0.63.0"), + ); + }); + + it("removes and rebuilds a partial versioned environment", async () => { + mockedFs.existsSync.mockImplementation((filePath) => { + if (environmentCreated) return true; + const value = String(filePath); + if (value.endsWith("/miniforge/bin/conda")) return true; + if (value.endsWith("/env/bin/lfortran")) return false; + return value.includes("/setup-fortran/lfortran/linux/x64/0.63.0"); + }); + + await installDebian(baseInputs); + + expect(mockedFs.rmSync).toHaveBeenCalledWith( + expect.stringContaining("setup-fortran/lfortran/linux/x64/0.63.0"), + { recursive: true, force: true }, + ); + expect(mockedExec).toHaveBeenCalledWith("curl", expect.any(Array)); + }); + + it("installs lfortran on ARM64 via the aarch64 Miniforge installer", async () => { + const inputs = { ...baseInputs, arch: Arch.ARM64, version: "0.64.0" }; + await installDebian(inputs); + + expect(mockedExec).toHaveBeenCalledWith("curl", [ + "-fsSL", + "--retry", + "3", + "--retry-all-errors", + "--retry-delay", + "15", + "-o", + expect.stringContaining("miniforge.sh"), + expect.stringContaining("Miniforge3-26.3.2-2-Linux-aarch64.sh"), + ]); + expect(mockedExec).toHaveBeenCalledWith( + expect.stringContaining("conda"), + expect.arrayContaining([ + "create", + "-y", + "-p", + expect.stringContaining("0.64.0"), + "lfortran==0.64.0", + ]), + expect.objectContaining({ ignoreReturnCode: true }), + ); + }); + + it("rejects ARM64 versions that conda-forge does not publish for aarch64", async () => { + const inputs = { ...baseInputs, arch: Arch.ARM64, version: "0.63.0" }; + await expect(installDebian(inputs)).rejects.toThrow( + "lfortran 0.63.0 is not supported on linux (arm64). Supported versions: 0.67.0, 0.66.0, 0.65.0, 0.64.0", + ); + }); + + it("exports environment variables", async () => { + await installDebian(baseInputs); + + expect(core.addPath).toHaveBeenCalledWith(expect.stringContaining("bin")); + expect(mockedExportVariable).toHaveBeenCalledWith( + "LFORTRAN_OMP_LIB_DIR", + expect.stringContaining("lib"), + ); + }); + + it("resolves and returns the installed version", async () => { + const result = await installDebian(baseInputs); + expect(result).toEqual({ + version: "LFortran version 0.63.0", + fc: "lfortran", + cc: "clang", + cxx: "clang++", + }); + }); +}); diff --git a/__tests__/installers/lfortran/win32.test.ts b/__tests__/installers/lfortran/win32.test.ts new file mode 100644 index 00000000..5fccf9e1 --- /dev/null +++ b/__tests__/installers/lfortran/win32.test.ts @@ -0,0 +1,188 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as fs from "fs"; +import { installWin32 } from "../../../src/installers/lfortran/win32"; +import { setupMSYS2 } from "../../../src/setup_msys2"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("../../../src/setup_msys2"); +jest.mock("../../../src/verify_download"); +jest.mock("fs", () => ({ + ...jest.requireActual("fs"), + existsSync: jest.fn(), + renameSync: jest.fn(), + copyFileSync: jest.fn(), + writeFileSync: jest.fn(), +})); + +describe("installWin32 (LFortran)", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedFs = fs as jest.Mocked; + const mockedSetupMSYS2 = setupMSYS2 as jest.MockedFunction; + const mockedExportVariable = core.exportVariable as jest.MockedFunction< + typeof core.exportVariable + >; + let environmentCreated: boolean; + + const baseInputs: Inputs = { + compiler: Compiler.LFortran, + version: "0.63.0", + os: OS.Windows, + osVersion: "2022", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + beforeEach(() => { + jest.clearAllMocks(); + environmentCreated = false; + mockedSetupMSYS2.mockImplementation(async () => { + environmentCreated = true; + }); + mockedFs.existsSync.mockImplementation((filePath) => { + if (String(filePath).endsWith("lld-link.exe")) return environmentCreated; + return environmentCreated; + }); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (args?.[0] === "create") environmentCreated = true; + if (args?.[0] === "run" && args.includes("lfortran")) { + options?.listeners?.stdout?.(Buffer.from("LFortran version 0.63.0")); + } + if (commandLine.includes("lfortran") && args?.[0] === "--version") { + if (options?.listeners?.stdout) { + options.listeners.stdout(Buffer.from("LFortran version 0.63.0")); + } + } + return 0; + }); + }); + + describe("Native (Conda)", () => { + it("downloads and installs Miniforge", async () => { + await installWin32(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith("curl", [ + "-fsSL", + "--retry", + "3", + "--retry-all-errors", + "--retry-delay", + "15", + "-o", + expect.stringContaining("miniforge-install.exe"), + expect.stringContaining("Miniforge3-26.3.2-2-Windows-x86_64.exe"), + ]); + expect(mockedExec).toHaveBeenCalledWith( + expect.stringContaining("miniforge-install.exe"), + ["/S", expect.stringContaining("/D=")], + ); + }); + + it("installs lfortran via conda", async () => { + await installWin32(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith( + expect.stringContaining("conda.exe"), + expect.arrayContaining([ + "create", + "-y", + "-p", + expect.stringContaining("0.63.0"), + "lfortran==0.63.0", + ]), + expect.objectContaining({ ignoreReturnCode: true }), + ); + }); + + it("reuses a valid environment on a second invocation", async () => { + await installWin32(baseInputs); + await installWin32(baseInputs); + + expect( + mockedExec.mock.calls.filter(([command]) => command === "curl"), + ).toHaveLength(1); + expect(mockedFs.renameSync).not.toHaveBeenCalled(); + }); + + it("exports environment variables and sets linker", async () => { + const result = await installWin32(baseInputs); + + expect(core.addPath).toHaveBeenCalledWith( + expect.stringContaining("lfortran"), + ); + expect(mockedExportVariable).toHaveBeenCalledWith( + "LFORTRAN_LINKER", + expect.stringContaining("link.exe"), + ); + expect(result.cc).toBe("clang"); + expect(result.cxx).toBe("clang++"); + }); + + it("prepends the toolchain bin dir for Bash steps", async () => { + await installWin32(baseInputs); + + expect(mockedExportVariable).toHaveBeenCalledWith( + "BASH_ENV", + expect.stringContaining("setup-fortran-lfortran-bash-env.sh"), + ); + expect(mockedFs.writeFileSync).toHaveBeenCalledWith( + expect.stringContaining("setup-fortran-lfortran-bash-env.sh"), + expect.stringMatching(/^export PATH='.*Library\/bin':"\$PATH"\n$/), + { mode: 0o600 }, + ); + }); + }); + + describe("MSYS2", () => { + it("calls setupMSYS2 and exports variables", async () => { + const inputs = { + ...baseInputs, + msystem: Msystem.UCRT64, + version: "latest", + }; + await installWin32(inputs); + + expect(mockedSetupMSYS2).toHaveBeenCalledWith(Msystem.UCRT64, [ + "lfortran", + ]); + expect(mockedExportVariable).not.toHaveBeenCalledWith( + "BASH_ENV", + expect.anything(), + ); + }); + + it("reuses a working MSYS2 installation on a second invocation", async () => { + const inputs = { + ...baseInputs, + msystem: Msystem.UCRT64, + version: "latest", + }; + await installWin32(inputs); + await installWin32(inputs); + + expect(mockedSetupMSYS2).toHaveBeenCalledTimes(1); + }); + + it("calls setupMSYS2 with Clang64 and exports variables", async () => { + const inputs = { + ...baseInputs, + msystem: Msystem.Clang64, + version: "latest", + }; + const result = await installWin32(inputs); + + expect(mockedSetupMSYS2).toHaveBeenCalledWith(Msystem.Clang64, [ + "lfortran", + ]); + expect(mockedExportVariable).toHaveBeenCalledWith( + "WINDOWS_ENV", + Msystem.Clang64, + ); + expect(result.fc).toEqual(expect.stringContaining("clang64")); + }); + }); +}); diff --git a/__tests__/installers/nvfortran/debian.test.ts b/__tests__/installers/nvfortran/debian.test.ts new file mode 100644 index 00000000..d4ad45e4 --- /dev/null +++ b/__tests__/installers/nvfortran/debian.test.ts @@ -0,0 +1,416 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import * as cache from "@actions/cache"; +import { installDebian } from "../../../src/installers/nvfortran/debian"; +import { Arch, Compiler, OS, Msystem, type Inputs } from "../../../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("@actions/cache"); +jest.mock("../../../src/verify_download"); + +describe("installDebian nvfortran", () => { + const mockedExec = exec.exec as jest.MockedFunction; + const mockedGetExecOutput = exec.getExecOutput as jest.MockedFunction< + typeof exec.getExecOutput + >; + const mockedCache = cache as jest.Mocked; + const mockedExportVariable = core.exportVariable as jest.MockedFunction< + typeof core.exportVariable + >; + + const baseInputs: Inputs = { + compiler: Compiler.NVFortran, + version: "24.1", + os: OS.Linux, + osVersion: "22.04", + arch: Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; + + beforeEach(() => { + jest.clearAllMocks(); + mockedCache.restoreCache.mockResolvedValue(undefined); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "nvfortran" && args?.[0] === "--version") { + options?.listeners?.stdout?.(Buffer.from("nvfortran 24.1-0")); + } + return 0; + }); + (exec.getExecOutput as jest.Mock).mockResolvedValue({ + stdout: "install ok installed install ok installed", + exitCode: 0, + }); + }); + + it("installs legacy ncurses via direct download when needed", async () => { + // Version <= 24.3 triggers ncurses check + const inputs = { ...baseInputs, version: "24.3" }; + + // Simulate ncurses not installed + (exec.getExecOutput as jest.Mock).mockResolvedValue({ + stdout: "", + exitCode: 0, + }); + + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "nvfortran" && args?.[0] === "--version") { + options?.listeners?.stdout?.(Buffer.from("nvfortran 24.1-0")); + } + return 0; + }); + + await installDebian(inputs); + + // Should download each pinned .deb from the Ubuntu archive pool + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining([ + "https://security.ubuntu.com/ubuntu/pool/universe/n/ncurses/libtinfo5_6.3-2_amd64.deb", + ]), + ); + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining([ + "https://security.ubuntu.com/ubuntu/pool/universe/n/ncurses/libncursesw5_6.3-2_amd64.deb", + ]), + ); + // Should install each .deb via dpkg + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + expect.arrayContaining([ + "dpkg", + "-i", + expect.stringContaining("libtinfo5"), + ]), + ); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + expect.arrayContaining([ + "dpkg", + "-i", + expect.stringContaining("libncursesw5"), + ]), + ); + }); + + it("downloads the arm64 ncurses debs from ports.ubuntu.com", async () => { + const inputs = { + ...baseInputs, + version: "24.3", + arch: Arch.ARM64, + }; + + // Simulate ncurses not installed + (exec.getExecOutput as jest.Mock).mockResolvedValue({ + stdout: "", + exitCode: 0, + }); + + await installDebian(inputs); + + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining([ + "https://ports.ubuntu.com/ubuntu-ports/pool/universe/n/ncurses/libtinfo5_6.3-2_arm64.deb", + ]), + ); + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining([ + "https://ports.ubuntu.com/ubuntu-ports/pool/universe/n/ncurses/libncursesw5_6.3-2_arm64.deb", + ]), + ); + }); + + it("skips ncurses install if already present", async () => { + const inputs = { ...baseInputs, version: "24.3" }; + + // Already installed + (exec.getExecOutput as jest.Mock).mockResolvedValue({ + stdout: "install ok installed install ok installed", + exitCode: 0, + }); + + await installDebian(inputs); + + // Should not download any .deb + expect(mockedExec).not.toHaveBeenCalledWith( + "curl", + expect.arrayContaining(["-o"]), + expect.anything(), + ); + }); + + it("skips ncurses install for newer nvhpc versions", async () => { + // Version > 24.3 + const inputs = { ...baseInputs, version: "25.1" }; + + await installDebian(inputs); + + // Should not download any .deb + expect(mockedExec).not.toHaveBeenCalledWith( + "curl", + expect.arrayContaining(["-o"]), + expect.anything(), + ); + }); + + it("exports compiler variables and returns the installation result", async () => { + const result = await installDebian(baseInputs); + + expect(result).toEqual({ + version: "nvfortran 24.1-0", + fc: "nvfortran", + cc: "nvc", + cxx: "nvc++", + }); + }); + + it("does not write global APT settings or rewrite Ubuntu mirrors", async () => { + await installDebian(baseInputs); + + expect(mockedExec).not.toHaveBeenCalledWith( + "sudo", + expect.arrayContaining([ + // Scoped-update options (`Dir::Etc::SourceList=...`) are runtime + // overrides, not file writes, so only real /etc/apt paths are checked. + expect.stringMatching( + /apt\.conf\.d|\/etc\/apt\/sources\.list|ubuntu\.sources/, + ), + ]), + ); + }); + + it("scopes apt-get update to the NVIDIA repository", async () => { + await installDebian(baseInputs); + + // Unrelated repositories baked into the runner image (e.g. transient + // packages.microsoft.com failures) must not break the nvhpc install. + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + expect.arrayContaining([ + "apt-get", + "update", + "-o", + "Dir::Etc::SourceList=sources.list.d/nvhpc.list", + "-o", + "Dir::Etc::SourceParts=-", + ]), + ); + }); + + it("allows 25 minutes for the nvhpc apt install", async () => { + await installDebian(baseInputs); + + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + expect.arrayContaining(["25m", "apt-get", "install", "nvhpc-24-1"]), + ); + }); + + it("falls back to the versioned tarball after one apt install failure", async () => { + const inputs = { ...baseInputs, version: "26.3" }; + mockedGetExecOutput.mockImplementation(async (command, args) => ({ + stdout: + command === "dpkg-query" + ? "install ok installed install ok installed" + : "", + stderr: "", + exitCode: + command === "curl" && + args?.some((arg) => arg.includes("_cuda_13.1.tar.gz")) + ? 0 + : command === "dpkg-query" + ? 0 + : 22, + })); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "nvfortran" && args?.[0] === "--version") { + options?.listeners?.stdout?.(Buffer.from("nvfortran 26.3-0")); + } + if ( + commandLine === "sudo" && + args?.includes("apt-get") && + args.includes("nvhpc-26-3") + ) { + throw new Error("404 Not Found"); + } + return 0; + }); + + await installDebian(inputs); + + expect( + mockedExec.mock.calls.filter( + ([command, args]) => + command === "sudo" && + args?.includes("apt-get") && + args.includes("nvhpc-26-3"), + ), + ).toHaveLength(1); + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining([ + "-o", + expect.stringContaining("nvhpc_2026_263_Linux_x86_64_cuda_13.1.tar.gz"), + "https://developer.download.nvidia.com/hpc-sdk/26.3/nvhpc_2026_263_Linux_x86_64_cuda_13.1.tar.gz", + ]), + ); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + expect.arrayContaining([ + "env", + "NVHPC_SILENT=true", + "NVHPC_INSTALL_DIR=/opt/nvidia/hpc_sdk", + "NVHPC_INSTALL_TYPE=single", + expect.stringContaining( + "nvhpc_2026_263_Linux_x86_64_cuda_13.1/install", + ), + ]), + ); + }); + + it("falls back to the 13.3 tarball for version 26.9", async () => { + const inputs = { ...baseInputs, version: "26.9" }; + mockedGetExecOutput.mockImplementation(async (command, args) => ({ + stdout: + command === "dpkg-query" + ? "install ok installed install ok installed" + : "", + stderr: "", + exitCode: + command === "curl" && + args?.some((arg) => arg.includes("_cuda_13.3.tar.gz")) + ? 0 + : command === "dpkg-query" + ? 0 + : 22, + })); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "nvfortran" && args?.[0] === "--version") { + options?.listeners?.stdout?.(Buffer.from("nvfortran 26.9-0")); + } + if ( + commandLine === "sudo" && + args?.includes("apt-get") && + args.includes("nvhpc-26-9") + ) { + throw new Error("404 Not Found"); + } + return 0; + }); + + await installDebian(inputs); + + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + expect.arrayContaining(["apt-get", "install", "nvhpc-26-9"]), + ); + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining([ + "-o", + expect.stringContaining("nvhpc_2026_269_Linux_x86_64_cuda_13.3.tar.gz"), + "https://developer.download.nvidia.com/hpc-sdk/26.9/nvhpc_2026_269_Linux_x86_64_cuda_13.3.tar.gz", + ]), + ); + expect(mockedExec).toHaveBeenCalledWith( + "sudo", + expect.arrayContaining([ + "env", + "NVHPC_SILENT=true", + "NVHPC_INSTALL_DIR=/opt/nvidia/hpc_sdk", + "NVHPC_INSTALL_TYPE=single", + expect.stringContaining( + "nvhpc_2026_269_Linux_x86_64_cuda_13.3/install", + ), + ]), + ); + }); + + it("retries the tarball install when the first curl download fails", async () => { + const inputs = { ...baseInputs, version: "20.7" }; + let tarballDownloads = 0; + mockedGetExecOutput.mockImplementation(async (command) => ({ + stdout: + command === "dpkg-query" + ? "install ok installed install ok installed" + : "", + stderr: "", + exitCode: 0, + })); + mockedExec.mockImplementation(async (commandLine, args, options) => { + if (commandLine === "nvfortran" && args?.[0] === "--version") { + options?.listeners?.stdout?.(Buffer.from("nvfortran 20.7-0")); + } + if ( + commandLine === "curl" && + args?.some( + (arg) => typeof arg === "string" && arg.includes("nvhpc_2020_207_"), + ) + ) { + tarballDownloads += 1; + if (tarballDownloads === 1) { + throw new Error("curl: (92) HTTP/2 framing layer error"); + } + } + return 0; + }); + + jest.useFakeTimers(); + const installPromise = installDebian(inputs); + + // 20.7 skips APT and goes straight to the tarball path. The first + // download fails, so we advance past the 15s backoff and let the + // second attempt succeed. + for (let i = 0; i < 10; i++) await Promise.resolve(); + jest.advanceTimersByTime(15_000); + for (let i = 0; i < 10; i++) await Promise.resolve(); + + await installPromise; + jest.useRealTimers(); + + // The tarball path runs directly for 20.7 (no APT attempt) and is retried + // once on failure. We expect exactly 2 download attempts, but the first + // attempt throws before reaching the installer, so only the second + // attempt actually invokes the NVIDIA installer. + expect(tarballDownloads).toBe(2); + expect( + mockedExec.mock.calls.filter( + ([command, args]) => + command === "sudo" && args?.includes("NVHPC_SILENT=true"), + ), + ).toHaveLength(1); + }); + + it("uses the CUDA 11.0 Arm tarball directly for version 20.9", async () => { + const inputs = { + ...baseInputs, + version: "20.9", + arch: Arch.ARM64, + }; + + await installDebian(inputs); + + expect(mockedExec).not.toHaveBeenCalledWith( + "bash", + expect.arrayContaining([ + "-c", + expect.stringContaining("nvidia-hpcsdk-archive-keyring"), + ]), + ); + expect(mockedExec).toHaveBeenCalledWith( + "curl", + expect.arrayContaining([ + "-o", + expect.stringContaining( + "nvhpc_2020_209_Linux_aarch64_cuda_11.0.tar.gz", + ), + "https://developer.download.nvidia.com/hpc-sdk/20.9/nvhpc_2020_209_Linux_aarch64_cuda_11.0.tar.gz", + ]), + ); + }); +}); diff --git a/__tests__/installers/platform_guards.test.ts b/__tests__/installers/platform_guards.test.ts new file mode 100644 index 00000000..9d79db95 --- /dev/null +++ b/__tests__/installers/platform_guards.test.ts @@ -0,0 +1,134 @@ +import { installAOCC } from "../../src/installers/aocc"; +import { installArmFlang } from "../../src/installers/armflang"; +import { installIFX } from "../../src/installers/ifx"; +import { installIFort } from "../../src/installers/ifort"; +import { installNVFortran } from "../../src/installers/nvfortran"; +import * as aoccDebian from "../../src/installers/aocc/debian"; +import * as armflangDebian from "../../src/installers/armflang/debian"; +import * as ifxDebian from "../../src/installers/ifx/debian"; +import * as ifortDebian from "../../src/installers/ifort/debian"; +import * as ifortWin32 from "../../src/installers/ifort/win32"; +import * as nvfortranDebian from "../../src/installers/nvfortran/debian"; +import { Arch, Compiler, Msystem, OS, type Inputs } from "../../src/types"; + +function makeInputs(overrides: Partial = {}): Inputs { + return { + compiler: Compiler.GFortran, + version: "latest", + os: OS.Linux, + osVersion: "24.04", + arch: Arch.X64, + msystem: Msystem.Native, + cleanupDisk: false, + updateEnvironment: true, + ...overrides, + }; +} + +// The platform guards are the FIRST statement of each installer's dispatch. +// Asserting the exact guard message (which can only originate there) together +// with a spy showing the platform-specific installer was never called proves +// that an unsupported compiler/OS/arch combination is rejected before any cache +// restore, download, package-repository change, or other mutation. +describe("platform guards fail fast before any mutation", () => { + it.each([ + [Compiler.NVFortran, installNVFortran, nvfortranDebian], + [Compiler.AOCC, installAOCC, aoccDebian], + [Compiler.ArmFlang, installArmFlang, armflangDebian], + ])( + "%s on macOS is rejected before its installer runs", + async (compiler, install, module) => { + const spy = jest.spyOn(module, "installDebian"); + await expect( + install(makeInputs({ compiler, os: OS.MacOS, arch: Arch.X64 })), + ).rejects.toThrow( + `${compiler} is only supported on Linux. Got: ${OS.MacOS}`, + ); + expect(spy).not.toHaveBeenCalled(); + spy.mockRestore(); + }, + ); + + it.each([ + [Compiler.NVFortran, installNVFortran, nvfortranDebian], + [Compiler.AOCC, installAOCC, aoccDebian], + [Compiler.ArmFlang, installArmFlang, armflangDebian], + ])( + "%s on Windows is rejected before its installer runs", + async (compiler, install, module) => { + const spy = jest.spyOn(module, "installDebian"); + await expect( + install(makeInputs({ compiler, os: OS.Windows, arch: Arch.X64 })), + ).rejects.toThrow( + `${compiler} is only supported on Linux. Got: ${OS.Windows}`, + ); + expect(spy).not.toHaveBeenCalled(); + spy.mockRestore(); + }, + ); + + // armflang's OS guard passes on Linux; the arch guard lives in resolveVersion, + // which is the first statement of installDebian — before any I/O. + it("rejects armflang on Linux x64 via resolveVersion (arch-level preflight)", async () => { + const spy = jest.spyOn(armflangDebian, "installDebian"); + await expect( + installArmFlang( + makeInputs({ + compiler: Compiler.ArmFlang, + os: OS.Linux, + arch: Arch.X64, + }), + ), + ).rejects.toThrow( + /No supported versions found for armflang on linux \(x64\)/, + ); + expect(spy).toHaveBeenCalledTimes(1); + spy.mockRestore(); + }); + + // --- ifx / intel alias on macOS: no silent fallback to ifort --- + it("ifx on macOS is rejected before its installer runs", async () => { + const spy = jest.spyOn(ifxDebian, "installDebian"); + await expect( + installIFX( + makeInputs({ compiler: Compiler.IFX, os: OS.MacOS, arch: Arch.X64 }), + ), + ).rejects.toThrow(/ifx is not supported on macOS/); + expect(spy).not.toHaveBeenCalled(); + spy.mockRestore(); + }); + + // --- ifort on unsupported architectures: fail via resolveVersion/resolveWindowsVersion --- + it("ifort on Linux ARM64 is rejected via resolveVersion (arch-level preflight)", async () => { + const spy = jest.spyOn(ifortDebian, "installDebian"); + await expect( + installIFort( + makeInputs({ + compiler: Compiler.IFort, + os: OS.Linux, + arch: Arch.ARM64, + }), + ), + ).rejects.toThrow( + /No supported versions found for ifort on linux \(arm64\)/, + ); + expect(spy).toHaveBeenCalledTimes(1); + spy.mockRestore(); + }); + + it("ifort on Windows ARM64 is rejected via resolveWindowsVersion (arch-level preflight)", async () => { + const spy = jest.spyOn(ifortWin32, "installWin32"); + await expect( + installIFort( + makeInputs({ + compiler: Compiler.IFort, + os: OS.Windows, + arch: Arch.ARM64, + osVersion: "2022", + }), + ), + ).rejects.toThrow(/not supported for Windows arm64/); + expect(spy).toHaveBeenCalledTimes(1); + spy.mockRestore(); + }); +}); diff --git a/__tests__/legacy_version_compat.test.ts b/__tests__/legacy_version_compat.test.ts new file mode 100644 index 00000000..ba441794 --- /dev/null +++ b/__tests__/legacy_version_compat.test.ts @@ -0,0 +1,389 @@ +import { resolveVersion, resolveWindowsVersion } from "../src/resolve_version"; +import { Arch, Compiler, LATEST, OS, Msystem, type Inputs } from "../src/types"; + +jest.mock("@actions/core"); + +// =========================================================================== +// Incumbent parity guard. +// +// Every green cell that fortran-lang/setup-fortran verified in its generated +// compatibility matrix (previously `setup-fortran/.github/compat/compat.csv`) +// must still be accepted by the replacement's resolvers after the selector is +// migrated to the canonical compiler name. The version tables below +// intentionally mirror the installed SUPPORTED_VERSIONS arrays in +// src/installers/*; they are kept in this dedicated test so dropping a legacy +// spelling produces a visible test failure instead of a quiet behaviour change. +// +// One incumbent behavior is a deliberate, documented deviation: +// - `ifx` (compiler: intel) on macOS: the replacement intentionally fails +// instead of silently redirecting to intel-classic/ifort. +// =========================================================================== + +type Os = "linux" | "macos" | "windows"; +type ArchLabel = "x64" | "arm64"; +type CompatCell = readonly [string, string, Os, ArchLabel]; + +// Generated from setup-fortran/.github/compat/compat.csv green cells +// (modern runners: ubuntu-22.04/24.04, macos-14/15/15-intel/26/26-intel, +// windows-2022/2025/2025-vs2026) with selectors mapped to canonical names. +const INCUMBENT_GREEN_CELLS: readonly CompatCell[] = [ + // AOCC 5.1.0 (X.Y.0 spelling is normalized to the 5.1 entry). + ["aocc", "5.1.0", "linux", "x64"], + // GCC 13/14/15 on every runner (gcc -> gfortran). + ["gfortran", "13", "linux", "x64"], + ["gfortran", "13", "macos", "arm64"], + ["gfortran", "13", "macos", "x64"], + ["gfortran", "13", "windows", "x64"], + ["gfortran", "14", "linux", "x64"], + ["gfortran", "14", "macos", "arm64"], + ["gfortran", "14", "macos", "x64"], + ["gfortran", "14", "windows", "x64"], + ["gfortran", "15", "linux", "x64"], + ["gfortran", "15", "macos", "arm64"], + ["gfortran", "15", "macos", "x64"], + ["gfortran", "15", "windows", "x64"], + // ifort (intel-classic) on Linux, macOS (x64 and ARM64 via Rosetta 2), and + // Windows. + ["ifort", "2021.1", "linux", "x64"], + ["ifort", "2021.1", "macos", "arm64"], + ["ifort", "2021.1", "macos", "x64"], + ["ifort", "2021.1.2", "linux", "x64"], + ["ifort", "2021.10", "linux", "x64"], + ["ifort", "2021.10", "macos", "arm64"], + ["ifort", "2021.10", "macos", "x64"], + ["ifort", "2021.10", "windows", "x64"], + ["ifort", "2021.11", "linux", "x64"], + ["ifort", "2021.11", "windows", "x64"], + ["ifort", "2021.12", "linux", "x64"], + ["ifort", "2021.12", "windows", "x64"], + ["ifort", "2021.2", "linux", "x64"], + ["ifort", "2021.2", "macos", "arm64"], + ["ifort", "2021.2", "macos", "x64"], + ["ifort", "2021.3", "macos", "arm64"], + ["ifort", "2021.3", "macos", "x64"], + ["ifort", "2021.4", "linux", "x64"], + ["ifort", "2021.5", "linux", "x64"], + ["ifort", "2021.5", "macos", "arm64"], + ["ifort", "2021.5", "macos", "x64"], + ["ifort", "2021.6", "linux", "x64"], + ["ifort", "2021.6", "macos", "arm64"], + ["ifort", "2021.6", "macos", "x64"], + ["ifort", "2021.6", "windows", "x64"], + ["ifort", "2021.7.1", "linux", "x64"], + ["ifort", "2021.8", "linux", "x64"], + ["ifort", "2021.8", "macos", "arm64"], + ["ifort", "2021.8", "macos", "x64"], + ["ifort", "2021.9", "linux", "x64"], + ["ifort", "2021.9", "macos", "arm64"], + ["ifort", "2021.9", "macos", "x64"], + ["ifort", "2021.9", "windows", "x64"], + // ifx (intel) on Linux — all 18 incumbent spellings. + ["ifx", "2021.1", "linux", "x64"], + ["ifx", "2021.1.2", "linux", "x64"], + ["ifx", "2021.2", "linux", "x64"], + ["ifx", "2021.4", "linux", "x64"], + ["ifx", "2022.0", "linux", "x64"], + ["ifx", "2022.1", "linux", "x64"], + ["ifx", "2022.2", "linux", "x64"], + ["ifx", "2022.2.1", "linux", "x64"], + ["ifx", "2023.0", "linux", "x64"], + ["ifx", "2023.1", "linux", "x64"], + ["ifx", "2023.2", "linux", "x64"], + ["ifx", "2024.0", "linux", "x64"], + ["ifx", "2024.1", "linux", "x64"], + ["ifx", "2025.0", "linux", "x64"], + ["ifx", "2025.2", "linux", "x64"], + ["ifx", "2025.3", "linux", "x64"], + ["ifx", "2026.0", "linux", "x64"], + ["ifx", "2026.1", "linux", "x64"], + // ifx (intel) on Windows — incumbent-verified set plus code-level 2023.0. + // ["ifx", "2022.1", "windows", "x64"], + ["ifx", "2022.2", "windows", "x64"], + ["ifx", "2023.0", "windows", "x64"], + ["ifx", "2023.1", "windows", "x64"], + ["ifx", "2023.2", "windows", "x64"], + ["ifx", "2024.0", "windows", "x64"], + ["ifx", "2024.1", "windows", "x64"], + ["ifx", "2025.0", "windows", "x64"], + ["ifx", "2025.2", "windows", "x64"], + ["ifx", "2025.3", "windows", "x64"], + ["ifx", "2026.0", "windows", "x64"], + ["ifx", "2026.1", "windows", "x64"], + // LFortran 0.57.0 / 0.58.0 on every runner. + ["lfortran", "0.57.0", "linux", "x64"], + ["lfortran", "0.57.0", "macos", "arm64"], + ["lfortran", "0.57.0", "macos", "x64"], + ["lfortran", "0.57.0", "windows", "x64"], + ["lfortran", "0.58.0", "linux", "x64"], + ["lfortran", "0.58.0", "macos", "arm64"], + ["lfortran", "0.58.0", "macos", "x64"], + ["lfortran", "0.58.0", "windows", "x64"], + // nvidia-hpc (nvfortran) on Ubuntu. + ["nvfortran", "23.11", "linux", "x64"], + ["nvfortran", "23.3", "linux", "x64"], + ["nvfortran", "23.5", "linux", "x64"], + ["nvfortran", "23.7", "linux", "x64"], + ["nvfortran", "23.9", "linux", "x64"], + ["nvfortran", "24.1", "linux", "x64"], + ["nvfortran", "24.3", "linux", "x64"], + ["nvfortran", "24.5", "linux", "x64"], + ["nvfortran", "25.1", "linux", "x64"], + ["nvfortran", "25.3", "linux", "x64"], + ["nvfortran", "25.5", "linux", "x64"], + ["nvfortran", "25.7", "linux", "x64"], + ["nvfortran", "25.9", "linux", "x64"], + ["nvfortran", "26.1", "linux", "x64"], +]; + +// --------------------------------------------------------------------------- +// Installer-mirroring supported tables (native msystem). Keep in sync with +// the SUPPORTED_VERSIONS arrays in src/installers/*. +// --------------------------------------------------------------------------- + +const GF = ["16", "15", "14", "13", "12", "11"] as const; + +const IFX_LINUX = [ + "2026.1", + "2026.0", + "2025.3", + "2025.2", + "2025.1", + "2025.0", + "2024.2", + "2024.1", + "2024.0", + "2023.2.4", + "2023.2.3", + "2023.2.2", + "2023.2.1", + "2023.2.0", + "2023.1.0", + "2023.0.0", + "2022.2.1", + "2022.2.0", + "2022.1.0", + "2022.0.2", + "2022.0.1", + "2021.4.0", + "2021.3.0", + "2021.2.0", + "2021.1.2", + "2021.1.1", +] as const; + +const IFX_WINDOWS = [ + "2026.1.1", + "2026.1.0", + "2026.0.0", + "2025.3.3", + "2025.3.2", + "2025.3.1", + "2025.3.0", + "2025.2.1", + "2025.2.0", + "2025.1.0", + "2025.0.4", + "2025.0.3", + "2025.0.1", + "2025.0.0", + "2024.2.1", + "2024.2.0", + "2024.1.0", + "2024.0.2", + "2024.0.1", + "2023.2.1", + "2023.2.0", + "2023.1.0", + "2023.0.0", + "2022.3.0", + "2022.2.0", +] as const; + +const IFORT_LINUX = [ + "2021.13", + "2021.12", + "2021.11", + "2021.10", + "2021.9", + "2021.8", + "2021.7.1", + "2021.7", + "2021.6", + "2021.5", + "2021.4", + "2021.3", + "2021.2", + "2021.1.2", + "2021.1", +] as const; + +const IFORT_MACOS = [ + "2021.10", + "2021.9", + "2021.8", + "2021.6", + "2021.5", + "2021.3", + "2021.2", + "2021.1", +] as const; + +const IFORT_WINDOWS = [ + "2021.13", + "2021.12", + "2021.11", + "2021.10", + "2021.9", + "2021.7", + "2021.6", +] as const; + +const NVF = [ + "26.9", + "26.5", + "26.3", + "26.1", + "25.11", + "25.9", + "25.7", + "25.5", + "25.3", + "25.1", + "24.11", + "24.9", + "24.7", + "24.5", + "24.3", + "24.1", + "23.11", + "23.9", + "23.7", + "23.5", + "23.3", + "23.1", + "22.11", + "22.9", + "22.7", + "22.5", + "22.3", + "22.2", + "22.1", + "21.11", + "21.9", + "21.7", + "21.5", + "21.3", + "21.2", + "21.1", + "20.11", + "20.9", + "20.7", +] as const; + +const LF = [ + "0.67.0", + "0.66.0", + "0.65.0", + "0.64.0", + "0.63.0", + "0.62.0", + "0.61.0", + "0.60.0", + "0.59.0", + "0.58.0", + "0.57.0", +] as const; + +const AOCC = ["5.2", "5.1", "5.0", "4.2", "4.1"] as const; + +function buildInputs( + compiler: string, + version: string, + os: string, + arch: string, +): Inputs { + const platform = + os === "windows" ? OS.Windows : os === "macos" ? OS.MacOS : OS.Linux; + return { + compiler: compiler as Compiler, + version, + os: platform, + osVersion: "22.04", + arch: arch === "arm64" ? Arch.ARM64 : Arch.X64, + cleanupDisk: false, + updateEnvironment: true, + msystem: Msystem.Native, + }; +} + +function winTable( + versions: readonly string[], +): Record | undefined> { + return { + [Arch.X64]: { + [Msystem.Native]: versions, + [Msystem.UCRT64]: [LATEST], + [Msystem.Clang64]: [LATEST], + }, + [Arch.ARM64]: undefined, + }; +} + +function archTable( + versions: readonly string[], +): Record { + return { [Arch.X64]: versions, [Arch.ARM64]: undefined }; +} + +// Both architectures are supported for these compilers on the recorded OSes. +function bothArchTable( + versions: readonly string[], +): Record { + return { [Arch.X64]: versions, [Arch.ARM64]: versions }; +} + +function resolve(cell: CompatCell): string { + const [compiler, version, os, arch] = cell; + const inputs = buildInputs(compiler, version, os, arch); + + switch (compiler) { + case "gfortran": + return resolveVersion(inputs, bothArchTable(GF)); + case "ifx": + if (os === "windows") { + return resolveWindowsVersion(inputs, winTable(IFX_WINDOWS), { + resolveMinorToLatestPatch: true, + }); + } + return resolveVersion(inputs, archTable(IFX_LINUX), { + resolveMinorToLatestPatch: true, + }); + case "ifort": + if (os === "windows") { + return resolveWindowsVersion(inputs, winTable(IFORT_WINDOWS)); + } + return resolveVersion( + inputs, + os === "macos" ? bothArchTable(IFORT_MACOS) : archTable(IFORT_LINUX), + ); + case "nvfortran": + return resolveVersion(inputs, archTable(NVF)); + case "lfortran": + return resolveVersion(inputs, bothArchTable(LF)); + case "aocc": + return resolveVersion(inputs, archTable(AOCC), { + stripPatchZero: true, + }); + default: + throw new Error(`Unknown compiler in test fixture: ${compiler}`); + } +} + +describe("incumbent compat.csv parity", () => { + it.each(INCUMBENT_GREEN_CELLS)( + "accepts %s %s on %s/%s", + (compiler, version, os, arch) => { + expect(() => resolve([compiler, version, os, arch])).not.toThrow(); + }, + ); +}); diff --git a/__tests__/lfortran_environment.test.ts b/__tests__/lfortran_environment.test.ts new file mode 100644 index 00000000..1cf2d986 --- /dev/null +++ b/__tests__/lfortran_environment.test.ts @@ -0,0 +1,121 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import { condaCreateWithRetry } from "../src/lfortran_environment"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); + +const mockedExec = exec.exec as jest.MockedFunction; +const mockedInfo = core.info as jest.MockedFunction; + +const CONDA_BIN = "/tool-cache/miniforge/bin/conda"; +const CREATE_ARGS = [ + "create", + "-y", + "-p", + "/tool-cache/env", + "-c", + "conda-forge", + "lfortran==0.63.0", +]; + +beforeEach(() => { + // resetAllMocks (not clearAllMocks) drops any lingering mock implementation + // so each test starts from a clean, default-resolving exec mock. + jest.resetAllMocks(); + jest.useFakeTimers(); +}); + +afterEach(() => { + jest.useRealTimers(); +}); + +describe("condaCreateWithRetry", () => { + it("creates the environment on the first attempt without retrying", async () => { + mockedExec.mockResolvedValue(0); + + await condaCreateWithRetry(CONDA_BIN, CREATE_ARGS); + + expect(mockedExec).toHaveBeenCalledTimes(1); + expect(mockedInfo).not.toHaveBeenCalled(); + }); + + it("runs conda with ignoreReturnCode so a non-zero exit is retryable", async () => { + mockedExec.mockResolvedValue(0); + + await condaCreateWithRetry(CONDA_BIN, CREATE_ARGS); + + expect(mockedExec).toHaveBeenCalledWith( + CONDA_BIN, + CREATE_ARGS, + expect.objectContaining({ ignoreReturnCode: true }), + ); + }); + + it("retries once and succeeds after a transient failure", async () => { + mockedExec.mockResolvedValueOnce(1).mockResolvedValueOnce(0); + + const install = condaCreateWithRetry(CONDA_BIN, CREATE_ARGS); + + await jest.advanceTimersByTimeAsync(15_000); + await install; + + expect(mockedExec).toHaveBeenCalledTimes(2); + expect(mockedInfo).toHaveBeenCalledTimes(1); + expect(mockedInfo).toHaveBeenCalledWith( + "conda create failed (attempt 1/3), retrying in 15s...", + ); + }); + + it("escalates the backoff before each subsequent retry", async () => { + mockedExec + .mockResolvedValueOnce(1) + .mockResolvedValueOnce(1) + .mockResolvedValueOnce(0); + + const install = condaCreateWithRetry(CONDA_BIN, CREATE_ARGS); + + await jest.advanceTimersByTimeAsync(15_000); + expect(mockedExec).toHaveBeenCalledTimes(2); + expect(mockedInfo).toHaveBeenNthCalledWith( + 1, + "conda create failed (attempt 1/3), retrying in 15s...", + ); + + await jest.advanceTimersByTimeAsync(30_000); + await install; + + expect(mockedExec).toHaveBeenCalledTimes(3); + expect(mockedInfo).toHaveBeenNthCalledWith( + 2, + "conda create failed (attempt 2/3), retrying in 30s...", + ); + }); + + it("gives up after exhausting all retries", async () => { + mockedExec.mockResolvedValue(1); + + const install = condaCreateWithRetry(CONDA_BIN, CREATE_ARGS, 3); + const expectation = expect(install).rejects.toThrow( + "conda create failed after 3 attempts.", + ); + + await jest.advanceTimersByTimeAsync(15_000); + await jest.advanceTimersByTimeAsync(30_000); + await expectation; + + expect(mockedExec).toHaveBeenCalledTimes(3); + expect(mockedInfo).toHaveBeenCalledTimes(2); + }); + + it("does not warn or retry when only one attempt is allowed", async () => { + mockedExec.mockResolvedValue(1); + + await expect( + condaCreateWithRetry(CONDA_BIN, CREATE_ARGS, 1), + ).rejects.toThrow("conda create failed after 1 attempts."); + + expect(mockedExec).toHaveBeenCalledTimes(1); + expect(mockedInfo).not.toHaveBeenCalled(); + }); +}); diff --git a/__tests__/miniforge.test.ts b/__tests__/miniforge.test.ts new file mode 100644 index 00000000..2a10ea0b --- /dev/null +++ b/__tests__/miniforge.test.ts @@ -0,0 +1,25 @@ +import { miniforgeInstaller, MINIFORGE_VERSION } from "../src/miniforge"; +import { Arch, OS } from "../src/types"; + +describe("Miniforge installer metadata", () => { + it.each([ + [OS.Linux, Arch.X64], + [OS.Linux, Arch.ARM64], + [OS.MacOS, Arch.X64], + [OS.MacOS, Arch.ARM64], + [OS.Windows, Arch.X64], + ])("pins a versioned installer for %s %s", (os, arch) => { + const installer = miniforgeInstaller(os, arch); + + expect(installer.url).toContain(`/download/${MINIFORGE_VERSION}/`); + expect(installer.url).not.toContain("/latest/"); + expect(installer.filename).toContain(MINIFORGE_VERSION); + expect(installer.sha256).toMatch(/^[a-f0-9]{64}$/); + }); + + it("rejects unsupported platforms", () => { + expect(() => miniforgeInstaller(OS.Windows, Arch.ARM64)).toThrow( + "unavailable", + ); + }); +}); diff --git a/__tests__/parse_input.test.ts b/__tests__/parse_input.test.ts new file mode 100644 index 00000000..978da51c --- /dev/null +++ b/__tests__/parse_input.test.ts @@ -0,0 +1,517 @@ +import * as core from "@actions/core"; +import * as os from "os"; +import { parseInputs } from "../src/parse_inputs"; +import { installIFX } from "../src/installers/ifx"; +import { Compiler, OS, Arch, Msystem, LATEST } from "../src/types"; + +jest.mock("@actions/core"); +jest.mock("os"); + +describe("parseInputs", () => { + const mockedGetInput = core.getInput as jest.MockedFunction< + typeof core.getInput + >; + const mockedGetBooleanInput = core.getBooleanInput as jest.MockedFunction< + typeof core.getBooleanInput + >; + const mockedArch = os.arch as jest.MockedFunction; + const mockedRelease = os.release as jest.MockedFunction; + + let originalPlatform: string; + let originalEnv: NodeJS.ProcessEnv; + + beforeAll(() => { + originalPlatform = process.platform; + originalEnv = { ...process.env }; + }); + + afterAll(() => { + Object.defineProperty(process, "platform", { + value: originalPlatform, + }); + process.env = originalEnv; + }); + + beforeEach(() => { + jest.clearAllMocks(); + mockedGetInput.mockReturnValue(""); + mockedGetBooleanInput.mockImplementation((name) => { + if (name === "update-environment") return true; + return false; + }); + mockedArch.mockReturnValue("x64"); + mockedRelease.mockReturnValue("5.15.0"); + delete process.env.ImageOS; + setPlatform("linux"); + }); + + function setPlatform(platform: string) { + Object.defineProperty(process, "platform", { + value: platform, + configurable: true, + }); + } + + it("returns default values when no inputs are provided", () => { + const result = parseInputs(); + expect(result).toEqual({ + compiler: Compiler.GFortran, + version: LATEST, + os: OS.Linux, + osVersion: "5.15.0", + arch: Arch.X64, + msystem: Msystem.Native, + cleanupDisk: false, + updateEnvironment: true, + }); + }); + + it("handles whitespace-only inputs by falling back to defaults where appropriate", () => { + mockedGetInput.mockReturnValue(" "); + const result = parseInputs(); + expect(result).toEqual({ + compiler: Compiler.GFortran, + version: LATEST, + os: OS.Linux, + osVersion: "5.15.0", + arch: Arch.X64, + msystem: Msystem.Native, + cleanupDisk: false, + updateEnvironment: true, + }); + }); + + describe("compiler input", () => { + it("parses valid compiler names case-insensitively", () => { + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return " IFX "; + return ""; + }); + const result = parseInputs(); + expect(result.compiler).toBe(Compiler.IFX); + }); + + it.each([ + [Compiler.GFortran, "gfortran"], + [Compiler.IFX, "ifx"], + [Compiler.IFort, "ifort"], + [Compiler.NVFortran, "nvfortran"], + [Compiler.AOCC, "aocc"], + [Compiler.Flang, "flang"], + [Compiler.LFortran, "lfortran"], + [Compiler.ArmFlang, "armflang"], + ])("parses %s compiler", (expected, input) => { + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return input; + return ""; + }); + expect(parseInputs().compiler).toBe(expected); + }); + + it.each([ + ["gcc", Compiler.GFortran], + ["intel", Compiler.IFX], + ["intel-classic", Compiler.IFort], + ["nvidia-hpc", Compiler.NVFortran], + ])("maps the %s alias to %s", (input, expected) => { + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return input; + return ""; + }); + expect(parseInputs().compiler).toBe(expected); + }); + + it.each([ + ["gcc", "gfortran"], + ["intel", "ifx"], + ["intel-classic", "ifort"], + ["nvidia-hpc", "nvfortran"], + ])( + "produces identical inputs for alias %s and canonical name %s", + (alias, canonical) => { + let compilerInput = alias; + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return compilerInput; + if (name === "version") return "2025.2"; + return ""; + }); + + const aliasInputs = parseInputs(); + compilerInput = canonical; + const canonicalInputs = parseInputs(); + + expect(aliasInputs).toEqual(canonicalInputs); + }, + ); + + it("maps aliases case-insensitively and ignores surrounding whitespace", () => { + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return " InTeL-ClAsSiC "; + return ""; + }); + expect(parseInputs().compiler).toBe(Compiler.IFort); + }); + + it.each([ + ["gcc", Compiler.GFortran], + ["intel", Compiler.IFX], + ["intel-classic", Compiler.IFort], + ["nvidia-hpc", Compiler.NVFortran], + ])( + "warns that the %s alias is deprecated in favor of %s", + (alias, canonical) => { + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return alias; + return ""; + }); + parseInputs(); + expect(core.warning).toHaveBeenCalledWith( + `The compiler selector "${alias}" is deprecated. Please use "${canonical}" instead.`, + ); + }, + ); + + it("warns for aliases case-insensitively and with surrounding whitespace", () => { + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return " InTeL-ClAsSiC "; + return ""; + }); + parseInputs(); + expect(core.warning).toHaveBeenCalledWith( + 'The compiler selector "intel-classic" is deprecated. Please use "ifort" instead.', + ); + }); + + it("does not warn when a canonical compiler name is used", () => { + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return "ifx"; + return ""; + }); + parseInputs(); + expect(core.warning).not.toHaveBeenCalled(); + }); + + it("does not warn when the default compiler is used", () => { + mockedGetInput.mockReturnValue(""); + parseInputs(); + expect(core.warning).not.toHaveBeenCalled(); + }); + + it("maps intel to ifx on macOS and retains ifx unsupported-platform behavior", async () => { + setPlatform("darwin"); + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return "intel"; + return ""; + }); + + const inputs = parseInputs(); + expect(inputs.compiler).toBe(Compiler.IFX); + await expect(installIFX(inputs)).rejects.toThrow( + "ifx is not supported on macOS.", + ); + }); + + it("throws error for unknown compiler", () => { + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return "unknown-compiler"; + return ""; + }); + expect(() => parseInputs()).toThrow( + 'Unknown compiler "unknown-compiler". Valid options: gfortran, ifx, ifort, nvfortran, aocc, flang, lfortran, armflang, gcc, intel, intel-classic, nvidia-hpc', + ); + }); + + // Dedicated alias compatibility matrix: every alias must map to its + // canonical compiler deterministically on ALL OS families, regardless of + // whether the canonical compiler is ultimately installable on that platform. + // The canonical integration workflows alone cannot catch alias behavior + // because they exercise canonical names; this matrix ensures aliases are + // transparent substitutions at the input layer. + describe("alias compatibility matrix across all OS families", () => { + const ALIASES: [string, Compiler][] = [ + ["gcc", Compiler.GFortran], + ["intel", Compiler.IFX], + ["intel-classic", Compiler.IFort], + ["nvidia-hpc", Compiler.NVFortran], + ]; + const PLATFORMS: [string, OS][] = [ + ["linux", OS.Linux], + ["darwin", OS.MacOS], + ["win32", OS.Windows], + ]; + + it.each(ALIASES)( + "alias '%s' maps to %s on all three OS families", + (alias, canonical) => { + for (const [platform, expectedOS] of PLATFORMS) { + setPlatform(platform); + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return alias; + return ""; + }); + + const inputs = parseInputs(); + expect(inputs.compiler).toBe(canonical); + expect(inputs.os).toBe(expectedOS); + } + }, + ); + + it.each(ALIASES)( + "alias '%s' and canonical name '%s' produce identical parsed inputs on all OS families", + (alias, canonical) => { + for (const [platform, expectedOS] of PLATFORMS) { + setPlatform(platform); + + // Parse with alias + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return alias; + if (name === "version") return "2025.2"; + return ""; + }); + const aliasInputs = parseInputs(); + + // Parse with canonical + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return canonical; + if (name === "version") return "2025.2"; + return ""; + }); + const canonicalInputs = parseInputs(); + + expect(aliasInputs).toEqual(canonicalInputs); + expect(aliasInputs.os).toBe(expectedOS); + } + }, + ); + }); + }); + + describe("version input", () => { + it("returns the provided version string", () => { + mockedGetInput.mockImplementation((name) => { + if (name === "version") return "13.2.0"; + return ""; + }); + const result = parseInputs(); + expect(result.version).toBe("13.2.0"); + }); + + it("handles year-based versions like 2022.2.1", () => { + mockedGetInput.mockImplementation((name) => { + if (name === "version") return "2022.2.1"; + return ""; + }); + const result = parseInputs(); + expect(result.version).toBe("2022.2.1"); + }); + + it("handles short year-based versions like 2025.2", () => { + mockedGetInput.mockImplementation((name) => { + if (name === "version") return "2025.2"; + return ""; + }); + const result = parseInputs(); + expect(result.version).toBe("2025.2"); + }); + + it("trims whitespace from version strings", () => { + mockedGetInput.mockImplementation((name) => { + if (name === "version") return " 14.1 "; + return ""; + }); + const result = parseInputs(); + expect(result.version).toBe("14.1"); + }); + + it("accepts the literal string 'latest' and resolves to LATEST", () => { + mockedGetInput.mockImplementation((name) => { + if (name === "version") return "latest"; + return ""; + }); + const result = parseInputs(); + expect(result.version).toBe(LATEST); + }); + }); + + describe("mixed inputs", () => { + it("correctly merges provided inputs with defaults", () => { + mockedGetInput.mockImplementation((name) => { + if (name === "compiler") return "ifort"; + // version is missing, should be default + return ""; + }); + const result = parseInputs(); + expect(result).toMatchObject({ + compiler: Compiler.IFort, + version: LATEST, + os: OS.Linux, + }); + }); + }); + + describe("defaults across all OS families", () => { + const PLATFORMS: [string, OS][] = [ + ["linux", OS.Linux], + ["darwin", OS.MacOS], + ["win32", OS.Windows], + ]; + + it.each(PLATFORMS)( + "defaults compiler to gfortran, version to LATEST, and updateEnvironment to true on %s", + (platform, expectedOS) => { + setPlatform(platform); + const result = parseInputs(); + expect(result).toMatchObject({ + compiler: Compiler.GFortran, + version: LATEST, + os: expectedOS, + updateEnvironment: true, + }); + }, + ); + + it.each(PLATFORMS)( + "update-environment=false is parsed on %s", + (platform) => { + setPlatform(platform); + mockedGetBooleanInput.mockImplementation((name) => { + if (name === "update-environment") return false; + return false; + }); + const result = parseInputs(); + expect(result.updateEnvironment).toBe(false); + }, + ); + + it.each(PLATFORMS)( + "omitted compiler defaults to gfortran and omitted version defaults to LATEST on %s", + (platform) => { + setPlatform(platform); + const result = parseInputs(); + expect(result.compiler).toBe(Compiler.GFortran); + expect(result.version).toBe(LATEST); + }, + ); + }); + + describe("msystem input", () => { + it("parses valid msystem names case-insensitively", () => { + mockedGetInput.mockImplementation((name) => { + if (name === "msystem") return " UCRT64 "; + return ""; + }); + const result = parseInputs(); + expect(result.msystem).toBe(Msystem.UCRT64); + }); + + it("throws error for unknown msystem", () => { + mockedGetInput.mockImplementation((name) => { + if (name === "msystem") return "msys"; // incomplete + return ""; + }); + expect(() => parseInputs()).toThrow( + 'Unknown msystem "msys". Valid options: native, ucrt64', + ); + }); + }); + + describe("update-environment input", () => { + it("defaults to true when omitted", () => { + // getBooleanInput is mocked, so this simulates the action.yml default + // being applied; it does not exercise the real parsing path. + mockedGetBooleanInput.mockImplementation((name) => { + if (name === "update-environment") return true; + return false; + }); + const result = parseInputs(); + expect(result.updateEnvironment).toBe(true); + }); + + it("parses true", () => { + mockedGetBooleanInput.mockImplementation((name) => { + if (name === "update-environment") return true; + return false; + }); + const result = parseInputs(); + expect(result.updateEnvironment).toBe(true); + }); + + it("parses false", () => { + mockedGetBooleanInput.mockImplementation((name) => { + if (name === "update-environment") return false; + return false; + }); + const result = parseInputs(); + expect(result.updateEnvironment).toBe(false); + }); + + it("throws for invalid values", () => { + mockedGetBooleanInput.mockImplementation((name) => { + if (name === "update-environment") { + throw new Error( + 'Input "update-environment" must be true or false; received "maybe".', + ); + } + return false; + }); + expect(() => parseInputs()).toThrow( + 'Input "update-environment" must be true or false; received "maybe".', + ); + }); + }); + + describe("OS detection", () => { + it("detects Linux", () => { + setPlatform("linux"); + expect(parseInputs().os).toBe(OS.Linux); + }); + + it("detects MacOS", () => { + setPlatform("darwin"); + expect(parseInputs().os).toBe(OS.MacOS); + }); + + it("detects Windows", () => { + setPlatform("win32"); + expect(parseInputs().os).toBe(OS.Windows); + }); + + it("throws for unsupported OS", () => { + setPlatform("freebsd"); + expect(() => parseInputs()).toThrow( + 'Not implemented yet: "freebsd" case', + ); + }); + }); + + describe("Architecture detection", () => { + it("detects x64", () => { + mockedArch.mockReturnValue("x64"); + expect(parseInputs().arch).toBe(Arch.X64); + }); + + it("detects arm64", () => { + mockedArch.mockReturnValue("arm64"); + expect(parseInputs().arch).toBe(Arch.ARM64); + }); + + it("throws for unsupported architecture", () => { + mockedArch.mockReturnValue("arm" as any); + expect(() => parseInputs()).toThrow('Not implemented yet: "arm" case'); + }); + }); + + describe("osVersion population", () => { + it("uses ImageOS environment variable if available", () => { + process.env.ImageOS = "ubuntu22"; + expect(parseInputs().osVersion).toBe("ubuntu22"); + }); + + it("falls back to os.release() if ImageOS is not set", () => { + delete process.env.ImageOS; + mockedRelease.mockReturnValue("22.04.1-Ubuntu"); + expect(parseInputs().osVersion).toBe("22.04.1-Ubuntu"); + }); + }); +}); diff --git a/__tests__/resolve_version.test.ts b/__tests__/resolve_version.test.ts new file mode 100644 index 00000000..a4cc90fb --- /dev/null +++ b/__tests__/resolve_version.test.ts @@ -0,0 +1,1218 @@ +import { + resolveVersion, + resolveWindowsVersion, + resolveLatestPatch, + resolveLatestPatchWithAsset, + verifyAssetExists, + stripTrailingPatchZero, +} from "../src/resolve_version"; +import { Arch, Compiler, LATEST, OS, Msystem } from "../src/types"; +import type { Inputs } from "../src/types"; +import * as core from "@actions/core"; + +jest.mock("@actions/core"); + +const baseInputs: Inputs = { + compiler: Compiler.GFortran, + version: LATEST, + os: OS.Linux, + osVersion: "22.04", + arch: Arch.X64, + msystem: Msystem.Native, + cleanupDisk: false, + updateEnvironment: true, +}; + +const SUPPORTED: Record = { + [Arch.X64]: ["15", "14", "13"], + [Arch.ARM64]: ["14", "13"], +}; + +describe("resolveVersion", () => { + beforeAll(() => { + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + json: async () => [{ tag_name: "llvmorg-19.1.7", prerelease: false }], + } as unknown as Response); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + afterAll(() => { + jest.restoreAllMocks(); + }); + + describe("when version is LATEST", () => { + it("returns the first entry for x64", () => { + const result = resolveVersion(baseInputs, SUPPORTED); + expect(result).toBe("15"); + }); + + it("returns the first entry for arm64", () => { + const inputs: Inputs = { ...baseInputs, arch: Arch.ARM64 }; + const result = resolveVersion(inputs, SUPPORTED); + expect(result).toBe("14"); + }); + }); + + describe("when a specific version is requested", () => { + it("returns the version if it is supported", () => { + const inputs: Inputs = { ...baseInputs, version: "14" }; + const result = resolveVersion(inputs, SUPPORTED); + expect(result).toBe("14"); + }); + + it("handles 2-part versions like 24.1 if present in supported list", () => { + const inputs: Inputs = { ...baseInputs, version: "24.1" }; + const supported = { [Arch.X64]: ["24.3", "24.1"] }; + const result = resolveVersion(inputs, supported); + expect(result).toBe("24.1"); + }); + + it("throws if the version is not supported on this arch", () => { + const inputs: Inputs = { ...baseInputs, arch: Arch.ARM64, version: "15" }; + expect(() => resolveVersion(inputs, SUPPORTED)).toThrow( + "gfortran 15 is not supported on linux (arm64). Supported versions: 14, 13", + ); + }); + + it("throws if the version is not supported on any arch", () => { + const inputs: Inputs = { ...baseInputs, version: "9" }; + expect(() => resolveVersion(inputs, SUPPORTED)).toThrow( + "gfortran 9 is not supported on linux (x64). Supported versions: 15, 14, 13", + ); + }); + }); + + describe("resolveMinorToLatestPatch", () => { + const supported = { + [Arch.X64]: ["2025.2.1", "2025.2.0", "2025.1.0", "2024.0.0"], + }; + + it("resolves YYYY.minor to the latest patch if resolveMinorToLatestPatch is true", () => { + const inputs: Inputs = { ...baseInputs, version: "2025.2" }; + const result = resolveVersion(inputs, supported, { + resolveMinorToLatestPatch: true, + }); + expect(result).toBe("2025.2.1"); + }); + + it("returns the original version if resolveMinorToLatestPatch is false", () => { + const inputs: Inputs = { ...baseInputs, version: "2024.0.0" }; + const result = resolveVersion(inputs, supported, { + resolveMinorToLatestPatch: false, + }); + expect(result).toBe("2024.0.0"); + }); + + it("does not affect 3-part versions", () => { + const inputs: Inputs = { ...baseInputs, version: "2025.2.0" }; + const result = resolveVersion(inputs, supported, { + resolveMinorToLatestPatch: true, + }); + expect(result).toBe("2025.2.0"); + }); + + it("returns the latest patch even if the requested version is a latest patch", () => { + const inputs: Inputs = { ...baseInputs, version: "2025.2.1" }; + const result = resolveVersion(inputs, supported, { + resolveMinorToLatestPatch: true, + }); + expect(result).toBe("2025.2.1"); + }); + }); + + describe("stripPatchZero option", () => { + const supported = { + [Arch.X64]: ["5.2", "5.1", "5.0", "4.2", "4.1"], + }; + + const mockedWarning = core.warning as jest.MockedFunction< + typeof core.warning + >; + + beforeEach(() => { + mockedWarning.mockClear(); + }); + + it("normalizes X.Y.0 to X.Y when stripPatchZero is true", () => { + const inputs: Inputs = { + ...baseInputs, + compiler: Compiler.AOCC, + version: "5.1.0", + }; + const result = resolveVersion(inputs, supported, { + stripPatchZero: true, + }); + expect(result).toBe("5.1"); + expect(mockedWarning).toHaveBeenCalledWith( + expect.stringContaining( + 'normalized to "5.1". Consider dropping the patch number.', + ), + ); + }); + + it("normalizes X.Y.0 for any AOCC-like version", () => { + const inputs: Inputs = { + ...baseInputs, + compiler: Compiler.AOCC, + version: "4.2.0", + }; + const result = resolveVersion(inputs, supported, { + stripPatchZero: true, + }); + expect(result).toBe("4.2"); + expect(mockedWarning).toHaveBeenCalledWith( + expect.stringContaining( + 'normalized to "4.2". Consider dropping the patch number.', + ), + ); + }); + + it("leaves X.Y untouched when stripPatchZero is true", () => { + const inputs: Inputs = { + ...baseInputs, + compiler: Compiler.AOCC, + version: "5.1", + }; + const result = resolveVersion(inputs, supported, { + stripPatchZero: true, + }); + expect(result).toBe("5.1"); + expect(mockedWarning).not.toHaveBeenCalled(); + }); + + it("does not normalize X.Y.1 (non-zero patch) and fails clearly", () => { + const inputs: Inputs = { + ...baseInputs, + compiler: Compiler.AOCC, + version: "5.1.1", + }; + expect(() => + resolveVersion(inputs, supported, { stripPatchZero: true }), + ).toThrow( + "aocc 5.1.1 is not supported on linux (x64). Supported versions: 5.2, 5.1, 5.0, 4.2, 4.1", + ); + expect(mockedWarning).not.toHaveBeenCalled(); + }); + + it("does not normalize when stripPatchZero is false (default)", () => { + const inputs: Inputs = { + ...baseInputs, + compiler: Compiler.AOCC, + version: "5.1.0", + }; + expect(() => resolveVersion(inputs, supported)).toThrow( + "aocc 5.1.0 is not supported on linux (x64). Supported versions: 5.2, 5.1, 5.0, 4.2, 4.1", + ); + expect(mockedWarning).not.toHaveBeenCalled(); + }); + + it("still resolves latest when stripPatchZero is true", () => { + const inputs: Inputs = { + ...baseInputs, + compiler: Compiler.AOCC, + version: LATEST, + }; + const result = resolveVersion(inputs, supported, { + stripPatchZero: true, + }); + expect(result).toBe("5.2"); + expect(mockedWarning).not.toHaveBeenCalled(); + }); + }); +}); + +describe("stripTrailingPatchZero", () => { + it.each([ + ["5.1.0", "5.1"], + ["4.2.0", "4.2"], + ["5.0.0", "5.0"], + ["10.1.0", "10.1"], + ])("normalizes %s to %s", (input, expected) => { + expect(stripTrailingPatchZero(input)).toBe(expected); + }); + + it.each(["5.1", "5.1.1", "5.1.2", "latest", "5", "5.0.1"])( + "leaves %s unchanged", + (input) => { + expect(stripTrailingPatchZero(input)).toBe(input); + }, + ); +}); + +describe("resolveWindowsVersion", () => { + const winInputs: Inputs = { + ...baseInputs, + os: OS.Windows, + osVersion: "2022", + arch: Arch.X64, + msystem: Msystem.Native, + }; + + const SUPPORTED_WIN = { + [Arch.X64]: { + [Msystem.Native]: ["14", "13"], + [Msystem.UCRT64]: ["latest"], + }, + }; + + it("returns the requested version if it matches the first entry for x64", () => { + const result = resolveWindowsVersion(winInputs, SUPPORTED_WIN as any); + expect(result).toBe("14"); + }); + + it("returns the requested version if supported", () => { + const inputs: Inputs = { ...winInputs, version: "13" }; + const result = resolveWindowsVersion(inputs, SUPPORTED_WIN as any); + expect(result).toBe("13"); + }); + + it("returns latest for UCRT64 msystem", () => { + const inputs: Inputs = { ...winInputs, msystem: Msystem.UCRT64 }; + const result = resolveWindowsVersion(inputs, SUPPORTED_WIN as any); + expect(result).toBe("latest"); + }); + + it("throws if version is not supported", () => { + const inputs: Inputs = { ...winInputs, version: "9" }; + expect(() => resolveWindowsVersion(inputs, SUPPORTED_WIN as any)).toThrow( + "gfortran 9 is not supported on win32 (x64). Supported versions: 14, 13", + ); + }); +}); + +describe("resolveVersion / resolveWindowsVersion error paths", () => { + const linuxInputs: Inputs = { + compiler: Compiler.GFortran, + version: LATEST, + os: OS.Linux, + osVersion: "22.04", + arch: Arch.X64, + msystem: Msystem.Native, + cleanupDisk: false, + updateEnvironment: true, + }; + + it("throws when the requested arch has no supported-versions entry", () => { + // An entirely absent arch cell (`undefined`) — the "unsupported" sentinel. + expect(() => resolveVersion(linuxInputs, {})).toThrow( + "No supported versions found for gfortran on linux (x64).", + ); + }); + + it("throws a clear error when 'latest' resolves an empty version list", () => { + const supported = { [Arch.X64]: [] as readonly string[] }; + expect(() => resolveVersion(linuxInputs, supported)).toThrow( + "No supported versions found for gfortran on linux (x64).", + ); + }); + + it("refuses an explicit version when the version list is empty", () => { + const supported = { [Arch.X64]: [] as readonly string[] }; + const inputs: Inputs = { ...linuxInputs, version: "14" }; + // `versions[0]` is undefined only for "latest"; an explicit version still + // reaches `versionList.includes`, which misses on `[]` and emits a message + // whose `Supported versions:` tail is empty — the symptom of a structural + // defect that supported_versions.test.ts ("is non-empty") forbids in real + // tables. This pins resolveVersion's failure mode regardless. + expect(() => resolveVersion(inputs, supported)).toThrow( + /gfortran 14 is not supported on linux \(x64\)\. Supported versions: $/, + ); + }); + + it("throws when an unsupported arch is requested on Windows", () => { + const win: Inputs = { + ...linuxInputs, + os: OS.Windows, + osVersion: "2022", + msystem: Msystem.Native, + }; + // x64 is intentionally absent from the table. + expect(() => resolveWindowsVersion(win, {} as any)).toThrow( + /Architecture "x64" is not supported for gfortran on Windows\./, + ); + }); + + it("throws when the msystem is unsupported on Windows", () => { + const win: Inputs = { + ...linuxInputs, + os: OS.Windows, + osVersion: "2022", + msystem: Msystem.Native, + }; + // x64 is present, but no msystem cell is defined. + expect(() => resolveWindowsVersion(win, { [Arch.X64]: {} } as any)).toThrow( + /The environment "native" is not supported for Windows x64\./, + ); + }); +}); + +describe("resolveLatestPatch", () => { + beforeEach(() => { + jest.useFakeTimers(); + global.fetch = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + it("resolves the latest patch version correctly", async () => { + const mockFetch = global.fetch as jest.Mock; + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => [{ tag_name: "llvmorg-19.1.7", prerelease: false }], + }); + + const result = await resolveLatestPatch("llvm/llvm-project", "19"); + expect(result).toBe("19.1.7"); + expect(mockFetch).toHaveBeenCalledTimes(1); + }); + + it("retries on HTTP error and eventually succeeds", async () => { + const mockFetch = global.fetch as jest.Mock; + mockFetch + .mockResolvedValueOnce({ + ok: false, + status: 500, + statusText: "Internal Server Error", + }) + .mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => [{ tag_name: "llvmorg-19.1.7", prerelease: false }], + }); + + const promise = resolveLatestPatch("llvm/llvm-project", "19"); + + await Promise.resolve(); + await Promise.resolve(); + + await jest.advanceTimersByTimeAsync(4000); + + const result = await promise; + expect(result).toBe("19.1.7"); + expect(mockFetch).toHaveBeenCalledTimes(2); + }); + + it("retries on timeout and eventually succeeds", async () => { + const mockFetch = global.fetch as jest.Mock; + + mockFetch.mockImplementationOnce( + (_url, options) => + new Promise((_resolve, reject) => { + if (options.signal) { + options.signal.addEventListener("abort", () => { + const error = new Error("The operation was aborted"); + error.name = "AbortError"; + reject(error); + }); + } + }), + ); + + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => [{ tag_name: "llvmorg-19.1.7", prerelease: false }], + }); + + const promise = resolveLatestPatch("llvm/llvm-project", "19"); + + await Promise.resolve(); + await Promise.resolve(); + + await jest.advanceTimersByTimeAsync(5000); + + await Promise.resolve(); + await Promise.resolve(); + + await jest.advanceTimersByTimeAsync(4000); + + const result = await promise; + expect(result).toBe("19.1.7"); + expect(mockFetch).toHaveBeenCalledTimes(2); + }); + + it("throws error after exhausting all retries", async () => { + const mockFetch = global.fetch as jest.Mock; + mockFetch.mockResolvedValue({ + ok: false, + status: 502, + statusText: "Bad Gateway", + }); + + const promise = resolveLatestPatch("llvm/llvm-project", "19"); + promise.catch(() => {}); + + await Promise.resolve(); + await Promise.resolve(); + + await jest.advanceTimersByTimeAsync(4000); + + await Promise.resolve(); + await Promise.resolve(); + + await jest.advanceTimersByTimeAsync(8000); + + await Promise.resolve(); + await Promise.resolve(); + + await expect(promise).rejects.toThrow("Request failed after 3 attempts"); + expect(mockFetch).toHaveBeenCalledTimes(3); + }); + + it("respects custom tagPrefix and tagStripper", async () => { + const mockFetch = global.fetch as jest.Mock; + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => [{ tag_name: "v1.2.3", prerelease: false }], + }); + + const result = await resolveLatestPatch("repo", "1", "v1.", (tag) => + tag.substring(1), + ); + expect(result).toBe("1.2.3"); + }); + + it("throws error if no stable release is found", async () => { + const mockFetch = global.fetch as jest.Mock; + mockFetch.mockResolvedValue({ + ok: true, + status: 200, + json: async () => [ + { tag_name: "llvmorg-19.1.0-rc1", prerelease: false }, + { tag_name: "llvmorg-20.0.0", prerelease: true }, + ], + }); + + const promise = resolveLatestPatch("llvm/llvm-project", "19"); + + await expect(promise).rejects.toThrow( + "No stable release found for llvm/llvm-project major 19 within visible historical GitHub releases.", + ); + expect(mockFetch).toHaveBeenCalledTimes(3); + }); + + it("waits for a nearby GitHub rate-limit reset", async () => { + const mockFetch = global.fetch as jest.Mock; + const resetTime = Math.floor(Date.now() / 1000) + 2; + + mockFetch + .mockResolvedValueOnce({ + ok: false, + status: 403, + headers: new Headers({ + "x-ratelimit-reset": resetTime.toString(), + }), + }) + .mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => [{ tag_name: "llvmorg-19.1.7", prerelease: false }], + }); + + const promise = resolveLatestPatch("llvm/llvm-project", "19"); + + await Promise.resolve(); + await Promise.resolve(); + + await jest.advanceTimersByTimeAsync(3000); + + const result = await promise; + expect(result).toBe("19.1.7"); + expect(mockFetch).toHaveBeenCalledTimes(2); + }); + + it("fails immediately when the GitHub rate-limit reset is too distant", async () => { + const mockFetch = global.fetch as jest.Mock; + const resetTime = Math.floor(Date.now() / 1000) + 60 * 60; + mockFetch.mockResolvedValueOnce({ + ok: false, + status: 403, + headers: new Headers({ + "x-ratelimit-reset": resetTime.toString(), + }), + }); + + await expect(resolveLatestPatch("llvm/llvm-project", "19")).rejects.toThrow( + /exceeds the 30-second maximum wait.*GITHUB_TOKEN/, + ); + expect(mockFetch).toHaveBeenCalledTimes(1); + expect(core.warning).not.toHaveBeenCalledWith( + expect.stringContaining("Sleeping for"), + ); + }); + + it("reports an invalid GitHub rate-limit reset header", async () => { + const mockFetch = global.fetch as jest.Mock; + mockFetch.mockResolvedValueOnce({ + ok: false, + status: 429, + headers: new Headers({ "x-ratelimit-reset": "not-a-timestamp" }), + }); + + await expect(resolveLatestPatch("llvm/llvm-project", "19")).rejects.toThrow( + "invalid x-ratelimit-reset value", + ); + expect(mockFetch).toHaveBeenCalledTimes(1); + }); + + it("fails safely when a rate-limit response has no reset header", async () => { + const mockFetch = global.fetch as jest.Mock; + mockFetch.mockResolvedValueOnce({ + ok: false, + status: 429, + headers: new Headers(), + }); + + await expect(resolveLatestPatch("llvm/llvm-project", "19")).rejects.toThrow( + /without a usable x-ratelimit-reset header.*cannot retry safely/, + ); + expect(mockFetch).toHaveBeenCalledTimes(1); + }); +}); + +describe("verifyAssetExists", () => { + beforeEach(() => { + jest.useFakeTimers(); + global.fetch = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + jest.useRealTimers(); + }); + + it("verifies asset existence successfully", async () => { + const mockFetch = global.fetch as jest.Mock; + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ + assets: [{ name: "fortran.tar.gz" }, { name: "other.zip" }], + }), + }); + + await expect( + verifyAssetExists("repo", "19.1.7", "fortran.tar.gz"), + ).resolves.not.toThrow(); + expect(mockFetch).toHaveBeenCalledTimes(1); + }); + + it("returns the GitHub-provided SHA-256 digest", async () => { + const mockFetch = global.fetch as jest.Mock; + const digest = "a".repeat(64); + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ + assets: [{ name: "fortran.tar.gz", digest: `sha256:${digest}` }], + }), + }); + + await expect( + verifyAssetExists("repo", "19.1.7", "fortran.tar.gz"), + ).resolves.toBe(digest); + }); + + it("throws error if release does not exist (404)", async () => { + const mockFetch = global.fetch as jest.Mock; + mockFetch.mockResolvedValueOnce({ + ok: false, + status: 404, + json: async () => ({ message: "Not Found" }), + }); + + await expect( + verifyAssetExists("repo", "19.1.7", "fortran.tar.gz"), + ).rejects.toThrow( + 'Requested version "19.1.7" does not exist (no release for llvmorg-19.1.7 in repo).', + ); + expect(mockFetch).toHaveBeenCalledTimes(1); + }); + + it("throws error if asset is missing in existing release", async () => { + const mockFetch = global.fetch as jest.Mock; + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ + assets: [{ name: "other.zip" }], + }), + }); + + await expect( + verifyAssetExists("repo", "19.1.7", "fortran.tar.gz"), + ).rejects.toThrow( + 'Release llvmorg-19.1.7 in repo exists but has no asset "fortran.tar.gz".', + ); + expect(mockFetch).toHaveBeenCalledTimes(1); + }); + + it("retries on network error and succeeds", async () => { + const mockFetch = global.fetch as jest.Mock; + mockFetch + .mockResolvedValueOnce({ + ok: false, + status: 503, + statusText: "Service Unavailable", + }) + .mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ + assets: [{ name: "fortran.tar.gz" }], + }), + }); + + const promise = verifyAssetExists("repo", "19.1.7", "fortran.tar.gz"); + + await Promise.resolve(); + await Promise.resolve(); + + await jest.advanceTimersByTimeAsync(4000); + + await expect(promise).resolves.not.toThrow(); + expect(mockFetch).toHaveBeenCalledTimes(2); + }); + + it("retries on timeout and succeeds", async () => { + const mockFetch = global.fetch as jest.Mock; + + mockFetch.mockImplementationOnce( + (_url, options) => + new Promise((_resolve, reject) => { + if (options.signal) { + options.signal.addEventListener("abort", () => { + const error = new Error("The operation was aborted"); + error.name = "AbortError"; + reject(error); + }); + } + }), + ); + + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ + assets: [{ name: "fortran.tar.gz" }], + }), + }); + + const promise = verifyAssetExists("repo", "19.1.7", "fortran.tar.gz"); + + await Promise.resolve(); + await Promise.resolve(); + + await jest.advanceTimersByTimeAsync(5000); + + await Promise.resolve(); + await Promise.resolve(); + + await jest.advanceTimersByTimeAsync(4000); + + await expect(promise).resolves.not.toThrow(); + expect(mockFetch).toHaveBeenCalledTimes(2); + }); + + it("respects custom tagFromPatch", async () => { + const mockFetch = global.fetch as jest.Mock; + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + json: async () => ({ + assets: [{ name: "fortran.tar.gz" }], + }), + }); + + await verifyAssetExists("repo", "1.2.3", "fortran.tar.gz", (p) => `v${p}`); + + expect(mockFetch).toHaveBeenCalledWith( + "https://api.github.com/repos/repo/releases/tags/v1.2.3", + expect.any(Object), + ); + }); +}); + +// =========================================================================== +// Year-version coercion: ambiguous bare numbers are rejected, not recovered. +// +// GitHub Actions coerces an unquoted `version: 2024.0` YAML scalar to the bare +// string "2024", which is ambiguous ("2024.0" vs "latest in 2024"). Rather than +// guess, the resolver rejects bare numeric versions for compilers whose +// supported tables never contain a bare integer (ifx/ifort/nvfortran/lfortran/ +// aocc/armflang), pointing the user at an exact quoted table entry. gfortran and +// flang accept bare integer majors and are exempt. +// =========================================================================== + +// Representative installer tables (mirrors src/installers/ifx/*). +const IFX_LINUX = [ + "2026.1", + "2026.0", + "2025.3", + "2025.2", + "2025.0", + "2024.1", + "2024.0", + "2023.2.0", + "2022.2.1", + "2022.1.0", + "2021.4.0", + "2021.1.2", +] as const; + +const IFX_WINDOWS: Record< + string, + Record +> = { + [Arch.X64]: { + [Msystem.Native]: [ + "2026.1.1", + "2026.0.0", + "2025.0.0", + "2024.0.2", + "2024.0.1", + "2023.0.0", + "2022.2.0", + ], + [Msystem.UCRT64]: undefined, + [Msystem.Clang64]: undefined, + }, +}; + +const IFORT_LINUX = [ + "2021.13", + "2021.12", + "2021.10", + "2021.9", + "2021.7.1", + "2021.1.2", + "2021.1", +] as const; + +const NVFORTRAN_LINUX = ["26.1", "25.9", "25.5", "24.1", "23.1"] as const; + +function ifxInputs( + version: string, + os: OS = OS.Linux, + arch: Arch = Arch.X64, +): Inputs { + return { + ...baseInputs, + compiler: Compiler.IFX, + version, + os, + arch, + msystem: Msystem.Native, + }; +} + +function ifortInputs(version: string): Inputs { + return { + ...baseInputs, + compiler: Compiler.IFort, + version, + os: OS.Linux, + arch: Arch.X64, + msystem: Msystem.Native, + }; +} + +function nvfortranInputs(version: string): Inputs { + return { + ...baseInputs, + compiler: Compiler.NVFortran, + version, + os: OS.Linux, + arch: Arch.X64, + msystem: Msystem.Native, + }; +} + +const AMBIGUOUS_ERROR = /ambiguous and must be quoted/; + +describe("year-version coercion: reject ambiguous bare numbers", () => { + const mockedWarning = core.warning as jest.MockedFunction< + typeof core.warning + >; + beforeEach(() => jest.clearAllMocks()); + + it("rejects a bare year for ifx on Linux with an actionable error", () => { + const inputs = ifxInputs("2024", OS.Linux); + expect(() => + resolveVersion(inputs, { + [Arch.X64]: IFX_LINUX, + [Arch.ARM64]: undefined, + }), + ).toThrow(AMBIGUOUS_ERROR); + // The error must NOT be the generic "is not supported" fallthrough. + expect(() => + resolveVersion(inputs, { + [Arch.X64]: IFX_LINUX, + [Arch.ARM64]: undefined, + }), + ).not.toThrow(/is not supported/); + }); + + it("rejects a bare year for ifx on Windows", () => { + const inputs = ifxInputs("2024", OS.Windows); + expect(() => + resolveWindowsVersion(inputs, IFX_WINDOWS, { + resolveMinorToLatestPatch: true, + }), + ).toThrow(AMBIGUOUS_ERROR); + }); + + it.each(["2024", "2025", "2026", "2021", "2030"])( + "rejects every bare ifx year %s without silent resolution", + (year) => { + const inputs = ifxInputs(year, OS.Linux); + expect(() => + resolveVersion(inputs, { + [Arch.X64]: IFX_LINUX, + [Arch.ARM64]: undefined, + }), + ).toThrow(AMBIGUOUS_ERROR); + }, + ); + + it("rejects a bare year for ifort on Linux (ifort has no bare-integer releases)", () => { + expect(() => + resolveVersion(ifortInputs("2021"), { + [Arch.X64]: IFORT_LINUX, + [Arch.ARM64]: undefined, + }), + ).toThrow(AMBIGUOUS_ERROR); + }); + + it("rejects a bare number for nvfortran (e.g. a coerced '26.0' -> '26')", () => { + expect(() => + resolveVersion(nvfortranInputs("26"), { + [Arch.X64]: NVFORTRAN_LINUX, + [Arch.ARM64]: undefined, + }), + ).toThrow(AMBIGUOUS_ERROR); + }); + + it("does NOT reject bare numbers for gfortran (bare integers are valid majors)", () => { + const inputs = { + ...baseInputs, + compiler: Compiler.GFortran, + version: "14", + }; + expect( + resolveVersion(inputs, { + [Arch.X64]: ["16", "15", "14", "13"], + [Arch.ARM64]: ["15", "14", "13"], + }), + ).toBe("14"); + expect(mockedWarning).not.toHaveBeenCalled(); + }); + + it("still gives gfortran a generic (non-ambiguity) error for a bare but unsupported integer", () => { + const inputs = { + ...baseInputs, + compiler: Compiler.GFortran, + version: "99", + }; + expect(() => + resolveVersion(inputs, { + [Arch.X64]: ["16", "15", "14", "13"], + [Arch.ARM64]: ["15", "14", "13"], + }), + ).toThrow(/gfortran 99 is not supported/); + // gfortran is exempt, so the ambiguity branch must NOT fire. + expect(() => + resolveVersion(inputs, { + [Arch.X64]: ["16", "15", "14", "13"], + [Arch.ARM64]: ["15", "14", "13"], + }), + ).not.toThrow(AMBIGUOUS_ERROR); + }); + + it("accepts quoted year.0 versions unchanged (no error, no warning)", () => { + const resolved = resolveVersion(ifxInputs("2024.0", OS.Linux), { + [Arch.X64]: IFX_LINUX, + [Arch.ARM64]: undefined, + }); + expect(resolved).toBe("2024.0"); + expect(mockedWarning).not.toHaveBeenCalled(); + }); + + it("accepts 2-part year versions that round-trip in YAML (no coercion)", () => { + // "2024.1" survives unquoted -> it is a real table entry and must resolve. + expect( + resolveVersion(ifxInputs("2024.1", OS.Linux), { + [Arch.X64]: IFX_LINUX, + [Arch.ARM64]: undefined, + }), + ).toBe("2024.1"); + expect(mockedWarning).not.toHaveBeenCalled(); + }); + + it("leaves the unrecoverable 2021.10 -> '2021.1' collision to the existing patch-prefix fallthrough", () => { + // "2021.1" (coerced from 2021.10) is NOT a bare integer, so the ambiguity + // guard does not fire; resolveMinorToLatestPatch (which ifx uses) expands it. + // This is the documented, unrecoverable case addressed by quoting. + expect( + resolveVersion( + ifxInputs("2021.1", OS.Linux), + { + [Arch.X64]: IFX_LINUX, + [Arch.ARM64]: undefined, + }, + { resolveMinorToLatestPatch: true }, + ), + ).toBe("2021.1.2"); + }); + + it("ambiguity error message names the compiler, the bad input, and the gfortran/flang exemption", () => { + let msg = ""; + try { + resolveVersion(ifxInputs("2024", OS.Linux), { + [Arch.X64]: IFX_LINUX, + [Arch.ARM64]: undefined, + }); + } catch (e) { + msg = (e as Error).message; + } + expect(msg).toContain("ifx"); + expect(msg).toContain('"2024"'); + expect(msg).toContain("ambiguous"); + expect(msg).toContain("quoted exactly as listed"); + // The suggested examples are drawn from the real table, not hardcoded. + expect(msg).toContain('"2026.1"'); + expect(msg).toContain('"2026.0"'); + expect(msg).toContain("gfortran and flang accept bare"); + expect(msg).toContain("Supported versions"); + }); + + it("advocates real table entries in the error, not year-shaped examples (lfortran)", () => { + const inputs = { + ...baseInputs, + compiler: Compiler.LFortran, + version: "0", + }; + let msg = ""; + try { + resolveVersion(inputs, { + [Arch.X64]: ["0.64.0", "0.63.0"], + [Arch.ARM64]: undefined, + }); + } catch (e) { + msg = (e as Error).message; + } + expect(msg).toMatch(AMBIGUOUS_ERROR); + expect(msg).toContain('"0.64.0"'); + expect(msg).not.toContain("2026"); + }); + + it("rejects a bare number for aocc (aocc uses dotted minor releases, never bare integers)", () => { + const inputs = { + ...baseInputs, + compiler: Compiler.AOCC, + version: "5", + }; + expect(() => + resolveVersion( + inputs, + { [Arch.X64]: ["5.2", "5.1", "5.0"], [Arch.ARM64]: undefined }, + { stripPatchZero: true }, + ), + ).toThrow(AMBIGUOUS_ERROR); + }); +}); + +describe("resolveLatestPatchWithAsset", () => { + beforeEach(() => { + jest.useRealTimers(); + global.fetch = jest.fn(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + const woa64 = (p: string) => `LLVM-${p}-woa64.msi`; + + function listResponse(releases: unknown[]) { + return { + ok: true, + status: 200, + json: async () => releases, + }; + } + + function tagResponse(names: string[]) { + return { + ok: true, + status: 200, + json: async () => ({ assets: names.map((name) => ({ name })) }), + }; + } + + // Mocks the releases list (paginated; only page 1 has data, rest empty) + // so the helper never runs past the mocked pages. + function mockListOnce(releases: unknown[]) { + const mockFetch = global.fetch as jest.Mock; + mockFetch.mockImplementation(async (url: string | URL) => { + const s = String(url); + if (s.includes("/releases?")) { + const page = new URL(s).searchParams.get("page") ?? "1"; + return page === "1" ? listResponse(releases) : listResponse([]); + } + throw new Error(`unexpected fetch: ${s}`); + }); + return mockFetch; + } + + it("returns the newest patch when it ships the asset", async () => { + const mockFetch = mockListOnce([ + { + tag_name: "llvmorg-23.1.1", + prerelease: false, + assets: [{ name: woa64("23.1.1") }, { name: "other" }], + }, + { + tag_name: "llvmorg-23.1.0", + prerelease: false, + assets: [{ name: woa64("23.1.0") }], + }, + ]); + + const digest = "c".repeat(64); + mockFetch.mockImplementation(async (url: string | URL) => { + const s = String(url); + if (s.includes("/releases?")) { + const page = new URL(s).searchParams.get("page") ?? "1"; + return page === "1" + ? listResponse([ + { + tag_name: "llvmorg-23.1.1", + prerelease: false, + assets: [{ name: woa64("23.1.1") }, { name: "other" }], + }, + { + tag_name: "llvmorg-23.1.0", + prerelease: false, + assets: [{ name: woa64("23.1.0") }], + }, + ]) + : listResponse([]); + } + return { + ok: true, + status: 200, + json: async () => ({ + assets: [{ name: woa64("23.1.1"), digest: `sha256:${digest}` }], + }), + }; + }); + + const result = await resolveLatestPatchWithAsset( + "llvm/llvm-project", + "23", + woa64, + ); + expect(result).toEqual({ patch: "23.1.1", digest }); + // List + single verify of the chosen patch. + expect(mockFetch).toHaveBeenCalledTimes(2); + }); + + it("skips a newer patch missing the asset (23.1.2 has no woa64.msi)", async () => { + const mockFetch = global.fetch as jest.Mock; + const digest = "d".repeat(64); + mockFetch.mockImplementation(async (url: string | URL) => { + const s = String(url); + if (s.includes("/releases?")) { + const page = new URL(s).searchParams.get("page") ?? "1"; + return page === "1" + ? listResponse([ + { + tag_name: "llvmorg-23.1.2", + prerelease: false, + assets: [{ name: "LLVM-23.1.2-win64.msi" }], + }, + { + tag_name: "llvmorg-23.1.1", + prerelease: false, + assets: [ + { name: "LLVM-23.1.1-win64.msi" }, + { name: woa64("23.1.1") }, + ], + }, + ]) + : listResponse([]); + } + return { + ok: true, + status: 200, + json: async () => ({ + assets: [{ name: woa64("23.1.1"), digest: `sha256:${digest}` }], + }), + }; + }); + + const result = await resolveLatestPatchWithAsset( + "llvm/llvm-project", + "23", + woa64, + ); + expect(result).toEqual({ patch: "23.1.1", digest }); + expect(mockFetch).toHaveBeenCalledTimes(2); + }); + + it("probes per-tag when the list embeds no asset names", async () => { + const mockFetch = global.fetch as jest.Mock; + mockFetch.mockImplementation(async (url: string | URL) => { + const s = String(url); + if (s.includes("/releases?")) { + const page = new URL(s).searchParams.get("page") ?? "1"; + if (page !== "1") return listResponse([]); + return listResponse([ + { tag_name: "llvmorg-23.1.2", prerelease: false }, + { tag_name: "llvmorg-23.1.1", prerelease: false }, + ]); + } + if (s.includes("llvmorg-23.1.1")) { + return tagResponse(["LLVM-23.1.1-win64.msi", woa64("23.1.1")]); + } + return tagResponse(["LLVM-23.1.2-win64.msi"]); + }); + + const result = await resolveLatestPatchWithAsset( + "llvm/llvm-project", + "23", + woa64, + ); + // No digest in the tag mock, so digest is undefined — still resolves. + expect(result).toEqual({ patch: "23.1.1", digest: undefined }); + // 1 list fetch + 2 tag probes (newest fails, older succeeds; the + // successful probe's digest is reused, no extra verify call). + expect(mockFetch).toHaveBeenCalledTimes(3); + }); + + it("throws when no patch ships the asset", async () => { + mockListOnce([ + { + tag_name: "llvmorg-23.1.2", + prerelease: false, + assets: [{ name: "LLVM-23.1.2-win64.msi" }], + }, + ]); + + await expect( + resolveLatestPatchWithAsset("llvm/llvm-project", "23", woa64), + ).rejects.toThrow(/No stable release found.*with required asset.*23\.1\.2/); + }); +}); diff --git a/__tests__/setup_msvc.test.ts b/__tests__/setup_msvc.test.ts new file mode 100644 index 00000000..a92023ef --- /dev/null +++ b/__tests__/setup_msvc.test.ts @@ -0,0 +1,134 @@ +import * as core from "@actions/core"; +import * as fs from "fs"; +import * as path from "path"; +import { + addIntelCompilerBinFromPath, + addMsvcBinFromPath, + persistMsvcBinForBash, + toMsysPath, +} from "../src/setup_msvc"; + +jest.mock("@actions/core"); +jest.mock("fs"); + +describe("addMsvcBinFromPath", () => { + const runnerTemp = "D:\\a\\_temp"; + // Built the same way persistMsvcBinForBash builds it (plain path.join), + // so this matches regardless of whether the test runs on Linux CI or a + // native Windows machine: both sides track the same host OS. + const msvcBashEnvPath = path.join( + runnerTemp, + "setup-fortran-msvc-bash-env.sh", + ); + + beforeEach(() => { + jest.clearAllMocks(); + process.env.RUNNER_TEMP = runnerTemp; + delete process.env.BASH_ENV; + }); + + afterAll(() => { + delete process.env.RUNNER_TEMP; + delete process.env.BASH_ENV; + }); + + it("persists the MSVC executable directory", () => { + const msvcBin = + "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC\\14.44.35207\\bin\\HostX64\\x64"; + + expect(addMsvcBinFromPath(`C:\\tools;${msvcBin};C:\\Windows`)).toBe( + msvcBin, + ); + expect(core.addPath).toHaveBeenCalledWith(msvcBin); + expect(core.exportVariable).toHaveBeenCalledWith( + "BASH_ENV", + toMsysPath(msvcBashEnvPath), + ); + }); + + it("warns when the MSVC executable directory is absent", () => { + expect(addMsvcBinFromPath("C:\\tools;C:\\Windows")).toBeUndefined(); + expect(core.addPath).not.toHaveBeenCalled(); + expect(core.warning).toHaveBeenCalledWith( + "Could not find the MSVC executable directory in PATH.", + ); + }); + + it("prepends the MSVC directory for subsequent Bash steps", () => { + const msvcBin = + "C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\VC\\Tools\\MSVC\\14.51.36231\\bin\\HostX64\\x64"; + + expect(persistMsvcBinForBash(msvcBin)).toBe(msvcBashEnvPath); + expect(fs.writeFileSync).toHaveBeenCalledWith( + msvcBashEnvPath, + `export PATH='/c/Program Files/Microsoft Visual Studio/18/Enterprise/VC/Tools/MSVC/14.51.36231/bin/HostX64/x64':"$PATH"\n`, + { mode: 0o600 }, + ); + }); + + it("preserves an existing Bash environment file", () => { + process.env.BASH_ENV = "D:\\a\\existing env.sh"; + + persistMsvcBinForBash("C:\\MSVC\\bin"); + + expect(fs.writeFileSync).toHaveBeenCalledWith( + expect.any(String), + `. '/d/a/existing env.sh'\nexport PATH='/c/MSVC/bin':"$PATH"\n`, + { mode: 0o600 }, + ); + }); + + it("does not source itself when setup runs more than once", () => { + process.env.BASH_ENV = toMsysPath(msvcBashEnvPath); + + persistMsvcBinForBash("C:\\MSVC\\bin"); + + expect(fs.writeFileSync).toHaveBeenCalledWith( + expect.any(String), + `export PATH='/c/MSVC/bin':"$PATH"\n`, + { mode: 0o600 }, + ); + }); +}); + +describe("addIntelCompilerBinFromPath", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("registers ifx's compiler bin directory", () => { + const ifxBin = + "C:\\Program Files (x86)\\Intel\\oneAPI\\compiler\\latest\\bin"; + + expect(addIntelCompilerBinFromPath(`C:\\tools;${ifxBin};C:\\Windows`)).toBe( + ifxBin, + ); + expect(core.addPath).toHaveBeenCalledWith(ifxBin); + }); + + it("registers classic ifort's compiler bin directory", () => { + const ifortBin = + "C:\\Program Files (x86)\\Intel\\oneAPI\\compiler\\latest\\windows\\bin"; + + expect(addIntelCompilerBinFromPath(`C:\\tools;${ifortBin}`)).toBe(ifortBin); + expect(core.addPath).toHaveBeenCalledWith(ifortBin); + }); + + it("warns when the Intel compiler executable directory is absent", () => { + expect( + addIntelCompilerBinFromPath("C:\\tools;C:\\Windows"), + ).toBeUndefined(); + expect(core.addPath).not.toHaveBeenCalled(); + expect(core.warning).toHaveBeenCalledWith( + "Could not find the Intel compiler executable directory in PATH.", + ); + }); +}); + +describe("toMsysPath", () => { + it("converts Windows drive paths for Git Bash", () => { + expect(toMsysPath("C:\\Program Files\\LLVM\\bin")).toBe( + "/c/Program Files/LLVM/bin", + ); + }); +}); diff --git a/__tests__/setup_msvc_bash.test.ts b/__tests__/setup_msvc_bash.test.ts new file mode 100644 index 00000000..a56d9fb6 --- /dev/null +++ b/__tests__/setup_msvc_bash.test.ts @@ -0,0 +1,83 @@ +import * as core from "@actions/core"; +import { execFileSync } from "child_process"; +import { mkdtempSync, rmSync, writeFileSync } from "fs"; +import { tmpdir } from "os"; +import { join } from "path"; +import { persistMsvcBinForBash, toMsysPath } from "../src/setup_msvc"; + +jest.mock("@actions/core"); + +describe("persistMsvcBinForBash shell integration", () => { + let testDir: string; + const originalRunnerTemp = process.env.RUNNER_TEMP; + const originalBashEnv = process.env.BASH_ENV; + + beforeEach(() => { + jest.clearAllMocks(); + testDir = mkdtempSync(join(tmpdir(), "setup-fortran-bash-env-")); + process.env.RUNNER_TEMP = testDir; + delete process.env.BASH_ENV; + }); + + afterEach(() => { + rmSync(testDir, { recursive: true, force: true }); + }); + + afterAll(() => { + if (originalRunnerTemp === undefined) delete process.env.RUNNER_TEMP; + else process.env.RUNNER_TEMP = originalRunnerTemp; + + if (originalBashEnv === undefined) delete process.env.BASH_ENV; + else process.env.BASH_ENV = originalBashEnv; + }); + + it("prepends the tool directory in a fresh non-interactive Bash", () => { + const msvcBin = "/opt/MSVC's tools/bin"; + const bashEnv = persistMsvcBinForBash(msvcBin); + + const firstPathEntry = execFileSync( + "bash", + ["--noprofile", "--norc", "-c", 'printf "%s" "${PATH%%:*}"'], + { + encoding: "utf8", + env: { ...process.env, BASH_ENV: bashEnv }, + }, + ); + + expect(firstPathEntry).toBe(msvcBin); + // The exported BASH_ENV is msys-converted (toMsysPath) so Git Bash can + // source it; the returned bashEnv is the native path used internally + // (e.g. by fs.writeFileSync) and only equals the exported value on + // POSIX, where toMsysPath is a no-op. + expect(core.exportVariable).toHaveBeenCalledWith( + "BASH_ENV", + toMsysPath(bashEnv), + ); + }); + + it("sources a pre-existing Bash environment before adding the tool path", () => { + const previousBashEnv = join(testDir, "existing env.sh"); + writeFileSync( + previousBashEnv, + "export EXISTING_BASH_ENV_WAS_SOURCED=yes\n", + ); + process.env.BASH_ENV = previousBashEnv; + + const bashEnv = persistMsvcBinForBash("/opt/msvc/bin"); + const result = execFileSync( + "bash", + [ + "--noprofile", + "--norc", + "-c", + 'printf "%s|%s" "$EXISTING_BASH_ENV_WAS_SOURCED" "${PATH%%:*}"', + ], + { + encoding: "utf8", + env: { ...process.env, BASH_ENV: bashEnv }, + }, + ); + + expect(result).toBe("yes|/opt/msvc/bin"); + }); +}); diff --git a/__tests__/setup_msys2.test.ts b/__tests__/setup_msys2.test.ts new file mode 100644 index 00000000..d3c14d0a --- /dev/null +++ b/__tests__/setup_msys2.test.ts @@ -0,0 +1,210 @@ +import * as core from "@actions/core"; +import * as exec from "@actions/exec"; +import { + setupMSYS2, + msys2PackageAvailable, + msys2PkgName, + pacmanInstallWithRetry, +} from "../src/setup_msys2"; +import { Msystem } from "../src/types"; + +jest.mock("@actions/core"); +jest.mock("@actions/exec"); + +const mockedExec = exec.exec as jest.MockedFunction; +const mockedInfo = core.info as jest.MockedFunction; + +const PACMAN_CMD = "C:\\msys64\\usr\\bin\\bash.exe"; + +function expectPacmanCall(pkgList: string): void { + expect(mockedExec).toHaveBeenCalledWith(PACMAN_CMD, [ + "-lc", + `pacman -S --noconfirm --needed ${pkgList}`, + ]); +} + +beforeEach(() => { + // resetAllMocks (not clearAllMocks) drops any lingering mock implementation + // — e.g. a mockRejectedValue left over from a previous test — so each test + // starts from a clean, default-resolving exec mock. + jest.resetAllMocks(); + jest.useFakeTimers(); +}); + +afterEach(() => { + jest.useRealTimers(); +}); + +describe("setupMSYS2", () => { + it("installs the requested packages and exports the UCRT64 environment", async () => { + await setupMSYS2(Msystem.UCRT64, ["gcc"]); + + expect(mockedExec).toHaveBeenCalledTimes(1); + expectPacmanCall("mingw-w64-ucrt-x86_64-gcc"); + expect(core.addPath).toHaveBeenCalledWith( + expect.stringContaining("ucrt64"), + ); + expect(core.addPath).toHaveBeenCalledWith(expect.stringContaining("bin")); + expect(core.exportVariable).toHaveBeenCalledWith("MSYSTEM", "UCRT64"); + expect(core.exportVariable).toHaveBeenCalledWith( + "MSYS2_PATH_TYPE", + "inherit", + ); + expect(core.exportVariable).toHaveBeenCalledWith( + "PKG_CONFIG_PATH", + expect.stringContaining("pkgconfig"), + ); + }); + + it("installs multiple packages in a single pacman invocation", async () => { + await setupMSYS2(Msystem.Clang64, ["gcc", "openblas"]); + + expect(mockedExec).toHaveBeenCalledTimes(1); + expectPacmanCall( + "mingw-w64-clang-x86_64-gcc mingw-w64-clang-x86_64-openblas", + ); + expect(core.addPath).toHaveBeenCalledWith( + expect.stringContaining("clang64"), + ); + }); + + it("does nothing when no packages are requested", async () => { + await setupMSYS2(Msystem.UCRT64, []); + + expect(mockedExec).not.toHaveBeenCalled(); + expect(core.addPath).not.toHaveBeenCalled(); + expect(core.exportVariable).not.toHaveBeenCalled(); + }); + + it("throws for the Native environment before invoking pacman", () => { + expect(() => msys2PkgName(Msystem.Native, "gcc")).toThrow( + "No MSYS2 package prefix known for environment: native", + ); + }); + + it("propagates a persistent install failure and skips environment setup", async () => { + mockedExec.mockRejectedValue(new Error("mirror stalled")); + + const install = setupMSYS2(Msystem.UCRT64, ["gcc"]); + // Attach the rejection handler before advancing timers: advanceTimersByTimeAsync + // drains microtasks, which would otherwise reject `install` with no handler + // (an unhandled rejection) before the assertion below can observe it. + const expectation = expect(install).rejects.toThrow("mirror stalled"); + + await jest.advanceTimersByTimeAsync(15_000); + await jest.advanceTimersByTimeAsync(30_000); + await expectation; + + expect(mockedExec).toHaveBeenCalledTimes(3); + expect(core.addPath).not.toHaveBeenCalled(); + expect(core.exportVariable).not.toHaveBeenCalled(); + }); +}); + +describe("msys2PackageAvailable", () => { + it("reports a package the database knows about", async () => { + await expect( + msys2PackageAvailable(Msystem.UCRT64, "libgomp"), + ).resolves.toBe(true); + + expect(mockedExec).toHaveBeenCalledWith( + PACMAN_CMD, + ["-lc", "pacman -Si mingw-w64-ucrt-x86_64-libgomp"], + { silent: true }, + ); + }); + + it("reports a package missing from the database", async () => { + mockedExec.mockRejectedValue( + new Error("error: package 'libgomp' was not found"), + ); + + await expect( + msys2PackageAvailable(Msystem.UCRT64, "libgomp"), + ).resolves.toBe(false); + }); +}); + +describe("pacmanInstallWithRetry", () => { + it("installs on the first attempt without retrying", async () => { + await pacmanInstallWithRetry("mingw-w64-ucrt-x86_64-gcc"); + + expect(mockedExec).toHaveBeenCalledTimes(1); + expectPacmanCall("mingw-w64-ucrt-x86_64-gcc"); + expect(mockedInfo).not.toHaveBeenCalled(); + }); + + it("retries once and succeeds after a transient failure", async () => { + mockedExec + .mockRejectedValueOnce(new Error("mirror stalled")) + .mockResolvedValueOnce(0); + + const install = pacmanInstallWithRetry("mingw-w64-ucrt-x86_64-gcc"); + + await jest.advanceTimersByTimeAsync(15_000); + await install; + + expect(mockedExec).toHaveBeenCalledTimes(2); + expect(mockedInfo).toHaveBeenCalledTimes(1); + expect(mockedInfo).toHaveBeenCalledWith( + "pacman install failed (attempt 1/3), retrying in 15s...", + ); + }); + + it("escalates the backoff before each subsequent retry", async () => { + mockedExec + .mockRejectedValueOnce(new Error("first failure")) + .mockRejectedValueOnce(new Error("second failure")) + .mockResolvedValueOnce(0); + + const install = pacmanInstallWithRetry("mingw-w64-ucrt-x86_64-gcc"); + + await jest.advanceTimersByTimeAsync(15_000); + expect(mockedExec).toHaveBeenCalledTimes(2); + expect(mockedInfo).toHaveBeenNthCalledWith( + 1, + "pacman install failed (attempt 1/3), retrying in 15s...", + ); + + await jest.advanceTimersByTimeAsync(30_000); + await install; + + expect(mockedExec).toHaveBeenCalledTimes(3); + expect(mockedInfo).toHaveBeenNthCalledWith( + 2, + "pacman install failed (attempt 2/3), retrying in 30s...", + ); + }); + + it("gives up after exhausting all retries and rethrows the last error", async () => { + mockedExec.mockRejectedValue(new Error("mirror stalled")); + + const install = pacmanInstallWithRetry("mingw-w64-ucrt-x86_64-gcc", 3); + const expectation = expect(install).rejects.toThrow("mirror stalled"); + + await jest.advanceTimersByTimeAsync(15_000); + await jest.advanceTimersByTimeAsync(30_000); + await expectation; + + expect(mockedExec).toHaveBeenCalledTimes(3); + expect(mockedInfo).toHaveBeenNthCalledWith( + 1, + "pacman install failed (attempt 1/3), retrying in 15s...", + ); + expect(mockedInfo).toHaveBeenNthCalledWith( + 2, + "pacman install failed (attempt 2/3), retrying in 30s...", + ); + }); + + it("does not warn or retry when only one attempt is allowed", async () => { + mockedExec.mockRejectedValue(new Error("mirror stalled")); + + await expect( + pacmanInstallWithRetry("mingw-w64-ucrt-x86_64-gcc", 1), + ).rejects.toThrow("mirror stalled"); + + expect(mockedExec).toHaveBeenCalledTimes(1); + expect(mockedInfo).not.toHaveBeenCalled(); + }); +}); diff --git a/__tests__/supported_versions.test.ts b/__tests__/supported_versions.test.ts new file mode 100644 index 00000000..8264336b --- /dev/null +++ b/__tests__/supported_versions.test.ts @@ -0,0 +1,924 @@ +/* + * Regression guard for `version: latest` resolution. + * + * `resolveVersion`/`resolveWindowsVersion` install `versions[0]` when the user + * requests `"latest"`. That only resolves to the *newest* release when each + * installer's SUPPORTED_VERSIONS list is kept in descending numeric order (with + * the `LATEST` rolling-release marker leading, if present). The installer + * sources carry a manual comment asking for exactly this ordering — this test + * makes it enforced instead of wishful thinking, so adding a new compiler + * release without re-sorting the list fails CI rather than silently pointing + * `latest` at a stale release. + */ + +import { LATEST } from "../src/types"; +import { + compareVersions, + isVersionListDescending, +} from "../src/resolve_version"; +import * as fs from "fs"; +import * as path from "path"; +import { load as yamlLoad } from "js-yaml"; + +// Installer modules transitively import the `@actions/*` libraries, which +// assume a live runner. Stub them out so we can import the (static) version +// tables in a plain node test environment. +jest.mock("@actions/core"); +jest.mock("@actions/exec"); +jest.mock("@actions/cache"); +jest.mock("@actions/tool-cache"); + +import { SUPPORTED_VERSIONS as gfortranDarwin } from "../src/installers/gfortran/darwin"; +import { SUPPORTED_VERSIONS as gfortranDebian } from "../src/installers/gfortran/debian"; +import { SUPPORTED_VERSIONS as gfortranWin32 } from "../src/installers/gfortran/win32"; +import { SUPPORTED_VERSIONS as flangDarwin } from "../src/installers/flang/darwin"; +import { SUPPORTED_VERSIONS as flangDebian } from "../src/installers/flang/debian"; +import { SUPPORTED_VERSIONS as flangWin32 } from "../src/installers/flang/win32"; +import { SUPPORTED_VERSIONS as armflangDebian } from "../src/installers/armflang/debian"; +import { SUPPORTED_VERSIONS as nvfortranDebian } from "../src/installers/nvfortran/debian"; +import { SUPPORTED_VERSIONS as aoccDebian } from "../src/installers/aocc/debian"; +import { SUPPORTED_VERSIONS as ifortDarwin } from "../src/installers/ifort/darwin"; +import { SUPPORTED_VERSIONS as ifortDebian } from "../src/installers/ifort/debian"; +import { SUPPORTED_VERSIONS as ifortWin32 } from "../src/installers/ifort/win32"; +import { SUPPORTED_VERSIONS as lfortranDarwin } from "../src/installers/lfortran/darwin"; +import { SUPPORTED_VERSIONS as lfortranDebian } from "../src/installers/lfortran/debian"; +import { SUPPORTED_VERSIONS as lfortranWin32 } from "../src/installers/lfortran/win32"; +import { SUPPORTED_VERSIONS as ifxDebian } from "../src/installers/ifx/debian"; +import { SUPPORTED_VERSIONS as ifxWin32 } from "../src/installers/ifx/win32"; + +interface VersionList { + label: string; + versions: readonly string[]; +} + +/** + * Recursively harvests every leaf `string[]` from a nested version table. + * + * Tables come in two shapes: + * - Linux: Record + * - Windows: Record> + * This walker is shape-agnostic: it recurses into objects and collects leaf + * arrays, ignoring `undefined` cells (unsupported arch/msystem). Empty arrays + * are *not* ignored — they are harvested so the structural test can reject them + * (an empty list is truthy, so `resolveVersion` would bypass its `!versions` + * guard and then fail on `versions[0] === undefined` with a confusing error). + * `LATEST` entries are kept as-is so the descending assertion also enforces + * that they lead the list. + */ +function collectVersionLists(table: unknown, prefix: string): VersionList[] { + const lists: VersionList[] = []; + if (table === null || typeof table !== "object") return lists; + for (const [key, value] of Object.entries(table as Record)) { + const loc = `${prefix}[${key}]`; + if (Array.isArray(value)) { + if (value.every((v): v is string => typeof v === "string")) { + lists.push({ label: loc, versions: value }); + } + } else if (value !== null && typeof value === "object") { + lists.push(...collectVersionLists(value, loc)); + } + } + return lists; +} + +const VERSION_TABLES = [ + { module: "gfortran/darwin", table: gfortranDarwin }, + { module: "gfortran/debian", table: gfortranDebian }, + { module: "gfortran/win32", table: gfortranWin32 }, + { module: "flang/darwin", table: flangDarwin }, + { module: "flang/debian", table: flangDebian }, + { module: "flang/win32", table: flangWin32 }, + { module: "armflang/debian", table: armflangDebian }, + { module: "nvfortran/debian", table: nvfortranDebian }, + { module: "aocc/debian", table: aoccDebian }, + { module: "ifort/darwin", table: ifortDarwin }, + { module: "ifort/debian", table: ifortDebian }, + { module: "ifort/win32", table: ifortWin32 }, + { module: "lfortran/darwin", table: lfortranDarwin }, + { module: "lfortran/debian", table: lfortranDebian }, + { module: "lfortran/win32", table: lfortranWin32 }, + { module: "ifx/debian", table: ifxDebian }, + { module: "ifx/win32", table: ifxWin32 }, +] as const; + +const ALL_VERSION_LISTS = VERSION_TABLES.flatMap((entry) => + collectVersionLists(entry.table, entry.module), +); + +describe("version ordering helpers", () => { + it("treats LATEST as newer than any concrete version", () => { + expect(compareVersions(LATEST, "999.0")).toBeGreaterThan(0); + expect(compareVersions("999.0", LATEST)).toBeLessThan(0); + }); + + it("ranks a longer segment sequence above its prefix (2026.1.1 > 2026.1)", () => { + expect(compareVersions("2026.1.1", "2026.1")).toBeGreaterThan(0); + expect(compareVersions("2026.1", "2026.1.1")).toBeLessThan(0); + }); + + it("compares numeric segments, not lexicographical (2021.10 > 2021.2)", () => { + expect(compareVersions("2021.10", "2021.2")).toBeGreaterThan(0); + }); + + it("flags ascending and out-of-order lists as not descending", () => { + expect(isVersionListDescending(["2026.1", "2026.1.1"])).toBe(false); + expect(isVersionListDescending(["15", "16"])).toBe(false); + expect(isVersionListDescending(["2026.1.1", "2026.1", "2026.2"])).toBe( + false, + ); + }); + + it("accepts a single version, an empty list, and a leading LATEST", () => { + expect(isVersionListDescending(["16"])).toBe(true); + expect(isVersionListDescending([])).toBe(true); + expect(isVersionListDescending([LATEST, "22", "21", "20"])).toBe(true); + expect(isVersionListDescending(["22", "21", "20"])).toBe(true); + }); + + it("rejects a LATEST that is not the leading entry", () => { + expect(isVersionListDescending(["19", LATEST, "18"])).toBe(false); + }); +}); + +describe("supported version tables are well-formed", () => { + // Each table cell is typed as `readonly string[] | undefined` (unsupported + // arch/msystem) — an invariant the compiler already enforces. The one thing + // the type system cannot express is that a present list is never empty: an + // empty array is truthy, so `resolveVersion` skips its `!versions` guard and + // then fails on `versions[0] === undefined` with a misleading error. Catch + // that structural defect here rather than at install time. + it.each(ALL_VERSION_LISTS)("is non-empty: $label", ({ versions }) => { + expect(versions.length).toBeGreaterThan(0); + }); +}); + +describe("supported version tables are ordered newest-first", () => { + // `resolveVersion` installs `versions[0]` when the user requests "latest". + // A list that is not descending therefore resolves "latest" to a stale + // release — exactly the regression the maintainers' "make sure the versions + // are always in descending order" comments warn about. + it.each(ALL_VERSION_LISTS.filter(({ versions }) => versions.length > 0))( + "resolves latest correctly for $label", + ({ versions }) => { + const newest = versions.reduce((acc, v) => + compareVersions(v, acc) > 0 ? v : acc, + ); + expect(isVersionListDescending(versions)).toBe(true); + // The first entry is what `version: "latest"` installs; it must be the + // numerically newest so "latest" never silently lands on a stale release. + expect(versions[0]).toBe(newest); + }, + ); + + it("exercises a version list for every installer that resolves versions", () => { + // Guard against silently dropping an installer (or an arch/msystem) from + // this check: each exported table must contribute at least one list. + const covered = new Set(ALL_VERSION_LISTS.map((l) => l.label)); + expect(covered.size).toBeGreaterThan(0); + expect( + VERSION_TABLES.every((entry) => + ALL_VERSION_LISTS.some((l) => l.label.startsWith(entry.module)), + ), + ).toBe(true); + }); +}); + +// --------------------------------------------------------------------------- +// Test B: every version the action claims to support (SUPPORTED_VERSIONS) must +// actually be exercised by the per-compiler CI matrix +// (`.github/workflows/ci-.yml`) — on a runner that matches the table +// leaf it comes from — so a supported release is never shipped untested. Two +// guarantees: +// +// 1. Concrete versions: each non-`LATEST` token in a table *leaf* (a leaf is +// one `Record` cell, e.g. `lfortran/debian[arm64]`) must have +// a matching (image, toolchain) combination in its ci-.yml +// matrix. Matching is leaf-aware three ways: +// - OS: the leaf's platform (debian/darwin/win32) must match the +// runner image's OS (ubuntu-*/macos-*/windows-*); +// - arch: the leaf's arch must match the runner image's arch +// (`-arm` suffix => arm64, `-intel` suffix => x64, bare +// `macos-*` => arm64 since macos-14); +// - msystem: Windows leaves carry the table's msystem key; `native` +// (and non-Windows leaves) match toolchain entries without +// an `msystem`, `ucrt64`/`clang64` match entries with it. +// Version matching is by *family* (exact, or one token extends the other +// by a dotted-segment boundary) so a table entry is satisfied by an exact +// CI pin OR a fuller CI spelling (e.g. table `21` <-> CI `21.1.6`; +// `2026.1` <-> `2026.1.0`; `16` is NOT satisfied by CI `17`). Extra +// patch-resolution CI entries therefore do not count as gaps. +// A version tested only on the wrong OS/arch/msystem does NOT cover the +// leaf — this is what guards e.g. the ubuntu-arm `include:` entries. +// 2. Rolling-only cells: a leaf list of exactly `[LATEST]` (the +// present-only-for-latest platforms, e.g. Windows `ucrt64`/`clang64`) must +// have a matching no-`version` CI entry, on the same `msystem` when one is +// present. +// +// The TS `SUPPORTED_VERSIONS` tables are the source of truth (they are what +// `resolveVersion` enforces at runtime and what Test A already guards); the +// README compatibility tables advertise the same set, so agreement holds there +// too. The `js-yaml` + `fs` reads parse the real CI YAML rather than duplicating +// its matrix by hand. +// --------------------------------------------------------------------------- + +const WORKFLOWS_DIR = path.resolve(__dirname, "../.github/workflows"); + +type RunnerOs = "linux" | "macos" | "windows"; +type RunnerArch = "x64" | "arm64"; + +// One (image, toolchain) combination the CI matrix actually executes: the +// cross-product `image` x `toolchain` minus `exclude`, plus every `include` +// entry that pairs an image with a toolchain. +interface CiCombo { + image: string; + os: RunnerOs; + arch: RunnerArch; + version?: string; // undefined for `latest` / version-less entries + msystem: string; // "" when the toolchain entry carries no msystem (native) +} + +interface CiMatrixInfo { + combos: CiCombo[]; + latestMsystrings: Set; + hasLatest: boolean; +} +const ciCache = new Map(); + +// Walk the GitHub Actions document and return the first `strategy.matrix` +// object found (each ci-.yml has a single job with one matrix). +function findMatrix(doc: unknown): Record | undefined { + if (!doc || typeof doc !== "object") return undefined; + const jobs = (doc as Record).jobs; + if (!jobs || typeof jobs !== "object") return undefined; + for (const job of Object.values(jobs as Record)) { + if (!job || typeof job !== "object") continue; + const matrix = (job as Record).strategy as + { matrix?: unknown } | undefined; + if (matrix?.matrix && typeof matrix.matrix === "object") { + return matrix.matrix as Record; + } + } + return undefined; +} + +// Extract a Windows msystem (`ucrt64`/`clang64`/`native`) from a table leaf +// label like "flang/win32[x64][ucrt64]". +function msystemOf(label: string): string | undefined { + const m = /\[(ucrt64|clang64|native)\]/.exec(label); + return m ? m[1] : undefined; +} + +// Classify a GitHub Actions runner image by OS and architecture. +// - `-arm` suffix => arm64 (ubuntu-*-arm, windows-11-arm) +// - `-intel` suffix => x64 (macos-*-intel, the old Intel Mac runners) +// - bare `macos-*` => arm64 (macos-14 and later are Apple Silicon) +// - everything else => x64 (ubuntu, windows) +// `*-latest` aliases resolve by the same rules (macos-latest is arm64 since +// it points at macos-14+); they only ever appear in version-less includes, so +// they never satisfy a concrete-version leaf. +function classifyImage( + image: string, +): { os: RunnerOs; arch: RunnerArch } | undefined { + if (image.startsWith("ubuntu-")) { + return { os: "linux", arch: image.endsWith("-arm") ? "arm64" : "x64" }; + } + if (image.startsWith("macos-")) { + return { os: "macos", arch: image.endsWith("-intel") ? "x64" : "arm64" }; + } + if (image.startsWith("windows-")) { + return { os: "windows", arch: image.endsWith("-arm") ? "arm64" : "x64" }; + } + return undefined; +} + +interface LeafSpec { + os: RunnerOs; + arch: RunnerArch; + msystem?: string; // only Windows leaves carry one; "native" = no msystem +} + +// Parse a table leaf label (as produced by `collectVersionLists`) into the +// runner characteristics a covering CI combination must have: +// "lfortran/debian[arm64]" -> linux/arm64, no msystem +// "flang/win32[x64][ucrt64]" -> windows/x64, ucrt64 +function leafSpec(label: string): LeafSpec { + const m = + /^([a-z0-9]+)\/(debian|darwin|win32)\[(x64|arm64)\](?:\[(ucrt64|clang64|native)\])?$/.exec( + label, + ); + if (!m) throw new Error(`Unrecognized version table label: ${label}`); + const os: RunnerOs = + m[2] === "debian" ? "linux" : m[2] === "darwin" ? "macos" : "windows"; + return { os, arch: m[3] as RunnerArch, msystem: m[4] }; +} + +function parseCiMatrix(compiler: string): CiMatrixInfo { + const cached = ciCache.get(compiler); + if (cached) return cached; + + const file = path.join(WORKFLOWS_DIR, `ci-${compiler}.yml`); + const doc = yamlLoad(fs.readFileSync(file, "utf8")) as Record< + string, + unknown + >; + const matrix = findMatrix(doc); + if (!matrix) { + throw new Error(`No strategy.matrix found in ${file}`); + } + + const combos: CiCombo[] = []; + const latestMsystrings = new Set(); + let hasLatest = false; + + const makeCombo = ( + image: string, + e: Record, + ): CiCombo | undefined => { + const cls = classifyImage(image); + if (!cls) return undefined; + const version = + typeof e.version === "string" && e.version !== LATEST + ? e.version + : undefined; + return { + image, + os: cls.os, + arch: cls.arch, + version, + msystem: typeof e.msystem === "string" ? e.msystem : "", + }; + }; + + // A toolchain entry with no `version` (or version === "latest") pins the + // latest available release; record its `msystem` (if any) so [LATEST]-only + // cells can be matched to the right Windows build. + const visit = (image: string, entry: unknown): void => { + if (!entry || typeof entry !== "object" || Array.isArray(entry)) return; + const e = entry as Record; + if (typeof e.compiler !== "string") return; // not a toolchain entry + const combo = makeCombo(image, e); + if (combo) combos.push(combo); + const v = e.version; + if (typeof v !== "string" || v === LATEST) { + hasLatest = true; + if (typeof e.msystem === "string") latestMsystrings.add(e.msystem); + } + }; + + const images = Array.isArray(matrix.image) + ? matrix.image.filter((i): i is string => typeof i === "string") + : []; + const tc = matrix.toolchain; + const toolchains: unknown[] = Array.isArray(tc) + ? tc + : tc && typeof tc === "object" + ? [tc] + : []; + + // GitHub semantics: an `exclude` item removes every cross-product + // combination whose keys all match. Includes are never excluded. + const excludes = Array.isArray(matrix.exclude) + ? matrix.exclude.filter( + (x): x is Record => + !!x && typeof x === "object" && !Array.isArray(x), + ) + : []; + const isExcluded = (image: string, entry: Record): boolean => + excludes.some((x) => { + if (typeof x.image === "string" && x.image !== image) return false; + const xt = x.toolchain; + if (xt && typeof xt === "object" && !Array.isArray(xt)) { + for (const [k, v] of Object.entries(xt as Record)) { + if (entry[k] !== v) return false; + } + } + return true; + }); + + for (const image of images) { + for (const t of toolchains) { + if (!t || typeof t !== "object" || Array.isArray(t)) continue; + const e = t as Record; + if (typeof e.compiler !== "string") continue; + if (isExcluded(image, e)) continue; + visit(image, e); + } + } + + const inc = matrix.include ?? []; + if (Array.isArray(inc)) { + for (const item of inc) { + if (!item || typeof item !== "object") continue; + const o = item as Record; + const image = typeof o.image === "string" ? o.image : undefined; + const entries: unknown[] = Array.isArray(o.toolchain) + ? o.toolchain + : o.toolchain && typeof o.toolchain === "object" + ? [o.toolchain] + : []; + if (entries.length > 0) { + for (const t of entries) { + if (!t || typeof t !== "object" || Array.isArray(t)) continue; + const e = t as Record; + if (typeof e.compiler !== "string") continue; + if (image) { + visit(image, e); + } else if (typeof e.version !== "string" || e.version === LATEST) { + // Toolchain-only include without an image: only meaningful for + // latest tracking, it can never cover a concrete-version leaf. + hasLatest = true; + if (typeof e.msystem === "string") latestMsystrings.add(e.msystem); + } + } + } else if (image) { + hasLatest = true; // image-only include => default compiler at "latest" + } + } + } + + const info: CiMatrixInfo = { combos, latestMsystrings, hasLatest }; + ciCache.set(compiler, info); + return info; +} + +// --- Pure, IO-free coverage predicates (unit-tested below as negative cases) - + +// A concrete table token is satisfied by an exact CI pin, a more-specific CI +// release (CI "21.1.6" covers table "21"), or a less-specific CI release +// (CI "2026.1" covers table "2026.1.0"). The dotted-segment boundary keeps +// "2026" from matching "20260" and "21" from matching "21.1". +function versionFamilyCovered(token: string, ciVersions: Set): boolean { + if (ciVersions.has(token)) return true; + for (const v of ciVersions) { + if (token.startsWith(`${v}.`) || v.startsWith(`${token}.`)) return true; + } + return false; +} + +// A `[LATEST]`-only cell is satisfied when CI has a no-`version` entry: on the +// same `msystem` when the cell declares one, or anywhere when it does not. +function latestCellCovered( + label: string, + latestMsystrings: Set, + hasLatest: boolean, +): boolean { + const msystem = msystemOf(label); + return msystem ? latestMsystrings.has(msystem) : hasLatest; +} + +// A concrete table token is covered by a CI combination only when that +// combination runs on a runner matching the token's leaf (OS, arch, and — +// for Windows leaves — msystem) AND pins a version in the token's family. +// A version-less (latest) combination never covers a concrete token. +function comboCoversLeaf( + combo: CiCombo, + spec: LeafSpec, + token: string, +): boolean { + if (combo.os !== spec.os || combo.arch !== spec.arch) return false; + const requiredMsystem = + spec.msystem === undefined || spec.msystem === "native" ? "" : spec.msystem; + if (combo.msystem !== requiredMsystem) return false; + return ( + combo.version !== undefined && + versionFamilyCovered(token, new Set([combo.version])) + ); +} + +describe("supported version tables are exercised by CI (Test B)", () => { + it("every concrete supported version is tested by its ci-.yml matrix on a matching runner", () => { + const uncovered: Array<{ where: string; compiler: string; token: string }> = + []; + for (const list of ALL_VERSION_LISTS) { + const compiler = list.label.split("/")[0]; + const spec = leafSpec(list.label); + const ci = parseCiMatrix(compiler); + for (const token of list.versions) { + if (token === LATEST) continue; + if (!ci.combos.some((c) => comboCoversLeaf(c, spec, token))) { + uncovered.push({ where: list.label, compiler, token }); + } + } + } + if (uncovered.length > 0) { + throw new Error( + "These supported versions have no CI coverage on a matching runner " + + "(OS/arch/msystem of their table leaf):\n" + + uncovered + .map( + (u) => ` - ${u.where}: "${u.token}" (compiler "${u.compiler}")`, + ) + .join("\n"), + ); + } + }); + + it("every [LATEST]-only table cell has a latest test in CI", () => { + const onlyLatestLeaves = ALL_VERSION_LISTS.filter( + (l) => l.versions.length === 1 && l.versions[0] === LATEST, + ); + const missing: Array<{ where: string; compiler: string }> = []; + for (const list of onlyLatestLeaves) { + const compiler = list.label.split("/")[0]; + const ci = parseCiMatrix(compiler); + if (!latestCellCovered(list.label, ci.latestMsystrings, ci.hasLatest)) { + missing.push({ where: list.label, compiler }); + } + } + if (missing.length > 0) { + throw new Error( + "These [LATEST]-only cells have no latest test in CI:\n" + + missing + .map((m) => ` - ${m.where} (compiler "${m.compiler}")`) + .join("\n"), + ); + } + }); +}); + +// --------------------------------------------------------------------------- +// Test B negative cases: the coverage predicates must *detect* synthetic gaps +// (not, like the integration tests above, only affirm the green state). These +// are pure unit tests over `versionFamilyCovered` / `latestCellCovered` so they +// pin the detection behaviour independently of the live CI YAML. A real +// mutation (dropping a version from ci-flang.yml) is covered by the runtime +// integration test above; these cover the contract the integration test relies +// on. +// --------------------------------------------------------------------------- +describe("Test B coverage predicates detect gaps (negative cases)", () => { + // [token, ciVersions, expected] — positive rows confirm the rule isn't overly + // strict; negative rows confirm it flags a genuinely-missing release and does + // NOT false-match across dotted major/minor boundaries. + it.each([ + // --- positives: table release is satisfied by CI --- + ["16", ["16"], true], // exact pin + ["21", ["21.1.6"], true], // CI patch satisfies table major + ["21.1.6", ["21"], true], // CI major satisfies table patch + ["2026.1", ["2026.1.0", "2026.1.1"], true], // CI minor satisfies table minor + ["2023.2", ["2023.2.4"], true], // ifx-style: minor <-> patch family + // --- negatives: missing release (the gap the test exists to catch) --- + ["16", ["17", "18", "19", "20", "21", "22"], false], // flang-16-style gap + ["18", ["17", "19", "20", "21", "22"], false], // hole in the middle + ["16", [], false], // empty CI matrix for the compiler + // --- boundary: dotted major/minor must not cross-match --- + ["21", ["21.1"], true], // 21 IS the family root of 21.1 + ["2021.1", ["2021.10"], false], // NOT the same family (no dot extension) + ["2021.10", ["2021.1"], false], // symmetric: must not match + ["2026", ["20260"], false], // plain vs double-digit, no false match + ])("versionFamilyCovered(%p, %p) => %p", (token, ciVersions, expected) => { + expect(versionFamilyCovered(token, new Set(ciVersions))).toBe(expected); + }); + + // The headline negative the user described: a `[LATEST]`-only cell for one + // configuration (e.g. Windows ucrt64) while CI only ships a latest entry for + // a *different* configuration (clang64) — or ships no latest entry at all. + it("flags a [LATEST]-only cell whose msystem has no latest CI entry", () => { + // CI only ships a latest flang build for clang64, never ucrt64. + const ci = { latestMsystrings: new Set(["clang64"]), hasLatest: true }; + expect( + latestCellCovered( + "flang/win32[x64][ucrt64]", + ci.latestMsystrings, + ci.hasLatest, + ), + ).toBe(false); + expect( + latestCellCovered( + "flang/win32[x64][clang64]", + ci.latestMsystrings, + ci.hasLatest, + ), + ).toBe(true); + }); + + it("flags a [LATEST]-only cell when CI ships no latest entry at all", () => { + const ci = { latestMsystrings: new Set(), hasLatest: false }; + expect( + latestCellCovered( + "flang/win32[x64][ucrt64]", + ci.latestMsystrings, + ci.hasLatest, + ), + ).toBe(false); + // ...and a msystem-less cell is likewise uncovered when there is no latest. + expect( + latestCellCovered( + "gfortran/win32[x64][ucrt64]", + ci.latestMsystrings, + ci.hasLatest, + ), + ).toBe(false); + }); + + it("a msystem-less [LATEST] cell is satisfied by any latest CI entry", () => { + // Image-only includes (no msystem) only need the compiler to have a latest + // build somewhere in CI. + const ci = { latestMsystrings: new Set(["ucrt64"]), hasLatest: true }; + expect( + latestCellCovered( + "gfortran/darwin[x64]", + ci.latestMsystrings, + ci.hasLatest, + ), + ).toBe(true); + expect( + latestCellCovered("gfortran/darwin[x64]", new Set(), false), + ).toBe(false); + }); +}); + +// --------------------------------------------------------------------------- +// Negative cases for the leaf-aware coverage building blocks: `classifyImage` +// must map runner names to the right OS/arch, and `comboCoversLeaf` must +// reject combinations that run the right version on the wrong runner +// (the regression that used to slip through: a supported ubuntu-arm release +// covered only by x64 CI jobs). +// --------------------------------------------------------------------------- +describe("leaf-aware CI coverage building blocks (negative cases)", () => { + const combo = (image: string, version?: string, msystem = ""): CiCombo => { + const cls = classifyImage(image); + if (!cls) throw new Error(`test helper: unclassifiable image ${image}`); + return { image, os: cls.os, arch: cls.arch, version, msystem }; + }; + const covered = (spec: LeafSpec, token: string, combos: CiCombo[]) => + combos.some((c) => comboCoversLeaf(c, spec, token)); + + it.each([ + ["ubuntu-24.04", { os: "linux", arch: "x64" }], + ["ubuntu-22.04", { os: "linux", arch: "x64" }], + ["ubuntu-24.04-arm", { os: "linux", arch: "arm64" }], + ["ubuntu-22.04-arm", { os: "linux", arch: "arm64" }], + ["macos-14", { os: "macos", arch: "arm64" }], + ["macos-15", { os: "macos", arch: "arm64" }], + ["macos-26", { os: "macos", arch: "arm64" }], + ["macos-15-intel", { os: "macos", arch: "x64" }], + ["macos-26-intel", { os: "macos", arch: "x64" }], + ["windows-2022", { os: "windows", arch: "x64" }], + ["windows-2025", { os: "windows", arch: "x64" }], + ["windows-11-arm", { os: "windows", arch: "arm64" }], + ["ubuntu-latest", { os: "linux", arch: "x64" }], + ["macos-latest", { os: "macos", arch: "arm64" }], + ["windows-latest", { os: "windows", arch: "x64" }], + ])("classifyImage(%p) => %p", (image, expected) => { + expect(classifyImage(image)).toEqual(expected); + }); + + it("classifies unknown runner names as unusable", () => { + expect(classifyImage("self-hosted")).toBeUndefined(); + }); + + it.each([ + ["lfortran/debian[arm64]", { os: "linux", arch: "arm64" }], + ["lfortran/debian[x64]", { os: "linux", arch: "x64" }], + ["flang/darwin[arm64]", { os: "macos", arch: "arm64" }], + [ + "ifx/win32[x64][native]", + { os: "windows", arch: "x64", msystem: "native" }, + ], + [ + "flang/win32[x64][ucrt64]", + { os: "windows", arch: "x64", msystem: "ucrt64" }, + ], + [ + "lfortran/win32[x64][clang64]", + { os: "windows", arch: "x64", msystem: "clang64" }, + ], + ])("leafSpec(%p) => %p", (label, expected) => { + expect(leafSpec(label)).toEqual(expected); + }); + + it("flags a version that is only tested on the wrong architecture", () => { + // The headline regression: 0.65.0 is supported on linux/arm64 but the CI + // entries for it were deleted from the ubuntu-arm includes, leaving only + // x64 jobs. + const spec = leafSpec("lfortran/debian[arm64]"); + const x64Only = [ + combo("ubuntu-24.04", "0.65.0"), + combo("ubuntu-22.04", "0.65.0"), + combo("macos-14", "0.65.0"), + combo("windows-2025", "0.65.0"), + ]; + expect(covered(spec, "0.65.0", x64Only)).toBe(false); + expect( + covered(spec, "0.65.0", [ + ...x64Only, + combo("ubuntu-24.04-arm", "0.65.0"), + ]), + ).toBe(true); + }); + + it("flags a version that is only tested on the wrong OS", () => { + const spec = leafSpec("lfortran/debian[x64]"); + expect( + covered(spec, "0.65.0", [ + combo("macos-14", "0.65.0"), + combo("windows-2025", "0.65.0"), + ]), + ).toBe(false); + }); + + it("requires the msystem to match for Windows leaves", () => { + const native = leafSpec("lfortran/win32[x64][native]"); + const ucrt64 = leafSpec("lfortran/win32[x64][ucrt64]"); + // A native (msystem-less) entry never covers an ucrt64 leaf and vice + // versa, even at the same version. + expect(covered(native, "0.65.0", [combo("windows-2025", "0.65.0")])).toBe( + true, + ); + expect( + covered(native, "0.65.0", [combo("windows-2025", "0.65.0", "ucrt64")]), + ).toBe(false); + expect( + covered(ucrt64, "0.65.0", [combo("windows-2025", "0.65.0", "ucrt64")]), + ).toBe(true); + expect(covered(ucrt64, "0.65.0", [combo("windows-2025", "0.65.0")])).toBe( + false, + ); + }); + + it("never lets a msystem-bearing combination cover a non-Windows leaf", () => { + const spec = leafSpec("lfortran/debian[arm64]"); + expect( + covered(spec, "0.65.0", [combo("ubuntu-24.04-arm", "0.65.0", "ucrt64")]), + ).toBe(false); + }); + + it("a latest (version-less) combination never covers a concrete token", () => { + const spec = leafSpec("lfortran/debian[arm64]"); + expect(covered(spec, "0.65.0", [combo("ubuntu-24.04-arm")])).toBe(false); + }); + + it("still applies version-family matching within a matching combination", () => { + const spec = leafSpec("flang/darwin[x64]"); + const combos = [combo("macos-15-intel", "21.1.6")]; + expect(covered(spec, "21", combos)).toBe(true); + expect(covered(spec, "22", combos)).toBe(false); + }); +}); + +// --------------------------------------------------------------------------- +// Test C: the hand-maintained README "Compiler Support" tables must both +// (a) not over-promise — every concrete version the README claims (a checkmark +// on any platform column) is family-supported by the TS SUPPORTED_VERSIONS +// tables (the runtime source of truth, as in Test B) — and +// (b) not be incomplete — every concrete version the tables actually support +// is advertised in the README, and a table carrying the `LATEST` marker has +// a README "latest" row. +// (b) is what catches a *deleted* README row for a still-supported release +// (a deletion that (a) alone never detects, since removing an advert can only +// make the "no over-promise" check stay green). +// +// The README "latest" row itself is excluded from the concrete checks: +// `version: "latest"` always resolves to `versions[0]` (Test A guarantees +// newest-first ordering), and installers such as ifx represent the newest via a +// resolved patch rather than the literal LATEST marker — so "latest" is +// structurally always satisfiable and is only asserted via (b)'s marker check. +// +// Flang is handled identically to Test B (family match): the README lists flang +// as the majors {22..16} and the tables list the same majors, so flang is green +// here; the family rule also bridges any major<->patch/minor spelling +// differences for the other compilers (e.g. README `2026.1.1` is family-covered +// by table `2026.1`, and table `2025.0.4` by README `2025.0`). Matching is +// platform-agnostic (table union across every arch/msystem), mirroring Test +// B's compiler-level check, rather than binding each README column to a +// specific installer leaf. +// --------------------------------------------------------------------------- + +const README_COMPILERS = [ + "gfortran", + "ifx", + "ifort", + "nvfortran", + "aocc", + "lfortran", + "flang", + "armflang", +] as const; + +interface ReadmeTable { + versions: Set; // concrete advertised versions (any checkmark) + hasLatest: boolean; // a README "latest" row is present for this compiler +} + +// Parse the README "Compiler Support" markdown tables into +// {compiler -> ReadmeTable}. Only `### \`compiler\`` sections are read; rows +// with no checkmark and non-table lines are ignored. The `latest` row is +// recorded in `hasLatest` (it is structurally satisfiable; see Test C comment). +function parseReadmeTables(md: string): Record { + const tables: Record = {}; + let current: string | undefined; + const begin = (name: string): ReadmeTable => { + if (!tables[name]) tables[name] = { versions: new Set(), hasLatest: false }; + return tables[name]; + }; + for (const raw of md.split(/\r?\n/)) { + const heading = raw.match(/^###\s+`([a-z0-9_-]+)`/i); + if (heading) { + const name = heading[1].toLowerCase(); + current = README_COMPILERS.includes(name as never) ? name : undefined; + if (current) begin(current); + continue; + } + if (!current) continue; + const row = raw.match(/^\|(.+)\|$/); + if (!row) continue; // only markdown table rows carry a version claim + const cols = row[1].split("|").map((c) => c.trim()); + const [first] = cols; + if (!first || first === "---" || first.toLowerCase() === "version") + continue; + if (first.toLowerCase() === "latest") { + begin(current).hasLatest = true; + continue; + } + if (cols.slice(1).some((c) => c.includes("✓"))) + begin(current).versions.add(first); + } + return tables; +} + +describe("README compatibility tables match SUPPORTED_VERSIONS (Test C)", () => { + const advertised = parseReadmeTables( + fs.readFileSync(path.resolve(__dirname, "../README.md"), "utf8"), + ); + + // Union of every table token (across all archs/msystems) for one compiler — + // the set a README-advertised version must family-match against. + const tableUnion = (compiler: string): Set => + new Set( + ALL_VERSION_LISTS.filter((l) => + l.label.startsWith(`${compiler}/`), + ).flatMap((l) => l.versions), + ); + + it("every advertised concrete version is family-supported by the tables", () => { + const unsupported: Array<{ compiler: string; version: string }> = []; + for (const compiler of README_COMPILERS) { + const union = tableUnion(compiler); + for (const v of advertised[compiler]?.versions ?? []) { + if (!versionFamilyCovered(v, union)) { + unsupported.push({ compiler, version: v }); + } + } + } + if (unsupported.length > 0) { + throw new Error( + `README advertises versions not supported by SUPPORTED_VERSIONS:\n` + + unsupported.map((u) => ` - ${u.compiler}: ${u.version}`).join("\n"), + ); + } + }); + + // (b) Completeness: the README must document every version the tables support. + // This is what fails when a README row for a still-supported release is + // deleted — e.g. removing armflang "20.1" while the table still lists it. + it("every supported concrete version is advertised in the README", () => { + const unadvertised: Array<{ compiler: string; version: string }> = []; + for (const compiler of README_COMPILERS) { + const union = tableUnion(compiler); + for (const token of union) { + if (token === LATEST) continue; + if ( + !versionFamilyCovered( + token, + advertised[compiler]?.versions ?? new Set(), + ) + ) { + unadvertised.push({ compiler, version: token }); + } + } + if (union.has(LATEST) && !advertised[compiler]?.hasLatest) { + unadvertised.push({ compiler, version: "latest" }); + } + } + if (unadvertised.length > 0) { + throw new Error( + `SUPPORTED_VERSIONS lists versions not documented in the README:\n` + + unadvertised.map((u) => ` - ${u.compiler}: ${u.version}`).join("\n"), + ); + } + }); + + // Negative cases for the README parser itself, so a future README edit that + // breaks table structure is caught here rather than as a silent no-coverage + // false-pass. + it("parseReadmeTables only collects checkmarked concrete versions + latest", () => { + const md = [ + "### `flang` (LLVM Flang)", + "", + "| Version | windows-2025 (ucrt64) | windows-2022 (ucrt64) |", + "| ------- | --------------------- | --------------------- |", + "| latest | ✓ | ✓ |", + "| 16 | | ✓ |", + "| 15 | | |", + "", + "> Specific patch versions (e.g. `21.1.6`) are supported.", + "", + "### Basic Usage", + "", + "some prose | not | a | table", + ].join("\n"); + expect(parseReadmeTables(md)).toEqual({ + flang: { versions: new Set(["16"]), hasLatest: true }, + }); + }); +}); diff --git a/__tests__/verify_download.test.ts b/__tests__/verify_download.test.ts new file mode 100644 index 00000000..c2d40257 --- /dev/null +++ b/__tests__/verify_download.test.ts @@ -0,0 +1,61 @@ +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; +import * as exec from "@actions/exec"; +import { + computeSha256, + verifyIntelAuthenticode, + verifySha256, +} from "../src/verify_download"; + +jest.mock("@actions/exec"); + +describe("download verification", () => { + let tempDir: string; + let testFile: string; + + beforeEach(() => { + jest.clearAllMocks(); + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "verify-download-")); + testFile = path.join(tempDir, "artifact.bin"); + fs.writeFileSync(testFile, "setup-fortran\n"); + }); + + afterEach(() => { + fs.rmSync(tempDir, { recursive: true, force: true }); + }); + + it("computes and accepts a matching SHA-256 digest", async () => { + const digest = await computeSha256(testFile); + await expect(verifySha256(testFile, digest)).resolves.toBeUndefined(); + }); + + it("rejects a mismatched SHA-256 digest", async () => { + await expect(verifySha256(testFile, "0".repeat(64))).rejects.toThrow( + "SHA-256 verification failed", + ); + }); + + it("rejects a malformed expected digest", async () => { + await expect(verifySha256(testFile, "not-a-digest")).rejects.toThrow( + "Invalid expected SHA-256 digest", + ); + }); + + it("verifies Intel installers through Authenticode without interpolating the path", async () => { + await verifyIntelAuthenticode("C:\\Temp\\Intel installer.exe"); + + expect(exec.exec).toHaveBeenCalledWith( + "powershell", + expect.arrayContaining([ + "-Command", + expect.stringContaining("Intel Corporation"), + ]), + expect.objectContaining({ + env: expect.objectContaining({ + SETUP_FORTRAN_INSTALLER: "C:\\Temp\\Intel installer.exe", + }), + }), + ); + }); +}); diff --git a/__tests__/windows_executable_path.test.ts b/__tests__/windows_executable_path.test.ts new file mode 100644 index 00000000..dbbc4cf4 --- /dev/null +++ b/__tests__/windows_executable_path.test.ts @@ -0,0 +1,21 @@ +import { normalizeWindowsExecutablePath } from "../src/windows_executable_path"; + +describe("normalizeWindowsExecutablePath", () => { + it("treats the optional executable suffix as equivalent", () => { + expect(normalizeWindowsExecutablePath("/c/tools/link")).toBe( + normalizeWindowsExecutablePath("/c/tools/link.exe"), + ); + }); + + it("normalizes Windows case and path separators", () => { + expect(normalizeWindowsExecutablePath("C:\\MSVC\\Bin\\LINK.EXE")).toBe( + "c:/msvc/bin/link", + ); + }); + + it("trims command output without changing the executable name", () => { + expect(normalizeWindowsExecutablePath(" /c/msvc/linker.exe\r\n")).toBe( + "/c/msvc/linker", + ); + }); +}); diff --git a/__tests__/workflow_schema.test.ts b/__tests__/workflow_schema.test.ts new file mode 100644 index 00000000..17353826 --- /dev/null +++ b/__tests__/workflow_schema.test.ts @@ -0,0 +1,188 @@ +import * as fs from "fs"; +import * as path from "path"; +import yaml from "js-yaml"; + +const repoRoot = path.resolve(__dirname, ".."); + +interface WorkflowJob { + name?: string; + uses?: string; + "runs-on"?: string | string[]; + needs?: string | string[]; + if?: string; + permissions?: Record; + steps?: unknown[]; + strategy?: unknown; + "continue-on-error"?: boolean | string; + [key: string]: unknown; +} + +interface Workflow { + name: string; + on: Record | string; + permissions?: Record; + env?: Record; + jobs: Record; + [key: string]: unknown; +} + +function loadWorkflows(): Record { + const workflowDir = path.join(repoRoot, ".github", "workflows"); + const result: Record = {}; + for (const file of fs.readdirSync(workflowDir)) { + if (!file.endsWith(".yml") && !file.endsWith(".yaml")) continue; + const filePath = path.join(workflowDir, file); + const content = fs.readFileSync(filePath, "utf-8"); + const doc = yaml.load(content) as Workflow; + result[file] = doc; + } + return result; +} + +describe("workflow YAML schema", () => { + const workflows = loadWorkflows(); + const workflowNames = Object.keys(workflows); + + it("loads all workflow files", () => { + expect(workflowNames.length).toBeGreaterThan(0); + expect(workflowNames).toContain("ci.yml"); + expect(workflowNames).toContain("validation.yml"); + }); + + for (const [fileName, workflow] of Object.entries(workflows)) { + describe(fileName, () => { + it("has a name", () => { + expect(workflow.name).toBeTruthy(); + expect(typeof workflow.name).toBe("string"); + }); + + it("has an 'on' trigger", () => { + expect(workflow.on).toBeTruthy(); + }); + + it("has jobs", () => { + expect(workflow.jobs).toBeTruthy(); + expect(typeof workflow.jobs).toBe("object"); + expect(Object.keys(workflow.jobs).length).toBeGreaterThan(0); + }); + + for (const [jobId, job] of Object.entries(workflow.jobs)) { + describe(`job: ${jobId}`, () => { + it("has runs-on or uses", () => { + const hasRunner = + Array.isArray(job["runs-on"]) || + typeof job["runs-on"] === "string"; + const hasUses = typeof job.uses === "string"; + expect(hasRunner || hasUses).toBe(true); + }); + + it("does not define both runs-on and uses", () => { + const hasRunner = job["runs-on"] !== undefined; + const hasUses = job.uses !== undefined; + expect(hasRunner && hasUses).toBe(false); + }); + + if (job.uses) { + const uses = job.uses; + it("references an existing local reusable workflow", () => { + expect(uses.startsWith("./")).toBe(true); + expect(uses.endsWith(".yml") || uses.endsWith(".yaml")).toBe( + true, + ); + const refPath = uses.slice(2); + const resolved = path.resolve(repoRoot, refPath); + expect(fs.existsSync(resolved)).toBe(true); + }); + } + + it("does not have duplicate needs", () => { + if (job.needs) { + const needs = Array.isArray(job.needs) ? job.needs : [job.needs]; + const unique = new Set(needs); + expect(unique.size).toBe(needs.length); + } + }); + }); + } + }); + } +}); + +describe("ci.yml canary structure", () => { + const ciYml = loadWorkflows()["ci.yml"]; + + it("has a weekly schedule trigger", () => { + const schedule = ciYml.on as Record; + expect(schedule).toHaveProperty("schedule"); + const cronList = schedule["schedule"] as Array<{ cron: string }>; + expect(cronList).toHaveLength(1); + // Friday at 00:00 UTC: minute=0 hour=0 day=* month=* weekday=5 + const [minute, hour, day, month, weekday] = cronList[0].cron.split(" "); + expect(minute).toBe("0"); + expect(hour).toBe("0"); + expect(day).toBe("*"); + expect(month).toBe("*"); + expect(weekday).toBe("5"); + }); + + it("protects the weekly schedule run from being canceled by an unrelated push/PR", () => { + const concurrency = ciYml.concurrency as { group: string } | undefined; + expect(concurrency?.group).toBeTruthy(); + const group = concurrency!.group; + const scheduleGuardIndex = group.indexOf("github.event_name == 'schedule'"); + const runIdIndex = group.indexOf("github.run_id"); + expect(scheduleGuardIndex).toBeGreaterThan(-1); + expect(runIdIndex).toBeGreaterThan(scheduleGuardIndex); + }); + + it("cancels in-progress runs on new pushes", () => { + const concurrency = ciYml.concurrency as + { "cancel-in-progress": boolean } | undefined; + expect(concurrency?.["cancel-in-progress"]).toBe(true); + }); + + it("has a canary-report job", () => { + expect(ciYml.jobs).toHaveProperty("canary-report"); + }); + + it("canary-report job only runs on schedule", () => { + const canary = ciYml.jobs["canary-report"]; + expect(canary.if).toContain("github.event_name == 'schedule'"); + expect(canary.if).not.toContain("workflow_dispatch"); + }); + + it("canary-report has issues write permission", () => { + const canary = ciYml.jobs["canary-report"]; + expect(canary.permissions).toHaveProperty("issues", "write"); + expect(canary.permissions).toHaveProperty("contents", "read"); + }); + + it("canary-report needs all compiler jobs", () => { + const canary = ciYml.jobs["canary-report"]; + const needs = canary.needs as string[]; + const compilers = [ + "aocc", + "armflang", + "gfortran", + "flang", + "ifort", + "ifx", + "lfortran", + "nvfortran", + ]; + for (const c of compilers) { + expect(needs).toContain(c); + } + }); + + it("canary-report creates an issue via actions/github-script", () => { + const canary = ciYml.jobs["canary-report"]; + const steps = canary.steps as Array<{ + uses: string; + with: { script: string }; + }>; + expect(steps).toHaveLength(1); + expect(steps[0].uses).toBe("actions/github-script@v9"); + expect(steps[0].with.script).toContain("github.rest.issues.create"); + }); +}); diff --git a/action.yml b/action.yml index 2eccf142..311c3339 100644 --- a/action.yml +++ b/action.yml @@ -1,155 +1,55 @@ -name: "Setup Fortran" -description: "Setup Fortran compiler and toolchain" +name: "Setup Fortran Compilers" +description: Set up Fortran compilers on GitHub Actions runners +author: fortran-lang +branding: + icon: cpu + color: purple + inputs: compiler: - description: "Toolchain or compiler to install" - required: true - default: "gcc" + description: >- + Fortran compiler to install. Canonical names are `gfortran`, `ifx`, + `ifort`, `nvfortran`, `aocc`, `lfortran`, `flang`, and `armflang`. + Compatibility aliases `gcc`, `intel`, `intel-classic`, and `nvidia-hpc` + are also accepted, but canonical names are recommended. Defaults to + `gfortran`. + required: false + default: gfortran version: - description: "Version of toolchain or compiler" + description: >- + Compiler version to install. Enclose the version string in quotation marks + (e.g. `"2026.1.1"` or `"0.64.0"`). Defaults to the latest version. + required: false + msystem: + description: >- + Windows toolchain environment to use. Defaults to `native`. Ignored on + non-Windows runners. required: false + default: native + cleanup-disk: + description: >- + Free up disk space by removing large pre-installed toolkits. Currently + only supported for `nvfortran` installations. Defaults to `false`. + required: false + default: "false" update-environment: - description: "Whether to set environment variables" + description: >- + Whether to export toolchain environment variables (FC, CC, CXX, FPM_*, + F77, F90, FORTRAN_COMPILER) to the runner environment. Accepts `true` or + `false`. Defaults to `true`. required: false - default: true + default: "true" + outputs: + version: + description: The installed compiler version. fc: - description: "Path to Fortran compiler" - value: ${{ steps.outputs.outputs.fc }} + description: The Fortran compiler command or path. cc: - description: "Path to C compiler" - value: ${{ steps.outputs.outputs.cc }} + description: The C compiler command or path. cxx: - description: "Path to C++ compiler" - value: ${{ steps.outputs.outputs.cxx }} -runs: - using: "composite" - steps: - # On Windows runners the Intel toolchain is very slow to install, - # setup caching with daily key rotation. Steps are also needed to - # activate the oneAPI environment. - - name: Set oneAPI install dir - id: oneapi-root - if: runner.os == 'Windows' && contains(inputs.compiler, 'intel') - shell: bash - run: echo "ONEAPI_ROOT=C:\Program Files (x86)\Intel\oneAPI" >> "$GITHUB_ENV" - - name: Get date - if: runner.os == 'Windows' && contains(inputs.compiler, 'intel') - id: get-date - shell: bash - run: echo "date=$(/bin/date -u "+%Y%m%d")" >> "$GITHUB_OUTPUT" - - name: Restore cache - if: runner.os == 'Windows' && contains(inputs.compiler, 'intel') - id: cache - uses: actions/cache/restore@v6 - with: - path: ${{ env.ONEAPI_ROOT }} - key: ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.version || 'latest' }}-${{ steps.get-date.outputs.date }} - - # Use micromamba for lfortran installs, on all platforms: it gives us a - # fresh, empty env to install into, rather than the base env baked into - # each runner image's pre-installed system conda, which can carry pins - # (python, zstd, etc.) that conflict with lfortran's own dependencies. - # Check if micromamba already exists, only install it if needed. If we - # install it, clean it up after. - - name: Check for micromamba - id: check-umamba - if: contains(inputs.compiler, 'lfortran') - shell: bash - run: | - if [ "$(command -v micromamba)" ]; then - echo "install=false" >> $GITHUB_OUTPUT - else - echo "install=true" >> $GITHUB_OUTPUT - fi - - uses: mamba-org/setup-micromamba@v3.2.1 - if: contains(inputs.compiler, 'lfortran') && steps.check-umamba.outputs.install == 'true' - with: - init-shell: bash - post-cleanup: 'all' - - # Set up the selected toolchain or compiler - - name: Setup toolchain - id: setup - if: steps.cache.outputs.cache-hit != 'true' - shell: bash - env: - COMPILER: ${{ inputs.compiler }} - VERSION: ${{ inputs.version }} - run: | - cd $(echo "/$GITHUB_ACTION_PATH" | sed -e 's/\\/\//g' -e 's/://') - source ./main.sh + description: The C++ compiler command or path. - if [[ "${{ inputs.update-environment }}" == "true" ]]; then - echo "FC=${FC}" >> $GITHUB_ENV - echo "CC=${CC}" >> $GITHUB_ENV - echo "CXX=${CXX}" >> $GITHUB_ENV - fi - - # save oneAPI cache and activate environment - - name: Save cache - if: runner.os == 'Windows' && contains(inputs.compiler, 'intel') && steps.cache.outputs.cache-hit != 'true' - uses: actions/cache/save@v6 - with: - path: ${{ env.ONEAPI_ROOT }} - key: ${{ runner.os }}-${{ inputs.compiler }}-${{ inputs.version || 'latest' }}-${{ steps.get-date.outputs.date }} - - name: Activate oneAPI - if: runner.os == 'Windows' && contains(inputs.compiler, 'intel') - shell: cmd - run: | - ver | findstr /i "10\.0\.17" && set VS2019INSTALLDIR=C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise - ver | findstr /i "10\.0\.20" && set VS2022INSTALLDIR=C:\Program Files\Microsoft Visual Studio\2022\Enterprise - call "%ONEAPI_ROOT%\setvars.bat" - set | findstr /c:"oneAPI" >> "%GITHUB_ENV%" - - - name: Set outputs and env vars - shell: bash - id: outputs - run: | - # Intel compilers may have restored from cache so env vars may not be set - if [[ "$RUNNER_OS" == "Windows" ]]; then - if [[ "${{ inputs.compiler }}" == "intel" ]]; then - FC=ifx - CC=icx - CXX=icx - echo SETVARS_COMPLETED=1>>$GITHUB_ENV - elif [[ "${{ inputs.compiler }}" == "intel-classic" ]]; then - FC=ifort - CC=icl - CXX=icl - echo SETVARS_COMPLETED=1>>$GITHUB_ENV - fi - fi - - if [[ "${{ inputs.update-environment }}" == "true" ]]; then - # cmake - echo FC=$FC>>$GITHUB_ENV - echo CC=$CC>>$GITHUB_ENV - echo CXX=$CXX>>$GITHUB_ENV - # fpm - echo FPM_FC=$FC>>$GITHUB_ENV - echo FPM_CC=$CC>>$GITHUB_ENV - echo FPM_CXX=$CXX>>$GITHUB_ENV - fi - - # set action outputs - echo fc=$FC>>$GITHUB_OUTPUT - echo cc=$CC>>$GITHUB_OUTPUT - echo cxx=$CXX>>$GITHUB_OUTPUT - - - name: Hide GNU linker (Windows) - if: runner.os == 'Windows' && !contains(inputs.compiler, 'gcc') - shell: bash - run: mv /usr/bin/link $RUNNER_TEMP/link - - - name: Get architecture (Windows) - id: msvc-arch - if: runner.os == 'Windows' && contains(inputs.compiler, 'lfortran') - shell: bash - run: echo "arch=${RUNNER_ARCH,,}" >> $GITHUB_OUTPUT - - - name: Setup MSVC toolchain (Windows) - if: runner.os == 'Windows' && contains(inputs.compiler, 'lfortran') - uses: ilammy/msvc-dev-cmd@v1.13.0 - with: - arch: ${{ steps.msvc-arch.outputs.arch }} +runs: + using: node24 + main: dist/index.js diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 00000000..8d884a95 --- /dev/null +++ b/dist/index.js @@ -0,0 +1,104086 @@ +/******/ (() => { // webpackBootstrap +/******/ var __webpack_modules__ = ({ + +/***/ 7889: +/***/ (function(__unused_webpack_module, exports) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ClientStreamingCall = void 0; +/** + * A client streaming RPC call. This means that the clients sends 0, 1, or + * more messages to the server, and the server replies with exactly one + * message. + */ +class ClientStreamingCall { + constructor(method, requestHeaders, request, headers, response, status, trailers) { + this.method = method; + this.requestHeaders = requestHeaders; + this.requests = request; + this.headers = headers; + this.response = response; + this.status = status; + this.trailers = trailers; + } + /** + * Instead of awaiting the response status and trailers, you can + * just as well await this call itself to receive the server outcome. + * Note that it may still be valid to send more request messages. + */ + then(onfulfilled, onrejected) { + return this.promiseFinished().then(value => onfulfilled ? Promise.resolve(onfulfilled(value)) : value, reason => onrejected ? Promise.resolve(onrejected(reason)) : Promise.reject(reason)); + } + promiseFinished() { + return __awaiter(this, void 0, void 0, function* () { + let [headers, response, status, trailers] = yield Promise.all([this.headers, this.response, this.status, this.trailers]); + return { + method: this.method, + requestHeaders: this.requestHeaders, + headers, + response, + status, + trailers + }; + }); + } +} +exports.ClientStreamingCall = ClientStreamingCall; + + +/***/ }), + +/***/ 1409: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.Deferred = exports.DeferredState = void 0; +var DeferredState; +(function (DeferredState) { + DeferredState[DeferredState["PENDING"] = 0] = "PENDING"; + DeferredState[DeferredState["REJECTED"] = 1] = "REJECTED"; + DeferredState[DeferredState["RESOLVED"] = 2] = "RESOLVED"; +})(DeferredState = exports.DeferredState || (exports.DeferredState = {})); +/** + * A deferred promise. This is a "controller" for a promise, which lets you + * pass a promise around and reject or resolve it from the outside. + * + * Warning: This class is to be used with care. Using it can make code very + * difficult to read. It is intended for use in library code that exposes + * promises, not for regular business logic. + */ +class Deferred { + /** + * @param preventUnhandledRejectionWarning - prevents the warning + * "Unhandled Promise rejection" by adding a noop rejection handler. + * Working with calls returned from the runtime-rpc package in an + * async function usually means awaiting one call property after + * the other. This means that the "status" is not being awaited when + * an earlier await for the "headers" is rejected. This causes the + * "unhandled promise reject" warning. A more correct behaviour for + * calls might be to become aware whether at least one of the + * promises is handled and swallow the rejection warning for the + * others. + */ + constructor(preventUnhandledRejectionWarning = true) { + this._state = DeferredState.PENDING; + this._promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + }); + if (preventUnhandledRejectionWarning) { + this._promise.catch(_ => { }); + } + } + /** + * Get the current state of the promise. + */ + get state() { + return this._state; + } + /** + * Get the deferred promise. + */ + get promise() { + return this._promise; + } + /** + * Resolve the promise. Throws if the promise is already resolved or rejected. + */ + resolve(value) { + if (this.state !== DeferredState.PENDING) + throw new Error(`cannot resolve ${DeferredState[this.state].toLowerCase()}`); + this._resolve(value); + this._state = DeferredState.RESOLVED; + } + /** + * Reject the promise. Throws if the promise is already resolved or rejected. + */ + reject(reason) { + if (this.state !== DeferredState.PENDING) + throw new Error(`cannot reject ${DeferredState[this.state].toLowerCase()}`); + this._reject(reason); + this._state = DeferredState.REJECTED; + } + /** + * Resolve the promise. Ignore if not pending. + */ + resolvePending(val) { + if (this._state === DeferredState.PENDING) + this.resolve(val); + } + /** + * Reject the promise. Ignore if not pending. + */ + rejectPending(reason) { + if (this._state === DeferredState.PENDING) + this.reject(reason); + } +} +exports.Deferred = Deferred; + + +/***/ }), + +/***/ 6826: +/***/ (function(__unused_webpack_module, exports) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.DuplexStreamingCall = void 0; +/** + * A duplex streaming RPC call. This means that the clients sends an + * arbitrary amount of messages to the server, while at the same time, + * the server sends an arbitrary amount of messages to the client. + */ +class DuplexStreamingCall { + constructor(method, requestHeaders, request, headers, response, status, trailers) { + this.method = method; + this.requestHeaders = requestHeaders; + this.requests = request; + this.headers = headers; + this.responses = response; + this.status = status; + this.trailers = trailers; + } + /** + * Instead of awaiting the response status and trailers, you can + * just as well await this call itself to receive the server outcome. + * Note that it may still be valid to send more request messages. + */ + then(onfulfilled, onrejected) { + return this.promiseFinished().then(value => onfulfilled ? Promise.resolve(onfulfilled(value)) : value, reason => onrejected ? Promise.resolve(onrejected(reason)) : Promise.reject(reason)); + } + promiseFinished() { + return __awaiter(this, void 0, void 0, function* () { + let [headers, status, trailers] = yield Promise.all([this.headers, this.status, this.trailers]); + return { + method: this.method, + requestHeaders: this.requestHeaders, + headers, + status, + trailers, + }; + }); + } +} +exports.DuplexStreamingCall = DuplexStreamingCall; + + +/***/ }), + +/***/ 4420: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; +var __webpack_unused_export__; + +// Public API of the rpc runtime. +// Note: we do not use `export * from ...` to help tree shakers, +// webpack verbose output hints that this should be useful +__webpack_unused_export__ = ({ value: true }); +var service_type_1 = __nccwpck_require__(6892); +Object.defineProperty(exports, "C0", ({ enumerable: true, get: function () { return service_type_1.ServiceType; } })); +var reflection_info_1 = __nccwpck_require__(2496); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return reflection_info_1.readMethodOptions; } }); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return reflection_info_1.readMethodOption; } }); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return reflection_info_1.readServiceOption; } }); +var rpc_error_1 = __nccwpck_require__(8636); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return rpc_error_1.RpcError; } }); +var rpc_options_1 = __nccwpck_require__(8576); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return rpc_options_1.mergeRpcOptions; } }); +var rpc_output_stream_1 = __nccwpck_require__(2726); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return rpc_output_stream_1.RpcOutputStreamController; } }); +var test_transport_1 = __nccwpck_require__(9122); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return test_transport_1.TestTransport; } }); +var deferred_1 = __nccwpck_require__(1409); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return deferred_1.Deferred; } }); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return deferred_1.DeferredState; } }); +var duplex_streaming_call_1 = __nccwpck_require__(6826); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return duplex_streaming_call_1.DuplexStreamingCall; } }); +var client_streaming_call_1 = __nccwpck_require__(7889); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return client_streaming_call_1.ClientStreamingCall; } }); +var server_streaming_call_1 = __nccwpck_require__(6173); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return server_streaming_call_1.ServerStreamingCall; } }); +var unary_call_1 = __nccwpck_require__(9288); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return unary_call_1.UnaryCall; } }); +var rpc_interceptor_1 = __nccwpck_require__(2849); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return rpc_interceptor_1.stackIntercept; } }); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return rpc_interceptor_1.stackDuplexStreamingInterceptors; } }); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return rpc_interceptor_1.stackClientStreamingInterceptors; } }); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return rpc_interceptor_1.stackServerStreamingInterceptors; } }); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return rpc_interceptor_1.stackUnaryInterceptors; } }); +var server_call_context_1 = __nccwpck_require__(3352); +__webpack_unused_export__ = ({ enumerable: true, get: function () { return server_call_context_1.ServerCallContextController; } }); + + +/***/ }), + +/***/ 2496: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.readServiceOption = exports.readMethodOption = exports.readMethodOptions = exports.normalizeMethodInfo = void 0; +const runtime_1 = __nccwpck_require__(8886); +/** + * Turns PartialMethodInfo into MethodInfo. + */ +function normalizeMethodInfo(method, service) { + var _a, _b, _c; + let m = method; + m.service = service; + m.localName = (_a = m.localName) !== null && _a !== void 0 ? _a : runtime_1.lowerCamelCase(m.name); + // noinspection PointlessBooleanExpressionJS + m.serverStreaming = !!m.serverStreaming; + // noinspection PointlessBooleanExpressionJS + m.clientStreaming = !!m.clientStreaming; + m.options = (_b = m.options) !== null && _b !== void 0 ? _b : {}; + m.idempotency = (_c = m.idempotency) !== null && _c !== void 0 ? _c : undefined; + return m; +} +exports.normalizeMethodInfo = normalizeMethodInfo; +/** + * Read custom method options from a generated service client. + * + * @deprecated use readMethodOption() + */ +function readMethodOptions(service, methodName, extensionName, extensionType) { + var _a; + const options = (_a = service.methods.find((m, i) => m.localName === methodName || i === methodName)) === null || _a === void 0 ? void 0 : _a.options; + return options && options[extensionName] ? extensionType.fromJson(options[extensionName]) : undefined; +} +exports.readMethodOptions = readMethodOptions; +function readMethodOption(service, methodName, extensionName, extensionType) { + var _a; + const options = (_a = service.methods.find((m, i) => m.localName === methodName || i === methodName)) === null || _a === void 0 ? void 0 : _a.options; + if (!options) { + return undefined; + } + const optionVal = options[extensionName]; + if (optionVal === undefined) { + return optionVal; + } + return extensionType ? extensionType.fromJson(optionVal) : optionVal; +} +exports.readMethodOption = readMethodOption; +function readServiceOption(service, extensionName, extensionType) { + const options = service.options; + if (!options) { + return undefined; + } + const optionVal = options[extensionName]; + if (optionVal === undefined) { + return optionVal; + } + return extensionType ? extensionType.fromJson(optionVal) : optionVal; +} +exports.readServiceOption = readServiceOption; + + +/***/ }), + +/***/ 8636: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.RpcError = void 0; +/** + * An error that occurred while calling a RPC method. + */ +class RpcError extends Error { + constructor(message, code = 'UNKNOWN', meta) { + super(message); + this.name = 'RpcError'; + // see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#example + Object.setPrototypeOf(this, new.target.prototype); + this.code = code; + this.meta = meta !== null && meta !== void 0 ? meta : {}; + } + toString() { + const l = [this.name + ': ' + this.message]; + if (this.code) { + l.push(''); + l.push('Code: ' + this.code); + } + if (this.serviceName && this.methodName) { + l.push('Method: ' + this.serviceName + '/' + this.methodName); + } + let m = Object.entries(this.meta); + if (m.length) { + l.push(''); + l.push('Meta:'); + for (let [k, v] of m) { + l.push(` ${k}: ${v}`); + } + } + return l.join('\n'); + } +} +exports.RpcError = RpcError; + + +/***/ }), + +/***/ 2849: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.stackDuplexStreamingInterceptors = exports.stackClientStreamingInterceptors = exports.stackServerStreamingInterceptors = exports.stackUnaryInterceptors = exports.stackIntercept = void 0; +const runtime_1 = __nccwpck_require__(8886); +/** + * Creates a "stack" of of all interceptors specified in the given `RpcOptions`. + * Used by generated client implementations. + * @internal + */ +function stackIntercept(kind, transport, method, options, input) { + var _a, _b, _c, _d; + if (kind == "unary") { + let tail = (mtd, inp, opt) => transport.unary(mtd, inp, opt); + for (const curr of ((_a = options.interceptors) !== null && _a !== void 0 ? _a : []).filter(i => i.interceptUnary).reverse()) { + const next = tail; + tail = (mtd, inp, opt) => curr.interceptUnary(next, mtd, inp, opt); + } + return tail(method, input, options); + } + if (kind == "serverStreaming") { + let tail = (mtd, inp, opt) => transport.serverStreaming(mtd, inp, opt); + for (const curr of ((_b = options.interceptors) !== null && _b !== void 0 ? _b : []).filter(i => i.interceptServerStreaming).reverse()) { + const next = tail; + tail = (mtd, inp, opt) => curr.interceptServerStreaming(next, mtd, inp, opt); + } + return tail(method, input, options); + } + if (kind == "clientStreaming") { + let tail = (mtd, opt) => transport.clientStreaming(mtd, opt); + for (const curr of ((_c = options.interceptors) !== null && _c !== void 0 ? _c : []).filter(i => i.interceptClientStreaming).reverse()) { + const next = tail; + tail = (mtd, opt) => curr.interceptClientStreaming(next, mtd, opt); + } + return tail(method, options); + } + if (kind == "duplex") { + let tail = (mtd, opt) => transport.duplex(mtd, opt); + for (const curr of ((_d = options.interceptors) !== null && _d !== void 0 ? _d : []).filter(i => i.interceptDuplex).reverse()) { + const next = tail; + tail = (mtd, opt) => curr.interceptDuplex(next, mtd, opt); + } + return tail(method, options); + } + runtime_1.assertNever(kind); +} +exports.stackIntercept = stackIntercept; +/** + * @deprecated replaced by `stackIntercept()`, still here to support older generated code + */ +function stackUnaryInterceptors(transport, method, input, options) { + return stackIntercept("unary", transport, method, options, input); +} +exports.stackUnaryInterceptors = stackUnaryInterceptors; +/** + * @deprecated replaced by `stackIntercept()`, still here to support older generated code + */ +function stackServerStreamingInterceptors(transport, method, input, options) { + return stackIntercept("serverStreaming", transport, method, options, input); +} +exports.stackServerStreamingInterceptors = stackServerStreamingInterceptors; +/** + * @deprecated replaced by `stackIntercept()`, still here to support older generated code + */ +function stackClientStreamingInterceptors(transport, method, options) { + return stackIntercept("clientStreaming", transport, method, options); +} +exports.stackClientStreamingInterceptors = stackClientStreamingInterceptors; +/** + * @deprecated replaced by `stackIntercept()`, still here to support older generated code + */ +function stackDuplexStreamingInterceptors(transport, method, options) { + return stackIntercept("duplex", transport, method, options); +} +exports.stackDuplexStreamingInterceptors = stackDuplexStreamingInterceptors; + + +/***/ }), + +/***/ 8576: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.mergeRpcOptions = void 0; +const runtime_1 = __nccwpck_require__(8886); +/** + * Merges custom RPC options with defaults. Returns a new instance and keeps + * the "defaults" and the "options" unmodified. + * + * Merges `RpcMetadata` "meta", overwriting values from "defaults" with + * values from "options". Does not append values to existing entries. + * + * Merges "jsonOptions", including "jsonOptions.typeRegistry", by creating + * a new array that contains types from "options.jsonOptions.typeRegistry" + * first, then types from "defaults.jsonOptions.typeRegistry". + * + * Merges "binaryOptions". + * + * Merges "interceptors" by creating a new array that contains interceptors + * from "defaults" first, then interceptors from "options". + * + * Works with objects that extend `RpcOptions`, but only if the added + * properties are of type Date, primitive like string, boolean, or Array + * of primitives. If you have other property types, you have to merge them + * yourself. + */ +function mergeRpcOptions(defaults, options) { + if (!options) + return defaults; + let o = {}; + copy(defaults, o); + copy(options, o); + for (let key of Object.keys(options)) { + let val = options[key]; + switch (key) { + case "jsonOptions": + o.jsonOptions = runtime_1.mergeJsonOptions(defaults.jsonOptions, o.jsonOptions); + break; + case "binaryOptions": + o.binaryOptions = runtime_1.mergeBinaryOptions(defaults.binaryOptions, o.binaryOptions); + break; + case "meta": + o.meta = {}; + copy(defaults.meta, o.meta); + copy(options.meta, o.meta); + break; + case "interceptors": + o.interceptors = defaults.interceptors ? defaults.interceptors.concat(val) : val.concat(); + break; + } + } + return o; +} +exports.mergeRpcOptions = mergeRpcOptions; +function copy(a, into) { + if (!a) + return; + let c = into; + for (let [k, v] of Object.entries(a)) { + if (v instanceof Date) + c[k] = new Date(v.getTime()); + else if (Array.isArray(v)) + c[k] = v.concat(); + else + c[k] = v; + } +} + + +/***/ }), + +/***/ 2726: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.RpcOutputStreamController = void 0; +const deferred_1 = __nccwpck_require__(1409); +const runtime_1 = __nccwpck_require__(8886); +/** + * A `RpcOutputStream` that you control. + */ +class RpcOutputStreamController { + constructor() { + this._lis = { + nxt: [], + msg: [], + err: [], + cmp: [], + }; + this._closed = false; + // --- RpcOutputStream async iterator API + // iterator state. + // is undefined when no iterator has been acquired yet. + this._itState = { q: [] }; + } + // --- RpcOutputStream callback API + onNext(callback) { + return this.addLis(callback, this._lis.nxt); + } + onMessage(callback) { + return this.addLis(callback, this._lis.msg); + } + onError(callback) { + return this.addLis(callback, this._lis.err); + } + onComplete(callback) { + return this.addLis(callback, this._lis.cmp); + } + addLis(callback, list) { + list.push(callback); + return () => { + let i = list.indexOf(callback); + if (i >= 0) + list.splice(i, 1); + }; + } + // remove all listeners + clearLis() { + for (let l of Object.values(this._lis)) + l.splice(0, l.length); + } + // --- Controller API + /** + * Is this stream already closed by a completion or error? + */ + get closed() { + return this._closed !== false; + } + /** + * Emit message, close with error, or close successfully, but only one + * at a time. + * Can be used to wrap a stream by using the other stream's `onNext`. + */ + notifyNext(message, error, complete) { + runtime_1.assert((message ? 1 : 0) + (error ? 1 : 0) + (complete ? 1 : 0) <= 1, 'only one emission at a time'); + if (message) + this.notifyMessage(message); + if (error) + this.notifyError(error); + if (complete) + this.notifyComplete(); + } + /** + * Emits a new message. Throws if stream is closed. + * + * Triggers onNext and onMessage callbacks. + */ + notifyMessage(message) { + runtime_1.assert(!this.closed, 'stream is closed'); + this.pushIt({ value: message, done: false }); + this._lis.msg.forEach(l => l(message)); + this._lis.nxt.forEach(l => l(message, undefined, false)); + } + /** + * Closes the stream with an error. Throws if stream is closed. + * + * Triggers onNext and onError callbacks. + */ + notifyError(error) { + runtime_1.assert(!this.closed, 'stream is closed'); + this._closed = error; + this.pushIt(error); + this._lis.err.forEach(l => l(error)); + this._lis.nxt.forEach(l => l(undefined, error, false)); + this.clearLis(); + } + /** + * Closes the stream successfully. Throws if stream is closed. + * + * Triggers onNext and onComplete callbacks. + */ + notifyComplete() { + runtime_1.assert(!this.closed, 'stream is closed'); + this._closed = true; + this.pushIt({ value: null, done: true }); + this._lis.cmp.forEach(l => l()); + this._lis.nxt.forEach(l => l(undefined, undefined, true)); + this.clearLis(); + } + /** + * Creates an async iterator (that can be used with `for await {...}`) + * to consume the stream. + * + * Some things to note: + * - If an error occurs, the `for await` will throw it. + * - If an error occurred before the `for await` was started, `for await` + * will re-throw it. + * - If the stream is already complete, the `for await` will be empty. + * - If your `for await` consumes slower than the stream produces, + * for example because you are relaying messages in a slow operation, + * messages are queued. + */ + [Symbol.asyncIterator]() { + // if we are closed, we are definitely not receiving any more messages. + // but we can't let the iterator get stuck. we want to either: + // a) finish the new iterator immediately, because we are completed + // b) reject the new iterator, because we errored + if (this._closed === true) + this.pushIt({ value: null, done: true }); + else if (this._closed !== false) + this.pushIt(this._closed); + // the async iterator + return { + next: () => { + let state = this._itState; + runtime_1.assert(state, "bad state"); // if we don't have a state here, code is broken + // there should be no pending result. + // did the consumer call next() before we resolved our previous result promise? + runtime_1.assert(!state.p, "iterator contract broken"); + // did we produce faster than the iterator consumed? + // return the oldest result from the queue. + let first = state.q.shift(); + if (first) + return ("value" in first) ? Promise.resolve(first) : Promise.reject(first); + // we have no result ATM, but we promise one. + // as soon as we have a result, we must resolve promise. + state.p = new deferred_1.Deferred(); + return state.p.promise; + }, + }; + } + // "push" a new iterator result. + // this either resolves a pending promise, or enqueues the result. + pushIt(result) { + let state = this._itState; + // is the consumer waiting for us? + if (state.p) { + // yes, consumer is waiting for this promise. + const p = state.p; + runtime_1.assert(p.state == deferred_1.DeferredState.PENDING, "iterator contract broken"); + // resolve the promise + ("value" in result) ? p.resolve(result) : p.reject(result); + // must cleanup, otherwise iterator.next() would pick it up again. + delete state.p; + } + else { + // we are producing faster than the iterator consumes. + // push result onto queue. + state.q.push(result); + } + } +} +exports.RpcOutputStreamController = RpcOutputStreamController; + + +/***/ }), + +/***/ 3352: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ServerCallContextController = void 0; +class ServerCallContextController { + constructor(method, headers, deadline, sendResponseHeadersFn, defaultStatus = { code: 'OK', detail: '' }) { + this._cancelled = false; + this._listeners = []; + this.method = method; + this.headers = headers; + this.deadline = deadline; + this.trailers = {}; + this._sendRH = sendResponseHeadersFn; + this.status = defaultStatus; + } + /** + * Set the call cancelled. + * + * Invokes all callbacks registered with onCancel() and + * sets `cancelled = true`. + */ + notifyCancelled() { + if (!this._cancelled) { + this._cancelled = true; + for (let l of this._listeners) { + l(); + } + } + } + /** + * Send response headers. + */ + sendResponseHeaders(data) { + this._sendRH(data); + } + /** + * Is the call cancelled? + * + * When the client closes the connection before the server + * is done, the call is cancelled. + * + * If you want to cancel a request on the server, throw a + * RpcError with the CANCELLED status code. + */ + get cancelled() { + return this._cancelled; + } + /** + * Add a callback for cancellation. + */ + onCancel(callback) { + const l = this._listeners; + l.push(callback); + return () => { + let i = l.indexOf(callback); + if (i >= 0) + l.splice(i, 1); + }; + } +} +exports.ServerCallContextController = ServerCallContextController; + + +/***/ }), + +/***/ 6173: +/***/ (function(__unused_webpack_module, exports) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ServerStreamingCall = void 0; +/** + * A server streaming RPC call. The client provides exactly one input message + * but the server may respond with 0, 1, or more messages. + */ +class ServerStreamingCall { + constructor(method, requestHeaders, request, headers, response, status, trailers) { + this.method = method; + this.requestHeaders = requestHeaders; + this.request = request; + this.headers = headers; + this.responses = response; + this.status = status; + this.trailers = trailers; + } + /** + * Instead of awaiting the response status and trailers, you can + * just as well await this call itself to receive the server outcome. + * You should first setup some listeners to the `request` to + * see the actual messages the server replied with. + */ + then(onfulfilled, onrejected) { + return this.promiseFinished().then(value => onfulfilled ? Promise.resolve(onfulfilled(value)) : value, reason => onrejected ? Promise.resolve(onrejected(reason)) : Promise.reject(reason)); + } + promiseFinished() { + return __awaiter(this, void 0, void 0, function* () { + let [headers, status, trailers] = yield Promise.all([this.headers, this.status, this.trailers]); + return { + method: this.method, + requestHeaders: this.requestHeaders, + request: this.request, + headers, + status, + trailers, + }; + }); + } +} +exports.ServerStreamingCall = ServerStreamingCall; + + +/***/ }), + +/***/ 6892: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ServiceType = void 0; +const reflection_info_1 = __nccwpck_require__(2496); +class ServiceType { + constructor(typeName, methods, options) { + this.typeName = typeName; + this.methods = methods.map(i => reflection_info_1.normalizeMethodInfo(i, this)); + this.options = options !== null && options !== void 0 ? options : {}; + } +} +exports.ServiceType = ServiceType; + + +/***/ }), + +/***/ 9122: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.TestTransport = void 0; +const rpc_error_1 = __nccwpck_require__(8636); +const runtime_1 = __nccwpck_require__(8886); +const rpc_output_stream_1 = __nccwpck_require__(2726); +const rpc_options_1 = __nccwpck_require__(8576); +const unary_call_1 = __nccwpck_require__(9288); +const server_streaming_call_1 = __nccwpck_require__(6173); +const client_streaming_call_1 = __nccwpck_require__(7889); +const duplex_streaming_call_1 = __nccwpck_require__(6826); +/** + * Transport for testing. + */ +class TestTransport { + /** + * Initialize with mock data. Omitted fields have default value. + */ + constructor(data) { + /** + * Suppress warning / error about uncaught rejections of + * "status" and "trailers". + */ + this.suppressUncaughtRejections = true; + this.headerDelay = 10; + this.responseDelay = 50; + this.betweenResponseDelay = 10; + this.afterResponseDelay = 10; + this.data = data !== null && data !== void 0 ? data : {}; + } + /** + * Sent message(s) during the last operation. + */ + get sentMessages() { + if (this.lastInput instanceof TestInputStream) { + return this.lastInput.sent; + } + else if (typeof this.lastInput == "object") { + return [this.lastInput.single]; + } + return []; + } + /** + * Sending message(s) completed? + */ + get sendComplete() { + if (this.lastInput instanceof TestInputStream) { + return this.lastInput.completed; + } + else if (typeof this.lastInput == "object") { + return true; + } + return false; + } + // Creates a promise for response headers from the mock data. + promiseHeaders() { + var _a; + const headers = (_a = this.data.headers) !== null && _a !== void 0 ? _a : TestTransport.defaultHeaders; + return headers instanceof rpc_error_1.RpcError + ? Promise.reject(headers) + : Promise.resolve(headers); + } + // Creates a promise for a single, valid, message from the mock data. + promiseSingleResponse(method) { + if (this.data.response instanceof rpc_error_1.RpcError) { + return Promise.reject(this.data.response); + } + let r; + if (Array.isArray(this.data.response)) { + runtime_1.assert(this.data.response.length > 0); + r = this.data.response[0]; + } + else if (this.data.response !== undefined) { + r = this.data.response; + } + else { + r = method.O.create(); + } + runtime_1.assert(method.O.is(r)); + return Promise.resolve(r); + } + /** + * Pushes response messages from the mock data to the output stream. + * If an error response, status or trailers are mocked, the stream is + * closed with the respective error. + * Otherwise, stream is completed successfully. + * + * The returned promise resolves when the stream is closed. It should + * not reject. If it does, code is broken. + */ + streamResponses(method, stream, abort) { + return __awaiter(this, void 0, void 0, function* () { + // normalize "data.response" into an array of valid output messages + const messages = []; + if (this.data.response === undefined) { + messages.push(method.O.create()); + } + else if (Array.isArray(this.data.response)) { + for (let msg of this.data.response) { + runtime_1.assert(method.O.is(msg)); + messages.push(msg); + } + } + else if (!(this.data.response instanceof rpc_error_1.RpcError)) { + runtime_1.assert(method.O.is(this.data.response)); + messages.push(this.data.response); + } + // start the stream with an initial delay. + // if the request is cancelled, notify() error and exit. + try { + yield delay(this.responseDelay, abort)(undefined); + } + catch (error) { + stream.notifyError(error); + return; + } + // if error response was mocked, notify() error (stream is now closed with error) and exit. + if (this.data.response instanceof rpc_error_1.RpcError) { + stream.notifyError(this.data.response); + return; + } + // regular response messages were mocked. notify() them. + for (let msg of messages) { + stream.notifyMessage(msg); + // add a short delay between responses + // if the request is cancelled, notify() error and exit. + try { + yield delay(this.betweenResponseDelay, abort)(undefined); + } + catch (error) { + stream.notifyError(error); + return; + } + } + // error status was mocked, notify() error (stream is now closed with error) and exit. + if (this.data.status instanceof rpc_error_1.RpcError) { + stream.notifyError(this.data.status); + return; + } + // error trailers were mocked, notify() error (stream is now closed with error) and exit. + if (this.data.trailers instanceof rpc_error_1.RpcError) { + stream.notifyError(this.data.trailers); + return; + } + // stream completed successfully + stream.notifyComplete(); + }); + } + // Creates a promise for response status from the mock data. + promiseStatus() { + var _a; + const status = (_a = this.data.status) !== null && _a !== void 0 ? _a : TestTransport.defaultStatus; + return status instanceof rpc_error_1.RpcError + ? Promise.reject(status) + : Promise.resolve(status); + } + // Creates a promise for response trailers from the mock data. + promiseTrailers() { + var _a; + const trailers = (_a = this.data.trailers) !== null && _a !== void 0 ? _a : TestTransport.defaultTrailers; + return trailers instanceof rpc_error_1.RpcError + ? Promise.reject(trailers) + : Promise.resolve(trailers); + } + maybeSuppressUncaught(...promise) { + if (this.suppressUncaughtRejections) { + for (let p of promise) { + p.catch(() => { + }); + } + } + } + mergeOptions(options) { + return rpc_options_1.mergeRpcOptions({}, options); + } + unary(method, input, options) { + var _a; + const requestHeaders = (_a = options.meta) !== null && _a !== void 0 ? _a : {}, headersPromise = this.promiseHeaders() + .then(delay(this.headerDelay, options.abort)), responsePromise = headersPromise + .catch(_ => { + }) + .then(delay(this.responseDelay, options.abort)) + .then(_ => this.promiseSingleResponse(method)), statusPromise = responsePromise + .catch(_ => { + }) + .then(delay(this.afterResponseDelay, options.abort)) + .then(_ => this.promiseStatus()), trailersPromise = responsePromise + .catch(_ => { + }) + .then(delay(this.afterResponseDelay, options.abort)) + .then(_ => this.promiseTrailers()); + this.maybeSuppressUncaught(statusPromise, trailersPromise); + this.lastInput = { single: input }; + return new unary_call_1.UnaryCall(method, requestHeaders, input, headersPromise, responsePromise, statusPromise, trailersPromise); + } + serverStreaming(method, input, options) { + var _a; + const requestHeaders = (_a = options.meta) !== null && _a !== void 0 ? _a : {}, headersPromise = this.promiseHeaders() + .then(delay(this.headerDelay, options.abort)), outputStream = new rpc_output_stream_1.RpcOutputStreamController(), responseStreamClosedPromise = headersPromise + .then(delay(this.responseDelay, options.abort)) + .catch(() => { + }) + .then(() => this.streamResponses(method, outputStream, options.abort)) + .then(delay(this.afterResponseDelay, options.abort)), statusPromise = responseStreamClosedPromise + .then(() => this.promiseStatus()), trailersPromise = responseStreamClosedPromise + .then(() => this.promiseTrailers()); + this.maybeSuppressUncaught(statusPromise, trailersPromise); + this.lastInput = { single: input }; + return new server_streaming_call_1.ServerStreamingCall(method, requestHeaders, input, headersPromise, outputStream, statusPromise, trailersPromise); + } + clientStreaming(method, options) { + var _a; + const requestHeaders = (_a = options.meta) !== null && _a !== void 0 ? _a : {}, headersPromise = this.promiseHeaders() + .then(delay(this.headerDelay, options.abort)), responsePromise = headersPromise + .catch(_ => { + }) + .then(delay(this.responseDelay, options.abort)) + .then(_ => this.promiseSingleResponse(method)), statusPromise = responsePromise + .catch(_ => { + }) + .then(delay(this.afterResponseDelay, options.abort)) + .then(_ => this.promiseStatus()), trailersPromise = responsePromise + .catch(_ => { + }) + .then(delay(this.afterResponseDelay, options.abort)) + .then(_ => this.promiseTrailers()); + this.maybeSuppressUncaught(statusPromise, trailersPromise); + this.lastInput = new TestInputStream(this.data, options.abort); + return new client_streaming_call_1.ClientStreamingCall(method, requestHeaders, this.lastInput, headersPromise, responsePromise, statusPromise, trailersPromise); + } + duplex(method, options) { + var _a; + const requestHeaders = (_a = options.meta) !== null && _a !== void 0 ? _a : {}, headersPromise = this.promiseHeaders() + .then(delay(this.headerDelay, options.abort)), outputStream = new rpc_output_stream_1.RpcOutputStreamController(), responseStreamClosedPromise = headersPromise + .then(delay(this.responseDelay, options.abort)) + .catch(() => { + }) + .then(() => this.streamResponses(method, outputStream, options.abort)) + .then(delay(this.afterResponseDelay, options.abort)), statusPromise = responseStreamClosedPromise + .then(() => this.promiseStatus()), trailersPromise = responseStreamClosedPromise + .then(() => this.promiseTrailers()); + this.maybeSuppressUncaught(statusPromise, trailersPromise); + this.lastInput = new TestInputStream(this.data, options.abort); + return new duplex_streaming_call_1.DuplexStreamingCall(method, requestHeaders, this.lastInput, headersPromise, outputStream, statusPromise, trailersPromise); + } +} +exports.TestTransport = TestTransport; +TestTransport.defaultHeaders = { + responseHeader: "test" +}; +TestTransport.defaultStatus = { + code: "OK", detail: "all good" +}; +TestTransport.defaultTrailers = { + responseTrailer: "test" +}; +function delay(ms, abort) { + return (v) => new Promise((resolve, reject) => { + if (abort === null || abort === void 0 ? void 0 : abort.aborted) { + reject(new rpc_error_1.RpcError("user cancel", "CANCELLED")); + } + else { + const id = setTimeout(() => resolve(v), ms); + if (abort) { + abort.addEventListener("abort", ev => { + clearTimeout(id); + reject(new rpc_error_1.RpcError("user cancel", "CANCELLED")); + }); + } + } + }); +} +class TestInputStream { + constructor(data, abort) { + this._completed = false; + this._sent = []; + this.data = data; + this.abort = abort; + } + get sent() { + return this._sent; + } + get completed() { + return this._completed; + } + send(message) { + if (this.data.inputMessage instanceof rpc_error_1.RpcError) { + return Promise.reject(this.data.inputMessage); + } + const delayMs = this.data.inputMessage === undefined + ? 10 + : this.data.inputMessage; + return Promise.resolve(undefined) + .then(() => { + this._sent.push(message); + }) + .then(delay(delayMs, this.abort)); + } + complete() { + if (this.data.inputComplete instanceof rpc_error_1.RpcError) { + return Promise.reject(this.data.inputComplete); + } + const delayMs = this.data.inputComplete === undefined + ? 10 + : this.data.inputComplete; + return Promise.resolve(undefined) + .then(() => { + this._completed = true; + }) + .then(delay(delayMs, this.abort)); + } +} + + +/***/ }), + +/***/ 9288: +/***/ (function(__unused_webpack_module, exports) { + +"use strict"; + +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.UnaryCall = void 0; +/** + * A unary RPC call. Unary means there is exactly one input message and + * exactly one output message unless an error occurred. + */ +class UnaryCall { + constructor(method, requestHeaders, request, headers, response, status, trailers) { + this.method = method; + this.requestHeaders = requestHeaders; + this.request = request; + this.headers = headers; + this.response = response; + this.status = status; + this.trailers = trailers; + } + /** + * If you are only interested in the final outcome of this call, + * you can await it to receive a `FinishedUnaryCall`. + */ + then(onfulfilled, onrejected) { + return this.promiseFinished().then(value => onfulfilled ? Promise.resolve(onfulfilled(value)) : value, reason => onrejected ? Promise.resolve(onrejected(reason)) : Promise.reject(reason)); + } + promiseFinished() { + return __awaiter(this, void 0, void 0, function* () { + let [headers, response, status, trailers] = yield Promise.all([this.headers, this.response, this.status, this.trailers]); + return { + method: this.method, + requestHeaders: this.requestHeaders, + request: this.request, + headers, + response, + status, + trailers + }; + }); + } +} +exports.UnaryCall = UnaryCall; + + +/***/ }), + +/***/ 8602: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.assertFloat32 = exports.assertUInt32 = exports.assertInt32 = exports.assertNever = exports.assert = void 0; +/** + * assert that condition is true or throw error (with message) + */ +function assert(condition, msg) { + if (!condition) { + throw new Error(msg); + } +} +exports.assert = assert; +/** + * assert that value cannot exist = type `never`. throw runtime error if it does. + */ +function assertNever(value, msg) { + throw new Error(msg !== null && msg !== void 0 ? msg : 'Unexpected object: ' + value); +} +exports.assertNever = assertNever; +const FLOAT32_MAX = 3.4028234663852886e+38, FLOAT32_MIN = -3.4028234663852886e+38, UINT32_MAX = 0xFFFFFFFF, INT32_MAX = 0X7FFFFFFF, INT32_MIN = -0X80000000; +function assertInt32(arg) { + if (typeof arg !== "number") + throw new Error('invalid int 32: ' + typeof arg); + if (!Number.isInteger(arg) || arg > INT32_MAX || arg < INT32_MIN) + throw new Error('invalid int 32: ' + arg); +} +exports.assertInt32 = assertInt32; +function assertUInt32(arg) { + if (typeof arg !== "number") + throw new Error('invalid uint 32: ' + typeof arg); + if (!Number.isInteger(arg) || arg > UINT32_MAX || arg < 0) + throw new Error('invalid uint 32: ' + arg); +} +exports.assertUInt32 = assertUInt32; +function assertFloat32(arg) { + if (typeof arg !== "number") + throw new Error('invalid float 32: ' + typeof arg); + if (!Number.isFinite(arg)) + return; + if (arg > FLOAT32_MAX || arg < FLOAT32_MIN) + throw new Error('invalid float 32: ' + arg); +} +exports.assertFloat32 = assertFloat32; + + +/***/ }), + +/***/ 6335: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.base64encode = exports.base64decode = void 0; +// lookup table from base64 character to byte +let encTable = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''); +// lookup table from base64 character *code* to byte because lookup by number is fast +let decTable = []; +for (let i = 0; i < encTable.length; i++) + decTable[encTable[i].charCodeAt(0)] = i; +// support base64url variants +decTable["-".charCodeAt(0)] = encTable.indexOf("+"); +decTable["_".charCodeAt(0)] = encTable.indexOf("/"); +/** + * Decodes a base64 string to a byte array. + * + * - ignores white-space, including line breaks and tabs + * - allows inner padding (can decode concatenated base64 strings) + * - does not require padding + * - understands base64url encoding: + * "-" instead of "+", + * "_" instead of "/", + * no padding + */ +function base64decode(base64Str) { + // estimate byte size, not accounting for inner padding and whitespace + let es = base64Str.length * 3 / 4; + // if (es % 3 !== 0) + // throw new Error('invalid base64 string'); + if (base64Str[base64Str.length - 2] == '=') + es -= 2; + else if (base64Str[base64Str.length - 1] == '=') + es -= 1; + let bytes = new Uint8Array(es), bytePos = 0, // position in byte array + groupPos = 0, // position in base64 group + b, // current byte + p = 0 // previous byte + ; + for (let i = 0; i < base64Str.length; i++) { + b = decTable[base64Str.charCodeAt(i)]; + if (b === undefined) { + // noinspection FallThroughInSwitchStatementJS + switch (base64Str[i]) { + case '=': + groupPos = 0; // reset state when padding found + case '\n': + case '\r': + case '\t': + case ' ': + continue; // skip white-space, and padding + default: + throw Error(`invalid base64 string.`); + } + } + switch (groupPos) { + case 0: + p = b; + groupPos = 1; + break; + case 1: + bytes[bytePos++] = p << 2 | (b & 48) >> 4; + p = b; + groupPos = 2; + break; + case 2: + bytes[bytePos++] = (p & 15) << 4 | (b & 60) >> 2; + p = b; + groupPos = 3; + break; + case 3: + bytes[bytePos++] = (p & 3) << 6 | b; + groupPos = 0; + break; + } + } + if (groupPos == 1) + throw Error(`invalid base64 string.`); + return bytes.subarray(0, bytePos); +} +exports.base64decode = base64decode; +/** + * Encodes a byte array to a base64 string. + * Adds padding at the end. + * Does not insert newlines. + */ +function base64encode(bytes) { + let base64 = '', groupPos = 0, // position in base64 group + b, // current byte + p = 0; // carry over from previous byte + for (let i = 0; i < bytes.length; i++) { + b = bytes[i]; + switch (groupPos) { + case 0: + base64 += encTable[b >> 2]; + p = (b & 3) << 4; + groupPos = 1; + break; + case 1: + base64 += encTable[p | b >> 4]; + p = (b & 15) << 2; + groupPos = 2; + break; + case 2: + base64 += encTable[p | b >> 6]; + base64 += encTable[b & 63]; + groupPos = 0; + break; + } + } + // padding required? + if (groupPos) { + base64 += encTable[p]; + base64 += '='; + if (groupPos == 1) + base64 += '='; + } + return base64; +} +exports.base64encode = base64encode; + + +/***/ }), + +/***/ 4816: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.WireType = exports.mergeBinaryOptions = exports.UnknownFieldHandler = void 0; +/** + * This handler implements the default behaviour for unknown fields. + * When reading data, unknown fields are stored on the message, in a + * symbol property. + * When writing data, the symbol property is queried and unknown fields + * are serialized into the output again. + */ +var UnknownFieldHandler; +(function (UnknownFieldHandler) { + /** + * The symbol used to store unknown fields for a message. + * The property must conform to `UnknownFieldContainer`. + */ + UnknownFieldHandler.symbol = Symbol.for("protobuf-ts/unknown"); + /** + * Store an unknown field during binary read directly on the message. + * This method is compatible with `BinaryReadOptions.readUnknownField`. + */ + UnknownFieldHandler.onRead = (typeName, message, fieldNo, wireType, data) => { + let container = is(message) ? message[UnknownFieldHandler.symbol] : message[UnknownFieldHandler.symbol] = []; + container.push({ no: fieldNo, wireType, data }); + }; + /** + * Write unknown fields stored for the message to the writer. + * This method is compatible with `BinaryWriteOptions.writeUnknownFields`. + */ + UnknownFieldHandler.onWrite = (typeName, message, writer) => { + for (let { no, wireType, data } of UnknownFieldHandler.list(message)) + writer.tag(no, wireType).raw(data); + }; + /** + * List unknown fields stored for the message. + * Note that there may be multiples fields with the same number. + */ + UnknownFieldHandler.list = (message, fieldNo) => { + if (is(message)) { + let all = message[UnknownFieldHandler.symbol]; + return fieldNo ? all.filter(uf => uf.no == fieldNo) : all; + } + return []; + }; + /** + * Returns the last unknown field by field number. + */ + UnknownFieldHandler.last = (message, fieldNo) => UnknownFieldHandler.list(message, fieldNo).slice(-1)[0]; + const is = (message) => message && Array.isArray(message[UnknownFieldHandler.symbol]); +})(UnknownFieldHandler = exports.UnknownFieldHandler || (exports.UnknownFieldHandler = {})); +/** + * Merges binary write or read options. Later values override earlier values. + */ +function mergeBinaryOptions(a, b) { + return Object.assign(Object.assign({}, a), b); +} +exports.mergeBinaryOptions = mergeBinaryOptions; +/** + * Protobuf binary format wire types. + * + * A wire type provides just enough information to find the length of the + * following value. + * + * See https://developers.google.com/protocol-buffers/docs/encoding#structure + */ +var WireType; +(function (WireType) { + /** + * Used for int32, int64, uint32, uint64, sint32, sint64, bool, enum + */ + WireType[WireType["Varint"] = 0] = "Varint"; + /** + * Used for fixed64, sfixed64, double. + * Always 8 bytes with little-endian byte order. + */ + WireType[WireType["Bit64"] = 1] = "Bit64"; + /** + * Used for string, bytes, embedded messages, packed repeated fields + * + * Only repeated numeric types (types which use the varint, 32-bit, + * or 64-bit wire types) can be packed. In proto3, such fields are + * packed by default. + */ + WireType[WireType["LengthDelimited"] = 2] = "LengthDelimited"; + /** + * Used for groups + * @deprecated + */ + WireType[WireType["StartGroup"] = 3] = "StartGroup"; + /** + * Used for groups + * @deprecated + */ + WireType[WireType["EndGroup"] = 4] = "EndGroup"; + /** + * Used for fixed32, sfixed32, float. + * Always 4 bytes with little-endian byte order. + */ + WireType[WireType["Bit32"] = 5] = "Bit32"; +})(WireType = exports.WireType || (exports.WireType = {})); + + +/***/ }), + +/***/ 2889: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.BinaryReader = exports.binaryReadOptions = void 0; +const binary_format_contract_1 = __nccwpck_require__(4816); +const pb_long_1 = __nccwpck_require__(1753); +const goog_varint_1 = __nccwpck_require__(3223); +const defaultsRead = { + readUnknownField: true, + readerFactory: bytes => new BinaryReader(bytes), +}; +/** + * Make options for reading binary data form partial options. + */ +function binaryReadOptions(options) { + return options ? Object.assign(Object.assign({}, defaultsRead), options) : defaultsRead; +} +exports.binaryReadOptions = binaryReadOptions; +class BinaryReader { + constructor(buf, textDecoder) { + this.varint64 = goog_varint_1.varint64read; // dirty cast for `this` + /** + * Read a `uint32` field, an unsigned 32 bit varint. + */ + this.uint32 = goog_varint_1.varint32read; // dirty cast for `this` and access to protected `buf` + this.buf = buf; + this.len = buf.length; + this.pos = 0; + this.view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength); + this.textDecoder = textDecoder !== null && textDecoder !== void 0 ? textDecoder : new TextDecoder("utf-8", { + fatal: true, + ignoreBOM: true, + }); + } + /** + * Reads a tag - field number and wire type. + */ + tag() { + let tag = this.uint32(), fieldNo = tag >>> 3, wireType = tag & 7; + if (fieldNo <= 0 || wireType < 0 || wireType > 5) + throw new Error("illegal tag: field no " + fieldNo + " wire type " + wireType); + return [fieldNo, wireType]; + } + /** + * Skip one element on the wire and return the skipped data. + * Supports WireType.StartGroup since v2.0.0-alpha.23. + */ + skip(wireType) { + let start = this.pos; + // noinspection FallThroughInSwitchStatementJS + switch (wireType) { + case binary_format_contract_1.WireType.Varint: + while (this.buf[this.pos++] & 0x80) { + // ignore + } + break; + case binary_format_contract_1.WireType.Bit64: + this.pos += 4; + case binary_format_contract_1.WireType.Bit32: + this.pos += 4; + break; + case binary_format_contract_1.WireType.LengthDelimited: + let len = this.uint32(); + this.pos += len; + break; + case binary_format_contract_1.WireType.StartGroup: + // From descriptor.proto: Group type is deprecated, not supported in proto3. + // But we must still be able to parse and treat as unknown. + let t; + while ((t = this.tag()[1]) !== binary_format_contract_1.WireType.EndGroup) { + this.skip(t); + } + break; + default: + throw new Error("cant skip wire type " + wireType); + } + this.assertBounds(); + return this.buf.subarray(start, this.pos); + } + /** + * Throws error if position in byte array is out of range. + */ + assertBounds() { + if (this.pos > this.len) + throw new RangeError("premature EOF"); + } + /** + * Read a `int32` field, a signed 32 bit varint. + */ + int32() { + return this.uint32() | 0; + } + /** + * Read a `sint32` field, a signed, zigzag-encoded 32-bit varint. + */ + sint32() { + let zze = this.uint32(); + // decode zigzag + return (zze >>> 1) ^ -(zze & 1); + } + /** + * Read a `int64` field, a signed 64-bit varint. + */ + int64() { + return new pb_long_1.PbLong(...this.varint64()); + } + /** + * Read a `uint64` field, an unsigned 64-bit varint. + */ + uint64() { + return new pb_long_1.PbULong(...this.varint64()); + } + /** + * Read a `sint64` field, a signed, zig-zag-encoded 64-bit varint. + */ + sint64() { + let [lo, hi] = this.varint64(); + // decode zig zag + let s = -(lo & 1); + lo = ((lo >>> 1 | (hi & 1) << 31) ^ s); + hi = (hi >>> 1 ^ s); + return new pb_long_1.PbLong(lo, hi); + } + /** + * Read a `bool` field, a variant. + */ + bool() { + let [lo, hi] = this.varint64(); + return lo !== 0 || hi !== 0; + } + /** + * Read a `fixed32` field, an unsigned, fixed-length 32-bit integer. + */ + fixed32() { + return this.view.getUint32((this.pos += 4) - 4, true); + } + /** + * Read a `sfixed32` field, a signed, fixed-length 32-bit integer. + */ + sfixed32() { + return this.view.getInt32((this.pos += 4) - 4, true); + } + /** + * Read a `fixed64` field, an unsigned, fixed-length 64 bit integer. + */ + fixed64() { + return new pb_long_1.PbULong(this.sfixed32(), this.sfixed32()); + } + /** + * Read a `fixed64` field, a signed, fixed-length 64-bit integer. + */ + sfixed64() { + return new pb_long_1.PbLong(this.sfixed32(), this.sfixed32()); + } + /** + * Read a `float` field, 32-bit floating point number. + */ + float() { + return this.view.getFloat32((this.pos += 4) - 4, true); + } + /** + * Read a `double` field, a 64-bit floating point number. + */ + double() { + return this.view.getFloat64((this.pos += 8) - 8, true); + } + /** + * Read a `bytes` field, length-delimited arbitrary data. + */ + bytes() { + let len = this.uint32(); + let start = this.pos; + this.pos += len; + this.assertBounds(); + return this.buf.subarray(start, start + len); + } + /** + * Read a `string` field, length-delimited data converted to UTF-8 text. + */ + string() { + return this.textDecoder.decode(this.bytes()); + } +} +exports.BinaryReader = BinaryReader; + + +/***/ }), + +/***/ 3957: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.BinaryWriter = exports.binaryWriteOptions = void 0; +const pb_long_1 = __nccwpck_require__(1753); +const goog_varint_1 = __nccwpck_require__(3223); +const assert_1 = __nccwpck_require__(8602); +const defaultsWrite = { + writeUnknownFields: true, + writerFactory: () => new BinaryWriter(), +}; +/** + * Make options for writing binary data form partial options. + */ +function binaryWriteOptions(options) { + return options ? Object.assign(Object.assign({}, defaultsWrite), options) : defaultsWrite; +} +exports.binaryWriteOptions = binaryWriteOptions; +class BinaryWriter { + constructor(textEncoder) { + /** + * Previous fork states. + */ + this.stack = []; + this.textEncoder = textEncoder !== null && textEncoder !== void 0 ? textEncoder : new TextEncoder(); + this.chunks = []; + this.buf = []; + } + /** + * Return all bytes written and reset this writer. + */ + finish() { + this.chunks.push(new Uint8Array(this.buf)); // flush the buffer + let len = 0; + for (let i = 0; i < this.chunks.length; i++) + len += this.chunks[i].length; + let bytes = new Uint8Array(len); + let offset = 0; + for (let i = 0; i < this.chunks.length; i++) { + bytes.set(this.chunks[i], offset); + offset += this.chunks[i].length; + } + this.chunks = []; + return bytes; + } + /** + * Start a new fork for length-delimited data like a message + * or a packed repeated field. + * + * Must be joined later with `join()`. + */ + fork() { + this.stack.push({ chunks: this.chunks, buf: this.buf }); + this.chunks = []; + this.buf = []; + return this; + } + /** + * Join the last fork. Write its length and bytes, then + * return to the previous state. + */ + join() { + // get chunk of fork + let chunk = this.finish(); + // restore previous state + let prev = this.stack.pop(); + if (!prev) + throw new Error('invalid state, fork stack empty'); + this.chunks = prev.chunks; + this.buf = prev.buf; + // write length of chunk as varint + this.uint32(chunk.byteLength); + return this.raw(chunk); + } + /** + * Writes a tag (field number and wire type). + * + * Equivalent to `uint32( (fieldNo << 3 | type) >>> 0 )`. + * + * Generated code should compute the tag ahead of time and call `uint32()`. + */ + tag(fieldNo, type) { + return this.uint32((fieldNo << 3 | type) >>> 0); + } + /** + * Write a chunk of raw bytes. + */ + raw(chunk) { + if (this.buf.length) { + this.chunks.push(new Uint8Array(this.buf)); + this.buf = []; + } + this.chunks.push(chunk); + return this; + } + /** + * Write a `uint32` value, an unsigned 32 bit varint. + */ + uint32(value) { + assert_1.assertUInt32(value); + // write value as varint 32, inlined for speed + while (value > 0x7f) { + this.buf.push((value & 0x7f) | 0x80); + value = value >>> 7; + } + this.buf.push(value); + return this; + } + /** + * Write a `int32` value, a signed 32 bit varint. + */ + int32(value) { + assert_1.assertInt32(value); + goog_varint_1.varint32write(value, this.buf); + return this; + } + /** + * Write a `bool` value, a variant. + */ + bool(value) { + this.buf.push(value ? 1 : 0); + return this; + } + /** + * Write a `bytes` value, length-delimited arbitrary data. + */ + bytes(value) { + this.uint32(value.byteLength); // write length of chunk as varint + return this.raw(value); + } + /** + * Write a `string` value, length-delimited data converted to UTF-8 text. + */ + string(value) { + let chunk = this.textEncoder.encode(value); + this.uint32(chunk.byteLength); // write length of chunk as varint + return this.raw(chunk); + } + /** + * Write a `float` value, 32-bit floating point number. + */ + float(value) { + assert_1.assertFloat32(value); + let chunk = new Uint8Array(4); + new DataView(chunk.buffer).setFloat32(0, value, true); + return this.raw(chunk); + } + /** + * Write a `double` value, a 64-bit floating point number. + */ + double(value) { + let chunk = new Uint8Array(8); + new DataView(chunk.buffer).setFloat64(0, value, true); + return this.raw(chunk); + } + /** + * Write a `fixed32` value, an unsigned, fixed-length 32-bit integer. + */ + fixed32(value) { + assert_1.assertUInt32(value); + let chunk = new Uint8Array(4); + new DataView(chunk.buffer).setUint32(0, value, true); + return this.raw(chunk); + } + /** + * Write a `sfixed32` value, a signed, fixed-length 32-bit integer. + */ + sfixed32(value) { + assert_1.assertInt32(value); + let chunk = new Uint8Array(4); + new DataView(chunk.buffer).setInt32(0, value, true); + return this.raw(chunk); + } + /** + * Write a `sint32` value, a signed, zigzag-encoded 32-bit varint. + */ + sint32(value) { + assert_1.assertInt32(value); + // zigzag encode + value = ((value << 1) ^ (value >> 31)) >>> 0; + goog_varint_1.varint32write(value, this.buf); + return this; + } + /** + * Write a `fixed64` value, a signed, fixed-length 64-bit integer. + */ + sfixed64(value) { + let chunk = new Uint8Array(8); + let view = new DataView(chunk.buffer); + let long = pb_long_1.PbLong.from(value); + view.setInt32(0, long.lo, true); + view.setInt32(4, long.hi, true); + return this.raw(chunk); + } + /** + * Write a `fixed64` value, an unsigned, fixed-length 64 bit integer. + */ + fixed64(value) { + let chunk = new Uint8Array(8); + let view = new DataView(chunk.buffer); + let long = pb_long_1.PbULong.from(value); + view.setInt32(0, long.lo, true); + view.setInt32(4, long.hi, true); + return this.raw(chunk); + } + /** + * Write a `int64` value, a signed 64-bit varint. + */ + int64(value) { + let long = pb_long_1.PbLong.from(value); + goog_varint_1.varint64write(long.lo, long.hi, this.buf); + return this; + } + /** + * Write a `sint64` value, a signed, zig-zag-encoded 64-bit varint. + */ + sint64(value) { + let long = pb_long_1.PbLong.from(value), + // zigzag encode + sign = long.hi >> 31, lo = (long.lo << 1) ^ sign, hi = ((long.hi << 1) | (long.lo >>> 31)) ^ sign; + goog_varint_1.varint64write(lo, hi, this.buf); + return this; + } + /** + * Write a `uint64` value, an unsigned 64-bit varint. + */ + uint64(value) { + let long = pb_long_1.PbULong.from(value); + goog_varint_1.varint64write(long.lo, long.hi, this.buf); + return this; + } +} +exports.BinaryWriter = BinaryWriter; + + +/***/ }), + +/***/ 257: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.listEnumNumbers = exports.listEnumNames = exports.listEnumValues = exports.isEnumObject = void 0; +/** + * Is this a lookup object generated by Typescript, for a Typescript enum + * generated by protobuf-ts? + * + * - No `const enum` (enum must not be inlined, we need reverse mapping). + * - No string enum (we need int32 for protobuf). + * - Must have a value for 0 (otherwise, we would need to support custom default values). + */ +function isEnumObject(arg) { + if (typeof arg != 'object' || arg === null) { + return false; + } + if (!arg.hasOwnProperty(0)) { + return false; + } + for (let k of Object.keys(arg)) { + let num = parseInt(k); + if (!Number.isNaN(num)) { + // is there a name for the number? + let nam = arg[num]; + if (nam === undefined) + return false; + // does the name resolve back to the number? + if (arg[nam] !== num) + return false; + } + else { + // is there a number for the name? + let num = arg[k]; + if (num === undefined) + return false; + // is it a string enum? + if (typeof num !== 'number') + return false; + // do we know the number? + if (arg[num] === undefined) + return false; + } + } + return true; +} +exports.isEnumObject = isEnumObject; +/** + * Lists all values of a Typescript enum, as an array of objects with a "name" + * property and a "number" property. + * + * Note that it is possible that a number appears more than once, because it is + * possible to have aliases in an enum. + * + * Throws if the enum does not adhere to the rules of enums generated by + * protobuf-ts. See `isEnumObject()`. + */ +function listEnumValues(enumObject) { + if (!isEnumObject(enumObject)) + throw new Error("not a typescript enum object"); + let values = []; + for (let [name, number] of Object.entries(enumObject)) + if (typeof number == "number") + values.push({ name, number }); + return values; +} +exports.listEnumValues = listEnumValues; +/** + * Lists the names of a Typescript enum. + * + * Throws if the enum does not adhere to the rules of enums generated by + * protobuf-ts. See `isEnumObject()`. + */ +function listEnumNames(enumObject) { + return listEnumValues(enumObject).map(val => val.name); +} +exports.listEnumNames = listEnumNames; +/** + * Lists the numbers of a Typescript enum. + * + * Throws if the enum does not adhere to the rules of enums generated by + * protobuf-ts. See `isEnumObject()`. + */ +function listEnumNumbers(enumObject) { + return listEnumValues(enumObject) + .map(val => val.number) + .filter((num, index, arr) => arr.indexOf(num) == index); +} +exports.listEnumNumbers = listEnumNumbers; + + +/***/ }), + +/***/ 3223: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +// Copyright 2008 Google Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Code generated by the Protocol Buffer compiler is owned by the owner +// of the input file used when generating it. This code is not +// standalone and requires a support library to be linked with it. This +// support library is itself covered by the above license. +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.varint32read = exports.varint32write = exports.int64toString = exports.int64fromString = exports.varint64write = exports.varint64read = void 0; +/** + * Read a 64 bit varint as two JS numbers. + * + * Returns tuple: + * [0]: low bits + * [0]: high bits + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/buffer_decoder.js#L175 + */ +function varint64read() { + let lowBits = 0; + let highBits = 0; + for (let shift = 0; shift < 28; shift += 7) { + let b = this.buf[this.pos++]; + lowBits |= (b & 0x7F) << shift; + if ((b & 0x80) == 0) { + this.assertBounds(); + return [lowBits, highBits]; + } + } + let middleByte = this.buf[this.pos++]; + // last four bits of the first 32 bit number + lowBits |= (middleByte & 0x0F) << 28; + // 3 upper bits are part of the next 32 bit number + highBits = (middleByte & 0x70) >> 4; + if ((middleByte & 0x80) == 0) { + this.assertBounds(); + return [lowBits, highBits]; + } + for (let shift = 3; shift <= 31; shift += 7) { + let b = this.buf[this.pos++]; + highBits |= (b & 0x7F) << shift; + if ((b & 0x80) == 0) { + this.assertBounds(); + return [lowBits, highBits]; + } + } + throw new Error('invalid varint'); +} +exports.varint64read = varint64read; +/** + * Write a 64 bit varint, given as two JS numbers, to the given bytes array. + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/writer.js#L344 + */ +function varint64write(lo, hi, bytes) { + for (let i = 0; i < 28; i = i + 7) { + const shift = lo >>> i; + const hasNext = !((shift >>> 7) == 0 && hi == 0); + const byte = (hasNext ? shift | 0x80 : shift) & 0xFF; + bytes.push(byte); + if (!hasNext) { + return; + } + } + const splitBits = ((lo >>> 28) & 0x0F) | ((hi & 0x07) << 4); + const hasMoreBits = !((hi >> 3) == 0); + bytes.push((hasMoreBits ? splitBits | 0x80 : splitBits) & 0xFF); + if (!hasMoreBits) { + return; + } + for (let i = 3; i < 31; i = i + 7) { + const shift = hi >>> i; + const hasNext = !((shift >>> 7) == 0); + const byte = (hasNext ? shift | 0x80 : shift) & 0xFF; + bytes.push(byte); + if (!hasNext) { + return; + } + } + bytes.push((hi >>> 31) & 0x01); +} +exports.varint64write = varint64write; +// constants for binary math +const TWO_PWR_32_DBL = (1 << 16) * (1 << 16); +/** + * Parse decimal string of 64 bit integer value as two JS numbers. + * + * Returns tuple: + * [0]: minus sign? + * [1]: low bits + * [2]: high bits + * + * Copyright 2008 Google Inc. + */ +function int64fromString(dec) { + // Check for minus sign. + let minus = dec[0] == '-'; + if (minus) + dec = dec.slice(1); + // Work 6 decimal digits at a time, acting like we're converting base 1e6 + // digits to binary. This is safe to do with floating point math because + // Number.isSafeInteger(ALL_32_BITS * 1e6) == true. + const base = 1e6; + let lowBits = 0; + let highBits = 0; + function add1e6digit(begin, end) { + // Note: Number('') is 0. + const digit1e6 = Number(dec.slice(begin, end)); + highBits *= base; + lowBits = lowBits * base + digit1e6; + // Carry bits from lowBits to highBits + if (lowBits >= TWO_PWR_32_DBL) { + highBits = highBits + ((lowBits / TWO_PWR_32_DBL) | 0); + lowBits = lowBits % TWO_PWR_32_DBL; + } + } + add1e6digit(-24, -18); + add1e6digit(-18, -12); + add1e6digit(-12, -6); + add1e6digit(-6); + return [minus, lowBits, highBits]; +} +exports.int64fromString = int64fromString; +/** + * Format 64 bit integer value (as two JS numbers) to decimal string. + * + * Copyright 2008 Google Inc. + */ +function int64toString(bitsLow, bitsHigh) { + // Skip the expensive conversion if the number is small enough to use the + // built-in conversions. + if ((bitsHigh >>> 0) <= 0x1FFFFF) { + return '' + (TWO_PWR_32_DBL * bitsHigh + (bitsLow >>> 0)); + } + // What this code is doing is essentially converting the input number from + // base-2 to base-1e7, which allows us to represent the 64-bit range with + // only 3 (very large) digits. Those digits are then trivial to convert to + // a base-10 string. + // The magic numbers used here are - + // 2^24 = 16777216 = (1,6777216) in base-1e7. + // 2^48 = 281474976710656 = (2,8147497,6710656) in base-1e7. + // Split 32:32 representation into 16:24:24 representation so our + // intermediate digits don't overflow. + let low = bitsLow & 0xFFFFFF; + let mid = (((bitsLow >>> 24) | (bitsHigh << 8)) >>> 0) & 0xFFFFFF; + let high = (bitsHigh >> 16) & 0xFFFF; + // Assemble our three base-1e7 digits, ignoring carries. The maximum + // value in a digit at this step is representable as a 48-bit integer, which + // can be stored in a 64-bit floating point number. + let digitA = low + (mid * 6777216) + (high * 6710656); + let digitB = mid + (high * 8147497); + let digitC = (high * 2); + // Apply carries from A to B and from B to C. + let base = 10000000; + if (digitA >= base) { + digitB += Math.floor(digitA / base); + digitA %= base; + } + if (digitB >= base) { + digitC += Math.floor(digitB / base); + digitB %= base; + } + // Convert base-1e7 digits to base-10, with optional leading zeroes. + function decimalFrom1e7(digit1e7, needLeadingZeros) { + let partial = digit1e7 ? String(digit1e7) : ''; + if (needLeadingZeros) { + return '0000000'.slice(partial.length) + partial; + } + return partial; + } + return decimalFrom1e7(digitC, /*needLeadingZeros=*/ 0) + + decimalFrom1e7(digitB, /*needLeadingZeros=*/ digitC) + + // If the final 1e7 digit didn't need leading zeros, we would have + // returned via the trivial code path at the top. + decimalFrom1e7(digitA, /*needLeadingZeros=*/ 1); +} +exports.int64toString = int64toString; +/** + * Write a 32 bit varint, signed or unsigned. Same as `varint64write(0, value, bytes)` + * + * Copyright 2008 Google Inc. All rights reserved. + * + * See https://github.com/protocolbuffers/protobuf/blob/1b18833f4f2a2f681f4e4a25cdf3b0a43115ec26/js/binary/encoder.js#L144 + */ +function varint32write(value, bytes) { + if (value >= 0) { + // write value as varint 32 + while (value > 0x7f) { + bytes.push((value & 0x7f) | 0x80); + value = value >>> 7; + } + bytes.push(value); + } + else { + for (let i = 0; i < 9; i++) { + bytes.push(value & 127 | 128); + value = value >> 7; + } + bytes.push(1); + } +} +exports.varint32write = varint32write; +/** + * Read an unsigned 32 bit varint. + * + * See https://github.com/protocolbuffers/protobuf/blob/8a71927d74a4ce34efe2d8769fda198f52d20d12/js/experimental/runtime/kernel/buffer_decoder.js#L220 + */ +function varint32read() { + let b = this.buf[this.pos++]; + let result = b & 0x7F; + if ((b & 0x80) == 0) { + this.assertBounds(); + return result; + } + b = this.buf[this.pos++]; + result |= (b & 0x7F) << 7; + if ((b & 0x80) == 0) { + this.assertBounds(); + return result; + } + b = this.buf[this.pos++]; + result |= (b & 0x7F) << 14; + if ((b & 0x80) == 0) { + this.assertBounds(); + return result; + } + b = this.buf[this.pos++]; + result |= (b & 0x7F) << 21; + if ((b & 0x80) == 0) { + this.assertBounds(); + return result; + } + // Extract only last 4 bits + b = this.buf[this.pos++]; + result |= (b & 0x0F) << 28; + for (let readBytes = 5; ((b & 0x80) !== 0) && readBytes < 10; readBytes++) + b = this.buf[this.pos++]; + if ((b & 0x80) != 0) + throw new Error('invalid varint'); + this.assertBounds(); + // Result can have 32 bits, convert it to unsigned + return result >>> 0; +} +exports.varint32read = varint32read; + + +/***/ }), + +/***/ 8886: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +// Public API of the protobuf-ts runtime. +// Note: we do not use `export * from ...` to help tree shakers, +// webpack verbose output hints that this should be useful +Object.defineProperty(exports, "__esModule", ({ value: true })); +// Convenience JSON typings and corresponding type guards +var json_typings_1 = __nccwpck_require__(9999); +Object.defineProperty(exports, "typeofJsonValue", ({ enumerable: true, get: function () { return json_typings_1.typeofJsonValue; } })); +Object.defineProperty(exports, "isJsonObject", ({ enumerable: true, get: function () { return json_typings_1.isJsonObject; } })); +// Base 64 encoding +var base64_1 = __nccwpck_require__(6335); +Object.defineProperty(exports, "base64decode", ({ enumerable: true, get: function () { return base64_1.base64decode; } })); +Object.defineProperty(exports, "base64encode", ({ enumerable: true, get: function () { return base64_1.base64encode; } })); +// UTF8 encoding +var protobufjs_utf8_1 = __nccwpck_require__(8950); +Object.defineProperty(exports, "utf8read", ({ enumerable: true, get: function () { return protobufjs_utf8_1.utf8read; } })); +// Binary format contracts, options for reading and writing, for example +var binary_format_contract_1 = __nccwpck_require__(4816); +Object.defineProperty(exports, "WireType", ({ enumerable: true, get: function () { return binary_format_contract_1.WireType; } })); +Object.defineProperty(exports, "mergeBinaryOptions", ({ enumerable: true, get: function () { return binary_format_contract_1.mergeBinaryOptions; } })); +Object.defineProperty(exports, "UnknownFieldHandler", ({ enumerable: true, get: function () { return binary_format_contract_1.UnknownFieldHandler; } })); +// Standard IBinaryReader implementation +var binary_reader_1 = __nccwpck_require__(2889); +Object.defineProperty(exports, "BinaryReader", ({ enumerable: true, get: function () { return binary_reader_1.BinaryReader; } })); +Object.defineProperty(exports, "binaryReadOptions", ({ enumerable: true, get: function () { return binary_reader_1.binaryReadOptions; } })); +// Standard IBinaryWriter implementation +var binary_writer_1 = __nccwpck_require__(3957); +Object.defineProperty(exports, "BinaryWriter", ({ enumerable: true, get: function () { return binary_writer_1.BinaryWriter; } })); +Object.defineProperty(exports, "binaryWriteOptions", ({ enumerable: true, get: function () { return binary_writer_1.binaryWriteOptions; } })); +// Int64 and UInt64 implementations required for the binary format +var pb_long_1 = __nccwpck_require__(1753); +Object.defineProperty(exports, "PbLong", ({ enumerable: true, get: function () { return pb_long_1.PbLong; } })); +Object.defineProperty(exports, "PbULong", ({ enumerable: true, get: function () { return pb_long_1.PbULong; } })); +// JSON format contracts, options for reading and writing, for example +var json_format_contract_1 = __nccwpck_require__(9367); +Object.defineProperty(exports, "jsonReadOptions", ({ enumerable: true, get: function () { return json_format_contract_1.jsonReadOptions; } })); +Object.defineProperty(exports, "jsonWriteOptions", ({ enumerable: true, get: function () { return json_format_contract_1.jsonWriteOptions; } })); +Object.defineProperty(exports, "mergeJsonOptions", ({ enumerable: true, get: function () { return json_format_contract_1.mergeJsonOptions; } })); +// Message type contract +var message_type_contract_1 = __nccwpck_require__(3785); +Object.defineProperty(exports, "MESSAGE_TYPE", ({ enumerable: true, get: function () { return message_type_contract_1.MESSAGE_TYPE; } })); +// Message type implementation via reflection +var message_type_1 = __nccwpck_require__(5106); +Object.defineProperty(exports, "MessageType", ({ enumerable: true, get: function () { return message_type_1.MessageType; } })); +// Reflection info, generated by the plugin, exposed to the user, used by reflection ops +var reflection_info_1 = __nccwpck_require__(7910); +Object.defineProperty(exports, "ScalarType", ({ enumerable: true, get: function () { return reflection_info_1.ScalarType; } })); +Object.defineProperty(exports, "LongType", ({ enumerable: true, get: function () { return reflection_info_1.LongType; } })); +Object.defineProperty(exports, "RepeatType", ({ enumerable: true, get: function () { return reflection_info_1.RepeatType; } })); +Object.defineProperty(exports, "normalizeFieldInfo", ({ enumerable: true, get: function () { return reflection_info_1.normalizeFieldInfo; } })); +Object.defineProperty(exports, "readFieldOptions", ({ enumerable: true, get: function () { return reflection_info_1.readFieldOptions; } })); +Object.defineProperty(exports, "readFieldOption", ({ enumerable: true, get: function () { return reflection_info_1.readFieldOption; } })); +Object.defineProperty(exports, "readMessageOption", ({ enumerable: true, get: function () { return reflection_info_1.readMessageOption; } })); +// Message operations via reflection +var reflection_type_check_1 = __nccwpck_require__(5167); +Object.defineProperty(exports, "ReflectionTypeCheck", ({ enumerable: true, get: function () { return reflection_type_check_1.ReflectionTypeCheck; } })); +var reflection_create_1 = __nccwpck_require__(5726); +Object.defineProperty(exports, "reflectionCreate", ({ enumerable: true, get: function () { return reflection_create_1.reflectionCreate; } })); +var reflection_scalar_default_1 = __nccwpck_require__(9526); +Object.defineProperty(exports, "reflectionScalarDefault", ({ enumerable: true, get: function () { return reflection_scalar_default_1.reflectionScalarDefault; } })); +var reflection_merge_partial_1 = __nccwpck_require__(8044); +Object.defineProperty(exports, "reflectionMergePartial", ({ enumerable: true, get: function () { return reflection_merge_partial_1.reflectionMergePartial; } })); +var reflection_equals_1 = __nccwpck_require__(4827); +Object.defineProperty(exports, "reflectionEquals", ({ enumerable: true, get: function () { return reflection_equals_1.reflectionEquals; } })); +var reflection_binary_reader_1 = __nccwpck_require__(9611); +Object.defineProperty(exports, "ReflectionBinaryReader", ({ enumerable: true, get: function () { return reflection_binary_reader_1.ReflectionBinaryReader; } })); +var reflection_binary_writer_1 = __nccwpck_require__(6907); +Object.defineProperty(exports, "ReflectionBinaryWriter", ({ enumerable: true, get: function () { return reflection_binary_writer_1.ReflectionBinaryWriter; } })); +var reflection_json_reader_1 = __nccwpck_require__(6790); +Object.defineProperty(exports, "ReflectionJsonReader", ({ enumerable: true, get: function () { return reflection_json_reader_1.ReflectionJsonReader; } })); +var reflection_json_writer_1 = __nccwpck_require__(1094); +Object.defineProperty(exports, "ReflectionJsonWriter", ({ enumerable: true, get: function () { return reflection_json_writer_1.ReflectionJsonWriter; } })); +var reflection_contains_message_type_1 = __nccwpck_require__(9946); +Object.defineProperty(exports, "containsMessageType", ({ enumerable: true, get: function () { return reflection_contains_message_type_1.containsMessageType; } })); +// Oneof helpers +var oneof_1 = __nccwpck_require__(8063); +Object.defineProperty(exports, "isOneofGroup", ({ enumerable: true, get: function () { return oneof_1.isOneofGroup; } })); +Object.defineProperty(exports, "setOneofValue", ({ enumerable: true, get: function () { return oneof_1.setOneofValue; } })); +Object.defineProperty(exports, "getOneofValue", ({ enumerable: true, get: function () { return oneof_1.getOneofValue; } })); +Object.defineProperty(exports, "clearOneofValue", ({ enumerable: true, get: function () { return oneof_1.clearOneofValue; } })); +Object.defineProperty(exports, "getSelectedOneofValue", ({ enumerable: true, get: function () { return oneof_1.getSelectedOneofValue; } })); +// Enum object type guard and reflection util, may be interesting to the user. +var enum_object_1 = __nccwpck_require__(257); +Object.defineProperty(exports, "listEnumValues", ({ enumerable: true, get: function () { return enum_object_1.listEnumValues; } })); +Object.defineProperty(exports, "listEnumNames", ({ enumerable: true, get: function () { return enum_object_1.listEnumNames; } })); +Object.defineProperty(exports, "listEnumNumbers", ({ enumerable: true, get: function () { return enum_object_1.listEnumNumbers; } })); +Object.defineProperty(exports, "isEnumObject", ({ enumerable: true, get: function () { return enum_object_1.isEnumObject; } })); +// lowerCamelCase() is exported for plugin, rpc-runtime and other rpc packages +var lower_camel_case_1 = __nccwpck_require__(4073); +Object.defineProperty(exports, "lowerCamelCase", ({ enumerable: true, get: function () { return lower_camel_case_1.lowerCamelCase; } })); +// assertion functions are exported for plugin, may also be useful to user +var assert_1 = __nccwpck_require__(8602); +Object.defineProperty(exports, "assert", ({ enumerable: true, get: function () { return assert_1.assert; } })); +Object.defineProperty(exports, "assertNever", ({ enumerable: true, get: function () { return assert_1.assertNever; } })); +Object.defineProperty(exports, "assertInt32", ({ enumerable: true, get: function () { return assert_1.assertInt32; } })); +Object.defineProperty(exports, "assertUInt32", ({ enumerable: true, get: function () { return assert_1.assertUInt32; } })); +Object.defineProperty(exports, "assertFloat32", ({ enumerable: true, get: function () { return assert_1.assertFloat32; } })); + + +/***/ }), + +/***/ 9367: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.mergeJsonOptions = exports.jsonWriteOptions = exports.jsonReadOptions = void 0; +const defaultsWrite = { + emitDefaultValues: false, + enumAsInteger: false, + useProtoFieldName: false, + prettySpaces: 0, +}, defaultsRead = { + ignoreUnknownFields: false, +}; +/** + * Make options for reading JSON data from partial options. + */ +function jsonReadOptions(options) { + return options ? Object.assign(Object.assign({}, defaultsRead), options) : defaultsRead; +} +exports.jsonReadOptions = jsonReadOptions; +/** + * Make options for writing JSON data from partial options. + */ +function jsonWriteOptions(options) { + return options ? Object.assign(Object.assign({}, defaultsWrite), options) : defaultsWrite; +} +exports.jsonWriteOptions = jsonWriteOptions; +/** + * Merges JSON write or read options. Later values override earlier values. Type registries are merged. + */ +function mergeJsonOptions(a, b) { + var _a, _b; + let c = Object.assign(Object.assign({}, a), b); + c.typeRegistry = [...((_a = a === null || a === void 0 ? void 0 : a.typeRegistry) !== null && _a !== void 0 ? _a : []), ...((_b = b === null || b === void 0 ? void 0 : b.typeRegistry) !== null && _b !== void 0 ? _b : [])]; + return c; +} +exports.mergeJsonOptions = mergeJsonOptions; + + +/***/ }), + +/***/ 9999: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.isJsonObject = exports.typeofJsonValue = void 0; +/** + * Get the type of a JSON value. + * Distinguishes between array, null and object. + */ +function typeofJsonValue(value) { + let t = typeof value; + if (t == "object") { + if (Array.isArray(value)) + return "array"; + if (value === null) + return "null"; + } + return t; +} +exports.typeofJsonValue = typeofJsonValue; +/** + * Is this a JSON object (instead of an array or null)? + */ +function isJsonObject(value) { + return value !== null && typeof value == "object" && !Array.isArray(value); +} +exports.isJsonObject = isJsonObject; + + +/***/ }), + +/***/ 4073: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.lowerCamelCase = void 0; +/** + * Converts snake_case to lowerCamelCase. + * + * Should behave like protoc: + * https://github.com/protocolbuffers/protobuf/blob/e8ae137c96444ea313485ed1118c5e43b2099cf1/src/google/protobuf/compiler/java/java_helpers.cc#L118 + */ +function lowerCamelCase(snakeCase) { + let capNext = false; + const sb = []; + for (let i = 0; i < snakeCase.length; i++) { + let next = snakeCase.charAt(i); + if (next == '_') { + capNext = true; + } + else if (/\d/.test(next)) { + sb.push(next); + capNext = true; + } + else if (capNext) { + sb.push(next.toUpperCase()); + capNext = false; + } + else if (i == 0) { + sb.push(next.toLowerCase()); + } + else { + sb.push(next); + } + } + return sb.join(''); +} +exports.lowerCamelCase = lowerCamelCase; + + +/***/ }), + +/***/ 3785: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.MESSAGE_TYPE = void 0; +/** + * The symbol used as a key on message objects to store the message type. + * + * Note that this is an experimental feature - it is here to stay, but + * implementation details may change without notice. + */ +exports.MESSAGE_TYPE = Symbol.for("protobuf-ts/message-type"); + + +/***/ }), + +/***/ 5106: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.MessageType = void 0; +const message_type_contract_1 = __nccwpck_require__(3785); +const reflection_info_1 = __nccwpck_require__(7910); +const reflection_type_check_1 = __nccwpck_require__(5167); +const reflection_json_reader_1 = __nccwpck_require__(6790); +const reflection_json_writer_1 = __nccwpck_require__(1094); +const reflection_binary_reader_1 = __nccwpck_require__(9611); +const reflection_binary_writer_1 = __nccwpck_require__(6907); +const reflection_create_1 = __nccwpck_require__(5726); +const reflection_merge_partial_1 = __nccwpck_require__(8044); +const json_typings_1 = __nccwpck_require__(9999); +const json_format_contract_1 = __nccwpck_require__(9367); +const reflection_equals_1 = __nccwpck_require__(4827); +const binary_writer_1 = __nccwpck_require__(3957); +const binary_reader_1 = __nccwpck_require__(2889); +const baseDescriptors = Object.getOwnPropertyDescriptors(Object.getPrototypeOf({})); +const messageTypeDescriptor = baseDescriptors[message_type_contract_1.MESSAGE_TYPE] = {}; +/** + * This standard message type provides reflection-based + * operations to work with a message. + */ +class MessageType { + constructor(name, fields, options) { + this.defaultCheckDepth = 16; + this.typeName = name; + this.fields = fields.map(reflection_info_1.normalizeFieldInfo); + this.options = options !== null && options !== void 0 ? options : {}; + messageTypeDescriptor.value = this; + this.messagePrototype = Object.create(null, baseDescriptors); + this.refTypeCheck = new reflection_type_check_1.ReflectionTypeCheck(this); + this.refJsonReader = new reflection_json_reader_1.ReflectionJsonReader(this); + this.refJsonWriter = new reflection_json_writer_1.ReflectionJsonWriter(this); + this.refBinReader = new reflection_binary_reader_1.ReflectionBinaryReader(this); + this.refBinWriter = new reflection_binary_writer_1.ReflectionBinaryWriter(this); + } + create(value) { + let message = reflection_create_1.reflectionCreate(this); + if (value !== undefined) { + reflection_merge_partial_1.reflectionMergePartial(this, message, value); + } + return message; + } + /** + * Clone the message. + * + * Unknown fields are discarded. + */ + clone(message) { + let copy = this.create(); + reflection_merge_partial_1.reflectionMergePartial(this, copy, message); + return copy; + } + /** + * Determines whether two message of the same type have the same field values. + * Checks for deep equality, traversing repeated fields, oneof groups, maps + * and messages recursively. + * Will also return true if both messages are `undefined`. + */ + equals(a, b) { + return reflection_equals_1.reflectionEquals(this, a, b); + } + /** + * Is the given value assignable to our message type + * and contains no [excess properties](https://www.typescriptlang.org/docs/handbook/interfaces.html#excess-property-checks)? + */ + is(arg, depth = this.defaultCheckDepth) { + return this.refTypeCheck.is(arg, depth, false); + } + /** + * Is the given value assignable to our message type, + * regardless of [excess properties](https://www.typescriptlang.org/docs/handbook/interfaces.html#excess-property-checks)? + */ + isAssignable(arg, depth = this.defaultCheckDepth) { + return this.refTypeCheck.is(arg, depth, true); + } + /** + * Copy partial data into the target message. + */ + mergePartial(target, source) { + reflection_merge_partial_1.reflectionMergePartial(this, target, source); + } + /** + * Create a new message from binary format. + */ + fromBinary(data, options) { + let opt = binary_reader_1.binaryReadOptions(options); + return this.internalBinaryRead(opt.readerFactory(data), data.byteLength, opt); + } + /** + * Read a new message from a JSON value. + */ + fromJson(json, options) { + return this.internalJsonRead(json, json_format_contract_1.jsonReadOptions(options)); + } + /** + * Read a new message from a JSON string. + * This is equivalent to `T.fromJson(JSON.parse(json))`. + */ + fromJsonString(json, options) { + let value = JSON.parse(json); + return this.fromJson(value, options); + } + /** + * Write the message to canonical JSON value. + */ + toJson(message, options) { + return this.internalJsonWrite(message, json_format_contract_1.jsonWriteOptions(options)); + } + /** + * Convert the message to canonical JSON string. + * This is equivalent to `JSON.stringify(T.toJson(t))` + */ + toJsonString(message, options) { + var _a; + let value = this.toJson(message, options); + return JSON.stringify(value, null, (_a = options === null || options === void 0 ? void 0 : options.prettySpaces) !== null && _a !== void 0 ? _a : 0); + } + /** + * Write the message to binary format. + */ + toBinary(message, options) { + let opt = binary_writer_1.binaryWriteOptions(options); + return this.internalBinaryWrite(message, opt.writerFactory(), opt).finish(); + } + /** + * This is an internal method. If you just want to read a message from + * JSON, use `fromJson()` or `fromJsonString()`. + * + * Reads JSON value and merges the fields into the target + * according to protobuf rules. If the target is omitted, + * a new instance is created first. + */ + internalJsonRead(json, options, target) { + if (json !== null && typeof json == "object" && !Array.isArray(json)) { + let message = target !== null && target !== void 0 ? target : this.create(); + this.refJsonReader.read(json, message, options); + return message; + } + throw new Error(`Unable to parse message ${this.typeName} from JSON ${json_typings_1.typeofJsonValue(json)}.`); + } + /** + * This is an internal method. If you just want to write a message + * to JSON, use `toJson()` or `toJsonString(). + * + * Writes JSON value and returns it. + */ + internalJsonWrite(message, options) { + return this.refJsonWriter.write(message, options); + } + /** + * This is an internal method. If you just want to write a message + * in binary format, use `toBinary()`. + * + * Serializes the message in binary format and appends it to the given + * writer. Returns passed writer. + */ + internalBinaryWrite(message, writer, options) { + this.refBinWriter.write(message, writer, options); + return writer; + } + /** + * This is an internal method. If you just want to read a message from + * binary data, use `fromBinary()`. + * + * Reads data from binary format and merges the fields into + * the target according to protobuf rules. If the target is + * omitted, a new instance is created first. + */ + internalBinaryRead(reader, length, options, target) { + let message = target !== null && target !== void 0 ? target : this.create(); + this.refBinReader.read(reader, message, options, length); + return message; + } +} +exports.MessageType = MessageType; + + +/***/ }), + +/***/ 8063: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.getSelectedOneofValue = exports.clearOneofValue = exports.setUnknownOneofValue = exports.setOneofValue = exports.getOneofValue = exports.isOneofGroup = void 0; +/** + * Is the given value a valid oneof group? + * + * We represent protobuf `oneof` as algebraic data types (ADT) in generated + * code. But when working with messages of unknown type, the ADT does not + * help us. + * + * This type guard checks if the given object adheres to the ADT rules, which + * are as follows: + * + * 1) Must be an object. + * + * 2) Must have a "oneofKind" discriminator property. + * + * 3) If "oneofKind" is `undefined`, no member field is selected. The object + * must not have any other properties. + * + * 4) If "oneofKind" is a `string`, the member field with this name is + * selected. + * + * 5) If a member field is selected, the object must have a second property + * with this name. The property must not be `undefined`. + * + * 6) No extra properties are allowed. The object has either one property + * (no selection) or two properties (selection). + * + */ +function isOneofGroup(any) { + if (typeof any != 'object' || any === null || !any.hasOwnProperty('oneofKind')) { + return false; + } + switch (typeof any.oneofKind) { + case "string": + if (any[any.oneofKind] === undefined) + return false; + return Object.keys(any).length == 2; + case "undefined": + return Object.keys(any).length == 1; + default: + return false; + } +} +exports.isOneofGroup = isOneofGroup; +/** + * Returns the value of the given field in a oneof group. + */ +function getOneofValue(oneof, kind) { + return oneof[kind]; +} +exports.getOneofValue = getOneofValue; +function setOneofValue(oneof, kind, value) { + if (oneof.oneofKind !== undefined) { + delete oneof[oneof.oneofKind]; + } + oneof.oneofKind = kind; + if (value !== undefined) { + oneof[kind] = value; + } +} +exports.setOneofValue = setOneofValue; +function setUnknownOneofValue(oneof, kind, value) { + if (oneof.oneofKind !== undefined) { + delete oneof[oneof.oneofKind]; + } + oneof.oneofKind = kind; + if (value !== undefined && kind !== undefined) { + oneof[kind] = value; + } +} +exports.setUnknownOneofValue = setUnknownOneofValue; +/** + * Removes the selected field in a oneof group. + * + * Note that the recommended way to modify a oneof group is to set + * a new object: + * + * ```ts + * message.result = { oneofKind: undefined }; + * ``` + */ +function clearOneofValue(oneof) { + if (oneof.oneofKind !== undefined) { + delete oneof[oneof.oneofKind]; + } + oneof.oneofKind = undefined; +} +exports.clearOneofValue = clearOneofValue; +/** + * Returns the selected value of the given oneof group. + * + * Not that the recommended way to access a oneof group is to check + * the "oneofKind" property and let TypeScript narrow down the union + * type for you: + * + * ```ts + * if (message.result.oneofKind === "error") { + * message.result.error; // string + * } + * ``` + * + * In the rare case you just need the value, and do not care about + * which protobuf field is selected, you can use this function + * for convenience. + */ +function getSelectedOneofValue(oneof) { + if (oneof.oneofKind === undefined) { + return undefined; + } + return oneof[oneof.oneofKind]; +} +exports.getSelectedOneofValue = getSelectedOneofValue; + + +/***/ }), + +/***/ 1753: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.PbLong = exports.PbULong = exports.detectBi = void 0; +const goog_varint_1 = __nccwpck_require__(3223); +let BI; +function detectBi() { + const dv = new DataView(new ArrayBuffer(8)); + const ok = globalThis.BigInt !== undefined + && typeof dv.getBigInt64 === "function" + && typeof dv.getBigUint64 === "function" + && typeof dv.setBigInt64 === "function" + && typeof dv.setBigUint64 === "function"; + BI = ok ? { + MIN: BigInt("-9223372036854775808"), + MAX: BigInt("9223372036854775807"), + UMIN: BigInt("0"), + UMAX: BigInt("18446744073709551615"), + C: BigInt, + V: dv, + } : undefined; +} +exports.detectBi = detectBi; +detectBi(); +function assertBi(bi) { + if (!bi) + throw new Error("BigInt unavailable, see https://github.com/timostamm/protobuf-ts/blob/v1.0.8/MANUAL.md#bigint-support"); +} +// used to validate from(string) input (when bigint is unavailable) +const RE_DECIMAL_STR = /^-?[0-9]+$/; +// constants for binary math +const TWO_PWR_32_DBL = 0x100000000; +const HALF_2_PWR_32 = 0x080000000; +// base class for PbLong and PbULong provides shared code +class SharedPbLong { + /** + * Create a new instance with the given bits. + */ + constructor(lo, hi) { + this.lo = lo | 0; + this.hi = hi | 0; + } + /** + * Is this instance equal to 0? + */ + isZero() { + return this.lo == 0 && this.hi == 0; + } + /** + * Convert to a native number. + */ + toNumber() { + let result = this.hi * TWO_PWR_32_DBL + (this.lo >>> 0); + if (!Number.isSafeInteger(result)) + throw new Error("cannot convert to safe number"); + return result; + } +} +/** + * 64-bit unsigned integer as two 32-bit values. + * Converts between `string`, `number` and `bigint` representations. + */ +class PbULong extends SharedPbLong { + /** + * Create instance from a `string`, `number` or `bigint`. + */ + static from(value) { + if (BI) + // noinspection FallThroughInSwitchStatementJS + switch (typeof value) { + case "string": + if (value == "0") + return this.ZERO; + if (value == "") + throw new Error('string is no integer'); + value = BI.C(value); + case "number": + if (value === 0) + return this.ZERO; + value = BI.C(value); + case "bigint": + if (!value) + return this.ZERO; + if (value < BI.UMIN) + throw new Error('signed value for ulong'); + if (value > BI.UMAX) + throw new Error('ulong too large'); + BI.V.setBigUint64(0, value, true); + return new PbULong(BI.V.getInt32(0, true), BI.V.getInt32(4, true)); + } + else + switch (typeof value) { + case "string": + if (value == "0") + return this.ZERO; + value = value.trim(); + if (!RE_DECIMAL_STR.test(value)) + throw new Error('string is no integer'); + let [minus, lo, hi] = goog_varint_1.int64fromString(value); + if (minus) + throw new Error('signed value for ulong'); + return new PbULong(lo, hi); + case "number": + if (value == 0) + return this.ZERO; + if (!Number.isSafeInteger(value)) + throw new Error('number is no integer'); + if (value < 0) + throw new Error('signed value for ulong'); + return new PbULong(value, value / TWO_PWR_32_DBL); + } + throw new Error('unknown value ' + typeof value); + } + /** + * Convert to decimal string. + */ + toString() { + return BI ? this.toBigInt().toString() : goog_varint_1.int64toString(this.lo, this.hi); + } + /** + * Convert to native bigint. + */ + toBigInt() { + assertBi(BI); + BI.V.setInt32(0, this.lo, true); + BI.V.setInt32(4, this.hi, true); + return BI.V.getBigUint64(0, true); + } +} +exports.PbULong = PbULong; +/** + * ulong 0 singleton. + */ +PbULong.ZERO = new PbULong(0, 0); +/** + * 64-bit signed integer as two 32-bit values. + * Converts between `string`, `number` and `bigint` representations. + */ +class PbLong extends SharedPbLong { + /** + * Create instance from a `string`, `number` or `bigint`. + */ + static from(value) { + if (BI) + // noinspection FallThroughInSwitchStatementJS + switch (typeof value) { + case "string": + if (value == "0") + return this.ZERO; + if (value == "") + throw new Error('string is no integer'); + value = BI.C(value); + case "number": + if (value === 0) + return this.ZERO; + value = BI.C(value); + case "bigint": + if (!value) + return this.ZERO; + if (value < BI.MIN) + throw new Error('signed long too small'); + if (value > BI.MAX) + throw new Error('signed long too large'); + BI.V.setBigInt64(0, value, true); + return new PbLong(BI.V.getInt32(0, true), BI.V.getInt32(4, true)); + } + else + switch (typeof value) { + case "string": + if (value == "0") + return this.ZERO; + value = value.trim(); + if (!RE_DECIMAL_STR.test(value)) + throw new Error('string is no integer'); + let [minus, lo, hi] = goog_varint_1.int64fromString(value); + if (minus) { + if (hi > HALF_2_PWR_32 || (hi == HALF_2_PWR_32 && lo != 0)) + throw new Error('signed long too small'); + } + else if (hi >= HALF_2_PWR_32) + throw new Error('signed long too large'); + let pbl = new PbLong(lo, hi); + return minus ? pbl.negate() : pbl; + case "number": + if (value == 0) + return this.ZERO; + if (!Number.isSafeInteger(value)) + throw new Error('number is no integer'); + return value > 0 + ? new PbLong(value, value / TWO_PWR_32_DBL) + : new PbLong(-value, -value / TWO_PWR_32_DBL).negate(); + } + throw new Error('unknown value ' + typeof value); + } + /** + * Do we have a minus sign? + */ + isNegative() { + return (this.hi & HALF_2_PWR_32) !== 0; + } + /** + * Negate two's complement. + * Invert all the bits and add one to the result. + */ + negate() { + let hi = ~this.hi, lo = this.lo; + if (lo) + lo = ~lo + 1; + else + hi += 1; + return new PbLong(lo, hi); + } + /** + * Convert to decimal string. + */ + toString() { + if (BI) + return this.toBigInt().toString(); + if (this.isNegative()) { + let n = this.negate(); + return '-' + goog_varint_1.int64toString(n.lo, n.hi); + } + return goog_varint_1.int64toString(this.lo, this.hi); + } + /** + * Convert to native bigint. + */ + toBigInt() { + assertBi(BI); + BI.V.setInt32(0, this.lo, true); + BI.V.setInt32(4, this.hi, true); + return BI.V.getBigInt64(0, true); + } +} +exports.PbLong = PbLong; +/** + * long 0 singleton. + */ +PbLong.ZERO = new PbLong(0, 0); + + +/***/ }), + +/***/ 8950: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +// Copyright (c) 2016, Daniel Wirtz All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// * Neither the name of its author, nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.utf8read = void 0; +const fromCharCodes = (chunk) => String.fromCharCode.apply(String, chunk); +/** + * @deprecated This function will no longer be exported with the next major + * release, since protobuf-ts has switch to TextDecoder API. If you need this + * function, please migrate to @protobufjs/utf8. For context, see + * https://github.com/timostamm/protobuf-ts/issues/184 + * + * Reads UTF8 bytes as a string. + * + * See [protobufjs / utf8](https://github.com/protobufjs/protobuf.js/blob/9893e35b854621cce64af4bf6be2cff4fb892796/lib/utf8/index.js#L40) + * + * Copyright (c) 2016, Daniel Wirtz + */ +function utf8read(bytes) { + if (bytes.length < 1) + return ""; + let pos = 0, // position in bytes + parts = [], chunk = [], i = 0, // char offset + t; // temporary + let len = bytes.length; + while (pos < len) { + t = bytes[pos++]; + if (t < 128) + chunk[i++] = t; + else if (t > 191 && t < 224) + chunk[i++] = (t & 31) << 6 | bytes[pos++] & 63; + else if (t > 239 && t < 365) { + t = ((t & 7) << 18 | (bytes[pos++] & 63) << 12 | (bytes[pos++] & 63) << 6 | bytes[pos++] & 63) - 0x10000; + chunk[i++] = 0xD800 + (t >> 10); + chunk[i++] = 0xDC00 + (t & 1023); + } + else + chunk[i++] = (t & 15) << 12 | (bytes[pos++] & 63) << 6 | bytes[pos++] & 63; + if (i > 8191) { + parts.push(fromCharCodes(chunk)); + i = 0; + } + } + if (parts.length) { + if (i) + parts.push(fromCharCodes(chunk.slice(0, i))); + return parts.join(""); + } + return fromCharCodes(chunk.slice(0, i)); +} +exports.utf8read = utf8read; + + +/***/ }), + +/***/ 9611: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ReflectionBinaryReader = void 0; +const binary_format_contract_1 = __nccwpck_require__(4816); +const reflection_info_1 = __nccwpck_require__(7910); +const reflection_long_convert_1 = __nccwpck_require__(3402); +const reflection_scalar_default_1 = __nccwpck_require__(9526); +/** + * Reads proto3 messages in binary format using reflection information. + * + * https://developers.google.com/protocol-buffers/docs/encoding + */ +class ReflectionBinaryReader { + constructor(info) { + this.info = info; + } + prepare() { + var _a; + if (!this.fieldNoToField) { + const fieldsInput = (_a = this.info.fields) !== null && _a !== void 0 ? _a : []; + this.fieldNoToField = new Map(fieldsInput.map(field => [field.no, field])); + } + } + /** + * Reads a message from binary format into the target message. + * + * Repeated fields are appended. Map entries are added, overwriting + * existing keys. + * + * If a message field is already present, it will be merged with the + * new data. + */ + read(reader, message, options, length) { + this.prepare(); + const end = length === undefined ? reader.len : reader.pos + length; + while (reader.pos < end) { + // read the tag and find the field + const [fieldNo, wireType] = reader.tag(), field = this.fieldNoToField.get(fieldNo); + if (!field) { + let u = options.readUnknownField; + if (u == "throw") + throw new Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.info.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? binary_format_contract_1.UnknownFieldHandler.onRead : u)(this.info.typeName, message, fieldNo, wireType, d); + continue; + } + // target object for the field we are reading + let target = message, repeated = field.repeat, localName = field.localName; + // if field is member of oneof ADT, use ADT as target + if (field.oneof) { + target = target[field.oneof]; + // if other oneof member selected, set new ADT + if (target.oneofKind !== localName) + target = message[field.oneof] = { + oneofKind: localName + }; + } + // we have handled oneof above, we just have read the value into `target[localName]` + switch (field.kind) { + case "scalar": + case "enum": + let T = field.kind == "enum" ? reflection_info_1.ScalarType.INT32 : field.T; + let L = field.kind == "scalar" ? field.L : undefined; + if (repeated) { + let arr = target[localName]; // safe to assume presence of array, oneof cannot contain repeated values + if (wireType == binary_format_contract_1.WireType.LengthDelimited && T != reflection_info_1.ScalarType.STRING && T != reflection_info_1.ScalarType.BYTES) { + let e = reader.uint32() + reader.pos; + while (reader.pos < e) + arr.push(this.scalar(reader, T, L)); + } + else + arr.push(this.scalar(reader, T, L)); + } + else + target[localName] = this.scalar(reader, T, L); + break; + case "message": + if (repeated) { + let arr = target[localName]; // safe to assume presence of array, oneof cannot contain repeated values + let msg = field.T().internalBinaryRead(reader, reader.uint32(), options); + arr.push(msg); + } + else + target[localName] = field.T().internalBinaryRead(reader, reader.uint32(), options, target[localName]); + break; + case "map": + let [mapKey, mapVal] = this.mapEntry(field, reader, options); + // safe to assume presence of map object, oneof cannot contain repeated values + target[localName][mapKey] = mapVal; + break; + } + } + } + /** + * Read a map field, expecting key field = 1, value field = 2 + */ + mapEntry(field, reader, options) { + let length = reader.uint32(); + let end = reader.pos + length; + let key = undefined; // javascript only allows number or string for object properties + let val = undefined; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case 1: + if (field.K == reflection_info_1.ScalarType.BOOL) + key = reader.bool().toString(); + else + // long types are read as string, number types are okay as number + key = this.scalar(reader, field.K, reflection_info_1.LongType.STRING); + break; + case 2: + switch (field.V.kind) { + case "scalar": + val = this.scalar(reader, field.V.T, field.V.L); + break; + case "enum": + val = reader.int32(); + break; + case "message": + val = field.V.T().internalBinaryRead(reader, reader.uint32(), options); + break; + } + break; + default: + throw new Error(`Unknown field ${fieldNo} (wire type ${wireType}) in map entry for ${this.info.typeName}#${field.name}`); + } + } + if (key === undefined) { + let keyRaw = reflection_scalar_default_1.reflectionScalarDefault(field.K); + key = field.K == reflection_info_1.ScalarType.BOOL ? keyRaw.toString() : keyRaw; + } + if (val === undefined) + switch (field.V.kind) { + case "scalar": + val = reflection_scalar_default_1.reflectionScalarDefault(field.V.T, field.V.L); + break; + case "enum": + val = 0; + break; + case "message": + val = field.V.T().create(); + break; + } + return [key, val]; + } + scalar(reader, type, longType) { + switch (type) { + case reflection_info_1.ScalarType.INT32: + return reader.int32(); + case reflection_info_1.ScalarType.STRING: + return reader.string(); + case reflection_info_1.ScalarType.BOOL: + return reader.bool(); + case reflection_info_1.ScalarType.DOUBLE: + return reader.double(); + case reflection_info_1.ScalarType.FLOAT: + return reader.float(); + case reflection_info_1.ScalarType.INT64: + return reflection_long_convert_1.reflectionLongConvert(reader.int64(), longType); + case reflection_info_1.ScalarType.UINT64: + return reflection_long_convert_1.reflectionLongConvert(reader.uint64(), longType); + case reflection_info_1.ScalarType.FIXED64: + return reflection_long_convert_1.reflectionLongConvert(reader.fixed64(), longType); + case reflection_info_1.ScalarType.FIXED32: + return reader.fixed32(); + case reflection_info_1.ScalarType.BYTES: + return reader.bytes(); + case reflection_info_1.ScalarType.UINT32: + return reader.uint32(); + case reflection_info_1.ScalarType.SFIXED32: + return reader.sfixed32(); + case reflection_info_1.ScalarType.SFIXED64: + return reflection_long_convert_1.reflectionLongConvert(reader.sfixed64(), longType); + case reflection_info_1.ScalarType.SINT32: + return reader.sint32(); + case reflection_info_1.ScalarType.SINT64: + return reflection_long_convert_1.reflectionLongConvert(reader.sint64(), longType); + } + } +} +exports.ReflectionBinaryReader = ReflectionBinaryReader; + + +/***/ }), + +/***/ 6907: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ReflectionBinaryWriter = void 0; +const binary_format_contract_1 = __nccwpck_require__(4816); +const reflection_info_1 = __nccwpck_require__(7910); +const assert_1 = __nccwpck_require__(8602); +const pb_long_1 = __nccwpck_require__(1753); +/** + * Writes proto3 messages in binary format using reflection information. + * + * https://developers.google.com/protocol-buffers/docs/encoding + */ +class ReflectionBinaryWriter { + constructor(info) { + this.info = info; + } + prepare() { + if (!this.fields) { + const fieldsInput = this.info.fields ? this.info.fields.concat() : []; + this.fields = fieldsInput.sort((a, b) => a.no - b.no); + } + } + /** + * Writes the message to binary format. + */ + write(message, writer, options) { + this.prepare(); + for (const field of this.fields) { + let value, // this will be our field value, whether it is member of a oneof or not + emitDefault, // whether we emit the default value (only true for oneof members) + repeated = field.repeat, localName = field.localName; + // handle oneof ADT + if (field.oneof) { + const group = message[field.oneof]; + if (group.oneofKind !== localName) + continue; // if field is not selected, skip + value = group[localName]; + emitDefault = true; + } + else { + value = message[localName]; + emitDefault = false; + } + // we have handled oneof above. we just have to honor `emitDefault`. + switch (field.kind) { + case "scalar": + case "enum": + let T = field.kind == "enum" ? reflection_info_1.ScalarType.INT32 : field.T; + if (repeated) { + assert_1.assert(Array.isArray(value)); + if (repeated == reflection_info_1.RepeatType.PACKED) + this.packed(writer, T, field.no, value); + else + for (const item of value) + this.scalar(writer, T, field.no, item, true); + } + else if (value === undefined) + assert_1.assert(field.opt); + else + this.scalar(writer, T, field.no, value, emitDefault || field.opt); + break; + case "message": + if (repeated) { + assert_1.assert(Array.isArray(value)); + for (const item of value) + this.message(writer, options, field.T(), field.no, item); + } + else { + this.message(writer, options, field.T(), field.no, value); + } + break; + case "map": + assert_1.assert(typeof value == 'object' && value !== null); + for (const [key, val] of Object.entries(value)) + this.mapEntry(writer, options, field, key, val); + break; + } + } + let u = options.writeUnknownFields; + if (u !== false) + (u === true ? binary_format_contract_1.UnknownFieldHandler.onWrite : u)(this.info.typeName, message, writer); + } + mapEntry(writer, options, field, key, value) { + writer.tag(field.no, binary_format_contract_1.WireType.LengthDelimited); + writer.fork(); + // javascript only allows number or string for object properties + // we convert from our representation to the protobuf type + let keyValue = key; + switch (field.K) { + case reflection_info_1.ScalarType.INT32: + case reflection_info_1.ScalarType.FIXED32: + case reflection_info_1.ScalarType.UINT32: + case reflection_info_1.ScalarType.SFIXED32: + case reflection_info_1.ScalarType.SINT32: + keyValue = Number.parseInt(key); + break; + case reflection_info_1.ScalarType.BOOL: + assert_1.assert(key == 'true' || key == 'false'); + keyValue = key == 'true'; + break; + } + // write key, expecting key field number = 1 + this.scalar(writer, field.K, 1, keyValue, true); + // write value, expecting value field number = 2 + switch (field.V.kind) { + case 'scalar': + this.scalar(writer, field.V.T, 2, value, true); + break; + case 'enum': + this.scalar(writer, reflection_info_1.ScalarType.INT32, 2, value, true); + break; + case 'message': + this.message(writer, options, field.V.T(), 2, value); + break; + } + writer.join(); + } + message(writer, options, handler, fieldNo, value) { + if (value === undefined) + return; + handler.internalBinaryWrite(value, writer.tag(fieldNo, binary_format_contract_1.WireType.LengthDelimited).fork(), options); + writer.join(); + } + /** + * Write a single scalar value. + */ + scalar(writer, type, fieldNo, value, emitDefault) { + let [wireType, method, isDefault] = this.scalarInfo(type, value); + if (!isDefault || emitDefault) { + writer.tag(fieldNo, wireType); + writer[method](value); + } + } + /** + * Write an array of scalar values in packed format. + */ + packed(writer, type, fieldNo, value) { + if (!value.length) + return; + assert_1.assert(type !== reflection_info_1.ScalarType.BYTES && type !== reflection_info_1.ScalarType.STRING); + // write tag + writer.tag(fieldNo, binary_format_contract_1.WireType.LengthDelimited); + // begin length-delimited + writer.fork(); + // write values without tags + let [, method,] = this.scalarInfo(type); + for (let i = 0; i < value.length; i++) + writer[method](value[i]); + // end length delimited + writer.join(); + } + /** + * Get information for writing a scalar value. + * + * Returns tuple: + * [0]: appropriate WireType + * [1]: name of the appropriate method of IBinaryWriter + * [2]: whether the given value is a default value + * + * If argument `value` is omitted, [2] is always false. + */ + scalarInfo(type, value) { + let t = binary_format_contract_1.WireType.Varint; + let m; + let i = value === undefined; + let d = value === 0; + switch (type) { + case reflection_info_1.ScalarType.INT32: + m = "int32"; + break; + case reflection_info_1.ScalarType.STRING: + d = i || !value.length; + t = binary_format_contract_1.WireType.LengthDelimited; + m = "string"; + break; + case reflection_info_1.ScalarType.BOOL: + d = value === false; + m = "bool"; + break; + case reflection_info_1.ScalarType.UINT32: + m = "uint32"; + break; + case reflection_info_1.ScalarType.DOUBLE: + t = binary_format_contract_1.WireType.Bit64; + m = "double"; + break; + case reflection_info_1.ScalarType.FLOAT: + t = binary_format_contract_1.WireType.Bit32; + m = "float"; + break; + case reflection_info_1.ScalarType.INT64: + d = i || pb_long_1.PbLong.from(value).isZero(); + m = "int64"; + break; + case reflection_info_1.ScalarType.UINT64: + d = i || pb_long_1.PbULong.from(value).isZero(); + m = "uint64"; + break; + case reflection_info_1.ScalarType.FIXED64: + d = i || pb_long_1.PbULong.from(value).isZero(); + t = binary_format_contract_1.WireType.Bit64; + m = "fixed64"; + break; + case reflection_info_1.ScalarType.BYTES: + d = i || !value.byteLength; + t = binary_format_contract_1.WireType.LengthDelimited; + m = "bytes"; + break; + case reflection_info_1.ScalarType.FIXED32: + t = binary_format_contract_1.WireType.Bit32; + m = "fixed32"; + break; + case reflection_info_1.ScalarType.SFIXED32: + t = binary_format_contract_1.WireType.Bit32; + m = "sfixed32"; + break; + case reflection_info_1.ScalarType.SFIXED64: + d = i || pb_long_1.PbLong.from(value).isZero(); + t = binary_format_contract_1.WireType.Bit64; + m = "sfixed64"; + break; + case reflection_info_1.ScalarType.SINT32: + m = "sint32"; + break; + case reflection_info_1.ScalarType.SINT64: + d = i || pb_long_1.PbLong.from(value).isZero(); + m = "sint64"; + break; + } + return [t, m, i || d]; + } +} +exports.ReflectionBinaryWriter = ReflectionBinaryWriter; + + +/***/ }), + +/***/ 9946: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.containsMessageType = void 0; +const message_type_contract_1 = __nccwpck_require__(3785); +/** + * Check if the provided object is a proto message. + * + * Note that this is an experimental feature - it is here to stay, but + * implementation details may change without notice. + */ +function containsMessageType(msg) { + return msg[message_type_contract_1.MESSAGE_TYPE] != null; +} +exports.containsMessageType = containsMessageType; + + +/***/ }), + +/***/ 5726: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.reflectionCreate = void 0; +const reflection_scalar_default_1 = __nccwpck_require__(9526); +const message_type_contract_1 = __nccwpck_require__(3785); +/** + * Creates an instance of the generic message, using the field + * information. + */ +function reflectionCreate(type) { + /** + * This ternary can be removed in the next major version. + * The `Object.create()` code path utilizes a new `messagePrototype` + * property on the `IMessageType` which has this same `MESSAGE_TYPE` + * non-enumerable property on it. Doing it this way means that we only + * pay the cost of `Object.defineProperty()` once per `IMessageType` + * class of once per "instance". The falsy code path is only provided + * for backwards compatibility in cases where the runtime library is + * updated without also updating the generated code. + */ + const msg = type.messagePrototype + ? Object.create(type.messagePrototype) + : Object.defineProperty({}, message_type_contract_1.MESSAGE_TYPE, { value: type }); + for (let field of type.fields) { + let name = field.localName; + if (field.opt) + continue; + if (field.oneof) + msg[field.oneof] = { oneofKind: undefined }; + else if (field.repeat) + msg[name] = []; + else + switch (field.kind) { + case "scalar": + msg[name] = reflection_scalar_default_1.reflectionScalarDefault(field.T, field.L); + break; + case "enum": + // we require 0 to be default value for all enums + msg[name] = 0; + break; + case "map": + msg[name] = {}; + break; + } + } + return msg; +} +exports.reflectionCreate = reflectionCreate; + + +/***/ }), + +/***/ 4827: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.reflectionEquals = void 0; +const reflection_info_1 = __nccwpck_require__(7910); +/** + * Determines whether two message of the same type have the same field values. + * Checks for deep equality, traversing repeated fields, oneof groups, maps + * and messages recursively. + * Will also return true if both messages are `undefined`. + */ +function reflectionEquals(info, a, b) { + if (a === b) + return true; + if (!a || !b) + return false; + for (let field of info.fields) { + let localName = field.localName; + let val_a = field.oneof ? a[field.oneof][localName] : a[localName]; + let val_b = field.oneof ? b[field.oneof][localName] : b[localName]; + switch (field.kind) { + case "enum": + case "scalar": + let t = field.kind == "enum" ? reflection_info_1.ScalarType.INT32 : field.T; + if (!(field.repeat + ? repeatedPrimitiveEq(t, val_a, val_b) + : primitiveEq(t, val_a, val_b))) + return false; + break; + case "map": + if (!(field.V.kind == "message" + ? repeatedMsgEq(field.V.T(), objectValues(val_a), objectValues(val_b)) + : repeatedPrimitiveEq(field.V.kind == "enum" ? reflection_info_1.ScalarType.INT32 : field.V.T, objectValues(val_a), objectValues(val_b)))) + return false; + break; + case "message": + let T = field.T(); + if (!(field.repeat + ? repeatedMsgEq(T, val_a, val_b) + : T.equals(val_a, val_b))) + return false; + break; + } + } + return true; +} +exports.reflectionEquals = reflectionEquals; +const objectValues = Object.values; +function primitiveEq(type, a, b) { + if (a === b) + return true; + if (type !== reflection_info_1.ScalarType.BYTES) + return false; + let ba = a; + let bb = b; + if (ba.length !== bb.length) + return false; + for (let i = 0; i < ba.length; i++) + if (ba[i] != bb[i]) + return false; + return true; +} +function repeatedPrimitiveEq(type, a, b) { + if (a.length !== b.length) + return false; + for (let i = 0; i < a.length; i++) + if (!primitiveEq(type, a[i], b[i])) + return false; + return true; +} +function repeatedMsgEq(type, a, b) { + if (a.length !== b.length) + return false; + for (let i = 0; i < a.length; i++) + if (!type.equals(a[i], b[i])) + return false; + return true; +} + + +/***/ }), + +/***/ 7910: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.readMessageOption = exports.readFieldOption = exports.readFieldOptions = exports.normalizeFieldInfo = exports.RepeatType = exports.LongType = exports.ScalarType = void 0; +const lower_camel_case_1 = __nccwpck_require__(4073); +/** + * Scalar value types. This is a subset of field types declared by protobuf + * enum google.protobuf.FieldDescriptorProto.Type The types GROUP and MESSAGE + * are omitted, but the numerical values are identical. + */ +var ScalarType; +(function (ScalarType) { + // 0 is reserved for errors. + // Order is weird for historical reasons. + ScalarType[ScalarType["DOUBLE"] = 1] = "DOUBLE"; + ScalarType[ScalarType["FLOAT"] = 2] = "FLOAT"; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT64 if + // negative values are likely. + ScalarType[ScalarType["INT64"] = 3] = "INT64"; + ScalarType[ScalarType["UINT64"] = 4] = "UINT64"; + // Not ZigZag encoded. Negative numbers take 10 bytes. Use TYPE_SINT32 if + // negative values are likely. + ScalarType[ScalarType["INT32"] = 5] = "INT32"; + ScalarType[ScalarType["FIXED64"] = 6] = "FIXED64"; + ScalarType[ScalarType["FIXED32"] = 7] = "FIXED32"; + ScalarType[ScalarType["BOOL"] = 8] = "BOOL"; + ScalarType[ScalarType["STRING"] = 9] = "STRING"; + // Tag-delimited aggregate. + // Group type is deprecated and not supported in proto3. However, Proto3 + // implementations should still be able to parse the group wire format and + // treat group fields as unknown fields. + // TYPE_GROUP = 10, + // TYPE_MESSAGE = 11, // Length-delimited aggregate. + // New in version 2. + ScalarType[ScalarType["BYTES"] = 12] = "BYTES"; + ScalarType[ScalarType["UINT32"] = 13] = "UINT32"; + // TYPE_ENUM = 14, + ScalarType[ScalarType["SFIXED32"] = 15] = "SFIXED32"; + ScalarType[ScalarType["SFIXED64"] = 16] = "SFIXED64"; + ScalarType[ScalarType["SINT32"] = 17] = "SINT32"; + ScalarType[ScalarType["SINT64"] = 18] = "SINT64"; +})(ScalarType = exports.ScalarType || (exports.ScalarType = {})); +/** + * JavaScript representation of 64 bit integral types. Equivalent to the + * field option "jstype". + * + * By default, protobuf-ts represents 64 bit types as `bigint`. + * + * You can change the default behaviour by enabling the plugin parameter + * `long_type_string`, which will represent 64 bit types as `string`. + * + * Alternatively, you can change the behaviour for individual fields + * with the field option "jstype": + * + * ```protobuf + * uint64 my_field = 1 [jstype = JS_STRING]; + * uint64 other_field = 2 [jstype = JS_NUMBER]; + * ``` + */ +var LongType; +(function (LongType) { + /** + * Use JavaScript `bigint`. + * + * Field option `[jstype = JS_NORMAL]`. + */ + LongType[LongType["BIGINT"] = 0] = "BIGINT"; + /** + * Use JavaScript `string`. + * + * Field option `[jstype = JS_STRING]`. + */ + LongType[LongType["STRING"] = 1] = "STRING"; + /** + * Use JavaScript `number`. + * + * Large values will loose precision. + * + * Field option `[jstype = JS_NUMBER]`. + */ + LongType[LongType["NUMBER"] = 2] = "NUMBER"; +})(LongType = exports.LongType || (exports.LongType = {})); +/** + * Protobuf 2.1.0 introduced packed repeated fields. + * Setting the field option `[packed = true]` enables packing. + * + * In proto3, all repeated fields are packed by default. + * Setting the field option `[packed = false]` disables packing. + * + * Packed repeated fields are encoded with a single tag, + * then a length-delimiter, then the element values. + * + * Unpacked repeated fields are encoded with a tag and + * value for each element. + * + * `bytes` and `string` cannot be packed. + */ +var RepeatType; +(function (RepeatType) { + /** + * The field is not repeated. + */ + RepeatType[RepeatType["NO"] = 0] = "NO"; + /** + * The field is repeated and should be packed. + * Invalid for `bytes` and `string`, they cannot be packed. + */ + RepeatType[RepeatType["PACKED"] = 1] = "PACKED"; + /** + * The field is repeated but should not be packed. + * The only valid repeat type for repeated `bytes` and `string`. + */ + RepeatType[RepeatType["UNPACKED"] = 2] = "UNPACKED"; +})(RepeatType = exports.RepeatType || (exports.RepeatType = {})); +/** + * Turns PartialFieldInfo into FieldInfo. + */ +function normalizeFieldInfo(field) { + var _a, _b, _c, _d; + field.localName = (_a = field.localName) !== null && _a !== void 0 ? _a : lower_camel_case_1.lowerCamelCase(field.name); + field.jsonName = (_b = field.jsonName) !== null && _b !== void 0 ? _b : lower_camel_case_1.lowerCamelCase(field.name); + field.repeat = (_c = field.repeat) !== null && _c !== void 0 ? _c : RepeatType.NO; + field.opt = (_d = field.opt) !== null && _d !== void 0 ? _d : (field.repeat ? false : field.oneof ? false : field.kind == "message"); + return field; +} +exports.normalizeFieldInfo = normalizeFieldInfo; +/** + * Read custom field options from a generated message type. + * + * @deprecated use readFieldOption() + */ +function readFieldOptions(messageType, fieldName, extensionName, extensionType) { + var _a; + const options = (_a = messageType.fields.find((m, i) => m.localName == fieldName || i == fieldName)) === null || _a === void 0 ? void 0 : _a.options; + return options && options[extensionName] ? extensionType.fromJson(options[extensionName]) : undefined; +} +exports.readFieldOptions = readFieldOptions; +function readFieldOption(messageType, fieldName, extensionName, extensionType) { + var _a; + const options = (_a = messageType.fields.find((m, i) => m.localName == fieldName || i == fieldName)) === null || _a === void 0 ? void 0 : _a.options; + if (!options) { + return undefined; + } + const optionVal = options[extensionName]; + if (optionVal === undefined) { + return optionVal; + } + return extensionType ? extensionType.fromJson(optionVal) : optionVal; +} +exports.readFieldOption = readFieldOption; +function readMessageOption(messageType, extensionName, extensionType) { + const options = messageType.options; + const optionVal = options[extensionName]; + if (optionVal === undefined) { + return optionVal; + } + return extensionType ? extensionType.fromJson(optionVal) : optionVal; +} +exports.readMessageOption = readMessageOption; + + +/***/ }), + +/***/ 6790: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ReflectionJsonReader = void 0; +const json_typings_1 = __nccwpck_require__(9999); +const base64_1 = __nccwpck_require__(6335); +const reflection_info_1 = __nccwpck_require__(7910); +const pb_long_1 = __nccwpck_require__(1753); +const assert_1 = __nccwpck_require__(8602); +const reflection_long_convert_1 = __nccwpck_require__(3402); +/** + * Reads proto3 messages in canonical JSON format using reflection information. + * + * https://developers.google.com/protocol-buffers/docs/proto3#json + */ +class ReflectionJsonReader { + constructor(info) { + this.info = info; + } + prepare() { + var _a; + if (this.fMap === undefined) { + this.fMap = {}; + const fieldsInput = (_a = this.info.fields) !== null && _a !== void 0 ? _a : []; + for (const field of fieldsInput) { + this.fMap[field.name] = field; + this.fMap[field.jsonName] = field; + this.fMap[field.localName] = field; + } + } + } + // Cannot parse JSON for #. + assert(condition, fieldName, jsonValue) { + if (!condition) { + let what = json_typings_1.typeofJsonValue(jsonValue); + if (what == "number" || what == "boolean") + what = jsonValue.toString(); + throw new Error(`Cannot parse JSON ${what} for ${this.info.typeName}#${fieldName}`); + } + } + /** + * Reads a message from canonical JSON format into the target message. + * + * Repeated fields are appended. Map entries are added, overwriting + * existing keys. + * + * If a message field is already present, it will be merged with the + * new data. + */ + read(input, message, options) { + this.prepare(); + const oneofsHandled = []; + for (const [jsonKey, jsonValue] of Object.entries(input)) { + const field = this.fMap[jsonKey]; + if (!field) { + if (!options.ignoreUnknownFields) + throw new Error(`Found unknown field while reading ${this.info.typeName} from JSON format. JSON key: ${jsonKey}`); + continue; + } + const localName = field.localName; + // handle oneof ADT + let target; // this will be the target for the field value, whether it is member of a oneof or not + if (field.oneof) { + if (jsonValue === null && (field.kind !== 'enum' || field.T()[0] !== 'google.protobuf.NullValue')) { + continue; + } + // since json objects are unordered by specification, it is not possible to take the last of multiple oneofs + if (oneofsHandled.includes(field.oneof)) + throw new Error(`Multiple members of the oneof group "${field.oneof}" of ${this.info.typeName} are present in JSON.`); + oneofsHandled.push(field.oneof); + target = message[field.oneof] = { + oneofKind: localName + }; + } + else { + target = message; + } + // we have handled oneof above. we just have read the value into `target`. + if (field.kind == 'map') { + if (jsonValue === null) { + continue; + } + // check input + this.assert(json_typings_1.isJsonObject(jsonValue), field.name, jsonValue); + // our target to put map entries into + const fieldObj = target[localName]; + // read entries + for (const [jsonObjKey, jsonObjValue] of Object.entries(jsonValue)) { + this.assert(jsonObjValue !== null, field.name + " map value", null); + // read value + let val; + switch (field.V.kind) { + case "message": + val = field.V.T().internalJsonRead(jsonObjValue, options); + break; + case "enum": + val = this.enum(field.V.T(), jsonObjValue, field.name, options.ignoreUnknownFields); + if (val === false) + continue; + break; + case "scalar": + val = this.scalar(jsonObjValue, field.V.T, field.V.L, field.name); + break; + } + this.assert(val !== undefined, field.name + " map value", jsonObjValue); + // read key + let key = jsonObjKey; + if (field.K == reflection_info_1.ScalarType.BOOL) + key = key == "true" ? true : key == "false" ? false : key; + key = this.scalar(key, field.K, reflection_info_1.LongType.STRING, field.name).toString(); + fieldObj[key] = val; + } + } + else if (field.repeat) { + if (jsonValue === null) + continue; + // check input + this.assert(Array.isArray(jsonValue), field.name, jsonValue); + // our target to put array entries into + const fieldArr = target[localName]; + // read array entries + for (const jsonItem of jsonValue) { + this.assert(jsonItem !== null, field.name, null); + let val; + switch (field.kind) { + case "message": + val = field.T().internalJsonRead(jsonItem, options); + break; + case "enum": + val = this.enum(field.T(), jsonItem, field.name, options.ignoreUnknownFields); + if (val === false) + continue; + break; + case "scalar": + val = this.scalar(jsonItem, field.T, field.L, field.name); + break; + } + this.assert(val !== undefined, field.name, jsonValue); + fieldArr.push(val); + } + } + else { + switch (field.kind) { + case "message": + if (jsonValue === null && field.T().typeName != 'google.protobuf.Value') { + this.assert(field.oneof === undefined, field.name + " (oneof member)", null); + continue; + } + target[localName] = field.T().internalJsonRead(jsonValue, options, target[localName]); + break; + case "enum": + if (jsonValue === null) + continue; + let val = this.enum(field.T(), jsonValue, field.name, options.ignoreUnknownFields); + if (val === false) + continue; + target[localName] = val; + break; + case "scalar": + if (jsonValue === null) + continue; + target[localName] = this.scalar(jsonValue, field.T, field.L, field.name); + break; + } + } + } + } + /** + * Returns `false` for unrecognized string representations. + * + * google.protobuf.NullValue accepts only JSON `null` (or the old `"NULL_VALUE"`). + */ + enum(type, json, fieldName, ignoreUnknownFields) { + if (type[0] == 'google.protobuf.NullValue') + assert_1.assert(json === null || json === "NULL_VALUE", `Unable to parse field ${this.info.typeName}#${fieldName}, enum ${type[0]} only accepts null.`); + if (json === null) + // we require 0 to be default value for all enums + return 0; + switch (typeof json) { + case "number": + assert_1.assert(Number.isInteger(json), `Unable to parse field ${this.info.typeName}#${fieldName}, enum can only be integral number, got ${json}.`); + return json; + case "string": + let localEnumName = json; + if (type[2] && json.substring(0, type[2].length) === type[2]) + // lookup without the shared prefix + localEnumName = json.substring(type[2].length); + let enumNumber = type[1][localEnumName]; + if (typeof enumNumber === 'undefined' && ignoreUnknownFields) { + return false; + } + assert_1.assert(typeof enumNumber == "number", `Unable to parse field ${this.info.typeName}#${fieldName}, enum ${type[0]} has no value for "${json}".`); + return enumNumber; + } + assert_1.assert(false, `Unable to parse field ${this.info.typeName}#${fieldName}, cannot parse enum value from ${typeof json}".`); + } + scalar(json, type, longType, fieldName) { + let e; + try { + switch (type) { + // float, double: JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". + // Either numbers or strings are accepted. Exponent notation is also accepted. + case reflection_info_1.ScalarType.DOUBLE: + case reflection_info_1.ScalarType.FLOAT: + if (json === null) + return .0; + if (json === "NaN") + return Number.NaN; + if (json === "Infinity") + return Number.POSITIVE_INFINITY; + if (json === "-Infinity") + return Number.NEGATIVE_INFINITY; + if (json === "") { + e = "empty string"; + break; + } + if (typeof json == "string" && json.trim().length !== json.length) { + e = "extra whitespace"; + break; + } + if (typeof json != "string" && typeof json != "number") { + break; + } + let float = Number(json); + if (Number.isNaN(float)) { + e = "not a number"; + break; + } + if (!Number.isFinite(float)) { + // infinity and -infinity are handled by string representation above, so this is an error + e = "too large or small"; + break; + } + if (type == reflection_info_1.ScalarType.FLOAT) + assert_1.assertFloat32(float); + return float; + // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. + case reflection_info_1.ScalarType.INT32: + case reflection_info_1.ScalarType.FIXED32: + case reflection_info_1.ScalarType.SFIXED32: + case reflection_info_1.ScalarType.SINT32: + case reflection_info_1.ScalarType.UINT32: + if (json === null) + return 0; + let int32; + if (typeof json == "number") + int32 = json; + else if (json === "") + e = "empty string"; + else if (typeof json == "string") { + if (json.trim().length !== json.length) + e = "extra whitespace"; + else + int32 = Number(json); + } + if (int32 === undefined) + break; + if (type == reflection_info_1.ScalarType.UINT32) + assert_1.assertUInt32(int32); + else + assert_1.assertInt32(int32); + return int32; + // int64, fixed64, uint64: JSON value will be a decimal string. Either numbers or strings are accepted. + case reflection_info_1.ScalarType.INT64: + case reflection_info_1.ScalarType.SFIXED64: + case reflection_info_1.ScalarType.SINT64: + if (json === null) + return reflection_long_convert_1.reflectionLongConvert(pb_long_1.PbLong.ZERO, longType); + if (typeof json != "number" && typeof json != "string") + break; + return reflection_long_convert_1.reflectionLongConvert(pb_long_1.PbLong.from(json), longType); + case reflection_info_1.ScalarType.FIXED64: + case reflection_info_1.ScalarType.UINT64: + if (json === null) + return reflection_long_convert_1.reflectionLongConvert(pb_long_1.PbULong.ZERO, longType); + if (typeof json != "number" && typeof json != "string") + break; + return reflection_long_convert_1.reflectionLongConvert(pb_long_1.PbULong.from(json), longType); + // bool: + case reflection_info_1.ScalarType.BOOL: + if (json === null) + return false; + if (typeof json !== "boolean") + break; + return json; + // string: + case reflection_info_1.ScalarType.STRING: + if (json === null) + return ""; + if (typeof json !== "string") { + e = "extra whitespace"; + break; + } + try { + encodeURIComponent(json); + } + catch (e) { + e = "invalid UTF8"; + break; + } + return json; + // bytes: JSON value will be the data encoded as a string using standard base64 encoding with paddings. + // Either standard or URL-safe base64 encoding with/without paddings are accepted. + case reflection_info_1.ScalarType.BYTES: + if (json === null || json === "") + return new Uint8Array(0); + if (typeof json !== 'string') + break; + return base64_1.base64decode(json); + } + } + catch (error) { + e = error.message; + } + this.assert(false, fieldName + (e ? " - " + e : ""), json); + } +} +exports.ReflectionJsonReader = ReflectionJsonReader; + + +/***/ }), + +/***/ 1094: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ReflectionJsonWriter = void 0; +const base64_1 = __nccwpck_require__(6335); +const pb_long_1 = __nccwpck_require__(1753); +const reflection_info_1 = __nccwpck_require__(7910); +const assert_1 = __nccwpck_require__(8602); +/** + * Writes proto3 messages in canonical JSON format using reflection + * information. + * + * https://developers.google.com/protocol-buffers/docs/proto3#json + */ +class ReflectionJsonWriter { + constructor(info) { + var _a; + this.fields = (_a = info.fields) !== null && _a !== void 0 ? _a : []; + } + /** + * Converts the message to a JSON object, based on the field descriptors. + */ + write(message, options) { + const json = {}, source = message; + for (const field of this.fields) { + // field is not part of a oneof, simply write as is + if (!field.oneof) { + let jsonValue = this.field(field, source[field.localName], options); + if (jsonValue !== undefined) + json[options.useProtoFieldName ? field.name : field.jsonName] = jsonValue; + continue; + } + // field is part of a oneof + const group = source[field.oneof]; + if (group.oneofKind !== field.localName) + continue; // not selected, skip + const opt = field.kind == 'scalar' || field.kind == 'enum' + ? Object.assign(Object.assign({}, options), { emitDefaultValues: true }) : options; + let jsonValue = this.field(field, group[field.localName], opt); + assert_1.assert(jsonValue !== undefined); + json[options.useProtoFieldName ? field.name : field.jsonName] = jsonValue; + } + return json; + } + field(field, value, options) { + let jsonValue = undefined; + if (field.kind == 'map') { + assert_1.assert(typeof value == "object" && value !== null); + const jsonObj = {}; + switch (field.V.kind) { + case "scalar": + for (const [entryKey, entryValue] of Object.entries(value)) { + const val = this.scalar(field.V.T, entryValue, field.name, false, true); + assert_1.assert(val !== undefined); + jsonObj[entryKey.toString()] = val; // JSON standard allows only (double quoted) string as property key + } + break; + case "message": + const messageType = field.V.T(); + for (const [entryKey, entryValue] of Object.entries(value)) { + const val = this.message(messageType, entryValue, field.name, options); + assert_1.assert(val !== undefined); + jsonObj[entryKey.toString()] = val; // JSON standard allows only (double quoted) string as property key + } + break; + case "enum": + const enumInfo = field.V.T(); + for (const [entryKey, entryValue] of Object.entries(value)) { + assert_1.assert(entryValue === undefined || typeof entryValue == 'number'); + const val = this.enum(enumInfo, entryValue, field.name, false, true, options.enumAsInteger); + assert_1.assert(val !== undefined); + jsonObj[entryKey.toString()] = val; // JSON standard allows only (double quoted) string as property key + } + break; + } + if (options.emitDefaultValues || Object.keys(jsonObj).length > 0) + jsonValue = jsonObj; + } + else if (field.repeat) { + assert_1.assert(Array.isArray(value)); + const jsonArr = []; + switch (field.kind) { + case "scalar": + for (let i = 0; i < value.length; i++) { + const val = this.scalar(field.T, value[i], field.name, field.opt, true); + assert_1.assert(val !== undefined); + jsonArr.push(val); + } + break; + case "enum": + const enumInfo = field.T(); + for (let i = 0; i < value.length; i++) { + assert_1.assert(value[i] === undefined || typeof value[i] == 'number'); + const val = this.enum(enumInfo, value[i], field.name, field.opt, true, options.enumAsInteger); + assert_1.assert(val !== undefined); + jsonArr.push(val); + } + break; + case "message": + const messageType = field.T(); + for (let i = 0; i < value.length; i++) { + const val = this.message(messageType, value[i], field.name, options); + assert_1.assert(val !== undefined); + jsonArr.push(val); + } + break; + } + // add converted array to json output + if (options.emitDefaultValues || jsonArr.length > 0 || options.emitDefaultValues) + jsonValue = jsonArr; + } + else { + switch (field.kind) { + case "scalar": + jsonValue = this.scalar(field.T, value, field.name, field.opt, options.emitDefaultValues); + break; + case "enum": + jsonValue = this.enum(field.T(), value, field.name, field.opt, options.emitDefaultValues, options.enumAsInteger); + break; + case "message": + jsonValue = this.message(field.T(), value, field.name, options); + break; + } + } + return jsonValue; + } + /** + * Returns `null` as the default for google.protobuf.NullValue. + */ + enum(type, value, fieldName, optional, emitDefaultValues, enumAsInteger) { + if (type[0] == 'google.protobuf.NullValue') + return !emitDefaultValues && !optional ? undefined : null; + if (value === undefined) { + assert_1.assert(optional); + return undefined; + } + if (value === 0 && !emitDefaultValues && !optional) + // we require 0 to be default value for all enums + return undefined; + assert_1.assert(typeof value == 'number'); + assert_1.assert(Number.isInteger(value)); + if (enumAsInteger || !type[1].hasOwnProperty(value)) + // if we don't now the enum value, just return the number + return value; + if (type[2]) + // restore the dropped prefix + return type[2] + type[1][value]; + return type[1][value]; + } + message(type, value, fieldName, options) { + if (value === undefined) + return options.emitDefaultValues ? null : undefined; + return type.internalJsonWrite(value, options); + } + scalar(type, value, fieldName, optional, emitDefaultValues) { + if (value === undefined) { + assert_1.assert(optional); + return undefined; + } + const ed = emitDefaultValues || optional; + // noinspection FallThroughInSwitchStatementJS + switch (type) { + // int32, fixed32, uint32: JSON value will be a decimal number. Either numbers or strings are accepted. + case reflection_info_1.ScalarType.INT32: + case reflection_info_1.ScalarType.SFIXED32: + case reflection_info_1.ScalarType.SINT32: + if (value === 0) + return ed ? 0 : undefined; + assert_1.assertInt32(value); + return value; + case reflection_info_1.ScalarType.FIXED32: + case reflection_info_1.ScalarType.UINT32: + if (value === 0) + return ed ? 0 : undefined; + assert_1.assertUInt32(value); + return value; + // float, double: JSON value will be a number or one of the special string values "NaN", "Infinity", and "-Infinity". + // Either numbers or strings are accepted. Exponent notation is also accepted. + case reflection_info_1.ScalarType.FLOAT: + assert_1.assertFloat32(value); + case reflection_info_1.ScalarType.DOUBLE: + if (value === 0) + return ed ? 0 : undefined; + assert_1.assert(typeof value == 'number'); + if (Number.isNaN(value)) + return 'NaN'; + if (value === Number.POSITIVE_INFINITY) + return 'Infinity'; + if (value === Number.NEGATIVE_INFINITY) + return '-Infinity'; + return value; + // string: + case reflection_info_1.ScalarType.STRING: + if (value === "") + return ed ? '' : undefined; + assert_1.assert(typeof value == 'string'); + return value; + // bool: + case reflection_info_1.ScalarType.BOOL: + if (value === false) + return ed ? false : undefined; + assert_1.assert(typeof value == 'boolean'); + return value; + // JSON value will be a decimal string. Either numbers or strings are accepted. + case reflection_info_1.ScalarType.UINT64: + case reflection_info_1.ScalarType.FIXED64: + assert_1.assert(typeof value == 'number' || typeof value == 'string' || typeof value == 'bigint'); + let ulong = pb_long_1.PbULong.from(value); + if (ulong.isZero() && !ed) + return undefined; + return ulong.toString(); + // JSON value will be a decimal string. Either numbers or strings are accepted. + case reflection_info_1.ScalarType.INT64: + case reflection_info_1.ScalarType.SFIXED64: + case reflection_info_1.ScalarType.SINT64: + assert_1.assert(typeof value == 'number' || typeof value == 'string' || typeof value == 'bigint'); + let long = pb_long_1.PbLong.from(value); + if (long.isZero() && !ed) + return undefined; + return long.toString(); + // bytes: JSON value will be the data encoded as a string using standard base64 encoding with paddings. + // Either standard or URL-safe base64 encoding with/without paddings are accepted. + case reflection_info_1.ScalarType.BYTES: + assert_1.assert(value instanceof Uint8Array); + if (!value.byteLength) + return ed ? "" : undefined; + return base64_1.base64encode(value); + } + } +} +exports.ReflectionJsonWriter = ReflectionJsonWriter; + + +/***/ }), + +/***/ 3402: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.reflectionLongConvert = void 0; +const reflection_info_1 = __nccwpck_require__(7910); +/** + * Utility method to convert a PbLong or PbUlong to a JavaScript + * representation during runtime. + * + * Works with generated field information, `undefined` is equivalent + * to `STRING`. + */ +function reflectionLongConvert(long, type) { + switch (type) { + case reflection_info_1.LongType.BIGINT: + return long.toBigInt(); + case reflection_info_1.LongType.NUMBER: + return long.toNumber(); + default: + // case undefined: + // case LongType.STRING: + return long.toString(); + } +} +exports.reflectionLongConvert = reflectionLongConvert; + + +/***/ }), + +/***/ 8044: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.reflectionMergePartial = void 0; +/** + * Copy partial data into the target message. + * + * If a singular scalar or enum field is present in the source, it + * replaces the field in the target. + * + * If a singular message field is present in the source, it is merged + * with the target field by calling mergePartial() of the responsible + * message type. + * + * If a repeated field is present in the source, its values replace + * all values in the target array, removing extraneous values. + * Repeated message fields are copied, not merged. + * + * If a map field is present in the source, entries are added to the + * target map, replacing entries with the same key. Entries that only + * exist in the target remain. Entries with message values are copied, + * not merged. + * + * Note that this function differs from protobuf merge semantics, + * which appends repeated fields. + */ +function reflectionMergePartial(info, target, source) { + let fieldValue, // the field value we are working with + input = source, output; // where we want our field value to go + for (let field of info.fields) { + let name = field.localName; + if (field.oneof) { + const group = input[field.oneof]; // this is the oneof`s group in the source + if ((group === null || group === void 0 ? void 0 : group.oneofKind) == undefined) { // the user is free to omit + continue; // we skip this field, and all other members too + } + fieldValue = group[name]; // our value comes from the the oneof group of the source + output = target[field.oneof]; // and our output is the oneof group of the target + output.oneofKind = group.oneofKind; // always update discriminator + if (fieldValue == undefined) { + delete output[name]; // remove any existing value + continue; // skip further work on field + } + } + else { + fieldValue = input[name]; // we are using the source directly + output = target; // we want our field value to go directly into the target + if (fieldValue == undefined) { + continue; // skip further work on field, existing value is used as is + } + } + if (field.repeat) + output[name].length = fieldValue.length; // resize target array to match source array + // now we just work with `fieldValue` and `output` to merge the value + switch (field.kind) { + case "scalar": + case "enum": + if (field.repeat) + for (let i = 0; i < fieldValue.length; i++) + output[name][i] = fieldValue[i]; // not a reference type + else + output[name] = fieldValue; // not a reference type + break; + case "message": + let T = field.T(); + if (field.repeat) + for (let i = 0; i < fieldValue.length; i++) + output[name][i] = T.create(fieldValue[i]); + else if (output[name] === undefined) + output[name] = T.create(fieldValue); // nothing to merge with + else + T.mergePartial(output[name], fieldValue); + break; + case "map": + // Map and repeated fields are simply overwritten, not appended or merged + switch (field.V.kind) { + case "scalar": + case "enum": + Object.assign(output[name], fieldValue); // elements are not reference types + break; + case "message": + let T = field.V.T(); + for (let k of Object.keys(fieldValue)) + output[name][k] = T.create(fieldValue[k]); + break; + } + break; + } + } +} +exports.reflectionMergePartial = reflectionMergePartial; + + +/***/ }), + +/***/ 9526: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.reflectionScalarDefault = void 0; +const reflection_info_1 = __nccwpck_require__(7910); +const reflection_long_convert_1 = __nccwpck_require__(3402); +const pb_long_1 = __nccwpck_require__(1753); +/** + * Creates the default value for a scalar type. + */ +function reflectionScalarDefault(type, longType = reflection_info_1.LongType.STRING) { + switch (type) { + case reflection_info_1.ScalarType.BOOL: + return false; + case reflection_info_1.ScalarType.UINT64: + case reflection_info_1.ScalarType.FIXED64: + return reflection_long_convert_1.reflectionLongConvert(pb_long_1.PbULong.ZERO, longType); + case reflection_info_1.ScalarType.INT64: + case reflection_info_1.ScalarType.SFIXED64: + case reflection_info_1.ScalarType.SINT64: + return reflection_long_convert_1.reflectionLongConvert(pb_long_1.PbLong.ZERO, longType); + case reflection_info_1.ScalarType.DOUBLE: + case reflection_info_1.ScalarType.FLOAT: + return 0.0; + case reflection_info_1.ScalarType.BYTES: + return new Uint8Array(0); + case reflection_info_1.ScalarType.STRING: + return ""; + default: + // case ScalarType.INT32: + // case ScalarType.UINT32: + // case ScalarType.SINT32: + // case ScalarType.FIXED32: + // case ScalarType.SFIXED32: + return 0; + } +} +exports.reflectionScalarDefault = reflectionScalarDefault; + + +/***/ }), + +/***/ 5167: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.ReflectionTypeCheck = void 0; +const reflection_info_1 = __nccwpck_require__(7910); +const oneof_1 = __nccwpck_require__(8063); +// noinspection JSMethodCanBeStatic +class ReflectionTypeCheck { + constructor(info) { + var _a; + this.fields = (_a = info.fields) !== null && _a !== void 0 ? _a : []; + } + prepare() { + if (this.data) + return; + const req = [], known = [], oneofs = []; + for (let field of this.fields) { + if (field.oneof) { + if (!oneofs.includes(field.oneof)) { + oneofs.push(field.oneof); + req.push(field.oneof); + known.push(field.oneof); + } + } + else { + known.push(field.localName); + switch (field.kind) { + case "scalar": + case "enum": + if (!field.opt || field.repeat) + req.push(field.localName); + break; + case "message": + if (field.repeat) + req.push(field.localName); + break; + case "map": + req.push(field.localName); + break; + } + } + } + this.data = { req, known, oneofs: Object.values(oneofs) }; + } + /** + * Is the argument a valid message as specified by the + * reflection information? + * + * Checks all field types recursively. The `depth` + * specifies how deep into the structure the check will be. + * + * With a depth of 0, only the presence of fields + * is checked. + * + * With a depth of 1 or more, the field types are checked. + * + * With a depth of 2 or more, the members of map, repeated + * and message fields are checked. + * + * Message fields will be checked recursively with depth - 1. + * + * The number of map entries / repeated values being checked + * is < depth. + */ + is(message, depth, allowExcessProperties = false) { + if (depth < 0) + return true; + if (message === null || message === undefined || typeof message != 'object') + return false; + this.prepare(); + let keys = Object.keys(message), data = this.data; + // if a required field is missing in arg, this cannot be a T + if (keys.length < data.req.length || data.req.some(n => !keys.includes(n))) + return false; + if (!allowExcessProperties) { + // if the arg contains a key we dont know, this is not a literal T + if (keys.some(k => !data.known.includes(k))) + return false; + } + // "With a depth of 0, only the presence and absence of fields is checked." + // "With a depth of 1 or more, the field types are checked." + if (depth < 1) { + return true; + } + // check oneof group + for (const name of data.oneofs) { + const group = message[name]; + if (!oneof_1.isOneofGroup(group)) + return false; + if (group.oneofKind === undefined) + continue; + const field = this.fields.find(f => f.localName === group.oneofKind); + if (!field) + return false; // we found no field, but have a kind, something is wrong + if (!this.field(group[group.oneofKind], field, allowExcessProperties, depth)) + return false; + } + // check types + for (const field of this.fields) { + if (field.oneof !== undefined) + continue; + if (!this.field(message[field.localName], field, allowExcessProperties, depth)) + return false; + } + return true; + } + field(arg, field, allowExcessProperties, depth) { + let repeated = field.repeat; + switch (field.kind) { + case "scalar": + if (arg === undefined) + return field.opt; + if (repeated) + return this.scalars(arg, field.T, depth, field.L); + return this.scalar(arg, field.T, field.L); + case "enum": + if (arg === undefined) + return field.opt; + if (repeated) + return this.scalars(arg, reflection_info_1.ScalarType.INT32, depth); + return this.scalar(arg, reflection_info_1.ScalarType.INT32); + case "message": + if (arg === undefined) + return true; + if (repeated) + return this.messages(arg, field.T(), allowExcessProperties, depth); + return this.message(arg, field.T(), allowExcessProperties, depth); + case "map": + if (typeof arg != 'object' || arg === null) + return false; + if (depth < 2) + return true; + if (!this.mapKeys(arg, field.K, depth)) + return false; + switch (field.V.kind) { + case "scalar": + return this.scalars(Object.values(arg), field.V.T, depth, field.V.L); + case "enum": + return this.scalars(Object.values(arg), reflection_info_1.ScalarType.INT32, depth); + case "message": + return this.messages(Object.values(arg), field.V.T(), allowExcessProperties, depth); + } + break; + } + return true; + } + message(arg, type, allowExcessProperties, depth) { + if (allowExcessProperties) { + return type.isAssignable(arg, depth); + } + return type.is(arg, depth); + } + messages(arg, type, allowExcessProperties, depth) { + if (!Array.isArray(arg)) + return false; + if (depth < 2) + return true; + if (allowExcessProperties) { + for (let i = 0; i < arg.length && i < depth; i++) + if (!type.isAssignable(arg[i], depth - 1)) + return false; + } + else { + for (let i = 0; i < arg.length && i < depth; i++) + if (!type.is(arg[i], depth - 1)) + return false; + } + return true; + } + scalar(arg, type, longType) { + let argType = typeof arg; + switch (type) { + case reflection_info_1.ScalarType.UINT64: + case reflection_info_1.ScalarType.FIXED64: + case reflection_info_1.ScalarType.INT64: + case reflection_info_1.ScalarType.SFIXED64: + case reflection_info_1.ScalarType.SINT64: + switch (longType) { + case reflection_info_1.LongType.BIGINT: + return argType == "bigint"; + case reflection_info_1.LongType.NUMBER: + return argType == "number" && !isNaN(arg); + default: + return argType == "string"; + } + case reflection_info_1.ScalarType.BOOL: + return argType == 'boolean'; + case reflection_info_1.ScalarType.STRING: + return argType == 'string'; + case reflection_info_1.ScalarType.BYTES: + return arg instanceof Uint8Array; + case reflection_info_1.ScalarType.DOUBLE: + case reflection_info_1.ScalarType.FLOAT: + return argType == 'number' && !isNaN(arg); + default: + // case ScalarType.UINT32: + // case ScalarType.FIXED32: + // case ScalarType.INT32: + // case ScalarType.SINT32: + // case ScalarType.SFIXED32: + return argType == 'number' && Number.isInteger(arg); + } + } + scalars(arg, type, depth, longType) { + if (!Array.isArray(arg)) + return false; + if (depth < 2) + return true; + if (Array.isArray(arg)) + for (let i = 0; i < arg.length && i < depth; i++) + if (!this.scalar(arg[i], type, longType)) + return false; + return true; + } + mapKeys(map, type, depth) { + let keys = Object.keys(map); + switch (type) { + case reflection_info_1.ScalarType.INT32: + case reflection_info_1.ScalarType.FIXED32: + case reflection_info_1.ScalarType.SFIXED32: + case reflection_info_1.ScalarType.SINT32: + case reflection_info_1.ScalarType.UINT32: + return this.scalars(keys.slice(0, depth).map(k => parseInt(k)), type, depth); + case reflection_info_1.ScalarType.BOOL: + return this.scalars(keys.slice(0, depth).map(k => k == 'true' ? true : k == 'false' ? false : k), type, depth); + default: + return this.scalars(keys, type, depth, reflection_info_1.LongType.STRING); + } + } +} +exports.ReflectionTypeCheck = ReflectionTypeCheck; + + +/***/ }), + +/***/ 5183: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.req = exports.json = exports.toBuffer = void 0; +const http = __importStar(__nccwpck_require__(8611)); +const https = __importStar(__nccwpck_require__(5692)); +async function toBuffer(stream) { + let length = 0; + const chunks = []; + for await (const chunk of stream) { + length += chunk.length; + chunks.push(chunk); + } + return Buffer.concat(chunks, length); +} +exports.toBuffer = toBuffer; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +async function json(stream) { + const buf = await toBuffer(stream); + const str = buf.toString('utf8'); + try { + return JSON.parse(str); + } + catch (_err) { + const err = _err; + err.message += ` (input: ${str})`; + throw err; + } +} +exports.json = json; +function req(url, opts = {}) { + const href = typeof url === 'string' ? url : url.href; + const req = (href.startsWith('https:') ? https : http).request(url, opts); + const promise = new Promise((resolve, reject) => { + req + .once('response', resolve) + .once('error', reject) + .end(); + }); + req.then = promise.then.bind(promise); + return req; +} +exports.req = req; +//# sourceMappingURL=helpers.js.map + +/***/ }), + +/***/ 8894: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.Agent = void 0; +const net = __importStar(__nccwpck_require__(9278)); +const http = __importStar(__nccwpck_require__(8611)); +const https_1 = __nccwpck_require__(5692); +__exportStar(__nccwpck_require__(5183), exports); +const INTERNAL = Symbol('AgentBaseInternalState'); +class Agent extends http.Agent { + constructor(opts) { + super(opts); + this[INTERNAL] = {}; + } + /** + * Determine whether this is an `http` or `https` request. + */ + isSecureEndpoint(options) { + if (options) { + // First check the `secureEndpoint` property explicitly, since this + // means that a parent `Agent` is "passing through" to this instance. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (typeof options.secureEndpoint === 'boolean') { + return options.secureEndpoint; + } + // If no explicit `secure` endpoint, check if `protocol` property is + // set. This will usually be the case since using a full string URL + // or `URL` instance should be the most common usage. + if (typeof options.protocol === 'string') { + return options.protocol === 'https:'; + } + } + // Finally, if no `protocol` property was set, then fall back to + // checking the stack trace of the current call stack, and try to + // detect the "https" module. + const { stack } = new Error(); + if (typeof stack !== 'string') + return false; + return stack + .split('\n') + .some((l) => l.indexOf('(https.js:') !== -1 || + l.indexOf('node:https:') !== -1); + } + // In order to support async signatures in `connect()` and Node's native + // connection pooling in `http.Agent`, the array of sockets for each origin + // has to be updated synchronously. This is so the length of the array is + // accurate when `addRequest()` is next called. We achieve this by creating a + // fake socket and adding it to `sockets[origin]` and incrementing + // `totalSocketCount`. + incrementSockets(name) { + // If `maxSockets` and `maxTotalSockets` are both Infinity then there is no + // need to create a fake socket because Node.js native connection pooling + // will never be invoked. + if (this.maxSockets === Infinity && this.maxTotalSockets === Infinity) { + return null; + } + // All instances of `sockets` are expected TypeScript errors. The + // alternative is to add it as a private property of this class but that + // will break TypeScript subclassing. + if (!this.sockets[name]) { + // @ts-expect-error `sockets` is readonly in `@types/node` + this.sockets[name] = []; + } + const fakeSocket = new net.Socket({ writable: false }); + this.sockets[name].push(fakeSocket); + // @ts-expect-error `totalSocketCount` isn't defined in `@types/node` + this.totalSocketCount++; + return fakeSocket; + } + decrementSockets(name, socket) { + if (!this.sockets[name] || socket === null) { + return; + } + const sockets = this.sockets[name]; + const index = sockets.indexOf(socket); + if (index !== -1) { + sockets.splice(index, 1); + // @ts-expect-error `totalSocketCount` isn't defined in `@types/node` + this.totalSocketCount--; + if (sockets.length === 0) { + // @ts-expect-error `sockets` is readonly in `@types/node` + delete this.sockets[name]; + } + } + } + // In order to properly update the socket pool, we need to call `getName()` on + // the core `https.Agent` if it is a secureEndpoint. + getName(options) { + const secureEndpoint = this.isSecureEndpoint(options); + if (secureEndpoint) { + // @ts-expect-error `getName()` isn't defined in `@types/node` + return https_1.Agent.prototype.getName.call(this, options); + } + // @ts-expect-error `getName()` isn't defined in `@types/node` + return super.getName(options); + } + createSocket(req, options, cb) { + const connectOpts = { + ...options, + secureEndpoint: this.isSecureEndpoint(options), + }; + const name = this.getName(connectOpts); + const fakeSocket = this.incrementSockets(name); + Promise.resolve() + .then(() => this.connect(req, connectOpts)) + .then((socket) => { + this.decrementSockets(name, fakeSocket); + if (socket instanceof http.Agent) { + try { + // @ts-expect-error `addRequest()` isn't defined in `@types/node` + return socket.addRequest(req, connectOpts); + } + catch (err) { + return cb(err); + } + } + this[INTERNAL].currentSocket = socket; + // @ts-expect-error `createSocket()` isn't defined in `@types/node` + super.createSocket(req, options, cb); + }, (err) => { + this.decrementSockets(name, fakeSocket); + cb(err); + }); + } + createConnection() { + const socket = this[INTERNAL].currentSocket; + this[INTERNAL].currentSocket = undefined; + if (!socket) { + throw new Error('No socket was returned in the `connect()` function'); + } + return socket; + } + get defaultPort() { + return (this[INTERNAL].defaultPort ?? + (this.protocol === 'https:' ? 443 : 80)); + } + set defaultPort(v) { + if (this[INTERNAL]) { + this[INTERNAL].defaultPort = v; + } + } + get protocol() { + return (this[INTERNAL].protocol ?? + (this.isSecureEndpoint() ? 'https:' : 'http:')); + } + set protocol(v) { + if (this[INTERNAL]) { + this[INTERNAL].protocol = v; + } + } +} +exports.Agent = Agent; +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 6110: +/***/ ((module, exports, __nccwpck_require__) => { + +/* eslint-env browser */ + +/** + * This is the web browser implementation of `debug()`. + */ + +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = localstorage(); +exports.destroy = (() => { + let warned = false; + + return () => { + if (!warned) { + warned = true; + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + }; +})(); + +/** + * Colors. + */ + +exports.colors = [ + '#0000CC', + '#0000FF', + '#0033CC', + '#0033FF', + '#0066CC', + '#0066FF', + '#0099CC', + '#0099FF', + '#00CC00', + '#00CC33', + '#00CC66', + '#00CC99', + '#00CCCC', + '#00CCFF', + '#3300CC', + '#3300FF', + '#3333CC', + '#3333FF', + '#3366CC', + '#3366FF', + '#3399CC', + '#3399FF', + '#33CC00', + '#33CC33', + '#33CC66', + '#33CC99', + '#33CCCC', + '#33CCFF', + '#6600CC', + '#6600FF', + '#6633CC', + '#6633FF', + '#66CC00', + '#66CC33', + '#9900CC', + '#9900FF', + '#9933CC', + '#9933FF', + '#99CC00', + '#99CC33', + '#CC0000', + '#CC0033', + '#CC0066', + '#CC0099', + '#CC00CC', + '#CC00FF', + '#CC3300', + '#CC3333', + '#CC3366', + '#CC3399', + '#CC33CC', + '#CC33FF', + '#CC6600', + '#CC6633', + '#CC9900', + '#CC9933', + '#CCCC00', + '#CCCC33', + '#FF0000', + '#FF0033', + '#FF0066', + '#FF0099', + '#FF00CC', + '#FF00FF', + '#FF3300', + '#FF3333', + '#FF3366', + '#FF3399', + '#FF33CC', + '#FF33FF', + '#FF6600', + '#FF6633', + '#FF9900', + '#FF9933', + '#FFCC00', + '#FFCC33' +]; + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +// eslint-disable-next-line complexity +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) { + return true; + } + + // Internet Explorer and Edge do not support colors. + if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { + return false; + } + + let m; + + // Is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + // eslint-disable-next-line no-return-assign + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // Is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // Is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31) || + // Double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +} + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs(args) { + args[0] = (this.useColors ? '%c' : '') + + this.namespace + + (this.useColors ? ' %c' : ' ') + + args[0] + + (this.useColors ? '%c ' : ' ') + + '+' + module.exports.humanize(this.diff); + + if (!this.useColors) { + return; + } + + const c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit'); + + // The final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + let index = 0; + let lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, match => { + if (match === '%%') { + return; + } + index++; + if (match === '%c') { + // We only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); +} + +/** + * Invokes `console.debug()` when available. + * No-op when `console.debug` is not a "function". + * If `console.debug` is not available, falls back + * to `console.log`. + * + * @api public + */ +exports.log = console.debug || console.log || (() => {}); + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ +function save(namespaces) { + try { + if (namespaces) { + exports.storage.setItem('debug', namespaces); + } else { + exports.storage.removeItem('debug'); + } + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ +function load() { + let r; + try { + r = exports.storage.getItem('debug') || exports.storage.getItem('DEBUG') ; + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } + + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage() { + try { + // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context + // The Browser also has localStorage in the global context. + return localStorage; + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } +} + +module.exports = __nccwpck_require__(897)(exports); + +const {formatters} = module.exports; + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +formatters.j = function (v) { + try { + return JSON.stringify(v); + } catch (error) { + return '[UnexpectedJSONParseError]: ' + error.message; + } +}; + + +/***/ }), + +/***/ 897: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + */ + +function setup(env) { + createDebug.debug = createDebug; + createDebug.default = createDebug; + createDebug.coerce = coerce; + createDebug.disable = disable; + createDebug.enable = enable; + createDebug.enabled = enabled; + createDebug.humanize = __nccwpck_require__(744); + createDebug.destroy = destroy; + + Object.keys(env).forEach(key => { + createDebug[key] = env[key]; + }); + + /** + * The currently active debug mode names, and names to skip. + */ + + createDebug.names = []; + createDebug.skips = []; + + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + createDebug.formatters = {}; + + /** + * Selects a color for a debug namespace + * @param {String} namespace The namespace string for the debug instance to be colored + * @return {Number|String} An ANSI color code for the given namespace + * @api private + */ + function selectColor(namespace) { + let hash = 0; + + for (let i = 0; i < namespace.length; i++) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; + } + createDebug.selectColor = selectColor; + + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + function createDebug(namespace) { + let prevTime; + let enableOverride = null; + let namespacesCache; + let enabledCache; + + function debug(...args) { + // Disabled? + if (!debug.enabled) { + return; + } + + const self = debug; + + // Set `diff` timestamp + const curr = Number(new Date()); + const ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + args[0] = createDebug.coerce(args[0]); + + if (typeof args[0] !== 'string') { + // Anything else let's inspect with %O + args.unshift('%O'); + } + + // Apply any `formatters` transformations + let index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { + // If we encounter an escaped % then don't increase the array index + if (match === '%%') { + return '%'; + } + index++; + const formatter = createDebug.formatters[format]; + if (typeof formatter === 'function') { + const val = args[index]; + match = formatter.call(self, val); + + // Now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + // Apply env-specific formatting (colors, etc.) + createDebug.formatArgs.call(self, args); + + const logFn = self.log || createDebug.log; + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.useColors = createDebug.useColors(); + debug.color = createDebug.selectColor(namespace); + debug.extend = extend; + debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. + + Object.defineProperty(debug, 'enabled', { + enumerable: true, + configurable: false, + get: () => { + if (enableOverride !== null) { + return enableOverride; + } + if (namespacesCache !== createDebug.namespaces) { + namespacesCache = createDebug.namespaces; + enabledCache = createDebug.enabled(namespace); + } + + return enabledCache; + }, + set: v => { + enableOverride = v; + } + }); + + // Env-specific initialization logic for debug instances + if (typeof createDebug.init === 'function') { + createDebug.init(debug); + } + + return debug; + } + + function extend(namespace, delimiter) { + const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); + newDebug.log = this.log; + return newDebug; + } + + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + function enable(namespaces) { + createDebug.save(namespaces); + createDebug.namespaces = namespaces; + + createDebug.names = []; + createDebug.skips = []; + + const split = (typeof namespaces === 'string' ? namespaces : '') + .trim() + .replace(/\s+/g, ',') + .split(',') + .filter(Boolean); + + for (const ns of split) { + if (ns[0] === '-') { + createDebug.skips.push(ns.slice(1)); + } else { + createDebug.names.push(ns); + } + } + } + + /** + * Checks if the given string matches a namespace template, honoring + * asterisks as wildcards. + * + * @param {String} search + * @param {String} template + * @return {Boolean} + */ + function matchesTemplate(search, template) { + let searchIndex = 0; + let templateIndex = 0; + let starIndex = -1; + let matchIndex = 0; + + while (searchIndex < search.length) { + if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === '*')) { + // Match character or proceed with wildcard + if (template[templateIndex] === '*') { + starIndex = templateIndex; + matchIndex = searchIndex; + templateIndex++; // Skip the '*' + } else { + searchIndex++; + templateIndex++; + } + } else if (starIndex !== -1) { // eslint-disable-line no-negated-condition + // Backtrack to the last '*' and try to match more characters + templateIndex = starIndex + 1; + matchIndex++; + searchIndex = matchIndex; + } else { + return false; // No match + } + } + + // Handle trailing '*' in template + while (templateIndex < template.length && template[templateIndex] === '*') { + templateIndex++; + } + + return templateIndex === template.length; + } + + /** + * Disable debug output. + * + * @return {String} namespaces + * @api public + */ + function disable() { + const namespaces = [ + ...createDebug.names, + ...createDebug.skips.map(namespace => '-' + namespace) + ].join(','); + createDebug.enable(''); + return namespaces; + } + + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + function enabled(name) { + for (const skip of createDebug.skips) { + if (matchesTemplate(name, skip)) { + return false; + } + } + + for (const ns of createDebug.names) { + if (matchesTemplate(name, ns)) { + return true; + } + } + + return false; + } + + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + function coerce(val) { + if (val instanceof Error) { + return val.stack || val.message; + } + return val; + } + + /** + * XXX DO NOT USE. This is a temporary stub function. + * XXX It WILL be removed in the next major release. + */ + function destroy() { + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + + createDebug.enable(createDebug.load()); + + return createDebug; +} + +module.exports = setup; + + +/***/ }), + +/***/ 2830: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +/** + * Detect Electron renderer / nwjs process, which is node, but we should + * treat as a browser. + */ + +if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) { + module.exports = __nccwpck_require__(6110); +} else { + module.exports = __nccwpck_require__(5108); +} + + +/***/ }), + +/***/ 5108: +/***/ ((module, exports, __nccwpck_require__) => { + +/** + * Module dependencies. + */ + +const tty = __nccwpck_require__(2018); +const util = __nccwpck_require__(9023); + +/** + * This is the Node.js implementation of `debug()`. + */ + +exports.init = init; +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.destroy = util.deprecate( + () => {}, + 'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.' +); + +/** + * Colors. + */ + +exports.colors = [6, 2, 3, 4, 5, 1]; + +try { + // Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json) + // eslint-disable-next-line import/no-extraneous-dependencies + const supportsColor = __nccwpck_require__(1450); + + if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) { + exports.colors = [ + 20, + 21, + 26, + 27, + 32, + 33, + 38, + 39, + 40, + 41, + 42, + 43, + 44, + 45, + 56, + 57, + 62, + 63, + 68, + 69, + 74, + 75, + 76, + 77, + 78, + 79, + 80, + 81, + 92, + 93, + 98, + 99, + 112, + 113, + 128, + 129, + 134, + 135, + 148, + 149, + 160, + 161, + 162, + 163, + 164, + 165, + 166, + 167, + 168, + 169, + 170, + 171, + 172, + 173, + 178, + 179, + 184, + 185, + 196, + 197, + 198, + 199, + 200, + 201, + 202, + 203, + 204, + 205, + 206, + 207, + 208, + 209, + 214, + 215, + 220, + 221 + ]; + } +} catch (error) { + // Swallow - we only care if `supports-color` is available; it doesn't have to be. +} + +/** + * Build up the default `inspectOpts` object from the environment variables. + * + * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js + */ + +exports.inspectOpts = Object.keys(process.env).filter(key => { + return /^debug_/i.test(key); +}).reduce((obj, key) => { + // Camel-case + const prop = key + .substring(6) + .toLowerCase() + .replace(/_([a-z])/g, (_, k) => { + return k.toUpperCase(); + }); + + // Coerce string value into JS value + let val = process.env[key]; + if (/^(yes|on|true|enabled)$/i.test(val)) { + val = true; + } else if (/^(no|off|false|disabled)$/i.test(val)) { + val = false; + } else if (val === 'null') { + val = null; + } else { + val = Number(val); + } + + obj[prop] = val; + return obj; +}, {}); + +/** + * Is stdout a TTY? Colored output is enabled when `true`. + */ + +function useColors() { + return 'colors' in exports.inspectOpts ? + Boolean(exports.inspectOpts.colors) : + tty.isatty(process.stderr.fd); +} + +/** + * Adds ANSI color escape codes if enabled. + * + * @api public + */ + +function formatArgs(args) { + const {namespace: name, useColors} = this; + + if (useColors) { + const c = this.color; + const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c); + const prefix = ` ${colorCode};1m${name} \u001B[0m`; + + args[0] = prefix + args[0].split('\n').join('\n' + prefix); + args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m'); + } else { + args[0] = getDate() + name + ' ' + args[0]; + } +} + +function getDate() { + if (exports.inspectOpts.hideDate) { + return ''; + } + return new Date().toISOString() + ' '; +} + +/** + * Invokes `util.formatWithOptions()` with the specified arguments and writes to stderr. + */ + +function log(...args) { + return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args) + '\n'); +} + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ +function save(namespaces) { + if (namespaces) { + process.env.DEBUG = namespaces; + } else { + // If you set a process.env field to null or undefined, it gets cast to the + // string 'null' or 'undefined'. Just delete instead. + delete process.env.DEBUG; + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + +function load() { + return process.env.DEBUG; +} + +/** + * Init logic for `debug` instances. + * + * Create a new `inspectOpts` object in case `useColors` is set + * differently for a particular `debug` instance. + */ + +function init(debug) { + debug.inspectOpts = {}; + + const keys = Object.keys(exports.inspectOpts); + for (let i = 0; i < keys.length; i++) { + debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; + } +} + +module.exports = __nccwpck_require__(897)(exports); + +const {formatters} = module.exports; + +/** + * Map %o to `util.inspect()`, all on a single line. + */ + +formatters.o = function (v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts) + .split('\n') + .map(str => str.trim()) + .join(' '); +}; + +/** + * Map %O to `util.inspect()`, allowing multiple lines if needed. + */ + +formatters.O = function (v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts); +}; + + +/***/ }), + +/***/ 3813: +/***/ ((module) => { + +"use strict"; + + +module.exports = (flag, argv = process.argv) => { + const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--'); + const position = argv.indexOf(prefix + flag); + const terminatorPosition = argv.indexOf('--'); + return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition); +}; + + +/***/ }), + +/***/ 1970: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.HttpProxyAgent = void 0; +const net = __importStar(__nccwpck_require__(9278)); +const tls = __importStar(__nccwpck_require__(4756)); +const debug_1 = __importDefault(__nccwpck_require__(2830)); +const events_1 = __nccwpck_require__(4434); +const agent_base_1 = __nccwpck_require__(8894); +const url_1 = __nccwpck_require__(7016); +const debug = (0, debug_1.default)('http-proxy-agent'); +/** + * The `HttpProxyAgent` implements an HTTP Agent subclass that connects + * to the specified "HTTP proxy server" in order to proxy HTTP requests. + */ +class HttpProxyAgent extends agent_base_1.Agent { + constructor(proxy, opts) { + super(opts); + this.proxy = typeof proxy === 'string' ? new url_1.URL(proxy) : proxy; + this.proxyHeaders = opts?.headers ?? {}; + debug('Creating new HttpProxyAgent instance: %o', this.proxy.href); + // Trim off the brackets from IPv6 addresses + const host = (this.proxy.hostname || this.proxy.host).replace(/^\[|\]$/g, ''); + const port = this.proxy.port + ? parseInt(this.proxy.port, 10) + : this.proxy.protocol === 'https:' + ? 443 + : 80; + this.connectOpts = { + ...(opts ? omit(opts, 'headers') : null), + host, + port, + }; + } + addRequest(req, opts) { + req._header = null; + this.setRequestProps(req, opts); + // @ts-expect-error `addRequest()` isn't defined in `@types/node` + super.addRequest(req, opts); + } + setRequestProps(req, opts) { + const { proxy } = this; + const protocol = opts.secureEndpoint ? 'https:' : 'http:'; + const hostname = req.getHeader('host') || 'localhost'; + const base = `${protocol}//${hostname}`; + const url = new url_1.URL(req.path, base); + if (opts.port !== 80) { + url.port = String(opts.port); + } + // Change the `http.ClientRequest` instance's "path" field + // to the absolute path of the URL that will be requested. + req.path = String(url); + // Inject the `Proxy-Authorization` header if necessary. + const headers = typeof this.proxyHeaders === 'function' + ? this.proxyHeaders() + : { ...this.proxyHeaders }; + if (proxy.username || proxy.password) { + const auth = `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`; + headers['Proxy-Authorization'] = `Basic ${Buffer.from(auth).toString('base64')}`; + } + if (!headers['Proxy-Connection']) { + headers['Proxy-Connection'] = this.keepAlive + ? 'Keep-Alive' + : 'close'; + } + for (const name of Object.keys(headers)) { + const value = headers[name]; + if (value) { + req.setHeader(name, value); + } + } + } + async connect(req, opts) { + req._header = null; + if (!req.path.includes('://')) { + this.setRequestProps(req, opts); + } + // At this point, the http ClientRequest's internal `_header` field + // might have already been set. If this is the case then we'll need + // to re-generate the string since we just changed the `req.path`. + let first; + let endOfHeaders; + debug('Regenerating stored HTTP header string for request'); + req._implicitHeader(); + if (req.outputData && req.outputData.length > 0) { + debug('Patching connection write() output buffer with updated header'); + first = req.outputData[0].data; + endOfHeaders = first.indexOf('\r\n\r\n') + 4; + req.outputData[0].data = + req._header + first.substring(endOfHeaders); + debug('Output buffer: %o', req.outputData[0].data); + } + // Create a socket connection to the proxy server. + let socket; + if (this.proxy.protocol === 'https:') { + debug('Creating `tls.Socket`: %o', this.connectOpts); + socket = tls.connect(this.connectOpts); + } + else { + debug('Creating `net.Socket`: %o', this.connectOpts); + socket = net.connect(this.connectOpts); + } + // Wait for the socket's `connect` event, so that this `callback()` + // function throws instead of the `http` request machinery. This is + // important for i.e. `PacProxyAgent` which determines a failed proxy + // connection via the `callback()` function throwing. + await (0, events_1.once)(socket, 'connect'); + return socket; + } +} +HttpProxyAgent.protocols = ['http', 'https']; +exports.HttpProxyAgent = HttpProxyAgent; +function omit(obj, ...keys) { + const ret = {}; + let key; + for (key in obj) { + if (!keys.includes(key)) { + ret[key] = obj[key]; + } + } + return ret; +} +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 3669: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.HttpsProxyAgent = void 0; +const net = __importStar(__nccwpck_require__(9278)); +const tls = __importStar(__nccwpck_require__(4756)); +const assert_1 = __importDefault(__nccwpck_require__(2613)); +const debug_1 = __importDefault(__nccwpck_require__(2830)); +const agent_base_1 = __nccwpck_require__(8894); +const url_1 = __nccwpck_require__(7016); +const parse_proxy_response_1 = __nccwpck_require__(7943); +const debug = (0, debug_1.default)('https-proxy-agent'); +const setServernameFromNonIpHost = (options) => { + if (options.servername === undefined && + options.host && + !net.isIP(options.host)) { + return { + ...options, + servername: options.host, + }; + } + return options; +}; +/** + * The `HttpsProxyAgent` implements an HTTP Agent subclass that connects to + * the specified "HTTP(s) proxy server" in order to proxy HTTPS requests. + * + * Outgoing HTTP requests are first tunneled through the proxy server using the + * `CONNECT` HTTP request method to establish a connection to the proxy server, + * and then the proxy server connects to the destination target and issues the + * HTTP request from the proxy server. + * + * `https:` requests have their socket connection upgraded to TLS once + * the connection to the proxy server has been established. + */ +class HttpsProxyAgent extends agent_base_1.Agent { + constructor(proxy, opts) { + super(opts); + this.options = { path: undefined }; + this.proxy = typeof proxy === 'string' ? new url_1.URL(proxy) : proxy; + this.proxyHeaders = opts?.headers ?? {}; + debug('Creating new HttpsProxyAgent instance: %o', this.proxy.href); + // Trim off the brackets from IPv6 addresses + const host = (this.proxy.hostname || this.proxy.host).replace(/^\[|\]$/g, ''); + const port = this.proxy.port + ? parseInt(this.proxy.port, 10) + : this.proxy.protocol === 'https:' + ? 443 + : 80; + this.connectOpts = { + // Attempt to negotiate http/1.1 for proxy servers that support http/2 + ALPNProtocols: ['http/1.1'], + ...(opts ? omit(opts, 'headers') : null), + host, + port, + }; + } + /** + * Called when the node-core HTTP client library is creating a + * new HTTP request. + */ + async connect(req, opts) { + const { proxy } = this; + if (!opts.host) { + throw new TypeError('No "host" provided'); + } + // Create a socket connection to the proxy server. + let socket; + if (proxy.protocol === 'https:') { + debug('Creating `tls.Socket`: %o', this.connectOpts); + socket = tls.connect(setServernameFromNonIpHost(this.connectOpts)); + } + else { + debug('Creating `net.Socket`: %o', this.connectOpts); + socket = net.connect(this.connectOpts); + } + const headers = typeof this.proxyHeaders === 'function' + ? this.proxyHeaders() + : { ...this.proxyHeaders }; + const host = net.isIPv6(opts.host) ? `[${opts.host}]` : opts.host; + let payload = `CONNECT ${host}:${opts.port} HTTP/1.1\r\n`; + // Inject the `Proxy-Authorization` header if necessary. + if (proxy.username || proxy.password) { + const auth = `${decodeURIComponent(proxy.username)}:${decodeURIComponent(proxy.password)}`; + headers['Proxy-Authorization'] = `Basic ${Buffer.from(auth).toString('base64')}`; + } + headers.Host = `${host}:${opts.port}`; + if (!headers['Proxy-Connection']) { + headers['Proxy-Connection'] = this.keepAlive + ? 'Keep-Alive' + : 'close'; + } + for (const name of Object.keys(headers)) { + payload += `${name}: ${headers[name]}\r\n`; + } + const proxyResponsePromise = (0, parse_proxy_response_1.parseProxyResponse)(socket); + socket.write(`${payload}\r\n`); + const { connect, buffered } = await proxyResponsePromise; + req.emit('proxyConnect', connect); + this.emit('proxyConnect', connect, req); + if (connect.statusCode === 200) { + req.once('socket', resume); + if (opts.secureEndpoint) { + // The proxy is connecting to a TLS server, so upgrade + // this socket connection to a TLS connection. + debug('Upgrading socket connection to TLS'); + return tls.connect({ + ...omit(setServernameFromNonIpHost(opts), 'host', 'path', 'port'), + socket, + }); + } + return socket; + } + // Some other status code that's not 200... need to re-play the HTTP + // header "data" events onto the socket once the HTTP machinery is + // attached so that the node core `http` can parse and handle the + // error status code. + // Close the original socket, and a new "fake" socket is returned + // instead, so that the proxy doesn't get the HTTP request + // written to it (which may contain `Authorization` headers or other + // sensitive data). + // + // See: https://hackerone.com/reports/541502 + socket.destroy(); + const fakeSocket = new net.Socket({ writable: false }); + fakeSocket.readable = true; + // Need to wait for the "socket" event to re-play the "data" events. + req.once('socket', (s) => { + debug('Replaying proxy buffer for failed request'); + (0, assert_1.default)(s.listenerCount('data') > 0); + // Replay the "buffered" Buffer onto the fake `socket`, since at + // this point the HTTP module machinery has been hooked up for + // the user. + s.push(buffered); + s.push(null); + }); + return fakeSocket; + } +} +HttpsProxyAgent.protocols = ['http', 'https']; +exports.HttpsProxyAgent = HttpsProxyAgent; +function resume(socket) { + socket.resume(); +} +function omit(obj, ...keys) { + const ret = {}; + let key; + for (key in obj) { + if (!keys.includes(key)) { + ret[key] = obj[key]; + } + } + return ret; +} +//# sourceMappingURL=index.js.map + +/***/ }), + +/***/ 7943: +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { + +"use strict"; + +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.parseProxyResponse = void 0; +const debug_1 = __importDefault(__nccwpck_require__(2830)); +const debug = (0, debug_1.default)('https-proxy-agent:parse-proxy-response'); +function parseProxyResponse(socket) { + return new Promise((resolve, reject) => { + // we need to buffer any HTTP traffic that happens with the proxy before we get + // the CONNECT response, so that if the response is anything other than an "200" + // response code, then we can re-play the "data" events on the socket once the + // HTTP parser is hooked up... + let buffersLength = 0; + const buffers = []; + function read() { + const b = socket.read(); + if (b) + ondata(b); + else + socket.once('readable', read); + } + function cleanup() { + socket.removeListener('end', onend); + socket.removeListener('error', onerror); + socket.removeListener('readable', read); + } + function onend() { + cleanup(); + debug('onend'); + reject(new Error('Proxy connection ended before receiving CONNECT response')); + } + function onerror(err) { + cleanup(); + debug('onerror %o', err); + reject(err); + } + function ondata(b) { + buffers.push(b); + buffersLength += b.length; + const buffered = Buffer.concat(buffers, buffersLength); + const endOfHeaders = buffered.indexOf('\r\n\r\n'); + if (endOfHeaders === -1) { + // keep buffering + debug('have not received end of HTTP headers yet...'); + read(); + return; + } + const headerParts = buffered + .slice(0, endOfHeaders) + .toString('ascii') + .split('\r\n'); + const firstLine = headerParts.shift(); + if (!firstLine) { + socket.destroy(); + return reject(new Error('No header received from proxy CONNECT response')); + } + const firstLineParts = firstLine.split(' '); + const statusCode = +firstLineParts[1]; + const statusText = firstLineParts.slice(2).join(' '); + const headers = {}; + for (const header of headerParts) { + if (!header) + continue; + const firstColon = header.indexOf(':'); + if (firstColon === -1) { + socket.destroy(); + return reject(new Error(`Invalid header from proxy CONNECT response: "${header}"`)); + } + const key = header.slice(0, firstColon).toLowerCase(); + const value = header.slice(firstColon + 1).trimStart(); + const current = headers[key]; + if (typeof current === 'string') { + headers[key] = [current, value]; + } + else if (Array.isArray(current)) { + current.push(value); + } + else { + headers[key] = value; + } + } + debug('got proxy server response: %o %o', firstLine, headers); + cleanup(); + resolve({ + connect: { + statusCode, + statusText, + headers, + }, + buffered, + }); + } + socket.on('error', onerror); + socket.on('end', onend); + read(); + }); +} +exports.parseProxyResponse = parseProxyResponse; +//# sourceMappingURL=parse-proxy-response.js.map + +/***/ }), + +/***/ 744: +/***/ ((module) => { + +/** + * Helpers. + */ + +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var w = d * 7; +var y = d * 365.25; + +/** + * Parse or format the given `val`. + * + * Options: + * + * - `long` verbose formatting [false] + * + * @param {String|Number} val + * @param {Object} [options] + * @throws {Error} throw an error if val is not a non-empty string or a number + * @return {String|Number} + * @api public + */ + +module.exports = function (val, options) { + options = options || {}; + var type = typeof val; + if (type === 'string' && val.length > 0) { + return parse(val); + } else if (type === 'number' && isFinite(val)) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + 'val is not a non-empty string or a valid number. val=' + + JSON.stringify(val) + ); +}; + +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ + +function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || 'ms').toLowerCase(); + switch (type) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return n * y; + case 'weeks': + case 'week': + case 'w': + return n * w; + case 'days': + case 'day': + case 'd': + return n * d; + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return n * h; + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return n * m; + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return n * s; + case 'milliseconds': + case 'millisecond': + case 'msecs': + case 'msec': + case 'ms': + return n; + default: + return undefined; + } +} + +/** + * Short format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtShort(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return Math.round(ms / d) + 'd'; + } + if (msAbs >= h) { + return Math.round(ms / h) + 'h'; + } + if (msAbs >= m) { + return Math.round(ms / m) + 'm'; + } + if (msAbs >= s) { + return Math.round(ms / s) + 's'; + } + return ms + 'ms'; +} + +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ + +function fmtLong(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return plural(ms, msAbs, d, 'day'); + } + if (msAbs >= h) { + return plural(ms, msAbs, h, 'hour'); + } + if (msAbs >= m) { + return plural(ms, msAbs, m, 'minute'); + } + if (msAbs >= s) { + return plural(ms, msAbs, s, 'second'); + } + return ms + ' ms'; +} + +/** + * Pluralization helper. + */ + +function plural(ms, msAbs, n, name) { + var isPlural = msAbs >= n * 1.5; + return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : ''); +} + + +/***/ }), + +/***/ 9379: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const ANY = Symbol('SemVer ANY') +// hoisted class for cyclic dependency +class Comparator { + static get ANY () { + return ANY + } + + constructor (comp, options) { + options = parseOptions(options) + + if (comp instanceof Comparator) { + if (comp.loose === !!options.loose) { + return comp + } else { + comp = comp.value + } + } + + comp = comp.trim().split(/\s+/).join(' ') + debug('comparator', comp, options) + this.options = options + this.loose = !!options.loose + this.parse(comp) + + if (this.semver === ANY) { + this.value = '' + } else { + this.value = this.operator + this.semver.version + } + + debug('comp', this) + } + + parse (comp) { + const r = this.options.loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR] + const m = comp.match(r) + + if (!m) { + throw new TypeError(`Invalid comparator: ${comp}`) + } + + this.operator = m[1] !== undefined ? m[1] : '' + if (this.operator === '=') { + this.operator = '' + } + + // if it literally is just '>' or '' then allow anything. + if (!m[2]) { + this.semver = ANY + } else { + this.semver = new SemVer(m[2], this.options.loose) + } + } + + toString () { + return this.value + } + + test (version) { + debug('Comparator.test', version, this.options.loose) + + if (this.semver === ANY || version === ANY) { + return true + } + + if (typeof version === 'string') { + try { + version = new SemVer(version, this.options) + } catch (er) { + return false + } + } + + return cmp(version, this.operator, this.semver, this.options) + } + + intersects (comp, options) { + if (!(comp instanceof Comparator)) { + throw new TypeError('a Comparator is required') + } + + if (this.operator === '') { + if (this.value === '') { + return true + } + return new Range(comp.value, options).test(this.value) + } else if (comp.operator === '') { + if (comp.value === '') { + return true + } + return new Range(this.value, options).test(comp.semver) + } + + options = parseOptions(options) + + // Special cases where nothing can possibly be lower + if (options.includePrerelease && + (this.value === '<0.0.0-0' || comp.value === '<0.0.0-0')) { + return false + } + if (!options.includePrerelease && + (this.value.startsWith('<0.0.0') || comp.value.startsWith('<0.0.0'))) { + return false + } + + // Same direction increasing (> or >=) + if (this.operator.startsWith('>') && comp.operator.startsWith('>')) { + return true + } + // Same direction decreasing (< or <=) + if (this.operator.startsWith('<') && comp.operator.startsWith('<')) { + return true + } + // same SemVer and both sides are inclusive (<= or >=) + if ( + (this.semver.version === comp.semver.version) && + this.operator.includes('=') && comp.operator.includes('=')) { + return true + } + // opposite directions less than + if (cmp(this.semver, '<', comp.semver, options) && + this.operator.startsWith('>') && comp.operator.startsWith('<')) { + return true + } + // opposite directions greater than + if (cmp(this.semver, '>', comp.semver, options) && + this.operator.startsWith('<') && comp.operator.startsWith('>')) { + return true + } + return false + } +} + +module.exports = Comparator + +const parseOptions = __nccwpck_require__(356) +const { safeRe: re, t } = __nccwpck_require__(5471) +const cmp = __nccwpck_require__(8646) +const debug = __nccwpck_require__(1159) +const SemVer = __nccwpck_require__(7163) +const Range = __nccwpck_require__(6782) + + +/***/ }), + +/***/ 6782: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const SPACE_CHARACTERS = /\s+/g + +// hoisted class for cyclic dependency +class Range { + constructor (range, options) { + options = parseOptions(options) + + if (range instanceof Range) { + if ( + range.loose === !!options.loose && + range.includePrerelease === !!options.includePrerelease + ) { + return range + } else { + return new Range(range.raw, options) + } + } + + if (range instanceof Comparator) { + // just put it in the set and return + this.raw = range.value + this.set = [[range]] + this.formatted = undefined + return this + } + + this.options = options + this.loose = !!options.loose + this.includePrerelease = !!options.includePrerelease + + // First reduce all whitespace as much as possible so we do not have to rely + // on potentially slow regexes like \s*. This is then stored and used for + // future error messages as well. + this.raw = range.trim().replace(SPACE_CHARACTERS, ' ') + + // First, split on || + this.set = this.raw + .split('||') + // map the range to a 2d array of comparators + .map(r => this.parseRange(r.trim())) + // throw out any comparator lists that are empty + // this generally means that it was not a valid range, which is allowed + // in loose mode, but will still throw if the WHOLE range is invalid. + .filter(c => c.length) + + if (!this.set.length) { + throw new TypeError(`Invalid SemVer Range: ${this.raw}`) + } + + // if we have any that are not the null set, throw out null sets. + if (this.set.length > 1) { + // keep the first one, in case they're all null sets + const first = this.set[0] + this.set = this.set.filter(c => !isNullSet(c[0])) + if (this.set.length === 0) { + this.set = [first] + } else if (this.set.length > 1) { + // if we have any that are *, then the range is just * + for (const c of this.set) { + if (c.length === 1 && isAny(c[0])) { + this.set = [c] + break + } + } + } + } + + this.formatted = undefined + } + + get range () { + if (this.formatted === undefined) { + this.formatted = '' + for (let i = 0; i < this.set.length; i++) { + if (i > 0) { + this.formatted += '||' + } + const comps = this.set[i] + for (let k = 0; k < comps.length; k++) { + if (k > 0) { + this.formatted += ' ' + } + this.formatted += comps[k].toString().trim() + } + } + } + return this.formatted + } + + format () { + return this.range + } + + toString () { + return this.range + } + + parseRange (range) { + // strip build metadata so it can't bleed into the version + range = range.replace(BUILDSTRIPRE, '') + + // memoize range parsing for performance. + // this is a very hot path, and fully deterministic. + const memoOpts = + (this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) | + (this.options.loose && FLAG_LOOSE) + const memoKey = memoOpts + ':' + range + const cached = cache.get(memoKey) + if (cached) { + return cached + } + + const loose = this.options.loose + // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` + const hr = loose ? re[t.HYPHENRANGELOOSE] : re[t.HYPHENRANGE] + range = range.replace(hr, hyphenReplace(this.options.includePrerelease)) + debug('hyphen replace', range) + + // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` + range = range.replace(re[t.COMPARATORTRIM], comparatorTrimReplace) + debug('comparator trim', range) + + // `~ 1.2.3` => `~1.2.3` + range = range.replace(re[t.TILDETRIM], tildeTrimReplace) + debug('tilde trim', range) + + // `^ 1.2.3` => `^1.2.3` + range = range.replace(re[t.CARETTRIM], caretTrimReplace) + debug('caret trim', range) + + // At this point, the range is completely trimmed and + // ready to be split into comparators. + + let rangeList = range + .split(' ') + .map(comp => parseComparator(comp, this.options)) + .join(' ') + .split(/\s+/) + // >=0.0.0 is equivalent to * + .map(comp => replaceGTE0(comp, this.options)) + + if (loose) { + // in loose mode, throw out any that are not valid comparators + rangeList = rangeList.filter(comp => { + debug('loose invalid filter', comp, this.options) + return !!comp.match(re[t.COMPARATORLOOSE]) + }) + } + debug('range list', rangeList) + + // if any comparators are the null set, then replace with JUST null set + // if more than one comparator, remove any * comparators + // also, don't include the same comparator more than once + const rangeMap = new Map() + const comparators = rangeList.map(comp => new Comparator(comp, this.options)) + for (const comp of comparators) { + if (isNullSet(comp)) { + return [comp] + } + rangeMap.set(comp.value, comp) + } + if (rangeMap.size > 1 && rangeMap.has('')) { + rangeMap.delete('') + } + + const result = [...rangeMap.values()] + cache.set(memoKey, result) + return result + } + + intersects (range, options) { + if (!(range instanceof Range)) { + throw new TypeError('a Range is required') + } + + return this.set.some((thisComparators) => { + return ( + isSatisfiable(thisComparators, options) && + range.set.some((rangeComparators) => { + return ( + isSatisfiable(rangeComparators, options) && + thisComparators.every((thisComparator) => { + return rangeComparators.every((rangeComparator) => { + return thisComparator.intersects(rangeComparator, options) + }) + }) + ) + }) + ) + }) + } + + // if ANY of the sets match ALL of its comparators, then pass + test (version) { + if (!version) { + return false + } + + if (typeof version === 'string') { + try { + version = new SemVer(version, this.options) + } catch (er) { + return false + } + } + + for (let i = 0; i < this.set.length; i++) { + if (testSet(this.set[i], version, this.options)) { + return true + } + } + return false + } +} + +module.exports = Range + +const LRU = __nccwpck_require__(1383) +const cache = new LRU() + +const parseOptions = __nccwpck_require__(356) +const Comparator = __nccwpck_require__(9379) +const debug = __nccwpck_require__(1159) +const SemVer = __nccwpck_require__(7163) +const { + safeRe: re, + src, + t, + comparatorTrimReplace, + tildeTrimReplace, + caretTrimReplace, +} = __nccwpck_require__(5471) +const { FLAG_INCLUDE_PRERELEASE, FLAG_LOOSE } = __nccwpck_require__(5101) + +// unbounded global build-metadata stripper used by parseRange +const BUILDSTRIPRE = new RegExp(src[t.BUILD], 'g') + +const isNullSet = c => c.value === '<0.0.0-0' +const isAny = c => c.value === '' + +// take a set of comparators and determine whether there +// exists a version which can satisfy it +const isSatisfiable = (comparators, options) => { + let result = true + const remainingComparators = comparators.slice() + let testComparator = remainingComparators.pop() + + while (result && remainingComparators.length) { + result = remainingComparators.every((otherComparator) => { + return testComparator.intersects(otherComparator, options) + }) + + testComparator = remainingComparators.pop() + } + + return result +} + +// comprised of xranges, tildes, stars, and gtlt's at this point. +// already replaced the hyphen ranges +// turn into a set of JUST comparators. +const parseComparator = (comp, options) => { + comp = comp.replace(re[t.BUILD], '') + debug('comp', comp, options) + comp = replaceCarets(comp, options) + debug('caret', comp) + comp = replaceTildes(comp, options) + debug('tildes', comp) + comp = replaceXRanges(comp, options) + debug('xrange', comp) + comp = replaceStars(comp, options) + debug('stars', comp) + return comp +} + +const isX = id => !id || id.toLowerCase() === 'x' || id === '*' + +const invalidXRangeOrder = (M, m, p) => ( + (isX(M) && !isX(m)) || + (isX(m) && p && !isX(p)) +) + +// ~, ~> --> * (any, kinda silly) +// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0 +// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0 +// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0 +// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0 +// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0 +// ~0.0.1 --> >=0.0.1 <0.1.0-0 +const replaceTildes = (comp, options) => { + return comp + .trim() + .split(/\s+/) + .map((c) => replaceTilde(c, options)) + .join(' ') +} + +const replaceTilde = (comp, options) => { + const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE] + // if we're including prereleases in the match, then the lower bound is + // -0, the lowest possible prerelease value, just like x-ranges and carets. + // this keeps `~1.2` equivalent to the `1.2.x` x-range it's documented as. + const z = options.includePrerelease ? '-0' : '' + return comp.replace(r, (_, M, m, p, pr) => { + debug('tilde', comp, _, M, m, p, pr) + let ret + + if (isX(M)) { + ret = '' + } else if (isX(m)) { + ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0` + } else if (isX(p)) { + // ~1.2 == >=1.2.0 <1.3.0-0 + ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0` + } else if (pr) { + debug('replaceTilde pr', pr) + ret = `>=${M}.${m}.${p}-${pr + } <${M}.${+m + 1}.0-0` + } else { + // ~1.2.3 == >=1.2.3 <1.3.0-0 + ret = `>=${M}.${m}.${p + } <${M}.${+m + 1}.0-0` + } + + debug('tilde return', ret) + return ret + }) +} + +// ^ --> * (any, kinda silly) +// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0-0 +// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0-0 +// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0 +// ^1.2.3 --> >=1.2.3 <2.0.0-0 +// ^1.2.0 --> >=1.2.0 <2.0.0-0 +// ^0.0.1 --> >=0.0.1 <0.0.2-0 +// ^0.1.0 --> >=0.1.0 <0.2.0-0 +const replaceCarets = (comp, options) => { + return comp + .trim() + .split(/\s+/) + .map((c) => replaceCaret(c, options)) + .join(' ') +} + +const replaceCaret = (comp, options) => { + debug('caret', comp, options) + const r = options.loose ? re[t.CARETLOOSE] : re[t.CARET] + const z = options.includePrerelease ? '-0' : '' + return comp.replace(r, (_, M, m, p, pr) => { + debug('caret', comp, _, M, m, p, pr) + let ret + + if (isX(M)) { + ret = '' + } else if (isX(m)) { + ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0` + } else if (isX(p)) { + if (M === '0') { + ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0` + } else { + ret = `>=${M}.${m}.0${z} <${+M + 1}.0.0-0` + } + } else if (pr) { + debug('replaceCaret pr', pr) + if (M === '0') { + if (m === '0') { + ret = `>=${M}.${m}.${p}-${pr + } <${M}.${m}.${+p + 1}-0` + } else { + ret = `>=${M}.${m}.${p}-${pr + } <${M}.${+m + 1}.0-0` + } + } else { + ret = `>=${M}.${m}.${p}-${pr + } <${+M + 1}.0.0-0` + } + } else { + debug('no pr') + if (M === '0') { + if (m === '0') { + ret = `>=${M}.${m}.${p + } <${M}.${m}.${+p + 1}-0` + } else { + ret = `>=${M}.${m}.${p + } <${M}.${+m + 1}.0-0` + } + } else { + ret = `>=${M}.${m}.${p + } <${+M + 1}.0.0-0` + } + } + + debug('caret return', ret) + return ret + }) +} + +const replaceXRanges = (comp, options) => { + debug('replaceXRanges', comp, options) + return comp + .split(/\s+/) + .map((c) => replaceXRange(c, options)) + .join(' ') +} + +const replaceXRange = (comp, options) => { + comp = comp.trim() + const r = options.loose ? re[t.XRANGELOOSE] : re[t.XRANGE] + return comp.replace(r, (ret, gtlt, M, m, p, pr) => { + debug('xRange', comp, ret, gtlt, M, m, p, pr) + if (invalidXRangeOrder(M, m, p)) { + return comp + } + + const xM = isX(M) + const xm = xM || isX(m) + const xp = xm || isX(p) + const anyX = xp + + if (gtlt === '=' && anyX) { + gtlt = '' + } + + // if we're including prereleases in the match, then we need + // to fix this to -0, the lowest possible prerelease value + pr = options.includePrerelease ? '-0' : '' + + if (xM) { + if (gtlt === '>' || gtlt === '<') { + // nothing is allowed + ret = '<0.0.0-0' + } else { + // nothing is forbidden + ret = '*' + } + } else if (gtlt && anyX) { + // we know patch is an x, because we have any x at all. + // replace X with 0 + if (xm) { + m = 0 + } + p = 0 + + if (gtlt === '>') { + // >1 => >=2.0.0 + // >1.2 => >=1.3.0 + gtlt = '>=' + if (xm) { + M = +M + 1 + m = 0 + p = 0 + } else { + m = +m + 1 + p = 0 + } + } else if (gtlt === '<=') { + // <=0.7.x is actually <0.8.0, since any 0.7.x should + // pass. Similarly, <=7.x is actually <8.0.0, etc. + gtlt = '<' + if (xm) { + M = +M + 1 + } else { + m = +m + 1 + } + } + + if (gtlt === '<') { + pr = '-0' + } + + ret = `${gtlt + M}.${m}.${p}${pr}` + } else if (xm) { + ret = `>=${M}.0.0${pr} <${+M + 1}.0.0-0` + } else if (xp) { + ret = `>=${M}.${m}.0${pr + } <${M}.${+m + 1}.0-0` + } + + debug('xRange return', ret) + + return ret + }) +} + +// Because * is AND-ed with everything else in the comparator, +// and '' means "any version", just remove the *s entirely. +const replaceStars = (comp, options) => { + debug('replaceStars', comp, options) + // Looseness is ignored here. star is always as loose as it gets! + return comp + .trim() + .replace(re[t.STAR], '') +} + +const replaceGTE0 = (comp, options) => { + debug('replaceGTE0', comp, options) + return comp + .trim() + .replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '') +} + +// This function is passed to string.replace(re[t.HYPHENRANGE]) +// M, m, patch, prerelease, build +// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 +// 1.2.3 - 3.4 => >=1.2.0 <3.5.0-0 Any 3.4.x will do +// 1.2 - 3.4 => >=1.2.0 <3.5.0-0 +// TODO build? +const hyphenReplace = incPr => ($0, + from, fM, fm, fp, fpr, fb, + to, tM, tm, tp, tpr) => { + if (isX(fM)) { + from = '' + } else if (isX(fm)) { + from = `>=${fM}.0.0${incPr ? '-0' : ''}` + } else if (isX(fp)) { + from = `>=${fM}.${fm}.0${incPr ? '-0' : ''}` + } else if (fpr) { + from = `>=${from}` + } else { + from = `>=${from}${incPr ? '-0' : ''}` + } + + if (isX(tM)) { + to = '' + } else if (isX(tm)) { + to = `<${+tM + 1}.0.0-0` + } else if (isX(tp)) { + to = `<${tM}.${+tm + 1}.0-0` + } else if (tpr) { + to = `<=${tM}.${tm}.${tp}-${tpr}` + } else if (incPr) { + to = `<${tM}.${tm}.${+tp + 1}-0` + } else { + to = `<=${to}` + } + + return `${from} ${to}`.trim() +} + +const testSet = (set, version, options) => { + for (let i = 0; i < set.length; i++) { + if (!set[i].test(version)) { + return false + } + } + + if (version.prerelease.length && !options.includePrerelease) { + // Find the set of versions that are allowed to have prereleases + // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 + // That should allow `1.2.3-pr.2` to pass. + // However, `1.2.4-alpha.notready` should NOT be allowed, + // even though it's within the range set by the comparators. + for (let i = 0; i < set.length; i++) { + debug(set[i].semver) + if (set[i].semver === Comparator.ANY) { + continue + } + + if (set[i].semver.prerelease.length > 0) { + const allowed = set[i].semver + if (allowed.major === version.major && + allowed.minor === version.minor && + allowed.patch === version.patch) { + return true + } + } + } + + // Version has a -pre, but it's not one of the ones we like. + return false + } + + return true +} + + +/***/ }), + +/***/ 7163: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const debug = __nccwpck_require__(1159) +const { MAX_LENGTH, MAX_SAFE_INTEGER } = __nccwpck_require__(5101) +const { safeRe: re, t } = __nccwpck_require__(5471) + +const parseOptions = __nccwpck_require__(356) +const { compareIdentifiers } = __nccwpck_require__(3348) + +const isPrereleaseIdentifier = (prerelease, identifier) => { + const identifiers = identifier.split('.') + if (identifiers.length > prerelease.length) { + return false + } + + for (let i = 0; i < identifiers.length; i++) { + if (compareIdentifiers(prerelease[i], identifiers[i]) !== 0) { + return false + } + } + + return true +} + +class SemVer { + constructor (version, options) { + options = parseOptions(options) + + if (version instanceof SemVer) { + if (version.loose === !!options.loose && + version.includePrerelease === !!options.includePrerelease) { + return version + } else { + version = version.version + } + } else if (typeof version !== 'string') { + throw new TypeError(`Invalid version. Must be a string. Got type "${typeof version}".`) + } + + if (version.length > MAX_LENGTH) { + throw new TypeError( + `version is longer than ${MAX_LENGTH} characters` + ) + } + + debug('SemVer', version, options) + this.options = options + this.loose = !!options.loose + // this isn't actually relevant for versions, but keep it so that we + // don't run into trouble passing this.options around. + this.includePrerelease = !!options.includePrerelease + + const m = version.trim().match(options.loose ? re[t.LOOSE] : re[t.FULL]) + + if (!m) { + throw new TypeError(`Invalid Version: ${version}`) + } + + this.raw = version + + // these are actually numbers + this.major = +m[1] + this.minor = +m[2] + this.patch = +m[3] + + if (this.major > MAX_SAFE_INTEGER || this.major < 0) { + throw new TypeError('Invalid major version') + } + + if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { + throw new TypeError('Invalid minor version') + } + + if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { + throw new TypeError('Invalid patch version') + } + + // numberify any prerelease numeric ids + if (!m[4]) { + this.prerelease = [] + } else { + this.prerelease = m[4].split('.').map((id) => { + if (/^[0-9]+$/.test(id)) { + const num = +id + if (num >= 0 && num < MAX_SAFE_INTEGER) { + return num + } + } + return id + }) + } + + this.build = m[5] ? m[5].split('.') : [] + this.format() + } + + format () { + this.version = `${this.major}.${this.minor}.${this.patch}` + if (this.prerelease.length) { + this.version += `-${this.prerelease.join('.')}` + } + return this.version + } + + toString () { + return this.version + } + + compare (other) { + debug('SemVer.compare', this.version, this.options, other) + if (!(other instanceof SemVer)) { + if (typeof other === 'string' && other === this.version) { + return 0 + } + other = new SemVer(other, this.options) + } + + if (other.version === this.version) { + return 0 + } + + return this.compareMain(other) || this.comparePre(other) + } + + compareMain (other) { + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.options) + } + + if (this.major < other.major) { + return -1 + } + if (this.major > other.major) { + return 1 + } + if (this.minor < other.minor) { + return -1 + } + if (this.minor > other.minor) { + return 1 + } + if (this.patch < other.patch) { + return -1 + } + if (this.patch > other.patch) { + return 1 + } + return 0 + } + + comparePre (other) { + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.options) + } + + // NOT having a prerelease is > having one + if (this.prerelease.length && !other.prerelease.length) { + return -1 + } else if (!this.prerelease.length && other.prerelease.length) { + return 1 + } else if (!this.prerelease.length && !other.prerelease.length) { + return 0 + } + + let i = 0 + do { + const a = this.prerelease[i] + const b = other.prerelease[i] + debug('prerelease compare', i, a, b) + if (a === undefined && b === undefined) { + return 0 + } else if (b === undefined) { + return 1 + } else if (a === undefined) { + return -1 + } else if (a === b) { + continue + } else { + return compareIdentifiers(a, b) + } + } while (++i) + } + + compareBuild (other) { + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.options) + } + + let i = 0 + do { + const a = this.build[i] + const b = other.build[i] + debug('build compare', i, a, b) + if (a === undefined && b === undefined) { + return 0 + } else if (b === undefined) { + return 1 + } else if (a === undefined) { + return -1 + } else if (a === b) { + continue + } else { + return compareIdentifiers(a, b) + } + } while (++i) + } + + // preminor will bump the version up to the next minor release, and immediately + // down to pre-release. premajor and prepatch work the same way. + inc (release, identifier, identifierBase) { + if (release.startsWith('pre')) { + if (!identifier && identifierBase === false) { + throw new Error('invalid increment argument: identifier is empty') + } + // Avoid an invalid semver results + if (identifier) { + const match = `-${identifier}`.match(this.options.loose ? re[t.PRERELEASELOOSE] : re[t.PRERELEASE]) + if (!match || match[1] !== identifier) { + throw new Error(`invalid identifier: ${identifier}`) + } + } + } + + switch (release) { + case 'premajor': + this.prerelease.length = 0 + this.patch = 0 + this.minor = 0 + this.major++ + this.inc('pre', identifier, identifierBase) + break + case 'preminor': + this.prerelease.length = 0 + this.patch = 0 + this.minor++ + this.inc('pre', identifier, identifierBase) + break + case 'prepatch': + // If this is already a prerelease, it will bump to the next version + // drop any prereleases that might already exist, since they are not + // relevant at this point. + this.prerelease.length = 0 + this.inc('patch', identifier, identifierBase) + this.inc('pre', identifier, identifierBase) + break + // If the input is a non-prerelease version, this acts the same as + // prepatch. + case 'prerelease': + if (this.prerelease.length === 0) { + this.inc('patch', identifier, identifierBase) + } + this.inc('pre', identifier, identifierBase) + break + case 'release': + if (this.prerelease.length === 0) { + throw new Error(`version ${this.raw} is not a prerelease`) + } + this.prerelease.length = 0 + break + + case 'major': + // If this is a pre-major version, bump up to the same major version. + // Otherwise increment major. + // 1.0.0-5 bumps to 1.0.0 + // 1.1.0 bumps to 2.0.0 + if ( + this.minor !== 0 || + this.patch !== 0 || + this.prerelease.length === 0 + ) { + this.major++ + } + this.minor = 0 + this.patch = 0 + this.prerelease = [] + break + case 'minor': + // If this is a pre-minor version, bump up to the same minor version. + // Otherwise increment minor. + // 1.2.0-5 bumps to 1.2.0 + // 1.2.1 bumps to 1.3.0 + if (this.patch !== 0 || this.prerelease.length === 0) { + this.minor++ + } + this.patch = 0 + this.prerelease = [] + break + case 'patch': + // If this is not a pre-release version, it will increment the patch. + // If it is a pre-release it will bump up to the same patch version. + // 1.2.0-5 patches to 1.2.0 + // 1.2.0 patches to 1.2.1 + if (this.prerelease.length === 0) { + this.patch++ + } + this.prerelease = [] + break + // This probably shouldn't be used publicly. + // 1.0.0 'pre' would become 1.0.0-0 which is the wrong direction. + case 'pre': { + const base = Number(identifierBase) ? 1 : 0 + + if (this.prerelease.length === 0) { + this.prerelease = [base] + } else { + let i = this.prerelease.length + while (--i >= 0) { + if (typeof this.prerelease[i] === 'number') { + this.prerelease[i]++ + i = -2 + } + } + if (i === -1) { + // didn't increment anything + if (identifier === this.prerelease.join('.') && identifierBase === false) { + throw new Error('invalid increment argument: identifier already exists') + } + this.prerelease.push(base) + } + } + if (identifier) { + // 1.2.0-beta.1 bumps to 1.2.0-beta.2, + // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 + let prerelease = [identifier, base] + if (identifierBase === false) { + prerelease = [identifier] + } + if (isPrereleaseIdentifier(this.prerelease, identifier)) { + const prereleaseBase = this.prerelease[identifier.split('.').length] + if (isNaN(prereleaseBase)) { + this.prerelease = prerelease + } + } else { + this.prerelease = prerelease + } + } + break + } + default: + throw new Error(`invalid increment argument: ${release}`) + } + this.raw = this.format() + if (this.build.length) { + this.raw += `+${this.build.join('.')}` + } + return this + } +} + +module.exports = SemVer + + +/***/ }), + +/***/ 1799: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const parse = __nccwpck_require__(6353) +const clean = (version, options) => { + const s = parse(version.trim().replace(/^[=v]+/, ''), options) + return s ? s.version : null +} +module.exports = clean + + +/***/ }), + +/***/ 8646: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const eq = __nccwpck_require__(5082) +const neq = __nccwpck_require__(4974) +const gt = __nccwpck_require__(6599) +const gte = __nccwpck_require__(1236) +const lt = __nccwpck_require__(3872) +const lte = __nccwpck_require__(6717) + +const cmp = (a, op, b, loose) => { + switch (op) { + case '===': + if (typeof a === 'object') { + a = a.version + } + if (typeof b === 'object') { + b = b.version + } + return a === b + + case '!==': + if (typeof a === 'object') { + a = a.version + } + if (typeof b === 'object') { + b = b.version + } + return a !== b + + case '': + case '=': + case '==': + return eq(a, b, loose) + + case '!=': + return neq(a, b, loose) + + case '>': + return gt(a, b, loose) + + case '>=': + return gte(a, b, loose) + + case '<': + return lt(a, b, loose) + + case '<=': + return lte(a, b, loose) + + default: + throw new TypeError(`Invalid operator: ${op}`) + } +} +module.exports = cmp + + +/***/ }), + +/***/ 5385: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const SemVer = __nccwpck_require__(7163) +const parse = __nccwpck_require__(6353) +const { safeRe: re, t } = __nccwpck_require__(5471) + +const coerce = (version, options) => { + if (version instanceof SemVer) { + return version + } + + if (typeof version === 'number') { + version = String(version) + } + + if (typeof version !== 'string') { + return null + } + + options = options || {} + + let match = null + if (!options.rtl) { + match = version.match(options.includePrerelease ? re[t.COERCEFULL] : re[t.COERCE]) + } else { + // Find the right-most coercible string that does not share + // a terminus with a more left-ward coercible string. + // Eg, '1.2.3.4' wants to coerce '2.3.4', not '3.4' or '4' + // With includePrerelease option set, '1.2.3.4-rc' wants to coerce '2.3.4-rc', not '2.3.4' + // + // Walk through the string checking with a /g regexp + // Manually set the index so as to pick up overlapping matches. + // Stop when we get a match that ends at the string end, since no + // coercible string can be more right-ward without the same terminus. + const coerceRtlRegex = options.includePrerelease ? re[t.COERCERTLFULL] : re[t.COERCERTL] + let next + while ((next = coerceRtlRegex.exec(version)) && + (!match || match.index + match[0].length !== version.length) + ) { + if (!match || + next.index + next[0].length !== match.index + match[0].length) { + match = next + } + coerceRtlRegex.lastIndex = next.index + next[1].length + next[2].length + } + // leave it in a clean state + coerceRtlRegex.lastIndex = -1 + } + + if (match === null) { + return null + } + + const major = match[2] + const minor = match[3] || '0' + const patch = match[4] || '0' + const prerelease = options.includePrerelease && match[5] ? `-${match[5]}` : '' + const build = options.includePrerelease && match[6] ? `+${match[6]}` : '' + + return parse(`${major}.${minor}.${patch}${prerelease}${build}`, options) +} +module.exports = coerce + + +/***/ }), + +/***/ 7648: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const SemVer = __nccwpck_require__(7163) +const compareBuild = (a, b, loose) => { + const versionA = new SemVer(a, loose) + const versionB = new SemVer(b, loose) + return versionA.compare(versionB) || versionA.compareBuild(versionB) +} +module.exports = compareBuild + + +/***/ }), + +/***/ 6874: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const compare = __nccwpck_require__(8469) +const compareLoose = (a, b) => compare(a, b, true) +module.exports = compareLoose + + +/***/ }), + +/***/ 8469: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const SemVer = __nccwpck_require__(7163) +const compare = (a, b, loose) => + new SemVer(a, loose).compare(new SemVer(b, loose)) + +module.exports = compare + + +/***/ }), + +/***/ 711: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const parse = __nccwpck_require__(6353) + +const diff = (version1, version2) => { + const v1 = parse(version1, null, true) + const v2 = parse(version2, null, true) + const comparison = v1.compare(v2) + + if (comparison === 0) { + return null + } + + const v1Higher = comparison > 0 + const highVersion = v1Higher ? v1 : v2 + const lowVersion = v1Higher ? v2 : v1 + const highHasPre = !!highVersion.prerelease.length + const lowHasPre = !!lowVersion.prerelease.length + + if (lowHasPre && !highHasPre) { + // Going from prerelease -> no prerelease requires some special casing + + // If the low version has only a major, then it will always be a major + // Some examples: + // 1.0.0-1 -> 1.0.0 + // 1.0.0-1 -> 1.1.1 + // 1.0.0-1 -> 2.0.0 + if (!lowVersion.patch && !lowVersion.minor) { + return 'major' + } + + // If the main part has no difference + if (lowVersion.compareMain(highVersion) === 0) { + if (lowVersion.minor && !lowVersion.patch) { + return 'minor' + } + return 'patch' + } + } + + // add the `pre` prefix if we are going to a prerelease version + const prefix = highHasPre ? 'pre' : '' + + if (v1.major !== v2.major) { + return prefix + 'major' + } + + if (v1.minor !== v2.minor) { + return prefix + 'minor' + } + + if (v1.patch !== v2.patch) { + return prefix + 'patch' + } + + // high and low are prereleases + return 'prerelease' +} + +module.exports = diff + + +/***/ }), + +/***/ 5082: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const compare = __nccwpck_require__(8469) +const eq = (a, b, loose) => compare(a, b, loose) === 0 +module.exports = eq + + +/***/ }), + +/***/ 6599: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const compare = __nccwpck_require__(8469) +const gt = (a, b, loose) => compare(a, b, loose) > 0 +module.exports = gt + + +/***/ }), + +/***/ 1236: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const compare = __nccwpck_require__(8469) +const gte = (a, b, loose) => compare(a, b, loose) >= 0 +module.exports = gte + + +/***/ }), + +/***/ 2338: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const SemVer = __nccwpck_require__(7163) + +const inc = (version, release, options, identifier, identifierBase) => { + if (typeof (options) === 'string') { + identifierBase = identifier + identifier = options + options = undefined + } + + try { + return new SemVer( + version instanceof SemVer ? version.version : version, + options + ).inc(release, identifier, identifierBase).version + } catch (er) { + return null + } +} +module.exports = inc + + +/***/ }), + +/***/ 3872: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const compare = __nccwpck_require__(8469) +const lt = (a, b, loose) => compare(a, b, loose) < 0 +module.exports = lt + + +/***/ }), + +/***/ 6717: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const compare = __nccwpck_require__(8469) +const lte = (a, b, loose) => compare(a, b, loose) <= 0 +module.exports = lte + + +/***/ }), + +/***/ 8511: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const SemVer = __nccwpck_require__(7163) +const major = (a, loose) => new SemVer(a, loose).major +module.exports = major + + +/***/ }), + +/***/ 2603: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const SemVer = __nccwpck_require__(7163) +const minor = (a, loose) => new SemVer(a, loose).minor +module.exports = minor + + +/***/ }), + +/***/ 4974: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const compare = __nccwpck_require__(8469) +const neq = (a, b, loose) => compare(a, b, loose) !== 0 +module.exports = neq + + +/***/ }), + +/***/ 6353: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const SemVer = __nccwpck_require__(7163) +const parse = (version, options, throwErrors = false) => { + if (version instanceof SemVer) { + return version + } + try { + return new SemVer(version, options) + } catch (er) { + if (!throwErrors) { + return null + } + throw er + } +} + +module.exports = parse + + +/***/ }), + +/***/ 8756: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const SemVer = __nccwpck_require__(7163) +const patch = (a, loose) => new SemVer(a, loose).patch +module.exports = patch + + +/***/ }), + +/***/ 5714: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const parse = __nccwpck_require__(6353) +const prerelease = (version, options) => { + const parsed = parse(version, options) + return (parsed && parsed.prerelease.length) ? parsed.prerelease : null +} +module.exports = prerelease + + +/***/ }), + +/***/ 2173: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const compare = __nccwpck_require__(8469) +const rcompare = (a, b, loose) => compare(b, a, loose) +module.exports = rcompare + + +/***/ }), + +/***/ 7192: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const compareBuild = __nccwpck_require__(7648) +const rsort = (list, loose) => list.sort((a, b) => compareBuild(b, a, loose)) +module.exports = rsort + + +/***/ }), + +/***/ 8011: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Range = __nccwpck_require__(6782) +const satisfies = (version, range, options) => { + try { + range = new Range(range, options) + } catch (er) { + return false + } + return range.test(version) +} +module.exports = satisfies + + +/***/ }), + +/***/ 9872: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const compareBuild = __nccwpck_require__(7648) +const sort = (list, loose) => list.sort((a, b) => compareBuild(a, b, loose)) +module.exports = sort + + +/***/ }), + +/***/ 6114: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const parse = __nccwpck_require__(6353) +const constants = __nccwpck_require__(5101) +const SemVer = __nccwpck_require__(7163) + +const truncate = (version, truncation, options) => { + if (!constants.RELEASE_TYPES.includes(truncation)) { + return null + } + + const clonedVersion = cloneInputVersion(version, options) + return clonedVersion && doTruncation(clonedVersion, truncation) +} + +const cloneInputVersion = (version, options) => { + const versionStringToParse = ( + version instanceof SemVer ? version.version : version + ) + + return parse(versionStringToParse, options) +} + +const doTruncation = (version, truncation) => { + if (isPrerelease(truncation)) { + return version.version + } + + version.prerelease = [] + + switch (truncation) { + case 'major': + version.minor = 0 + version.patch = 0 + break + case 'minor': + version.patch = 0 + break + } + + return version.format() +} + +const isPrerelease = (type) => { + return type.startsWith('pre') +} + +module.exports = truncate + + +/***/ }), + +/***/ 8780: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const parse = __nccwpck_require__(6353) +const valid = (version, options) => { + const v = parse(version, options) + return v ? v.version : null +} +module.exports = valid + + +/***/ }), + +/***/ 2088: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +// just pre-load all the stuff that index.js lazily exports +const internalRe = __nccwpck_require__(5471) +const constants = __nccwpck_require__(5101) +const SemVer = __nccwpck_require__(7163) +const identifiers = __nccwpck_require__(3348) +const parse = __nccwpck_require__(6353) +const valid = __nccwpck_require__(8780) +const clean = __nccwpck_require__(1799) +const inc = __nccwpck_require__(2338) +const diff = __nccwpck_require__(711) +const major = __nccwpck_require__(8511) +const minor = __nccwpck_require__(2603) +const patch = __nccwpck_require__(8756) +const prerelease = __nccwpck_require__(5714) +const compare = __nccwpck_require__(8469) +const rcompare = __nccwpck_require__(2173) +const compareLoose = __nccwpck_require__(6874) +const compareBuild = __nccwpck_require__(7648) +const sort = __nccwpck_require__(9872) +const rsort = __nccwpck_require__(7192) +const gt = __nccwpck_require__(6599) +const lt = __nccwpck_require__(3872) +const eq = __nccwpck_require__(5082) +const neq = __nccwpck_require__(4974) +const gte = __nccwpck_require__(1236) +const lte = __nccwpck_require__(6717) +const cmp = __nccwpck_require__(8646) +const coerce = __nccwpck_require__(5385) +const truncate = __nccwpck_require__(6114) +const Comparator = __nccwpck_require__(9379) +const Range = __nccwpck_require__(6782) +const satisfies = __nccwpck_require__(8011) +const toComparators = __nccwpck_require__(4750) +const maxSatisfying = __nccwpck_require__(5574) +const minSatisfying = __nccwpck_require__(8595) +const minVersion = __nccwpck_require__(1866) +const validRange = __nccwpck_require__(4737) +const outside = __nccwpck_require__(280) +const gtr = __nccwpck_require__(2276) +const ltr = __nccwpck_require__(5213) +const intersects = __nccwpck_require__(3465) +const simplifyRange = __nccwpck_require__(2028) +const subset = __nccwpck_require__(1489) +module.exports = { + parse, + valid, + clean, + inc, + diff, + major, + minor, + patch, + prerelease, + compare, + rcompare, + compareLoose, + compareBuild, + sort, + rsort, + gt, + lt, + eq, + neq, + gte, + lte, + cmp, + coerce, + truncate, + Comparator, + Range, + satisfies, + toComparators, + maxSatisfying, + minSatisfying, + minVersion, + validRange, + outside, + gtr, + ltr, + intersects, + simplifyRange, + subset, + SemVer, + re: internalRe.re, + src: internalRe.src, + tokens: internalRe.t, + SEMVER_SPEC_VERSION: constants.SEMVER_SPEC_VERSION, + RELEASE_TYPES: constants.RELEASE_TYPES, + compareIdentifiers: identifiers.compareIdentifiers, + rcompareIdentifiers: identifiers.rcompareIdentifiers, +} + + +/***/ }), + +/***/ 5101: +/***/ ((module) => { + +"use strict"; + + +// Note: this is the semver.org version of the spec that it implements +// Not necessarily the package version of this code. +const SEMVER_SPEC_VERSION = '2.0.0' + +const MAX_LENGTH = 256 +const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || +/* istanbul ignore next */ 9007199254740991 + +// Max safe segment length for coercion. +const MAX_SAFE_COMPONENT_LENGTH = 16 + +// Max safe length for a build identifier. The max length minus 6 characters for +// the shortest version with a build 0.0.0+BUILD. +const MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6 + +const RELEASE_TYPES = [ + 'major', + 'premajor', + 'minor', + 'preminor', + 'patch', + 'prepatch', + 'prerelease', +] + +module.exports = { + MAX_LENGTH, + MAX_SAFE_COMPONENT_LENGTH, + MAX_SAFE_BUILD_LENGTH, + MAX_SAFE_INTEGER, + RELEASE_TYPES, + SEMVER_SPEC_VERSION, + FLAG_INCLUDE_PRERELEASE: 0b001, + FLAG_LOOSE: 0b010, +} + + +/***/ }), + +/***/ 1159: +/***/ ((module) => { + +"use strict"; + + +const debug = ( + typeof process === 'object' && + process.env && + process.env.NODE_DEBUG && + /\bsemver\b/i.test(process.env.NODE_DEBUG) +) ? (...args) => console.error('SEMVER', ...args) + : () => {} + +module.exports = debug + + +/***/ }), + +/***/ 3348: +/***/ ((module) => { + +"use strict"; + + +const numeric = /^[0-9]+$/ +const compareIdentifiers = (a, b) => { + if (typeof a === 'number' && typeof b === 'number') { + return a === b ? 0 : a < b ? -1 : 1 + } + + const anum = numeric.test(a) + const bnum = numeric.test(b) + + if (anum && bnum) { + a = +a + b = +b + } + + return a === b ? 0 + : (anum && !bnum) ? -1 + : (bnum && !anum) ? 1 + : a < b ? -1 + : 1 +} + +const rcompareIdentifiers = (a, b) => compareIdentifiers(b, a) + +module.exports = { + compareIdentifiers, + rcompareIdentifiers, +} + + +/***/ }), + +/***/ 1383: +/***/ ((module) => { + +"use strict"; + + +class LRUCache { + constructor () { + this.max = 1000 + this.map = new Map() + } + + get (key) { + const value = this.map.get(key) + if (value === undefined) { + return undefined + } else { + // Remove the key from the map and add it to the end + this.map.delete(key) + this.map.set(key, value) + return value + } + } + + delete (key) { + return this.map.delete(key) + } + + set (key, value) { + const deleted = this.delete(key) + + if (!deleted && value !== undefined) { + // If cache is full, delete the least recently used item + if (this.map.size >= this.max) { + const firstKey = this.map.keys().next().value + this.delete(firstKey) + } + + this.map.set(key, value) + } + + return this + } +} + +module.exports = LRUCache + + +/***/ }), + +/***/ 356: +/***/ ((module) => { + +"use strict"; + + +// parse out just the options we care about +const looseOption = Object.freeze({ loose: true }) +const emptyOpts = Object.freeze({ }) +const parseOptions = options => { + if (!options) { + return emptyOpts + } + + if (typeof options !== 'object') { + return looseOption + } + + return options +} +module.exports = parseOptions + + +/***/ }), + +/***/ 5471: +/***/ ((module, exports, __nccwpck_require__) => { + +"use strict"; + + +const { + MAX_SAFE_COMPONENT_LENGTH, + MAX_SAFE_BUILD_LENGTH, + MAX_LENGTH, +} = __nccwpck_require__(5101) +const debug = __nccwpck_require__(1159) +exports = module.exports = {} + +// The actual regexps go on exports.re +const re = exports.re = [] +const safeRe = exports.safeRe = [] +const src = exports.src = [] +const safeSrc = exports.safeSrc = [] +const t = exports.t = {} +let R = 0 + +const LETTERDASHNUMBER = '[a-zA-Z0-9-]' + +// Replace some greedy regex tokens to prevent regex dos issues. These regex are +// used internally via the safeRe object since all inputs in this library get +// normalized first to trim and collapse all extra whitespace. The original +// regexes are exported for userland consumption and lower level usage. A +// future breaking change could export the safer regex only with a note that +// all input should have extra whitespace removed. +const safeRegexReplacements = [ + ['\\s', 1], + ['\\d', MAX_LENGTH], + [LETTERDASHNUMBER, MAX_SAFE_BUILD_LENGTH], +] + +const makeSafeRegex = (value) => { + for (const [token, max] of safeRegexReplacements) { + value = value + .split(`${token}*`).join(`${token}{0,${max}}`) + .split(`${token}+`).join(`${token}{1,${max}}`) + } + return value +} + +const createToken = (name, value, isGlobal) => { + const safe = makeSafeRegex(value) + const index = R++ + debug(name, index, value) + t[name] = index + src[index] = value + safeSrc[index] = safe + re[index] = new RegExp(value, isGlobal ? 'g' : undefined) + safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined) +} + +// The following Regular Expressions can be used for tokenizing, +// validating, and parsing SemVer version strings. + +// ## Numeric Identifier +// A single `0`, or a non-zero digit followed by zero or more digits. + +createToken('NUMERICIDENTIFIER', '0|[1-9]\\d*') +createToken('NUMERICIDENTIFIERLOOSE', '\\d+') + +// ## Non-numeric Identifier +// Zero or more digits, followed by a letter or hyphen, and then zero or +// more letters, digits, or hyphens. + +createToken('NONNUMERICIDENTIFIER', `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`) + +// ## Main Version +// Three dot-separated numeric identifiers. + +createToken('MAINVERSION', `(${src[t.NUMERICIDENTIFIER]})\\.` + + `(${src[t.NUMERICIDENTIFIER]})\\.` + + `(${src[t.NUMERICIDENTIFIER]})`) + +createToken('MAINVERSIONLOOSE', `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + + `(${src[t.NUMERICIDENTIFIERLOOSE]})\\.` + + `(${src[t.NUMERICIDENTIFIERLOOSE]})`) + +// ## Pre-release Version Identifier +// A numeric identifier, or a non-numeric identifier. +// Non-numeric identifiers include numeric identifiers but can be longer. +// Therefore non-numeric identifiers must go first. + +createToken('PRERELEASEIDENTIFIER', `(?:${src[t.NONNUMERICIDENTIFIER] +}|${src[t.NUMERICIDENTIFIER]})`) + +createToken('PRERELEASEIDENTIFIERLOOSE', `(?:${src[t.NONNUMERICIDENTIFIER] +}|${src[t.NUMERICIDENTIFIERLOOSE]})`) + +// ## Pre-release Version +// Hyphen, followed by one or more dot-separated pre-release version +// identifiers. + +createToken('PRERELEASE', `(?:-(${src[t.PRERELEASEIDENTIFIER] +}(?:\\.${src[t.PRERELEASEIDENTIFIER]})*))`) + +createToken('PRERELEASELOOSE', `(?:-?(${src[t.PRERELEASEIDENTIFIERLOOSE] +}(?:\\.${src[t.PRERELEASEIDENTIFIERLOOSE]})*))`) + +// ## Build Metadata Identifier +// Any combination of digits, letters, or hyphens. + +createToken('BUILDIDENTIFIER', `${LETTERDASHNUMBER}+`) + +// ## Build Metadata +// Plus sign, followed by one or more period-separated build metadata +// identifiers. + +createToken('BUILD', `(?:\\+(${src[t.BUILDIDENTIFIER] +}(?:\\.${src[t.BUILDIDENTIFIER]})*))`) + +// ## Full Version String +// A main version, followed optionally by a pre-release version and +// build metadata. + +// Note that the only major, minor, patch, and pre-release sections of +// the version string are capturing groups. The build metadata is not a +// capturing group, because it should not ever be used in version +// comparison. + +createToken('FULLPLAIN', `v?${src[t.MAINVERSION] +}${src[t.PRERELEASE]}?${ + src[t.BUILD]}?`) + +createToken('FULL', `^${src[t.FULLPLAIN]}$`) + +// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. +// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty +// common in the npm registry. +createToken('LOOSEPLAIN', `[v=\\s]*${src[t.MAINVERSIONLOOSE] +}${src[t.PRERELEASELOOSE]}?${ + src[t.BUILD]}?`) + +createToken('LOOSE', `^${src[t.LOOSEPLAIN]}$`) + +createToken('GTLT', '((?:<|>)?=?)') + +// Something like "2.*" or "1.2.x". +// Note that "x.x" is a valid xRange identifier, meaning "any version" +// Only the first item is strictly required. +createToken('XRANGEIDENTIFIERLOOSE', `${src[t.NUMERICIDENTIFIERLOOSE]}|x|X|\\*`) +createToken('XRANGEIDENTIFIER', `${src[t.NUMERICIDENTIFIER]}|x|X|\\*`) + +createToken('XRANGEPLAIN', `[v=\\s]*(${src[t.XRANGEIDENTIFIER]})` + + `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + + `(?:\\.(${src[t.XRANGEIDENTIFIER]})` + + `(?:${src[t.PRERELEASE]})?${ + src[t.BUILD]}?` + + `)?)?`) + +createToken('XRANGEPLAINLOOSE', `[v=\\s]*(${src[t.XRANGEIDENTIFIERLOOSE]})` + + `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + + `(?:\\.(${src[t.XRANGEIDENTIFIERLOOSE]})` + + `(?:${src[t.PRERELEASELOOSE]})?${ + src[t.BUILD]}?` + + `)?)?`) + +createToken('XRANGE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAIN]}$`) +createToken('XRANGELOOSE', `^${src[t.GTLT]}\\s*${src[t.XRANGEPLAINLOOSE]}$`) + +// Coercion. +// Extract anything that could conceivably be a part of a valid semver +createToken('COERCEPLAIN', `${'(^|[^\\d])' + + '(\\d{1,'}${MAX_SAFE_COMPONENT_LENGTH}})` + + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?` + + `(?:\\.(\\d{1,${MAX_SAFE_COMPONENT_LENGTH}}))?`) +createToken('COERCE', `${src[t.COERCEPLAIN]}(?:$|[^\\d])`) +createToken('COERCEFULL', src[t.COERCEPLAIN] + + `(?:${src[t.PRERELEASE]})?` + + `(?:${src[t.BUILD]})?` + + `(?:$|[^\\d])`) +createToken('COERCERTL', src[t.COERCE], true) +createToken('COERCERTLFULL', src[t.COERCEFULL], true) + +// Tilde ranges. +// Meaning is "reasonably at or greater than" +createToken('LONETILDE', '(?:~>?)') + +createToken('TILDETRIM', `(\\s*)${src[t.LONETILDE]}\\s+`, true) +exports.tildeTrimReplace = '$1~' + +createToken('TILDE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAIN]}$`) +createToken('TILDELOOSE', `^${src[t.LONETILDE]}${src[t.XRANGEPLAINLOOSE]}$`) + +// Caret ranges. +// Meaning is "at least and backwards compatible with" +createToken('LONECARET', '(?:\\^)') + +createToken('CARETTRIM', `(\\s*)${src[t.LONECARET]}\\s+`, true) +exports.caretTrimReplace = '$1^' + +createToken('CARET', `^${src[t.LONECARET]}${src[t.XRANGEPLAIN]}$`) +createToken('CARETLOOSE', `^${src[t.LONECARET]}${src[t.XRANGEPLAINLOOSE]}$`) + +// A simple gt/lt/eq thing, or just "" to indicate "any version" +createToken('COMPARATORLOOSE', `^${src[t.GTLT]}\\s*(${src[t.LOOSEPLAIN]})$|^$`) +createToken('COMPARATOR', `^${src[t.GTLT]}\\s*(${src[t.FULLPLAIN]})$|^$`) + +// An expression to strip any whitespace between the gtlt and the thing +// it modifies, so that `> 1.2.3` ==> `>1.2.3` +createToken('COMPARATORTRIM', `(\\s*)${src[t.GTLT] +}\\s*(${src[t.LOOSEPLAIN]}|${src[t.XRANGEPLAIN]})`, true) +exports.comparatorTrimReplace = '$1$2$3' + +// Something like `1.2.3 - 1.2.4` +// Note that these all use the loose form, because they'll be +// checked against either the strict or loose comparator form +// later. +createToken('HYPHENRANGE', `^\\s*(${src[t.XRANGEPLAIN]})` + + `\\s+-\\s+` + + `(${src[t.XRANGEPLAIN]})` + + `\\s*$`) + +createToken('HYPHENRANGELOOSE', `^\\s*(${src[t.XRANGEPLAINLOOSE]})` + + `\\s+-\\s+` + + `(${src[t.XRANGEPLAINLOOSE]})` + + `\\s*$`) + +// Star ranges basically just allow anything at all. +createToken('STAR', '(<|>)?=?\\s*\\*') +// >=0.0.0 is like a star +createToken('GTE0', '^\\s*>=\\s*0\\.0\\.0\\s*$') +createToken('GTE0PRE', '^\\s*>=\\s*0\\.0\\.0-0\\s*$') + + +/***/ }), + +/***/ 2276: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +// Determine if version is greater than all the versions possible in the range. +const outside = __nccwpck_require__(280) +const gtr = (version, range, options) => outside(version, range, '>', options) +module.exports = gtr + + +/***/ }), + +/***/ 3465: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Range = __nccwpck_require__(6782) +const intersects = (r1, r2, options) => { + r1 = new Range(r1, options) + r2 = new Range(r2, options) + return r1.intersects(r2, options) +} +module.exports = intersects + + +/***/ }), + +/***/ 5213: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const outside = __nccwpck_require__(280) +// Determine if version is less than all the versions possible in the range +const ltr = (version, range, options) => outside(version, range, '<', options) +module.exports = ltr + + +/***/ }), + +/***/ 5574: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const SemVer = __nccwpck_require__(7163) +const Range = __nccwpck_require__(6782) + +const maxSatisfying = (versions, range, options) => { + let max = null + let maxSV = null + let rangeObj = null + try { + rangeObj = new Range(range, options) + } catch (er) { + return null + } + versions.forEach((v) => { + if (rangeObj.test(v)) { + // satisfies(v, range, options) + if (!max || maxSV.compare(v) === -1) { + // compare(max, v, true) + max = v + maxSV = new SemVer(max, options) + } + } + }) + return max +} +module.exports = maxSatisfying + + +/***/ }), + +/***/ 8595: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const SemVer = __nccwpck_require__(7163) +const Range = __nccwpck_require__(6782) +const minSatisfying = (versions, range, options) => { + let min = null + let minSV = null + let rangeObj = null + try { + rangeObj = new Range(range, options) + } catch (er) { + return null + } + versions.forEach((v) => { + if (rangeObj.test(v)) { + // satisfies(v, range, options) + if (!min || minSV.compare(v) === 1) { + // compare(min, v, true) + min = v + minSV = new SemVer(min, options) + } + } + }) + return min +} +module.exports = minSatisfying + + +/***/ }), + +/***/ 1866: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const SemVer = __nccwpck_require__(7163) +const Range = __nccwpck_require__(6782) +const gt = __nccwpck_require__(6599) + +const minVersion = (range, loose) => { + range = new Range(range, loose) + + let minver = new SemVer('0.0.0') + if (range.test(minver)) { + return minver + } + + minver = new SemVer('0.0.0-0') + if (range.test(minver)) { + return minver + } + + minver = null + for (let i = 0; i < range.set.length; ++i) { + const comparators = range.set[i] + + let setMin = null + comparators.forEach((comparator) => { + // Clone to avoid manipulating the comparator's semver object. + const compver = new SemVer(comparator.semver.version) + switch (comparator.operator) { + case '>': + if (compver.prerelease.length === 0) { + compver.patch++ + } else { + compver.prerelease.push(0) + } + compver.raw = compver.format() + /* fallthrough */ + case '': + case '>=': + if (!setMin || gt(compver, setMin)) { + setMin = compver + } + break + case '<': + case '<=': + /* Ignore maximum versions */ + break + /* istanbul ignore next */ + default: + throw new Error(`Unexpected operation: ${comparator.operator}`) + } + }) + if (setMin && (!minver || gt(minver, setMin))) { + minver = setMin + } + } + + if (minver && range.test(minver)) { + return minver + } + + return null +} +module.exports = minVersion + + +/***/ }), + +/***/ 280: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const SemVer = __nccwpck_require__(7163) +const Comparator = __nccwpck_require__(9379) +const { ANY } = Comparator +const Range = __nccwpck_require__(6782) +const satisfies = __nccwpck_require__(8011) +const gt = __nccwpck_require__(6599) +const lt = __nccwpck_require__(3872) +const lte = __nccwpck_require__(6717) +const gte = __nccwpck_require__(1236) + +const outside = (version, range, hilo, options) => { + version = new SemVer(version, options) + range = new Range(range, options) + + let gtfn, ltefn, ltfn, comp, ecomp + switch (hilo) { + case '>': + gtfn = gt + ltefn = lte + ltfn = lt + comp = '>' + ecomp = '>=' + break + case '<': + gtfn = lt + ltefn = gte + ltfn = gt + comp = '<' + ecomp = '<=' + break + default: + throw new TypeError('Must provide a hilo val of "<" or ">"') + } + + // If it satisfies the range it is not outside + if (satisfies(version, range, options)) { + return false + } + + // From now on, variable terms are as if we're in "gtr" mode. + // but note that everything is flipped for the "ltr" function. + + for (let i = 0; i < range.set.length; ++i) { + const comparators = range.set[i] + + let high = null + let low = null + + comparators.forEach((comparator) => { + if (comparator.semver === ANY) { + comparator = new Comparator('>=0.0.0') + } + high = high || comparator + low = low || comparator + if (gtfn(comparator.semver, high.semver, options)) { + high = comparator + } else if (ltfn(comparator.semver, low.semver, options)) { + low = comparator + } + }) + + // If the edge version comparator has a operator then our version + // isn't outside it + if (high.operator === comp || high.operator === ecomp) { + return false + } + + // If the lowest version comparator has an operator and our version + // is less than it then it isn't higher than the range + if ((!low.operator || low.operator === comp) && + ltefn(version, low.semver)) { + return false + } else if (low.operator === ecomp && ltfn(version, low.semver)) { + return false + } + } + return true +} + +module.exports = outside + + +/***/ }), + +/***/ 2028: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +// given a set of versions and a range, create a "simplified" range +// that includes the same versions that the original range does +// If the original range is shorter than the simplified one, return that. +const satisfies = __nccwpck_require__(8011) +const compare = __nccwpck_require__(8469) +module.exports = (versions, range, options) => { + const set = [] + let first = null + let prev = null + const v = versions.sort((a, b) => compare(a, b, options)) + for (const version of v) { + const included = satisfies(version, range, options) + if (included) { + prev = version + if (!first) { + first = version + } + } else { + if (prev) { + set.push([first, prev]) + } + prev = null + first = null + } + } + if (first) { + set.push([first, null]) + } + + const ranges = [] + for (const [min, max] of set) { + if (min === max) { + ranges.push(min) + } else if (!max && min === v[0]) { + ranges.push('*') + } else if (!max) { + ranges.push(`>=${min}`) + } else if (min === v[0]) { + ranges.push(`<=${max}`) + } else { + ranges.push(`${min} - ${max}`) + } + } + const simplified = ranges.join(' || ') + const original = typeof range.raw === 'string' ? range.raw : String(range) + return simplified.length < original.length ? simplified : range +} + + +/***/ }), + +/***/ 1489: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Range = __nccwpck_require__(6782) +const Comparator = __nccwpck_require__(9379) +const { ANY } = Comparator +const satisfies = __nccwpck_require__(8011) +const compare = __nccwpck_require__(8469) + +// Complex range `r1 || r2 || ...` is a subset of `R1 || R2 || ...` iff: +// - Every simple range `r1, r2, ...` is a null set, OR +// - Every simple range `r1, r2, ...` which is not a null set is a subset of +// some `R1, R2, ...` +// +// Simple range `c1 c2 ...` is a subset of simple range `C1 C2 ...` iff: +// - If c is only the ANY comparator +// - If C is only the ANY comparator, return true +// - Else if in prerelease mode, return false +// - else replace c with `[>=0.0.0]` +// - If C is only the ANY comparator +// - if in prerelease mode, return true +// - else replace C with `[>=0.0.0]` +// - Let EQ be the set of = comparators in c +// - If EQ is more than one, return true (null set) +// - Let GT be the highest > or >= comparator in c +// - Let LT be the lowest < or <= comparator in c +// - If GT and LT, and GT.semver > LT.semver, return true (null set) +// - If any C is a = range, and GT or LT are set, return false +// - If EQ +// - If GT, and EQ does not satisfy GT, return true (null set) +// - If LT, and EQ does not satisfy LT, return true (null set) +// - If EQ satisfies every C, return true +// - Else return false +// - If GT +// - If GT.semver is lower than any > or >= comp in C, return false +// - If GT is >=, and GT.semver does not satisfy every C, return false +// - If GT.semver has a prerelease, and not in prerelease mode +// - If no C has a prerelease and the GT.semver tuple, return false +// - If LT +// - If LT.semver is greater than any < or <= comp in C, return false +// - If LT is <=, and LT.semver does not satisfy every C, return false +// - If LT.semver has a prerelease, and not in prerelease mode +// - If no C has a prerelease and the LT.semver tuple, return false +// - Else return true + +const subset = (sub, dom, options = {}) => { + if (sub === dom) { + return true + } + + sub = new Range(sub, options) + dom = new Range(dom, options) + let sawNonNull = false + + OUTER: for (const simpleSub of sub.set) { + for (const simpleDom of dom.set) { + const isSub = simpleSubset(simpleSub, simpleDom, options) + sawNonNull = sawNonNull || isSub !== null + if (isSub) { + continue OUTER + } + } + // the null set is a subset of everything, but null simple ranges in + // a complex range should be ignored. so if we saw a non-null range, + // then we know this isn't a subset, but if EVERY simple range was null, + // then it is a subset. + if (sawNonNull) { + return false + } + } + return true +} + +const minimumVersionWithPreRelease = [new Comparator('>=0.0.0-0')] +const minimumVersion = [new Comparator('>=0.0.0')] + +const simpleSubset = (sub, dom, options) => { + if (sub === dom) { + return true + } + + if (sub.length === 1 && sub[0].semver === ANY) { + if (dom.length === 1 && dom[0].semver === ANY) { + return true + } else if (options.includePrerelease) { + sub = minimumVersionWithPreRelease + } else { + sub = minimumVersion + } + } + + if (dom.length === 1 && dom[0].semver === ANY) { + if (options.includePrerelease) { + return true + } else { + dom = minimumVersion + } + } + + const eqSet = new Set() + let gt, lt + for (const c of sub) { + if (c.operator === '>' || c.operator === '>=') { + gt = higherGT(gt, c, options) + } else if (c.operator === '<' || c.operator === '<=') { + lt = lowerLT(lt, c, options) + } else { + eqSet.add(c.semver) + } + } + + if (eqSet.size > 1) { + return null + } + + let gtltComp + if (gt && lt) { + gtltComp = compare(gt.semver, lt.semver, options) + if (gtltComp > 0) { + return null + } else if (gtltComp === 0 && (gt.operator !== '>=' || lt.operator !== '<=')) { + return null + } + } + + // will iterate one or zero times + for (const eq of eqSet) { + if (gt && !satisfies(eq, String(gt), options)) { + return null + } + + if (lt && !satisfies(eq, String(lt), options)) { + return null + } + + for (const c of dom) { + if (!satisfies(eq, String(c), options)) { + return false + } + } + + return true + } + + let higher, lower + let hasDomLT, hasDomGT + // if the subset has a prerelease, we need a comparator in the superset + // with the same tuple and a prerelease, or it's not a subset + let needDomLTPre = lt && + !options.includePrerelease && + lt.semver.prerelease.length ? lt.semver : false + let needDomGTPre = gt && + !options.includePrerelease && + gt.semver.prerelease.length ? gt.semver : false + // exception: <1.2.3-0 is the same as <1.2.3 + if (needDomLTPre && needDomLTPre.prerelease.length === 1 && + lt.operator === '<' && needDomLTPre.prerelease[0] === 0) { + needDomLTPre = false + } + + for (const c of dom) { + hasDomGT = hasDomGT || c.operator === '>' || c.operator === '>=' + hasDomLT = hasDomLT || c.operator === '<' || c.operator === '<=' + if (gt) { + if (needDomGTPre) { + if (c.semver.prerelease && c.semver.prerelease.length && + c.semver.major === needDomGTPre.major && + c.semver.minor === needDomGTPre.minor && + c.semver.patch === needDomGTPre.patch) { + needDomGTPre = false + } + } + if (c.operator === '>' || c.operator === '>=') { + higher = higherGT(gt, c, options) + if (higher === c && higher !== gt) { + return false + } + } else if (gt.operator === '>=' && !c.test(gt.semver)) { + return false + } + } + if (lt) { + if (needDomLTPre) { + if (c.semver.prerelease && c.semver.prerelease.length && + c.semver.major === needDomLTPre.major && + c.semver.minor === needDomLTPre.minor && + c.semver.patch === needDomLTPre.patch) { + needDomLTPre = false + } + } + if (c.operator === '<' || c.operator === '<=') { + lower = lowerLT(lt, c, options) + if (lower === c && lower !== lt) { + return false + } + } else if (lt.operator === '<=' && !c.test(lt.semver)) { + return false + } + } + if (!c.operator && (lt || gt) && gtltComp !== 0) { + return false + } + } + + // if there was a < or >, and nothing in the dom, then must be false + // UNLESS it was limited by another range in the other direction. + // Eg, >1.0.0 <1.0.1 is still a subset of <2.0.0 + if (gt && hasDomLT && !lt && gtltComp !== 0) { + return false + } + + if (lt && hasDomGT && !gt && gtltComp !== 0) { + return false + } + + // we needed a prerelease range in a specific tuple, but didn't get one + // then this isn't a subset. eg >=1.2.3-pre is not a subset of >=1.0.0, + // because it includes prereleases in the 1.2.3 tuple + if (needDomGTPre || needDomLTPre) { + return false + } + + return true +} + +// >=1.2.3 is lower than >1.2.3 +const higherGT = (a, b, options) => { + if (!a) { + return b + } + const comp = compare(a.semver, b.semver, options) + return comp > 0 ? a + : comp < 0 ? b + : b.operator === '>' && a.operator === '>=' ? b + : a +} + +// <=1.2.3 is higher than <1.2.3 +const lowerLT = (a, b, options) => { + if (!a) { + return b + } + const comp = compare(a.semver, b.semver, options) + return comp < 0 ? a + : comp > 0 ? b + : b.operator === '<' && a.operator === '<=' ? b + : a +} + +module.exports = subset + + +/***/ }), + +/***/ 4750: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Range = __nccwpck_require__(6782) + +// Mostly just for testing and legacy API reasons +const toComparators = (range, options) => + new Range(range, options).set + .map(comp => comp.map(c => c.value).join(' ').trim().split(' ')) + +module.exports = toComparators + + +/***/ }), + +/***/ 4737: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Range = __nccwpck_require__(6782) +const validRange = (range, options) => { + try { + // Return '*' instead of '' so that truthiness works. + // This will throw if it's invalid anyway + return new Range(range, options).range || '*' + } catch (er) { + return null + } +} +module.exports = validRange + + +/***/ }), + +/***/ 1450: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + +const os = __nccwpck_require__(857); +const tty = __nccwpck_require__(2018); +const hasFlag = __nccwpck_require__(3813); + +const {env} = process; + +let forceColor; +if (hasFlag('no-color') || + hasFlag('no-colors') || + hasFlag('color=false') || + hasFlag('color=never')) { + forceColor = 0; +} else if (hasFlag('color') || + hasFlag('colors') || + hasFlag('color=true') || + hasFlag('color=always')) { + forceColor = 1; +} + +if ('FORCE_COLOR' in env) { + if (env.FORCE_COLOR === 'true') { + forceColor = 1; + } else if (env.FORCE_COLOR === 'false') { + forceColor = 0; + } else { + forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3); + } +} + +function translateLevel(level) { + if (level === 0) { + return false; + } + + return { + level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3 + }; +} + +function supportsColor(haveStream, streamIsTTY) { + if (forceColor === 0) { + return 0; + } + + if (hasFlag('color=16m') || + hasFlag('color=full') || + hasFlag('color=truecolor')) { + return 3; + } + + if (hasFlag('color=256')) { + return 2; + } + + if (haveStream && !streamIsTTY && forceColor === undefined) { + return 0; + } + + const min = forceColor || 0; + + if (env.TERM === 'dumb') { + return min; + } + + if (process.platform === 'win32') { + // Windows 10 build 10586 is the first Windows release that supports 256 colors. + // Windows 10 build 14931 is the first release that supports 16m/TrueColor. + const osRelease = os.release().split('.'); + if ( + Number(osRelease[0]) >= 10 && + Number(osRelease[2]) >= 10586 + ) { + return Number(osRelease[2]) >= 14931 ? 3 : 2; + } + + return 1; + } + + if ('CI' in env) { + if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI', 'GITHUB_ACTIONS', 'BUILDKITE'].some(sign => sign in env) || env.CI_NAME === 'codeship') { + return 1; + } + + return min; + } + + if ('TEAMCITY_VERSION' in env) { + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; + } + + if (env.COLORTERM === 'truecolor') { + return 3; + } + + if ('TERM_PROGRAM' in env) { + const version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); + + switch (env.TERM_PROGRAM) { + case 'iTerm.app': + return version >= 3 ? 3 : 2; + case 'Apple_Terminal': + return 2; + // No default + } + } + + if (/-256(color)?$/i.test(env.TERM)) { + return 2; + } + + if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { + return 1; + } + + if ('COLORTERM' in env) { + return 1; + } + + return min; +} + +function getSupportLevel(stream) { + const level = supportsColor(stream, stream && stream.isTTY); + return translateLevel(level); +} + +module.exports = { + supportsColor: getSupportLevel, + stdout: translateLevel(supportsColor(true, tty.isatty(1))), + stderr: translateLevel(supportsColor(true, tty.isatty(2))) +}; + + +/***/ }), + +/***/ 770: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +module.exports = __nccwpck_require__(218); + + +/***/ }), + +/***/ 218: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; +var __webpack_unused_export__; + + +var net = __nccwpck_require__(9278); +var tls = __nccwpck_require__(4756); +var http = __nccwpck_require__(8611); +var https = __nccwpck_require__(5692); +var events = __nccwpck_require__(4434); +var assert = __nccwpck_require__(2613); +var util = __nccwpck_require__(9023); + + +exports.httpOverHttp = httpOverHttp; +exports.httpsOverHttp = httpsOverHttp; +exports.httpOverHttps = httpOverHttps; +exports.httpsOverHttps = httpsOverHttps; + + +function httpOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http.request; + return agent; +} + +function httpsOverHttp(options) { + var agent = new TunnelingAgent(options); + agent.request = http.request; + agent.createSocket = createSecureSocket; + agent.defaultPort = 443; + return agent; +} + +function httpOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https.request; + return agent; +} + +function httpsOverHttps(options) { + var agent = new TunnelingAgent(options); + agent.request = https.request; + agent.createSocket = createSecureSocket; + agent.defaultPort = 443; + return agent; +} + + +function TunnelingAgent(options) { + var self = this; + self.options = options || {}; + self.proxyOptions = self.options.proxy || {}; + self.maxSockets = self.options.maxSockets || http.Agent.defaultMaxSockets; + self.requests = []; + self.sockets = []; + + self.on('free', function onFree(socket, host, port, localAddress) { + var options = toOptions(host, port, localAddress); + for (var i = 0, len = self.requests.length; i < len; ++i) { + var pending = self.requests[i]; + if (pending.host === options.host && pending.port === options.port) { + // Detect the request to connect same origin server, + // reuse the connection. + self.requests.splice(i, 1); + pending.request.onSocket(socket); + return; + } + } + socket.destroy(); + self.removeSocket(socket); + }); +} +util.inherits(TunnelingAgent, events.EventEmitter); + +TunnelingAgent.prototype.addRequest = function addRequest(req, host, port, localAddress) { + var self = this; + var options = mergeOptions({request: req}, self.options, toOptions(host, port, localAddress)); + + if (self.sockets.length >= this.maxSockets) { + // We are over limit so we'll add it to the queue. + self.requests.push(options); + return; + } + + // If we are under maxSockets create a new one. + self.createSocket(options, function(socket) { + socket.on('free', onFree); + socket.on('close', onCloseOrRemove); + socket.on('agentRemove', onCloseOrRemove); + req.onSocket(socket); + + function onFree() { + self.emit('free', socket, options); + } + + function onCloseOrRemove(err) { + self.removeSocket(socket); + socket.removeListener('free', onFree); + socket.removeListener('close', onCloseOrRemove); + socket.removeListener('agentRemove', onCloseOrRemove); + } + }); +}; + +TunnelingAgent.prototype.createSocket = function createSocket(options, cb) { + var self = this; + var placeholder = {}; + self.sockets.push(placeholder); + + var connectOptions = mergeOptions({}, self.proxyOptions, { + method: 'CONNECT', + path: options.host + ':' + options.port, + agent: false, + headers: { + host: options.host + ':' + options.port + } + }); + if (options.localAddress) { + connectOptions.localAddress = options.localAddress; + } + if (connectOptions.proxyAuth) { + connectOptions.headers = connectOptions.headers || {}; + connectOptions.headers['Proxy-Authorization'] = 'Basic ' + + new Buffer(connectOptions.proxyAuth).toString('base64'); + } + + debug('making CONNECT request'); + var connectReq = self.request(connectOptions); + connectReq.useChunkedEncodingByDefault = false; // for v0.6 + connectReq.once('response', onResponse); // for v0.6 + connectReq.once('upgrade', onUpgrade); // for v0.6 + connectReq.once('connect', onConnect); // for v0.7 or later + connectReq.once('error', onError); + connectReq.end(); + + function onResponse(res) { + // Very hacky. This is necessary to avoid http-parser leaks. + res.upgrade = true; + } + + function onUpgrade(res, socket, head) { + // Hacky. + process.nextTick(function() { + onConnect(res, socket, head); + }); + } + + function onConnect(res, socket, head) { + connectReq.removeAllListeners(); + socket.removeAllListeners(); + + if (res.statusCode !== 200) { + debug('tunneling socket could not be established, statusCode=%d', + res.statusCode); + socket.destroy(); + var error = new Error('tunneling socket could not be established, ' + + 'statusCode=' + res.statusCode); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + return; + } + if (head.length > 0) { + debug('got illegal response body from proxy'); + socket.destroy(); + var error = new Error('got illegal response body from proxy'); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + return; + } + debug('tunneling connection has established'); + self.sockets[self.sockets.indexOf(placeholder)] = socket; + return cb(socket); + } + + function onError(cause) { + connectReq.removeAllListeners(); + + debug('tunneling socket could not be established, cause=%s\n', + cause.message, cause.stack); + var error = new Error('tunneling socket could not be established, ' + + 'cause=' + cause.message); + error.code = 'ECONNRESET'; + options.request.emit('error', error); + self.removeSocket(placeholder); + } +}; + +TunnelingAgent.prototype.removeSocket = function removeSocket(socket) { + var pos = this.sockets.indexOf(socket) + if (pos === -1) { + return; + } + this.sockets.splice(pos, 1); + + var pending = this.requests.shift(); + if (pending) { + // If we have pending requests and a socket gets closed a new one + // needs to be created to take over in the pool for the one that closed. + this.createSocket(pending, function(socket) { + pending.request.onSocket(socket); + }); + } +}; + +function createSecureSocket(options, cb) { + var self = this; + TunnelingAgent.prototype.createSocket.call(self, options, function(socket) { + var hostHeader = options.request.getHeader('host'); + var tlsOptions = mergeOptions({}, self.options, { + socket: socket, + servername: hostHeader ? hostHeader.replace(/:.*$/, '') : options.host + }); + + // 0 is dummy port for v0.6 + var secureSocket = tls.connect(0, tlsOptions); + self.sockets[self.sockets.indexOf(socket)] = secureSocket; + cb(secureSocket); + }); +} + + +function toOptions(host, port, localAddress) { + if (typeof host === 'string') { // since v0.10 + return { + host: host, + port: port, + localAddress: localAddress + }; + } + return host; // for v0.11 or later +} + +function mergeOptions(target) { + for (var i = 1, len = arguments.length; i < len; ++i) { + var overrides = arguments[i]; + if (typeof overrides === 'object') { + var keys = Object.keys(overrides); + for (var j = 0, keyLen = keys.length; j < keyLen; ++j) { + var k = keys[j]; + if (overrides[k] !== undefined) { + target[k] = overrides[k]; + } + } + } + } + return target; +} + + +var debug; +if (process.env.NODE_DEBUG && /\btunnel\b/.test(process.env.NODE_DEBUG)) { + debug = function() { + var args = Array.prototype.slice.call(arguments); + if (typeof args[0] === 'string') { + args[0] = 'TUNNEL: ' + args[0]; + } else { + args.unshift('TUNNEL:'); + } + console.error.apply(console, args); + } +} else { + debug = function() {}; +} +__webpack_unused_export__ = debug; // for test + + +/***/ }), + +/***/ 6752: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +var __webpack_unused_export__; + + +const Client = __nccwpck_require__(3701) +const Dispatcher = __nccwpck_require__(883) +const Pool = __nccwpck_require__(628) +const BalancedPool = __nccwpck_require__(837) +const Agent = __nccwpck_require__(7405) +const ProxyAgent = __nccwpck_require__(6672) +const EnvHttpProxyAgent = __nccwpck_require__(3137) +const RetryAgent = __nccwpck_require__(50) +const errors = __nccwpck_require__(8707) +const util = __nccwpck_require__(3440) +const { InvalidArgumentError } = errors +const api = __nccwpck_require__(6615) +const buildConnector = __nccwpck_require__(9136) +const MockClient = __nccwpck_require__(7365) +const MockAgent = __nccwpck_require__(7501) +const MockPool = __nccwpck_require__(4004) +const mockErrors = __nccwpck_require__(2429) +const RetryHandler = __nccwpck_require__(7816) +const { getGlobalDispatcher, setGlobalDispatcher } = __nccwpck_require__(2581) +const DecoratorHandler = __nccwpck_require__(8155) +const RedirectHandler = __nccwpck_require__(8754) +const createRedirectInterceptor = __nccwpck_require__(5092) + +Object.assign(Dispatcher.prototype, api) + +__webpack_unused_export__ = Dispatcher +__webpack_unused_export__ = Client +__webpack_unused_export__ = Pool +__webpack_unused_export__ = BalancedPool +__webpack_unused_export__ = Agent +module.exports.kT = ProxyAgent +__webpack_unused_export__ = EnvHttpProxyAgent +__webpack_unused_export__ = RetryAgent +__webpack_unused_export__ = RetryHandler + +__webpack_unused_export__ = DecoratorHandler +__webpack_unused_export__ = RedirectHandler +__webpack_unused_export__ = createRedirectInterceptor +__webpack_unused_export__ = { + redirect: __nccwpck_require__(1514), + retry: __nccwpck_require__(2026), + dump: __nccwpck_require__(8060), + dns: __nccwpck_require__(379) +} + +__webpack_unused_export__ = buildConnector +__webpack_unused_export__ = errors +__webpack_unused_export__ = { + parseHeaders: util.parseHeaders, + headerNameToString: util.headerNameToString +} + +function makeDispatcher (fn) { + return (url, opts, handler) => { + if (typeof opts === 'function') { + handler = opts + opts = null + } + + if (!url || (typeof url !== 'string' && typeof url !== 'object' && !(url instanceof URL))) { + throw new InvalidArgumentError('invalid url') + } + + if (opts != null && typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + if (opts && opts.path != null) { + if (typeof opts.path !== 'string') { + throw new InvalidArgumentError('invalid opts.path') + } + + let path = opts.path + if (!opts.path.startsWith('/')) { + path = `/${path}` + } + + url = new URL(util.parseOrigin(url).origin + path) + } else { + if (!opts) { + opts = typeof url === 'object' ? url : {} + } + + url = util.parseURL(url) + } + + const { agent, dispatcher = getGlobalDispatcher() } = opts + + if (agent) { + throw new InvalidArgumentError('unsupported opts.agent. Did you mean opts.client?') + } + + return fn.call(dispatcher, { + ...opts, + origin: url.origin, + path: url.search ? `${url.pathname}${url.search}` : url.pathname, + method: opts.method || (opts.body ? 'PUT' : 'GET') + }, handler) + } +} + +__webpack_unused_export__ = setGlobalDispatcher +__webpack_unused_export__ = getGlobalDispatcher + +const fetchImpl = (__nccwpck_require__(4398).fetch) +__webpack_unused_export__ = async function fetch (init, options = undefined) { + try { + return await fetchImpl(init, options) + } catch (err) { + if (err && typeof err === 'object') { + Error.captureStackTrace(err) + } + + throw err + } +} +/* unused reexport */ __nccwpck_require__(660).Headers +/* unused reexport */ __nccwpck_require__(9051).Response +/* unused reexport */ __nccwpck_require__(9967).Request +/* unused reexport */ __nccwpck_require__(5910).FormData +__webpack_unused_export__ = globalThis.File ?? (__nccwpck_require__(4573).File) +/* unused reexport */ __nccwpck_require__(8355).FileReader + +const { setGlobalOrigin, getGlobalOrigin } = __nccwpck_require__(1059) + +__webpack_unused_export__ = setGlobalOrigin +__webpack_unused_export__ = getGlobalOrigin + +const { CacheStorage } = __nccwpck_require__(3245) +const { kConstruct } = __nccwpck_require__(109) + +// Cache & CacheStorage are tightly coupled with fetch. Even if it may run +// in an older version of Node, it doesn't have any use without fetch. +__webpack_unused_export__ = new CacheStorage(kConstruct) + +const { deleteCookie, getCookies, getSetCookies, setCookie } = __nccwpck_require__(9061) + +__webpack_unused_export__ = deleteCookie +__webpack_unused_export__ = getCookies +__webpack_unused_export__ = getSetCookies +__webpack_unused_export__ = setCookie + +const { parseMIMEType, serializeAMimeType } = __nccwpck_require__(1900) + +__webpack_unused_export__ = parseMIMEType +__webpack_unused_export__ = serializeAMimeType + +const { CloseEvent, ErrorEvent, MessageEvent } = __nccwpck_require__(5188) +/* unused reexport */ __nccwpck_require__(3726).WebSocket +__webpack_unused_export__ = CloseEvent +__webpack_unused_export__ = ErrorEvent +__webpack_unused_export__ = MessageEvent + +__webpack_unused_export__ = makeDispatcher(api.request) +__webpack_unused_export__ = makeDispatcher(api.stream) +__webpack_unused_export__ = makeDispatcher(api.pipeline) +__webpack_unused_export__ = makeDispatcher(api.connect) +__webpack_unused_export__ = makeDispatcher(api.upgrade) + +__webpack_unused_export__ = MockClient +__webpack_unused_export__ = MockPool +__webpack_unused_export__ = MockAgent +__webpack_unused_export__ = mockErrors + +const { EventSource } = __nccwpck_require__(1238) + +__webpack_unused_export__ = EventSource + + +/***/ }), + +/***/ 158: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const { addAbortListener } = __nccwpck_require__(3440) +const { RequestAbortedError } = __nccwpck_require__(8707) + +const kListener = Symbol('kListener') +const kSignal = Symbol('kSignal') + +function abort (self) { + if (self.abort) { + self.abort(self[kSignal]?.reason) + } else { + self.reason = self[kSignal]?.reason ?? new RequestAbortedError() + } + removeSignal(self) +} + +function addSignal (self, signal) { + self.reason = null + + self[kSignal] = null + self[kListener] = null + + if (!signal) { + return + } + + if (signal.aborted) { + abort(self) + return + } + + self[kSignal] = signal + self[kListener] = () => { + abort(self) + } + + addAbortListener(self[kSignal], self[kListener]) +} + +function removeSignal (self) { + if (!self[kSignal]) { + return + } + + if ('removeEventListener' in self[kSignal]) { + self[kSignal].removeEventListener('abort', self[kListener]) + } else { + self[kSignal].removeListener('abort', self[kListener]) + } + + self[kSignal] = null + self[kListener] = null +} + +module.exports = { + addSignal, + removeSignal +} + + +/***/ }), + +/***/ 2279: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const assert = __nccwpck_require__(4589) +const { AsyncResource } = __nccwpck_require__(6698) +const { InvalidArgumentError, SocketError } = __nccwpck_require__(8707) +const util = __nccwpck_require__(3440) +const { addSignal, removeSignal } = __nccwpck_require__(158) + +class ConnectHandler extends AsyncResource { + constructor (opts, callback) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + const { signal, opaque, responseHeaders } = opts + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + super('UNDICI_CONNECT') + + this.opaque = opaque || null + this.responseHeaders = responseHeaders || null + this.callback = callback + this.abort = null + + addSignal(this, signal) + } + + onConnect (abort, context) { + if (this.reason) { + abort(this.reason) + return + } + + assert(this.callback) + + this.abort = abort + this.context = context + } + + onHeaders () { + throw new SocketError('bad connect', null) + } + + onUpgrade (statusCode, rawHeaders, socket) { + const { callback, opaque, context } = this + + removeSignal(this) + + this.callback = null + + let headers = rawHeaders + // Indicates is an HTTP2Session + if (headers != null) { + headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + } + + this.runInAsyncScope(callback, null, null, { + statusCode, + headers, + socket, + opaque, + context + }) + } + + onError (err) { + const { callback, opaque } = this + + removeSignal(this) + + if (callback) { + this.callback = null + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }) + }) + } + } +} + +function connect (opts, callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + connect.call(this, opts, (err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + try { + const connectHandler = new ConnectHandler(opts, callback) + this.dispatch({ ...opts, method: 'CONNECT' }, connectHandler) + } catch (err) { + if (typeof callback !== 'function') { + throw err + } + const opaque = opts?.opaque + queueMicrotask(() => callback(err, { opaque })) + } +} + +module.exports = connect + + +/***/ }), + +/***/ 6862: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + Readable, + Duplex, + PassThrough +} = __nccwpck_require__(7075) +const { + InvalidArgumentError, + InvalidReturnValueError, + RequestAbortedError +} = __nccwpck_require__(8707) +const util = __nccwpck_require__(3440) +const { AsyncResource } = __nccwpck_require__(6698) +const { addSignal, removeSignal } = __nccwpck_require__(158) +const assert = __nccwpck_require__(4589) + +const kResume = Symbol('resume') + +class PipelineRequest extends Readable { + constructor () { + super({ autoDestroy: true }) + + this[kResume] = null + } + + _read () { + const { [kResume]: resume } = this + + if (resume) { + this[kResume] = null + resume() + } + } + + _destroy (err, callback) { + this._read() + + callback(err) + } +} + +class PipelineResponse extends Readable { + constructor (resume) { + super({ autoDestroy: true }) + this[kResume] = resume + } + + _read () { + this[kResume]() + } + + _destroy (err, callback) { + if (!err && !this._readableState.endEmitted) { + err = new RequestAbortedError() + } + + callback(err) + } +} + +class PipelineHandler extends AsyncResource { + constructor (opts, handler) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + if (typeof handler !== 'function') { + throw new InvalidArgumentError('invalid handler') + } + + const { signal, method, opaque, onInfo, responseHeaders } = opts + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + if (method === 'CONNECT') { + throw new InvalidArgumentError('invalid method') + } + + if (onInfo && typeof onInfo !== 'function') { + throw new InvalidArgumentError('invalid onInfo callback') + } + + super('UNDICI_PIPELINE') + + this.opaque = opaque || null + this.responseHeaders = responseHeaders || null + this.handler = handler + this.abort = null + this.context = null + this.onInfo = onInfo || null + + this.req = new PipelineRequest().on('error', util.nop) + + this.ret = new Duplex({ + readableObjectMode: opts.objectMode, + autoDestroy: true, + read: () => { + const { body } = this + + if (body?.resume) { + body.resume() + } + }, + write: (chunk, encoding, callback) => { + const { req } = this + + if (req.push(chunk, encoding) || req._readableState.destroyed) { + callback() + } else { + req[kResume] = callback + } + }, + destroy: (err, callback) => { + const { body, req, res, ret, abort } = this + + if (!err && !ret._readableState.endEmitted) { + err = new RequestAbortedError() + } + + if (abort && err) { + abort() + } + + util.destroy(body, err) + util.destroy(req, err) + util.destroy(res, err) + + removeSignal(this) + + callback(err) + } + }).on('prefinish', () => { + const { req } = this + + // Node < 15 does not call _final in same tick. + req.push(null) + }) + + this.res = null + + addSignal(this, signal) + } + + onConnect (abort, context) { + const { ret, res } = this + + if (this.reason) { + abort(this.reason) + return + } + + assert(!res, 'pipeline cannot be retried') + assert(!ret.destroyed) + + this.abort = abort + this.context = context + } + + onHeaders (statusCode, rawHeaders, resume) { + const { opaque, handler, context } = this + + if (statusCode < 200) { + if (this.onInfo) { + const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + this.onInfo({ statusCode, headers }) + } + return + } + + this.res = new PipelineResponse(resume) + + let body + try { + this.handler = null + const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + body = this.runInAsyncScope(handler, null, { + statusCode, + headers, + opaque, + body: this.res, + context + }) + } catch (err) { + this.res.on('error', util.nop) + throw err + } + + if (!body || typeof body.on !== 'function') { + throw new InvalidReturnValueError('expected Readable') + } + + body + .on('data', (chunk) => { + const { ret, body } = this + + if (!ret.push(chunk) && body.pause) { + body.pause() + } + }) + .on('error', (err) => { + const { ret } = this + + util.destroy(ret, err) + }) + .on('end', () => { + const { ret } = this + + ret.push(null) + }) + .on('close', () => { + const { ret } = this + + if (!ret._readableState.ended) { + util.destroy(ret, new RequestAbortedError()) + } + }) + + this.body = body + } + + onData (chunk) { + const { res } = this + return res.push(chunk) + } + + onComplete (trailers) { + const { res } = this + res.push(null) + } + + onError (err) { + const { ret } = this + this.handler = null + util.destroy(ret, err) + } +} + +function pipeline (opts, handler) { + try { + const pipelineHandler = new PipelineHandler(opts, handler) + this.dispatch({ ...opts, body: pipelineHandler.req }, pipelineHandler) + return pipelineHandler.ret + } catch (err) { + return new PassThrough().destroy(err) + } +} + +module.exports = pipeline + + +/***/ }), + +/***/ 4043: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const assert = __nccwpck_require__(4589) +const { Readable } = __nccwpck_require__(9927) +const { InvalidArgumentError, RequestAbortedError } = __nccwpck_require__(8707) +const util = __nccwpck_require__(3440) +const { getResolveErrorBodyCallback } = __nccwpck_require__(7655) +const { AsyncResource } = __nccwpck_require__(6698) + +class RequestHandler extends AsyncResource { + constructor (opts, callback) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError, highWaterMark } = opts + + try { + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + if (highWaterMark && (typeof highWaterMark !== 'number' || highWaterMark < 0)) { + throw new InvalidArgumentError('invalid highWaterMark') + } + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + if (method === 'CONNECT') { + throw new InvalidArgumentError('invalid method') + } + + if (onInfo && typeof onInfo !== 'function') { + throw new InvalidArgumentError('invalid onInfo callback') + } + + super('UNDICI_REQUEST') + } catch (err) { + if (util.isStream(body)) { + util.destroy(body.on('error', util.nop), err) + } + throw err + } + + this.method = method + this.responseHeaders = responseHeaders || null + this.opaque = opaque || null + this.callback = callback + this.res = null + this.abort = null + this.body = body + this.trailers = {} + this.context = null + this.onInfo = onInfo || null + this.throwOnError = throwOnError + this.highWaterMark = highWaterMark + this.signal = signal + this.reason = null + this.removeAbortListener = null + + if (util.isStream(body)) { + body.on('error', (err) => { + this.onError(err) + }) + } + + if (this.signal) { + if (this.signal.aborted) { + this.reason = this.signal.reason ?? new RequestAbortedError() + } else { + this.removeAbortListener = util.addAbortListener(this.signal, () => { + this.reason = this.signal.reason ?? new RequestAbortedError() + if (this.res) { + util.destroy(this.res.on('error', util.nop), this.reason) + } else if (this.abort) { + this.abort(this.reason) + } + + if (this.removeAbortListener) { + this.res?.off('close', this.removeAbortListener) + this.removeAbortListener() + this.removeAbortListener = null + } + }) + } + } + } + + onConnect (abort, context) { + if (this.reason) { + abort(this.reason) + return + } + + assert(this.callback) + + this.abort = abort + this.context = context + } + + onHeaders (statusCode, rawHeaders, resume, statusMessage) { + const { callback, opaque, abort, context, responseHeaders, highWaterMark } = this + + const headers = responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + + if (statusCode < 200) { + if (this.onInfo) { + this.onInfo({ statusCode, headers }) + } + return + } + + const parsedHeaders = responseHeaders === 'raw' ? util.parseHeaders(rawHeaders) : headers + const contentType = parsedHeaders['content-type'] + const contentLength = parsedHeaders['content-length'] + const res = new Readable({ + resume, + abort, + contentType, + contentLength: this.method !== 'HEAD' && contentLength + ? Number(contentLength) + : null, + highWaterMark + }) + + if (this.removeAbortListener) { + res.on('close', this.removeAbortListener) + } + + this.callback = null + this.res = res + if (callback !== null) { + if (this.throwOnError && statusCode >= 400) { + this.runInAsyncScope(getResolveErrorBodyCallback, null, + { callback, body: res, contentType, statusCode, statusMessage, headers } + ) + } else { + this.runInAsyncScope(callback, null, null, { + statusCode, + headers, + trailers: this.trailers, + opaque, + body: res, + context + }) + } + } + } + + onData (chunk) { + return this.res.push(chunk) + } + + onComplete (trailers) { + util.parseHeaders(trailers, this.trailers) + this.res.push(null) + } + + onError (err) { + const { res, callback, body, opaque } = this + + if (callback) { + // TODO: Does this need queueMicrotask? + this.callback = null + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }) + }) + } + + if (res) { + this.res = null + // Ensure all queued handlers are invoked before destroying res. + queueMicrotask(() => { + util.destroy(res, err) + }) + } + + if (body) { + this.body = null + util.destroy(body, err) + } + + if (this.removeAbortListener) { + res?.off('close', this.removeAbortListener) + this.removeAbortListener() + this.removeAbortListener = null + } + } +} + +function request (opts, callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + request.call(this, opts, (err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + try { + this.dispatch(opts, new RequestHandler(opts, callback)) + } catch (err) { + if (typeof callback !== 'function') { + throw err + } + const opaque = opts?.opaque + queueMicrotask(() => callback(err, { opaque })) + } +} + +module.exports = request +module.exports.RequestHandler = RequestHandler + + +/***/ }), + +/***/ 3560: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const assert = __nccwpck_require__(4589) +const { finished, PassThrough } = __nccwpck_require__(7075) +const { InvalidArgumentError, InvalidReturnValueError } = __nccwpck_require__(8707) +const util = __nccwpck_require__(3440) +const { getResolveErrorBodyCallback } = __nccwpck_require__(7655) +const { AsyncResource } = __nccwpck_require__(6698) +const { addSignal, removeSignal } = __nccwpck_require__(158) + +class StreamHandler extends AsyncResource { + constructor (opts, factory, callback) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + const { signal, method, opaque, body, onInfo, responseHeaders, throwOnError } = opts + + try { + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + if (typeof factory !== 'function') { + throw new InvalidArgumentError('invalid factory') + } + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + if (method === 'CONNECT') { + throw new InvalidArgumentError('invalid method') + } + + if (onInfo && typeof onInfo !== 'function') { + throw new InvalidArgumentError('invalid onInfo callback') + } + + super('UNDICI_STREAM') + } catch (err) { + if (util.isStream(body)) { + util.destroy(body.on('error', util.nop), err) + } + throw err + } + + this.responseHeaders = responseHeaders || null + this.opaque = opaque || null + this.factory = factory + this.callback = callback + this.res = null + this.abort = null + this.context = null + this.trailers = null + this.body = body + this.onInfo = onInfo || null + this.throwOnError = throwOnError || false + + if (util.isStream(body)) { + body.on('error', (err) => { + this.onError(err) + }) + } + + addSignal(this, signal) + } + + onConnect (abort, context) { + if (this.reason) { + abort(this.reason) + return + } + + assert(this.callback) + + this.abort = abort + this.context = context + } + + onHeaders (statusCode, rawHeaders, resume, statusMessage) { + const { factory, opaque, context, callback, responseHeaders } = this + + const headers = responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + + if (statusCode < 200) { + if (this.onInfo) { + this.onInfo({ statusCode, headers }) + } + return + } + + this.factory = null + + let res + + if (this.throwOnError && statusCode >= 400) { + const parsedHeaders = responseHeaders === 'raw' ? util.parseHeaders(rawHeaders) : headers + const contentType = parsedHeaders['content-type'] + res = new PassThrough() + + this.callback = null + this.runInAsyncScope(getResolveErrorBodyCallback, null, + { callback, body: res, contentType, statusCode, statusMessage, headers } + ) + } else { + if (factory === null) { + return + } + + res = this.runInAsyncScope(factory, null, { + statusCode, + headers, + opaque, + context + }) + + if ( + !res || + typeof res.write !== 'function' || + typeof res.end !== 'function' || + typeof res.on !== 'function' + ) { + throw new InvalidReturnValueError('expected Writable') + } + + // TODO: Avoid finished. It registers an unnecessary amount of listeners. + finished(res, { readable: false }, (err) => { + const { callback, res, opaque, trailers, abort } = this + + this.res = null + if (err || !res.readable) { + util.destroy(res, err) + } + + this.callback = null + this.runInAsyncScope(callback, null, err || null, { opaque, trailers }) + + if (err) { + abort() + } + }) + } + + res.on('drain', resume) + + this.res = res + + const needDrain = res.writableNeedDrain !== undefined + ? res.writableNeedDrain + : res._writableState?.needDrain + + return needDrain !== true + } + + onData (chunk) { + const { res } = this + + return res ? res.write(chunk) : true + } + + onComplete (trailers) { + const { res } = this + + removeSignal(this) + + if (!res) { + return + } + + this.trailers = util.parseHeaders(trailers) + + res.end() + } + + onError (err) { + const { res, callback, opaque, body } = this + + removeSignal(this) + + this.factory = null + + if (res) { + this.res = null + util.destroy(res, err) + } else if (callback) { + this.callback = null + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }) + }) + } + + if (body) { + this.body = null + util.destroy(body, err) + } + } +} + +function stream (opts, factory, callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + stream.call(this, opts, factory, (err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + try { + this.dispatch(opts, new StreamHandler(opts, factory, callback)) + } catch (err) { + if (typeof callback !== 'function') { + throw err + } + const opaque = opts?.opaque + queueMicrotask(() => callback(err, { opaque })) + } +} + +module.exports = stream + + +/***/ }), + +/***/ 1882: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { InvalidArgumentError, SocketError } = __nccwpck_require__(8707) +const { AsyncResource } = __nccwpck_require__(6698) +const util = __nccwpck_require__(3440) +const { addSignal, removeSignal } = __nccwpck_require__(158) +const assert = __nccwpck_require__(4589) + +class UpgradeHandler extends AsyncResource { + constructor (opts, callback) { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('invalid opts') + } + + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + const { signal, opaque, responseHeaders } = opts + + if (signal && typeof signal.on !== 'function' && typeof signal.addEventListener !== 'function') { + throw new InvalidArgumentError('signal must be an EventEmitter or EventTarget') + } + + super('UNDICI_UPGRADE') + + this.responseHeaders = responseHeaders || null + this.opaque = opaque || null + this.callback = callback + this.abort = null + this.context = null + + addSignal(this, signal) + } + + onConnect (abort, context) { + if (this.reason) { + abort(this.reason) + return + } + + assert(this.callback) + + this.abort = abort + this.context = null + } + + onHeaders () { + throw new SocketError('bad upgrade', null) + } + + onUpgrade (statusCode, rawHeaders, socket) { + assert(statusCode === 101) + + const { callback, opaque, context } = this + + removeSignal(this) + + this.callback = null + const headers = this.responseHeaders === 'raw' ? util.parseRawHeaders(rawHeaders) : util.parseHeaders(rawHeaders) + this.runInAsyncScope(callback, null, null, { + headers, + socket, + opaque, + context + }) + } + + onError (err) { + const { callback, opaque } = this + + removeSignal(this) + + if (callback) { + this.callback = null + queueMicrotask(() => { + this.runInAsyncScope(callback, null, err, { opaque }) + }) + } + } +} + +function upgrade (opts, callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + upgrade.call(this, opts, (err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + try { + const upgradeHandler = new UpgradeHandler(opts, callback) + this.dispatch({ + ...opts, + method: opts.method || 'GET', + upgrade: opts.protocol || 'Websocket' + }, upgradeHandler) + } catch (err) { + if (typeof callback !== 'function') { + throw err + } + const opaque = opts?.opaque + queueMicrotask(() => callback(err, { opaque })) + } +} + +module.exports = upgrade + + +/***/ }), + +/***/ 6615: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +module.exports.request = __nccwpck_require__(4043) +module.exports.stream = __nccwpck_require__(3560) +module.exports.pipeline = __nccwpck_require__(6862) +module.exports.upgrade = __nccwpck_require__(1882) +module.exports.connect = __nccwpck_require__(2279) + + +/***/ }), + +/***/ 9927: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// Ported from https://github.com/nodejs/undici/pull/907 + + + +const assert = __nccwpck_require__(4589) +const { Readable } = __nccwpck_require__(7075) +const { RequestAbortedError, NotSupportedError, InvalidArgumentError, AbortError } = __nccwpck_require__(8707) +const util = __nccwpck_require__(3440) +const { ReadableStreamFrom } = __nccwpck_require__(3440) + +const kConsume = Symbol('kConsume') +const kReading = Symbol('kReading') +const kBody = Symbol('kBody') +const kAbort = Symbol('kAbort') +const kContentType = Symbol('kContentType') +const kContentLength = Symbol('kContentLength') + +const noop = () => {} + +class BodyReadable extends Readable { + constructor ({ + resume, + abort, + contentType = '', + contentLength, + highWaterMark = 64 * 1024 // Same as nodejs fs streams. + }) { + super({ + autoDestroy: true, + read: resume, + highWaterMark + }) + + this._readableState.dataEmitted = false + + this[kAbort] = abort + this[kConsume] = null + this[kBody] = null + this[kContentType] = contentType + this[kContentLength] = contentLength + + // Is stream being consumed through Readable API? + // This is an optimization so that we avoid checking + // for 'data' and 'readable' listeners in the hot path + // inside push(). + this[kReading] = false + } + + destroy (err) { + if (!err && !this._readableState.endEmitted) { + err = new RequestAbortedError() + } + + if (err) { + this[kAbort]() + } + + return super.destroy(err) + } + + _destroy (err, callback) { + // Workaround for Node "bug". If the stream is destroyed in same + // tick as it is created, then a user who is waiting for a + // promise (i.e micro tick) for installing a 'error' listener will + // never get a chance and will always encounter an unhandled exception. + if (!this[kReading]) { + setImmediate(() => { + callback(err) + }) + } else { + callback(err) + } + } + + on (ev, ...args) { + if (ev === 'data' || ev === 'readable') { + this[kReading] = true + } + return super.on(ev, ...args) + } + + addListener (ev, ...args) { + return this.on(ev, ...args) + } + + off (ev, ...args) { + const ret = super.off(ev, ...args) + if (ev === 'data' || ev === 'readable') { + this[kReading] = ( + this.listenerCount('data') > 0 || + this.listenerCount('readable') > 0 + ) + } + return ret + } + + removeListener (ev, ...args) { + return this.off(ev, ...args) + } + + push (chunk) { + if (this[kConsume] && chunk !== null) { + consumePush(this[kConsume], chunk) + return this[kReading] ? super.push(chunk) : true + } + return super.push(chunk) + } + + // https://fetch.spec.whatwg.org/#dom-body-text + async text () { + return consume(this, 'text') + } + + // https://fetch.spec.whatwg.org/#dom-body-json + async json () { + return consume(this, 'json') + } + + // https://fetch.spec.whatwg.org/#dom-body-blob + async blob () { + return consume(this, 'blob') + } + + // https://fetch.spec.whatwg.org/#dom-body-bytes + async bytes () { + return consume(this, 'bytes') + } + + // https://fetch.spec.whatwg.org/#dom-body-arraybuffer + async arrayBuffer () { + return consume(this, 'arrayBuffer') + } + + // https://fetch.spec.whatwg.org/#dom-body-formdata + async formData () { + // TODO: Implement. + throw new NotSupportedError() + } + + // https://fetch.spec.whatwg.org/#dom-body-bodyused + get bodyUsed () { + return util.isDisturbed(this) + } + + // https://fetch.spec.whatwg.org/#dom-body-body + get body () { + if (!this[kBody]) { + this[kBody] = ReadableStreamFrom(this) + if (this[kConsume]) { + // TODO: Is this the best way to force a lock? + this[kBody].getReader() // Ensure stream is locked. + assert(this[kBody].locked) + } + } + return this[kBody] + } + + async dump (opts) { + let limit = Number.isFinite(opts?.limit) ? opts.limit : 128 * 1024 + const signal = opts?.signal + + if (signal != null && (typeof signal !== 'object' || !('aborted' in signal))) { + throw new InvalidArgumentError('signal must be an AbortSignal') + } + + signal?.throwIfAborted() + + if (this._readableState.closeEmitted) { + return null + } + + return await new Promise((resolve, reject) => { + if (this[kContentLength] > limit) { + this.destroy(new AbortError()) + } + + const onAbort = () => { + this.destroy(signal.reason ?? new AbortError()) + } + signal?.addEventListener('abort', onAbort) + + this + .on('close', function () { + signal?.removeEventListener('abort', onAbort) + if (signal?.aborted) { + reject(signal.reason ?? new AbortError()) + } else { + resolve(null) + } + }) + .on('error', noop) + .on('data', function (chunk) { + limit -= chunk.length + if (limit <= 0) { + this.destroy() + } + }) + .resume() + }) + } +} + +// https://streams.spec.whatwg.org/#readablestream-locked +function isLocked (self) { + // Consume is an implicit lock. + return (self[kBody] && self[kBody].locked === true) || self[kConsume] +} + +// https://fetch.spec.whatwg.org/#body-unusable +function isUnusable (self) { + return util.isDisturbed(self) || isLocked(self) +} + +async function consume (stream, type) { + assert(!stream[kConsume]) + + return new Promise((resolve, reject) => { + if (isUnusable(stream)) { + const rState = stream._readableState + if (rState.destroyed && rState.closeEmitted === false) { + stream + .on('error', err => { + reject(err) + }) + .on('close', () => { + reject(new TypeError('unusable')) + }) + } else { + reject(rState.errored ?? new TypeError('unusable')) + } + } else { + queueMicrotask(() => { + stream[kConsume] = { + type, + stream, + resolve, + reject, + length: 0, + body: [] + } + + stream + .on('error', function (err) { + consumeFinish(this[kConsume], err) + }) + .on('close', function () { + if (this[kConsume].body !== null) { + consumeFinish(this[kConsume], new RequestAbortedError()) + } + }) + + consumeStart(stream[kConsume]) + }) + } + }) +} + +function consumeStart (consume) { + if (consume.body === null) { + return + } + + const { _readableState: state } = consume.stream + + if (state.bufferIndex) { + const start = state.bufferIndex + const end = state.buffer.length + for (let n = start; n < end; n++) { + consumePush(consume, state.buffer[n]) + } + } else { + for (const chunk of state.buffer) { + consumePush(consume, chunk) + } + } + + if (state.endEmitted) { + consumeEnd(this[kConsume]) + } else { + consume.stream.on('end', function () { + consumeEnd(this[kConsume]) + }) + } + + consume.stream.resume() + + while (consume.stream.read() != null) { + // Loop + } +} + +/** + * @param {Buffer[]} chunks + * @param {number} length + */ +function chunksDecode (chunks, length) { + if (chunks.length === 0 || length === 0) { + return '' + } + const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks, length) + const bufferLength = buffer.length + + // Skip BOM. + const start = + bufferLength > 2 && + buffer[0] === 0xef && + buffer[1] === 0xbb && + buffer[2] === 0xbf + ? 3 + : 0 + return buffer.utf8Slice(start, bufferLength) +} + +/** + * @param {Buffer[]} chunks + * @param {number} length + * @returns {Uint8Array} + */ +function chunksConcat (chunks, length) { + if (chunks.length === 0 || length === 0) { + return new Uint8Array(0) + } + if (chunks.length === 1) { + // fast-path + return new Uint8Array(chunks[0]) + } + const buffer = new Uint8Array(Buffer.allocUnsafeSlow(length).buffer) + + let offset = 0 + for (let i = 0; i < chunks.length; ++i) { + const chunk = chunks[i] + buffer.set(chunk, offset) + offset += chunk.length + } + + return buffer +} + +function consumeEnd (consume) { + const { type, body, resolve, stream, length } = consume + + try { + if (type === 'text') { + resolve(chunksDecode(body, length)) + } else if (type === 'json') { + resolve(JSON.parse(chunksDecode(body, length))) + } else if (type === 'arrayBuffer') { + resolve(chunksConcat(body, length).buffer) + } else if (type === 'blob') { + resolve(new Blob(body, { type: stream[kContentType] })) + } else if (type === 'bytes') { + resolve(chunksConcat(body, length)) + } + + consumeFinish(consume) + } catch (err) { + stream.destroy(err) + } +} + +function consumePush (consume, chunk) { + consume.length += chunk.length + consume.body.push(chunk) +} + +function consumeFinish (consume, err) { + if (consume.body === null) { + return + } + + if (err) { + consume.reject(err) + } else { + consume.resolve() + } + + consume.type = null + consume.stream = null + consume.resolve = null + consume.reject = null + consume.length = 0 + consume.body = null +} + +module.exports = { Readable: BodyReadable, chunksDecode } + + +/***/ }), + +/***/ 7655: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const assert = __nccwpck_require__(4589) +const { + ResponseStatusCodeError +} = __nccwpck_require__(8707) + +const { chunksDecode } = __nccwpck_require__(9927) +const CHUNK_LIMIT = 128 * 1024 + +async function getResolveErrorBodyCallback ({ callback, body, contentType, statusCode, statusMessage, headers }) { + assert(body) + + let chunks = [] + let length = 0 + + try { + for await (const chunk of body) { + chunks.push(chunk) + length += chunk.length + if (length > CHUNK_LIMIT) { + chunks = [] + length = 0 + break + } + } + } catch { + chunks = [] + length = 0 + // Do nothing.... + } + + const message = `Response status code ${statusCode}${statusMessage ? `: ${statusMessage}` : ''}` + + if (statusCode === 204 || !contentType || !length) { + queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers))) + return + } + + const stackTraceLimit = Error.stackTraceLimit + Error.stackTraceLimit = 0 + let payload + + try { + if (isContentTypeApplicationJson(contentType)) { + payload = JSON.parse(chunksDecode(chunks, length)) + } else if (isContentTypeText(contentType)) { + payload = chunksDecode(chunks, length) + } + } catch { + // process in a callback to avoid throwing in the microtask queue + } finally { + Error.stackTraceLimit = stackTraceLimit + } + queueMicrotask(() => callback(new ResponseStatusCodeError(message, statusCode, headers, payload))) +} + +const isContentTypeApplicationJson = (contentType) => { + return ( + contentType.length > 15 && + contentType[11] === '/' && + contentType[0] === 'a' && + contentType[1] === 'p' && + contentType[2] === 'p' && + contentType[3] === 'l' && + contentType[4] === 'i' && + contentType[5] === 'c' && + contentType[6] === 'a' && + contentType[7] === 't' && + contentType[8] === 'i' && + contentType[9] === 'o' && + contentType[10] === 'n' && + contentType[12] === 'j' && + contentType[13] === 's' && + contentType[14] === 'o' && + contentType[15] === 'n' + ) +} + +const isContentTypeText = (contentType) => { + return ( + contentType.length > 4 && + contentType[4] === '/' && + contentType[0] === 't' && + contentType[1] === 'e' && + contentType[2] === 'x' && + contentType[3] === 't' + ) +} + +module.exports = { + getResolveErrorBodyCallback, + isContentTypeApplicationJson, + isContentTypeText +} + + +/***/ }), + +/***/ 9136: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const net = __nccwpck_require__(7030) +const assert = __nccwpck_require__(4589) +const util = __nccwpck_require__(3440) +const { InvalidArgumentError, ConnectTimeoutError } = __nccwpck_require__(8707) +const timers = __nccwpck_require__(6603) + +function noop () {} + +let tls // include tls conditionally since it is not always available + +// TODO: session re-use does not wait for the first +// connection to resolve the session and might therefore +// resolve the same servername multiple times even when +// re-use is enabled. + +let SessionCache +// FIXME: remove workaround when the Node bug is fixed +// https://github.com/nodejs/node/issues/49344#issuecomment-1741776308 +if (global.FinalizationRegistry && !(process.env.NODE_V8_COVERAGE || process.env.UNDICI_NO_FG)) { + SessionCache = class WeakSessionCache { + constructor (maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions + this._sessionCache = new Map() + this._sessionRegistry = new global.FinalizationRegistry((key) => { + if (this._sessionCache.size < this._maxCachedSessions) { + return + } + + const ref = this._sessionCache.get(key) + if (ref !== undefined && ref.deref() === undefined) { + this._sessionCache.delete(key) + } + }) + } + + get (sessionKey) { + const ref = this._sessionCache.get(sessionKey) + return ref ? ref.deref() : null + } + + set (sessionKey, session) { + if (this._maxCachedSessions === 0) { + return + } + + this._sessionCache.set(sessionKey, new WeakRef(session)) + this._sessionRegistry.register(session, sessionKey) + } + } +} else { + SessionCache = class SimpleSessionCache { + constructor (maxCachedSessions) { + this._maxCachedSessions = maxCachedSessions + this._sessionCache = new Map() + } + + get (sessionKey) { + return this._sessionCache.get(sessionKey) + } + + set (sessionKey, session) { + if (this._maxCachedSessions === 0) { + return + } + + if (this._sessionCache.size >= this._maxCachedSessions) { + // remove the oldest session + const { value: oldestKey } = this._sessionCache.keys().next() + this._sessionCache.delete(oldestKey) + } + + this._sessionCache.set(sessionKey, session) + } + } +} + +function buildConnector ({ allowH2, maxCachedSessions, socketPath, timeout, session: customSession, ...opts }) { + if (maxCachedSessions != null && (!Number.isInteger(maxCachedSessions) || maxCachedSessions < 0)) { + throw new InvalidArgumentError('maxCachedSessions must be a positive integer or zero') + } + + const options = { path: socketPath, ...opts } + const sessionCache = new SessionCache(maxCachedSessions == null ? 100 : maxCachedSessions) + timeout = timeout == null ? 10e3 : timeout + allowH2 = allowH2 != null ? allowH2 : false + return function connect ({ hostname, host, protocol, port, servername, localAddress, httpSocket }, callback) { + let socket + if (protocol === 'https:') { + if (!tls) { + tls = __nccwpck_require__(1692) + } + servername = servername || options.servername || util.getServerName(host) || null + + const sessionKey = servername || hostname + assert(sessionKey) + + const session = customSession || sessionCache.get(sessionKey) || null + + port = port || 443 + + socket = tls.connect({ + highWaterMark: 16384, // TLS in node can't have bigger HWM anyway... + ...options, + servername, + session, + localAddress, + // TODO(HTTP/2): Add support for h2c + ALPNProtocols: allowH2 ? ['http/1.1', 'h2'] : ['http/1.1'], + socket: httpSocket, // upgrade socket connection + port, + host: hostname + }) + + socket + .on('session', function (session) { + // TODO (fix): Can a session become invalid once established? Don't think so? + sessionCache.set(sessionKey, session) + }) + } else { + assert(!httpSocket, 'httpSocket can only be sent on TLS update') + + port = port || 80 + + socket = net.connect({ + highWaterMark: 64 * 1024, // Same as nodejs fs streams. + ...options, + localAddress, + port, + host: hostname + }) + } + + // Set TCP keep alive options on the socket here instead of in connect() for the case of assigning the socket + if (options.keepAlive == null || options.keepAlive) { + const keepAliveInitialDelay = options.keepAliveInitialDelay === undefined ? 60e3 : options.keepAliveInitialDelay + socket.setKeepAlive(true, keepAliveInitialDelay) + } + + const clearConnectTimeout = setupConnectTimeout(new WeakRef(socket), { timeout, hostname, port }) + + socket + .setNoDelay(true) + .once(protocol === 'https:' ? 'secureConnect' : 'connect', function () { + queueMicrotask(clearConnectTimeout) + + if (callback) { + const cb = callback + callback = null + cb(null, this) + } + }) + .on('error', function (err) { + queueMicrotask(clearConnectTimeout) + + if (callback) { + const cb = callback + callback = null + cb(err) + } + }) + + return socket + } +} + +/** + * @param {WeakRef} socketWeakRef + * @param {object} opts + * @param {number} opts.timeout + * @param {string} opts.hostname + * @param {number} opts.port + * @returns {() => void} + */ +const setupConnectTimeout = process.platform === 'win32' + ? (socketWeakRef, opts) => { + if (!opts.timeout) { + return noop + } + + let s1 = null + let s2 = null + const fastTimer = timers.setFastTimeout(() => { + // setImmediate is added to make sure that we prioritize socket error events over timeouts + s1 = setImmediate(() => { + // Windows needs an extra setImmediate probably due to implementation differences in the socket logic + s2 = setImmediate(() => onConnectTimeout(socketWeakRef.deref(), opts)) + }) + }, opts.timeout) + return () => { + timers.clearFastTimeout(fastTimer) + clearImmediate(s1) + clearImmediate(s2) + } + } + : (socketWeakRef, opts) => { + if (!opts.timeout) { + return noop + } + + let s1 = null + const fastTimer = timers.setFastTimeout(() => { + // setImmediate is added to make sure that we prioritize socket error events over timeouts + s1 = setImmediate(() => { + onConnectTimeout(socketWeakRef.deref(), opts) + }) + }, opts.timeout) + return () => { + timers.clearFastTimeout(fastTimer) + clearImmediate(s1) + } + } + +/** + * @param {net.Socket} socket + * @param {object} opts + * @param {number} opts.timeout + * @param {string} opts.hostname + * @param {number} opts.port + */ +function onConnectTimeout (socket, opts) { + // The socket could be already garbage collected + if (socket == null) { + return + } + + let message = 'Connect Timeout Error' + if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) { + message += ` (attempted addresses: ${socket.autoSelectFamilyAttemptedAddresses.join(', ')},` + } else { + message += ` (attempted address: ${opts.hostname}:${opts.port},` + } + + message += ` timeout: ${opts.timeout}ms)` + + util.destroy(socket, new ConnectTimeoutError(message)) +} + +module.exports = buildConnector + + +/***/ }), + +/***/ 735: +/***/ ((module) => { + +"use strict"; + + +/** @type {Record} */ +const headerNameLowerCasedRecord = {} + +// https://developer.mozilla.org/docs/Web/HTTP/Headers +const wellknownHeaderNames = [ + 'Accept', + 'Accept-Encoding', + 'Accept-Language', + 'Accept-Ranges', + 'Access-Control-Allow-Credentials', + 'Access-Control-Allow-Headers', + 'Access-Control-Allow-Methods', + 'Access-Control-Allow-Origin', + 'Access-Control-Expose-Headers', + 'Access-Control-Max-Age', + 'Access-Control-Request-Headers', + 'Access-Control-Request-Method', + 'Age', + 'Allow', + 'Alt-Svc', + 'Alt-Used', + 'Authorization', + 'Cache-Control', + 'Clear-Site-Data', + 'Connection', + 'Content-Disposition', + 'Content-Encoding', + 'Content-Language', + 'Content-Length', + 'Content-Location', + 'Content-Range', + 'Content-Security-Policy', + 'Content-Security-Policy-Report-Only', + 'Content-Type', + 'Cookie', + 'Cross-Origin-Embedder-Policy', + 'Cross-Origin-Opener-Policy', + 'Cross-Origin-Resource-Policy', + 'Date', + 'Device-Memory', + 'Downlink', + 'ECT', + 'ETag', + 'Expect', + 'Expect-CT', + 'Expires', + 'Forwarded', + 'From', + 'Host', + 'If-Match', + 'If-Modified-Since', + 'If-None-Match', + 'If-Range', + 'If-Unmodified-Since', + 'Keep-Alive', + 'Last-Modified', + 'Link', + 'Location', + 'Max-Forwards', + 'Origin', + 'Permissions-Policy', + 'Pragma', + 'Proxy-Authenticate', + 'Proxy-Authorization', + 'RTT', + 'Range', + 'Referer', + 'Referrer-Policy', + 'Refresh', + 'Retry-After', + 'Sec-WebSocket-Accept', + 'Sec-WebSocket-Extensions', + 'Sec-WebSocket-Key', + 'Sec-WebSocket-Protocol', + 'Sec-WebSocket-Version', + 'Server', + 'Server-Timing', + 'Service-Worker-Allowed', + 'Service-Worker-Navigation-Preload', + 'Set-Cookie', + 'SourceMap', + 'Strict-Transport-Security', + 'Supports-Loading-Mode', + 'TE', + 'Timing-Allow-Origin', + 'Trailer', + 'Transfer-Encoding', + 'Upgrade', + 'Upgrade-Insecure-Requests', + 'User-Agent', + 'Vary', + 'Via', + 'WWW-Authenticate', + 'X-Content-Type-Options', + 'X-DNS-Prefetch-Control', + 'X-Frame-Options', + 'X-Permitted-Cross-Domain-Policies', + 'X-Powered-By', + 'X-Requested-With', + 'X-XSS-Protection' +] + +for (let i = 0; i < wellknownHeaderNames.length; ++i) { + const key = wellknownHeaderNames[i] + const lowerCasedKey = key.toLowerCase() + headerNameLowerCasedRecord[key] = headerNameLowerCasedRecord[lowerCasedKey] = + lowerCasedKey +} + +// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. +Object.setPrototypeOf(headerNameLowerCasedRecord, null) + +module.exports = { + wellknownHeaderNames, + headerNameLowerCasedRecord +} + + +/***/ }), + +/***/ 2414: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + +const diagnosticsChannel = __nccwpck_require__(3053) +const util = __nccwpck_require__(7975) + +const undiciDebugLog = util.debuglog('undici') +const fetchDebuglog = util.debuglog('fetch') +const websocketDebuglog = util.debuglog('websocket') +let isClientSet = false +const channels = { + // Client + beforeConnect: diagnosticsChannel.channel('undici:client:beforeConnect'), + connected: diagnosticsChannel.channel('undici:client:connected'), + connectError: diagnosticsChannel.channel('undici:client:connectError'), + sendHeaders: diagnosticsChannel.channel('undici:client:sendHeaders'), + // Request + create: diagnosticsChannel.channel('undici:request:create'), + bodySent: diagnosticsChannel.channel('undici:request:bodySent'), + headers: diagnosticsChannel.channel('undici:request:headers'), + trailers: diagnosticsChannel.channel('undici:request:trailers'), + error: diagnosticsChannel.channel('undici:request:error'), + // WebSocket + open: diagnosticsChannel.channel('undici:websocket:open'), + close: diagnosticsChannel.channel('undici:websocket:close'), + socketError: diagnosticsChannel.channel('undici:websocket:socket_error'), + ping: diagnosticsChannel.channel('undici:websocket:ping'), + pong: diagnosticsChannel.channel('undici:websocket:pong') +} + +if (undiciDebugLog.enabled || fetchDebuglog.enabled) { + const debuglog = fetchDebuglog.enabled ? fetchDebuglog : undiciDebugLog + + // Track all Client events + diagnosticsChannel.channel('undici:client:beforeConnect').subscribe(evt => { + const { + connectParams: { version, protocol, port, host } + } = evt + debuglog( + 'connecting to %s using %s%s', + `${host}${port ? `:${port}` : ''}`, + protocol, + version + ) + }) + + diagnosticsChannel.channel('undici:client:connected').subscribe(evt => { + const { + connectParams: { version, protocol, port, host } + } = evt + debuglog( + 'connected to %s using %s%s', + `${host}${port ? `:${port}` : ''}`, + protocol, + version + ) + }) + + diagnosticsChannel.channel('undici:client:connectError').subscribe(evt => { + const { + connectParams: { version, protocol, port, host }, + error + } = evt + debuglog( + 'connection to %s using %s%s errored - %s', + `${host}${port ? `:${port}` : ''}`, + protocol, + version, + error.message + ) + }) + + diagnosticsChannel.channel('undici:client:sendHeaders').subscribe(evt => { + const { + request: { method, path, origin } + } = evt + debuglog('sending request to %s %s/%s', method, origin, path) + }) + + // Track Request events + diagnosticsChannel.channel('undici:request:headers').subscribe(evt => { + const { + request: { method, path, origin }, + response: { statusCode } + } = evt + debuglog( + 'received response to %s %s/%s - HTTP %d', + method, + origin, + path, + statusCode + ) + }) + + diagnosticsChannel.channel('undici:request:trailers').subscribe(evt => { + const { + request: { method, path, origin } + } = evt + debuglog('trailers received from %s %s/%s', method, origin, path) + }) + + diagnosticsChannel.channel('undici:request:error').subscribe(evt => { + const { + request: { method, path, origin }, + error + } = evt + debuglog( + 'request to %s %s/%s errored - %s', + method, + origin, + path, + error.message + ) + }) + + isClientSet = true +} + +if (websocketDebuglog.enabled) { + if (!isClientSet) { + const debuglog = undiciDebugLog.enabled ? undiciDebugLog : websocketDebuglog + diagnosticsChannel.channel('undici:client:beforeConnect').subscribe(evt => { + const { + connectParams: { version, protocol, port, host } + } = evt + debuglog( + 'connecting to %s%s using %s%s', + host, + port ? `:${port}` : '', + protocol, + version + ) + }) + + diagnosticsChannel.channel('undici:client:connected').subscribe(evt => { + const { + connectParams: { version, protocol, port, host } + } = evt + debuglog( + 'connected to %s%s using %s%s', + host, + port ? `:${port}` : '', + protocol, + version + ) + }) + + diagnosticsChannel.channel('undici:client:connectError').subscribe(evt => { + const { + connectParams: { version, protocol, port, host }, + error + } = evt + debuglog( + 'connection to %s%s using %s%s errored - %s', + host, + port ? `:${port}` : '', + protocol, + version, + error.message + ) + }) + + diagnosticsChannel.channel('undici:client:sendHeaders').subscribe(evt => { + const { + request: { method, path, origin } + } = evt + debuglog('sending request to %s %s/%s', method, origin, path) + }) + } + + // Track all WebSocket events + diagnosticsChannel.channel('undici:websocket:open').subscribe(evt => { + const { + address: { address, port } + } = evt + websocketDebuglog('connection opened %s%s', address, port ? `:${port}` : '') + }) + + diagnosticsChannel.channel('undici:websocket:close').subscribe(evt => { + const { websocket, code, reason } = evt + websocketDebuglog( + 'closed connection to %s - %s %s', + websocket.url, + code, + reason + ) + }) + + diagnosticsChannel.channel('undici:websocket:socket_error').subscribe(err => { + websocketDebuglog('connection errored - %s', err.message) + }) + + diagnosticsChannel.channel('undici:websocket:ping').subscribe(evt => { + websocketDebuglog('ping received') + }) + + diagnosticsChannel.channel('undici:websocket:pong').subscribe(evt => { + websocketDebuglog('pong received') + }) +} + +module.exports = { + channels +} + + +/***/ }), + +/***/ 8707: +/***/ ((module) => { + +"use strict"; + + +const kUndiciError = Symbol.for('undici.error.UND_ERR') +class UndiciError extends Error { + constructor (message) { + super(message) + this.name = 'UndiciError' + this.code = 'UND_ERR' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kUndiciError] === true + } + + [kUndiciError] = true +} + +const kConnectTimeoutError = Symbol.for('undici.error.UND_ERR_CONNECT_TIMEOUT') +class ConnectTimeoutError extends UndiciError { + constructor (message) { + super(message) + this.name = 'ConnectTimeoutError' + this.message = message || 'Connect Timeout Error' + this.code = 'UND_ERR_CONNECT_TIMEOUT' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kConnectTimeoutError] === true + } + + [kConnectTimeoutError] = true +} + +const kHeadersTimeoutError = Symbol.for('undici.error.UND_ERR_HEADERS_TIMEOUT') +class HeadersTimeoutError extends UndiciError { + constructor (message) { + super(message) + this.name = 'HeadersTimeoutError' + this.message = message || 'Headers Timeout Error' + this.code = 'UND_ERR_HEADERS_TIMEOUT' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kHeadersTimeoutError] === true + } + + [kHeadersTimeoutError] = true +} + +const kHeadersOverflowError = Symbol.for('undici.error.UND_ERR_HEADERS_OVERFLOW') +class HeadersOverflowError extends UndiciError { + constructor (message) { + super(message) + this.name = 'HeadersOverflowError' + this.message = message || 'Headers Overflow Error' + this.code = 'UND_ERR_HEADERS_OVERFLOW' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kHeadersOverflowError] === true + } + + [kHeadersOverflowError] = true +} + +const kBodyTimeoutError = Symbol.for('undici.error.UND_ERR_BODY_TIMEOUT') +class BodyTimeoutError extends UndiciError { + constructor (message) { + super(message) + this.name = 'BodyTimeoutError' + this.message = message || 'Body Timeout Error' + this.code = 'UND_ERR_BODY_TIMEOUT' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kBodyTimeoutError] === true + } + + [kBodyTimeoutError] = true +} + +const kResponseStatusCodeError = Symbol.for('undici.error.UND_ERR_RESPONSE_STATUS_CODE') +class ResponseStatusCodeError extends UndiciError { + constructor (message, statusCode, headers, body) { + super(message) + this.name = 'ResponseStatusCodeError' + this.message = message || 'Response Status Code Error' + this.code = 'UND_ERR_RESPONSE_STATUS_CODE' + this.body = body + this.status = statusCode + this.statusCode = statusCode + this.headers = headers + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kResponseStatusCodeError] === true + } + + [kResponseStatusCodeError] = true +} + +const kInvalidArgumentError = Symbol.for('undici.error.UND_ERR_INVALID_ARG') +class InvalidArgumentError extends UndiciError { + constructor (message) { + super(message) + this.name = 'InvalidArgumentError' + this.message = message || 'Invalid Argument Error' + this.code = 'UND_ERR_INVALID_ARG' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kInvalidArgumentError] === true + } + + [kInvalidArgumentError] = true +} + +const kInvalidReturnValueError = Symbol.for('undici.error.UND_ERR_INVALID_RETURN_VALUE') +class InvalidReturnValueError extends UndiciError { + constructor (message) { + super(message) + this.name = 'InvalidReturnValueError' + this.message = message || 'Invalid Return Value Error' + this.code = 'UND_ERR_INVALID_RETURN_VALUE' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kInvalidReturnValueError] === true + } + + [kInvalidReturnValueError] = true +} + +const kAbortError = Symbol.for('undici.error.UND_ERR_ABORT') +class AbortError extends UndiciError { + constructor (message) { + super(message) + this.name = 'AbortError' + this.message = message || 'The operation was aborted' + this.code = 'UND_ERR_ABORT' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kAbortError] === true + } + + [kAbortError] = true +} + +const kRequestAbortedError = Symbol.for('undici.error.UND_ERR_ABORTED') +class RequestAbortedError extends AbortError { + constructor (message) { + super(message) + this.name = 'AbortError' + this.message = message || 'Request aborted' + this.code = 'UND_ERR_ABORTED' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kRequestAbortedError] === true + } + + [kRequestAbortedError] = true +} + +const kInformationalError = Symbol.for('undici.error.UND_ERR_INFO') +class InformationalError extends UndiciError { + constructor (message) { + super(message) + this.name = 'InformationalError' + this.message = message || 'Request information' + this.code = 'UND_ERR_INFO' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kInformationalError] === true + } + + [kInformationalError] = true +} + +const kRequestContentLengthMismatchError = Symbol.for('undici.error.UND_ERR_REQ_CONTENT_LENGTH_MISMATCH') +class RequestContentLengthMismatchError extends UndiciError { + constructor (message) { + super(message) + this.name = 'RequestContentLengthMismatchError' + this.message = message || 'Request body length does not match content-length header' + this.code = 'UND_ERR_REQ_CONTENT_LENGTH_MISMATCH' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kRequestContentLengthMismatchError] === true + } + + [kRequestContentLengthMismatchError] = true +} + +const kResponseContentLengthMismatchError = Symbol.for('undici.error.UND_ERR_RES_CONTENT_LENGTH_MISMATCH') +class ResponseContentLengthMismatchError extends UndiciError { + constructor (message) { + super(message) + this.name = 'ResponseContentLengthMismatchError' + this.message = message || 'Response body length does not match content-length header' + this.code = 'UND_ERR_RES_CONTENT_LENGTH_MISMATCH' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kResponseContentLengthMismatchError] === true + } + + [kResponseContentLengthMismatchError] = true +} + +const kClientDestroyedError = Symbol.for('undici.error.UND_ERR_DESTROYED') +class ClientDestroyedError extends UndiciError { + constructor (message) { + super(message) + this.name = 'ClientDestroyedError' + this.message = message || 'The client is destroyed' + this.code = 'UND_ERR_DESTROYED' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kClientDestroyedError] === true + } + + [kClientDestroyedError] = true +} + +const kClientClosedError = Symbol.for('undici.error.UND_ERR_CLOSED') +class ClientClosedError extends UndiciError { + constructor (message) { + super(message) + this.name = 'ClientClosedError' + this.message = message || 'The client is closed' + this.code = 'UND_ERR_CLOSED' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kClientClosedError] === true + } + + [kClientClosedError] = true +} + +const kSocketError = Symbol.for('undici.error.UND_ERR_SOCKET') +class SocketError extends UndiciError { + constructor (message, socket) { + super(message) + this.name = 'SocketError' + this.message = message || 'Socket error' + this.code = 'UND_ERR_SOCKET' + this.socket = socket + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kSocketError] === true + } + + [kSocketError] = true +} + +const kNotSupportedError = Symbol.for('undici.error.UND_ERR_NOT_SUPPORTED') +class NotSupportedError extends UndiciError { + constructor (message) { + super(message) + this.name = 'NotSupportedError' + this.message = message || 'Not supported error' + this.code = 'UND_ERR_NOT_SUPPORTED' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kNotSupportedError] === true + } + + [kNotSupportedError] = true +} + +const kBalancedPoolMissingUpstreamError = Symbol.for('undici.error.UND_ERR_BPL_MISSING_UPSTREAM') +class BalancedPoolMissingUpstreamError extends UndiciError { + constructor (message) { + super(message) + this.name = 'MissingUpstreamError' + this.message = message || 'No upstream has been added to the BalancedPool' + this.code = 'UND_ERR_BPL_MISSING_UPSTREAM' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kBalancedPoolMissingUpstreamError] === true + } + + [kBalancedPoolMissingUpstreamError] = true +} + +const kHTTPParserError = Symbol.for('undici.error.UND_ERR_HTTP_PARSER') +class HTTPParserError extends Error { + constructor (message, code, data) { + super(message) + this.name = 'HTTPParserError' + this.code = code ? `HPE_${code}` : undefined + this.data = data ? data.toString() : undefined + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kHTTPParserError] === true + } + + [kHTTPParserError] = true +} + +const kResponseExceededMaxSizeError = Symbol.for('undici.error.UND_ERR_RES_EXCEEDED_MAX_SIZE') +class ResponseExceededMaxSizeError extends UndiciError { + constructor (message) { + super(message) + this.name = 'ResponseExceededMaxSizeError' + this.message = message || 'Response content exceeded max size' + this.code = 'UND_ERR_RES_EXCEEDED_MAX_SIZE' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kResponseExceededMaxSizeError] === true + } + + [kResponseExceededMaxSizeError] = true +} + +const kRequestRetryError = Symbol.for('undici.error.UND_ERR_REQ_RETRY') +class RequestRetryError extends UndiciError { + constructor (message, code, { headers, data }) { + super(message) + this.name = 'RequestRetryError' + this.message = message || 'Request retry error' + this.code = 'UND_ERR_REQ_RETRY' + this.statusCode = code + this.data = data + this.headers = headers + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kRequestRetryError] === true + } + + [kRequestRetryError] = true +} + +const kResponseError = Symbol.for('undici.error.UND_ERR_RESPONSE') +class ResponseError extends UndiciError { + constructor (message, code, { headers, data }) { + super(message) + this.name = 'ResponseError' + this.message = message || 'Response error' + this.code = 'UND_ERR_RESPONSE' + this.statusCode = code + this.data = data + this.headers = headers + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kResponseError] === true + } + + [kResponseError] = true +} + +const kSecureProxyConnectionError = Symbol.for('undici.error.UND_ERR_PRX_TLS') +class SecureProxyConnectionError extends UndiciError { + constructor (cause, message, options) { + super(message, { cause, ...(options ?? {}) }) + this.name = 'SecureProxyConnectionError' + this.message = message || 'Secure Proxy Connection failed' + this.code = 'UND_ERR_PRX_TLS' + this.cause = cause + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kSecureProxyConnectionError] === true + } + + [kSecureProxyConnectionError] = true +} + +const kMessageSizeExceededError = Symbol.for('undici.error.UND_ERR_WS_MESSAGE_SIZE_EXCEEDED') +class MessageSizeExceededError extends UndiciError { + constructor (message) { + super(message) + this.name = 'MessageSizeExceededError' + this.message = message || 'Max decompressed message size exceeded' + this.code = 'UND_ERR_WS_MESSAGE_SIZE_EXCEEDED' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kMessageSizeExceededError] === true + } + + get [kMessageSizeExceededError] () { + return true + } +} + +module.exports = { + AbortError, + HTTPParserError, + UndiciError, + HeadersTimeoutError, + HeadersOverflowError, + BodyTimeoutError, + RequestContentLengthMismatchError, + ConnectTimeoutError, + ResponseStatusCodeError, + InvalidArgumentError, + InvalidReturnValueError, + RequestAbortedError, + ClientDestroyedError, + ClientClosedError, + InformationalError, + SocketError, + NotSupportedError, + ResponseContentLengthMismatchError, + BalancedPoolMissingUpstreamError, + ResponseExceededMaxSizeError, + RequestRetryError, + ResponseError, + SecureProxyConnectionError, + MessageSizeExceededError +} + + +/***/ }), + +/***/ 4655: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + InvalidArgumentError, + NotSupportedError +} = __nccwpck_require__(8707) +const assert = __nccwpck_require__(4589) +const { + isValidHTTPToken, + isValidHeaderValue, + isStream, + destroy, + isBuffer, + isFormDataLike, + isIterable, + isBlobLike, + buildURL, + validateHandler, + getServerName, + normalizedMethodRecords +} = __nccwpck_require__(3440) +const { channels } = __nccwpck_require__(2414) +const { headerNameLowerCasedRecord } = __nccwpck_require__(735) + +// Verifies that a given path is valid does not contain control chars \x00 to \x20 +const invalidPathRegex = /[^\u0021-\u00ff]/ + +const kHandler = Symbol('handler') + +class Request { + constructor (origin, { + path, + method, + body, + headers, + query, + idempotent, + blocking, + upgrade, + headersTimeout, + bodyTimeout, + reset, + throwOnError, + expectContinue, + servername + }, handler) { + if (typeof path !== 'string') { + throw new InvalidArgumentError('path must be a string') + } else if ( + path[0] !== '/' && + !(path.startsWith('http://') || path.startsWith('https://')) && + method !== 'CONNECT' + ) { + throw new InvalidArgumentError('path must be an absolute URL or start with a slash') + } else if (invalidPathRegex.test(path)) { + throw new InvalidArgumentError('invalid request path') + } + + if (typeof method !== 'string') { + throw new InvalidArgumentError('method must be a string') + } else if (normalizedMethodRecords[method] === undefined && !isValidHTTPToken(method)) { + throw new InvalidArgumentError('invalid request method') + } + + if (upgrade && typeof upgrade !== 'string') { + throw new InvalidArgumentError('upgrade must be a string') + } + + if (upgrade && !isValidHeaderValue(upgrade)) { + throw new InvalidArgumentError('invalid upgrade header') + } + + if (headersTimeout != null && (!Number.isFinite(headersTimeout) || headersTimeout < 0)) { + throw new InvalidArgumentError('invalid headersTimeout') + } + + if (bodyTimeout != null && (!Number.isFinite(bodyTimeout) || bodyTimeout < 0)) { + throw new InvalidArgumentError('invalid bodyTimeout') + } + + if (reset != null && typeof reset !== 'boolean') { + throw new InvalidArgumentError('invalid reset') + } + + if (expectContinue != null && typeof expectContinue !== 'boolean') { + throw new InvalidArgumentError('invalid expectContinue') + } + + this.headersTimeout = headersTimeout + + this.bodyTimeout = bodyTimeout + + this.throwOnError = throwOnError === true + + this.method = method + + this.abort = null + + if (body == null) { + this.body = null + } else if (isStream(body)) { + this.body = body + + const rState = this.body._readableState + if (!rState || !rState.autoDestroy) { + this.endHandler = function autoDestroy () { + destroy(this) + } + this.body.on('end', this.endHandler) + } + + this.errorHandler = err => { + if (this.abort) { + this.abort(err) + } else { + this.error = err + } + } + this.body.on('error', this.errorHandler) + } else if (isBuffer(body)) { + this.body = body.byteLength ? body : null + } else if (ArrayBuffer.isView(body)) { + this.body = body.buffer.byteLength ? Buffer.from(body.buffer, body.byteOffset, body.byteLength) : null + } else if (body instanceof ArrayBuffer) { + this.body = body.byteLength ? Buffer.from(body) : null + } else if (typeof body === 'string') { + this.body = body.length ? Buffer.from(body) : null + } else if (isFormDataLike(body) || isIterable(body) || isBlobLike(body)) { + this.body = body + } else { + throw new InvalidArgumentError('body must be a string, a Buffer, a Readable stream, an iterable, or an async iterable') + } + + this.completed = false + + this.aborted = false + + this.upgrade = upgrade || null + + this.path = query ? buildURL(path, query) : path + + this.origin = origin + + this.idempotent = idempotent == null + ? method === 'HEAD' || method === 'GET' + : idempotent + + this.blocking = blocking == null ? false : blocking + + this.reset = reset == null ? null : reset + + this.host = null + + this.contentLength = null + + this.contentType = null + + this.headers = [] + + // Only for H2 + this.expectContinue = expectContinue != null ? expectContinue : false + + if (Array.isArray(headers)) { + if (headers.length % 2 !== 0) { + throw new InvalidArgumentError('headers array must be even') + } + for (let i = 0; i < headers.length; i += 2) { + processHeader(this, headers[i], headers[i + 1]) + } + } else if (headers && typeof headers === 'object') { + if (headers[Symbol.iterator]) { + for (const header of headers) { + if (!Array.isArray(header) || header.length !== 2) { + throw new InvalidArgumentError('headers must be in key-value pair format') + } + processHeader(this, header[0], header[1]) + } + } else { + const keys = Object.keys(headers) + for (let i = 0; i < keys.length; ++i) { + processHeader(this, keys[i], headers[keys[i]]) + } + } + } else if (headers != null) { + throw new InvalidArgumentError('headers must be an object or an array') + } + + validateHandler(handler, method, upgrade) + + this.servername = servername || getServerName(this.host) + + this[kHandler] = handler + + if (channels.create.hasSubscribers) { + channels.create.publish({ request: this }) + } + } + + onBodySent (chunk) { + if (this[kHandler].onBodySent) { + try { + return this[kHandler].onBodySent(chunk) + } catch (err) { + this.abort(err) + } + } + } + + onRequestSent () { + if (channels.bodySent.hasSubscribers) { + channels.bodySent.publish({ request: this }) + } + + if (this[kHandler].onRequestSent) { + try { + return this[kHandler].onRequestSent() + } catch (err) { + this.abort(err) + } + } + } + + onConnect (abort) { + assert(!this.aborted) + assert(!this.completed) + + if (this.error) { + abort(this.error) + } else { + this.abort = abort + return this[kHandler].onConnect(abort) + } + } + + onResponseStarted () { + return this[kHandler].onResponseStarted?.() + } + + onHeaders (statusCode, headers, resume, statusText) { + assert(!this.aborted) + assert(!this.completed) + + if (channels.headers.hasSubscribers) { + channels.headers.publish({ request: this, response: { statusCode, headers, statusText } }) + } + + try { + return this[kHandler].onHeaders(statusCode, headers, resume, statusText) + } catch (err) { + this.abort(err) + } + } + + onData (chunk) { + assert(!this.aborted) + assert(!this.completed) + + try { + return this[kHandler].onData(chunk) + } catch (err) { + this.abort(err) + return false + } + } + + /** + * @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) + + 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) { + this.onFinally() + + assert(!this.aborted) + + this.completed = true + if (channels.trailers.hasSubscribers) { + channels.trailers.publish({ request: this, trailers }) + } + + try { + return this[kHandler].onComplete(trailers) + } catch (err) { + // TODO (fix): This might be a bad idea? + this.onError(err) + } + } + + onError (error) { + this.onFinally() + + if (channels.error.hasSubscribers) { + channels.error.publish({ request: this, error }) + } + + if (this.aborted) { + return + } + this.aborted = true + + return this[kHandler].onError(error) + } + + onFinally () { + if (this.errorHandler) { + this.body.off('error', this.errorHandler) + this.errorHandler = null + } + + if (this.endHandler) { + this.body.off('end', this.endHandler) + this.endHandler = null + } + } + + addHeader (key, value) { + processHeader(this, key, value) + return this + } +} + +function processHeader (request, key, val) { + if (val && (typeof val === 'object' && !Array.isArray(val))) { + throw new InvalidArgumentError(`invalid ${key} header`) + } else if (val === undefined) { + return + } + + let headerName = headerNameLowerCasedRecord[key] + + if (headerName === undefined) { + headerName = key.toLowerCase() + if (headerNameLowerCasedRecord[headerName] === undefined && !isValidHTTPToken(headerName)) { + throw new InvalidArgumentError('invalid header key') + } + } + + if (Array.isArray(val)) { + const arr = [] + for (let i = 0; i < val.length; i++) { + if (typeof val[i] === 'string') { + if (!isValidHeaderValue(val[i])) { + throw new InvalidArgumentError(`invalid ${key} header`) + } + arr.push(val[i]) + } else if (val[i] === null) { + arr.push('') + } else if (typeof val[i] === 'object') { + throw new InvalidArgumentError(`invalid ${key} header`) + } else { + // Coerce primitives (and reject unsafe coercions such as functions + // with a crafted toString/Symbol.toPrimitive). + const str = `${val[i]}` + if (!isValidHeaderValue(str)) { + throw new InvalidArgumentError(`invalid ${key} header`) + } + arr.push(str) + } + } + val = arr + } else if (typeof val === 'string') { + if (!isValidHeaderValue(val)) { + throw new InvalidArgumentError(`invalid ${key} header`) + } + } else if (val === null) { + val = '' + } else { + // Coerce primitives (and reject unsafe coercions such as functions + // with a crafted toString/Symbol.toPrimitive). + val = `${val}` + if (!isValidHeaderValue(val)) { + throw new InvalidArgumentError(`invalid ${key} header`) + } + } + + if (headerName === 'host') { + if (request.host !== null) { + throw new InvalidArgumentError('duplicate host header') + } + if (typeof val !== 'string') { + throw new InvalidArgumentError('invalid host header') + } + // Consumed by Client + request.host = val + } else if (headerName === 'content-length') { + if (request.contentLength !== null) { + throw new InvalidArgumentError('duplicate content-length header') + } + request.contentLength = parseInt(val, 10) + if (!Number.isFinite(request.contentLength)) { + throw new InvalidArgumentError('invalid content-length header') + } + } else if (request.contentType === null && headerName === 'content-type') { + request.contentType = val + request.headers.push(key, val) + } else if (headerName === 'transfer-encoding' || headerName === 'keep-alive' || headerName === 'upgrade') { + throw new InvalidArgumentError(`invalid ${headerName} header`) + } else if (headerName === 'connection') { + const value = typeof val === 'string' ? val.toLowerCase() : null + if (value !== 'close' && value !== 'keep-alive') { + throw new InvalidArgumentError('invalid connection header') + } + + if (value === 'close') { + request.reset = true + } + } else if (headerName === 'expect') { + throw new NotSupportedError('expect header not supported') + } else { + request.headers.push(key, val) + } +} + +module.exports = Request + + +/***/ }), + +/***/ 6443: +/***/ ((module) => { + +module.exports = { + kClose: Symbol('close'), + kDestroy: Symbol('destroy'), + kDispatch: Symbol('dispatch'), + kUrl: Symbol('url'), + kWriting: Symbol('writing'), + kResuming: Symbol('resuming'), + kQueue: Symbol('queue'), + kConnect: Symbol('connect'), + kConnecting: Symbol('connecting'), + kKeepAliveDefaultTimeout: Symbol('default keep alive timeout'), + kKeepAliveMaxTimeout: Symbol('max keep alive timeout'), + kKeepAliveTimeoutThreshold: Symbol('keep alive timeout threshold'), + kKeepAliveTimeoutValue: Symbol('keep alive timeout'), + kKeepAlive: Symbol('keep alive'), + kHeadersTimeout: Symbol('headers timeout'), + kBodyTimeout: Symbol('body timeout'), + kServerName: Symbol('server name'), + kLocalAddress: Symbol('local address'), + kHost: Symbol('host'), + kNoRef: Symbol('no ref'), + kBodyUsed: Symbol('used'), + kBody: Symbol('abstracted request body'), + kRunning: Symbol('running'), + kBlocking: Symbol('blocking'), + kPending: Symbol('pending'), + kSize: Symbol('size'), + kBusy: Symbol('busy'), + kQueued: Symbol('queued'), + kFree: Symbol('free'), + kConnected: Symbol('connected'), + kClosed: Symbol('closed'), + kNeedDrain: Symbol('need drain'), + kReset: Symbol('reset'), + kDestroyed: Symbol.for('nodejs.stream.destroyed'), + kResume: Symbol('resume'), + kOnError: Symbol('on error'), + kMaxHeadersSize: Symbol('max headers size'), + kRunningIdx: Symbol('running index'), + kPendingIdx: Symbol('pending index'), + kError: Symbol('error'), + kClients: Symbol('clients'), + kClient: Symbol('client'), + kParser: Symbol('parser'), + kOnDestroyed: Symbol('destroy callbacks'), + kPipelining: Symbol('pipelining'), + kSocket: Symbol('socket'), + kHostHeader: Symbol('host header'), + kConnector: Symbol('connector'), + kStrictContentLength: Symbol('strict content length'), + kMaxRedirections: Symbol('maxRedirections'), + kMaxRequests: Symbol('maxRequestsPerClient'), + kProxy: Symbol('proxy agent options'), + kCounter: Symbol('socket request counter'), + kInterceptors: Symbol('dispatch interceptors'), + kMaxResponseSize: Symbol('max response size'), + kHTTP2Session: Symbol('http2Session'), + kHTTP2SessionState: Symbol('http2Session state'), + kRetryHandlerDefaultRetry: Symbol('retry agent default retry'), + kConstruct: Symbol('constructable'), + kListeners: Symbol('listeners'), + kHTTPContext: Symbol('http context'), + kMaxConcurrentStreams: Symbol('max concurrent streams'), + kNoProxyAgent: Symbol('no proxy agent'), + kHttpProxyAgent: Symbol('http proxy agent'), + kHttpsProxyAgent: Symbol('https proxy agent') +} + + +/***/ }), + +/***/ 7752: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + wellknownHeaderNames, + headerNameLowerCasedRecord +} = __nccwpck_require__(735) + +class TstNode { + /** @type {any} */ + value = null + /** @type {null | TstNode} */ + left = null + /** @type {null | TstNode} */ + middle = null + /** @type {null | TstNode} */ + right = null + /** @type {number} */ + code + /** + * @param {string} key + * @param {any} value + * @param {number} index + */ + constructor (key, value, index) { + if (index === undefined || index >= key.length) { + throw new TypeError('Unreachable') + } + const code = this.code = key.charCodeAt(index) + // check code is ascii string + if (code > 0x7F) { + throw new TypeError('key must be ascii string') + } + if (key.length !== ++index) { + this.middle = new TstNode(key, value, index) + } else { + this.value = value + } + } + + /** + * @param {string} key + * @param {any} value + */ + add (key, value) { + const length = key.length + if (length === 0) { + throw new TypeError('Unreachable') + } + let index = 0 + let node = this + while (true) { + const code = key.charCodeAt(index) + // check code is ascii string + if (code > 0x7F) { + throw new TypeError('key must be ascii string') + } + if (node.code === code) { + if (length === ++index) { + node.value = value + break + } else if (node.middle !== null) { + node = node.middle + } else { + node.middle = new TstNode(key, value, index) + break + } + } else if (node.code < code) { + if (node.left !== null) { + node = node.left + } else { + node.left = new TstNode(key, value, index) + break + } + } else if (node.right !== null) { + node = node.right + } else { + node.right = new TstNode(key, value, index) + break + } + } + } + + /** + * @param {Uint8Array} key + * @return {TstNode | null} + */ + search (key) { + const keylength = key.length + let index = 0 + let node = this + while (node !== null && index < keylength) { + let code = key[index] + // A-Z + // First check if it is bigger than 0x5a. + // Lowercase letters have higher char codes than uppercase ones. + // Also we assume that headers will mostly contain lowercase characters. + if (code <= 0x5a && code >= 0x41) { + // Lowercase for uppercase. + code |= 32 + } + while (node !== null) { + if (code === node.code) { + if (keylength === ++index) { + // Returns Node since it is the last key. + return node + } + node = node.middle + break + } + node = node.code < code ? node.left : node.right + } + } + return null + } +} + +class TernarySearchTree { + /** @type {TstNode | null} */ + node = null + + /** + * @param {string} key + * @param {any} value + * */ + insert (key, value) { + if (this.node === null) { + this.node = new TstNode(key, value, 0) + } else { + this.node.add(key, value) + } + } + + /** + * @param {Uint8Array} key + * @return {any} + */ + lookup (key) { + return this.node?.search(key)?.value ?? null + } +} + +const tree = new TernarySearchTree() + +for (let i = 0; i < wellknownHeaderNames.length; ++i) { + const key = headerNameLowerCasedRecord[wellknownHeaderNames[i]] + tree.insert(key, key) +} + +module.exports = { + TernarySearchTree, + tree +} + + +/***/ }), + +/***/ 3440: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const assert = __nccwpck_require__(4589) +const { kDestroyed, kBodyUsed, kListeners, kBody } = __nccwpck_require__(6443) +const { IncomingMessage } = __nccwpck_require__(7067) +const stream = __nccwpck_require__(7075) +const net = __nccwpck_require__(7030) +const { Blob } = __nccwpck_require__(4573) +const nodeUtil = __nccwpck_require__(7975) +const { stringify } = __nccwpck_require__(1792) +const { EventEmitter: EE } = __nccwpck_require__(8474) +const { InvalidArgumentError } = __nccwpck_require__(8707) +const { headerNameLowerCasedRecord } = __nccwpck_require__(735) +const { tree } = __nccwpck_require__(7752) + +const [nodeMajor, nodeMinor] = process.versions.node.split('.').map(v => Number(v)) + +class BodyAsyncIterable { + constructor (body) { + this[kBody] = body + this[kBodyUsed] = false + } + + async * [Symbol.asyncIterator] () { + assert(!this[kBodyUsed], 'disturbed') + this[kBodyUsed] = true + yield * this[kBody] + } +} + +function wrapRequestBody (body) { + if (isStream(body)) { + // TODO (fix): Provide some way for the user to cache the file to e.g. /tmp + // so that it can be dispatched again? + // TODO (fix): Do we need 100-expect support to provide a way to do this properly? + if (bodyLength(body) === 0) { + body + .on('data', function () { + assert(false) + }) + } + + if (typeof body.readableDidRead !== 'boolean') { + body[kBodyUsed] = false + EE.prototype.on.call(body, 'data', function () { + this[kBodyUsed] = true + }) + } + + return body + } else if (body && typeof body.pipeTo === 'function') { + // TODO (fix): We can't access ReadableStream internal state + // to determine whether or not it has been disturbed. This is just + // a workaround. + return new BodyAsyncIterable(body) + } else if ( + body && + typeof body !== 'string' && + !ArrayBuffer.isView(body) && + isIterable(body) + ) { + // TODO: Should we allow re-using iterable if !this.opts.idempotent + // or through some other flag? + return new BodyAsyncIterable(body) + } else { + return body + } +} + +function nop () {} + +function isStream (obj) { + return obj && typeof obj === 'object' && typeof obj.pipe === 'function' && typeof obj.on === 'function' +} + +// based on https://github.com/node-fetch/fetch-blob/blob/8ab587d34080de94140b54f07168451e7d0b655e/index.js#L229-L241 (MIT License) +function isBlobLike (object) { + if (object === null) { + return false + } else if (object instanceof Blob) { + return true + } else if (typeof object !== 'object') { + return false + } else { + const sTag = object[Symbol.toStringTag] + + return (sTag === 'Blob' || sTag === 'File') && ( + ('stream' in object && typeof object.stream === 'function') || + ('arrayBuffer' in object && typeof object.arrayBuffer === 'function') + ) + } +} + +function buildURL (url, queryParams) { + if (url.includes('?') || url.includes('#')) { + throw new Error('Query params cannot be passed when url already contains "?" or "#".') + } + + const stringified = stringify(queryParams) + + if (stringified) { + url += '?' + stringified + } + + return url +} + +function isValidPort (port) { + const value = parseInt(port, 10) + return ( + value === Number(port) && + value >= 0 && + value <= 65535 + ) +} + +function isHttpOrHttpsPrefixed (value) { + return ( + value != null && + value[0] === 'h' && + value[1] === 't' && + value[2] === 't' && + value[3] === 'p' && + ( + value[4] === ':' || + ( + value[4] === 's' && + value[5] === ':' + ) + ) + ) +} + +function parseURL (url) { + if (typeof url === 'string') { + url = new URL(url) + + if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) { + throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.') + } + + return url + } + + if (!url || typeof url !== 'object') { + throw new InvalidArgumentError('Invalid URL: The URL argument must be a non-null object.') + } + + if (!(url instanceof URL)) { + if (url.port != null && url.port !== '' && isValidPort(url.port) === false) { + throw new InvalidArgumentError('Invalid URL: port must be a valid integer or a string representation of an integer.') + } + + if (url.path != null && typeof url.path !== 'string') { + throw new InvalidArgumentError('Invalid URL path: the path must be a string or null/undefined.') + } + + if (url.pathname != null && typeof url.pathname !== 'string') { + throw new InvalidArgumentError('Invalid URL pathname: the pathname must be a string or null/undefined.') + } + + if (url.hostname != null && typeof url.hostname !== 'string') { + throw new InvalidArgumentError('Invalid URL hostname: the hostname must be a string or null/undefined.') + } + + if (url.origin != null && typeof url.origin !== 'string') { + throw new InvalidArgumentError('Invalid URL origin: the origin must be a string or null/undefined.') + } + + if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) { + throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.') + } + + const port = url.port != null + ? url.port + : (url.protocol === 'https:' ? 443 : 80) + let origin = url.origin != null + ? url.origin + : `${url.protocol || ''}//${url.hostname || ''}:${port}` + let path = url.path != null + ? url.path + : `${url.pathname || ''}${url.search || ''}` + + if (origin[origin.length - 1] === '/') { + origin = origin.slice(0, origin.length - 1) + } + + if (path && path[0] !== '/') { + path = `/${path}` + } + // new URL(path, origin) is unsafe when `path` contains an absolute URL + // From https://developer.mozilla.org/en-US/docs/Web/API/URL/URL: + // If first parameter is a relative URL, second param is required, and will be used as the base URL. + // If first parameter is an absolute URL, a given second param will be ignored. + return new URL(`${origin}${path}`) + } + + if (!isHttpOrHttpsPrefixed(url.origin || url.protocol)) { + throw new InvalidArgumentError('Invalid URL protocol: the URL must start with `http:` or `https:`.') + } + + return url +} + +function parseOrigin (url) { + url = parseURL(url) + + if (url.pathname !== '/' || url.search || url.hash) { + throw new InvalidArgumentError('invalid url') + } + + return url +} + +function getHostname (host) { + if (host[0] === '[') { + const idx = host.indexOf(']') + + assert(idx !== -1) + return host.substring(1, idx) + } + + const idx = host.indexOf(':') + if (idx === -1) return host + + return host.substring(0, idx) +} + +// IP addresses are not valid server names per RFC6066 +// > Currently, the only server names supported are DNS hostnames +function getServerName (host) { + if (!host) { + return null + } + + assert(typeof host === 'string') + + const servername = getHostname(host) + if (net.isIP(servername)) { + return '' + } + + return servername +} + +function deepClone (obj) { + return JSON.parse(JSON.stringify(obj)) +} + +function isAsyncIterable (obj) { + return !!(obj != null && typeof obj[Symbol.asyncIterator] === 'function') +} + +function isIterable (obj) { + return !!(obj != null && (typeof obj[Symbol.iterator] === 'function' || typeof obj[Symbol.asyncIterator] === 'function')) +} + +function bodyLength (body) { + if (body == null) { + return 0 + } else if (isStream(body)) { + const state = body._readableState + return state && state.objectMode === false && state.ended === true && Number.isFinite(state.length) + ? state.length + : null + } else if (isBlobLike(body)) { + return body.size != null ? body.size : null + } else if (isBuffer(body)) { + return body.byteLength + } + + return null +} + +function isDestroyed (body) { + return body && !!(body.destroyed || body[kDestroyed] || (stream.isDestroyed?.(body))) +} + +function destroy (stream, err) { + if (stream == null || !isStream(stream) || isDestroyed(stream)) { + return + } + + if (typeof stream.destroy === 'function') { + if (Object.getPrototypeOf(stream).constructor === IncomingMessage) { + // See: https://github.com/nodejs/node/pull/38505/files + stream.socket = null + } + + stream.destroy(err) + } else if (err) { + queueMicrotask(() => { + stream.emit('error', err) + }) + } + + if (stream.destroyed !== true) { + stream[kDestroyed] = true + } +} + +const KEEPALIVE_TIMEOUT_EXPR = /timeout=(\d+)/ +function parseKeepAliveTimeout (val) { + const m = val.toString().match(KEEPALIVE_TIMEOUT_EXPR) + return m ? parseInt(m[1], 10) * 1000 : null +} + +/** + * Retrieves a header name and returns its lowercase value. + * @param {string | Buffer} value Header name + * @returns {string} + */ +function headerNameToString (value) { + return typeof value === 'string' + ? headerNameLowerCasedRecord[value] ?? value.toLowerCase() + : tree.lookup(value) ?? value.toString('latin1').toLowerCase() +} + +/** + * Receive the buffer as a string and return its lowercase value. + * @param {Buffer} value Header name + * @returns {string} + */ +function bufferToLowerCasedHeaderName (value) { + return tree.lookup(value) ?? value.toString('latin1').toLowerCase() +} + +/** + * @param {Record | (Buffer | string | (Buffer | string)[])[]} headers + * @param {Record} [obj] + * @returns {Record} + */ +function parseHeaders (headers, obj) { + if (obj === undefined) obj = {} + for (let i = 0; i < headers.length; i += 2) { + const key = headerNameToString(headers[i]) + let val = obj[key] + + if (val) { + if (typeof val === 'string') { + val = [val] + obj[key] = val + } + val.push(headers[i + 1].toString('utf8')) + } else { + const headersValue = headers[i + 1] + if (typeof headersValue === 'string') { + obj[key] = headersValue + } else { + obj[key] = Array.isArray(headersValue) ? headersValue.map(x => x.toString('utf8')) : headersValue.toString('utf8') + } + } + } + + // See https://github.com/nodejs/node/pull/46528 + if ('content-length' in obj && 'content-disposition' in obj) { + obj['content-disposition'] = Buffer.from(obj['content-disposition']).toString('latin1') + } + + return obj +} + +function parseRawHeaders (headers) { + const len = headers.length + const ret = new Array(len) + + let hasContentLength = false + let contentDispositionIdx = -1 + let key + let val + let kLen = 0 + + for (let n = 0; n < headers.length; n += 2) { + key = headers[n] + val = headers[n + 1] + + typeof key !== 'string' && (key = key.toString()) + typeof val !== 'string' && (val = val.toString('utf8')) + + kLen = key.length + if (kLen === 14 && key[7] === '-' && (key === 'content-length' || key.toLowerCase() === 'content-length')) { + hasContentLength = true + } else if (kLen === 19 && key[7] === '-' && (key === 'content-disposition' || key.toLowerCase() === 'content-disposition')) { + contentDispositionIdx = n + 1 + } + ret[n] = key + ret[n + 1] = val + } + + // See https://github.com/nodejs/node/pull/46528 + if (hasContentLength && contentDispositionIdx !== -1) { + ret[contentDispositionIdx] = Buffer.from(ret[contentDispositionIdx]).toString('latin1') + } + + return ret +} + +function isBuffer (buffer) { + // See, https://github.com/mcollina/undici/pull/319 + return buffer instanceof Uint8Array || Buffer.isBuffer(buffer) +} + +function validateHandler (handler, method, upgrade) { + if (!handler || typeof handler !== 'object') { + throw new InvalidArgumentError('handler must be an object') + } + + if (typeof handler.onConnect !== 'function') { + throw new InvalidArgumentError('invalid onConnect method') + } + + if (typeof handler.onError !== 'function') { + throw new InvalidArgumentError('invalid onError method') + } + + if (typeof handler.onBodySent !== 'function' && handler.onBodySent !== undefined) { + throw new InvalidArgumentError('invalid onBodySent method') + } + + if (upgrade || method === 'CONNECT') { + if (typeof handler.onUpgrade !== 'function') { + throw new InvalidArgumentError('invalid onUpgrade method') + } + } else { + if (typeof handler.onHeaders !== 'function') { + throw new InvalidArgumentError('invalid onHeaders method') + } + + if (typeof handler.onData !== 'function') { + throw new InvalidArgumentError('invalid onData method') + } + + if (typeof handler.onComplete !== 'function') { + throw new InvalidArgumentError('invalid onComplete method') + } + } +} + +// A body is disturbed if it has been read from and it cannot +// be re-used without losing state or data. +function isDisturbed (body) { + // TODO (fix): Why is body[kBodyUsed] needed? + return !!(body && (stream.isDisturbed(body) || body[kBodyUsed])) +} + +function isErrored (body) { + return !!(body && stream.isErrored(body)) +} + +function isReadable (body) { + return !!(body && stream.isReadable(body)) +} + +function getSocketInfo (socket) { + return { + localAddress: socket.localAddress, + localPort: socket.localPort, + remoteAddress: socket.remoteAddress, + remotePort: socket.remotePort, + remoteFamily: socket.remoteFamily, + timeout: socket.timeout, + bytesWritten: socket.bytesWritten, + bytesRead: socket.bytesRead + } +} + +/** @type {globalThis['ReadableStream']} */ +function ReadableStreamFrom (iterable) { + // We cannot use ReadableStream.from here because it does not return a byte stream. + + let iterator + return new ReadableStream( + { + async start () { + iterator = iterable[Symbol.asyncIterator]() + }, + async pull (controller) { + const { done, value } = await iterator.next() + if (done) { + queueMicrotask(() => { + controller.close() + controller.byobRequest?.respond(0) + }) + } else { + const buf = Buffer.isBuffer(value) ? value : Buffer.from(value) + if (buf.byteLength) { + controller.enqueue(new Uint8Array(buf)) + } + } + return controller.desiredSize > 0 + }, + async cancel (reason) { + await iterator.return() + }, + type: 'bytes' + } + ) +} + +// The chunk should be a FormData instance and contains +// all the required methods. +function isFormDataLike (object) { + return ( + object && + typeof object === 'object' && + typeof object.append === 'function' && + typeof object.delete === 'function' && + typeof object.get === 'function' && + typeof object.getAll === 'function' && + typeof object.has === 'function' && + typeof object.set === 'function' && + object[Symbol.toStringTag] === 'FormData' + ) +} + +function addAbortListener (signal, listener) { + if ('addEventListener' in signal) { + signal.addEventListener('abort', listener, { once: true }) + return () => signal.removeEventListener('abort', listener) + } + signal.addListener('abort', listener) + return () => signal.removeListener('abort', listener) +} + +const hasToWellFormed = typeof String.prototype.toWellFormed === 'function' +const hasIsWellFormed = typeof String.prototype.isWellFormed === 'function' + +/** + * @param {string} val + */ +function toUSVString (val) { + return hasToWellFormed ? `${val}`.toWellFormed() : nodeUtil.toUSVString(val) +} + +/** + * @param {string} val + */ +// TODO: move this to webidl +function isUSVString (val) { + return hasIsWellFormed ? `${val}`.isWellFormed() : toUSVString(val) === `${val}` +} + +/** + * @see https://tools.ietf.org/html/rfc7230#section-3.2.6 + * @param {number} c + */ +function isTokenCharCode (c) { + switch (c) { + case 0x22: + case 0x28: + case 0x29: + case 0x2c: + case 0x2f: + case 0x3a: + case 0x3b: + case 0x3c: + case 0x3d: + case 0x3e: + case 0x3f: + case 0x40: + case 0x5b: + case 0x5c: + case 0x5d: + case 0x7b: + case 0x7d: + // DQUOTE and "(),/:;<=>?@[\]{}" + return false + default: + // VCHAR %x21-7E + return c >= 0x21 && c <= 0x7e + } +} + +/** + * @param {string} characters + */ +function isValidHTTPToken (characters) { + if (characters.length === 0) { + return false + } + for (let i = 0; i < characters.length; ++i) { + if (!isTokenCharCode(characters.charCodeAt(i))) { + return false + } + } + return true +} + +// headerCharRegex have been lifted from +// https://github.com/nodejs/node/blob/main/lib/_http_common.js + +/** + * Matches if val contains an invalid field-vchar + * field-value = *( field-content / obs-fold ) + * field-content = field-vchar [ 1*( SP / HTAB ) field-vchar ] + * field-vchar = VCHAR / obs-text + */ +const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/ + +/** + * @param {string} characters + */ +function isValidHeaderValue (characters) { + return !headerCharRegex.test(characters) +} + +// Parsed accordingly to RFC 9110 +// https://www.rfc-editor.org/rfc/rfc9110#field.content-range +function parseRangeHeader (range) { + if (range == null || range === '') return { start: 0, end: null, size: null } + + const m = range ? range.match(/^bytes (\d+)-(\d+)\/(\d+)?$/) : null + return m + ? { + start: parseInt(m[1]), + end: m[2] ? parseInt(m[2]) : null, + size: m[3] ? parseInt(m[3]) : null + } + : null +} + +function addListener (obj, name, listener) { + const listeners = (obj[kListeners] ??= []) + listeners.push([name, listener]) + obj.on(name, listener) + return obj +} + +function removeAllListeners (obj) { + for (const [name, listener] of obj[kListeners] ?? []) { + obj.removeListener(name, listener) + } + obj[kListeners] = null +} + +function errorRequest (client, request, err) { + try { + request.onError(err) + assert(request.aborted) + } catch (err) { + client.emit('error', err) + } +} + +const kEnumerableProperty = Object.create(null) +kEnumerableProperty.enumerable = true + +const normalizedMethodRecordsBase = { + delete: 'DELETE', + DELETE: 'DELETE', + get: 'GET', + GET: 'GET', + head: 'HEAD', + HEAD: 'HEAD', + options: 'OPTIONS', + OPTIONS: 'OPTIONS', + post: 'POST', + POST: 'POST', + put: 'PUT', + PUT: 'PUT' +} + +const normalizedMethodRecords = { + ...normalizedMethodRecordsBase, + patch: 'patch', + PATCH: 'PATCH' +} + +// Note: object prototypes should not be able to be referenced. e.g. `Object#hasOwnProperty`. +Object.setPrototypeOf(normalizedMethodRecordsBase, null) +Object.setPrototypeOf(normalizedMethodRecords, null) + +module.exports = { + kEnumerableProperty, + nop, + isDisturbed, + isErrored, + isReadable, + toUSVString, + isUSVString, + isBlobLike, + parseOrigin, + parseURL, + getServerName, + isStream, + isIterable, + isAsyncIterable, + isDestroyed, + headerNameToString, + bufferToLowerCasedHeaderName, + addListener, + removeAllListeners, + errorRequest, + parseRawHeaders, + parseHeaders, + parseKeepAliveTimeout, + destroy, + bodyLength, + deepClone, + ReadableStreamFrom, + isBuffer, + validateHandler, + getSocketInfo, + isFormDataLike, + buildURL, + addAbortListener, + isValidHTTPToken, + isValidHeaderValue, + isTokenCharCode, + parseRangeHeader, + normalizedMethodRecordsBase, + normalizedMethodRecords, + isValidPort, + isHttpOrHttpsPrefixed, + nodeMajor, + nodeMinor, + safeHTTPMethods: ['GET', 'HEAD', 'OPTIONS', 'TRACE'], + wrapRequestBody +} + + +/***/ }), + +/***/ 7405: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { InvalidArgumentError } = __nccwpck_require__(8707) +const { kClients, kRunning, kClose, kDestroy, kDispatch, kInterceptors } = __nccwpck_require__(6443) +const DispatcherBase = __nccwpck_require__(1841) +const Pool = __nccwpck_require__(628) +const Client = __nccwpck_require__(3701) +const util = __nccwpck_require__(3440) +const createRedirectInterceptor = __nccwpck_require__(5092) + +const kOnConnect = Symbol('onConnect') +const kOnDisconnect = Symbol('onDisconnect') +const kOnConnectionError = Symbol('onConnectionError') +const kMaxRedirections = Symbol('maxRedirections') +const kOnDrain = Symbol('onDrain') +const kFactory = Symbol('factory') +const kOptions = Symbol('options') + +function defaultFactory (origin, opts) { + return opts && opts.connections === 1 + ? new Client(origin, opts) + : new Pool(origin, opts) +} + +class Agent extends DispatcherBase { + constructor ({ factory = defaultFactory, maxRedirections = 0, connect, ...options } = {}) { + if (typeof factory !== 'function') { + throw new InvalidArgumentError('factory must be a function.') + } + + if (connect != null && typeof connect !== 'function' && typeof connect !== 'object') { + throw new InvalidArgumentError('connect must be a function or an object') + } + + if (!Number.isInteger(maxRedirections) || maxRedirections < 0) { + throw new InvalidArgumentError('maxRedirections must be a positive number') + } + + super(options) + + if (connect && typeof connect !== 'function') { + connect = { ...connect } + } + + this[kInterceptors] = options.interceptors?.Agent && Array.isArray(options.interceptors.Agent) + ? options.interceptors.Agent + : [createRedirectInterceptor({ maxRedirections })] + + this[kOptions] = { ...util.deepClone(options), connect } + this[kOptions].interceptors = options.interceptors + ? { ...options.interceptors } + : undefined + this[kMaxRedirections] = maxRedirections + this[kFactory] = factory + this[kClients] = new Map() + + this[kOnDrain] = (origin, targets) => { + this.emit('drain', origin, [this, ...targets]) + } + + this[kOnConnect] = (origin, targets) => { + this.emit('connect', origin, [this, ...targets]) + } + + this[kOnDisconnect] = (origin, targets, err) => { + this.emit('disconnect', origin, [this, ...targets], err) + } + + this[kOnConnectionError] = (origin, targets, err) => { + this.emit('connectionError', origin, [this, ...targets], err) + } + } + + get [kRunning] () { + let ret = 0 + for (const client of this[kClients].values()) { + ret += client[kRunning] + } + return ret + } + + [kDispatch] (opts, handler) { + let key + if (opts.origin && (typeof opts.origin === 'string' || opts.origin instanceof URL)) { + key = String(opts.origin) + } else { + throw new InvalidArgumentError('opts.origin must be a non-empty string or URL.') + } + + let dispatcher = this[kClients].get(key) + + if (!dispatcher) { + dispatcher = this[kFactory](opts.origin, this[kOptions]) + .on('drain', this[kOnDrain]) + .on('connect', this[kOnConnect]) + .on('disconnect', this[kOnDisconnect]) + .on('connectionError', this[kOnConnectionError]) + + // This introduces a tiny memory leak, as dispatchers are never removed from the map. + // TODO(mcollina): remove te timer when the client/pool do not have any more + // active connections. + this[kClients].set(key, dispatcher) + } + + return dispatcher.dispatch(opts, handler) + } + + async [kClose] () { + const closePromises = [] + for (const client of this[kClients].values()) { + closePromises.push(client.close()) + } + this[kClients].clear() + + await Promise.all(closePromises) + } + + async [kDestroy] (err) { + const destroyPromises = [] + for (const client of this[kClients].values()) { + destroyPromises.push(client.destroy(err)) + } + this[kClients].clear() + + await Promise.all(destroyPromises) + } +} + +module.exports = Agent + + +/***/ }), + +/***/ 837: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + BalancedPoolMissingUpstreamError, + InvalidArgumentError +} = __nccwpck_require__(8707) +const { + PoolBase, + kClients, + kNeedDrain, + kAddClient, + kRemoveClient, + kGetDispatcher +} = __nccwpck_require__(2128) +const Pool = __nccwpck_require__(628) +const { kUrl, kInterceptors } = __nccwpck_require__(6443) +const { parseOrigin } = __nccwpck_require__(3440) +const kFactory = Symbol('factory') + +const kOptions = Symbol('options') +const kGreatestCommonDivisor = Symbol('kGreatestCommonDivisor') +const kCurrentWeight = Symbol('kCurrentWeight') +const kIndex = Symbol('kIndex') +const kWeight = Symbol('kWeight') +const kMaxWeightPerServer = Symbol('kMaxWeightPerServer') +const kErrorPenalty = Symbol('kErrorPenalty') + +/** + * Calculate the greatest common divisor of two numbers by + * using the Euclidean algorithm. + * + * @param {number} a + * @param {number} b + * @returns {number} + */ +function getGreatestCommonDivisor (a, b) { + if (a === 0) return b + + while (b !== 0) { + const t = b + b = a % b + a = t + } + return a +} + +function defaultFactory (origin, opts) { + return new Pool(origin, opts) +} + +class BalancedPool extends PoolBase { + constructor (upstreams = [], { factory = defaultFactory, ...opts } = {}) { + super() + + this[kOptions] = opts + this[kIndex] = -1 + this[kCurrentWeight] = 0 + + this[kMaxWeightPerServer] = this[kOptions].maxWeightPerServer || 100 + this[kErrorPenalty] = this[kOptions].errorPenalty || 15 + + if (!Array.isArray(upstreams)) { + upstreams = [upstreams] + } + + if (typeof factory !== 'function') { + throw new InvalidArgumentError('factory must be a function.') + } + + this[kInterceptors] = opts.interceptors?.BalancedPool && Array.isArray(opts.interceptors.BalancedPool) + ? opts.interceptors.BalancedPool + : [] + this[kFactory] = factory + + for (const upstream of upstreams) { + this.addUpstream(upstream) + } + this._updateBalancedPoolStats() + } + + addUpstream (upstream) { + const upstreamOrigin = parseOrigin(upstream).origin + + if (this[kClients].find((pool) => ( + pool[kUrl].origin === upstreamOrigin && + pool.closed !== true && + pool.destroyed !== true + ))) { + return this + } + const pool = this[kFactory](upstreamOrigin, Object.assign({}, this[kOptions])) + + this[kAddClient](pool) + pool.on('connect', () => { + pool[kWeight] = Math.min(this[kMaxWeightPerServer], pool[kWeight] + this[kErrorPenalty]) + }) + + pool.on('connectionError', () => { + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]) + this._updateBalancedPoolStats() + }) + + pool.on('disconnect', (...args) => { + const err = args[2] + if (err && err.code === 'UND_ERR_SOCKET') { + // decrease the weight of the pool. + pool[kWeight] = Math.max(1, pool[kWeight] - this[kErrorPenalty]) + this._updateBalancedPoolStats() + } + }) + + for (const client of this[kClients]) { + client[kWeight] = this[kMaxWeightPerServer] + } + + this._updateBalancedPoolStats() + + return this + } + + _updateBalancedPoolStats () { + let result = 0 + for (let i = 0; i < this[kClients].length; i++) { + result = getGreatestCommonDivisor(this[kClients][i][kWeight], result) + } + + this[kGreatestCommonDivisor] = result + } + + removeUpstream (upstream) { + const upstreamOrigin = parseOrigin(upstream).origin + + const pool = this[kClients].find((pool) => ( + pool[kUrl].origin === upstreamOrigin && + pool.closed !== true && + pool.destroyed !== true + )) + + if (pool) { + this[kRemoveClient](pool) + } + + return this + } + + get upstreams () { + return this[kClients] + .filter(dispatcher => dispatcher.closed !== true && dispatcher.destroyed !== true) + .map((p) => p[kUrl].origin) + } + + [kGetDispatcher] () { + // We validate that pools is greater than 0, + // otherwise we would have to wait until an upstream + // is added, which might never happen. + if (this[kClients].length === 0) { + throw new BalancedPoolMissingUpstreamError() + } + + const dispatcher = this[kClients].find(dispatcher => ( + !dispatcher[kNeedDrain] && + dispatcher.closed !== true && + dispatcher.destroyed !== true + )) + + if (!dispatcher) { + return + } + + const allClientsBusy = this[kClients].map(pool => pool[kNeedDrain]).reduce((a, b) => a && b, true) + + if (allClientsBusy) { + return + } + + let counter = 0 + + let maxWeightIndex = this[kClients].findIndex(pool => !pool[kNeedDrain]) + + while (counter++ < this[kClients].length) { + this[kIndex] = (this[kIndex] + 1) % this[kClients].length + const pool = this[kClients][this[kIndex]] + + // find pool index with the largest weight + if (pool[kWeight] > this[kClients][maxWeightIndex][kWeight] && !pool[kNeedDrain]) { + maxWeightIndex = this[kIndex] + } + + // decrease the current weight every `this[kClients].length`. + if (this[kIndex] === 0) { + // Set the current weight to the next lower weight. + this[kCurrentWeight] = this[kCurrentWeight] - this[kGreatestCommonDivisor] + + if (this[kCurrentWeight] <= 0) { + this[kCurrentWeight] = this[kMaxWeightPerServer] + } + } + if (pool[kWeight] >= this[kCurrentWeight] && (!pool[kNeedDrain])) { + return pool + } + } + + this[kCurrentWeight] = this[kClients][maxWeightIndex][kWeight] + this[kIndex] = maxWeightIndex + return this[kClients][maxWeightIndex] + } +} + +module.exports = BalancedPool + + +/***/ }), + +/***/ 637: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +/* global WebAssembly */ + +const assert = __nccwpck_require__(4589) +const util = __nccwpck_require__(3440) +const { channels } = __nccwpck_require__(2414) +const timers = __nccwpck_require__(6603) +const { + RequestContentLengthMismatchError, + ResponseContentLengthMismatchError, + RequestAbortedError, + InvalidArgumentError, + HeadersTimeoutError, + HeadersOverflowError, + SocketError, + InformationalError, + BodyTimeoutError, + HTTPParserError, + ResponseExceededMaxSizeError +} = __nccwpck_require__(8707) +const { + kUrl, + kReset, + kClient, + kParser, + kBlocking, + kRunning, + kPending, + kSize, + kWriting, + kQueue, + kNoRef, + kKeepAliveDefaultTimeout, + kHostHeader, + kPendingIdx, + kRunningIdx, + kError, + kPipelining, + kSocket, + kKeepAliveTimeoutValue, + kMaxHeadersSize, + kKeepAliveMaxTimeout, + kKeepAliveTimeoutThreshold, + kHeadersTimeout, + kBodyTimeout, + kStrictContentLength, + kMaxRequests, + kCounter, + kMaxResponseSize, + kOnError, + kResume, + kHTTPContext +} = __nccwpck_require__(6443) + +const constants = __nccwpck_require__(2824) +const EMPTY_BUF = Buffer.alloc(0) +const FastBuffer = Buffer[Symbol.species] +const addListener = util.addListener +const removeAllListeners = util.removeAllListeners +const kIdleSocketValidation = Symbol('kIdleSocketValidation') +const kIdleSocketValidationTimeout = Symbol('kIdleSocketValidationTimeout') +const kSocketUsed = Symbol('kSocketUsed') + +let extractBody + +async function lazyllhttp () { + const llhttpWasmData = process.env.JEST_WORKER_ID ? __nccwpck_require__(3870) : undefined + + let mod + try { + mod = await WebAssembly.compile(__nccwpck_require__(3434)) + } catch (e) { + /* istanbul ignore next */ + + // We could check if the error was caused by the simd option not + // being enabled, but the occurring of this other error + // * https://github.com/emscripten-core/emscripten/issues/11495 + // got me to remove that check to avoid breaking Node 12. + mod = await WebAssembly.compile(llhttpWasmData || __nccwpck_require__(3870)) + } + + return await WebAssembly.instantiate(mod, { + env: { + /* eslint-disable camelcase */ + + wasm_on_url: (p, at, len) => { + /* istanbul ignore next */ + return 0 + }, + wasm_on_status: (p, at, len) => { + assert(currentParser.ptr === p) + const start = at - currentBufferPtr + currentBufferRef.byteOffset + return currentParser.onStatus(new FastBuffer(currentBufferRef.buffer, start, len)) || 0 + }, + wasm_on_message_begin: (p) => { + assert(currentParser.ptr === p) + return currentParser.onMessageBegin() || 0 + }, + wasm_on_header_field: (p, at, len) => { + assert(currentParser.ptr === p) + const start = at - currentBufferPtr + currentBufferRef.byteOffset + return currentParser.onHeaderField(new FastBuffer(currentBufferRef.buffer, start, len)) || 0 + }, + wasm_on_header_value: (p, at, len) => { + assert(currentParser.ptr === p) + const start = at - currentBufferPtr + currentBufferRef.byteOffset + return currentParser.onHeaderValue(new FastBuffer(currentBufferRef.buffer, start, len)) || 0 + }, + wasm_on_headers_complete: (p, statusCode, upgrade, shouldKeepAlive) => { + assert(currentParser.ptr === p) + return currentParser.onHeadersComplete(statusCode, Boolean(upgrade), Boolean(shouldKeepAlive)) || 0 + }, + wasm_on_body: (p, at, len) => { + assert(currentParser.ptr === p) + const start = at - currentBufferPtr + currentBufferRef.byteOffset + return currentParser.onBody(new FastBuffer(currentBufferRef.buffer, start, len)) || 0 + }, + wasm_on_message_complete: (p) => { + assert(currentParser.ptr === p) + return currentParser.onMessageComplete() || 0 + } + + /* eslint-enable camelcase */ + } + }) +} + +let llhttpInstance = null +let llhttpPromise = lazyllhttp() +llhttpPromise.catch() + +let currentParser = null +let currentBufferRef = null +let currentBufferSize = 0 +let currentBufferPtr = null + +const USE_NATIVE_TIMER = 0 +const USE_FAST_TIMER = 1 + +// Use fast timers for headers and body to take eventual event loop +// latency into account. +const TIMEOUT_HEADERS = 2 | USE_FAST_TIMER +const TIMEOUT_BODY = 4 | USE_FAST_TIMER + +// Use native timers to ignore event loop latency for keep-alive +// handling. +const TIMEOUT_KEEP_ALIVE = 8 | USE_NATIVE_TIMER + +class Parser { + constructor (client, socket, { exports }) { + assert(Number.isFinite(client[kMaxHeadersSize]) && client[kMaxHeadersSize] > 0) + + this.llhttp = exports + this.ptr = this.llhttp.llhttp_alloc(constants.TYPE.RESPONSE) + this.client = client + this.socket = socket + this.timeout = null + this.timeoutValue = null + this.timeoutType = null + this.statusCode = null + this.statusText = '' + this.upgrade = false + this.headers = [] + this.headersSize = 0 + this.headersMaxSize = client[kMaxHeadersSize] + this.shouldKeepAlive = false + this.paused = false + this.resume = this.resume.bind(this) + + this.bytesRead = 0 + + this.keepAlive = '' + this.contentLength = '' + this.connection = '' + this.maxResponseSize = client[kMaxResponseSize] + } + + setTimeout (delay, type) { + // If the existing timer and the new timer are of different timer type + // (fast or native) or have different delay, we need to clear the existing + // timer and set a new one. + if ( + delay !== this.timeoutValue || + (type & USE_FAST_TIMER) ^ (this.timeoutType & USE_FAST_TIMER) + ) { + // If a timeout is already set, clear it with clearTimeout of the fast + // timer implementation, as it can clear fast and native timers. + if (this.timeout) { + timers.clearTimeout(this.timeout) + this.timeout = null + } + + if (delay) { + if (type & USE_FAST_TIMER) { + this.timeout = timers.setFastTimeout(onParserTimeout, delay, new WeakRef(this)) + } else { + this.timeout = setTimeout(onParserTimeout, delay, new WeakRef(this)) + this.timeout.unref() + } + } + + this.timeoutValue = delay + } else if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) { + this.timeout.refresh() + } + } + + this.timeoutType = type + } + + resume () { + if (this.socket.destroyed || !this.paused) { + return + } + + assert(this.ptr != null) + assert(currentParser == null) + + this.llhttp.llhttp_resume(this.ptr) + + assert(this.timeoutType === TIMEOUT_BODY) + if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) { + this.timeout.refresh() + } + } + + this.paused = false + this.execute(this.socket.read() || EMPTY_BUF) // Flush parser. + this.readMore() + } + + readMore () { + while (!this.paused && this.ptr) { + const chunk = this.socket.read() + if (chunk === null) { + break + } + this.execute(chunk) + } + } + + execute (data) { + assert(this.ptr != null) + assert(currentParser == null) + assert(!this.paused) + + const { socket, llhttp } = this + + if (data.length > currentBufferSize) { + if (currentBufferPtr) { + llhttp.free(currentBufferPtr) + } + currentBufferSize = Math.ceil(data.length / 4096) * 4096 + currentBufferPtr = llhttp.malloc(currentBufferSize) + } + + new Uint8Array(llhttp.memory.buffer, currentBufferPtr, currentBufferSize).set(data) + + // Call `execute` on the wasm parser. + // We pass the `llhttp_parser` pointer address, the pointer address of buffer view data, + // and finally the length of bytes to parse. + // The return value is an error code or `constants.ERROR.OK`. + try { + let ret + + try { + currentBufferRef = data + currentParser = this + ret = llhttp.llhttp_execute(this.ptr, currentBufferPtr, data.length) + /* eslint-disable-next-line no-useless-catch */ + } catch (err) { + /* istanbul ignore next: difficult to make a test case for */ + throw err + } finally { + currentParser = null + currentBufferRef = null + } + + const offset = llhttp.llhttp_get_error_pos(this.ptr) - currentBufferPtr + + if (ret !== constants.ERROR.OK) { + const body = data.subarray(offset) + + if (ret === constants.ERROR.PAUSED_UPGRADE) { + this.onUpgrade(body) + } else if (ret === constants.ERROR.PAUSED) { + this.paused = true + socket.unshift(body) + } else { + throw this.createError(ret, body) + } + } + } catch (err) { + util.destroy(socket, err) + } + } + + finish () { + assert(currentParser === null) + assert(this.ptr != null) + assert(!this.paused) + + const { llhttp } = this + + let ret + + try { + currentParser = this + ret = llhttp.llhttp_finish(this.ptr) + } finally { + currentParser = null + } + + if (ret === constants.ERROR.OK) { + return null + } + + if (ret === constants.ERROR.PAUSED || ret === constants.ERROR.PAUSED_UPGRADE) { + this.paused = true + return null + } + + return this.createError(ret, EMPTY_BUF) + } + + createError (ret, data) { + const { llhttp, contentLength, bytesRead } = this + + if (contentLength && bytesRead !== parseInt(contentLength, 10)) { + return new ResponseContentLengthMismatchError() + } + + const ptr = llhttp.llhttp_get_error_reason(this.ptr) + let message = '' + if (ptr) { + const len = new Uint8Array(llhttp.memory.buffer, ptr).indexOf(0) + message = + 'Response does not match the HTTP/1.1 protocol (' + + Buffer.from(llhttp.memory.buffer, ptr, len).toString() + + ')' + } + + return new HTTPParserError(message, constants.ERROR[ret], data) + } + + destroy () { + assert(this.ptr != null) + assert(currentParser == null) + + this.llhttp.llhttp_free(this.ptr) + this.ptr = null + + this.timeout && timers.clearTimeout(this.timeout) + this.timeout = null + this.timeoutValue = null + this.timeoutType = null + + this.paused = false + } + + onStatus (buf) { + this.statusText = buf.toString() + } + + onMessageBegin () { + const { socket, client } = this + + /* istanbul ignore next: difficult to make a test case for */ + if (socket.destroyed) { + return -1 + } + + if (client[kRunning] === 0) { + util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket))) + return -1 + } + + const request = client[kQueue][client[kRunningIdx]] + if (!request) { + return -1 + } + request.onResponseStarted() + } + + onHeaderField (buf) { + const len = this.headers.length + + if ((len & 1) === 0) { + this.headers.push(buf) + } else { + this.headers[len - 1] = Buffer.concat([this.headers[len - 1], buf]) + } + + this.trackHeader(buf.length) + } + + onHeaderValue (buf) { + let len = this.headers.length + + if ((len & 1) === 1) { + this.headers.push(buf) + len += 1 + } else { + this.headers[len - 1] = Buffer.concat([this.headers[len - 1], buf]) + } + + const key = this.headers[len - 2] + if (key.length === 10) { + const headerName = util.bufferToLowerCasedHeaderName(key) + if (headerName === 'keep-alive') { + this.keepAlive += buf.toString() + } else if (headerName === 'connection') { + this.connection += buf.toString() + } + } else if (key.length === 14 && util.bufferToLowerCasedHeaderName(key) === 'content-length') { + this.contentLength += buf.toString() + } + + this.trackHeader(buf.length) + } + + trackHeader (len) { + this.headersSize += len + if (this.headersSize >= this.headersMaxSize) { + util.destroy(this.socket, new HeadersOverflowError()) + } + } + + onUpgrade (head) { + const { upgrade, client, socket, headers, statusCode, statusText } = this + + assert(upgrade) + assert(client[kSocket] === socket) + assert(!socket.destroyed) + assert(!this.paused) + assert((headers.length & 1) === 0) + + const request = client[kQueue][client[kRunningIdx]] + assert(request) + assert(request.upgrade || request.method === 'CONNECT') + + this.statusCode = null + this.statusText = '' + this.shouldKeepAlive = null + + this.headers = [] + this.headersSize = 0 + + socket.unshift(head) + + socket[kParser].destroy() + socket[kParser] = null + + socket[kClient] = null + socket[kError] = null + + removeAllListeners(socket) + + client[kSocket] = null + client[kHTTPContext] = null // TODO (fix): This is hacky... + client[kQueue][client[kRunningIdx]++] = null + client.emit('disconnect', client[kUrl], [client], new InformationalError('upgrade')) + + try { + request.onUpgrade(statusCode, headers, socket, statusText) + } catch (error) { + util.errorRequest(client, request, error) + util.destroy(socket, error) + } + + client[kResume]() + } + + onHeadersComplete (statusCode, upgrade, shouldKeepAlive) { + const { client, socket, headers, statusText } = this + + /* istanbul ignore next: difficult to make a test case for */ + if (socket.destroyed) { + return -1 + } + + if (client[kRunning] === 0) { + util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket))) + return -1 + } + + const request = client[kQueue][client[kRunningIdx]] + + /* istanbul ignore next: difficult to make a test case for */ + if (!request) { + return -1 + } + + assert(!this.upgrade) + assert(this.statusCode < 200) + + if (statusCode === 100) { + util.destroy(socket, new SocketError('bad response', util.getSocketInfo(socket))) + return -1 + } + + /* this can only happen if server is misbehaving */ + if (upgrade && !request.upgrade) { + util.destroy(socket, new SocketError('bad upgrade', util.getSocketInfo(socket))) + return -1 + } + + assert(this.timeoutType === TIMEOUT_HEADERS) + + this.statusCode = statusCode + this.shouldKeepAlive = ( + shouldKeepAlive || + // Override llhttp value which does not allow keepAlive for HEAD. + (request.method === 'HEAD' && !socket[kReset] && this.connection.toLowerCase() === 'keep-alive') + ) + + if (this.statusCode >= 200) { + const bodyTimeout = request.bodyTimeout != null + ? request.bodyTimeout + : client[kBodyTimeout] + this.setTimeout(bodyTimeout, TIMEOUT_BODY) + } else if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) { + this.timeout.refresh() + } + } + + if (request.method === 'CONNECT') { + assert(client[kRunning] === 1) + this.upgrade = true + return 2 + } + + if (upgrade) { + assert(client[kRunning] === 1) + this.upgrade = true + return 2 + } + + assert((this.headers.length & 1) === 0) + this.headers = [] + this.headersSize = 0 + + if (this.shouldKeepAlive && client[kPipelining]) { + const keepAliveTimeout = this.keepAlive ? util.parseKeepAliveTimeout(this.keepAlive) : null + + if (keepAliveTimeout != null) { + const timeout = Math.min( + keepAliveTimeout - client[kKeepAliveTimeoutThreshold], + client[kKeepAliveMaxTimeout] + ) + if (timeout <= 0) { + socket[kReset] = true + } else { + client[kKeepAliveTimeoutValue] = timeout + } + } else { + client[kKeepAliveTimeoutValue] = client[kKeepAliveDefaultTimeout] + } + } else { + // Stop more requests from being dispatched. + socket[kReset] = true + } + + const pause = request.onHeaders(statusCode, headers, this.resume, statusText) === false + + if (request.aborted) { + return -1 + } + + if (request.method === 'HEAD') { + return 1 + } + + if (statusCode < 200) { + return 1 + } + + if (socket[kBlocking]) { + socket[kBlocking] = false + client[kResume]() + } + + return pause ? constants.ERROR.PAUSED : 0 + } + + onBody (buf) { + const { client, socket, statusCode, maxResponseSize } = this + + if (socket.destroyed) { + return -1 + } + + const request = client[kQueue][client[kRunningIdx]] + assert(request) + + assert(this.timeoutType === TIMEOUT_BODY) + if (this.timeout) { + // istanbul ignore else: only for jest + if (this.timeout.refresh) { + this.timeout.refresh() + } + } + + assert(statusCode >= 200) + + if (maxResponseSize > -1 && this.bytesRead + buf.length > maxResponseSize) { + util.destroy(socket, new ResponseExceededMaxSizeError()) + return -1 + } + + this.bytesRead += buf.length + + if (request.onData(buf) === false) { + return constants.ERROR.PAUSED + } + } + + onMessageComplete () { + const { client, socket, statusCode, upgrade, headers, contentLength, bytesRead, shouldKeepAlive } = this + + if (socket.destroyed && (!statusCode || shouldKeepAlive)) { + return -1 + } + + if (upgrade) { + return + } + + assert(statusCode >= 100) + assert((this.headers.length & 1) === 0) + + const request = client[kQueue][client[kRunningIdx]] + assert(request) + + this.statusCode = null + this.statusText = '' + this.bytesRead = 0 + this.contentLength = '' + this.keepAlive = '' + this.connection = '' + + this.headers = [] + this.headersSize = 0 + + if (statusCode < 200) { + return + } + + /* istanbul ignore next: should be handled by llhttp? */ + if (request.method !== 'HEAD' && contentLength && bytesRead !== parseInt(contentLength, 10)) { + util.destroy(socket, new ResponseContentLengthMismatchError()) + return -1 + } + + request.onComplete(headers) + + client[kQueue][client[kRunningIdx]++] = null + socket[kSocketUsed] = true + + if (socket[kWriting]) { + assert(client[kRunning] === 0) + // Response completed before request. + util.destroy(socket, new InformationalError('reset')) + return constants.ERROR.PAUSED + } else if (!shouldKeepAlive) { + util.destroy(socket, new InformationalError('reset')) + return constants.ERROR.PAUSED + } else if (socket[kReset] && client[kRunning] === 0) { + // Destroy socket once all requests have completed. + // The request at the tail of the pipeline is the one + // that requested reset and no further requests should + // have been queued since then. + util.destroy(socket, new InformationalError('reset')) + return constants.ERROR.PAUSED + } else if (client[kPipelining] == null || client[kPipelining] === 1) { + // We must wait a full event loop cycle to reuse this socket to make sure + // that non-spec compliant servers are not closing the connection even if they + // said they won't. + setImmediate(() => client[kResume]()) + } else { + client[kResume]() + } + } +} + +function onParserTimeout (parser) { + const { socket, timeoutType, client, paused } = parser.deref() + + /* istanbul ignore else */ + if (timeoutType === TIMEOUT_HEADERS) { + if (!socket[kWriting] || socket.writableNeedDrain || client[kRunning] > 1) { + assert(!paused, 'cannot be paused while waiting for headers') + util.destroy(socket, new HeadersTimeoutError()) + } + } else if (timeoutType === TIMEOUT_BODY) { + if (!paused) { + util.destroy(socket, new BodyTimeoutError()) + } + } else if (timeoutType === TIMEOUT_KEEP_ALIVE) { + assert(client[kRunning] === 0 && client[kKeepAliveTimeoutValue]) + util.destroy(socket, new InformationalError('socket idle timeout')) + } +} + +async function connectH1 (client, socket) { + client[kSocket] = socket + + if (!llhttpInstance) { + llhttpInstance = await llhttpPromise + llhttpPromise = null + } + + socket[kNoRef] = false + socket[kWriting] = false + socket[kReset] = false + socket[kBlocking] = false + socket[kIdleSocketValidation] = 0 + socket[kIdleSocketValidationTimeout] = null + socket[kSocketUsed] = false + socket[kParser] = new Parser(client, socket, llhttpInstance) + + addListener(socket, 'error', function (err) { + assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID') + + const parser = this[kParser] + + // On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded + // to the user. + if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) { + const parserErr = parser.finish() + if (parserErr) { + this[kError] = parserErr + this[kClient][kOnError](parserErr) + } + return + } + + this[kError] = err + + this[kClient][kOnError](err) + }) + addListener(socket, 'readable', function () { + const parser = this[kParser] + + if (parser) { + parser.readMore() + } + }) + addListener(socket, 'end', function () { + const parser = this[kParser] + + if (parser.statusCode && !parser.shouldKeepAlive) { + const parserErr = parser.finish() + if (parserErr) { + util.destroy(this, parserErr) + } + return + } + + util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this))) + }) + addListener(socket, 'close', function () { + const client = this[kClient] + const parser = this[kParser] + + clearIdleSocketValidation(this) + + if (parser) { + if (!this[kError] && parser.statusCode && !parser.shouldKeepAlive) { + this[kError] = parser.finish() || this[kError] + } + + this[kParser].destroy() + this[kParser] = null + } + + const err = this[kError] || new SocketError('closed', util.getSocketInfo(this)) + + client[kSocket] = null + client[kHTTPContext] = null // TODO (fix): This is hacky... + + if (client.destroyed) { + assert(client[kPending] === 0) + + // Fail entire queue. + const requests = client[kQueue].splice(client[kRunningIdx]) + for (let i = 0; i < requests.length; i++) { + const request = requests[i] + util.errorRequest(client, request, err) + } + } else if (client[kRunning] > 0 && err.code !== 'UND_ERR_INFO') { + // Fail head of pipeline. + const request = client[kQueue][client[kRunningIdx]] + client[kQueue][client[kRunningIdx]++] = null + + util.errorRequest(client, request, err) + } + + client[kPendingIdx] = client[kRunningIdx] + + assert(client[kRunning] === 0) + + client.emit('disconnect', client[kUrl], [client], err) + + client[kResume]() + }) + + let closed = false + socket.on('close', () => { + closed = true + }) + + return { + version: 'h1', + defaultPipelining: 1, + write (...args) { + return writeH1(client, ...args) + }, + resume () { + resumeH1(client) + }, + destroy (err, callback) { + if (closed) { + queueMicrotask(callback) + } else { + socket.destroy(err).on('close', callback) + } + }, + get destroyed () { + return socket.destroyed + }, + busy (request) { + if (socket[kWriting] || socket[kReset] || socket[kBlocking] || socket[kIdleSocketValidation] === 1) { + return true + } + + if (request) { + if (client[kRunning] > 0 && !request.idempotent) { + // Non-idempotent request cannot be retried. + // Ensure that no other requests are inflight and + // could cause failure. + return true + } + + if (client[kRunning] > 0 && (request.upgrade || request.method === 'CONNECT')) { + // Don't dispatch an upgrade until all preceding requests have completed. + // A misbehaving server might upgrade the connection before all pipelined + // request has completed. + return true + } + + if (client[kRunning] > 0 && util.bodyLength(request.body) !== 0 && + (util.isStream(request.body) || util.isAsyncIterable(request.body) || util.isFormDataLike(request.body))) { + // Request with stream or iterator body can error while other requests + // are inflight and indirectly error those as well. + // Ensure this doesn't happen by waiting for inflight + // to complete before dispatching. + + // Request with stream or iterator body cannot be retried. + // Ensure that no other requests are inflight and + // could cause failure. + return true + } + } + + return false + } + } +} + +function clearIdleSocketValidation (socket) { + if (socket[kIdleSocketValidationTimeout]) { + clearImmediate(socket[kIdleSocketValidationTimeout]) + socket[kIdleSocketValidationTimeout] = null + } + + socket[kIdleSocketValidation] = 0 +} + +function scheduleIdleSocketValidation (client, socket) { + socket[kIdleSocketValidation] = 1 + // 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]() + } + }) +} + +/** + * @param {import('./client.js')} client + */ +function resumeH1 (client) { + const socket = client[kSocket] + + if (socket && !socket.destroyed) { + if (client[kSize] === 0) { + if (!socket[kNoRef] && socket.unref) { + socket.unref() + socket[kNoRef] = true + } + } else if (socket[kNoRef] && socket.ref) { + socket.ref() + socket[kNoRef] = false + } + + if (client[kRunning] === 0 && client[kPending] > 0 && socket[kSocketUsed]) { + if (socket[kIdleSocketValidation] === 0) { + scheduleIdleSocketValidation(client, socket) + socket[kParser].readMore() + if (socket.destroyed) { + return + } + return + } + + if (socket[kIdleSocketValidation] === 1) { + socket[kParser].readMore() + if (socket.destroyed) { + return + } + return + } + } + + if (client[kRunning] === 0) { + socket[kParser].readMore() + if (socket.destroyed) { + return + } + } + + if (client[kSize] === 0) { + if (socket[kParser].timeoutType !== TIMEOUT_KEEP_ALIVE) { + socket[kParser].setTimeout(client[kKeepAliveTimeoutValue], TIMEOUT_KEEP_ALIVE) + } + } else if (client[kRunning] > 0 && socket[kParser].statusCode < 200) { + if (socket[kParser].timeoutType !== TIMEOUT_HEADERS) { + const request = client[kQueue][client[kRunningIdx]] + const headersTimeout = request.headersTimeout != null + ? request.headersTimeout + : client[kHeadersTimeout] + socket[kParser].setTimeout(headersTimeout, TIMEOUT_HEADERS) + } + } + } +} + +// https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2 +function shouldSendContentLength (method) { + return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && method !== 'TRACE' && method !== 'CONNECT' +} + +function writeH1 (client, request) { + const { method, path, host, upgrade, blocking, reset } = request + + let { body, headers, contentLength } = request + + // https://tools.ietf.org/html/rfc7231#section-4.3.1 + // https://tools.ietf.org/html/rfc7231#section-4.3.2 + // https://tools.ietf.org/html/rfc7231#section-4.3.5 + + // Sending a payload body on a request that does not + // expect it can cause undefined behavior on some + // servers and corrupt connection state. Do not + // re-use the connection for further requests. + + const expectsPayload = ( + method === 'PUT' || + method === 'POST' || + method === 'PATCH' || + method === 'QUERY' || + method === 'PROPFIND' || + method === 'PROPPATCH' + ) + + if (util.isFormDataLike(body)) { + if (!extractBody) { + extractBody = (__nccwpck_require__(4492).extractBody) + } + + const [bodyStream, contentType] = extractBody(body) + if (request.contentType == null) { + headers.push('content-type', contentType) + } + body = bodyStream.stream + contentLength = bodyStream.length + } else if (util.isBlobLike(body) && request.contentType == null) { + const contentType = body.type + if (contentType) { + const contentTypeValue = `${contentType}` + if (!util.isValidHeaderValue(contentTypeValue)) { + util.errorRequest(client, request, new InvalidArgumentError('invalid content-type header')) + return false + } + headers.push('content-type', contentTypeValue) + } + } + + if (body && typeof body.read === 'function') { + // Try to read EOF in order to get length. + body.read(0) + } + + const bodyLength = util.bodyLength(body) + + contentLength = bodyLength ?? contentLength + + if (contentLength === null) { + contentLength = request.contentLength + } + + if (contentLength === 0 && !expectsPayload) { + // https://tools.ietf.org/html/rfc7230#section-3.3.2 + // A user agent SHOULD NOT send a Content-Length header field when + // the request message does not contain a payload body and the method + // semantics do not anticipate such a body. + + contentLength = null + } + + // https://github.com/nodejs/undici/issues/2046 + // A user agent may send a Content-Length header with 0 value, this should be allowed. + if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength !== null && request.contentLength !== contentLength) { + if (client[kStrictContentLength]) { + util.errorRequest(client, request, new RequestContentLengthMismatchError()) + return false + } + + process.emitWarning(new RequestContentLengthMismatchError()) + } + + const socket = client[kSocket] + clearIdleSocketValidation(socket) + + /** + * @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, error || new RequestAbortedError()) + + util.destroy(body) + util.destroy(socket, new InformationalError('aborted')) + } + + try { + request.onConnect(abort) + } catch (err) { + util.errorRequest(client, request, err) + } + + if (request.aborted) { + return false + } + + if (method === 'HEAD') { + // https://github.com/mcollina/undici/issues/258 + // Close after a HEAD request to interop with misbehaving servers + // that may send a body in the response. + + socket[kReset] = true + } + + if (upgrade || method === 'CONNECT') { + // On CONNECT or upgrade, block pipeline from dispatching further + // requests on this connection. + + socket[kReset] = true + } + + if (reset != null) { + socket[kReset] = reset + } + + if (client[kMaxRequests] && socket[kCounter]++ >= client[kMaxRequests]) { + socket[kReset] = true + } + + if (blocking) { + socket[kBlocking] = true + } + + let header = `${method} ${path} HTTP/1.1\r\n` + + if (typeof host === 'string') { + header += `host: ${host}\r\n` + } else { + header += client[kHostHeader] + } + + if (upgrade) { + header += `connection: upgrade\r\nupgrade: ${upgrade}\r\n` + } else if (client[kPipelining] && !socket[kReset]) { + header += 'connection: keep-alive\r\n' + } else { + header += 'connection: close\r\n' + } + + if (Array.isArray(headers)) { + for (let n = 0; n < headers.length; n += 2) { + const key = headers[n + 0] + const val = headers[n + 1] + + if (Array.isArray(val)) { + for (let i = 0; i < val.length; i++) { + header += `${key}: ${val[i]}\r\n` + } + } else { + header += `${key}: ${val}\r\n` + } + } + } + + if (channels.sendHeaders.hasSubscribers) { + channels.sendHeaders.publish({ request, headers: header, socket }) + } + + /* istanbul ignore else: assertion */ + if (!body || bodyLength === 0) { + writeBuffer(abort, null, client, request, socket, contentLength, header, expectsPayload) + } else if (util.isBuffer(body)) { + writeBuffer(abort, body, client, request, socket, contentLength, header, expectsPayload) + } else if (util.isBlobLike(body)) { + if (typeof body.stream === 'function') { + writeIterable(abort, body.stream(), client, request, socket, contentLength, header, expectsPayload) + } else { + writeBlob(abort, body, client, request, socket, contentLength, header, expectsPayload) + } + } else if (util.isStream(body)) { + writeStream(abort, body, client, request, socket, contentLength, header, expectsPayload) + } else if (util.isIterable(body)) { + writeIterable(abort, body, client, request, socket, contentLength, header, expectsPayload) + } else { + assert(false) + } + + return true +} + +function writeStream (abort, body, client, request, socket, contentLength, header, expectsPayload) { + assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined') + + let finished = false + + const writer = new AsyncWriter({ abort, socket, request, contentLength, client, expectsPayload, header }) + + const onData = function (chunk) { + if (finished) { + return + } + + try { + if (!writer.write(chunk) && this.pause) { + this.pause() + } + } catch (err) { + util.destroy(this, err) + } + } + const onDrain = function () { + if (finished) { + return + } + + if (body.resume) { + body.resume() + } + } + const onClose = function () { + // 'close' might be emitted *before* 'error' for + // broken streams. Wait a tick to avoid this case. + queueMicrotask(() => { + // It's only safe to remove 'error' listener after + // 'close'. + body.removeListener('error', onFinished) + }) + + if (!finished) { + const err = new RequestAbortedError() + queueMicrotask(() => onFinished(err)) + } + } + const onFinished = function (err) { + if (finished) { + return + } + + finished = true + + assert(socket.destroyed || (socket[kWriting] && client[kRunning] <= 1)) + + socket + .off('drain', onDrain) + .off('error', onFinished) + + body + .removeListener('data', onData) + .removeListener('end', onFinished) + .removeListener('close', onClose) + + if (!err) { + try { + writer.end() + } catch (er) { + err = er + } + } + + writer.destroy(err) + + if (err && (err.code !== 'UND_ERR_INFO' || err.message !== 'reset')) { + util.destroy(body, err) + } else { + util.destroy(body) + } + } + + body + .on('data', onData) + .on('end', onFinished) + .on('error', onFinished) + .on('close', onClose) + + if (body.resume) { + body.resume() + } + + socket + .on('drain', onDrain) + .on('error', onFinished) + + if (body.errorEmitted ?? body.errored) { + setImmediate(() => onFinished(body.errored)) + } else if (body.endEmitted ?? body.readableEnded) { + setImmediate(() => onFinished(null)) + } + + if (body.closeEmitted ?? body.closed) { + setImmediate(onClose) + } +} + +function writeBuffer (abort, body, client, request, socket, contentLength, header, expectsPayload) { + try { + if (!body) { + if (contentLength === 0) { + socket.write(`${header}content-length: 0\r\n\r\n`, 'latin1') + } else { + assert(contentLength === null, 'no body must not have content length') + socket.write(`${header}\r\n`, 'latin1') + } + } else if (util.isBuffer(body)) { + assert(contentLength === body.byteLength, 'buffer body must have content length') + + socket.cork() + socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1') + socket.write(body) + socket.uncork() + request.onBodySent(body) + + if (!expectsPayload && request.reset !== false) { + socket[kReset] = true + } + } + request.onRequestSent() + + client[kResume]() + } catch (err) { + abort(err) + } +} + +async function writeBlob (abort, body, client, request, socket, contentLength, header, expectsPayload) { + assert(contentLength === body.size, 'blob body must have content length') + + try { + if (contentLength != null && contentLength !== body.size) { + throw new RequestContentLengthMismatchError() + } + + const buffer = Buffer.from(await body.arrayBuffer()) + + socket.cork() + socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1') + socket.write(buffer) + socket.uncork() + + request.onBodySent(buffer) + request.onRequestSent() + + if (!expectsPayload && request.reset !== false) { + socket[kReset] = true + } + + client[kResume]() + } catch (err) { + abort(err) + } +} + +async function writeIterable (abort, body, client, request, socket, contentLength, header, expectsPayload) { + assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined') + + let callback = null + function onDrain () { + if (callback) { + const cb = callback + callback = null + cb() + } + } + + const waitForDrain = () => new Promise((resolve, reject) => { + assert(callback === null) + + if (socket[kError]) { + reject(socket[kError]) + } else { + callback = resolve + } + }) + + socket + .on('close', onDrain) + .on('drain', onDrain) + + const writer = new AsyncWriter({ abort, socket, request, contentLength, client, expectsPayload, header }) + try { + // It's up to the user to somehow abort the async iterable. + for await (const chunk of body) { + if (socket[kError]) { + throw socket[kError] + } + + if (!writer.write(chunk)) { + await waitForDrain() + } + } + + writer.end() + } catch (err) { + writer.destroy(err) + } finally { + socket + .off('close', onDrain) + .off('drain', onDrain) + } +} + +class AsyncWriter { + constructor ({ abort, socket, request, contentLength, client, expectsPayload, header }) { + this.socket = socket + this.request = request + this.contentLength = contentLength + this.client = client + this.bytesWritten = 0 + this.expectsPayload = expectsPayload + this.header = header + this.abort = abort + + socket[kWriting] = true + } + + write (chunk) { + const { socket, request, contentLength, client, bytesWritten, expectsPayload, header } = this + + if (socket[kError]) { + throw socket[kError] + } + + if (socket.destroyed) { + return false + } + + const len = Buffer.byteLength(chunk) + if (!len) { + return true + } + + // We should defer writing chunks. + if (contentLength !== null && bytesWritten + len > contentLength) { + if (client[kStrictContentLength]) { + throw new RequestContentLengthMismatchError() + } + + process.emitWarning(new RequestContentLengthMismatchError()) + } + + socket.cork() + + if (bytesWritten === 0) { + if (!expectsPayload && request.reset !== false) { + socket[kReset] = true + } + + if (contentLength === null) { + socket.write(`${header}transfer-encoding: chunked\r\n`, 'latin1') + } else { + socket.write(`${header}content-length: ${contentLength}\r\n\r\n`, 'latin1') + } + } + + if (contentLength === null) { + socket.write(`\r\n${len.toString(16)}\r\n`, 'latin1') + } + + this.bytesWritten += len + + const ret = socket.write(chunk) + + socket.uncork() + + request.onBodySent(chunk) + + if (!ret) { + if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) { + // istanbul ignore else: only for jest + if (socket[kParser].timeout.refresh) { + socket[kParser].timeout.refresh() + } + } + } + + return ret + } + + end () { + const { socket, contentLength, client, bytesWritten, expectsPayload, header, request } = this + request.onRequestSent() + + socket[kWriting] = false + + if (socket[kError]) { + throw socket[kError] + } + + if (socket.destroyed) { + return + } + + if (bytesWritten === 0) { + if (expectsPayload) { + // https://tools.ietf.org/html/rfc7230#section-3.3.2 + // A user agent SHOULD send a Content-Length in a request message when + // no Transfer-Encoding is sent and the request method defines a meaning + // for an enclosed payload body. + + socket.write(`${header}content-length: 0\r\n\r\n`, 'latin1') + } else { + socket.write(`${header}\r\n`, 'latin1') + } + } else if (contentLength === null) { + socket.write('\r\n0\r\n\r\n', 'latin1') + } + + if (contentLength !== null && bytesWritten !== contentLength) { + if (client[kStrictContentLength]) { + throw new RequestContentLengthMismatchError() + } else { + process.emitWarning(new RequestContentLengthMismatchError()) + } + } + + if (socket[kParser].timeout && socket[kParser].timeoutType === TIMEOUT_HEADERS) { + // istanbul ignore else: only for jest + if (socket[kParser].timeout.refresh) { + socket[kParser].timeout.refresh() + } + } + + client[kResume]() + } + + destroy (err) { + const { socket, client, abort } = this + + socket[kWriting] = false + + if (err) { + assert(client[kRunning] <= 1, 'pipeline should only contain this request') + abort(err) + } + } +} + +module.exports = connectH1 + + +/***/ }), + +/***/ 8788: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const assert = __nccwpck_require__(4589) +const { errorMonitor } = __nccwpck_require__(8474) +const { pipeline } = __nccwpck_require__(7075) +const util = __nccwpck_require__(3440) +const { + RequestContentLengthMismatchError, + RequestAbortedError, + SocketError, + InformationalError +} = __nccwpck_require__(8707) +const { + kUrl, + kReset, + kClient, + kRunning, + kPending, + kQueue, + kPendingIdx, + kRunningIdx, + kError, + kSocket, + kStrictContentLength, + kOnError, + kMaxConcurrentStreams, + kHTTP2Session, + kResume, + kSize, + kHTTPContext +} = __nccwpck_require__(6443) + +const kOpenStreams = Symbol('open streams') + +let extractBody + +// Experimental +let h2ExperimentalWarned = false + +/** @type {import('http2')} */ +let http2 +try { + http2 = __nccwpck_require__(2467) +} catch { + // @ts-ignore + http2 = { constants: {} } +} + +const { + constants: { + HTTP2_HEADER_AUTHORITY, + HTTP2_HEADER_METHOD, + HTTP2_HEADER_PATH, + HTTP2_HEADER_SCHEME, + HTTP2_HEADER_CONTENT_LENGTH, + HTTP2_HEADER_EXPECT, + HTTP2_HEADER_STATUS + } +} = http2 + +function parseH2Headers (headers) { + const result = [] + + for (const [name, value] of Object.entries(headers)) { + // h2 may concat the header value by array + // e.g. Set-Cookie + if (Array.isArray(value)) { + for (const subvalue of value) { + // we need to provide each header value of header name + // because the headers handler expect name-value pair + result.push(Buffer.from(name), Buffer.from(subvalue)) + } + } else { + result.push(Buffer.from(name), Buffer.from(value)) + } + } + + 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 + + if (!h2ExperimentalWarned) { + h2ExperimentalWarned = true + process.emitWarning('H2 support is experimental, expect them to change at any time.', { + code: 'UNDICI-H2' + }) + } + + const session = http2.connect(client[kUrl], { + createConnection: () => socket, + peerMaxConcurrentStreams: client[kMaxConcurrentStreams] + }) + + session[kOpenStreams] = 0 + session[kClient] = client + session[kSocket] = socket + + util.addListener(session, 'error', onHttp2SessionError) + util.addListener(session, 'frameError', onHttp2FrameError) + util.addListener(session, 'end', onHttp2SessionEnd) + util.addListener(session, 'goaway', onHTTP2GoAway) + util.addListener(session, 'close', function () { + const { [kClient]: client } = this + const { [kSocket]: socket } = client + + const err = this[kSocket][kError] || this[kError] || new SocketError('closed', util.getSocketInfo(socket)) + + client[kHTTP2Session] = null + + if (client.destroyed) { + assert(client[kPending] === 0) + + // Fail entire queue. + const requests = client[kQueue].splice(client[kRunningIdx]) + for (let i = 0; i < requests.length; i++) { + const request = requests[i] + util.errorRequest(client, request, err) + } + } + }) + + session.unref() + + client[kHTTP2Session] = session + socket[kHTTP2Session] = session + + util.addListener(socket, 'error', function (err) { + assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID') + + this[kError] = err + + this[kClient][kOnError](err) + }) + + util.addListener(socket, 'end', function () { + util.destroy(this, new SocketError('other side closed', util.getSocketInfo(this))) + }) + + util.addListener(socket, 'close', function () { + const err = this[kError] || new SocketError('closed', util.getSocketInfo(this)) + + client[kSocket] = null + + if (this[kHTTP2Session] != null) { + this[kHTTP2Session].destroy(err) + } + + client[kPendingIdx] = client[kRunningIdx] + + assert(client[kRunning] === 0) + + client.emit('disconnect', client[kUrl], [client], err) + + client[kResume]() + }) + + let closed = false + socket.on('close', () => { + closed = true + }) + + return { + version: 'h2', + defaultPipelining: Infinity, + write (...args) { + return writeH2(client, ...args) + }, + resume () { + resumeH2(client) + }, + destroy (err, callback) { + if (closed) { + queueMicrotask(callback) + } else { + // Destroying the socket will trigger the session close + socket.destroy(err).on('close', callback) + } + }, + get destroyed () { + return socket.destroyed + }, + busy () { + return false + } + } +} + +function resumeH2 (client) { + const socket = client[kSocket] + + if (socket?.destroyed === false) { + if (client[kSize] === 0 && client[kMaxConcurrentStreams] === 0) { + socket.unref() + client[kHTTP2Session].unref() + } else { + socket.ref() + client[kHTTP2Session].ref() + } + } +} + +function onHttp2SessionError (err) { + assert(err.code !== 'ERR_TLS_CERT_ALTNAME_INVALID') + + this[kSocket][kError] = err + this[kClient][kOnError](err) +} + +function onHttp2FrameError (type, code, id) { + if (id === 0) { + const err = new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`) + this[kSocket][kError] = err + this[kClient][kOnError](err) + } +} + +function onHttp2SessionEnd () { + const err = new SocketError('other side closed', util.getSocketInfo(this[kSocket])) + this.destroy(err) + util.destroy(this[kSocket], err) +} + +/** + * This is the root cause of #3011 + * We need to handle GOAWAY frames properly, and trigger the session close + * along with the socket right away + */ +function onHTTP2GoAway (code) { + // We cannot recover, so best to close the session and the socket + const err = this[kError] || new SocketError(`HTTP/2: "GOAWAY" frame received with code ${code}`, util.getSocketInfo(this)) + const client = this[kClient] + + client[kSocket] = null + client[kHTTPContext] = null + + if (this[kHTTP2Session] != null) { + this[kHTTP2Session].destroy(err) + this[kHTTP2Session] = null + } + + util.destroy(this[kSocket], err) + + // Fail head of pipeline. + if (client[kRunningIdx] < client[kQueue].length) { + const request = client[kQueue][client[kRunningIdx]] + client[kQueue][client[kRunningIdx]++] = null + util.errorRequest(client, request, err) + client[kPendingIdx] = client[kRunningIdx] + } + + assert(client[kRunning] === 0) + + client.emit('disconnect', client[kUrl], [client], err) + + client[kResume]() +} + +// https://www.rfc-editor.org/rfc/rfc7230#section-3.3.2 +function shouldSendContentLength (method) { + return method !== 'GET' && method !== 'HEAD' && method !== 'OPTIONS' && method !== 'TRACE' && method !== 'CONNECT' +} + +function writeH2 (client, request) { + const session = client[kHTTP2Session] + const { method, path, host, upgrade, expectContinue, signal, headers: reqHeaders } = request + let { body } = request + + if (upgrade) { + util.errorRequest(client, request, new Error('Upgrade not supported for H2')) + return false + } + + const headers = {} + for (let n = 0; n < reqHeaders.length; n += 2) { + const key = reqHeaders[n + 0] + const val = reqHeaders[n + 1] + + if (Array.isArray(val)) { + for (let i = 0; i < val.length; i++) { + if (headers[key]) { + headers[key] += `,${val[i]}` + } else { + headers[key] = val[i] + } + } + } else { + headers[key] = val + } + } + + /** @type {import('node:http2').ClientHttp2Stream} */ + let stream + + const { hostname, port } = client[kUrl] + + headers[HTTP2_HEADER_AUTHORITY] = host || `${hostname}${port ? `:${port}` : ''}` + headers[HTTP2_HEADER_METHOD] = method + + /** + * @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 + } + + error = error || new RequestAbortedError() + + util.errorRequest(client, request, error) + + if (stream != null) { + 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, error) + client[kQueue][client[kRunningIdx]++] = null + client[kResume]() + } + + try { + // We are already connected, streams are pending. + // We can call on connect, and wait for abort + request.onConnect(abort) + } catch (err) { + util.errorRequest(client, request, err) + } + + if (request.aborted) { + return false + } + + if (method === 'CONNECT') { + session.ref() + // We disabled endStream to allow the user to write to the stream + stream = session.request(headers, { endStream: false, signal }) + let upgradeResponseFinished = false + + /** + * @param {import('node:http2').IncomingHttpHeaders} headers + */ + const onResponse = (headers) => { + upgradeResponseFinished = true + stream.off(errorMonitor, onUpgradeError) + request.onUpgradeResponse(Number(headers[HTTP2_HEADER_STATUS]), headers, parseH2ResponseHeaders) + } + + /** + * @param {Error} error + */ + const onUpgradeError = (error) => { + upgradeResponseFinished = true + stream.off('response', onResponse) + request.onUpgradeError(error) + } + + const onReady = () => { + try { + request.onUpgrade(null, null, stream) + } catch (error) { + stream.off('response', onResponse) + abort(error) + return + } + + if (request.aborted) { + return + } + + stream.off('error', abort) + stream.once(errorMonitor, onUpgradeError) + client[kQueue][client[kRunningIdx]++] = null + } + + stream.once('response', onResponse) + stream.once('error', abort) + ++session[kOpenStreams] + onReady() + + stream.once('close', () => { + if (!upgradeResponseFinished && request.completed) { + stream.off('response', onResponse) + stream.off(errorMonitor, onUpgradeError) + request.onUpgradeError(new InformationalError(`HTTP/2: "stream error" received - code ${stream.rstCode}`)) + } + session[kOpenStreams] -= 1 + if (session[kOpenStreams] === 0) session.unref() + }) + + return true + } + + // https://tools.ietf.org/html/rfc7540#section-8.3 + // :path and :scheme headers must be omitted when sending CONNECT + + headers[HTTP2_HEADER_PATH] = path + headers[HTTP2_HEADER_SCHEME] = 'https' + + // https://tools.ietf.org/html/rfc7231#section-4.3.1 + // https://tools.ietf.org/html/rfc7231#section-4.3.2 + // https://tools.ietf.org/html/rfc7231#section-4.3.5 + + // Sending a payload body on a request that does not + // expect it can cause undefined behavior on some + // servers and corrupt connection state. Do not + // re-use the connection for further requests. + + const expectsPayload = ( + method === 'PUT' || + method === 'POST' || + method === 'PATCH' + ) + + if (body && typeof body.read === 'function') { + // Try to read EOF in order to get length. + body.read(0) + } + + let contentLength = util.bodyLength(body) + + if (util.isFormDataLike(body)) { + extractBody ??= (__nccwpck_require__(4492).extractBody) + + const [bodyStream, contentType] = extractBody(body) + headers['content-type'] = contentType + + body = bodyStream.stream + contentLength = bodyStream.length + } + + if (contentLength == null) { + contentLength = request.contentLength + } + + if (contentLength === 0 || !expectsPayload) { + // https://tools.ietf.org/html/rfc7230#section-3.3.2 + // A user agent SHOULD NOT send a Content-Length header field when + // the request message does not contain a payload body and the method + // semantics do not anticipate such a body. + + contentLength = null + } + + // https://github.com/nodejs/undici/issues/2046 + // A user agent may send a Content-Length header with 0 value, this should be allowed. + if (shouldSendContentLength(method) && contentLength > 0 && request.contentLength != null && request.contentLength !== contentLength) { + if (client[kStrictContentLength]) { + util.errorRequest(client, request, new RequestContentLengthMismatchError()) + return false + } + + process.emitWarning(new RequestContentLengthMismatchError()) + } + + if (contentLength != null) { + assert(body, 'no body must not have content length') + headers[HTTP2_HEADER_CONTENT_LENGTH] = `${contentLength}` + } + + session.ref() + + const shouldEndStream = method === 'GET' || method === 'HEAD' || body === null + if (expectContinue) { + headers[HTTP2_HEADER_EXPECT] = '100-continue' + stream = session.request(headers, { endStream: shouldEndStream, signal }) + + stream.once('continue', writeBodyH2) + } else { + stream = session.request(headers, { + endStream: shouldEndStream, + signal + }) + writeBodyH2() + } + + // Increment counter as we have new streams open + ++session[kOpenStreams] + + stream.once('response', headers => { + const { [HTTP2_HEADER_STATUS]: statusCode, ...realHeaders } = headers + request.onResponseStarted() + + // Due to the stream nature, it is possible we face a race condition + // where the stream has been assigned, but the request has been aborted + // the request remains in-flight and headers hasn't been received yet + // for those scenarios, best effort is to destroy the stream immediately + // as there's no value to keep it open. + if (request.aborted) { + const err = new RequestAbortedError() + util.errorRequest(client, request, err) + util.destroy(stream, err) + return + } + + if (request.onHeaders(Number(statusCode), parseH2Headers(realHeaders), stream.resume.bind(stream), '') === false) { + stream.pause() + } + + stream.on('data', (chunk) => { + if (request.onData(chunk) === false) { + stream.pause() + } + }) + }) + + stream.once('end', () => { + // When state is null, it means we haven't consumed body and the stream still do not have + // a state. + // Present specially when using pipeline or stream + if (stream.state?.state == null || stream.state.state < 6) { + request.onComplete([]) + } + + if (session[kOpenStreams] === 0) { + // Stream is closed or half-closed-remote (6), decrement counter and cleanup + // It does not have sense to continue working with the stream as we do not + // have yet RST_STREAM support on client-side + + session.unref() + } + + abort(new InformationalError('HTTP/2: stream half-closed (remote)')) + client[kQueue][client[kRunningIdx]++] = null + client[kPendingIdx] = client[kRunningIdx] + client[kResume]() + }) + + stream.once('close', () => { + session[kOpenStreams] -= 1 + if (session[kOpenStreams] === 0) { + session.unref() + } + }) + + stream.once('error', function (err) { + abort(err) + }) + + stream.once('frameError', (type, code) => { + abort(new InformationalError(`HTTP/2: "frameError" received - type ${type}, code ${code}`)) + }) + + // stream.on('aborted', () => { + // // TODO(HTTP/2): Support aborted + // }) + + // stream.on('timeout', () => { + // // TODO(HTTP/2): Support timeout + // }) + + // stream.on('push', headers => { + // // TODO(HTTP/2): Support push + // }) + + // stream.on('trailers', headers => { + // // TODO(HTTP/2): Support trailers + // }) + + return true + + function writeBodyH2 () { + /* istanbul ignore else: assertion */ + if (!body || contentLength === 0) { + writeBuffer( + abort, + stream, + null, + client, + request, + client[kSocket], + contentLength, + expectsPayload + ) + } else if (util.isBuffer(body)) { + writeBuffer( + abort, + stream, + body, + client, + request, + client[kSocket], + contentLength, + expectsPayload + ) + } else if (util.isBlobLike(body)) { + if (typeof body.stream === 'function') { + writeIterable( + abort, + stream, + body.stream(), + client, + request, + client[kSocket], + contentLength, + expectsPayload + ) + } else { + writeBlob( + abort, + stream, + body, + client, + request, + client[kSocket], + contentLength, + expectsPayload + ) + } + } else if (util.isStream(body)) { + writeStream( + abort, + client[kSocket], + expectsPayload, + stream, + body, + client, + request, + contentLength + ) + } else if (util.isIterable(body)) { + writeIterable( + abort, + stream, + body, + client, + request, + client[kSocket], + contentLength, + expectsPayload + ) + } else { + assert(false) + } + } +} + +function writeBuffer (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) { + try { + if (body != null && util.isBuffer(body)) { + assert(contentLength === body.byteLength, 'buffer body must have content length') + h2stream.cork() + h2stream.write(body) + h2stream.uncork() + h2stream.end() + + request.onBodySent(body) + } + + if (!expectsPayload) { + socket[kReset] = true + } + + request.onRequestSent() + client[kResume]() + } catch (error) { + abort(error) + } +} + +function writeStream (abort, socket, expectsPayload, h2stream, body, client, request, contentLength) { + assert(contentLength !== 0 || client[kRunning] === 0, 'stream body cannot be pipelined') + + // For HTTP/2, is enough to pipe the stream + const pipe = pipeline( + body, + h2stream, + (err) => { + if (err) { + util.destroy(pipe, err) + abort(err) + } else { + util.removeAllListeners(pipe) + request.onRequestSent() + + if (!expectsPayload) { + socket[kReset] = true + } + + client[kResume]() + } + } + ) + + util.addListener(pipe, 'data', onPipeData) + + function onPipeData (chunk) { + request.onBodySent(chunk) + } +} + +async function writeBlob (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) { + assert(contentLength === body.size, 'blob body must have content length') + + try { + if (contentLength != null && contentLength !== body.size) { + throw new RequestContentLengthMismatchError() + } + + const buffer = Buffer.from(await body.arrayBuffer()) + + h2stream.cork() + h2stream.write(buffer) + h2stream.uncork() + h2stream.end() + + request.onBodySent(buffer) + request.onRequestSent() + + if (!expectsPayload) { + socket[kReset] = true + } + + client[kResume]() + } catch (err) { + abort(err) + } +} + +async function writeIterable (abort, h2stream, body, client, request, socket, contentLength, expectsPayload) { + assert(contentLength !== 0 || client[kRunning] === 0, 'iterator body cannot be pipelined') + + let callback = null + function onDrain () { + if (callback) { + const cb = callback + callback = null + cb() + } + } + + const waitForDrain = () => new Promise((resolve, reject) => { + assert(callback === null) + + if (socket[kError]) { + reject(socket[kError]) + } else { + callback = resolve + } + }) + + h2stream + .on('close', onDrain) + .on('drain', onDrain) + + try { + // It's up to the user to somehow abort the async iterable. + for await (const chunk of body) { + if (socket[kError]) { + throw socket[kError] + } + + const res = h2stream.write(chunk) + request.onBodySent(chunk) + if (!res) { + await waitForDrain() + } + } + + h2stream.end() + + request.onRequestSent() + + if (!expectsPayload) { + socket[kReset] = true + } + + client[kResume]() + } catch (err) { + abort(err) + } finally { + h2stream + .off('close', onDrain) + .off('drain', onDrain) + } +} + +module.exports = connectH2 + + +/***/ }), + +/***/ 3701: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; +// @ts-check + + + +const assert = __nccwpck_require__(4589) +const net = __nccwpck_require__(7030) +const http = __nccwpck_require__(7067) +const util = __nccwpck_require__(3440) +const { channels } = __nccwpck_require__(2414) +const Request = __nccwpck_require__(4655) +const DispatcherBase = __nccwpck_require__(1841) +const { + InvalidArgumentError, + InformationalError, + ClientDestroyedError +} = __nccwpck_require__(8707) +const buildConnector = __nccwpck_require__(9136) +const { + kUrl, + kServerName, + kClient, + kBusy, + kConnect, + kResuming, + kRunning, + kPending, + kSize, + kQueue, + kConnected, + kConnecting, + kNeedDrain, + kKeepAliveDefaultTimeout, + kHostHeader, + kPendingIdx, + kRunningIdx, + kError, + kPipelining, + kKeepAliveTimeoutValue, + kMaxHeadersSize, + kKeepAliveMaxTimeout, + kKeepAliveTimeoutThreshold, + kHeadersTimeout, + kBodyTimeout, + kStrictContentLength, + kConnector, + kMaxRedirections, + kMaxRequests, + kCounter, + kClose, + kDestroy, + kDispatch, + kInterceptors, + kLocalAddress, + kMaxResponseSize, + kOnError, + kHTTPContext, + kMaxConcurrentStreams, + kResume +} = __nccwpck_require__(6443) +const connectH1 = __nccwpck_require__(637) +const connectH2 = __nccwpck_require__(8788) +let deprecatedInterceptorWarned = false + +const kClosedResolve = Symbol('kClosedResolve') + +const noop = () => {} + +function getPipelining (client) { + return client[kPipelining] ?? client[kHTTPContext]?.defaultPipelining ?? 1 +} + +/** + * @type {import('../../types/client.js').default} + */ +class Client extends DispatcherBase { + /** + * + * @param {string|URL} url + * @param {import('../../types/client.js').Client.Options} options + */ + constructor (url, { + interceptors, + maxHeaderSize, + headersTimeout, + socketTimeout, + requestTimeout, + connectTimeout, + bodyTimeout, + idleTimeout, + keepAlive, + keepAliveTimeout, + maxKeepAliveTimeout, + keepAliveMaxTimeout, + keepAliveTimeoutThreshold, + socketPath, + pipelining, + tls, + strictContentLength, + maxCachedSessions, + maxRedirections, + connect, + maxRequestsPerClient, + localAddress, + maxResponseSize, + autoSelectFamily, + autoSelectFamilyAttemptTimeout, + // h2 + maxConcurrentStreams, + allowH2, + webSocket + } = {}) { + super({ webSocket }) + + if (keepAlive !== undefined) { + throw new InvalidArgumentError('unsupported keepAlive, use pipelining=0 instead') + } + + if (socketTimeout !== undefined) { + throw new InvalidArgumentError('unsupported socketTimeout, use headersTimeout & bodyTimeout instead') + } + + if (requestTimeout !== undefined) { + throw new InvalidArgumentError('unsupported requestTimeout, use headersTimeout & bodyTimeout instead') + } + + if (idleTimeout !== undefined) { + throw new InvalidArgumentError('unsupported idleTimeout, use keepAliveTimeout instead') + } + + if (maxKeepAliveTimeout !== undefined) { + throw new InvalidArgumentError('unsupported maxKeepAliveTimeout, use keepAliveMaxTimeout instead') + } + + if (maxHeaderSize != null && !Number.isFinite(maxHeaderSize)) { + throw new InvalidArgumentError('invalid maxHeaderSize') + } + + if (socketPath != null && typeof socketPath !== 'string') { + throw new InvalidArgumentError('invalid socketPath') + } + + if (connectTimeout != null && (!Number.isFinite(connectTimeout) || connectTimeout < 0)) { + throw new InvalidArgumentError('invalid connectTimeout') + } + + if (keepAliveTimeout != null && (!Number.isFinite(keepAliveTimeout) || keepAliveTimeout <= 0)) { + throw new InvalidArgumentError('invalid keepAliveTimeout') + } + + if (keepAliveMaxTimeout != null && (!Number.isFinite(keepAliveMaxTimeout) || keepAliveMaxTimeout <= 0)) { + throw new InvalidArgumentError('invalid keepAliveMaxTimeout') + } + + if (keepAliveTimeoutThreshold != null && !Number.isFinite(keepAliveTimeoutThreshold)) { + throw new InvalidArgumentError('invalid keepAliveTimeoutThreshold') + } + + if (headersTimeout != null && (!Number.isInteger(headersTimeout) || headersTimeout < 0)) { + throw new InvalidArgumentError('headersTimeout must be a positive integer or zero') + } + + if (bodyTimeout != null && (!Number.isInteger(bodyTimeout) || bodyTimeout < 0)) { + throw new InvalidArgumentError('bodyTimeout must be a positive integer or zero') + } + + if (connect != null && typeof connect !== 'function' && typeof connect !== 'object') { + throw new InvalidArgumentError('connect must be a function or an object') + } + + if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) { + throw new InvalidArgumentError('maxRedirections must be a positive number') + } + + if (maxRequestsPerClient != null && (!Number.isInteger(maxRequestsPerClient) || maxRequestsPerClient < 0)) { + throw new InvalidArgumentError('maxRequestsPerClient must be a positive number') + } + + if (localAddress != null && (typeof localAddress !== 'string' || net.isIP(localAddress) === 0)) { + throw new InvalidArgumentError('localAddress must be valid string IP address') + } + + if (maxResponseSize != null && (!Number.isInteger(maxResponseSize) || maxResponseSize < -1)) { + throw new InvalidArgumentError('maxResponseSize must be a positive number') + } + + if ( + autoSelectFamilyAttemptTimeout != null && + (!Number.isInteger(autoSelectFamilyAttemptTimeout) || autoSelectFamilyAttemptTimeout < -1) + ) { + throw new InvalidArgumentError('autoSelectFamilyAttemptTimeout must be a positive number') + } + + // h2 + if (allowH2 != null && typeof allowH2 !== 'boolean') { + throw new InvalidArgumentError('allowH2 must be a valid boolean value') + } + + if (maxConcurrentStreams != null && (typeof maxConcurrentStreams !== 'number' || maxConcurrentStreams < 1)) { + throw new InvalidArgumentError('maxConcurrentStreams must be a positive integer, greater than 0') + } + + if (typeof connect !== 'function') { + connect = buildConnector({ + ...tls, + maxCachedSessions, + allowH2, + socketPath, + timeout: connectTimeout, + ...(autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined), + ...connect + }) + } + + if (interceptors?.Client && Array.isArray(interceptors.Client)) { + this[kInterceptors] = interceptors.Client + if (!deprecatedInterceptorWarned) { + deprecatedInterceptorWarned = true + process.emitWarning('Client.Options#interceptor is deprecated. Use Dispatcher#compose instead.', { + code: 'UNDICI-CLIENT-INTERCEPTOR-DEPRECATED' + }) + } + } else { + this[kInterceptors] = [createRedirectInterceptor({ maxRedirections })] + } + + this[kUrl] = util.parseOrigin(url) + this[kConnector] = connect + this[kPipelining] = pipelining != null ? pipelining : 1 + this[kMaxHeadersSize] = maxHeaderSize || http.maxHeaderSize + this[kKeepAliveDefaultTimeout] = keepAliveTimeout == null ? 4e3 : keepAliveTimeout + this[kKeepAliveMaxTimeout] = keepAliveMaxTimeout == null ? 600e3 : keepAliveMaxTimeout + this[kKeepAliveTimeoutThreshold] = keepAliveTimeoutThreshold == null ? 2e3 : keepAliveTimeoutThreshold + this[kKeepAliveTimeoutValue] = this[kKeepAliveDefaultTimeout] + this[kServerName] = null + this[kLocalAddress] = localAddress != null ? localAddress : null + this[kResuming] = 0 // 0, idle, 1, scheduled, 2 resuming + this[kNeedDrain] = 0 // 0, idle, 1, scheduled, 2 resuming + this[kHostHeader] = `host: ${this[kUrl].hostname}${this[kUrl].port ? `:${this[kUrl].port}` : ''}\r\n` + this[kBodyTimeout] = bodyTimeout != null ? bodyTimeout : 300e3 + this[kHeadersTimeout] = headersTimeout != null ? headersTimeout : 300e3 + this[kStrictContentLength] = strictContentLength == null ? true : strictContentLength + this[kMaxRedirections] = maxRedirections + this[kMaxRequests] = maxRequestsPerClient + this[kClosedResolve] = null + this[kMaxResponseSize] = maxResponseSize > -1 ? maxResponseSize : -1 + this[kMaxConcurrentStreams] = maxConcurrentStreams != null ? maxConcurrentStreams : 100 // Max peerConcurrentStreams for a Node h2 server + this[kHTTPContext] = null + + // kQueue is built up of 3 sections separated by + // the kRunningIdx and kPendingIdx indices. + // | complete | running | pending | + // ^ kRunningIdx ^ kPendingIdx ^ kQueue.length + // kRunningIdx points to the first running element. + // kPendingIdx points to the first pending element. + // This implements a fast queue with an amortized + // time of O(1). + + this[kQueue] = [] + this[kRunningIdx] = 0 + this[kPendingIdx] = 0 + + this[kResume] = (sync) => resume(this, sync) + this[kOnError] = (err) => onError(this, err) + } + + get pipelining () { + return this[kPipelining] + } + + set pipelining (value) { + this[kPipelining] = value + this[kResume](true) + } + + get [kPending] () { + return this[kQueue].length - this[kPendingIdx] + } + + get [kRunning] () { + return this[kPendingIdx] - this[kRunningIdx] + } + + get [kSize] () { + return this[kQueue].length - this[kRunningIdx] + } + + get [kConnected] () { + return !!this[kHTTPContext] && !this[kConnecting] && !this[kHTTPContext].destroyed + } + + get [kBusy] () { + return Boolean( + this[kHTTPContext]?.busy(null) || + (this[kSize] >= (getPipelining(this) || 1)) || + this[kPending] > 0 + ) + } + + /* istanbul ignore: only used for test */ + [kConnect] (cb) { + connect(this) + this.once('connect', cb) + } + + [kDispatch] (opts, handler) { + const origin = opts.origin || this[kUrl].origin + const request = new Request(origin, opts, handler) + + this[kQueue].push(request) + if (this[kResuming]) { + // Do nothing. + } else if (util.bodyLength(request.body) == null && util.isIterable(request.body)) { + // Wait a tick in case stream/iterator is ended in the same tick. + this[kResuming] = 1 + queueMicrotask(() => resume(this)) + } else { + this[kResume](true) + } + + if (this[kResuming] && this[kNeedDrain] !== 2 && this[kBusy]) { + this[kNeedDrain] = 2 + } + + return this[kNeedDrain] < 2 + } + + async [kClose] () { + // TODO: for H2 we need to gracefully flush the remaining enqueued + // request and close each stream. + return new Promise((resolve) => { + if (this[kSize]) { + this[kClosedResolve] = resolve + } else { + resolve(null) + } + }) + } + + async [kDestroy] (err) { + return new Promise((resolve) => { + const requests = this[kQueue].splice(this[kPendingIdx]) + for (let i = 0; i < requests.length; i++) { + const request = requests[i] + util.errorRequest(this, request, err) + } + + const callback = () => { + if (this[kClosedResolve]) { + // TODO (fix): Should we error here with ClientDestroyedError? + this[kClosedResolve]() + this[kClosedResolve] = null + } + resolve(null) + } + + if (this[kHTTPContext]) { + this[kHTTPContext].destroy(err, callback) + this[kHTTPContext] = null + } else { + queueMicrotask(callback) + } + + this[kResume]() + }) + } +} + +const createRedirectInterceptor = __nccwpck_require__(5092) + +function onError (client, err) { + if ( + client[kRunning] === 0 && + err.code !== 'UND_ERR_INFO' && + err.code !== 'UND_ERR_SOCKET' + ) { + // Error is not caused by running request and not a recoverable + // socket error. + + assert(client[kPendingIdx] === client[kRunningIdx]) + + const requests = client[kQueue].splice(client[kRunningIdx]) + + for (let i = 0; i < requests.length; i++) { + const request = requests[i] + util.errorRequest(client, request, err) + } + assert(client[kSize] === 0) + } +} + +/** + * @param {Client} client + * @returns + */ +async function connect (client) { + assert(!client[kConnecting]) + assert(!client[kHTTPContext]) + + let { host, hostname, protocol, port } = client[kUrl] + + // Resolve ipv6 + if (hostname[0] === '[') { + const idx = hostname.indexOf(']') + + assert(idx !== -1) + const ip = hostname.substring(1, idx) + + assert(net.isIP(ip)) + hostname = ip + } + + client[kConnecting] = true + + if (channels.beforeConnect.hasSubscribers) { + channels.beforeConnect.publish({ + connectParams: { + host, + hostname, + protocol, + port, + version: client[kHTTPContext]?.version, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector] + }) + } + + try { + const socket = await new Promise((resolve, reject) => { + client[kConnector]({ + host, + hostname, + protocol, + port, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, (err, socket) => { + if (err) { + reject(err) + } else { + resolve(socket) + } + }) + }) + + if (client.destroyed) { + util.destroy(socket.on('error', noop), new ClientDestroyedError()) + return + } + + assert(socket) + + try { + client[kHTTPContext] = socket.alpnProtocol === 'h2' + ? await connectH2(client, socket) + : await connectH1(client, socket) + } catch (err) { + socket.destroy().on('error', noop) + throw err + } + + client[kConnecting] = false + + socket[kCounter] = 0 + socket[kMaxRequests] = client[kMaxRequests] + socket[kClient] = client + socket[kError] = null + + if (channels.connected.hasSubscribers) { + channels.connected.publish({ + connectParams: { + host, + hostname, + protocol, + port, + version: client[kHTTPContext]?.version, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector], + socket + }) + } + client.emit('connect', client[kUrl], [client]) + } catch (err) { + if (client.destroyed) { + return + } + + client[kConnecting] = false + + if (channels.connectError.hasSubscribers) { + channels.connectError.publish({ + connectParams: { + host, + hostname, + protocol, + port, + version: client[kHTTPContext]?.version, + servername: client[kServerName], + localAddress: client[kLocalAddress] + }, + connector: client[kConnector], + error: err + }) + } + + if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') { + assert(client[kRunning] === 0) + while (client[kPending] > 0 && client[kQueue][client[kPendingIdx]].servername === client[kServerName]) { + const request = client[kQueue][client[kPendingIdx]++] + util.errorRequest(client, request, err) + } + } else { + onError(client, err) + } + + client.emit('connectionError', client[kUrl], [client], err) + } + + client[kResume]() +} + +function emitDrain (client) { + client[kNeedDrain] = 0 + client.emit('drain', client[kUrl], [client]) +} + +function resume (client, sync) { + if (client[kResuming] === 2) { + return + } + + client[kResuming] = 2 + + _resume(client, sync) + client[kResuming] = 0 + + if (client[kRunningIdx] > 256) { + client[kQueue].splice(0, client[kRunningIdx]) + client[kPendingIdx] -= client[kRunningIdx] + client[kRunningIdx] = 0 + } +} + +function _resume (client, sync) { + while (true) { + if (client.destroyed) { + assert(client[kPending] === 0) + return + } + + if (client[kClosedResolve] && !client[kSize]) { + client[kClosedResolve]() + client[kClosedResolve] = null + return + } + + if (client[kHTTPContext]) { + client[kHTTPContext].resume() + } + + if (client[kBusy]) { + client[kNeedDrain] = 2 + } else if (client[kNeedDrain] === 2) { + if (sync) { + client[kNeedDrain] = 1 + queueMicrotask(() => emitDrain(client)) + } else { + emitDrain(client) + } + continue + } + + if (client[kPending] === 0) { + return + } + + if (client[kRunning] >= (getPipelining(client) || 1)) { + return + } + + const request = client[kQueue][client[kPendingIdx]] + + if (client[kUrl].protocol === 'https:' && client[kServerName] !== request.servername) { + if (client[kRunning] > 0) { + return + } + + client[kServerName] = request.servername + client[kHTTPContext]?.destroy(new InformationalError('servername changed'), () => { + client[kHTTPContext] = null + resume(client) + }) + } + + if (client[kConnecting]) { + return + } + + if (!client[kHTTPContext]) { + connect(client) + return + } + + if (client[kHTTPContext].destroyed) { + return + } + + if (client[kHTTPContext].busy(request)) { + return + } + + if (!request.aborted && client[kHTTPContext].write(request)) { + client[kPendingIdx]++ + } else { + client[kQueue].splice(client[kPendingIdx], 1) + } + } +} + +module.exports = Client + + +/***/ }), + +/***/ 1841: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Dispatcher = __nccwpck_require__(883) +const { + ClientDestroyedError, + ClientClosedError, + InvalidArgumentError +} = __nccwpck_require__(8707) +const { kDestroy, kClose, kClosed, kDestroyed, kDispatch, kInterceptors } = __nccwpck_require__(6443) + +const kOnDestroyed = Symbol('onDestroyed') +const kOnClosed = Symbol('onClosed') +const kInterceptedDispatch = Symbol('Intercepted Dispatch') +const kWebSocketOptions = Symbol('webSocketOptions') + +class DispatcherBase extends Dispatcher { + constructor (opts) { + super() + + this[kDestroyed] = false + this[kOnDestroyed] = null + this[kClosed] = false + this[kOnClosed] = [] + this[kWebSocketOptions] = opts?.webSocket ?? {} + } + + get webSocketOptions () { + return { + maxFragments: this[kWebSocketOptions].maxFragments ?? 131072, + maxPayloadSize: this[kWebSocketOptions].maxPayloadSize ?? 128 * 1024 * 1024 + } + } + + get destroyed () { + return this[kDestroyed] + } + + get closed () { + return this[kClosed] + } + + get interceptors () { + return this[kInterceptors] + } + + set interceptors (newInterceptors) { + if (newInterceptors) { + for (let i = newInterceptors.length - 1; i >= 0; i--) { + const interceptor = this[kInterceptors][i] + if (typeof interceptor !== 'function') { + throw new InvalidArgumentError('interceptor must be an function') + } + } + } + + this[kInterceptors] = newInterceptors + } + + close (callback) { + if (callback === undefined) { + return new Promise((resolve, reject) => { + this.close((err, data) => { + return err ? reject(err) : resolve(data) + }) + }) + } + + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + if (this[kDestroyed]) { + queueMicrotask(() => callback(new ClientDestroyedError(), null)) + return + } + + if (this[kClosed]) { + if (this[kOnClosed]) { + this[kOnClosed].push(callback) + } else { + queueMicrotask(() => callback(null, null)) + } + return + } + + this[kClosed] = true + this[kOnClosed].push(callback) + + const onClosed = () => { + const callbacks = this[kOnClosed] + this[kOnClosed] = null + for (let i = 0; i < callbacks.length; i++) { + callbacks[i](null, null) + } + } + + // Should not error. + this[kClose]() + .then(() => this.destroy()) + .then(() => { + queueMicrotask(onClosed) + }) + } + + destroy (err, callback) { + if (typeof err === 'function') { + callback = err + err = null + } + + if (callback === undefined) { + return new Promise((resolve, reject) => { + this.destroy(err, (err, data) => { + return err ? /* istanbul ignore next: should never error */ reject(err) : resolve(data) + }) + }) + } + + if (typeof callback !== 'function') { + throw new InvalidArgumentError('invalid callback') + } + + if (this[kDestroyed]) { + if (this[kOnDestroyed]) { + this[kOnDestroyed].push(callback) + } else { + queueMicrotask(() => callback(null, null)) + } + return + } + + if (!err) { + err = new ClientDestroyedError() + } + + this[kDestroyed] = true + this[kOnDestroyed] = this[kOnDestroyed] || [] + this[kOnDestroyed].push(callback) + + const onDestroyed = () => { + const callbacks = this[kOnDestroyed] + this[kOnDestroyed] = null + for (let i = 0; i < callbacks.length; i++) { + callbacks[i](null, null) + } + } + + // Should not error. + this[kDestroy](err).then(() => { + queueMicrotask(onDestroyed) + }) + } + + [kInterceptedDispatch] (opts, handler) { + if (!this[kInterceptors] || this[kInterceptors].length === 0) { + this[kInterceptedDispatch] = this[kDispatch] + return this[kDispatch](opts, handler) + } + + let dispatch = this[kDispatch].bind(this) + for (let i = this[kInterceptors].length - 1; i >= 0; i--) { + dispatch = this[kInterceptors][i](dispatch) + } + this[kInterceptedDispatch] = dispatch + return dispatch(opts, handler) + } + + dispatch (opts, handler) { + if (!handler || typeof handler !== 'object') { + throw new InvalidArgumentError('handler must be an object') + } + + try { + if (!opts || typeof opts !== 'object') { + throw new InvalidArgumentError('opts must be an object.') + } + + if (this[kDestroyed] || this[kOnDestroyed]) { + throw new ClientDestroyedError() + } + + if (this[kClosed]) { + throw new ClientClosedError() + } + + return this[kInterceptedDispatch](opts, handler) + } catch (err) { + if (typeof handler.onError !== 'function') { + throw new InvalidArgumentError('invalid onError method') + } + + handler.onError(err) + + return false + } + } +} + +module.exports = DispatcherBase + + +/***/ }), + +/***/ 883: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + +const EventEmitter = __nccwpck_require__(8474) + +class Dispatcher extends EventEmitter { + dispatch () { + throw new Error('not implemented') + } + + close () { + throw new Error('not implemented') + } + + destroy () { + throw new Error('not implemented') + } + + compose (...args) { + // So we handle [interceptor1, interceptor2] or interceptor1, interceptor2, ... + const interceptors = Array.isArray(args[0]) ? args[0] : args + let dispatch = this.dispatch.bind(this) + + for (const interceptor of interceptors) { + if (interceptor == null) { + continue + } + + if (typeof interceptor !== 'function') { + throw new TypeError(`invalid interceptor, expected function received ${typeof interceptor}`) + } + + dispatch = interceptor(dispatch) + + if (dispatch == null || typeof dispatch !== 'function' || dispatch.length !== 2) { + throw new TypeError('invalid interceptor') + } + } + + return new ComposedDispatcher(this, dispatch) + } +} + +class ComposedDispatcher extends Dispatcher { + #dispatcher = null + #dispatch = null + + constructor (dispatcher, dispatch) { + super() + this.#dispatcher = dispatcher + this.#dispatch = dispatch + } + + dispatch (...args) { + this.#dispatch(...args) + } + + close (...args) { + return this.#dispatcher.close(...args) + } + + destroy (...args) { + return this.#dispatcher.destroy(...args) + } +} + +module.exports = Dispatcher + + +/***/ }), + +/***/ 3137: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const DispatcherBase = __nccwpck_require__(1841) +const { kClose, kDestroy, kClosed, kDestroyed, kDispatch, kNoProxyAgent, kHttpProxyAgent, kHttpsProxyAgent } = __nccwpck_require__(6443) +const ProxyAgent = __nccwpck_require__(6672) +const Agent = __nccwpck_require__(7405) + +const DEFAULT_PORTS = { + 'http:': 80, + 'https:': 443 +} + +let experimentalWarned = false + +class EnvHttpProxyAgent extends DispatcherBase { + #noProxyValue = null + #noProxyEntries = null + #opts = null + + constructor (opts = {}) { + super() + this.#opts = opts + + if (!experimentalWarned) { + experimentalWarned = true + process.emitWarning('EnvHttpProxyAgent is experimental, expect them to change at any time.', { + code: 'UNDICI-EHPA' + }) + } + + const { httpProxy, httpsProxy, noProxy, ...agentOpts } = opts + + this[kNoProxyAgent] = new Agent(agentOpts) + + const HTTP_PROXY = httpProxy ?? process.env.http_proxy ?? process.env.HTTP_PROXY + if (HTTP_PROXY) { + this[kHttpProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTP_PROXY }) + } else { + this[kHttpProxyAgent] = this[kNoProxyAgent] + } + + const HTTPS_PROXY = httpsProxy ?? process.env.https_proxy ?? process.env.HTTPS_PROXY + if (HTTPS_PROXY) { + this[kHttpsProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTPS_PROXY }) + } else { + this[kHttpsProxyAgent] = this[kHttpProxyAgent] + } + + this.#parseNoProxy() + } + + [kDispatch] (opts, handler) { + const url = new URL(opts.origin) + const agent = this.#getProxyAgentForUrl(url) + return agent.dispatch(opts, handler) + } + + async [kClose] () { + await this[kNoProxyAgent].close() + if (!this[kHttpProxyAgent][kClosed]) { + await this[kHttpProxyAgent].close() + } + if (!this[kHttpsProxyAgent][kClosed]) { + await this[kHttpsProxyAgent].close() + } + } + + async [kDestroy] (err) { + await this[kNoProxyAgent].destroy(err) + if (!this[kHttpProxyAgent][kDestroyed]) { + await this[kHttpProxyAgent].destroy(err) + } + if (!this[kHttpsProxyAgent][kDestroyed]) { + await this[kHttpsProxyAgent].destroy(err) + } + } + + #getProxyAgentForUrl (url) { + let { protocol, host: hostname, port } = url + + // Stripping ports in this way instead of using parsedUrl.hostname to make + // sure that the brackets around IPv6 addresses are kept. + hostname = hostname.replace(/:\d*$/, '').toLowerCase() + port = Number.parseInt(port, 10) || DEFAULT_PORTS[protocol] || 0 + if (!this.#shouldProxy(hostname, port)) { + return this[kNoProxyAgent] + } + if (protocol === 'https:') { + return this[kHttpsProxyAgent] + } + return this[kHttpProxyAgent] + } + + #shouldProxy (hostname, port) { + if (this.#noProxyChanged) { + this.#parseNoProxy() + } + + if (this.#noProxyEntries.length === 0) { + return true // Always proxy if NO_PROXY is not set or empty. + } + if (this.#noProxyValue === '*') { + return false // Never proxy if wildcard is set. + } + + for (let i = 0; i < this.#noProxyEntries.length; i++) { + const entry = this.#noProxyEntries[i] + if (entry.port && entry.port !== port) { + continue // Skip if ports don't match. + } + if (!/^[.*]/.test(entry.hostname)) { + // No wildcards, so don't proxy only if there is not an exact match. + if (hostname === entry.hostname) { + return false + } + } else { + // Don't proxy if the hostname ends with the no_proxy host. + if (hostname.endsWith(entry.hostname.replace(/^\*/, ''))) { + return false + } + } + } + + return true + } + + #parseNoProxy () { + const noProxyValue = this.#opts.noProxy ?? this.#noProxyEnv + const noProxySplit = noProxyValue.split(/[,\s]/) + const noProxyEntries = [] + + for (let i = 0; i < noProxySplit.length; i++) { + const entry = noProxySplit[i] + if (!entry) { + continue + } + const parsed = entry.match(/^(.+):(\d+)$/) + noProxyEntries.push({ + hostname: (parsed ? parsed[1] : entry).toLowerCase(), + port: parsed ? Number.parseInt(parsed[2], 10) : 0 + }) + } + + this.#noProxyValue = noProxyValue + this.#noProxyEntries = noProxyEntries + } + + get #noProxyChanged () { + if (this.#opts.noProxy !== undefined) { + return false + } + return this.#noProxyValue !== this.#noProxyEnv + } + + get #noProxyEnv () { + return process.env.no_proxy ?? process.env.NO_PROXY ?? '' + } +} + +module.exports = EnvHttpProxyAgent + + +/***/ }), + +/***/ 4660: +/***/ ((module) => { + +"use strict"; +/* eslint-disable */ + + + +// Extracted from node/lib/internal/fixed_queue.js + +// Currently optimal queue size, tested on V8 6.0 - 6.6. Must be power of two. +const kSize = 2048; +const kMask = kSize - 1; + +// The FixedQueue is implemented as a singly-linked list of fixed-size +// circular buffers. It looks something like this: +// +// head tail +// | | +// v v +// +-----------+ <-----\ +-----------+ <------\ +-----------+ +// | [null] | \----- | next | \------- | next | +// +-----------+ +-----------+ +-----------+ +// | item | <-- bottom | item | <-- bottom | [empty] | +// | item | | item | | [empty] | +// | item | | item | | [empty] | +// | item | | item | | [empty] | +// | item | | item | bottom --> | item | +// | item | | item | | item | +// | ... | | ... | | ... | +// | item | | item | | item | +// | item | | item | | item | +// | [empty] | <-- top | item | | item | +// | [empty] | | item | | item | +// | [empty] | | [empty] | <-- top top --> | [empty] | +// +-----------+ +-----------+ +-----------+ +// +// Or, if there is only one circular buffer, it looks something +// like either of these: +// +// head tail head tail +// | | | | +// v v v v +// +-----------+ +-----------+ +// | [null] | | [null] | +// +-----------+ +-----------+ +// | [empty] | | item | +// | [empty] | | item | +// | item | <-- bottom top --> | [empty] | +// | item | | [empty] | +// | [empty] | <-- top bottom --> | item | +// | [empty] | | item | +// +-----------+ +-----------+ +// +// Adding a value means moving `top` forward by one, removing means +// moving `bottom` forward by one. After reaching the end, the queue +// wraps around. +// +// When `top === bottom` the current queue is empty and when +// `top + 1 === bottom` it's full. This wastes a single space of storage +// but allows much quicker checks. + +class FixedCircularBuffer { + constructor() { + this.bottom = 0; + this.top = 0; + this.list = new Array(kSize); + this.next = null; + } + + isEmpty() { + return this.top === this.bottom; + } + + isFull() { + return ((this.top + 1) & kMask) === this.bottom; + } + + push(data) { + this.list[this.top] = data; + this.top = (this.top + 1) & kMask; + } + + shift() { + const nextItem = this.list[this.bottom]; + if (nextItem === undefined) + return null; + this.list[this.bottom] = undefined; + this.bottom = (this.bottom + 1) & kMask; + return nextItem; + } +} + +module.exports = class FixedQueue { + constructor() { + this.head = this.tail = new FixedCircularBuffer(); + } + + isEmpty() { + return this.head.isEmpty(); + } + + push(data) { + if (this.head.isFull()) { + // Head is full: Creates a new queue, sets the old queue's `.next` to it, + // and sets it as the new main queue. + this.head = this.head.next = new FixedCircularBuffer(); + } + this.head.push(data); + } + + shift() { + const tail = this.tail; + const next = tail.shift(); + if (tail.isEmpty() && tail.next !== null) { + // If there is another queue, it forms the new tail. + this.tail = tail.next; + } + return next; + } +}; + + +/***/ }), + +/***/ 2128: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const DispatcherBase = __nccwpck_require__(1841) +const FixedQueue = __nccwpck_require__(4660) +const { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl, kClose, kDestroy, kDispatch } = __nccwpck_require__(6443) +const PoolStats = __nccwpck_require__(3246) + +const kClients = Symbol('clients') +const kNeedDrain = Symbol('needDrain') +const kQueue = Symbol('queue') +const kClosedResolve = Symbol('closed resolve') +const kOnDrain = Symbol('onDrain') +const kOnConnect = Symbol('onConnect') +const kOnDisconnect = Symbol('onDisconnect') +const kOnConnectionError = Symbol('onConnectionError') +const kGetDispatcher = Symbol('get dispatcher') +const kAddClient = Symbol('add client') +const kRemoveClient = Symbol('remove client') +const kStats = Symbol('stats') + +class PoolBase extends DispatcherBase { + constructor (opts) { + super(opts) + + this[kQueue] = new FixedQueue() + this[kClients] = [] + this[kQueued] = 0 + + const pool = this + + this[kOnDrain] = function onDrain (origin, targets) { + const queue = pool[kQueue] + + let needDrain = false + + while (!needDrain) { + const item = queue.shift() + if (!item) { + break + } + pool[kQueued]-- + needDrain = !this.dispatch(item.opts, item.handler) + } + + this[kNeedDrain] = needDrain + + if (!this[kNeedDrain] && pool[kNeedDrain]) { + pool[kNeedDrain] = false + pool.emit('drain', origin, [pool, ...targets]) + } + + if (pool[kClosedResolve] && queue.isEmpty()) { + Promise + .all(pool[kClients].map(c => c.close())) + .then(pool[kClosedResolve]) + } + } + + this[kOnConnect] = (origin, targets) => { + pool.emit('connect', origin, [pool, ...targets]) + } + + this[kOnDisconnect] = (origin, targets, err) => { + pool.emit('disconnect', origin, [pool, ...targets], err) + } + + this[kOnConnectionError] = (origin, targets, err) => { + pool.emit('connectionError', origin, [pool, ...targets], err) + } + + this[kStats] = new PoolStats(this) + } + + get [kBusy] () { + return this[kNeedDrain] + } + + get [kConnected] () { + return this[kClients].filter(client => client[kConnected]).length + } + + get [kFree] () { + return this[kClients].filter(client => client[kConnected] && !client[kNeedDrain]).length + } + + get [kPending] () { + let ret = this[kQueued] + for (const { [kPending]: pending } of this[kClients]) { + ret += pending + } + return ret + } + + get [kRunning] () { + let ret = 0 + for (const { [kRunning]: running } of this[kClients]) { + ret += running + } + return ret + } + + get [kSize] () { + let ret = this[kQueued] + for (const { [kSize]: size } of this[kClients]) { + ret += size + } + return ret + } + + get stats () { + return this[kStats] + } + + async [kClose] () { + if (this[kQueue].isEmpty()) { + await Promise.all(this[kClients].map(c => c.close())) + } else { + await new Promise((resolve) => { + this[kClosedResolve] = resolve + }) + } + } + + async [kDestroy] (err) { + while (true) { + const item = this[kQueue].shift() + if (!item) { + break + } + item.handler.onError(err) + } + + await Promise.all(this[kClients].map(c => c.destroy(err))) + } + + [kDispatch] (opts, handler) { + const dispatcher = this[kGetDispatcher]() + + if (!dispatcher) { + this[kNeedDrain] = true + this[kQueue].push({ opts, handler }) + this[kQueued]++ + } else if (!dispatcher.dispatch(opts, handler)) { + dispatcher[kNeedDrain] = true + this[kNeedDrain] = !this[kGetDispatcher]() + } + + return !this[kNeedDrain] + } + + [kAddClient] (client) { + client + .on('drain', this[kOnDrain]) + .on('connect', this[kOnConnect]) + .on('disconnect', this[kOnDisconnect]) + .on('connectionError', this[kOnConnectionError]) + + this[kClients].push(client) + + if (this[kNeedDrain]) { + queueMicrotask(() => { + if (this[kNeedDrain]) { + this[kOnDrain](client[kUrl], [this, client]) + } + }) + } + + return this + } + + [kRemoveClient] (client) { + client.close(() => { + const idx = this[kClients].indexOf(client) + if (idx !== -1) { + this[kClients].splice(idx, 1) + } + }) + + this[kNeedDrain] = this[kClients].some(dispatcher => ( + !dispatcher[kNeedDrain] && + dispatcher.closed !== true && + dispatcher.destroyed !== true + )) + } +} + +module.exports = { + PoolBase, + kClients, + kNeedDrain, + kAddClient, + kRemoveClient, + kGetDispatcher +} + + +/***/ }), + +/***/ 3246: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +const { kFree, kConnected, kPending, kQueued, kRunning, kSize } = __nccwpck_require__(6443) +const kPool = Symbol('pool') + +class PoolStats { + constructor (pool) { + this[kPool] = pool + } + + get connected () { + return this[kPool][kConnected] + } + + get free () { + return this[kPool][kFree] + } + + get pending () { + return this[kPool][kPending] + } + + get queued () { + return this[kPool][kQueued] + } + + get running () { + return this[kPool][kRunning] + } + + get size () { + return this[kPool][kSize] + } +} + +module.exports = PoolStats + + +/***/ }), + +/***/ 628: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { + PoolBase, + kClients, + kNeedDrain, + kAddClient, + kGetDispatcher +} = __nccwpck_require__(2128) +const Client = __nccwpck_require__(3701) +const { + InvalidArgumentError +} = __nccwpck_require__(8707) +const util = __nccwpck_require__(3440) +const { kUrl, kInterceptors } = __nccwpck_require__(6443) +const buildConnector = __nccwpck_require__(9136) + +const kOptions = Symbol('options') +const kConnections = Symbol('connections') +const kFactory = Symbol('factory') + +function defaultFactory (origin, opts) { + return new Client(origin, opts) +} + +class Pool extends PoolBase { + constructor (origin, { + connections, + factory = defaultFactory, + connect, + connectTimeout, + tls, + maxCachedSessions, + socketPath, + autoSelectFamily, + autoSelectFamilyAttemptTimeout, + allowH2, + ...options + } = {}) { + if (connections != null && (!Number.isFinite(connections) || connections < 0)) { + throw new InvalidArgumentError('invalid connections') + } + + if (typeof factory !== 'function') { + throw new InvalidArgumentError('factory must be a function.') + } + + if (connect != null && typeof connect !== 'function' && typeof connect !== 'object') { + throw new InvalidArgumentError('connect must be a function or an object') + } + + if (typeof connect !== 'function') { + connect = buildConnector({ + ...tls, + maxCachedSessions, + allowH2, + socketPath, + timeout: connectTimeout, + ...(autoSelectFamily ? { autoSelectFamily, autoSelectFamilyAttemptTimeout } : undefined), + ...connect + }) + } + + super(options) + + this[kInterceptors] = options.interceptors?.Pool && Array.isArray(options.interceptors.Pool) + ? options.interceptors.Pool + : [] + this[kConnections] = connections || null + this[kUrl] = util.parseOrigin(origin) + this[kOptions] = { ...util.deepClone(options), connect, allowH2 } + this[kOptions].interceptors = options.interceptors + ? { ...options.interceptors } + : undefined + this[kFactory] = factory + + this.on('connectionError', (origin, targets, error) => { + // If a connection error occurs, we remove the client from the pool, + // and emit a connectionError event. They will not be re-used. + // Fixes https://github.com/nodejs/undici/issues/3895 + for (const target of targets) { + // Do not use kRemoveClient here, as it will close the client, + // but the client cannot be closed in this state. + const idx = this[kClients].indexOf(target) + if (idx !== -1) { + this[kClients].splice(idx, 1) + } + } + }) + } + + [kGetDispatcher] () { + for (const client of this[kClients]) { + if (!client[kNeedDrain]) { + return client + } + } + + if (!this[kConnections] || this[kClients].length < this[kConnections]) { + const dispatcher = this[kFactory](this[kUrl], this[kOptions]) + this[kAddClient](dispatcher) + return dispatcher + } + } +} + +module.exports = Pool + + +/***/ }), + +/***/ 6672: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kProxy, kClose, kDestroy, kDispatch, kInterceptors } = __nccwpck_require__(6443) +const { URL } = __nccwpck_require__(3136) +const Agent = __nccwpck_require__(7405) +const Pool = __nccwpck_require__(628) +const DispatcherBase = __nccwpck_require__(1841) +const { InvalidArgumentError, RequestAbortedError, SecureProxyConnectionError } = __nccwpck_require__(8707) +const buildConnector = __nccwpck_require__(9136) +const Client = __nccwpck_require__(3701) + +const kAgent = Symbol('proxy agent') +const kClient = Symbol('proxy client') +const kProxyHeaders = Symbol('proxy headers') +const kRequestTls = Symbol('request tls settings') +const kProxyTls = Symbol('proxy tls settings') +const kConnectEndpoint = Symbol('connect endpoint function') +const kTunnelProxy = Symbol('tunnel proxy') + +function defaultProtocolPort (protocol) { + return protocol === 'https:' ? 443 : 80 +} + +function defaultFactory (origin, opts) { + return new Pool(origin, opts) +} + +const noop = () => {} + +function defaultAgentFactory (origin, opts) { + if (opts.connections === 1) { + return new Client(origin, opts) + } + return new Pool(origin, opts) +} + +class Http1ProxyWrapper extends DispatcherBase { + #client + + constructor (proxyUrl, { headers = {}, connect, factory }) { + super() + if (!proxyUrl) { + throw new InvalidArgumentError('Proxy URL is mandatory') + } + + this[kProxyHeaders] = headers + if (factory) { + this.#client = factory(proxyUrl, { connect }) + } else { + this.#client = new Client(proxyUrl, { connect }) + } + } + + [kDispatch] (opts, handler) { + const onHeaders = handler.onHeaders + handler.onHeaders = function (statusCode, data, resume) { + if (statusCode === 407) { + if (typeof handler.onError === 'function') { + handler.onError(new InvalidArgumentError('Proxy Authentication Required (407)')) + } + return + } + if (onHeaders) onHeaders.call(this, statusCode, data, resume) + } + + // Rewrite request as an HTTP1 Proxy request, without tunneling. + const { + origin, + path = '/', + headers = {} + } = opts + + opts.path = origin + path + + if (!('host' in headers) && !('Host' in headers)) { + const { host } = new URL(origin) + headers.host = host + } + opts.headers = { ...this[kProxyHeaders], ...headers } + + return this.#client[kDispatch](opts, handler) + } + + async [kClose] () { + return this.#client.close() + } + + async [kDestroy] (err) { + return this.#client.destroy(err) + } +} + +class ProxyAgent extends DispatcherBase { + constructor (opts) { + super() + + if (!opts || (typeof opts === 'object' && !(opts instanceof URL) && !opts.uri)) { + throw new InvalidArgumentError('Proxy uri is mandatory') + } + + const { clientFactory = defaultFactory } = opts + if (typeof clientFactory !== 'function') { + throw new InvalidArgumentError('Proxy opts.clientFactory must be a function.') + } + + const { proxyTunnel = true } = opts + + const url = this.#getUrl(opts) + const { href, origin, port, protocol, username, password, hostname: proxyHostname } = url + + this[kProxy] = { uri: href, protocol } + this[kInterceptors] = opts.interceptors?.ProxyAgent && Array.isArray(opts.interceptors.ProxyAgent) + ? opts.interceptors.ProxyAgent + : [] + this[kRequestTls] = opts.requestTls + this[kProxyTls] = opts.proxyTls + this[kProxyHeaders] = opts.headers || {} + this[kTunnelProxy] = proxyTunnel + + if (opts.auth && opts.token) { + throw new InvalidArgumentError('opts.auth cannot be used in combination with opts.token') + } else if (opts.auth) { + /* @deprecated in favour of opts.token */ + this[kProxyHeaders]['proxy-authorization'] = `Basic ${opts.auth}` + } else if (opts.token) { + this[kProxyHeaders]['proxy-authorization'] = opts.token + } else if (username && password) { + this[kProxyHeaders]['proxy-authorization'] = `Basic ${Buffer.from(`${decodeURIComponent(username)}:${decodeURIComponent(password)}`).toString('base64')}` + } + + const connect = buildConnector({ ...opts.proxyTls }) + this[kConnectEndpoint] = buildConnector({ ...opts.requestTls }) + + const agentFactory = opts.factory || defaultAgentFactory + const factory = (origin, options) => { + const { protocol } = new URL(origin) + if (!this[kTunnelProxy] && protocol === 'http:' && this[kProxy].protocol === 'http:') { + return new Http1ProxyWrapper(this[kProxy].uri, { + headers: this[kProxyHeaders], + connect, + factory: agentFactory + }) + } + return agentFactory(origin, options) + } + this[kClient] = clientFactory(url, { connect }) + this[kAgent] = new Agent({ + ...opts, + factory, + connect: async (opts, callback) => { + let requestedPath = opts.host + if (!opts.port) { + requestedPath += `:${defaultProtocolPort(opts.protocol)}` + } + try { + const { socket, statusCode } = await this[kClient].connect({ + origin, + port, + path: requestedPath, + signal: opts.signal, + headers: { + ...this[kProxyHeaders], + host: opts.host + }, + servername: this[kProxyTls]?.servername || proxyHostname + }) + if (statusCode !== 200) { + socket.on('error', noop).destroy() + callback(new RequestAbortedError(`Proxy response (${statusCode}) !== 200 when HTTP Tunneling`)) + } + if (opts.protocol !== 'https:') { + callback(null, socket) + return + } + let servername + if (this[kRequestTls]) { + servername = this[kRequestTls].servername + } else { + servername = opts.servername + } + this[kConnectEndpoint]({ ...opts, servername, httpSocket: socket }, callback) + } catch (err) { + if (err.code === 'ERR_TLS_CERT_ALTNAME_INVALID') { + // Throw a custom error to avoid loop in client.js#connect + callback(new SecureProxyConnectionError(err)) + } else { + callback(err) + } + } + } + }) + } + + dispatch (opts, handler) { + const headers = buildHeaders(opts.headers) + throwIfProxyAuthIsSent(headers) + + if (headers && !('host' in headers) && !('Host' in headers)) { + const { host } = new URL(opts.origin) + headers.host = host + } + + return this[kAgent].dispatch( + { + ...opts, + headers + }, + handler + ) + } + + /** + * @param {import('../types/proxy-agent').ProxyAgent.Options | string | URL} opts + * @returns {URL} + */ + #getUrl (opts) { + if (typeof opts === 'string') { + return new URL(opts) + } else if (opts instanceof URL) { + return opts + } else { + return new URL(opts.uri) + } + } + + async [kClose] () { + await this[kAgent].close() + await this[kClient].close() + } + + async [kDestroy] () { + await this[kAgent].destroy() + await this[kClient].destroy() + } +} + +/** + * @param {string[] | Record} headers + * @returns {Record} + */ +function buildHeaders (headers) { + // When using undici.fetch, the headers list is stored + // as an array. + if (Array.isArray(headers)) { + /** @type {Record} */ + const headersPair = {} + + for (let i = 0; i < headers.length; i += 2) { + headersPair[headers[i]] = headers[i + 1] + } + + return headersPair + } + + return headers +} + +/** + * @param {Record} headers + * + * Previous versions of ProxyAgent suggests the Proxy-Authorization in request headers + * Nevertheless, it was changed and to avoid a security vulnerability by end users + * this check was created. + * It should be removed in the next major version for performance reasons + */ +function throwIfProxyAuthIsSent (headers) { + const existProxyAuth = headers && Object.keys(headers) + .find((key) => key.toLowerCase() === 'proxy-authorization') + if (existProxyAuth) { + throw new InvalidArgumentError('Proxy-Authorization should be sent in ProxyAgent constructor') + } +} + +module.exports = ProxyAgent + + +/***/ }), + +/***/ 50: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const Dispatcher = __nccwpck_require__(883) +const RetryHandler = __nccwpck_require__(7816) + +class RetryAgent extends Dispatcher { + #agent = null + #options = null + constructor (agent, options = {}) { + super(options) + this.#agent = agent + this.#options = options + } + + dispatch (opts, handler) { + const retry = new RetryHandler({ + ...opts, + retryOptions: this.#options + }, { + dispatch: this.#agent.dispatch.bind(this.#agent), + handler + }) + return this.#agent.dispatch(opts, retry) + } + + close () { + return this.#agent.close() + } + + destroy () { + return this.#agent.destroy() + } +} + +module.exports = RetryAgent + + +/***/ }), + +/***/ 2581: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +// We include a version number for the Dispatcher API. In case of breaking changes, +// this version number must be increased to avoid conflicts. +const globalDispatcher = Symbol.for('undici.globalDispatcher.1') +const { InvalidArgumentError } = __nccwpck_require__(8707) +const Agent = __nccwpck_require__(7405) + +if (getGlobalDispatcher() === undefined) { + setGlobalDispatcher(new Agent()) +} + +function setGlobalDispatcher (agent) { + if (!agent || typeof agent.dispatch !== 'function') { + throw new InvalidArgumentError('Argument agent must implement Agent') + } + Object.defineProperty(globalThis, globalDispatcher, { + value: agent, + writable: true, + enumerable: false, + configurable: false + }) +} + +function getGlobalDispatcher () { + return globalThis[globalDispatcher] +} + +module.exports = { + setGlobalDispatcher, + getGlobalDispatcher +} + + +/***/ }), + +/***/ 8155: +/***/ ((module) => { + +"use strict"; + + +module.exports = class DecoratorHandler { + #handler + + constructor (handler) { + if (typeof handler !== 'object' || handler === null) { + throw new TypeError('handler must be an object') + } + this.#handler = handler + } + + onConnect (...args) { + return this.#handler.onConnect?.(...args) + } + + onError (...args) { + return this.#handler.onError?.(...args) + } + + onUpgrade (...args) { + return this.#handler.onUpgrade?.(...args) + } + + onResponseStarted (...args) { + return this.#handler.onResponseStarted?.(...args) + } + + onHeaders (...args) { + return this.#handler.onHeaders?.(...args) + } + + onData (...args) { + return this.#handler.onData?.(...args) + } + + onComplete (...args) { + return this.#handler.onComplete?.(...args) + } + + onBodySent (...args) { + return this.#handler.onBodySent?.(...args) + } +} + + +/***/ }), + +/***/ 8754: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const util = __nccwpck_require__(3440) +const { kBodyUsed } = __nccwpck_require__(6443) +const assert = __nccwpck_require__(4589) +const { InvalidArgumentError } = __nccwpck_require__(8707) +const EE = __nccwpck_require__(8474) + +const redirectableStatusCodes = [300, 301, 302, 303, 307, 308] + +const kBody = Symbol('body') + +class BodyAsyncIterable { + constructor (body) { + this[kBody] = body + this[kBodyUsed] = false + } + + async * [Symbol.asyncIterator] () { + assert(!this[kBodyUsed], 'disturbed') + this[kBodyUsed] = true + yield * this[kBody] + } +} + +class RedirectHandler { + constructor (dispatch, maxRedirections, opts, handler) { + if (maxRedirections != null && (!Number.isInteger(maxRedirections) || maxRedirections < 0)) { + throw new InvalidArgumentError('maxRedirections must be a positive number') + } + + util.validateHandler(handler, opts.method, opts.upgrade) + + this.dispatch = dispatch + this.location = null + this.abort = null + this.opts = { ...opts, maxRedirections: 0 } // opts must be a copy + this.maxRedirections = maxRedirections + this.handler = handler + this.history = [] + this.redirectionLimitReached = false + + if (util.isStream(this.opts.body)) { + // TODO (fix): Provide some way for the user to cache the file to e.g. /tmp + // so that it can be dispatched again? + // TODO (fix): Do we need 100-expect support to provide a way to do this properly? + if (util.bodyLength(this.opts.body) === 0) { + this.opts.body + .on('data', function () { + assert(false) + }) + } + + if (typeof this.opts.body.readableDidRead !== 'boolean') { + this.opts.body[kBodyUsed] = false + EE.prototype.on.call(this.opts.body, 'data', function () { + this[kBodyUsed] = true + }) + } + } else if (this.opts.body && typeof this.opts.body.pipeTo === 'function') { + // TODO (fix): We can't access ReadableStream internal state + // to determine whether or not it has been disturbed. This is just + // a workaround. + this.opts.body = new BodyAsyncIterable(this.opts.body) + } else if ( + this.opts.body && + typeof this.opts.body !== 'string' && + !ArrayBuffer.isView(this.opts.body) && + util.isIterable(this.opts.body) + ) { + // TODO: Should we allow re-using iterable if !this.opts.idempotent + // or through some other flag? + this.opts.body = new BodyAsyncIterable(this.opts.body) + } + } + + onConnect (abort) { + this.abort = abort + this.handler.onConnect(abort, { history: this.history }) + } + + onUpgrade (statusCode, headers, socket) { + this.handler.onUpgrade(statusCode, headers, socket) + } + + onError (error) { + this.handler.onError(error) + } + + onHeaders (statusCode, headers, resume, statusText) { + this.location = this.history.length >= this.maxRedirections || util.isDisturbed(this.opts.body) + ? null + : parseLocation(statusCode, headers) + + if (this.opts.throwOnMaxRedirect && this.history.length >= this.maxRedirections) { + if (this.request) { + this.request.abort(new Error('max redirects')) + } + + this.redirectionLimitReached = true + this.abort(new Error('max redirects')) + return + } + + if (this.opts.origin) { + this.history.push(new URL(this.opts.path, this.opts.origin)) + } + + if (!this.location) { + return this.handler.onHeaders(statusCode, headers, resume, statusText) + } + + const { origin, pathname, search } = util.parseURL(new URL(this.location, this.opts.origin && new URL(this.opts.path, this.opts.origin))) + const path = search ? `${pathname}${search}` : pathname + + // Remove headers referring to the original URL. + // By default it is Host only, unless it's a 303 (see below), which removes also all Content-* headers. + // https://tools.ietf.org/html/rfc7231#section-6.4 + this.opts.headers = cleanRequestHeaders(this.opts.headers, statusCode === 303, this.opts.origin !== origin) + this.opts.path = path + this.opts.origin = origin + this.opts.maxRedirections = 0 + this.opts.query = null + + // https://tools.ietf.org/html/rfc7231#section-6.4.4 + // In case of HTTP 303, always replace method to be either HEAD or GET + if (statusCode === 303 && this.opts.method !== 'HEAD') { + this.opts.method = 'GET' + this.opts.body = null + } + } + + onData (chunk) { + if (this.location) { + /* + https://tools.ietf.org/html/rfc7231#section-6.4 + + TLDR: undici always ignores 3xx response bodies. + + Redirection is used to serve the requested resource from another URL, so it is assumes that + no body is generated (and thus can be ignored). Even though generating a body is not prohibited. + + For status 301, 302, 303, 307 and 308 (the latter from RFC 7238), the specs mention that the body usually + (which means it's optional and not mandated) contain just an hyperlink to the value of + the Location response header, so the body can be ignored safely. + + For status 300, which is "Multiple Choices", the spec mentions both generating a Location + response header AND a response body with the other possible location to follow. + Since the spec explicitly chooses not to specify a format for such body and leave it to + servers and browsers implementors, we ignore the body as there is no specified way to eventually parse it. + */ + } else { + return this.handler.onData(chunk) + } + } + + onComplete (trailers) { + if (this.location) { + /* + https://tools.ietf.org/html/rfc7231#section-6.4 + + TLDR: undici always ignores 3xx response trailers as they are not expected in case of redirections + and neither are useful if present. + + See comment on onData method above for more detailed information. + */ + + this.location = null + this.abort = null + + this.dispatch(this.opts, this) + } else { + this.handler.onComplete(trailers) + } + } + + onBodySent (chunk) { + if (this.handler.onBodySent) { + this.handler.onBodySent(chunk) + } + } +} + +function parseLocation (statusCode, headers) { + if (redirectableStatusCodes.indexOf(statusCode) === -1) { + return null + } + + for (let i = 0; i < headers.length; i += 2) { + if (headers[i].length === 8 && util.headerNameToString(headers[i]) === 'location') { + return headers[i + 1] + } + } +} + +// https://tools.ietf.org/html/rfc7231#section-6.4.4 +function shouldRemoveHeader (header, removeContent, unknownOrigin) { + if (header.length === 4) { + return util.headerNameToString(header) === 'host' + } + if (removeContent && util.headerNameToString(header).startsWith('content-')) { + return true + } + if (unknownOrigin && (header.length === 13 || header.length === 6 || header.length === 19)) { + const name = util.headerNameToString(header) + return name === 'authorization' || name === 'cookie' || name === 'proxy-authorization' + } + return false +} + +// https://tools.ietf.org/html/rfc7231#section-6.4 +function cleanRequestHeaders (headers, removeContent, unknownOrigin) { + const ret = [] + if (Array.isArray(headers)) { + for (let i = 0; i < headers.length; i += 2) { + if (!shouldRemoveHeader(headers[i], removeContent, unknownOrigin)) { + ret.push(headers[i], headers[i + 1]) + } + } + } else if (headers && typeof headers === 'object') { + for (const key of Object.keys(headers)) { + if (!shouldRemoveHeader(key, removeContent, unknownOrigin)) { + ret.push(key, headers[key]) + } + } + } else { + assert(headers == null, 'headers must be an object or an array') + } + return ret +} + +module.exports = RedirectHandler + + +/***/ }), + +/***/ 7816: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + +const assert = __nccwpck_require__(4589) + +const { kRetryHandlerDefaultRetry } = __nccwpck_require__(6443) +const { RequestRetryError } = __nccwpck_require__(8707) +const { + isDisturbed, + parseHeaders, + parseRangeHeader, + wrapRequestBody +} = __nccwpck_require__(3440) + +function calculateRetryAfterHeader (retryAfter) { + const current = Date.now() + return new Date(retryAfter).getTime() - current +} + +function validatePartialResponseContentLength (headers, range, statusCode, retryCount) { + const contentLength = headers['content-length'] + if (contentLength == null) { + return null + } + + if (!Number.isFinite(range.start) || !Number.isFinite(range.end)) { + return null + } + + const length = Number(contentLength) + const expectedLength = range.end - range.start + 1 + if (!Number.isFinite(length) || length !== expectedLength) { + return new RequestRetryError('Content-Length mismatch', statusCode, { + headers, + data: { count: retryCount } + }) + } + + return null +} + +class RetryHandler { + constructor (opts, handlers) { + const { retryOptions, ...dispatchOpts } = opts + const { + // Retry scoped + retry: retryFn, + maxRetries, + maxTimeout, + minTimeout, + timeoutFactor, + // Response scoped + methods, + errorCodes, + retryAfter, + statusCodes + } = retryOptions ?? {} + + this.dispatch = handlers.dispatch + this.handler = handlers.handler + this.opts = { ...dispatchOpts, body: wrapRequestBody(opts.body) } + this.abort = null + this.aborted = false + this.retryOpts = { + retry: retryFn ?? RetryHandler[kRetryHandlerDefaultRetry], + retryAfter: retryAfter ?? true, + maxTimeout: maxTimeout ?? 30 * 1000, // 30s, + minTimeout: minTimeout ?? 500, // .5s + timeoutFactor: timeoutFactor ?? 2, + maxRetries: maxRetries ?? 5, + // What errors we should retry + methods: methods ?? ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE', 'TRACE'], + // Indicates which errors to retry + statusCodes: statusCodes ?? [500, 502, 503, 504, 429], + // List of errors to retry + errorCodes: errorCodes ?? [ + 'ECONNRESET', + 'ECONNREFUSED', + 'ENOTFOUND', + 'ENETDOWN', + 'ENETUNREACH', + 'EHOSTDOWN', + 'EHOSTUNREACH', + 'EPIPE', + 'UND_ERR_SOCKET' + ] + } + + this.retryCount = 0 + this.retryCountCheckpoint = 0 + this.start = 0 + this.end = null + this.etag = null + this.resume = null + this.headersSent = false + + // Handle possible onConnect duplication + this.handler.onConnect(reason => { + this.aborted = true + if (this.abort) { + this.abort(reason) + } else { + this.reason = reason + } + }) + } + + 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() + } + } + + onUpgrade (statusCode, headers, socket) { + if (this.handler.onUpgrade) { + this.handler.onUpgrade(statusCode, headers, socket) + } + } + + onConnect (abort) { + if (this.aborted) { + abort(this.reason) + } else { + this.abort = abort + } + } + + onBodySent (chunk) { + if (this.handler.onBodySent) return this.handler.onBodySent(chunk) + } + + static [kRetryHandlerDefaultRetry] (err, { state, opts }, cb) { + const { statusCode, code, headers } = err + const { method, retryOptions } = opts + const { + maxRetries, + minTimeout, + maxTimeout, + timeoutFactor, + statusCodes, + errorCodes, + methods + } = retryOptions + const { counter } = state + + // Any code that is not a Undici's originated and allowed to retry + if (code && code !== 'UND_ERR_REQ_RETRY' && !errorCodes.includes(code)) { + cb(err) + return + } + + // If a set of method are provided and the current method is not in the list + if (Array.isArray(methods) && !methods.includes(method)) { + cb(err) + return + } + + // If a set of status code are provided and the current status code is not in the list + if ( + statusCode != null && + Array.isArray(statusCodes) && + !statusCodes.includes(statusCode) + ) { + cb(err) + return + } + + // If we reached the max number of retries + if (counter > maxRetries) { + cb(err) + return + } + + let retryAfterHeader = headers?.['retry-after'] + if (retryAfterHeader) { + retryAfterHeader = Number(retryAfterHeader) + retryAfterHeader = Number.isNaN(retryAfterHeader) + ? calculateRetryAfterHeader(retryAfterHeader) + : retryAfterHeader * 1e3 // Retry-After is in seconds + } + + const retryTimeout = + retryAfterHeader > 0 + ? Math.min(retryAfterHeader, maxTimeout) + : Math.min(minTimeout * timeoutFactor ** (counter - 1), maxTimeout) + + setTimeout(() => cb(null), retryTimeout) + } + + onHeaders (statusCode, rawHeaders, resume, statusMessage) { + const headers = parseHeaders(rawHeaders) + + this.retryCount += 1 + + if (statusCode >= 300) { + // 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, + resume, + statusMessage + ) + } else { + this.abort( + new RequestRetryError('Request failed', statusCode, { + headers, + data: { + count: this.retryCount + } + }) + ) + return false + } + } + + // Checkpoint for resume from where we left it + if (this.resume != null) { + this.resume = null + + // Only Partial Content 206 supposed to provide Content-Range, + // any other status code that partially consumed the payload + // should not be retry because it would result in downstream + // wrongly concatanete multiple responses. + if (statusCode !== 206 && (this.start > 0 || statusCode !== 200)) { + this.abort( + new RequestRetryError('server does not support the range header and the payload was partially consumed', statusCode, { + headers, + data: { count: this.retryCount } + }) + ) + return false + } + + const contentRange = parseRangeHeader(headers['content-range']) + // If no content range + if (!contentRange) { + this.abort( + new RequestRetryError('Content-Range mismatch', statusCode, { + headers, + data: { count: this.retryCount } + }) + ) + return false + } + + // Let's start with a weak etag check + if (this.etag != null && this.etag !== headers.etag) { + this.abort( + new RequestRetryError('ETag mismatch', statusCode, { + headers, + data: { count: this.retryCount } + }) + ) + return false + } + + const contentLengthError = validatePartialResponseContentLength(headers, contentRange, statusCode, this.retryCount) + if (contentLengthError != null) { + this.abort(contentLengthError) + return false + } + + const { start, size, end = size - 1 } = contentRange + + 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 + } + + if (this.end == null) { + if (statusCode === 206) { + // First time we receive 206 + const range = parseRangeHeader(headers['content-range']) + + if (range == null) { + this.headersSent = true + return this.handler.onHeaders( + statusCode, + rawHeaders, + resume, + statusMessage + ) + } + + const contentLengthError = validatePartialResponseContentLength(headers, range, statusCode, this.retryCount) + if (contentLengthError != null) { + this.abort(contentLengthError) + return false + } + + const { start, size, end = size - 1 } = range + assert( + start != null && Number.isFinite(start), + 'content-range mismatch' + ) + assert(end != null && Number.isFinite(end), 'invalid content-length') + + this.start = start + this.end = end + } + + // We make our best to checkpoint the body for further range headers + if (this.end == null) { + const contentLength = headers['content-length'] + this.end = contentLength != null ? Number(contentLength) - 1 : null + } + + assert(Number.isFinite(this.start)) + assert( + this.end == null || Number.isFinite(this.end), + 'invalid content-length' + ) + + this.resume = resume + this.headersSent = true + this.etag = headers.etag != null ? headers.etag : null + + // Weak etags are not useful for comparison nor cache + // for instance not safe to assume if the response is byte-per-byte + // equal + if (this.etag != null && this.etag.startsWith('W/')) { + this.etag = null + } + + return this.handler.onHeaders( + statusCode, + rawHeaders, + resume, + statusMessage + ) + } + + const err = new RequestRetryError('Request failed', statusCode, { + headers, + data: { count: this.retryCount } + }) + + this.abort(err) + + return false + } + + onData (chunk) { + this.start += chunk.length + + return this.handler.onData(chunk) + } + + onComplete (rawTrailers) { + this.retryCount = 0 + return this.handler.onComplete(rawTrailers) + } + + onError (err) { + if (this.aborted || isDisturbed(this.opts.body) || (this.headersSent && this.resume == null)) { + return this.handler.onError(err) + } + + // We reconcile in case of a mix between network errors + // and server error response + if (this.retryCount - this.retryCountCheckpoint > 0) { + // We count the difference between the last checkpoint and the current retry count + this.retryCount = + this.retryCountCheckpoint + + (this.retryCount - this.retryCountCheckpoint) + } else { + this.retryCount += 1 + } + + this.retryOpts.retry( + err, + { + state: { counter: this.retryCount }, + opts: { retryOptions: this.retryOpts, ...this.opts } + }, + onRetry.bind(this) + ) + + function onRetry (err) { + if (err != null || this.aborted || isDisturbed(this.opts.body)) { + return this.handler.onError(err) + } + + if (this.start !== 0) { + const headers = { range: `bytes=${this.start}-${this.end ?? ''}` } + + // Weak etag check - weak etags will make comparison algorithms never match + if (this.etag != null) { + headers['if-match'] = this.etag + } + + this.opts = { + ...this.opts, + headers: { + ...this.opts.headers, + ...headers + } + } + } + + try { + this.retryCountCheckpoint = this.retryCount + this.dispatch(this.opts, this) + } catch (err) { + this.handler.onError(err) + } + } + } +} + +module.exports = RetryHandler + + +/***/ }), + +/***/ 379: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + +const { isIP } = __nccwpck_require__(7030) +const { lookup } = __nccwpck_require__(610) +const DecoratorHandler = __nccwpck_require__(8155) +const { InvalidArgumentError, InformationalError } = __nccwpck_require__(8707) +const maxInt = Math.pow(2, 31) - 1 + +class DNSInstance { + #maxTTL = 0 + #maxItems = 0 + #records = new Map() + dualStack = true + affinity = null + lookup = null + pick = null + + constructor (opts) { + this.#maxTTL = opts.maxTTL + this.#maxItems = opts.maxItems + this.dualStack = opts.dualStack + this.affinity = opts.affinity + this.lookup = opts.lookup ?? this.#defaultLookup + this.pick = opts.pick ?? this.#defaultPick + } + + get full () { + return this.#records.size === this.#maxItems + } + + runLookup (origin, opts, cb) { + const ips = this.#records.get(origin.hostname) + + // If full, we just return the origin + if (ips == null && this.full) { + cb(null, origin.origin) + return + } + + const newOpts = { + affinity: this.affinity, + dualStack: this.dualStack, + lookup: this.lookup, + pick: this.pick, + ...opts.dns, + maxTTL: this.#maxTTL, + maxItems: this.#maxItems + } + + // If no IPs we lookup + if (ips == null) { + this.lookup(origin, newOpts, (err, addresses) => { + if (err || addresses == null || addresses.length === 0) { + cb(err ?? new InformationalError('No DNS entries found')) + return + } + + this.setRecords(origin, addresses) + const records = this.#records.get(origin.hostname) + + const ip = this.pick( + origin, + records, + newOpts.affinity + ) + + let port + if (typeof ip.port === 'number') { + port = `:${ip.port}` + } else if (origin.port !== '') { + port = `:${origin.port}` + } else { + port = '' + } + + cb( + null, + `${origin.protocol}//${ + ip.family === 6 ? `[${ip.address}]` : ip.address + }${port}` + ) + }) + } else { + // If there's IPs we pick + const ip = this.pick( + origin, + ips, + newOpts.affinity + ) + + // If no IPs we lookup - deleting old records + if (ip == null) { + this.#records.delete(origin.hostname) + this.runLookup(origin, opts, cb) + return + } + + let port + if (typeof ip.port === 'number') { + port = `:${ip.port}` + } else if (origin.port !== '') { + port = `:${origin.port}` + } else { + port = '' + } + + cb( + null, + `${origin.protocol}//${ + ip.family === 6 ? `[${ip.address}]` : ip.address + }${port}` + ) + } + } + + #defaultLookup (origin, opts, cb) { + lookup( + origin.hostname, + { + all: true, + family: this.dualStack === false ? this.affinity : 0, + order: 'ipv4first' + }, + (err, addresses) => { + if (err) { + return cb(err) + } + + const results = new Map() + + for (const addr of addresses) { + // On linux we found duplicates, we attempt to remove them with + // the latest record + results.set(`${addr.address}:${addr.family}`, addr) + } + + cb(null, results.values()) + } + ) + } + + #defaultPick (origin, hostnameRecords, affinity) { + let ip = null + const { records, offset } = hostnameRecords + + let family + if (this.dualStack) { + if (affinity == null) { + // Balance between ip families + if (offset == null || offset === maxInt) { + hostnameRecords.offset = 0 + affinity = 4 + } else { + hostnameRecords.offset++ + affinity = (hostnameRecords.offset & 1) === 1 ? 6 : 4 + } + } + + if (records[affinity] != null && records[affinity].ips.length > 0) { + family = records[affinity] + } else { + family = records[affinity === 4 ? 6 : 4] + } + } else { + family = records[affinity] + } + + // If no IPs we return null + if (family == null || family.ips.length === 0) { + return ip + } + + if (family.offset == null || family.offset === maxInt) { + family.offset = 0 + } else { + family.offset++ + } + + const position = family.offset % family.ips.length + ip = family.ips[position] ?? null + + if (ip == null) { + return ip + } + + if (Date.now() - ip.timestamp > ip.ttl) { // record TTL is already in ms + // We delete expired records + // It is possible that they have different TTL, so we manage them individually + family.ips.splice(position, 1) + return this.pick(origin, hostnameRecords, affinity) + } + + return ip + } + + setRecords (origin, addresses) { + const timestamp = Date.now() + const records = { records: { 4: null, 6: null } } + for (const record of addresses) { + record.timestamp = timestamp + if (typeof record.ttl === 'number') { + // The record TTL is expected to be in ms + record.ttl = Math.min(record.ttl, this.#maxTTL) + } else { + record.ttl = this.#maxTTL + } + + const familyRecords = records.records[record.family] ?? { ips: [] } + + familyRecords.ips.push(record) + records.records[record.family] = familyRecords + } + + this.#records.set(origin.hostname, records) + } + + getHandler (meta, opts) { + return new DNSDispatchHandler(this, meta, opts) + } +} + +class DNSDispatchHandler extends DecoratorHandler { + #state = null + #opts = null + #dispatch = null + #handler = null + #origin = null + + constructor (state, { origin, handler, dispatch }, opts) { + super(handler) + this.#origin = origin + this.#handler = handler + this.#opts = { ...opts } + this.#state = state + this.#dispatch = dispatch + } + + onError (err) { + switch (err.code) { + case 'ETIMEDOUT': + case 'ECONNREFUSED': { + if (this.#state.dualStack) { + // We delete the record and retry + this.#state.runLookup(this.#origin, this.#opts, (err, newOrigin) => { + if (err) { + return this.#handler.onError(err) + } + + const dispatchOpts = { + ...this.#opts, + origin: newOrigin + } + + this.#dispatch(dispatchOpts, this) + }) + + // if dual-stack disabled, we error out + return + } + + this.#handler.onError(err) + return + } + case 'ENOTFOUND': + this.#state.deleteRecord(this.#origin) + // eslint-disable-next-line no-fallthrough + default: + this.#handler.onError(err) + break + } + } +} + +module.exports = interceptorOpts => { + if ( + interceptorOpts?.maxTTL != null && + (typeof interceptorOpts?.maxTTL !== 'number' || interceptorOpts?.maxTTL < 0) + ) { + throw new InvalidArgumentError('Invalid maxTTL. Must be a positive number') + } + + if ( + interceptorOpts?.maxItems != null && + (typeof interceptorOpts?.maxItems !== 'number' || + interceptorOpts?.maxItems < 1) + ) { + throw new InvalidArgumentError( + 'Invalid maxItems. Must be a positive number and greater than zero' + ) + } + + if ( + interceptorOpts?.affinity != null && + interceptorOpts?.affinity !== 4 && + interceptorOpts?.affinity !== 6 + ) { + throw new InvalidArgumentError('Invalid affinity. Must be either 4 or 6') + } + + if ( + interceptorOpts?.dualStack != null && + typeof interceptorOpts?.dualStack !== 'boolean' + ) { + throw new InvalidArgumentError('Invalid dualStack. Must be a boolean') + } + + if ( + interceptorOpts?.lookup != null && + typeof interceptorOpts?.lookup !== 'function' + ) { + throw new InvalidArgumentError('Invalid lookup. Must be a function') + } + + if ( + interceptorOpts?.pick != null && + typeof interceptorOpts?.pick !== 'function' + ) { + throw new InvalidArgumentError('Invalid pick. Must be a function') + } + + const dualStack = interceptorOpts?.dualStack ?? true + let affinity + if (dualStack) { + affinity = interceptorOpts?.affinity ?? null + } else { + affinity = interceptorOpts?.affinity ?? 4 + } + + const opts = { + maxTTL: interceptorOpts?.maxTTL ?? 10e3, // Expressed in ms + lookup: interceptorOpts?.lookup ?? null, + pick: interceptorOpts?.pick ?? null, + dualStack, + affinity, + maxItems: interceptorOpts?.maxItems ?? Infinity + } + + const instance = new DNSInstance(opts) + + return dispatch => { + return function dnsInterceptor (origDispatchOpts, handler) { + const origin = + origDispatchOpts.origin.constructor === URL + ? origDispatchOpts.origin + : new URL(origDispatchOpts.origin) + + if (isIP(origin.hostname) !== 0) { + return dispatch(origDispatchOpts, handler) + } + + instance.runLookup(origin, origDispatchOpts, (err, newOrigin) => { + if (err) { + return handler.onError(err) + } + + let dispatchOpts = null + dispatchOpts = { + ...origDispatchOpts, + servername: origin.hostname, // For SNI on TLS + origin: newOrigin, + headers: { + host: origin.hostname, + ...origDispatchOpts.headers + } + } + + dispatch( + dispatchOpts, + instance.getHandler({ origin, dispatch, handler }, origDispatchOpts) + ) + }) + + return true + } + } +} + + +/***/ }), + +/***/ 8060: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const util = __nccwpck_require__(3440) +const { InvalidArgumentError, RequestAbortedError } = __nccwpck_require__(8707) +const DecoratorHandler = __nccwpck_require__(8155) + +class DumpHandler extends DecoratorHandler { + #maxSize = 1024 * 1024 + #abort = null + #dumped = false + #aborted = false + #size = 0 + #reason = null + #handler = null + + constructor ({ maxSize }, handler) { + super(handler) + + if (maxSize != null && (!Number.isFinite(maxSize) || maxSize < 1)) { + throw new InvalidArgumentError('maxSize must be a number greater than 0') + } + + this.#maxSize = maxSize ?? this.#maxSize + this.#handler = handler + } + + onConnect (abort) { + this.#abort = abort + + this.#handler.onConnect(this.#customAbort.bind(this)) + } + + #customAbort (reason) { + this.#aborted = true + this.#reason = reason + } + + // TODO: will require adjustment after new hooks are out + onHeaders (statusCode, rawHeaders, resume, statusMessage) { + const headers = util.parseHeaders(rawHeaders) + const contentLength = headers['content-length'] + + if (contentLength != null && contentLength > this.#maxSize) { + throw new RequestAbortedError( + `Response size (${contentLength}) larger than maxSize (${ + this.#maxSize + })` + ) + } + + if (this.#aborted) { + return true + } + + return this.#handler.onHeaders( + statusCode, + rawHeaders, + resume, + statusMessage + ) + } + + onError (err) { + if (this.#dumped) { + return + } + + err = this.#reason ?? err + + this.#handler.onError(err) + } + + onData (chunk) { + this.#size = this.#size + chunk.length + + if (this.#size >= this.#maxSize) { + this.#dumped = true + + if (this.#aborted) { + this.#handler.onError(this.#reason) + } else { + this.#handler.onComplete([]) + } + } + + return true + } + + onComplete (trailers) { + if (this.#dumped) { + return + } + + if (this.#aborted) { + this.#handler.onError(this.reason) + return + } + + this.#handler.onComplete(trailers) + } +} + +function createDumpInterceptor ( + { maxSize: defaultMaxSize } = { + maxSize: 1024 * 1024 + } +) { + return dispatch => { + return function Intercept (opts, handler) { + const { dumpMaxSize = defaultMaxSize } = + opts + + const dumpHandler = new DumpHandler( + { maxSize: dumpMaxSize }, + handler + ) + + return dispatch(opts, dumpHandler) + } + } +} + +module.exports = createDumpInterceptor + + +/***/ }), + +/***/ 5092: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const RedirectHandler = __nccwpck_require__(8754) + +function createRedirectInterceptor ({ maxRedirections: defaultMaxRedirections }) { + return (dispatch) => { + return function Intercept (opts, handler) { + const { maxRedirections = defaultMaxRedirections } = opts + + if (!maxRedirections) { + return dispatch(opts, handler) + } + + const redirectHandler = new RedirectHandler(dispatch, maxRedirections, opts, handler) + opts = { ...opts, maxRedirections: 0 } // Stop sub dispatcher from also redirecting. + return dispatch(opts, redirectHandler) + } + } +} + +module.exports = createRedirectInterceptor + + +/***/ }), + +/***/ 1514: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + +const RedirectHandler = __nccwpck_require__(8754) + +module.exports = opts => { + const globalMaxRedirections = opts?.maxRedirections + return dispatch => { + return function redirectInterceptor (opts, handler) { + const { maxRedirections = globalMaxRedirections, ...baseOpts } = opts + + if (!maxRedirections) { + return dispatch(opts, handler) + } + + const redirectHandler = new RedirectHandler( + dispatch, + maxRedirections, + opts, + handler + ) + + return dispatch(baseOpts, redirectHandler) + } + } +} + + +/***/ }), + +/***/ 2026: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + +const RetryHandler = __nccwpck_require__(7816) + +module.exports = globalOpts => { + return dispatch => { + return function retryInterceptor (opts, handler) { + return dispatch( + opts, + new RetryHandler( + { ...opts, retryOptions: { ...globalOpts, ...opts.retryOptions } }, + { + handler, + dispatch + } + ) + ) + } + } +} + + +/***/ }), + +/***/ 2824: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.SPECIAL_HEADERS = exports.HEADER_STATE = exports.MINOR = exports.MAJOR = exports.CONNECTION_TOKEN_CHARS = exports.HEADER_CHARS = exports.TOKEN = exports.STRICT_TOKEN = exports.HEX = exports.URL_CHAR = exports.STRICT_URL_CHAR = exports.USERINFO_CHARS = exports.MARK = exports.ALPHANUM = exports.NUM = exports.HEX_MAP = exports.NUM_MAP = exports.ALPHA = exports.FINISH = exports.H_METHOD_MAP = exports.METHOD_MAP = exports.METHODS_RTSP = exports.METHODS_ICE = exports.METHODS_HTTP = exports.METHODS = exports.LENIENT_FLAGS = exports.FLAGS = exports.TYPE = exports.ERROR = void 0; +const utils_1 = __nccwpck_require__(172); +// C headers +var ERROR; +(function (ERROR) { + ERROR[ERROR["OK"] = 0] = "OK"; + ERROR[ERROR["INTERNAL"] = 1] = "INTERNAL"; + ERROR[ERROR["STRICT"] = 2] = "STRICT"; + ERROR[ERROR["LF_EXPECTED"] = 3] = "LF_EXPECTED"; + ERROR[ERROR["UNEXPECTED_CONTENT_LENGTH"] = 4] = "UNEXPECTED_CONTENT_LENGTH"; + ERROR[ERROR["CLOSED_CONNECTION"] = 5] = "CLOSED_CONNECTION"; + ERROR[ERROR["INVALID_METHOD"] = 6] = "INVALID_METHOD"; + ERROR[ERROR["INVALID_URL"] = 7] = "INVALID_URL"; + ERROR[ERROR["INVALID_CONSTANT"] = 8] = "INVALID_CONSTANT"; + ERROR[ERROR["INVALID_VERSION"] = 9] = "INVALID_VERSION"; + ERROR[ERROR["INVALID_HEADER_TOKEN"] = 10] = "INVALID_HEADER_TOKEN"; + ERROR[ERROR["INVALID_CONTENT_LENGTH"] = 11] = "INVALID_CONTENT_LENGTH"; + ERROR[ERROR["INVALID_CHUNK_SIZE"] = 12] = "INVALID_CHUNK_SIZE"; + ERROR[ERROR["INVALID_STATUS"] = 13] = "INVALID_STATUS"; + ERROR[ERROR["INVALID_EOF_STATE"] = 14] = "INVALID_EOF_STATE"; + ERROR[ERROR["INVALID_TRANSFER_ENCODING"] = 15] = "INVALID_TRANSFER_ENCODING"; + ERROR[ERROR["CB_MESSAGE_BEGIN"] = 16] = "CB_MESSAGE_BEGIN"; + ERROR[ERROR["CB_HEADERS_COMPLETE"] = 17] = "CB_HEADERS_COMPLETE"; + ERROR[ERROR["CB_MESSAGE_COMPLETE"] = 18] = "CB_MESSAGE_COMPLETE"; + ERROR[ERROR["CB_CHUNK_HEADER"] = 19] = "CB_CHUNK_HEADER"; + ERROR[ERROR["CB_CHUNK_COMPLETE"] = 20] = "CB_CHUNK_COMPLETE"; + ERROR[ERROR["PAUSED"] = 21] = "PAUSED"; + ERROR[ERROR["PAUSED_UPGRADE"] = 22] = "PAUSED_UPGRADE"; + ERROR[ERROR["PAUSED_H2_UPGRADE"] = 23] = "PAUSED_H2_UPGRADE"; + ERROR[ERROR["USER"] = 24] = "USER"; +})(ERROR = exports.ERROR || (exports.ERROR = {})); +var TYPE; +(function (TYPE) { + TYPE[TYPE["BOTH"] = 0] = "BOTH"; + TYPE[TYPE["REQUEST"] = 1] = "REQUEST"; + TYPE[TYPE["RESPONSE"] = 2] = "RESPONSE"; +})(TYPE = exports.TYPE || (exports.TYPE = {})); +var FLAGS; +(function (FLAGS) { + FLAGS[FLAGS["CONNECTION_KEEP_ALIVE"] = 1] = "CONNECTION_KEEP_ALIVE"; + FLAGS[FLAGS["CONNECTION_CLOSE"] = 2] = "CONNECTION_CLOSE"; + FLAGS[FLAGS["CONNECTION_UPGRADE"] = 4] = "CONNECTION_UPGRADE"; + FLAGS[FLAGS["CHUNKED"] = 8] = "CHUNKED"; + FLAGS[FLAGS["UPGRADE"] = 16] = "UPGRADE"; + FLAGS[FLAGS["CONTENT_LENGTH"] = 32] = "CONTENT_LENGTH"; + FLAGS[FLAGS["SKIPBODY"] = 64] = "SKIPBODY"; + FLAGS[FLAGS["TRAILING"] = 128] = "TRAILING"; + // 1 << 8 is unused + FLAGS[FLAGS["TRANSFER_ENCODING"] = 512] = "TRANSFER_ENCODING"; +})(FLAGS = exports.FLAGS || (exports.FLAGS = {})); +var LENIENT_FLAGS; +(function (LENIENT_FLAGS) { + LENIENT_FLAGS[LENIENT_FLAGS["HEADERS"] = 1] = "HEADERS"; + LENIENT_FLAGS[LENIENT_FLAGS["CHUNKED_LENGTH"] = 2] = "CHUNKED_LENGTH"; + LENIENT_FLAGS[LENIENT_FLAGS["KEEP_ALIVE"] = 4] = "KEEP_ALIVE"; +})(LENIENT_FLAGS = exports.LENIENT_FLAGS || (exports.LENIENT_FLAGS = {})); +var METHODS; +(function (METHODS) { + METHODS[METHODS["DELETE"] = 0] = "DELETE"; + METHODS[METHODS["GET"] = 1] = "GET"; + METHODS[METHODS["HEAD"] = 2] = "HEAD"; + METHODS[METHODS["POST"] = 3] = "POST"; + METHODS[METHODS["PUT"] = 4] = "PUT"; + /* pathological */ + METHODS[METHODS["CONNECT"] = 5] = "CONNECT"; + METHODS[METHODS["OPTIONS"] = 6] = "OPTIONS"; + METHODS[METHODS["TRACE"] = 7] = "TRACE"; + /* WebDAV */ + METHODS[METHODS["COPY"] = 8] = "COPY"; + METHODS[METHODS["LOCK"] = 9] = "LOCK"; + METHODS[METHODS["MKCOL"] = 10] = "MKCOL"; + METHODS[METHODS["MOVE"] = 11] = "MOVE"; + METHODS[METHODS["PROPFIND"] = 12] = "PROPFIND"; + METHODS[METHODS["PROPPATCH"] = 13] = "PROPPATCH"; + METHODS[METHODS["SEARCH"] = 14] = "SEARCH"; + METHODS[METHODS["UNLOCK"] = 15] = "UNLOCK"; + METHODS[METHODS["BIND"] = 16] = "BIND"; + METHODS[METHODS["REBIND"] = 17] = "REBIND"; + METHODS[METHODS["UNBIND"] = 18] = "UNBIND"; + METHODS[METHODS["ACL"] = 19] = "ACL"; + /* subversion */ + METHODS[METHODS["REPORT"] = 20] = "REPORT"; + METHODS[METHODS["MKACTIVITY"] = 21] = "MKACTIVITY"; + METHODS[METHODS["CHECKOUT"] = 22] = "CHECKOUT"; + METHODS[METHODS["MERGE"] = 23] = "MERGE"; + /* upnp */ + METHODS[METHODS["M-SEARCH"] = 24] = "M-SEARCH"; + METHODS[METHODS["NOTIFY"] = 25] = "NOTIFY"; + METHODS[METHODS["SUBSCRIBE"] = 26] = "SUBSCRIBE"; + METHODS[METHODS["UNSUBSCRIBE"] = 27] = "UNSUBSCRIBE"; + /* RFC-5789 */ + METHODS[METHODS["PATCH"] = 28] = "PATCH"; + METHODS[METHODS["PURGE"] = 29] = "PURGE"; + /* CalDAV */ + METHODS[METHODS["MKCALENDAR"] = 30] = "MKCALENDAR"; + /* RFC-2068, section 19.6.1.2 */ + METHODS[METHODS["LINK"] = 31] = "LINK"; + METHODS[METHODS["UNLINK"] = 32] = "UNLINK"; + /* icecast */ + METHODS[METHODS["SOURCE"] = 33] = "SOURCE"; + /* RFC-7540, section 11.6 */ + METHODS[METHODS["PRI"] = 34] = "PRI"; + /* RFC-2326 RTSP */ + METHODS[METHODS["DESCRIBE"] = 35] = "DESCRIBE"; + METHODS[METHODS["ANNOUNCE"] = 36] = "ANNOUNCE"; + METHODS[METHODS["SETUP"] = 37] = "SETUP"; + METHODS[METHODS["PLAY"] = 38] = "PLAY"; + METHODS[METHODS["PAUSE"] = 39] = "PAUSE"; + METHODS[METHODS["TEARDOWN"] = 40] = "TEARDOWN"; + METHODS[METHODS["GET_PARAMETER"] = 41] = "GET_PARAMETER"; + METHODS[METHODS["SET_PARAMETER"] = 42] = "SET_PARAMETER"; + METHODS[METHODS["REDIRECT"] = 43] = "REDIRECT"; + METHODS[METHODS["RECORD"] = 44] = "RECORD"; + /* RAOP */ + METHODS[METHODS["FLUSH"] = 45] = "FLUSH"; +})(METHODS = exports.METHODS || (exports.METHODS = {})); +exports.METHODS_HTTP = [ + METHODS.DELETE, + METHODS.GET, + METHODS.HEAD, + METHODS.POST, + METHODS.PUT, + METHODS.CONNECT, + METHODS.OPTIONS, + METHODS.TRACE, + METHODS.COPY, + METHODS.LOCK, + METHODS.MKCOL, + METHODS.MOVE, + METHODS.PROPFIND, + METHODS.PROPPATCH, + METHODS.SEARCH, + METHODS.UNLOCK, + METHODS.BIND, + METHODS.REBIND, + METHODS.UNBIND, + METHODS.ACL, + METHODS.REPORT, + METHODS.MKACTIVITY, + METHODS.CHECKOUT, + METHODS.MERGE, + METHODS['M-SEARCH'], + METHODS.NOTIFY, + METHODS.SUBSCRIBE, + METHODS.UNSUBSCRIBE, + METHODS.PATCH, + METHODS.PURGE, + METHODS.MKCALENDAR, + METHODS.LINK, + METHODS.UNLINK, + METHODS.PRI, + // TODO(indutny): should we allow it with HTTP? + METHODS.SOURCE, +]; +exports.METHODS_ICE = [ + METHODS.SOURCE, +]; +exports.METHODS_RTSP = [ + METHODS.OPTIONS, + METHODS.DESCRIBE, + METHODS.ANNOUNCE, + METHODS.SETUP, + METHODS.PLAY, + METHODS.PAUSE, + METHODS.TEARDOWN, + METHODS.GET_PARAMETER, + METHODS.SET_PARAMETER, + METHODS.REDIRECT, + METHODS.RECORD, + METHODS.FLUSH, + // For AirPlay + METHODS.GET, + METHODS.POST, +]; +exports.METHOD_MAP = utils_1.enumToMap(METHODS); +exports.H_METHOD_MAP = {}; +Object.keys(exports.METHOD_MAP).forEach((key) => { + if (/^H/.test(key)) { + exports.H_METHOD_MAP[key] = exports.METHOD_MAP[key]; + } +}); +var FINISH; +(function (FINISH) { + FINISH[FINISH["SAFE"] = 0] = "SAFE"; + FINISH[FINISH["SAFE_WITH_CB"] = 1] = "SAFE_WITH_CB"; + FINISH[FINISH["UNSAFE"] = 2] = "UNSAFE"; +})(FINISH = exports.FINISH || (exports.FINISH = {})); +exports.ALPHA = []; +for (let i = 'A'.charCodeAt(0); i <= 'Z'.charCodeAt(0); i++) { + // Upper case + exports.ALPHA.push(String.fromCharCode(i)); + // Lower case + exports.ALPHA.push(String.fromCharCode(i + 0x20)); +} +exports.NUM_MAP = { + 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, + 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, +}; +exports.HEX_MAP = { + 0: 0, 1: 1, 2: 2, 3: 3, 4: 4, + 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, + A: 0XA, B: 0XB, C: 0XC, D: 0XD, E: 0XE, F: 0XF, + a: 0xa, b: 0xb, c: 0xc, d: 0xd, e: 0xe, f: 0xf, +}; +exports.NUM = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', +]; +exports.ALPHANUM = exports.ALPHA.concat(exports.NUM); +exports.MARK = ['-', '_', '.', '!', '~', '*', '\'', '(', ')']; +exports.USERINFO_CHARS = exports.ALPHANUM + .concat(exports.MARK) + .concat(['%', ';', ':', '&', '=', '+', '$', ',']); +// TODO(indutny): use RFC +exports.STRICT_URL_CHAR = [ + '!', '"', '$', '%', '&', '\'', + '(', ')', '*', '+', ',', '-', '.', '/', + ':', ';', '<', '=', '>', + '@', '[', '\\', ']', '^', '_', + '`', + '{', '|', '}', '~', +].concat(exports.ALPHANUM); +exports.URL_CHAR = exports.STRICT_URL_CHAR + .concat(['\t', '\f']); +// All characters with 0x80 bit set to 1 +for (let i = 0x80; i <= 0xff; i++) { + exports.URL_CHAR.push(i); +} +exports.HEX = exports.NUM.concat(['a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F']); +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * separators = "(" | ")" | "<" | ">" | "@" + * | "," | ";" | ":" | "\" | <"> + * | "/" | "[" | "]" | "?" | "=" + * | "{" | "}" | SP | HT + */ +exports.STRICT_TOKEN = [ + '!', '#', '$', '%', '&', '\'', + '*', '+', '-', '.', + '^', '_', '`', + '|', '~', +].concat(exports.ALPHANUM); +exports.TOKEN = exports.STRICT_TOKEN.concat([' ']); +/* + * Verify that a char is a valid visible (printable) US-ASCII + * character or %x80-FF + */ +exports.HEADER_CHARS = ['\t']; +for (let i = 32; i <= 255; i++) { + if (i !== 127) { + exports.HEADER_CHARS.push(i); + } +} +// ',' = \x44 +exports.CONNECTION_TOKEN_CHARS = exports.HEADER_CHARS.filter((c) => c !== 44); +exports.MAJOR = exports.NUM_MAP; +exports.MINOR = exports.MAJOR; +var HEADER_STATE; +(function (HEADER_STATE) { + HEADER_STATE[HEADER_STATE["GENERAL"] = 0] = "GENERAL"; + HEADER_STATE[HEADER_STATE["CONNECTION"] = 1] = "CONNECTION"; + HEADER_STATE[HEADER_STATE["CONTENT_LENGTH"] = 2] = "CONTENT_LENGTH"; + HEADER_STATE[HEADER_STATE["TRANSFER_ENCODING"] = 3] = "TRANSFER_ENCODING"; + HEADER_STATE[HEADER_STATE["UPGRADE"] = 4] = "UPGRADE"; + HEADER_STATE[HEADER_STATE["CONNECTION_KEEP_ALIVE"] = 5] = "CONNECTION_KEEP_ALIVE"; + HEADER_STATE[HEADER_STATE["CONNECTION_CLOSE"] = 6] = "CONNECTION_CLOSE"; + HEADER_STATE[HEADER_STATE["CONNECTION_UPGRADE"] = 7] = "CONNECTION_UPGRADE"; + HEADER_STATE[HEADER_STATE["TRANSFER_ENCODING_CHUNKED"] = 8] = "TRANSFER_ENCODING_CHUNKED"; +})(HEADER_STATE = exports.HEADER_STATE || (exports.HEADER_STATE = {})); +exports.SPECIAL_HEADERS = { + 'connection': HEADER_STATE.CONNECTION, + 'content-length': HEADER_STATE.CONTENT_LENGTH, + 'proxy-connection': HEADER_STATE.CONNECTION, + 'transfer-encoding': HEADER_STATE.TRANSFER_ENCODING, + 'upgrade': HEADER_STATE.UPGRADE, +}; +//# sourceMappingURL=constants.js.map + +/***/ }), + +/***/ 3870: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { Buffer } = __nccwpck_require__(4573) + +module.exports = Buffer.from('AGFzbQEAAAABJwdgAX8Bf2ADf39/AX9gAX8AYAJ/fwBgBH9/f38Bf2AAAGADf39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQAEA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAAy0sBQYAAAIAAAAAAAACAQIAAgICAAADAAAAAAMDAwMBAQEBAQEBAQEAAAIAAAAEBQFwARISBQMBAAIGCAF/AUGA1AQLB9EFIgZtZW1vcnkCAAtfaW5pdGlhbGl6ZQAIGV9faW5kaXJlY3RfZnVuY3Rpb25fdGFibGUBAAtsbGh0dHBfaW5pdAAJGGxsaHR0cF9zaG91bGRfa2VlcF9hbGl2ZQAvDGxsaHR0cF9hbGxvYwALBm1hbGxvYwAxC2xsaHR0cF9mcmVlAAwEZnJlZQAMD2xsaHR0cF9nZXRfdHlwZQANFWxsaHR0cF9nZXRfaHR0cF9tYWpvcgAOFWxsaHR0cF9nZXRfaHR0cF9taW5vcgAPEWxsaHR0cF9nZXRfbWV0aG9kABAWbGxodHRwX2dldF9zdGF0dXNfY29kZQAREmxsaHR0cF9nZXRfdXBncmFkZQASDGxsaHR0cF9yZXNldAATDmxsaHR0cF9leGVjdXRlABQUbGxodHRwX3NldHRpbmdzX2luaXQAFQ1sbGh0dHBfZmluaXNoABYMbGxodHRwX3BhdXNlABcNbGxodHRwX3Jlc3VtZQAYG2xsaHR0cF9yZXN1bWVfYWZ0ZXJfdXBncmFkZQAZEGxsaHR0cF9nZXRfZXJybm8AGhdsbGh0dHBfZ2V0X2Vycm9yX3JlYXNvbgAbF2xsaHR0cF9zZXRfZXJyb3JfcmVhc29uABwUbGxodHRwX2dldF9lcnJvcl9wb3MAHRFsbGh0dHBfZXJybm9fbmFtZQAeEmxsaHR0cF9tZXRob2RfbmFtZQAfEmxsaHR0cF9zdGF0dXNfbmFtZQAgGmxsaHR0cF9zZXRfbGVu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PgtBhwEMPQtBiAEMPAtBiQEMOwtBigEMOgtBiwEMOQtBjAEMOAtBjQEMNwtBjgEMNgtBjwEMNQtBkAEMNAtBkQEMMwtBkgEMMgtBkwEMMQtBlAEMMAtBlQEMLwtBlgEMLgtBlwEMLQtBmAEMLAtBmQEMKwtBmgEMKgtBmwEMKQtBnAEMKAtBnQEMJwtBngEMJgtBnwEMJQtBoAEMJAtBoQEMIwtBogEMIgtBowEMIQtBpAEMIAtBpQEMHwtBpgEMHgtBpwEMHQtBqAEMHAtBqQEMGwtBqgEMGgtBqwEMGQtBrAEMGAtBrQEMFwtBrgEMFgtBAQwVC0GvAQwUC0GwAQwTC0GxAQwSC0GzAQwRC0GyAQwQC0G0AQwPC0G1AQwOC0G2AQwNC0G3AQwMC0G4AQwLC0G5AQwKC0G6AQwJC0G7AQwIC0HGAQwHC0G8AQwGC0G9AQwFC0G+AQwEC0G/AQwDC0HAAQwCC0HCAQwBC0HBAQshAwNAAkACQAJAAkACQAJAAkACQAJAIAICfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAgJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACfwJAAkACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAC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iQEhAwykAgtBASABLQAAQd8ARw1nGiABQQFqIQEMXgtBigEhAyABIARGDaICIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgNAIAEtAAAgAEGMzwBqLQAARw1kIABBAUYN+gEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMogILQYsBIQMgASAERg2hAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGOzwBqLQAARw1kIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyiAgsgAkEANgIAIAZBAWohAUECDGULQYwBIQMgASAERg2gAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHwzwBqLQAARw1jIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyhAgsgAkEANgIAIAZBAWohAUEfDGQLQY0BIQMgASAERg2fAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHyzwBqLQAARw1iIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAygAgsgAkEANgIAIAZBAWohAUEJDGMLIAEgBEYEQEGOASEDDJ8CCwJAAkAgAS0AAEHJAGsOBwBiYmJiYgFiCyABQQFqIQFB+AAhAwyGAgsgAUEBaiEBQfkAIQMMhQILQY8BIQMgASAERg2dAiACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEGRzwBqLQAARw1gIABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyeAgsgAkEANgIAIAZBAWohAUEYDGELQZABIQMgASAERg2cAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGXzwBqLQAARw1fIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAydAgsgAkEANgIAIAZBAWohAUEXDGALQZEBIQMgASAERg2bAiACKAIAIgAgBCABa2ohBSABIABrQQZqIQYCQANAIAEtAAAgAEGazwBqLQAARw1eIABBBkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAycAgsgAkEANgIAIAZBAWohAUEVDF8LQZIBIQMgASAERg2aAiACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEGhzwBqLQAARw1dIABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAybAgsgAkEANgIAIAZBAWohAUEeDF4LIAEgBEYEQEGTASEDDJoCCyABLQAAQcwARw1bIAFBAWohAUEKDF0LIAEgBEYEQEGUASEDDJkCCwJAAkAgAS0AAEHBAGsODwBcXFxcXFxcXFxcXFxcAVwLIAFBAWohAUH+ACEDDIACCyABQQFqIQFB/wAhAwz/AQsgASAERgRAQZUBIQMMmAILAkACQCABLQAAQcEAaw4DAFsBWwsgAUEBaiEBQf0AIQMM/wELIAFBAWohAUGAASEDDP4BC0GWASEDIAEgBEYNlgIgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABBp88Aai0AAEcNWSAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMlwILIAJBADYCACAGQQFqIQFBCwxaCyABIARGBEBBlwEhAwyWAgsCQAJAAkACQCABLQAAQS1rDiMAW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1sBW1tbW1sCW1tbA1sLIAFBAWohAUH7ACEDDP8BCyABQQFqIQFB/AAhAwz+AQsgAUEBaiEBQYEBIQMM/QELIAFBAWohAUGCASEDDPwBC0GYASEDIAEgBEYNlAIgAigCACIAIAQgAWtqIQUgASAAa0EEaiEGAkADQCABLQAAIABBqc8Aai0AAEcNVyAAQQRGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMlQILIAJBADYCACAGQQFqIQFBGQxYC0GZASEDIAEgBEYNkwIgAigCACIAIAQgAWtqIQUgASAAa0EFaiEGAkADQCABLQAAIABBrs8Aai0AAEcNViAAQQVGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMlAILIAJBADYCACAGQQFqIQFBBgxXC0GaASEDIAEgBEYNkgIgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABBtM8Aai0AAEcNVSAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMkwILIAJBADYCACAGQQFqIQFBHAxWC0GbASEDIAEgBEYNkQIgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABBts8Aai0AAEcNVCAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMkgILIAJBADYCACAGQQFqIQFBJwxVCyABIARGBEBBnAEhAwyRAgsCQAJAIAEtAABB1ABrDgIAAVQLIAFBAWohAUGGASEDDPgBCyABQQFqIQFBhwEhAwz3AQtBnQEhAyABIARGDY8CIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgJAA0AgAS0AACAAQbjPAGotAABHDVIgAEEBRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADJACCyACQQA2AgAgBkEBaiEBQSYMUwtBngEhAyABIARGDY4CIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgJAA0AgAS0AACAAQbrPAGotAABHDVEgAEEBRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADI8CCyACQQA2AgAgBkEBaiEBQQMMUgtBnwEhAyABIARGDY0CIAIoAgAiACAEIAFraiEFIAEgAGtBAmohBgJAA0AgAS0AACAAQe3PAGotAABHDVAgAEECRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADI4CCyACQQA2AgAgBkEBaiEBQQwMUQtBoAEhAyABIARGDYwCIAIoAgAiACAEIAFraiEFIAEgAGtBA2ohBgJAA0AgAS0AACAAQbzPAGotAABHDU8gAEEDRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADI0CCyACQQA2AgAgBkEBaiEBQQ0MUAsgASAERgRAQaEBIQMMjAILAkACQCABLQAAQcYAaw4LAE9PT09PT09PTwFPCyABQQFqIQFBiwEhAwzzAQsgAUEBaiEBQYwBIQMM8gELIAEgBEYEQEGiASEDDIsCCyABLQAAQdAARw1MIAFBAWohAQxGCyABIARGBEBBowEhAwyKAgsCQAJAIAEtAABByQBrDgcBTU1NTU0ATQsgAUEBaiEBQY4BIQMM8QELIAFBAWohAUEiDE0LQaQBIQMgASAERg2IAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHAzwBqLQAARw1LIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyJAgsgAkEANgIAIAZBAWohAUEdDEwLIAEgBEYEQEGlASEDDIgCCwJAAkAgAS0AAEHSAGsOAwBLAUsLIAFBAWohAUGQASEDDO8BCyABQQFqIQFBBAxLCyABIARGBEBBpgEhAwyHAgsCQAJAAkACQAJAIAEtAABBwQBrDhUATU1NTU1NTU1NTQFNTQJNTQNNTQRNCyABQQFqIQFBiAEhAwzxAQsgAUEBaiEBQYkBIQMM8AELIAFBAWohAUGKASEDDO8BCyABQQFqIQFBjwEhAwzuAQsgAUEBaiEBQZEBIQMM7QELQacBIQMgASAERg2FAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHtzwBqLQAARw1IIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyGAgsgAkEANgIAIAZBAWohAUERDEkLQagBIQMgASAERg2EAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHCzwBqLQAARw1HIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyFAgsgAkEANgIAIAZBAWohAUEsDEgLQakBIQMgASAERg2DAiACKAIAIgAgBCABa2ohBSABIABrQQRqIQYCQANAIAEtAAAgAEHFzwBqLQAARw1GIABBBEYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyEAgsgAkEANgIAIAZBAWohAUErDEcLQaoBIQMgASAERg2CAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHKzwBqLQAARw1FIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyDAgsgAkEANgIAIAZBAWohAUEUDEYLIAEgBEYEQEGrASEDDIICCwJAAkACQAJAIAEtAABBwgBrDg8AAQJHR0dHR0dHR0dHRwNHCyABQQFqIQFBkwEhAwzrAQsgAUEBaiEBQZQBIQMM6gELIAFBAWohAUGVASEDDOkBCyABQQFqIQFBlgEhAwzoAQsgASAERgRAQawBIQMMgQILIAEtAABBxQBHDUIgAUEBaiEBDD0LQa0BIQMgASAERg3/ASACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHNzwBqLQAARw1CIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyAAgsgAkEANgIAIAZBAWohAUEODEMLIAEgBEYEQEGuASEDDP8BCyABLQAAQdAARw1AIAFBAWohAUElDEILQa8BIQMgASAERg39ASACKAIAIgAgBCABa2ohBSABIABrQQhqIQYCQANAIAEtAAAgAEHQzwBqLQAARw1AIABBCEYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAz+AQsgAkEANgIAIAZBAWohAUEqDEELIAEgBEYEQEGwASEDDP0BCwJAAkAgAS0AAEHVAGsOCwBAQEBAQEBAQEABQAsgAUEBaiEBQZoBIQMM5AELIAFBAWohAUGbASEDDOMBCyABIARGBEBBsQEhAwz8AQsCQAJAIAEtAABBwQBrDhQAPz8/Pz8/Pz8/Pz8/Pz8/Pz8/AT8LIAFBAWohAUGZASEDDOMBCyABQQFqIQFBnAEhAwziAQtBsgEhAyABIARGDfoBIAIoAgAiACAEIAFraiEFIAEgAGtBA2ohBgJAA0AgAS0AACAAQdnPAGotAABHDT0gAEEDRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADPsBCyACQQA2AgAgBkEBaiEBQSEMPgtBswEhAyABIARGDfkBIAIoAgAiACAEIAFraiEFIAEgAGtBBmohBgJAA0AgAS0AACAAQd3PAGotAABHDTwgAEEGRg0BIABBAWohACAEIAFBAWoiAUcNAAsgAiAFNgIADPoBCyACQQA2AgAgBkEBaiEBQRoMPQsgASAERgRAQbQBIQMM+QELAkACQAJAIAEtAABBxQBrDhEAPT09PT09PT09AT09PT09Aj0LIAFBAWohAUGdASEDDOEBCyABQQFqIQFBngEhAwzgAQsgAUEBaiEBQZ8BIQMM3wELQbUBIQMgASAERg33ASACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEHkzwBqLQAARw06IABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAz4AQsgAkEANgIAIAZBAWohAUEoDDsLQbYBIQMgASAERg32ASACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEHqzwBqLQAARw05IABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAz3AQsgAkEANgIAIAZBAWohAUEHDDoLIAEgBEYEQEG3ASEDDPYBCwJAAkAgAS0AAEHFAGsODgA5OTk5OTk5OTk5OTkBOQsgAUEBaiEBQaEBIQMM3QELIAFBAWohAUGiASEDDNwBC0G4ASEDIAEgBEYN9AEgAigCACIAIAQgAWtqIQUgASAAa0ECaiEGAkADQCABLQAAIABB7c8Aai0AAEcNNyAAQQJGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAM9QELIAJBADYCACAGQQFqIQFBEgw4C0G5ASEDIAEgBEYN8wEgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABB8M8Aai0AAEcNNiAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAM9AELIAJBADYCACAGQQFqIQFBIAw3C0G6ASEDIAEgBEYN8gEgAigCACIAIAQgAWtqIQUgASAAa0EBaiEGAkADQCABLQAAIABB8s8Aai0AAEcNNSAAQQFGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAM8wELIAJBADYCACAGQQFqIQFBDww2CyABIARGBEBBuwEhAwzyAQsCQAJAIAEtAABByQBrDgcANTU1NTUBNQsgAUEBaiEBQaUBIQMM2QELIAFBAWohAUGmASEDDNgBC0G8ASEDIAEgBEYN8AEgAigCACIAIAQgAWtqIQUgASAAa0EHaiEGAkADQCABLQAAIABB9M8Aai0AAEcNMyAAQQdGDQEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAM8QELIAJBADYCACAGQQFqIQFBGww0CyABIARGBEBBvQEhAwzwAQsCQAJAAkAgAS0AAEHCAGsOEgA0NDQ0NDQ0NDQBNDQ0NDQ0AjQLIAFBAWohAUGkASEDDNgBCyABQQFqIQFBpwEhAwzXAQsgAUEBaiEBQagBIQMM1gELIAEgBEYEQEG+ASEDDO8BCyABLQAAQc4ARw0wIAFBAWohAQwsCyABIARGBEBBvwEhAwzuAQsCQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQCABLQAAQcEAaw4VAAECAz8EBQY/Pz8HCAkKCz8MDQ4PPwsgAUEBaiEBQegAIQMM4wELIAFBAWohAUHpACEDDOIBCyABQQFqIQFB7gAhAwzhAQsgAUEBaiEBQfIAIQMM4AELIAFBAWohAUHzACEDDN8BCyABQQFqIQFB9gAhAwzeAQsgAUEBaiEBQfcAIQMM3QELIAFBAWohAUH6ACEDDNwBCyABQQFqIQFBgwEhAwzbAQsgAUEBaiEBQYQBIQMM2gELIAFBAWohAUGFASEDDNkBCyABQQFqIQFBkgEhAwzYAQsgAUEBaiEBQZgBIQMM1wELIAFBAWohAUGgASEDDNYBCyABQQFqIQFBowEhAwzVAQsgAUEBaiEBQaoBIQMM1AELIAEgBEcEQCACQRA2AgggAiABNgIEQasBIQMM1AELQcABIQMM7AELQQAhAAJAIAIoAjgiA0UNACADKAI0IgNFDQAgAiADEQAAIQALIABFDV4gAEEVRw0HIAJB0QA2AhwgAiABNgIUIAJBsBc2AhAgAkEVNgIMQQAhAwzrAQsgAUEBaiABIARHDQgaQcIBIQMM6gELA0ACQCABLQAAQQprDgQIAAALAAsgBCABQQFqIgFHDQALQcMBIQMM6QELIAEgBEcEQCACQRE2AgggAiABNgIEQQEhAwzQAQtBxAEhAwzoAQsgASAERgRAQcUBIQMM6AELAkACQCABLQAAQQprDgQBKCgAKAsgAUEBagwJCyABQQFqDAULIAEgBEYEQEHGASEDDOcBCwJAAkAgAS0AAEEKaw4XAQsLAQsLCwsLCwsLCwsLCwsLCwsLCwALCyABQQFqIQELQbABIQMMzQELIAEgBEYEQEHIASEDDOYBCyABLQAAQSBHDQkgAkEAOwEyIAFBAWohAUGzASEDDMwBCwNAIAEhAAJAIAEgBEcEQCABLQAAQTBrQf8BcSIDQQpJDQEMJwtBxwEhAwzmAQsCQCACLwEyIgFBmTNLDQAgAiABQQpsIgU7ATIgBUH+/wNxIANB//8Dc0sNACAAQQFqIQEgAiADIAVqIgM7ATIgA0H//wNxQegHSQ0BCwtBACEDIAJBADYCHCACQcEJNgIQIAJBDTYCDCACIABBAWo2AhQM5AELIAJBADYCHCACIAE2AhQgAkHwDDYCECACQRs2AgxBACEDDOMBCyACKAIEIQAgAkEANgIEIAIgACABECYiAA0BIAFBAWoLIQFBrQEhAwzIAQsgAkHBATYCHCACIAA2AgwgAiABQQFqNgIUQQAhAwzgAQsgAigCBCEAIAJBADYCBCACIAAgARAmIgANASABQQFqCyEBQa4BIQMMxQELIAJBwgE2AhwgAiAANgIMIAIgAUEBajYCFEEAIQMM3QELIAJBADYCHCACIAE2AhQgAkGXCzYCECACQQ02AgxBACEDDNwBCyACQQA2AhwgAiABNgIUIAJB4xA2AhAgAkEJNgIMQQAhAwzbAQsgAkECOgAoDKwBC0EAIQMgAkEANgIcIAJBrws2AhAgAkECNgIMIAIgAUEBajYCFAzZAQtBAiEDDL8BC0ENIQMMvgELQSYhAwy9AQtBFSEDDLwBC0EWIQMMuwELQRghAwy6AQtBHCEDDLkBC0EdIQMMuAELQSAhAwy3AQtBISEDDLYBC0EjIQMMtQELQcYAIQMMtAELQS4hAwyzAQtBPSEDDLIBC0HLACEDDLEBC0HOACEDDLABC0HYACEDDK8BC0HZACEDDK4BC0HbACEDDK0BC0HxACEDDKwBC0H0ACEDDKsBC0GNASEDDKoBC0GXASEDDKkBC0GpASEDDKgBC0GvASEDDKcBC0GxASEDDKYBCyACQQA2AgALQQAhAyACQQA2AhwgAiABNgIUIAJB8Rs2AhAgAkEGNgIMDL0BCyACQQA2AgAgBkEBaiEBQSQLOgApIAIoAgQhACACQQA2AgQgAiAAIAEQJyIARQRAQeUAIQMMowELIAJB+QA2AhwgAiABNgIUIAIgADYCDEEAIQMMuwELIABBFUcEQCACQQA2AhwgAiABNgIUIAJBzA42AhAgAkEgNgIMQQAhAwy7AQsgAkH4ADYCHCACIAE2AhQgAkHKGDYCECACQRU2AgxBACEDDLoBCyACQQA2AhwgAiABNgIUIAJBjhs2AhAgAkEGNgIMQQAhAwy5AQsgAkEANgIcIAIgATYCFCACQf4RNgIQIAJBBzYCDEEAIQMMuAELIAJBADYCHCACIAE2AhQgAkGMHDYCECACQQc2AgxBACEDDLcBCyACQQA2AhwgAiABNgIUIAJBww82AhAgAkEHNgIMQQAhAwy2AQsgAkEANgIcIAIgATYCFCACQcMPNgIQIAJBBzYCDEEAIQMMtQELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0RIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMtAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0gIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMswELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0iIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMsgELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0OIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMsQELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0dIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMsAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0fIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMrwELIABBP0cNASABQQFqCyEBQQUhAwyUAQtBACEDIAJBADYCHCACIAE2AhQgAkH9EjYCECACQQc2AgwMrAELIAJBADYCHCACIAE2AhQgAkHcCDYCECACQQc2AgxBACEDDKsBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNByACQeUANgIcIAIgATYCFCACIAA2AgxBACEDDKoBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNFiACQdMANgIcIAIgATYCFCACIAA2AgxBACEDDKkBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNGCACQdIANgIcIAIgATYCFCACIAA2AgxBACEDDKgBCyACQQA2AhwgAiABNgIUIAJBxgo2AhAgAkEHNgIMQQAhAwynAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQMgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwymAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRIgAkHTADYCHCACIAE2AhQgAiAANgIMQQAhAwylAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRQgAkHSADYCHCACIAE2AhQgAiAANgIMQQAhAwykAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQAgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwyjAQtB1QAhAwyJAQsgAEEVRwRAIAJBADYCHCACIAE2AhQgAkG5DTYCECACQRo2AgxB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AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABB5ABrDvQDY2IAAWFhYWFhYQIDBAVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhBgcICQoLDA0OD2FhYWFhEGFhYWFhYWFhYWFhEWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYRITFBUWFxgZGhthYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2YTc4OTphYWFhYWFhYTthYWE8YWFhYT0+P2FhYWFhYWFhQGFhQWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYUJDREVGR0hJSktMTU5PUFFSU2FhYWFhYWFhVFVWV1hZWlthXF1hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFeYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhX2BhC0HhJw8LQaQhDwtByywPC0H+MQ8LQcAkDwtBqyQPC0GNKA8LQeImDwtBgDAPC0G5Lw8LQdckDwtB7x8PC0HhHw8LQfofDwtB8iAPC0GoLw8LQa4yDwtBiDAPC0HsJw8LQYIiDwtBjh0PC0HQLg8LQcojDwtBxTIPC0HfHA8LQdIcDwtBxCAPC0HXIA8LQaIfDwtB7S4PC0GrMA8LQdQlDwtBzC4PC0H6Lg8LQfwrDwtB0jAPC0HxHQ8LQbsgDwtB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CABB3AgLii1JbnZhbGlkIGNoYXIgaW4gdXJsIHF1ZXJ5AFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fYm9keQBDb250ZW50LUxlbmd0aCBvdmVyZmxvdwBDaHVuayBzaXplIG92ZXJmbG93AFJlc3BvbnNlIG92ZXJmbG93AEludmFsaWQgbWV0aG9kIGZvciBIVFRQL3gueCByZXF1ZXN0AEludmFsaWQgbWV0aG9kIGZvciBSVFNQL3gueCByZXF1ZXN0AEV4cGVjdGVkIFNPVVJDRSBtZXRob2QgZm9yIElDRS94LnggcmVxdWVzdABJbnZhbGlkIGNoYXIgaW4gdXJsIGZyYWdtZW50IHN0YXJ0AEV4cGVjdGVkIGRvdABTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3N0YXR1cwBJbnZhbGlkIHJlc3BvbnNlIHN0YXR1cwBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zAFVzZXIgY2FsbGJhY2sgZXJyb3IAYG9uX3Jlc2V0YCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfaGVhZGVyYCBjYWxsYmFjayBlcnJvcgBgb25fbWVzc2FnZV9iZWdpbmAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2V4dGVuc2lvbl92YWx1ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX3N0YXR1c19jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX3ZlcnNpb25fY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl91cmxfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX2hlYWRlcl92YWx1ZV9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX21lc3NhZ2VfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9tZXRob2RfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9oZWFkZXJfZmllbGRfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19leHRlbnNpb25fbmFtZWAgY2FsbGJhY2sgZXJyb3IAVW5leHBlY3RlZCBjaGFyIGluIHVybCBzZXJ2ZXIASW52YWxpZCBoZWFkZXIgdmFsdWUgY2hhcgBJbnZhbGlkIGhlYWRlciBmaWVsZCBjaGFyAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fdmVyc2lvbgBJbnZhbGlkIG1pbm9yIHZlcnNpb24ASW52YWxpZCBtYWpvciB2ZXJzaW9uAEV4cGVjdGVkIHNwYWNlIGFmdGVyIHZlcnNpb24ARXhwZWN0ZWQgQ1JMRiBhZnRlciB2ZXJzaW9uAEludmFsaWQgSFRUUCB2ZXJzaW9uAEludmFsaWQgaGVhZGVyIHRva2VuAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fdXJsAEludmFsaWQgY2hhcmFjdGVycyBpbiB1cmwAVW5leHBlY3RlZCBzdGFydCBjaGFyIGluIHVybABEb3VibGUgQCBpbiB1cmwARW1wdHkgQ29udGVudC1MZW5ndGgASW52YWxpZCBjaGFyYWN0ZXIgaW4gQ29udGVudC1MZW5ndGgARHVwbGljYXRlIENvbnRlbnQtTGVuZ3RoAEludmFsaWQgY2hhciBpbiB1cmwgcGF0aABDb250ZW50LUxlbmd0aCBjYW4ndCBiZSBwcmVzZW50IHdpdGggVHJhbnNmZXItRW5jb2RpbmcASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgc2l6ZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2hlYWRlcl92YWx1ZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2NodW5rX2V4dGVuc2lvbl92YWx1ZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIHZhbHVlAE1pc3NpbmcgZXhwZWN0ZWQgTEYgYWZ0ZXIgaGVhZGVyIHZhbHVlAEludmFsaWQgYFRyYW5zZmVyLUVuY29kaW5nYCBoZWFkZXIgdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBxdW90ZSB2YWx1ZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIHF1b3RlZCB2YWx1ZQBQYXVzZWQgYnkgb25faGVhZGVyc19jb21wbGV0ZQBJbnZhbGlkIEVPRiBzdGF0ZQBvbl9yZXNldCBwYXVzZQBvbl9jaHVua19oZWFkZXIgcGF1c2UAb25fbWVzc2FnZV9iZWdpbiBwYXVzZQBvbl9jaHVua19leHRlbnNpb25fdmFsdWUgcGF1c2UAb25fc3RhdHVzX2NvbXBsZXRlIHBhdXNlAG9uX3ZlcnNpb25fY29tcGxldGUgcGF1c2UAb25fdXJsX2NvbXBsZXRlIHBhdXNlAG9uX2NodW5rX2NvbXBsZXRlIHBhdXNlAG9uX2hlYWRlcl92YWx1ZV9jb21wbGV0ZSBwYXVzZQBvbl9tZXNzYWdlX2NvbXBsZXRlIHBhdXNlAG9uX21ldGhvZF9jb21wbGV0ZSBwYXVzZQBvbl9oZWFkZXJfZmllbGRfY29tcGxldGUgcGF1c2UAb25fY2h1bmtfZXh0ZW5zaW9uX25hbWUgcGF1c2UAVW5leHBlY3RlZCBzcGFjZSBhZnRlciBzdGFydCBsaW5lAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fY2h1bmtfZXh0ZW5zaW9uX25hbWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBuYW1lAFBhdXNlIG9uIENPTk5FQ1QvVXBncmFkZQBQYXVzZSBvbiBQUkkvVXBncmFkZQBFeHBlY3RlZCBIVFRQLzIgQ29ubmVjdGlvbiBQcmVmYWNlAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fbWV0aG9kAEV4cGVjdGVkIHNwYWNlIGFmdGVyIG1ldGhvZABTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2hlYWRlcl9maWVsZABQYXVzZWQASW52YWxpZCB3b3JkIGVuY291bnRlcmVkAEludmFsaWQgbWV0aG9kIGVuY291bnRlcmVkAFVuZXhwZWN0ZWQgY2hhciBpbiB1cmwgc2NoZW1hAFJlcXVlc3QgaGFzIGludmFsaWQgYFRyYW5zZmVyLUVuY29kaW5nYABTV0lUQ0hfUFJPWFkAVVNFX1BST1hZAE1LQUNUSVZJVFkAVU5QUk9DRVNTQUJMRV9FTlRJVFkAQ09QWQBNT1ZFRF9QRVJNQU5FTlRMWQBUT09fRUFSTFkATk9USUZZAEZBSUxFRF9ERVBFTkRFTkNZAEJBRF9HQVRFV0FZAFBMQVkAUFVUAENIRUNLT1VUAEdBVEVXQVlfVElNRU9VVABSRVFVRVNUX1RJTUVPVVQATkVUV09SS19DT05ORUNUX1RJTUVPVVQAQ09OTkVDVElPTl9USU1FT1VUAExPR0lOX1RJTUVPVVQATkVUV09SS19SRUFEX1RJTUVPVVQAUE9TVABNSVNESVJFQ1RFRF9SRVFVRVNUAENMSUVOVF9DTE9TRURfUkVRVUVTVABDTElFTlRfQ0xPU0VEX0xPQURfQkFMQU5DRURfUkVRVUVTVABCQURfUkVRVUVTVABIVFRQX1JFUVVFU1RfU0VOVF9UT19IVFRQU19QT1JUAFJFUE9SVABJTV9BX1RFQVBPVABSRVNFVF9DT05URU5UAE5PX0NPTlRFTlQAUEFSVElBTF9DT05URU5UAEhQRV9JTlZBTElEX0NPTlNUQU5UAEhQRV9DQl9SRVNFVABHRVQASFBFX1NUUklDVABDT05GTElDVABURU1QT1JBUllfUkVESVJFQ1QAUEVSTUFORU5UX1JFRElSRUNUAENPTk5FQ1QATVVMVElfU1RBVFVTAEhQRV9JTlZBTElEX1NUQVRVUwBUT09fTUFOWV9SRVFVRVNUUwBFQVJMWV9ISU5UUwBVTkFWQUlMQUJMRV9GT1JfTEVHQUxfUkVBU09OUwBPUFRJT05TAFNXSVRDSElOR19QUk9UT0NPTFMAVkFSSUFOVF9BTFNPX05FR09USUFURVMATVVMVElQTEVfQ0hPSUNFUwBJTlRFUk5BTF9TRVJWRVJfRVJST1IAV0VCX1NFUlZFUl9VTktOT1dOX0VSUk9SAFJBSUxHVU5fRVJST1IASURFTlRJVFlfUFJPVklERVJfQVVUSEVOVElDQVRJT05fRVJST1IAU1NMX0NFUlRJRklDQVRFX0VSUk9SAElOVkFMSURfWF9GT1JXQVJERURfRk9SAFNFVF9QQVJBTUVURVIAR0VUX1BBUkFNRVRFUgBIUEVfVVNFUgBTRUVfT1RIRVIASFBFX0NCX0NIVU5LX0hFQURFUgBNS0NBTEVOREFSAFNFVFVQAFdFQl9TRVJWRVJfSVNfRE9XTgBURUFSRE9XTgBIUEVfQ0xPU0VEX0NPTk5FQ1RJT04ASEVVUklTVElDX0VYUElSQVRJT04ARElTQ09OTkVDVEVEX09QRVJBVElPTgBOT05fQVVUSE9SSVRBVElWRV9JTkZPUk1BVElPTgBIUEVfSU5WQUxJRF9WRVJTSU9OAEhQRV9DQl9NRVNTQUdFX0JFR0lOAFNJVEVfSVNfRlJPWkVOAEhQRV9JTlZBTElEX0hFQURFUl9UT0tFTgBJTlZBTElEX1RPS0VOAEZPUkJJRERFTgBFTkhBTkNFX1lPVVJfQ0FMTQBIUEVfSU5WQUxJRF9VUkwAQkxPQ0tFRF9CWV9QQVJFTlRBTF9DT05UUk9MAE1LQ09MAEFDTABIUEVfSU5URVJOQUwAUkVRVUVTVF9IRUFERVJfRklFTERTX1RPT19MQVJHRV9VTk9GRklDSUFMAEhQRV9PSwBVTkxJTksAVU5MT0NLAFBSSQBSRVRSWV9XSVRIAEhQRV9JTlZBTElEX0NPTlRFTlRfTEVOR1RIAEhQRV9VTkVYUEVDVEVEX0NPTlRFTlRfTEVOR1RIAEZMVVNIAFBST1BQQVRDSABNLVNFQVJDSABVUklfVE9PX0xPTkcAUFJPQ0VTU0lORwBNSVNDRUxMQU5FT1VTX1BFUlNJU1RFTlRfV0FSTklORwBNSVNDRUxMQU5FT1VTX1dBUk5JTkcASFBFX0lOVkFMSURfVFJBTlNGRVJfRU5DT0RJTkcARXhwZWN0ZWQgQ1JMRgBIUEVfSU5WQUxJRF9DSFVOS19TSVpFAE1PVkUAQ09OVElOVUUASFBFX0NCX1NUQVRVU19DT01QTEVURQBIUEVfQ0JfSEVBREVSU19DT01QTEVURQBIUEVfQ0JfVkVSU0lPTl9DT01QTEVURQBIUEVfQ0JfVVJMX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19DT01QTEVURQBIUEVfQ0JfSEVBREVSX1ZBTFVFX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19FWFRFTlNJT05fVkFMVUVfQ09NUExFVEUASFBFX0NCX0NIVU5LX0VYVEVOU0lPTl9OQU1FX0NPTVBMRVRFAEhQRV9DQl9NRVNTQUdFX0NPTVBMRVRFAEhQRV9DQl9NRVRIT0RfQ09NUExFVEUASFBFX0NCX0hFQURFUl9GSUVMRF9DT01QTEVURQBERUxFVEUASFBFX0lOVkFMSURfRU9GX1NUQVRFAElOVkFMSURfU1NMX0NFUlRJRklDQVRFAFBBVVNFAE5PX1JFU1BPTlNFAFVOU1VQUE9SVEVEX01FRElBX1RZUEUAR09ORQBOT1RfQUNDRVBUQUJMRQBTRVJWSUNFX1VOQVZBSUxBQkxFAFJBTkdFX05PVF9TQVRJU0ZJQUJMRQBPUklHSU5fSVNfVU5SRUFDSEFCTEUAUkVTUE9OU0VfSVNfU1RBTEUAUFVSR0UATUVSR0UAUkVRVUVTVF9IRUFERVJfRklFTERTX1RPT19MQVJHRQBSRVFVRVNUX0hFQURFUl9UT09fTEFSR0UAUEFZTE9BRF9UT09fTEFSR0UASU5TVUZGSUNJRU5UX1NUT1JBR0UASFBFX1BBVVNFRF9VUEdSQURFAEhQRV9QQVVTRURfSDJfVVBHUkFERQBTT1VSQ0UAQU5OT1VOQ0UAVFJBQ0UASFBFX1VORVhQRUNURURfU1BBQ0UAREVTQ1JJQkUAVU5TVUJTQ1JJQkUAUkVDT1JEAEhQRV9JTlZBTElEX01FVEhPRABOT1RfRk9VTkQAUFJPUEZJTkQAVU5CSU5EAFJFQklORABVTkFVVEhPUklaRUQATUVUSE9EX05PVF9BTExPV0VEAEhUVFBfVkVSU0lPTl9OT1RfU1VQUE9SVEVEAEFMUkVBRFlfUkVQT1JURUQAQUNDRVBURUQATk9UX0lNUExFTUVOVEVEAExPT1BfREVURUNURUQASFBFX0NSX0VYUEVDVEVEAEhQRV9MRl9FWFBFQ1RFRABDUkVBVEVEAElNX1VTRUQASFBFX1BBVVNFRABUSU1FT1VUX09DQ1VSRUQAUEFZTUVOVF9SRVFVSVJFRABQUkVDT05ESVRJT05fUkVRVUlSRUQAUFJPWFlfQVVUSEVOVElDQVRJT05fUkVRVUlSRUQATkVUV09SS19BVVRIRU5USUNBVElPTl9SRVFVSVJFRABMRU5HVEhfUkVRVUlSRUQAU1NMX0NFUlRJRklDQVRFX1JFUVVJUkVEAFVQR1JBREVfUkVRVUlSRUQAUEFHRV9FWFBJUkVEAFBSRUNPTkRJVElPTl9GQUlMRUQARVhQRUNUQVRJT05fRkFJTEVEAFJFVkFMSURBVElPTl9GQUlMRUQAU1NMX0hBTkRTSEFLRV9GQUlMRUQATE9DS0VEAFRSQU5TRk9STUFUSU9OX0FQUExJRUQATk9UX01PRElGSUVEAE5PVF9FWFRFTkRFRABCQU5EV0lEVEhfTElNSVRfRVhDRUVERUQAU0lURV9JU19PVkVSTE9BREVEAEhFQUQARXhwZWN0ZWQgSFRUUC8AAF4TAAAmEwAAMBAAAPAXAACdEwAAFRIAADkXAADwEgAAChAAAHUSAACtEgAAghMAAE8UAAB/EAAAoBUAACMUAACJEgAAixQAAE0VAADUEQAAzxQAABAYAADJFgAA3BYAAMERAADgFwAAuxQAAHQUAAB8FQAA5RQAAAgXAAAfEAAAZRUAAKMUAAAoFQAAAhUAAJkVAAAsEAAAixkAAE8PAADUDgAAahAAAM4QAAACFwAAiQ4AAG4TAAAcEwAAZhQAAFYXAADBEwAAzRMAAGwTAABoFwAAZhcAAF8XAAAiEwAAzg8AAGkOAADYDgAAYxYAAMsTAACqDgAAKBcAACYXAADFEwAAXRYAAOgRAABnEwAAZRMAAPIWAABzEwAAHRcAAPkWAADzEQAAzw4AAM4VAAAMEgAAsxEAAKURAABhEAAAMhcAALsTAEH5NQsBAQBBkDYL4AEBAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB/TcLAQEAQZE4C14CAwICAgICAAACAgACAgACAgICAgICAgICAAQAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAgACAEH9OQsBAQBBkToLXgIAAgICAgIAAAICAAICAAICAgICAgICAgIAAwAEAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgACAAIAQfA7Cw1sb3NlZWVwLWFsaXZlAEGJPAsBAQBBoDwL4AEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBBiT4LAQEAQaA+C+cBAQEBAQEBAQEBAQEBAgEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQFjaHVua2VkAEGwwAALXwEBAAEBAQEBAAABAQABAQABAQEBAQEBAQEBAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQABAEGQwgALIWVjdGlvbmVudC1sZW5ndGhvbnJveHktY29ubmVjdGlvbgBBwMIACy1yYW5zZmVyLWVuY29kaW5ncGdyYWRlDQoNCg0KU00NCg0KVFRQL0NFL1RTUC8AQfnCAAsFAQIAAQMAQZDDAAvgAQQBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAEH5xAALBQECAAEDAEGQxQAL4AEEAQEFAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB+cYACwQBAAABAEGRxwAL3wEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAEH6yAALBAEAAAIAQZDJAAtfAwQAAAQEBAQEBAQEBAQEBQQEBAQEBAQEBAQEBAAEAAYHBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQABAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAQAQfrKAAsEAQAAAQBBkMsACwEBAEGqywALQQIAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAEH6zAALBAEAAAEAQZDNAAsBAQBBms0ACwYCAAAAAAIAQbHNAAs6AwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBB8M4AC5YBTk9VTkNFRUNLT1VUTkVDVEVURUNSSUJFTFVTSEVURUFEU0VBUkNIUkdFQ1RJVklUWUxFTkRBUlZFT1RJRllQVElPTlNDSFNFQVlTVEFUQ0hHRU9SRElSRUNUT1JUUkNIUEFSQU1FVEVSVVJDRUJTQ1JJQkVBUkRPV05BQ0VJTkROS0NLVUJTQ1JJQkVIVFRQL0FEVFAv', 'base64') + + +/***/ }), + +/***/ 3434: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { Buffer } = __nccwpck_require__(4573) + +module.exports = Buffer.from('AGFzbQEAAAABJwdgAX8Bf2ADf39/AX9gAX8AYAJ/fwBgBH9/f38Bf2AAAGADf39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQAEA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAAy0sBQYAAAIAAAAAAAACAQIAAgICAAADAAAAAAMDAwMBAQEBAQEBAQEAAAIAAAAEBQFwARISBQMBAAIGCAF/AUGA1AQLB9EFIgZtZW1vcnkCAAtfaW5pdGlhbGl6ZQAIGV9faW5kaXJlY3RfZnVuY3Rpb25fdGFibGUBAAtsbGh0dHBfaW5pdAAJGGxsaHR0cF9zaG91bGRfa2VlcF9hbGl2ZQAvDGxsaHR0cF9hbGxvYwALBm1hbGxvYwAxC2xsaHR0cF9mcmVlAAwEZnJlZQAMD2xsaHR0cF9nZXRfdHlwZQANFWxsaHR0cF9nZXRfaHR0cF9tYWpvcgAOFWxsaHR0cF9nZXRfaHR0cF9taW5vcgAPEWxsaHR0cF9nZXRfbWV0aG9kABAWbGxodHRwX2dldF9zdGF0dXNfY29kZQAREmxsaHR0cF9nZXRfdXBncmFkZQASDGxsaHR0cF9yZXNldAATDmxsaHR0cF9leGVjdXRlABQUbGxodHRwX3NldHRpbmdzX2luaXQAFQ1sbGh0dHBfZmluaXNoABYMbGxodHRwX3BhdXNlABcNbGxodHRwX3Jlc3VtZQAYG2xsaHR0cF9yZXN1bWVfYWZ0ZXJfdXBncmFkZQAZEGxsaHR0cF9nZXRfZXJybm8AGhdsbGh0dHBfZ2V0X2Vycm9yX3JlYXNvbgAbF2xsaHR0cF9zZXRfZXJyb3JfcmVhc29uABwUbGxodHRwX2dldF9lcnJvcl9wb3MAHRFsbGh0dHBfZXJybm9fbmFtZQAeEmxsaHR0cF9tZXRob2RfbmFtZQAfEmxsaHR0cF9zdGF0dXNfbmFtZQAgGmxsaHR0cF9zZXRfbGVu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PgtBhwEMPQtBiAEMPAtBiQEMOwtBigEMOgtBiwEMOQtBjAEMOAtBjQEMNwtBjgEMNgtBjwEMNQtBkAEMNAtBkQEMMwtBkgEMMgtBkwEMMQtBlAEMMAtBlQEMLwtBlgEMLgtBlwEMLQtBmAEMLAtBmQEMKwtBmgEMKgtBmwEMKQtBnAEMKAtBnQEMJwtBngEMJgtBnwEMJQtBoAEMJAtBoQEMIwtBogEMIgtBowEMIQtBpAEMIAtBpQEMHwtBpgEMHgtBpwEMHQtBqAEMHAtBqQEMGwtBqgEMGgtBqwEMGQtBrAEMGAtBrQEMFwtBrgEMFgtBAQwVC0GvAQwUC0GwAQwTC0GxAQwSC0GzAQwRC0GyAQwQC0G0AQwPC0G1AQwOC0G2AQwNC0G3AQwMC0G4AQwLC0G5AQwKC0G6AQwJC0G7AQwIC0HGAQwHC0G8AQwGC0G9AQwFC0G+AQwEC0G/AQwDC0HAAQwCC0HCAQwBC0HBAQshAwNAAkACQAJAAkACQAJAAkACQAJAIAICfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAgJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACfwJAAkACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAC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iQEhAwykAgtBASABLQAAQd8ARw1nGiABQQFqIQEMXgtBigEhAyABIARGDaICIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgNAIAEtAAAgAEGMzwBqLQAARw1kIABBAUYN+gEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMogILQYsBIQMgASAERg2hAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGOzwBqLQAARw1kIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyiAgsgAkEANgIAIAZBAWohAUECDGULQYwBIQMgASAERg2gAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHwzwBqLQAARw1jIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyhAgsgAkEANgIAIAZBAWohAUEfDGQLQY0BIQMgASAERg2fAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHyzwBqLQAARw1iIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAygAgsgAkEANgIAIAZBAWohAUEJDGMLIAEgBEYEQEGOASEDDJ8CCwJAAkAgAS0AAEHJAGsOBwBiYmJiYgFiCyABQQFqIQFB+AAhAwyGAgsgAUEBaiEBQfkAIQMMhQILQY8BIQMgASAERg2dAiACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEGRzwBqLQAARw1gIABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyeAgsgAkEANgIAIAZBAWohAUEYDGELQZABIQMgASAERg2cAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGXzwBqLQAARw1fIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAydAgsgAkEANgIAIAZBAWohAUEXDGALQZEBIQMgASAERg2bAiACKAIAIgAgBCABa2ohBSABIABrQQZqIQYCQANAIAEtAAAgAEGazwBqLQAARw1eIABBBkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAycAgsgAkEANgIAIAZBAWohAUEV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JSIARQ0RIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMtAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0gIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMswELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0iIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMsgELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0OIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMsQELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0dIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMsAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0fIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMrwELIABBP0cNASABQQFqCyEBQQUhAwyUAQtBACEDIAJBADYCHCACIAE2AhQgAkH9EjYCECACQQc2AgwMrAELIAJBADYCHCACIAE2AhQgAkHcCDYCECACQQc2AgxBACEDDKsBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNByACQeUANgIcIAIgATYCFCACIAA2AgxBACEDDKoBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNFiACQdMANgIcIAIgATYCFCACIAA2AgxBACEDDKkBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNGCACQdIANgIcIAIgATYCFCACIAA2AgxBACEDDKgBCyACQQA2AhwgAiABNgIUIAJBxgo2AhAgAkEHNgIMQQAhAwynAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQMgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwymAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRIgAkHTADYCHCACIAE2AhQgAiAANgIMQQAhAwylAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRQgAkHSADYCHCACIAE2AhQgAiAANgIMQQAhAwykAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQAgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwyjAQtB1QAhAwyJAQsgAEEVRwRAIAJBADYCHCACIAE2AhQgAkG5DTYCECACQRo2AgxB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AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABB5ABrDvQDY2IAAWFhYWFhYQIDBAVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhBgcICQoLDA0OD2FhYWFhEGFhYWFhYWFhYWFhEWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYRITFBUWFxgZGhthYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2YTc4OTphYWFhYWFhYTthYWE8YWFhYT0+P2FhYWFhYWFhQGFhQWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYUJDREVGR0hJSktMTU5PUFFSU2FhYWFhYWFhVFVWV1hZWlthXF1hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFeYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhX2BhC0HhJw8LQaQhDwtByywPC0H+MQ8LQcAkDwtBqyQPC0GNKA8LQeImDwtBgDAPC0G5Lw8LQdckDwtB7x8PC0HhHw8LQfofDwtB8iAPC0GoLw8LQa4yDwtBiDAPC0HsJw8LQYIiDwtBjh0PC0HQLg8LQcojDwtBxTIPC0HfHA8LQdIcDwtBxCAPC0HXIA8LQaIfDwtB7S4PC0GrMA8LQdQlDwtBzC4PC0H6Lg8LQfwrDwtB0jAPC0HxHQ8LQbsgDwtB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AAADAEGUCAsFBAAAAAUAQaQICwkGAAAABwAAAAgAQdwIC4otSW52YWxpZCBjaGFyIGluIHVybCBxdWVyeQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2JvZHkAQ29udGVudC1MZW5ndGggb3ZlcmZsb3cAQ2h1bmsgc2l6ZSBvdmVyZmxvdwBSZXNwb25zZSBvdmVyZmxvdwBJbnZhbGlkIG1ldGhvZCBmb3IgSFRUUC94LnggcmVxdWVzdABJbnZhbGlkIG1ldGhvZCBmb3IgUlRTUC94LnggcmVxdWVzdABFeHBlY3RlZCBTT1VSQ0UgbWV0aG9kIGZvciBJQ0UveC54IHJlcXVlc3QASW52YWxpZCBjaGFyIGluIHVybCBmcmFnbWVudCBzdGFydABFeHBlY3RlZCBkb3QAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9zdGF0dXMASW52YWxpZCByZXNwb25zZSBzdGF0dXMASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucwBVc2VyIGNhbGxiYWNrIGVycm9yAGBvbl9yZXNldGAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2hlYWRlcmAgY2FsbGJhY2sgZXJyb3IAYG9uX21lc3NhZ2VfYmVnaW5gIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19leHRlbnNpb25fdmFsdWVgIGNhbGxiYWNrIGVycm9yAGBvbl9zdGF0dXNfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl92ZXJzaW9uX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fdXJsX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9tZXNzYWdlX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fbWV0aG9kX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfZXh0ZW5zaW9uX25hbWVgIGNhbGxiYWNrIGVycm9yAFVuZXhwZWN0ZWQgY2hhciBpbiB1cmwgc2VydmVyAEludmFsaWQgaGVhZGVyIHZhbHVlIGNoYXIASW52YWxpZCBoZWFkZXIgZmllbGQgY2hhcgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3ZlcnNpb24ASW52YWxpZCBtaW5vciB2ZXJzaW9uAEludmFsaWQgbWFqb3IgdmVyc2lvbgBFeHBlY3RlZCBzcGFjZSBhZnRlciB2ZXJzaW9uAEV4cGVjdGVkIENSTEYgYWZ0ZXIgdmVyc2lvbgBJbnZhbGlkIEhUVFAgdmVyc2lvbgBJbnZhbGlkIGhlYWRlciB0b2tlbgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3VybABJbnZhbGlkIGNoYXJhY3RlcnMgaW4gdXJsAFVuZXhwZWN0ZWQgc3RhcnQgY2hhciBpbiB1cmwARG91YmxlIEAgaW4gdXJsAEVtcHR5IENvbnRlbnQtTGVuZ3RoAEludmFsaWQgY2hhcmFjdGVyIGluIENvbnRlbnQtTGVuZ3RoAER1cGxpY2F0ZSBDb250ZW50LUxlbmd0aABJbnZhbGlkIGNoYXIgaW4gdXJsIHBhdGgAQ29udGVudC1MZW5ndGggY2FuJ3QgYmUgcHJlc2VudCB3aXRoIFRyYW5zZmVyLUVuY29kaW5nAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIHNpemUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfdmFsdWUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9jaHVua19leHRlbnNpb25fdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyB2YWx1ZQBNaXNzaW5nIGV4cGVjdGVkIExGIGFmdGVyIGhlYWRlciB2YWx1ZQBJbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AgaGVhZGVyIHZhbHVlAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgcXVvdGUgdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBxdW90ZWQgdmFsdWUAUGF1c2VkIGJ5IG9uX2hlYWRlcnNfY29tcGxldGUASW52YWxpZCBFT0Ygc3RhdGUAb25fcmVzZXQgcGF1c2UAb25fY2h1bmtfaGVhZGVyIHBhdXNlAG9uX21lc3NhZ2VfYmVnaW4gcGF1c2UAb25fY2h1bmtfZXh0ZW5zaW9uX3ZhbHVlIHBhdXNlAG9uX3N0YXR1c19jb21wbGV0ZSBwYXVzZQBvbl92ZXJzaW9uX2NvbXBsZXRlIHBhdXNlAG9uX3VybF9jb21wbGV0ZSBwYXVzZQBvbl9jaHVua19jb21wbGV0ZSBwYXVzZQBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGUgcGF1c2UAb25fbWVzc2FnZV9jb21wbGV0ZSBwYXVzZQBvbl9tZXRob2RfY29tcGxldGUgcGF1c2UAb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlIHBhdXNlAG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lIHBhdXNlAFVuZXhwZWN0ZWQgc3BhY2UgYWZ0ZXIgc3RhcnQgbGluZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgbmFtZQBQYXVzZSBvbiBDT05ORUNUL1VwZ3JhZGUAUGF1c2Ugb24gUFJJL1VwZ3JhZGUARXhwZWN0ZWQgSFRUUC8yIENvbm5lY3Rpb24gUHJlZmFjZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX21ldGhvZABFeHBlY3RlZCBzcGFjZSBhZnRlciBtZXRob2QAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfZmllbGQAUGF1c2VkAEludmFsaWQgd29yZCBlbmNvdW50ZXJlZABJbnZhbGlkIG1ldGhvZCBlbmNvdW50ZXJlZABVbmV4cGVjdGVkIGNoYXIgaW4gdXJsIHNjaGVtYQBSZXF1ZXN0IGhhcyBpbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AAU1dJVENIX1BST1hZAFVTRV9QUk9YWQBNS0FDVElWSVRZAFVOUFJPQ0VTU0FCTEVfRU5USVRZAENPUFkATU9WRURfUEVSTUFORU5UTFkAVE9PX0VBUkxZAE5PVElGWQBGQUlMRURfREVQRU5ERU5DWQBCQURfR0FURVdBWQBQTEFZAFBVVABDSEVDS09VVABHQVRFV0FZX1RJTUVPVVQAUkVRVUVTVF9USU1FT1VUAE5FVFdPUktfQ09OTkVDVF9USU1FT1VUAENPTk5FQ1RJT05fVElNRU9VVABMT0dJTl9USU1FT1VUAE5FVFdPUktfUkVBRF9USU1FT1VUAFBPU1QATUlTRElSRUNURURfUkVRVUVTVABDTElFTlRfQ0xPU0VEX1JFUVVFU1QAQ0xJRU5UX0NMT1NFRF9MT0FEX0JBTEFOQ0VEX1JFUVVFU1QAQkFEX1JFUVVFU1QASFRUUF9SRVFVRVNUX1NFTlRfVE9fSFRUUFNfUE9SVABSRVBPUlQASU1fQV9URUFQT1QAUkVTRVRfQ09OVEVOVABOT19DT05URU5UAFBBUlRJQUxfQ09OVEVOVABIUEVfSU5WQUxJRF9DT05TVEFOVABIUEVfQ0JfUkVTRVQAR0VUAEhQRV9TVFJJQ1QAQ09ORkxJQ1QAVEVNUE9SQVJZX1JFRElSRUNUAFBFUk1BTkVOVF9SRURJUkVDVABDT05ORUNUAE1VTFRJX1NUQVRVUwBIUEVfSU5WQUxJRF9TVEFUVVMAVE9PX01BTllfUkVRVUVTVFMARUFSTFlfSElOVFMAVU5BVkFJTEFCTEVfRk9SX0xFR0FMX1JFQVNPTlMAT1BUSU9OUwBTV0lUQ0hJTkdfUFJPVE9DT0xTAFZBUklBTlRfQUxTT19ORUdPVElBVEVTAE1VTFRJUExFX0NIT0lDRVMASU5URVJOQUxfU0VSVkVSX0VSUk9SAFdFQl9TRVJWRVJfVU5LTk9XTl9FUlJPUgBSQUlMR1VOX0VSUk9SAElERU5USVRZX1BST1ZJREVSX0FVVEhFTlRJQ0FUSU9OX0VSUk9SAFNTTF9DRVJUSUZJQ0FURV9FUlJPUgBJTlZBTElEX1hfRk9SV0FSREVEX0ZPUgBTRVRfUEFSQU1FVEVSAEdFVF9QQVJBTUVURVIASFBFX1VTRVIAU0VFX09USEVSAEhQRV9DQl9DSFVOS19IRUFERVIATUtDQUxFTkRBUgBTRVRVUABXRUJfU0VSVkVSX0lTX0RPV04AVEVBUkRPV04ASFBFX0NMT1NFRF9DT05ORUNUSU9OAEhFVVJJU1RJQ19FWFBJUkFUSU9OAERJU0NPTk5FQ1RFRF9PUEVSQVRJT04ATk9OX0FVVEhPUklUQVRJVkVfSU5GT1JNQVRJT04ASFBFX0lOVkFMSURfVkVSU0lPTgBIUEVfQ0JfTUVTU0FHRV9CRUdJTgBTSVRFX0lTX0ZST1pFTgBIUEVfSU5WQUxJRF9IRUFERVJfVE9LRU4ASU5WQUxJRF9UT0tFTgBGT1JCSURERU4ARU5IQU5DRV9ZT1VSX0NBTE0ASFBFX0lOVkFMSURfVVJMAEJMT0NLRURfQllfUEFSRU5UQUxfQ09OVFJPTABNS0NPTABBQ0wASFBFX0lOVEVSTkFMAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0VfVU5PRkZJQ0lBTABIUEVfT0sAVU5MSU5LAFVOTE9DSwBQUkkAUkVUUllfV0lUSABIUEVfSU5WQUxJRF9DT05URU5UX0xFTkdUSABIUEVfVU5FWFBFQ1RFRF9DT05URU5UX0xFTkdUSABGTFVTSABQUk9QUEFUQ0gATS1TRUFSQ0gAVVJJX1RPT19MT05HAFBST0NFU1NJTkcATUlTQ0VMTEFORU9VU19QRVJTSVNURU5UX1dBUk5JTkcATUlTQ0VMTEFORU9VU19XQVJOSU5HAEhQRV9JTlZBTElEX1RSQU5TRkVSX0VOQ09ESU5HAEV4cGVjdGVkIENSTEYASFBFX0lOVkFMSURfQ0hVTktfU0laRQBNT1ZFAENPTlRJTlVFAEhQRV9DQl9TVEFUVVNfQ09NUExFVEUASFBFX0NCX0hFQURFUlNfQ09NUExFVEUASFBFX0NCX1ZFUlNJT05fQ09NUExFVEUASFBFX0NCX1VSTF9DT01QTEVURQBIUEVfQ0JfQ0hVTktfQ09NUExFVEUASFBFX0NCX0hFQURFUl9WQUxVRV9DT01QTEVURQBIUEVfQ0JfQ0hVTktfRVhURU5TSU9OX1ZBTFVFX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19FWFRFTlNJT05fTkFNRV9DT01QTEVURQBIUEVfQ0JfTUVTU0FHRV9DT01QTEVURQBIUEVfQ0JfTUVUSE9EX0NPTVBMRVRFAEhQRV9DQl9IRUFERVJfRklFTERfQ09NUExFVEUAREVMRVRFAEhQRV9JTlZBTElEX0VPRl9TVEFURQBJTlZBTElEX1NTTF9DRVJUSUZJQ0FURQBQQVVTRQBOT19SRVNQT05TRQBVTlNVUFBPUlRFRF9NRURJQV9UWVBFAEdPTkUATk9UX0FDQ0VQVEFCTEUAU0VSVklDRV9VTkFWQUlMQUJMRQBSQU5HRV9OT1RfU0FUSVNGSUFCTEUAT1JJR0lOX0lTX1VOUkVBQ0hBQkxFAFJFU1BPTlNFX0lTX1NUQUxFAFBVUkdFAE1FUkdFAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0UAUkVRVUVTVF9IRUFERVJfVE9PX0xBUkdFAFBBWUxPQURfVE9PX0xBUkdFAElOU1VGRklDSUVOVF9TVE9SQUdFAEhQRV9QQVVTRURfVVBHUkFERQBIUEVfUEFVU0VEX0gyX1VQR1JBREUAU09VUkNFAEFOTk9VTkNFAFRSQUNFAEhQRV9VTkVYUEVDVEVEX1NQQUNFAERFU0NSSUJFAFVOU1VCU0NSSUJFAFJFQ09SRABIUEVfSU5WQUxJRF9NRVRIT0QATk9UX0ZPVU5EAFBST1BGSU5EAFVOQklORABSRUJJTkQAVU5BVVRIT1JJWkVEAE1FVEhPRF9OT1RfQUxMT1dFRABIVFRQX1ZFUlNJT05fTk9UX1NVUFBPUlRFRABBTFJFQURZX1JFUE9SVEVEAEFDQ0VQVEVEAE5PVF9JTVBMRU1FTlRFRABMT09QX0RFVEVDVEVEAEhQRV9DUl9FWFBFQ1RFRABIUEVfTEZfRVhQRUNURUQAQ1JFQVRFRABJTV9VU0VEAEhQRV9QQVVTRUQAVElNRU9VVF9PQ0NVUkVEAFBBWU1FTlRfUkVRVUlSRUQAUFJFQ09ORElUSU9OX1JFUVVJUkVEAFBST1hZX0FVVEhFTlRJQ0FUSU9OX1JFUVVJUkVEAE5FVFdPUktfQVVUSEVOVElDQVRJT05fUkVRVUlSRUQATEVOR1RIX1JFUVVJUkVEAFNTTF9DRVJUSUZJQ0FURV9SRVFVSVJFRABVUEdSQURFX1JFUVVJUkVEAFBBR0VfRVhQSVJFRABQUkVDT05ESVRJT05fRkFJTEVEAEVYUEVDVEFUSU9OX0ZBSUxFRABSRVZBTElEQVRJT05fRkFJTEVEAFNTTF9IQU5EU0hBS0VfRkFJTEVEAExPQ0tFRABUUkFOU0ZPUk1BVElPTl9BUFBMSUVEAE5PVF9NT0RJRklFRABOT1RfRVhURU5ERUQAQkFORFdJRFRIX0xJTUlUX0VYQ0VFREVEAFNJVEVfSVNfT1ZFUkxPQURFRABIRUFEAEV4cGVjdGVkIEhUVFAvAABeEwAAJhMAADAQAADwFwAAnRMAABUSAAA5FwAA8BIAAAoQAAB1EgAArRIAAIITAABPFAAAfxAAAKAVAAAjFAAAiRIAAIsUAABNFQAA1BEAAM8UAAAQGAAAyRYAANwWAADBEQAA4BcAALsUAAB0FAAAfBUAAOUUAAAIFwAAHxAAAGUVAACjFAAAKBUAAAIVAACZFQAALBAAAIsZAABPDwAA1A4AAGoQAADOEAAAAhcAAIkOAABuEwAAHBMAAGYUAABWFwAAwRMAAM0TAABsEwAAaBcAAGYXAABfFwAAIhMAAM4PAABpDgAA2A4AAGMWAADLEwAAqg4AACgXAAAmFwAAxRMAAF0WAADoEQAAZxMAAGUTAADyFgAAcxMAAB0XAAD5FgAA8xEAAM8OAADOFQAADBIAALMRAAClEQAAYRAAADIXAAC7EwBB+TULAQEAQZA2C+ABAQECAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAQf03CwEBAEGROAteAgMCAgICAgAAAgIAAgIAAgICAgICAgICAgAEAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAAIAAgBB/TkLAQEAQZE6C14CAAICAgICAAACAgACAgACAgICAgICAgICAAMABAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAgACAEHwOwsNbG9zZWVlcC1hbGl2ZQBBiTwLAQEAQaA8C+ABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAQYk+CwEBAEGgPgvnAQEBAQEBAQEBAQEBAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBY2h1bmtlZABBsMAAC18BAQABAQEBAQAAAQEAAQEAAQEBAQEBAQEBAQAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQBBkMIACyFlY3Rpb25lbnQtbGVuZ3Rob25yb3h5LWNvbm5lY3Rpb24AQcDCAAstcmFuc2Zlci1lbmNvZGluZ3BncmFkZQ0KDQoNClNNDQoNClRUUC9DRS9UU1AvAEH5wgALBQECAAEDAEGQwwAL4AEEAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB+cQACwUBAgABAwBBkMUAC+ABBAEBBQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAQfnGAAsEAQAAAQBBkccAC98BAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB+sgACwQBAAACAEGQyQALXwMEAAAEBAQEBAQEBAQEBAUEBAQEBAQEBAQEBAQABAAGBwQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEAEH6ygALBAEAAAEAQZDLAAsBAQBBqssAC0ECAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBB+swACwQBAAABAEGQzQALAQEAQZrNAAsGAgAAAAACAEGxzQALOgMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAQfDOAAuWAU5PVU5DRUVDS09VVE5FQ1RFVEVDUklCRUxVU0hFVEVBRFNFQVJDSFJHRUNUSVZJVFlMRU5EQVJWRU9USUZZUFRJT05TQ0hTRUFZU1RBVENIR0VPUkRJUkVDVE9SVFJDSFBBUkFNRVRFUlVSQ0VCU0NSSUJFQVJET1dOQUNFSU5ETktDS1VCU0NSSUJFSFRUUC9BRFRQLw==', 'base64') + + +/***/ }), + +/***/ 172: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.enumToMap = void 0; +function enumToMap(obj) { + const res = {}; + Object.keys(obj).forEach((key) => { + const value = obj[key]; + if (typeof value === 'number') { + res[key] = value; + } + }); + return res; +} +exports.enumToMap = enumToMap; +//# sourceMappingURL=utils.js.map + +/***/ }), + +/***/ 7501: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kClients } = __nccwpck_require__(6443) +const Agent = __nccwpck_require__(7405) +const { + kAgent, + kMockAgentSet, + kMockAgentGet, + kDispatches, + kIsMockActive, + kNetConnect, + kGetNetConnect, + kOptions, + kFactory +} = __nccwpck_require__(1117) +const MockClient = __nccwpck_require__(7365) +const MockPool = __nccwpck_require__(4004) +const { matchValue, buildMockOptions } = __nccwpck_require__(3397) +const { InvalidArgumentError, UndiciError } = __nccwpck_require__(8707) +const Dispatcher = __nccwpck_require__(883) +const Pluralizer = __nccwpck_require__(1529) +const PendingInterceptorsFormatter = __nccwpck_require__(6142) + +class MockAgent extends Dispatcher { + constructor (opts) { + super(opts) + + this[kNetConnect] = true + this[kIsMockActive] = true + + // Instantiate Agent and encapsulate + if ((opts?.agent && typeof opts.agent.dispatch !== 'function')) { + throw new InvalidArgumentError('Argument opts.agent must implement Agent') + } + const agent = opts?.agent ? opts.agent : new Agent(opts) + this[kAgent] = agent + + this[kClients] = agent[kClients] + this[kOptions] = buildMockOptions(opts) + } + + get (origin) { + let dispatcher = this[kMockAgentGet](origin) + + if (!dispatcher) { + dispatcher = this[kFactory](origin) + this[kMockAgentSet](origin, dispatcher) + } + return dispatcher + } + + dispatch (opts, handler) { + // Call MockAgent.get to perform additional setup before dispatching as normal + this.get(opts.origin) + return this[kAgent].dispatch(opts, handler) + } + + async close () { + await this[kAgent].close() + this[kClients].clear() + } + + deactivate () { + this[kIsMockActive] = false + } + + activate () { + this[kIsMockActive] = true + } + + enableNetConnect (matcher) { + if (typeof matcher === 'string' || typeof matcher === 'function' || matcher instanceof RegExp) { + if (Array.isArray(this[kNetConnect])) { + this[kNetConnect].push(matcher) + } else { + this[kNetConnect] = [matcher] + } + } else if (typeof matcher === 'undefined') { + this[kNetConnect] = true + } else { + throw new InvalidArgumentError('Unsupported matcher. Must be one of String|Function|RegExp.') + } + } + + disableNetConnect () { + this[kNetConnect] = false + } + + // This is required to bypass issues caused by using global symbols - see: + // https://github.com/nodejs/undici/issues/1447 + get isMockActive () { + return this[kIsMockActive] + } + + [kMockAgentSet] (origin, dispatcher) { + this[kClients].set(origin, dispatcher) + } + + [kFactory] (origin) { + const mockOptions = Object.assign({ agent: this }, this[kOptions]) + return this[kOptions] && this[kOptions].connections === 1 + ? new MockClient(origin, mockOptions) + : new MockPool(origin, mockOptions) + } + + [kMockAgentGet] (origin) { + // First check if we can immediately find it + const client = this[kClients].get(origin) + if (client) { + return client + } + + // If the origin is not a string create a dummy parent pool and return to user + if (typeof origin !== 'string') { + const dispatcher = this[kFactory]('http://localhost:9999') + this[kMockAgentSet](origin, dispatcher) + return dispatcher + } + + // If we match, create a pool and assign the same dispatches + for (const [keyMatcher, nonExplicitDispatcher] of Array.from(this[kClients])) { + if (nonExplicitDispatcher && typeof keyMatcher !== 'string' && matchValue(keyMatcher, origin)) { + const dispatcher = this[kFactory](origin) + this[kMockAgentSet](origin, dispatcher) + dispatcher[kDispatches] = nonExplicitDispatcher[kDispatches] + return dispatcher + } + } + } + + [kGetNetConnect] () { + return this[kNetConnect] + } + + pendingInterceptors () { + const mockAgentClients = this[kClients] + + return Array.from(mockAgentClients.entries()) + .flatMap(([origin, scope]) => scope[kDispatches].map(dispatch => ({ ...dispatch, origin }))) + .filter(({ pending }) => pending) + } + + assertNoPendingInterceptors ({ pendingInterceptorsFormatter = new PendingInterceptorsFormatter() } = {}) { + const pending = this.pendingInterceptors() + + if (pending.length === 0) { + return + } + + const pluralizer = new Pluralizer('interceptor', 'interceptors').pluralize(pending.length) + + throw new UndiciError(` +${pluralizer.count} ${pluralizer.noun} ${pluralizer.is} pending: + +${pendingInterceptorsFormatter.format(pending)} +`.trim()) + } +} + +module.exports = MockAgent + + +/***/ }), + +/***/ 7365: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { promisify } = __nccwpck_require__(7975) +const Client = __nccwpck_require__(3701) +const { buildMockDispatch } = __nccwpck_require__(3397) +const { + kDispatches, + kMockAgent, + kClose, + kOriginalClose, + kOrigin, + kOriginalDispatch, + kConnected +} = __nccwpck_require__(1117) +const { MockInterceptor } = __nccwpck_require__(1511) +const Symbols = __nccwpck_require__(6443) +const { InvalidArgumentError } = __nccwpck_require__(8707) + +/** + * MockClient provides an API that extends the Client to influence the mockDispatches. + */ +class MockClient extends Client { + constructor (origin, opts) { + super(origin, opts) + + if (!opts || !opts.agent || typeof opts.agent.dispatch !== 'function') { + throw new InvalidArgumentError('Argument opts.agent must implement Agent') + } + + this[kMockAgent] = opts.agent + this[kOrigin] = origin + this[kDispatches] = [] + this[kConnected] = 1 + this[kOriginalDispatch] = this.dispatch + this[kOriginalClose] = this.close.bind(this) + + this.dispatch = buildMockDispatch.call(this) + this.close = this[kClose] + } + + get [Symbols.kConnected] () { + return this[kConnected] + } + + /** + * Sets up the base interceptor for mocking replies from undici. + */ + intercept (opts) { + return new MockInterceptor(opts, this[kDispatches]) + } + + async [kClose] () { + await promisify(this[kOriginalClose])() + this[kConnected] = 0 + this[kMockAgent][Symbols.kClients].delete(this[kOrigin]) + } +} + +module.exports = MockClient + + +/***/ }), + +/***/ 2429: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { UndiciError } = __nccwpck_require__(8707) + +const kMockNotMatchedError = Symbol.for('undici.error.UND_MOCK_ERR_MOCK_NOT_MATCHED') + +/** + * The request does not match any registered mock dispatches. + */ +class MockNotMatchedError extends UndiciError { + constructor (message) { + super(message) + Error.captureStackTrace(this, MockNotMatchedError) + this.name = 'MockNotMatchedError' + this.message = message || 'The request does not match any registered mock dispatches' + this.code = 'UND_MOCK_ERR_MOCK_NOT_MATCHED' + } + + static [Symbol.hasInstance] (instance) { + return instance && instance[kMockNotMatchedError] === true + } + + [kMockNotMatchedError] = true +} + +module.exports = { + MockNotMatchedError +} + + +/***/ }), + +/***/ 1511: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { getResponseData, buildKey, addMockDispatch } = __nccwpck_require__(3397) +const { + kDispatches, + kDispatchKey, + kDefaultHeaders, + kDefaultTrailers, + kContentLength, + kMockDispatch +} = __nccwpck_require__(1117) +const { InvalidArgumentError } = __nccwpck_require__(8707) +const { buildURL } = __nccwpck_require__(3440) + +/** + * Defines the scope API for an interceptor reply + */ +class MockScope { + constructor (mockDispatch) { + this[kMockDispatch] = mockDispatch + } + + /** + * Delay a reply by a set amount in ms. + */ + delay (waitInMs) { + if (typeof waitInMs !== 'number' || !Number.isInteger(waitInMs) || waitInMs <= 0) { + throw new InvalidArgumentError('waitInMs must be a valid integer > 0') + } + + this[kMockDispatch].delay = waitInMs + return this + } + + /** + * For a defined reply, never mark as consumed. + */ + persist () { + this[kMockDispatch].persist = true + return this + } + + /** + * Allow one to define a reply for a set amount of matching requests. + */ + times (repeatTimes) { + if (typeof repeatTimes !== 'number' || !Number.isInteger(repeatTimes) || repeatTimes <= 0) { + throw new InvalidArgumentError('repeatTimes must be a valid integer > 0') + } + + this[kMockDispatch].times = repeatTimes + return this + } +} + +/** + * Defines an interceptor for a Mock + */ +class MockInterceptor { + constructor (opts, mockDispatches) { + if (typeof opts !== 'object') { + throw new InvalidArgumentError('opts must be an object') + } + if (typeof opts.path === 'undefined') { + throw new InvalidArgumentError('opts.path must be defined') + } + if (typeof opts.method === 'undefined') { + opts.method = 'GET' + } + // See https://github.com/nodejs/undici/issues/1245 + // As per RFC 3986, clients are not supposed to send URI + // fragments to servers when they retrieve a document, + if (typeof opts.path === 'string') { + if (opts.query) { + opts.path = buildURL(opts.path, opts.query) + } else { + // Matches https://github.com/nodejs/undici/blob/main/lib/web/fetch/index.js#L1811 + const parsedURL = new URL(opts.path, 'data://') + opts.path = parsedURL.pathname + parsedURL.search + } + } + if (typeof opts.method === 'string') { + opts.method = opts.method.toUpperCase() + } + + this[kDispatchKey] = buildKey(opts) + this[kDispatches] = mockDispatches + this[kDefaultHeaders] = {} + this[kDefaultTrailers] = {} + this[kContentLength] = false + } + + createMockScopeDispatchData ({ statusCode, data, responseOptions }) { + const responseData = getResponseData(data) + const contentLength = this[kContentLength] ? { 'content-length': responseData.length } : {} + const headers = { ...this[kDefaultHeaders], ...contentLength, ...responseOptions.headers } + const trailers = { ...this[kDefaultTrailers], ...responseOptions.trailers } + + return { statusCode, data, headers, trailers } + } + + validateReplyParameters (replyParameters) { + if (typeof replyParameters.statusCode === 'undefined') { + throw new InvalidArgumentError('statusCode must be defined') + } + if (typeof replyParameters.responseOptions !== 'object' || replyParameters.responseOptions === null) { + throw new InvalidArgumentError('responseOptions must be an object') + } + } + + /** + * Mock an undici request with a defined reply. + */ + reply (replyOptionsCallbackOrStatusCode) { + // Values of reply aren't available right now as they + // can only be available when the reply callback is invoked. + if (typeof replyOptionsCallbackOrStatusCode === 'function') { + // We'll first wrap the provided callback in another function, + // this function will properly resolve the data from the callback + // when invoked. + const wrappedDefaultsCallback = (opts) => { + // Our reply options callback contains the parameter for statusCode, data and options. + const resolvedData = replyOptionsCallbackOrStatusCode(opts) + + // Check if it is in the right format + if (typeof resolvedData !== 'object' || resolvedData === null) { + throw new InvalidArgumentError('reply options callback must return an object') + } + + const replyParameters = { data: '', responseOptions: {}, ...resolvedData } + this.validateReplyParameters(replyParameters) + // Since the values can be obtained immediately we return them + // from this higher order function that will be resolved later. + return { + ...this.createMockScopeDispatchData(replyParameters) + } + } + + // Add usual dispatch data, but this time set the data parameter to function that will eventually provide data. + const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], wrappedDefaultsCallback) + return new MockScope(newMockDispatch) + } + + // We can have either one or three parameters, if we get here, + // we should have 1-3 parameters. So we spread the arguments of + // this function to obtain the parameters, since replyData will always + // just be the statusCode. + const replyParameters = { + statusCode: replyOptionsCallbackOrStatusCode, + data: arguments[1] === undefined ? '' : arguments[1], + responseOptions: arguments[2] === undefined ? {} : arguments[2] + } + this.validateReplyParameters(replyParameters) + + // Send in-already provided data like usual + const dispatchData = this.createMockScopeDispatchData(replyParameters) + const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], dispatchData) + return new MockScope(newMockDispatch) + } + + /** + * Mock an undici request with a defined error. + */ + replyWithError (error) { + if (typeof error === 'undefined') { + throw new InvalidArgumentError('error must be defined') + } + + const newMockDispatch = addMockDispatch(this[kDispatches], this[kDispatchKey], { error }) + return new MockScope(newMockDispatch) + } + + /** + * Set default reply headers on the interceptor for subsequent replies + */ + defaultReplyHeaders (headers) { + if (typeof headers === 'undefined') { + throw new InvalidArgumentError('headers must be defined') + } + + this[kDefaultHeaders] = headers + return this + } + + /** + * Set default reply trailers on the interceptor for subsequent replies + */ + defaultReplyTrailers (trailers) { + if (typeof trailers === 'undefined') { + throw new InvalidArgumentError('trailers must be defined') + } + + this[kDefaultTrailers] = trailers + return this + } + + /** + * Set reply content length header for replies on the interceptor + */ + replyContentLength () { + this[kContentLength] = true + return this + } +} + +module.exports.MockInterceptor = MockInterceptor +module.exports.MockScope = MockScope + + +/***/ }), + +/***/ 4004: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { promisify } = __nccwpck_require__(7975) +const Pool = __nccwpck_require__(628) +const { buildMockDispatch } = __nccwpck_require__(3397) +const { + kDispatches, + kMockAgent, + kClose, + kOriginalClose, + kOrigin, + kOriginalDispatch, + kConnected +} = __nccwpck_require__(1117) +const { MockInterceptor } = __nccwpck_require__(1511) +const Symbols = __nccwpck_require__(6443) +const { InvalidArgumentError } = __nccwpck_require__(8707) + +/** + * MockPool provides an API that extends the Pool to influence the mockDispatches. + */ +class MockPool extends Pool { + constructor (origin, opts) { + super(origin, opts) + + if (!opts || !opts.agent || typeof opts.agent.dispatch !== 'function') { + throw new InvalidArgumentError('Argument opts.agent must implement Agent') + } + + this[kMockAgent] = opts.agent + this[kOrigin] = origin + this[kDispatches] = [] + this[kConnected] = 1 + this[kOriginalDispatch] = this.dispatch + this[kOriginalClose] = this.close.bind(this) + + this.dispatch = buildMockDispatch.call(this) + this.close = this[kClose] + } + + get [Symbols.kConnected] () { + return this[kConnected] + } + + /** + * Sets up the base interceptor for mocking replies from undici. + */ + intercept (opts) { + return new MockInterceptor(opts, this[kDispatches]) + } + + async [kClose] () { + await promisify(this[kOriginalClose])() + this[kConnected] = 0 + this[kMockAgent][Symbols.kClients].delete(this[kOrigin]) + } +} + +module.exports = MockPool + + +/***/ }), + +/***/ 1117: +/***/ ((module) => { + +"use strict"; + + +module.exports = { + kAgent: Symbol('agent'), + kOptions: Symbol('options'), + kFactory: Symbol('factory'), + kDispatches: Symbol('dispatches'), + kDispatchKey: Symbol('dispatch key'), + kDefaultHeaders: Symbol('default headers'), + kDefaultTrailers: Symbol('default trailers'), + kContentLength: Symbol('content length'), + kMockAgent: Symbol('mock agent'), + kMockAgentSet: Symbol('mock agent set'), + kMockAgentGet: Symbol('mock agent get'), + kMockDispatch: Symbol('mock dispatch'), + kClose: Symbol('close'), + kOriginalClose: Symbol('original agent close'), + kOrigin: Symbol('origin'), + kIsMockActive: Symbol('is mock active'), + kNetConnect: Symbol('net connect'), + kGetNetConnect: Symbol('get net connect'), + kConnected: Symbol('connected') +} + + +/***/ }), + +/***/ 3397: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { MockNotMatchedError } = __nccwpck_require__(2429) +const { + kDispatches, + kMockAgent, + kOriginalDispatch, + kOrigin, + kGetNetConnect +} = __nccwpck_require__(1117) +const { buildURL } = __nccwpck_require__(3440) +const { STATUS_CODES } = __nccwpck_require__(7067) +const { + types: { + isPromise + } +} = __nccwpck_require__(7975) + +function matchValue (match, value) { + if (typeof match === 'string') { + return match === value + } + if (match instanceof RegExp) { + return match.test(value) + } + if (typeof match === 'function') { + return match(value) === true + } + return false +} + +function lowerCaseEntries (headers) { + return Object.fromEntries( + Object.entries(headers).map(([headerName, headerValue]) => { + return [headerName.toLocaleLowerCase(), headerValue] + }) + ) +} + +/** + * @param {import('../../index').Headers|string[]|Record} headers + * @param {string} key + */ +function getHeaderByName (headers, key) { + if (Array.isArray(headers)) { + for (let i = 0; i < headers.length; i += 2) { + if (headers[i].toLocaleLowerCase() === key.toLocaleLowerCase()) { + return headers[i + 1] + } + } + + return undefined + } else if (typeof headers.get === 'function') { + return headers.get(key) + } else { + return lowerCaseEntries(headers)[key.toLocaleLowerCase()] + } +} + +/** @param {string[]} headers */ +function buildHeadersFromArray (headers) { // fetch HeadersList + const clone = headers.slice() + const entries = [] + for (let index = 0; index < clone.length; index += 2) { + entries.push([clone[index], clone[index + 1]]) + } + return Object.fromEntries(entries) +} + +function matchHeaders (mockDispatch, headers) { + if (typeof mockDispatch.headers === 'function') { + if (Array.isArray(headers)) { // fetch HeadersList + headers = buildHeadersFromArray(headers) + } + return mockDispatch.headers(headers ? lowerCaseEntries(headers) : {}) + } + if (typeof mockDispatch.headers === 'undefined') { + return true + } + if (typeof headers !== 'object' || typeof mockDispatch.headers !== 'object') { + return false + } + + for (const [matchHeaderName, matchHeaderValue] of Object.entries(mockDispatch.headers)) { + const headerValue = getHeaderByName(headers, matchHeaderName) + + if (!matchValue(matchHeaderValue, headerValue)) { + return false + } + } + return true +} + +function safeUrl (path) { + if (typeof path !== 'string') { + return path + } + + const pathSegments = path.split('?') + + if (pathSegments.length !== 2) { + return path + } + + const qp = new URLSearchParams(pathSegments.pop()) + qp.sort() + return [...pathSegments, qp.toString()].join('?') +} + +function matchKey (mockDispatch, { path, method, body, headers }) { + const pathMatch = matchValue(mockDispatch.path, path) + const methodMatch = matchValue(mockDispatch.method, method) + const bodyMatch = typeof mockDispatch.body !== 'undefined' ? matchValue(mockDispatch.body, body) : true + const headersMatch = matchHeaders(mockDispatch, headers) + return pathMatch && methodMatch && bodyMatch && headersMatch +} + +function getResponseData (data) { + if (Buffer.isBuffer(data)) { + return data + } else if (data instanceof Uint8Array) { + return data + } else if (data instanceof ArrayBuffer) { + return data + } else if (typeof data === 'object') { + return JSON.stringify(data) + } else { + return data.toString() + } +} + +function getMockDispatch (mockDispatches, key) { + const basePath = key.query ? buildURL(key.path, key.query) : key.path + const resolvedPath = typeof basePath === 'string' ? safeUrl(basePath) : basePath + + // Match path + let matchedMockDispatches = mockDispatches.filter(({ consumed }) => !consumed).filter(({ path }) => matchValue(safeUrl(path), resolvedPath)) + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for path '${resolvedPath}'`) + } + + // Match method + matchedMockDispatches = matchedMockDispatches.filter(({ method }) => matchValue(method, key.method)) + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for method '${key.method}' on path '${resolvedPath}'`) + } + + // Match body + matchedMockDispatches = matchedMockDispatches.filter(({ body }) => typeof body !== 'undefined' ? matchValue(body, key.body) : true) + if (matchedMockDispatches.length === 0) { + throw new MockNotMatchedError(`Mock dispatch not matched for body '${key.body}' on path '${resolvedPath}'`) + } + + // Match headers + matchedMockDispatches = matchedMockDispatches.filter((mockDispatch) => matchHeaders(mockDispatch, key.headers)) + if (matchedMockDispatches.length === 0) { + const headers = typeof key.headers === 'object' ? JSON.stringify(key.headers) : key.headers + throw new MockNotMatchedError(`Mock dispatch not matched for headers '${headers}' on path '${resolvedPath}'`) + } + + return matchedMockDispatches[0] +} + +function addMockDispatch (mockDispatches, key, data) { + const baseData = { timesInvoked: 0, times: 1, persist: false, consumed: false } + const replyData = typeof data === 'function' ? { callback: data } : { ...data } + const newMockDispatch = { ...baseData, ...key, pending: true, data: { error: null, ...replyData } } + mockDispatches.push(newMockDispatch) + return newMockDispatch +} + +function deleteMockDispatch (mockDispatches, key) { + const index = mockDispatches.findIndex(dispatch => { + if (!dispatch.consumed) { + return false + } + return matchKey(dispatch, key) + }) + if (index !== -1) { + mockDispatches.splice(index, 1) + } +} + +function buildKey (opts) { + const { path, method, body, headers, query } = opts + return { + path, + method, + body, + headers, + query + } +} + +function generateKeyValues (data) { + const keys = Object.keys(data) + const result = [] + for (let i = 0; i < keys.length; ++i) { + const key = keys[i] + const value = data[key] + const name = Buffer.from(`${key}`) + if (Array.isArray(value)) { + for (let j = 0; j < value.length; ++j) { + result.push(name, Buffer.from(`${value[j]}`)) + } + } else { + result.push(name, Buffer.from(`${value}`)) + } + } + return result +} + +/** + * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Status + * @param {number} statusCode + */ +function getStatusText (statusCode) { + return STATUS_CODES[statusCode] || 'unknown' +} + +async function getResponse (body) { + const buffers = [] + for await (const data of body) { + buffers.push(data) + } + return Buffer.concat(buffers).toString('utf8') +} + +/** + * Mock dispatch function used to simulate undici dispatches + */ +function mockDispatch (opts, handler) { + // Get mock dispatch from built key + const key = buildKey(opts) + const mockDispatch = getMockDispatch(this[kDispatches], key) + + mockDispatch.timesInvoked++ + + // Here's where we resolve a callback if a callback is present for the dispatch data. + if (mockDispatch.data.callback) { + mockDispatch.data = { ...mockDispatch.data, ...mockDispatch.data.callback(opts) } + } + + // Parse mockDispatch data + const { data: { statusCode, data, headers, trailers, error }, delay, persist } = mockDispatch + const { timesInvoked, times } = mockDispatch + + // If it's used up and not persistent, mark as consumed + mockDispatch.consumed = !persist && timesInvoked >= times + mockDispatch.pending = timesInvoked < times + + // If specified, trigger dispatch error + if (error !== null) { + deleteMockDispatch(this[kDispatches], key) + handler.onError(error) + return true + } + + // Handle the request with a delay if necessary + if (typeof delay === 'number' && delay > 0) { + setTimeout(() => { + handleReply(this[kDispatches]) + }, delay) + } else { + handleReply(this[kDispatches]) + } + + function handleReply (mockDispatches, _data = data) { + // fetch's HeadersList is a 1D string array + const optsHeaders = Array.isArray(opts.headers) + ? buildHeadersFromArray(opts.headers) + : opts.headers + const body = typeof _data === 'function' + ? _data({ ...opts, headers: optsHeaders }) + : _data + + // util.types.isPromise is likely needed for jest. + if (isPromise(body)) { + // If handleReply is asynchronous, throwing an error + // in the callback will reject the promise, rather than + // synchronously throw the error, which breaks some tests. + // Rather, we wait for the callback to resolve if it is a + // promise, and then re-run handleReply with the new body. + body.then((newData) => handleReply(mockDispatches, newData)) + return + } + + const responseData = getResponseData(body) + const responseHeaders = generateKeyValues(headers) + const responseTrailers = generateKeyValues(trailers) + + handler.onConnect?.(err => handler.onError(err), null) + handler.onHeaders?.(statusCode, responseHeaders, resume, getStatusText(statusCode)) + handler.onData?.(Buffer.from(responseData)) + handler.onComplete?.(responseTrailers) + deleteMockDispatch(mockDispatches, key) + } + + function resume () {} + + return true +} + +function buildMockDispatch () { + const agent = this[kMockAgent] + const origin = this[kOrigin] + const originalDispatch = this[kOriginalDispatch] + + return function dispatch (opts, handler) { + if (agent.isMockActive) { + try { + mockDispatch.call(this, opts, handler) + } catch (error) { + if (error instanceof MockNotMatchedError) { + const netConnect = agent[kGetNetConnect]() + if (netConnect === false) { + throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect disabled)`) + } + if (checkNetConnect(netConnect, origin)) { + originalDispatch.call(this, opts, handler) + } else { + throw new MockNotMatchedError(`${error.message}: subsequent request to origin ${origin} was not allowed (net.connect is not enabled for this origin)`) + } + } else { + throw error + } + } + } else { + originalDispatch.call(this, opts, handler) + } + } +} + +function checkNetConnect (netConnect, origin) { + const url = new URL(origin) + if (netConnect === true) { + return true + } else if (Array.isArray(netConnect) && netConnect.some((matcher) => matchValue(matcher, url.host))) { + return true + } + return false +} + +function buildMockOptions (opts) { + if (opts) { + const { agent, ...mockOptions } = opts + return mockOptions + } +} + +module.exports = { + getResponseData, + getMockDispatch, + addMockDispatch, + deleteMockDispatch, + buildKey, + generateKeyValues, + matchValue, + getResponse, + getStatusText, + mockDispatch, + buildMockDispatch, + checkNetConnect, + buildMockOptions, + getHeaderByName, + buildHeadersFromArray +} + + +/***/ }), + +/***/ 6142: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { Transform } = __nccwpck_require__(7075) +const { Console } = __nccwpck_require__(7540) + +const PERSISTENT = process.versions.icu ? '✅' : 'Y ' +const NOT_PERSISTENT = process.versions.icu ? '❌' : 'N ' + +/** + * Gets the output of `console.table(…)` as a string. + */ +module.exports = class PendingInterceptorsFormatter { + constructor ({ disableColors } = {}) { + this.transform = new Transform({ + transform (chunk, _enc, cb) { + cb(null, chunk) + } + }) + + this.logger = new Console({ + stdout: this.transform, + inspectOptions: { + colors: !disableColors && !process.env.CI + } + }) + } + + format (pendingInterceptors) { + const withPrettyHeaders = pendingInterceptors.map( + ({ method, path, data: { statusCode }, persist, times, timesInvoked, origin }) => ({ + Method: method, + Origin: origin, + Path: path, + 'Status code': statusCode, + Persistent: persist ? PERSISTENT : NOT_PERSISTENT, + Invocations: timesInvoked, + Remaining: persist ? Infinity : times - timesInvoked + })) + + this.logger.table(withPrettyHeaders) + return this.transform.read().toString() + } +} + + +/***/ }), + +/***/ 1529: +/***/ ((module) => { + +"use strict"; + + +const singulars = { + pronoun: 'it', + is: 'is', + was: 'was', + this: 'this' +} + +const plurals = { + pronoun: 'they', + is: 'are', + was: 'were', + this: 'these' +} + +module.exports = class Pluralizer { + constructor (singular, plural) { + this.singular = singular + this.plural = plural + } + + pluralize (count) { + const one = count === 1 + const keys = one ? singulars : plurals + const noun = one ? this.singular : this.plural + return { ...keys, count, noun } + } +} + + +/***/ }), + +/***/ 6603: +/***/ ((module) => { + +"use strict"; + + +/** + * This module offers an optimized timer implementation designed for scenarios + * where high precision is not critical. + * + * The timer achieves faster performance by using a low-resolution approach, + * with an accuracy target of within 500ms. This makes it particularly useful + * for timers with delays of 1 second or more, where exact timing is less + * crucial. + * + * It's important to note that Node.js timers are inherently imprecise, as + * delays can occur due to the event loop being blocked by other operations. + * Consequently, timers may trigger later than their scheduled time. + */ + +/** + * The fastNow variable contains the internal fast timer clock value. + * + * @type {number} + */ +let fastNow = 0 + +/** + * RESOLUTION_MS represents the target resolution time in milliseconds. + * + * @type {number} + * @default 1000 + */ +const RESOLUTION_MS = 1e3 + +/** + * TICK_MS defines the desired interval in milliseconds between each tick. + * The target value is set to half the resolution time, minus 1 ms, to account + * for potential event loop overhead. + * + * @type {number} + * @default 499 + */ +const TICK_MS = (RESOLUTION_MS >> 1) - 1 + +/** + * fastNowTimeout is a Node.js timer used to manage and process + * the FastTimers stored in the `fastTimers` array. + * + * @type {NodeJS.Timeout} + */ +let fastNowTimeout + +/** + * The kFastTimer symbol is used to identify FastTimer instances. + * + * @type {Symbol} + */ +const kFastTimer = Symbol('kFastTimer') + +/** + * The fastTimers array contains all active FastTimers. + * + * @type {FastTimer[]} + */ +const fastTimers = [] + +/** + * These constants represent the various states of a FastTimer. + */ + +/** + * The `NOT_IN_LIST` constant indicates that the FastTimer is not included + * in the `fastTimers` array. Timers with this status will not be processed + * during the next tick by the `onTick` function. + * + * A FastTimer can be re-added to the `fastTimers` array by invoking the + * `refresh` method on the FastTimer instance. + * + * @type {-2} + */ +const NOT_IN_LIST = -2 + +/** + * The `TO_BE_CLEARED` constant indicates that the FastTimer is scheduled + * for removal from the `fastTimers` array. A FastTimer in this state will + * be removed in the next tick by the `onTick` function and will no longer + * be processed. + * + * This status is also set when the `clear` method is called on the FastTimer instance. + * + * @type {-1} + */ +const TO_BE_CLEARED = -1 + +/** + * The `PENDING` constant signifies that the FastTimer is awaiting processing + * in the next tick by the `onTick` function. Timers with this status will have + * their `_idleStart` value set and their status updated to `ACTIVE` in the next tick. + * + * @type {0} + */ +const PENDING = 0 + +/** + * The `ACTIVE` constant indicates that the FastTimer is active and waiting + * for its timer to expire. During the next tick, the `onTick` function will + * check if the timer has expired, and if so, it will execute the associated callback. + * + * @type {1} + */ +const ACTIVE = 1 + +/** + * The onTick function processes the fastTimers array. + * + * @returns {void} + */ +function onTick () { + /** + * Increment the fastNow value by the TICK_MS value, despite the actual time + * that has passed since the last tick. This approach ensures independence + * from the system clock and delays caused by a blocked event loop. + * + * @type {number} + */ + fastNow += TICK_MS + + /** + * The `idx` variable is used to iterate over the `fastTimers` array. + * Expired timers are removed by replacing them with the last element in the array. + * Consequently, `idx` is only incremented when the current element is not removed. + * + * @type {number} + */ + let idx = 0 + + /** + * The len variable will contain the length of the fastTimers array + * and will be decremented when a FastTimer should be removed from the + * fastTimers array. + * + * @type {number} + */ + let len = fastTimers.length + + while (idx < len) { + /** + * @type {FastTimer} + */ + const timer = fastTimers[idx] + + // If the timer is in the ACTIVE state and the timer has expired, it will + // be processed in the next tick. + if (timer._state === PENDING) { + // Set the _idleStart value to the fastNow value minus the TICK_MS value + // to account for the time the timer was in the PENDING state. + timer._idleStart = fastNow - TICK_MS + timer._state = ACTIVE + } else if ( + timer._state === ACTIVE && + fastNow >= timer._idleStart + timer._idleTimeout + ) { + timer._state = TO_BE_CLEARED + timer._idleStart = -1 + timer._onTimeout(timer._timerArg) + } + + if (timer._state === TO_BE_CLEARED) { + timer._state = NOT_IN_LIST + + // Move the last element to the current index and decrement len if it is + // not the only element in the array. + if (--len !== 0) { + fastTimers[idx] = fastTimers[len] + } + } else { + ++idx + } + } + + // Set the length of the fastTimers array to the new length and thus + // removing the excess FastTimers elements from the array. + fastTimers.length = len + + // If there are still active FastTimers in the array, refresh the Timer. + // If there are no active FastTimers, the timer will be refreshed again + // when a new FastTimer is instantiated. + if (fastTimers.length !== 0) { + refreshTimeout() + } +} + +function refreshTimeout () { + // If the fastNowTimeout is already set, refresh it. + if (fastNowTimeout) { + fastNowTimeout.refresh() + // fastNowTimeout is not instantiated yet, create a new Timer. + } else { + clearTimeout(fastNowTimeout) + fastNowTimeout = setTimeout(onTick, TICK_MS) + + // If the Timer has an unref method, call it to allow the process to exit if + // there are no other active handles. + if (fastNowTimeout.unref) { + fastNowTimeout.unref() + } + } +} + +/** + * The `FastTimer` class is a data structure designed to store and manage + * timer information. + */ +class FastTimer { + [kFastTimer] = true + + /** + * The state of the timer, which can be one of the following: + * - NOT_IN_LIST (-2) + * - TO_BE_CLEARED (-1) + * - PENDING (0) + * - ACTIVE (1) + * + * @type {-2|-1|0|1} + * @private + */ + _state = NOT_IN_LIST + + /** + * The number of milliseconds to wait before calling the callback. + * + * @type {number} + * @private + */ + _idleTimeout = -1 + + /** + * The time in milliseconds when the timer was started. This value is used to + * calculate when the timer should expire. + * + * @type {number} + * @default -1 + * @private + */ + _idleStart = -1 + + /** + * The function to be executed when the timer expires. + * @type {Function} + * @private + */ + _onTimeout + + /** + * The argument to be passed to the callback when the timer expires. + * + * @type {*} + * @private + */ + _timerArg + + /** + * @constructor + * @param {Function} callback A function to be executed after the timer + * expires. + * @param {number} delay The time, in milliseconds that the timer should wait + * before the specified function or code is executed. + * @param {*} arg + */ + constructor (callback, delay, arg) { + this._onTimeout = callback + this._idleTimeout = delay + this._timerArg = arg + + this.refresh() + } + + /** + * Sets the timer's start time to the current time, and reschedules the timer + * to call its callback at the previously specified duration adjusted to the + * current time. + * Using this on a timer that has already called its callback will reactivate + * the timer. + * + * @returns {void} + */ + refresh () { + // In the special case that the timer is not in the list of active timers, + // add it back to the array to be processed in the next tick by the onTick + // function. + if (this._state === NOT_IN_LIST) { + fastTimers.push(this) + } + + // If the timer is the only active timer, refresh the fastNowTimeout for + // better resolution. + if (!fastNowTimeout || fastTimers.length === 1) { + refreshTimeout() + } + + // Setting the state to PENDING will cause the timer to be reset in the + // next tick by the onTick function. + this._state = PENDING + } + + /** + * The `clear` method cancels the timer, preventing it from executing. + * + * @returns {void} + * @private + */ + clear () { + // Set the state to TO_BE_CLEARED to mark the timer for removal in the next + // tick by the onTick function. + this._state = TO_BE_CLEARED + + // Reset the _idleStart value to -1 to indicate that the timer is no longer + // active. + this._idleStart = -1 + } +} + +/** + * This module exports a setTimeout and clearTimeout function that can be + * used as a drop-in replacement for the native functions. + */ +module.exports = { + /** + * The setTimeout() method sets a timer which executes a function once the + * timer expires. + * @param {Function} callback A function to be executed after the timer + * expires. + * @param {number} delay The time, in milliseconds that the timer should + * wait before the specified function or code is executed. + * @param {*} [arg] An optional argument to be passed to the callback function + * when the timer expires. + * @returns {NodeJS.Timeout|FastTimer} + */ + setTimeout (callback, delay, arg) { + // If the delay is less than or equal to the RESOLUTION_MS value return a + // native Node.js Timer instance. + return delay <= RESOLUTION_MS + ? setTimeout(callback, delay, arg) + : new FastTimer(callback, delay, arg) + }, + /** + * The clearTimeout method cancels an instantiated Timer previously created + * by calling setTimeout. + * + * @param {NodeJS.Timeout|FastTimer} timeout + */ + clearTimeout (timeout) { + // If the timeout is a FastTimer, call its own clear method. + if (timeout[kFastTimer]) { + /** + * @type {FastTimer} + */ + timeout.clear() + // Otherwise it is an instance of a native NodeJS.Timeout, so call the + // Node.js native clearTimeout function. + } else { + clearTimeout(timeout) + } + }, + /** + * The setFastTimeout() method sets a fastTimer which executes a function once + * the timer expires. + * @param {Function} callback A function to be executed after the timer + * expires. + * @param {number} delay The time, in milliseconds that the timer should + * wait before the specified function or code is executed. + * @param {*} [arg] An optional argument to be passed to the callback function + * when the timer expires. + * @returns {FastTimer} + */ + setFastTimeout (callback, delay, arg) { + return new FastTimer(callback, delay, arg) + }, + /** + * The clearTimeout method cancels an instantiated FastTimer previously + * created by calling setFastTimeout. + * + * @param {FastTimer} timeout + */ + clearFastTimeout (timeout) { + timeout.clear() + }, + /** + * The now method returns the value of the internal fast timer clock. + * + * @returns {number} + */ + now () { + return fastNow + }, + /** + * Trigger the onTick function to process the fastTimers array. + * Exported for testing purposes only. + * Marking as deprecated to discourage any use outside of testing. + * @deprecated + * @param {number} [delay=0] The delay in milliseconds to add to the now value. + */ + tick (delay = 0) { + fastNow += delay - RESOLUTION_MS + 1 + onTick() + onTick() + }, + /** + * Reset FastTimers. + * Exported for testing purposes only. + * Marking as deprecated to discourage any use outside of testing. + * @deprecated + */ + reset () { + fastNow = 0 + fastTimers.length = 0 + clearTimeout(fastNowTimeout) + fastNowTimeout = null + }, + /** + * Exporting for testing purposes only. + * Marking as deprecated to discourage any use outside of testing. + * @deprecated + */ + kFastTimer +} + + +/***/ }), + +/***/ 9634: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kConstruct } = __nccwpck_require__(109) +const { urlEquals, getFieldValues } = __nccwpck_require__(6798) +const { kEnumerableProperty, isDisturbed } = __nccwpck_require__(3440) +const { webidl } = __nccwpck_require__(5893) +const { Response, cloneResponse, fromInnerResponse } = __nccwpck_require__(9051) +const { Request, fromInnerRequest } = __nccwpck_require__(9967) +const { kState } = __nccwpck_require__(3627) +const { fetching } = __nccwpck_require__(4398) +const { urlIsHttpHttpsScheme, createDeferredPromise, readAllBytes } = __nccwpck_require__(3168) +const assert = __nccwpck_require__(4589) + +/** + * @see https://w3c.github.io/ServiceWorker/#dfn-cache-batch-operation + * @typedef {Object} CacheBatchOperation + * @property {'delete' | 'put'} type + * @property {any} request + * @property {any} response + * @property {import('../../types/cache').CacheQueryOptions} options + */ + +/** + * @see https://w3c.github.io/ServiceWorker/#dfn-request-response-list + * @typedef {[any, any][]} requestResponseList + */ + +class Cache { + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-request-response-list + * @type {requestResponseList} + */ + #relevantRequestResponseList + + constructor () { + if (arguments[0] !== kConstruct) { + webidl.illegalConstructor() + } + + webidl.util.markAsUncloneable(this) + this.#relevantRequestResponseList = arguments[1] + } + + async match (request, options = {}) { + webidl.brandCheck(this, Cache) + + const prefix = 'Cache.match' + webidl.argumentLengthCheck(arguments, 1, prefix) + + request = webidl.converters.RequestInfo(request, prefix, 'request') + options = webidl.converters.CacheQueryOptions(options, prefix, 'options') + + const p = this.#internalMatchAll(request, options, 1) + + if (p.length === 0) { + return + } + + return p[0] + } + + async matchAll (request = undefined, options = {}) { + webidl.brandCheck(this, Cache) + + const prefix = 'Cache.matchAll' + if (request !== undefined) request = webidl.converters.RequestInfo(request, prefix, 'request') + options = webidl.converters.CacheQueryOptions(options, prefix, 'options') + + return this.#internalMatchAll(request, options) + } + + async add (request) { + webidl.brandCheck(this, Cache) + + const prefix = 'Cache.add' + webidl.argumentLengthCheck(arguments, 1, prefix) + + request = webidl.converters.RequestInfo(request, prefix, 'request') + + // 1. + const requests = [request] + + // 2. + const responseArrayPromise = this.addAll(requests) + + // 3. + return await responseArrayPromise + } + + async addAll (requests) { + webidl.brandCheck(this, Cache) + + const prefix = 'Cache.addAll' + webidl.argumentLengthCheck(arguments, 1, prefix) + + // 1. + const responsePromises = [] + + // 2. + const requestList = [] + + // 3. + for (let request of requests) { + if (request === undefined) { + throw webidl.errors.conversionFailed({ + prefix, + argument: 'Argument 1', + types: ['undefined is not allowed'] + }) + } + + request = webidl.converters.RequestInfo(request) + + if (typeof request === 'string') { + continue + } + + // 3.1 + const r = request[kState] + + // 3.2 + if (!urlIsHttpHttpsScheme(r.url) || r.method !== 'GET') { + throw webidl.errors.exception({ + header: prefix, + message: 'Expected http/s scheme when method is not GET.' + }) + } + } + + // 4. + /** @type {ReturnType[]} */ + const fetchControllers = [] + + // 5. + for (const request of requests) { + // 5.1 + const r = new Request(request)[kState] + + // 5.2 + if (!urlIsHttpHttpsScheme(r.url)) { + throw webidl.errors.exception({ + header: prefix, + message: 'Expected http/s scheme.' + }) + } + + // 5.4 + r.initiator = 'fetch' + r.destination = 'subresource' + + // 5.5 + requestList.push(r) + + // 5.6 + const responsePromise = createDeferredPromise() + + // 5.7 + fetchControllers.push(fetching({ + request: r, + processResponse (response) { + // 1. + if (response.type === 'error' || response.status === 206 || response.status < 200 || response.status > 299) { + responsePromise.reject(webidl.errors.exception({ + header: 'Cache.addAll', + message: 'Received an invalid status code or the request failed.' + })) + } else if (response.headersList.contains('vary')) { // 2. + // 2.1 + const fieldValues = getFieldValues(response.headersList.get('vary')) + + // 2.2 + for (const fieldValue of fieldValues) { + // 2.2.1 + if (fieldValue === '*') { + responsePromise.reject(webidl.errors.exception({ + header: 'Cache.addAll', + message: 'invalid vary field value' + })) + + for (const controller of fetchControllers) { + controller.abort() + } + + return + } + } + } + }, + processResponseEndOfBody (response) { + // 1. + if (response.aborted) { + responsePromise.reject(new DOMException('aborted', 'AbortError')) + return + } + + // 2. + responsePromise.resolve(response) + } + })) + + // 5.8 + responsePromises.push(responsePromise.promise) + } + + // 6. + const p = Promise.all(responsePromises) + + // 7. + const responses = await p + + // 7.1 + const operations = [] + + // 7.2 + let index = 0 + + // 7.3 + for (const response of responses) { + // 7.3.1 + /** @type {CacheBatchOperation} */ + const operation = { + type: 'put', // 7.3.2 + request: requestList[index], // 7.3.3 + response // 7.3.4 + } + + operations.push(operation) // 7.3.5 + + index++ // 7.3.6 + } + + // 7.5 + const cacheJobPromise = createDeferredPromise() + + // 7.6.1 + let errorData = null + + // 7.6.2 + try { + this.#batchCacheOperations(operations) + } catch (e) { + errorData = e + } + + // 7.6.3 + queueMicrotask(() => { + // 7.6.3.1 + if (errorData === null) { + cacheJobPromise.resolve(undefined) + } else { + // 7.6.3.2 + cacheJobPromise.reject(errorData) + } + }) + + // 7.7 + return cacheJobPromise.promise + } + + async put (request, response) { + webidl.brandCheck(this, Cache) + + const prefix = 'Cache.put' + webidl.argumentLengthCheck(arguments, 2, prefix) + + request = webidl.converters.RequestInfo(request, prefix, 'request') + response = webidl.converters.Response(response, prefix, 'response') + + // 1. + let innerRequest = null + + // 2. + if (request instanceof Request) { + innerRequest = request[kState] + } else { // 3. + innerRequest = new Request(request)[kState] + } + + // 4. + if (!urlIsHttpHttpsScheme(innerRequest.url) || innerRequest.method !== 'GET') { + throw webidl.errors.exception({ + header: prefix, + message: 'Expected an http/s scheme when method is not GET' + }) + } + + // 5. + const innerResponse = response[kState] + + // 6. + if (innerResponse.status === 206) { + throw webidl.errors.exception({ + header: prefix, + message: 'Got 206 status' + }) + } + + // 7. + if (innerResponse.headersList.contains('vary')) { + // 7.1. + const fieldValues = getFieldValues(innerResponse.headersList.get('vary')) + + // 7.2. + for (const fieldValue of fieldValues) { + // 7.2.1 + if (fieldValue === '*') { + throw webidl.errors.exception({ + header: prefix, + message: 'Got * vary field value' + }) + } + } + } + + // 8. + if (innerResponse.body && (isDisturbed(innerResponse.body.stream) || innerResponse.body.stream.locked)) { + throw webidl.errors.exception({ + header: prefix, + message: 'Response body is locked or disturbed' + }) + } + + // 9. + const clonedResponse = cloneResponse(innerResponse) + + // 10. + const bodyReadPromise = createDeferredPromise() + + // 11. + if (innerResponse.body != null) { + // 11.1 + const stream = innerResponse.body.stream + + // 11.2 + const reader = stream.getReader() + + // 11.3 + readAllBytes(reader).then(bodyReadPromise.resolve, bodyReadPromise.reject) + } else { + bodyReadPromise.resolve(undefined) + } + + // 12. + /** @type {CacheBatchOperation[]} */ + const operations = [] + + // 13. + /** @type {CacheBatchOperation} */ + const operation = { + type: 'put', // 14. + request: innerRequest, // 15. + response: clonedResponse // 16. + } + + // 17. + operations.push(operation) + + // 19. + const bytes = await bodyReadPromise.promise + + if (clonedResponse.body != null) { + clonedResponse.body.source = bytes + } + + // 19.1 + const cacheJobPromise = createDeferredPromise() + + // 19.2.1 + let errorData = null + + // 19.2.2 + try { + this.#batchCacheOperations(operations) + } catch (e) { + errorData = e + } + + // 19.2.3 + queueMicrotask(() => { + // 19.2.3.1 + if (errorData === null) { + cacheJobPromise.resolve() + } else { // 19.2.3.2 + cacheJobPromise.reject(errorData) + } + }) + + return cacheJobPromise.promise + } + + async delete (request, options = {}) { + webidl.brandCheck(this, Cache) + + const prefix = 'Cache.delete' + webidl.argumentLengthCheck(arguments, 1, prefix) + + request = webidl.converters.RequestInfo(request, prefix, 'request') + options = webidl.converters.CacheQueryOptions(options, prefix, 'options') + + /** + * @type {Request} + */ + let r = null + + if (request instanceof Request) { + r = request[kState] + + if (r.method !== 'GET' && !options.ignoreMethod) { + return false + } + } else { + assert(typeof request === 'string') + + r = new Request(request)[kState] + } + + /** @type {CacheBatchOperation[]} */ + const operations = [] + + /** @type {CacheBatchOperation} */ + const operation = { + type: 'delete', + request: r, + options + } + + operations.push(operation) + + const cacheJobPromise = createDeferredPromise() + + let errorData = null + let requestResponses + + try { + requestResponses = this.#batchCacheOperations(operations) + } catch (e) { + errorData = e + } + + queueMicrotask(() => { + if (errorData === null) { + cacheJobPromise.resolve(!!requestResponses?.length) + } else { + cacheJobPromise.reject(errorData) + } + }) + + return cacheJobPromise.promise + } + + /** + * @see https://w3c.github.io/ServiceWorker/#dom-cache-keys + * @param {any} request + * @param {import('../../types/cache').CacheQueryOptions} options + * @returns {Promise} + */ + async keys (request = undefined, options = {}) { + webidl.brandCheck(this, Cache) + + const prefix = 'Cache.keys' + + if (request !== undefined) request = webidl.converters.RequestInfo(request, prefix, 'request') + options = webidl.converters.CacheQueryOptions(options, prefix, 'options') + + // 1. + let r = null + + // 2. + if (request !== undefined) { + // 2.1 + if (request instanceof Request) { + // 2.1.1 + r = request[kState] + + // 2.1.2 + if (r.method !== 'GET' && !options.ignoreMethod) { + return [] + } + } else if (typeof request === 'string') { // 2.2 + r = new Request(request)[kState] + } + } + + // 4. + const promise = createDeferredPromise() + + // 5. + // 5.1 + const requests = [] + + // 5.2 + if (request === undefined) { + // 5.2.1 + for (const requestResponse of this.#relevantRequestResponseList) { + // 5.2.1.1 + requests.push(requestResponse[0]) + } + } else { // 5.3 + // 5.3.1 + const requestResponses = this.#queryCache(r, options) + + // 5.3.2 + for (const requestResponse of requestResponses) { + // 5.3.2.1 + requests.push(requestResponse[0]) + } + } + + // 5.4 + queueMicrotask(() => { + // 5.4.1 + const requestList = [] + + // 5.4.2 + for (const request of requests) { + const requestObject = fromInnerRequest( + request, + new AbortController().signal, + 'immutable' + ) + // 5.4.2.1 + requestList.push(requestObject) + } + + // 5.4.3 + promise.resolve(Object.freeze(requestList)) + }) + + return promise.promise + } + + /** + * @see https://w3c.github.io/ServiceWorker/#batch-cache-operations-algorithm + * @param {CacheBatchOperation[]} operations + * @returns {requestResponseList} + */ + #batchCacheOperations (operations) { + // 1. + const cache = this.#relevantRequestResponseList + + // 2. + const backupCache = [...cache] + + // 3. + const addedItems = [] + + // 4.1 + const resultList = [] + + try { + // 4.2 + for (const operation of operations) { + // 4.2.1 + if (operation.type !== 'delete' && operation.type !== 'put') { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'operation type does not match "delete" or "put"' + }) + } + + // 4.2.2 + if (operation.type === 'delete' && operation.response != null) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'delete operation should not have an associated response' + }) + } + + // 4.2.3 + if (this.#queryCache(operation.request, operation.options, addedItems).length) { + throw new DOMException('???', 'InvalidStateError') + } + + // 4.2.4 + let requestResponses + + // 4.2.5 + if (operation.type === 'delete') { + // 4.2.5.1 + requestResponses = this.#queryCache(operation.request, operation.options) + + // TODO: the spec is wrong, this is needed to pass WPTs + if (requestResponses.length === 0) { + return [] + } + + // 4.2.5.2 + for (const requestResponse of requestResponses) { + const idx = cache.indexOf(requestResponse) + assert(idx !== -1) + + // 4.2.5.2.1 + cache.splice(idx, 1) + } + } else if (operation.type === 'put') { // 4.2.6 + // 4.2.6.1 + if (operation.response == null) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'put operation should have an associated response' + }) + } + + // 4.2.6.2 + const r = operation.request + + // 4.2.6.3 + if (!urlIsHttpHttpsScheme(r.url)) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'expected http or https scheme' + }) + } + + // 4.2.6.4 + if (r.method !== 'GET') { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'not get method' + }) + } + + // 4.2.6.5 + if (operation.options != null) { + throw webidl.errors.exception({ + header: 'Cache.#batchCacheOperations', + message: 'options must not be defined' + }) + } + + // 4.2.6.6 + requestResponses = this.#queryCache(operation.request) + + // 4.2.6.7 + for (const requestResponse of requestResponses) { + const idx = cache.indexOf(requestResponse) + assert(idx !== -1) + + // 4.2.6.7.1 + cache.splice(idx, 1) + } + + // 4.2.6.8 + cache.push([operation.request, operation.response]) + + // 4.2.6.10 + addedItems.push([operation.request, operation.response]) + } + + // 4.2.7 + resultList.push([operation.request, operation.response]) + } + + // 4.3 + return resultList + } catch (e) { // 5. + // 5.1 + this.#relevantRequestResponseList.length = 0 + + // 5.2 + this.#relevantRequestResponseList = backupCache + + // 5.3 + throw e + } + } + + /** + * @see https://w3c.github.io/ServiceWorker/#query-cache + * @param {any} requestQuery + * @param {import('../../types/cache').CacheQueryOptions} options + * @param {requestResponseList} targetStorage + * @returns {requestResponseList} + */ + #queryCache (requestQuery, options, targetStorage) { + /** @type {requestResponseList} */ + const resultList = [] + + const storage = targetStorage ?? this.#relevantRequestResponseList + + for (const requestResponse of storage) { + const [cachedRequest, cachedResponse] = requestResponse + if (this.#requestMatchesCachedItem(requestQuery, cachedRequest, cachedResponse, options)) { + resultList.push(requestResponse) + } + } + + return resultList + } + + /** + * @see https://w3c.github.io/ServiceWorker/#request-matches-cached-item-algorithm + * @param {any} requestQuery + * @param {any} request + * @param {any | null} response + * @param {import('../../types/cache').CacheQueryOptions | undefined} options + * @returns {boolean} + */ + #requestMatchesCachedItem (requestQuery, request, response = null, options) { + // if (options?.ignoreMethod === false && request.method === 'GET') { + // return false + // } + + const queryURL = new URL(requestQuery.url) + + const cachedURL = new URL(request.url) + + if (options?.ignoreSearch) { + cachedURL.search = '' + + queryURL.search = '' + } + + if (!urlEquals(queryURL, cachedURL, true)) { + return false + } + + if ( + response == null || + options?.ignoreVary || + !response.headersList.contains('vary') + ) { + return true + } + + const fieldValues = getFieldValues(response.headersList.get('vary')) + + for (const fieldValue of fieldValues) { + if (fieldValue === '*') { + return false + } + + const requestValue = request.headersList.get(fieldValue) + const queryValue = requestQuery.headersList.get(fieldValue) + + // If one has the header and the other doesn't, or one has + // a different value than the other, return false + if (requestValue !== queryValue) { + return false + } + } + + return true + } + + #internalMatchAll (request, options, maxResponses = Infinity) { + // 1. + let r = null + + // 2. + if (request !== undefined) { + if (request instanceof Request) { + // 2.1.1 + r = request[kState] + + // 2.1.2 + if (r.method !== 'GET' && !options.ignoreMethod) { + return [] + } + } else if (typeof request === 'string') { + // 2.2.1 + r = new Request(request)[kState] + } + } + + // 5. + // 5.1 + const responses = [] + + // 5.2 + if (request === undefined) { + // 5.2.1 + for (const requestResponse of this.#relevantRequestResponseList) { + responses.push(requestResponse[1]) + } + } else { // 5.3 + // 5.3.1 + const requestResponses = this.#queryCache(r, options) + + // 5.3.2 + for (const requestResponse of requestResponses) { + responses.push(requestResponse[1]) + } + } + + // 5.4 + // We don't implement CORs so we don't need to loop over the responses, yay! + + // 5.5.1 + const responseList = [] + + // 5.5.2 + for (const response of responses) { + // 5.5.2.1 + const responseObject = fromInnerResponse(response, 'immutable') + + responseList.push(responseObject.clone()) + + if (responseList.length >= maxResponses) { + break + } + } + + // 6. + return Object.freeze(responseList) + } +} + +Object.defineProperties(Cache.prototype, { + [Symbol.toStringTag]: { + value: 'Cache', + configurable: true + }, + match: kEnumerableProperty, + matchAll: kEnumerableProperty, + add: kEnumerableProperty, + addAll: kEnumerableProperty, + put: kEnumerableProperty, + delete: kEnumerableProperty, + keys: kEnumerableProperty +}) + +const cacheQueryOptionConverters = [ + { + key: 'ignoreSearch', + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: 'ignoreMethod', + converter: webidl.converters.boolean, + defaultValue: () => false + }, + { + key: 'ignoreVary', + converter: webidl.converters.boolean, + defaultValue: () => false + } +] + +webidl.converters.CacheQueryOptions = webidl.dictionaryConverter(cacheQueryOptionConverters) + +webidl.converters.MultiCacheQueryOptions = webidl.dictionaryConverter([ + ...cacheQueryOptionConverters, + { + key: 'cacheName', + converter: webidl.converters.DOMString + } +]) + +webidl.converters.Response = webidl.interfaceConverter(Response) + +webidl.converters['sequence'] = webidl.sequenceConverter( + webidl.converters.RequestInfo +) + +module.exports = { + Cache +} + + +/***/ }), + +/***/ 3245: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { kConstruct } = __nccwpck_require__(109) +const { Cache } = __nccwpck_require__(9634) +const { webidl } = __nccwpck_require__(5893) +const { kEnumerableProperty } = __nccwpck_require__(3440) + +class CacheStorage { + /** + * @see https://w3c.github.io/ServiceWorker/#dfn-relevant-name-to-cache-map + * @type {Map} + */ + async has (cacheName) { + webidl.brandCheck(this, CacheStorage) + + const prefix = 'CacheStorage.has' + webidl.argumentLengthCheck(arguments, 1, prefix) + + cacheName = webidl.converters.DOMString(cacheName, prefix, 'cacheName') + + // 2.1.1 + // 2.2 + return this.#caches.has(cacheName) + } + + /** + * @see https://w3c.github.io/ServiceWorker/#dom-cachestorage-open + * @param {string} cacheName + * @returns {Promise} + */ + async open (cacheName) { + webidl.brandCheck(this, CacheStorage) + + const prefix = 'CacheStorage.open' + webidl.argumentLengthCheck(arguments, 1, prefix) + + cacheName = webidl.converters.DOMString(cacheName, prefix, 'cacheName') + + // 2.1 + if (this.#caches.has(cacheName)) { + // await caches.open('v1') !== await caches.open('v1') + + // 2.1.1 + const cache = this.#caches.get(cacheName) + + // 2.1.1.1 + return new Cache(kConstruct, cache) + } + + // 2.2 + const cache = [] + + // 2.3 + this.#caches.set(cacheName, cache) + + // 2.4 + return new Cache(kConstruct, cache) + } + + /** + * @see https://w3c.github.io/ServiceWorker/#cache-storage-delete + * @param {string} cacheName + * @returns {Promise} + */ + async delete (cacheName) { + webidl.brandCheck(this, CacheStorage) + + const prefix = 'CacheStorage.delete' + webidl.argumentLengthCheck(arguments, 1, prefix) + + cacheName = webidl.converters.DOMString(cacheName, prefix, 'cacheName') + + return this.#caches.delete(cacheName) + } + + /** + * @see https://w3c.github.io/ServiceWorker/#cache-storage-keys + * @returns {Promise} + */ + async keys () { + webidl.brandCheck(this, CacheStorage) + + // 2.1 + const keys = this.#caches.keys() + + // 2.2 + return [...keys] + } +} + +Object.defineProperties(CacheStorage.prototype, { + [Symbol.toStringTag]: { + value: 'CacheStorage', + configurable: true + }, + match: kEnumerableProperty, + has: kEnumerableProperty, + open: kEnumerableProperty, + delete: kEnumerableProperty, + keys: kEnumerableProperty +}) + +module.exports = { + CacheStorage +} + + +/***/ }), + +/***/ 109: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +module.exports = { + kConstruct: (__nccwpck_require__(6443).kConstruct) +} + + +/***/ }), + +/***/ 6798: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const assert = __nccwpck_require__(4589) +const { URLSerializer } = __nccwpck_require__(1900) +const { isValidHeaderName } = __nccwpck_require__(3168) + +/** + * @see https://url.spec.whatwg.org/#concept-url-equals + * @param {URL} A + * @param {URL} B + * @param {boolean | undefined} excludeFragment + * @returns {boolean} + */ +function urlEquals (A, B, excludeFragment = false) { + const serializedA = URLSerializer(A, excludeFragment) + + const serializedB = URLSerializer(B, excludeFragment) + + return serializedA === serializedB +} + +/** + * @see https://github.com/chromium/chromium/blob/694d20d134cb553d8d89e5500b9148012b1ba299/content/browser/cache_storage/cache_storage_cache.cc#L260-L262 + * @param {string} header + */ +function getFieldValues (header) { + assert(header !== null) + + const values = [] + + for (let value of header.split(',')) { + value = value.trim() + + if (isValidHeaderName(value)) { + values.push(value) + } + } + + return values +} + +module.exports = { + urlEquals, + getFieldValues +} + + +/***/ }), + +/***/ 1276: +/***/ ((module) => { + +"use strict"; + + +// https://wicg.github.io/cookie-store/#cookie-maximum-attribute-value-size +const maxAttributeValueSize = 1024 + +// https://wicg.github.io/cookie-store/#cookie-maximum-name-value-pair-size +const maxNameValuePairSize = 4096 + +module.exports = { + maxAttributeValueSize, + maxNameValuePairSize +} + + +/***/ }), + +/***/ 9061: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { parseSetCookie } = __nccwpck_require__(1978) +const { stringify } = __nccwpck_require__(7797) +const { webidl } = __nccwpck_require__(5893) +const { Headers } = __nccwpck_require__(660) + +/** + * @typedef {Object} Cookie + * @property {string} name + * @property {string} value + * @property {Date|number|undefined} expires + * @property {number|undefined} maxAge + * @property {string|undefined} domain + * @property {string|undefined} path + * @property {boolean|undefined} secure + * @property {boolean|undefined} httpOnly + * @property {'Strict'|'Lax'|'None'} sameSite + * @property {string[]} unparsed + */ + +/** + * @param {Headers} headers + * @returns {Record} + */ +function getCookies (headers) { + webidl.argumentLengthCheck(arguments, 1, 'getCookies') + + webidl.brandCheck(headers, Headers, { strict: false }) + + const cookie = headers.get('cookie') + const out = {} + + if (!cookie) { + return out + } + + for (const piece of cookie.split(';')) { + const [name, ...value] = piece.split('=') + + out[name.trim()] = value.join('=') + } + + return out +} + +/** + * @param {Headers} headers + * @param {string} name + * @param {{ path?: string, domain?: string }|undefined} attributes + * @returns {void} + */ +function deleteCookie (headers, name, attributes) { + webidl.brandCheck(headers, Headers, { strict: false }) + + const prefix = 'deleteCookie' + webidl.argumentLengthCheck(arguments, 2, prefix) + + name = webidl.converters.DOMString(name, prefix, 'name') + attributes = webidl.converters.DeleteCookieAttributes(attributes) + + // Matches behavior of + // https://github.com/denoland/deno_std/blob/63827b16330b82489a04614027c33b7904e08be5/http/cookie.ts#L278 + setCookie(headers, { + name, + value: '', + expires: new Date(0), + ...attributes + }) +} + +/** + * @param {Headers} headers + * @returns {Cookie[]} + */ +function getSetCookies (headers) { + webidl.argumentLengthCheck(arguments, 1, 'getSetCookies') + + webidl.brandCheck(headers, Headers, { strict: false }) + + const cookies = headers.getSetCookie() + + if (!cookies) { + return [] + } + + return cookies.map((pair) => parseSetCookie(pair)) +} + +/** + * @param {Headers} headers + * @param {Cookie} cookie + * @returns {void} + */ +function setCookie (headers, cookie) { + webidl.argumentLengthCheck(arguments, 2, 'setCookie') + + webidl.brandCheck(headers, Headers, { strict: false }) + + cookie = webidl.converters.Cookie(cookie) + + const str = stringify(cookie) + + if (str) { + headers.append('Set-Cookie', str) + } +} + +webidl.converters.DeleteCookieAttributes = webidl.dictionaryConverter([ + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: 'path', + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: 'domain', + defaultValue: () => null + } +]) + +webidl.converters.Cookie = webidl.dictionaryConverter([ + { + converter: webidl.converters.DOMString, + key: 'name' + }, + { + converter: webidl.converters.DOMString, + key: 'value' + }, + { + converter: webidl.nullableConverter((value) => { + if (typeof value === 'number') { + return webidl.converters['unsigned long long'](value) + } + + return new Date(value) + }), + key: 'expires', + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters['long long']), + key: 'maxAge', + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: 'domain', + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.DOMString), + key: 'path', + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.boolean), + key: 'secure', + defaultValue: () => null + }, + { + converter: webidl.nullableConverter(webidl.converters.boolean), + key: 'httpOnly', + defaultValue: () => null + }, + { + converter: webidl.converters.USVString, + key: 'sameSite', + allowedValues: ['Strict', 'Lax', 'None'] + }, + { + converter: webidl.sequenceConverter(webidl.converters.DOMString), + key: 'unparsed', + defaultValue: () => new Array(0) + } +]) + +module.exports = { + getCookies, + deleteCookie, + getSetCookies, + setCookie +} + + +/***/ }), + +/***/ 1978: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +"use strict"; + + +const { maxNameValuePairSize, maxAttributeValueSize } = __nccwpck_require__(1276) +const { isCTLExcludingHtab } = __nccwpck_require__(7797) +const { collectASequenceOfCodePointsFast } = __nccwpck_require__(1900) +const assert = __nccwpck_require__(4589) + +/** + * @description Parses the field-value attributes of a set-cookie header string. + * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4 + * @param {string} header + * @returns if the header is invalid, null will be returned + */ +function parseSetCookie (header) { + // 1. If the set-cookie-string contains a %x00-08 / %x0A-1F / %x7F + // character (CTL characters excluding HTAB): Abort these steps and + // ignore the set-cookie-string entirely. + if (isCTLExcludingHtab(header)) { + return null + } + + let nameValuePair = '' + let unparsedAttributes = '' + let name = '' + let value = '' + + // 2. If the set-cookie-string contains a %x3B (";") character: + if (header.includes(';')) { + // 1. The name-value-pair string consists of the characters up to, + // but not including, the first %x3B (";"), and the unparsed- + // attributes consist of the remainder of the set-cookie-string + // (including the %x3B (";") in question). + const position = { position: 0 } + + nameValuePair = collectASequenceOfCodePointsFast(';', header, position) + unparsedAttributes = header.slice(position.position) + } else { + // Otherwise: + + // 1. The name-value-pair string consists of all the characters + // contained in the set-cookie-string, and the unparsed- + // attributes is the empty string. + nameValuePair = header + } + + // 3. If the name-value-pair string lacks a %x3D ("=") character, then + // the name string is empty, and the value string is the value of + // name-value-pair. + if (!nameValuePair.includes('=')) { + value = nameValuePair + } else { + // Otherwise, the name string consists of the characters up to, but + // not including, the first %x3D ("=") character, and the (possibly + // empty) value string consists of the characters after the first + // %x3D ("=") character. + const position = { position: 0 } + name = collectASequenceOfCodePointsFast( + '=', + nameValuePair, + position + ) + value = nameValuePair.slice(position.position + 1) + } + + // 4. Remove any leading or trailing WSP characters from the name + // string and the value string. + name = name.trim() + value = value.trim() + + // 5. If the sum of the lengths of the name string and the value string + // is more than 4096 octets, abort these steps and ignore the set- + // cookie-string entirely. + if (name.length + value.length > maxNameValuePairSize) { + return null + } + + // 6. The cookie-name is the name string, and the cookie-value is the + // value string. + return { + name, value, ...parseUnparsedAttributes(unparsedAttributes) + } +} + +/** + * Parses the remaining attributes of a set-cookie header + * @see https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4 + * @param {string} unparsedAttributes + * @param {[Object.]={}} cookieAttributeList + */ +function parseUnparsedAttributes (unparsedAttributes, cookieAttributeList = {}) { + // 1. If the unparsed-attributes string is empty, skip the rest of + // these steps. + if (unparsedAttributes.length === 0) { + return cookieAttributeList + } + + // 2. Discard the first character of the unparsed-attributes (which + // will be a %x3B (";") character). + assert(unparsedAttributes[0] === ';') + unparsedAttributes = unparsedAttributes.slice(1) + + let cookieAv = '' + + // 3. If the remaining unparsed-attributes contains a %x3B (";") + // character: + if (unparsedAttributes.includes(';')) { + // 1. Consume the characters of the unparsed-attributes up to, but + // not including, the first %x3B (";") character. + cookieAv = collectASequenceOfCodePointsFast( + ';', + unparsedAttributes, + { position: 0 } + ) + unparsedAttributes = unparsedAttributes.slice(cookieAv.length) + } else { + // Otherwise: + + // 1. Consume the remainder of the unparsed-attributes. + cookieAv = unparsedAttributes + unparsedAttributes = '' + } + + // Let the cookie-av string be the characters consumed in this step. + + let attributeName = '' + let attributeValue = '' + + // 4. If the cookie-av string contains a %x3D ("=") character: + if (cookieAv.includes('=')) { + // 1. The (possibly empty) attribute-name string consists of the + // characters up to, but not including, the first %x3D ("=") + // character, and the (possibly empty) attribute-value string + // consists of the characters after the first %x3D ("=") + // character. + const position = { position: 0 } + + attributeName = collectASequenceOfCodePointsFast( + '=', + cookieAv, + position + ) + attributeValue = cookieAv.slice(position.position + 1) + } else { + // Otherwise: + + // 1. The attribute-name string consists of the entire cookie-av + // string, and the attribute-value string is empty. + attributeName = cookieAv + } + + // 5. Remove any leading or trailing WSP characters from the attribute- + // name string and the attribute-value string. + attributeName = attributeName.trim() + attributeValue = attributeValue.trim() + + // 6. If the attribute-value is longer than 1024 octets, ignore the + // cookie-av string and return to Step 1 of this algorithm. + if (attributeValue.length > maxAttributeValueSize) { + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList) + } + + // 7. Process the attribute-name and attribute-value according to the + // requirements in the following subsections. (Notice that + // attributes with unrecognized attribute-names are ignored.) + const attributeNameLowercase = attributeName.toLowerCase() + + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.1 + // If the attribute-name case-insensitively matches the string + // "Expires", the user agent MUST process the cookie-av as follows. + if (attributeNameLowercase === 'expires') { + // 1. Let the expiry-time be the result of parsing the attribute-value + // as cookie-date (see Section 5.1.1). + const expiryTime = new Date(attributeValue) + + // 2. If the attribute-value failed to parse as a cookie date, ignore + // the cookie-av. + + cookieAttributeList.expires = expiryTime + } else if (attributeNameLowercase === 'max-age') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.2 + // If the attribute-name case-insensitively matches the string "Max- + // Age", the user agent MUST process the cookie-av as follows. + + // 1. If the first character of the attribute-value is not a DIGIT or a + // "-" character, ignore the cookie-av. + const charCode = attributeValue.charCodeAt(0) + + if ((charCode < 48 || charCode > 57) && attributeValue[0] !== '-') { + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList) + } + + // 2. If the remainder of attribute-value contains a non-DIGIT + // character, ignore the cookie-av. + if (!/^\d+$/.test(attributeValue)) { + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList) + } + + // 3. Let delta-seconds be the attribute-value converted to an integer. + const deltaSeconds = Number(attributeValue) + + // 4. Let cookie-age-limit be the maximum age of the cookie (which + // SHOULD be 400 days or less, see Section 4.1.2.2). + + // 5. Set delta-seconds to the smaller of its present value and cookie- + // age-limit. + // deltaSeconds = Math.min(deltaSeconds * 1000, maxExpiresMs) + + // 6. If delta-seconds is less than or equal to zero (0), let expiry- + // time be the earliest representable date and time. Otherwise, let + // the expiry-time be the current date and time plus delta-seconds + // seconds. + // const expiryTime = deltaSeconds <= 0 ? Date.now() : Date.now() + deltaSeconds + + // 7. Append an attribute to the cookie-attribute-list with an + // attribute-name of Max-Age and an attribute-value of expiry-time. + cookieAttributeList.maxAge = deltaSeconds + } else if (attributeNameLowercase === 'domain') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.3 + // If the attribute-name case-insensitively matches the string "Domain", + // the user agent MUST process the cookie-av as follows. + + // 1. Let cookie-domain be the attribute-value. + let cookieDomain = attributeValue + + // 2. If cookie-domain starts with %x2E ("."), let cookie-domain be + // cookie-domain without its leading %x2E ("."). + if (cookieDomain[0] === '.') { + cookieDomain = cookieDomain.slice(1) + } + + // 3. Convert the cookie-domain to lower case. + cookieDomain = cookieDomain.toLowerCase() + + // 4. Append an attribute to the cookie-attribute-list with an + // attribute-name of Domain and an attribute-value of cookie-domain. + cookieAttributeList.domain = cookieDomain + } else if (attributeNameLowercase === 'path') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.4 + // If the attribute-name case-insensitively matches the string "Path", + // the user agent MUST process the cookie-av as follows. + + // 1. If the attribute-value is empty or if the first character of the + // attribute-value is not %x2F ("/"): + let cookiePath = '' + if (attributeValue.length === 0 || attributeValue[0] !== '/') { + // 1. Let cookie-path be the default-path. + cookiePath = '/' + } else { + // Otherwise: + + // 1. Let cookie-path be the attribute-value. + cookiePath = attributeValue + } + + // 2. Append an attribute to the cookie-attribute-list with an + // attribute-name of Path and an attribute-value of cookie-path. + cookieAttributeList.path = cookiePath + } else if (attributeNameLowercase === 'secure') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.5 + // If the attribute-name case-insensitively matches the string "Secure", + // the user agent MUST append an attribute to the cookie-attribute-list + // with an attribute-name of Secure and an empty attribute-value. + + cookieAttributeList.secure = true + } else if (attributeNameLowercase === 'httponly') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.6 + // If the attribute-name case-insensitively matches the string + // "HttpOnly", the user agent MUST append an attribute to the cookie- + // attribute-list with an attribute-name of HttpOnly and an empty + // attribute-value. + + cookieAttributeList.httpOnly = true + } else if (attributeNameLowercase === 'samesite') { + // https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-5.4.7 + // If the attribute-name case-insensitively matches the string + // "SameSite", the user agent MUST process the cookie-av as follows: + + const attributeValueLowercase = attributeValue.toLowerCase() + + // 1. If cookie-av's attribute-value is a case-insensitive match for + // "None", append an attribute to the cookie-attribute-list with an + // attribute-name of "SameSite" and an attribute-value of "None". + if (attributeValueLowercase === 'none') { + cookieAttributeList.sameSite = 'None' + } else if (attributeValueLowercase === 'strict') { + // 2. If cookie-av's attribute-value is a case-insensitive match for + // "Strict", append an attribute to the cookie-attribute-list with + // an attribute-name of "SameSite" and an attribute-value of + // "Strict". + cookieAttributeList.sameSite = 'Strict' + } else if (attributeValueLowercase === 'lax') { + // 3. If cookie-av's attribute-value is a case-insensitive match for + // "Lax", append an attribute to the cookie-attribute-list with an + // attribute-name of "SameSite" and an attribute-value of "Lax". + cookieAttributeList.sameSite = 'Lax' + } + } else { + cookieAttributeList.unparsed ??= [] + + cookieAttributeList.unparsed.push(`${attributeName}=${attributeValue}`) + } + + // 8. Return to Step 1 of this algorithm. + return parseUnparsedAttributes(unparsedAttributes, cookieAttributeList) +} + +module.exports = { + parseSetCookie, + parseUnparsedAttributes +} + + +/***/ }), + +/***/ 7797: +/***/ ((module) => { + +"use strict"; + + +/** + * @param {string} value + * @returns {boolean} + */ +function isCTLExcludingHtab (value) { + for (let i = 0; i < value.length; ++i) { + const code = value.charCodeAt(i) + + if ( + (code >= 0x00 && code <= 0x08) || + (code >= 0x0A && code <= 0x1F) || + code === 0x7F + ) { + return true + } + } + return false +} + +/** + CHAR = + token = 1* + separators = "(" | ")" | "<" | ">" | "@" + | "," | ";" | ":" | "\" | <"> + | "/" | "[" | "]" | "?" | "=" + | "{" | "}" | SP | HT + * @param {string} name + */ +function validateCookieName (name) { + for (let i = 0; i < name.length; ++i) { + const code = name.charCodeAt(i) + + if ( + code < 0x21 || // exclude CTLs (0-31), SP and HT + code > 0x7E || // exclude non-ascii and DEL + code === 0x22 || // " + code === 0x28 || // ( + code === 0x29 || // ) + code === 0x3C || // < + code === 0x3E || // > + code === 0x40 || // @ + code === 0x2C || // , + code === 0x3B || // ; + code === 0x3A || // : + code === 0x5C || // \ + code === 0x2F || // / + code === 0x5B || // [ + code === 0x5D || // ] + code === 0x3F || // ? + code === 0x3D || // = + code === 0x7B || // { + code === 0x7D // } + ) { + throw new Error('Invalid cookie name') + } + } +} + +/** + cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) + cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E + ; US-ASCII characters excluding CTLs, + ; whitespace DQUOTE, comma, semicolon, + ; and backslash + * @param {string} value + */ +function validateCookieValue (value) { + let len = value.length + let i = 0 + + // if the value is wrapped in DQUOTE + if (value[0] === '"') { + if (len === 1 || value[len - 1] !== '"') { + throw new Error('Invalid cookie value') + } + --len + ++i + } + + while (i < len) { + const code = value.charCodeAt(i++) + + if ( + code < 0x21 || // exclude CTLs (0-31) + code > 0x7E || // non-ascii and DEL (127) + code === 0x22 || // " + code === 0x2C || // , + code === 0x3B || // ; + code === 0x5C // \ + ) { + throw new Error('Invalid cookie value') + } + } +} + +/** + * path-value = + * @param {string} path + */ +function validateCookiePath (path) { + for (let i = 0; i < path.length; ++i) { + const code = path.charCodeAt(i) + + if ( + code < 0x20 || // exclude CTLs (0-31) + code > 0x7E || // exclude DEL and non-ascii + code === 0x3B // ; + ) { + throw new Error('Invalid cookie path') + } + } +} + +/** + * ::= | + * + * ::= any one of the 52 alphabetic characters A through Z in + * upper case and a through z in lower case + * + * ::= any one of the ten digits 0 through 9r + * + * @see https://www.rfc-editor.org/rfc/rfc1034#section-3.5 + * @param {number} code + */ +function isLetterOrDigit (code) { + return ( + (code >= 0x30 && code <= 0x39) || // 0-9 + (code >= 0x41 && code <= 0x5A) || // A-Z + (code >= 0x61 && code <= 0x7A) // a-z + ) +} + +/** + * Validates a cookie domain against the "preferred name syntax". + * + * ::= | " " + * ::=