diff --git a/.github/workflows/check-dist.yml b/.github/workflows/check-dist.yml
index 4c3d69e2..6274fd28 100644
--- a/.github/workflows/check-dist.yml
+++ b/.github/workflows/check-dist.yml
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set Node.js 16.x
uses: actions/setup-node@v3
@@ -45,7 +45,7 @@ jobs:
id: diff
# If index.js was different than expected, upload the expected version as an artifact
- - uses: actions/upload-artifact@v2
+ - uses: actions/upload-artifact@v3
if: ${{ failure() && steps.diff.conclusion == 'failure' }}
with:
name: dist
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index c30fab46..3ea240d8 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -18,11 +18,11 @@ jobs:
steps:
- name: Checkout repository
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v1
+ uses: github/codeql-action/init@v2
# Override language selection by uncommenting this and choosing your languages
# with:
# languages: go, javascript, csharp, python, cpp, java
@@ -30,7 +30,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@v1
+ uses: github/codeql-action/autobuild@v2
# âšī¸ Command-line programs to run using the OS shell.
# đ https://git.io/JvXDl
@@ -44,4 +44,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ uses: github/codeql-action/analyze@v2
diff --git a/.github/workflows/e2e-cache.yml b/.github/workflows/e2e-cache.yml
index ce3366a1..2e903a48 100644
--- a/.github/workflows/e2e-cache.yml
+++ b/.github/workflows/e2e-cache.yml
@@ -39,7 +39,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
- python-version: ['3.9', 'pypy-3.7-v7.x']
+ python-version: ['3.9', 'pypy-3.9-v7.x']
steps:
- uses: actions/checkout@v3
- name: Setup Python
@@ -48,11 +48,17 @@ jobs:
python-version: ${{ matrix.python-version }}
cache: 'pipenv'
- name: Install pipenv
- run: pipx install pipenv
+ run: curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python
- name: Install dependencies
+ shell: pwsh
run: |
- cd __tests__/data
- pipenv install --verbose
+ mv ./__tests__/data/Pipfile.lock .
+ mv ./__tests__/data/Pipfile .
+ if ("${{ matrix.python-version }}" -Match "pypy") {
+ pipenv install --keep-outdated --python pypy
+ } else {
+ pipenv install --keep-outdated --python ${{ matrix.python-version }}
+ }
python-poetry-dependencies-caching:
name: Test poetry (Python ${{ matrix.python-version}}, ${{ matrix.os }})
@@ -66,15 +72,15 @@ jobs:
- uses: actions/checkout@v3
- name: Install poetry
run: pipx install poetry
+ - name: Init pyproject.toml
+ run: mv ./__tests__/data/pyproject.toml .
- name: Setup Python
uses: ./
with:
python-version: ${{ matrix.python-version }}
cache: 'poetry'
- - name: Init pyproject.toml
- run: poetry init -n
- name: Install dependencies
- run: poetry add flake8
+ run: poetry install
python-pip-dependencies-caching-path:
name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }})
@@ -102,7 +108,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
- python-version: ['3.9', 'pypy-3.7-v7.x']
+ python-version: ['3.9', 'pypy-3.9-v7.x']
steps:
- uses: actions/checkout@v3
- name: Setup Python
@@ -112,8 +118,14 @@ jobs:
cache: 'pipenv'
cache-dependency-path: '**/pipenv-requirements.txt'
- name: Install pipenv
- run: pipx install pipenv
+ run: curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python
- name: Install dependencies
+ shell: pwsh
run: |
- cd __tests__/data
- pipenv install --verbose
\ No newline at end of file
+ mv ./__tests__/data/Pipfile.lock .
+ mv ./__tests__/data/Pipfile .
+ if ("${{ matrix.python-version }}" -Match "pypy") {
+ pipenv install --keep-outdated --python pypy
+ } else {
+ pipenv install --keep-outdated --python ${{ matrix.python-version }}
+ }
\ No newline at end of file
diff --git a/.github/workflows/licensed.yml b/.github/workflows/licensed.yml
index a78560b8..6f4cd922 100644
--- a/.github/workflows/licensed.yml
+++ b/.github/workflows/licensed.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
name: Check licenses
steps:
- - uses: actions/checkout@v2
+ - uses: actions/checkout@v3
- name: Set Node.js 16.x
uses: actions/setup-node@v3
with:
diff --git a/.github/workflows/release-new-action-version.yml b/.github/workflows/release-new-action-version.yml
index 955ced7e..968c77fb 100644
--- a/.github/workflows/release-new-action-version.yml
+++ b/.github/workflows/release-new-action-version.yml
@@ -21,7 +21,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Update the ${{ env.TAG_NAME }} tag
- uses: actions/publish-action@v0.1.0
+ uses: actions/publish-action@v0.2.0
with:
source-tag: ${{ env.TAG_NAME }}
- slack-webhook: ${{ secrets.SLACK_WEBHOOK }}
\ No newline at end of file
+ slack-webhook: ${{ secrets.SLACK_WEBHOOK }}
diff --git a/.github/workflows/test-pypy.yml b/.github/workflows/test-pypy.yml
index 5f23b213..de9ba6b7 100644
--- a/.github/workflows/test-pypy.yml
+++ b/.github/workflows/test-pypy.yml
@@ -34,7 +34,7 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: setup-python ${{ matrix.pypy }}
id: setup-python
@@ -65,3 +65,62 @@ jobs:
EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffixe
${EXECUTABLE} --version
shell: bash
+
+ setup-pypy-noenv:
+ name: Setup PyPy ${{ matrix.pypy }} ${{ matrix.os }} (noenv)
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [macos-latest, windows-latest, ubuntu-18.04, ubuntu-latest]
+ pypy: ['pypy2.7', 'pypy3.7', 'pypy3.8', 'pypy3.9-nightly']
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: setup-python ${{ matrix.pypy }}
+ id: setup-python
+ uses: ./
+ with:
+ python-version: ${{ matrix.pypy }}
+ update-environment: false
+
+ - name: PyPy and Python version
+ run: ${{ steps.setup-python.outputs.python-path }} --version
+
+ - name: Run simple code
+ run: ${{ steps.setup-python.outputs.python-path }} -c 'import math; print(math.factorial(5))'
+
+ check-latest:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup PyPy and check latest
+ uses: ./
+ with:
+ python-version: 'pypy-3.7-v7.3.x'
+ check-latest: true
+ - name: PyPy and Python version
+ run: python --version
+
+ - name: Run simple code
+ run: python -c 'import math; print(math.factorial(5))'
+
+ - name: Assert PyPy is running
+ run: |
+ import platform
+ assert platform.python_implementation().lower() == "pypy"
+ shell: python
+
+ - name: Assert expected binaries (or symlinks) are present
+ run: |
+ EXECUTABLE="pypy-3.7-v7.3.x"
+ EXECUTABLE=${EXECUTABLE/-/} # remove the first '-' in "pypy-X.Y" -> "pypyX.Y" to match executable name
+ EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffixe
+ ${EXECUTABLE} --version
+ shell: bash
\ No newline at end of file
diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml
index e0d9a47d..921449fb 100644
--- a/.github/workflows/test-python.yml
+++ b/.github/workflows/test-python.yml
@@ -147,3 +147,52 @@ jobs:
- name: Run simple code
run: python -c 'import math; print(math.factorial(5))'
+
+ setup-versions-noenv:
+ name: Setup ${{ matrix.python }} ${{ matrix.os }} (noenv)
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [macos-latest, windows-latest, ubuntu-18.04, ubuntu-20.04]
+ python: ["3.7", "3.8", "3.9", "3.10"]
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+
+ - name: setup-python ${{ matrix.python }}
+ id: setup-python
+ uses: ./
+ with:
+ python-version: ${{ matrix.python }}
+ update-environment: false
+
+ - name: Python version
+ run: ${{ steps.setup-python.outputs.python-path }} --version
+
+ - name: Run simple code
+ run: ${{ steps.setup-python.outputs.python-path }} -c 'import math; print(math.factorial(5))'
+
+ check-latest:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ python-version: ["3.8", "3.9", "3.10"]
+ steps:
+ - uses: actions/checkout@v3
+ - name: Setup Python and check latest
+ uses: ./
+ with:
+ python-version: ${{ matrix.python-version }}
+ check-latest: true
+ - name: Validate version
+ run: |
+ $pythonVersion = (python --version)
+ if ("$pythonVersion" -NotMatch "${{ matrix.python }}"){
+ Write-Host "The current version is $pythonVersion; expected version is ${{ matrix.python }}"
+ exit 1
+ }
+ $pythonVersion
+ shell: pwsh
\ No newline at end of file
diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml
index 171e2055..5c8cc77f 100644
--- a/.github/workflows/workflow.yml
+++ b/.github/workflows/workflow.yml
@@ -17,7 +17,7 @@ jobs:
operating-system: [ubuntu-latest, windows-latest]
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Set Node.js 16.x
uses: actions/setup-node@v3
diff --git a/.licenses/npm/@actions/cache.dep.yml b/.licenses/npm/@actions/cache.dep.yml
index f7aa5837..c3c245ef 100644
--- a/.licenses/npm/@actions/cache.dep.yml
+++ b/.licenses/npm/@actions/cache.dep.yml
@@ -1,6 +1,6 @@
---
name: "@actions/cache"
-version: 2.0.2
+version: 3.0.0
type: npm
summary: Actions cache lib
homepage: https://github.com/actions/toolkit/tree/main/packages/cache
diff --git a/.licenses/npm/@actions/core.dep.yml b/.licenses/npm/@actions/core.dep.yml
index b1152f59..6ef1a3d4 100644
--- a/.licenses/npm/@actions/core.dep.yml
+++ b/.licenses/npm/@actions/core.dep.yml
@@ -1,6 +1,6 @@
---
name: "@actions/core"
-version: 1.2.6
+version: 1.7.0
type: npm
summary: Actions core lib
homepage: https://github.com/actions/toolkit/tree/main/packages/core
diff --git a/.licenses/npm/@actions/http-client-1.0.8.dep.yml b/.licenses/npm/@actions/http-client-2.0.1.dep.yml
similarity index 98%
rename from .licenses/npm/@actions/http-client-1.0.8.dep.yml
rename to .licenses/npm/@actions/http-client-2.0.1.dep.yml
index d18a24ff..88e4e663 100644
--- a/.licenses/npm/@actions/http-client-1.0.8.dep.yml
+++ b/.licenses/npm/@actions/http-client-2.0.1.dep.yml
@@ -1,6 +1,6 @@
---
name: "@actions/http-client"
-version: 1.0.8
+version: 2.0.1
type: npm
summary: Actions Http Client
homepage: https://github.com/actions/http-client#readme
diff --git a/README.md b/README.md
index d5968fdc..bc637ed8 100644
--- a/README.md
+++ b/README.md
@@ -1,257 +1,61 @@
-# setup-python V4
+# setup-python
-This action sets up a Python environment for use in actions by:
+This action provides the following functionality for GitHub Actions users:
-- optionally installing and adding to PATH a version of Python that is already installed in the tools cache.
-- downloading, installing and adding to PATH an available version of Python from GitHub Releases ([actions/python-versions](https://github.com/actions/python-versions/releases)) if a specific version is not available in the tools cache.
-- failing if a specific version of Python is not preinstalled or available for download.
-- optionally caching dependencies for pip, pipenv and poetry.
-- registering problem matchers for error output.
+- Installing a version of Python or PyPy and (by default) adding it to the PATH
+- Optionally caching dependencies for pip, pipenv and poetry
+- Registering problem matchers for error output
-# What's new
-
-- Ability to download, install and set up Python packages from `actions/python-versions` that do not come preinstalled on runners.
- - Allows for pinning to a specific patch version of Python without the worry of it ever being removed or changed.
-- Automatic setup and download of Python packages if using a self-hosted runner.
-- Support for pre-release versions of Python.
-- Support for installing any version of PyPy on-flight
-- Support for built-in caching of pip, pipenv and poetry dependencies
-- Support for `.python-version` file
-
-# Usage
+## Basic usage
See [action.yml](action.yml)
-Basic:
+**Python**
```yaml
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
- python-version: '3.x' # Version range or exact version of a Python version to use, using SemVer's version range syntax
- architecture: 'x64' # optional x64 or x86. Defaults to x64 if not specified
+ python-version: '3.10'
- run: python my_script.py
```
-Read Python version from file:
+**PyPy**
```yaml
steps:
- uses: actions/checkout@v3
-- uses: actions/setup-python@v4
+- uses: actions/setup-python@v4
with:
- python-version-file: '.python-version' # Read python version from a file
+ python-version: 'pypy3.9'
- run: python my_script.py
```
+The `python-version` input is optional. If not supplied, the action will try to resolve the version from the default `.python-version` file. If the `.python-version` file doesn't exist Python or PyPy version from the PATH will be used. The default version of Python or PyPy in PATH varies between runners and can be changed unexpectedly so we recommend always using `setup-python`.
-Matrix Testing:
-```yaml
-jobs:
- build:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- python-version: [ '2.x', '3.x', 'pypy2.7', 'pypy3.7', 'pypy3.8' ]
- name: Python ${{ matrix.python-version }} sample
- steps:
- - uses: actions/checkout@v3
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: ${{ matrix.python-version }}
- architecture: x64
- - run: python my_script.py
-```
+The action will first check the local [tool cache](docs/advanced-usage.md#hosted-tool-cache) for a [semver](https://github.com/npm/node-semver#versions) match. If unable to find a specific version in the tool cache, the action will attempt to download a version of Python from [GitHub Releases](https://github.com/actions/python-versions/releases) and for PyPy from the official [PyPy's dist](https://downloads.python.org/pypy/).
-Exclude a specific Python version:
-```yaml
-jobs:
- build:
- runs-on: ${{ matrix.os }}
- strategy:
- matrix:
- os: [ubuntu-latest, macos-latest, windows-latest]
- python-version: ['2.7', '3.7', '3.8', '3.9', '3.10', 'pypy2.7', 'pypy3.8']
- exclude:
- - os: macos-latest
- python-version: '3.8'
- - os: windows-latest
- python-version: '3.6'
- steps:
- - uses: actions/checkout@v3
- - name: Set up Python
- uses: actions/setup-python@v4
- with:
- python-version: ${{ matrix.python-version }}
- - name: Display Python version
- run: python --version
-```
+For information regarding locally cached versions of Python or PyPy on GitHub hosted runners, check out [GitHub Actions Virtual Environments](https://github.com/actions/virtual-environments).
-Download and set up a version of Python that does not come preinstalled on an image:
-```yaml
-jobs:
- build:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- # in this example, there is a newer version already installed, 3.7.7, so the older version will be downloaded
- python-version: ['3.7.4', '3.8', '3.9', '3.10']
- steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-python@v4
- with:
- python-version: ${{ matrix.python-version }}
- - run: python my_script.py
-```
+## Supported version syntax
-Download and set up an accurate pre-release version of Python:
-```yaml
-steps:
-- uses: actions/checkout@v3
-- uses: actions/setup-python@v4
- with:
- python-version: '3.11.0-alpha.1'
-- run: python my_script.py
-```
+The `python-version` input supports the [Semantic Versioning Specification](https://semver.org/) and some special version notations (e.g. `semver ranges`, `x.y-dev syntax`, etc.), for detailed examples please refer to the section: [Using python-version input](docs/advanced-usage.md#using-the-python-version-input) of the [Advanced usage](docs/advanced-usage.md) guide.
-Download and set up the latest available version of Python (includes both pre-release and stable versions):
-```yaml
-steps:
-- uses: actions/checkout@v3
-- uses: actions/setup-python@v4
- with:
- python-version: '3.11.0-alpha - 3.11.0' # SemVer's version range syntax
-- run: python my_script.py
-```
+## Supported architectures
-Download and set up PyPy:
+Using `architecture` input it is possible to specify the required Python or PyPy interpreter architecture: `x86` or `x64`. If the input is not specified the architecture defaults to `x64`.
-```yaml
-jobs:
- build:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- python-version:
- - 'pypy3.7' # the latest available version of PyPy that supports Python 3.7
- - 'pypy3.7-v7.3.3' # Python 3.7 and PyPy 7.3.3
- - 'pypy3.8' # the latest available version of PyPy that supports Python 3.8
- steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-python@v4
- with:
- python-version: ${{ matrix.python-version }}
- - run: python my_script.py
-```
-More details on PyPy syntax and examples of using preview / nightly versions of PyPy can be found in the [Available versions of PyPy](#available-versions-of-pypy) section.
+## Caching packages dependencies
-An output is available with the absolute path of the python interpreter executable if you need it:
-```yaml
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-python@v4
- id: cp310
- with:
- python-version: "3.10"
- - run: pipx run --python '${{ steps.cp310.outputs.python-path }}' nox --version
-```
+The action has built-in functionality for caching and restoring dependencies. It uses [toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under the hood for caching dependencies but requires less configuration settings. Supported package managers are `pip`, `pipenv` and `poetry`. The `cache` input is optional, and caching is turned off by default.
->The environment variable `pythonLocation` also becomes available after Python or PyPy installation. It contains the absolute path to the folder where the desired version of Python or PyPy is installed.
+The action defaults to searching for a dependency file (`requirements.txt` for pip, `Pipfile.lock` for pipenv or `poetry.lock` for poetry) in the repository, and uses its hash as a part of the cache key. Input `cache-dependency-path` is used for cases when multiple dependency files are used, they are located in different subdirectories or different files for the hash that want to be used.
-# Getting started with Python + Actions
-
-Check out our detailed guide on using [Python with GitHub Actions](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/using-python-with-github-actions).
-
-# Available versions of Python
-
-`setup-python` is able to configure Python from two sources:
-
-- Preinstalled versions of Python in the tools cache on GitHub-hosted runners.
- - For detailed information regarding the available versions of Python that are installed, see [Supported software](https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners#supported-software).
- - For every minor version of Python, expect only the latest patch to be preinstalled.
- - If `3.8.1` is installed for example, and `3.8.2` is released, expect `3.8.1` to be removed and replaced by `3.8.2` in the tools cache.
- - If the exact patch version doesn't matter to you, specifying just the major and minor version will get you the latest preinstalled patch version. In the previous example, the version spec `3.8` will use the `3.8.2` Python version found in the cache.
- - Use `-dev` instead of a patch number (e.g., `3.11-dev`) to install the latest patch version release for a given minor version, *alpha and beta releases included*.
-- Downloadable Python versions from GitHub Releases ([actions/python-versions](https://github.com/actions/python-versions/releases)).
- - All available versions are listed in the [version-manifest.json](https://github.com/actions/python-versions/blob/main/versions-manifest.json) file.
- - If there is a specific version of Python that is not available, you can open an issue here
-
-**Note:** Python versions used in this action are generated in the [python-versions](https://github.com/actions/python-versions) repository. For macOS and Ubuntu images python versions are built from the source code. For Windows the python-versions repository uses installation executable. For more information please refer to the [python-versions](https://github.com/actions/python-versions) repository.
-
- # Available versions of PyPy
-
- `setup-python` is able to configure PyPy from two sources:
-
-- Preinstalled versions of PyPy in the tools cache on GitHub-hosted runners
- - For detailed information regarding the available versions of PyPy that are installed, see [Supported software](https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners#supported-software).
- - For the latest PyPy release, all versions of Python are cached.
- - Cache is updated with a 1-2 week delay. If you specify the PyPy version as `pypy3.7` or `pypy-3.7`, the cached version will be used although a newer version is available. If you need to start using the recently released version right after release, you should specify the exact PyPy version using `pypy3.7-v7.3.3` or `pypy-3.7-v7.3.3`.
-
-- Downloadable PyPy versions from the [official PyPy site](https://downloads.python.org/pypy/).
- - All available versions that we can download are listed in [versions.json](https://downloads.python.org/pypy/versions.json) file.
- - PyPy < 7.3.3 are not available to install on-flight.
- - If some versions are not available, you can open an issue in https://foss.heptapod.net/pypy/pypy/
-
-# Hosted Tool Cache
-
-GitHub hosted runners have a tools cache that comes with a few versions of Python + PyPy already installed. This tools cache helps speed up runs and tool setup by not requiring any new downloads. There is an environment variable called `RUNNER_TOOL_CACHE` on each runner that describes the location of this tools cache and there is where you will find Python and PyPy installed. `setup-python` works by taking a specific version of Python or PyPy in this tools cache and adding it to PATH.
-
-|| Location |
-|------|-------|
-|**Tool Cache Directory** |`RUNNER_TOOL_CACHE`|
-|**Python Tool Cache**|`RUNNER_TOOL_CACHE/Python/*`|
-|**PyPy Tool Cache**|`RUNNER_TOOL_CACHE/PyPy/*`|
-
-GitHub virtual environments are setup in [actions/virtual-environments](https://github.com/actions/virtual-environments). During the setup, the available versions of Python and PyPy are automatically downloaded, setup and documented.
-- Tools cache setup for Ubuntu: [Install-Toolset.ps1](https://github.com/actions/virtual-environments/blob/main/images/linux/scripts/installers/Install-Toolset.ps1) [Configure-Toolset.ps1](https://github.com/actions/virtual-environments/blob/main/images/linux/scripts/installers/Configure-Toolset.ps1)
-- Tools cache setup for Windows: [Install-Toolset.ps1](https://github.com/actions/virtual-environments/blob/main/images/win/scripts/Installers/Install-Toolset.ps1) [Configure-Toolset.ps1](https://github.com/actions/virtual-environments/blob/main/images/win/scripts/Installers/Configure-Toolset.ps1)
-
-# Specifying a Python version
-
-If there is a specific version of Python that you need and you don't want to worry about any potential breaking changes due to patch updates (going from `3.7.5` to `3.7.6` for example), you should specify the exact major, minor, and patch version (such as `3.7.5`)
- - The only downside to this is that set up will take a little longer since the exact version will have to be downloaded if the exact version is not already installed on the runner due to more recent versions.
- - MSI installers are used on Windows for this, so runs will take a little longer to set up vs Mac and Linux.
-
-You should specify only a major and minor version if you are okay with the most recent patch version being used.
- - There will be a single patch version already installed on each runner for every minor version of Python that is supported.
- - The patch version that will be preinstalled, will generally be the latest and every time there is a new patch released, the older version that is preinstalled will be replaced.
- - Using the most recent patch version will result in a very quick setup since no downloads will be required since a locally installed version Python on the runner will be used.
-
-# Specifying a PyPy version
-The version of PyPy should be specified in the format `pypy[-v]` or `pypy-[-v]`.
-The `` parameter is optional and can be skipped. The latest version will be used in this case.
-
-```
-pypy3.7 or pypy-3.7 # the latest available version of PyPy that supports Python 3.7
-pypy3.8 or pypy-3.8 # the latest available version of PyPy that supports Python 3.8
-pypy2.7 or pypy-2.7 # the latest available version of PyPy that supports Python 2.7
-pypy3.7-v7.3.3 or pypy-3.7-v7.3.3 # Python 3.7 and PyPy 7.3.3
-pypy3.7-v7.x or pypy-3.7-v7.x # Python 3.7 and the latest available PyPy 7.x
-pypy3.7-v7.3.3rc1 or pypy-3.7-v7.3.3rc1 # Python 3.7 and preview version of PyPy
-pypy3.7-nightly or pypy-3.7-nightly # Python 3.7 and nightly PyPy
-```
-
-Note: `pypy2` and `pypy3` have been removed in v3. Use the format above instead.
-
-# Caching packages dependencies
-
-The action has built-in functionality for caching and restoring dependencies. It uses [actions/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under the hood for caching dependencies but requires less configuration settings. Supported package managers are `pip`, `pipenv` and `poetry`. The `cache` input is optional, and caching is turned off by default.
-
-The action defaults to searching for a dependency file (`requirements.txt` for pip, `Pipfile.lock` for pipenv or `poetry.lock` for poetry) in the repository, and uses its hash as a part of the cache key. Use `cache-dependency-path` for cases where multiple dependency files are used, they are located in different subdirectories or different files for the hash want to be used.
-
- - For pip, the action will cache global cache directory
- - For pipenv, the action will cache virtualenv directory
- - For poetry, the action will cache virtualenv directory
-
-**Please Note:** Restored cache will not be used if the requirements.txt file is not updated for a long time and a newer version of the dependency is available that can lead to an increase in total build time.
-
-The requirements file format allows to specify dependency versions using logical operators (for example chardet>=3.0.4) or specify dependencies without any versions. In this case the pip install -r requirements.txt command will always try to install the latest available package version. To be sure that the cache will be used, please stick to a specific dependency version and update it manually if necessary.
+ - For `pip`, the action will cache the global cache directory
+ - For `pipenv`, the action will cache virtualenv directory
+ - For `poetry`, the action will cache virtualenv directory
**Caching pip dependencies:**
@@ -261,117 +65,31 @@ steps:
- uses: actions/setup-python@v4
with:
python-version: '3.9'
- cache: 'pip'
+ cache: 'pip' # caching pip dependencies
- run: pip install -r requirements.txt
```
+>**Note:** Restored cache will not be used if the requirements.txt file is not updated for a long time and a newer version of the dependency is available which can lead to an increase in total build time.
-**Caching pipenv dependencies:**
-```yaml
-steps:
-- uses: actions/checkout@v3
-- name: Install pipenv
- run: pipx install pipenv
-- uses: actions/setup-python@v4
- with:
- python-version: '3.9'
- cache: 'pipenv'
-- run: pipenv install
-```
+>The requirements file format allows for specifying dependency versions using logical operators (for example chardet>=3.0.4) or specifying dependencies without any versions. In this case the pip install -r requirements.txt command will always try to install the latest available package version. To be sure that the cache will be used, please stick to a specific dependency version and update it manually if necessary.
-**Caching poetry dependencies:**
-```yaml
-steps:
-- uses: actions/checkout@v3
-- name: Install poetry
- run: pipx install poetry
-- uses: actions/setup-python@v4
- with:
- python-version: '3.9'
- cache: 'poetry'
-- run: poetry install
-- run: poetry run pytest
-```
+See examples of using `cache` and `cache-dependency-path` for `pipenv` and `poetry` in the section: [Caching packages](docs/advanced-usage.md#caching-packages) of the [Advanced usage](docs/advanced-usage.md) guide.
-**Using wildcard patterns to cache dependencies**
-```yaml
-steps:
-- uses: actions/checkout@v3
-- uses: actions/setup-python@v4
- with:
- python-version: '3.9'
- cache: 'pip'
- cache-dependency-path: '**/requirements-dev.txt'
-- run: pip install -r subdirectory/requirements-dev.txt
-```
+## Advanced usage
-**Using a list of file paths to cache dependencies**
-```yaml
-steps:
-- uses: actions/checkout@v3
-- name: Install pipenv
- run: pipx install pipenv
-- uses: actions/setup-python@v4
- with:
- python-version: '3.9'
- cache: 'pipenv'
- cache-dependency-path: |
- server/app/Pipfile.lock
- __test__/app/Pipfile.lock
-- run: pipenv install
-```
+- [Using the python-version input](docs/advanced-usage.md#using-the-python-version-input)
+- [Using the python-version-file input](docs/advanced-usage.md#using-the-python-version-file-input)
+- [Check latest version](docs/advanced-usage.md#check-latest-version)
+- [Caching packages](docs/advanced-usage.md#caching-packages)
+- [Outputs and environment variables](docs/advanced-usage.md#outputs-and-environment-variables)
+- [Available versions of Python and PyPy](docs/advanced-usage.md#available-versions-of-python-and-pypy)
+- [Hosted tool cache](docs/advanced-usage.md#hosted-tool-cache)
+- [Using `setup-python` with a self-hosted runner](docs/advanced-usage.md#using-setup-python-with-a-self-hosted-runner)
+- [Using `setup-python` on GHES](docs/advanced-usage.md#using-setup-python-on-ghes)
-# Using `setup-python` with a self hosted runner
-
-Python distributions are only available for the same [environments](https://github.com/actions/virtual-environments#available-environments) that GitHub Actions hosted environments are available for. If you are using an unsupported version of Ubuntu such as `19.04` or another Linux distribution such as Fedora, `setup-python` will not work. If you have a supported self-hosted runner and you would like to use `setup-python`, there are a few extra things you need to make sure are set up so that new versions of Python can be downloaded and configured on your runner.
-
-If you are experiencing problems while configuring Python on your self-hosted runner, turn on [step debugging](https://github.com/actions/toolkit/blob/main/docs/action-debugging.md#step-debug-logs) to see addition logs.
-
-### Windows
-
-- Your runner needs to be running with administrator privileges so that the appropriate directories and files can be set up when downloading and installing a new version of Python for the first time.
-- If your runner is configured as a service, make sure the account that is running the service has the appropriate write permissions so that Python can get installed. The default `NT AUTHORITY\NETWORK SERVICE` should be sufficient.
-- You need `7zip` installed and added to your `PATH` so that the downloaded versions of Python files can be extracted properly during first-time setup.
-- MSI installers are used when setting up Python on Windows. A word of caution as MSI installers update registry settings.
-- The 3.8 MSI installer for Windows will not let you install another 3.8 version of Python. If `setup-python` fails for a 3.8 version of Python, make sure any previously installed versions are removed by going to "Apps & Features" in the Settings app.
-
-### Linux
-
-- The Python packages that are downloaded from `actions/python-versions` are originally compiled from source in `/opt/hostedtoolcache/` with the [--enable-shared](https://github.com/actions/python-versions/blob/94f04ae6806c6633c82db94c6406a16e17decd5c/builders/ubuntu-python-builder.psm1#L35) flag, which makes them non-relocatable.
-- Create an environment variable called `AGENT_TOOLSDIRECTORY` and set it to `/opt/hostedtoolcache`. This controls where the runner downloads and installs tools.
- - In the same shell that your runner is using, type `export AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache`.
- - A more permanent way of setting the environment variable is to create a `.env` file in the same directory as your runner and to add `AGENT_TOOLSDIRECTORY=/opt/hostedtoolcache`. This ensures the variable is always set if your runner is configured as a service.
-- Create a directory called `hostedtoolcache` inside `/opt`.
-- The user starting the runner must have write permission to the `/opt/hostedtoolcache` directory. It is not possible to start the Linux runner with `sudo` and the `/opt` directory usually requires root privileges to write to. Check the current user and group that the runner belongs to by typing `ls -l` inside the runners root directory.
-- The runner can be granted write access to the `/opt/hostedtoolcache` directory using a few techniques:
- - The user starting the runner is the owner, and the owner has write permission.
- - The user starting the runner is in the owning group, and the owning group has write permission.
- - All users have write permission.
-- One quick way to grant access is to change the user and group of `/opt/hostedtoolcache` to be the same as the runners using `chown`.
- - `sudo chown runner-user:runner-group /opt/hostedtoolcache/`.
-- If your runner is configured as a service and you run into problems, make sure the user that the service is running as is correct. For more information, you can [check the status of your self-hosted runner](https://help.github.com/en/actions/hosting-your-own-runners/configuring-the-self-hosted-runner-application-as-a-service#checking-the-status-of-the-service).
-
-### Mac
-
-- The same setup that applies to `Linux` also applies to `Mac`, just with a different tools cache directory.
-- Create a directory called `/Users/runner/hostedtoolcache`.
-- Set the `AGENT_TOOLSDIRECTORY` environment variable to `/Users/runner/hostedtoolcache`.
-- Change the permissions of `/Users/runner/hostedtoolcache` so that the runner has write access.
-
-
-# Using Python without `setup-python`
-
-`setup-python` helps keep your dependencies explicit and ensures consistent behavior between different runners. If you use `python` in a shell on a GitHub hosted runner without `setup-python` it will default to whatever is in PATH. The default version of Python in PATH vary between runners and can change unexpectedly so we recommend you always use `setup-python`.
-
-# Using `setup-python` on GHES
-
-`setup-python` comes pre-installed on the appliance with GHES if Actions is enabled. When dynamically downloading Python distributions, `setup-python` downloads distributions from [`actions/python-versions`](https://github.com/actions/python-versions) on github.com (outside of the appliance). These calls to `actions/python-versions` are made via unauthenticated requests, which are limited to [60 requests per hour per IP](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting). If more requests are made within the time frame, then you will start to see rate-limit errors during download that read `##[error]API rate limit exceeded for...`.
-
-To avoid hitting rate-limit problems, we recommend [setting up your own runner tool cache](https://docs.github.com/en/enterprise-server@2.22/admin/github-actions/managing-access-to-actions-from-githubcom/setting-up-the-tool-cache-on-self-hosted-runners-without-internet-access#about-the-included-setup-actions-and-the-runner-tool-cache).
-
-# License
+## License
The scripts and documentation in this project are released under the [MIT License](LICENSE).
-# Contributions
+## Contributions
-Contributions are welcome! See our [Contributor's Guide](docs/contributors.md).
+Contributions are welcome! See our [Contributor's Guide](docs/contributors.md).
\ No newline at end of file
diff --git a/__tests__/cache-restore.test.ts b/__tests__/cache-restore.test.ts
index 99b674ef..8331b812 100644
--- a/__tests__/cache-restore.test.ts
+++ b/__tests__/cache-restore.test.ts
@@ -1,11 +1,13 @@
import * as core from '@actions/core';
import * as cache from '@actions/cache';
import * as exec from '@actions/exec';
+import * as io from '@actions/io';
import {getCacheDistributor} from '../src/cache-distributions/cache-factory';
+import * as utils from './../src/utils';
describe('restore-cache', () => {
const pipFileLockHash =
- 'd1dd6218299d8a6db5fc2001d988b34a8b31f1e9d0bb4534d377dde7c19f64b3';
+ 'a3bdcc71289e4979ca9e051810d81999cc99823109faf6912e17ff14c8e621a6';
const requirementsHash =
'd8110e0006d7fb5ee76365d565eef9d37df1d11598b912d3eb66d398d57a1121';
const requirementsLinuxHash =
@@ -28,6 +30,7 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
let saveSatetSpy: jest.SpyInstance;
let getStateSpy: jest.SpyInstance;
let setOutputSpy: jest.SpyInstance;
+ let getLinuxOSReleaseInfoSpy: jest.SpyInstance;
// cache spy
let restoreCacheSpy: jest.SpyInstance;
@@ -35,6 +38,9 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
// exec spy
let getExecOutputSpy: jest.SpyInstance;
+ // io spy
+ let whichSpy: jest.SpyInstance;
+
beforeEach(() => {
process.env['RUNNER_OS'] = process.env['RUNNER_OS'] ?? 'linux';
@@ -74,6 +80,10 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
return primaryKey;
}
);
+
+ whichSpy = jest.spyOn(io, 'which');
+ whichSpy.mockImplementation(() => '/path/to/python');
+ getLinuxOSReleaseInfoSpy = jest.spyOn(utils, 'getLinuxOSReleaseInfo');
});
describe('Validate provided package manager', () => {
@@ -109,11 +119,24 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
pythonVersion,
dependencyFile
);
+
+ if (process.platform === 'linux') {
+ getLinuxOSReleaseInfoSpy.mockImplementation(() =>
+ Promise.resolve('Ubuntu-20.4')
+ );
+ }
+
await cacheDistributor.restoreCache();
- expect(infoSpy).toHaveBeenCalledWith(
- `Cache restored from key: setup-python-${process.env['RUNNER_OS']}-python-${pythonVersion}-${packageManager}-${fileHash}`
- );
+ if (process.platform === 'linux' && packageManager === 'pip') {
+ expect(infoSpy).toHaveBeenCalledWith(
+ `Cache restored from key: setup-python-${process.env['RUNNER_OS']}-Ubuntu-20.4-python-${pythonVersion}-${packageManager}-${fileHash}`
+ );
+ } else {
+ expect(infoSpy).toHaveBeenCalledWith(
+ `Cache restored from key: setup-python-${process.env['RUNNER_OS']}-python-${pythonVersion}-${packageManager}-${fileHash}`
+ );
+ }
},
30000
);
diff --git a/__tests__/cache-save.test.ts b/__tests__/cache-save.test.ts
index 3fad9a2d..f90ca7f6 100644
--- a/__tests__/cache-save.test.ts
+++ b/__tests__/cache-save.test.ts
@@ -212,6 +212,59 @@ describe('run', () => {
);
expect(setFailedSpy).not.toHaveBeenCalled();
});
+
+ it('saves with -1 cacheId , should not fail workflow', async () => {
+ inputs['cache'] = 'poetry';
+ getStateSpy.mockImplementation((name: string) => {
+ if (name === State.STATE_CACHE_PRIMARY_KEY) {
+ return poetryLockHash;
+ } else if (name === State.CACHE_PATHS) {
+ return JSON.stringify([__dirname]);
+ } else {
+ return requirementsHash;
+ }
+ });
+
+ saveCacheSpy.mockImplementation(() => {
+ return -1;
+ });
+
+ await run();
+
+ expect(getInputSpy).toHaveBeenCalled();
+ expect(getStateSpy).toHaveBeenCalledTimes(3);
+ expect(infoSpy).not.toHaveBeenCalled();
+ expect(saveCacheSpy).toHaveBeenCalled();
+ expect(infoSpy).not.toHaveBeenLastCalledWith(
+ `Cache saved with the key: ${poetryLockHash}`
+ );
+ expect(setFailedSpy).not.toHaveBeenCalled();
+ });
+
+ it('saves with error from toolkit, should fail workflow', async () => {
+ inputs['cache'] = 'npm';
+ getStateSpy.mockImplementation((name: string) => {
+ if (name === State.STATE_CACHE_PRIMARY_KEY) {
+ return poetryLockHash;
+ } else if (name === State.CACHE_PATHS) {
+ return JSON.stringify([__dirname]);
+ } else {
+ return requirementsHash;
+ }
+ });
+
+ saveCacheSpy.mockImplementation(() => {
+ throw new cache.ValidationError('Validation failed');
+ });
+
+ await run();
+
+ expect(getInputSpy).toHaveBeenCalled();
+ expect(getStateSpy).toHaveBeenCalledTimes(3);
+ expect(infoSpy).not.toHaveBeenCalledWith();
+ expect(saveCacheSpy).toHaveBeenCalled();
+ expect(setFailedSpy).toHaveBeenCalled();
+ });
});
afterEach(() => {
diff --git a/__tests__/data/Pipfile b/__tests__/data/Pipfile
index 8208ab35..8db54551 100644
--- a/__tests__/data/Pipfile
+++ b/__tests__/data/Pipfile
@@ -4,8 +4,8 @@ verify_ssl = true
name = "pypi"
[packages]
-numpy = "1.22.3"
-pandas = "1.4.2"
+flake8 = "==4.0.1"
+numpy = "==1.23.0"
[dev-packages]
diff --git a/__tests__/data/Pipfile.lock b/__tests__/data/Pipfile.lock
index 5a67a8fc..9fcecefb 100644
--- a/__tests__/data/Pipfile.lock
+++ b/__tests__/data/Pipfile.lock
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
- "sha256": "33e3640eff8b2b6c7149b85568151f39a66c544033b4b3f3f2ec9ad5ce6dfe7e"
+ "sha256": "e9c37110984955621040e2dc8548c026eb8466c23db1b8e69430289b10be8938"
},
"pipfile-spec": 6,
"requires": {
@@ -16,81 +16,64 @@
]
},
"default": {
+ "flake8": {
+ "hashes": [
+ "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d",
+ "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"
+ ],
+ "index": "pypi",
+ "version": "==4.0.1"
+ },
+ "mccabe": {
+ "hashes": [
+ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
+ "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
+ ],
+ "version": "==0.6.1"
+ },
"numpy": {
"hashes": [
- "sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676",
- "sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4",
- "sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce",
- "sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123",
- "sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1",
- "sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e",
- "sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5",
- "sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d",
- "sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a",
- "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab",
- "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75",
- "sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168",
- "sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4",
- "sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f",
- "sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18",
- "sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62",
- "sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe",
- "sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430",
- "sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802",
- "sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa"
+ "sha256:092f5e6025813e64ad6d1b52b519165d08c730d099c114a9247c9bb635a2a450",
+ "sha256:196cd074c3f97c4121601790955f915187736f9cf458d3ee1f1b46aff2b1ade0",
+ "sha256:1c29b44905af288b3919803aceb6ec7fec77406d8b08aaa2e8b9e63d0fe2f160",
+ "sha256:2b2da66582f3a69c8ce25ed7921dcd8010d05e59ac8d89d126a299be60421171",
+ "sha256:5043bcd71fcc458dfb8a0fc5509bbc979da0131b9d08e3d5f50fb0bbb36f169a",
+ "sha256:58bfd40eb478f54ff7a5710dd61c8097e169bc36cc68333d00a9bcd8def53b38",
+ "sha256:79a506cacf2be3a74ead5467aee97b81fca00c9c4c8b3ba16dbab488cd99ba10",
+ "sha256:94b170b4fa0168cd6be4becf37cb5b127bd12a795123984385b8cd4aca9857e5",
+ "sha256:97a76604d9b0e79f59baeca16593c711fddb44936e40310f78bfef79ee9a835f",
+ "sha256:98e8e0d8d69ff4d3fa63e6c61e8cfe2d03c29b16b58dbef1f9baa175bbed7860",
+ "sha256:ac86f407873b952679f5f9e6c0612687e51547af0e14ddea1eedfcb22466babd",
+ "sha256:ae8adff4172692ce56233db04b7ce5792186f179c415c37d539c25de7298d25d",
+ "sha256:bd3fa4fe2e38533d5336e1272fc4e765cabbbde144309ccee8675509d5cd7b05",
+ "sha256:d0d2094e8f4d760500394d77b383a1b06d3663e8892cdf5df3c592f55f3bff66",
+ "sha256:d54b3b828d618a19779a84c3ad952e96e2c2311b16384e973e671aa5be1f6187",
+ "sha256:d6ca8dabe696c2785d0c8c9b0d8a9b6e5fdbe4f922bde70d57fa1a2848134f95",
+ "sha256:d8cc87bed09de55477dba9da370c1679bd534df9baa171dd01accbb09687dac3",
+ "sha256:f0f18804df7370571fb65db9b98bf1378172bd4e962482b857e612d1fec0f53e",
+ "sha256:f1d88ef79e0a7fa631bb2c3dda1ea46b32b1fe614e10fedd611d3d5398447f2f",
+ "sha256:f9c3fc2adf67762c9fe1849c859942d23f8d3e0bee7b5ed3d4a9c3eeb50a2f07",
+ "sha256:fc431493df245f3c627c0c05c2bd134535e7929dbe2e602b80e42bf52ff760bc",
+ "sha256:fe8b9683eb26d2c4d5db32cd29b38fdcf8381324ab48313b5b69088e0e355379"
],
"index": "pypi",
- "version": "==1.22.3"
+ "version": "==1.23.0"
},
- "pandas": {
+ "pycodestyle": {
"hashes": [
- "sha256:0010771bd9223f7afe5f051eb47c4a49534345dfa144f2f5470b27189a4dd3b5",
- "sha256:061609334a8182ab500a90fe66d46f6f387de62d3a9cb9aa7e62e3146c712167",
- "sha256:09d8be7dd9e1c4c98224c4dfe8abd60d145d934e9fc1f5f411266308ae683e6a",
- "sha256:295872bf1a09758aba199992c3ecde455f01caf32266d50abc1a073e828a7b9d",
- "sha256:3228198333dd13c90b6434ddf61aa6d57deaca98cf7b654f4ad68a2db84f8cfe",
- "sha256:385c52e85aaa8ea6a4c600a9b2821181a51f8be0aee3af6f2dcb41dafc4fc1d0",
- "sha256:51649ef604a945f781105a6d2ecf88db7da0f4868ac5d45c51cb66081c4d9c73",
- "sha256:5586cc95692564b441f4747c47c8a9746792e87b40a4680a2feb7794defb1ce3",
- "sha256:5a206afa84ed20e07603f50d22b5f0db3fb556486d8c2462d8bc364831a4b417",
- "sha256:5b79af3a69e5175c6fa7b4e046b21a646c8b74e92c6581a9d825687d92071b51",
- "sha256:5c54ea4ef3823108cd4ec7fb27ccba4c3a775e0f83e39c5e17f5094cb17748bc",
- "sha256:8c5bf555b6b0075294b73965adaafb39cf71c312e38c5935c93d78f41c19828a",
- "sha256:92bc1fc585f1463ca827b45535957815b7deb218c549b7c18402c322c7549a12",
- "sha256:95c1e422ced0199cf4a34385ff124b69412c4bc912011ce895582bee620dfcaa",
- "sha256:b8134651258bce418cb79c71adeff0a44090c98d955f6953168ba16cc285d9f7",
- "sha256:be67c782c4f1b1f24c2f16a157e12c2693fd510f8df18e3287c77f33d124ed07",
- "sha256:c072c7f06b9242c855ed8021ff970c0e8f8b10b35e2640c657d2a541c5950f59",
- "sha256:d0d4f13e4be7ce89d7057a786023c461dd9370040bdb5efa0a7fe76b556867a0",
- "sha256:df82739e00bb6daf4bba4479a40f38c718b598a84654cbd8bb498fd6b0aa8c16",
- "sha256:f549097993744ff8c41b5e8f2f0d3cbfaabe89b4ae32c8c08ead6cc535b80139",
- "sha256:ff08a14ef21d94cdf18eef7c569d66f2e24e0bc89350bcd7d243dd804e3b5eb2"
+ "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20",
+ "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"
],
- "index": "pypi",
- "version": "==1.4.2"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
+ "version": "==2.8.0"
},
- "python-dateutil": {
+ "pyflakes": {
"hashes": [
- "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86",
- "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"
+ "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c",
+ "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"
],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
- "version": "==2.8.2"
- },
- "pytz": {
- "hashes": [
- "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7",
- "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"
- ],
- "version": "==2022.1"
- },
- "six": {
- "hashes": [
- "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
- "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
- ],
- "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'",
- "version": "==1.16.0"
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.4.0"
}
},
"develop": {}
diff --git a/__tests__/data/pyproject.toml b/__tests__/data/pyproject.toml
new file mode 100644
index 00000000..ff4d8bd0
--- /dev/null
+++ b/__tests__/data/pyproject.toml
@@ -0,0 +1,15 @@
+[tool.poetry]
+name = "testactiontasks"
+version = "0.1.0"
+description = ""
+authors = ["Your Name "]
+
+[tool.poetry.dependencies]
+python = "^3.8"
+flake8 = "^4.0.1"
+
+[tool.poetry.dev-dependencies]
+
+[build-system]
+requires = ["poetry-core>=1.0.0"]
+build-backend = "poetry.core.masonry.api"
diff --git a/__tests__/find-pypy.test.ts b/__tests__/find-pypy.test.ts
index 8cb32509..660f23d3 100644
--- a/__tests__/find-pypy.test.ts
+++ b/__tests__/find-pypy.test.ts
@@ -5,6 +5,7 @@ import {HttpClient} from '@actions/http-client';
import * as ifm from '@actions/http-client/interfaces';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
+import * as core from '@actions/core';
import * as path from 'path';
import * as semver from 'semver';
@@ -13,7 +14,6 @@ import * as finder from '../src/find-pypy';
import {
IPyPyManifestRelease,
IS_WINDOWS,
- validateVersion,
getPyPyVersionFromPath
} from '../src/utils';
@@ -81,6 +81,12 @@ describe('findPyPyToolCache', () => {
const pypyPath = path.join('PyPy', actualPythonVersion, architecture);
let tcFind: jest.SpyInstance;
let spyReadExactPyPyVersion: jest.SpyInstance;
+ let infoSpy: jest.SpyInstance;
+ let warningSpy: jest.SpyInstance;
+ let debugSpy: jest.SpyInstance;
+ let addPathSpy: jest.SpyInstance;
+ let exportVariableSpy: jest.SpyInstance;
+ let setOutputSpy: jest.SpyInstance;
beforeEach(() => {
tcFind = jest.spyOn(tc, 'find');
@@ -93,6 +99,24 @@ describe('findPyPyToolCache', () => {
spyReadExactPyPyVersion = jest.spyOn(utils, 'readExactPyPyVersionFile');
spyReadExactPyPyVersion.mockImplementation(() => actualPyPyVersion);
+
+ infoSpy = jest.spyOn(core, 'info');
+ infoSpy.mockImplementation(() => null);
+
+ warningSpy = jest.spyOn(core, 'warning');
+ warningSpy.mockImplementation(() => null);
+
+ debugSpy = jest.spyOn(core, 'debug');
+ debugSpy.mockImplementation(() => null);
+
+ addPathSpy = jest.spyOn(core, 'addPath');
+ addPathSpy.mockImplementation(() => null);
+
+ exportVariableSpy = jest.spyOn(core, 'exportVariable');
+ exportVariableSpy.mockImplementation(() => null);
+
+ setOutputSpy = jest.spyOn(core, 'setOutput');
+ setOutputSpy.mockImplementation(() => null);
});
afterEach(() => {
@@ -135,6 +159,13 @@ describe('findPyPyToolCache', () => {
});
describe('findPyPyVersion', () => {
+ let getBooleanInputSpy: jest.SpyInstance;
+ let warningSpy: jest.SpyInstance;
+ let debugSpy: jest.SpyInstance;
+ let infoSpy: jest.SpyInstance;
+ let addPathSpy: jest.SpyInstance;
+ let exportVariableSpy: jest.SpyInstance;
+ let setOutputSpy: jest.SpyInstance;
let tcFind: jest.SpyInstance;
let spyExtractZip: jest.SpyInstance;
let spyExtractTar: jest.SpyInstance;
@@ -148,8 +179,34 @@ describe('findPyPyVersion', () => {
let spyWriteExactPyPyVersionFile: jest.SpyInstance;
let spyCacheDir: jest.SpyInstance;
let spyChmodSync: jest.SpyInstance;
+ let spyCoreAddPath: jest.SpyInstance;
+ let spyCoreExportVariable: jest.SpyInstance;
+ const env = process.env;
beforeEach(() => {
+ getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
+ getBooleanInputSpy.mockImplementation(() => false);
+
+ infoSpy = jest.spyOn(core, 'info');
+ infoSpy.mockImplementation(() => {});
+
+ warningSpy = jest.spyOn(core, 'warning');
+ warningSpy.mockImplementation(() => null);
+
+ debugSpy = jest.spyOn(core, 'debug');
+ debugSpy.mockImplementation(() => null);
+
+ addPathSpy = jest.spyOn(core, 'addPath');
+ addPathSpy.mockImplementation(() => null);
+
+ exportVariableSpy = jest.spyOn(core, 'exportVariable');
+ exportVariableSpy.mockImplementation(() => null);
+
+ setOutputSpy = jest.spyOn(core, 'setOutput');
+ setOutputSpy.mockImplementation(() => null);
+
+ jest.resetModules();
+ process.env = {...env};
tcFind = jest.spyOn(tc, 'find');
tcFind.mockImplementation((tool: string, version: string) => {
const semverRange = new semver.Range(version);
@@ -201,32 +258,46 @@ describe('findPyPyVersion', () => {
spyExistsSync = jest.spyOn(fs, 'existsSync');
spyExistsSync.mockReturnValue(true);
+
+ spyCoreAddPath = jest.spyOn(core, 'addPath');
+
+ spyCoreExportVariable = jest.spyOn(core, 'exportVariable');
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
jest.restoreAllMocks();
+ process.env = env;
});
it('found PyPy in toolcache', async () => {
await expect(
- finder.findPyPyVersion('pypy-3.6-v7.3.x', architecture)
+ finder.findPyPyVersion('pypy-3.6-v7.3.x', architecture, true, false)
).resolves.toEqual({
resolvedPythonVersion: '3.6.12',
resolvedPyPyVersion: '7.3.3'
});
+ expect(spyCoreAddPath).toHaveBeenCalled();
+ expect(spyCoreExportVariable).toHaveBeenCalledWith(
+ 'pythonLocation',
+ expect.anything()
+ );
+ expect(spyCoreExportVariable).toHaveBeenCalledWith(
+ 'PKG_CONFIG_PATH',
+ expect.anything()
+ );
});
it('throw on invalid input format', async () => {
await expect(
- finder.findPyPyVersion('pypy3.7-v7.3.x', architecture)
+ finder.findPyPyVersion('pypy3.7-v7.3.x', architecture, true, false)
).rejects.toThrow();
});
it('throw on invalid input format pypy3.7-7.3.x', async () => {
await expect(
- finder.findPyPyVersion('pypy3.7-v7.3.x', architecture)
+ finder.findPyPyVersion('pypy3.7-v7.3.x', architecture, true, false)
).rejects.toThrow();
});
@@ -238,18 +309,96 @@ describe('findPyPyVersion', () => {
spyChmodSync = jest.spyOn(fs, 'chmodSync');
spyChmodSync.mockImplementation(() => undefined);
await expect(
- finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture)
+ finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture, true, false)
).resolves.toEqual({
resolvedPythonVersion: '3.7.9',
resolvedPyPyVersion: '7.3.3'
});
+ expect(spyCoreAddPath).toHaveBeenCalled();
+ expect(spyCoreExportVariable).toHaveBeenCalledWith(
+ 'pythonLocation',
+ expect.anything()
+ );
+ expect(spyCoreExportVariable).toHaveBeenCalledWith(
+ 'PKG_CONFIG_PATH',
+ expect.anything()
+ );
+ });
+
+ it('found and install successfully without environment update', async () => {
+ spyCacheDir = jest.spyOn(tc, 'cacheDir');
+ spyCacheDir.mockImplementation(() =>
+ path.join(toolDir, 'PyPy', '3.7.7', architecture)
+ );
+ spyChmodSync = jest.spyOn(fs, 'chmodSync');
+ spyChmodSync.mockImplementation(() => undefined);
+ await expect(
+ finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture, false, false)
+ ).resolves.toEqual({
+ resolvedPythonVersion: '3.7.9',
+ resolvedPyPyVersion: '7.3.3'
+ });
+ expect(spyCoreAddPath).not.toHaveBeenCalled();
+ expect(spyCoreExportVariable).not.toHaveBeenCalled();
});
it('throw if release is not found', async () => {
await expect(
- finder.findPyPyVersion('pypy-3.7-v7.5.x', architecture)
+ finder.findPyPyVersion('pypy-3.7-v7.5.x', architecture, true, false)
).rejects.toThrowError(
`PyPy version 3.7 (v7.5.x) with arch ${architecture} not found`
);
});
+
+ it('check-latest enabled version found and used from toolcache', async () => {
+ await expect(
+ finder.findPyPyVersion('pypy-3.6-v7.3.x', architecture, false, true)
+ ).resolves.toEqual({
+ resolvedPythonVersion: '3.6.12',
+ resolvedPyPyVersion: '7.3.3'
+ });
+
+ expect(infoSpy).toHaveBeenCalledWith(
+ 'Resolved as PyPy 7.3.3 with Python (3.6.12)'
+ );
+ });
+
+ it('check-latest enabled version found and install successfully', async () => {
+ spyCacheDir = jest.spyOn(tc, 'cacheDir');
+ spyCacheDir.mockImplementation(() =>
+ path.join(toolDir, 'PyPy', '3.7.7', architecture)
+ );
+ spyChmodSync = jest.spyOn(fs, 'chmodSync');
+ spyChmodSync.mockImplementation(() => undefined);
+ await expect(
+ finder.findPyPyVersion('pypy-3.7-v7.3.x', architecture, false, true)
+ ).resolves.toEqual({
+ resolvedPythonVersion: '3.7.9',
+ resolvedPyPyVersion: '7.3.3'
+ });
+ expect(infoSpy).toHaveBeenCalledWith(
+ 'Resolved as PyPy 7.3.3 with Python (3.7.9)'
+ );
+ });
+
+ it('check-latest enabled version is not found and used from toolcache', async () => {
+ tcFind.mockImplementationOnce((tool: string, version: string) => {
+ const semverRange = new semver.Range(version);
+ let pypyPath = '';
+ if (semver.satisfies('3.8.8', semverRange)) {
+ pypyPath = path.join(toolDir, 'PyPy', '3.8.8', architecture);
+ }
+ return pypyPath;
+ });
+ await expect(
+ finder.findPyPyVersion('pypy-3.8-v7.3.x', architecture, false, true)
+ ).resolves.toEqual({
+ resolvedPythonVersion: '3.8.8',
+ resolvedPyPyVersion: '7.3.3'
+ });
+
+ expect(infoSpy).toHaveBeenCalledWith(
+ 'Failed to resolve PyPy v7.3.x with Python (3.8) from manifest'
+ );
+ });
});
diff --git a/__tests__/finder.test.ts b/__tests__/finder.test.ts
index d4805696..d2fe775b 100644
--- a/__tests__/finder.test.ts
+++ b/__tests__/finder.test.ts
@@ -1,6 +1,7 @@
-import io = require('@actions/io');
-import fs = require('fs');
-import path = require('path');
+import * as io from '@actions/io';
+import os from 'os';
+import fs from 'fs';
+import path from 'path';
const toolDir = path.join(
__dirname,
@@ -19,29 +20,71 @@ process.env['RUNNER_TOOL_CACHE'] = toolDir;
process.env['RUNNER_TEMP'] = tempDir;
import * as tc from '@actions/tool-cache';
+import * as core from '@actions/core';
import * as finder from '../src/find-python';
import * as installer from '../src/install-python';
const manifestData = require('./data/versions-manifest.json');
describe('Finder tests', () => {
+ let writeSpy: jest.SpyInstance;
+ let spyCoreAddPath: jest.SpyInstance;
+ let spyCoreExportVariable: jest.SpyInstance;
+ const env = process.env;
+
+ beforeEach(() => {
+ writeSpy = jest.spyOn(process.stdout, 'write');
+ writeSpy.mockImplementation(() => {});
+ jest.resetModules();
+ process.env = {...env};
+ spyCoreAddPath = jest.spyOn(core, 'addPath');
+ spyCoreExportVariable = jest.spyOn(core, 'exportVariable');
+ });
+
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
+ jest.restoreAllMocks();
+ process.env = env;
});
it('Finds Python if it is installed', async () => {
+ const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
+ getBooleanInputSpy.mockImplementation(input => false);
+
const pythonDir: string = path.join(toolDir, 'Python', '3.0.0', 'x64');
await io.mkdirP(pythonDir);
fs.writeFileSync(`${pythonDir}.complete`, 'hello');
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
- await finder.useCpythonVersion('3.x', 'x64');
+ await finder.useCpythonVersion('3.x', 'x64', true, false);
+ expect(spyCoreAddPath).toHaveBeenCalled();
+ expect(spyCoreExportVariable).toHaveBeenCalledWith(
+ 'pythonLocation',
+ expect.anything()
+ );
+ expect(spyCoreExportVariable).toHaveBeenCalledWith(
+ 'PKG_CONFIG_PATH',
+ expect.anything()
+ );
+ });
+
+ it('Finds Python if it is installed without environment update', async () => {
+ const pythonDir: string = path.join(toolDir, 'Python', '3.0.0', 'x64');
+ await io.mkdirP(pythonDir);
+ fs.writeFileSync(`${pythonDir}.complete`, 'hello');
+ // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
+ await finder.useCpythonVersion('3.x', 'x64', false, false);
+ expect(spyCoreAddPath).not.toHaveBeenCalled();
+ expect(spyCoreExportVariable).not.toHaveBeenCalled();
});
it('Finds stable Python version if it is not installed, but exists in the manifest', async () => {
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => manifestData);
+ const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
+ getBooleanInputSpy.mockImplementation(input => false);
+
const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
@@ -52,13 +95,25 @@ describe('Finder tests', () => {
fs.writeFileSync(`${pythonDir}.complete`, 'hello');
});
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
- await finder.useCpythonVersion('1.2.3', 'x64');
+ await finder.useCpythonVersion('1.2.3', 'x64', true, false);
+ expect(spyCoreAddPath).toHaveBeenCalled();
+ expect(spyCoreExportVariable).toHaveBeenCalledWith(
+ 'pythonLocation',
+ expect.anything()
+ );
+ expect(spyCoreExportVariable).toHaveBeenCalledWith(
+ 'PKG_CONFIG_PATH',
+ expect.anything()
+ );
});
it('Finds pre-release Python version in the manifest', async () => {
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => manifestData);
+ const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
+ getBooleanInputSpy.mockImplementation(input => false);
+
const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
@@ -74,17 +129,86 @@ describe('Finder tests', () => {
fs.writeFileSync(`${pythonDir}.complete`, 'hello');
});
// This will throw if it doesn't find it in the manifest (because no such version exists)
- await finder.useCpythonVersion('1.2.3-beta.2', 'x64');
+ await finder.useCpythonVersion('1.2.3-beta.2', 'x64', false, false);
+ });
+
+ it('Check-latest true, finds the latest version in the manifest', async () => {
+ const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
+ findSpy.mockImplementation(() => manifestData);
+
+ const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
+ getBooleanInputSpy.mockImplementation(input => true);
+
+ const cnSpy: jest.SpyInstance = jest.spyOn(process.stdout, 'write');
+ cnSpy.mockImplementation(line => {
+ // uncomment to debug
+ // process.stderr.write('write:' + line + '\n');
+ });
+
+ const addPathSpy: jest.SpyInstance = jest.spyOn(core, 'addPath');
+ addPathSpy.mockImplementation(() => null);
+
+ const infoSpy: jest.SpyInstance = jest.spyOn(core, 'info');
+ infoSpy.mockImplementation(() => {});
+
+ const debugSpy: jest.SpyInstance = jest.spyOn(core, 'debug');
+ debugSpy.mockImplementation(() => {});
+
+ const pythonDir: string = path.join(toolDir, 'Python', '1.2.2', 'x64');
+ const expPath: string = path.join(toolDir, 'Python', '1.2.3', 'x64');
+
+ const installSpy: jest.SpyInstance = jest.spyOn(
+ installer,
+ 'installCpythonFromRelease'
+ );
+ installSpy.mockImplementation(async () => {
+ await io.mkdirP(expPath);
+ fs.writeFileSync(`${expPath}.complete`, 'hello');
+ });
+
+ const tcFindSpy: jest.SpyInstance = jest.spyOn(tc, 'find');
+ tcFindSpy
+ .mockImplementationOnce(() => '')
+ .mockImplementationOnce(() => expPath);
+
+ await io.mkdirP(pythonDir);
+ await io.rmRF(path.join(toolDir, 'Python', '1.2.3'));
+
+ fs.writeFileSync(`${pythonDir}.complete`, 'hello');
+ // This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
+ await finder.useCpythonVersion('1.2', 'x64', true, true);
+
+ expect(infoSpy).toHaveBeenCalledWith("Resolved as '1.2.3'");
+ expect(infoSpy).toHaveBeenCalledWith(
+ 'Version 1.2.3 was not found in the local cache'
+ );
+ expect(infoSpy).toBeCalledWith(
+ 'Version 1.2.3 is available for downloading'
+ );
+ expect(installSpy).toHaveBeenCalled();
+ expect(addPathSpy).toHaveBeenCalledWith(expPath);
+ await finder.useCpythonVersion('1.2.3-beta.2', 'x64', false, true);
+ expect(spyCoreAddPath).toHaveBeenCalled();
+ expect(spyCoreExportVariable).toHaveBeenCalledWith(
+ 'pythonLocation',
+ expect.anything()
+ );
+ expect(spyCoreExportVariable).toHaveBeenCalledWith(
+ 'PKG_CONFIG_PATH',
+ expect.anything()
+ );
});
it('Errors if Python is not installed', async () => {
// This will throw if it doesn't find it in the cache and in the manifest (because no such version exists)
let thrown = false;
try {
- await finder.useCpythonVersion('3.300000', 'x64');
+ await finder.useCpythonVersion('3.300000', 'x64', true, false);
} catch {
thrown = true;
}
expect(thrown).toBeTruthy();
+ expect(spyCoreAddPath).not.toHaveBeenCalled();
+ expect(spyCoreExportVariable).not.toHaveBeenCalled();
});
});
diff --git a/__tests__/install-pypy.test.ts b/__tests__/install-pypy.test.ts
index cffc90e8..ae7fb4a6 100644
--- a/__tests__/install-pypy.test.ts
+++ b/__tests__/install-pypy.test.ts
@@ -4,6 +4,7 @@ import {HttpClient} from '@actions/http-client';
import * as ifm from '@actions/http-client/interfaces';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
+import * as core from '@actions/core';
import * as path from 'path';
import * as installer from '../src/install-pypy';
@@ -51,6 +52,22 @@ describe('findRelease', () => {
download_url: `https://test.download.python.org/pypy/pypy3.6-v7.3.3-${extensionName}`
};
+ let getBooleanInputSpy: jest.SpyInstance;
+ let warningSpy: jest.SpyInstance;
+ let debugSpy: jest.SpyInstance;
+ let infoSpy: jest.SpyInstance;
+
+ beforeEach(() => {
+ infoSpy = jest.spyOn(core, 'info');
+ infoSpy.mockImplementation(() => {});
+
+ warningSpy = jest.spyOn(core, 'warning');
+ warningSpy.mockImplementation(() => null);
+
+ debugSpy = jest.spyOn(core, 'debug');
+ debugSpy.mockImplementation(() => null);
+ });
+
it("Python version is found, but PyPy version doesn't match", () => {
const pythonVersion = '3.6';
const pypyVersion = '7.3.7';
@@ -133,6 +150,10 @@ describe('findRelease', () => {
describe('installPyPy', () => {
let tcFind: jest.SpyInstance;
+ let getBooleanInputSpy: jest.SpyInstance;
+ let warningSpy: jest.SpyInstance;
+ let debugSpy: jest.SpyInstance;
+ let infoSpy: jest.SpyInstance;
let spyExtractZip: jest.SpyInstance;
let spyExtractTar: jest.SpyInstance;
let spyFsReadDir: jest.SpyInstance;
@@ -158,6 +179,15 @@ describe('installPyPy', () => {
spyExtractTar = jest.spyOn(tc, 'extractTar');
spyExtractTar.mockImplementation(() => tempDir);
+ infoSpy = jest.spyOn(core, 'info');
+ infoSpy.mockImplementation(() => {});
+
+ warningSpy = jest.spyOn(core, 'warning');
+ warningSpy.mockImplementation(() => null);
+
+ debugSpy = jest.spyOn(core, 'debug');
+ debugSpy.mockImplementation(() => null);
+
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation(() => ['PyPyTest']);
@@ -194,7 +224,7 @@ describe('installPyPy', () => {
it('throw if release is not found', async () => {
await expect(
- installer.installPyPy('7.3.3', '3.6.17', architecture)
+ installer.installPyPy('7.3.3', '3.6.17', architecture, undefined)
).rejects.toThrowError(
`PyPy version 3.6.17 (7.3.3) with arch ${architecture} not found`
);
@@ -214,7 +244,7 @@ describe('installPyPy', () => {
spyChmodSync.mockImplementation(() => undefined);
await expect(
- installer.installPyPy('7.3.x', '3.6.12', architecture)
+ installer.installPyPy('7.3.x', '3.6.12', architecture, undefined)
).resolves.toEqual({
installDir: path.join(toolDir, 'PyPy', '3.6.12', architecture),
resolvedPythonVersion: '3.6.12',
diff --git a/action.yml b/action.yml
index 6b0224f4..5ff5761f 100644
--- a/action.yml
+++ b/action.yml
@@ -1,29 +1,35 @@
---
-name: 'Setup Python'
-description: 'Set up a specific version of Python and add the command-line tools to the PATH.'
-author: 'GitHub'
+name: "Setup Python"
+description: "Set up a specific version of Python and add the command-line tools to the PATH."
+author: "GitHub"
inputs:
python-version:
- description: "Version range or exact version of Python to use, using SemVer's version range syntax. Reads from .python-version if unset."
+ description: "Version range or exact version of Python or PyPy to use, using SemVer's version range syntax. Reads from .python-version if unset."
python-version-file:
description: "File containing the Python version to use. Example: .python-version"
cache:
- description: 'Used to specify a package manager for caching in the default directory. Supported values: pip, pipenv, poetry.'
+ description: "Used to specify a package manager for caching in the default directory. Supported values: pip, pipenv, poetry."
required: false
architecture:
- description: 'The target architecture (x86, x64) of the Python interpreter.'
+ description: "The target architecture (x86, x64) of the Python or PyPy interpreter."
+ check-latest:
+ 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: Used to pull python distributions from actions/python-versions. Since there's a default, this is typically not supplied by the user.
+ description: "Used to pull python distributions from actions/python-versions. Since there's a default, this is typically not supplied by the user."
default: ${{ github.server_url == 'https://github.com' && github.token || '' }}
cache-dependency-path:
- description: 'Used to specify the path to dependency files. Supports wildcards or a list of file names for caching multiple dependencies.'
+ description: "Used to specify the path to dependency files. Supports wildcards or a list of file names for caching multiple dependencies."
+ update-environment:
+ description: "Set this option if you want the action to update environment variables."
+ default: true
outputs:
python-version:
- description: "The installed python version. Useful when given a version range as input."
+ description: "The installed Python or PyPy version. Useful when given a version range as input."
cache-hit:
- description: 'A boolean value to indicate a cache entry was found'
+ description: "A boolean value to indicate a cache entry was found"
python-path:
- description: "The absolute path to the Python executable."
+ description: "The absolute path to the Python or PyPy executable."
runs:
using: 'node16'
main: 'dist/setup/index.js'
diff --git a/dist/cache-save/index.js b/dist/cache-save/index.js
index 35cedd90..b3fd8a73 100644
--- a/dist/cache-save/index.js
+++ b/dist/cache-save/index.js
@@ -90,17 +90,18 @@ function restoreCache(paths, primaryKey, restoreKeys, options) {
checkKey(key);
}
const compressionMethod = yield utils.getCompressionMethod();
- // path are needed to compute version
- const cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, {
- compressionMethod
- });
- if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) {
- // Cache not found
- return undefined;
- }
- const archivePath = path.join(yield utils.createTempDirectory(), utils.getCacheFileName(compressionMethod));
- core.debug(`Archive Path: ${archivePath}`);
+ let archivePath = '';
try {
+ // path are needed to compute version
+ const cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, {
+ compressionMethod
+ });
+ if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) {
+ // Cache not found
+ return undefined;
+ }
+ archivePath = path.join(yield utils.createTempDirectory(), utils.getCacheFileName(compressionMethod));
+ core.debug(`Archive Path: ${archivePath}`);
// Download the cache from the cache entry
yield cacheHttpClient.downloadCache(cacheEntry.archiveLocation, archivePath, options);
if (core.isDebug()) {
@@ -110,6 +111,17 @@ function restoreCache(paths, primaryKey, restoreKeys, options) {
core.info(`Cache Size: ~${Math.round(archiveFileSize / (1024 * 1024))} MB (${archiveFileSize} B)`);
yield tar_1.extractTar(archivePath, compressionMethod);
core.info('Cache restored successfully');
+ return cacheEntry.cacheKey;
+ }
+ catch (error) {
+ const typedError = error;
+ if (typedError.name === ValidationError.name) {
+ throw error;
+ }
+ else {
+ // Supress all non-validation cache related errors because caching should be optional
+ core.warning(`Failed to restore: ${error.message}`);
+ }
}
finally {
// Try to delete the archive to save space
@@ -120,7 +132,7 @@ function restoreCache(paths, primaryKey, restoreKeys, options) {
core.debug(`Failed to delete archive: ${error}`);
}
}
- return cacheEntry.cacheKey;
+ return undefined;
});
}
exports.restoreCache = restoreCache;
@@ -138,10 +150,13 @@ function saveCache(paths, key, options) {
checkPaths(paths);
checkKey(key);
const compressionMethod = yield utils.getCompressionMethod();
- let cacheId = null;
+ let cacheId = -1;
const cachePaths = yield utils.resolvePaths(paths);
core.debug('Cache Paths:');
core.debug(`${JSON.stringify(cachePaths)}`);
+ if (cachePaths.length === 0) {
+ throw new Error(`Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.`);
+ }
const archiveFolder = yield utils.createTempDirectory();
const archivePath = path.join(archiveFolder, utils.getCacheFileName(compressionMethod));
core.debug(`Archive Path: ${archivePath}`);
@@ -174,6 +189,18 @@ function saveCache(paths, key, options) {
core.debug(`Saving Cache (ID: ${cacheId})`);
yield cacheHttpClient.saveCache(cacheId, archivePath, options);
}
+ catch (error) {
+ const typedError = error;
+ if (typedError.name === ValidationError.name) {
+ throw error;
+ }
+ else if (typedError.name === ReserveCacheError.name) {
+ core.info(`Failed to save: ${typedError.message}`);
+ }
+ else {
+ core.warning(`Failed to save: ${typedError.message}`);
+ }
+ }
finally {
// Try to delete the archive to save space
try {
@@ -214,8 +241,8 @@ var __importStar = (this && this.__importStar) || function (mod) {
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const core = __importStar(__nccwpck_require__(2186));
-const http_client_1 = __nccwpck_require__(7320);
-const auth_1 = __nccwpck_require__(7093);
+const http_client_1 = __nccwpck_require__(1825);
+const auth_1 = __nccwpck_require__(2001);
const crypto = __importStar(__nccwpck_require__(6113));
const fs = __importStar(__nccwpck_require__(7147));
const url_1 = __nccwpck_require__(7310);
@@ -647,7 +674,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const core = __importStar(__nccwpck_require__(2186));
-const http_client_1 = __nccwpck_require__(7320);
+const http_client_1 = __nccwpck_require__(1825);
const storage_blob_1 = __nccwpck_require__(4100);
const buffer = __importStar(__nccwpck_require__(4300));
const fs = __importStar(__nccwpck_require__(7147));
@@ -885,7 +912,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const core = __importStar(__nccwpck_require__(2186));
-const http_client_1 = __nccwpck_require__(7320);
+const http_client_1 = __nccwpck_require__(1825);
const constants_1 = __nccwpck_require__(8840);
function isSuccessStatusCode(statusCode) {
if (!statusCode) {
@@ -962,7 +989,7 @@ function retryTypedResponse(name, method, maxAttempts = constants_1.DefaultRetry
return __awaiter(this, void 0, void 0, function* () {
return yield retry(name, method, (response) => response.statusCode, maxAttempts, delay,
// If the error object contains the statusCode property, extract it and return
- // an ITypedResponse so it can be processed by the retry logic.
+ // an TypedResponse so it can be processed by the retry logic.
(error) => {
if (error instanceof http_client_1.HttpClientError) {
return {
@@ -1120,6 +1147,8 @@ function createTar(archiveFolder, sourceDirectories, compressionMethod) {
...getCompressionProgram(),
'-cf',
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
+ '--exclude',
+ cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
'-P',
'-C',
workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
@@ -2292,28 +2321,41 @@ exports.SearchState = SearchState;
/***/ }),
-/***/ 7093:
-/***/ ((__unused_webpack_module, exports) => {
+/***/ 2001:
+/***/ (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.PersonalAccessTokenCredentialHandler = exports.BearerCredentialHandler = exports.BasicCredentialHandler = void 0;
class BasicCredentialHandler {
constructor(username, password) {
this.username = username;
this.password = password;
}
prepareRequest(options) {
- options.headers['Authorization'] =
- 'Basic ' +
- Buffer.from(this.username + ':' + this.password).toString('base64');
+ if (!options.headers) {
+ throw Error('The request has no headers');
+ }
+ options.headers['Authorization'] = `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`;
}
// This handler cannot handle 401
- canHandleAuthentication(response) {
+ canHandleAuthentication() {
return false;
}
- handleAuthentication(httpClient, requestInfo, objs) {
- return null;
+ handleAuthentication() {
+ return __awaiter(this, void 0, void 0, function* () {
+ throw new Error('not implemented');
+ });
}
}
exports.BasicCredentialHandler = BasicCredentialHandler;
@@ -2324,14 +2366,19 @@ class BearerCredentialHandler {
// currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401
prepareRequest(options) {
- options.headers['Authorization'] = 'Bearer ' + this.token;
+ if (!options.headers) {
+ throw Error('The request has no headers');
+ }
+ options.headers['Authorization'] = `Bearer ${this.token}`;
}
// This handler cannot handle 401
- canHandleAuthentication(response) {
+ canHandleAuthentication() {
return false;
}
- handleAuthentication(httpClient, requestInfo, objs) {
- return null;
+ handleAuthentication() {
+ return __awaiter(this, void 0, void 0, function* () {
+ throw new Error('not implemented');
+ });
}
}
exports.BearerCredentialHandler = BearerCredentialHandler;
@@ -2342,32 +2389,66 @@ class PersonalAccessTokenCredentialHandler {
// currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401
prepareRequest(options) {
- options.headers['Authorization'] =
- 'Basic ' + Buffer.from('PAT:' + this.token).toString('base64');
+ if (!options.headers) {
+ throw Error('The request has no headers');
+ }
+ options.headers['Authorization'] = `Basic ${Buffer.from(`PAT:${this.token}`).toString('base64')}`;
}
// This handler cannot handle 401
- canHandleAuthentication(response) {
+ canHandleAuthentication() {
return false;
}
- handleAuthentication(httpClient, requestInfo, objs) {
- return null;
+ handleAuthentication() {
+ return __awaiter(this, void 0, void 0, function* () {
+ throw new Error('not implemented');
+ });
}
}
exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler;
-
+//# sourceMappingURL=auth.js.map
/***/ }),
-/***/ 7320:
-/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
+/***/ 1825:
+/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
+/* eslint-disable @typescript-eslint/no-explicit-any */
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (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.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
+ return result;
+};
+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 }));
-const http = __nccwpck_require__(3685);
-const https = __nccwpck_require__(5687);
-const pm = __nccwpck_require__(7326);
-let tunnel;
+exports.HttpClient = exports.isHttps = exports.HttpClientResponse = exports.HttpClientError = exports.getProxyUrl = exports.MediaTypes = exports.Headers = exports.HttpCodes = void 0;
+const http = __importStar(__nccwpck_require__(3685));
+const https = __importStar(__nccwpck_require__(5687));
+const pm = __importStar(__nccwpck_require__(4977));
+const tunnel = __importStar(__nccwpck_require__(4294));
var HttpCodes;
(function (HttpCodes) {
HttpCodes[HttpCodes["OK"] = 200] = "OK";
@@ -2412,7 +2493,7 @@ var MediaTypes;
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
function getProxyUrl(serverUrl) {
- let proxyUrl = pm.getProxyUrl(new URL(serverUrl));
+ const proxyUrl = pm.getProxyUrl(new URL(serverUrl));
return proxyUrl ? proxyUrl.href : '';
}
exports.getProxyUrl = getProxyUrl;
@@ -2445,20 +2526,22 @@ class HttpClientResponse {
this.message = message;
}
readBody() {
- return new Promise(async (resolve, reject) => {
- let output = Buffer.alloc(0);
- this.message.on('data', (chunk) => {
- output = Buffer.concat([output, chunk]);
- });
- this.message.on('end', () => {
- resolve(output.toString());
- });
+ return __awaiter(this, void 0, void 0, function* () {
+ return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
+ let output = Buffer.alloc(0);
+ this.message.on('data', (chunk) => {
+ output = Buffer.concat([output, chunk]);
+ });
+ this.message.on('end', () => {
+ resolve(output.toString());
+ });
+ }));
});
}
}
exports.HttpClientResponse = HttpClientResponse;
function isHttps(requestUrl) {
- let parsedUrl = new URL(requestUrl);
+ const parsedUrl = new URL(requestUrl);
return parsedUrl.protocol === 'https:';
}
exports.isHttps = isHttps;
@@ -2501,141 +2584,169 @@ class HttpClient {
}
}
options(requestUrl, additionalHeaders) {
- return this.request('OPTIONS', requestUrl, null, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('OPTIONS', requestUrl, null, additionalHeaders || {});
+ });
}
get(requestUrl, additionalHeaders) {
- return this.request('GET', requestUrl, null, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('GET', requestUrl, null, additionalHeaders || {});
+ });
}
del(requestUrl, additionalHeaders) {
- return this.request('DELETE', requestUrl, null, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('DELETE', requestUrl, null, additionalHeaders || {});
+ });
}
post(requestUrl, data, additionalHeaders) {
- return this.request('POST', requestUrl, data, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('POST', requestUrl, data, additionalHeaders || {});
+ });
}
patch(requestUrl, data, additionalHeaders) {
- return this.request('PATCH', requestUrl, data, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('PATCH', requestUrl, data, additionalHeaders || {});
+ });
}
put(requestUrl, data, additionalHeaders) {
- return this.request('PUT', requestUrl, data, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('PUT', requestUrl, data, additionalHeaders || {});
+ });
}
head(requestUrl, additionalHeaders) {
- return this.request('HEAD', requestUrl, null, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('HEAD', requestUrl, null, additionalHeaders || {});
+ });
}
sendStream(verb, requestUrl, stream, additionalHeaders) {
- return this.request(verb, requestUrl, stream, additionalHeaders);
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request(verb, requestUrl, stream, additionalHeaders);
+ });
}
/**
* Gets a typed object from an endpoint
* Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
*/
- async getJson(requestUrl, additionalHeaders = {}) {
- additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
- let res = await this.get(requestUrl, additionalHeaders);
- return this._processResponse(res, this.requestOptions);
+ getJson(requestUrl, additionalHeaders = {}) {
+ return __awaiter(this, void 0, void 0, function* () {
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
+ const res = yield this.get(requestUrl, additionalHeaders);
+ return this._processResponse(res, this.requestOptions);
+ });
}
- async postJson(requestUrl, obj, additionalHeaders = {}) {
- let data = JSON.stringify(obj, null, 2);
- additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
- additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
- let res = await this.post(requestUrl, data, additionalHeaders);
- return this._processResponse(res, this.requestOptions);
+ postJson(requestUrl, obj, additionalHeaders = {}) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const data = JSON.stringify(obj, null, 2);
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
+ additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
+ const res = yield this.post(requestUrl, data, additionalHeaders);
+ return this._processResponse(res, this.requestOptions);
+ });
}
- async putJson(requestUrl, obj, additionalHeaders = {}) {
- let data = JSON.stringify(obj, null, 2);
- additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
- additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
- let res = await this.put(requestUrl, data, additionalHeaders);
- return this._processResponse(res, this.requestOptions);
+ putJson(requestUrl, obj, additionalHeaders = {}) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const data = JSON.stringify(obj, null, 2);
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
+ additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
+ const res = yield this.put(requestUrl, data, additionalHeaders);
+ return this._processResponse(res, this.requestOptions);
+ });
}
- async patchJson(requestUrl, obj, additionalHeaders = {}) {
- let data = JSON.stringify(obj, null, 2);
- additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
- additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
- let res = await this.patch(requestUrl, data, additionalHeaders);
- return this._processResponse(res, this.requestOptions);
+ patchJson(requestUrl, obj, additionalHeaders = {}) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const data = JSON.stringify(obj, null, 2);
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
+ additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
+ const res = yield this.patch(requestUrl, data, additionalHeaders);
+ return this._processResponse(res, this.requestOptions);
+ });
}
/**
* Makes a raw http request.
* All other methods such as get, post, patch, and request ultimately call this.
* Prefer get, del, post and patch
*/
- async request(verb, requestUrl, data, headers) {
- if (this._disposed) {
- throw new Error('Client has already been disposed.');
- }
- let parsedUrl = new URL(requestUrl);
- let info = this._prepareRequest(verb, parsedUrl, headers);
- // Only perform retries on reads since writes may not be idempotent.
- let maxTries = this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1
- ? this._maxRetries + 1
- : 1;
- let numTries = 0;
- let response;
- while (numTries < maxTries) {
- response = await this.requestRaw(info, data);
- // Check if it's an authentication challenge
- if (response &&
- response.message &&
- response.message.statusCode === HttpCodes.Unauthorized) {
- let authenticationHandler;
- for (let i = 0; i < this.handlers.length; i++) {
- if (this.handlers[i].canHandleAuthentication(response)) {
- authenticationHandler = this.handlers[i];
- break;
- }
- }
- if (authenticationHandler) {
- return authenticationHandler.handleAuthentication(this, info, data);
- }
- else {
- // We have received an unauthorized response but have no handlers to handle it.
- // Let the response return to the caller.
- return response;
- }
+ request(verb, requestUrl, data, headers) {
+ return __awaiter(this, void 0, void 0, function* () {
+ if (this._disposed) {
+ throw new Error('Client has already been disposed.');
}
- let redirectsRemaining = this._maxRedirects;
- while (HttpRedirectCodes.indexOf(response.message.statusCode) != -1 &&
- this._allowRedirects &&
- redirectsRemaining > 0) {
- const redirectUrl = response.message.headers['location'];
- if (!redirectUrl) {
- // if there's no location to redirect to, we won't
- break;
- }
- let parsedRedirectUrl = new URL(redirectUrl);
- if (parsedUrl.protocol == 'https:' &&
- parsedUrl.protocol != parsedRedirectUrl.protocol &&
- !this._allowRedirectDowngrade) {
- throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.');
- }
- // we need to finish reading the response before reassigning response
- // which will leak the open socket.
- await response.readBody();
- // strip authorization header if redirected to a different hostname
- if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
- for (let header in headers) {
- // header names are case insensitive
- if (header.toLowerCase() === 'authorization') {
- delete headers[header];
+ const parsedUrl = new URL(requestUrl);
+ let info = this._prepareRequest(verb, parsedUrl, headers);
+ // Only perform retries on reads since writes may not be idempotent.
+ const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb)
+ ? this._maxRetries + 1
+ : 1;
+ let numTries = 0;
+ let response;
+ do {
+ response = yield this.requestRaw(info, data);
+ // Check if it's an authentication challenge
+ if (response &&
+ response.message &&
+ response.message.statusCode === HttpCodes.Unauthorized) {
+ let authenticationHandler;
+ for (const handler of this.handlers) {
+ if (handler.canHandleAuthentication(response)) {
+ authenticationHandler = handler;
+ break;
}
}
+ if (authenticationHandler) {
+ return authenticationHandler.handleAuthentication(this, info, data);
+ }
+ else {
+ // We have received an unauthorized response but have no handlers to handle it.
+ // Let the response return to the caller.
+ return response;
+ }
}
- // let's make the request with the new redirectUrl
- info = this._prepareRequest(verb, parsedRedirectUrl, headers);
- response = await this.requestRaw(info, data);
- redirectsRemaining--;
- }
- if (HttpResponseRetryCodes.indexOf(response.message.statusCode) == -1) {
- // If not a retry code, return immediately instead of retrying
- return response;
- }
- numTries += 1;
- if (numTries < maxTries) {
- await response.readBody();
- await this._performExponentialBackoff(numTries);
- }
- }
- return response;
+ let redirectsRemaining = this._maxRedirects;
+ while (response.message.statusCode &&
+ HttpRedirectCodes.includes(response.message.statusCode) &&
+ this._allowRedirects &&
+ redirectsRemaining > 0) {
+ const redirectUrl = response.message.headers['location'];
+ if (!redirectUrl) {
+ // if there's no location to redirect to, we won't
+ break;
+ }
+ const parsedRedirectUrl = new URL(redirectUrl);
+ if (parsedUrl.protocol === 'https:' &&
+ parsedUrl.protocol !== parsedRedirectUrl.protocol &&
+ !this._allowRedirectDowngrade) {
+ throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.');
+ }
+ // we need to finish reading the response before reassigning response
+ // which will leak the open socket.
+ yield response.readBody();
+ // strip authorization header if redirected to a different hostname
+ if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
+ for (const header in headers) {
+ // header names are case insensitive
+ if (header.toLowerCase() === 'authorization') {
+ delete headers[header];
+ }
+ }
+ }
+ // let's make the request with the new redirectUrl
+ info = this._prepareRequest(verb, parsedRedirectUrl, headers);
+ response = yield this.requestRaw(info, data);
+ redirectsRemaining--;
+ }
+ if (!response.message.statusCode ||
+ !HttpResponseRetryCodes.includes(response.message.statusCode)) {
+ // If not a retry code, return immediately instead of retrying
+ return response;
+ }
+ numTries += 1;
+ if (numTries < maxTries) {
+ yield response.readBody();
+ yield this._performExponentialBackoff(numTries);
+ }
+ } while (numTries < maxTries);
+ return response;
+ });
}
/**
* Needs to be called if keepAlive is set to true in request options.
@@ -2652,14 +2763,22 @@ class HttpClient {
* @param data
*/
requestRaw(info, data) {
- return new Promise((resolve, reject) => {
- let callbackForResult = function (err, res) {
- if (err) {
- reject(err);
+ return __awaiter(this, void 0, void 0, function* () {
+ return new Promise((resolve, reject) => {
+ function callbackForResult(err, res) {
+ if (err) {
+ reject(err);
+ }
+ else if (!res) {
+ // If `err` is not passed, then `res` must be passed.
+ reject(new Error('Unknown error'));
+ }
+ else {
+ resolve(res);
+ }
}
- resolve(res);
- };
- this.requestRawWithCallback(info, data, callbackForResult);
+ this.requestRawWithCallback(info, data, callbackForResult);
+ });
});
}
/**
@@ -2669,21 +2788,24 @@ class HttpClient {
* @param onResult
*/
requestRawWithCallback(info, data, onResult) {
- let socket;
if (typeof data === 'string') {
+ if (!info.options.headers) {
+ info.options.headers = {};
+ }
info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
}
let callbackCalled = false;
- let handleResult = (err, res) => {
+ function handleResult(err, res) {
if (!callbackCalled) {
callbackCalled = true;
onResult(err, res);
}
- };
- let req = info.httpModule.request(info.options, (msg) => {
- let res = new HttpClientResponse(msg);
- handleResult(null, res);
+ }
+ const req = info.httpModule.request(info.options, (msg) => {
+ const res = new HttpClientResponse(msg);
+ handleResult(undefined, res);
});
+ let socket;
req.on('socket', sock => {
socket = sock;
});
@@ -2692,12 +2814,12 @@ class HttpClient {
if (socket) {
socket.end();
}
- handleResult(new Error('Request timeout: ' + info.options.path), null);
+ handleResult(new Error(`Request timeout: ${info.options.path}`));
});
req.on('error', function (err) {
// err has statusCode property
// res should have headers
- handleResult(err, null);
+ handleResult(err);
});
if (data && typeof data === 'string') {
req.write(data, 'utf8');
@@ -2718,7 +2840,7 @@ class HttpClient {
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
getAgent(serverUrl) {
- let parsedUrl = new URL(serverUrl);
+ const parsedUrl = new URL(serverUrl);
return this._getAgent(parsedUrl);
}
_prepareRequest(method, requestUrl, headers) {
@@ -2742,21 +2864,19 @@ class HttpClient {
info.options.agent = this._getAgent(info.parsedUrl);
// gives handlers an opportunity to participate
if (this.handlers) {
- this.handlers.forEach(handler => {
+ for (const handler of this.handlers) {
handler.prepareRequest(info.options);
- });
+ }
}
return info;
}
_mergeHeaders(headers) {
- const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {});
if (this.requestOptions && this.requestOptions.headers) {
- return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers));
+ return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers || {}));
}
return lowercaseKeys(headers || {});
}
_getExistingOrDefaultHeader(additionalHeaders, header, _default) {
- const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {});
let clientHeader;
if (this.requestOptions && this.requestOptions.headers) {
clientHeader = lowercaseKeys(this.requestOptions.headers)[header];
@@ -2765,8 +2885,8 @@ class HttpClient {
}
_getAgent(parsedUrl) {
let agent;
- let proxyUrl = pm.getProxyUrl(parsedUrl);
- let useProxy = proxyUrl && proxyUrl.hostname;
+ const proxyUrl = pm.getProxyUrl(parsedUrl);
+ const useProxy = proxyUrl && proxyUrl.hostname;
if (this._keepAlive && useProxy) {
agent = this._proxyAgent;
}
@@ -2774,29 +2894,22 @@ class HttpClient {
agent = this._agent;
}
// if agent is already assigned use that agent.
- if (!!agent) {
+ if (agent) {
return agent;
}
const usingSsl = parsedUrl.protocol === 'https:';
let maxSockets = 100;
- if (!!this.requestOptions) {
+ if (this.requestOptions) {
maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets;
}
- if (useProxy) {
- // If using proxy, need tunnel
- if (!tunnel) {
- tunnel = __nccwpck_require__(4294);
- }
+ // This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis.
+ if (proxyUrl && proxyUrl.hostname) {
const agentOptions = {
- maxSockets: maxSockets,
+ maxSockets,
keepAlive: this._keepAlive,
- proxy: {
- ...((proxyUrl.username || proxyUrl.password) && {
- proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`
- }),
- host: proxyUrl.hostname,
- port: proxyUrl.port
- }
+ proxy: Object.assign(Object.assign({}, ((proxyUrl.username || proxyUrl.password) && {
+ proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`
+ })), { host: proxyUrl.hostname, port: proxyUrl.port })
};
let tunnelAgent;
const overHttps = proxyUrl.protocol === 'https:';
@@ -2811,7 +2924,7 @@ class HttpClient {
}
// if reusing agent across request and tunneling agent isn't assigned create a new agent
if (this._keepAlive && !agent) {
- const options = { keepAlive: this._keepAlive, maxSockets: maxSockets };
+ const options = { keepAlive: this._keepAlive, maxSockets };
agent = usingSsl ? new https.Agent(options) : new http.Agent(options);
this._agent = agent;
}
@@ -2830,109 +2943,117 @@ class HttpClient {
return agent;
}
_performExponentialBackoff(retryNumber) {
- retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber);
- const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber);
- return new Promise(resolve => setTimeout(() => resolve(), ms));
+ return __awaiter(this, void 0, void 0, function* () {
+ retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber);
+ const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber);
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+ });
}
- static dateTimeDeserializer(key, value) {
- if (typeof value === 'string') {
- let a = new Date(value);
- if (!isNaN(a.valueOf())) {
- return a;
- }
- }
- return value;
- }
- async _processResponse(res, options) {
- return new Promise(async (resolve, reject) => {
- const statusCode = res.message.statusCode;
- const response = {
- statusCode: statusCode,
- result: null,
- headers: {}
- };
- // not found leads to null obj returned
- if (statusCode == HttpCodes.NotFound) {
- resolve(response);
- }
- let obj;
- let contents;
- // get the result from the body
- try {
- contents = await res.readBody();
- if (contents && contents.length > 0) {
- if (options && options.deserializeDates) {
- obj = JSON.parse(contents, HttpClient.dateTimeDeserializer);
+ _processResponse(res, options) {
+ return __awaiter(this, void 0, void 0, function* () {
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
+ const statusCode = res.message.statusCode || 0;
+ const response = {
+ statusCode,
+ result: null,
+ headers: {}
+ };
+ // not found leads to null obj returned
+ if (statusCode === HttpCodes.NotFound) {
+ resolve(response);
+ }
+ // get the result from the body
+ function dateTimeDeserializer(key, value) {
+ if (typeof value === 'string') {
+ const a = new Date(value);
+ if (!isNaN(a.valueOf())) {
+ return a;
+ }
+ }
+ return value;
+ }
+ let obj;
+ let contents;
+ try {
+ contents = yield res.readBody();
+ if (contents && contents.length > 0) {
+ if (options && options.deserializeDates) {
+ obj = JSON.parse(contents, dateTimeDeserializer);
+ }
+ else {
+ obj = JSON.parse(contents);
+ }
+ response.result = obj;
+ }
+ response.headers = res.message.headers;
+ }
+ catch (err) {
+ // Invalid resource (contents not json); leaving result obj null
+ }
+ // note that 3xx redirects are handled by the http layer.
+ if (statusCode > 299) {
+ let msg;
+ // if exception/error in body, attempt to get better error
+ if (obj && obj.message) {
+ msg = obj.message;
+ }
+ else if (contents && contents.length > 0) {
+ // it may be the case that the exception is in the body message as string
+ msg = contents;
}
else {
- obj = JSON.parse(contents);
+ msg = `Failed request: (${statusCode})`;
}
- response.result = obj;
- }
- response.headers = res.message.headers;
- }
- catch (err) {
- // Invalid resource (contents not json); leaving result obj null
- }
- // note that 3xx redirects are handled by the http layer.
- if (statusCode > 299) {
- let msg;
- // if exception/error in body, attempt to get better error
- if (obj && obj.message) {
- msg = obj.message;
- }
- else if (contents && contents.length > 0) {
- // it may be the case that the exception is in the body message as string
- msg = contents;
+ const err = new HttpClientError(msg, statusCode);
+ err.result = response.result;
+ reject(err);
}
else {
- msg = 'Failed request: (' + statusCode + ')';
+ resolve(response);
}
- let err = new HttpClientError(msg, statusCode);
- err.result = response.result;
- reject(err);
- }
- else {
- resolve(response);
- }
+ }));
});
}
}
exports.HttpClient = HttpClient;
-
+const lowercaseKeys = (obj) => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {});
+//# sourceMappingURL=index.js.map
/***/ }),
-/***/ 7326:
+/***/ 4977:
/***/ ((__unused_webpack_module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.checkBypass = exports.getProxyUrl = void 0;
function getProxyUrl(reqUrl) {
- let usingSsl = reqUrl.protocol === 'https:';
- let proxyUrl;
+ const usingSsl = reqUrl.protocol === 'https:';
if (checkBypass(reqUrl)) {
- return proxyUrl;
+ return undefined;
}
- let proxyVar;
- if (usingSsl) {
- proxyVar = process.env['https_proxy'] || process.env['HTTPS_PROXY'];
+ const proxyVar = (() => {
+ if (usingSsl) {
+ return process.env['https_proxy'] || process.env['HTTPS_PROXY'];
+ }
+ else {
+ return process.env['http_proxy'] || process.env['HTTP_PROXY'];
+ }
+ })();
+ if (proxyVar) {
+ return new URL(proxyVar);
}
else {
- proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY'];
+ return undefined;
}
- if (proxyVar) {
- proxyUrl = new URL(proxyVar);
- }
- return proxyUrl;
}
exports.getProxyUrl = getProxyUrl;
function checkBypass(reqUrl) {
if (!reqUrl.hostname) {
return false;
}
- let noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '';
+ const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '';
if (!noProxy) {
return false;
}
@@ -2948,12 +3069,12 @@ function checkBypass(reqUrl) {
reqPort = 443;
}
// Format the request hostname and hostname with port
- let upperReqHosts = [reqUrl.hostname.toUpperCase()];
+ const upperReqHosts = [reqUrl.hostname.toUpperCase()];
if (typeof reqPort === 'number') {
upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`);
}
// Compare request host against noproxy
- for (let upperNoProxyItem of noProxy
+ for (const upperNoProxyItem of noProxy
.split(',')
.map(x => x.trim().toUpperCase())
.filter(x => x)) {
@@ -2964,7 +3085,7 @@ function checkBypass(reqUrl) {
return false;
}
exports.checkBypass = checkBypass;
-
+//# sourceMappingURL=proxy.js.map
/***/ }),
@@ -4576,14 +4697,27 @@ function coerce (version, options) {
"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (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 (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
- result["default"] = mod;
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.issue = exports.issueCommand = void 0;
const os = __importStar(__nccwpck_require__(2037));
const utils_1 = __nccwpck_require__(5278);
/**
@@ -4662,6 +4796,25 @@ function escapeProperty(s) {
"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (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.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
+ return result;
+};
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) {
@@ -4671,19 +4824,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
-var __importStar = (this && this.__importStar) || function (mod) {
- if (mod && mod.__esModule) return mod;
- var result = {};
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
- result["default"] = mod;
- return result;
-};
Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.getIDToken = exports.getState = exports.saveState = exports.group = exports.endGroup = exports.startGroup = exports.info = exports.notice = exports.warning = exports.error = exports.debug = exports.isDebug = exports.setFailed = exports.setCommandEcho = exports.setOutput = exports.getBooleanInput = exports.getMultilineInput = exports.getInput = exports.addPath = exports.setSecret = exports.exportVariable = exports.ExitCode = void 0;
const command_1 = __nccwpck_require__(7351);
const file_command_1 = __nccwpck_require__(717);
const utils_1 = __nccwpck_require__(5278);
const os = __importStar(__nccwpck_require__(2037));
const path = __importStar(__nccwpck_require__(1017));
+const oidc_utils_1 = __nccwpck_require__(8041);
/**
* The code to exit an action
*/
@@ -4745,7 +4893,9 @@ function addPath(inputPath) {
}
exports.addPath = addPath;
/**
- * Gets the value of an input. The value is also trimmed.
+ * Gets the value of an input.
+ * Unless trimWhitespace is set to false in InputOptions, the value is also trimmed.
+ * Returns an empty string if the value is not defined.
*
* @param name name of the input to get
* @param options optional. See InputOptions.
@@ -4756,9 +4906,49 @@ function getInput(name, options) {
if (options && options.required && !val) {
throw new Error(`Input required and not supplied: ${name}`);
}
+ if (options && options.trimWhitespace === false) {
+ return val;
+ }
return val.trim();
}
exports.getInput = getInput;
+/**
+ * Gets the values of an multiline input. Each value is also trimmed.
+ *
+ * @param name name of the input to get
+ * @param options optional. See InputOptions.
+ * @returns string[]
+ *
+ */
+function getMultilineInput(name, options) {
+ const inputs = getInput(name, options)
+ .split('\n')
+ .filter(x => x !== '');
+ return inputs;
+}
+exports.getMultilineInput = getMultilineInput;
+/**
+ * Gets the input value of the boolean type in the YAML 1.2 "core schema" specification.
+ * Support boolean input list: `true | True | TRUE | false | False | FALSE` .
+ * The return value is also in boolean type.
+ * ref: https://yaml.org/spec/1.2/spec.html#id2804923
+ *
+ * @param name name of the input to get
+ * @param options optional. See InputOptions.
+ * @returns boolean
+ */
+function getBooleanInput(name, options) {
+ const trueValue = ['true', 'True', 'TRUE'];
+ const falseValue = ['false', 'False', 'FALSE'];
+ const val = getInput(name, options);
+ if (trueValue.includes(val))
+ return true;
+ if (falseValue.includes(val))
+ return false;
+ throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${name}\n` +
+ `Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
+}
+exports.getBooleanInput = getBooleanInput;
/**
* Sets the value of an output.
*
@@ -4767,6 +4957,7 @@ exports.getInput = getInput;
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setOutput(name, value) {
+ process.stdout.write(os.EOL);
command_1.issueCommand('set-output', { name }, value);
}
exports.setOutput = setOutput;
@@ -4813,19 +5004,30 @@ exports.debug = debug;
/**
* Adds an error issue
* @param message error issue message. Errors will be converted to string via toString()
+ * @param properties optional properties to add to the annotation.
*/
-function error(message) {
- command_1.issue('error', message instanceof Error ? message.toString() : message);
+function error(message, properties = {}) {
+ command_1.issueCommand('error', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
}
exports.error = error;
/**
- * Adds an warning issue
+ * Adds a warning issue
* @param message warning issue message. Errors will be converted to string via toString()
+ * @param properties optional properties to add to the annotation.
*/
-function warning(message) {
- command_1.issue('warning', message instanceof Error ? message.toString() : message);
+function warning(message, properties = {}) {
+ command_1.issueCommand('warning', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
}
exports.warning = warning;
+/**
+ * Adds a notice issue
+ * @param message notice issue message. Errors will be converted to string via toString()
+ * @param properties optional properties to add to the annotation.
+ */
+function notice(message, properties = {}) {
+ command_1.issueCommand('notice', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
+}
+exports.notice = notice;
/**
* Writes info to log with console.log.
* @param message info message
@@ -4898,6 +5100,17 @@ function getState(name) {
return process.env[`STATE_${name}`] || '';
}
exports.getState = getState;
+function getIDToken(aud) {
+ return __awaiter(this, void 0, void 0, function* () {
+ return yield oidc_utils_1.OidcClient.getIDToken(aud);
+ });
+}
+exports.getIDToken = getIDToken;
+/**
+ * Markdown summary exports
+ */
+var markdown_summary_1 = __nccwpck_require__(8042);
+Object.defineProperty(exports, "markdownSummary", ({ enumerable: true, get: function () { return markdown_summary_1.markdownSummary; } }));
//# sourceMappingURL=core.js.map
/***/ }),
@@ -4908,14 +5121,27 @@ exports.getState = getState;
"use strict";
// For internal use, subject to change.
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (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 (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
- result["default"] = mod;
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.issueCommand = void 0;
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
const fs = __importStar(__nccwpck_require__(7147));
@@ -4938,6 +5164,376 @@ exports.issueCommand = issueCommand;
/***/ }),
+/***/ 8042:
+/***/ (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.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0;
+const os_1 = __nccwpck_require__(2037);
+const fs_1 = __nccwpck_require__(7147);
+const { access, appendFile, writeFile } = fs_1.promises;
+exports.SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY';
+exports.SUMMARY_DOCS_URL = 'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary';
+class MarkdownSummary {
+ constructor() {
+ this._buffer = '';
+ }
+ /**
+ * Finds the summary file path from the environment, rejects if env var is not found or file does not exist
+ * Also checks r/w permissions.
+ *
+ * @returns step summary file path
+ */
+ filePath() {
+ return __awaiter(this, void 0, void 0, function* () {
+ if (this._filePath) {
+ return this._filePath;
+ }
+ const pathFromEnv = process.env[exports.SUMMARY_ENV_VAR];
+ if (!pathFromEnv) {
+ throw new Error(`Unable to find environment variable for $${exports.SUMMARY_ENV_VAR}. Check if your runtime environment supports markdown summaries.`);
+ }
+ try {
+ yield access(pathFromEnv, fs_1.constants.R_OK | fs_1.constants.W_OK);
+ }
+ catch (_a) {
+ throw new Error(`Unable to access summary file: '${pathFromEnv}'. Check if the file has correct read/write permissions.`);
+ }
+ this._filePath = pathFromEnv;
+ return this._filePath;
+ });
+ }
+ /**
+ * Wraps content in an HTML tag, adding any HTML attributes
+ *
+ * @param {string} tag HTML tag to wrap
+ * @param {string | null} content content within the tag
+ * @param {[attribute: string]: string} attrs key-value list of HTML attributes to add
+ *
+ * @returns {string} content wrapped in HTML element
+ */
+ wrap(tag, content, attrs = {}) {
+ const htmlAttrs = Object.entries(attrs)
+ .map(([key, value]) => ` ${key}="${value}"`)
+ .join('');
+ if (!content) {
+ return `<${tag}${htmlAttrs}>`;
+ }
+ return `<${tag}${htmlAttrs}>${content}${tag}>`;
+ }
+ /**
+ * Writes text in the buffer to the summary buffer file and empties buffer. Will append by default.
+ *
+ * @param {SummaryWriteOptions} [options] (optional) options for write operation
+ *
+ * @returns {Promise} markdown summary instance
+ */
+ write(options) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const overwrite = !!(options === null || options === void 0 ? void 0 : options.overwrite);
+ const filePath = yield this.filePath();
+ const writeFunc = overwrite ? writeFile : appendFile;
+ yield writeFunc(filePath, this._buffer, { encoding: 'utf8' });
+ return this.emptyBuffer();
+ });
+ }
+ /**
+ * Clears the summary buffer and wipes the summary file
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ clear() {
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.emptyBuffer().write({ overwrite: true });
+ });
+ }
+ /**
+ * Returns the current summary buffer as a string
+ *
+ * @returns {string} string of summary buffer
+ */
+ stringify() {
+ return this._buffer;
+ }
+ /**
+ * If the summary buffer is empty
+ *
+ * @returns {boolen} true if the buffer is empty
+ */
+ isEmptyBuffer() {
+ return this._buffer.length === 0;
+ }
+ /**
+ * Resets the summary buffer without writing to summary file
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ emptyBuffer() {
+ this._buffer = '';
+ return this;
+ }
+ /**
+ * Adds raw text to the summary buffer
+ *
+ * @param {string} text content to add
+ * @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false)
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addRaw(text, addEOL = false) {
+ this._buffer += text;
+ return addEOL ? this.addEOL() : this;
+ }
+ /**
+ * Adds the operating system-specific end-of-line marker to the buffer
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addEOL() {
+ return this.addRaw(os_1.EOL);
+ }
+ /**
+ * Adds an HTML codeblock to the summary buffer
+ *
+ * @param {string} code content to render within fenced code block
+ * @param {string} lang (optional) language to syntax highlight code
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addCodeBlock(code, lang) {
+ const attrs = Object.assign({}, (lang && { lang }));
+ const element = this.wrap('pre', this.wrap('code', code), attrs);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML list to the summary buffer
+ *
+ * @param {string[]} items list of items to render
+ * @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false)
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addList(items, ordered = false) {
+ const tag = ordered ? 'ol' : 'ul';
+ const listItems = items.map(item => this.wrap('li', item)).join('');
+ const element = this.wrap(tag, listItems);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML table to the summary buffer
+ *
+ * @param {SummaryTableCell[]} rows table rows
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addTable(rows) {
+ const tableBody = rows
+ .map(row => {
+ const cells = row
+ .map(cell => {
+ if (typeof cell === 'string') {
+ return this.wrap('td', cell);
+ }
+ const { header, data, colspan, rowspan } = cell;
+ const tag = header ? 'th' : 'td';
+ const attrs = Object.assign(Object.assign({}, (colspan && { colspan })), (rowspan && { rowspan }));
+ return this.wrap(tag, data, attrs);
+ })
+ .join('');
+ return this.wrap('tr', cells);
+ })
+ .join('');
+ const element = this.wrap('table', tableBody);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds a collapsable HTML details element to the summary buffer
+ *
+ * @param {string} label text for the closed state
+ * @param {string} content collapsable content
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addDetails(label, content) {
+ const element = this.wrap('details', this.wrap('summary', label) + content);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML image tag to the summary buffer
+ *
+ * @param {string} src path to the image you to embed
+ * @param {string} alt text description of the image
+ * @param {SummaryImageOptions} options (optional) addition image attributes
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addImage(src, alt, options) {
+ const { width, height } = options || {};
+ const attrs = Object.assign(Object.assign({}, (width && { width })), (height && { height }));
+ const element = this.wrap('img', null, Object.assign({ src, alt }, attrs));
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML section heading element
+ *
+ * @param {string} text heading text
+ * @param {number | string} [level=1] (optional) the heading level, default: 1
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addHeading(text, level) {
+ const tag = `h${level}`;
+ const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)
+ ? tag
+ : 'h1';
+ const element = this.wrap(allowedTag, text);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML thematic break (
) to the summary buffer
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addSeparator() {
+ const element = this.wrap('hr', null);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML line break (
) to the summary buffer
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addBreak() {
+ const element = this.wrap('br', null);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML blockquote to the summary buffer
+ *
+ * @param {string} text quote text
+ * @param {string} cite (optional) citation url
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addQuote(text, cite) {
+ const attrs = Object.assign({}, (cite && { cite }));
+ const element = this.wrap('blockquote', text, attrs);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML anchor tag to the summary buffer
+ *
+ * @param {string} text link text/content
+ * @param {string} href hyperlink
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addLink(text, href) {
+ const element = this.wrap('a', text, { href });
+ return this.addRaw(element).addEOL();
+ }
+}
+// singleton export
+exports.markdownSummary = new MarkdownSummary();
+//# sourceMappingURL=markdown-summary.js.map
+
+/***/ }),
+
+/***/ 8041:
+/***/ (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.OidcClient = void 0;
+const http_client_1 = __nccwpck_require__(9925);
+const auth_1 = __nccwpck_require__(3702);
+const core_1 = __nccwpck_require__(2186);
+class OidcClient {
+ static createHttpClient(allowRetry = true, maxRetry = 10) {
+ const requestOptions = {
+ allowRetries: allowRetry,
+ maxRetries: maxRetry
+ };
+ return new http_client_1.HttpClient('actions/oidc-client', [new auth_1.BearerCredentialHandler(OidcClient.getRequestToken())], requestOptions);
+ }
+ static getRequestToken() {
+ const token = process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'];
+ if (!token) {
+ throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable');
+ }
+ return token;
+ }
+ static getIDTokenUrl() {
+ const runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL'];
+ if (!runtimeUrl) {
+ throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable');
+ }
+ return runtimeUrl;
+ }
+ static getCall(id_token_url) {
+ var _a;
+ return __awaiter(this, void 0, void 0, function* () {
+ const httpclient = OidcClient.createHttpClient();
+ const res = yield httpclient
+ .getJson(id_token_url)
+ .catch(error => {
+ throw new Error(`Failed to get ID Token. \n
+ Error Code : ${error.statusCode}\n
+ Error Message: ${error.result.message}`);
+ });
+ const id_token = (_a = res.result) === null || _a === void 0 ? void 0 : _a.value;
+ if (!id_token) {
+ throw new Error('Response json body do not have ID Token field');
+ }
+ return id_token;
+ });
+ }
+ static getIDToken(audience) {
+ return __awaiter(this, void 0, void 0, function* () {
+ try {
+ // New ID Token is requested from action service
+ let id_token_url = OidcClient.getIDTokenUrl();
+ if (audience) {
+ const encodedAudience = encodeURIComponent(audience);
+ id_token_url = `${id_token_url}&audience=${encodedAudience}`;
+ }
+ core_1.debug(`ID token url is ${id_token_url}`);
+ const id_token = yield OidcClient.getCall(id_token_url);
+ core_1.setSecret(id_token);
+ return id_token;
+ }
+ catch (error) {
+ throw new Error(`Error message: ${error.message}`);
+ }
+ });
+ }
+}
+exports.OidcClient = OidcClient;
+//# sourceMappingURL=oidc-utils.js.map
+
+/***/ }),
+
/***/ 5278:
/***/ ((__unused_webpack_module, exports) => {
@@ -4946,6 +5542,7 @@ exports.issueCommand = issueCommand;
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.toCommandProperties = exports.toCommandValue = void 0;
/**
* Sanitizes an input into a string so it can be passed into issueCommand safely
* @param input input to sanitize into a string
@@ -4960,6 +5557,26 @@ function toCommandValue(input) {
return JSON.stringify(input);
}
exports.toCommandValue = toCommandValue;
+/**
+ *
+ * @param annotationProperties
+ * @returns The command properties to send with the actual annotation command
+ * See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646
+ */
+function toCommandProperties(annotationProperties) {
+ if (!Object.keys(annotationProperties).length) {
+ return {};
+ }
+ return {
+ title: annotationProperties.title,
+ file: annotationProperties.file,
+ line: annotationProperties.startLine,
+ endLine: annotationProperties.endLine,
+ col: annotationProperties.startColumn,
+ endColumn: annotationProperties.endColumn
+ };
+}
+exports.toCommandProperties = toCommandProperties;
//# sourceMappingURL=utils.js.map
/***/ }),
@@ -5697,6 +6314,682 @@ class ExecState extends events.EventEmitter {
}
//# sourceMappingURL=toolrunner.js.map
+/***/ }),
+
+/***/ 3702:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+class BasicCredentialHandler {
+ constructor(username, password) {
+ this.username = username;
+ this.password = password;
+ }
+ prepareRequest(options) {
+ options.headers['Authorization'] =
+ 'Basic ' +
+ Buffer.from(this.username + ':' + this.password).toString('base64');
+ }
+ // This handler cannot handle 401
+ canHandleAuthentication(response) {
+ return false;
+ }
+ handleAuthentication(httpClient, requestInfo, objs) {
+ return null;
+ }
+}
+exports.BasicCredentialHandler = BasicCredentialHandler;
+class BearerCredentialHandler {
+ constructor(token) {
+ this.token = token;
+ }
+ // currently implements pre-authorization
+ // TODO: support preAuth = false where it hooks on 401
+ prepareRequest(options) {
+ options.headers['Authorization'] = 'Bearer ' + this.token;
+ }
+ // This handler cannot handle 401
+ canHandleAuthentication(response) {
+ return false;
+ }
+ handleAuthentication(httpClient, requestInfo, objs) {
+ return null;
+ }
+}
+exports.BearerCredentialHandler = BearerCredentialHandler;
+class PersonalAccessTokenCredentialHandler {
+ constructor(token) {
+ this.token = token;
+ }
+ // currently implements pre-authorization
+ // TODO: support preAuth = false where it hooks on 401
+ prepareRequest(options) {
+ options.headers['Authorization'] =
+ 'Basic ' + Buffer.from('PAT:' + this.token).toString('base64');
+ }
+ // This handler cannot handle 401
+ canHandleAuthentication(response) {
+ return false;
+ }
+ handleAuthentication(httpClient, requestInfo, objs) {
+ return null;
+ }
+}
+exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler;
+
+
+/***/ }),
+
+/***/ 9925:
+/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+const http = __nccwpck_require__(3685);
+const https = __nccwpck_require__(5687);
+const pm = __nccwpck_require__(6443);
+let tunnel;
+var HttpCodes;
+(function (HttpCodes) {
+ HttpCodes[HttpCodes["OK"] = 200] = "OK";
+ HttpCodes[HttpCodes["MultipleChoices"] = 300] = "MultipleChoices";
+ HttpCodes[HttpCodes["MovedPermanently"] = 301] = "MovedPermanently";
+ HttpCodes[HttpCodes["ResourceMoved"] = 302] = "ResourceMoved";
+ HttpCodes[HttpCodes["SeeOther"] = 303] = "SeeOther";
+ HttpCodes[HttpCodes["NotModified"] = 304] = "NotModified";
+ HttpCodes[HttpCodes["UseProxy"] = 305] = "UseProxy";
+ HttpCodes[HttpCodes["SwitchProxy"] = 306] = "SwitchProxy";
+ HttpCodes[HttpCodes["TemporaryRedirect"] = 307] = "TemporaryRedirect";
+ HttpCodes[HttpCodes["PermanentRedirect"] = 308] = "PermanentRedirect";
+ HttpCodes[HttpCodes["BadRequest"] = 400] = "BadRequest";
+ HttpCodes[HttpCodes["Unauthorized"] = 401] = "Unauthorized";
+ HttpCodes[HttpCodes["PaymentRequired"] = 402] = "PaymentRequired";
+ HttpCodes[HttpCodes["Forbidden"] = 403] = "Forbidden";
+ HttpCodes[HttpCodes["NotFound"] = 404] = "NotFound";
+ HttpCodes[HttpCodes["MethodNotAllowed"] = 405] = "MethodNotAllowed";
+ HttpCodes[HttpCodes["NotAcceptable"] = 406] = "NotAcceptable";
+ HttpCodes[HttpCodes["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired";
+ HttpCodes[HttpCodes["RequestTimeout"] = 408] = "RequestTimeout";
+ HttpCodes[HttpCodes["Conflict"] = 409] = "Conflict";
+ HttpCodes[HttpCodes["Gone"] = 410] = "Gone";
+ HttpCodes[HttpCodes["TooManyRequests"] = 429] = "TooManyRequests";
+ HttpCodes[HttpCodes["InternalServerError"] = 500] = "InternalServerError";
+ HttpCodes[HttpCodes["NotImplemented"] = 501] = "NotImplemented";
+ HttpCodes[HttpCodes["BadGateway"] = 502] = "BadGateway";
+ HttpCodes[HttpCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable";
+ HttpCodes[HttpCodes["GatewayTimeout"] = 504] = "GatewayTimeout";
+})(HttpCodes = exports.HttpCodes || (exports.HttpCodes = {}));
+var Headers;
+(function (Headers) {
+ Headers["Accept"] = "accept";
+ Headers["ContentType"] = "content-type";
+})(Headers = exports.Headers || (exports.Headers = {}));
+var MediaTypes;
+(function (MediaTypes) {
+ MediaTypes["ApplicationJson"] = "application/json";
+})(MediaTypes = exports.MediaTypes || (exports.MediaTypes = {}));
+/**
+ * Returns the proxy URL, depending upon the supplied url and proxy environment variables.
+ * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
+ */
+function getProxyUrl(serverUrl) {
+ let proxyUrl = pm.getProxyUrl(new URL(serverUrl));
+ return proxyUrl ? proxyUrl.href : '';
+}
+exports.getProxyUrl = getProxyUrl;
+const HttpRedirectCodes = [
+ HttpCodes.MovedPermanently,
+ HttpCodes.ResourceMoved,
+ HttpCodes.SeeOther,
+ HttpCodes.TemporaryRedirect,
+ HttpCodes.PermanentRedirect
+];
+const HttpResponseRetryCodes = [
+ HttpCodes.BadGateway,
+ HttpCodes.ServiceUnavailable,
+ HttpCodes.GatewayTimeout
+];
+const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD'];
+const ExponentialBackoffCeiling = 10;
+const ExponentialBackoffTimeSlice = 5;
+class HttpClientError extends Error {
+ constructor(message, statusCode) {
+ super(message);
+ this.name = 'HttpClientError';
+ this.statusCode = statusCode;
+ Object.setPrototypeOf(this, HttpClientError.prototype);
+ }
+}
+exports.HttpClientError = HttpClientError;
+class HttpClientResponse {
+ constructor(message) {
+ this.message = message;
+ }
+ readBody() {
+ return new Promise(async (resolve, reject) => {
+ let output = Buffer.alloc(0);
+ this.message.on('data', (chunk) => {
+ output = Buffer.concat([output, chunk]);
+ });
+ this.message.on('end', () => {
+ resolve(output.toString());
+ });
+ });
+ }
+}
+exports.HttpClientResponse = HttpClientResponse;
+function isHttps(requestUrl) {
+ let parsedUrl = new URL(requestUrl);
+ return parsedUrl.protocol === 'https:';
+}
+exports.isHttps = isHttps;
+class HttpClient {
+ constructor(userAgent, handlers, requestOptions) {
+ this._ignoreSslError = false;
+ this._allowRedirects = true;
+ this._allowRedirectDowngrade = false;
+ this._maxRedirects = 50;
+ this._allowRetries = false;
+ this._maxRetries = 1;
+ this._keepAlive = false;
+ this._disposed = false;
+ this.userAgent = userAgent;
+ this.handlers = handlers || [];
+ this.requestOptions = requestOptions;
+ if (requestOptions) {
+ if (requestOptions.ignoreSslError != null) {
+ this._ignoreSslError = requestOptions.ignoreSslError;
+ }
+ this._socketTimeout = requestOptions.socketTimeout;
+ if (requestOptions.allowRedirects != null) {
+ this._allowRedirects = requestOptions.allowRedirects;
+ }
+ if (requestOptions.allowRedirectDowngrade != null) {
+ this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade;
+ }
+ if (requestOptions.maxRedirects != null) {
+ this._maxRedirects = Math.max(requestOptions.maxRedirects, 0);
+ }
+ if (requestOptions.keepAlive != null) {
+ this._keepAlive = requestOptions.keepAlive;
+ }
+ if (requestOptions.allowRetries != null) {
+ this._allowRetries = requestOptions.allowRetries;
+ }
+ if (requestOptions.maxRetries != null) {
+ this._maxRetries = requestOptions.maxRetries;
+ }
+ }
+ }
+ options(requestUrl, additionalHeaders) {
+ return this.request('OPTIONS', requestUrl, null, additionalHeaders || {});
+ }
+ get(requestUrl, additionalHeaders) {
+ return this.request('GET', requestUrl, null, additionalHeaders || {});
+ }
+ del(requestUrl, additionalHeaders) {
+ return this.request('DELETE', requestUrl, null, additionalHeaders || {});
+ }
+ post(requestUrl, data, additionalHeaders) {
+ return this.request('POST', requestUrl, data, additionalHeaders || {});
+ }
+ patch(requestUrl, data, additionalHeaders) {
+ return this.request('PATCH', requestUrl, data, additionalHeaders || {});
+ }
+ put(requestUrl, data, additionalHeaders) {
+ return this.request('PUT', requestUrl, data, additionalHeaders || {});
+ }
+ head(requestUrl, additionalHeaders) {
+ return this.request('HEAD', requestUrl, null, additionalHeaders || {});
+ }
+ sendStream(verb, requestUrl, stream, additionalHeaders) {
+ return this.request(verb, requestUrl, stream, additionalHeaders);
+ }
+ /**
+ * Gets a typed object from an endpoint
+ * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
+ */
+ async getJson(requestUrl, additionalHeaders = {}) {
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
+ let res = await this.get(requestUrl, additionalHeaders);
+ return this._processResponse(res, this.requestOptions);
+ }
+ async postJson(requestUrl, obj, additionalHeaders = {}) {
+ let data = JSON.stringify(obj, null, 2);
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
+ additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
+ let res = await this.post(requestUrl, data, additionalHeaders);
+ return this._processResponse(res, this.requestOptions);
+ }
+ async putJson(requestUrl, obj, additionalHeaders = {}) {
+ let data = JSON.stringify(obj, null, 2);
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
+ additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
+ let res = await this.put(requestUrl, data, additionalHeaders);
+ return this._processResponse(res, this.requestOptions);
+ }
+ async patchJson(requestUrl, obj, additionalHeaders = {}) {
+ let data = JSON.stringify(obj, null, 2);
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
+ additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
+ let res = await this.patch(requestUrl, data, additionalHeaders);
+ return this._processResponse(res, this.requestOptions);
+ }
+ /**
+ * Makes a raw http request.
+ * All other methods such as get, post, patch, and request ultimately call this.
+ * Prefer get, del, post and patch
+ */
+ async request(verb, requestUrl, data, headers) {
+ if (this._disposed) {
+ throw new Error('Client has already been disposed.');
+ }
+ let parsedUrl = new URL(requestUrl);
+ let info = this._prepareRequest(verb, parsedUrl, headers);
+ // Only perform retries on reads since writes may not be idempotent.
+ let maxTries = this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1
+ ? this._maxRetries + 1
+ : 1;
+ let numTries = 0;
+ let response;
+ while (numTries < maxTries) {
+ response = await this.requestRaw(info, data);
+ // Check if it's an authentication challenge
+ if (response &&
+ response.message &&
+ response.message.statusCode === HttpCodes.Unauthorized) {
+ let authenticationHandler;
+ for (let i = 0; i < this.handlers.length; i++) {
+ if (this.handlers[i].canHandleAuthentication(response)) {
+ authenticationHandler = this.handlers[i];
+ break;
+ }
+ }
+ if (authenticationHandler) {
+ return authenticationHandler.handleAuthentication(this, info, data);
+ }
+ else {
+ // We have received an unauthorized response but have no handlers to handle it.
+ // Let the response return to the caller.
+ return response;
+ }
+ }
+ let redirectsRemaining = this._maxRedirects;
+ while (HttpRedirectCodes.indexOf(response.message.statusCode) != -1 &&
+ this._allowRedirects &&
+ redirectsRemaining > 0) {
+ const redirectUrl = response.message.headers['location'];
+ if (!redirectUrl) {
+ // if there's no location to redirect to, we won't
+ break;
+ }
+ let parsedRedirectUrl = new URL(redirectUrl);
+ if (parsedUrl.protocol == 'https:' &&
+ parsedUrl.protocol != parsedRedirectUrl.protocol &&
+ !this._allowRedirectDowngrade) {
+ throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.');
+ }
+ // we need to finish reading the response before reassigning response
+ // which will leak the open socket.
+ await response.readBody();
+ // strip authorization header if redirected to a different hostname
+ if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
+ for (let header in headers) {
+ // header names are case insensitive
+ if (header.toLowerCase() === 'authorization') {
+ delete headers[header];
+ }
+ }
+ }
+ // let's make the request with the new redirectUrl
+ info = this._prepareRequest(verb, parsedRedirectUrl, headers);
+ response = await this.requestRaw(info, data);
+ redirectsRemaining--;
+ }
+ if (HttpResponseRetryCodes.indexOf(response.message.statusCode) == -1) {
+ // If not a retry code, return immediately instead of retrying
+ return response;
+ }
+ numTries += 1;
+ if (numTries < maxTries) {
+ await response.readBody();
+ await this._performExponentialBackoff(numTries);
+ }
+ }
+ return response;
+ }
+ /**
+ * Needs to be called if keepAlive is set to true in request options.
+ */
+ dispose() {
+ if (this._agent) {
+ this._agent.destroy();
+ }
+ this._disposed = true;
+ }
+ /**
+ * Raw request.
+ * @param info
+ * @param data
+ */
+ requestRaw(info, data) {
+ return new Promise((resolve, reject) => {
+ let callbackForResult = function (err, res) {
+ if (err) {
+ reject(err);
+ }
+ resolve(res);
+ };
+ this.requestRawWithCallback(info, data, callbackForResult);
+ });
+ }
+ /**
+ * Raw request with callback.
+ * @param info
+ * @param data
+ * @param onResult
+ */
+ requestRawWithCallback(info, data, onResult) {
+ let socket;
+ if (typeof data === 'string') {
+ info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
+ }
+ let callbackCalled = false;
+ let handleResult = (err, res) => {
+ if (!callbackCalled) {
+ callbackCalled = true;
+ onResult(err, res);
+ }
+ };
+ let req = info.httpModule.request(info.options, (msg) => {
+ let res = new HttpClientResponse(msg);
+ handleResult(null, res);
+ });
+ req.on('socket', sock => {
+ socket = sock;
+ });
+ // If we ever get disconnected, we want the socket to timeout eventually
+ req.setTimeout(this._socketTimeout || 3 * 60000, () => {
+ if (socket) {
+ socket.end();
+ }
+ handleResult(new Error('Request timeout: ' + info.options.path), null);
+ });
+ req.on('error', function (err) {
+ // err has statusCode property
+ // res should have headers
+ handleResult(err, null);
+ });
+ if (data && typeof data === 'string') {
+ req.write(data, 'utf8');
+ }
+ if (data && typeof data !== 'string') {
+ data.on('close', function () {
+ req.end();
+ });
+ data.pipe(req);
+ }
+ else {
+ req.end();
+ }
+ }
+ /**
+ * Gets an http agent. This function is useful when you need an http agent that handles
+ * routing through a proxy server - depending upon the url and proxy environment variables.
+ * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
+ */
+ getAgent(serverUrl) {
+ let parsedUrl = new URL(serverUrl);
+ return this._getAgent(parsedUrl);
+ }
+ _prepareRequest(method, requestUrl, headers) {
+ const info = {};
+ info.parsedUrl = requestUrl;
+ const usingSsl = info.parsedUrl.protocol === 'https:';
+ info.httpModule = usingSsl ? https : http;
+ const defaultPort = usingSsl ? 443 : 80;
+ info.options = {};
+ info.options.host = info.parsedUrl.hostname;
+ info.options.port = info.parsedUrl.port
+ ? parseInt(info.parsedUrl.port)
+ : defaultPort;
+ info.options.path =
+ (info.parsedUrl.pathname || '') + (info.parsedUrl.search || '');
+ info.options.method = method;
+ info.options.headers = this._mergeHeaders(headers);
+ if (this.userAgent != null) {
+ info.options.headers['user-agent'] = this.userAgent;
+ }
+ info.options.agent = this._getAgent(info.parsedUrl);
+ // gives handlers an opportunity to participate
+ if (this.handlers) {
+ this.handlers.forEach(handler => {
+ handler.prepareRequest(info.options);
+ });
+ }
+ return info;
+ }
+ _mergeHeaders(headers) {
+ const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {});
+ if (this.requestOptions && this.requestOptions.headers) {
+ return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers));
+ }
+ return lowercaseKeys(headers || {});
+ }
+ _getExistingOrDefaultHeader(additionalHeaders, header, _default) {
+ const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {});
+ let clientHeader;
+ if (this.requestOptions && this.requestOptions.headers) {
+ clientHeader = lowercaseKeys(this.requestOptions.headers)[header];
+ }
+ return additionalHeaders[header] || clientHeader || _default;
+ }
+ _getAgent(parsedUrl) {
+ let agent;
+ let proxyUrl = pm.getProxyUrl(parsedUrl);
+ let useProxy = proxyUrl && proxyUrl.hostname;
+ if (this._keepAlive && useProxy) {
+ agent = this._proxyAgent;
+ }
+ if (this._keepAlive && !useProxy) {
+ agent = this._agent;
+ }
+ // if agent is already assigned use that agent.
+ if (!!agent) {
+ return agent;
+ }
+ const usingSsl = parsedUrl.protocol === 'https:';
+ let maxSockets = 100;
+ if (!!this.requestOptions) {
+ maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets;
+ }
+ if (useProxy) {
+ // If using proxy, need tunnel
+ if (!tunnel) {
+ tunnel = __nccwpck_require__(4294);
+ }
+ const agentOptions = {
+ maxSockets: maxSockets,
+ keepAlive: this._keepAlive,
+ proxy: {
+ ...((proxyUrl.username || proxyUrl.password) && {
+ proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`
+ }),
+ host: proxyUrl.hostname,
+ port: proxyUrl.port
+ }
+ };
+ let tunnelAgent;
+ const overHttps = proxyUrl.protocol === 'https:';
+ if (usingSsl) {
+ tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp;
+ }
+ else {
+ tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp;
+ }
+ agent = tunnelAgent(agentOptions);
+ this._proxyAgent = agent;
+ }
+ // if reusing agent across request and tunneling agent isn't assigned create a new agent
+ if (this._keepAlive && !agent) {
+ const options = { keepAlive: this._keepAlive, maxSockets: maxSockets };
+ agent = usingSsl ? new https.Agent(options) : new http.Agent(options);
+ this._agent = agent;
+ }
+ // if not using private agent and tunnel agent isn't setup then use global agent
+ if (!agent) {
+ agent = usingSsl ? https.globalAgent : http.globalAgent;
+ }
+ if (usingSsl && this._ignoreSslError) {
+ // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
+ // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options
+ // we have to cast it to any and change it directly
+ agent.options = Object.assign(agent.options || {}, {
+ rejectUnauthorized: false
+ });
+ }
+ return agent;
+ }
+ _performExponentialBackoff(retryNumber) {
+ retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber);
+ const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber);
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+ }
+ static dateTimeDeserializer(key, value) {
+ if (typeof value === 'string') {
+ let a = new Date(value);
+ if (!isNaN(a.valueOf())) {
+ return a;
+ }
+ }
+ return value;
+ }
+ async _processResponse(res, options) {
+ return new Promise(async (resolve, reject) => {
+ const statusCode = res.message.statusCode;
+ const response = {
+ statusCode: statusCode,
+ result: null,
+ headers: {}
+ };
+ // not found leads to null obj returned
+ if (statusCode == HttpCodes.NotFound) {
+ resolve(response);
+ }
+ let obj;
+ let contents;
+ // get the result from the body
+ try {
+ contents = await res.readBody();
+ if (contents && contents.length > 0) {
+ if (options && options.deserializeDates) {
+ obj = JSON.parse(contents, HttpClient.dateTimeDeserializer);
+ }
+ else {
+ obj = JSON.parse(contents);
+ }
+ response.result = obj;
+ }
+ response.headers = res.message.headers;
+ }
+ catch (err) {
+ // Invalid resource (contents not json); leaving result obj null
+ }
+ // note that 3xx redirects are handled by the http layer.
+ if (statusCode > 299) {
+ let msg;
+ // if exception/error in body, attempt to get better error
+ if (obj && obj.message) {
+ msg = obj.message;
+ }
+ else if (contents && contents.length > 0) {
+ // it may be the case that the exception is in the body message as string
+ msg = contents;
+ }
+ else {
+ msg = 'Failed request: (' + statusCode + ')';
+ }
+ let err = new HttpClientError(msg, statusCode);
+ err.result = response.result;
+ reject(err);
+ }
+ else {
+ resolve(response);
+ }
+ });
+ }
+}
+exports.HttpClient = HttpClient;
+
+
+/***/ }),
+
+/***/ 6443:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+function getProxyUrl(reqUrl) {
+ let usingSsl = reqUrl.protocol === 'https:';
+ let proxyUrl;
+ if (checkBypass(reqUrl)) {
+ return proxyUrl;
+ }
+ let proxyVar;
+ if (usingSsl) {
+ proxyVar = process.env['https_proxy'] || process.env['HTTPS_PROXY'];
+ }
+ else {
+ proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY'];
+ }
+ if (proxyVar) {
+ proxyUrl = new URL(proxyVar);
+ }
+ return proxyUrl;
+}
+exports.getProxyUrl = getProxyUrl;
+function checkBypass(reqUrl) {
+ if (!reqUrl.hostname) {
+ return false;
+ }
+ let noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '';
+ if (!noProxy) {
+ return false;
+ }
+ // Determine the request port
+ let reqPort;
+ if (reqUrl.port) {
+ reqPort = Number(reqUrl.port);
+ }
+ else if (reqUrl.protocol === 'http:') {
+ reqPort = 80;
+ }
+ else if (reqUrl.protocol === 'https:') {
+ reqPort = 443;
+ }
+ // Format the request hostname and hostname with port
+ let upperReqHosts = [reqUrl.hostname.toUpperCase()];
+ if (typeof reqPort === 'number') {
+ upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`);
+ }
+ // Compare request host against noproxy
+ for (let upperNoProxyItem of noProxy
+ .split(',')
+ .map(x => x.trim().toUpperCase())
+ .filter(x => x)) {
+ if (upperReqHosts.some(x => x === upperNoProxyItem)) {
+ return true;
+ }
+ }
+ return false;
+}
+exports.checkBypass = checkBypass;
+
+
/***/ }),
/***/ 1962:
@@ -57624,19 +58917,11 @@ function saveCache(packageManager) {
core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`);
return;
}
- try {
- yield cache.saveCache(cachePaths, primaryKey);
- core.info(`Cache saved with the key: ${primaryKey}`);
- }
- catch (error) {
- const err = error;
- if (err.name === cache.ReserveCacheError.name) {
- core.info(err.message);
- }
- else {
- throw error;
- }
+ const cacheId = yield cache.saveCache(cachePaths, primaryKey);
+ if (cacheId == -1) {
+ return;
}
+ core.info(`Cache saved with the key: ${primaryKey}`);
});
}
function isCacheDirectoryExists(cacheDirectory) {
diff --git a/dist/setup/index.js b/dist/setup/index.js
index 3c417588..f77bae2d 100644
--- a/dist/setup/index.js
+++ b/dist/setup/index.js
@@ -90,17 +90,18 @@ function restoreCache(paths, primaryKey, restoreKeys, options) {
checkKey(key);
}
const compressionMethod = yield utils.getCompressionMethod();
- // path are needed to compute version
- const cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, {
- compressionMethod
- });
- if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) {
- // Cache not found
- return undefined;
- }
- const archivePath = path.join(yield utils.createTempDirectory(), utils.getCacheFileName(compressionMethod));
- core.debug(`Archive Path: ${archivePath}`);
+ let archivePath = '';
try {
+ // path are needed to compute version
+ const cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, {
+ compressionMethod
+ });
+ if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) {
+ // Cache not found
+ return undefined;
+ }
+ archivePath = path.join(yield utils.createTempDirectory(), utils.getCacheFileName(compressionMethod));
+ core.debug(`Archive Path: ${archivePath}`);
// Download the cache from the cache entry
yield cacheHttpClient.downloadCache(cacheEntry.archiveLocation, archivePath, options);
if (core.isDebug()) {
@@ -110,6 +111,17 @@ function restoreCache(paths, primaryKey, restoreKeys, options) {
core.info(`Cache Size: ~${Math.round(archiveFileSize / (1024 * 1024))} MB (${archiveFileSize} B)`);
yield tar_1.extractTar(archivePath, compressionMethod);
core.info('Cache restored successfully');
+ return cacheEntry.cacheKey;
+ }
+ catch (error) {
+ const typedError = error;
+ if (typedError.name === ValidationError.name) {
+ throw error;
+ }
+ else {
+ // Supress all non-validation cache related errors because caching should be optional
+ core.warning(`Failed to restore: ${error.message}`);
+ }
}
finally {
// Try to delete the archive to save space
@@ -120,7 +132,7 @@ function restoreCache(paths, primaryKey, restoreKeys, options) {
core.debug(`Failed to delete archive: ${error}`);
}
}
- return cacheEntry.cacheKey;
+ return undefined;
});
}
exports.restoreCache = restoreCache;
@@ -138,10 +150,13 @@ function saveCache(paths, key, options) {
checkPaths(paths);
checkKey(key);
const compressionMethod = yield utils.getCompressionMethod();
- let cacheId = null;
+ let cacheId = -1;
const cachePaths = yield utils.resolvePaths(paths);
core.debug('Cache Paths:');
core.debug(`${JSON.stringify(cachePaths)}`);
+ if (cachePaths.length === 0) {
+ throw new Error(`Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.`);
+ }
const archiveFolder = yield utils.createTempDirectory();
const archivePath = path.join(archiveFolder, utils.getCacheFileName(compressionMethod));
core.debug(`Archive Path: ${archivePath}`);
@@ -174,6 +189,18 @@ function saveCache(paths, key, options) {
core.debug(`Saving Cache (ID: ${cacheId})`);
yield cacheHttpClient.saveCache(cacheId, archivePath, options);
}
+ catch (error) {
+ const typedError = error;
+ if (typedError.name === ValidationError.name) {
+ throw error;
+ }
+ else if (typedError.name === ReserveCacheError.name) {
+ core.info(`Failed to save: ${typedError.message}`);
+ }
+ else {
+ core.warning(`Failed to save: ${typedError.message}`);
+ }
+ }
finally {
// Try to delete the archive to save space
try {
@@ -214,8 +241,8 @@ var __importStar = (this && this.__importStar) || function (mod) {
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const core = __importStar(__nccwpck_require__(2186));
-const http_client_1 = __nccwpck_require__(7320);
-const auth_1 = __nccwpck_require__(7093);
+const http_client_1 = __nccwpck_require__(1825);
+const auth_1 = __nccwpck_require__(2001);
const crypto = __importStar(__nccwpck_require__(6113));
const fs = __importStar(__nccwpck_require__(7147));
const url_1 = __nccwpck_require__(7310);
@@ -647,7 +674,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const core = __importStar(__nccwpck_require__(2186));
-const http_client_1 = __nccwpck_require__(7320);
+const http_client_1 = __nccwpck_require__(1825);
const storage_blob_1 = __nccwpck_require__(4100);
const buffer = __importStar(__nccwpck_require__(4300));
const fs = __importStar(__nccwpck_require__(7147));
@@ -885,7 +912,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const core = __importStar(__nccwpck_require__(2186));
-const http_client_1 = __nccwpck_require__(7320);
+const http_client_1 = __nccwpck_require__(1825);
const constants_1 = __nccwpck_require__(8840);
function isSuccessStatusCode(statusCode) {
if (!statusCode) {
@@ -962,7 +989,7 @@ function retryTypedResponse(name, method, maxAttempts = constants_1.DefaultRetry
return __awaiter(this, void 0, void 0, function* () {
return yield retry(name, method, (response) => response.statusCode, maxAttempts, delay,
// If the error object contains the statusCode property, extract it and return
- // an ITypedResponse so it can be processed by the retry logic.
+ // an TypedResponse so it can be processed by the retry logic.
(error) => {
if (error instanceof http_client_1.HttpClientError) {
return {
@@ -1120,6 +1147,8 @@ function createTar(archiveFolder, sourceDirectories, compressionMethod) {
...getCompressionProgram(),
'-cf',
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
+ '--exclude',
+ cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
'-P',
'-C',
workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
@@ -2292,28 +2321,41 @@ exports.SearchState = SearchState;
/***/ }),
-/***/ 7093:
-/***/ ((__unused_webpack_module, exports) => {
+/***/ 2001:
+/***/ (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.PersonalAccessTokenCredentialHandler = exports.BearerCredentialHandler = exports.BasicCredentialHandler = void 0;
class BasicCredentialHandler {
constructor(username, password) {
this.username = username;
this.password = password;
}
prepareRequest(options) {
- options.headers['Authorization'] =
- 'Basic ' +
- Buffer.from(this.username + ':' + this.password).toString('base64');
+ if (!options.headers) {
+ throw Error('The request has no headers');
+ }
+ options.headers['Authorization'] = `Basic ${Buffer.from(`${this.username}:${this.password}`).toString('base64')}`;
}
// This handler cannot handle 401
- canHandleAuthentication(response) {
+ canHandleAuthentication() {
return false;
}
- handleAuthentication(httpClient, requestInfo, objs) {
- return null;
+ handleAuthentication() {
+ return __awaiter(this, void 0, void 0, function* () {
+ throw new Error('not implemented');
+ });
}
}
exports.BasicCredentialHandler = BasicCredentialHandler;
@@ -2324,14 +2366,19 @@ class BearerCredentialHandler {
// currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401
prepareRequest(options) {
- options.headers['Authorization'] = 'Bearer ' + this.token;
+ if (!options.headers) {
+ throw Error('The request has no headers');
+ }
+ options.headers['Authorization'] = `Bearer ${this.token}`;
}
// This handler cannot handle 401
- canHandleAuthentication(response) {
+ canHandleAuthentication() {
return false;
}
- handleAuthentication(httpClient, requestInfo, objs) {
- return null;
+ handleAuthentication() {
+ return __awaiter(this, void 0, void 0, function* () {
+ throw new Error('not implemented');
+ });
}
}
exports.BearerCredentialHandler = BearerCredentialHandler;
@@ -2342,32 +2389,66 @@ class PersonalAccessTokenCredentialHandler {
// currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401
prepareRequest(options) {
- options.headers['Authorization'] =
- 'Basic ' + Buffer.from('PAT:' + this.token).toString('base64');
+ if (!options.headers) {
+ throw Error('The request has no headers');
+ }
+ options.headers['Authorization'] = `Basic ${Buffer.from(`PAT:${this.token}`).toString('base64')}`;
}
// This handler cannot handle 401
- canHandleAuthentication(response) {
+ canHandleAuthentication() {
return false;
}
- handleAuthentication(httpClient, requestInfo, objs) {
- return null;
+ handleAuthentication() {
+ return __awaiter(this, void 0, void 0, function* () {
+ throw new Error('not implemented');
+ });
}
}
exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler;
-
+//# sourceMappingURL=auth.js.map
/***/ }),
-/***/ 7320:
-/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
+/***/ 1825:
+/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {
"use strict";
+/* eslint-disable @typescript-eslint/no-explicit-any */
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (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.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
+ return result;
+};
+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 }));
-const http = __nccwpck_require__(3685);
-const https = __nccwpck_require__(5687);
-const pm = __nccwpck_require__(7326);
-let tunnel;
+exports.HttpClient = exports.isHttps = exports.HttpClientResponse = exports.HttpClientError = exports.getProxyUrl = exports.MediaTypes = exports.Headers = exports.HttpCodes = void 0;
+const http = __importStar(__nccwpck_require__(3685));
+const https = __importStar(__nccwpck_require__(5687));
+const pm = __importStar(__nccwpck_require__(4977));
+const tunnel = __importStar(__nccwpck_require__(4294));
var HttpCodes;
(function (HttpCodes) {
HttpCodes[HttpCodes["OK"] = 200] = "OK";
@@ -2412,7 +2493,7 @@ var MediaTypes;
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
function getProxyUrl(serverUrl) {
- let proxyUrl = pm.getProxyUrl(new URL(serverUrl));
+ const proxyUrl = pm.getProxyUrl(new URL(serverUrl));
return proxyUrl ? proxyUrl.href : '';
}
exports.getProxyUrl = getProxyUrl;
@@ -2445,20 +2526,22 @@ class HttpClientResponse {
this.message = message;
}
readBody() {
- return new Promise(async (resolve, reject) => {
- let output = Buffer.alloc(0);
- this.message.on('data', (chunk) => {
- output = Buffer.concat([output, chunk]);
- });
- this.message.on('end', () => {
- resolve(output.toString());
- });
+ return __awaiter(this, void 0, void 0, function* () {
+ return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () {
+ let output = Buffer.alloc(0);
+ this.message.on('data', (chunk) => {
+ output = Buffer.concat([output, chunk]);
+ });
+ this.message.on('end', () => {
+ resolve(output.toString());
+ });
+ }));
});
}
}
exports.HttpClientResponse = HttpClientResponse;
function isHttps(requestUrl) {
- let parsedUrl = new URL(requestUrl);
+ const parsedUrl = new URL(requestUrl);
return parsedUrl.protocol === 'https:';
}
exports.isHttps = isHttps;
@@ -2501,141 +2584,169 @@ class HttpClient {
}
}
options(requestUrl, additionalHeaders) {
- return this.request('OPTIONS', requestUrl, null, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('OPTIONS', requestUrl, null, additionalHeaders || {});
+ });
}
get(requestUrl, additionalHeaders) {
- return this.request('GET', requestUrl, null, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('GET', requestUrl, null, additionalHeaders || {});
+ });
}
del(requestUrl, additionalHeaders) {
- return this.request('DELETE', requestUrl, null, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('DELETE', requestUrl, null, additionalHeaders || {});
+ });
}
post(requestUrl, data, additionalHeaders) {
- return this.request('POST', requestUrl, data, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('POST', requestUrl, data, additionalHeaders || {});
+ });
}
patch(requestUrl, data, additionalHeaders) {
- return this.request('PATCH', requestUrl, data, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('PATCH', requestUrl, data, additionalHeaders || {});
+ });
}
put(requestUrl, data, additionalHeaders) {
- return this.request('PUT', requestUrl, data, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('PUT', requestUrl, data, additionalHeaders || {});
+ });
}
head(requestUrl, additionalHeaders) {
- return this.request('HEAD', requestUrl, null, additionalHeaders || {});
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request('HEAD', requestUrl, null, additionalHeaders || {});
+ });
}
sendStream(verb, requestUrl, stream, additionalHeaders) {
- return this.request(verb, requestUrl, stream, additionalHeaders);
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.request(verb, requestUrl, stream, additionalHeaders);
+ });
}
/**
* Gets a typed object from an endpoint
* Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
*/
- async getJson(requestUrl, additionalHeaders = {}) {
- additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
- let res = await this.get(requestUrl, additionalHeaders);
- return this._processResponse(res, this.requestOptions);
+ getJson(requestUrl, additionalHeaders = {}) {
+ return __awaiter(this, void 0, void 0, function* () {
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
+ const res = yield this.get(requestUrl, additionalHeaders);
+ return this._processResponse(res, this.requestOptions);
+ });
}
- async postJson(requestUrl, obj, additionalHeaders = {}) {
- let data = JSON.stringify(obj, null, 2);
- additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
- additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
- let res = await this.post(requestUrl, data, additionalHeaders);
- return this._processResponse(res, this.requestOptions);
+ postJson(requestUrl, obj, additionalHeaders = {}) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const data = JSON.stringify(obj, null, 2);
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
+ additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
+ const res = yield this.post(requestUrl, data, additionalHeaders);
+ return this._processResponse(res, this.requestOptions);
+ });
}
- async putJson(requestUrl, obj, additionalHeaders = {}) {
- let data = JSON.stringify(obj, null, 2);
- additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
- additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
- let res = await this.put(requestUrl, data, additionalHeaders);
- return this._processResponse(res, this.requestOptions);
+ putJson(requestUrl, obj, additionalHeaders = {}) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const data = JSON.stringify(obj, null, 2);
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
+ additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
+ const res = yield this.put(requestUrl, data, additionalHeaders);
+ return this._processResponse(res, this.requestOptions);
+ });
}
- async patchJson(requestUrl, obj, additionalHeaders = {}) {
- let data = JSON.stringify(obj, null, 2);
- additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
- additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
- let res = await this.patch(requestUrl, data, additionalHeaders);
- return this._processResponse(res, this.requestOptions);
+ patchJson(requestUrl, obj, additionalHeaders = {}) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const data = JSON.stringify(obj, null, 2);
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
+ additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
+ const res = yield this.patch(requestUrl, data, additionalHeaders);
+ return this._processResponse(res, this.requestOptions);
+ });
}
/**
* Makes a raw http request.
* All other methods such as get, post, patch, and request ultimately call this.
* Prefer get, del, post and patch
*/
- async request(verb, requestUrl, data, headers) {
- if (this._disposed) {
- throw new Error('Client has already been disposed.');
- }
- let parsedUrl = new URL(requestUrl);
- let info = this._prepareRequest(verb, parsedUrl, headers);
- // Only perform retries on reads since writes may not be idempotent.
- let maxTries = this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1
- ? this._maxRetries + 1
- : 1;
- let numTries = 0;
- let response;
- while (numTries < maxTries) {
- response = await this.requestRaw(info, data);
- // Check if it's an authentication challenge
- if (response &&
- response.message &&
- response.message.statusCode === HttpCodes.Unauthorized) {
- let authenticationHandler;
- for (let i = 0; i < this.handlers.length; i++) {
- if (this.handlers[i].canHandleAuthentication(response)) {
- authenticationHandler = this.handlers[i];
- break;
- }
- }
- if (authenticationHandler) {
- return authenticationHandler.handleAuthentication(this, info, data);
- }
- else {
- // We have received an unauthorized response but have no handlers to handle it.
- // Let the response return to the caller.
- return response;
- }
+ request(verb, requestUrl, data, headers) {
+ return __awaiter(this, void 0, void 0, function* () {
+ if (this._disposed) {
+ throw new Error('Client has already been disposed.');
}
- let redirectsRemaining = this._maxRedirects;
- while (HttpRedirectCodes.indexOf(response.message.statusCode) != -1 &&
- this._allowRedirects &&
- redirectsRemaining > 0) {
- const redirectUrl = response.message.headers['location'];
- if (!redirectUrl) {
- // if there's no location to redirect to, we won't
- break;
- }
- let parsedRedirectUrl = new URL(redirectUrl);
- if (parsedUrl.protocol == 'https:' &&
- parsedUrl.protocol != parsedRedirectUrl.protocol &&
- !this._allowRedirectDowngrade) {
- throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.');
- }
- // we need to finish reading the response before reassigning response
- // which will leak the open socket.
- await response.readBody();
- // strip authorization header if redirected to a different hostname
- if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
- for (let header in headers) {
- // header names are case insensitive
- if (header.toLowerCase() === 'authorization') {
- delete headers[header];
+ const parsedUrl = new URL(requestUrl);
+ let info = this._prepareRequest(verb, parsedUrl, headers);
+ // Only perform retries on reads since writes may not be idempotent.
+ const maxTries = this._allowRetries && RetryableHttpVerbs.includes(verb)
+ ? this._maxRetries + 1
+ : 1;
+ let numTries = 0;
+ let response;
+ do {
+ response = yield this.requestRaw(info, data);
+ // Check if it's an authentication challenge
+ if (response &&
+ response.message &&
+ response.message.statusCode === HttpCodes.Unauthorized) {
+ let authenticationHandler;
+ for (const handler of this.handlers) {
+ if (handler.canHandleAuthentication(response)) {
+ authenticationHandler = handler;
+ break;
}
}
+ if (authenticationHandler) {
+ return authenticationHandler.handleAuthentication(this, info, data);
+ }
+ else {
+ // We have received an unauthorized response but have no handlers to handle it.
+ // Let the response return to the caller.
+ return response;
+ }
}
- // let's make the request with the new redirectUrl
- info = this._prepareRequest(verb, parsedRedirectUrl, headers);
- response = await this.requestRaw(info, data);
- redirectsRemaining--;
- }
- if (HttpResponseRetryCodes.indexOf(response.message.statusCode) == -1) {
- // If not a retry code, return immediately instead of retrying
- return response;
- }
- numTries += 1;
- if (numTries < maxTries) {
- await response.readBody();
- await this._performExponentialBackoff(numTries);
- }
- }
- return response;
+ let redirectsRemaining = this._maxRedirects;
+ while (response.message.statusCode &&
+ HttpRedirectCodes.includes(response.message.statusCode) &&
+ this._allowRedirects &&
+ redirectsRemaining > 0) {
+ const redirectUrl = response.message.headers['location'];
+ if (!redirectUrl) {
+ // if there's no location to redirect to, we won't
+ break;
+ }
+ const parsedRedirectUrl = new URL(redirectUrl);
+ if (parsedUrl.protocol === 'https:' &&
+ parsedUrl.protocol !== parsedRedirectUrl.protocol &&
+ !this._allowRedirectDowngrade) {
+ throw new Error('Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.');
+ }
+ // we need to finish reading the response before reassigning response
+ // which will leak the open socket.
+ yield response.readBody();
+ // strip authorization header if redirected to a different hostname
+ if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
+ for (const header in headers) {
+ // header names are case insensitive
+ if (header.toLowerCase() === 'authorization') {
+ delete headers[header];
+ }
+ }
+ }
+ // let's make the request with the new redirectUrl
+ info = this._prepareRequest(verb, parsedRedirectUrl, headers);
+ response = yield this.requestRaw(info, data);
+ redirectsRemaining--;
+ }
+ if (!response.message.statusCode ||
+ !HttpResponseRetryCodes.includes(response.message.statusCode)) {
+ // If not a retry code, return immediately instead of retrying
+ return response;
+ }
+ numTries += 1;
+ if (numTries < maxTries) {
+ yield response.readBody();
+ yield this._performExponentialBackoff(numTries);
+ }
+ } while (numTries < maxTries);
+ return response;
+ });
}
/**
* Needs to be called if keepAlive is set to true in request options.
@@ -2652,14 +2763,22 @@ class HttpClient {
* @param data
*/
requestRaw(info, data) {
- return new Promise((resolve, reject) => {
- let callbackForResult = function (err, res) {
- if (err) {
- reject(err);
+ return __awaiter(this, void 0, void 0, function* () {
+ return new Promise((resolve, reject) => {
+ function callbackForResult(err, res) {
+ if (err) {
+ reject(err);
+ }
+ else if (!res) {
+ // If `err` is not passed, then `res` must be passed.
+ reject(new Error('Unknown error'));
+ }
+ else {
+ resolve(res);
+ }
}
- resolve(res);
- };
- this.requestRawWithCallback(info, data, callbackForResult);
+ this.requestRawWithCallback(info, data, callbackForResult);
+ });
});
}
/**
@@ -2669,21 +2788,24 @@ class HttpClient {
* @param onResult
*/
requestRawWithCallback(info, data, onResult) {
- let socket;
if (typeof data === 'string') {
+ if (!info.options.headers) {
+ info.options.headers = {};
+ }
info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8');
}
let callbackCalled = false;
- let handleResult = (err, res) => {
+ function handleResult(err, res) {
if (!callbackCalled) {
callbackCalled = true;
onResult(err, res);
}
- };
- let req = info.httpModule.request(info.options, (msg) => {
- let res = new HttpClientResponse(msg);
- handleResult(null, res);
+ }
+ const req = info.httpModule.request(info.options, (msg) => {
+ const res = new HttpClientResponse(msg);
+ handleResult(undefined, res);
});
+ let socket;
req.on('socket', sock => {
socket = sock;
});
@@ -2692,12 +2814,12 @@ class HttpClient {
if (socket) {
socket.end();
}
- handleResult(new Error('Request timeout: ' + info.options.path), null);
+ handleResult(new Error(`Request timeout: ${info.options.path}`));
});
req.on('error', function (err) {
// err has statusCode property
// res should have headers
- handleResult(err, null);
+ handleResult(err);
});
if (data && typeof data === 'string') {
req.write(data, 'utf8');
@@ -2718,7 +2840,7 @@ class HttpClient {
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
getAgent(serverUrl) {
- let parsedUrl = new URL(serverUrl);
+ const parsedUrl = new URL(serverUrl);
return this._getAgent(parsedUrl);
}
_prepareRequest(method, requestUrl, headers) {
@@ -2742,21 +2864,19 @@ class HttpClient {
info.options.agent = this._getAgent(info.parsedUrl);
// gives handlers an opportunity to participate
if (this.handlers) {
- this.handlers.forEach(handler => {
+ for (const handler of this.handlers) {
handler.prepareRequest(info.options);
- });
+ }
}
return info;
}
_mergeHeaders(headers) {
- const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {});
if (this.requestOptions && this.requestOptions.headers) {
- return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers));
+ return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers || {}));
}
return lowercaseKeys(headers || {});
}
_getExistingOrDefaultHeader(additionalHeaders, header, _default) {
- const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {});
let clientHeader;
if (this.requestOptions && this.requestOptions.headers) {
clientHeader = lowercaseKeys(this.requestOptions.headers)[header];
@@ -2765,8 +2885,8 @@ class HttpClient {
}
_getAgent(parsedUrl) {
let agent;
- let proxyUrl = pm.getProxyUrl(parsedUrl);
- let useProxy = proxyUrl && proxyUrl.hostname;
+ const proxyUrl = pm.getProxyUrl(parsedUrl);
+ const useProxy = proxyUrl && proxyUrl.hostname;
if (this._keepAlive && useProxy) {
agent = this._proxyAgent;
}
@@ -2774,29 +2894,22 @@ class HttpClient {
agent = this._agent;
}
// if agent is already assigned use that agent.
- if (!!agent) {
+ if (agent) {
return agent;
}
const usingSsl = parsedUrl.protocol === 'https:';
let maxSockets = 100;
- if (!!this.requestOptions) {
+ if (this.requestOptions) {
maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets;
}
- if (useProxy) {
- // If using proxy, need tunnel
- if (!tunnel) {
- tunnel = __nccwpck_require__(4294);
- }
+ // This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis.
+ if (proxyUrl && proxyUrl.hostname) {
const agentOptions = {
- maxSockets: maxSockets,
+ maxSockets,
keepAlive: this._keepAlive,
- proxy: {
- ...((proxyUrl.username || proxyUrl.password) && {
- proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`
- }),
- host: proxyUrl.hostname,
- port: proxyUrl.port
- }
+ proxy: Object.assign(Object.assign({}, ((proxyUrl.username || proxyUrl.password) && {
+ proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`
+ })), { host: proxyUrl.hostname, port: proxyUrl.port })
};
let tunnelAgent;
const overHttps = proxyUrl.protocol === 'https:';
@@ -2811,7 +2924,7 @@ class HttpClient {
}
// if reusing agent across request and tunneling agent isn't assigned create a new agent
if (this._keepAlive && !agent) {
- const options = { keepAlive: this._keepAlive, maxSockets: maxSockets };
+ const options = { keepAlive: this._keepAlive, maxSockets };
agent = usingSsl ? new https.Agent(options) : new http.Agent(options);
this._agent = agent;
}
@@ -2830,109 +2943,117 @@ class HttpClient {
return agent;
}
_performExponentialBackoff(retryNumber) {
- retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber);
- const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber);
- return new Promise(resolve => setTimeout(() => resolve(), ms));
+ return __awaiter(this, void 0, void 0, function* () {
+ retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber);
+ const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber);
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+ });
}
- static dateTimeDeserializer(key, value) {
- if (typeof value === 'string') {
- let a = new Date(value);
- if (!isNaN(a.valueOf())) {
- return a;
- }
- }
- return value;
- }
- async _processResponse(res, options) {
- return new Promise(async (resolve, reject) => {
- const statusCode = res.message.statusCode;
- const response = {
- statusCode: statusCode,
- result: null,
- headers: {}
- };
- // not found leads to null obj returned
- if (statusCode == HttpCodes.NotFound) {
- resolve(response);
- }
- let obj;
- let contents;
- // get the result from the body
- try {
- contents = await res.readBody();
- if (contents && contents.length > 0) {
- if (options && options.deserializeDates) {
- obj = JSON.parse(contents, HttpClient.dateTimeDeserializer);
+ _processResponse(res, options) {
+ return __awaiter(this, void 0, void 0, function* () {
+ return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
+ const statusCode = res.message.statusCode || 0;
+ const response = {
+ statusCode,
+ result: null,
+ headers: {}
+ };
+ // not found leads to null obj returned
+ if (statusCode === HttpCodes.NotFound) {
+ resolve(response);
+ }
+ // get the result from the body
+ function dateTimeDeserializer(key, value) {
+ if (typeof value === 'string') {
+ const a = new Date(value);
+ if (!isNaN(a.valueOf())) {
+ return a;
+ }
+ }
+ return value;
+ }
+ let obj;
+ let contents;
+ try {
+ contents = yield res.readBody();
+ if (contents && contents.length > 0) {
+ if (options && options.deserializeDates) {
+ obj = JSON.parse(contents, dateTimeDeserializer);
+ }
+ else {
+ obj = JSON.parse(contents);
+ }
+ response.result = obj;
+ }
+ response.headers = res.message.headers;
+ }
+ catch (err) {
+ // Invalid resource (contents not json); leaving result obj null
+ }
+ // note that 3xx redirects are handled by the http layer.
+ if (statusCode > 299) {
+ let msg;
+ // if exception/error in body, attempt to get better error
+ if (obj && obj.message) {
+ msg = obj.message;
+ }
+ else if (contents && contents.length > 0) {
+ // it may be the case that the exception is in the body message as string
+ msg = contents;
}
else {
- obj = JSON.parse(contents);
+ msg = `Failed request: (${statusCode})`;
}
- response.result = obj;
- }
- response.headers = res.message.headers;
- }
- catch (err) {
- // Invalid resource (contents not json); leaving result obj null
- }
- // note that 3xx redirects are handled by the http layer.
- if (statusCode > 299) {
- let msg;
- // if exception/error in body, attempt to get better error
- if (obj && obj.message) {
- msg = obj.message;
- }
- else if (contents && contents.length > 0) {
- // it may be the case that the exception is in the body message as string
- msg = contents;
+ const err = new HttpClientError(msg, statusCode);
+ err.result = response.result;
+ reject(err);
}
else {
- msg = 'Failed request: (' + statusCode + ')';
+ resolve(response);
}
- let err = new HttpClientError(msg, statusCode);
- err.result = response.result;
- reject(err);
- }
- else {
- resolve(response);
- }
+ }));
});
}
}
exports.HttpClient = HttpClient;
-
+const lowercaseKeys = (obj) => Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {});
+//# sourceMappingURL=index.js.map
/***/ }),
-/***/ 7326:
+/***/ 4977:
/***/ ((__unused_webpack_module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.checkBypass = exports.getProxyUrl = void 0;
function getProxyUrl(reqUrl) {
- let usingSsl = reqUrl.protocol === 'https:';
- let proxyUrl;
+ const usingSsl = reqUrl.protocol === 'https:';
if (checkBypass(reqUrl)) {
- return proxyUrl;
+ return undefined;
}
- let proxyVar;
- if (usingSsl) {
- proxyVar = process.env['https_proxy'] || process.env['HTTPS_PROXY'];
+ const proxyVar = (() => {
+ if (usingSsl) {
+ return process.env['https_proxy'] || process.env['HTTPS_PROXY'];
+ }
+ else {
+ return process.env['http_proxy'] || process.env['HTTP_PROXY'];
+ }
+ })();
+ if (proxyVar) {
+ return new URL(proxyVar);
}
else {
- proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY'];
+ return undefined;
}
- if (proxyVar) {
- proxyUrl = new URL(proxyVar);
- }
- return proxyUrl;
}
exports.getProxyUrl = getProxyUrl;
function checkBypass(reqUrl) {
if (!reqUrl.hostname) {
return false;
}
- let noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '';
+ const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '';
if (!noProxy) {
return false;
}
@@ -2948,12 +3069,12 @@ function checkBypass(reqUrl) {
reqPort = 443;
}
// Format the request hostname and hostname with port
- let upperReqHosts = [reqUrl.hostname.toUpperCase()];
+ const upperReqHosts = [reqUrl.hostname.toUpperCase()];
if (typeof reqPort === 'number') {
upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`);
}
// Compare request host against noproxy
- for (let upperNoProxyItem of noProxy
+ for (const upperNoProxyItem of noProxy
.split(',')
.map(x => x.trim().toUpperCase())
.filter(x => x)) {
@@ -2964,7 +3085,7 @@ function checkBypass(reqUrl) {
return false;
}
exports.checkBypass = checkBypass;
-
+//# sourceMappingURL=proxy.js.map
/***/ }),
@@ -4576,14 +4697,27 @@ function coerce (version, options) {
"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (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 (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
- result["default"] = mod;
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.issue = exports.issueCommand = void 0;
const os = __importStar(__nccwpck_require__(2037));
const utils_1 = __nccwpck_require__(5278);
/**
@@ -4662,6 +4796,25 @@ function escapeProperty(s) {
"use strict";
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (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.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
+ return result;
+};
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) {
@@ -4671,19 +4824,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
-var __importStar = (this && this.__importStar) || function (mod) {
- if (mod && mod.__esModule) return mod;
- var result = {};
- if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
- result["default"] = mod;
- return result;
-};
Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.getIDToken = exports.getState = exports.saveState = exports.group = exports.endGroup = exports.startGroup = exports.info = exports.notice = exports.warning = exports.error = exports.debug = exports.isDebug = exports.setFailed = exports.setCommandEcho = exports.setOutput = exports.getBooleanInput = exports.getMultilineInput = exports.getInput = exports.addPath = exports.setSecret = exports.exportVariable = exports.ExitCode = void 0;
const command_1 = __nccwpck_require__(7351);
const file_command_1 = __nccwpck_require__(717);
const utils_1 = __nccwpck_require__(5278);
const os = __importStar(__nccwpck_require__(2037));
const path = __importStar(__nccwpck_require__(1017));
+const oidc_utils_1 = __nccwpck_require__(8041);
/**
* The code to exit an action
*/
@@ -4745,7 +4893,9 @@ function addPath(inputPath) {
}
exports.addPath = addPath;
/**
- * Gets the value of an input. The value is also trimmed.
+ * Gets the value of an input.
+ * Unless trimWhitespace is set to false in InputOptions, the value is also trimmed.
+ * Returns an empty string if the value is not defined.
*
* @param name name of the input to get
* @param options optional. See InputOptions.
@@ -4756,9 +4906,49 @@ function getInput(name, options) {
if (options && options.required && !val) {
throw new Error(`Input required and not supplied: ${name}`);
}
+ if (options && options.trimWhitespace === false) {
+ return val;
+ }
return val.trim();
}
exports.getInput = getInput;
+/**
+ * Gets the values of an multiline input. Each value is also trimmed.
+ *
+ * @param name name of the input to get
+ * @param options optional. See InputOptions.
+ * @returns string[]
+ *
+ */
+function getMultilineInput(name, options) {
+ const inputs = getInput(name, options)
+ .split('\n')
+ .filter(x => x !== '');
+ return inputs;
+}
+exports.getMultilineInput = getMultilineInput;
+/**
+ * Gets the input value of the boolean type in the YAML 1.2 "core schema" specification.
+ * Support boolean input list: `true | True | TRUE | false | False | FALSE` .
+ * The return value is also in boolean type.
+ * ref: https://yaml.org/spec/1.2/spec.html#id2804923
+ *
+ * @param name name of the input to get
+ * @param options optional. See InputOptions.
+ * @returns boolean
+ */
+function getBooleanInput(name, options) {
+ const trueValue = ['true', 'True', 'TRUE'];
+ const falseValue = ['false', 'False', 'FALSE'];
+ const val = getInput(name, options);
+ if (trueValue.includes(val))
+ return true;
+ if (falseValue.includes(val))
+ return false;
+ throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${name}\n` +
+ `Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
+}
+exports.getBooleanInput = getBooleanInput;
/**
* Sets the value of an output.
*
@@ -4767,6 +4957,7 @@ exports.getInput = getInput;
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setOutput(name, value) {
+ process.stdout.write(os.EOL);
command_1.issueCommand('set-output', { name }, value);
}
exports.setOutput = setOutput;
@@ -4813,19 +5004,30 @@ exports.debug = debug;
/**
* Adds an error issue
* @param message error issue message. Errors will be converted to string via toString()
+ * @param properties optional properties to add to the annotation.
*/
-function error(message) {
- command_1.issue('error', message instanceof Error ? message.toString() : message);
+function error(message, properties = {}) {
+ command_1.issueCommand('error', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
}
exports.error = error;
/**
- * Adds an warning issue
+ * Adds a warning issue
* @param message warning issue message. Errors will be converted to string via toString()
+ * @param properties optional properties to add to the annotation.
*/
-function warning(message) {
- command_1.issue('warning', message instanceof Error ? message.toString() : message);
+function warning(message, properties = {}) {
+ command_1.issueCommand('warning', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
}
exports.warning = warning;
+/**
+ * Adds a notice issue
+ * @param message notice issue message. Errors will be converted to string via toString()
+ * @param properties optional properties to add to the annotation.
+ */
+function notice(message, properties = {}) {
+ command_1.issueCommand('notice', utils_1.toCommandProperties(properties), message instanceof Error ? message.toString() : message);
+}
+exports.notice = notice;
/**
* Writes info to log with console.log.
* @param message info message
@@ -4898,6 +5100,17 @@ function getState(name) {
return process.env[`STATE_${name}`] || '';
}
exports.getState = getState;
+function getIDToken(aud) {
+ return __awaiter(this, void 0, void 0, function* () {
+ return yield oidc_utils_1.OidcClient.getIDToken(aud);
+ });
+}
+exports.getIDToken = getIDToken;
+/**
+ * Markdown summary exports
+ */
+var markdown_summary_1 = __nccwpck_require__(8042);
+Object.defineProperty(exports, "markdownSummary", ({ enumerable: true, get: function () { return markdown_summary_1.markdownSummary; } }));
//# sourceMappingURL=core.js.map
/***/ }),
@@ -4908,14 +5121,27 @@ exports.getState = getState;
"use strict";
// For internal use, subject to change.
+var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
+ if (k2 === undefined) k2 = k;
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+}) : (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 (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
- result["default"] = mod;
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
+ __setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.issueCommand = void 0;
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
const fs = __importStar(__nccwpck_require__(7147));
@@ -4938,6 +5164,376 @@ exports.issueCommand = issueCommand;
/***/ }),
+/***/ 8042:
+/***/ (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.markdownSummary = exports.SUMMARY_DOCS_URL = exports.SUMMARY_ENV_VAR = void 0;
+const os_1 = __nccwpck_require__(2037);
+const fs_1 = __nccwpck_require__(7147);
+const { access, appendFile, writeFile } = fs_1.promises;
+exports.SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY';
+exports.SUMMARY_DOCS_URL = 'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary';
+class MarkdownSummary {
+ constructor() {
+ this._buffer = '';
+ }
+ /**
+ * Finds the summary file path from the environment, rejects if env var is not found or file does not exist
+ * Also checks r/w permissions.
+ *
+ * @returns step summary file path
+ */
+ filePath() {
+ return __awaiter(this, void 0, void 0, function* () {
+ if (this._filePath) {
+ return this._filePath;
+ }
+ const pathFromEnv = process.env[exports.SUMMARY_ENV_VAR];
+ if (!pathFromEnv) {
+ throw new Error(`Unable to find environment variable for $${exports.SUMMARY_ENV_VAR}. Check if your runtime environment supports markdown summaries.`);
+ }
+ try {
+ yield access(pathFromEnv, fs_1.constants.R_OK | fs_1.constants.W_OK);
+ }
+ catch (_a) {
+ throw new Error(`Unable to access summary file: '${pathFromEnv}'. Check if the file has correct read/write permissions.`);
+ }
+ this._filePath = pathFromEnv;
+ return this._filePath;
+ });
+ }
+ /**
+ * Wraps content in an HTML tag, adding any HTML attributes
+ *
+ * @param {string} tag HTML tag to wrap
+ * @param {string | null} content content within the tag
+ * @param {[attribute: string]: string} attrs key-value list of HTML attributes to add
+ *
+ * @returns {string} content wrapped in HTML element
+ */
+ wrap(tag, content, attrs = {}) {
+ const htmlAttrs = Object.entries(attrs)
+ .map(([key, value]) => ` ${key}="${value}"`)
+ .join('');
+ if (!content) {
+ return `<${tag}${htmlAttrs}>`;
+ }
+ return `<${tag}${htmlAttrs}>${content}${tag}>`;
+ }
+ /**
+ * Writes text in the buffer to the summary buffer file and empties buffer. Will append by default.
+ *
+ * @param {SummaryWriteOptions} [options] (optional) options for write operation
+ *
+ * @returns {Promise} markdown summary instance
+ */
+ write(options) {
+ return __awaiter(this, void 0, void 0, function* () {
+ const overwrite = !!(options === null || options === void 0 ? void 0 : options.overwrite);
+ const filePath = yield this.filePath();
+ const writeFunc = overwrite ? writeFile : appendFile;
+ yield writeFunc(filePath, this._buffer, { encoding: 'utf8' });
+ return this.emptyBuffer();
+ });
+ }
+ /**
+ * Clears the summary buffer and wipes the summary file
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ clear() {
+ return __awaiter(this, void 0, void 0, function* () {
+ return this.emptyBuffer().write({ overwrite: true });
+ });
+ }
+ /**
+ * Returns the current summary buffer as a string
+ *
+ * @returns {string} string of summary buffer
+ */
+ stringify() {
+ return this._buffer;
+ }
+ /**
+ * If the summary buffer is empty
+ *
+ * @returns {boolen} true if the buffer is empty
+ */
+ isEmptyBuffer() {
+ return this._buffer.length === 0;
+ }
+ /**
+ * Resets the summary buffer without writing to summary file
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ emptyBuffer() {
+ this._buffer = '';
+ return this;
+ }
+ /**
+ * Adds raw text to the summary buffer
+ *
+ * @param {string} text content to add
+ * @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false)
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addRaw(text, addEOL = false) {
+ this._buffer += text;
+ return addEOL ? this.addEOL() : this;
+ }
+ /**
+ * Adds the operating system-specific end-of-line marker to the buffer
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addEOL() {
+ return this.addRaw(os_1.EOL);
+ }
+ /**
+ * Adds an HTML codeblock to the summary buffer
+ *
+ * @param {string} code content to render within fenced code block
+ * @param {string} lang (optional) language to syntax highlight code
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addCodeBlock(code, lang) {
+ const attrs = Object.assign({}, (lang && { lang }));
+ const element = this.wrap('pre', this.wrap('code', code), attrs);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML list to the summary buffer
+ *
+ * @param {string[]} items list of items to render
+ * @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false)
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addList(items, ordered = false) {
+ const tag = ordered ? 'ol' : 'ul';
+ const listItems = items.map(item => this.wrap('li', item)).join('');
+ const element = this.wrap(tag, listItems);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML table to the summary buffer
+ *
+ * @param {SummaryTableCell[]} rows table rows
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addTable(rows) {
+ const tableBody = rows
+ .map(row => {
+ const cells = row
+ .map(cell => {
+ if (typeof cell === 'string') {
+ return this.wrap('td', cell);
+ }
+ const { header, data, colspan, rowspan } = cell;
+ const tag = header ? 'th' : 'td';
+ const attrs = Object.assign(Object.assign({}, (colspan && { colspan })), (rowspan && { rowspan }));
+ return this.wrap(tag, data, attrs);
+ })
+ .join('');
+ return this.wrap('tr', cells);
+ })
+ .join('');
+ const element = this.wrap('table', tableBody);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds a collapsable HTML details element to the summary buffer
+ *
+ * @param {string} label text for the closed state
+ * @param {string} content collapsable content
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addDetails(label, content) {
+ const element = this.wrap('details', this.wrap('summary', label) + content);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML image tag to the summary buffer
+ *
+ * @param {string} src path to the image you to embed
+ * @param {string} alt text description of the image
+ * @param {SummaryImageOptions} options (optional) addition image attributes
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addImage(src, alt, options) {
+ const { width, height } = options || {};
+ const attrs = Object.assign(Object.assign({}, (width && { width })), (height && { height }));
+ const element = this.wrap('img', null, Object.assign({ src, alt }, attrs));
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML section heading element
+ *
+ * @param {string} text heading text
+ * @param {number | string} [level=1] (optional) the heading level, default: 1
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addHeading(text, level) {
+ const tag = `h${level}`;
+ const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)
+ ? tag
+ : 'h1';
+ const element = this.wrap(allowedTag, text);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML thematic break (
) to the summary buffer
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addSeparator() {
+ const element = this.wrap('hr', null);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML line break (
) to the summary buffer
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addBreak() {
+ const element = this.wrap('br', null);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML blockquote to the summary buffer
+ *
+ * @param {string} text quote text
+ * @param {string} cite (optional) citation url
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addQuote(text, cite) {
+ const attrs = Object.assign({}, (cite && { cite }));
+ const element = this.wrap('blockquote', text, attrs);
+ return this.addRaw(element).addEOL();
+ }
+ /**
+ * Adds an HTML anchor tag to the summary buffer
+ *
+ * @param {string} text link text/content
+ * @param {string} href hyperlink
+ *
+ * @returns {MarkdownSummary} markdown summary instance
+ */
+ addLink(text, href) {
+ const element = this.wrap('a', text, { href });
+ return this.addRaw(element).addEOL();
+ }
+}
+// singleton export
+exports.markdownSummary = new MarkdownSummary();
+//# sourceMappingURL=markdown-summary.js.map
+
+/***/ }),
+
+/***/ 8041:
+/***/ (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.OidcClient = void 0;
+const http_client_1 = __nccwpck_require__(9925);
+const auth_1 = __nccwpck_require__(3702);
+const core_1 = __nccwpck_require__(2186);
+class OidcClient {
+ static createHttpClient(allowRetry = true, maxRetry = 10) {
+ const requestOptions = {
+ allowRetries: allowRetry,
+ maxRetries: maxRetry
+ };
+ return new http_client_1.HttpClient('actions/oidc-client', [new auth_1.BearerCredentialHandler(OidcClient.getRequestToken())], requestOptions);
+ }
+ static getRequestToken() {
+ const token = process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'];
+ if (!token) {
+ throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable');
+ }
+ return token;
+ }
+ static getIDTokenUrl() {
+ const runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL'];
+ if (!runtimeUrl) {
+ throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable');
+ }
+ return runtimeUrl;
+ }
+ static getCall(id_token_url) {
+ var _a;
+ return __awaiter(this, void 0, void 0, function* () {
+ const httpclient = OidcClient.createHttpClient();
+ const res = yield httpclient
+ .getJson(id_token_url)
+ .catch(error => {
+ throw new Error(`Failed to get ID Token. \n
+ Error Code : ${error.statusCode}\n
+ Error Message: ${error.result.message}`);
+ });
+ const id_token = (_a = res.result) === null || _a === void 0 ? void 0 : _a.value;
+ if (!id_token) {
+ throw new Error('Response json body do not have ID Token field');
+ }
+ return id_token;
+ });
+ }
+ static getIDToken(audience) {
+ return __awaiter(this, void 0, void 0, function* () {
+ try {
+ // New ID Token is requested from action service
+ let id_token_url = OidcClient.getIDTokenUrl();
+ if (audience) {
+ const encodedAudience = encodeURIComponent(audience);
+ id_token_url = `${id_token_url}&audience=${encodedAudience}`;
+ }
+ core_1.debug(`ID token url is ${id_token_url}`);
+ const id_token = yield OidcClient.getCall(id_token_url);
+ core_1.setSecret(id_token);
+ return id_token;
+ }
+ catch (error) {
+ throw new Error(`Error message: ${error.message}`);
+ }
+ });
+ }
+}
+exports.OidcClient = OidcClient;
+//# sourceMappingURL=oidc-utils.js.map
+
+/***/ }),
+
/***/ 5278:
/***/ ((__unused_webpack_module, exports) => {
@@ -4946,6 +5542,7 @@ exports.issueCommand = issueCommand;
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
Object.defineProperty(exports, "__esModule", ({ value: true }));
+exports.toCommandProperties = exports.toCommandValue = void 0;
/**
* Sanitizes an input into a string so it can be passed into issueCommand safely
* @param input input to sanitize into a string
@@ -4960,6 +5557,26 @@ function toCommandValue(input) {
return JSON.stringify(input);
}
exports.toCommandValue = toCommandValue;
+/**
+ *
+ * @param annotationProperties
+ * @returns The command properties to send with the actual annotation command
+ * See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646
+ */
+function toCommandProperties(annotationProperties) {
+ if (!Object.keys(annotationProperties).length) {
+ return {};
+ }
+ return {
+ title: annotationProperties.title,
+ file: annotationProperties.file,
+ line: annotationProperties.startLine,
+ endLine: annotationProperties.endLine,
+ col: annotationProperties.startColumn,
+ endColumn: annotationProperties.endColumn
+ };
+}
+exports.toCommandProperties = toCommandProperties;
//# sourceMappingURL=utils.js.map
/***/ }),
@@ -6884,6 +7501,72 @@ class SearchState {
exports.SearchState = SearchState;
//# sourceMappingURL=internal-search-state.js.map
+/***/ }),
+
+/***/ 3702:
+/***/ ((__unused_webpack_module, exports) => {
+
+"use strict";
+
+Object.defineProperty(exports, "__esModule", ({ value: true }));
+class BasicCredentialHandler {
+ constructor(username, password) {
+ this.username = username;
+ this.password = password;
+ }
+ prepareRequest(options) {
+ options.headers['Authorization'] =
+ 'Basic ' +
+ Buffer.from(this.username + ':' + this.password).toString('base64');
+ }
+ // This handler cannot handle 401
+ canHandleAuthentication(response) {
+ return false;
+ }
+ handleAuthentication(httpClient, requestInfo, objs) {
+ return null;
+ }
+}
+exports.BasicCredentialHandler = BasicCredentialHandler;
+class BearerCredentialHandler {
+ constructor(token) {
+ this.token = token;
+ }
+ // currently implements pre-authorization
+ // TODO: support preAuth = false where it hooks on 401
+ prepareRequest(options) {
+ options.headers['Authorization'] = 'Bearer ' + this.token;
+ }
+ // This handler cannot handle 401
+ canHandleAuthentication(response) {
+ return false;
+ }
+ handleAuthentication(httpClient, requestInfo, objs) {
+ return null;
+ }
+}
+exports.BearerCredentialHandler = BearerCredentialHandler;
+class PersonalAccessTokenCredentialHandler {
+ constructor(token) {
+ this.token = token;
+ }
+ // currently implements pre-authorization
+ // TODO: support preAuth = false where it hooks on 401
+ prepareRequest(options) {
+ options.headers['Authorization'] =
+ 'Basic ' + Buffer.from('PAT:' + this.token).toString('base64');
+ }
+ // This handler cannot handle 401
+ canHandleAuthentication(response) {
+ return false;
+ }
+ handleAuthentication(httpClient, requestInfo, objs) {
+ return null;
+ }
+}
+exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler;
+
+
/***/ }),
/***/ 9925:
@@ -6892,7 +7575,6 @@ exports.SearchState = SearchState;
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
-const url = __nccwpck_require__(7310);
const http = __nccwpck_require__(3685);
const https = __nccwpck_require__(5687);
const pm = __nccwpck_require__(6443);
@@ -6941,7 +7623,7 @@ var MediaTypes;
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
function getProxyUrl(serverUrl) {
- let proxyUrl = pm.getProxyUrl(url.parse(serverUrl));
+ let proxyUrl = pm.getProxyUrl(new URL(serverUrl));
return proxyUrl ? proxyUrl.href : '';
}
exports.getProxyUrl = getProxyUrl;
@@ -6960,6 +7642,15 @@ const HttpResponseRetryCodes = [
const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD'];
const ExponentialBackoffCeiling = 10;
const ExponentialBackoffTimeSlice = 5;
+class HttpClientError extends Error {
+ constructor(message, statusCode) {
+ super(message);
+ this.name = 'HttpClientError';
+ this.statusCode = statusCode;
+ Object.setPrototypeOf(this, HttpClientError.prototype);
+ }
+}
+exports.HttpClientError = HttpClientError;
class HttpClientResponse {
constructor(message) {
this.message = message;
@@ -6978,7 +7669,7 @@ class HttpClientResponse {
}
exports.HttpClientResponse = HttpClientResponse;
function isHttps(requestUrl) {
- let parsedUrl = url.parse(requestUrl);
+ let parsedUrl = new URL(requestUrl);
return parsedUrl.protocol === 'https:';
}
exports.isHttps = isHttps;
@@ -7083,7 +7774,7 @@ class HttpClient {
if (this._disposed) {
throw new Error('Client has already been disposed.');
}
- let parsedUrl = url.parse(requestUrl);
+ let parsedUrl = new URL(requestUrl);
let info = this._prepareRequest(verb, parsedUrl, headers);
// Only perform retries on reads since writes may not be idempotent.
let maxTries = this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1
@@ -7122,7 +7813,7 @@ class HttpClient {
// if there's no location to redirect to, we won't
break;
}
- let parsedRedirectUrl = url.parse(redirectUrl);
+ let parsedRedirectUrl = new URL(redirectUrl);
if (parsedUrl.protocol == 'https:' &&
parsedUrl.protocol != parsedRedirectUrl.protocol &&
!this._allowRedirectDowngrade) {
@@ -7238,7 +7929,7 @@ class HttpClient {
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
getAgent(serverUrl) {
- let parsedUrl = url.parse(serverUrl);
+ let parsedUrl = new URL(serverUrl);
return this._getAgent(parsedUrl);
}
_prepareRequest(method, requestUrl, headers) {
@@ -7311,7 +8002,9 @@ class HttpClient {
maxSockets: maxSockets,
keepAlive: this._keepAlive,
proxy: {
- proxyAuth: proxyUrl.auth,
+ ...((proxyUrl.username || proxyUrl.password) && {
+ proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`
+ }),
host: proxyUrl.hostname,
port: proxyUrl.port
}
@@ -7406,12 +8099,8 @@ class HttpClient {
else {
msg = 'Failed request: (' + statusCode + ')';
}
- let err = new Error(msg);
- // attach statusCode and body obj (if available) to the error object
- err['statusCode'] = statusCode;
- if (response.result) {
- err['result'] = response.result;
- }
+ let err = new HttpClientError(msg, statusCode);
+ err.result = response.result;
reject(err);
}
else {
@@ -7426,12 +8115,11 @@ exports.HttpClient = HttpClient;
/***/ }),
/***/ 6443:
-/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => {
+/***/ ((__unused_webpack_module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
-const url = __nccwpck_require__(7310);
function getProxyUrl(reqUrl) {
let usingSsl = reqUrl.protocol === 'https:';
let proxyUrl;
@@ -7446,7 +8134,7 @@ function getProxyUrl(reqUrl) {
proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY'];
}
if (proxyVar) {
- proxyUrl = url.parse(proxyVar);
+ proxyUrl = new URL(proxyVar);
}
return proxyUrl;
}
@@ -63742,8 +64430,17 @@ class PipCache extends cache_distributor_1.default {
computeKeys() {
return __awaiter(this, void 0, void 0, function* () {
const hash = yield glob.hashFiles(this.cacheDependencyPath);
- const primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
- const restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}`;
+ let primaryKey = '';
+ let restoreKey = '';
+ if (utils_1.IS_LINUX) {
+ const osRelease = yield utils_1.getLinuxOSReleaseInfo();
+ primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${osRelease}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
+ restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${osRelease}-python-${this.pythonVersion}-${this.packageManager}`;
+ }
+ else {
+ primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
+ restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}`;
+ }
return {
primaryKey,
restoreKey: [restoreKey]
@@ -63876,9 +64573,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const glob = __importStar(__nccwpck_require__(8090));
+const io = __importStar(__nccwpck_require__(7436));
const path = __importStar(__nccwpck_require__(1017));
const exec = __importStar(__nccwpck_require__(1514));
+const core = __importStar(__nccwpck_require__(2186));
const cache_distributor_1 = __importDefault(__nccwpck_require__(8953));
+const utils_1 = __nccwpck_require__(1314);
class PoetryCache extends cache_distributor_1.default {
constructor(pythonVersion, patterns = '**/poetry.lock') {
super('poetry', patterns);
@@ -63894,6 +64594,17 @@ class PoetryCache extends cache_distributor_1.default {
if (poetryConfig['virtualenvs.in-project'] === true) {
paths.push(path.join(process.cwd(), '.venv'));
}
+ const pythonLocation = yield io.which('python');
+ if (pythonLocation) {
+ core.debug(`pythonLocation is ${pythonLocation}`);
+ const { exitCode, stderr } = yield exec.getExecOutput(`poetry env use ${pythonLocation}`, undefined, { ignoreReturnCode: true });
+ if (exitCode) {
+ utils_1.logWarning(stderr);
+ }
+ }
+ else {
+ utils_1.logWarning('python binaries were not found in PATH');
+ }
return paths;
});
}
@@ -63974,29 +64685,52 @@ const utils_1 = __nccwpck_require__(1314);
const semver = __importStar(__nccwpck_require__(1383));
const core = __importStar(__nccwpck_require__(2186));
const tc = __importStar(__nccwpck_require__(7784));
-function findPyPyVersion(versionSpec, architecture) {
+function findPyPyVersion(versionSpec, architecture, updateEnvironment, checkLatest) {
return __awaiter(this, void 0, void 0, function* () {
let resolvedPyPyVersion = '';
let resolvedPythonVersion = '';
let installDir;
+ let releases;
const pypyVersionSpec = parsePyPyVersion(versionSpec);
+ if (checkLatest) {
+ releases = yield pypyInstall.getAvailablePyPyVersions();
+ if (releases && releases.length > 0) {
+ const releaseData = pypyInstall.findRelease(releases, pypyVersionSpec.pythonVersion, pypyVersionSpec.pypyVersion, architecture);
+ if (releaseData) {
+ core.info(`Resolved as PyPy ${releaseData.resolvedPyPyVersion} with Python (${releaseData.resolvedPythonVersion})`);
+ pypyVersionSpec.pythonVersion = releaseData.resolvedPythonVersion;
+ pypyVersionSpec.pypyVersion = releaseData.resolvedPyPyVersion;
+ }
+ else {
+ core.info(`Failed to resolve PyPy ${pypyVersionSpec.pypyVersion} with Python (${pypyVersionSpec.pythonVersion}) from manifest`);
+ }
+ }
+ }
({ installDir, resolvedPythonVersion, resolvedPyPyVersion } = findPyPyToolCache(pypyVersionSpec.pythonVersion, pypyVersionSpec.pypyVersion, architecture));
if (!installDir) {
({
installDir,
resolvedPythonVersion,
resolvedPyPyVersion
- } = yield pypyInstall.installPyPy(pypyVersionSpec.pypyVersion, pypyVersionSpec.pythonVersion, architecture));
+ } = yield pypyInstall.installPyPy(pypyVersionSpec.pypyVersion, pypyVersionSpec.pythonVersion, architecture, releases));
}
const pipDir = utils_1.IS_WINDOWS ? 'Scripts' : 'bin';
const _binDir = path.join(installDir, pipDir);
const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : '';
const pythonPath = path.join(utils_1.IS_WINDOWS ? installDir : _binDir, `python${binaryExtension}`);
const pythonLocation = pypyInstall.getPyPyBinaryPath(installDir);
- core.exportVariable('pythonLocation', installDir);
- core.exportVariable('PKG_CONFIG_PATH', pythonLocation + '/lib/pkgconfig');
- core.addPath(pythonLocation);
- core.addPath(_binDir);
+ if (updateEnvironment) {
+ core.exportVariable('pythonLocation', installDir);
+ // https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython
+ core.exportVariable('Python_ROOT_DIR', installDir);
+ // https://cmake.org/cmake/help/latest/module/FindPython2.html#module:FindPython2
+ core.exportVariable('Python2_ROOT_DIR', installDir);
+ // https://cmake.org/cmake/help/latest/module/FindPython3.html#module:FindPython3
+ core.exportVariable('Python3_ROOT_DIR', installDir);
+ core.exportVariable('PKG_CONFIG_PATH', pythonLocation + '/lib/pkgconfig');
+ core.addPath(pythonLocation);
+ core.addPath(_binDir);
+ }
core.setOutput('python-version', 'pypy' + resolvedPyPyVersion.trim());
core.setOutput('python-path', pythonPath);
return { resolvedPyPyVersion, resolvedPythonVersion };
@@ -64128,15 +64862,28 @@ function binDir(installDir) {
return path.join(installDir, 'bin');
}
}
-function useCpythonVersion(version, architecture) {
+function useCpythonVersion(version, architecture, updateEnvironment, checkLatest) {
+ var _a;
return __awaiter(this, void 0, void 0, function* () {
+ let manifest = null;
const desugaredVersionSpec = desugarDevVersion(version);
- const semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec);
+ let semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec);
core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`);
+ if (checkLatest) {
+ manifest = yield installer.getManifest();
+ const resolvedVersion = (_a = (yield installer.findReleaseFromManifest(semanticVersionSpec, architecture, manifest))) === null || _a === void 0 ? void 0 : _a.version;
+ if (resolvedVersion) {
+ semanticVersionSpec = resolvedVersion;
+ core.info(`Resolved as '${semanticVersionSpec}'`);
+ }
+ else {
+ core.info(`Failed to resolve version ${semanticVersionSpec} from manifest`);
+ }
+ }
let installDir = tc.find('Python', semanticVersionSpec, architecture);
if (!installDir) {
core.info(`Version ${semanticVersionSpec} was not found in the local cache`);
- const foundRelease = yield installer.findReleaseFromManifest(semanticVersionSpec, architecture);
+ const foundRelease = yield installer.findReleaseFromManifest(semanticVersionSpec, architecture, manifest);
if (foundRelease && foundRelease.files && foundRelease.files.length > 0) {
core.info(`Version ${semanticVersionSpec} is available for downloading`);
yield installer.installCpythonFromRelease(foundRelease);
@@ -64149,33 +64896,43 @@ function useCpythonVersion(version, architecture) {
`The list of all available versions can be found here: ${installer.MANIFEST_URL}`
].join(os.EOL));
}
- core.exportVariable('pythonLocation', installDir);
- core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig');
- if (utils_1.IS_LINUX) {
- const libPath = process.env.LD_LIBRARY_PATH
- ? `:${process.env.LD_LIBRARY_PATH}`
- : '';
- const pyLibPath = path.join(installDir, 'lib');
- if (!libPath.split(':').includes(pyLibPath)) {
- core.exportVariable('LD_LIBRARY_PATH', pyLibPath + libPath);
- }
- }
const _binDir = binDir(installDir);
const binaryExtension = utils_1.IS_WINDOWS ? '.exe' : '';
const pythonPath = path.join(utils_1.IS_WINDOWS ? installDir : _binDir, `python${binaryExtension}`);
- core.addPath(installDir);
- core.addPath(_binDir);
- if (utils_1.IS_WINDOWS) {
- // Add --user directory
- // `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python//x64/
- // So if `findLocalTool` succeeded above, we must have a conformant `installDir`
- const version = path.basename(path.dirname(installDir));
- const major = semver.major(version);
- const minor = semver.minor(version);
- const userScriptsDir = path.join(process.env['APPDATA'] || '', 'Python', `Python${major}${minor}`, 'Scripts');
- core.addPath(userScriptsDir);
+ if (updateEnvironment) {
+ core.exportVariable('pythonLocation', installDir);
+ core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig');
+ core.exportVariable('pythonLocation', installDir);
+ // https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython
+ core.exportVariable('Python_ROOT_DIR', installDir);
+ // https://cmake.org/cmake/help/latest/module/FindPython2.html#module:FindPython2
+ core.exportVariable('Python2_ROOT_DIR', installDir);
+ // https://cmake.org/cmake/help/latest/module/FindPython3.html#module:FindPython3
+ core.exportVariable('Python3_ROOT_DIR', installDir);
+ core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig');
+ if (utils_1.IS_LINUX) {
+ const libPath = process.env.LD_LIBRARY_PATH
+ ? `:${process.env.LD_LIBRARY_PATH}`
+ : '';
+ const pyLibPath = path.join(installDir, 'lib');
+ if (!libPath.split(':').includes(pyLibPath)) {
+ core.exportVariable('LD_LIBRARY_PATH', pyLibPath + libPath);
+ }
+ }
+ core.addPath(installDir);
+ core.addPath(_binDir);
+ if (utils_1.IS_WINDOWS) {
+ // Add --user directory
+ // `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python//x64/
+ // So if `findLocalTool` succeeded above, we must have a conformant `installDir`
+ const version = path.basename(path.dirname(installDir));
+ const major = semver.major(version);
+ const minor = semver.minor(version);
+ const userScriptsDir = path.join(process.env['APPDATA'] || '', 'Python', `Python${major}${minor}`, 'Scripts');
+ core.addPath(userScriptsDir);
+ }
+ // On Linux and macOS, pip will create the --user directory and add it to PATH as needed.
}
- // On Linux and macOS, pip will create the --user directory and add it to PATH as needed.
const installed = versionFromPath(installDir);
core.setOutput('python-version', installed);
core.setOutput('python-path', pythonPath);
@@ -64245,7 +65002,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.findAssetForMacOrLinux = exports.findAssetForWindows = exports.isArchPresentForMacOrLinux = exports.isArchPresentForWindows = exports.pypyVersionToSemantic = exports.getPyPyBinaryPath = exports.findRelease = exports.installPyPy = void 0;
+exports.findAssetForMacOrLinux = exports.findAssetForWindows = exports.isArchPresentForMacOrLinux = exports.isArchPresentForWindows = exports.pypyVersionToSemantic = exports.getPyPyBinaryPath = exports.findRelease = exports.getAvailablePyPyVersions = exports.installPyPy = void 0;
const path = __importStar(__nccwpck_require__(1017));
const core = __importStar(__nccwpck_require__(2186));
const tc = __importStar(__nccwpck_require__(7784));
@@ -64254,10 +65011,10 @@ const httpm = __importStar(__nccwpck_require__(9925));
const exec = __importStar(__nccwpck_require__(1514));
const fs_1 = __importDefault(__nccwpck_require__(7147));
const utils_1 = __nccwpck_require__(1314);
-function installPyPy(pypyVersion, pythonVersion, architecture) {
+function installPyPy(pypyVersion, pythonVersion, architecture, releases) {
return __awaiter(this, void 0, void 0, function* () {
let downloadDir;
- const releases = yield getAvailablePyPyVersions();
+ releases = releases !== null && releases !== void 0 ? releases : (yield getAvailablePyPyVersions());
if (!releases || releases.length === 0) {
throw new Error('No release was found in PyPy version.json');
}
@@ -64303,6 +65060,7 @@ function getAvailablePyPyVersions() {
return response.result;
});
}
+exports.getAvailablePyPyVersions = getAvailablePyPyVersions;
function createPyPySymlink(pypyBinaryPath, pythonVersion) {
return __awaiter(this, void 0, void 0, function* () {
const version = semver.coerce(pythonVersion);
@@ -64425,7 +65183,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.installCpythonFromRelease = exports.findReleaseFromManifest = exports.MANIFEST_URL = void 0;
+exports.installCpythonFromRelease = exports.getManifest = exports.findReleaseFromManifest = exports.MANIFEST_URL = void 0;
const path = __importStar(__nccwpck_require__(1017));
const core = __importStar(__nccwpck_require__(2186));
const tc = __importStar(__nccwpck_require__(7784));
@@ -64437,13 +65195,21 @@ const MANIFEST_REPO_OWNER = 'actions';
const MANIFEST_REPO_NAME = 'python-versions';
const MANIFEST_REPO_BRANCH = 'main';
exports.MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`;
-function findReleaseFromManifest(semanticVersionSpec, architecture) {
+function findReleaseFromManifest(semanticVersionSpec, architecture, manifest) {
return __awaiter(this, void 0, void 0, function* () {
- const manifest = yield tc.getManifestFromRepo(MANIFEST_REPO_OWNER, MANIFEST_REPO_NAME, AUTH, MANIFEST_REPO_BRANCH);
- return yield tc.findFromManifest(semanticVersionSpec, false, manifest, architecture);
+ if (!manifest) {
+ manifest = yield getManifest();
+ }
+ const foundRelease = yield tc.findFromManifest(semanticVersionSpec, false, manifest, architecture);
+ return foundRelease;
});
}
exports.findReleaseFromManifest = findReleaseFromManifest;
+function getManifest() {
+ core.debug(`Getting manifest from ${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}@${MANIFEST_REPO_BRANCH}`);
+ return tc.getManifestFromRepo(MANIFEST_REPO_OWNER, MANIFEST_REPO_NAME, AUTH, MANIFEST_REPO_BRANCH);
+}
+exports.getManifest = getManifest;
function installPython(workingDirectory) {
return __awaiter(this, void 0, void 0, function* () {
const options = {
@@ -64548,41 +65314,53 @@ function resolveVersionInput() {
let version = core.getInput('python-version');
let versionFile = core.getInput('python-version-file');
if (version && versionFile) {
- core.warning('Both python-version and python-version-file inputs are specified, only python-version will be used');
+ core.warning('Both python-version and python-version-file inputs are specified, only python-version will be used.');
}
if (version) {
return version;
}
- versionFile = versionFile || '.python-version';
- if (!fs_1.default.existsSync(versionFile)) {
- throw new Error(`The specified python version file at: ${versionFile} does not exist`);
+ if (versionFile) {
+ if (!fs_1.default.existsSync(versionFile)) {
+ throw new Error(`The specified python version file at: ${versionFile} doesn't exist.`);
+ }
+ version = fs_1.default.readFileSync(versionFile, 'utf8');
+ core.info(`Resolved ${versionFile} as ${version}`);
+ return version;
}
- version = fs_1.default.readFileSync(versionFile, 'utf8');
- core.info(`Resolved ${versionFile} as ${version}`);
+ utils_1.logWarning("Neither 'python-version' nor 'python-version-file' inputs were supplied. Attempting to find '.python-version' file.");
+ versionFile = '.python-version';
+ if (fs_1.default.existsSync(versionFile)) {
+ version = fs_1.default.readFileSync(versionFile, 'utf8');
+ core.info(`Resolved ${versionFile} as ${version}`);
+ return version;
+ }
+ utils_1.logWarning(`${versionFile} doesn't exist.`);
return version;
}
function run() {
var _a;
return __awaiter(this, void 0, void 0, function* () {
+ if (utils_1.IS_MAC) {
+ process.env['AGENT_TOOLSDIRECTORY'] = '/Users/runner/hostedtoolcache';
+ }
if ((_a = process.env.AGENT_TOOLSDIRECTORY) === null || _a === void 0 ? void 0 : _a.trim()) {
- core.debug(`Python is expected to be installed into AGENT_TOOLSDIRECTORY=${process.env['AGENT_TOOLSDIRECTORY']}`);
process.env['RUNNER_TOOL_CACHE'] = process.env['AGENT_TOOLSDIRECTORY'];
}
- else {
- core.debug(`Python is expected to be installed into RUNNER_TOOL_CACHE==${process.env['RUNNER_TOOL_CACHE']}`);
- }
+ core.debug(`Python is expected to be installed into ${process.env['RUNNER_TOOL_CACHE']}`);
try {
const version = resolveVersionInput();
+ const checkLatest = core.getBooleanInput('check-latest');
if (version) {
let pythonVersion;
const arch = core.getInput('architecture') || os.arch();
+ const updateEnvironment = core.getBooleanInput('update-environment');
if (isPyPyVersion(version)) {
- const installed = yield finderPyPy.findPyPyVersion(version, arch);
+ const installed = yield finderPyPy.findPyPyVersion(version, arch, updateEnvironment, checkLatest);
pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`;
core.info(`Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`);
}
else {
- const installed = yield finder.useCpythonVersion(version, arch);
+ const installed = yield finder.useCpythonVersion(version, arch, updateEnvironment, checkLatest);
pythonVersion = installed.version;
core.info(`Successfully set up ${installed.impl} (${pythonVersion})`);
}
@@ -64631,18 +65409,29 @@ var __importStar = (this && this.__importStar) || function (mod) {
__setModuleDefault(result, mod);
return result;
};
+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());
+ });
+};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
-exports.isCacheFeatureAvailable = exports.isGhes = exports.validatePythonVersionFormatForPyPy = exports.writeExactPyPyVersionFile = exports.readExactPyPyVersionFile = exports.getPyPyVersionFromPath = exports.isNightlyKeyword = exports.validateVersion = exports.createSymlinkInFolder = exports.WINDOWS_PLATFORMS = exports.WINDOWS_ARCHS = exports.IS_LINUX = exports.IS_WINDOWS = void 0;
+exports.logWarning = exports.getLinuxOSReleaseInfo = exports.isCacheFeatureAvailable = exports.isGhes = exports.validatePythonVersionFormatForPyPy = exports.writeExactPyPyVersionFile = exports.readExactPyPyVersionFile = exports.getPyPyVersionFromPath = exports.isNightlyKeyword = exports.validateVersion = exports.createSymlinkInFolder = exports.WINDOWS_PLATFORMS = exports.WINDOWS_ARCHS = exports.IS_MAC = exports.IS_LINUX = exports.IS_WINDOWS = void 0;
const cache = __importStar(__nccwpck_require__(7799));
const core = __importStar(__nccwpck_require__(2186));
const fs_1 = __importDefault(__nccwpck_require__(7147));
const path = __importStar(__nccwpck_require__(1017));
const semver = __importStar(__nccwpck_require__(1383));
+const exec = __importStar(__nccwpck_require__(1514));
exports.IS_WINDOWS = process.platform === 'win32';
exports.IS_LINUX = process.platform === 'linux';
+exports.IS_MAC = process.platform === 'darwin';
exports.WINDOWS_ARCHS = ['x86', 'x64'];
exports.WINDOWS_PLATFORMS = ['win32', 'win64'];
const PYPY_VERSION_FILE = 'PYPY_VERSION';
@@ -64724,6 +65513,22 @@ function isCacheFeatureAvailable() {
return true;
}
exports.isCacheFeatureAvailable = isCacheFeatureAvailable;
+function getLinuxOSReleaseInfo() {
+ return __awaiter(this, void 0, void 0, function* () {
+ const { stdout, stderr, exitCode } = yield exec.getExecOutput('lsb_release', ['-i', '-r', '-s'], {
+ silent: true
+ });
+ const [osRelease, osVersion] = stdout.trim().split('\n');
+ core.debug(`OS Release: ${osRelease}, Version: ${osVersion}`);
+ return `${osVersion}-${osRelease}`;
+ });
+}
+exports.getLinuxOSReleaseInfo = getLinuxOSReleaseInfo;
+function logWarning(message) {
+ const warningPrefix = '[warning]';
+ core.info(`${warningPrefix}${message}`);
+}
+exports.logWarning = logWarning;
/***/ }),
diff --git a/docs/advanced-usage.md b/docs/advanced-usage.md
new file mode 100644
index 00000000..024e8b1e
--- /dev/null
+++ b/docs/advanced-usage.md
@@ -0,0 +1,476 @@
+# Advanced Usage
+- [Using the python-version input](advanced-usage.md#using-the-python-version-input)
+ - [Specifying a Python version](advanced-usage.md#specifying-a-python-version)
+ - [Specifying a PyPy version](advanced-usage.md#specifying-a-pypy-version)
+ - [Matrix Testing](advanced-usage.md#matrix-testing)
+- [Using the python-version-file input](advanced-usage.md#using-the-python-version-file-input)
+- [Check latest version](advanced-usage.md#check-latest-version)
+- [Caching packages](advanced-usage.md#caching-packages)
+- [Outputs and environment variables](advanced-usage.md#outputs-and-environment-variables)
+ - [Outputs](advanced-usage.md#outputs)
+ - [Environment variables](advanced-usage.md#environment-variables)
+ - [Using update-environment flag](advanced-usage.md#using-update-environment-flag)
+- [Available versions of Python and PyPy](advanced-usage.md#available-versions-of-python-and-pypy)
+ - [Python](advanced-usage.md#python)
+ - [PyPy](advanced-usage.md#pypy)
+- [Hosted tool cache](advanced-usage.md#hosted-tool-cache)
+- [Using `setup-python` with a self-hosted runner](advanced-usage.md#using-setup-python-with-a-self-hosted-runner)
+ - [Windows](advanced-usage.md#windows)
+ - [Linux](advanced-usage.md#linux)
+ - [macOS](advanced-usage.md#macos)
+- [Using `setup-python` on GHES](advanced-usage.md#using-setup-python-on-ghes)
+
+## Using the `python-version` input
+
+### Specifying a Python version
+
+If there is a specific version of Python that you need and you don't want to worry about any potential breaking changes due to patch updates (going from `3.7.5` to `3.7.6` for example), you should specify the **exact major, minor, and patch version** (such as `3.7.5`):
+
+```yaml
+steps:
+- uses: actions/checkout@v3
+- uses: actions/setup-python@v4
+ with:
+ python-version: '3.7.5'
+- run: python my_script.py
+```
+
+- The only downside to this is that setup may take a little longer. If the exact version is not already installed on the runner due to more recent versions, the exact version will have to be downloaded.
+- MSI installers are used on Windows for this, so runs will take a little longer to set up vs macOS and Linux.
+
+You can specify **only a major and minor version** if you are okay with the most recent patch version being used:
+
+```yaml
+steps:
+- uses: actions/checkout@v3
+- uses: actions/setup-python@v4
+ with:
+ python-version: '3.7'
+- run: python my_script.py
+```
+- There will be a single patch version already installed on each runner for every minor version of Python that is supported.
+- The patch version that will be preinstalled, will generally be the latest and every time there is a new patch released, the older version that is preinstalled will be replaced.
+- Using the most recent patch version will result in a very quick setup since no downloads will be required since a locally installed version of Python on the runner will be used.
+
+You can specify the version with **prerelease tag** to download and set up an accurate pre-release version of Python:
+
+```yaml
+steps:
+- uses: actions/checkout@v3
+- uses: actions/setup-python@v4
+ with:
+ python-version: '3.11.0-alpha.1'
+- run: python my_script.py
+```
+
+It's also possible to use **x.y-dev syntax** to download and set up the latest patch version of Python, alpha and beta releases included. (for specified major & minor versions):
+
+```yaml
+steps:
+- uses: actions/checkout@v3
+- uses: actions/setup-python@v4
+ with:
+ python-version: '3.11-dev'
+- run: python my_script.py
+```
+
+You can also use several types of ranges that are specified in [semver](https://github.com/npm/node-semver#ranges), for instance:
+
+- **[hyphen ranges](https://github.com/npm/node-semver#hyphen-ranges-xyz---abc)** to download and set up the latest available version of Python (includes both pre-release and stable versions):
+
+```yaml
+steps:
+- uses: actions/checkout@v3
+- uses: actions/setup-python@v4
+ with:
+ python-version: '3.11.0-alpha - 3.11.0'
+- run: python my_script.py
+```
+
+- **[x-ranges](https://github.com/npm/node-semver#x-ranges-12x-1x-12-)** to specify the latest stable version of Python (for specified major version):
+
+```yaml
+steps:
+- uses: actions/checkout@v3
+- uses: actions/setup-python@v4
+ with:
+ python-version: '3.x'
+- run: python my_script.py
+```
+Please refer to the [Advanced range syntax section](https://github.com/npm/node-semver#advanced-range-syntax) of the [semver](https://github.com/npm/node-semver) to check other available range syntaxes.
+
+### Specifying a PyPy version
+The version of PyPy should be specified in the format `pypy[-v]` or `pypy-[-v]`.
+The `-v` parameter is optional and can be skipped. The latest PyPy version will be used in this case.
+
+```
+pypy3.8 or pypy-3.8 # the latest available version of PyPy that supports Python 3.8
+pypy2.7 or pypy-2.7 # the latest available version of PyPy that supports Python 2.7
+pypy3.7-v7.3.3 or pypy-3.7-v7.3.3 # Python 3.7 and PyPy 7.3.3
+pypy3.7-v7.x or pypy-3.7-v7.x # Python 3.7 and the latest available PyPy 7.x
+pypy3.7-v7.3.3rc1 or pypy-3.7-v7.3.3rc1 # Python 3.7 and preview version of PyPy
+pypy3.7-nightly or pypy-3.7-nightly # Python 3.7 and nightly PyPy
+```
+
+Download and set up PyPy:
+
+```yaml
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version:
+ - 'pypy3.7' # the latest available version of PyPy that supports Python 3.7
+ - 'pypy3.7-v7.3.3' # Python 3.7 and PyPy 7.3.3
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ - run: python my_script.py
+```
+More details on PyPy syntax can be found in the [Available versions of PyPy](#pypy) section.
+
+### Matrix Testing
+
+Using `setup-python` it's possible to use [matrix syntax](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix) to install several versions of Python or PyPy:
+
+```yaml
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ python-version: [ '2.x', '3.x', 'pypy2.7', 'pypy3.7', 'pypy3.8' ]
+ name: Python ${{ matrix.python-version }} sample
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ architecture: x64
+ - run: python my_script.py
+```
+
+Exclude a specific Python version:
+
+```yaml
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ os: [ubuntu-latest, macos-latest, windows-latest]
+ python-version: ['2.7', '3.7', '3.8', '3.9', '3.10', 'pypy2.7', 'pypy3.8']
+ exclude:
+ - os: macos-latest
+ python-version: '3.8'
+ - os: windows-latest
+ python-version: '3.6'
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Display Python version
+ run: python --version
+```
+
+## Using the `python-version-file` input
+
+`setup-python` action can read Python or PyPy version from a version file. `python-version-file` input is used for specifying the path to the version file. If the file that was supplied to `python-version-file` input doesn't exist, the action will fail with error.
+
+>In case both `python-version` and `python-version-file` inputs are supplied, the `python-version-file` input will be ignored due to its lower priority.
+
+```yaml
+steps:
+- uses: actions/checkout@v3
+- uses: actions/setup-python@v4
+ with:
+ python-version-file: '.python-version' # Read python version from a file .python-version
+- run: python my_script.py
+```
+## Check latest version
+
+The `check-latest` flag defaults to `false`. Use the default or set `check-latest` to `false` if you prefer stability and if you want to ensure a specific `Python or PyPy` version is always used.
+
+If `check-latest` is set to `true`, the action first checks if the cached version is the latest one. If the locally cached version is not the most up-to-date, a `Python or PyPy` version will then be downloaded. Set `check-latest` to `true` if you want the most up-to-date `Python or PyPy` version to always be used.
+
+```yaml
+steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v3
+ with:
+ python-version: '3.7'
+ check-latest: true
+ - run: python my_script.py
+```
+> Setting `check-latest` to `true` has performance implications as downloading `Python or PyPy` versions is slower than using cached versions.
+
+
+## Caching packages
+
+**Caching pipenv dependencies:**
+```yaml
+steps:
+- uses: actions/checkout@v3
+- uses: actions/setup-python@v4
+ with:
+ python-version: '3.9'
+ cache: 'pipenv'
+- name: Install pipenv
+ run: curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python
+- run: pipenv install
+```
+
+**Caching poetry dependencies:**
+```yaml
+steps:
+- uses: actions/checkout@v3
+- name: Install poetry
+ run: pipx install poetry
+- uses: actions/setup-python@v4
+ with:
+ python-version: '3.9'
+ cache: 'poetry'
+- run: poetry install
+- run: poetry run pytest
+```
+
+**Using a list of file paths to cache dependencies**
+```yaml
+steps:
+- uses: actions/checkout@v3
+- uses: actions/setup-python@v4
+ with:
+ python-version: '3.9'
+ cache: 'pipenv'
+ cache-dependency-path: |
+ server/app/Pipfile.lock
+ __test__/app/Pipfile.lock
+- name: Install pipenv
+ run: curl https://raw.githubusercontent.com/pypa/pipenv/master/get-pipenv.py | python
+- run: pipenv install
+```
+**Using wildcard patterns to cache dependencies**
+```yaml
+steps:
+- uses: actions/checkout@v3
+- uses: actions/setup-python@v4
+ with:
+ python-version: '3.9'
+ cache: 'pip'
+ cache-dependency-path: '**/requirements-dev.txt'
+- run: pip install -r subdirectory/requirements-dev.txt
+```
+
+**Using a list of wildcard patterns to cache dependencies**
+```yaml
+steps:
+- uses: actions/checkout@v3
+- uses: actions/setup-python@v4
+ with:
+ python-version: '3.10'
+ cache: 'pip'
+ cache-dependency-path: |
+ **/setup.cfg
+ **/requirements*.txt
+- run: pip install -e . -r subdirectory/requirements-dev.txt
+```
+
+# Outputs and environment variables
+
+## Outputs
+
+### `python-version`
+
+Using **python-version** output it's possible to get the installed by action Python or PyPy version. This output is useful when the input `python-version` is given as a range (e.g. 3.8.0 - 3.10.0 ), but down in a workflow you need to operate with the exact installed version (e.g. 3.10.1).
+
+```yaml
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ id: cp310
+ with:
+ python-version: "3.8.0 - 3.10.0"
+ - run: echo '${{ steps.cp310.outputs.python-version }}'
+```
+
+### `python-path`
+
+**python-path** output is available with the absolute path of the Python or PyPy interpreter executable if you need it:
+
+```yaml
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ id: cp310
+ with:
+ python-version: "3.10"
+ - run: pipx run --python '${{ steps.cp310.outputs.python-path }}' nox --version
+```
+### `cache-hit`
+
+**cache-hit** output is available with a boolean value that indicates whether a cache hit occurred on the primary key:
+
+```
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ id: cp310
+ with:
+ python-version: "3.8.0"
+ cache: "poetry"
+ - run: echo '${{ steps.cp310.outputs.cache-hit }}' # true if cache-hit occured on the primary key
+```
+
+## Environment variables
+
+These environment variables become available after setup-python action execution:
+
+| **Env.variable** | **Description** |
+| ----------- | ----------- |
+| pythonLocation |Contains the absolute path to the folder where the requested version of Python or PyPy is installed|
+| Python_ROOT_DIR | https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython |
+| Python2_ROOT_DIR |https://cmake.org/cmake/help/latest/module/FindPython2.html#module:FindPython2|
+| Python3_ROOT_DIR |https://cmake.org/cmake/help/latest/module/FindPython2.html#module:FindPython3|
+
+## Using `update-environment` flag
+
+The `update-environment` flag defaults to `true`.
+With this setting, the action will add/update environment variables (e.g. `PATH`, `PKG_CONFIG_PATH`, `pythonLocation`) for Python or PyPy to just work out of the box.
+
+If `update-environment` is set to `false`, the action will not add/update environment variables.
+This can prove useful if you want the only side-effect to be to ensure Python or PyPy is installed and rely on the `python-path` output to run executable.
+Such a requirement on side-effect could be because you don't want your composite action messing with your user's workflows.
+
+```yaml
+ steps:
+ - uses: actions/checkout@v3
+ - uses: actions/setup-python@v4
+ id: cp310
+ with:
+ python-version: '3.10'
+ update-environment: false
+ - run: ${{ steps.cp310.outputs.python-path }} my_script.py
+```
+## Available versions of Python and PyPy
+### Python
+
+`setup-python` is able to configure **Python** from two sources:
+
+- Preinstalled versions of Python in the tool cache on GitHub-hosted runners.
+ - For detailed information regarding the available versions of Python that are installed, see [Supported software](https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners#supported-software).
+ - For every minor version of Python, expect only the latest patch to be preinstalled.
+ - If `3.8.1` is installed for example, and `3.8.2` is released, expect `3.8.1` to be removed and replaced by `3.8.2` in the tool cache.
+ - If the exact patch version doesn't matter to you, specifying just the major and minor versions will get you the latest preinstalled patch version. In the previous example, the version spec `3.8` will use the `3.8.2` Python version found in the cache.
+ - Use `-dev` instead of a patch number (e.g., `3.11-dev`) to install the latest patch version release for a given minor version, *alpha and beta releases included*.
+- Downloadable Python versions from GitHub Releases ([actions/python-versions](https://github.com/actions/python-versions/releases)).
+ - All available versions are listed in the [version-manifest.json](https://github.com/actions/python-versions/blob/main/versions-manifest.json) file.
+ - If there is a specific version of Python that is not available, you can open an issue here
+
+>**Note:** Python versions used in this action are generated in the [python-versions](https://github.com/actions/python-versions) repository. For macOS and Ubuntu images, python versions are built from the source code. For Windows, the python-versions repository uses installation executable. For more information please refer to the [python-versions](https://github.com/actions/python-versions) repository.
+
+### PyPy
+
+ `setup-python` is able to configure **PyPy** from two sources:
+
+- Preinstalled versions of PyPy in the tool cache on GitHub-hosted runners
+ - For detailed information regarding the available versions of PyPy that are installed, see [Supported software](https://docs.github.com/en/actions/reference/specifications-for-github-hosted-runners#supported-software).
+ - For the latest PyPy release, all versions of Python are cached.
+ - Cache is updated with a 1-2 week delay. If you specify the PyPy version as `pypy3.7` or `pypy-3.7`, the cached version will be used although a newer version is available. If you need to start using the recently released version right after release, you should specify the exact PyPy version using `pypy3.7-v7.3.3` or `pypy-3.7-v7.3.3`.
+
+- Downloadable PyPy versions from the [official PyPy site](https://downloads.python.org/pypy/).
+ - All available versions that we can download are listed in [versions.json](https://downloads.python.org/pypy/versions.json) file.
+ - PyPy < 7.3.3 are not available to install on-flight.
+ - If some versions are not available, you can open an issue in https://foss.heptapod.net/pypy/pypy/
+
+## Hosted tool cache
+
+GitHub hosted runners have a tool cache that comes with a few versions of Python + PyPy already installed. This tool cache helps speed up runs and tool setup by not requiring any new downloads. There is an environment variable called `RUNNER_TOOL_CACHE` on each runner that describes the location of the tool cache with Python and PyPy installed. `setup-python` works by taking a specific version of Python or PyPy from this tool cache and adding it to PATH.
+
+|| Location |
+|------|-------|
+|**Tool cache Directory** |`RUNNER_TOOL_CACHE`|
+|**Python tool cache**|`RUNNER_TOOL_CACHE/Python/*`|
+|**PyPy tool cache**|`RUNNER_TOOL_CACHE/PyPy/*`|
+
+GitHub virtual environments are set up in [actions/virtual-environments](https://github.com/actions/virtual-environments). During the setup, the available versions of Python and PyPy are automatically downloaded, set up and documented.
+- Tool cache setup for Ubuntu: [Install-Toolset.ps1](https://github.com/actions/virtual-environments/blob/main/images/linux/scripts/installers/Install-Toolset.ps1) [Configure-Toolset.ps1](https://github.com/actions/virtual-environments/blob/main/images/linux/scripts/installers/Configure-Toolset.ps1)
+- Tool cache setup for Windows: [Install-Toolset.ps1](https://github.com/actions/virtual-environments/blob/main/images/win/scripts/Installers/Install-Toolset.ps1) [Configure-Toolset.ps1](https://github.com/actions/virtual-environments/blob/main/images/win/scripts/Installers/Configure-Toolset.ps1)
+
+
+## Using `setup-python` with a self-hosted runner
+
+Python distributions are only available for the same [environments](https://github.com/actions/virtual-environments#available-environments) that GitHub Actions hosted environments are available for. If you are using an unsupported version of Ubuntu such as `19.04` or another Linux distribution such as Fedora, `setup-python` may not work.
+
+If you have a supported self-hosted runner and you would like to use `setup-python`, there are a few extra things you need to make sure are set up so that new versions of Python can be downloaded and configured on your runner.
+
+
+### Windows
+
+- Your runner needs to be running with administrator privileges so that the appropriate directories and files can be set up when downloading and installing a new version of Python for the first time.
+- If your runner is configured as a service, make sure the account that is running the service has the appropriate write permissions so that Python can get installed. The default `NT AUTHORITY\NETWORK SERVICE` should be sufficient.
+- You need `7zip` installed and added to your `PATH` so that the downloaded versions of Python files can be extracted properly during the first-time setup.
+- MSI installers are used when setting up Python on Windows. A word of caution as MSI installers update registry settings.
+- The 3.8 MSI installer for Windows will not let you install another 3.8 version of Python. If `setup-python` fails for a 3.8 version of Python, make sure any previously installed versions are removed by going to "Apps & Features" in the Settings app.
+
+> By default runner downloads and installs tools into the folder set up by `RUNNER_TOOL_CACHE` environment variable. The environment variable called `AGENT_TOOLSDIRECTORY` can be set to change this location for Windows self-hosted runners.
+
+>If you are experiencing problems while configuring Python on your self-hosted runner, turn on [step debugging](https://github.com/actions/toolkit/blob/main/docs/action-debugging.md#step-debug-logs) to see additional logs.
+
+### Linux
+
+By default runner downloads and installs tools into the folder set up by `RUNNER_TOOL_CACHE` environment variable. The environment variable called `AGENT_TOOLSDIRECTORY` can be set to change this location for Linux self-hosted runners:
+- In the same shell that your runner is using, type `export AGENT_TOOLSDIRECTORY=/path/to/folder`.
+- More permanent way of setting the environment variable is to create an `.env` file in the same directory as your runner and to add `AGENT_TOOLSDIRECTORY=/path/to/folder`. This ensures the variable is always set if your runner is configured as a service.
+
+If you're using a non-default tool cache directory be sure that the user starting the runner has write permission to the new tool cache directory. To check the current user and group that the runner belongs type `ls -l` inside the runner's root directory.
+
+The runner can be granted write access to any directory using a few techniques:
+- The user starting the runner is the owner, and the owner has write permission.
+- The user starting the runner is in the owning group, and the owning group has write permission.
+- All users have write permission.
+One quick way to grant access is to change the user and group of the non-default tool cache folder to be the same as the runners using `chown`:
+`sudo chown runner-user:runner-group /path/to/folder`.
+
+
+> If your runner is configured as a service and you run into problems, make sure the user that the service is running as is correct. For more information, you can [check the status of your self-hosted runner](https://docs.github.com/en/actions/hosting-your-own-runners/configuring-the-self-hosted-runner-application-as-a-service#checking-the-status-of-the-service).
+
+
+### macOS
+
+ The Python packages for macOS that are downloaded from `actions/python-versions` are originally compiled from the source in `/Users/runner/hostedtoolcache`. Due to the fixed shared library path, these Python packages are non-relocatable and require to be installed only in `/Users/runner/hostedtoolcache`. Before the use of `setup-python` on the macOS self-hosted runner:
+
+ - Create a directory called `/Users/runner/hostedtoolcache`
+ - Change the permissions of `/Users/runner/hostedtoolcache` so that the runner has write access
+
+You can check the current user and group that the runner belongs to by typing `ls -l` inside the runner's root directory.
+The runner can be granted write access to the `/Users/runner/hostedtoolcache` directory using a few techniques:
+ - The user starting the runner is the owner, and the owner has write permission
+ - The user starting the runner is in the owning group, and the owning group has write permission
+ - All users have write permission.
+One quick way to grant access is to change the user and group of `/Users/runner/hostedtoolcache` to be the same as the runners using `chown`:
+`sudo chown runner-user:runner-group /Users/runner/hostedtoolcache`
+
+> If your runner is configured as a service and you run into problems, make sure the user that the service is running as is correct. For more information, you can [check the status of your self-hosted runner](https://docs.github.com/en/actions/hosting-your-own-runners/configuring-the-self-hosted-runner-application-as-a-service#checking-the-status-of-the-service).
+
+
+
+## Using `setup-python` on GHES
+
+`setup-python` comes pre-installed on the appliance with GHES if Actions is enabled. When dynamically downloading Python distributions, `setup-python` downloads distributions from [`actions/python-versions`](https://github.com/actions/python-versions) on github.com (outside of the appliance). These calls to `actions/python-versions` are made via unauthenticated requests, which are limited to [60 requests per hour per IP](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting). If more requests are made within the time frame, then you will start to see rate-limit errors during downloading that looks like: `##[error]API rate limit exceeded for...`.
+
+To avoid hitting rate-limit problems, we recommend [setting up your own runner tool cache](https://docs.github.com/en/enterprise-server@2.22/admin/github-actions/managing-access-to-actions-from-githubcom/setting-up-the-tool-cache-on-self-hosted-runners-without-internet-access#about-the-included-setup-actions-and-the-runner-tool-cache).
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index d009e435..12dbdb27 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,8 +9,8 @@
"version": "4.0.0",
"license": "MIT",
"dependencies": {
- "@actions/cache": "^2.0.2",
- "@actions/core": "^1.2.3",
+ "@actions/cache": "^3.0.0",
+ "@actions/core": "^1.7.0",
"@actions/exec": "^1.1.0",
"@actions/glob": "^0.2.0",
"@actions/io": "^1.0.2",
@@ -31,14 +31,14 @@
}
},
"node_modules/@actions/cache": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-2.0.2.tgz",
- "integrity": "sha512-K1DCaW/OtHj5mV7hI7HEXiceX3rM4Nc0iG2hfYsrkEy6GiOeqlCC/LyICrBZIRDM6+vSrS12tg1ORl4hghomBA==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-3.0.0.tgz",
+ "integrity": "sha512-GL9CT1Fnu+pqs8TTB621q8Xa8Cilw2n9MwvbgMedetH7L1q2n6jY61gzbwGbKgtVbp3gVJ12aNMi4osSGXx3KQ==",
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.1",
"@actions/glob": "^0.1.0",
- "@actions/http-client": "^1.0.9",
+ "@actions/http-client": "^2.0.1",
"@actions/io": "^1.0.1",
"@azure/ms-rest-js": "^2.6.0",
"@azure/storage-blob": "^12.8.0",
@@ -56,11 +56,11 @@
}
},
"node_modules/@actions/cache/node_modules/@actions/http-client": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
- "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
+ "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"dependencies": {
- "tunnel": "0.0.6"
+ "tunnel": "^0.0.6"
}
},
"node_modules/@actions/cache/node_modules/semver": {
@@ -72,9 +72,12 @@
}
},
"node_modules/@actions/core": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
- "integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.7.0.tgz",
+ "integrity": "sha512-7fPSS7yKOhTpgLMbw7lBLc1QJWvJBBAgyTX2PEhagWcKK8t0H8AKCoPMfnrHqIm5cRYH4QFPqD1/ruhuUE7YcQ==",
+ "dependencies": {
+ "@actions/http-client": "^1.0.11"
+ }
},
"node_modules/@actions/exec": {
"version": "1.1.0",
@@ -94,9 +97,9 @@
}
},
"node_modules/@actions/http-client": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz",
- "integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==",
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
+ "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"dependencies": {
"tunnel": "0.0.6"
}
@@ -11337,14 +11340,14 @@
},
"dependencies": {
"@actions/cache": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-2.0.2.tgz",
- "integrity": "sha512-K1DCaW/OtHj5mV7hI7HEXiceX3rM4Nc0iG2hfYsrkEy6GiOeqlCC/LyICrBZIRDM6+vSrS12tg1ORl4hghomBA==",
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-3.0.0.tgz",
+ "integrity": "sha512-GL9CT1Fnu+pqs8TTB621q8Xa8Cilw2n9MwvbgMedetH7L1q2n6jY61gzbwGbKgtVbp3gVJ12aNMi4osSGXx3KQ==",
"requires": {
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.1",
"@actions/glob": "^0.1.0",
- "@actions/http-client": "^1.0.9",
+ "@actions/http-client": "^2.0.1",
"@actions/io": "^1.0.1",
"@azure/ms-rest-js": "^2.6.0",
"@azure/storage-blob": "^12.8.0",
@@ -11362,11 +11365,11 @@
}
},
"@actions/http-client": {
- "version": "1.0.11",
- "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
- "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
+ "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"requires": {
- "tunnel": "0.0.6"
+ "tunnel": "^0.0.6"
}
},
"semver": {
@@ -11377,9 +11380,12 @@
}
},
"@actions/core": {
- "version": "1.2.6",
- "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
- "integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.7.0.tgz",
+ "integrity": "sha512-7fPSS7yKOhTpgLMbw7lBLc1QJWvJBBAgyTX2PEhagWcKK8t0H8AKCoPMfnrHqIm5cRYH4QFPqD1/ruhuUE7YcQ==",
+ "requires": {
+ "@actions/http-client": "^1.0.11"
+ }
},
"@actions/exec": {
"version": "1.1.0",
@@ -11399,9 +11405,9 @@
}
},
"@actions/http-client": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz",
- "integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==",
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
+ "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"requires": {
"tunnel": "0.0.6"
}
diff --git a/package.json b/package.json
index d7d88f24..0325cae4 100644
--- a/package.json
+++ b/package.json
@@ -9,7 +9,7 @@
"format": "prettier --write \"{,!(node_modules)/**/}*.ts\"",
"format-check": "prettier --check \"{,!(node_modules)/**/}*.ts\"",
"release": "ncc build -o dist/setup src/setup-python.ts && ncc build -o dist/cache-save src/cache-save.ts && git add -f dist/",
- "test": "jest"
+ "test": "jest --coverage"
},
"repository": {
"type": "git",
@@ -23,8 +23,8 @@
"author": "GitHub",
"license": "MIT",
"dependencies": {
- "@actions/cache": "^2.0.2",
- "@actions/core": "^1.2.3",
+ "@actions/cache": "^3.0.0",
+ "@actions/core": "^1.7.0",
"@actions/exec": "^1.1.0",
"@actions/glob": "^0.2.0",
"@actions/io": "^1.0.2",
diff --git a/src/cache-distributions/pip-cache.ts b/src/cache-distributions/pip-cache.ts
index 17055ea5..460b097c 100644
--- a/src/cache-distributions/pip-cache.ts
+++ b/src/cache-distributions/pip-cache.ts
@@ -7,7 +7,7 @@ import * as path from 'path';
import os from 'os';
import CacheDistributor from './cache-distributor';
-import {IS_WINDOWS} from '../utils';
+import {getLinuxOSReleaseInfo, IS_LINUX, IS_WINDOWS} from '../utils';
class PipCache extends CacheDistributor {
constructor(
@@ -57,8 +57,17 @@ class PipCache extends CacheDistributor {
protected async computeKeys() {
const hash = await glob.hashFiles(this.cacheDependencyPath);
- const primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
- const restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}`;
+ let primaryKey = '';
+ let restoreKey = '';
+
+ if (IS_LINUX) {
+ const osRelease = await getLinuxOSReleaseInfo();
+ primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${osRelease}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
+ restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${osRelease}-python-${this.pythonVersion}-${this.packageManager}`;
+ } else {
+ primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
+ restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-python-${this.pythonVersion}-${this.packageManager}`;
+ }
return {
primaryKey,
diff --git a/src/cache-distributions/poetry-cache.ts b/src/cache-distributions/poetry-cache.ts
index 5e22b5dd..60ecea2b 100644
--- a/src/cache-distributions/poetry-cache.ts
+++ b/src/cache-distributions/poetry-cache.ts
@@ -1,9 +1,11 @@
import * as glob from '@actions/glob';
-import * as os from 'os';
+import * as io from '@actions/io';
import * as path from 'path';
import * as exec from '@actions/exec';
+import * as core from '@actions/core';
import CacheDistributor from './cache-distributor';
+import {logWarning} from '../utils';
class PoetryCache extends CacheDistributor {
constructor(
@@ -28,6 +30,26 @@ class PoetryCache extends CacheDistributor {
paths.push(path.join(process.cwd(), '.venv'));
}
+ const pythonLocation = await io.which('python');
+
+ if (pythonLocation) {
+ core.debug(`pythonLocation is ${pythonLocation}`);
+ const {
+ exitCode,
+ stderr
+ } = await exec.getExecOutput(
+ `poetry env use ${pythonLocation}`,
+ undefined,
+ {ignoreReturnCode: true}
+ );
+
+ if (exitCode) {
+ logWarning(stderr);
+ }
+ } else {
+ logWarning('python binaries were not found in PATH');
+ }
+
return paths;
}
diff --git a/src/cache-save.ts b/src/cache-save.ts
index 0f9d0372..6e96be12 100644
--- a/src/cache-save.ts
+++ b/src/cache-save.ts
@@ -43,17 +43,11 @@ async function saveCache(packageManager: string) {
return;
}
- try {
- await cache.saveCache(cachePaths, primaryKey);
- core.info(`Cache saved with the key: ${primaryKey}`);
- } catch (error) {
- const err = error as Error;
- if (err.name === cache.ReserveCacheError.name) {
- core.info(err.message);
- } else {
- throw error;
- }
+ const cacheId = await cache.saveCache(cachePaths, primaryKey);
+ if (cacheId == -1) {
+ return;
}
+ core.info(`Cache saved with the key: ${primaryKey}`);
}
function isCacheDirectoryExists(cacheDirectory: string[]) {
diff --git a/src/find-pypy.ts b/src/find-pypy.ts
index 630d685c..20b9821e 100644
--- a/src/find-pypy.ts
+++ b/src/find-pypy.ts
@@ -6,7 +6,8 @@ import {
validateVersion,
getPyPyVersionFromPath,
readExactPyPyVersionFile,
- validatePythonVersionFormatForPyPy
+ validatePythonVersionFormatForPyPy,
+ IPyPyManifestRelease
} from './utils';
import * as semver from 'semver';
@@ -20,14 +21,41 @@ interface IPyPyVersionSpec {
export async function findPyPyVersion(
versionSpec: string,
- architecture: string
+ architecture: string,
+ updateEnvironment: boolean,
+ checkLatest: boolean
): Promise<{resolvedPyPyVersion: string; resolvedPythonVersion: string}> {
let resolvedPyPyVersion = '';
let resolvedPythonVersion = '';
let installDir: string | null;
+ let releases: IPyPyManifestRelease[] | undefined;
const pypyVersionSpec = parsePyPyVersion(versionSpec);
+ if (checkLatest) {
+ releases = await pypyInstall.getAvailablePyPyVersions();
+ if (releases && releases.length > 0) {
+ const releaseData = pypyInstall.findRelease(
+ releases,
+ pypyVersionSpec.pythonVersion,
+ pypyVersionSpec.pypyVersion,
+ architecture
+ );
+
+ if (releaseData) {
+ core.info(
+ `Resolved as PyPy ${releaseData.resolvedPyPyVersion} with Python (${releaseData.resolvedPythonVersion})`
+ );
+ pypyVersionSpec.pythonVersion = releaseData.resolvedPythonVersion;
+ pypyVersionSpec.pypyVersion = releaseData.resolvedPyPyVersion;
+ } else {
+ core.info(
+ `Failed to resolve PyPy ${pypyVersionSpec.pypyVersion} with Python (${pypyVersionSpec.pythonVersion}) from manifest`
+ );
+ }
+ }
+ }
+
({installDir, resolvedPythonVersion, resolvedPyPyVersion} = findPyPyToolCache(
pypyVersionSpec.pythonVersion,
pypyVersionSpec.pypyVersion,
@@ -42,7 +70,8 @@ export async function findPyPyVersion(
} = await pypyInstall.installPyPy(
pypyVersionSpec.pypyVersion,
pypyVersionSpec.pythonVersion,
- architecture
+ architecture,
+ releases
));
}
@@ -54,10 +83,18 @@ export async function findPyPyVersion(
`python${binaryExtension}`
);
const pythonLocation = pypyInstall.getPyPyBinaryPath(installDir);
- core.exportVariable('pythonLocation', installDir);
- core.exportVariable('PKG_CONFIG_PATH', pythonLocation + '/lib/pkgconfig');
- core.addPath(pythonLocation);
- core.addPath(_binDir);
+ if (updateEnvironment) {
+ core.exportVariable('pythonLocation', installDir);
+ // https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython
+ core.exportVariable('Python_ROOT_DIR', installDir);
+ // https://cmake.org/cmake/help/latest/module/FindPython2.html#module:FindPython2
+ core.exportVariable('Python2_ROOT_DIR', installDir);
+ // https://cmake.org/cmake/help/latest/module/FindPython3.html#module:FindPython3
+ core.exportVariable('Python3_ROOT_DIR', installDir);
+ core.exportVariable('PKG_CONFIG_PATH', pythonLocation + '/lib/pkgconfig');
+ core.addPath(pythonLocation);
+ core.addPath(_binDir);
+ }
core.setOutput('python-version', 'pypy' + resolvedPyPyVersion.trim());
core.setOutput('python-path', pythonPath);
diff --git a/src/find-python.ts b/src/find-python.ts
index f1dad3a9..4e54a94b 100644
--- a/src/find-python.ts
+++ b/src/find-python.ts
@@ -32,12 +32,35 @@ function binDir(installDir: string): string {
export async function useCpythonVersion(
version: string,
- architecture: string
+ architecture: string,
+ updateEnvironment: boolean,
+ checkLatest: boolean
): Promise {
+ let manifest: tc.IToolRelease[] | null = null;
const desugaredVersionSpec = desugarDevVersion(version);
- const semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec);
+ let semanticVersionSpec = pythonVersionToSemantic(desugaredVersionSpec);
core.debug(`Semantic version spec of ${version} is ${semanticVersionSpec}`);
+ if (checkLatest) {
+ manifest = await installer.getManifest();
+ const resolvedVersion = (
+ await installer.findReleaseFromManifest(
+ semanticVersionSpec,
+ architecture,
+ manifest
+ )
+ )?.version;
+
+ if (resolvedVersion) {
+ semanticVersionSpec = resolvedVersion;
+ core.info(`Resolved as '${semanticVersionSpec}'`);
+ } else {
+ core.info(
+ `Failed to resolve version ${semanticVersionSpec} from manifest`
+ );
+ }
+ }
+
let installDir: string | null = tc.find(
'Python',
semanticVersionSpec,
@@ -49,7 +72,8 @@ export async function useCpythonVersion(
);
const foundRelease = await installer.findReleaseFromManifest(
semanticVersionSpec,
- architecture
+ architecture,
+ manifest
);
if (foundRelease && foundRelease.files && foundRelease.files.length > 0) {
@@ -69,46 +93,55 @@ export async function useCpythonVersion(
);
}
- core.exportVariable('pythonLocation', installDir);
- core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig');
-
- if (IS_LINUX) {
- const libPath = process.env.LD_LIBRARY_PATH
- ? `:${process.env.LD_LIBRARY_PATH}`
- : '';
- const pyLibPath = path.join(installDir, 'lib');
-
- if (!libPath.split(':').includes(pyLibPath)) {
- core.exportVariable('LD_LIBRARY_PATH', pyLibPath + libPath);
- }
- }
-
const _binDir = binDir(installDir);
const binaryExtension = IS_WINDOWS ? '.exe' : '';
const pythonPath = path.join(
IS_WINDOWS ? installDir : _binDir,
`python${binaryExtension}`
);
- core.addPath(installDir);
- core.addPath(_binDir);
+ if (updateEnvironment) {
+ core.exportVariable('pythonLocation', installDir);
+ core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig');
+ core.exportVariable('pythonLocation', installDir);
+ // https://cmake.org/cmake/help/latest/module/FindPython.html#module:FindPython
+ core.exportVariable('Python_ROOT_DIR', installDir);
+ // https://cmake.org/cmake/help/latest/module/FindPython2.html#module:FindPython2
+ core.exportVariable('Python2_ROOT_DIR', installDir);
+ // https://cmake.org/cmake/help/latest/module/FindPython3.html#module:FindPython3
+ core.exportVariable('Python3_ROOT_DIR', installDir);
+ core.exportVariable('PKG_CONFIG_PATH', installDir + '/lib/pkgconfig');
- if (IS_WINDOWS) {
- // Add --user directory
- // `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python//x64/
- // So if `findLocalTool` succeeded above, we must have a conformant `installDir`
- const version = path.basename(path.dirname(installDir));
- const major = semver.major(version);
- const minor = semver.minor(version);
+ if (IS_LINUX) {
+ const libPath = process.env.LD_LIBRARY_PATH
+ ? `:${process.env.LD_LIBRARY_PATH}`
+ : '';
+ const pyLibPath = path.join(installDir, 'lib');
- const userScriptsDir = path.join(
- process.env['APPDATA'] || '',
- 'Python',
- `Python${major}${minor}`,
- 'Scripts'
- );
- core.addPath(userScriptsDir);
+ if (!libPath.split(':').includes(pyLibPath)) {
+ core.exportVariable('LD_LIBRARY_PATH', pyLibPath + libPath);
+ }
+ }
+ core.addPath(installDir);
+ core.addPath(_binDir);
+
+ if (IS_WINDOWS) {
+ // Add --user directory
+ // `installDir` from tool cache should look like $RUNNER_TOOL_CACHE/Python//x64/
+ // So if `findLocalTool` succeeded above, we must have a conformant `installDir`
+ const version = path.basename(path.dirname(installDir));
+ const major = semver.major(version);
+ const minor = semver.minor(version);
+
+ const userScriptsDir = path.join(
+ process.env['APPDATA'] || '',
+ 'Python',
+ `Python${major}${minor}`,
+ 'Scripts'
+ );
+ core.addPath(userScriptsDir);
+ }
+ // On Linux and macOS, pip will create the --user directory and add it to PATH as needed.
}
- // On Linux and macOS, pip will create the --user directory and add it to PATH as needed.
const installed = versionFromPath(installDir);
core.setOutput('python-version', installed);
diff --git a/src/install-pypy.ts b/src/install-pypy.ts
index c3718b79..4c49e115 100644
--- a/src/install-pypy.ts
+++ b/src/install-pypy.ts
@@ -19,11 +19,13 @@ import {
export async function installPyPy(
pypyVersion: string,
pythonVersion: string,
- architecture: string
+ architecture: string,
+ releases: IPyPyManifestRelease[] | undefined
) {
let downloadDir;
- const releases = await getAvailablePyPyVersions();
+ releases = releases ?? (await getAvailablePyPyVersions());
+
if (!releases || releases.length === 0) {
throw new Error('No release was found in PyPy version.json');
}
@@ -78,7 +80,7 @@ export async function installPyPy(
return {installDir, resolvedPythonVersion, resolvedPyPyVersion};
}
-async function getAvailablePyPyVersions() {
+export async function getAvailablePyPyVersions() {
const url = 'https://downloads.python.org/pypy/versions.json';
const http: httpm.HttpClient = new httpm.HttpClient('tool-cache');
diff --git a/src/install-python.ts b/src/install-python.ts
index 397da0cb..6e5c8518 100644
--- a/src/install-python.ts
+++ b/src/install-python.ts
@@ -14,20 +14,33 @@ export const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_O
export async function findReleaseFromManifest(
semanticVersionSpec: string,
- architecture: string
+ architecture: string,
+ manifest: tc.IToolRelease[] | null
): Promise {
- const manifest: tc.IToolRelease[] = await tc.getManifestFromRepo(
- MANIFEST_REPO_OWNER,
- MANIFEST_REPO_NAME,
- AUTH,
- MANIFEST_REPO_BRANCH
- );
- return await tc.findFromManifest(
+ if (!manifest) {
+ manifest = await getManifest();
+ }
+
+ const foundRelease = await tc.findFromManifest(
semanticVersionSpec,
false,
manifest,
architecture
);
+
+ return foundRelease;
+}
+
+export function getManifest(): Promise {
+ core.debug(
+ `Getting manifest from ${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}@${MANIFEST_REPO_BRANCH}`
+ );
+ return tc.getManifestFromRepo(
+ MANIFEST_REPO_OWNER,
+ MANIFEST_REPO_NAME,
+ AUTH,
+ MANIFEST_REPO_BRANCH
+ );
}
async function installPython(workingDirectory: string) {
diff --git a/src/setup-python.ts b/src/setup-python.ts
index 2ffeb1ad..d6e6bdaf 100644
--- a/src/setup-python.ts
+++ b/src/setup-python.ts
@@ -5,7 +5,7 @@ import * as path from 'path';
import * as os from 'os';
import fs from 'fs';
import {getCacheDistributor} from './cache-distributions/cache-factory';
-import {isCacheFeatureAvailable} from './utils';
+import {isCacheFeatureAvailable, logWarning, IS_MAC} from './utils';
function isPyPyVersion(versionSpec: string) {
return versionSpec.startsWith('pypy');
@@ -28,7 +28,7 @@ function resolveVersionInput(): string {
if (version && versionFile) {
core.warning(
- 'Both python-version and python-version-file inputs are specified, only python-version will be used'
+ 'Both python-version and python-version-file inputs are specified, only python-version will be used.'
);
}
@@ -36,42 +36,70 @@ function resolveVersionInput(): string {
return version;
}
- versionFile = versionFile || '.python-version';
- if (!fs.existsSync(versionFile)) {
- throw new Error(
- `The specified python version file at: ${versionFile} does not exist`
- );
+ if (versionFile) {
+ if (!fs.existsSync(versionFile)) {
+ throw new Error(
+ `The specified python version file at: ${versionFile} doesn't exist.`
+ );
+ }
+ version = fs.readFileSync(versionFile, 'utf8');
+ core.info(`Resolved ${versionFile} as ${version}`);
+ return version;
}
- version = fs.readFileSync(versionFile, 'utf8');
- core.info(`Resolved ${versionFile} as ${version}`);
+
+ logWarning(
+ "Neither 'python-version' nor 'python-version-file' inputs were supplied. Attempting to find '.python-version' file."
+ );
+ versionFile = '.python-version';
+ if (fs.existsSync(versionFile)) {
+ version = fs.readFileSync(versionFile, 'utf8');
+ core.info(`Resolved ${versionFile} as ${version}`);
+ return version;
+ }
+
+ logWarning(`${versionFile} doesn't exist.`);
return version;
}
async function run() {
- if (process.env.AGENT_TOOLSDIRECTORY?.trim()) {
- core.debug(
- `Python is expected to be installed into AGENT_TOOLSDIRECTORY=${process.env['AGENT_TOOLSDIRECTORY']}`
- );
- process.env['RUNNER_TOOL_CACHE'] = process.env['AGENT_TOOLSDIRECTORY'];
- } else {
- core.debug(
- `Python is expected to be installed into RUNNER_TOOL_CACHE==${process.env['RUNNER_TOOL_CACHE']}`
- );
+ if (IS_MAC) {
+ process.env['AGENT_TOOLSDIRECTORY'] = '/Users/runner/hostedtoolcache';
}
+
+ if (process.env.AGENT_TOOLSDIRECTORY?.trim()) {
+ process.env['RUNNER_TOOL_CACHE'] = process.env['AGENT_TOOLSDIRECTORY'];
+ }
+
+ core.debug(
+ `Python is expected to be installed into ${process.env['RUNNER_TOOL_CACHE']}`
+ );
try {
const version = resolveVersionInput();
+ const checkLatest = core.getBooleanInput('check-latest');
+
if (version) {
let pythonVersion: string;
const arch: string = core.getInput('architecture') || os.arch();
+ const updateEnvironment = core.getBooleanInput('update-environment');
if (isPyPyVersion(version)) {
- const installed = await finderPyPy.findPyPyVersion(version, arch);
+ const installed = await finderPyPy.findPyPyVersion(
+ version,
+ arch,
+ updateEnvironment,
+ checkLatest
+ );
pythonVersion = `${installed.resolvedPyPyVersion}-${installed.resolvedPythonVersion}`;
core.info(
`Successfully set up PyPy ${installed.resolvedPyPyVersion} with Python (${installed.resolvedPythonVersion})`
);
} else {
- const installed = await finder.useCpythonVersion(version, arch);
+ const installed = await finder.useCpythonVersion(
+ version,
+ arch,
+ updateEnvironment,
+ checkLatest
+ );
pythonVersion = installed.version;
core.info(`Successfully set up ${installed.impl} (${pythonVersion})`);
}
diff --git a/src/utils.ts b/src/utils.ts
index eb3a1ba6..b29c79d6 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -3,9 +3,11 @@ import * as core from '@actions/core';
import fs from 'fs';
import * as path from 'path';
import * as semver from 'semver';
+import * as exec from '@actions/exec';
export const IS_WINDOWS = process.platform === 'win32';
export const IS_LINUX = process.platform === 'linux';
+export const IS_MAC = process.platform === 'darwin';
export const WINDOWS_ARCHS = ['x86', 'x64'];
export const WINDOWS_PLATFORMS = ['win32', 'win64'];
const PYPY_VERSION_FILE = 'PYPY_VERSION';
@@ -119,3 +121,24 @@ export function isCacheFeatureAvailable(): boolean {
return true;
}
+
+export async function getLinuxOSReleaseInfo() {
+ const {stdout, stderr, exitCode} = await exec.getExecOutput(
+ 'lsb_release',
+ ['-i', '-r', '-s'],
+ {
+ silent: true
+ }
+ );
+
+ const [osRelease, osVersion] = stdout.trim().split('\n');
+
+ core.debug(`OS Release: ${osRelease}, Version: ${osVersion}`);
+
+ return `${osVersion}-${osRelease}`;
+}
+
+export function logWarning(message: string): void {
+ const warningPrefix = '[warning]';
+ core.info(`${warningPrefix}${message}`);
+}