Compare commits

...

18 Commits

Author SHA1 Message Date
Philip Gai fa55574a2f chore(deps): update @actions/cache license record to 6.2.0
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f8715b78-279e-4fcf-9342-5636bae71b61
2026-07-15 13:48:35 -05:00
Philip Gai aaf6add6b7 chore(deps): bump @actions/cache to 6.2.0
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f8715b78-279e-4fcf-9342-5636bae71b61
2026-07-15 13:27:26 -05:00
Priya Gupta f8cf4291c8 Migrate to ESM and upgrade dependencies (#1330)
* Migrate to ESM and upgrade dependencies

* Add ESM migration note to README for V7

* Remove unnecessary devDependencies: ts-node, @types/jest

* npm audit fix

* Upgrade @types/node to version 26.0.0

* Clarify ESM migration details in README for V7

* Update README and dependencies

* Fix lint issue
2026-07-15 11:40:44 -05:00
Priya Gupta 54baeea5b3 Validate and retry manifest fetch to prevent silent failures (#1332)
* validate and retry manifest fetch

* Refactor error handling in isRateLimitError function for improved clarity
2026-07-14 18:02:48 -05:00
lmvysakh c7092773a3 Annotation code fix (#1335)
Co-authored-by: lmvysakh <lmvysakh@github.com>
2026-07-13 13:28:55 -05:00
priya-kinthali 6849080452 remove EOL Python versions and Bumps numpy text fixture (#1333)
* remove EOL Python versions

* Fix typo in comments for executable suffix removal in test-pypy.yml
2026-07-13 13:25:11 -05:00
dependabot[bot] 0903b469fb Bump certifi from 2020.6.20 to 2024.7.4 in /__tests__/data (#1328)
Bumps [certifi](https://github.com/certifi/python-certifi) from 2020.6.20 to 2024.7.4.
- [Commits](https://github.com/certifi/python-certifi/compare/2020.06.20...2024.07.04)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2024.7.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-10 10:35:36 -05:00
Priya Gupta ece7cb06ca Fix pip cache error handling on Windows. (#1040)
* Add error handling for Windows 'pip cache dir' execution

* Add comments
2026-06-23 13:01:57 -05:00
Nic 1d18d7af5f Update advanced-usage.md (#811)
Fix invalid Url
2026-06-23 12:44:45 -05:00
Haritha d2b357a6a3 Update dependency versions and test workflow configuration (#1322)
* chore: update dependencies in project configuration files

* Add bootstrap step for packaging tools

Added a step to bootstrap packaging tools before installing dependencies.

* Resolve audit issues

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2026-06-23 11:35:00 -05:00
Bassem Dghaidi 8f639b1e75 Merge pull request #1324 from jasongin/update-actions-cache-5.1.0
Bump @actions/cache to 5.1.0, log cache write denied
2026-06-23 11:21:36 +02:00
Jason Ginchereau 6731c2ba87 Resolve high-severity audit issues 2026-06-18 18:02:00 -07:00
Priya Gupta 0cb1a84326 Add RHEL support and include Linux distro in cache keys (#1323)
* Add RHEL support for manifest matching and OS detection

* update dist

* make cache keys distro-aware and key RHEL by major version

* Normalize RHEL OS detection and improve cache key consistency

* Refactor OS info retrieval to use getOSInfo and handle null cases for improved reliability
2026-06-18 18:47:28 -05:00
Jason Ginchereau dc6eab6194 Update dist 2026-06-18 15:55:40 -07:00
Jason Ginchereau 6f4b74bfa2 Strict equality 2026-06-18 15:46:13 -07:00
Jason Ginchereau fa8bde1a9c Bump @actions/cache to 5.1.0, log cache write denied 2026-06-18 15:19:32 -07:00
Copilot c8813ba1bc Upgrade @actions dependencies and update licenses (#1303)
Agent-Logs-Url: https://github.com/actions/setup-python/sessions/d7501c8f-2fae-40ed-a7fc-e78f5087585a

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: gowridurgad <159780674+gowridurgad@users.noreply.github.com>
2026-04-15 12:25:40 -05:00
dependabot[bot] 28f2168f4d Bump minimatch from 3.1.2 to 3.1.5 (#1281)
* Bump minimatch from 3.1.2 to 3.1.5

Bumps [minimatch](https://github.com/isaacs/minimatch) from 3.1.2 to 3.1.5.
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 3.1.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Check failure fix

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: gowridurgad <gowridurgad@gmail.com>
2026-03-09 10:05:35 -05:00
89 changed files with 155031 additions and 139962 deletions
-6
View File
@@ -1,6 +0,0 @@
# Ignore list
/*
# Do not ignore these folders:
!__tests__/
!src/
-51
View File
@@ -1,51 +0,0 @@
// This is a reusable configuration file copied from https://github.com/actions/reusable-workflows/tree/main/reusable-configurations. Please don't make changes to this file as it's the subject of an automatic update.
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:eslint-plugin-jest/recommended',
'eslint-config-prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'eslint-plugin-node', 'eslint-plugin-jest'],
rules: {
'@typescript-eslint/no-require-imports': 'error',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-ignore': 'allow-with-description'
}
],
'no-console': 'error',
'yoda': 'error',
'prefer-const': [
'error',
{
destructuring: 'all'
}
],
'no-control-regex': 'off',
'no-constant-condition': ['error', {checkLoops: false}],
'node/no-extraneous-import': 'error'
},
overrides: [
{
files: ['**/*{test,spec}.ts'],
rules: {
'@typescript-eslint/no-unused-vars': 'off',
'jest/no-standalone-expect': 'off',
'jest/no-conditional-expect': 'off',
'no-console': 'off',
}
}
],
env: {
node: true,
es6: true,
'jest/globals': true
}
};
+69 -45
View File
@@ -23,15 +23,18 @@ jobs:
matrix:
os:
[
ubuntu-latest,
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: [3.13.0t, 3.13.1t, 3.13.2t]
python-version: [3.13.14t, 3.14.6t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps:
- uses: actions/checkout@v6
- name: Setup Python
@@ -50,15 +53,18 @@ jobs:
matrix:
os:
[
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: [3.13.1t, 3.13.2t, 3.13.5t]
python-version: [3.13.14t, 3.14.6t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps:
- uses: actions/checkout@v6
- name: Setup Python
@@ -90,15 +96,15 @@ jobs:
matrix:
os:
[
ubuntu-latest,
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: [3.13.0, 3.13.1, 3.13.2]
python-version: [3.13.14, 3.14.6]
steps:
- uses: actions/checkout@v6
- name: Install poetry
@@ -122,15 +128,18 @@ jobs:
matrix:
os:
[
ubuntu-latest,
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: [3.13.0t, 3.13.1t, 3.13.2t]
python-version: [3.13.14t, 3.14.6t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps:
- uses: actions/checkout@v6
- name: Setup Python
@@ -150,15 +159,18 @@ jobs:
matrix:
os:
[
ubuntu-latest,
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: [3.13.1t, 3.13.2t, 3.13.5t]
python-version: [3.13.14t, 3.14.6t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps:
- uses: actions/checkout@v6
- name: Setup Python
@@ -191,15 +203,18 @@ jobs:
matrix:
os:
[
ubuntu-latest,
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: [3.13.0t, 3.13.1t, 3.13.2t]
python-version: [3.13.14t, 3.14.6t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps:
- uses: actions/checkout@v6
- name: Setup Python
@@ -219,15 +234,18 @@ jobs:
matrix:
os:
[
ubuntu-latest,
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: [3.13.0t, 3.13.1t, 3.13.2t]
python-version: [3.13.14t, 3.14.6t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps:
- uses: actions/checkout@v6
- name: Setup Python
@@ -248,15 +266,18 @@ jobs:
matrix:
os:
[
ubuntu-latest,
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: [3.13.0t, 3.13.1t, 3.13.2t]
python-version: [3.13.14t, 3.14.6t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps:
- uses: actions/checkout@v6
- name: Setup Python
@@ -274,15 +295,18 @@ jobs:
matrix:
os:
[
ubuntu-latest,
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: [3.13.0t, 3.13.1t, 3.13.2t]
python-version: [3.13.14t, 3.14.6t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps:
- uses: actions/checkout@v6
- name: Setup Python
+82 -87
View File
@@ -23,30 +23,23 @@ jobs:
matrix:
os:
[
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version:
[
'3.9',
'pypy-3.9-v7.x',
'3.10',
'pypy-3.10-v7.x',
'3.11',
'pypy-3.11-v7.x',
'3.12',
'3.13'
]
['3.10', '3.11', 'pypy-3.11-v7.x', '3.12', '3.13', '3.14']
exclude:
- os: windows-latest
python-version: pypy-3.11-v7.x
- os: macos-latest
python-version: pypy-3.9-v7.x
- os: windows-11-arm
python-version: pypy-3.11-v7.x
- os: windows-11-arm
python-version: '3.10'
steps:
- uses: actions/checkout@v6
@@ -66,27 +59,22 @@ jobs:
matrix:
os:
[
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version:
['3.10', 'pypy-3.10-v7.x', '3.11', 'pypy-3.11-v7.x', '3.12', '3.13']
python-version: ['3.11', 'pypy-3.11-v7.x', '3.12', '3.13', '3.14']
exclude:
- os: windows-latest
python-version: pypy-3.11-v7.x
- os: ubuntu-22.04
python-version: pypy-3.11-v7.x
- os: ubuntu-22.04-arm
python-version: pypy-3.10-v7.x
- os: ubuntu-22.04-arm
- os: windows-11-arm
python-version: pypy-3.11-v7.x
- os: ubuntu-22.04-arm
python-version: pypy-3.10-v7.x
steps:
- uses: actions/checkout@v6
- name: Setup Python
@@ -123,29 +111,30 @@ jobs:
matrix:
os:
[
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version:
[
'3.9',
'pypy-3.9-v7.x',
'3.10',
'pypy-3.10-v7.x',
'3.11',
'pypy-3.11-v7.x',
'3.12',
'3.13'
]
['3.10', '3.11', 'pypy-3.11-v7.x', '3.12', '3.13', '3.14']
exclude:
- os: windows-latest
python-version: pypy-3.11-v7.x
- os: ubuntu-22.04
python-version: pypy-3.11-v7.x
- os: windows-11-arm
python-version: pypy-3.11-v7.x
- os: windows-11-arm
python-version: '3.10'
steps:
- uses: actions/checkout@v6
- name: Install poetry
run: pipx install poetry
run: |
pipx install poetry
- name: Init pyproject.toml
run: mv ./__tests__/data/pyproject.toml .
- name: Setup Python
@@ -153,6 +142,10 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
cache: 'poetry'
- name: Bootstrap packaging
run: |
python -m ensurepip --upgrade
python -m pip install --upgrade pip setuptools wheel packaging
- name: Install dependencies
run: poetry install --no-root
@@ -164,30 +157,23 @@ jobs:
matrix:
os:
[
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version:
[
'3.9',
'pypy-3.9-v7.x',
'3.10',
'pypy-3.10-v7.x',
'3.11',
'pypy-3.11-v7.x',
'3.12',
'3.13'
]
['3.10', '3.11', 'pypy-3.11-v7.x', '3.12', '3.13', '3.14']
exclude:
- os: windows-latest
python-version: pypy-3.11-v7.x
- os: macos-latest
python-version: pypy-3.9-v7.x
- os: windows-11-arm
python-version: pypy-3.11-v7.x
- os: windows-11-arm
python-version: '3.10'
steps:
- uses: actions/checkout@v6
- name: Setup Python
@@ -207,24 +193,21 @@ jobs:
matrix:
os:
[
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version:
['3.10', 'pypy-3.10-v7.x', '3.11', 'pypy-3.11-v7.x', '3.12', '3.13']
python-version: ['3.11', 'pypy-3.11-v7.x', '3.12', '3.13', '3.14']
exclude:
- os: windows-latest
python-version: pypy-3.11-v7.x
- os: ubuntu-22.04
python-version: pypy-3.11-v7.x
- os: ubuntu-22.04-arm
python-version: pypy-3.10-v7.x
- os: ubuntu-22.04-arm
- os: windows-11-arm
python-version: pypy-3.11-v7.x
steps:
- uses: actions/checkout@v6
@@ -263,15 +246,18 @@ jobs:
matrix:
os:
[
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
exclude:
- os: windows-11-arm
python-version: '3.10'
steps:
- uses: actions/checkout@v6
- name: Setup Python
@@ -291,15 +277,18 @@ jobs:
matrix:
os:
[
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
exclude:
- os: windows-11-arm
python-version: '3.10'
steps:
- uses: actions/checkout@v6
- name: Setup Python
@@ -320,15 +309,18 @@ jobs:
matrix:
os:
[
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
exclude:
- os: windows-11-arm
python-version: '3.10'
steps:
- uses: actions/checkout@v6
- name: Setup Python
@@ -346,15 +338,18 @@ jobs:
matrix:
os:
[
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
exclude:
- os: windows-11-arm
python-version: '3.10'
steps:
- uses: actions/checkout@v6
- name: Setup Python
+37 -35
View File
@@ -19,30 +19,25 @@ jobs:
matrix:
operating-system:
[
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-latest,
ubuntu-24.04-arm,
macos-latest,
macos-15-intel
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Run with setup-python 3.9.13
uses: ./
with:
python-version: 3.9.13
- name: Verify 3.9.13
run: python __tests__/verify-python.py 3.9.13
- name: Run with setup-python 3.10.11
if: matrix.operating-system != 'windows-11-arm'
uses: ./
with:
python-version: 3.10.11
- name: Verify 3.10.11
if: matrix.operating-system != 'windows-11-arm'
run: python __tests__/verify-python.py 3.10.11
- name: Run with setup-python 3.11.9
@@ -52,51 +47,58 @@ jobs:
- name: Verify 3.11.9
run: python __tests__/verify-python.py 3.11.9
- name: Run with setup-python 3.12.7
- name: Run with setup-python 3.12.10
uses: ./
with:
python-version: 3.12.7
- name: Verify 3.12.7
run: python __tests__/verify-python.py 3.12.7
python-version: 3.12.10
- name: Verify 3.12.10
run: python __tests__/verify-python.py 3.12.10
- name: Run with setup-python 3.13.0
- name: Run with setup-python 3.13.14
uses: ./
with:
python-version: 3.13.0
- name: Verify 3.13.0
run: python __tests__/verify-python.py 3.13.0
python-version: 3.13.14
- name: Verify 3.13.14
run: python __tests__/verify-python.py 3.13.14
- name: Run with setup-python 3.13
id: cp313
- name: Run with setup-python 3.14.6
uses: ./
with:
python-version: '3.13'
- name: Verify 3.13
run: python __tests__/verify-python.py 3.13
python-version: 3.14.6
- name: Verify 3.14.6
run: python __tests__/verify-python.py 3.14.6
- name: Run with setup-python 3.14
id: cp314
uses: ./
with:
python-version: '3.14'
- name: Verify 3.14
run: python __tests__/verify-python.py 3.14
- name: Install pipx (Windows)
if: runner. os == 'Windows'
if: runner.os == 'Windows'
run: python -m pip install pipx
- name: Run python-path sample 3.13
run: pipx run --python '${{ steps.cp313.outputs.python-path }}' nox --version
- name: Run python-path sample 3.14
run: pipx run --python '${{ steps.cp314.outputs.python-path }}' nox --version
env:
PIPX_HOME: '${{ runner.temp }}/pipx'
PIPX_BIN_DIR: '${{ runner.temp }}/pipx/bin'
- name: Run with setup-python ==3.13
- name: Run with setup-python ==3.14
uses: ./
with:
python-version: '==3.13'
- name: Verify ==3.13
run: python __tests__/verify-python.py 3.13
python-version: '==3.14'
- name: Verify ==3.14
run: python __tests__/verify-python.py 3.14
- name: Run with setup-python <3.13
- name: Run with setup-python <3.14
uses: ./
with:
python-version: '<3.13'
- name: Verify <3.13
run: python __tests__/verify-python.py 3.12
python-version: '<3.14'
- name: Verify <3.14
run: python __tests__/verify-python.py 3.13
- name: Test Raw Endpoint Access
run: |
+4 -5
View File
@@ -28,10 +28,9 @@ jobs:
macos-15-intel
]
graalpy:
- 'graalpy-22.3'
- 'graalpy-23.0'
- 'graalpy-23.1'
- 'graalpy-24.1'
- 'graalpy-24.2'
- 'graalpy-25.0'
steps:
- name: Checkout
@@ -82,7 +81,7 @@ jobs:
ubuntu-latest,
macos-15-intel
]
graalpy: ['graalpy22.3', 'graalpy23.0', 'graalpy23.1', 'graalpy24.1']
graalpy: ['graalpy23.1', 'graalpy24.2', 'graalpy25.0']
steps:
- name: Checkout
@@ -113,7 +112,7 @@ jobs:
uses: ./
id: graalpy
with:
python-version: 'graalpy-24.x'
python-version: 'graalpy-25.x'
check-latest: true
- name: GraalPy and Python version
run: python --version
+35 -47
View File
@@ -22,27 +22,20 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
ubuntu-24.04-arm,
windows-latest,
macos-15-intel,
macos-latest
]
pypy:
- 'pypy-2.7'
- 'pypy-3.10'
- 'pypy3.9'
- 'pypy-2.7-v7.3.17'
- 'pypy-3.10-v7.3.17'
- 'pypy-3.10-v7.3.16'
- 'pypy-3.10-v7.3.x'
- 'pypy-3.10-v7.x'
- 'pypy-2.7-v7.3.12rc1'
- 'pypy-3.10-nightly'
- 'pypy3.10-v7.3.17'
- 'pypy3.11-v7.3.19'
- 'pypy-3.11'
- 'pypy-3.11-v7.3.23'
- 'pypy-3.11-v7.3.x'
- 'pypy-3.11-v7.x'
- 'pypy-3.11-nightly'
- 'pypy3.11-v7.3.23'
steps:
- name: Checkout
@@ -85,16 +78,15 @@ jobs:
fail-fast: false
matrix:
os:
- macos-15-intel
- macos-14
- macos-15
- windows-2022
- windows-2025
- ubuntu-22.04
- ubuntu-24.04
- ubuntu-22.04-arm
- ubuntu-24.04-arm
pypy: ['pypy-2.7', 'pypy-3.10', 'pypy-3.11']
[
ubuntu-22.04,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest,
macos-15-intel,
macos-latest
]
pypy: ['pypy-3.11']
steps:
- name: Checkout
@@ -138,15 +130,14 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
ubuntu-24.04-arm,
windows-latest,
macos-15-intel,
macos-latest
]
pypy: ['pypy2.7', 'pypy3.9', 'pypy3.10-nightly', 'pypy3.11']
pypy: ['pypy3.11-nightly', 'pypy3.11']
steps:
- name: Checkout
@@ -172,13 +163,12 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
ubuntu-24.04-arm,
windows-latest,
macos-15-intel,
macos-latest
]
steps:
- uses: actions/checkout@v6
@@ -214,13 +204,12 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
ubuntu-24.04-arm,
windows-latest,
macos-15-intel,
macos-latest
]
steps:
- uses: actions/checkout@v6
@@ -230,7 +219,6 @@ jobs:
python-version: |
pypy-3.11-v7.3.x
pypy-3.10-v7.3.x
pypy3.9
check-latest: true
- name: PyPy and Python version
run: python --version
@@ -248,13 +236,13 @@ jobs:
run: |
EXECUTABLE="pypy-3.11-v7.3.x"
EXECUTABLE=${EXECUTABLE/-/} # remove the first '-' in "pypy-X.Y" -> "pypyX.Y" to match executable name
EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffixe
EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffix
${EXECUTABLE} --version
shell: bash
- name: Assert expected binaries (or symlinks) are present
run: |
EXECUTABLE='pypy3.9'
EXECUTABLE='pypy-3.10-v7.3.x'
EXECUTABLE=${EXECUTABLE/pypy-/pypy} # remove the first '-' in "pypy-X.Y" -> "pypyX.Y" to match executable name
EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffixe
EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffix
${EXECUTABLE} --version
shell: bash
+101 -101
View File
@@ -22,15 +22,15 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.13.0t, 3.13.1t, 3.13.2t]
python: [3.13.14t, 3.14.6t]
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -59,15 +59,15 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.13.0t, 3.13.1t, 3.13.2t]
python: [3.13.14t, 3.14.6t]
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -99,15 +99,15 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.13.0t, 3.13.1t, 3.13.2t]
python: [3.13.14t, 3.14.6t]
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -137,15 +137,15 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.13.0, 3.13.1, 3.13.2]
python: [3.13.14, 3.14.6]
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -178,15 +178,15 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.13.0, 3.13.1, 3.13.2]
python: [3.13.14, 3.14.6]
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -219,15 +219,15 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.13.0t, 3.13.1t, 3.13.2t, 3.14t-dev]
python: [3.13.14t, 3.14.6t, 3.15t-dev]
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -250,15 +250,15 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.13t, 3.14t-dev]
python: [3.13t, 3.15t-dev]
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -290,15 +290,15 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.13.0t, 3.13.1t, 3.13.2t, 3.14t-dev]
python: [3.13.14t, 3.14.6t, 3.15t-dev]
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -323,30 +323,30 @@ jobs:
run: python -c 'import math; print(math.factorial(5))'
setup-pre-release-version-from-manifest:
name: Setup 3.14.0-alpha.6 ${{ matrix.os }}
name: Setup 3.15.0-beta.3 ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: setup-python 3.14.0-alpha.6
- name: setup-python 3.15.0-beta.3
id: setup-python
uses: ./
with:
python-version: '3.14.0-alpha.6'
python-version: '3.15.0-beta.3'
freethreaded: true
- name: Check python-path
@@ -360,67 +360,67 @@ jobs:
run: python -c 'import math; print(math.factorial(5))'
setup-dev-version:
name: Setup 3.14t-dev ${{ matrix.os }}
name: Setup 3.15t-dev ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: setup-python 3.14t-dev
- name: setup-python 3.15t-dev
id: setup-python
uses: ./
with:
python-version: '3.14t-dev'
python-version: '3.15t-dev'
- name: Check python-path
run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}'
shell: bash
- name: Validate version
run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.14.') }}
run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.15.') }}
shell: bash
- name: Run simple code
run: python -c 'import math; print(math.factorial(5))'
setup-prerelease-version:
name: Setup 3.14t ${{ matrix.os }}
name: Setup 3.15t ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: setup-python 3.14t
- name: setup-python 3.15t
id: setup-python
uses: ./
with:
python-version: '3.14t'
python-version: '3.15t'
allow-prereleases: true
- name: Check python-path
@@ -428,7 +428,7 @@ jobs:
shell: bash
- name: Validate version
run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.14.') }}
run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.15.') }}
shell: bash
- name: Run simple code
@@ -442,15 +442,15 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.13.0t, 3.13.1t, 3.13.2t]
python: [3.13.14t, 3.14.6t]
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -475,15 +475,15 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: [3.13t, 3.14t-dev]
python-version: [3.13t, 3.15t-dev]
steps:
- uses: actions/checkout@v6
- name: Setup Python and check latest
@@ -502,24 +502,24 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
steps:
- uses: actions/checkout@v6
- name: Setup Python and check latest
- name: Setup multiple Python versions
id: setup-python
uses: ./
with:
python-version: |
3.13.1t
3.13.2t
3.14t-dev
3.13.14t
3.14.6t
3.15t-dev
- name: Verify Python version
run: ${{ steps.setup-python.outputs.python-path }} -VVV
@@ -531,15 +531,15 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.13.1, 3.13.2, 3.14-dev, 3.14.0-alpha.6]
python: [3.13.14, 3.14.6, 3.15-dev, 3.15.0-beta.3]
steps:
- name: Checkout
uses: actions/checkout@v6
+127 -97
View File
@@ -22,15 +22,18 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.9.13, 3.10.11, 3.11.9, 3.12.3, 3.13.2]
python: [3.10.11, 3.11.9, 3.12.10, 3.13.14, 3.14.6]
exclude:
- os: windows-11-arm
python: 3.10.11
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -66,15 +69,18 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.9.13, 3.10.11, 3.11.9, 3.12.3, 3.13.2]
python: [3.10.11, 3.11.9, 3.12.10, 3.13.14, 3.14.6]
exclude:
- os: windows-11-arm
python: 3.10.11
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -113,15 +119,18 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.9.13, 3.10.11, 3.11.9, 3.12.3, 3.13.2]
python: [3.10.11, 3.11.9, 3.12.10, 3.13.14, 3.14.6]
exclude:
- os: windows-11-arm
python: 3.10.11
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -158,15 +167,18 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.9.13, 3.10.11, 3.11.9, '==3.12.3', 3.13.2]
python: [3.10.11, 3.11.9, '==3.12.10', 3.13.14, 3.14.6]
exclude:
- os: windows-11-arm
python: 3.10.11
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -208,15 +220,18 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.9.13, 3.10.11, 3.11.9, 3.12.3, 3.13.2]
python: [3.10.11, 3.11.9, 3.12.10, 3.13.14, 3.14.6]
exclude:
- os: windows-11-arm
python: 3.10.11
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -258,18 +273,22 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [pypy3.11-7.3.18, graalpy-24.1.2, 3.13.2, 3.14-dev]
python: [pypy3.11-7.3.18, graalpy-24.1.2, 3.13.14, 3.15-dev]
exclude:
- os: windows-latest
python: graalpy-24.1.2
- os: windows-11-arm
python: graalpy-24.1.2
- os: windows-11-arm
python: pypy3.11-7.3.18
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -292,15 +311,18 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.9.13, 3.10.11, 3.11.9, 3.13.2]
python: [3.10.11, 3.11.9, 3.13.14, 3.14.6]
exclude:
- os: windows-11-arm
python: 3.10.11
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -342,15 +364,18 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: [3.9.13, 3.10.11, 3.11.9, 3.13.2]
python: [3.10.11, 3.11.9, 3.13.14, 3.14.6]
exclude:
- os: windows-11-arm
python: 3.10.11
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -385,30 +410,30 @@ jobs:
run: python -c 'import math; print(math.factorial(5))'
setup-pre-release-version-from-manifest:
name: Setup 3.14.0-alpha.6 ${{ matrix.os }}
name: Setup 3.15.0-beta.3 ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: setup-python 3.14.0-alpha.6
- name: setup-python 3.15.0-beta.3
id: setup-python
uses: ./
with:
python-version: '3.14.0-alpha.6'
python-version: '3.15.0-beta.3'
- name: Check python-path
run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}'
@@ -417,8 +442,8 @@ jobs:
- name: Validate version
run: |
$pythonVersion = (python --version)
if ("Python 3.14.0a6" -ne "$pythonVersion"){
Write-Host "The current version is $pythonVersion; expected version is 3.14.0a6"
if ("Python 3.15.0b3" -ne "$pythonVersion"){
Write-Host "The current version is $pythonVersion; expected version is 3.15.0b3"
exit 1
}
$pythonVersion
@@ -428,67 +453,67 @@ jobs:
run: python -c 'import math; print(math.factorial(5))'
setup-dev-version:
name: Setup 3.14-dev ${{ matrix.os }}
name: Setup 3.15-dev ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: setup-python 3.14-dev
- name: setup-python 3.15-dev
id: setup-python
uses: ./
with:
python-version: '3.14-dev'
python-version: '3.15-dev'
- name: Check python-path
run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}'
shell: bash
- name: Validate version
run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.14.') }}
run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.15.') }}
shell: bash
- name: Run simple code
run: python -c 'import math; print(math.factorial(5))'
setup-prerelease-version:
name: Setup 3.14 ${{ matrix.os }}
name: Setup 3.15 ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
steps:
- name: Checkout
uses: actions/checkout@v6
- name: setup-python 3.14
- name: setup-python 3.15
id: setup-python
uses: ./
with:
python-version: '3.14'
python-version: '3.15'
allow-prereleases: true
- name: Check python-path
@@ -496,7 +521,7 @@ jobs:
shell: bash
- name: Validate version
run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.14.') }}
run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.15.') }}
shell: bash
- name: Run simple code
@@ -510,15 +535,18 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel,
ubuntu-latest,
ubuntu-24.04-arm
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python: ['3.9', '3.10', '3.11', '3.12', '3.13']
python: ['3.10', '3.11', '3.12', '3.13', '3.14']
exclude:
- os: windows-11-arm
python: '3.10'
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -543,15 +571,18 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
exclude:
- os: windows-11-arm
python-version: '3.10'
steps:
- uses: actions/checkout@v6
- name: Setup Python and check latest
@@ -576,13 +607,13 @@ jobs:
matrix:
os:
[
macos-latest,
windows-latest,
ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
ubuntu-24.04-arm,
windows-latest,
windows-11-arm,
macos-15-intel,
macos-latest
]
steps:
- uses: actions/checkout@v6
@@ -590,17 +621,16 @@ jobs:
uses: ./
with:
python-version: |
3.9
3.10
3.11
3.12
3.13
3.14
check-latest: true
- name: Validate version
run: |
$pythonVersion = (python --version)
if ("$pythonVersion" -NotMatch "3.13"){
Write-Host "The current version is $pythonVersion; expected version is 3.13"
if ("$pythonVersion" -NotMatch "3.14"){
Write-Host "The current version is $pythonVersion; expected version is 3.14"
exit 1
}
$pythonVersion
+3 -1
View File
@@ -14,4 +14,6 @@ allowed:
reviewed:
npm:
- "@actions/http-client"
- "@actions/http-client"
- "balanced-match"
- "brace-expansion"
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@actions/cache"
version: 5.0.1
version: 6.2.0
type: npm
summary: Actions cache lib
homepage: https://github.com/actions/toolkit/tree/main/packages/cache
-20
View File
@@ -1,20 +0,0 @@
---
name: "@actions/core"
version: 1.11.1
type: npm
summary: Actions core lib
homepage: https://github.com/actions/toolkit/tree/main/packages/core
license: mit
licenses:
- sources: LICENSE.md
text: |-
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []
@@ -1,6 +1,6 @@
---
name: "@actions/core"
version: 2.0.1
version: 3.0.1
type: npm
summary: Actions core lib
homepage: https://github.com/actions/toolkit/tree/main/packages/core
@@ -1,6 +1,6 @@
---
name: "@actions/exec"
version: 1.1.1
version: 3.0.0
type: npm
summary: Actions exec lib
homepage: https://github.com/actions/toolkit/tree/main/packages/exec
@@ -1,6 +1,6 @@
---
name: "@actions/glob"
version: 0.5.0
version: 0.6.1
type: npm
summary: Actions glob lib
homepage: https://github.com/actions/toolkit/tree/main/packages/glob
@@ -1,9 +1,9 @@
---
name: "@actions/exec"
version: 2.0.0
name: "@actions/glob"
version: 0.7.0
type: npm
summary: Actions exec lib
homepage: https://github.com/actions/toolkit/tree/main/packages/exec
summary: Actions glob lib
homepage: https://github.com/actions/toolkit/tree/main/packages/glob
license: mit
licenses:
- sources: LICENSE.md
-32
View File
@@ -1,32 +0,0 @@
---
name: "@actions/http-client"
version: 3.0.0
type: npm
summary: Actions Http Client
homepage: https://github.com/actions/toolkit/tree/main/packages/http-client
license: other
licenses:
- sources: LICENSE
text: |
Actions Http Client for Node.js
Copyright (c) GitHub, Inc.
All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []
@@ -1,6 +1,6 @@
---
name: "@actions/http-client"
version: 2.2.3
version: 4.0.1
type: npm
summary: Actions Http Client
homepage: https://github.com/actions/toolkit/tree/main/packages/http-client
-20
View File
@@ -1,20 +0,0 @@
---
name: "@actions/io"
version: 2.0.0
type: npm
summary: Actions io lib
homepage: https://github.com/actions/toolkit/tree/main/packages/io
license: mit
licenses:
- sources: LICENSE.md
text: |-
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []
@@ -1,6 +1,6 @@
---
name: "@actions/io"
version: 1.1.3
version: 3.0.2
type: npm
summary: Actions io lib
homepage: https://github.com/actions/toolkit/tree/main/packages/io
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@actions/tool-cache"
version: 2.0.2
version: 4.0.0
type: npm
summary: Actions tool-cache lib
homepage: https://github.com/actions/toolkit/tree/main/packages/tool-cache
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@azure/core-client"
version: 1.10.1
version: 1.10.2
type: npm
summary: Core library for interfacing with AutoRest generated code
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-client/
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@azure/core-http-compat"
version: 2.3.1
version: 2.4.0
type: npm
summary: Core HTTP Compatibility Library to bridge the gap between Core V1 & V2 packages.
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-compat/
+2 -2
View File
@@ -1,9 +1,9 @@
---
name: "@azure/core-rest-pipeline"
version: 1.22.2
version: 1.24.0
type: npm
summary: Isomorphic client library for making HTTP requests in node.js and browser.
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-rest-pipeline/
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/core-rest-pipeline/README.md
license: mit
licenses:
- sources: LICENSE
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: "@azure/core-xml"
version: 1.5.0
version: 1.5.1
type: npm
summary: Core library for interacting with XML payloads
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-xml/
+2 -2
View File
@@ -1,9 +1,9 @@
---
name: "@azure/storage-blob"
version: 12.29.1
version: 12.32.0
type: npm
summary: Microsoft Azure Storage SDK for JavaScript - Blob
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/storage/storage-blob/
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/storage/storage-blob/README.md
license: mit
licenses:
- sources: LICENSE
+2 -2
View File
@@ -1,9 +1,9 @@
---
name: "@azure/storage-common"
version: 12.1.1
version: 12.4.0
type: npm
summary: Azure Storage Common Client Library for JavaScript
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/storage/storage-internal-avro/
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/storage/storage-common/README.md
license: mit
licenses:
- sources: LICENSE
+11
View File
@@ -0,0 +1,11 @@
---
name: "@nodable/entities"
version: 2.2.0
type: npm
summary: Entity parser for XML, HTML, External entites with security and NCR control
homepage:
license: mit
licenses:
- sources: README.md
text: MIT
notices: []
+2 -2
View File
@@ -1,9 +1,9 @@
---
name: "@typespec/ts-http-runtime"
version: 0.3.2
version: 0.3.6
type: npm
summary: Isomorphic client library for making HTTP requests in node.js and browser.
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/ts-http-runtime/
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/ts-http-runtime/README.md
license: mit
licenses:
- sources: LICENSE
+35
View File
@@ -0,0 +1,35 @@
---
name: anynum
version: 1.0.1
type: npm
summary: Normalize all Unicode decimal digits (Devanagari, Arabic, Thai, etc.) to
ASCII numerals. Zero dependencies, performance-first.
homepage:
license: mit
licenses:
- sources: LICENSE
text: |
MIT License
Copyright (c) 2026 Natural Intelligence
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- sources: README.md
text: MIT
notices: []
+34
View File
@@ -0,0 +1,34 @@
---
name: balanced-match
version: 4.0.4
type: npm
summary: Match balanced character pairs, like "{" and "}"
homepage:
license: other
licenses:
- sources: LICENSE.md
text: |
(MIT)
Original code Copyright Julian Gruber <julian@juliangruber.com>
Port to TypeScript Copyright Isaac Z. Schlueter <i@izs.me>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
notices: []
@@ -1,6 +1,6 @@
---
name: brace-expansion
version: 1.1.12
version: 1.1.15
type: npm
summary: Brace expansion as known from sh/bash
homepage: https://github.com/juliangruber/brace-expansion
@@ -1,30 +1,34 @@
---
name: "@fastify/busboy"
version: 2.1.1
name: brace-expansion
version: 5.0.6
type: npm
summary: A streaming parser for HTML form data for node.js
summary: Brace expansion as known from sh/bash
homepage:
license: mit
license: other
licenses:
- sources: LICENSE
text: |-
Copyright Brian White. All rights reserved.
text: |
MIT License
Copyright Julian Gruber <julian@juliangruber.com>
TypeScript port Copyright Isaac Z. Schlueter <i@izs.me>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
notices: []
@@ -1,16 +1,16 @@
---
name: "@azure/abort-controller"
version: 1.1.0
name: fast-xml-builder
version: 1.2.0
type: npm
summary: Microsoft Azure SDK for JavaScript - Aborter
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/abort-controller/README.md
summary: Build XML from JSON without C/C++ based libraries
homepage:
license: mit
licenses:
- sources: LICENSE
text: |
The MIT License (MIT)
MIT License
Copyright (c) 2020 Microsoft
Copyright (c) 2026 Natural Intelligence
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: fast-xml-parser
version: 5.3.3
version: 5.9.3
type: npm
summary: Validate XML, Parse XML, Build XML without C/C++ based libraries
homepage:
+35
View File
@@ -0,0 +1,35 @@
---
name: is-unsafe
version: 1.0.1
type: npm
summary: Zero-dependency, DOM-free, pure predicate for detecting unsafe strings across
HTML, XML, SVG, SQL, SHELL, and REGEX contexts
homepage:
license: mit
licenses:
- sources: LICENSE
text: |
MIT License
Copyright (c) 2026 Natural Intelligence
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
- sources: README.md
text: MIT
notices: []
+66
View File
@@ -0,0 +1,66 @@
---
name: minimatch
version: 10.2.5
type: npm
summary: a glob matcher in javascript
homepage:
license: blueoak-1.0.0
licenses:
- sources: LICENSE.md
text: |
# Blue Oak Model License
Version 1.0.0
## Purpose
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
**_As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim._**
notices: []
@@ -1,9 +1,9 @@
---
name: minimatch
version: 3.1.2
version: 3.1.5
type: npm
summary: a glob matcher in javascript
homepage: https://github.com/isaacs/minimatch#readme
homepage:
license: isc
licenses:
- sources: LICENSE
+32
View File
@@ -0,0 +1,32 @@
---
name: path-expression-matcher
version: 1.6.0
type: npm
summary: Efficient path tracking and pattern matching for XML/JSON parsers
homepage: https://github.com/NaturalIntelligence/path-expression-matcher#readme
license: mit
licenses:
- sources: LICENSE
text: |
MIT License
Copyright (c) 2024
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
notices: []
-26
View File
@@ -1,26 +0,0 @@
---
name: semver
version: 6.3.1
type: npm
summary: The semantic version parser used by npm.
homepage:
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
notices: []
@@ -1,6 +1,6 @@
---
name: semver
version: 7.7.3
version: 7.8.5
type: npm
summary: The semantic version parser used by npm.
homepage:
+2 -2
View File
@@ -1,9 +1,9 @@
---
name: strnum
version: 2.1.2
version: 2.4.1
type: npm
summary: Parse String to Number based on configuration
homepage:
homepage:
license: mit
licenses:
- sources: LICENSE
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: undici
version: 5.29.0
version: 6.27.0
type: npm
summary: An HTTP/1.1 client, written from scratch for Node.js
homepage: https://undici.nodejs.org
+12
View File
@@ -0,0 +1,12 @@
---
name: xml-naming
version: 0.1.0
type: npm
summary: Validates XML name productions — Name, NCName, QName, NMToken, NMTokens —
for XML 1.0 and 1.1
homepage:
license: mit
licenses:
- sources: README.md
text: MIT
notices: []
-11
View File
@@ -1,11 +0,0 @@
// This is a reusable configuration file copied from https://github.com/actions/reusable-workflows/tree/main/reusable-configurations. Please don't make changes to this file as it's the subject of an automatic update.
module.exports = {
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
trailingComma: 'none',
bracketSpacing: false,
arrowParens: 'avoid'
};
+10
View File
@@ -0,0 +1,10 @@
{
"printWidth": 80,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": false,
"arrowParens": "avoid"
}
+4
View File
@@ -11,6 +11,10 @@ This action provides the following functionality for GitHub Actions users:
- Optionally caching dependencies for pip, pipenv and poetry
- Registering problem matchers for error output
## What's new in V7
- Migrated action internals to ESM for compatibility with latest `@actions/*` packages. No changes to action inputs, outputs, or behavior.
## Breaking changes in V6
- Upgraded action from node20 to node24
+112 -62
View File
@@ -1,10 +1,62 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import {fileURLToPath} from 'url';
import * as path from 'path';
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 {State} from '../src/cache-distributions/cache-distributor';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Mock @actions modules before importing anything that depends on them
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
jest.unstable_mockModule('@actions/cache', () => ({
saveCache: jest.fn(),
restoreCache: jest.fn(),
isFeatureAvailable: jest.fn()
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
jest.unstable_mockModule('@actions/io', () => ({
which: jest.fn(),
mkdirP: jest.fn(),
rmRF: jest.fn(),
mv: jest.fn(),
cp: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const cache = await import('@actions/cache');
const exec = await import('@actions/exec');
const io = await import('@actions/io');
const {getCacheDistributor} =
await import('../src/cache-distributions/cache-factory.js');
const {State} = await import('../src/cache-distributions/cache-distributor.js');
describe('restore-cache', () => {
const pipFileLockHash =
@@ -24,43 +76,38 @@ virtualenvs.in-project = true
virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/pypoetry/virtualenvs
`;
// core spy
let infoSpy: jest.SpyInstance;
let warningSpy: jest.SpyInstance;
let debugSpy: jest.SpyInstance;
let saveStateSpy: jest.SpyInstance;
let getStateSpy: jest.SpyInstance;
let setOutputSpy: jest.SpyInstance;
// cache spy
let restoreCacheSpy: jest.SpyInstance;
// exec spy
let getExecOutputSpy: jest.SpyInstance;
// io spy
let whichSpy: jest.SpyInstance;
let infoSpy: jest.Mock;
let warningSpy: jest.Mock;
let debugSpy: jest.Mock;
let saveStateSpy: jest.Mock;
let getStateSpy: jest.Mock;
let setOutputSpy: jest.Mock;
let restoreCacheSpy: jest.Mock;
let getExecOutputSpy: jest.Mock;
let whichSpy: jest.Mock;
beforeEach(() => {
process.env['RUNNER_OS'] = process.env['RUNNER_OS'] ?? 'linux';
infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(input => undefined);
infoSpy = core.info as jest.Mock;
infoSpy.mockImplementation(() => undefined);
warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(input => undefined);
warningSpy = core.warning as jest.Mock;
warningSpy.mockImplementation(() => undefined);
debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(input => undefined);
debugSpy = core.debug as jest.Mock;
debugSpy.mockImplementation(() => undefined);
saveStateSpy = jest.spyOn(core, 'saveState');
saveStateSpy.mockImplementation(input => undefined);
saveStateSpy = core.saveState as jest.Mock;
saveStateSpy.mockImplementation(() => undefined);
getStateSpy = jest.spyOn(core, 'getState');
getStateSpy.mockImplementation(input => undefined);
getStateSpy = core.getState as jest.Mock;
getStateSpy.mockImplementation(() => undefined);
getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
getExecOutputSpy.mockImplementation((input: string) => {
getExecOutputSpy = exec.getExecOutput as jest.Mock;
(
getExecOutputSpy as jest.Mock<typeof exec.getExecOutput>
).mockImplementation(async (input: string) => {
if (input.includes('pip')) {
return {stdout: 'pip', stderr: '', exitCode: 0};
}
@@ -74,17 +121,19 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
return {stdout: '', stderr: 'Error occured', exitCode: 2};
});
setOutputSpy = jest.spyOn(core, 'setOutput');
setOutputSpy.mockImplementation(input => undefined);
setOutputSpy = core.setOutput as jest.Mock;
setOutputSpy.mockImplementation(() => undefined);
restoreCacheSpy = jest.spyOn(cache, 'restoreCache');
restoreCacheSpy.mockImplementation(
(cachePaths: string[], primaryKey: string, restoreKey?: string) => {
return primaryKey;
restoreCacheSpy = cache.restoreCache as jest.Mock;
(
restoreCacheSpy as jest.Mock<typeof cache.restoreCache>
).mockImplementation(
(cachePaths: string[], primaryKey: string, restoreKey?: string[]) => {
return Promise.resolve(primaryKey);
}
);
whichSpy = jest.spyOn(io, 'which');
whichSpy = io.which as jest.Mock;
whichSpy.mockImplementation(() => '/path/to/python');
});
@@ -163,9 +212,13 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
fileHash,
cachePaths
) => {
restoreCacheSpy.mockImplementation(
(cachePaths: string[], primaryKey: string, restoreKey?: string) => {
return primaryKey.includes(fileHash) ? primaryKey : '';
(
restoreCacheSpy as jest.Mock<typeof cache.restoreCache>
).mockImplementation(
(cachePaths: string[], primaryKey: string, restoreKey?: string[]) => {
return Promise.resolve(
primaryKey.includes(fileHash) ? primaryKey : ''
);
}
);
@@ -184,25 +237,18 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
);
}
const restoredKeys = restoreCacheSpy.mock.results.map(
result => result.value
const restoredKeys = await Promise.all(
restoreCacheSpy.mock.results.map(result => result.value)
);
restoredKeys.forEach(restoredKey => {
if (restoredKey) {
if (process.platform === 'linux' && packageManager === 'pip') {
expect(infoSpy).toHaveBeenCalledWith(
`Cache restored from key: setup-python-${process.env['RUNNER_OS']}-${process.arch}-20.04-Ubuntu-python-${pythonVersion}-${packageManager}-${fileHash}`
);
} else if (packageManager === 'poetry') {
expect(infoSpy).toHaveBeenCalledWith(
`Cache restored from key: setup-python-${process.env['RUNNER_OS']}-${process.arch}-python-${pythonVersion}-${packageManager}-v2-${fileHash}`
);
} else {
expect(infoSpy).toHaveBeenCalledWith(
`Cache restored from key: setup-python-${process.env['RUNNER_OS']}-${process.arch}-python-${pythonVersion}-${packageManager}-${fileHash}`
);
}
const osSegment =
process.platform === 'linux' ? '-20.04-Ubuntu' : '';
const versionSuffix = packageManager === 'poetry' ? '-v2' : '';
expect(infoSpy).toHaveBeenCalledWith(
`Cache restored from key: setup-python-${process.env['RUNNER_OS']}-${process.arch}${osSegment}-python-${pythonVersion}-${packageManager}${versionSuffix}-${fileHash}`
);
} else {
expect(infoSpy).toHaveBeenCalledWith(
`${packageManager} cache is not found`
@@ -291,9 +337,13 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
])(
'restored dependencies for %s by primaryKey',
async (packageManager, pythonVersion, dependencyFile, fileHash) => {
restoreCacheSpy.mockImplementation(
(cachePaths: string[], primaryKey: string, restoreKey?: string) => {
return primaryKey !== fileHash && restoreKey ? pipFileLockHash : '';
(
restoreCacheSpy as jest.Mock<typeof cache.restoreCache>
).mockImplementation(
(cachePaths: string[], primaryKey: string, restoreKey?: string[]) => {
return Promise.resolve(
primaryKey !== fileHash && restoreKey ? pipFileLockHash : ''
);
}
);
const cacheDistributor = getCacheDistributor(
+143 -79
View File
@@ -1,8 +1,61 @@
import * as core from '@actions/core';
import * as cache from '@actions/cache';
import * as exec from '@actions/exec';
import {run} from '../src/cache-save';
import {State} from '../src/cache-distributions/cache-distributor';
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import {fileURLToPath} from 'url';
import path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Mock @actions modules before importing anything that depends on them
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
class MockValidationError extends Error {
constructor(message: string) {
super(message);
this.name = 'ValidationError';
}
}
jest.unstable_mockModule('@actions/cache', () => ({
saveCache: jest.fn(),
restoreCache: jest.fn(),
isFeatureAvailable: jest.fn(),
ValidationError: MockValidationError,
ReserveCacheError: MockValidationError
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const cache = await import('@actions/cache');
const exec = await import('@actions/exec');
const {run} = await import('../src/cache-save.js');
const {State} = await import('../src/cache-distributions/cache-distributor.js');
describe('run', () => {
const pipFileLockHash =
@@ -14,53 +67,54 @@ describe('run', () => {
const poetryLockHash =
'571bf984f8d210e6a97f854e479fdd4a2b5af67b5fdac109ec337a0ea16e7836';
// core spy
let infoSpy: jest.SpyInstance;
let warningSpy: jest.SpyInstance;
let debugSpy: jest.SpyInstance;
let saveStateSpy: jest.SpyInstance;
let getStateSpy: jest.SpyInstance;
let getInputSpy: jest.SpyInstance;
let setFailedSpy: jest.SpyInstance;
// cache spy
let saveCacheSpy: jest.SpyInstance;
// exec spy
let getExecOutputSpy: jest.SpyInstance;
let infoSpy: jest.Mock;
let warningSpy: jest.Mock;
let debugSpy: jest.Mock;
let saveStateSpy: jest.Mock;
let getStateSpy: jest.Mock;
let getInputSpy: jest.Mock;
let setFailedSpy: jest.Mock;
let saveCacheSpy: jest.Mock;
let getExecOutputSpy: jest.Mock;
let inputs = {} as any;
beforeEach(() => {
process.env['RUNNER_OS'] = process.env['RUNNER_OS'] ?? 'linux';
infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(input => undefined);
infoSpy = core.info as jest.Mock;
infoSpy.mockImplementation(() => undefined);
warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(input => undefined);
warningSpy = core.warning as jest.Mock;
warningSpy.mockImplementation(() => undefined);
debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(input => undefined);
debugSpy = core.debug as jest.Mock;
debugSpy.mockImplementation(() => undefined);
saveStateSpy = jest.spyOn(core, 'saveState');
saveStateSpy.mockImplementation(input => undefined);
saveStateSpy = core.saveState as jest.Mock;
saveStateSpy.mockImplementation(() => undefined);
getStateSpy = jest.spyOn(core, 'getState');
getStateSpy.mockImplementation(input => {
if (input === State.CACHE_PATHS) {
return JSON.stringify([__dirname]);
getStateSpy = core.getState as jest.Mock;
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation(
(input: string) => {
if (input === State.CACHE_PATHS) {
return JSON.stringify([__dirname]);
}
return requirementsHash;
}
return requirementsHash;
});
);
setFailedSpy = jest.spyOn(core, 'setFailed');
setFailedSpy = core.setFailed as jest.Mock;
getInputSpy = jest.spyOn(core, 'getInput');
getInputSpy.mockImplementation(input => inputs[input]);
getInputSpy = core.getInput as jest.Mock;
(getInputSpy as jest.Mock<typeof core.getInput>).mockImplementation(
(input: string) => inputs[input]
);
getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
getExecOutputSpy.mockImplementation((input: string) => {
getExecOutputSpy = exec.getExecOutput as jest.Mock;
(
getExecOutputSpy as jest.Mock<typeof exec.getExecOutput>
).mockImplementation(async (input: string) => {
if (input.includes('pip')) {
return {stdout: 'pip', stderr: '', exitCode: 0};
}
@@ -68,7 +122,7 @@ describe('run', () => {
return {stdout: '', stderr: 'Error occured', exitCode: 2};
});
saveCacheSpy = jest.spyOn(cache, 'saveCache');
saveCacheSpy = cache.saveCache as jest.Mock;
saveCacheSpy.mockImplementation(() => undefined);
});
@@ -124,15 +178,17 @@ describe('run', () => {
it('saves cache from pip', async () => {
inputs['cache'] = 'pip';
inputs['python-version'] = '3.10.0';
getStateSpy.mockImplementation((name: string) => {
if (name === State.CACHE_MATCHED_KEY) {
return requirementsHash;
} else if (name === State.CACHE_PATHS) {
return JSON.stringify([__dirname]);
} else {
return pipFileLockHash;
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation(
(name: string) => {
if (name === State.CACHE_MATCHED_KEY) {
return requirementsHash;
} else if (name === State.CACHE_PATHS) {
return JSON.stringify([__dirname]);
} else {
return pipFileLockHash;
}
}
});
);
await run();
@@ -151,15 +207,17 @@ describe('run', () => {
it('saves cache from pipenv', async () => {
inputs['cache'] = 'pipenv';
inputs['python-version'] = '3.10.0';
getStateSpy.mockImplementation((name: string) => {
if (name === State.CACHE_MATCHED_KEY) {
return pipFileLockHash;
} else if (name === State.CACHE_PATHS) {
return JSON.stringify([__dirname]);
} else {
return requirementsHash;
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation(
(name: string) => {
if (name === State.CACHE_MATCHED_KEY) {
return pipFileLockHash;
} else if (name === State.CACHE_PATHS) {
return JSON.stringify([__dirname]);
} else {
return requirementsHash;
}
}
});
);
await run();
@@ -178,15 +236,17 @@ describe('run', () => {
it('saves cache from poetry', async () => {
inputs['cache'] = 'poetry';
inputs['python-version'] = '3.10.0';
getStateSpy.mockImplementation((name: string) => {
if (name === State.CACHE_MATCHED_KEY) {
return poetryLockHash;
} else if (name === State.CACHE_PATHS) {
return JSON.stringify([__dirname]);
} else {
return requirementsHash;
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation(
(name: string) => {
if (name === State.CACHE_MATCHED_KEY) {
return poetryLockHash;
} else if (name === State.CACHE_PATHS) {
return JSON.stringify([__dirname]);
} else {
return requirementsHash;
}
}
});
);
await run();
@@ -205,15 +265,17 @@ describe('run', () => {
it('saves with -1 cacheId , should not fail workflow', async () => {
inputs['cache'] = 'poetry';
inputs['python-version'] = '3.10.0';
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;
(getStateSpy as jest.Mock<typeof core.getState>).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;
@@ -234,15 +296,17 @@ describe('run', () => {
it('saves with error from toolkit, should not fail the workflow', async () => {
inputs['cache'] = 'npm';
inputs['python-version'] = '3.10.0';
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;
(getStateSpy as jest.Mock<typeof core.getState>).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');
+1 -1
View File
@@ -5,7 +5,7 @@ name = "pypi"
[packages]
flake8 = "==6.0.0"
numpy = "==2.2.4"
numpy = "==2.4.6"
[dev-packages]
+75 -58
View File
@@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "9d0bc7ff1f9078396b19f05bc5cd57894f3783c68f96fa9d623b1ea5a1b168f5"
"sha256": "0060d5c4915f1f35aae1f3e9fa6de3f65d1a851e6e33ac57a04665d5d1c796fa"
},
"pipfile-spec": 6,
"requires": {
@@ -35,65 +35,82 @@
},
"numpy": {
"hashes": [
"sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286",
"sha256:0d54974f9cf14acf49c60f0f7f4084b6579d24d439453d5fc5805d46a165b542",
"sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f",
"sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d",
"sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0",
"sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7",
"sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3",
"sha256:218f061d2faa73621fa23d6359442b0fc658d5b9a70801373625d958259eaca3",
"sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146",
"sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1",
"sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6",
"sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc",
"sha256:4ba5054787e89c59c593a4169830ab362ac2bee8a969249dc56e5d7d20ff8df9",
"sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592",
"sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00",
"sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298",
"sha256:7051ee569db5fbac144335e0f3b9c2337e0c8d5c9fee015f259a5bd70772b7e8",
"sha256:7716e4a9b7af82c06a2543c53ca476fa0b57e4d760481273e09da04b74ee6ee2",
"sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392",
"sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb",
"sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8",
"sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd",
"sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9",
"sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0",
"sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687",
"sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc",
"sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f",
"sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4",
"sha256:a0258ad1f44f138b791327961caedffbf9612bfa504ab9597157806faa95194a",
"sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39",
"sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4",
"sha256:a84eda42bd12edc36eb5b53bbcc9b406820d3353f1994b6cfe453a33ff101775",
"sha256:ab2939cd5bec30a7430cbdb2287b63151b77cf9624de0532d629c9a1c59b1d5c",
"sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd",
"sha256:adf8c1d66f432ce577d0197dceaac2ac00c0759f573f28516246351c58a85020",
"sha256:b4adfbbc64014976d2f91084915ca4e626fbf2057fb81af209c1a6d776d23e3d",
"sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24",
"sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7",
"sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f",
"sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba",
"sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880",
"sha256:d0f35b19894a9e08639fd60a1ec1978cb7f5f7f1eace62f38dd36be8aecdef4d",
"sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6",
"sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854",
"sha256:df2f57871a96bbc1b69733cd4c51dc33bea66146b8c63cacbfed73eec0883017",
"sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8",
"sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae",
"sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4",
"sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09",
"sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff",
"sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960",
"sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee",
"sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5",
"sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c",
"sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91"
"sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1",
"sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4",
"sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f",
"sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079",
"sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096",
"sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47",
"sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66",
"sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d",
"sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1",
"sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e",
"sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147",
"sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd",
"sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75",
"sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063",
"sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73",
"sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab",
"sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4",
"sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41",
"sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402",
"sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698",
"sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7",
"sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8",
"sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b",
"sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8",
"sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0",
"sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662",
"sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91",
"sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0",
"sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f",
"sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3",
"sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f",
"sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67",
"sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6",
"sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997",
"sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b",
"sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e",
"sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538",
"sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627",
"sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93",
"sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02",
"sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853",
"sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c",
"sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43",
"sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd",
"sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8",
"sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089",
"sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778",
"sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1",
"sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb",
"sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261",
"sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb",
"sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a",
"sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8",
"sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359",
"sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5",
"sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7",
"sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751",
"sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8",
"sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605",
"sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e",
"sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45",
"sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2",
"sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895",
"sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe",
"sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb",
"sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a",
"sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577",
"sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d",
"sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a",
"sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda",
"sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6",
"sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20"
],
"index": "pypi",
"markers": "python_version >= '3.10'",
"version": "==2.2.4"
"markers": "python_version >= '3.11'",
"version": "==2.4.6"
},
"pycodestyle": {
"hashes": [
+1
View File
@@ -9,6 +9,7 @@ python = ">=3.9,<3.14"
flake8 = "^4.0.1"
pyinstaller = "6.10.0"
setuptools = ">=78.1.1"
packaging = ">=22.0,<26"
[tool.poetry.dev-dependencies]
+5 -5
View File
@@ -1,12 +1,12 @@
certifi==2020.6.20
certifi==2024.7.4
chardet==3.0.4
docutils==0.16
idna==3.7
idna==3.15
Kivy==2.0.0rc3
Kivy-Garden==0.1.4
packaging==20.7
pdf2image==1.12.1
Pygments==2.6.1
requests==2.32.4
urllib3==2.6.3
Pygments==2.20.0
requests==2.33.0
urllib3==2.7.0
xlrd==1.2.0
+5 -5
View File
@@ -1,6 +1,6 @@
altgraph==0.17.2
certifi==2020.6.20
certifi==2024.7.4
chardet==3.0.4
@@ -8,7 +8,7 @@ docutils==0.16
future==0.18.2; python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'
idna==3.7
idna==3.15
itsdangerous==1.1.0
@@ -32,7 +32,7 @@ pefile==2021.9.3; python_full_version >= '3.6.0'
pillow>=10.2.0
pygments==2.6.1
pygments==2.20.0
pyinstaller==6.10.0
@@ -40,8 +40,8 @@ pyparsing==2.4.7; python_version >= '2.6' and python_version not in '3.0, 3.1, 3
pywin32-ctypes==0.2.0
requests==2.32.4
requests==2.33.0
urllib3==2.6.3
urllib3==2.7.0
xlrd==1.2.0
+141 -84
View File
@@ -1,18 +1,70 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import {fileURLToPath} from 'url';
import fs from 'fs';
import {HttpClient} from '@actions/http-client';
import * as ifm from '@actions/http-client/lib/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';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
import * as semver from 'semver';
import * as finder from '../src/find-graalpy';
import {IGraalPyManifestRelease} from '../src/utils';
// Mock @actions modules
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
import manifestData from './data/graalpy.json';
jest.unstable_mockModule('@actions/tool-cache', () => ({
find: jest.fn(),
findAllVersions: jest.fn(),
downloadTool: jest.fn(),
extractZip: jest.fn(),
extractTar: jest.fn(),
extract7z: jest.fn(),
extractXar: jest.fn(),
cacheDir: jest.fn(),
cacheFile: jest.fn(),
getManifestFromRepo: jest.fn(),
findFromManifest: jest.fn(),
evaluateVersions: jest.fn()
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const tc = await import('@actions/tool-cache');
const exec = await import('@actions/exec');
const finder = await import('../src/find-graalpy.js');
const utils = await import('../src/utils.js');
// Non-mocked imports
import {HttpClient} from '@actions/http-client';
import type * as ifm from '@actions/http-client/lib/interfaces';
import type {IGraalPyManifestRelease} from '../src/utils.js';
import manifestData from './data/graalpy.json' with {type: 'json'};
const architecture = 'x64';
@@ -41,39 +93,41 @@ describe('parseGraalPyVersion', () => {
describe('findGraalPyToolCache', () => {
const actualGraalPyVersion = '23.0.0';
const graalpyPath = path.join('GraalPy', actualGraalPyVersion, architecture);
let tcFind: 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;
let tcFind: jest.Mock;
let infoSpy: jest.Mock;
let warningSpy: jest.Mock;
let debugSpy: jest.Mock;
let addPathSpy: jest.Mock;
let exportVariableSpy: jest.Mock;
let setOutputSpy: jest.Mock;
beforeEach(() => {
tcFind = jest.spyOn(tc, 'find');
tcFind.mockImplementation((toolname: string, pythonVersion: string) => {
const semverVersion = new semver.Range(pythonVersion);
return semver.satisfies(actualGraalPyVersion, semverVersion)
? graalpyPath
: '';
});
tcFind = tc.find as jest.Mock;
(tcFind as jest.Mock<typeof tc.find>).mockImplementation(
(toolname: string, pythonVersion: string) => {
const semverVersion = new semver.Range(pythonVersion);
return semver.satisfies(actualGraalPyVersion, semverVersion)
? graalpyPath
: '';
}
);
infoSpy = jest.spyOn(core, 'info');
infoSpy = core.info as jest.Mock;
infoSpy.mockImplementation(() => null);
warningSpy = jest.spyOn(core, 'warning');
warningSpy = core.warning as jest.Mock;
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy = core.debug as jest.Mock;
debugSpy.mockImplementation(() => null);
addPathSpy = jest.spyOn(core, 'addPath');
addPathSpy = core.addPath as jest.Mock;
addPathSpy.mockImplementation(() => null);
exportVariableSpy = jest.spyOn(core, 'exportVariable');
exportVariableSpy = core.exportVariable as jest.Mock;
exportVariableSpy.mockImplementation(() => null);
setOutputSpy = jest.spyOn(core, 'setOutput');
setOutputSpy = core.setOutput as jest.Mock;
setOutputSpy.mockImplementation(() => null);
});
@@ -106,73 +160,74 @@ describe('findGraalPyToolCache', () => {
});
describe('findGraalPyVersion', () => {
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;
let spyHttpClient: jest.SpyInstance;
let spyExistsSync: jest.SpyInstance;
let spyExec: jest.SpyInstance;
let spySymlinkSync: jest.SpyInstance;
let spyDownloadTool: jest.SpyInstance;
let spyFsReadDir: jest.SpyInstance;
let spyCacheDir: jest.SpyInstance;
let spyChmodSync: jest.SpyInstance;
let spyCoreAddPath: jest.SpyInstance;
let spyCoreExportVariable: jest.SpyInstance;
let getBooleanInputSpy: jest.Mock;
let warningSpy: jest.Mock;
let debugSpy: jest.Mock;
let infoSpy: jest.Mock;
let addPathSpy: jest.Mock;
let exportVariableSpy: jest.Mock;
let setOutputSpy: jest.Mock;
let tcFind: jest.Mock;
let spyExtractZip: jest.Mock;
let spyExtractTar: jest.Mock;
let spyHttpClient: jest.SpiedFunction<typeof HttpClient.prototype.getJson>;
let spyExistsSync: jest.SpiedFunction<typeof fs.existsSync>;
let spyExec: jest.Mock;
let spySymlinkSync: jest.SpiedFunction<typeof fs.symlinkSync>;
let spyDownloadTool: jest.Mock;
let spyFsReadDir: jest.SpiedFunction<typeof fs.readdirSync>;
let spyCacheDir: jest.Mock;
let spyChmodSync: jest.SpiedFunction<typeof fs.chmodSync>;
let spyCoreAddPath: jest.Mock;
let spyCoreExportVariable: jest.Mock;
const env = process.env;
beforeEach(() => {
getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy = core.getBooleanInput as jest.Mock;
getBooleanInputSpy.mockImplementation(() => false);
infoSpy = jest.spyOn(core, 'info');
infoSpy = core.info as jest.Mock;
infoSpy.mockImplementation(() => {});
warningSpy = jest.spyOn(core, 'warning');
warningSpy = core.warning as jest.Mock;
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy = core.debug as jest.Mock;
debugSpy.mockImplementation(() => null);
addPathSpy = jest.spyOn(core, 'addPath');
addPathSpy = core.addPath as jest.Mock;
addPathSpy.mockImplementation(() => null);
exportVariableSpy = jest.spyOn(core, 'exportVariable');
exportVariableSpy = core.exportVariable as jest.Mock;
exportVariableSpy.mockImplementation(() => null);
setOutputSpy = jest.spyOn(core, 'setOutput');
setOutputSpy = core.setOutput as jest.Mock;
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);
let graalpyPath = '';
if (semver.satisfies('23.0.0', semverRange)) {
graalpyPath = path.join(toolDir, 'GraalPy', '23.0.0', architecture);
tcFind = tc.find as jest.Mock;
(tcFind as jest.Mock<typeof tc.find>).mockImplementation(
(tool: string, version: string) => {
const semverRange = new semver.Range(version);
let graalpyPath = '';
if (semver.satisfies('23.0.0', semverRange)) {
graalpyPath = path.join(toolDir, 'GraalPy', '23.0.0', architecture);
}
return graalpyPath;
}
return graalpyPath;
});
);
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
spyDownloadTool = tc.downloadTool as jest.Mock;
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'GraalPy'));
spyExtractZip = jest.spyOn(tc, 'extractZip');
spyExtractZip = tc.extractZip as jest.Mock;
spyExtractZip.mockImplementation(() => tempDir);
spyExtractTar = jest.spyOn(tc, 'extractTar');
spyExtractTar = tc.extractTar as jest.Mock;
spyExtractTar.mockImplementation(() => tempDir);
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation((directory: string) => ['GraalPyTest']);
spyFsReadDir.mockImplementation(() => ['GraalPyTest'] as any);
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
spyHttpClient.mockImplementation(
@@ -186,7 +241,7 @@ describe('findGraalPyVersion', () => {
}
);
spyExec = jest.spyOn(exec, 'exec');
spyExec = exec.exec as jest.Mock;
spyExec.mockImplementation(() => undefined);
spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
@@ -195,9 +250,9 @@ describe('findGraalPyVersion', () => {
spyExistsSync = jest.spyOn(fs, 'existsSync');
spyExistsSync.mockReturnValue(true);
spyCoreAddPath = jest.spyOn(core, 'addPath');
spyCoreAddPath = core.addPath as jest.Mock;
spyCoreExportVariable = jest.spyOn(core, 'exportVariable');
spyCoreExportVariable = core.exportVariable as jest.Mock;
});
afterEach(() => {
@@ -235,7 +290,7 @@ describe('findGraalPyVersion', () => {
});
it('found and install successfully', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir = tc.cacheDir as jest.Mock;
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '23.0.0', architecture)
);
@@ -262,7 +317,7 @@ describe('findGraalPyVersion', () => {
});
it('found and install successfully without environment update', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir = tc.cacheDir as jest.Mock;
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '23.0.0', architecture)
);
@@ -310,7 +365,7 @@ describe('findGraalPyVersion', () => {
});
it('check-latest enabled version found and install successfully', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir = tc.cacheDir as jest.Mock;
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '23.0.0', architecture)
);
@@ -329,14 +384,16 @@ describe('findGraalPyVersion', () => {
});
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 graalpyPath = '';
if (semver.satisfies('22.3.4', semverRange)) {
graalpyPath = path.join(toolDir, 'GraalPy', '22.3.4', architecture);
(tcFind as jest.Mock<typeof tc.find>).mockImplementationOnce(
(tool: string, version: string) => {
const semverRange = new semver.Range(version);
let graalpyPath = '';
if (semver.satisfies('22.3.4', semverRange)) {
graalpyPath = path.join(toolDir, 'GraalPy', '22.3.4', architecture);
}
return graalpyPath;
}
return graalpyPath;
});
);
await expect(
finder.findGraalPyVersion(
'graalpy-22.3.4',
@@ -353,7 +410,7 @@ describe('findGraalPyVersion', () => {
});
it('found and install successfully, pre-release fallback', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir = tc.cacheDir as jest.Mock;
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '24.1', architecture)
);
+160 -98
View File
@@ -1,23 +1,83 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import {fileURLToPath} from 'url';
import fs from 'fs';
import * as utils from '../src/utils';
import {HttpClient} from '@actions/http-client';
import * as ifm from '@actions/http-client/lib/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';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
import * as semver from 'semver';
import * as finder from '../src/find-pypy';
import {
IPyPyManifestRelease,
IS_WINDOWS,
getPyPyVersionFromPath
} from '../src/utils';
// Mock @actions modules
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
import manifestData from './data/pypy.json';
jest.unstable_mockModule('@actions/tool-cache', () => ({
find: jest.fn(),
findAllVersions: jest.fn(),
downloadTool: jest.fn(),
extractZip: jest.fn(),
extractTar: jest.fn(),
extract7z: jest.fn(),
extractXar: jest.fn(),
cacheDir: jest.fn(),
cacheFile: jest.fn(),
getManifestFromRepo: jest.fn(),
findFromManifest: jest.fn(),
evaluateVersions: jest.fn()
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Import real utils BEFORE mock registration to get real function references
const realUtils = await import('../src/utils.js');
// Mock local utils module for readExactPyPyVersionFile/writeExactPyPyVersionFile
jest.unstable_mockModule('../src/utils.js', () => ({
...realUtils,
readExactPyPyVersionFile: jest.fn(),
writeExactPyPyVersionFile: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const tc = await import('@actions/tool-cache');
const exec = await import('@actions/exec');
const utils = await import('../src/utils.js');
const finder = await import('../src/find-pypy.js');
// Non-mocked imports
import {HttpClient} from '@actions/http-client';
import type * as ifm from '@actions/http-client/lib/interfaces';
import type {IPyPyManifestRelease} from '../src/utils.js';
import manifestData from './data/pypy.json' with {type: 'json'};
const IS_WINDOWS = utils.IS_WINDOWS;
const getPyPyVersionFromPath = utils.getPyPyVersionFromPath;
let architecture: string;
@@ -79,43 +139,45 @@ describe('findPyPyToolCache', () => {
const actualPythonVersion = '3.6.17';
const actualPyPyVersion = '7.5.4';
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;
let tcFind: jest.Mock;
let spyReadExactPyPyVersion: jest.Mock;
let infoSpy: jest.Mock;
let warningSpy: jest.Mock;
let debugSpy: jest.Mock;
let addPathSpy: jest.Mock;
let exportVariableSpy: jest.Mock;
let setOutputSpy: jest.Mock;
beforeEach(() => {
tcFind = jest.spyOn(tc, 'find');
tcFind.mockImplementation((toolname: string, pythonVersion: string) => {
const semverVersion = new semver.Range(pythonVersion);
return semver.satisfies(actualPythonVersion, semverVersion)
? pypyPath
: '';
});
tcFind = tc.find as jest.Mock;
(tcFind as jest.Mock<typeof tc.find>).mockImplementation(
(toolname: string, pythonVersion: string) => {
const semverVersion = new semver.Range(pythonVersion);
return semver.satisfies(actualPythonVersion, semverVersion)
? pypyPath
: '';
}
);
spyReadExactPyPyVersion = jest.spyOn(utils, 'readExactPyPyVersionFile');
spyReadExactPyPyVersion = utils.readExactPyPyVersionFile as jest.Mock;
spyReadExactPyPyVersion.mockImplementation(() => actualPyPyVersion);
infoSpy = jest.spyOn(core, 'info');
infoSpy = core.info as jest.Mock;
infoSpy.mockImplementation(() => null);
warningSpy = jest.spyOn(core, 'warning');
warningSpy = core.warning as jest.Mock;
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy = core.debug as jest.Mock;
debugSpy.mockImplementation(() => null);
addPathSpy = jest.spyOn(core, 'addPath');
addPathSpy = core.addPath as jest.Mock;
addPathSpy.mockImplementation(() => null);
exportVariableSpy = jest.spyOn(core, 'exportVariable');
exportVariableSpy = core.exportVariable as jest.Mock;
exportVariableSpy.mockImplementation(() => null);
setOutputSpy = jest.spyOn(core, 'setOutput');
setOutputSpy = core.setOutput as jest.Mock;
setOutputSpy.mockImplementation(() => null);
});
@@ -159,84 +221,82 @@ 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;
let spyHttpClient: jest.SpyInstance;
let spyExistsSync: jest.SpyInstance;
let spyExec: jest.SpyInstance;
let spySymlinkSync: jest.SpyInstance;
let spyDownloadTool: jest.SpyInstance;
let spyReadExactPyPyVersion: jest.SpyInstance;
let spyFsReadDir: jest.SpyInstance;
let spyWriteExactPyPyVersionFile: jest.SpyInstance;
let spyCacheDir: jest.SpyInstance;
let spyChmodSync: jest.SpyInstance;
let spyCoreAddPath: jest.SpyInstance;
let spyCoreExportVariable: jest.SpyInstance;
let getBooleanInputSpy: jest.Mock;
let warningSpy: jest.Mock;
let debugSpy: jest.Mock;
let infoSpy: jest.Mock;
let addPathSpy: jest.Mock;
let exportVariableSpy: jest.Mock;
let setOutputSpy: jest.Mock;
let tcFind: jest.Mock;
let spyExtractZip: jest.Mock;
let spyExtractTar: jest.Mock;
let spyHttpClient: jest.SpiedFunction<typeof HttpClient.prototype.getJson>;
let spyExistsSync: jest.SpiedFunction<typeof fs.existsSync>;
let spyExec: jest.Mock;
let spySymlinkSync: jest.SpiedFunction<typeof fs.symlinkSync>;
let spyDownloadTool: jest.Mock;
let spyReadExactPyPyVersion: jest.Mock;
let spyFsReadDir: jest.SpiedFunction<typeof fs.readdirSync>;
let spyWriteExactPyPyVersionFile: jest.Mock;
let spyCacheDir: jest.Mock;
let spyChmodSync: jest.SpiedFunction<typeof fs.chmodSync>;
let spyCoreAddPath: jest.Mock;
let spyCoreExportVariable: jest.Mock;
const env = process.env;
beforeEach(() => {
getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy = core.getBooleanInput as jest.Mock;
getBooleanInputSpy.mockImplementation(() => false);
infoSpy = jest.spyOn(core, 'info');
infoSpy = core.info as jest.Mock;
infoSpy.mockImplementation(() => {});
warningSpy = jest.spyOn(core, 'warning');
warningSpy = core.warning as jest.Mock;
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy = core.debug as jest.Mock;
debugSpy.mockImplementation(() => null);
addPathSpy = jest.spyOn(core, 'addPath');
addPathSpy = core.addPath as jest.Mock;
addPathSpy.mockImplementation(() => null);
exportVariableSpy = jest.spyOn(core, 'exportVariable');
exportVariableSpy = core.exportVariable as jest.Mock;
exportVariableSpy.mockImplementation(() => null);
setOutputSpy = jest.spyOn(core, 'setOutput');
setOutputSpy = core.setOutput as jest.Mock;
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);
let pypyPath = '';
if (semver.satisfies('3.6.12', semverRange)) {
pypyPath = path.join(toolDir, 'PyPy', '3.6.12', architecture);
tcFind = tc.find as jest.Mock;
(tcFind as jest.Mock<typeof tc.find>).mockImplementation(
(tool: string, version: string) => {
const semverRange = new semver.Range(version);
let pypyPath = '';
if (semver.satisfies('3.6.12', semverRange)) {
pypyPath = path.join(toolDir, 'PyPy', '3.6.12', architecture);
}
return pypyPath;
}
return pypyPath;
});
spyWriteExactPyPyVersionFile = jest.spyOn(
utils,
'writeExactPyPyVersionFile'
);
spyWriteExactPyPyVersionFile = utils.writeExactPyPyVersionFile as jest.Mock;
spyWriteExactPyPyVersionFile.mockImplementation(() => null);
spyReadExactPyPyVersion = jest.spyOn(utils, 'readExactPyPyVersionFile');
spyReadExactPyPyVersion = utils.readExactPyPyVersionFile as jest.Mock;
spyReadExactPyPyVersion.mockImplementation(() => '7.3.3');
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
spyDownloadTool = tc.downloadTool as jest.Mock;
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'PyPy'));
spyExtractZip = jest.spyOn(tc, 'extractZip');
spyExtractZip = tc.extractZip as jest.Mock;
spyExtractZip.mockImplementation(() => tempDir);
spyExtractTar = jest.spyOn(tc, 'extractTar');
spyExtractTar = tc.extractTar as jest.Mock;
spyExtractTar.mockImplementation(() => tempDir);
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation((directory: string) => ['PyPyTest']);
spyFsReadDir.mockImplementation(() => ['PyPyTest'] as any);
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
spyHttpClient.mockImplementation(
@@ -250,7 +310,7 @@ describe('findPyPyVersion', () => {
}
);
spyExec = jest.spyOn(exec, 'exec');
spyExec = exec.exec as jest.Mock;
spyExec.mockImplementation(() => undefined);
spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
@@ -259,9 +319,9 @@ describe('findPyPyVersion', () => {
spyExistsSync = jest.spyOn(fs, 'existsSync');
spyExistsSync.mockReturnValue(true);
spyCoreAddPath = jest.spyOn(core, 'addPath');
spyCoreAddPath = core.addPath as jest.Mock;
spyCoreExportVariable = jest.spyOn(core, 'exportVariable');
spyCoreExportVariable = core.exportVariable as jest.Mock;
});
afterEach(() => {
@@ -308,7 +368,7 @@ describe('findPyPyVersion', () => {
});
it('found and install successfully', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir = tc.cacheDir as jest.Mock;
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.7.7', architecture)
);
@@ -338,7 +398,7 @@ describe('findPyPyVersion', () => {
});
it('found and install successfully without environment update', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir = tc.cacheDir as jest.Mock;
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.7.7', architecture)
);
@@ -394,7 +454,7 @@ describe('findPyPyVersion', () => {
});
it('check-latest enabled version found and install successfully', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir = tc.cacheDir as jest.Mock;
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.7.7', architecture)
);
@@ -418,14 +478,16 @@ describe('findPyPyVersion', () => {
});
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);
(tcFind as jest.Mock<typeof tc.find>).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;
}
return pypyPath;
});
);
await expect(
finder.findPyPyVersion(
'pypy-3.8-v7.3.x',
@@ -445,7 +507,7 @@ describe('findPyPyVersion', () => {
});
it('found and install successfully, pre-release fallback', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir = tc.cacheDir as jest.Mock;
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.8.12', architecture)
);
+2 -1
View File
@@ -1,4 +1,5 @@
import {desugarVersion, pythonVersionToSemantic} from '../src/find-python';
import {describe, it, expect} from '@jest/globals';
import {desugarVersion, pythonVersionToSemantic} from '../src/find-python.js';
describe('desugarVersion', () => {
it.each([
+97 -59
View File
@@ -1,8 +1,12 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import {fileURLToPath} from 'url';
import * as io from '@actions/io';
import os from 'os';
import fs from 'fs';
import path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const toolDir = path.join(
__dirname,
'runner',
@@ -19,26 +23,78 @@ const tempDir = path.join(
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';
// Mock @actions modules
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
import manifestData from './data/versions-manifest.json';
// Pre-import real @actions/tool-cache before any mocks
const realTc = await import('@actions/tool-cache');
jest.unstable_mockModule('@actions/tool-cache', () => ({
...realTc,
find: jest.fn(realTc.find),
getManifestFromRepo: jest.fn()
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Pre-import real install-python AFTER all its dependency mocks are registered
// so it captures the mocked @actions/tool-cache, @actions/core, @actions/exec
const realInstaller = await import('../src/install-python.js');
// Mock local install-python module - keep real getManifest/findReleaseFromManifest
jest.unstable_mockModule('../src/install-python.js', () => ({
...realInstaller,
installCpythonFromRelease: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const tc = await import('@actions/tool-cache');
const finder = await import('../src/find-python.js');
const installer = await import('../src/install-python.js');
import manifestData from './data/versions-manifest.json' with {type: 'json'};
describe('Finder tests', () => {
let writeSpy: jest.SpyInstance;
let spyCoreAddPath: jest.SpyInstance;
let spyCoreExportVariable: jest.SpyInstance;
let writeSpy: jest.SpiedFunction<typeof process.stdout.write>;
let spyCoreAddPath: jest.Mock;
let spyCoreExportVariable: jest.Mock;
const env = process.env;
beforeEach(() => {
writeSpy = jest.spyOn(process.stdout, 'write');
writeSpy.mockImplementation(() => {});
jest.resetModules();
writeSpy.mockImplementation(() => true);
process.env = {...env};
spyCoreAddPath = jest.spyOn(core, 'addPath');
spyCoreExportVariable = jest.spyOn(core, 'exportVariable');
spyCoreAddPath = core.addPath as jest.Mock;
spyCoreExportVariable = core.exportVariable as jest.Mock;
// Restore real tc.find default (cleared by jest.resetAllMocks)
(tc.find as jest.Mock).mockImplementation(realTc.find as any);
});
afterEach(() => {
@@ -49,8 +105,8 @@ describe('Finder tests', () => {
});
it('Finds Python if it is installed', async () => {
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(input => false);
const getBooleanInputSpy = core.getBooleanInput as jest.Mock;
getBooleanInputSpy.mockImplementation(() => false);
const pythonDir: string = path.join(toolDir, 'Python', '3.0.0', 'x64');
await io.mkdirP(pythonDir);
@@ -79,16 +135,13 @@ describe('Finder tests', () => {
});
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(() => <tc.IToolRelease[]>manifestData);
const findSpy = tc.getManifestFromRepo as jest.Mock;
findSpy.mockImplementation(() => manifestData);
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(input => false);
const getBooleanInputSpy = core.getBooleanInput as jest.Mock;
getBooleanInputSpy.mockImplementation(() => false);
const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
);
const installSpy = installer.installCpythonFromRelease as jest.Mock;
installSpy.mockImplementation(async () => {
const pythonDir: string = path.join(toolDir, 'Python', '1.2.3', 'x64');
await io.mkdirP(pythonDir);
@@ -113,16 +166,13 @@ describe('Finder tests', () => {
});
it('Finds pre-release Python version in the manifest', async () => {
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
const findSpy = tc.getManifestFromRepo as jest.Mock;
findSpy.mockImplementation(() => manifestData);
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(input => false);
const getBooleanInputSpy = core.getBooleanInput as jest.Mock;
getBooleanInputSpy.mockImplementation(() => false);
const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
);
const installSpy = installer.installCpythonFromRelease as jest.Mock;
installSpy.mockImplementation(async () => {
const pythonDir: string = path.join(
toolDir,
@@ -150,40 +200,34 @@ describe('Finder tests', () => {
});
it('Check-latest true, finds the latest version in the manifest', async () => {
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
const findSpy = tc.getManifestFromRepo as jest.Mock;
findSpy.mockImplementation(() => manifestData);
const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(input => true);
const getBooleanInputSpy = core.getBooleanInput as jest.Mock;
getBooleanInputSpy.mockImplementation(() => true);
const cnSpy: jest.SpyInstance = jest.spyOn(process.stdout, 'write');
cnSpy.mockImplementation(line => {
// uncomment to debug
// process.stderr.write('write:' + line + '\n');
});
const cnSpy = jest.spyOn(process.stdout, 'write');
cnSpy.mockImplementation(() => true);
const addPathSpy: jest.SpyInstance = jest.spyOn(core, 'addPath');
const addPathSpy = core.addPath as jest.Mock;
addPathSpy.mockImplementation(() => null);
const infoSpy: jest.SpyInstance = jest.spyOn(core, 'info');
const infoSpy = core.info as jest.Mock;
infoSpy.mockImplementation(() => {});
const debugSpy: jest.SpyInstance = jest.spyOn(core, 'debug');
const debugSpy = core.debug as jest.Mock;
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'
);
const installSpy = installer.installCpythonFromRelease as jest.Mock;
installSpy.mockImplementation(async () => {
await io.mkdirP(expPath);
fs.writeFileSync(`${expPath}.complete`, 'hello');
});
const tcFindSpy: jest.SpyInstance = jest.spyOn(tc, 'find');
const tcFindSpy = tc.find as jest.Mock;
tcFindSpy
.mockImplementationOnce(() => '')
.mockImplementationOnce(() => expPath);
@@ -224,13 +268,10 @@ describe('Finder tests', () => {
});
it('Finds stable Python version if it is not installed, but exists in the manifest, skipping newer pre-release', async () => {
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
const findSpy = tc.getManifestFromRepo as jest.Mock;
findSpy.mockImplementation(() => manifestData);
const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
);
const installSpy = installer.installCpythonFromRelease as jest.Mock;
installSpy.mockImplementation(async () => {
const pythonDir: string = path.join(toolDir, 'Python', '1.2.3', 'x64');
await io.mkdirP(pythonDir);
@@ -246,13 +287,10 @@ describe('Finder tests', () => {
});
it('Finds Python version if it is not installed, but exists in the manifest, pre-release fallback', async () => {
const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
const findSpy = tc.getManifestFromRepo as jest.Mock;
findSpy.mockImplementation(() => manifestData);
const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
);
const installSpy = installer.installCpythonFromRelease as jest.Mock;
installSpy.mockImplementation(async () => {
const pythonDir: string = path.join(
toolDir,
+106 -45
View File
@@ -1,20 +1,75 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import {fileURLToPath} from 'url';
import fs from 'fs';
import {HttpClient} from '@actions/http-client';
import * as ifm from '@actions/http-client/lib/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-graalpy';
import {
IGraalPyManifestRelease,
IGraalPyManifestAsset,
IS_WINDOWS
} from '../src/utils';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
import manifestData from './data/graalpy.json';
// Mock @actions modules
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
jest.unstable_mockModule('@actions/tool-cache', () => ({
find: jest.fn(),
findAllVersions: jest.fn(),
downloadTool: jest.fn(),
extractZip: jest.fn(),
extractTar: jest.fn(),
extract7z: jest.fn(),
extractXar: jest.fn(),
cacheDir: jest.fn(),
cacheFile: jest.fn(),
getManifestFromRepo: jest.fn(),
findFromManifest: jest.fn(),
evaluateVersions: jest.fn()
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const tc = await import('@actions/tool-cache');
const exec = await import('@actions/exec');
// Non-mocked imports
import {HttpClient} from '@actions/http-client';
import type * as ifm from '@actions/http-client/lib/interfaces';
const installer = await import('../src/install-graalpy.js');
const utils = await import('../src/utils.js');
import type {
IGraalPyManifestRelease,
IGraalPyManifestAsset
} from '../src/utils.js';
import manifestData from './data/graalpy.json' with {type: 'json'};
const IS_WINDOWS = utils.IS_WINDOWS;
const architecture = 'x64';
@@ -48,18 +103,18 @@ describe('findRelease', () => {
browser_download_url: `https://github.com/graalvm/graal-languages-ea-builds/releases/download/graalpy-24.1.0-ea.09/graalpy-24.1.0-ea.09-${extensionName}`
};
let warningSpy: jest.SpyInstance;
let debugSpy: jest.SpyInstance;
let infoSpy: jest.SpyInstance;
let warningSpy: jest.Mock;
let debugSpy: jest.Mock;
let infoSpy: jest.Mock;
beforeEach(() => {
infoSpy = jest.spyOn(core, 'info');
infoSpy = core.info as jest.Mock;
infoSpy.mockImplementation(() => {});
warningSpy = jest.spyOn(core, 'warning');
warningSpy = core.warning as jest.Mock;
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy = core.debug as jest.Mock;
debugSpy.mockImplementation(() => null);
});
@@ -115,51 +170,57 @@ describe('findRelease', () => {
resolvedGraalPyVersion: '24.1.0-ea.9'
});
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
jest.restoreAllMocks();
});
});
describe('installGraalPy', () => {
let tcFind: 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;
let spyFsWriteFile: jest.SpyInstance;
let spyHttpClient: jest.SpyInstance;
let spyExistsSync: jest.SpyInstance;
let spyExec: jest.SpyInstance;
let spySymlinkSync: jest.SpyInstance;
let spyDownloadTool: jest.SpyInstance;
let spyCacheDir: jest.SpyInstance;
let spyChmodSync: jest.SpyInstance;
let tcFind: jest.Mock;
let infoSpy: jest.Mock;
let warningSpy: jest.Mock;
let debugSpy: jest.Mock;
let spyExtractZip: jest.Mock;
let spyExtractTar: jest.Mock;
let spyFsReadDir: jest.SpiedFunction<typeof fs.readdirSync>;
let spyFsWriteFile: jest.SpiedFunction<typeof fs.writeFileSync>;
let spyHttpClient: jest.SpiedFunction<typeof HttpClient.prototype.getJson>;
let spyExistsSync: jest.SpiedFunction<typeof fs.existsSync>;
let spyExec: jest.Mock;
let spySymlinkSync: jest.SpiedFunction<typeof fs.symlinkSync>;
let spyDownloadTool: jest.Mock;
let spyCacheDir: jest.Mock;
let spyChmodSync: jest.SpiedFunction<typeof fs.chmodSync>;
beforeEach(() => {
tcFind = jest.spyOn(tc, 'find');
tcFind = tc.find as jest.Mock;
tcFind.mockImplementation(() =>
path.join('GraalPy', '3.6.12', architecture)
);
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
spyDownloadTool = tc.downloadTool as jest.Mock;
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'GraalPy'));
spyExtractZip = jest.spyOn(tc, 'extractZip');
spyExtractZip = tc.extractZip as jest.Mock;
spyExtractZip.mockImplementation(() => tempDir);
spyExtractTar = jest.spyOn(tc, 'extractTar');
spyExtractTar = tc.extractTar as jest.Mock;
spyExtractTar.mockImplementation(() => tempDir);
infoSpy = jest.spyOn(core, 'info');
infoSpy = core.info as jest.Mock;
infoSpy.mockImplementation(() => {});
warningSpy = jest.spyOn(core, 'warning');
warningSpy = core.warning as jest.Mock;
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy = core.debug as jest.Mock;
debugSpy.mockImplementation(() => null);
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation(() => ['GraalPyTest']);
spyFsReadDir.mockImplementation(() => ['GraalPyTest'] as any);
spyFsWriteFile = jest.spyOn(fs, 'writeFileSync');
spyFsWriteFile.mockImplementation(() => undefined);
@@ -176,7 +237,7 @@ describe('installGraalPy', () => {
}
);
spyExec = jest.spyOn(exec, 'exec');
spyExec = exec.exec as jest.Mock;
spyExec.mockImplementation(() => undefined);
spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
@@ -205,7 +266,7 @@ describe('installGraalPy', () => {
});
it('found and install GraalPy', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir = tc.cacheDir as jest.Mock;
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '21.3.0', architecture)
);
@@ -227,7 +288,7 @@ describe('installGraalPy', () => {
});
it('found and install GraalPy, pre-release fallback', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir = tc.cacheDir as jest.Mock;
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '24.1.0', architecture)
);
+103 -47
View File
@@ -1,20 +1,72 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import {fileURLToPath} from 'url';
import fs from 'fs';
import {HttpClient} from '@actions/http-client';
import * as ifm from '@actions/http-client/lib/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';
import {
IPyPyManifestRelease,
IPyPyManifestAsset,
IS_WINDOWS
} from '../src/utils';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
import manifestData from './data/pypy.json';
// Mock @actions modules
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
jest.unstable_mockModule('@actions/tool-cache', () => ({
find: jest.fn(),
findAllVersions: jest.fn(),
downloadTool: jest.fn(),
extractZip: jest.fn(),
extractTar: jest.fn(),
extract7z: jest.fn(),
extractXar: jest.fn(),
cacheDir: jest.fn(),
cacheFile: jest.fn(),
getManifestFromRepo: jest.fn(),
findFromManifest: jest.fn(),
evaluateVersions: jest.fn()
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const tc = await import('@actions/tool-cache');
const exec = await import('@actions/exec');
// Non-mocked imports
import {HttpClient} from '@actions/http-client';
import type * as ifm from '@actions/http-client/lib/interfaces';
const installer = await import('../src/install-pypy.js');
const utils = await import('../src/utils.js');
import type {IPyPyManifestRelease, IPyPyManifestAsset} from '../src/utils.js';
import manifestData from './data/pypy.json' with {type: 'json'};
const IS_WINDOWS = utils.IS_WINDOWS;
let architecture: string;
if (IS_WINDOWS) {
@@ -58,19 +110,18 @@ describe('findRelease', () => {
download_url: `https://test.download.python.org/pypy/pypy3.6-v7.4.0rc1-${extensionName}`
};
let getBooleanInputSpy: jest.SpyInstance;
let warningSpy: jest.SpyInstance;
let debugSpy: jest.SpyInstance;
let infoSpy: jest.SpyInstance;
let infoSpy: jest.Mock;
let warningSpy: jest.Mock;
let debugSpy: jest.Mock;
beforeEach(() => {
infoSpy = jest.spyOn(core, 'info');
infoSpy = core.info as jest.Mock;
infoSpy.mockImplementation(() => {});
warningSpy = jest.spyOn(core, 'warning');
warningSpy = core.warning as jest.Mock;
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy = core.debug as jest.Mock;
debugSpy.mockImplementation(() => null);
});
@@ -215,50 +266,55 @@ describe('findRelease', () => {
resolvedPyPyVersion: pypyVersion
});
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
jest.restoreAllMocks();
});
});
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;
let spyFsWriteFile: jest.SpyInstance;
let spyHttpClient: jest.SpyInstance;
let spyExistsSync: jest.SpyInstance;
let spyExec: jest.SpyInstance;
let spySymlinkSync: jest.SpyInstance;
let spyDownloadTool: jest.SpyInstance;
let spyCacheDir: jest.SpyInstance;
let spyChmodSync: jest.SpyInstance;
let tcFind: jest.Mock;
let infoSpy: jest.Mock;
let warningSpy: jest.Mock;
let debugSpy: jest.Mock;
let spyExtractZip: jest.Mock;
let spyExtractTar: jest.Mock;
let spyFsReadDir: jest.SpiedFunction<typeof fs.readdirSync>;
let spyFsWriteFile: jest.SpiedFunction<typeof fs.writeFileSync>;
let spyHttpClient: jest.SpiedFunction<typeof HttpClient.prototype.getJson>;
let spyExistsSync: jest.SpiedFunction<typeof fs.existsSync>;
let spyExec: jest.Mock;
let spySymlinkSync: jest.SpiedFunction<typeof fs.symlinkSync>;
let spyDownloadTool: jest.Mock;
let spyCacheDir: jest.Mock;
let spyChmodSync: jest.SpiedFunction<typeof fs.chmodSync>;
beforeEach(() => {
tcFind = jest.spyOn(tc, 'find');
tcFind = tc.find as jest.Mock;
tcFind.mockImplementation(() => path.join('PyPy', '3.6.12', architecture));
spyDownloadTool = jest.spyOn(tc, 'downloadTool');
spyDownloadTool = tc.downloadTool as jest.Mock;
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'PyPy'));
spyExtractZip = jest.spyOn(tc, 'extractZip');
spyExtractZip = tc.extractZip as jest.Mock;
spyExtractZip.mockImplementation(() => tempDir);
spyExtractTar = jest.spyOn(tc, 'extractTar');
spyExtractTar = tc.extractTar as jest.Mock;
spyExtractTar.mockImplementation(() => tempDir);
infoSpy = jest.spyOn(core, 'info');
infoSpy = core.info as jest.Mock;
infoSpy.mockImplementation(() => {});
warningSpy = jest.spyOn(core, 'warning');
warningSpy = core.warning as jest.Mock;
warningSpy.mockImplementation(() => null);
debugSpy = jest.spyOn(core, 'debug');
debugSpy = core.debug as jest.Mock;
debugSpy.mockImplementation(() => null);
spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation(() => ['PyPyTest']);
spyFsReadDir.mockImplementation(() => ['PyPyTest'] as any);
spyFsWriteFile = jest.spyOn(fs, 'writeFileSync');
spyFsWriteFile.mockImplementation(() => undefined);
@@ -275,7 +331,7 @@ describe('installPyPy', () => {
}
);
spyExec = jest.spyOn(exec, 'exec');
spyExec = exec.exec as jest.Mock;
spyExec.mockImplementation(() => undefined);
spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
@@ -304,7 +360,7 @@ describe('installPyPy', () => {
});
it('found and install PyPy', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir = tc.cacheDir as jest.Mock;
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.6.12', architecture)
);
@@ -328,7 +384,7 @@ describe('installPyPy', () => {
});
it('found and install PyPy, pre-release fallback', async () => {
spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir = tc.cacheDir as jest.Mock;
spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.6.12', architecture)
);
+151 -21
View File
@@ -1,16 +1,76 @@
import {
getManifest,
getManifestFromRepo,
getManifestFromURL
} from '../src/install-python';
import * as httpm from '@actions/http-client';
import * as tc from '@actions/tool-cache';
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
jest.mock('@actions/http-client');
jest.mock('@actions/tool-cache');
jest.mock('@actions/tool-cache', () => ({
class MockHttpClientError extends Error {
statusCode: number;
constructor(message: string, statusCode: number) {
super(message);
this.name = 'HttpClientError';
this.statusCode = statusCode;
}
}
// Mock @actions/http-client
jest.unstable_mockModule('@actions/http-client', () => ({
HttpClient: jest.fn().mockImplementation(() => ({
getJson: jest.fn()
})),
HttpClientError: MockHttpClientError,
HttpCodes: {
OK: 200,
NotFound: 404,
InternalServerError: 500
}
}));
// Mock @actions/cache (needed transitively by utils.ts)
jest.unstable_mockModule('@actions/cache', () => ({
saveCache: jest.fn(),
restoreCache: jest.fn(),
isFeatureAvailable: jest.fn()
}));
// Mock @actions/tool-cache
jest.unstable_mockModule('@actions/tool-cache', () => ({
getManifestFromRepo: jest.fn()
}));
// Mock @actions/core (needed by install-python.ts)
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
// Mock @actions/exec (needed by install-python.ts)
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Dynamic imports after mocking
const httpm = await import('@actions/http-client');
const tc = await import('@actions/tool-cache');
const {getManifest, getManifestFromRepo, getManifestFromURL} =
await import('../src/install-python.js');
const mockManifest = [
{
version: '1.0.0',
@@ -31,45 +91,115 @@ describe('getManifest', () => {
jest.resetAllMocks();
});
afterEach(() => {
jest.useRealTimers();
});
it('should return manifest from repo', async () => {
(tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest);
(tc.getManifestFromRepo as jest.Mock<any>).mockResolvedValue(mockManifest);
const manifest = await getManifest();
expect(manifest).toEqual(mockManifest);
});
it('should return manifest from URL if repo fetch fails', async () => {
(tc.getManifestFromRepo as jest.Mock).mockRejectedValue(
jest.useFakeTimers();
(tc.getManifestFromRepo as jest.Mock<any>).mockRejectedValue(
new Error('Fetch failed')
);
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
result: mockManifest
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
getJson: jest.fn(async () => ({result: mockManifest}))
}));
const promise = getManifest();
await jest.runAllTimersAsync();
const manifest = await promise;
expect(manifest).toEqual(mockManifest);
});
it('should fall back to URL if repo returns a truncated/empty manifest', async () => {
jest.useFakeTimers();
(tc.getManifestFromRepo as jest.Mock<any>).mockResolvedValue([]);
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
getJson: jest.fn(async () => ({result: mockManifest}))
}));
const promise = getManifest();
await jest.runAllTimersAsync();
const manifest = await promise;
expect(manifest).toEqual(mockManifest);
});
it('should retry on a transient invalid manifest and then succeed', async () => {
jest.useFakeTimers();
(tc.getManifestFromRepo as jest.Mock<any>)
.mockResolvedValueOnce([])
.mockResolvedValueOnce(mockManifest);
const promise = getManifest();
await jest.runAllTimersAsync();
const manifest = await promise;
expect(manifest).toEqual(mockManifest);
expect(tc.getManifestFromRepo as jest.Mock<any>).toHaveBeenCalledTimes(2);
});
it('should fail loudly when the manifest is truncated/empty on every source', async () => {
jest.useFakeTimers();
(tc.getManifestFromRepo as jest.Mock<any>).mockResolvedValue([]);
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
getJson: jest.fn(async () => ({result: []}))
}));
const promise = getManifest();
// Prevent unhandled rejection before timers advance.
const catchPromise = promise.catch(() => {});
await jest.runAllTimersAsync();
await catchPromise;
await expect(promise).rejects.toThrow(
'Failed to fetch the Python versions manifest'
);
});
it('should not retry the API on a rate-limit error and fall back to URL immediately', async () => {
const rateLimitError = Object.assign(new Error('API rate limit exceeded'), {
httpStatusCode: 403
});
(tc.getManifestFromRepo as jest.Mock<any>).mockRejectedValue(
rateLimitError
);
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
getJson: jest.fn(async () => ({result: mockManifest}))
}));
const manifest = await getManifest();
expect(manifest).toEqual(mockManifest);
expect(tc.getManifestFromRepo as jest.Mock<any>).toHaveBeenCalledTimes(1);
});
});
describe('getManifestFromRepo', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('should return manifest from repo', async () => {
(tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest);
(tc.getManifestFromRepo as jest.Mock<any>).mockResolvedValue(mockManifest);
const manifest = await getManifestFromRepo();
expect(manifest).toEqual(mockManifest);
});
});
describe('getManifestFromURL', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('should return manifest from URL', async () => {
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
result: mockManifest
});
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
getJson: jest.fn(async () => ({result: mockManifest}))
}));
const manifest = await getManifestFromURL();
expect(manifest).toEqual(mockManifest);
});
it('should throw error if unable to get manifest from URL', async () => {
(httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
result: null
});
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
getJson: jest.fn(async () => ({result: null}))
}));
await expect(getManifestFromURL()).rejects.toThrow(
'Unable to get manifest from'
);
+61 -14
View File
@@ -1,11 +1,57 @@
import * as cache from '@actions/cache';
import * as core from '@actions/core';
import {
jest,
describe,
it,
expect,
afterAll,
afterEach,
beforeEach
} from '@jest/globals';
import {fileURLToPath} from 'url';
import path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Mock @actions/cache
jest.unstable_mockModule('@actions/cache', () => ({
isFeatureAvailable: jest.fn(),
saveCache: jest.fn(),
restoreCache: jest.fn()
}));
// Mock @actions/core
jest.unstable_mockModule('@actions/core', () => ({
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
setOutput: jest.fn(),
setFailed: jest.fn(),
warning: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
startGroup: jest.fn(),
endGroup: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
const cache = await import('@actions/cache');
const core = await import('@actions/core');
import * as io from '@actions/io';
import fs from 'fs';
import path from 'path';
import {
const {
validateVersion,
validatePythonVersionFormatForPyPy,
isCacheFeatureAvailable,
@@ -18,10 +64,7 @@ import {
IS_WINDOWS,
getDownloadFileName,
getVersionInputFromToolVersions
} from '../src/utils';
jest.mock('@actions/cache');
jest.mock('@actions/core');
} = await import('../src/utils.js');
describe('validatePythonVersionFormatForPyPy', () => {
it.each([
@@ -55,8 +98,8 @@ describe('validateVersion', () => {
describe('isCacheFeatureAvailable', () => {
it('isCacheFeatureAvailable disabled on GHES', () => {
jest.spyOn(cache, 'isFeatureAvailable').mockImplementation(() => false);
const infoMock = jest.spyOn(core, 'warning');
(cache.isFeatureAvailable as jest.Mock).mockImplementation(() => false);
const infoMock = core.warning as jest.Mock;
const message =
'Caching is only supported on GHES version >= 3.5. If you are on a version >= 3.5, please check with your GHES admin if the Actions cache service is enabled or not.';
try {
@@ -69,8 +112,8 @@ describe('isCacheFeatureAvailable', () => {
});
it('isCacheFeatureAvailable disabled on dotcom', () => {
jest.spyOn(cache, 'isFeatureAvailable').mockImplementation(() => false);
const infoMock = jest.spyOn(core, 'warning');
(cache.isFeatureAvailable as jest.Mock).mockImplementation(() => false);
const infoMock = core.warning as jest.Mock;
const message =
'The runner was not able to contact the cache service. Caching will be skipped';
try {
@@ -83,9 +126,14 @@ describe('isCacheFeatureAvailable', () => {
});
it('isCacheFeatureAvailable is enabled', () => {
jest.spyOn(cache, 'isFeatureAvailable').mockImplementation(() => true);
(cache.isFeatureAvailable as jest.Mock).mockImplementation(() => true);
expect(isCacheFeatureAvailable()).toBe(true);
});
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
});
const tempDir = path.join(
@@ -345,7 +393,6 @@ describe('isGhes', () => {
const pristineEnv = process.env;
beforeEach(() => {
jest.resetModules();
process.env = {...pristineEnv};
});
+77357 -67030
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
{
"type": "module"
}
+72783 -69648
View File
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
{
"type": "module"
}
+2 -2
View File
@@ -555,8 +555,8 @@ GitHub hosted runners have a tool cache that comes with a few versions of Python
|**PyPy tool cache**|`RUNNER_TOOL_CACHE/PyPy/*`|
GitHub runner images are set up in [actions/runner-images](https://github.com/actions/runner-images). 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/runner-images/blob/main/images/linux/scripts/installers/Install-Toolset.ps1) [Configure-Toolset.ps1](https://github.com/actions/runner-images/blob/main/images/linux/scripts/installers/Configure-Toolset.ps1)
- Tool cache setup for Windows: [Install-Toolset.ps1](https://github.com/actions/runner-images/blob/main/images/win/scripts/Installers/Install-Toolset.ps1) [Configure-Toolset.ps1](https://github.com/actions/runner-images/blob/main/images/win/scripts/Installers/Configure-Toolset.ps1)
- Tool cache setup for Ubuntu: [Install-Toolset.ps1](https://github.com/actions/runner-images/blob/main/images/ubuntu/scripts/build/Install-Toolset.ps1) [Configure-Toolset.ps1](https://github.com/actions/runner-images/blob/main/images/ubuntu/scripts/build/Configure-Toolset.ps1)
- Tool cache setup for Windows: [Install-Toolset.ps1](https://github.com/actions/runner-images/blob/main/images/windows/scripts/build/Install-Toolset.ps1) [Configure-Toolset.ps1](https://github.com/actions/runner-images/blob/main/images/windows/scripts/build/Configure-Toolset.ps1)
## Using `setup-python` with a self-hosted runner
+74
View File
@@ -0,0 +1,74 @@
// This is a reusable configuration file copied from https://github.com/actions/reusable-workflows/tree/main/reusable-configurations. Please don't make changes to this file as it's the subject of an automatic update.
import js from '@eslint/js';
import tsParser from '@typescript-eslint/parser';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import jest from 'eslint-plugin-jest';
import n from 'eslint-plugin-n';
import prettier from 'eslint-config-prettier';
import globals from 'globals';
export default [
{
ignores: ['**/*', '!src/**', '!__tests__/**']
},
js.configs.recommended,
{
files: ['**/*.ts'],
languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: 'module',
globals: {
...globals.node,
...globals.es2015
}
},
plugins: {
'@typescript-eslint': tsPlugin,
n
},
rules: {
...tsPlugin.configs.recommended.rules,
'@typescript-eslint/no-require-imports': 'error',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-ignore': 'allow-with-description'
}
],
'no-console': 'error',
yoda: 'error',
'prefer-const': [
'error',
{
destructuring: 'all'
}
],
'no-control-regex': 'off',
'no-constant-condition': ['error', {checkLoops: false}],
'no-undef': 'off',
'no-useless-assignment': 'off',
'n/no-extraneous-import': 'error'
}
},
{
files: ['**/*{test,spec}.ts'],
plugins: {jest},
languageOptions: {
globals: {
...globals.jest
}
},
rules: {
...jest.configs['flat/recommended'].rules,
'@typescript-eslint/no-unused-vars': 'off',
'jest/no-standalone-expect': 'off',
'jest/no-conditional-expect': 'off',
'no-console': 'off'
}
},
prettier
];
-11
View File
@@ -1,11 +0,0 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
}
+24
View File
@@ -0,0 +1,24 @@
export default {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
roots: ['<rootDir>'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.ts$': [
'ts-jest',
{
useESM: true,
diagnostics: {
ignoreCodes: [151002]
}
}
]
},
extensionsToTreatAsEsm: ['.ts'],
transformIgnorePatterns: ['node_modules/(?!(@actions)/)'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1'
},
verbose: true
}
+2546 -1954
View File
File diff suppressed because it is too large Load Diff
+29 -27
View File
@@ -1,6 +1,7 @@
{
"name": "setup-python",
"version": "6.2.0",
"version": "7.0.0",
"type": "module",
"private": true,
"description": "Setup python action",
"main": "dist/index.js",
@@ -9,12 +10,12 @@
},
"scripts": {
"build": "ncc build -o dist/setup src/setup-python.ts && ncc build -o dist/cache-save src/cache-save.ts",
"format": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --write \"**/*.{ts,yml,yaml}\"",
"format-check": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --check \"**/*.{ts,yml,yaml}\"",
"lint": "eslint --config ./.eslintrc.js \"**/*.ts\"",
"lint:fix": "eslint --config ./.eslintrc.js \"**/*.ts\" --fix",
"format": "prettier --no-error-on-unmatched-pattern --write \"**/*.{ts,yml,yaml}\"",
"format-check": "prettier --no-error-on-unmatched-pattern --check \"**/*.{ts,yml,yaml}\"",
"lint": "eslint \"**/*.ts\"",
"lint:fix": "eslint \"**/*.ts\" --fix",
"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 --runInBand --coverage"
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --runInBand --coverage"
},
"repository": {
"type": "git",
@@ -28,31 +29,32 @@
"author": "GitHub",
"license": "MIT",
"dependencies": {
"@actions/cache": "^5.0.1",
"@actions/core": "^2.0.1",
"@actions/exec": "^2.0.0",
"@actions/glob": "^0.5.0",
"@actions/http-client": "^3.0.0",
"@actions/io": "^2.0.0",
"@actions/tool-cache": "^2.0.2",
"@actions/cache": "^6.2.0",
"@actions/core": "^3.0.1",
"@actions/exec": "^3.0.0",
"@actions/glob": "^0.7.0",
"@actions/http-client": "^4.0.1",
"@actions/io": "^3.0.2",
"@actions/tool-cache": "^4.0.0",
"@iarna/toml": "^3.0.0",
"semver": "^7.7.1"
"semver": "^7.8.5"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@eslint/js": "^10.0.1",
"@jest/globals": "^30.4.1",
"@types/node": "^24.10.1",
"@types/semver": "^7.7.0",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"@vercel/ncc": "^0.38.3",
"eslint": "^8.57.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-node": "^11.1.0",
"jest": "^29.7.0",
"jest-circus": "^29.7.0",
"prettier": "^3.6.2",
"ts-jest": "^29.3.2",
"typescript": "^5.9.3"
"@typescript-eslint/eslint-plugin": "^8.62.0",
"@typescript-eslint/parser": "^8.62.0",
"@vercel/ncc": "^0.44.0",
"eslint": "^10.5.0",
"eslint-config-prettier": "^10.0.0",
"eslint-plugin-jest": "^29.15.2",
"eslint-plugin-n": "^18.1.0",
"globals": "^17.7.0",
"jest": "^30.4.2",
"prettier": "^3.8.4",
"ts-jest": "^29.4.11",
"typescript": "^6.0.3"
}
}
+29 -1
View File
@@ -1,6 +1,7 @@
import * as cache from '@actions/cache';
import * as core from '@actions/core';
import {CACHE_DEPENDENCY_BACKUP_PATH} from './constants';
import {getOSInfo, IS_LINUX} from '../utils.js';
import {CACHE_DEPENDENCY_BACKUP_PATH} from './constants.js';
export enum State {
STATE_CACHE_PRIMARY_KEY = 'cache-primary-key',
@@ -22,6 +23,33 @@ abstract class CacheDistributor {
}>;
protected async handleLoadedCache() {}
/**
* Builds the Linux distro portion of a cache key (e.g. `-26.04-Ubuntu`, `-9-rhel`).
* RHEL is keyed by major version since it ships one ABI-stable artifact per major.
*/
protected async getLinuxInfoKeySegment(): Promise<string> {
if (!IS_LINUX) {
return '';
}
const osInfo = await getOSInfo();
if (!osInfo) {
return '';
}
// lsb_release reports RHEL as "RedHatEnterpriseLinux" while /etc/os-release
// reports it as "rhel"; normalize both to "rhel" so the key is consistent.
const normalizedName = osInfo.osName.toLowerCase();
const isRhel =
normalizedName === 'rhel' || normalizedName.includes('redhat');
const osName = isRhel ? 'rhel' : osInfo.osName;
const osVersion = isRhel
? osInfo.osVersion.split('.')[0]
: osInfo.osVersion;
return `-${osVersion}-${osName}`;
}
public async restoreCache() {
const {primaryKey, restoreKey} = await this.computeKeys();
if (primaryKey.endsWith('-')) {
+3 -3
View File
@@ -1,6 +1,6 @@
import PipCache from './pip-cache';
import PipenvCache from './pipenv-cache';
import PoetryCache from './poetry-cache';
import PipCache from './pip-cache.js';
import PipenvCache from './pipenv-cache.js';
import PoetryCache from './poetry-cache.js';
export enum PackageManagers {
Pip = 'pip',
+19 -23
View File
@@ -6,9 +6,9 @@ import utils from 'util';
import * as path from 'path';
import os from 'os';
import CacheDistributor from './cache-distributor';
import {getLinuxInfo, IS_LINUX, IS_WINDOWS} from '../utils';
import {CACHE_DEPENDENCY_BACKUP_PATH} from './constants';
import CacheDistributor from './cache-distributor.js';
import {IS_WINDOWS} from '../utils.js';
import {CACHE_DEPENDENCY_BACKUP_PATH} from './constants.js';
class PipCache extends CacheDistributor {
private cacheDependencyBackupPath: string = CACHE_DEPENDENCY_BACKUP_PATH;
@@ -21,24 +21,28 @@ class PipCache extends CacheDistributor {
}
protected async getCacheGlobalDirectories() {
let exitCode = 1;
let exitCode = 0;
let stdout = '';
let stderr = '';
// Add temporary fix for Windows
// On windows it is necessary to execute through an exec
// because the getExecOutput gives a non zero code or writes to stderr for pip 22.0.2,
// On Windows, it is necessary to execute through an exec
// because the getExecOutput gives a non-zero code or writes to stderr for pip 22.0.2,
// or spawn must be started with the shell option enabled for getExecOutput
// Related issue: https://github.com/actions/setup-python/issues/328
if (IS_WINDOWS) {
const execPromisify = utils.promisify(child_process.exec);
({stdout: stdout, stderr: stderr} = await execPromisify('pip cache dir'));
try {
({stdout, stderr} = await execPromisify('pip cache dir'));
} catch (err) {
// Pip outputs warnings to stderr (e.g., --no-python-version-warning flag deprecation warning), causing false failure detection
// Related issue: https://github.com/actions/setup-python/issues/1034
// If an error occurs, capture stderr and set exitCode to 1 to indicate failure
stderr = (err as any).stderr ?? (err as Error).message;
exitCode = 1;
}
} else {
({
stdout: stdout,
stderr: stderr,
exitCode: exitCode
} = await exec.getExecOutput('pip cache dir'));
({stdout, stderr, exitCode} = await exec.getExecOutput('pip cache dir'));
}
if (exitCode && stderr) {
@@ -62,17 +66,9 @@ class PipCache extends CacheDistributor {
const hash =
(await glob.hashFiles(this.cacheDependencyPath)) ||
(await glob.hashFiles(this.cacheDependencyBackupPath));
let primaryKey = '';
let restoreKey = '';
if (IS_LINUX) {
const osInfo = await getLinuxInfo();
primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${osInfo.osVersion}-${osInfo.osName}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-${osInfo.osVersion}-${osInfo.osName}-python-${this.pythonVersion}-${this.packageManager}`;
} else {
primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-python-${this.pythonVersion}-${this.packageManager}`;
}
const osSegment = await this.getLinuxInfoKeySegment();
const primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}${osSegment}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
const restoreKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}${osSegment}-python-${this.pythonVersion}-${this.packageManager}`;
return {
primaryKey,
+3 -2
View File
@@ -3,7 +3,7 @@ import * as os from 'os';
import * as path from 'path';
import * as core from '@actions/core';
import CacheDistributor from './cache-distributor';
import CacheDistributor from './cache-distributor.js';
class PipenvCache extends CacheDistributor {
constructor(
@@ -32,7 +32,8 @@ class PipenvCache extends CacheDistributor {
protected async computeKeys() {
const hash = await glob.hashFiles(this.patterns);
const primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
const osSegment = await this.getLinuxInfoKeySegment();
const primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}${osSegment}-python-${this.pythonVersion}-${this.packageManager}-${hash}`;
const restoreKey = undefined;
return {
primaryKey,
+4 -3
View File
@@ -4,8 +4,8 @@ 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';
import CacheDistributor from './cache-distributor.js';
import {logWarning} from '../utils.js';
class PoetryCache extends CacheDistributor {
constructor(
@@ -46,8 +46,9 @@ class PoetryCache extends CacheDistributor {
protected async computeKeys() {
const hash = await glob.hashFiles(this.patterns);
const osSegment = await this.getLinuxInfoKeySegment();
// "v2" is here to invalidate old caches of this cache distributor, which were created broken:
const primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}-python-${this.pythonVersion}-${this.packageManager}-v2-${hash}`;
const primaryKey = `${this.CACHE_KEY_PREFIX}-${process.env['RUNNER_OS']}-${process.arch}${osSegment}-python-${this.pythonVersion}-${this.packageManager}-v2-${hash}`;
const restoreKey = undefined;
return {
primaryKey,
+6 -2
View File
@@ -2,7 +2,7 @@ import * as core from '@actions/core';
import * as cache from '@actions/cache';
import fs from 'fs';
import {State} from './cache-distributions/cache-distributor';
import {State} from './cache-distributions/cache-distributor.js';
// Added early exit to resolve issue with slow post action step:
// - https://github.com/actions/setup-node/issues/878
@@ -70,7 +70,11 @@ async function saveCache(packageManager: string) {
return;
}
if (cacheId == -1) {
if (cacheId === -1) {
// saveCache returns -1 without throwing when the cache was not saved, e.g.
// a reserve collision or a read-only token (fork PR). @actions/cache has
// already logged the reason at the appropriate severity, so just trace it.
core.debug(`Cache was not saved for the key: ${primaryKey}`);
return;
}
core.info(`Cache saved with the key: ${primaryKey}`);
+2 -2
View File
@@ -1,6 +1,6 @@
import * as path from 'path';
import * as graalpyInstall from './install-graalpy';
import {IS_WINDOWS, validateVersion, IGraalPyManifestRelease} from './utils';
import * as graalpyInstall from './install-graalpy.js';
import {IS_WINDOWS, validateVersion, IGraalPyManifestRelease} from './utils.js';
import * as semver from 'semver';
import * as core from '@actions/core';
+2 -2
View File
@@ -1,5 +1,5 @@
import * as path from 'path';
import * as pypyInstall from './install-pypy';
import * as pypyInstall from './install-pypy.js';
import {
IS_WINDOWS,
WINDOWS_ARCHS,
@@ -9,7 +9,7 @@ import {
validatePythonVersionFormatForPyPy,
IPyPyManifestRelease,
getBinaryDirectory
} from './utils';
} from './utils.js';
import * as semver from 'semver';
import * as core from '@actions/core';
+2 -2
View File
@@ -1,10 +1,10 @@
import * as os from 'os';
import * as path from 'path';
import {IS_WINDOWS, IS_LINUX, getOSInfo} from './utils';
import {IS_WINDOWS, IS_LINUX, getOSInfo} from './utils.js';
import * as semver from 'semver';
import * as installer from './install-python';
import * as installer from './install-python.js';
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
+1 -1
View File
@@ -16,7 +16,7 @@ import {
createSymlinkInFolder,
isNightlyKeyword,
getNextPageUrl
} from './utils';
} from './utils.js';
const TOKEN = core.getInput('token');
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
+1 -1
View File
@@ -17,7 +17,7 @@ import {
writeExactPyPyVersionFile,
getBinaryDirectory,
getDownloadFileName
} from './utils';
} from './utils.js';
export async function installPyPy(
pypyVersion: string,
+177 -16
View File
@@ -2,9 +2,11 @@ import * as path from 'path';
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
import {ExecOptions} from '@actions/exec';
import * as httpm from '@actions/http-client';
import {ExecOptions} from '@actions/exec/lib/interfaces';
import {IS_WINDOWS, IS_LINUX, getDownloadFileName} from './utils';
import * as fs from 'fs';
import * as semver from 'semver';
import {IS_WINDOWS, IS_LINUX, getDownloadFileName} from './utils.js';
import {IToolRelease} from '@actions/tool-cache';
const TOKEN = core.getInput('token');
@@ -14,6 +16,73 @@ const MANIFEST_REPO_NAME = 'python-versions';
const MANIFEST_REPO_BRANCH = 'main';
export const MANIFEST_URL = `https://raw.githubusercontent.com/${MANIFEST_REPO_OWNER}/${MANIFEST_REPO_NAME}/${MANIFEST_REPO_BRANCH}/versions-manifest.json`;
interface LinuxOsRelease {
id: string;
versionId: string;
}
function getLinuxOsRelease(): LinuxOsRelease | null {
try {
const content = fs.readFileSync('/etc/os-release', 'utf8');
const lines = content.split('\n');
let id = '';
let versionId = '';
for (const line of lines) {
const parts = line.split('=');
if (parts.length === 2) {
const key = parts[0].trim();
const value = parts[1].trim().replace(/^"/, '').replace(/"$/, '');
if (key === 'ID') id = value;
if (key === 'VERSION_ID') versionId = value;
}
}
if (id && versionId) {
return {id, versionId};
}
return null;
} catch {
return null;
}
}
function findRhelRelease(
semanticVersionSpec: string,
architecture: string,
manifest: tc.IToolRelease[],
osVersion: string
): tc.IToolRelease | undefined {
for (const candidate of manifest) {
const version = candidate.version;
core.debug(`check ${version} satisfies ${semanticVersionSpec}`);
if (!semver.satisfies(version, semanticVersionSpec)) continue;
const file = candidate.files.find(item => {
core.debug(
`${item.arch}===${architecture} && ${item.platform}===rhel && ${item.platform_version}===${osVersion}`
);
const archMatch = item.arch === architecture;
const platformMatch = item.platform === 'rhel';
const versionMatch =
!item.platform_version ||
item.platform_version === osVersion ||
osVersion.startsWith(item.platform_version);
return archMatch && platformMatch && versionMatch;
});
if (file) {
core.debug(`matched ${candidate.version}`);
const result = Object.assign({}, candidate);
result.files = [file];
return result;
}
}
return undefined;
}
const MANIFEST_FETCH_MAX_ATTEMPTS = 3;
const MANIFEST_FETCH_RETRY_BASE_DELAY_MS = 1000;
export async function findReleaseFromManifest(
semanticVersionSpec: string,
architecture: string,
@@ -23,6 +92,23 @@ export async function findReleaseFromManifest(
manifest = await getManifest();
}
// On RHEL, tc.findFromManifest() won't match because os.platform() returns 'linux'
// but manifest entries use platform 'rhel'. Use custom filtering for RHEL.
if (IS_LINUX) {
const osRelease = getLinuxOsRelease();
if (osRelease && osRelease.id === 'rhel') {
core.debug(
`Detected RHEL ${osRelease.versionId}, using custom manifest filtering`
);
return findRhelRelease(
semanticVersionSpec,
architecture,
manifest,
osRelease.versionId
);
}
}
const foundRelease = await tc.findFromManifest(
semanticVersionSpec,
false,
@@ -32,6 +118,7 @@ export async function findReleaseFromManifest(
return foundRelease;
}
function isIToolRelease(obj: any): obj is IToolRelease {
return (
typeof obj === 'object' &&
@@ -48,28 +135,97 @@ function isIToolRelease(obj: any): obj is IToolRelease {
)
);
}
// Rejects empty or truncated manifest responses.
function isValidManifest(manifest: unknown): manifest is tc.IToolRelease[] {
return (
Array.isArray(manifest) &&
manifest.length > 0 &&
manifest.every(isIToolRelease)
);
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// HTTP 403/429 from http-client (`statusCode`) or tool-cache (`httpStatusCode`).
function isRateLimitError(err: unknown): boolean {
const e = err as
| {httpStatusCode?: number; statusCode?: number}
| null
| undefined;
const status = e?.httpStatusCode ?? e?.statusCode;
return status === 403 || status === 429;
}
// Fetches and validates a manifest, retrying transient failures with backoff.
async function fetchValidManifest(
source: string,
fetcher: () => Promise<tc.IToolRelease[]>
): Promise<tc.IToolRelease[]> {
let lastError: Error | undefined;
let attempts = 0;
for (let attempt = 1; attempt <= MANIFEST_FETCH_MAX_ATTEMPTS; attempt++) {
attempts = attempt;
try {
const manifest = await fetcher();
if (isValidManifest(manifest)) {
return manifest;
}
throw new Error(
`The manifest fetched from ${source} is empty, truncated, or does not contain any valid tool release entries.`
);
} catch (err) {
lastError = err instanceof Error ? err : new Error(String(err));
core.debug(
`Attempt ${attempt}/${MANIFEST_FETCH_MAX_ATTEMPTS} to fetch the manifest from ${source} failed: ${lastError.message}`
);
// Rate limits won't clear within the backoff window; fall back instead.
if (isRateLimitError(err)) {
core.debug(
`${source} is rate-limited; skipping retries for this source.`
);
break;
}
if (attempt < MANIFEST_FETCH_MAX_ATTEMPTS) {
const delay = MANIFEST_FETCH_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1);
core.debug(`Retrying in ${delay}ms...`);
await sleep(delay);
}
}
}
throw new Error(
`Failed to fetch a valid manifest from ${source} after ${attempts} attempt(s): ${lastError?.message}`
);
}
export async function getManifest(): Promise<tc.IToolRelease[]> {
try {
const repoManifest = await getManifestFromRepo();
if (
Array.isArray(repoManifest) &&
repoManifest.length &&
repoManifest.every(isIToolRelease)
) {
return repoManifest;
}
throw new Error(
'The repository manifest is invalid or does not include any valid tool release (IToolRelease) entries.'
);
return await fetchValidManifest('the GitHub API', getManifestFromRepo);
} catch (err) {
core.debug('Fetching the manifest via the API failed.');
if (err instanceof Error) {
core.debug(err.message);
} else {
core.error('An unexpected error occurred while fetching the manifest.');
core.debug('An unexpected error occurred while fetching the manifest.');
}
}
return await getManifestFromURL();
try {
return await fetchValidManifest('the raw URL', getManifestFromURL);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
// Fail loudly so the action doesn't exit 0 without installing Python.
throw new Error(
`Failed to fetch the Python versions manifest. The response was empty, truncated, or invalid, and all retries were exhausted. ${message}`,
{cause: err}
);
}
}
export function getManifestFromRepo(): Promise<tc.IToolRelease[]> {
@@ -108,7 +264,12 @@ async function installPython(workingDirectory: string) {
core.info(data.toString().trim());
},
stderr: (data: Buffer) => {
core.error(data.toString().trim());
const msg = data.toString().trim();
if (/^WARNING:/im.test(msg)) {
core.warning(msg);
} else {
core.error(msg);
}
}
}
};
+12 -7
View File
@@ -1,18 +1,19 @@
import * as core from '@actions/core';
import * as finder from './find-python';
import * as finderPyPy from './find-pypy';
import * as finderGraalPy from './find-graalpy';
import * as finder from './find-python.js';
import * as finderPyPy from './find-pypy.js';
import * as finderGraalPy from './find-graalpy.js';
import * as path from 'path';
import * as os from 'os';
import {fileURLToPath} from 'url';
import fs from 'fs';
import {getCacheDistributor} from './cache-distributions/cache-factory';
import {getCacheDistributor} from './cache-distributions/cache-factory.js';
import {
isCacheFeatureAvailable,
logWarning,
IS_MAC,
getVersionInputFromFile,
getVersionsInputFromPlainFile
} from './utils';
} from './utils.js';
import {exec} from '@actions/exec';
function isPyPyVersion(versionSpec: string) {
@@ -29,7 +30,7 @@ async function installPipPackages(pipInstall: string) {
const installArgs = pipInstall.trim().split(/\s+/);
await exec('python', ['-m', 'pip', 'install', ...installArgs]);
core.info('Successfully installed pip packages');
} catch (error) {
} catch {
core.setFailed(
`Failed to install pip packages from "${pipInstall}". Please verify that the package names, versions, or requirements files provided are correct and installable, that the specified packages and versions can be resolved from PyPI or the configured package index, and that your network connection is stable and allows access to the package index.`
);
@@ -168,7 +169,11 @@ async function run() {
'The `python-version` input is not set. The version of Python currently in `PATH` will be used.'
);
}
const matchersPath = path.join(__dirname, '../..', '.github');
const matchersPath = path.join(
path.dirname(fileURLToPath(import.meta.url)),
'../..',
'.github'
);
core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`);
} catch (err) {
core.setFailed((err as Error).message);
+32 -9
View File
@@ -173,15 +173,38 @@ async function getMacOSInfo() {
}
export async function getLinuxInfo() {
const {stdout} = await exec.getExecOutput('lsb_release', ['-i', '-r', '-s'], {
silent: true
});
try {
const {stdout} = await exec.getExecOutput(
'lsb_release',
['-i', '-r', '-s'],
{
silent: true
}
);
const [osName, osVersion] = stdout.trim().split('\n');
const [osName, osVersion] = stdout.trim().split('\n');
core.debug(`OS Name: ${osName}, Version: ${osVersion}`);
return {osName, osVersion};
} catch (err) {
core.debug(
`lsb_release failed (${(err as Error).message}). Falling back to /etc/os-release.`
);
core.debug(`OS Name: ${osName}, Version: ${osVersion}`);
const osReleaseContent = fs.readFileSync('/etc/os-release', 'utf8');
const osInfo: {[key: string]: string} = {};
return {osName: osName, osVersion: osVersion};
osReleaseContent.split('\n').forEach(line => {
const [key, value] = line.split('=');
if (key && value) {
osInfo[key.trim()] = value.trim().replace(/"/g, '');
}
});
const osName = osInfo['ID'] || 'Linux';
const osVersion = osInfo['VERSION_ID'] || '';
core.debug(`OS Name: ${osName}, Version: ${osVersion}`);
return {osName, osVersion};
}
}
export async function getOSInfo() {
@@ -235,7 +258,7 @@ export function getVersionInputFromTomlFile(versionFile: string): string[] {
pyprojectFile = pyprojectFile.replace(/\r\n/g, '\n');
const pyprojectConfig = toml.parse(pyprojectFile);
let keys = [];
let keys: string[] = [];
if ('project' in pyprojectConfig) {
// standard project metadata (PEP 621)
@@ -244,7 +267,7 @@ export function getVersionInputFromTomlFile(versionFile: string): string[] {
// python poetry
keys = ['tool', 'poetry', 'dependencies', 'python'];
}
const versions = [];
const versions: string[] = [];
const version = extractValue(pyprojectConfig, keys);
if (version !== undefined) {
versions.push(version);
@@ -353,7 +376,7 @@ export function getVersionInputFromPipfileFile(versionFile: string): string[] {
} else {
keys.push('python_version');
}
const versions = [];
const versions: string[] = [];
const version = extractValue(pipfileConfig, keys);
if (version !== undefined) {
versions.push(version);
+2 -2
View File
@@ -3,7 +3,7 @@
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "ES2022", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
"module": "NodeNext", /* Specify module code generation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
@@ -60,5 +60,5 @@
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
},
"exclude": ["node_modules", "**/*.test.ts"]
"exclude": ["node_modules", "**/*.test.ts", "jest.config.ts"]
}