diff --git a/.github/workflows/e2e-cache-freethreaded.yml b/.github/workflows/e2e-cache-freethreaded.yml index 21b46329..943a4d0f 100644 --- a/.github/workflows/e2e-cache-freethreaded.yml +++ b/.github/workflows/e2e-cache-freethreaded.yml @@ -36,7 +36,7 @@ jobs: - os: windows-11-arm python-version: 3.13.14t steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python uses: ./ with: @@ -66,7 +66,7 @@ jobs: - os: windows-11-arm python-version: 3.13.14t steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python id: cache-pipenv uses: ./ @@ -106,7 +106,7 @@ jobs: ] python-version: [3.13.14, 3.14.6] steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Install poetry run: pipx install poetry - name: Init pyproject.toml @@ -141,7 +141,7 @@ jobs: - os: windows-11-arm python-version: 3.13.14t steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python uses: ./ with: @@ -172,7 +172,7 @@ jobs: - os: windows-11-arm python-version: 3.13.14t steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python id: cache-pipenv uses: ./ @@ -216,7 +216,7 @@ jobs: - os: windows-11-arm python-version: 3.13.14t steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python uses: ./ with: @@ -247,7 +247,7 @@ jobs: - os: windows-11-arm python-version: 3.13.14t steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python uses: ./ with: diff --git a/.github/workflows/e2e-cache.yml b/.github/workflows/e2e-cache.yml index 97047f39..19b362b0 100644 --- a/.github/workflows/e2e-cache.yml +++ b/.github/workflows/e2e-cache.yml @@ -42,7 +42,7 @@ jobs: python-version: '3.10' steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python uses: ./ with: @@ -76,7 +76,7 @@ jobs: - os: windows-11-arm python-version: pypy-3.11-v7.x steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python id: cache-pipenv uses: ./ @@ -131,7 +131,7 @@ jobs: - os: windows-11-arm python-version: '3.10' steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Install poetry run: | pipx install poetry @@ -175,7 +175,7 @@ jobs: - os: windows-11-arm python-version: '3.10' steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python uses: ./ with: @@ -210,7 +210,7 @@ jobs: - os: windows-11-arm python-version: pypy-3.11-v7.x steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python id: cache-pipenv uses: ./ @@ -259,7 +259,7 @@ jobs: - os: windows-11-arm python-version: '3.10' steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python uses: ./ with: @@ -290,7 +290,7 @@ jobs: - os: windows-11-arm python-version: '3.10' steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python uses: ./ with: diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 0fbe5db2..eaaded01 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -29,7 +29,7 @@ jobs: ] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Run with setup-python 3.10.11 if: matrix.operating-system != 'windows-11-arm' diff --git a/.github/workflows/publish-immutable-actions.yml b/.github/workflows/publish-immutable-actions.yml index 4776796e..a4bd83f4 100644 --- a/.github/workflows/publish-immutable-actions.yml +++ b/.github/workflows/publish-immutable-actions.yml @@ -14,7 +14,7 @@ jobs: steps: - name: Checking out - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Publish id: publish uses: actions/publish-immutable-action@4bc8754ffc40f27910afb20287dbbbb675a4e978 # v0.0.4 diff --git a/.github/workflows/test-graalpy.yml b/.github/workflows/test-graalpy.yml index f7abb2ce..ef8ab398 100644 --- a/.github/workflows/test-graalpy.yml +++ b/.github/workflows/test-graalpy.yml @@ -34,7 +34,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python ${{ matrix.graalpy }} id: setup-python @@ -85,7 +85,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python ${{ matrix.graalpy }} id: setup-python @@ -107,7 +107,7 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel] steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup GraalPy and check latest uses: ./ id: graalpy diff --git a/.github/workflows/test-pypy.yml b/.github/workflows/test-pypy.yml index 1957a7ac..82888b11 100644 --- a/.github/workflows/test-pypy.yml +++ b/.github/workflows/test-pypy.yml @@ -39,7 +39,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python ${{ matrix.pypy }} id: setup-python @@ -90,7 +90,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python ${{ matrix.pypy }} id: setup-python @@ -141,7 +141,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python ${{ matrix.pypy }} id: setup-python @@ -171,7 +171,7 @@ jobs: macos-latest ] steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup PyPy and check latest uses: ./ with: @@ -212,7 +212,7 @@ jobs: macos-latest ] steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup PyPy and check latest uses: ./ with: diff --git a/.github/workflows/test-python-freethreaded.yml b/.github/workflows/test-python-freethreaded.yml index be29ed1a..708213bc 100644 --- a/.github/workflows/test-python-freethreaded.yml +++ b/.github/workflows/test-python-freethreaded.yml @@ -33,7 +33,7 @@ jobs: python: [3.13.14t, 3.14.6t] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python ${{ matrix.python }} id: setup-python @@ -70,7 +70,7 @@ jobs: python: [3.13.14t, 3.14.6t] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-version-file ${{ matrix.python }} run: echo ${{ matrix.python }} > .python-version @@ -110,7 +110,7 @@ jobs: python: [3.13.14t, 3.14.6t] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-version-file ${{ matrix.python }} run: echo ${{ matrix.python }} > .python-version @@ -148,7 +148,7 @@ jobs: python: [3.13.14, 3.14.6] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-version-file ${{ matrix.python }} run: | @@ -189,7 +189,7 @@ jobs: python: [3.13.14, 3.14.6] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-version-file ${{ matrix.python }} run: | @@ -230,7 +230,7 @@ jobs: python: [3.13.14t, 3.14.6t, 3.15t-dev] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-tool-versions-file ${{ matrix.python }} run: | @@ -261,7 +261,7 @@ jobs: python: [3.13t, 3.15t-dev] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-version-file ${{ matrix.python }} run: | @@ -301,7 +301,7 @@ jobs: python: [3.13.14t, 3.14.6t, 3.15t-dev] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-version-file ${{ matrix.python }} run: | @@ -340,7 +340,7 @@ jobs: ] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python 3.15.0-beta.3 id: setup-python @@ -377,7 +377,7 @@ jobs: ] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python 3.15t-dev id: setup-python @@ -414,7 +414,7 @@ jobs: ] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python 3.15t id: setup-python @@ -453,7 +453,7 @@ jobs: python: [3.13.14t, 3.14.6t] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python ${{ matrix.python }} id: setup-python @@ -485,7 +485,7 @@ jobs: ] python-version: [3.13t, 3.15t-dev] steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python and check latest id: setup-python uses: ./ @@ -511,7 +511,7 @@ jobs: macos-latest ] steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup multiple Python versions id: setup-python uses: ./ @@ -542,7 +542,7 @@ jobs: python: [3.13.14, 3.14.6, 3.15-dev, 3.15.0-beta.3] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python ${{ matrix.python }} id: setup-python uses: ./ diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index f963cdda..78d48cab 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -36,7 +36,7 @@ jobs: python: 3.10.11 steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python ${{ matrix.python }} id: setup-python @@ -61,32 +61,6 @@ jobs: - name: Run simple code run: python -c 'import math; print(math.factorial(5))' - setup-versions-via-mirror-input: - name: 'Setup via explicit mirror input: ${{ matrix.os }}' - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - steps: - - name: Checkout - uses: actions/checkout@v6 - - # The refs/heads/ form serves the same manifest as the default mirror and - # exercises the refs/heads/{branch} -> {branch} parsing on the API path. - # check-latest forces a manifest fetch even though 3.12 is preinstalled, - # so the job actually contacts the mirror instead of short-circuiting on - # the tool cache. - - name: setup-python with explicit mirror - uses: ./ - with: - python-version: 3.12 - check-latest: true - mirror: https://raw.githubusercontent.com/actions/python-versions/refs/heads/main - - - name: Run simple code - run: python -c 'import sys; print(sys.version)' - setup-versions-from-file: name: Setup ${{ matrix.python }} ${{ matrix.os }} version file runs-on: ${{ matrix.os }} @@ -109,7 +83,7 @@ jobs: python: 3.10.11 steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-version-file ${{ matrix.python }} run: echo ${{ matrix.python }} > .python-version @@ -159,7 +133,7 @@ jobs: python: 3.10.11 steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-version-file ${{ matrix.python }} run: echo ${{ matrix.python }} > .python-version @@ -207,7 +181,7 @@ jobs: python: 3.10.11 steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-version-file ${{ matrix.python }} run: | @@ -260,7 +234,7 @@ jobs: python: 3.10.11 steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-version-file ${{ matrix.python }} run: | @@ -317,7 +291,7 @@ jobs: python: pypy3.11-7.3.18 steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-tool-versions-file ${{ matrix.python }} run: | @@ -351,7 +325,7 @@ jobs: python: 3.10.11 steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-version-file ${{ matrix.python }} run: | @@ -404,7 +378,7 @@ jobs: python: 3.10.11 steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: build-version-file ${{ matrix.python }} run: | @@ -453,7 +427,7 @@ jobs: ] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python 3.15.0-beta.3 id: setup-python @@ -496,7 +470,7 @@ jobs: ] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python 3.15-dev id: setup-python @@ -533,7 +507,7 @@ jobs: ] steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python 3.15 id: setup-python @@ -575,7 +549,7 @@ jobs: python: '3.10' steps: - name: Checkout - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: setup-python ${{ matrix.python }} id: setup-python @@ -610,7 +584,7 @@ jobs: - os: windows-11-arm python-version: '3.10' steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python and check latest uses: ./ with: @@ -642,7 +616,7 @@ jobs: macos-latest ] steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - name: Setup Python and check latest uses: ./ with: diff --git a/.licenses/npm/@nodable/entities.dep.yml b/.licenses/npm/@nodable/entities.dep.yml index b02eaad6..c11ab9f1 100644 --- a/.licenses/npm/@nodable/entities.dep.yml +++ b/.licenses/npm/@nodable/entities.dep.yml @@ -1,9 +1,9 @@ --- name: "@nodable/entities" -version: 3.0.0 +version: 2.2.0 type: npm summary: Entity parser for XML, HTML, External entites with security and NCR control -homepage: +homepage: license: mit licenses: - sources: README.md diff --git a/.licenses/npm/balanced-match-1.0.2.dep.yml b/.licenses/npm/balanced-match-1.0.2.dep.yml new file mode 100644 index 00000000..36095592 --- /dev/null +++ b/.licenses/npm/balanced-match-1.0.2.dep.yml @@ -0,0 +1,55 @@ +--- +name: balanced-match +version: 1.0.2 +type: npm +summary: Match balanced character pairs, like "{" and "}" +homepage: https://github.com/juliangruber/balanced-match +license: mit +licenses: +- sources: LICENSE.md + text: | + (MIT) + + Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +- sources: README.md + text: |- + (MIT) + + Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +notices: [] diff --git a/.licenses/npm/balanced-match.dep.yml b/.licenses/npm/balanced-match-4.0.4.dep.yml similarity index 100% rename from .licenses/npm/balanced-match.dep.yml rename to .licenses/npm/balanced-match-4.0.4.dep.yml diff --git a/.licenses/npm/brace-expansion-1.1.15.dep.yml b/.licenses/npm/brace-expansion-1.1.15.dep.yml new file mode 100644 index 00000000..cc67fe75 --- /dev/null +++ b/.licenses/npm/brace-expansion-1.1.15.dep.yml @@ -0,0 +1,55 @@ +--- +name: brace-expansion +version: 1.1.15 +type: npm +summary: Brace expansion as known from sh/bash +homepage: https://github.com/juliangruber/brace-expansion +license: mit +licenses: +- sources: LICENSE + text: | + MIT License + + Copyright (c) 2013 Julian Gruber + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +- sources: README.md + text: |- + (MIT) + + Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +notices: [] diff --git a/.licenses/npm/brace-expansion.dep.yml b/.licenses/npm/brace-expansion-5.0.6.dep.yml similarity index 98% rename from .licenses/npm/brace-expansion.dep.yml rename to .licenses/npm/brace-expansion-5.0.6.dep.yml index fd7e18fe..af66077e 100644 --- a/.licenses/npm/brace-expansion.dep.yml +++ b/.licenses/npm/brace-expansion-5.0.6.dep.yml @@ -1,6 +1,6 @@ --- name: brace-expansion -version: 5.0.9 +version: 5.0.6 type: npm summary: Brace expansion as known from sh/bash homepage: diff --git a/.licenses/npm/concat-map.dep.yml b/.licenses/npm/concat-map.dep.yml new file mode 100644 index 00000000..20216b95 --- /dev/null +++ b/.licenses/npm/concat-map.dep.yml @@ -0,0 +1,31 @@ +--- +name: concat-map +version: 0.0.1 +type: npm +summary: concatenative mapdashery +homepage: https://github.com/substack/node-concat-map#readme +license: other +licenses: +- sources: LICENSE + text: | + This software is released under the MIT license: + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +- sources: README.markdown + text: MIT +notices: [] diff --git a/.licenses/npm/fast-xml-parser.dep.yml b/.licenses/npm/fast-xml-parser.dep.yml index 07fd88dd..8f13da7e 100644 --- a/.licenses/npm/fast-xml-parser.dep.yml +++ b/.licenses/npm/fast-xml-parser.dep.yml @@ -1,6 +1,6 @@ --- name: fast-xml-parser -version: 5.10.1 +version: 5.9.3 type: npm summary: Validate XML, Parse XML, Build XML without C/C++ based libraries homepage: diff --git a/.licenses/npm/is-unsafe.dep.yml b/.licenses/npm/is-unsafe.dep.yml index a0e275d4..02edc8dd 100644 --- a/.licenses/npm/is-unsafe.dep.yml +++ b/.licenses/npm/is-unsafe.dep.yml @@ -1,10 +1,10 @@ --- name: is-unsafe -version: 2.0.0 +version: 1.0.1 type: npm summary: Zero-dependency, DOM-free, pure predicate for detecting unsafe strings across HTML, XML, SVG, SQL, SHELL, and REGEX contexts -homepage: +homepage: license: mit licenses: - sources: LICENSE diff --git a/.licenses/npm/path-expression-matcher.dep.yml b/.licenses/npm/path-expression-matcher.dep.yml index a671e7d7..dcb076d5 100644 --- a/.licenses/npm/path-expression-matcher.dep.yml +++ b/.licenses/npm/path-expression-matcher.dep.yml @@ -1,6 +1,6 @@ --- name: path-expression-matcher -version: 1.6.2 +version: 1.6.0 type: npm summary: Efficient path tracking and pattern matching for XML/JSON parsers homepage: https://github.com/NaturalIntelligence/path-expression-matcher#readme diff --git a/.licenses/npm/undici.dep.yml b/.licenses/npm/undici.dep.yml index b339a448..c46a5c7b 100644 --- a/.licenses/npm/undici.dep.yml +++ b/.licenses/npm/undici.dep.yml @@ -1,6 +1,6 @@ --- name: undici -version: 6.28.0 +version: 6.27.0 type: npm summary: An HTTP/1.1 client, written from scratch for Node.js homepage: https://undici.nodejs.org diff --git a/.licenses/npm/xml-naming-0.3.0.dep.yml b/.licenses/npm/xml-naming-0.3.0.dep.yml deleted file mode 100644 index bfc32c71..00000000 --- a/.licenses/npm/xml-naming-0.3.0.dep.yml +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: xml-naming -version: 0.3.0 -type: npm -summary: Validates XML name productions — Name, NCName, QName, NMToken, NMTokens — - for XML 1.0 and 1.1 -homepage: -license: mit -licenses: -- sources: LICENSE - text: | - MIT License - - Copyright (c) 2026 Natural Intelligence - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -- sources: README.md - text: MIT -notices: [] diff --git a/.licenses/npm/xml-naming-0.1.0.dep.yml b/.licenses/npm/xml-naming.dep.yml similarity index 95% rename from .licenses/npm/xml-naming-0.1.0.dep.yml rename to .licenses/npm/xml-naming.dep.yml index 6df51e86..6871fb55 100644 --- a/.licenses/npm/xml-naming-0.1.0.dep.yml +++ b/.licenses/npm/xml-naming.dep.yml @@ -4,7 +4,7 @@ version: 0.1.0 type: npm summary: Validates XML name productions — Name, NCName, QName, NMToken, NMTokens — for XML 1.0 and 1.1 -homepage: +homepage: license: mit licenses: - sources: README.md diff --git a/__tests__/cache-restore.test.ts b/__tests__/cache-restore.test.ts index 4b549f51..914bd5e5 100644 --- a/__tests__/cache-restore.test.ts +++ b/__tests__/cache-restore.test.ts @@ -276,9 +276,7 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py await expect(cacheDistributor.restoreCache()).rejects.toThrow( `No file in ${process.cwd()} matched to [${cacheDependencyPath .split('\n') - .join( - ',' - )}] for ${packageManager}. Make sure you have checked out the target repository, or consider removing the cache step if there are no dependencies to cache.` + .join(',')}], make sure you have checked out the target repository` ); } ); diff --git a/__tests__/install-python-mirror.test.ts b/__tests__/install-python-mirror.test.ts deleted file mode 100644 index 75cdac9c..00000000 --- a/__tests__/install-python-mirror.test.ts +++ /dev/null @@ -1,590 +0,0 @@ -import {jest, describe, it, expect, beforeEach} from '@jest/globals'; - -// Inputs are read lazily by install-python.ts, so each test can set them -// before invoking the function under test. -const inputs: Record = {}; - -// Mock @actions/http-client -jest.unstable_mockModule('@actions/http-client', () => ({ - HttpClient: jest.fn().mockImplementation(() => ({ - getJson: jest.fn() - })), - HttpClientError: class HttpClientError extends Error {}, - HttpCodes: { - OK: 200, - NotFound: 404, - InternalServerError: 500 - } -})); - -// Mock @actions/cache (needed transitively by utils.ts) -jest.unstable_mockModule('@actions/cache', () => ({ - saveCache: jest.fn(), - restoreCache: jest.fn(), - isFeatureAvailable: jest.fn() -})); - -// Mock @actions/tool-cache -jest.unstable_mockModule('@actions/tool-cache', () => ({ - getManifestFromRepo: jest.fn(), - downloadTool: jest.fn(), - extractTar: jest.fn(), - extractZip: jest.fn(), - HTTPError: class HTTPError extends Error {} -})); - -// Mock @actions/core (needed by install-python.ts) -jest.unstable_mockModule('@actions/core', () => ({ - info: jest.fn(), - warning: jest.fn(), - debug: jest.fn(), - error: jest.fn(), - notice: jest.fn(), - setFailed: jest.fn(), - setOutput: jest.fn(), - getInput: jest.fn(), - getBooleanInput: jest.fn(), - getMultilineInput: jest.fn(), - addPath: jest.fn(), - exportVariable: jest.fn(), - saveState: jest.fn(), - getState: jest.fn(), - setSecret: jest.fn(), - isDebug: jest.fn(() => false), - startGroup: jest.fn(), - endGroup: jest.fn(), - group: jest.fn((_name: string, fn: () => Promise) => fn()), - toPlatformPath: jest.fn((p: string) => p), - toWin32Path: jest.fn((p: string) => p), - toPosixPath: jest.fn((p: string) => p) -})); - -// Mock @actions/exec (needed by install-python.ts) -jest.unstable_mockModule('@actions/exec', () => ({ - exec: jest.fn(), - getExecOutput: jest.fn() -})); - -// Import real utils BEFORE mock registration to get real function references -const realUtils = await import('../src/utils.js'); - -// Pin the platform so the download/extract assertions below behave the same -// on every runner OS. -jest.unstable_mockModule('../src/utils.js', () => ({ - ...realUtils, - IS_WINDOWS: false, - IS_LINUX: false -})); - -// Dynamic imports after mocking -const core = await import('@actions/core'); -const httpm = await import('@actions/http-client'); -const tc = await import('@actions/tool-cache'); -const { - getManifestUrl, - getManifest, - getManifestFromRepo, - getManifestFromURL, - resolveRepoCoords, - isMirrorCustomized, - installCpythonFromRelease -} = await import('../src/install-python.js'); - -const DEFAULT_MIRROR = - 'https://raw.githubusercontent.com/actions/python-versions/main'; - -const mockManifest = [ - { - version: '1.0.0', - stable: true, - files: [ - { - filename: 'tool-v1.0.0-linux-x64.tar.gz', - platform: 'linux', - arch: 'x64', - download_url: 'https://example.com/tool-v1.0.0-linux-x64.tar.gz' - } - ] - } -]; - -function setInputs(values: Record) { - Object.assign(inputs, values); -} - -beforeEach(() => { - jest.resetAllMocks(); - for (const key of Object.keys(inputs)) { - delete inputs[key]; - } - (core.getInput as jest.Mock).mockImplementation( - (name: string) => inputs[name] ?? '' - ); -}); - -describe('getManifestUrl', () => { - it('defaults to the actions/python-versions manifest', () => { - expect(getManifestUrl()).toBe(`${DEFAULT_MIRROR}/versions-manifest.json`); - }); - - it('appends versions-manifest.json to a custom mirror', () => { - setInputs({mirror: 'https://mirror.example/py'}); - expect(getManifestUrl()).toBe( - 'https://mirror.example/py/versions-manifest.json' - ); - }); - - it('strips trailing slashes from the mirror', () => { - setInputs({mirror: 'https://mirror.example/py///'}); - expect(getManifestUrl()).toBe( - 'https://mirror.example/py/versions-manifest.json' - ); - }); - - it('throws on a mirror that is not a valid URL', () => { - setInputs({mirror: 'not a url'}); - expect(() => getManifestUrl()).toThrow(/Invalid 'mirror' URL/); - }); - - it('keeps throwing the same error when called repeatedly', () => { - setInputs({mirror: 'not a url'}); - expect(() => getManifestUrl()).toThrow(/Invalid 'mirror' URL/); - // Memoized, so the second call must not silently succeed or change shape — - // find-python.ts calls this while building the "version not found" message. - expect(() => getManifestUrl()).toThrow(/Invalid 'mirror' URL/); - }); - - it('treats an invalid mirror as fatal on the auth path too', async () => { - // getManifestUrl() throws on a bad mirror; the auth resolution must agree - // rather than swallow the error and quietly skip the mirror-token branch. - setInputs({'mirror-token': 'MTOK', mirror: 'not a url'}); - (tc.downloadTool as jest.Mock).mockResolvedValue('/tmp/py.tgz'); - (tc.extractTar as jest.Mock).mockResolvedValue('/tmp/extracted'); - - const release = { - version: '3.12.0', - stable: true, - files: [ - { - filename: 'python-3.12.0-linux-x64.tar.gz', - platform: 'linux', - arch: 'x64', - download_url: 'https://cdn.example/py.tar.gz' - } - ] - } as any; - - await expect(installCpythonFromRelease(release)).rejects.toThrow( - /Invalid 'mirror' URL/ - ); - }); -}); - -describe('isMirrorCustomized', () => { - it('is false when the mirror input is empty', () => { - expect(isMirrorCustomized()).toBe(false); - }); - - it('is false when the mirror input equals the default', () => { - // action.yml gives `mirror` this exact default, so getInput() returns it on - // every run where the user did not set one. The PyPy/GraalPy warning must - // not fire in that case. - setInputs({mirror: DEFAULT_MIRROR}); - expect(isMirrorCustomized()).toBe(false); - }); - - it('is false when the mirror input is the default with trailing slashes', () => { - setInputs({mirror: `${DEFAULT_MIRROR}///`}); - expect(isMirrorCustomized()).toBe(false); - }); - - it('is true when the mirror input is a custom URL', () => { - setInputs({mirror: 'https://mirror.example/py'}); - expect(isMirrorCustomized()).toBe(true); - }); -}); - -describe('resolveRepoCoords', () => { - it('warns and returns null for a raw.githubusercontent.com mirror with a slash in the branch', () => { - setInputs({ - mirror: 'https://raw.githubusercontent.com/foo/bar/feature/riscv' - }); - - expect(resolveRepoCoords()).toBeNull(); - expect(core.warning).toHaveBeenCalledWith( - expect.stringMatching(/Branch names containing '\/' are not supported/) - ); - }); - - it('parses the refs/heads/{branch} form to the bare branch without warning', () => { - setInputs({ - mirror: - 'https://raw.githubusercontent.com/actions/python-versions/refs/heads/main' - }); - - expect(resolveRepoCoords()).toEqual({ - owner: 'actions', - repo: 'python-versions', - branch: 'main' - }); - // refs/heads/main is a valid single branch, so it must route through the - // API path and never hit the slash-branch warning. - expect(core.warning).not.toHaveBeenCalled(); - }); - - it('does not warn for a non-GitHub mirror', () => { - setInputs({mirror: 'https://mirror.example/py'}); - - expect(resolveRepoCoords()).toBeNull(); - expect(core.warning).not.toHaveBeenCalled(); - }); -}); - -describe('getManifestFromRepo mirror resolution', () => { - it('resolves the default mirror to actions/python-versions@main with token', async () => { - setInputs({token: 'TKN'}); - (tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest); - - await getManifestFromRepo(); - - expect(tc.getManifestFromRepo).toHaveBeenCalledWith( - 'actions', - 'python-versions', - 'token TKN', - 'main' - ); - }); - - it('extracts owner/repo/branch from a custom raw.githubusercontent.com mirror', async () => { - setInputs({ - token: 'TKN', - mirror: 'https://raw.githubusercontent.com/foo/bar/dev' - }); - (tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest); - - await getManifestFromRepo(); - - expect(tc.getManifestFromRepo).toHaveBeenCalledWith( - 'foo', - 'bar', - 'token TKN', - 'dev' - ); - }); - - it('strips a trailing slash before extracting the branch', async () => { - setInputs({ - token: 'TKN', - mirror: 'https://raw.githubusercontent.com/foo/bar/main/' - }); - (tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest); - - await getManifestFromRepo(); - - expect(tc.getManifestFromRepo).toHaveBeenCalledWith( - 'foo', - 'bar', - 'token TKN', - 'main' - ); - }); - - it('resolves the refs/heads/{branch} form to the bare branch for the API', async () => { - setInputs({ - token: 'TKN', - mirror: 'https://raw.githubusercontent.com/foo/bar/refs/heads/main' - }); - (tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest); - - await getManifestFromRepo(); - - // The GitHub tree API takes a bare branch, so the refs/heads/ prefix must - // be stripped rather than passed through as part of the branch name. - expect(tc.getManifestFromRepo).toHaveBeenCalledWith( - 'foo', - 'bar', - 'token TKN', - 'main' - ); - }); - - it('returns null for a non-GitHub mirror so the caller uses the raw URL', () => { - setInputs({mirror: 'https://mirror.example/py'}); - expect(resolveRepoCoords()).toBeNull(); - expect(tc.getManifestFromRepo).not.toHaveBeenCalled(); - }); - - it('prefers mirror-token over token for the GitHub API call', async () => { - setInputs({ - token: 'TKN', - 'mirror-token': 'MTOK', - mirror: 'https://raw.githubusercontent.com/foo/bar/main' - }); - (tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest); - - await getManifestFromRepo(); - - // The API requires the `token ` prefix, and naming a repo mirror is explicit intent to - // read that repo, so mirror-token is prefixed here even though downloads send it verbatim. - expect(tc.getManifestFromRepo).toHaveBeenCalledWith( - 'foo', - 'bar', - 'token MTOK', - 'main' - ); - }); - - it('sends no auth when neither token nor mirror-token is set', async () => { - (tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest); - - await getManifestFromRepo(); - - expect(tc.getManifestFromRepo).toHaveBeenCalledWith( - 'actions', - 'python-versions', - undefined, - 'main' - ); - }); -}); - -describe('getManifestFromURL mirror resolution', () => { - it('fetches {mirror}/versions-manifest.json without auth when no mirror-token is set', async () => { - setInputs({token: 'TKN', mirror: 'https://mirror.example/py'}); - const getJson = jest.fn(async () => ({result: mockManifest})); - (httpm.HttpClient as jest.Mock).mockImplementation(() => ({getJson})); - - await getManifestFromURL(); - - // `token` must not reach a non-GitHub mirror. - expect(getJson).toHaveBeenCalledWith( - 'https://mirror.example/py/versions-manifest.json', - undefined - ); - }); - - it('sends mirror-token verbatim on the manifest fetch', async () => { - setInputs({ - token: 'TKN', - 'mirror-token': 'Bearer MTOK', - mirror: 'https://mirror.example/py' - }); - const getJson = jest.fn(async () => ({result: mockManifest})); - (httpm.HttpClient as jest.Mock).mockImplementation(() => ({getJson})); - - await getManifestFromURL(); - - expect(getJson).toHaveBeenCalledWith( - 'https://mirror.example/py/versions-manifest.json', - {authorization: 'Bearer MTOK'} - ); - }); - - it('sends token as a prefixed header for a GitHub-hosted raw manifest', async () => { - // A slash-branch URL genuinely falls to the direct-URL path (it does not - // match the {owner}/{repo}/{branch} shape). raw.githubusercontent.com is a - // GitHub host, so the token is still attached here — contradicting any - // claim that the fallback fetch is anonymous. - setInputs({ - token: 'TKN', - mirror: 'https://raw.githubusercontent.com/foo/bar/feature/riscv' - }); - const getJson = jest.fn(async () => ({result: mockManifest})); - (httpm.HttpClient as jest.Mock).mockImplementation(() => ({getJson})); - - await getManifestFromURL(); - - expect(getJson).toHaveBeenCalledWith( - 'https://raw.githubusercontent.com/foo/bar/feature/riscv/versions-manifest.json', - {authorization: 'token TKN'} - ); - }); -}); - -describe('getManifest source routing', () => { - it('skips the GitHub API entirely for a non-GitHub mirror', async () => { - setInputs({mirror: 'https://mirror.example/py'}); - const getJson = jest.fn(async () => ({result: mockManifest})); - (httpm.HttpClient as jest.Mock).mockImplementation(() => ({getJson})); - - await expect(getManifest()).resolves.toEqual(mockManifest); - - // Routing straight to the URL fetch avoids 3 retries with backoff on a - // call that could never succeed. - expect(tc.getManifestFromRepo).not.toHaveBeenCalled(); - expect(getJson).toHaveBeenCalledTimes(1); - }); - - it('uses the GitHub API for a repo mirror without touching the raw URL', async () => { - setInputs({token: 'TKN'}); - (tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest); - const getJson = jest.fn(async () => ({result: mockManifest})); - (httpm.HttpClient as jest.Mock).mockImplementation(() => ({getJson})); - - await expect(getManifest()).resolves.toEqual(mockManifest); - - expect(tc.getManifestFromRepo).toHaveBeenCalledTimes(1); - expect(getJson).not.toHaveBeenCalled(); - }); -}); - -describe('installCpythonFromRelease auth gating', () => { - const makeRelease = (downloadUrl: string) => - ({ - version: '3.12.0', - stable: true, - release_url: '', - files: [ - { - filename: 'python-3.12.0-linux-x64.tar.gz', - platform: 'linux', - platform_version: '', - arch: 'x64', - download_url: downloadUrl - } - ] - }) as any; - - // Returns the auth argument tc.downloadTool was called with. - async function downloadAuthFor(downloadUrl: string) { - (tc.downloadTool as jest.Mock).mockResolvedValue('/tmp/py.tgz'); - (tc.extractTar as jest.Mock).mockResolvedValue('/tmp/extracted'); - - await installCpythonFromRelease(makeRelease(downloadUrl)); - - const call = (tc.downloadTool as jest.Mock).mock.calls[0]; - expect(call[0]).toBe(downloadUrl); - return call[2]; - } - - it('forwards token to github.com download URLs', async () => { - setInputs({token: 'TKN'}); - await expect( - downloadAuthFor( - 'https://github.com/actions/python-versions/releases/download/3.12.0-x/python-3.12.0-linux-x64.tar.gz' - ) - ).resolves.toBe('token TKN'); - }); - - it('forwards token to api.github.com download URLs', async () => { - setInputs({token: 'TKN'}); - await expect( - downloadAuthFor('https://api.github.com/repos/x/y/tarball/main') - ).resolves.toBe('token TKN'); - }); - - it('forwards token to *.githubusercontent.com download URLs', async () => { - setInputs({token: 'TKN'}); - await expect( - downloadAuthFor('https://objects.githubusercontent.com/x/python.tar.gz') - ).resolves.toBe('token TKN'); - }); - - it('does NOT forward token to a non-GitHub download URL', async () => { - setInputs({token: 'TKN', mirror: 'https://cdn.example'}); - await expect( - downloadAuthFor('https://cdn.example/py.tar.gz') - ).resolves.toBeUndefined(); - }); - - it('does NOT forward token to a lookalike host', async () => { - setInputs({token: 'TKN', mirror: 'https://evil-github.com'}); - await expect( - downloadAuthFor('https://evil-github.com/py.tar.gz') - ).resolves.toBeUndefined(); - }); - - it('forwards mirror-token verbatim to the mirror host', async () => { - setInputs({ - token: 'TKN', - 'mirror-token': 'Bearer MTOK', - mirror: 'https://cdn.example' - }); - await expect( - downloadAuthFor('https://cdn.example/py.tar.gz') - ).resolves.toBe('Bearer MTOK'); - }); - - it('withholds mirror-token from a same-host download URL on a different scheme', async () => { - setInputs({ - 'mirror-token': 'Bearer MTOK', - mirror: 'https://cdn.example' - }); - // The mirror is https, but the manifest points a download_url at http on - // the same host. Matching on origin (scheme + host + port) rather than host - // alone keeps the token from going out in cleartext. - await expect( - downloadAuthFor('http://cdn.example/py.tar.gz') - ).resolves.toBeUndefined(); - }); - - it('withholds mirror-token from a same-host download URL on a different port', async () => { - setInputs({ - 'mirror-token': 'Bearer MTOK', - mirror: 'https://cdn.example' - }); - // Different port is a different origin, so the nominated credential must - // not follow. - await expect( - downloadAuthFor('https://cdn.example:8443/py.tar.gz') - ).resolves.toBeUndefined(); - }); - - it('sends mirror-token to a same-origin download URL on an explicit port', async () => { - setInputs({ - 'mirror-token': 'Bearer MTOK', - mirror: 'https://cdn.example:8443' - }); - await expect( - downloadAuthFor('https://cdn.example:8443/py.tar.gz') - ).resolves.toBe('Bearer MTOK'); - }); - - it('withholds mirror-token from an incidental GitHub host and uses token there', async () => { - setInputs({ - token: 'TKN', - 'mirror-token': 'MTOK', - mirror: 'https://cdn.example' - }); - // A manifest hosted on the private mirror may still point release assets at - // GitHub; the private credential must not follow them there. - await expect( - downloadAuthFor('https://objects.githubusercontent.com/x/python.tar.gz') - ).resolves.toBe('token TKN'); - }); - - it('withholds mirror-token from a GitHub host when no token is set', async () => { - setInputs({'mirror-token': 'MTOK', mirror: 'https://cdn.example'}); - await expect( - downloadAuthFor('https://objects.githubusercontent.com/x/python.tar.gz') - ).resolves.toBeUndefined(); - }); - - it('withholds mirror-token from a third host that is neither the mirror nor GitHub', async () => { - setInputs({ - token: 'TKN', - 'mirror-token': 'MTOK', - mirror: 'https://cdn.example' - }); - await expect( - downloadAuthFor('https://other.example/py.tar.gz') - ).resolves.toBeUndefined(); - }); - - it('uses mirror-token for a GitHub mirror host when it is the nominated host', async () => { - setInputs({ - token: 'TKN', - 'mirror-token': 'token MTOK', - mirror: 'https://raw.githubusercontent.com/foo/bar/main' - }); - await expect( - downloadAuthFor('https://raw.githubusercontent.com/foo/bar/py.tar.gz') - ).resolves.toBe('token MTOK'); - }); - - it('sends no auth when no tokens are configured', async () => { - await expect( - downloadAuthFor('https://github.com/o/r/releases/download/v/py.tar.gz') - ).resolves.toBeUndefined(); - }); -}); diff --git a/action.yml b/action.yml index 645de4f5..df6c8235 100644 --- a/action.yml +++ b/action.yml @@ -16,14 +16,8 @@ inputs: description: "Set this option if you want the action to check for the latest available version that satisfies the version spec." default: false token: - description: "The token used to authenticate when fetching Python distributions from https://github.com/actions/python-versions. When running this action on github.com, the default value is sufficient. When running on GHES, you can pass a personal access token for github.com if you are experiencing rate limiting. This token is only sent to GitHub-owned hosts, never to a custom 'mirror'." + description: "The token used to authenticate when fetching Python distributions from https://github.com/actions/python-versions. When running this action on github.com, the default value is sufficient. When running on GHES, you can pass a personal access token for github.com if you are experiencing rate limiting." default: ${{ github.server_url == 'https://github.com' && github.token || '' }} - mirror: - description: "Base URL for downloading Python distributions (only applies to CPython; PyPy and GraalPy are unaffected). Defaults to https://raw.githubusercontent.com/actions/python-versions/main. See docs/advanced-usage.md for details." - default: "https://raw.githubusercontent.com/actions/python-versions/main" - mirror-token: - description: "Token used to authenticate requests to the host named in 'mirror'. Sent verbatim as the Authorization header, so include a scheme if your mirror needs one (e.g. 'Bearer ')." - required: false cache-dependency-path: description: "Used to specify the path to dependency files. Supports wildcards or a list of file names for caching multiple dependencies." update-environment: diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js index 019704a0..48944752 100644 --- a/dist/cache-save/index.js +++ b/dist/cache-save/index.js @@ -1,6 +1,285 @@ import { createRequire as __WEBPACK_EXTERNAL_createRequire } from "module"; /******/ var __webpack_modules__ = ({ +/***/ 8638: +/***/ ((module) => { + + +module.exports = balanced; +function balanced(a, b, str) { + if (a instanceof RegExp) a = maybeMatch(a, str); + if (b instanceof RegExp) b = maybeMatch(b, str); + + var r = range(a, b, str); + + return r && { + start: r[0], + end: r[1], + pre: str.slice(0, r[0]), + body: str.slice(r[0] + a.length, r[1]), + post: str.slice(r[1] + b.length) + }; +} + +function maybeMatch(reg, str) { + var m = str.match(reg); + return m ? m[0] : null; +} + +balanced.range = range; +function range(a, b, str) { + var begs, beg, left, right, result; + var ai = str.indexOf(a); + var bi = str.indexOf(b, ai + 1); + var i = ai; + + if (ai >= 0 && bi > 0) { + if(a===b) { + return [ai, bi]; + } + begs = []; + left = str.length; + + while (i >= 0 && !result) { + if (i == ai) { + begs.push(i); + ai = str.indexOf(a, i + 1); + } else if (begs.length == 1) { + result = [ begs.pop(), bi ]; + } else { + beg = begs.pop(); + if (beg < left) { + left = beg; + right = bi; + } + + bi = str.indexOf(b, i + 1); + } + + i = ai < bi && ai >= 0 ? ai : bi; + } + + if (begs.length) { + result = [ left, right ]; + } + } + + return result; +} + + +/***/ }), + +/***/ 6533: +/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { + +var concatMap = __nccwpck_require__(7087); +var balanced = __nccwpck_require__(8638); + +module.exports = expandTop; + +var escSlash = '\0SLASH'+Math.random()+'\0'; +var escOpen = '\0OPEN'+Math.random()+'\0'; +var escClose = '\0CLOSE'+Math.random()+'\0'; +var escComma = '\0COMMA'+Math.random()+'\0'; +var escPeriod = '\0PERIOD'+Math.random()+'\0'; + +function numeric(str) { + return parseInt(str, 10) == str + ? parseInt(str, 10) + : str.charCodeAt(0); +} + +function escapeBraces(str) { + return str.split('\\\\').join(escSlash) + .split('\\{').join(escOpen) + .split('\\}').join(escClose) + .split('\\,').join(escComma) + .split('\\.').join(escPeriod); +} + +function unescapeBraces(str) { + return str.split(escSlash).join('\\') + .split(escOpen).join('{') + .split(escClose).join('}') + .split(escComma).join(',') + .split(escPeriod).join('.'); +} + + +// Basically just str.split(","), but handling cases +// where we have nested braced sections, which should be +// treated as individual members, like {a,{b,c},d} +function parseCommaParts(str) { + if (!str) + return ['']; + + var parts = []; + var m = balanced('{', '}', str); + + if (!m) + return str.split(','); + + var pre = m.pre; + var body = m.body; + var post = m.post; + var p = pre.split(','); + + p[p.length-1] += '{' + body + '}'; + var postParts = parseCommaParts(post); + if (post.length) { + p[p.length-1] += postParts.shift(); + p.push.apply(p, postParts); + } + + parts.push.apply(parts, p); + + return parts; +} + +function expandTop(str, options) { + if (!str) + return []; + + options = options || {}; + var max = options.max == null ? Infinity : options.max; + + // I don't know why Bash 4.3 does this, but it does. + // Anything starting with {} will have the first two bytes preserved + // but *only* at the top level, so {},a}b will not expand to anything, + // but a{},b}c will be expanded to [a}c,abc]. + // One could argue that this is a bug in Bash, but since the goal of + // this module is to match Bash's rules, we escape a leading {} + if (str.substr(0, 2) === '{}') { + str = '\\{\\}' + str.substr(2); + } + + return expand(escapeBraces(str), max, true).map(unescapeBraces); +} + +function identity(e) { + return e; +} + +function embrace(str) { + return '{' + str + '}'; +} +function isPadded(el) { + return /^-?0\d/.test(el); +} + +function lte(i, y) { + return i <= y; +} +function gte(i, y) { + return i >= y; +} + +function expand(str, max, isTop) { + var expansions = []; + + var m = balanced('{', '}', str); + if (!m || /\$$/.test(m.pre)) return [str]; + + var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + var isSequence = isNumericSequence || isAlphaSequence; + var isOptions = m.body.indexOf(',') >= 0; + if (!isSequence && !isOptions) { + // {a},b} + if (m.post.match(/,(?!,).*\}/)) { + str = m.pre + '{' + m.body + escClose + m.post; + return expand(str, max, true); + } + return [str]; + } + + var n; + if (isSequence) { + n = m.body.split(/\.\./); + } else { + n = parseCommaParts(m.body); + if (n.length === 1) { + // x{{a,b}}y ==> x{a}y x{b}y + n = expand(n[0], max, false).map(embrace); + if (n.length === 1) { + var post = m.post.length + ? expand(m.post, max, false) + : ['']; + return post.map(function(p) { + return m.pre + n[0] + p; + }); + } + } + } + + // at this point, n is the parts, and we know it's not a comma set + // with a single entry. + + // no need to expand pre, since it is guaranteed to be free of brace-sets + var pre = m.pre; + var post = m.post.length + ? expand(m.post, max, false) + : ['']; + + var N; + + if (isSequence) { + var x = numeric(n[0]); + var y = numeric(n[1]); + var width = Math.max(n[0].length, n[1].length) + var incr = n.length == 3 + ? Math.max(Math.abs(numeric(n[2])), 1) + : 1; + var test = lte; + var reverse = y < x; + if (reverse) { + incr *= -1; + test = gte; + } + var pad = n.some(isPadded); + + N = []; + + for (var i = x; test(i, y) && N.length < max; i += incr) { + var c; + if (isAlphaSequence) { + c = String.fromCharCode(i); + if (c === '\\') + c = ''; + } else { + c = String(i); + if (pad) { + var need = width - c.length; + if (need > 0) { + var z = new Array(need + 1).join('0'); + if (i < 0) + c = '-' + z + c.slice(1); + else + c = z + c; + } + } + } + N.push(c); + } + } else { + N = concatMap(n, function(el) { return expand(el, max, false) }); + } + + for (var j = 0; j < N.length; j++) { + for (var k = 0; k < post.length && expansions.length < max; k++) { + var expansion = pre + N[j] + post[k]; + if (!isTop || isSequence || expansion) + expansions.push(expansion); + } + } + + return expansions; +} + + +/***/ }), + /***/ 4974: /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { @@ -13,7 +292,7 @@ var path = (function () { try { return __nccwpck_require__(6928) } catch (e) {}} minimatch.sep = path.sep var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {} -var expand = __nccwpck_require__(8968) +var expand = __nccwpck_require__(6533) var plTypes = { '!': { open: '(?:(?!(?:', close: '))[^/]*?)'}, @@ -8334,6 +8613,26 @@ class Agent extends http.Agent { exports.Agent = Agent; //# sourceMappingURL=index.js.map +/***/ }), + +/***/ 7087: +/***/ ((module) => { + +module.exports = function (xs, fn) { + var res = []; + for (var i = 0; i < xs.length; i++) { + var x = fn(xs[i], i); + if (isArray(x)) res.push.apply(res, x); + else res.push(x); + } + return res; +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; + + /***/ }), /***/ 6110: @@ -16069,13 +16368,7 @@ function processHeader (request, key, val) { } 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) + arr.push(`${val[i]}`) } } val = arr @@ -16086,12 +16379,7 @@ function processHeader (request, key, val) { } 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') { @@ -17463,7 +17751,6 @@ const { RequestContentLengthMismatchError, ResponseContentLengthMismatchError, RequestAbortedError, - InvalidArgumentError, HeadersTimeoutError, HeadersOverflowError, SocketError, @@ -18447,16 +18734,8 @@ function writeH1 (client, request) { } 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) - } + } else if (util.isBlobLike(body) && request.contentType == null && body.type) { + headers.push('content-type', body.type) } if (body && typeof body.read === 'function') { @@ -21929,28 +22208,6 @@ function calculateRetryAfterHeader (retryAfter) { 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 @@ -22165,12 +22422,6 @@ class RetryHandler { 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 assert(this.start === start, 'content-range mismatch') @@ -22194,12 +22445,6 @@ class RetryHandler { ) } - 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), @@ -26444,7 +26689,7 @@ function validateCookiePath (path) { if ( code < 0x20 || // exclude CTLs (0-31) - code > 0x7E || // exclude DEL and non-ascii + code === 0x7F || // DEL code === 0x3B // ; ) { throw new Error('Invalid cookie path') @@ -26453,80 +26698,16 @@ function validateCookiePath (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". - * - * ::= | " " - * ::=