Compare commits

..
49 changed files with 77184 additions and 70187 deletions
+3 -9
View File
@@ -4,15 +4,9 @@
"owner": "ruff-format", "owner": "ruff-format",
"pattern": [ "pattern": [
{ {
"code": 2, "file": 2,
"column": 5, "message": 1,
"endColumn": 7, "regexp": "^(Would reformat):\\s*(.+)$"
"endLine": 6,
"file": 3,
"line": 4,
"message": 8,
"regexp": "^(?:(error|warning|notice))\\s+title=([^,]+),file=([^,]+),line=(\\d+),col=(\\d+)(?:,endLine=(\\d+))?(?:,endColumn=(\\d+))?::(.+)$",
"severity": 1
} }
] ]
} }
-9
View File
@@ -1,9 +0,0 @@
{
"compilerOptions": {
"module": "nodenext",
"moduleResolution": "nodenext",
"target": "es2022",
"types": ["node"]
},
"include": ["check-all-tests-passed-needs.ts"]
}
+4 -4
View File
@@ -39,13 +39,13 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0 uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
source-root: src source-root: src
@@ -57,7 +57,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0 uses: github/codeql-action/autobuild@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
# ️ Command-line programs to run using the OS shell. # ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@@ -71,4 +71,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0 uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
+1 -1
View File
@@ -17,6 +17,6 @@ jobs:
pull-requests: read pull-requests: read
steps: steps:
- name: 🚀 Run Release Drafter - name: 🚀 Run Release Drafter
uses: release-drafter/release-drafter@5de93583980a40bd78603b6dfdcda5b4df377b32 # v7.2.0 uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-58
View File
@@ -1,58 +0,0 @@
name: Release
on:
workflow_dispatch:
inputs:
version:
description: "Release version (e.g., 8.1.0)"
required: true
type: string
permissions: {}
jobs:
release:
name: Release
runs-on: ubuntu-latest
environment: release
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Validate version
env:
VERSION: ${{ inputs.version }}
run: |
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "::error::Version must match MAJOR.MINOR.PATCH (e.g., 8.1.0)"
exit 1
fi
- name: Publish release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: v${{ inputs.version }}
run: |
RELEASE_JSON=$(gh release view "$TAG" --json isDraft,targetCommitish 2>&1) || {
echo "::error::No release found for $TAG"
exit 1
}
IS_DRAFT=$(echo "$RELEASE_JSON" | jq -r '.isDraft')
TARGET=$(echo "$RELEASE_JSON" | jq -r '.targetCommitish')
if [[ "$IS_DRAFT" != "true" ]]; then
echo "::error::Release $TAG already exists and is not a draft"
exit 1
fi
if [[ "$TARGET" != "$GITHUB_SHA" ]]; then
echo "::error::Draft release target ($TARGET) does not match current commit ($GITHUB_SHA)"
exit 1
fi
echo "Publishing draft release $TAG"
gh release edit "$TAG" --draft=false
+24 -116
View File
@@ -15,27 +15,22 @@ permissions: {}
jobs: jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
security-events: write # for zizmor
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Actionlint - name: Actionlint
uses: eifinger/actionlint-action@1fc89649be682d16ec5cf65ea16e269eb88d3982 # v1.10.2 uses: eifinger/actionlint-action@03ff1f78c0670b71017616a37170f327df932030 # v1.9.2
- name: Run zizmor - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: zizmorcore/zizmor-action@71321a20a9ded102f6e9ce5718a2fcec2c4f70d8 # v0.5.2
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with: with:
node-version-file: .nvmrc node-version: "20"
cache: npm
- run: | - run: |
npm ci --ignore-scripts npm install
- run: | - run: |
npm run all npm run all
- name: Check all jobs are in all-tests-passed.needs - name: Check all jobs are in all-tests-passed.needs
run: | run: |
tsc -p tsconfig.json tsc check-all-tests-passed-needs.ts
node check-all-tests-passed-needs.js node check-all-tests-passed-needs.js
working-directory: .github/scripts working-directory: .github/scripts
- name: Make sure no changes from linters are detected - name: Make sure no changes from linters are detected
@@ -47,7 +42,7 @@ jobs:
matrix: matrix:
os: [ubuntu-latest, macos-latest, macos-14, windows-latest] os: [ubuntu-latest, macos-latest, macos-14, windows-latest]
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use latest version - name: Use latest version
@@ -62,7 +57,7 @@ jobs:
ruff-version: ["0.1.7", "0.1.8", "0.4.7", "0.4.10", "0.7", "0.7.x", ">=0.7.0"] ruff-version: ["0.1.7", "0.1.8", "0.4.7", "0.4.10", "0.7", "0.7.x", ">=0.7.0"]
os: [ ubuntu-latest, ubuntu-24.04-arm, macos-latest, macos-14, windows-latest ] os: [ ubuntu-latest, ubuntu-24.04-arm, macos-latest, macos-14, windows-latest ]
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use version ${{ matrix.ruff-version }} - name: Use version ${{ matrix.ruff-version }}
@@ -73,7 +68,7 @@ jobs:
test-unsupported-version: test-unsupported-version:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Try install old version - name: Try install old version
@@ -93,7 +88,7 @@ jobs:
test-version-from-version-file-pyproject: test-version-from-version-file-pyproject:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use version from pyproject.toml - name: Use version from pyproject.toml
@@ -112,7 +107,7 @@ jobs:
test-default-version-from-pyproject: test-default-version-from-pyproject:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use default version from pyproject.toml - name: Use default version from pyproject.toml
@@ -130,7 +125,7 @@ jobs:
test-default-version-from-pyproject-dev-group: test-default-version-from-pyproject-dev-group:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use default version from pyproject.toml dev group - name: Use default version from pyproject.toml dev group
@@ -149,7 +144,7 @@ jobs:
test-default-version-from-pyproject-dependency-groups: test-default-version-from-pyproject-dependency-groups:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use default version from pyproject.toml dependency groups - name: Use default version from pyproject.toml dependency groups
@@ -165,30 +160,10 @@ jobs:
fi fi
env: env:
RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }} RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }}
test-default-version-from-pyproject-dependency-groups-with-env-marker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Use default version from pyproject.toml with environment marker
id: ruff-action
uses: ./
with:
src: __tests__/fixtures/python-project
version-file: __tests__/fixtures/pyproject-dependency-groups-with-env-marker/pyproject.toml
- name: Correct version gets installed
run: |
if [ "$RUFF_VERSION" != "0.13.3" ]; then
echo "Expected version 0.13.3 but got $RUFF_VERSION"
exit 1
fi
env:
RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }}
test-default-version-from-pyproject-poetry-groups: test-default-version-from-pyproject-poetry-groups:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use default version from pyproject.toml poetry group dependencies - name: Use default version from pyproject.toml poetry group dependencies
@@ -207,7 +182,7 @@ jobs:
test-default-version-from-pyproject-poetry: test-default-version-from-pyproject-poetry:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use default version from pyproject.toml poetry dependencies - name: Use default version from pyproject.toml poetry dependencies
@@ -226,7 +201,7 @@ jobs:
test-default-version-from-pyproject-optional-dependencies: test-default-version-from-pyproject-optional-dependencies:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use default version from pyproject.toml optional dependencies - name: Use default version from pyproject.toml optional dependencies
@@ -245,7 +220,7 @@ jobs:
test-default-version-from-requirements: test-default-version-from-requirements:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use default version from requirements.txt - name: Use default version from requirements.txt
@@ -261,29 +236,10 @@ jobs:
fi fi
env: env:
RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }} RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }}
test-default-version-from-requirements-with-hash:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Use default version from requirements-with-hash.txt
id: ruff-action
uses: ./
with:
src: __tests__/fixtures/python-project
version-file: __tests__/fixtures/requirements-with-hash.txt
- name: Correct version gets installed
run: |
if [ "$RUFF_VERSION" != "0.9.0" ]; then
exit 1
fi
env:
RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }}
test-semver-range: test-semver-range:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use version 0.7 - name: Use version 0.7
@@ -302,7 +258,7 @@ jobs:
test-pep440-version-specifier: test-pep440-version-specifier:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Install version 0.9.10 - name: Install version 0.9.10
@@ -328,7 +284,7 @@ jobs:
- os: macos-latest - os: macos-latest
checksum: "af9583bff12afbca5d5670334e0187dd60c4d91bc71317d1b2dde70cb1200ba9" checksum: "af9583bff12afbca5d5670334e0187dd60c4d91bc71317d1b2dde70cb1200ba9"
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Checksum matches expected - name: Checksum matches expected
@@ -340,7 +296,7 @@ jobs:
test-with-explicit-token: test-with-explicit-token:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use default version - name: Use default version
@@ -354,7 +310,7 @@ jobs:
matrix: matrix:
os: [ubuntu-latest, windows-latest] os: [ubuntu-latest, windows-latest]
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use args - name: Use args
@@ -365,7 +321,7 @@ jobs:
test-failure: test-failure:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Format should fail - name: Format should fail
@@ -401,7 +357,7 @@ jobs:
matrix: matrix:
os: [ ubuntu-latest, windows-latest ] os: [ ubuntu-latest, windows-latest ]
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- name: Use args - name: Use args
@@ -411,50 +367,6 @@ jobs:
src: >- src: >-
__tests__/fixtures/python-project/src/python_project/__init__.py __tests__/fixtures/python-project/src/python_project/__init__.py
__tests__/fixtures/python-project/src/python_project/hello_world.py __tests__/fixtures/python-project/src/python_project/hello_world.py
test-parent-directory-pyproject:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Use version from parent directory pyproject.toml
id: ruff-action
uses: ./
with:
src: __tests__/fixtures/parent-config-project/subproject
- name: Correct version gets installed
run: |
if [ "$RUFF_VERSION" != "0.10.0" ]; then
echo "Expected version 0.10.0 but got $RUFF_VERSION"
exit 1
fi
env:
RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }}
test-custom-manifest-file:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Create test src
run: |
mkdir -p "${{ runner.temp }}/ruff-manifest-test"
printf 'print("hello")\n' > "${{ runner.temp }}/ruff-manifest-test/hello.py"
- name: Install from custom manifest file
id: ruff-action
uses: ./
with:
src: ${{ runner.temp }}/ruff-manifest-test
manifest-file: "https://raw.githubusercontent.com/astral-sh/ruff-action/${{ github.ref }}/__tests__/download/custom-manifest.ndjson"
- name: Correct version gets installed
run: |
if [ "$RUFF_VERSION" != "0.15.10" ]; then
echo "Wrong ruff version: $RUFF_VERSION"
exit 1
fi
env:
RUFF_VERSION: ${{ steps.ruff-action.outputs.ruff-version }}
all-tests-passed: all-tests-passed:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -467,12 +379,10 @@ jobs:
- test-default-version-from-pyproject - test-default-version-from-pyproject
- test-default-version-from-pyproject-dev-group - test-default-version-from-pyproject-dev-group
- test-default-version-from-pyproject-dependency-groups - test-default-version-from-pyproject-dependency-groups
- test-default-version-from-pyproject-dependency-groups-with-env-marker
- test-default-version-from-pyproject-poetry-groups - test-default-version-from-pyproject-poetry-groups
- test-default-version-from-pyproject-poetry - test-default-version-from-pyproject-poetry
- test-default-version-from-pyproject-optional-dependencies - test-default-version-from-pyproject-optional-dependencies
- test-default-version-from-requirements - test-default-version-from-requirements
- test-default-version-from-requirements-with-hash
- test-semver-range - test-semver-range
- test-pep440-version-specifier - test-pep440-version-specifier
- test-checksum - test-checksum
@@ -480,8 +390,6 @@ jobs:
- test-args - test-args
- test-failure - test-failure
- test-multiple-src - test-multiple-src
- test-parent-directory-pyproject
- test-custom-manifest-file
if: always() if: always()
steps: steps:
- name: All tests passed - name: All tests passed
+6 -7
View File
@@ -11,21 +11,20 @@ jobs:
contents: write contents: write
pull-requests: write pull-requests: write
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: false persist-credentials: false
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0 - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with: with:
node-version-file: .nvmrc node-version: "20"
cache: npm
- name: Update known checksums - name: Update known checksums
id: update-known-checksums id: update-known-checksums
run: run:
node dist/update-known-checksums/index.cjs node dist/update-known-checksums/index.js
src/download/checksum/known-checksums.ts ${{ secrets.GITHUB_TOKEN }} src/download/checksum/known-checksums.ts ${{ secrets.GITHUB_TOKEN }}
- run: npm ci --ignore-scripts && npm run all - run: npm install && npm run all
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with: with:
commit-message: "chore: update known checksums" commit-message: "chore: update known checksums"
title: title:
@@ -16,9 +16,9 @@ jobs:
permissions: permissions:
contents: write contents: write
steps: steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
persist-credentials: true persist-credentials: false
- name: Update Major Minor Tags - name: Update Major Minor Tags
run: | run: |
set -x set -x
-1
View File
@@ -1 +0,0 @@
24
+21 -40
View File
@@ -20,7 +20,6 @@ anything `ruff` can (ex, fix).
- [Install a specific version](#install-a-specific-version) - [Install a specific version](#install-a-specific-version)
- [Install a version by supplying a semver range or pep440 specifier](#install-a-version-by-supplying-a-semver-range-or-pep440-specifier) - [Install a version by supplying a semver range or pep440 specifier](#install-a-version-by-supplying-a-semver-range-or-pep440-specifier)
- [Install a version from a specified version file](#install-a-version-from-a-specified-version-file) - [Install a version from a specified version file](#install-a-version-from-a-specified-version-file)
- [Install using a custom manifest URL](#install-using-a-custom-manifest-url)
- [Validate checksum](#validate-checksum) - [Validate checksum](#validate-checksum)
- [GitHub authentication token](#github-authentication-token) - [GitHub authentication token](#github-authentication-token)
- [Outputs](#outputs) - [Outputs](#outputs)
@@ -28,17 +27,13 @@ anything `ruff` can (ex, fix).
## Usage ## Usage
| Input | Description | Default | | Input | Description | Default |
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------------| |----------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------------|
| `version` | The version of Ruff to install. See [Install specific versions](#install-specific-versions) | discovered from `pyproject.toml`, else `latest` | | `version` | The version of Ruff to install. See [Install specific versions](#install-specific-versions) | `latest` |
| `version-file` | The file to read the version from. See [Install a version from a specified version file](#install-a-version-from-a-specified-version-file) | None | | `version-file` | The file to read the version from. See [Install a version from a specified version file](#install-a-version-from-a-specified-version-file) | None |
| `manifest-file` | URL to a custom Ruff manifest in the `astral-sh/versions` format. | None |
| `args` | The arguments to pass to the `ruff` command. See [Configuring Ruff] | `check` | | `args` | The arguments to pass to the `ruff` command. See [Configuring Ruff] | `check` |
| `src` | The directory or single files to run `ruff` on. | [github.workspace] | | `src` | The directory or single files to run `ruff` on. | [github.workspace] |
| `checksum` | The sha256 checksum of the downloaded artifact. | None | | `checksum` | The sha256 checksum of the downloaded executable. | None |
| `github-token` | The GitHub token to use when downloading Ruff release artifacts from GitHub. | `GITHUB_TOKEN` | | `github-token` | The GitHub token to use for authentication. | `GITHUB_TOKEN` |
By default, Ruff version metadata is resolved from the
[`astral-sh/versions` Ruff manifest](https://github.com/astral-sh/versions/blob/main/v1/ruff.ndjson).
### Basic ### Basic
@@ -95,10 +90,10 @@ you can use the `args` input to overwrite the default value (`check`):
### Install specific versions ### Install specific versions
By default this action searches upward from `src` until the workspace root to find the nearest By default this action looks for a pyproject.toml file in the root of the repository to determine
`pyproject.toml` and determine the Ruff version to install. If no `pyproject.toml` file is found, the ruff version to install. If no pyproject.toml file is found, or no ruff version is defined in
or no Ruff version is defined in `project.dependencies`, `project.optional-dependencies`, `project.dependencies`, `project.optional-dependencies`, or `dependency-groups`,
`dependency-groups`, or supported Poetry dependency tables, the latest version is installed. the latest version is installed.
> [!NOTE] > [!NOTE]
> This action does only support ruff versions v0.0.247 and above. > This action does only support ruff versions v0.0.247 and above.
@@ -151,8 +146,7 @@ to install the latest version that satisfies the range.
#### Install a version from a specified version file #### Install a version from a specified version file
You can specify a file to read the version from. You can specify a file to read the version from.
Currently `pyproject.toml` and `requirements.txt` are supported. If the file cannot be parsed Currently `pyproject.toml` and `requirements.txt` are supported.
or does not contain a Ruff version, the action warns and falls back to `latest`.
```yaml ```yaml
- name: Install a version from a specified version file - name: Install a version from a specified version file
@@ -161,26 +155,6 @@ or does not contain a Ruff version, the action warns and falls back to `latest`.
version-file: "my-path/to/pyproject.toml-or-requirements.txt" version-file: "my-path/to/pyproject.toml-or-requirements.txt"
``` ```
Version resolution precedence is:
1. `version`
2. `version-file`
3. nearest discoverable `pyproject.toml` found by searching upward from `src`
4. `latest`
#### Install using a custom manifest URL
You can override the default `astral-sh/versions` manifest with `manifest-file`.
This affects both version resolution and artifact selection.
```yaml
- name: Install Ruff from a custom manifest
uses: astral-sh/ruff-action@v3
with:
version: "latest"
manifest-file: "https://example.com/ruff.ndjson"
```
### Validate checksum ### Validate checksum
You can specify a checksum to validate the downloaded executable. Checksums up to the default version You can specify a checksum to validate the downloaded executable. Checksums up to the default version
@@ -197,11 +171,9 @@ are automatically verified by this action. The sha256 hashes can be found on the
### GitHub authentication token ### GitHub authentication token
By default, this action resolves available uv versions from This action uses the GitHub API to fetch the ruff release artifacts. To avoid hitting the GitHub API
[`astral-sh/versions`](https://github.com/astral-sh/versions) and downloads release artifacts from `https://releases.astral.sh`. If this fails this action falls back to downloading from the GitHub releases page of the ruff repository. rate limit too quickly, an authentication token can be provided via the `github-token` input. By
default, the `GITHUB_TOKEN` secret is used, which is automatically provided by GitHub Actions.
You can provide a token via `github-token` to authenticate those downloads. By default, the
`GITHUB_TOKEN` secret is used, which is automatically provided by GitHub Actions.
If the default If the default
[permissions for the GitHub token](https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token) [permissions for the GitHub token](https://docs.github.com/en/actions/security-for-github-actions/security-guides/automatic-token-authentication#permissions-for-the-github_token)
@@ -214,6 +186,15 @@ are not sufficient, you can provide a custom GitHub token with the necessary per
github-token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} github-token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
``` ```
## Reliability
This action includes built-in retry mechanisms to handle intermittent network issues that can occur when downloading Ruff or fetching version information from GitHub's API. The action will automatically retry failed operations up to 3 times with exponential backoff, which significantly reduces failure rates due to temporary connectivity issues.
- **GitHub API calls**: 30-second timeout with 3 retries
- **Binary downloads**: 60-second timeout with 3 retries
- **Smart error detection**: Automatically identifies retryable errors (timeouts, network issues, 5xx HTTP responses)
- **Detailed logging**: Provides comprehensive error information for debugging
## Outputs ## Outputs
| Output | Description | | Output | Description |
@@ -1 +0,0 @@
{"version":"0.15.10","artifacts":[{"platform":"x86_64-unknown-linux-gnu","variant":"default","url":"https://github.com/astral-sh/ruff/releases/download/0.15.10/ruff-x86_64-unknown-linux-gnu.tar.gz","archive_format":"tar.gz","sha256":"e3e9e5c791542f00d95edc74a506e1ac24efc0af9574de01ab338187bf1ff9f6"}]}
-511
View File
@@ -1,511 +0,0 @@
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
import * as semver from "semver";
const mockInfo = jest.fn();
const mockWarning = jest.fn();
jest.unstable_mockModule("@actions/core", () => ({
debug: jest.fn(),
info: mockInfo,
warning: mockWarning,
}));
const mockDownloadTool = jest.fn();
const mockExtractTar = jest.fn();
const mockExtractZip = jest.fn();
const mockCacheDir = jest.fn();
jest.unstable_mockModule("@actions/tool-cache", () => ({
cacheDir: mockCacheDir,
downloadTool: mockDownloadTool,
evaluateVersions: (versions: string[], range: string) =>
semver.maxSatisfying(versions, range) ?? "",
extractTar: mockExtractTar,
extractZip: mockExtractZip,
find: () => "",
findAllVersions: () => [],
isExplicitVersion: (version: string) => semver.valid(version) !== null,
}));
const mockGetLatestVersion = jest.fn();
const mockGetAllVersions = jest.fn();
const mockGetArtifact = jest.fn();
jest.unstable_mockModule("../../src/download/manifest", () => ({
getAllVersions: mockGetAllVersions,
getArtifact: mockGetArtifact,
getLatestVersion: mockGetLatestVersion,
}));
const mockValidateChecksum = jest.fn();
jest.unstable_mockModule("../../src/download/checksum/checksum", () => ({
validateChecksum: mockValidateChecksum,
}));
const mockCopyFile = jest.fn();
const mockReaddir = jest.fn();
jest.unstable_mockModule("node:fs", () => ({
default: {},
promises: {
copyFile: mockCopyFile,
readdir: mockReaddir,
},
}));
const { downloadVersion, rewriteToMirror } = await import(
"../../src/download/download-version"
);
const { resolveVersion } = await import("../../src/version/resolve");
describe("download-version", () => {
beforeEach(() => {
mockInfo.mockReset();
mockWarning.mockReset();
mockDownloadTool.mockReset();
mockExtractTar.mockReset();
mockExtractZip.mockReset();
mockCacheDir.mockReset();
mockGetLatestVersion.mockReset();
mockGetAllVersions.mockReset();
mockGetArtifact.mockReset();
mockValidateChecksum.mockReset();
mockCopyFile.mockReset();
mockReaddir.mockReset();
mockDownloadTool.mockResolvedValue("/tmp/downloaded");
mockExtractTar.mockResolvedValue("/tmp/extracted");
mockExtractZip.mockResolvedValue("/tmp/extracted");
mockCacheDir.mockResolvedValue("/tmp/cached");
mockReaddir.mockResolvedValue(["ruff"]);
});
describe("resolveVersion", () => {
it("uses the default manifest to resolve latest", async () => {
mockGetLatestVersion.mockResolvedValue("0.15.8");
const version = await resolveVersion("latest", undefined);
expect(version).toBe("0.15.8");
expect(mockGetLatestVersion).toHaveBeenCalledTimes(1);
expect(mockGetLatestVersion).toHaveBeenCalledWith(undefined);
});
it("uses the default manifest to resolve available versions", async () => {
mockGetAllVersions.mockResolvedValue(["0.15.8", "0.15.7"]);
const version = await resolveVersion("0.15.x", undefined);
expect(version).toBe("0.15.8");
expect(mockGetAllVersions).toHaveBeenCalledTimes(1);
expect(mockGetAllVersions).toHaveBeenCalledWith(undefined);
});
it("uses manifest-file when provided", async () => {
mockGetAllVersions.mockResolvedValue(["0.15.8", "0.15.7"]);
const version = await resolveVersion(
"0.15.x",
"https://example.com/custom.ndjson",
);
expect(version).toBe("0.15.8");
expect(mockGetAllVersions).toHaveBeenCalledWith(
"https://example.com/custom.ndjson",
);
});
});
describe("downloadVersion", () => {
it("fails when manifest lookup fails", async () => {
mockGetArtifact.mockRejectedValue(new Error("manifest unavailable"));
await expect(
downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.15.8",
undefined,
"token",
),
).rejects.toThrow("manifest unavailable");
expect(mockDownloadTool).not.toHaveBeenCalled();
expect(mockValidateChecksum).not.toHaveBeenCalled();
});
it("fails when no matching artifact exists in the default manifest", async () => {
mockGetArtifact.mockResolvedValue(undefined);
await expect(
downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.15.8",
undefined,
"token",
),
).rejects.toThrow(
"Could not find artifact for version 0.15.8, arch x86_64, platform unknown-linux-gnu in https://raw.githubusercontent.com/astral-sh/versions/main/v1/ruff.ndjson .",
);
expect(mockDownloadTool).not.toHaveBeenCalled();
expect(mockValidateChecksum).not.toHaveBeenCalled();
});
it("uses built-in checksums for default manifest downloads", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "manifest-checksum-that-should-be-ignored",
downloadUrl: "https://example.com/ruff.tar.gz",
});
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.15.8",
undefined,
"token",
);
expect(mockValidateChecksum).toHaveBeenCalledWith(
undefined,
"/tmp/downloaded",
"x86_64",
"unknown-linux-gnu",
"0.15.8",
);
});
it("rewrites GitHub Releases URLs to the Astral mirror", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "abc123",
downloadUrl:
"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-x86_64-unknown-linux-gnu.tar.gz",
});
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.15.8",
undefined,
"token",
);
expect(mockDownloadTool).toHaveBeenCalledWith(
"https://releases.astral.sh/github/ruff/releases/download/0.15.8/ruff-x86_64-unknown-linux-gnu.tar.gz",
undefined,
undefined,
);
});
it("does not rewrite non-GitHub URLs", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "abc123",
downloadUrl: "https://example.com/ruff.tar.gz",
});
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.15.8",
undefined,
"token",
);
expect(mockDownloadTool).toHaveBeenCalledWith(
"https://example.com/ruff.tar.gz",
undefined,
undefined,
);
});
it("falls back to GitHub Releases when the mirror download fails", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "abc123",
downloadUrl:
"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-x86_64-unknown-linux-gnu.tar.gz",
});
mockDownloadTool
.mockRejectedValueOnce(new Error("mirror unavailable"))
.mockResolvedValueOnce("/tmp/downloaded");
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.15.8",
undefined,
"token",
);
expect(mockDownloadTool).toHaveBeenCalledTimes(2);
expect(mockDownloadTool).toHaveBeenNthCalledWith(
1,
"https://releases.astral.sh/github/ruff/releases/download/0.15.8/ruff-x86_64-unknown-linux-gnu.tar.gz",
undefined,
undefined,
);
expect(mockDownloadTool).toHaveBeenNthCalledWith(
2,
"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-x86_64-unknown-linux-gnu.tar.gz",
undefined,
"token",
);
expect(mockWarning).toHaveBeenCalledWith(
"Failed to download from mirror, falling back to GitHub Releases: mirror unavailable",
);
});
it("falls back to the canonical old GitHub Releases URL", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "abc123",
downloadUrl:
"https://github.com/astral-sh/ruff/releases/download/0.4.7/ruff-x86_64-unknown-linux-gnu.tar.gz",
});
mockDownloadTool
.mockRejectedValueOnce(new Error("mirror unavailable"))
.mockResolvedValueOnce("/tmp/downloaded");
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.4.7",
undefined,
"token",
);
expect(mockDownloadTool).toHaveBeenNthCalledWith(
2,
"https://github.com/astral-sh/ruff/releases/download/v0.4.7/ruff-0.4.7-x86_64-unknown-linux-gnu.tar.gz",
undefined,
"token",
);
});
it("does not fall back when checksum validation fails", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "abc123",
downloadUrl:
"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-x86_64-unknown-linux-gnu.tar.gz",
});
mockValidateChecksum.mockRejectedValue(new Error("bad checksum"));
await expect(
downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.15.8",
undefined,
"token",
),
).rejects.toThrow("bad checksum");
expect(mockDownloadTool).toHaveBeenCalledTimes(1);
expect(mockWarning).not.toHaveBeenCalled();
});
it("does not fall back when extraction fails", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "abc123",
downloadUrl:
"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-x86_64-unknown-linux-gnu.tar.gz",
});
mockExtractTar.mockRejectedValue(new Error("extract failed"));
await expect(
downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.15.8",
undefined,
"token",
),
).rejects.toThrow("extract failed");
expect(mockDownloadTool).toHaveBeenCalledTimes(1);
expect(mockWarning).not.toHaveBeenCalled();
});
it("does not fall back for non-GitHub URLs", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "abc123",
downloadUrl: "https://example.com/ruff.tar.gz",
});
mockDownloadTool.mockRejectedValue(new Error("download failed"));
await expect(
downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.15.8",
undefined,
"token",
),
).rejects.toThrow("download failed");
expect(mockDownloadTool).toHaveBeenCalledTimes(1);
});
it("uses manifest-file checksum metadata when checksum input is unset", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "manifest-checksum",
downloadUrl: "https://example.com/custom-ruff.tar.gz",
});
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.15.8",
"",
"token",
"https://example.com/custom.ndjson",
);
expect(mockValidateChecksum).toHaveBeenCalledWith(
"manifest-checksum",
"/tmp/downloaded",
"x86_64",
"unknown-linux-gnu",
"0.15.8",
);
});
it("prefers checksum input over manifest-file checksum metadata", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "manifest-checksum",
downloadUrl: "https://example.com/custom-ruff.tar.gz",
});
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.15.8",
"user-checksum",
"token",
"https://example.com/custom.ndjson",
);
expect(mockValidateChecksum).toHaveBeenCalledWith(
"user-checksum",
"/tmp/downloaded",
"x86_64",
"unknown-linux-gnu",
"0.15.8",
);
});
it("preserves tar extraction behavior for newer versions", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "abc123",
downloadUrl: "https://example.com/ruff.tar.gz",
});
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.15.8",
"user-checksum",
"token",
);
expect(mockExtractTar).toHaveBeenCalledWith("/tmp/downloaded");
expect(mockCacheDir).toHaveBeenCalledWith(
"/tmp/extracted/ruff-x86_64-unknown-linux-gnu",
"ruff",
"0.15.8",
"x86_64",
);
});
it("preserves tar extraction behavior for older versions", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "tar.gz",
checksum: "abc123",
downloadUrl: "https://example.com/ruff.tar.gz",
});
await downloadVersion(
"unknown-linux-gnu",
"x86_64",
"0.4.10",
undefined,
"token",
);
expect(mockCacheDir).toHaveBeenCalledWith(
"/tmp/extracted",
"ruff",
"0.4.10",
"x86_64",
);
});
it("preserves zip extraction behavior on Windows", async () => {
mockGetArtifact.mockResolvedValue({
archiveFormat: "zip",
checksum: "abc123",
downloadUrl: "https://example.com/ruff.zip",
});
await downloadVersion(
"pc-windows-msvc",
"x86_64",
"0.15.8",
undefined,
"token",
);
expect(mockCopyFile).toHaveBeenCalledWith(
"/tmp/downloaded",
"/tmp/downloaded.zip",
);
expect(mockExtractZip).toHaveBeenCalledWith("/tmp/downloaded.zip");
expect(mockCacheDir).toHaveBeenCalledWith(
"/tmp/extracted",
"ruff",
"0.15.8",
"x86_64",
);
});
});
describe("rewriteToMirror", () => {
it("rewrites a GitHub Releases URL to the Astral mirror", () => {
expect(
rewriteToMirror(
"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-x86_64-unknown-linux-gnu.tar.gz",
),
).toBe(
"https://releases.astral.sh/github/ruff/releases/download/0.15.8/ruff-x86_64-unknown-linux-gnu.tar.gz",
);
});
it("returns undefined for non-GitHub URLs", () => {
expect(
rewriteToMirror("https://example.com/ruff.tar.gz"),
).toBeUndefined();
});
it("returns undefined for a different GitHub repo", () => {
expect(
rewriteToMirror(
"https://github.com/other/repo/releases/download/v1.0/file.tar.gz",
),
).toBeUndefined();
});
});
});
-196
View File
@@ -1,196 +0,0 @@
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
const mockFetch = jest.fn();
jest.unstable_mockModule("@actions/core", () => ({
debug: jest.fn(),
info: jest.fn(),
}));
jest.unstable_mockModule("../../src/utils/fetch", () => ({
fetch: mockFetch,
}));
const {
clearManifestCache,
fetchManifest,
getAllVersions,
getArtifact,
getLatestVersion,
parseManifest,
} = await import("../../src/download/manifest");
const sampleManifestResponse = `{"version":"0.15.8","artifacts":[{"platform":"aarch64-apple-darwin","variant":"default","url":"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-aarch64-apple-darwin.tar.gz","archive_format":"tar.gz","sha256":"fcf0a9ea6599c6ae28a4c854ac6da76f2c889354d7c36ce136ef071f7ab9721f"},{"platform":"x86_64-pc-windows-msvc","variant":"default","url":"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-x86_64-pc-windows-msvc.zip","archive_format":"zip","sha256":"eb02fd95d8e0eed462b4a67ecdd320d865b38c560bffcda9a0b87ec944bdf036"}]}
{"version":"0.15.7","artifacts":[{"platform":"aarch64-apple-darwin","variant":"default","url":"https://github.com/astral-sh/ruff/releases/download/0.15.7/ruff-aarch64-apple-darwin.tar.gz","archive_format":"tar.gz","sha256":"606b3c6949d971709f2526fa0d9f0fd23ccf60e09f117999b406b424af18a6a6"}]}`;
const multiVariantManifestResponse = `{"version":"0.15.8","artifacts":[{"platform":"aarch64-apple-darwin","variant":"python-managed","url":"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-aarch64-apple-darwin-managed.tar.gz","archive_format":"tar.gz","sha256":"managed-checksum"},{"platform":"aarch64-apple-darwin","variant":"default","url":"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-aarch64-apple-darwin.zip","archive_format":"zip","sha256":"default-checksum"}]}`;
const oldVersionManifestResponse = `{"version":"v0.4.7","artifacts":[{"platform":"0.4.7-aarch64-apple-darwin","variant":"default","url":"https://github.com/astral-sh/ruff/releases/download/v0.4.7/ruff-0.4.7-aarch64-apple-darwin.tar.gz","archive_format":"tar.gz","sha256":"old-checksum"}]}`;
function createMockResponse(
ok: boolean,
status: number,
statusText: string,
data: string,
) {
return {
ok,
status,
statusText,
text: async () => data,
};
}
describe("manifest", () => {
beforeEach(() => {
clearManifestCache();
mockFetch.mockReset();
});
describe("fetchManifest", () => {
it("fetches and parses manifest data", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleManifestResponse),
);
const versions = await fetchManifest();
expect(versions).toHaveLength(2);
expect(versions[0]?.version).toBe("0.15.8");
expect(versions[1]?.version).toBe("0.15.7");
});
it("throws on a failed fetch", async () => {
mockFetch.mockResolvedValue(
createMockResponse(false, 500, "Internal Server Error", ""),
);
await expect(fetchManifest()).rejects.toThrow(
"Failed to fetch manifest data: 500 Internal Server Error",
);
});
it("caches results per URL", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleManifestResponse),
);
await fetchManifest("https://example.com/custom.ndjson");
await fetchManifest("https://example.com/custom.ndjson");
expect(mockFetch).toHaveBeenCalledTimes(1);
});
});
describe("getAllVersions", () => {
it("returns all version strings", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleManifestResponse),
);
const versions = await getAllVersions(
"https://example.com/custom.ndjson",
);
expect(versions).toEqual(["0.15.8", "0.15.7"]);
});
});
describe("getLatestVersion", () => {
it("returns the first version string", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleManifestResponse),
);
await expect(
getLatestVersion("https://example.com/custom.ndjson"),
).resolves.toBe("0.15.8");
});
});
describe("getArtifact", () => {
beforeEach(() => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", sampleManifestResponse),
);
});
it("finds an artifact by version and platform", async () => {
const artifact = await getArtifact("0.15.8", "aarch64", "apple-darwin");
expect(artifact).toEqual({
archiveFormat: "tar.gz",
checksum:
"fcf0a9ea6599c6ae28a4c854ac6da76f2c889354d7c36ce136ef071f7ab9721f",
downloadUrl:
"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-aarch64-apple-darwin.tar.gz",
});
});
it("finds a windows artifact", async () => {
const artifact = await getArtifact("0.15.8", "x86_64", "pc-windows-msvc");
expect(artifact).toEqual({
archiveFormat: "zip",
checksum:
"eb02fd95d8e0eed462b4a67ecdd320d865b38c560bffcda9a0b87ec944bdf036",
downloadUrl:
"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-x86_64-pc-windows-msvc.zip",
});
});
it("prefers the default variant when multiple artifacts share a platform", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", multiVariantManifestResponse),
);
const artifact = await getArtifact("0.15.8", "aarch64", "apple-darwin");
expect(artifact).toEqual({
archiveFormat: "zip",
checksum: "default-checksum",
downloadUrl:
"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-aarch64-apple-darwin.zip",
});
});
it("finds an old artifact when the manifest version has a v prefix", async () => {
mockFetch.mockResolvedValue(
createMockResponse(true, 200, "OK", oldVersionManifestResponse),
);
const artifact = await getArtifact("0.4.7", "aarch64", "apple-darwin");
expect(artifact).toEqual({
archiveFormat: "tar.gz",
checksum: "old-checksum",
downloadUrl:
"https://github.com/astral-sh/ruff/releases/download/v0.4.7/ruff-0.4.7-aarch64-apple-darwin.tar.gz",
});
});
it("returns undefined for an unknown version", async () => {
const artifact = await getArtifact("0.0.1", "aarch64", "apple-darwin");
expect(artifact).toBeUndefined();
});
it("returns undefined for an unknown platform", async () => {
const artifact = await getArtifact(
"0.15.8",
"aarch64",
"unknown-linux-musl",
);
expect(artifact).toBeUndefined();
});
});
describe("parseManifest", () => {
it("throws for malformed manifest data", () => {
expect(() => parseManifest('{"version":"0.1.0"', "test-source")).toThrow(
"Failed to parse manifest data from test-source",
);
});
});
});
@@ -1,13 +0,0 @@
[project]
name = "parent-config-project"
version = "0.1.0"
description = "Test fixture for parent directory pyproject.toml search"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"ruff==0.10.0",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
@@ -1,3 +0,0 @@
"""Hello world example."""
print("Hello, world!")
@@ -1,15 +0,0 @@
[project]
name = "pyproject-dependency-groups-with-env-marker"
version = "0.1.0"
description = "Test fixture for issue #256 - environment markers"
readme = "README.md"
requires-python = ">=3.12"
[dependency-groups]
dev = [
"ruff~=0.13 ; python_version >= '3.11'",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
@@ -1,2 +0,0 @@
ruff==0.9.0 \
--hash=sha256:badc0de
-122
View File
@@ -1,122 +0,0 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
const debug = jest.fn();
const info = jest.fn();
jest.unstable_mockModule("@actions/core", () => ({
debug,
info,
}));
const { findPyprojectToml } = await import("../../src/utils/pyproject-finder");
const testFilePath = fileURLToPath(import.meta.url);
const testDir = path.dirname(testFilePath);
const repoRoot = path.resolve(testDir, "..", "..");
const fixturesDir = path.join(repoRoot, "__tests__", "fixtures");
describe("findPyprojectToml", () => {
beforeEach(() => {
debug.mockReset();
info.mockReset();
});
describe("when pyproject.toml exists in src directory", () => {
it("should return the exact path", () => {
const result = findPyprojectToml(fixturesDir, repoRoot);
expect(result).toContain("pyproject.toml");
expect(result).toContain("fixtures");
expect(info).toHaveBeenCalled();
});
});
describe("when pyproject.toml exists only in parent directory", () => {
it("should search upwards and find the parent's pyproject.toml", () => {
const subprojectDir = path.join(
fixturesDir,
"parent-config-project",
"subproject",
);
const result = findPyprojectToml(subprojectDir, repoRoot);
expect(result).toBeTruthy();
expect(result).toContain("pyproject.toml");
expect(result).toContain("parent-config-project");
expect(info).toHaveBeenCalled();
});
});
describe("boundary conditions", () => {
it("should stop searching at workspace root and return undefined when not found", () => {
const nodeModulesDir = path.join(repoRoot, "node_modules", "@actions");
const result = findPyprojectToml(nodeModulesDir, repoRoot);
expect(result).toBeUndefined();
expect(info).not.toHaveBeenCalledWith(
expect.stringContaining("Found pyproject.toml"),
);
});
it("should find pyproject.toml when it exists at workspace root", () => {
const parentConfigProjectDir = path.join(
fixturesDir,
"parent-config-project",
);
const subprojectDir = path.join(parentConfigProjectDir, "subproject");
const result = findPyprojectToml(subprojectDir, parentConfigProjectDir);
expect(result).toBeTruthy();
expect(result).toContain("pyproject.toml");
expect(result).toContain("parent-config-project");
});
it("should stop at workspace root even if searching from it", () => {
const result = findPyprojectToml(fixturesDir, fixturesDir);
expect(result).toBeTruthy();
expect(result).toContain("pyproject.toml");
expect(result).toContain("fixtures");
});
});
describe("edge cases", () => {
it("should handle relative paths", () => {
const result = findPyprojectToml("./__tests__/fixtures", ".");
expect(result).toBeTruthy();
expect(result).toContain("pyproject.toml");
});
it("should handle when src equals workspace root", () => {
const result = findPyprojectToml(fixturesDir, fixturesDir);
expect(result).toBeTruthy();
expect(result).toContain("pyproject.toml");
expect(result).toContain("fixtures");
});
it("should log debug messages for each checked path", () => {
const pythonProjectDir = path.join(fixturesDir, "python-project");
findPyprojectToml(pythonProjectDir, repoRoot);
expect(debug).toHaveBeenCalled();
expect(debug.mock.calls.length).toBeGreaterThan(0);
expect(debug.mock.calls[0][0]).toContain("Checking for");
expect(debug.mock.calls[0][0]).toContain("python-project");
});
it("should handle paths with trailing slashes", () => {
const result = findPyprojectToml(`${fixturesDir}/`, repoRoot);
expect(result).toBeTruthy();
expect(result).toContain("pyproject.toml");
});
});
});
-128
View File
@@ -1,128 +0,0 @@
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
const info = jest.fn();
const warning = jest.fn();
jest.unstable_mockModule("@actions/core", () => ({
debug: jest.fn(),
info,
warning,
}));
const { findRuffVersionInSpec, getRuffVersionFromFile } = await import(
"../../src/version/file-parser"
);
describe("file-parser", () => {
beforeEach(() => {
info.mockReset();
warning.mockReset();
});
describe("findRuffVersionInSpec", () => {
it("extracts version from 'ruff==0.9.3'", () => {
const result = findRuffVersionInSpec("ruff==0.9.3");
expect(result).toBe("0.9.3");
expect(info).toHaveBeenCalledWith(
"Found ruff version in requirements file: 0.9.3",
);
expect(warning).not.toHaveBeenCalled();
});
it("extracts version from 'ruff>=0.14'", () => {
const result = findRuffVersionInSpec("ruff>=0.14");
expect(result).toBe(">=0.14");
expect(warning).not.toHaveBeenCalled();
});
it("extracts version from 'ruff ~=1.0.0'", () => {
const result = findRuffVersionInSpec("ruff ~=1.0.0");
expect(result).toBe("~=1.0.0");
expect(warning).not.toHaveBeenCalled();
});
it("extracts version from 'ruff>=0.14,<1.0'", () => {
const result = findRuffVersionInSpec("ruff>=0.14,<1.0");
expect(result).toBe(">=0.14,<1.0");
expect(warning).not.toHaveBeenCalled();
});
it("extracts version from 'ruff>=0.14,<2.0,!=1.5.0'", () => {
const result = findRuffVersionInSpec("ruff>=0.14,<2.0,!=1.5.0");
expect(result).toBe(">=0.14,<2.0,!=1.5.0");
expect(warning).not.toHaveBeenCalled();
});
it("returns undefined for non-ruff dependencies", () => {
const result = findRuffVersionInSpec("another-dep==0.1.6");
expect(result).toBeUndefined();
expect(info).not.toHaveBeenCalled();
expect(warning).not.toHaveBeenCalled();
});
it("strips trailing backslash", () => {
const result = findRuffVersionInSpec("ruff==0.9.3 \\");
expect(result).toBe("0.9.3");
expect(info).toHaveBeenCalledWith(
"Found ruff version in requirements file: 0.9.3",
);
expect(warning).not.toHaveBeenCalled();
});
it("strips environment markers and warns", () => {
const result = findRuffVersionInSpec(
'ruff>=0.14 ; python_version >= "3.11"',
);
expect(result).toBe(">=0.14");
expect(info).toHaveBeenCalledWith(
"Found ruff version in requirements file: >=0.14",
);
expect(warning).toHaveBeenCalledWith(
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
);
});
it("handles whitespace", () => {
const result = findRuffVersionInSpec(" ruff >=0.14 ");
expect(result).toBe(">=0.14");
expect(warning).not.toHaveBeenCalled();
});
it("returns undefined for empty strings", () => {
const result = findRuffVersionInSpec("");
expect(result).toBeUndefined();
expect(info).not.toHaveBeenCalled();
expect(warning).not.toHaveBeenCalled();
});
});
describe("getRuffVersionFromFile", () => {
it("reads the version from requirements.txt", () => {
const result = getRuffVersionFromFile(
"__tests__/fixtures/requirements.txt",
);
expect(result).toBe("0.9.0");
});
it("reads the version from requirements files with hashes", () => {
const result = getRuffVersionFromFile(
"__tests__/fixtures/requirements-with-hash.txt",
);
expect(result).toBe("0.9.0");
});
it("reads the version from pyproject.toml dependencies", () => {
const result = getRuffVersionFromFile(
"__tests__/fixtures/pyproject.toml",
);
expect(result).toBe("0.9.3");
});
it("reads the version from Poetry dependencies", () => {
const result = getRuffVersionFromFile(
"__tests__/fixtures/pyproject-dependency-poetry-project/pyproject.toml",
);
expect(result).toBe("~0.8.2");
});
});
});
@@ -1,164 +0,0 @@
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import {
afterEach,
beforeEach,
describe,
expect,
it,
jest,
} from "@jest/globals";
const debug = jest.fn();
const info = jest.fn();
const warning = jest.fn();
jest.unstable_mockModule("@actions/core", () => ({
debug,
info,
warning,
}));
const { resolveVersionRequest } = await import(
"../../src/version/version-request-resolver"
);
const tempDirs: string[] = [];
function createTempProject(files: Record<string, string> = {}): string {
const dir = fs.mkdtempSync(
path.join(os.tmpdir(), "ruff-action-version-test-"),
);
tempDirs.push(dir);
for (const [relativePath, content] of Object.entries(files)) {
const filePath = path.join(dir, relativePath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, content);
}
return dir;
}
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
fs.rmSync(dir, { force: true, recursive: true });
}
});
describe("resolveVersionRequest", () => {
beforeEach(() => {
debug.mockReset();
info.mockReset();
warning.mockReset();
});
it("prefers explicit input over workspace discovery", () => {
const workspaceRoot = createTempProject({
"pyproject.toml": `[project]\ndependencies = ["ruff==0.5.14"]\n`,
});
const request = resolveVersionRequest({
sourceDirectory: workspaceRoot,
version: "==0.6.0",
workspaceRoot,
});
expect(request).toEqual({
source: "input",
specifier: "0.6.0",
});
});
it("uses requirements.txt when it is passed via version-file", () => {
const workspaceRoot = createTempProject({
"requirements.txt": "ruff==0.6.17\nruff-api==0.1.0\n",
});
const request = resolveVersionRequest({
sourceDirectory: workspaceRoot,
versionFile: path.join(workspaceRoot, "requirements.txt"),
workspaceRoot,
});
expect(request).toEqual({
format: "requirements",
source: "version-file",
sourcePath: path.join(workspaceRoot, "requirements.txt"),
specifier: "0.6.17",
});
});
it("warns and falls back to latest when version-file does not resolve a version", () => {
const workspaceRoot = createTempProject({
"requirements.txt": "ruff-api==0.1.0\n",
});
const request = resolveVersionRequest({
sourceDirectory: workspaceRoot,
versionFile: path.join(workspaceRoot, "requirements.txt"),
workspaceRoot,
});
expect(request).toEqual({
source: "default",
specifier: "latest",
});
expect(warning).toHaveBeenCalledWith(
`Could not parse version from ${path.join(workspaceRoot, "requirements.txt")}. Using latest version.`,
);
});
it("discovers pyproject.toml by searching upward from src", () => {
const workspaceRoot = createTempProject({
"pyproject.toml": `[project]\ndependencies = ["ruff==0.10.0"]\n`,
"subproject/nested/example.py": 'print("hello")\n',
});
const sourceDirectory = path.join(workspaceRoot, "subproject", "nested");
const request = resolveVersionRequest({
sourceDirectory,
workspaceRoot,
});
expect(request).toEqual({
format: "pyproject.toml",
source: "pyproject.toml",
sourcePath: path.join(workspaceRoot, "pyproject.toml"),
specifier: "0.10.0",
});
});
it("falls back to latest when no workspace version source is found", () => {
const workspaceRoot = createTempProject({
"subproject/example.py": 'print("hello")\n',
});
const request = resolveVersionRequest({
sourceDirectory: path.join(workspaceRoot, "subproject"),
workspaceRoot,
});
expect(request).toEqual({
source: "default",
specifier: "latest",
});
expect(info).toHaveBeenCalledWith(
"Could not find pyproject.toml. Using latest version.",
);
});
it("throws when both version and version-file are specified", () => {
const workspaceRoot = createTempProject();
expect(() =>
resolveVersionRequest({
sourceDirectory: workspaceRoot,
version: "0.6.0",
versionFile: path.join(workspaceRoot, "requirements.txt"),
workspaceRoot,
}),
).toThrow("It is not allowed to specify both version and version-file");
});
});
+6 -8
View File
@@ -11,29 +11,27 @@ inputs:
required: false required: false
default: ${{ github.workspace }} default: ${{ github.workspace }}
version: version:
description: "The version of Ruff to use, e.g., `0.6.0`. Defaults to the first discoverable version in pyproject.toml searched upward from `src`, or `latest`." description: "The version of Ruff to use, e.g., `0.6.0` Defaults to the version in pyproject.toml or 'latest'."
required: false required: false
default: "" default: ""
version-file: version-file:
description: "Path to a pyproject.toml or requirements.txt file to read the version from. If parsing fails, the action warns and falls back to `latest`." description: "Path to a pyproject.toml or requirements.txt file to read the version from."
required: false required: false
checksum: checksum:
description: "The checksum of the ruff version to install" description: "The checksum of the ruff version to install"
required: false required: false
manifest-file:
description: "URL to a custom manifest file in the astral-sh/versions format."
required: false
github-token: github-token:
description: description:
"Used for authenticated downloads of Ruff release artifacts from GitHub." "Used to increase the rate limit when retrieving versions and downloading
ruff."
required: false required: false
default: ${{ github.token }} default: ${{ github.token }}
outputs: outputs:
ruff-version: ruff-version:
description: "The installed ruff version. Useful when using latest." description: "The installed ruff version. Useful when using latest."
runs: runs:
using: "node24" using: "node20"
main: "dist/ruff-action/index.cjs" main: "dist/ruff-action/index.js"
branding: branding:
icon: "code" icon: "code"
color: "black" color: "black"
+1 -2
View File
@@ -1,5 +1,5 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.4.9/schema.json", "$schema": "https://biomejs.dev/schemas/2.1.4/schema.json",
"assist": { "assist": {
"actions": { "actions": {
"source": { "source": {
@@ -13,7 +13,6 @@
"ignoreUnknown": false, "ignoreUnknown": false,
"includes": [ "includes": [
"**", "**",
"!**/coverage",
"!**/dist", "!**/dist",
"!**/lib", "!**/lib",
"!**/node_modules", "!**/node_modules",
Generated Vendored
-29363
View File
File diff suppressed because one or more lines are too long
Generated Vendored
+39262
View File
File diff suppressed because one or more lines are too long
-29544
View File
File diff suppressed because one or more lines are too long
+37092
View File
File diff suppressed because one or more lines are too long
-12
View File
@@ -1,12 +0,0 @@
import { createDefaultEsmPreset } from "ts-jest";
const preset = createDefaultEsmPreset();
/** @type {import('jest').Config} */
export default {
...preset,
collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts"],
moduleFileExtensions: ["ts", "js", "mjs"],
testEnvironment: "node",
testMatch: ["<rootDir>/__tests__/**/*.test.ts"],
};
+112 -7845
View File
File diff suppressed because it is too large Load Diff
+17 -29
View File
@@ -2,52 +2,40 @@
"name": "ruff-action", "name": "ruff-action",
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"type": "module",
"description": "A GitHub Action to run Ruff, an extremely fast Python linter and code formatter.", "description": "A GitHub Action to run Ruff, an extremely fast Python linter and code formatter.",
"main": "dist/ruff-action/index.cjs", "main": "dist/ruff-action/index.js",
"scripts": { "scripts": {
"build": "tsc --noEmit", "build": "tsc",
"check": "biome check --write", "check": "biome check --write",
"package": "node scripts/build-dist.mjs", "package": "ncc build -o dist/ruff-action src/ruff-action.ts && ncc build -o dist/update-known-checksums src/update-known-checksums.ts",
"act": "act pull_request -W .github/workflows/test.yml --container-architecture linux/amd64 -s GITHUB_TOKEN=\"$(gh auth token)\"", "act": "act pull_request -W .github/workflows/test.yml --container-architecture linux/amd64 -s GITHUB_TOKEN=\"$(gh auth token)\"",
"update-known-checksums": "RUNNER_TEMP=known_checksums node dist/update-known-checksums/index.cjs src/download/checksum/known-checksums.ts \"$(gh auth token)\"", "update-known-checksums": "RUNNER_TEMP=known_checksums node dist/update-known-checksums/index.js src/download/checksum/known-checksums.ts \"$(gh auth token)\"",
"test:unit": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js", "all": "npm install && npm run build && npm run check && npm run package"
"test": "npm run build && npm run test:unit",
"all": "npm run build && npm run check && npm run package && npm run test:unit"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/astral-sh/ruff-action.git" "url": "git+https://github.com/astral-sh/ruff-action.git"
}, },
"keywords": [ "keywords": ["actions", "python", "ruff", "action"],
"actions",
"python",
"ruff",
"action"
],
"author": "@eifinger", "author": "@eifinger",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@actions/core": "^3.0.0", "@actions/core": "^1.11.1",
"@actions/exec": "^3.0.0", "@actions/exec": "^1.1.1",
"@actions/tool-cache": "^4.0.0", "@actions/tool-cache": "^2.0.2",
"@octokit/core": "^7.0.3", "@octokit/core": "^7.0.3",
"@octokit/plugin-paginate-rest": "^13.1.1", "@octokit/plugin-paginate-rest": "^13.1.1",
"@octokit/plugin-rest-endpoint-methods": "^16.0.0", "@octokit/plugin-rest-endpoint-methods": "^16.0.0",
"@renovatebot/pep440": "^4.2.1", "@renovatebot/pep440": "^4.1.0",
"smol-toml": "^1.6.0", "smol-toml": "^1.4.1"
"undici": "^6.24.1"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.4.7", "@biomejs/biome": "2.1.4",
"@types/jest": "^29.5.14",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/node": "^25.5.0", "@types/node": "^24.2.1",
"@types/semver": "^7.7.1", "@types/semver": "^7.7.0",
"esbuild": "^0.27.4", "@vercel/ncc": "^0.38.3",
"jest": "^30.3.0", "js-yaml": "^4.1.0",
"js-yaml": "^4.1.1", "typescript": "^5.9.2"
"ts-jest": "^29.4.6",
"typescript": "^5.9.3"
} }
} }
-41
View File
@@ -1,41 +0,0 @@
import { mkdir, rm } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { build } from "esbuild";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const repoRoot = path.resolve(__dirname, "..");
const builds = [
{
entryPoint: path.join(repoRoot, "src", "ruff-action.ts"),
outfile: path.join(repoRoot, "dist", "ruff-action", "index.cjs"),
staleOutfile: path.join(repoRoot, "dist", "ruff-action", "index.js"),
},
{
entryPoint: path.join(repoRoot, "src", "update-known-checksums.ts"),
outfile: path.join(repoRoot, "dist", "update-known-checksums", "index.cjs"),
staleOutfile: path.join(
repoRoot,
"dist",
"update-known-checksums",
"index.js",
),
},
];
await Promise.all(
builds.map(async ({ entryPoint, outfile, staleOutfile }) => {
await rm(staleOutfile, { force: true });
await mkdir(path.dirname(outfile), { recursive: true });
await build({
bundle: true,
entryPoints: [entryPoint],
format: "cjs",
outfile,
platform: "node",
target: "node24",
});
}),
);
-842
View File
@@ -1,847 +1,5 @@
// AUTOGENERATED_DO_NOT_EDIT // AUTOGENERATED_DO_NOT_EDIT
export const KNOWN_CHECKSUMS: { [key: string]: string } = { export const KNOWN_CHECKSUMS: { [key: string]: string } = {
"aarch64-apple-darwin-0.15.10":
"77c1df502dcfaaec52c6ce203b504b8554c88ab66ac01313410fa68ad9aafd5b",
"aarch64-pc-windows-msvc-0.15.10":
"1776bf104277b3fbb3b3e4b481655f492f6df10210e2e00cd94132e66e999bd4",
"aarch64-unknown-linux-gnu-0.15.10":
"b775a5a09484549ac3fd377b5ce34955cf633165169671d1c4a215c113ce15df",
"aarch64-unknown-linux-musl-0.15.10":
"84754f0e58f58fb123ea49b8d22b8ce2cc96e4046b7c1b1ed99f6af7fe76f8ef",
"arm-unknown-linux-musleabihf-0.15.10":
"e94eb3061a263217fad7219a0b20e5a746a31f44387cd21c31eb5103357dbb8c",
"armv7-unknown-linux-gnueabihf-0.15.10":
"7a348dd60f4c5563482504acabba49def8a8cd5d957b6e145a1dbdef7cdf0663",
"armv7-unknown-linux-musleabihf-0.15.10":
"8c85562d2ed09bc4edbbea151aec386e8f7e9f5499e6c5d0324fd26fe7c5dc8a",
"i686-pc-windows-msvc-0.15.10":
"a8b4132914f197d1fef5f48fd7f0f8e840546a814daf9f680109344407da79ac",
"i686-unknown-linux-gnu-0.15.10":
"6f9b23d07d90ef3ac148c8b81fc8ea37647f1241e4db18be1b0a24df43d479f8",
"i686-unknown-linux-musl-0.15.10":
"63d80b9427a8299c8bf08d34621e187fe5fe5f696f36b54635e00248d3ca7e5f",
"powerpc64le-unknown-linux-gnu-0.15.10":
"49cfcb83828844f61e00c643dd81182a015140b9bf58cee5b115f705e99749e6",
"riscv64gc-unknown-linux-gnu-0.15.10":
"8d7eeef9d9abd9e88b92d9420021be5fbd1dbd60e10ef7f989060213cd68c4c7",
"s390x-unknown-linux-gnu-0.15.10":
"80056787672af7b4e9217afdd666a8c19eebbe5eb2fe626a6a9a779ea6778a1b",
"x86_64-apple-darwin-0.15.10":
"7210e06196de876771cc0bad0f1d57678e709d039f184b491fdaa600d6a95a5e",
"x86_64-pc-windows-msvc-0.15.10":
"6f8f9a445102107ee3c0a05c8f386bacb32238199ecbc0983b9b06c5ea3d7c5e",
"x86_64-unknown-linux-gnu-0.15.10":
"e3e9e5c791542f00d95edc74a506e1ac24efc0af9574de01ab338187bf1ff9f6",
"x86_64-unknown-linux-musl-0.15.10":
"8b0a16bae81e371c9b6176a27fdb9db1deaa04c4cfe87e8604a898cc31686500",
"aarch64-apple-darwin-0.15.9":
"013d878f17c625550e4a6b19235c22fc229639f66f563bb72cb2c896aeca11e8",
"aarch64-pc-windows-msvc-0.15.9":
"813c3b9cf0c01ef913bfbc8d2dd17e140a8c3d54ae1b8f8c20ac53e2871674f0",
"aarch64-unknown-linux-gnu-0.15.9":
"ea71b14433318bed364e0dbb04203e57027cf134ab909d5e452be28d87d0fd08",
"aarch64-unknown-linux-musl-0.15.9":
"e017dd0c1fd7475aaddc49bde8cddcee3c27d42f6ce139a96df0c1022e06d85b",
"arm-unknown-linux-musleabihf-0.15.9":
"593382b4b5271cf63b8ea9972c7475a299f341ce8a9c22127ce1f8b7d82fcfe1",
"armv7-unknown-linux-gnueabihf-0.15.9":
"3c12ad897c62954fdc6a5c0f7baddd06f1cb37f1987618fa243ebd00a7b67d86",
"armv7-unknown-linux-musleabihf-0.15.9":
"9d9ebfe0075c7a6411f771ba79e210a3dffc4ba9706b542c5db72dc39d922893",
"i686-pc-windows-msvc-0.15.9":
"46cdcacb4522e19a26a18d586abb6addc68b4254bea958e93a347d7566add1c3",
"i686-unknown-linux-gnu-0.15.9":
"c04ddaa542d36b0483e325d29b00520242cf6b4e78e4cea0b73f0c74c77459ef",
"i686-unknown-linux-musl-0.15.9":
"97145060b15819b7d31df7c3fd160b02397e89c40069d485915ce6fbe92cb769",
"powerpc64le-unknown-linux-gnu-0.15.9":
"56e522a316427281d590aff0bcece176aedb15e9329799c1ada5cd8fd5e17b71",
"riscv64gc-unknown-linux-gnu-0.15.9":
"e0499cfd557515133118cc4dafb65a805c9656833a66b2fddaee9a9f8e847de4",
"s390x-unknown-linux-gnu-0.15.9":
"7402b9bdb1aaa79387f5b702696ec75d9ebed26f0a8e5097fc58924418c92097",
"x86_64-apple-darwin-0.15.9":
"7e0fe9daba25848f85cb3d43e47ecd7d23f14e92e8799f92c1bcd8319a4ce4f8",
"x86_64-pc-windows-msvc-0.15.9":
"e38fddd19805bc8f7329003c2abdaf49d8ca9e5bc0c6702e8472e16f127bcd44",
"x86_64-unknown-linux-gnu-0.15.9":
"223ce40fbea2245b0a650abf9f5093a6009b56a04e5e63c036f446cab328dcf7",
"x86_64-unknown-linux-musl-0.15.9":
"e30e6e50dbf925b42335f28e2fa296d404294f294159b314dca47b88317fc477",
"aarch64-apple-darwin-0.15.8":
"94fc061f928c8f2b04c4b3a98aad2b1b04f38b4c808839bc5b33a2f0a63a47a3",
"aarch64-pc-windows-msvc-0.15.8":
"5e2941bff2f14fa9b48532bba67d1bbeec2c26d5fecc5a4bc0f76d813ad644dc",
"aarch64-unknown-linux-gnu-0.15.8":
"7df2a2c86f1017936d8ce7b74d451ed05f2c648af8cf89add7ac0e4f3635f386",
"aarch64-unknown-linux-musl-0.15.8":
"15e6a6c21696bbe59c56d0f1c437452b960bcdfe81ecc3bc19fa89e6a7d70eb6",
"arm-unknown-linux-musleabihf-0.15.8":
"49847e5d218aa17da9be67df543d7c635e67558356e67406f97db0742776ed6b",
"armv7-unknown-linux-gnueabihf-0.15.8":
"4116ec65accfc2463a30720407f239339f72b1e40a66b341a9450651f3b43976",
"armv7-unknown-linux-musleabihf-0.15.8":
"e35ad1094bc8ef40b4d60fd32dbc6b129c4d9d4283f7ac3c838189f614c81d83",
"i686-pc-windows-msvc-0.15.8":
"01fd5224726810986121e1618602f2c24332a6abd2476d37c73d695be36679a7",
"i686-unknown-linux-gnu-0.15.8":
"66bf0839b384700624d946390ceb4493a787b5e56b93dd17795e2d5111db01f7",
"i686-unknown-linux-musl-0.15.8":
"098040cc3fdcb01efffbefa83bad0ccf4ecbcc00b7d0b5489f405d633fad1d5a",
"powerpc64le-unknown-linux-gnu-0.15.8":
"b3664a2793b002ef65888eb2818d449ede3d34b16ee71f17e8f1851b3d22eb23",
"riscv64gc-unknown-linux-gnu-0.15.8":
"7557039907a61cd7f1c7bad06fd77bdc38a7b2436471fe0b58e662d78ae13964",
"s390x-unknown-linux-gnu-0.15.8":
"127a53b418e6828816e156bf7ae959f70394ed67882bf9d69bcd15f3b34c6eba",
"x86_64-apple-darwin-0.15.8":
"153d1801068df606290e832058ce2e5601584ac302788a055d0390adf6c772ce",
"x86_64-pc-windows-msvc-0.15.8":
"1e6ebd021dc9cefa8b9f15b5d6500c275ec49a0f2da968824845c34f30060c78",
"x86_64-unknown-linux-gnu-0.15.8":
"45a6ed01c5b32873d4f6239f5fd6d9adde782295261b4a65962d754b3e37a849",
"x86_64-unknown-linux-musl-0.15.8":
"d541beae99d550ed4abb3a1d026b907886c7cdf44a533b24624871e3d8c81330",
"aarch64-apple-darwin-0.15.7":
"e573cdb504fce521af501cc16b7018fb6560ac0e7af5d05056c942b3a1ad5a79",
"aarch64-pc-windows-msvc-0.15.7":
"beb2eb063e52f197694fb79045cef276735a7becbbd8f8f79e1c99613a12d7e7",
"aarch64-unknown-linux-gnu-0.15.7":
"aee72470734c4220d367b269c3e901b7134485ecc70fe8635ff8141f09d9d11f",
"aarch64-unknown-linux-musl-0.15.7":
"a6e07403e1a2287ba87676e1e9fd3c6f392d2042ed4eb0ca83cda9d549926bfa",
"arm-unknown-linux-musleabihf-0.15.7":
"d5f527f70067a4cfaa7218383781ee5e0a5dcc0bbe9122c4fa8f6d1a88a991f8",
"armv7-unknown-linux-gnueabihf-0.15.7":
"0daac568f418577bc5e31c6c5d53f590c99dc42b740310a627ba0dc99dfcfe66",
"armv7-unknown-linux-musleabihf-0.15.7":
"9fbc574fcc28e104d0da617ab2655c904185f990660029483897aa76d2e60cea",
"i686-pc-windows-msvc-0.15.7":
"d6e02b3f65c29c64f443d7f0ebeb3a5a08b12fccfb3325d7ba2e026dedd2855b",
"i686-unknown-linux-gnu-0.15.7":
"18aa2cb27a5e383574189bf1983c9fdb9b1c6b9c1331f90cf03a5b0032c8325b",
"i686-unknown-linux-musl-0.15.7":
"02b6f4a2f6abf35e8e657e579e91718fbb36cf75e8f3095f13908d3d33dc4e66",
"powerpc64le-unknown-linux-gnu-0.15.7":
"ab153c03e331accb127098a8467e415524d8b2035a277f250e0b50908588bcf4",
"riscv64gc-unknown-linux-gnu-0.15.7":
"cf71700aed80851a8d32cd663e00d22b6a3eb03e17e057e0a63c7aaff4f20c57",
"s390x-unknown-linux-gnu-0.15.7":
"c854a5cf6537eeaae380d1b4515a9f09a8ee2da45ed71cc230b39b0b6d18638c",
"x86_64-apple-darwin-0.15.7":
"2cff55e19e759addc871a8ed13cca38a3ad576d483fd2092b8072bad972ecfa0",
"x86_64-pc-windows-msvc-0.15.7":
"2971a4d16a6b20efc2e51c1f4290ae5d4a85935bb964eb298c9b73f682160379",
"x86_64-unknown-linux-gnu-0.15.7":
"2253ba7f064023def4b77aaef127756d3724e92a0fc69d666d4692f5c019af6d",
"x86_64-unknown-linux-musl-0.15.7":
"a70f33ff907cbd05cd3fdcdd3c5dde828123295aaf46b4011e82dce8f806c3cc",
"aarch64-apple-darwin-0.15.6":
"e90a351ddc2e5e411168b5cbfc7d694231e793948d0a29aa82618ea6f17225f1",
"aarch64-pc-windows-msvc-0.15.6":
"5e5e28e52fd3246f89d5b46e168b485cb9cdd76cda95b5a46a5d130d7bd85afa",
"aarch64-unknown-linux-gnu-0.15.6":
"b9183a35a2693941e1089a5a80fd45724df886c135beebb0f8f1e103704af25e",
"aarch64-unknown-linux-musl-0.15.6":
"750164384c78e208b5e5bf764916f23270844c6a6ea0057dec1f7951bce02fca",
"arm-unknown-linux-musleabihf-0.15.6":
"8553be3deedfdcecda058bb1e12e06d71ad56a35cb5b1e5a935a71c80f3f2001",
"armv7-unknown-linux-gnueabihf-0.15.6":
"f778861adf8448f07c12100d7d058b729937c282319c1bbc396864ba658daa77",
"armv7-unknown-linux-musleabihf-0.15.6":
"db109618d4472d103582428ded89a744c3b6aca56568dbde105eb1e792a06404",
"i686-pc-windows-msvc-0.15.6":
"5d964d1001a685ecb0ca7d25d0a0584535d4e911140f2f795cfb400711d58f05",
"i686-unknown-linux-gnu-0.15.6":
"169690de71a2ef6fc2fdea5347f2309d2b016fda4b5d445ce7fb2c9a9f6375a1",
"i686-unknown-linux-musl-0.15.6":
"84284d143429d886c9a8d38d44770f5f64cf3486ac497df16ac0e90e35bceaa4",
"powerpc64le-unknown-linux-gnu-0.15.6":
"c6322e95b105fa14728a68993fdab25591a1eb0e9de0c5bdb35081c0bec7bfda",
"riscv64gc-unknown-linux-gnu-0.15.6":
"7b714890a8de0af70f6e48d4d37b33808d57d343c723a136bf0a68fa16e2c1ab",
"s390x-unknown-linux-gnu-0.15.6":
"c5f7ac20b04fe31471ee255d8ea27b77b9e4b29e2697492460c977f5e9ff8b24",
"x86_64-apple-darwin-0.15.6":
"aef752bd803e58a732651d7ecd8434288f2ce7f252e415ecfa5b4a1518aea218",
"x86_64-pc-windows-msvc-0.15.6":
"34548ff94a9f52c96c75672d34eee6068a4bf4fdcb3ca12d10e9f4b8abedad96",
"x86_64-unknown-linux-gnu-0.15.6":
"c253b106eb136f9cb4a319b5e3b4a9df78ec31fe15a3822efb69e7887ef9166e",
"x86_64-unknown-linux-musl-0.15.6":
"7ca0d590da2274429e8033cdaae1d923229ad19b2df7d8c2c3b94451527ab45c",
"aarch64-apple-darwin-0.15.5":
"f39400f9504c940fa4eb46b02e2d4889a9f86b81ce4c57bda02ea3568894d094",
"aarch64-pc-windows-msvc-0.15.5":
"c2d4ddc435005768f2cc2c87651474d07c20b9f18150a772b56485966840f7cf",
"aarch64-unknown-linux-gnu-0.15.5":
"cd01e3c9df2d6c1324744883a796e6693642494f2f6222a4f158da36f84908ce",
"aarch64-unknown-linux-musl-0.15.5":
"ae22fb3b6ad85cff59abf147d572d266397f42b73e51e6d55dba56fb3430fe1d",
"arm-unknown-linux-musleabihf-0.15.5":
"a3480f0daefa03518b66fd038cf8461348cbc8efe5e29dc01bd68c89e98f9c15",
"armv7-unknown-linux-gnueabihf-0.15.5":
"cb8cbe66b2a5face3bc5a94e565ffba313e428f75df4cd62ef84beb164dc83e4",
"armv7-unknown-linux-musleabihf-0.15.5":
"adeb46fa64c7434503c9958a99a84b3f523ce43f9a1233b54008137101e1c348",
"i686-pc-windows-msvc-0.15.5":
"aa23d64f605deaa39d0fdccd7b63a4cf12f447013b8791deb79c236dc75364b9",
"i686-unknown-linux-gnu-0.15.5":
"de7fa298d723016be9a8b025524bfa13faa7f91d36472fdeb4fbc1646be4327e",
"i686-unknown-linux-musl-0.15.5":
"96398e8a459cd5960aa654dcd0fb158e80cf91f5b1684dad1ee1582158e92683",
"powerpc64le-unknown-linux-gnu-0.15.5":
"c4b959cc3190bba098596435252ce300bf9e4a4650c7d5f7ebfc94ef0ac8f1ba",
"riscv64gc-unknown-linux-gnu-0.15.5":
"1a719ae01affd85902322878cb942ce13112fc32c5463146e1613752b47ba538",
"s390x-unknown-linux-gnu-0.15.5":
"590c125b598fcc84182627d46bd1de22380c95825eea5fbf15b701adbb6c3557",
"x86_64-apple-darwin-0.15.5":
"1e01b0d6354f14dac0f4de7a89488d18e00f8aa37ce3635b55bcaf1357b2a375",
"x86_64-pc-windows-msvc-0.15.5":
"9b71926a755bdefa151b7579cd5e19840e9df0ab2828c762241f1b773882d939",
"x86_64-unknown-linux-gnu-0.15.5":
"ac0336520fd986b3bd7c510b1f9d3049e0d150ddaab754cfa5c5eaf8cbb840ce",
"x86_64-unknown-linux-musl-0.15.5":
"da9b5c8ba7a789fe3bcf6287ea58ccbac9328a711b7674681706106e7580a836",
"aarch64-apple-darwin-0.15.4":
"2d63cc9fd12c9cc3b524563bbeb50470cf3f68f3194002228a417a53a2a56164",
"aarch64-pc-windows-msvc-0.15.4":
"7de874b0d667fe04c2cd15629c19baff6dfff55e1fd99dfb14cb9850b09e7a20",
"aarch64-unknown-linux-gnu-0.15.4":
"7e436cedadb1bac0166448b05c0b5d69bb1d7879b0b26696bfc198ebdffb7b2f",
"aarch64-unknown-linux-musl-0.15.4":
"f34909dacbebaf3773ececc0d321e0fa0599729e9b4570f1dbcec91b4c435913",
"arm-unknown-linux-musleabihf-0.15.4":
"c6b3dfa714d8f2b27db1339b9730d2ff41cff513069c2980870dc1db0c8e6267",
"armv7-unknown-linux-gnueabihf-0.15.4":
"98c3f14394f9300a66c5415b7daf71959fc72be40ad5654cd5f90e9049eeb328",
"armv7-unknown-linux-musleabihf-0.15.4":
"1469ec15b7c7a918bc2f34ece38549f5ab77b535c65e1fe983ec167f6afa9966",
"i686-pc-windows-msvc-0.15.4":
"6c3090b5724dd664b742165fb2dc92b1a8e3c19e84122dab1d2e121982fe1c62",
"i686-unknown-linux-gnu-0.15.4":
"db95fc72c326f8712a02bdf980f42cccd9745ed2de2f6a8ae02f5be123691707",
"i686-unknown-linux-musl-0.15.4":
"e54933995834a628dbaeeec26ec96ef506cbeb673c91666f08be36fd07658503",
"powerpc64le-unknown-linux-gnu-0.15.4":
"b7c3eb76a448e47df049ec721af91c5f5972f9dc6583c3bc6583a9d29c932a2b",
"riscv64gc-unknown-linux-gnu-0.15.4":
"7f1816df2f56e606a17d39c405295ede5c068566630e9112964ffd3977050d0a",
"s390x-unknown-linux-gnu-0.15.4":
"934a870f75f006fc9c151f9d65d604bb78ecf8ca8c3f3b9eba2b3c1464116808",
"x86_64-apple-darwin-0.15.4":
"f40e16784c867b60850fbe96a2cccd123589c90d6db71ad8ade62efdeabccc84",
"x86_64-pc-windows-msvc-0.15.4":
"ca4db783ce3a1b942e67aa4002ca9f3c6ff1b150a85cb4ca1345c4299ad12a0f",
"x86_64-unknown-linux-gnu-0.15.4":
"6e24501f753416bc84456383ccf62239889ab9fec8318549db9bee791612bd85",
"x86_64-unknown-linux-musl-0.15.4":
"e39111195ef761569773562209bbb7f943c834c961f2c1ed28e2126a15c4cd35",
"aarch64-apple-darwin-0.15.3":
"9135019481619b3d2d797784ed422cd8622d91ea14b0dfe5c7ebe177a98fabb6",
"aarch64-pc-windows-msvc-0.15.3":
"48716216a212f5555b8cd74f7b6c17e10baf4f4859309d978c347cebd285ea6f",
"aarch64-unknown-linux-gnu-0.15.3":
"14a4eedcf55d998c317b02670293f7a9fa6d55f7263951358f49e6a0be0121a3",
"aarch64-unknown-linux-musl-0.15.3":
"dea1c50f06820f27b34a56b3f358522df72b197e3d6d935c0d591562b7d8ceef",
"arm-unknown-linux-musleabihf-0.15.3":
"9b095c3207272b2fc8ec3426b7efee310e2c205dd98dcf9170ccdfb43c4b7ff7",
"armv7-unknown-linux-gnueabihf-0.15.3":
"28f0afb652685f810b9ec016b9f215cb0248614f2215c6d4cc7dae408332bfd3",
"armv7-unknown-linux-musleabihf-0.15.3":
"a5d0c2d46bcfa1636cd74f707220e2f9bc64b2ef2148107e9e99e31a451aa22f",
"i686-pc-windows-msvc-0.15.3":
"4c2ff5518c9b36493e6f7e3e6b804222f35bce9b8d22a9d8efb4c3a2e9d96678",
"i686-unknown-linux-gnu-0.15.3":
"9b7a248eb5e9e975cedba6d5abfed63cdb0325b83a7d93a0a4ee24092175e2b2",
"i686-unknown-linux-musl-0.15.3":
"c47c97dd8f1fdc8a2614db2bb677a72e4f2257f36edf069f97cf082239d036b2",
"powerpc64le-unknown-linux-gnu-0.15.3":
"4e11a1da6cad901dbd02878808d7f03de857e8e27589b35b3e823775382c7983",
"riscv64gc-unknown-linux-gnu-0.15.3":
"db8cffb6bb7ed3d299d0d4fe38bc4b53dc8ee04e5c70ddba4b41a130209ee72f",
"s390x-unknown-linux-gnu-0.15.3":
"16dd01475bdfc2ce4f254570415a04c8803401181d165be8b8be3aea6e6c8cd9",
"x86_64-apple-darwin-0.15.3":
"890958c88c244902171878209bc91d237d87f55518fb5e3f40ab76a5bb36e4bb",
"x86_64-pc-windows-msvc-0.15.3":
"dcef126c1d8aa26545149b2e3b43999100b927e4cdd498263669c506982c796d",
"x86_64-unknown-linux-gnu-0.15.3":
"49eda8819c92b862879b43fa506f5084a9dd2ce0376feed3ffa42a07648cd58f",
"x86_64-unknown-linux-musl-0.15.3":
"c3bbd085bc0a1438fccc912bf3b25a390fcf9f2bb46dbe67491a9589bff618ee",
"aarch64-apple-darwin-0.15.2":
"59a3a08a077e81d0fd99566604556687b834edb2da34a69522cfb5168a07123b",
"aarch64-pc-windows-msvc-0.15.2":
"8cbc83ae19c8653aa7d2127dc70e20730b0c93cb4cd9f1284c1d3973390c253d",
"aarch64-unknown-linux-gnu-0.15.2":
"4bef1a53089b3ae92cb271d3fd170cfb4834eff7c738d4faae379bb872b9a8e6",
"aarch64-unknown-linux-musl-0.15.2":
"b1417ad2977d38c93a40cc77b467b4c68d6b5578031852c38033f9b53b26a543",
"arm-unknown-linux-musleabihf-0.15.2":
"0762e6914f18be4717036b35926763bd36536cd4dc1a22a4e34bbb7fabdb5bce",
"armv7-unknown-linux-gnueabihf-0.15.2":
"16e5bc1787cad23396df645317267f55495c1d08c2878dc7e3c079042901f95c",
"armv7-unknown-linux-musleabihf-0.15.2":
"a31f545501d9693162c7d60afcb8362c72a307effb5fcbfbc418e304775e4538",
"i686-pc-windows-msvc-0.15.2":
"52110333dbf636948facf3a6fd2889acbc658258ead37f3185d0a5fbee925ba9",
"i686-unknown-linux-gnu-0.15.2":
"b20d9632e412ffb24828e79ad5aed435a067e56cb382c2af529eee51c3b2791b",
"i686-unknown-linux-musl-0.15.2":
"bfba760dc9f1806bcdacd2c42f5b0c559cd3fa2771429214d9797d1dd9f22d61",
"powerpc64le-unknown-linux-gnu-0.15.2":
"cf83cabdab0361b3f2707f548a23cc13d018d2e12893cf65b4e438d0882d691f",
"riscv64gc-unknown-linux-gnu-0.15.2":
"3e151c16226fe40db56d838fc90aee3bf7bf9a437fb1d8fdc1efb285899a37a8",
"s390x-unknown-linux-gnu-0.15.2":
"9d913855d0c4924df440f28961e49f8e8f460d6a88b7e4ee307f4cb80e0f3c8a",
"x86_64-apple-darwin-0.15.2":
"78702adcccc2309696f19442f18b5fbe6a4bf4211efa157576c2b5f498a4cc0f",
"x86_64-pc-windows-msvc-0.15.2":
"82a797f212d2e0c16e6ded37e3768c82af3a8b25d49887fafa56f6417f7fb5a9",
"x86_64-unknown-linux-gnu-0.15.2":
"278b307eccb4eef6a153d811466dd8170d4fd74970cc4a44c793b40bd897e403",
"x86_64-unknown-linux-musl-0.15.2":
"2b11788c9457ba8350f9b55bc302adf7f440d2f92a1d9660cbc3b20b6abf5e1c",
"aarch64-apple-darwin-0.15.1":
"196f6d4bd380f4a03f9d2d3bcfe17b991145a110f5fc9f5999521cd5e5335e1c",
"aarch64-pc-windows-msvc-0.15.1":
"f29b448a5a3648f4aae87ed1c778f0d9b2cccd40ec5892b2be06c1b568410829",
"aarch64-unknown-linux-gnu-0.15.1":
"003848ac89a6d2ca1a2ce4e663daec4f90212fc9fd6f338d7cd692c7c8ae9480",
"aarch64-unknown-linux-musl-0.15.1":
"05a41b8b7c068633b27e8f9149c70154ac2090e61d48e5ea9983de769593d29c",
"arm-unknown-linux-musleabihf-0.15.1":
"7101665b77808d7756fca4c4a989d23f48f42b06acfb843d98b83644ad9fe61e",
"armv7-unknown-linux-gnueabihf-0.15.1":
"8985e23e151deafb61015e03e564315805545bb59d2aa0032c28906a94dbaae4",
"armv7-unknown-linux-musleabihf-0.15.1":
"225fe8ab820c362bcea3c84cb7d60376b85c790bafa938a4ef2f2be0b4963213",
"i686-pc-windows-msvc-0.15.1":
"d9cfcea09126f510a675374f308b9b8abd7537e87ed1db3dbebf7211ed6d8fec",
"i686-unknown-linux-gnu-0.15.1":
"7a500cbb644e78d9fef68eaceeb01d224db1003d482a54e3412104bc616afc69",
"i686-unknown-linux-musl-0.15.1":
"dc55081b5eadd32cce183077862f29a693d38ff0b93992ed1144d8167fa39d94",
"powerpc64le-unknown-linux-gnu-0.15.1":
"6ccdf65f4258385b1d30e542c7eca97770f29a534619d66aa2eacce44ce6f5ce",
"riscv64gc-unknown-linux-gnu-0.15.1":
"1372c269badc8c6b8116b8871d41e2af38bb8ad11cbdcfc6e4b4a2865afc20af",
"s390x-unknown-linux-gnu-0.15.1":
"04fd2f54ac619916ada72d1566c9edbaaee4e241e01897918e0efacafa251248",
"x86_64-apple-darwin-0.15.1":
"55fd4437b4b6b0c75793525c980bb1d49d9723044edbdd7dcc962cb595d26d51",
"x86_64-pc-windows-msvc-0.15.1":
"4bd4d5bb5b3c3fa44a4c3d4748dc03f9fbd53808ff2d3adda75e50b1ec1374e2",
"x86_64-unknown-linux-gnu-0.15.1":
"f63d467b81ef1a7a8552fef001784215bb2fc4d7a7e32216c48aaa3bad066779",
"x86_64-unknown-linux-musl-0.15.1":
"c8d04b4f6ce053809d98d1ef99f897c52e6204708bb157551ede7fbab505fea2",
"aarch64-apple-darwin-0.15.0":
"093d355ac33c6b8e91e80b8497d5581c61b028c0405e265cf38fd88f9a291c5c",
"aarch64-pc-windows-msvc-0.15.0":
"14ff501eb6e436f7a8062c6def7c41888926e8890fcb44db169ae033c88751b3",
"aarch64-unknown-linux-gnu-0.15.0":
"abfde61faa5381537feb0493065120b1c363bbac1d608ec92fd6cd84daa8d6b1",
"aarch64-unknown-linux-musl-0.15.0":
"b9860c394ea814a9e9560e80bc798c53b8a3a5120ce961a2555fb563cee9f73b",
"arm-unknown-linux-musleabihf-0.15.0":
"fbafa4c0e3c125f80b32faf575a7fc21a7d6e4d6889fcfc2425d03ac52a23fa8",
"armv7-unknown-linux-gnueabihf-0.15.0":
"f622dfb00984e2850b5da02db6c6469214115634a4a3e7875f75d5b097302354",
"armv7-unknown-linux-musleabihf-0.15.0":
"093fb4d9e3e8cfda389ccf6b1a1030d83cadb429de889eca045fa2daf848a725",
"i686-pc-windows-msvc-0.15.0":
"09f178ebce6bd6b48422ba0fbff00d76a838aa24fc074c081929a6faa4bdbd31",
"i686-unknown-linux-gnu-0.15.0":
"8e1f5f5c62d82a5e8aaa316c0236380282f89b5e4027eaa631c159c7f8daf7d2",
"i686-unknown-linux-musl-0.15.0":
"cf125895296f7f22714f01298d79885aaec9c25cdd7b648cfbf92a583dfd7ad8",
"powerpc64le-unknown-linux-gnu-0.15.0":
"d62f860244824c34bc66d2941cf95842bb5988cce13cf2a9bde5d361557e7fdc",
"riscv64gc-unknown-linux-gnu-0.15.0":
"e76a0be3e0ad5733aed2f69e242d3d4af92ed2522007a0ebe846b4f37383d2ab",
"s390x-unknown-linux-gnu-0.15.0":
"3e414eabe22173a5b246a2b5b432ef9232b605fb1b3323f9b2968ea99e1d05fe",
"x86_64-apple-darwin-0.15.0":
"09fa6fe0d4172e1bb84cc6d937a0e1f42ff84c90b61163fff4f31c51a9c14879",
"x86_64-pc-windows-msvc-0.15.0":
"d1613f4231095d160ab4d3487bf56de23f29945888bcbe4521fcb0cb5e695d92",
"x86_64-unknown-linux-gnu-0.15.0":
"b38e69fb31501ebee3aba8e1778547bb2557adaf6b8f9dfae6f05980ee68b8ed",
"x86_64-unknown-linux-musl-0.15.0":
"f2bd69f091517ebc49405319a6e7818b1037e250b8d539336aa0b91c44ffa4aa",
"aarch64-apple-darwin-0.14.14":
"76a9b0ebe57d0eee56940dbe0b62462578d1369cca8314ed0d2a6f2102292d4f",
"aarch64-pc-windows-msvc-0.14.14":
"449982699657bd68d170440dad958281b030bc05865fe012d043cd225e78eb79",
"aarch64-unknown-linux-gnu-0.14.14":
"1eee1ce3467fb5f348738bb5e4598859b55816a79c3cd6a26ca0dae03d7e2672",
"aarch64-unknown-linux-musl-0.14.14":
"a4d7302aa201a6f8e71dfa217cd8273fddd4e434a93ee3b4b07047fd7a684ac1",
"arm-unknown-linux-musleabihf-0.14.14":
"c225db82587780d1675e220db02c5209f406b29afb0a525628e11ffaca537068",
"armv7-unknown-linux-gnueabihf-0.14.14":
"cdc9596d6317e6cb32fbf3fd6c0e5817f96676b215957e6583db28abf4a15427",
"armv7-unknown-linux-musleabihf-0.14.14":
"0f1938426c50500bac09a09df5396aa19d6ce9f01a3a18a531dfaee5cc0d93e8",
"i686-pc-windows-msvc-0.14.14":
"4f6af9d82acae3d1308c55b0fa51b84d57eafc380aedf97a53a32314118c9a71",
"i686-unknown-linux-gnu-0.14.14":
"5ceb5e2f1906d85e444cb71503379e14d96ded423bd68906f1a539b78d9fc2c3",
"i686-unknown-linux-musl-0.14.14":
"878cebcaf3481295d22a1274cea94ec050455917669ccecac586dcfe6b4a557e",
"powerpc64-unknown-linux-gnu-0.14.14":
"e01a6db6bbf15db82052269887fb49e6be79923d188f1f60ed74f93b6b9a90d4",
"powerpc64le-unknown-linux-gnu-0.14.14":
"6048f270a46cce01228a53fde14fa6182a99d9e99169d7c252f706d7ba2a4845",
"riscv64gc-unknown-linux-gnu-0.14.14":
"6eca650195a22c0a568adc923b645b6317dad3ecc8a3d5c02c08768d6b333393",
"s390x-unknown-linux-gnu-0.14.14":
"dbde3ba91f84d8c183ed49bca0790777285d21381ad64a290584bd19038b83a4",
"x86_64-apple-darwin-0.14.14":
"749396c675c6f07205be6c4ef89e2e95123d790062d681059a355030e9d7d119",
"x86_64-pc-windows-msvc-0.14.14":
"81bfeed34f15296e6c81ecea912b6fff4430b957de8a1181ce9365434e3d6744",
"x86_64-unknown-linux-gnu-0.14.14":
"9876634f799d933bdaa1de864f47d26f898b2e20aa8f4f85d63235c3a231068d",
"x86_64-unknown-linux-musl-0.14.14":
"55a1ee65f5ac9416cc40f99c2df62f0d4525d40369fe371caff945c495174d57",
"aarch64-apple-darwin-0.14.13":
"067d1a90da8add55614eff91990425883a092d8279d9e503258ff8be0f8e9c18",
"aarch64-pc-windows-msvc-0.14.13":
"f7b5b9740ae893ac6abe9b9f68865073ffb9146a6f352ba9718d53b102ad02ef",
"aarch64-unknown-linux-gnu-0.14.13":
"84ce8fe1ef9b2eba018ba396fd018ac2bc22865a9e2a02573899ea6ee8e1e5fd",
"aarch64-unknown-linux-musl-0.14.13":
"25941b777ff712f4d9473d26c1b875034214a3d5de20ea99b2add939dcd0b367",
"arm-unknown-linux-musleabihf-0.14.13":
"3d833d451accb334f9cb91141083bbee5b835dcf0b49d14e5ccb13e0b6a7c387",
"armv7-unknown-linux-gnueabihf-0.14.13":
"90aa92a6de0f63eacb2d380c324039c101c6097956690fbaec05ed8d922f8766",
"armv7-unknown-linux-musleabihf-0.14.13":
"192deb53946b4309c56c1ad7c401ddac7557d0a517da7addfc636f1188bab30d",
"i686-pc-windows-msvc-0.14.13":
"4ead4d5fc89a1f02aa97dad944e69306ae4177a689a3818d3575fed11aa994fe",
"i686-unknown-linux-gnu-0.14.13":
"a321fc4862748563a663472a4458fe514e34542d0fe8d767d67b1463d585fb2f",
"i686-unknown-linux-musl-0.14.13":
"597541ac81d553c2fe97339680bfdb3e926714763ee37f0b5dbcc572d695e376",
"powerpc64-unknown-linux-gnu-0.14.13":
"cad4c05db54969608ec292b695397ddaef7452f759b976a60b69d9fefc6c5724",
"powerpc64le-unknown-linux-gnu-0.14.13":
"bc7faa37e496ae0198ee7e1c9e8958df3c55025ccf8be175b74533f87626e726",
"riscv64gc-unknown-linux-gnu-0.14.13":
"d9c6fd360347b9b0079994246ed08186f64629529e2fd2dc1a97a31da2a214d5",
"s390x-unknown-linux-gnu-0.14.13":
"1995227a65970cb2c1aa3b03c9a5418ee1fa56210c4577e6f8e1a14d1e79ed5d",
"x86_64-apple-darwin-0.14.13":
"69e424a42ac3a7c6c7032ad96deb757c35c93c848b6ea329a3f4c605e6d89ef9",
"x86_64-pc-windows-msvc-0.14.13":
"d2af4376053458f283d74980b49dc0d61d3ef9d9b8684c5b25bd64b73e11634a",
"x86_64-unknown-linux-gnu-0.14.13":
"b1e03e4cf245411f184106ba3d973b3902021bf5327e2fde0cfad163aacdc2c1",
"x86_64-unknown-linux-musl-0.14.13":
"2fe394f493318551f271277a2228e56b31ca69a4842483e5709af4548f342bc3",
"aarch64-apple-darwin-0.14.11":
"c3fab6bcad9cc2f8a342829dd4ea011c54b9671d023b71baac1cbf2b3526cefd",
"aarch64-pc-windows-msvc-0.14.11":
"43faade7e8f33a8ffab726aa44ee2b6bc1fd9d51407320db797dcb3dac068461",
"aarch64-unknown-linux-gnu-0.14.11":
"e5c018ac042ddeb9c02c3d96b6efe3a0dcd3406ba9bd695253d6111785f7abf6",
"aarch64-unknown-linux-musl-0.14.11":
"4779a99b51812b56d56d180174be46ddb2fed98fecaea7565b450d37cff1b8c6",
"arm-unknown-linux-musleabihf-0.14.11":
"a400347f59192992b61dc0bb9dfdfe640f8063403112d9e3a70f6275d34ae386",
"armv7-unknown-linux-gnueabihf-0.14.11":
"7938729417dcd33b7078ae0a77440cc152bba33518bb095e19cb108c132b2602",
"armv7-unknown-linux-musleabihf-0.14.11":
"57da6726050047ff2989554734deb94eb053789fc92c297a1a722a6723106a77",
"i686-pc-windows-msvc-0.14.11":
"51bec5b8cdc4008fb76e11eba4cbb62b8617ff242d316465fa2247dabfd2dcb9",
"i686-unknown-linux-gnu-0.14.11":
"9051f2529d36e38b58b03b6e605d77f62b6378d5afe70eba692f9a214794b273",
"i686-unknown-linux-musl-0.14.11":
"3f94ba805fd14bd8c088b710c950cd89d9968294fa3cff68a618e50ad9dea73d",
"powerpc64-unknown-linux-gnu-0.14.11":
"eb69c9da0285717f39ed25f666a0860d7d6a50359a1de0bf76fba6f83d59e1ea",
"powerpc64le-unknown-linux-gnu-0.14.11":
"001b43b7558d32f320fa775c74af0035952a724d323bb5204b6f83017297c6db",
"riscv64gc-unknown-linux-gnu-0.14.11":
"8901e1a36846c59a94ef468808e4a1af58f74e9c2830f40e045f5f9e70d6867c",
"s390x-unknown-linux-gnu-0.14.11":
"d29ba2740b7735ab20663225dd420acfb0797b29358d902afc7b87ee0f1f4b08",
"x86_64-apple-darwin-0.14.11":
"b301d1c04facba1004d180c8fd9ae36d868b780900682c97ecf324015c75f0b3",
"x86_64-pc-windows-msvc-0.14.11":
"e254f04e054e095215b8f74d5cdb7288f2d3f226bdc42b35cd44751fc7a55b2d",
"x86_64-unknown-linux-gnu-0.14.11":
"b98f19bb2cc99e0ea4de1625fbc43661902a1db45ff49a72e7f4a58cfedaabfb",
"x86_64-unknown-linux-musl-0.14.11":
"8e8b7a3f791e0faf3a1728d808133f6f9bd5c1422eccb9956e7e7376ff5404b8",
"aarch64-apple-darwin-0.14.10":
"dd0f3ec914604d802658ae0d271ff9d767c117d1702a44222170dd2ffbad8d2c",
"aarch64-pc-windows-msvc-0.14.10":
"a5b5bb9525a47dfff0ce8ad31546549b31dc1184f54027517838b1bbc9d81129",
"aarch64-unknown-linux-gnu-0.14.10":
"f6c8735bf175d449d79151f6bfe84251770ca6032e86bea0d327bc3789179027",
"aarch64-unknown-linux-musl-0.14.10":
"0888883f71199ca3af3569052d0cc1aee8b316390ffe75bc19f9a8047ffd0158",
"arm-unknown-linux-musleabihf-0.14.10":
"5102cff6cbed54953d6cd9c9d920a9dd6fbe283e047136a6e9bc732ae14a5cf3",
"armv7-unknown-linux-gnueabihf-0.14.10":
"94a3ad56d89c39ed47d4e864a18ea31baf989384840d5e182c3f94d5f35a4c93",
"armv7-unknown-linux-musleabihf-0.14.10":
"2a051d384d5d5fa84521f843424454620ff5d22580f90cd882512373c36933b0",
"i686-pc-windows-msvc-0.14.10":
"29e7519db7afed48e3729d16289a467acae7c6c34113d50bdd1e7e1077e1c7b5",
"i686-unknown-linux-gnu-0.14.10":
"56f98f880933907b809de3180e866e72342971024445144c409a6d0a46237c1a",
"i686-unknown-linux-musl-0.14.10":
"0cce7104a85d2891a9f62d9ee2decb942b91888d6156e4e858c1cb350a71a57c",
"powerpc64-unknown-linux-gnu-0.14.10":
"7685d9de6c1b139fd767f70310598a79d5b6c613d744a366b2fe99e420d19c32",
"powerpc64le-unknown-linux-gnu-0.14.10":
"620476793ed106c1f6bfa15e3b0d7834a55cad048a853d63f0c118b9fb7c7337",
"riscv64gc-unknown-linux-gnu-0.14.10":
"922872274af2b681381b46a242e77fa9bf427fa745313e818b89a6246be81d77",
"s390x-unknown-linux-gnu-0.14.10":
"da9cf8172369821dfaabfc8861238f565fdbcec1aebe897d900020ce561572de",
"x86_64-apple-darwin-0.14.10":
"7d5af84e84a189b49ac235d7970d35588cbed0d087f4237c96e5ac3c392bc6fb",
"x86_64-pc-windows-msvc-0.14.10":
"9c3340d322fff7a6dc132b8783ba734f5eaf4c46f7dd8bc828abdf850b3bebe1",
"x86_64-unknown-linux-gnu-0.14.10":
"4695e6375b1fa2b9c5f839218d695d8e701cfccb15d3f622b6a6feedb086bffb",
"x86_64-unknown-linux-musl-0.14.10":
"e1fac6f2705057f48f3d000694d0f41c95b06e738cf4825315c670f3b9d24933",
"aarch64-apple-darwin-0.14.9":
"ea132e6d6af23dbcf21c81d7fd8c5a94ccf16b1fb36a8bc60da971e339301f10",
"aarch64-pc-windows-msvc-0.14.9":
"dcc2c8a687b9682ab345418ba2793bc8594ae1f2bdb78af520b363e17cf2e271",
"aarch64-unknown-linux-gnu-0.14.9":
"423a07140aca7d007d63a42ff9322c0c2017dfe0ed23efe8bcbc36ed21d1cfd7",
"aarch64-unknown-linux-musl-0.14.9":
"9bdedf2e8d6398e193c2b2feccb2b4c9f8d37a6dc87cc0b587340a60e87d14e2",
"arm-unknown-linux-musleabihf-0.14.9":
"5d04fe5dac3a625c576f48d441238896d164128ec5ba64c9964e5d9502e92b1e",
"armv7-unknown-linux-gnueabihf-0.14.9":
"1404c9aace1216ad5cf8b977be5b967ab507f7245eec7bc9bb3fab80010a7769",
"armv7-unknown-linux-musleabihf-0.14.9":
"e90707e761fb9ce528c3728e5e12ddbadd60ad45dbacfb1ab9dff4b308f10ae3",
"i686-pc-windows-msvc-0.14.9":
"a7e19280ec509dbefdffd097e29f0ca411cca7196a7272198d022d22d5fad6c3",
"i686-unknown-linux-gnu-0.14.9":
"33494807d987983f1a91e486cc6a028364aa3291c96c3784620f75a3f477411a",
"i686-unknown-linux-musl-0.14.9":
"816e6fdefd899f2dcca7261dc6575ecac82907b94238569cbfef2b1639680865",
"powerpc64-unknown-linux-gnu-0.14.9":
"6786baf11e8b59c29d19c0da127451606837243472797aa5084eecccd9a74445",
"powerpc64le-unknown-linux-gnu-0.14.9":
"ba7219c9dce6f2d931d9851ac9623923b3fc5dafeb08c640885c7e33a6074775",
"riscv64gc-unknown-linux-gnu-0.14.9":
"8f46292b808c5ce0d72d1a4428e189a641d9f41b3af0b077537ddb7631f48b8d",
"s390x-unknown-linux-gnu-0.14.9":
"d795afb56c7f4ba55a7a08eda03bce1fd8b32d71c9cf1741218ac6b5578d8b84",
"x86_64-apple-darwin-0.14.9":
"ce980eb4b44a84cbf278686f8cf71a21b353888b29472ca269ffe385ce4f2351",
"x86_64-pc-windows-msvc-0.14.9":
"98727a845f01782b78efe1a3f0913aa9ec76c9a24e09aca89e26b3b684ca86a0",
"x86_64-unknown-linux-gnu-0.14.9":
"b012ebe5dee8d8c125f4d553fccda058249fbb1c914c17175c6edda14a1fb7da",
"x86_64-unknown-linux-musl-0.14.9":
"3bdbdb32f95bcf2c5bc45502a2050f30706f1c205fa0087ee3d4c688f67854fc",
"aarch64-apple-darwin-0.14.8":
"4efc019832a6b9225f650ee256d31b2e875021cae662963d533c78b5cf865f52",
"aarch64-pc-windows-msvc-0.14.8":
"965217e69f6d43e4df25abeec7b51a5ea014dbec219b9b2866ca37dcd312405e",
"aarch64-unknown-linux-gnu-0.14.8":
"9bbce62639bb30abf76202f2ec32c4234498de3c93c21b4c32c99d51c802cc41",
"aarch64-unknown-linux-musl-0.14.8":
"0b3464f54fa56f514c29e92bcf05867b914ad6c246e2ddbe4cce5c0700f2a3d9",
"arm-unknown-linux-musleabihf-0.14.8":
"5496ae4d581e7f6cdf509f56d33830056d1f8a432aa489bf9e40fc9754fbf1fa",
"armv7-unknown-linux-gnueabihf-0.14.8":
"203de1734ffddac18f02e3756ba0c02bd1770665031aba617ccf488ccfe78b25",
"armv7-unknown-linux-musleabihf-0.14.8":
"5e424ba3ebc90306f73e05914244245b66b27c3a44dcdf9029d97bfc56131491",
"i686-pc-windows-msvc-0.14.8":
"2bdff511119eb9d94e5a240c15bef668c43b0640e61c549b32242d7b04efd062",
"i686-unknown-linux-gnu-0.14.8":
"8f0c15e9518d1b9c0423986d3d1045ed5c4005e1037589a6772eee724ec3ba02",
"i686-unknown-linux-musl-0.14.8":
"55326d64201ff99dc96e93b23de951c2656825089ee581fc8aee099140d4d310",
"powerpc64-unknown-linux-gnu-0.14.8":
"52ffb3434ca8ada1d2703df0c4b929758444c24d9a47a8608a0775ddf6dbfa6c",
"powerpc64le-unknown-linux-gnu-0.14.8":
"3a8808f7b02f37e649e1cc1145ad3d2e4d23b4154637525480c7f6536f4152b9",
"riscv64gc-unknown-linux-gnu-0.14.8":
"8e6f1f660c1145bfe1b13fe41b7e6f4e829be3351bd536acbd41f91478d8932c",
"s390x-unknown-linux-gnu-0.14.8":
"f3ed19ba06face77e21c682846a4e5b3d4d9bfe5132eea86e30a84522b70d2b3",
"x86_64-apple-darwin-0.14.8":
"c53af0ba3cbc5e9e8f7768d28a7ff2d0795843e39302891093227bc6df343e94",
"x86_64-pc-windows-msvc-0.14.8":
"a837e98f5e6a21222a896f60521f563aea2c686a07cad17da9f5b5f2c4e04f8a",
"x86_64-unknown-linux-gnu-0.14.8":
"dce933cd68e3ca69c64c277ce6671dcdee7adeeaa6ac5a15047a4c973b30741f",
"x86_64-unknown-linux-musl-0.14.8":
"0f5496a3d2413b3cb85bbf9e4a6f95b5db127ac10989ecb3d8c0c0a0a74a892b",
"aarch64-apple-darwin-0.14.7":
"24631da81fe9ce5f4112e8caeb71957e81682e752a563f0a50715735f923e3c4",
"aarch64-pc-windows-msvc-0.14.7":
"24ca141d57fcd5bd303ca51af002cb9a815f072d9076b40ec2813835d46c4a82",
"aarch64-unknown-linux-gnu-0.14.7":
"8cc84635155dfc11acca297af25ed6c8a30e36146729a90df9e7d348a8fc0a4d",
"aarch64-unknown-linux-musl-0.14.7":
"8d429dbc2c308377c33874dc71d4c2388728f0d01c0a5869d2c878d21a6ced87",
"arm-unknown-linux-musleabihf-0.14.7":
"3cf1d9fdfd58a113b0b629e0ef2d247bbadbc8deec25724d6dc7d6f6ca7e7304",
"armv7-unknown-linux-gnueabihf-0.14.7":
"058f52b31c88056291b0e917ea909eea036570dcdee32af1ce62eacff98c4e2a",
"armv7-unknown-linux-musleabihf-0.14.7":
"9dddf78485ba0b5da8d8a73b24332ecf759a4de95ca5700c5141067683b5b615",
"i686-pc-windows-msvc-0.14.7":
"46d16810e1a0e7efd5c25efc60b719225c3a8b8a7fb662b61f293eeb62ccafdb",
"i686-unknown-linux-gnu-0.14.7":
"29242e501fea264ef4cb03164d9c375e426fad5c298412f64a4d50bc9ea19bc1",
"i686-unknown-linux-musl-0.14.7":
"5b1eeea3f27745e7e68b19bd214267aa653ef64c62c25ef4cd8bb57af8477171",
"powerpc64-unknown-linux-gnu-0.14.7":
"3cc47bf9943a46839d847746ccae59a3147982c5bf93bdc786fa5279d7d28681",
"powerpc64le-unknown-linux-gnu-0.14.7":
"1dabc7d0251f6f11ade20620538daeed1c4d27223576a04ddb2a87adfed2ca11",
"riscv64gc-unknown-linux-gnu-0.14.7":
"075232dfa5a03ef399d183879848cdb5d71485639c2a07fc62a27f4d4a68af3d",
"s390x-unknown-linux-gnu-0.14.7":
"a83cb05567dd4bb59fa8fd36b8337b21d475dd6f82fe0600a2364f1de6c295c3",
"x86_64-apple-darwin-0.14.7":
"c3035811c5354d923f3d7fd6320cfb23de30600f1eb86aa1518bac27c3d7fffd",
"x86_64-pc-windows-msvc-0.14.7":
"9a54b82e12574ea068e0914cdb1398993cbf78338c0799b6f9003a783fec779b",
"x86_64-unknown-linux-gnu-0.14.7":
"8a545d346b3f6f979574df96b4fb2a983f2568fe200f1c1f690bd2cbdce5ea51",
"x86_64-unknown-linux-musl-0.14.7":
"0f6cd59aa2b266758f1083c73f1b8303e3aedde0d953f0e79777faf174d8104f",
"aarch64-apple-darwin-0.14.6":
"b2ace677f51d0f2b91ca27d312ac8b069658502049015f4891c16f8864136282",
"aarch64-pc-windows-msvc-0.14.6":
"90518b6d16fe8b705c891dcdd77b20ab41da5e042b085aced7bbc647f6f2fdcd",
"aarch64-unknown-linux-gnu-0.14.6":
"bf3d592d5f879f072520afaa9c4a76a3ad0f6cf4b7bf7e27fb5d0e728e863f14",
"aarch64-unknown-linux-musl-0.14.6":
"4d3c113e59c5fbc31f1def566cebf28732bb45ff6b83df8e142bc1c055b3afa3",
"arm-unknown-linux-musleabihf-0.14.6":
"d0a3ed0a773f8e7edc44d63644b952d446dba771fdbb3eefd02853c323373f90",
"armv7-unknown-linux-gnueabihf-0.14.6":
"d6b71f6a951963f8dd0f2e099206b0aabf95d9bb49451a607da3e5823b800596",
"armv7-unknown-linux-musleabihf-0.14.6":
"a048f1e9a97afe8ac2a63785f5733d5736b9d6e6767b4d5b6434916d38f888e1",
"i686-pc-windows-msvc-0.14.6":
"c7b76311939f0ed7fbf4fe1be7c6c96c05b26a73e500650dff2586cbbe5c0b45",
"i686-unknown-linux-gnu-0.14.6":
"fe3fbd9227a7f2c1277306c31cf7627ab1dc61c25fa30854d66d8db138a74872",
"i686-unknown-linux-musl-0.14.6":
"5a184f4c1e6a665f56f980464dd87eeb75f32e37bf67d195efdaa41bba634a5d",
"powerpc64-unknown-linux-gnu-0.14.6":
"fe54fda27408241e9bee7bc5c9740a9a47a87a073ecd3d9bb166c32329c33958",
"powerpc64le-unknown-linux-gnu-0.14.6":
"d4296a0f41ed25cbf56a04b04bda7f3661b8be9d3220b89cc342e6cb4e6ee259",
"riscv64gc-unknown-linux-gnu-0.14.6":
"e06b714ebb8846421ed6b998beac66a7fd1fad09f7bee89e4e6b671c6e8c3fc3",
"s390x-unknown-linux-gnu-0.14.6":
"2ba3e5b6e66d9ba60d6fac9b699f77dd79402c872fc244259db7fc7815180b3d",
"x86_64-apple-darwin-0.14.6":
"efe45a6defcf4f1b6de87d56ce94635f53b00f88ddc2fcb8e25e2c4e3a61261c",
"x86_64-pc-windows-msvc-0.14.6":
"3a8bb3499484c7f9e86060c89d0e61c47dac7f9fb4d7fe46bc59dca400b322c9",
"x86_64-unknown-linux-gnu-0.14.6":
"b0ee141ada4749c7ee16ba21465b4ef035455b495aa3ab951bbcb9bde9f4d001",
"x86_64-unknown-linux-musl-0.14.6":
"7ef1743875c7c469cc1c4bb6da6a9bbff3f3a7b89a75632d6cd2944ede470a5e",
"aarch64-apple-darwin-0.14.5":
"c07616663ac63792c16269d6798a47fb1f45b29997706178d60e90da698a9970",
"aarch64-pc-windows-msvc-0.14.5":
"c09ff1cdaafd6cc3733586dc517bfd3e0d0ddc39360c90343ab7b610c8ade130",
"aarch64-unknown-linux-gnu-0.14.5":
"da5692a609e728dd1773aebf087113c5c3561aafdfa9fe574b30de9438c60a23",
"aarch64-unknown-linux-musl-0.14.5":
"ebef9d7119119e67b1065a5a35128148898e1581037e7d68c3aa9223983af0a4",
"arm-unknown-linux-musleabihf-0.14.5":
"00c937e44474d21878bdb2e9e214d74005fddc8c43a4c76d8e71a9085c7cc6af",
"armv7-unknown-linux-gnueabihf-0.14.5":
"aa54fcaba5d7029180139491213f504e5789b36e0017c0a67aff7b6ec0c04c45",
"armv7-unknown-linux-musleabihf-0.14.5":
"5e06c211d5dc4e85eeafd29e87af4aed690a41b02f9296537d4d4ebe62fded9a",
"i686-pc-windows-msvc-0.14.5":
"6b54cf61dad3276995d157d000f501917f1aa54735391150bf93282329253c92",
"i686-unknown-linux-gnu-0.14.5":
"81aadc05c630e7e7c4ca68c510f5ce462e0645fa03650ca3f844cb4d9b2efc27",
"i686-unknown-linux-musl-0.14.5":
"556c63188c8bc7e253a4eaef8727857a808a81af6fef02f76f27a71e800554a0",
"powerpc64-unknown-linux-gnu-0.14.5":
"df85081b5e776d934188f9eb67a1d2d17dba1e986f7e76668ffeed6e541858d1",
"powerpc64le-unknown-linux-gnu-0.14.5":
"4a02aa69a73bc5d9e579b08f5081e0fec412e353bacafa6b4a0705d89b3486e6",
"riscv64gc-unknown-linux-gnu-0.14.5":
"a1a20ddca6e776a8f38d533da4aa1eeb543ecbfa08813c482e3cb7d5ef0c39fc",
"s390x-unknown-linux-gnu-0.14.5":
"242bf290de417e8c9e8ca5c844be933e611cca3f3b301dc8537f72d051ceb3b9",
"x86_64-apple-darwin-0.14.5":
"d7285770c5098be00c9b4ff099d6ef7f8b2a54b4019c9507b628ee9f0d404c06",
"x86_64-pc-windows-msvc-0.14.5":
"9b998bf1a790a18b829fab3368ee7404a8f1f2cec2acb650aa1fd1b74962d023",
"x86_64-unknown-linux-gnu-0.14.5":
"5bc52346f81098e42beecd622f49c4336a5361eb38b7b51d466b0a9647e99e33",
"x86_64-unknown-linux-musl-0.14.5":
"69ddecf374a4f53eeda238399614ed6f92c5e75fe04436c604fb78435d5658eb",
"aarch64-apple-darwin-0.14.4":
"ab26cd2b03d5b2ec42dc7ac1886a29055eb2003aa2db14b26c7091c7cd6b4c68",
"aarch64-pc-windows-msvc-0.14.4":
"270773b34836e5b546d3ec9fe3b6cab4057881eff69828566f230baffff7634a",
"aarch64-unknown-linux-gnu-0.14.4":
"8bf03ef4bb8ce42321ea0e59a505739b29b18d85b1d4f220736e242248b7da31",
"aarch64-unknown-linux-musl-0.14.4":
"c77b72e565e9044eddce02e71f9214edea045047b786bb2bc973a6d8f94fb13e",
"arm-unknown-linux-musleabihf-0.14.4":
"5211b338bf1c48faa2fc045bd68c56b50e1e3d898f2255c39a3a7040033738f1",
"armv7-unknown-linux-gnueabihf-0.14.4":
"62e05c9a4571ea1aca80db377b4a11dd2325100db9ca0d7db16b61b87cb3d0b5",
"armv7-unknown-linux-musleabihf-0.14.4":
"b20725ed5be925b58120172b2395bdbea9857bc562c3a7a7e47a4d9281bafb7f",
"i686-pc-windows-msvc-0.14.4":
"bbe517649a5c6134b424b3065db7b5dcfaed72c2f5e848ec87cb2b33be7a133f",
"i686-unknown-linux-gnu-0.14.4":
"a68939e63f7b384351172ff1535d37e73ecf81a94b0c89f369f3f41aabcccede",
"i686-unknown-linux-musl-0.14.4":
"ff05a300bbb06211ec9cc43daec05da197ed116b5f6a5ea9ac8fc80fc00df39f",
"powerpc64-unknown-linux-gnu-0.14.4":
"394eae2ad3485eb3530db3e9b51feacbf0b03e63ec8e93f2134a137c59da4abe",
"powerpc64le-unknown-linux-gnu-0.14.4":
"b12b41763fa3c1068d2378669fa2740b11e0dd3dea3f667f732241a25fb06c9f",
"riscv64gc-unknown-linux-gnu-0.14.4":
"c6385274d7a3b1d0c7ed05b36101faf2c7206e8adc0c7c107d32fc8e5d57628c",
"s390x-unknown-linux-gnu-0.14.4":
"dcbf1dce38298dccfea7edffb1ea237ab2ccf2ea5dbae6d5b2dc4fe88177b094",
"x86_64-apple-darwin-0.14.4":
"041b9429400bfc3aca8402940fe6fa0ec33f9d79e8f50fbfb3063f70b4327fa3",
"x86_64-pc-windows-msvc-0.14.4":
"5541af69b1f342d843f8fca6218bd0d5d9064e987d14405a2abf60ce94c615a4",
"x86_64-unknown-linux-gnu-0.14.4":
"c9120e68e358eb0f850123537afe18945f9095e9f17967944fd23585fc88bb0b",
"x86_64-unknown-linux-musl-0.14.4":
"dcb5b42fd364c9e837b6800886564a891d023c8b87c967224351399cb1cda154",
"aarch64-apple-darwin-0.14.3":
"fc4c2c153656bc4082fb6c928fbd6867d5eb932240b663b329365f3f1821fc82",
"aarch64-pc-windows-msvc-0.14.3":
"b7ed5d2006b1c43559e78d90e65f2fa1982bec3976de74978485660fcdd67548",
"aarch64-unknown-linux-gnu-0.14.3":
"daa9edc586b669079fbe204bf5f5b48ee05c5dac862d2daf5ad72d3c2777834b",
"aarch64-unknown-linux-musl-0.14.3":
"571058fe7e3381ad54bec8b3d9d127013e332d822b35978b503722147aa3b9cf",
"arm-unknown-linux-musleabihf-0.14.3":
"f5e9b79cea63116ad582ffb514a61a506978762b67859b9e9e9cdee4b2ad065d",
"armv7-unknown-linux-gnueabihf-0.14.3":
"07094c107682c58260d652708a0c8b2f3d38bdffd5e7ec9a81785e665154a137",
"armv7-unknown-linux-musleabihf-0.14.3":
"edfd2491e25383de77f60aafcbd5451350b2cb6f9af38b6cf5a2e725193333cd",
"i686-pc-windows-msvc-0.14.3":
"f46525c0684a220c063c1c1f8d516c8cccab8ec9f62417e17e02cd4c1a138417",
"i686-unknown-linux-gnu-0.14.3":
"c7180e275ccaa97eae1528a15a08828cd9aaa15b57d3828f8722b0edd347046d",
"i686-unknown-linux-musl-0.14.3":
"60d1db56b1b4064b00f349b5fc8e11f2e63b8fa3bce4679f9c9da91356d6c3f6",
"powerpc64-unknown-linux-gnu-0.14.3":
"b9bf7191d023fcdd938e51fa53b63226897a23896f82057fe32fde9df10b1649",
"powerpc64le-unknown-linux-gnu-0.14.3":
"c1f5e497fe183dc1e0b6bb984573a0703182b68ac07587add1cbc370957ebd20",
"riscv64gc-unknown-linux-gnu-0.14.3":
"909e92469524b80520af17537df55f245d6ba018928bc486fd8418e9983b539f",
"s390x-unknown-linux-gnu-0.14.3":
"d8c56807e9827f391e1266bd19e0235fa470a0620cf166975365862e5bf9e908",
"x86_64-apple-darwin-0.14.3":
"076e88da3dcb38d3fef61daccc759bf29b605a490143ae707c0e067ebf8d7050",
"x86_64-pc-windows-msvc-0.14.3":
"ccc1bd362cca52a7fb7f9ee2cec7317c139ccb86e0964a5c1d4b14efbf2118ff",
"x86_64-unknown-linux-gnu-0.14.3":
"cb8a88912aa91a1864ad662c24df4514699ddfcbc52e684218aaf5ac57d7de0d",
"x86_64-unknown-linux-musl-0.14.3":
"58fd05427420df7b7b51bc6bbd5a430f38d2dfd63c74660f27625a4632f12532",
"aarch64-apple-darwin-0.14.2":
"33225f67ce61188fce91b801ab50a4028f8b0d66abbf81810841dd7d42371e38",
"aarch64-pc-windows-msvc-0.14.2":
"4fb68c66c719b337c2364aeb8210326a28b8ae046ef9a9c90407d473264515dd",
"aarch64-unknown-linux-gnu-0.14.2":
"a907d6a13120679b7194eb61beace09103f04b90e32cf7c2ac11979e6f618e35",
"aarch64-unknown-linux-musl-0.14.2":
"e323e9716661c9e18152aefe62e3fada497c10c9b3ef3095359c31b4df4f866a",
"arm-unknown-linux-musleabihf-0.14.2":
"2b3ccfd36718b29e0f5d1dcc10540b61205c72ee1c6a43331e6a97f2a17f12d0",
"armv7-unknown-linux-gnueabihf-0.14.2":
"5539a66c2723670dbf54eadf615e106aa038decbc701b9d47f7ed535ac677e1d",
"armv7-unknown-linux-musleabihf-0.14.2":
"c59be0daa3144ba317c88dd6cb3af68cff0f8dfbf2f495bebfb5b630d6f4c7cd",
"i686-pc-windows-msvc-0.14.2":
"125e2010b28ccb01ed2a4596e3db8d8dc0a46ccba2ee08e6038cc56ebbfbcb96",
"i686-unknown-linux-gnu-0.14.2":
"e2accde14cb371ca1b60935028c72138390f552cd77fc8c52a75f28c68eaa963",
"i686-unknown-linux-musl-0.14.2":
"d11f67f949e909412c523edcc1944c16dfecde49ed3709bca40631e371300da1",
"powerpc64-unknown-linux-gnu-0.14.2":
"99cc0f2cf83a7f82bb4e0db9ff1de8bcc5bf966054c0c82d25e189ee4565b0d5",
"powerpc64le-unknown-linux-gnu-0.14.2":
"81ac087a458bcaac7905c9afe84ef58061394a71264e8dd115889be0bc96fc6e",
"riscv64gc-unknown-linux-gnu-0.14.2":
"7917eb80fbae72040ad199f634361ce6001bc6225c165caa09c6d983c90692b0",
"s390x-unknown-linux-gnu-0.14.2":
"e24bfabe70535ffeedbba74ff6ecdf6c6bb09c5d9bf2c6c902e5540b569d6236",
"x86_64-apple-darwin-0.14.2":
"f119057618599e8983bdeed64e949af54f67d7a62464d11de0ba1237619a9990",
"x86_64-pc-windows-msvc-0.14.2":
"0a1160f45263cc847acd4b42392bf0484c9420806eeb7f776e1384fb557aeade",
"x86_64-unknown-linux-gnu-0.14.2":
"b4457ef6c130165655e0fb91081dbef09011ec41955115aaa22c94f7655c97c2",
"x86_64-unknown-linux-musl-0.14.2":
"e5e177a829b370376abb6a1dc9edc8c59ac519ebe64b1366b65e2952fa524a8d",
"aarch64-apple-darwin-0.14.1":
"ee12f441b4c14eff354784a52f8a16767573bd6285627cad2ade04551e8a678b",
"aarch64-pc-windows-msvc-0.14.1":
"3bd0b34cc645faaf88b4010a639d9b0ec6c313359774ad7e48daaef4ddb3c4f7",
"aarch64-unknown-linux-gnu-0.14.1":
"8203922d5f55a04089005184942f2f449f9736a06e96854230ba5991bb91ee9a",
"aarch64-unknown-linux-musl-0.14.1":
"ddfc9df5c879a1984fef4e29838e46fa8e979adc3fdab5967f096363427e8790",
"arm-unknown-linux-musleabihf-0.14.1":
"a29be1acc5f29ee8cb5a5add223a7ed07e8ffd0f9a78219e115e57041c63b416",
"armv7-unknown-linux-gnueabihf-0.14.1":
"dd8ead33fc3158618a29f02d76fd7ff4f6914272b5d94a65e348b8b8c0753d78",
"armv7-unknown-linux-musleabihf-0.14.1":
"16d79262e9fc98ceef167577a391d54d9c8a2d8fe90de22266f456bf4644f7e5",
"i686-pc-windows-msvc-0.14.1":
"ffe525c5e641f3bef31d9bc6e1e3585f9f796dda1254c15e5a18ef51a91917c3",
"i686-unknown-linux-gnu-0.14.1":
"37f10652706cdf1f107c620970bf37591b0be44111614101509f56e596f866b3",
"i686-unknown-linux-musl-0.14.1":
"e39c79642ec4e3d95d31a0ae77fb648946f2a639cb3cbf876c0f5e1e2571e94e",
"powerpc64-unknown-linux-gnu-0.14.1":
"1ec026aef1c670715d5c197bbf18b4edd81e140fbecee51ed169e05b042c6045",
"powerpc64le-unknown-linux-gnu-0.14.1":
"193d66f5ed6e21a46b244118c983ab91628ed65ae542f7345f8e37a8c555bff0",
"riscv64gc-unknown-linux-gnu-0.14.1":
"4cbea006f37927abac8c32575212f982b86aaa4f73a32983e4a8934320ef7e9c",
"s390x-unknown-linux-gnu-0.14.1":
"5cd7f6e6e2de3b03ada4821de444e92bb87ce5c57981c1d8ba8128dc62f02668",
"x86_64-apple-darwin-0.14.1":
"7fda94bd7fc6114eecb67b858a8c93f1b4aa658aa1f5e887b37a2b95bf6f26a7",
"x86_64-pc-windows-msvc-0.14.1":
"ab0bc1876a16e1afa4177fe9f58d568fbe85921efb253395bd03d01e1e2c467d",
"x86_64-unknown-linux-gnu-0.14.1":
"8798f5892db7dc70a3250f89d3ef613f87f1fa5fdb4242921ee31427daf78e87",
"x86_64-unknown-linux-musl-0.14.1":
"ec5b8eb318ff9e2412988420eb78f965aca02e9f67df34eeea7609e0c5a0fb7f",
"aarch64-apple-darwin-0.14.0": "aarch64-apple-darwin-0.14.0":
"0b7c193d5c45eda02226720eb75239fabeca995d5a0eb3830fd2973caa3030ec", "0b7c193d5c45eda02226720eb75239fabeca995d5a0eb3830fd2973caa3030ec",
"aarch64-pc-windows-msvc-0.14.0": "aarch64-pc-windows-msvc-0.14.0":
@@ -1,5 +1,11 @@
import { promises as fs } from "node:fs"; import { promises as fs } from "node:fs";
import * as tc from "@actions/tool-cache"; import * as tc from "@actions/tool-cache";
import {
isRetryableError,
NonRetryableError,
RetryableError,
withRetry,
} from "../../utils/retry";
import { KNOWN_CHECKSUMS } from "./known-checksums"; import { KNOWN_CHECKSUMS } from "./known-checksums";
export async function updateChecksums( export async function updateChecksums(
filePath: string, filePath: string,
@@ -60,6 +66,27 @@ async function getOrDownloadChecksum(
} }
async function downloadAssetContent(downloadUrl: string): Promise<string> { async function downloadAssetContent(downloadUrl: string): Promise<string> {
return await withRetry(
async () => {
try {
const downloadPath = await tc.downloadTool(downloadUrl); const downloadPath = await tc.downloadTool(downloadUrl);
return await fs.readFile(downloadPath, "utf8"); return await fs.readFile(downloadPath, "utf8");
} catch (error) {
const err = error as Error;
if (isRetryableError(err)) {
throw new RetryableError(
`Failed to download checksum file: ${err.message}`,
err,
);
} else {
throw new NonRetryableError(
`Failed to download checksum file: ${err.message}`,
err,
);
}
}
},
{ maxRetries: 3, timeoutMs: 30000 },
"download checksum file",
);
} }
+243 -130
View File
@@ -2,16 +2,22 @@ import { promises as fs } from "node:fs";
import * as path from "node:path"; import * as path from "node:path";
import * as core from "@actions/core"; import * as core from "@actions/core";
import * as tc from "@actions/tool-cache"; import * as tc from "@actions/tool-cache";
import { Octokit } from "@octokit/core";
import { paginateRest } from "@octokit/plugin-paginate-rest";
import { restEndpointMethods } from "@octokit/plugin-rest-endpoint-methods";
import * as pep440 from "@renovatebot/pep440";
import * as semver from "semver"; import * as semver from "semver";
import { import { OWNER, REPO, TOOL_CACHE_NAME } from "../utils/constants";
ASTRAL_MIRROR_PREFIX,
GITHUB_RELEASES_PREFIX,
TOOL_CACHE_NAME,
VERSIONS_MANIFEST_URL,
} from "../utils/constants";
import type { Architecture, Platform } from "../utils/platforms"; import type { Architecture, Platform } from "../utils/platforms";
import {
isRetryableError,
NonRetryableError,
RetryableError,
withRetry,
} from "../utils/retry";
import { validateChecksum } from "./checksum/checksum"; import { validateChecksum } from "./checksum/checksum";
import { getArtifact } from "./manifest";
const PaginatingOctokit = Octokit.plugin(paginateRest, restEndpointMethods);
export function tryGetFromToolCache( export function tryGetFromToolCache(
arch: Architecture, arch: Architecture,
@@ -32,46 +38,49 @@ export async function downloadVersion(
platform: Platform, platform: Platform,
arch: Architecture, arch: Architecture,
version: string, version: string,
checksum: string | undefined, checkSum: string | undefined,
githubToken: string, githubToken: string,
manifestUrl?: string,
): Promise<{ version: string; cachedToolDir: string }> { ): Promise<{ version: string; cachedToolDir: string }> {
const artifact = await getArtifact(version, arch, platform, manifestUrl); const artifact = `ruff-${arch}-${platform}`;
let extension = ".tar.gz";
if (platform === "pc-windows-msvc") {
extension = ".zip";
}
const downloadUrl = constructDownloadUrl(version, platform, arch);
core.debug(`Downloading ruff from "${downloadUrl}" ...`);
if (!artifact) { const downloadPath = await withRetry(
throw new Error( async () => {
getMissingArtifactMessage(version, arch, platform, manifestUrl), try {
return await tc.downloadTool(downloadUrl, undefined, githubToken);
} catch (error) {
const err = error as Error;
if (isRetryableError(err)) {
throw new RetryableError(
`Failed to download ruff binary: ${err.message}`,
err,
);
} else {
throw new NonRetryableError(
`Failed to download ruff binary: ${err.message}`,
err,
); );
} }
}
// For the default astral-sh/versions source, checksum validation relies on },
// user input or the built-in KNOWN_CHECKSUMS table, not manifest sha256 values. { maxRetries: 3, timeoutMs: 60000 }, // 60 second timeout for downloads
const resolvedChecksum = "download ruff binary",
manifestUrl === undefined
? checksum
: resolveChecksum(checksum, artifact.checksum);
const downloadPath = await downloadArtifact(
artifact.downloadUrl,
platform,
arch,
version,
getDownloadToken(artifact.downloadUrl, githubToken),
);
await validateChecksum(
resolvedChecksum,
downloadPath,
arch,
platform,
version,
); );
core.debug(`Downloaded ruff to "${downloadPath}"`);
await validateChecksum(checkSum, downloadPath, arch, platform, version);
const extractedDir = await extractDownloadedArtifact( const extractedDir = await extractDownloadedArtifact(
version, version,
downloadPath, downloadPath,
getExtension(platform), extension,
platform, platform,
`ruff-${arch}-${platform}`, artifact,
); );
const cachedToolDir = await tc.cacheDir( const cachedToolDir = await tc.cacheDir(
@@ -80,60 +89,25 @@ export async function downloadVersion(
version, version,
arch, arch,
); );
return { cachedToolDir, version }; return { cachedToolDir, version: version };
} }
export function rewriteToMirror(url: string): string | undefined { function constructDownloadUrl(
if (!url.startsWith(GITHUB_RELEASES_PREFIX)) { version: string,
return undefined;
}
return ASTRAL_MIRROR_PREFIX + url.slice(GITHUB_RELEASES_PREFIX.length);
}
async function downloadArtifact(
downloadUrl: string,
platform: Platform, platform: Platform,
arch: Architecture, arch: Architecture,
version: string, ): string {
githubToken: string | undefined, const artifactVersionSuffix =
): Promise<string> { semver.lte(version, "v0.4.10") && semver.gte(version, "v0.1.8")
const mirrorUrl = rewriteToMirror(downloadUrl); ? `-${version}`
const resolvedDownloadUrl = mirrorUrl ?? downloadUrl; : "";
const artifact = `ruff${artifactVersionSuffix}-${arch}-${platform}`;
try { let extension = ".tar.gz";
return await downloadFile( if (platform === "pc-windows-msvc") {
resolvedDownloadUrl, extension = ".zip";
mirrorUrl !== undefined ? undefined : githubToken,
);
} catch (err) {
if (mirrorUrl === undefined) {
throw err;
} }
const versionPrefix = semver.lte(version, "v0.4.10") ? "v" : "";
core.warning( return `https://github.com/${OWNER}/${REPO}/releases/download/${versionPrefix}${version}/${artifact}${extension}`;
`Failed to download from mirror, falling back to GitHub Releases: ${(err as Error).message}`,
);
return await downloadFile(
constructDownloadUrl(version, platform, arch),
githubToken,
);
}
}
async function downloadFile(
downloadUrl: string,
githubToken: string | undefined,
): Promise<string> {
core.info(`Downloading ruff from "${downloadUrl}" ...`);
const downloadPath = await tc.downloadTool(
downloadUrl,
undefined,
githubToken,
);
core.debug(`Downloaded ruff to "${downloadPath}"`);
return downloadPath;
} }
async function extractDownloadedArtifact( async function extractDownloadedArtifact(
@@ -148,7 +122,7 @@ async function extractDownloadedArtifact(
const fullPathWithExtension = `${downloadPath}${extension}`; const fullPathWithExtension = `${downloadPath}${extension}`;
await fs.copyFile(downloadPath, fullPathWithExtension); await fs.copyFile(downloadPath, fullPathWithExtension);
ruffDir = await tc.extractZip(fullPathWithExtension); ruffDir = await tc.extractZip(fullPathWithExtension);
// On windows extracting the zip does not create an intermediate directory. // On windows extracting the zip does not create an intermediate directory
} else { } else {
ruffDir = await tc.extractTar(downloadPath); ruffDir = await tc.extractTar(downloadPath);
if (semver.gte(version, "v0.5.0")) { if (semver.gte(version, "v0.5.0")) {
@@ -161,57 +135,196 @@ async function extractDownloadedArtifact(
return ruffDir; return ruffDir;
} }
function getMissingArtifactMessage( export async function resolveVersion(
version: string, versionInput: string,
arch: Architecture, githubToken: string,
platform: Platform, ): Promise<string> {
manifestUrl?: string, core.debug(`Resolving ${versionInput}...`);
): string { const version =
if (manifestUrl === undefined) { versionInput === "latest"
return `Could not find artifact for version ${version}, arch ${arch}, platform ${platform} in ${VERSIONS_MANIFEST_URL} .`; ? await getLatestVersion(githubToken)
: versionInput;
if (tc.isExplicitVersion(version)) {
core.debug(`Version ${version} is an explicit version.`);
return version;
}
const availableVersions = await getAvailableVersions(githubToken);
const resolvedVersion = maxSatisfying(availableVersions, version);
if (resolvedVersion === undefined) {
throw new Error(`No version found for ${version}`);
}
core.debug(`Resolved version: ${resolvedVersion}`);
return resolvedVersion;
}
async function getAvailableVersions(githubToken: string): Promise<string[]> {
return await withRetry(
async () => {
try {
const octokit = new PaginatingOctokit({
auth: githubToken,
});
return await getReleaseTagNames(octokit);
} catch (err) {
const error = err as Error;
if (error.message.includes("Bad credentials")) {
core.info(
"No (valid) GitHub token provided. Falling back to anonymous. Requests might be rate limited.",
);
const octokit = new PaginatingOctokit();
return await getReleaseTagNames(octokit);
} }
return `manifest-file does not contain version ${version}, arch ${arch}, platform ${platform}.`; if (isRetryableError(error)) {
throw new RetryableError(
`Failed to get available versions: ${error.message}`,
error,
);
} else {
throw new NonRetryableError(
`Failed to get available versions: ${error.message}`,
error,
);
}
}
},
{ maxRetries: 3, timeoutMs: 30000 }, // 30 second timeout for API calls
"get available versions",
);
} }
function resolveChecksum( async function getReleaseTagNames(
checksum: string | undefined, octokit: InstanceType<typeof PaginatingOctokit>,
manifestChecksum: string, ): Promise<string[]> {
): string { const response = await withRetry(
return checksum !== undefined && checksum !== "" async () => {
? checksum try {
: manifestChecksum; return await octokit.paginate(octokit.rest.repos.listReleases, {
owner: OWNER,
repo: REPO,
});
} catch (error) {
const err = error as Error;
if (isRetryableError(err)) {
throw new RetryableError(
`Failed to list GitHub releases: ${err.message}`,
err,
);
} else {
throw new NonRetryableError(
`Failed to list GitHub releases: ${err.message}`,
err,
);
}
}
},
{ maxRetries: 3, timeoutMs: 30000 },
"list GitHub releases",
);
const releaseTagNames = response.map((release) => release.tag_name);
if (releaseTagNames.length === 0) {
throw Error(
"Github API request failed while getting releases. Check the GitHub status page for outages. Try again later.",
);
}
return response.map((release) => release.tag_name);
} }
function getDownloadToken( async function getLatestVersion(githubToken: string) {
downloadUrl: string, return await withRetry(
githubToken: string, async () => {
): string | undefined { const octokit = new PaginatingOctokit({
return downloadUrl.startsWith(GITHUB_RELEASES_PREFIX) auth: githubToken,
? githubToken });
: undefined;
let latestRelease: { tag_name: string } | undefined;
try {
latestRelease = await getLatestRelease(octokit);
} catch (err) {
const error = err as Error;
if (error.message.includes("Bad credentials")) {
core.info(
"No (valid) GitHub token provided. Falling back to anonymous. Requests might be rate limited.",
);
const octokit = new PaginatingOctokit();
latestRelease = await getLatestRelease(octokit);
} else {
core.error(
"Github API request failed while getting latest release. Check the GitHub status page for outages. Try again later.",
);
if (isRetryableError(error)) {
throw new RetryableError(
`Failed to get latest version: ${error.message}`,
error,
);
} else {
throw new NonRetryableError(
`Failed to get latest version: ${error.message}`,
error,
);
}
}
}
if (!latestRelease) {
throw new Error("Could not determine latest release.");
}
return latestRelease.tag_name;
},
{ maxRetries: 3, timeoutMs: 30000 },
"get latest version",
);
} }
function constructDownloadUrl( async function getLatestRelease(
octokit: InstanceType<typeof PaginatingOctokit>,
) {
return await withRetry(
async () => {
try {
const { data: latestRelease } =
await octokit.rest.repos.getLatestRelease({
owner: OWNER,
repo: REPO,
});
return latestRelease;
} catch (error) {
const err = error as Error;
if (isRetryableError(err)) {
throw new RetryableError(
`Failed to get latest release: ${err.message}`,
err,
);
} else {
throw new NonRetryableError(
`Failed to get latest release: ${err.message}`,
err,
);
}
}
},
{ maxRetries: 3, timeoutMs: 30000 },
"get latest release",
);
}
function maxSatisfying(
versions: string[],
version: string, version: string,
platform: Platform, ): string | undefined {
arch: Architecture, const maxSemver = tc.evaluateVersions(versions, version);
): string { if (maxSemver !== "") {
const normalizedVersion = stripVersionPrefix(version); core.debug(`Found a version that satisfies the semver range: ${maxSemver}`);
const artifactVersionSuffix = return maxSemver;
semver.lte(version, "v0.4.10") && semver.gte(version, "v0.1.8") }
? `-${normalizedVersion}` const maxPep440 = pep440.maxSatisfying(versions, version);
: ""; if (maxPep440 !== null) {
const artifact = `ruff${artifactVersionSuffix}-${arch}-${platform}`; core.debug(
const versionPrefix = semver.lte(version, "v0.4.10") ? "v" : ""; `Found a version that satisfies the pep440 specifier: ${maxPep440}`,
);
return `${GITHUB_RELEASES_PREFIX}${versionPrefix}${normalizedVersion}/${artifact}${getExtension(platform)}`; return maxPep440;
} }
return undefined;
function stripVersionPrefix(version: string): string {
return version.startsWith("v") ? version.slice(1) : version;
}
function getExtension(platform: Platform): string {
return platform === "pc-windows-msvc" ? ".zip" : ".tar.gz";
} }
-243
View File
@@ -1,243 +0,0 @@
import * as core from "@actions/core";
import { VERSIONS_MANIFEST_URL } from "../utils/constants";
import { fetch } from "../utils/fetch";
import { selectDefaultVariant } from "./variant-selection";
export interface ManifestArtifact {
platform: string;
variant?: string;
url: string;
archive_format: string;
sha256: string;
}
export interface ManifestVersion {
version: string;
artifacts: ManifestArtifact[];
}
export interface ArtifactResult {
archiveFormat: string;
checksum: string;
downloadUrl: string;
}
const cachedManifestData = new Map<string, ManifestVersion[]>();
export async function fetchManifest(
manifestUrl: string = VERSIONS_MANIFEST_URL,
): Promise<ManifestVersion[]> {
const cachedVersions = cachedManifestData.get(manifestUrl);
if (cachedVersions !== undefined) {
core.debug(`Using cached manifest data from ${manifestUrl}`);
return cachedVersions;
}
core.info(`Fetching manifest data from ${manifestUrl} ...`);
const response = await fetch(manifestUrl, {});
if (!response.ok) {
throw new Error(
`Failed to fetch manifest data: ${response.status} ${response.statusText}`,
);
}
const body = await response.text();
const versions = parseManifest(body, manifestUrl);
cachedManifestData.set(manifestUrl, versions);
return versions;
}
export function parseManifest(
data: string,
sourceDescription: string,
): ManifestVersion[] {
const trimmed = data.trim();
if (trimmed === "") {
throw new Error(`Manifest at ${sourceDescription} is empty.`);
}
if (trimmed.startsWith("[")) {
throw new Error(
`Legacy JSON array manifests are no longer supported in ${sourceDescription}. Use the astral-sh/versions manifest format instead.`,
);
}
const versions: ManifestVersion[] = [];
for (const [index, line] of data.split("\n").entries()) {
const record = line.trim();
if (record === "") {
continue;
}
let parsed: unknown;
try {
parsed = JSON.parse(record);
} catch (error) {
throw new Error(
`Failed to parse manifest data from ${sourceDescription} at line ${index + 1}: ${(error as Error).message}`,
);
}
if (!isManifestVersion(parsed)) {
throw new Error(
`Invalid manifest record in ${sourceDescription} at line ${index + 1}.`,
);
}
versions.push(parsed);
}
if (versions.length === 0) {
throw new Error(`No manifest data found in ${sourceDescription}.`);
}
return versions;
}
export async function getLatestVersion(
manifestUrl: string = VERSIONS_MANIFEST_URL,
): Promise<string> {
const latestVersion = (await fetchManifest(manifestUrl))[0]?.version;
if (latestVersion === undefined) {
throw new Error("No versions found in manifest data");
}
core.debug(`Latest version from manifest: ${latestVersion}`);
return latestVersion;
}
export async function getAllVersions(
manifestUrl: string = VERSIONS_MANIFEST_URL,
): Promise<string[]> {
core.info(
`Getting available versions from ${manifestSource(manifestUrl)} ...`,
);
const versions = await fetchManifest(manifestUrl);
return versions.map((versionData) => versionData.version);
}
export async function getArtifact(
version: string,
arch: string,
platform: string,
manifestUrl: string = VERSIONS_MANIFEST_URL,
): Promise<ArtifactResult | undefined> {
const versions = await fetchManifest(manifestUrl);
const versionData = versions.find((candidate) =>
matchesManifestVersion(candidate.version, version),
);
if (!versionData) {
core.debug(`Version ${version} not found in manifest ${manifestUrl}`);
return undefined;
}
const targetPlatforms = getTargetPlatforms(
versionData.version,
arch,
platform,
);
const matchingArtifacts = versionData.artifacts.filter((candidate) =>
targetPlatforms.includes(candidate.platform),
);
if (matchingArtifacts.length === 0) {
core.debug(
`Artifact for ${targetPlatforms.join(" or ")} not found in version ${version}. Available platforms: ${versionData.artifacts
.map((candidate) => candidate.platform)
.join(", ")}`,
);
return undefined;
}
const artifact = selectDefaultVariant(
matchingArtifacts,
`Multiple artifacts found for ${targetPlatforms.join(" or ")} in version ${version}`,
);
return {
archiveFormat: artifact.archive_format,
checksum: artifact.sha256,
downloadUrl: artifact.url,
};
}
export function clearManifestCache(manifestUrl?: string): void {
if (manifestUrl === undefined) {
cachedManifestData.clear();
return;
}
cachedManifestData.delete(manifestUrl);
}
function manifestSource(manifestUrl: string): string {
if (manifestUrl === VERSIONS_MANIFEST_URL) {
return VERSIONS_MANIFEST_URL;
}
return `manifest-file ${manifestUrl}`;
}
function matchesManifestVersion(
manifestVersion: string,
requestedVersion: string,
): boolean {
return (
manifestVersion === requestedVersion ||
manifestVersion === withVersionPrefix(requestedVersion)
);
}
function getTargetPlatforms(
manifestVersion: string,
arch: string,
platform: string,
): string[] {
const targetPlatform = `${arch}-${platform}`;
const versionPrefixedTargetPlatform = `${stripVersionPrefix(manifestVersion)}-${targetPlatform}`;
return [targetPlatform, versionPrefixedTargetPlatform];
}
function withVersionPrefix(version: string): string {
return version.startsWith("v") ? version : `v${version}`;
}
function stripVersionPrefix(version: string): string {
return version.startsWith("v") ? version.slice(1) : version;
}
function isManifestVersion(value: unknown): value is ManifestVersion {
if (!isRecord(value)) {
return false;
}
if (typeof value.version !== "string" || !Array.isArray(value.artifacts)) {
return false;
}
return value.artifacts.every(isManifestArtifact);
}
function isManifestArtifact(value: unknown): value is ManifestArtifact {
if (!isRecord(value)) {
return false;
}
const variantIsValid =
typeof value.variant === "string" || value.variant === undefined;
return (
typeof value.archive_format === "string" &&
typeof value.platform === "string" &&
typeof value.sha256 === "string" &&
typeof value.url === "string" &&
variantIsValid
);
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
-39
View File
@@ -1,39 +0,0 @@
interface VariantAwareEntry {
variant?: string;
}
export function selectDefaultVariant<T extends VariantAwareEntry>(
entries: T[],
duplicateEntryDescription: string,
): T {
const firstEntry = entries[0];
if (firstEntry === undefined) {
throw new Error("selectDefaultVariant requires at least one candidate.");
}
if (entries.length === 1) {
return firstEntry;
}
const defaultEntries = entries.filter((entry) =>
isDefaultVariant(entry.variant),
);
if (defaultEntries.length === 1) {
return defaultEntries[0];
}
throw new Error(
`${duplicateEntryDescription} with variants ${formatVariants(entries)}. ruff-action currently requires a single default variant for duplicate platform entries.`,
);
}
function isDefaultVariant(variant: string | undefined): boolean {
return variant === undefined || variant === "default";
}
function formatVariants<T extends VariantAwareEntry>(entries: T[]): string {
return entries
.map((entry) => entry.variant ?? "default")
.sort((left, right) => left.localeCompare(right))
.join(", ");
}
+38 -18
View File
@@ -1,16 +1,17 @@
import * as fs from "node:fs";
import * as path from "node:path"; import * as path from "node:path";
import * as core from "@actions/core"; import * as core from "@actions/core";
import * as exec from "@actions/exec"; import * as exec from "@actions/exec";
import * as semver from "semver"; import * as semver from "semver";
import { import {
downloadVersion, downloadVersion,
resolveVersion,
tryGetFromToolCache, tryGetFromToolCache,
} from "./download/download-version"; } from "./download/download-version";
import { import {
args, args,
checkSum, checkSum,
githubToken, githubToken,
manifestFile,
src, src,
version, version,
versionFile as versionFileInput, versionFile as versionFileInput,
@@ -21,7 +22,7 @@ import {
getPlatform, getPlatform,
type Platform, type Platform,
} from "./utils/platforms"; } from "./utils/platforms";
import { resolveRuffVersion } from "./version/resolve"; import { getRuffVersionFromRequirementsFile } from "./utils/pyproject";
async function run(): Promise<void> { async function run(): Promise<void> {
const platform = getPlatform(); const platform = getPlatform();
@@ -61,7 +62,6 @@ async function setupRuff(
githubToken: string, githubToken: string,
): Promise<{ ruffDir: string; version: string }> { ): Promise<{ ruffDir: string; version: string }> {
const resolvedVersion = await determineVersion(); const resolvedVersion = await determineVersion();
const manifestUrl = manifestFile || undefined;
if (semver.lt(resolvedVersion, "v0.0.247")) { if (semver.lt(resolvedVersion, "v0.0.247")) {
throw Error( throw Error(
"This action does not support ruff versions older than 0.0.247", "This action does not support ruff versions older than 0.0.247",
@@ -82,7 +82,6 @@ async function setupRuff(
resolvedVersion, resolvedVersion,
checkSum, checkSum,
githubToken, githubToken,
manifestUrl,
); );
return { return {
@@ -92,13 +91,35 @@ async function setupRuff(
} }
async function determineVersion(): Promise<string> { async function determineVersion(): Promise<string> {
return await resolveRuffVersion({ if (versionFileInput !== "" && version !== "") {
manifestFile: manifestFile || undefined, throw Error("It is not allowed to specify both version and version-file");
sourceDirectory: src, }
version, if (version !== "") {
versionFile: versionFileInput, return await resolveVersion(version, githubToken);
workspaceRoot: process.env.GITHUB_WORKSPACE || ".", }
}); if (versionFileInput !== "") {
const versionFromPyproject =
getRuffVersionFromRequirementsFile(versionFileInput);
if (versionFromPyproject === undefined) {
core.warning(
`Could not parse version from ${versionFileInput}. Using latest version.`,
);
}
return await resolveVersion(versionFromPyproject || "latest", githubToken);
}
const pyProjectPath = path.join(src, "pyproject.toml");
if (!fs.existsSync(pyProjectPath)) {
core.info(`Could not find ${pyProjectPath}. Using latest version.`);
return await resolveVersion("latest", githubToken);
}
const versionFromPyproject =
getRuffVersionFromRequirementsFile(pyProjectPath);
if (versionFromPyproject === undefined) {
core.info(
`Could not parse version from ${pyProjectPath}. Using latest version.`,
);
}
return await resolveVersion(versionFromPyproject || "latest", githubToken);
} }
function addRuffToPath(cachedPath: string): void { function addRuffToPath(cachedPath: string): void {
@@ -112,17 +133,16 @@ function setOutputFormat() {
} }
function addMatchers(): void { function addMatchers(): void {
const actionRoot = getActionRoot(); const matchersPath = path.join(
const matchersPath = path.join(actionRoot, ".github", "matchers"); __dirname,
`..${path.sep}..`,
".github",
"matchers",
);
core.info(`##[add-matcher]${path.join(matchersPath, "check.json")}`); core.info(`##[add-matcher]${path.join(matchersPath, "check.json")}`);
core.info(`##[add-matcher]${path.join(matchersPath, "format.json")}`); core.info(`##[add-matcher]${path.join(matchersPath, "format.json")}`);
} }
function getActionRoot(): string {
const entrypoint = process.argv[1] ?? process.cwd();
return path.resolve(path.dirname(entrypoint), "..", "..");
}
async function runRuff( async function runRuff(
ruffExecutablePath: string, ruffExecutablePath: string,
args: string[], args: string[],
+31 -4
View File
@@ -5,19 +5,46 @@ import { restEndpointMethods } from "@octokit/plugin-rest-endpoint-methods";
import * as semver from "semver"; import * as semver from "semver";
import { updateChecksums } from "./download/checksum/update-known-checksums"; import { updateChecksums } from "./download/checksum/update-known-checksums";
import { OWNER, REPO } from "./utils/constants"; import { OWNER, REPO } from "./utils/constants";
import {
isRetryableError,
NonRetryableError,
RetryableError,
withRetry,
} from "./utils/retry";
const PaginatingOctokit = Octokit.plugin(paginateRest, restEndpointMethods); const PaginatingOctokit = Octokit.plugin(paginateRest, restEndpointMethods);
async function run(): Promise<void> { async function run(): Promise<void> {
const checksumFilePath = process.argv.slice(2)[0]; const checksumFilePath = process.argv.slice(2)[0];
const githubToken = process.argv.slice(2)[1]; const github_token = process.argv.slice(2)[1];
const octokit = new PaginatingOctokit({ auth: githubToken }); const response = await withRetry(
async () => {
const response = await octokit.paginate(octokit.rest.repos.listReleases, { try {
const octokit = new PaginatingOctokit({ auth: github_token });
return await octokit.paginate(octokit.rest.repos.listReleases, {
owner: OWNER, owner: OWNER,
repo: REPO, repo: REPO,
}); });
} catch (error) {
const err = error as Error;
if (isRetryableError(err)) {
throw new RetryableError(
`Failed to list releases for checksum update: ${err.message}`,
err,
);
} else {
throw new NonRetryableError(
`Failed to list releases for checksum update: ${err.message}`,
err,
);
}
}
},
{ maxRetries: 3, timeoutMs: 60000 },
"list releases for checksum update",
);
const downloadUrls: string[] = response.flatMap((release) => const downloadUrls: string[] = response.flatMap((release) =>
release.assets release.assets
.filter((asset) => asset.name.endsWith(".sha256")) .filter((asset) => asset.name.endsWith(".sha256"))
-6
View File
@@ -1,9 +1,3 @@
export const REPO = "ruff"; export const REPO = "ruff";
export const OWNER = "astral-sh"; export const OWNER = "astral-sh";
export const TOOL_CACHE_NAME = "ruff"; export const TOOL_CACHE_NAME = "ruff";
export const VERSIONS_MANIFEST_URL =
"https://raw.githubusercontent.com/astral-sh/versions/main/v1/ruff.ndjson";
export const GITHUB_RELEASES_PREFIX =
"https://github.com/astral-sh/ruff/releases/download/";
export const ASTRAL_MIRROR_PREFIX =
"https://releases.astral.sh/github/ruff/releases/download/";
-21
View File
@@ -1,21 +0,0 @@
import { ProxyAgent, type RequestInit, fetch as undiciFetch } from "undici";
export function getProxyAgent() {
const httpProxy = process.env.HTTP_PROXY || process.env.http_proxy;
if (httpProxy) {
return new ProxyAgent(httpProxy);
}
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy;
if (httpsProxy) {
return new ProxyAgent(httpsProxy);
}
return undefined;
}
export const fetch = async (url: string, opts: RequestInit) =>
await undiciFetch(url, {
dispatcher: getProxyAgent(),
...opts,
});
-1
View File
@@ -6,4 +6,3 @@ export const githubToken = core.getInput("github-token");
export const args = core.getInput("args"); export const args = core.getInput("args");
export const src = core.getInput("src"); export const src = core.getInput("src");
export const versionFile = core.getInput("version-file"); export const versionFile = core.getInput("version-file");
export const manifestFile = core.getInput("manifest-file");
-58
View File
@@ -1,58 +0,0 @@
import * as fs from "node:fs";
import * as path from "node:path";
import * as core from "@actions/core";
/**
* Search for a pyproject.toml file starting from the given directory
* and traversing upwards through parent directories until reaching
* the GitHub workspace root.
*
* @param startDir The directory to start the search from (e.g., the src input)
* @param workspaceRoot The GitHub workspace directory (GITHUB_WORKSPACE)
* @returns The path to the found pyproject.toml, or undefined if not found
*/
export function findPyprojectToml(
startDir: string,
workspaceRoot: string,
): string | undefined {
let currentDir = path.resolve(startDir);
const resolvedWorkspaceRoot = path.resolve(workspaceRoot);
while (true) {
const pyprojectPath = path.join(currentDir, "pyproject.toml");
core.debug(`Checking for ${pyprojectPath}`);
if (fs.existsSync(pyprojectPath)) {
core.info(`Found pyproject.toml at ${pyprojectPath}`);
return pyprojectPath;
}
if (currentDir === resolvedWorkspaceRoot) {
return undefined;
}
const parentDir = path.dirname(currentDir);
if (
parentDir === currentDir ||
!isPathWithinWorkspace(parentDir, resolvedWorkspaceRoot)
) {
return undefined;
}
currentDir = parentDir;
}
}
/**
* Check if a given path is within or equal to the workspace root.
*
* @param checkPath The path to check
* @param workspaceRoot The workspace root directory
* @returns true if within or equal to workspace, false if outside, undefined if can't determine
*/
function isPathWithinWorkspace(
checkPath: string,
workspaceRoot: string,
): boolean {
const relativePath = path.relative(workspaceRoot, checkPath);
return !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
}
+95
View File
@@ -0,0 +1,95 @@
import * as fs from "node:fs";
import * as core from "@actions/core";
import * as toml from "smol-toml";
function getRuffVersionFromAllDependencies(
allDependencies: string[],
): string | undefined {
const ruffVersionDefinition = allDependencies.find((dep: string) =>
dep.startsWith("ruff"),
);
if (ruffVersionDefinition) {
const ruffVersion = ruffVersionDefinition
.match(/^ruff([^A-Z0-9._-]+.*)$/)?.[1]
.trim();
if (ruffVersion?.startsWith("==")) {
return ruffVersion.slice(2);
}
core.info(`Found ruff version in pyproject.toml: ${ruffVersion}`);
return ruffVersion;
}
return undefined;
}
interface Pyproject {
project?: {
dependencies?: string[];
"optional-dependencies"?: Record<string, string[]>;
};
"dependency-groups"?: Record<string, Array<string | object>>;
tool?: {
poetry?: {
dependencies?: Record<string, string | object>;
group?: Record<string, { dependencies: Record<string, string | object> }>;
};
};
}
function parsePyproject(pyprojectContent: string): string | undefined {
const pyproject: Pyproject = toml.parse(pyprojectContent);
const dependencies: string[] = pyproject?.project?.dependencies || [];
const optionalDependencies: string[] = Object.values(
pyproject?.project?.["optional-dependencies"] || {},
).flat();
const devDependencies: string[] = Object.values(
pyproject?.["dependency-groups"] || {},
)
.flat()
.filter((item: string | object) => typeof item === "string");
return (
getRuffVersionFromAllDependencies(
dependencies.concat(optionalDependencies, devDependencies),
) || getRuffVersionFromPoetryGroups(pyproject)
);
}
function getRuffVersionFromPoetryGroups(
pyproject: Pyproject,
): string | undefined {
// Special handling for Poetry until it supports PEP 735
// See: <https://github.com/python-poetry/poetry/issues/9751>
const poetry = pyproject?.tool?.poetry || {};
const poetryGroups = Object.values(poetry.group || {});
if (poetry.dependencies) {
poetryGroups.unshift({ dependencies: poetry.dependencies });
}
return poetryGroups
.flatMap((group) => Object.entries(group.dependencies))
.map(([name, spec]) => {
if (name === "ruff" && typeof spec === "string") return spec;
return undefined;
})
.find((version) => version !== undefined);
}
export function getRuffVersionFromRequirementsFile(
filePath: string,
): string | undefined {
if (!fs.existsSync(filePath)) {
core.warning(`Could not find file: ${filePath}`);
return undefined;
}
const pyprojectContent = fs.readFileSync(filePath, "utf-8");
if (filePath.endsWith(".txt")) {
return getRuffVersionFromAllDependencies(pyprojectContent.split("\n"));
}
try {
return parsePyproject(pyprojectContent);
} catch (err) {
const message = (err as Error).message;
core.warning(`Error while parsing ${filePath}: ${message}`);
return undefined;
}
}
+176
View File
@@ -0,0 +1,176 @@
import * as core from "@actions/core";
export interface RetryOptions {
maxRetries: number;
initialDelayMs: number;
maxDelayMs: number;
backoffMultiplier: number;
timeoutMs?: number;
}
export const DEFAULT_RETRY_OPTIONS: RetryOptions = {
backoffMultiplier: 2,
initialDelayMs: 1000,
maxDelayMs: 10000,
maxRetries: 3,
timeoutMs: 30000, // 30 second timeout
};
export class RetryableError extends Error {
constructor(
message: string,
public readonly cause?: Error,
) {
super(message);
this.name = "RetryableError";
}
}
export class NonRetryableError extends Error {
constructor(
message: string,
public readonly cause?: Error,
) {
super(message);
this.name = "NonRetryableError";
}
}
export async function withRetry<T>(
operation: () => Promise<T>,
options: Partial<RetryOptions> = {},
operationName = "operation",
): Promise<T> {
const opts = { ...DEFAULT_RETRY_OPTIONS, ...options };
let lastError: Error | undefined;
for (let attempt = 0; attempt <= opts.maxRetries; attempt++) {
try {
if (attempt > 0) {
const delay = Math.min(
opts.initialDelayMs * opts.backoffMultiplier ** (attempt - 1),
opts.maxDelayMs,
);
core.info(
`Retrying ${operationName} (attempt ${attempt + 1}/${opts.maxRetries + 1}) after ${delay}ms delay...`,
);
await sleep(delay);
}
// Wrap operation with timeout if specified
if (opts.timeoutMs) {
return await withTimeout(operation(), opts.timeoutMs, operationName);
} else {
return await operation();
}
} catch (error) {
lastError = error as Error;
// Don't retry on non-retryable errors
if (lastError instanceof NonRetryableError) {
core.debug(
`Non-retryable error in ${operationName}: ${lastError.message}`,
);
throw lastError.cause || lastError;
}
// Log the error for debugging
core.debug(
`Attempt ${attempt + 1} failed for ${operationName}: ${lastError.message}`,
);
// If this was the last attempt, throw the error
if (attempt === opts.maxRetries) {
core.error(
`${operationName} failed after ${opts.maxRetries + 1} attempts. Last error: ${lastError.message}`,
);
throw lastError;
}
}
}
throw lastError || new Error(`${operationName} failed for unknown reason`);
}
export async function withTimeout<T>(
promise: Promise<T>,
timeoutMs: number,
operationName = "operation",
): Promise<T> {
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => {
reject(
new RetryableError(`${operationName} timed out after ${timeoutMs}ms`),
);
}, timeoutMs);
});
return Promise.race([promise, timeoutPromise]);
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export function isRetryableError(error: Error): boolean {
const message = error.message.toLowerCase();
// Network-related errors that should be retried
const retryableMessages = [
"connect timeout",
"connection timeout",
"timeout",
"econnreset",
"econnrefused",
"enotfound",
"network error",
"request timeout",
"socket timeout",
"fetch failed",
"connect etimedout",
];
// HTTP status codes that should be retried
const retryableStatusCodes = [408, 429, 500, 502, 503, 504];
// Check for retryable messages
if (retryableMessages.some((msg) => message.includes(msg))) {
return true;
}
// Check for HTTP status codes in error message
const statusMatch = message.match(/status.*?(\d{3})/);
if (statusMatch) {
const statusCode = parseInt(statusMatch[1], 10);
if (retryableStatusCodes.includes(statusCode)) {
return true;
}
}
return false;
}
export function wrapWithRetryLogic<T>(
operation: () => Promise<T>,
operationName: string,
options: Partial<RetryOptions> = {},
): () => Promise<T> {
return async () => {
try {
return await withRetry(operation, options, operationName);
} catch (error) {
const err = error as Error;
if (isRetryableError(err)) {
throw new RetryableError(
`${operationName} failed: ${err.message}`,
err,
);
} else {
throw new NonRetryableError(
`${operationName} failed: ${err.message}`,
err,
);
}
}
};
}
-185
View File
@@ -1,185 +0,0 @@
import fs from "node:fs";
import * as core from "@actions/core";
import * as toml from "smol-toml";
import { normalizeVersionSpecifier } from "./specifier";
import type { ParsedVersionFile, VersionFileFormat } from "./types";
interface VersionFileParser {
format: VersionFileFormat;
parse(filePath: string): string | undefined;
supports(filePath: string): boolean;
}
interface Pyproject {
project?: {
dependencies?: string[];
"optional-dependencies"?: Record<string, string[]>;
};
"dependency-groups"?: Record<string, Array<string | object>>;
tool?: {
poetry?: {
dependencies?: Record<string, string | object>;
group?: Record<string, { dependencies: Record<string, string | object> }>;
};
};
}
const VERSION_FILE_PARSERS: VersionFileParser[] = [
{
format: "pyproject.toml",
parse: (filePath) => {
const fileContent = fs.readFileSync(filePath, "utf-8");
return getRuffVersionFromPyprojectContent(fileContent);
},
supports: (filePath) => filePath.endsWith("pyproject.toml"),
},
{
format: "requirements",
parse: (filePath) => {
const fileContent = fs.readFileSync(filePath, "utf-8");
return getRuffVersionFromRequirementsText(fileContent);
},
supports: (filePath) => filePath.endsWith(".txt"),
},
];
export function getParsedVersionFile(
filePath: string,
): ParsedVersionFile | undefined {
core.info(`Trying to find version for ruff in: ${filePath}`);
if (!fs.existsSync(filePath)) {
core.warning(`Could not find file: ${filePath}`);
return undefined;
}
const parser = getVersionFileParser(filePath);
if (parser === undefined) {
return undefined;
}
try {
const specifier = parser.parse(filePath);
if (specifier === undefined) {
return undefined;
}
const normalizedSpecifier = normalizeVersionSpecifier(specifier);
core.info(`Found version for ruff in ${filePath}: ${normalizedSpecifier}`);
return {
format: parser.format,
specifier: normalizedSpecifier,
};
} catch (error) {
core.warning(
`Error while parsing ${filePath}: ${(error as Error).message}`,
);
return undefined;
}
}
export function getRuffVersionFromFile(filePath: string): string | undefined {
return getParsedVersionFile(filePath)?.specifier;
}
export function findRuffVersionInSpec(spec: string): string | undefined {
const trimmedSpec = spec.trim();
if (!trimmedSpec.startsWith("ruff")) {
return undefined;
}
let versionSpec = trimmedSpec.slice("ruff".length);
if (!versionSpec.match(/^(?:\s+|[=<>~!])/)) {
return undefined;
}
versionSpec = versionSpec.replace(/\\$/, "").trim();
const match = versionSpec.match(/^([^;]+)(?:;.*)?$/);
if (match) {
let version = match[1].trim();
if (version) {
version = normalizeVersionSpecifier(version);
if (trimmedSpec.includes(";")) {
core.warning(
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
);
}
core.info(`Found ruff version in requirements file: ${version}`);
return version;
}
}
return undefined;
}
export function getRuffVersionFromRequirementsText(
fileContent: string,
): string | undefined {
return getRuffVersionFromAllDependencies(fileContent.split("\n"));
}
export function getRuffVersionFromPyprojectContent(
pyprojectContent: string,
): string | undefined {
const pyproject = parsePyprojectContent(pyprojectContent);
return getRuffVersionFromParsedPyproject(pyproject);
}
export function parsePyprojectContent(pyprojectContent: string): Pyproject {
return toml.parse(pyprojectContent) as Pyproject;
}
function getVersionFileParser(filePath: string): VersionFileParser | undefined {
return VERSION_FILE_PARSERS.find((parser) => parser.supports(filePath));
}
function getRuffVersionFromParsedPyproject(
pyproject: Pyproject,
): string | undefined {
const dependencies: string[] = pyproject.project?.dependencies || [];
const optionalDependencies: string[] = Object.values(
pyproject.project?.["optional-dependencies"] || {},
).flat();
const devDependencies: string[] = Object.values(
pyproject["dependency-groups"] || {},
)
.flat()
.filter((item: string | object) => typeof item === "string");
return (
getRuffVersionFromAllDependencies(
dependencies.concat(optionalDependencies, devDependencies),
) || getRuffVersionFromPoetryGroups(pyproject)
);
}
function getRuffVersionFromPoetryGroups(
pyproject: Pyproject,
): string | undefined {
const poetry = pyproject.tool?.poetry || {};
const poetryGroups = Object.values(poetry.group || {});
if (poetry.dependencies) {
poetryGroups.unshift({ dependencies: poetry.dependencies });
}
return poetryGroups
.flatMap((group) => Object.entries(group.dependencies))
.map(([name, spec]) => {
if (typeof spec === "string") {
return findRuffVersionInSpec(`${name} ${spec}`);
}
return undefined;
})
.find((version) => version !== undefined);
}
function getRuffVersionFromAllDependencies(
allDependencies: string[],
): string | undefined {
return allDependencies
.map((dependency) => findRuffVersionInSpec(dependency))
.find((version) => version !== undefined);
}
-125
View File
@@ -1,125 +0,0 @@
import * as core from "@actions/core";
import * as tc from "@actions/tool-cache";
import * as pep440 from "@renovatebot/pep440";
import { getAllVersions, getLatestVersion } from "../download/manifest";
import {
type ParsedVersionSpecifier,
parseVersionSpecifier,
} from "./specifier";
import type { ResolveRuffVersionOptions } from "./types";
import { resolveVersionRequest } from "./version-request-resolver";
interface ConcreteVersionResolutionContext {
manifestUrl?: string;
parsedSpecifier: ParsedVersionSpecifier;
}
interface ConcreteVersionResolver {
resolve(
context: ConcreteVersionResolutionContext,
): Promise<string | undefined>;
}
class ExactVersionResolver implements ConcreteVersionResolver {
async resolve(
context: ConcreteVersionResolutionContext,
): Promise<string | undefined> {
if (context.parsedSpecifier.kind !== "exact") {
return undefined;
}
core.debug(
`Version ${context.parsedSpecifier.normalized} is an explicit version.`,
);
return context.parsedSpecifier.normalized;
}
}
class LatestVersionResolver implements ConcreteVersionResolver {
async resolve(
context: ConcreteVersionResolutionContext,
): Promise<string | undefined> {
if (context.parsedSpecifier.kind !== "latest") {
return undefined;
}
return await getLatestVersion(context.manifestUrl);
}
}
class RangeVersionResolver implements ConcreteVersionResolver {
async resolve(
context: ConcreteVersionResolutionContext,
): Promise<string | undefined> {
if (context.parsedSpecifier.kind !== "range") {
return undefined;
}
const availableVersions = await getAllVersions(context.manifestUrl);
const resolvedVersion = maxSatisfying(
availableVersions,
context.parsedSpecifier.normalized,
);
if (resolvedVersion === undefined) {
throw new Error(`No version found for ${context.parsedSpecifier.raw}`);
}
core.debug(`Resolved version: ${resolvedVersion}`);
return resolvedVersion;
}
}
const CONCRETE_VERSION_RESOLVERS: ConcreteVersionResolver[] = [
new ExactVersionResolver(),
new LatestVersionResolver(),
new RangeVersionResolver(),
];
export async function resolveRuffVersion(
options: ResolveRuffVersionOptions,
): Promise<string> {
const request = resolveVersionRequest(options);
return await resolveVersion(request.specifier, options.manifestFile);
}
export async function resolveVersion(
versionInput: string,
manifestUrl?: string,
): Promise<string> {
core.debug(`Resolving ${versionInput}...`);
const context: ConcreteVersionResolutionContext = {
manifestUrl,
parsedSpecifier: parseVersionSpecifier(versionInput),
};
for (const resolver of CONCRETE_VERSION_RESOLVERS) {
const version = await resolver.resolve(context);
if (version !== undefined) {
return version;
}
}
throw new Error(`No version found for ${versionInput}`);
}
function maxSatisfying(
versions: string[],
version: string,
): string | undefined {
const maxSemver = tc.evaluateVersions(versions, version);
if (maxSemver !== "") {
core.debug(`Found a version that satisfies the semver range: ${maxSemver}`);
return maxSemver;
}
const maxPep440 = pep440.maxSatisfying(versions, version);
if (maxPep440 !== null) {
core.debug(
`Found a version that satisfies the pep440 specifier: ${maxPep440}`,
);
return maxPep440;
}
return undefined;
}
-57
View File
@@ -1,57 +0,0 @@
import * as tc from "@actions/tool-cache";
export type ParsedVersionSpecifier =
| {
kind: "exact";
normalized: string;
raw: string;
}
| {
kind: "latest";
normalized: "latest";
raw: string;
}
| {
kind: "range";
normalized: string;
raw: string;
};
export function normalizeVersionSpecifier(specifier: string): string {
const trimmedSpecifier = specifier.trim();
if (trimmedSpecifier.startsWith("==")) {
return trimmedSpecifier.slice(2);
}
return trimmedSpecifier;
}
export function parseVersionSpecifier(
specifier: string,
): ParsedVersionSpecifier {
const raw = specifier.trim();
const normalized = normalizeVersionSpecifier(raw);
if (normalized === "latest") {
return {
kind: "latest",
normalized: "latest",
raw,
};
}
if (tc.isExplicitVersion(normalized)) {
return {
kind: "exact",
normalized,
raw,
};
}
return {
kind: "range",
normalized,
raw,
};
}
-27
View File
@@ -1,27 +0,0 @@
export type VersionSource =
| "input"
| "version-file"
| "pyproject.toml"
| "default";
export type VersionFileFormat = "pyproject.toml" | "requirements";
export interface ParsedVersionFile {
format: VersionFileFormat;
specifier: string;
}
export interface ResolveRuffVersionOptions {
manifestFile?: string;
sourceDirectory: string;
version?: string;
versionFile?: string;
workspaceRoot: string;
}
export interface VersionRequest {
format?: VersionFileFormat;
source: VersionSource;
sourcePath?: string;
specifier: string;
}
-162
View File
@@ -1,162 +0,0 @@
import * as core from "@actions/core";
import { findPyprojectToml } from "../utils/pyproject-finder";
import { getParsedVersionFile } from "./file-parser";
import { normalizeVersionSpecifier } from "./specifier";
import type {
ParsedVersionFile,
ResolveRuffVersionOptions,
VersionRequest,
} from "./types";
export interface VersionRequestResolver {
resolve(context: VersionRequestContext): VersionRequest | undefined;
}
export class VersionRequestContext {
readonly sourceDirectory: string;
readonly version: string | undefined;
readonly versionFile: string | undefined;
readonly workspaceRoot: string;
private readonly parsedFiles = new Map<
string,
ParsedVersionFile | undefined
>();
constructor(
version: string | undefined,
versionFile: string | undefined,
sourceDirectory: string,
workspaceRoot: string,
) {
this.version = version;
this.versionFile = versionFile;
this.sourceDirectory = sourceDirectory;
this.workspaceRoot = workspaceRoot;
}
getVersionFile(filePath: string): ParsedVersionFile | undefined {
const cachedResult = this.parsedFiles.get(filePath);
if (cachedResult !== undefined || this.parsedFiles.has(filePath)) {
return cachedResult;
}
const result = getParsedVersionFile(filePath);
this.parsedFiles.set(filePath, result);
return result;
}
getWorkspacePyprojectPath(): string | undefined {
return findPyprojectToml(this.sourceDirectory, this.workspaceRoot);
}
}
export class ExplicitInputVersionResolver implements VersionRequestResolver {
resolve(context: VersionRequestContext): VersionRequest | undefined {
if (context.version === undefined) {
return undefined;
}
return {
source: "input",
specifier: normalizeVersionSpecifier(context.version),
};
}
}
export class VersionFileVersionResolver implements VersionRequestResolver {
resolve(context: VersionRequestContext): VersionRequest | undefined {
if (context.versionFile === undefined) {
return undefined;
}
const versionFile = context.getVersionFile(context.versionFile);
if (versionFile === undefined) {
core.warning(
`Could not parse version from ${context.versionFile}. Using latest version.`,
);
return undefined;
}
return {
format: versionFile.format,
source: "version-file",
sourcePath: context.versionFile,
specifier: versionFile.specifier,
};
}
}
export class WorkspaceVersionResolver implements VersionRequestResolver {
resolve(context: VersionRequestContext): VersionRequest | undefined {
const pyprojectPath = context.getWorkspacePyprojectPath();
if (!pyprojectPath) {
core.info("Could not find pyproject.toml. Using latest version.");
return undefined;
}
const versionFile = context.getVersionFile(pyprojectPath);
if (versionFile === undefined) {
core.info(
`Could not parse version from ${pyprojectPath}. Using latest version.`,
);
return undefined;
}
return {
format: versionFile.format,
source: "pyproject.toml",
sourcePath: pyprojectPath,
specifier: versionFile.specifier,
};
}
}
export class LatestVersionResolver implements VersionRequestResolver {
resolve(): VersionRequest {
return {
source: "default",
specifier: "latest",
};
}
}
const VERSION_REQUEST_RESOLVERS: VersionRequestResolver[] = [
new ExplicitInputVersionResolver(),
new VersionFileVersionResolver(),
new WorkspaceVersionResolver(),
new LatestVersionResolver(),
];
export function resolveVersionRequest(
options: ResolveRuffVersionOptions,
): VersionRequest {
const version = emptyToUndefined(options.version);
const versionFile = emptyToUndefined(options.versionFile);
if (version !== undefined && versionFile !== undefined) {
throw new Error(
"It is not allowed to specify both version and version-file",
);
}
const context = new VersionRequestContext(
version,
versionFile,
options.sourceDirectory,
options.workspaceRoot,
);
for (const resolver of VERSION_REQUEST_RESOLVERS) {
const request = resolver.resolve(context);
if (request !== undefined) {
return request;
}
}
throw new Error("Could not resolve a requested Ruff version.");
}
function emptyToUndefined(value: string | undefined): string | undefined {
return value === undefined || value === "" ? undefined : value;
}
+8 -8
View File
@@ -1,12 +1,12 @@
{ {
"compilerOptions": { "compilerOptions": {
"esModuleInterop": true, "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
"isolatedModules": true, "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
"module": "esnext", "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
"moduleResolution": "bundler", "outDir": "./lib" /* Redirect output structure to the directory. */,
"noImplicitAny": true, "rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
"strict": true, "strict": true /* Enable all strict type-checking options. */,
"target": "ES2022" "target": "ES2022" /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
}, },
"include": ["src/**/*.ts"] "exclude": ["node_modules", "**/*.test.ts"]
} }