Compare commits

..
Author SHA1 Message Date
Kevin (Mainz Fan) fd60375bed Fix: Update RUFF_VERSION check to use 0.14.11 (latest) 2026-01-25 12:54:19 +00:00
57 changed files with 77768 additions and 71610 deletions
@@ -1,53 +0,0 @@
---
name: dependabot-pr-rollup
description: Find open Dependabot PRs for ruff-action, compare each PR head to its base branch, replay only the remaining npm and GitHub Actions changes in a fresh worktree and branch, run repository validation, and optionally commit, push, and open a PR. Use when you want to batch or manually replicate active Dependabot updates.
license: MIT
compatibility: Requires git, git worktree, gh CLI auth, npm, and a GitHub repo with an origin remote.
---
# Dependabot PR Rollup
## When to use
Use this skill when the user wants to:
- find all open Dependabot PRs in this repo
- reproduce their net effect in one local branch
- validate the result with the repo's standard checks
- optionally commit, push, and open a PR
## Workflow
1. Inspect the current checkout state, but do not reuse a dirty worktree.
2. List open Dependabot PRs with `gh pr list --state open --author app/dependabot`.
3. For each PR, collect the title, base branch, head branch, changed files, and relevant diffs.
4. Compare each PR head against `origin/<base>` instead of trusting the PR title. Dependabot PRs can already be partially merged, superseded by newer versions, or have no remaining net effect.
5. Create a new worktree and branch from `origin/<base>`.
6. Reproduce only the remaining dependency changes in the new worktree.
- Inspect `package.json` before editing npm dependencies.
- Run `npm ci --ignore-scripts` before applying npm updates so dependency commands run from a clean install.
- Use `npm install ... --ignore-scripts` for direct npm dependency changes so `package-lock.json` stays in sync.
- Preserve whether an npm package is a `dependency` or `devDependency`.
- When updating `@biomejs/biome`, also update the Biome schema URL version in `biome.json` to match the installed Biome version.
- For GitHub Actions updates, apply the net workflow-file changes from the Dependabot PR heads after confirming they still apply cleanly to the selected base.
7. Run `npm run all`.
8. If requested, commit the changed source, lockfile, workflow files, and generated artifacts, then push and open a PR.
## Repo-specific notes
- Use `gh` for GitHub operations.
- Keep the user's original checkout untouched by working in a separate worktree.
- This repo has Dependabot configured for both `npm` and `github-actions`.
- In this repo, `npm run all` is the safest validation command because it runs build, check, package, and unit tests.
- `npm run check` may rewrite files with Biome; include those intentional formatting changes.
- If dependency changes affect bundled output, include the regenerated `dist/` files from `npm run package`.
## Report back
Always report:
- open Dependabot PRs found
- which PRs required no net changes
- new branch name
- new worktree path
- files changed
- `npm run all` result
- if applicable, commit SHA and PR URL
-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@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 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@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 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@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5 uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
+1 -3
View File
@@ -17,8 +17,6 @@ jobs:
pull-requests: read pull-requests: read
steps: steps:
- name: 🚀 Run Release Drafter - name: 🚀 Run Release Drafter
uses: release-drafter/release-drafter@c2e2804cc59f45f57076a99af580d0fedb697927 # v7.3.0 uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0
with:
commitish: ${{ github.sha }}
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-113
View File
@@ -1,113 +0,0 @@
name: Release
on:
workflow_dispatch:
inputs:
version:
description: "Release version (e.g., 8.1.0)"
required: true
type: string
permissions: {}
jobs:
validate-release:
name: Validate release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Validate version and draft release
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ inputs.version }}
TAG: v${{ 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
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
release-gate:
# N.B. This name should not change, it is used for downstream checks.
name: release-gate
needs:
- validate-release
runs-on: ubuntu-latest
environment:
name: release-gate
steps:
- run: echo "Release approved"
create-deployment:
name: create-deployment
needs:
- validate-release
- release-gate
runs-on: ubuntu-latest
environment:
name: release
steps:
- run: echo "Release deployment created"
release:
name: Release
needs:
- validate-release
- release-gate
- create-deployment
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Publish release
env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ inputs.version }}
TAG: v${{ 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
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
+27 -129
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@447fbfe7533062b7a9ea55f790f2396fba6d052a # v1.10.0
- name: Run zizmor - uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with: with:
node-version-file: .nvmrc node-version: "20"
cache: npm
- run: | - run: |
npm ci --ignore-scripts npm ci --ignore-scripts
- 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
@@ -122,7 +117,7 @@ jobs:
src: __tests__/fixtures/python-project src: __tests__/fixtures/python-project
- name: Correct version gets installed - name: Correct version gets installed
run: | run: |
if [ "$RUFF_VERSION" != "0.6.2" ]; then if [ "$RUFF_VERSION" != "0.14.11" ]; then
exit 1 exit 1
fi fi
env: env:
@@ -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
@@ -168,7 +163,7 @@ jobs:
test-default-version-from-pyproject-dependency-groups-with-env-marker: test-default-version-from-pyproject-dependency-groups-with-env-marker:
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 with environment marker - name: Use default version from pyproject.toml with environment marker
@@ -179,8 +174,7 @@ jobs:
version-file: __tests__/fixtures/pyproject-dependency-groups-with-env-marker/pyproject.toml version-file: __tests__/fixtures/pyproject-dependency-groups-with-env-marker/pyproject.toml
- name: Correct version gets installed - name: Correct version gets installed
run: | run: |
if [ "$RUFF_VERSION" != "0.13.3" ]; then if [ "$RUFF_VERSION" != "0.14.11" ]; then
echo "Expected version 0.13.3 but got $RUFF_VERSION"
exit 1 exit 1
fi fi
env: env:
@@ -188,7 +182,7 @@ jobs:
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 +201,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 +220,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 +239,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
@@ -264,7 +258,7 @@ jobs:
test-default-version-from-requirements-with-hash: test-default-version-from-requirements-with-hash:
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-with-hash.txt - name: Use default version from requirements-with-hash.txt
@@ -280,29 +274,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-uv-lock:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Use default version from uv.lock
id: ruff-action
uses: ./
with:
src: __tests__/fixtures/python-project
version-file: __tests__/fixtures/uv.lock
- name: Correct version gets installed
run: |
if [ "$RUFF_VERSION" != "0.9.5" ]; 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
@@ -321,7 +296,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
@@ -347,7 +322,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
@@ -359,7 +334,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
@@ -373,7 +348,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
@@ -384,7 +359,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
@@ -414,41 +389,13 @@ jobs:
fi fi
env: env:
CHECK_SHOULD_FAIL_OUTCOME: ${{ steps.check-should-fail.outcome }} CHECK_SHOULD_FAIL_OUTCOME: ${{ steps.check-should-fail.outcome }}
test-glob-src:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Use glob pattern in src
uses: ./
with:
src: __tests__/fixtures/glob-project/**/*.py
test-glob-single-star-src:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ ubuntu-latest, windows-latest ]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Use single-star glob pattern in src
uses: ./
with:
src: __tests__/fixtures/glob-single-star-project/foo/bar/*.py
test-multiple-src: test-multiple-src:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
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
@@ -458,50 +405,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
@@ -520,18 +423,13 @@ jobs:
- 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-default-version-from-requirements-with-hash
- test-default-version-from-uv-lock
- test-semver-range - test-semver-range
- test-pep440-version-specifier - test-pep440-version-specifier
- test-checksum - test-checksum
- test-with-explicit-token - test-with-explicit-token
- test-args - test-args
- test-failure - test-failure
- test-glob-src
- test-glob-single-star-src
- 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
+5 -6
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@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.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 ci --ignore-scripts && npm run all
- name: Create Pull Request - name: Create Pull Request
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
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
+28 -86
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,76 +27,44 @@ 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 |
| `download-from-astral-mirror` | Download Ruff from the Astral mirror instead of directly from GitHub Releases. | `true` |
| `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` | Source path(s) to run `ruff` on. Supports glob patterns. | [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
```yaml ```yaml
- uses: astral-sh/ruff-action@v4.1.0 - uses: astral-sh/ruff-action@v3
``` ```
### Specify a different source directory ### Specify a different source directory
```yaml ```yaml
- uses: astral-sh/ruff-action@v4.1.0 - uses: astral-sh/ruff-action@v3
with: with:
src: "./src" src: "./src"
``` ```
### Specify multiple files ### Specify multiple files
Separate multiple `src` values with whitespace.
```yaml ```yaml
- uses: astral-sh/ruff-action@v4.1.0 - uses: astral-sh/ruff-action@v3
with: with:
src: >- src: >-
path/to/file1.py path/to/file1.py
path/to/file2.py path/to/file2.py
``` ```
### Use glob patterns
Glob patterns (`*`, `?`, `[...]`, and `**`) are expanded by the action using
[`@actions/glob`](https://github.com/actions/toolkit/tree/main/packages/glob),
so they work consistently across Linux, macOS, and Windows runners. This action
does not emulate Bash, PowerShell, or other shell-specific glob expansion.
Hidden files and directories are skipped by default; target them explicitly, for
example with `.venv/**/*.py`, to include them. If a pattern does not match any
files, the original pattern is passed to Ruff so Ruff can report the missing
path. Literal glob metacharacters in file or directory names must be escaped
using `@actions/glob` syntax.
```yaml
- uses: astral-sh/ruff-action@v4.1.0
with:
src: "src/**/*.py"
```
> [!NOTE]
> When using multiple patterns, only the first is used to search for
> `pyproject.toml` to determine the Ruff version. Use the `version` input
> for explicit control. Large glob expansions may hit command-line length limits,
> especially on Windows.
### Use to install ruff ### Use to install ruff
This action adds ruff to the PATH, so you can use it in subsequent steps. This action adds ruff to the PATH, so you can use it in subsequent steps.
```yaml ```yaml
- uses: astral-sh/ruff-action@v4.1.0 - uses: astral-sh/ruff-action@v3
- run: ruff check --fix - run: ruff check --fix
- run: ruff format - run: ruff format
``` ```
@@ -108,7 +75,7 @@ you can use the `args` input to overwrite the default value (`check`):
```yaml ```yaml
- name: Install ruff without running check or format - name: Install ruff without running check or format
uses: astral-sh/ruff-action@v4.1.0 uses: astral-sh/ruff-action@v3
with: with:
args: "--version" args: "--version"
``` ```
@@ -116,17 +83,17 @@ you can use the `args` input to overwrite the default value (`check`):
### Use `ruff format` ### Use `ruff format`
```yaml ```yaml
- uses: astral-sh/ruff-action@v4.1.0 - uses: astral-sh/ruff-action@v3
with: with:
args: "format --check --diff" args: "format --check --diff"
``` ```
### 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.
@@ -135,7 +102,7 @@ or no Ruff version is defined in `project.dependencies`, `project.optional-depen
```yaml ```yaml
- name: Install the latest version of ruff - name: Install the latest version of ruff
uses: astral-sh/ruff-action@v4.1.0 uses: astral-sh/ruff-action@v3
with: with:
version: "latest" version: "latest"
``` ```
@@ -144,7 +111,7 @@ or no Ruff version is defined in `project.dependencies`, `project.optional-depen
```yaml ```yaml
- name: Install a specific version of ruff - name: Install a specific version of ruff
uses: astral-sh/ruff-action@v4.1.0 uses: astral-sh/ruff-action@v3
with: with:
version: "0.4.4" version: "0.4.4"
``` ```
@@ -157,21 +124,21 @@ to install the latest version that satisfies the range.
```yaml ```yaml
- name: Install a semver range of ruff - name: Install a semver range of ruff
uses: astral-sh/ruff-action@v4.1.0 uses: astral-sh/ruff-action@v3
with: with:
version: ">=0.4.0" version: ">=0.4.0"
``` ```
```yaml ```yaml
- name: Pinning a minor version of ruff - name: Pinning a minor version of ruff
uses: astral-sh/ruff-action@v4.1.0 uses: astral-sh/ruff-action@v3
with: with:
version: "0.4.x" version: "0.4.x"
``` ```
```yaml ```yaml
- name: Install a pep440-specifier-satisfying version of ruff - name: Install a pep440-specifier-satisfying version of ruff
uses: astral-sh/ruff-action@v4.1.0 uses: astral-sh/ruff-action@v3
with: with:
version: ">=0.11.10,<0.12.0" version: ">=0.11.10,<0.12.0"
``` ```
@@ -179,34 +146,13 @@ 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`, `requirements.txt` and `uv.lock` are supported. If the file cannot Currently `pyproject.toml` and `requirements.txt` are supported.
be parsed 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
uses: astral-sh/ruff-action@v4.1.0 uses: astral-sh/ruff-action@v3
with: with:
version-file: "my-path/to/pyproject.toml-requirements.txt-or-uv.lock" 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@v4.1.0
with:
version: "latest"
manifest-file: "https://example.com/ruff.ndjson"
``` ```
### Validate checksum ### Validate checksum
@@ -217,7 +163,7 @@ are automatically verified by this action. The sha256 hashes can be found on the
```yaml ```yaml
- name: Install a specific version and validate the checksum - name: Install a specific version and validate the checksum
uses: astral-sh/ruff-action@v4.1.0 uses: astral-sh/ruff-action@v3
with: with:
version: "0.7.4" version: "0.7.4"
checksum: "0de731c669b9ece77e799ac3f4a160c30849752714d9775c94cc4cfaf326860c" checksum: "0de731c669b9ece77e799ac3f4a160c30849752714d9775c94cc4cfaf326860c"
@@ -225,13 +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 Ruff 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.
Set `download-from-astral-mirror` to `false` to skip the Astral mirror and download directly from GitHub Releases.
You can provide a token via `github-token` to authenticate GitHub Releases 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)
@@ -239,7 +181,7 @@ are not sufficient, you can provide a custom GitHub token with the necessary per
```yaml ```yaml
- name: Install the latest version of ruff with a custom GitHub token - name: Install the latest version of ruff with a custom GitHub token
uses: astral-sh/ruff-action@v4.1.0 uses: astral-sh/ruff-action@v3
with: with:
github-token: ${{ secrets.CUSTOM_GITHUB_TOKEN }} github-token: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
``` ```
@@ -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"}]}
-537
View File
@@ -1,537 +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("skips the Astral mirror when downloadFromAstralMirror is false", 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",
undefined,
false,
);
expect(mockDownloadTool).toHaveBeenCalledWith(
"https://github.com/astral-sh/ruff/releases/download/0.15.8/ruff-x86_64-unknown-linux-gnu.tar.gz",
undefined,
"token",
);
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 = "pyython-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"ruff==0.6.2",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
@@ -1 +0,0 @@
print("hello")
@@ -1 +0,0 @@
print("deeper")
@@ -1 +0,0 @@
print("check me")
@@ -1,2 +0,0 @@
# This file should be ignored when using foo/bar/*.py glob
print("ignore me")
@@ -1,13 +0,0 @@
[project]
name = "glob-single-star-project"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"ruff==0.6.2",
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
@@ -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!")
@@ -7,7 +7,7 @@ requires-python = ">=3.12"
[dependency-groups] [dependency-groups]
dev = [ dev = [
"ruff~=0.13 ; python_version >= '3.11'", "ruff>=0.14 ; python_version >= '3.11'",
] ]
[build-system] [build-system]
-20
View File
@@ -1,20 +0,0 @@
version = 1
revision = 1
requires-python = ">=3.11"
[[package]]
name = "example-project"
version = "0.1.0"
source = { editable = "." }
dependencies = [
{ name = "ruff" },
]
[package.metadata]
requires-dist = [{ name = "ruff", specifier = ">=0.9.0" }]
[[package]]
name = "ruff"
version = "0.9.5"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/source/r/ruff/ruff-0.9.5.tar.gz", hash = "sha256:11aecd7a633932875ab3cb05a484c99970b9d52606ce9ea912b690b02653d56c", size = 3634177 }
-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");
});
});
});
-374
View File
@@ -1,374 +0,0 @@
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import { afterEach, describe, expect, it, jest } from "@jest/globals";
jest.unstable_mockModule("@actions/core", () => ({
debug: jest.fn(),
info: jest.fn(),
}));
const {
expandSourceInput,
getSourceBasePath,
getVersionSourceDirectory,
splitInput,
} = await import("../../src/utils/source-input");
const { findPyprojectToml } = await import("../../src/utils/pyproject-finder");
let tempDir: string | undefined;
afterEach(async () => {
if (tempDir !== undefined) {
await fs.rm(tempDir, { force: true, recursive: true });
tempDir = undefined;
}
});
async function createTempProject(): Promise<string> {
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "ruff-action-source-"));
await fs.mkdir(path.join(tempDir, "src", "package", "nested"), {
recursive: true,
});
await fs.writeFile(path.join(tempDir, "src", "package", "__init__.py"), "");
await fs.writeFile(path.join(tempDir, "src", "package", "main.py"), "");
await fs.writeFile(
path.join(tempDir, "src", "package", "nested", "mod.py"),
"",
);
await fs.writeFile(path.join(tempDir, "src", "package", "notes.txt"), "");
await fs.mkdir(path.join(tempDir, "src", "pkg[legacy]"), {
recursive: true,
});
await fs.writeFile(path.join(tempDir, "src", "pkg[legacy]", "main.py"), "");
await fs.mkdir(path.join(tempDir, "src", "pkgl"), { recursive: true });
await fs.writeFile(path.join(tempDir, "src", "pkgl", "main.py"), "");
return tempDir;
}
describe("splitInput", () => {
it("splits whitespace-delimited inputs", () => {
expect(splitInput(" check --fix\n--diff ")).toEqual([
"check",
"--fix",
"--diff",
]);
});
it("returns an empty array for empty input", () => {
expect(splitInput(" \n\t ")).toEqual([]);
});
});
describe("expandSourceInput", () => {
it("leaves non-glob sources unchanged", async () => {
await expect(expandSourceInput("src file.py")).resolves.toEqual([
"src",
"file.py",
]);
});
it("expands recursive globs without relying on shell behavior", async () => {
const projectDir = await createTempProject();
const pattern = path.join(projectDir, "src", "**", "*.py");
await expect(expandSourceInput(pattern)).resolves.toEqual([
path.join(projectDir, "src", "package", "__init__.py"),
path.join(projectDir, "src", "package", "main.py"),
path.join(projectDir, "src", "package", "nested", "mod.py"),
path.join(projectDir, "src", "pkg[legacy]", "main.py"),
path.join(projectDir, "src", "pkgl", "main.py"),
]);
});
it("preserves relative source patterns as relative paths", async () => {
const projectDir = await createTempProject();
const originalCwd = process.cwd();
try {
process.chdir(projectDir);
await expect(expandSourceInput("src/**/*.py")).resolves.toEqual([
path.join("src", "package", "__init__.py"),
path.join("src", "package", "main.py"),
path.join("src", "package", "nested", "mod.py"),
path.join("src", "pkg[legacy]", "main.py"),
path.join("src", "pkgl", "main.py"),
]);
} finally {
process.chdir(originalCwd);
}
});
it("maps relative root glob matches to . instead of an empty argument", async () => {
const projectDir = await createTempProject();
const originalCwd = process.cwd();
try {
process.chdir(projectDir);
for (const pattern of ["**", "**/", "./**"]) {
const sources = await expandSourceInput(pattern);
expect(sources).toContain(".");
expect(sources).not.toContain("");
}
} finally {
process.chdir(originalCwd);
}
});
it("excludes hidden paths by default while allowing explicit hidden patterns", async () => {
const projectDir = await createTempProject();
const originalCwd = process.cwd();
await fs.mkdir(path.join(projectDir, ".venv"));
await fs.writeFile(path.join(projectDir, ".venv", "ignored.py"), "");
await fs.mkdir(path.join(projectDir, ".tox"));
await fs.writeFile(path.join(projectDir, ".tox", "ignored.py"), "");
try {
process.chdir(projectDir);
const defaultSources = await expandSourceInput("**/*.py");
expect(defaultSources).not.toContain(path.join(".venv", "ignored.py"));
expect(defaultSources).not.toContain(path.join(".tox", "ignored.py"));
await expect(expandSourceInput(".venv/**/*.py")).resolves.toEqual([
path.join(".venv", "ignored.py"),
]);
} finally {
process.chdir(originalCwd);
}
});
it("preserves unmatched globs for Ruff to report", async () => {
const projectDir = await createTempProject();
const pattern = path.join(projectDir, "src", "**", "missing-*.py");
await expect(expandSourceInput(pattern)).resolves.toEqual([pattern]);
});
it("expands multiple sources in input order", async () => {
const projectDir = await createTempProject();
const first = path.join(projectDir, "src", "package", "main.py");
const second = path.join(projectDir, "src", "package", "nested", "*.py");
await expect(expandSourceInput(`${first} ${second}`)).resolves.toEqual([
first,
path.join(projectDir, "src", "package", "nested", "mod.py"),
]);
});
it("uses @actions/glob syntax for paths that contain glob metacharacters", async () => {
const projectDir = await createTempProject();
await expect(
expandSourceInput(path.join(projectDir, "src", "pkg[legacy]")),
).resolves.toEqual([path.join(projectDir, "src", "pkgl")]);
await expect(
expandSourceInput(path.join(projectDir, "src", "pkg[legacy]", "*.py")),
).resolves.toEqual([path.join(projectDir, "src", "pkgl", "main.py")]);
});
it("supports @actions/glob escaping for literal glob metacharacters", async () => {
const projectDir = await createTempProject();
const pattern = path.join(projectDir, "src", "pkg[[]legacy]", "*.py");
await expect(expandSourceInput(pattern)).resolves.toEqual([
path.join(projectDir, "src", "pkg[legacy]", "main.py"),
]);
});
it("does not traverse symlinked directories", async () => {
const projectDir = await createTempProject();
const externalDir = path.join(projectDir, "external");
const symlinkPath = path.join(projectDir, "src", "linked");
await fs.mkdir(externalDir);
await fs.writeFile(path.join(externalDir, "external.py"), "");
await fs.symlink(externalDir, symlinkPath, "dir");
const sources = await expandSourceInput(
path.join(projectDir, "src", "**", "*.py"),
);
expect(sources).not.toContain(path.join(symlinkPath, "external.py"));
});
it("still expands missing paths that contain glob metacharacters", async () => {
const projectDir = await createTempProject();
const pattern = path.join(projectDir, "src", "pkg[l]");
await expect(expandSourceInput(pattern)).resolves.toEqual([
path.join(projectDir, "src", "pkgl"),
]);
});
});
describe("getVersionSourceDirectory", () => {
it("does not scan src globs when version input is explicit", async () => {
await expect(
getVersionSourceDirectory("../**/*.py", "0.1.0", undefined),
).resolves.toBe(".");
});
it("does not scan src globs when version-file input is explicit", async () => {
await expect(
getVersionSourceDirectory("../**/*.py", undefined, "pyproject.toml"),
).resolves.toBe(".");
});
it("uses source discovery when no version input is explicit", async () => {
await expect(
getVersionSourceDirectory("../**/*.py", undefined, undefined),
).rejects.toThrow("Invalid pattern '../**/*.py'");
});
});
describe("getSourceBasePath", () => {
it("returns the directory for a plain path", async () => {
await expect(getSourceBasePath("my-project")).resolves.toBe("my-project");
});
it("returns the directory for a dotted path", async () => {
await expect(getSourceBasePath("./src")).resolves.toBe("./src");
});
it("preserves filesystem root paths", async () => {
const root = path.parse(process.cwd()).root;
await expect(getSourceBasePath(root)).resolves.toBe(root);
});
it("strips glob pattern and returns parent directory", async () => {
await expect(getSourceBasePath("src/**/*.py")).resolves.toBe("src");
});
it("returns . for a top-level glob", async () => {
await expect(getSourceBasePath("*.py")).resolves.toBe(".");
});
it("returns . for empty string", async () => {
await expect(getSourceBasePath("")).resolves.toBe(".");
});
it("uses only the first whitespace-delimited token", async () => {
await expect(getSourceBasePath("sub1/*.py sub2/")).resolves.toBe("sub1");
});
it("handles trailing slash in prefix", async () => {
await expect(getSourceBasePath("sub/*.py")).resolves.toBe("sub");
});
it("uses the parent directory when a glob appears in a filename", async () => {
await expect(getSourceBasePath("src/file*.py")).resolves.toBe("src");
});
it("handles ? glob", async () => {
await expect(getSourceBasePath("file?.py")).resolves.toBe(".");
});
it("handles bracket glob", async () => {
await expect(getSourceBasePath("file[0-9].py")).resolves.toBe(".");
});
it("uses @actions/glob syntax for base paths with glob metacharacters", async () => {
const projectDir = await createTempProject();
const originalCwd = process.cwd();
try {
process.chdir(projectDir);
await expect(getSourceBasePath("src/pkg[legacy]")).resolves.toBe(
path.join("src", "pkgl"),
);
await expect(getSourceBasePath("src/pkg[legacy]/*.py")).resolves.toBe(
path.join("src", "pkgl", "main.py"),
);
} finally {
process.chdir(originalCwd);
}
});
it("supports @actions/glob escaping for version discovery from literal glob metacharacters", async () => {
const projectDir = await createTempProject();
const originalCwd = process.cwd();
const pyprojectPath = path.join(
projectDir,
"src",
"pkg[legacy]",
"pyproject.toml",
);
await fs.writeFile(
pyprojectPath,
"[project]\ndependencies = ['ruff==0.6.2']\n",
);
try {
process.chdir(projectDir);
const sourceBasePath = await getSourceBasePath(
"src/pkg[[]legacy]/main.py",
);
expect(findPyprojectToml(sourceBasePath, projectDir)).toBe(
await fs.realpath(pyprojectPath),
);
} finally {
process.chdir(originalCwd);
}
});
it("uses the first expanded glob match for version discovery", async () => {
const projectDir = await createTempProject();
const originalCwd = process.cwd();
const pyprojectPath = path.join(
projectDir,
"packages",
"foo",
"pyproject.toml",
);
await fs.mkdir(path.join(projectDir, "packages", "foo", "src"), {
recursive: true,
});
await fs.writeFile(
pyprojectPath,
"[project]\ndependencies = ['ruff==0.10.0']\n",
);
await fs.writeFile(
path.join(projectDir, "packages", "foo", "src", "module.py"),
"",
);
try {
process.chdir(projectDir);
const sourceBasePath = await getSourceBasePath("packages/*/src/**/*.py");
expect(sourceBasePath).toBe(
path.join("packages", "foo", "src", "module.py"),
);
const workspaceRoot = await fs.realpath(projectDir);
expect(findPyprojectToml(sourceBasePath, workspaceRoot)).toBe(
await fs.realpath(pyprojectPath),
);
} finally {
process.chdir(originalCwd);
}
});
it("uses the unmatched glob fallback when version-discovery glob has no matches", async () => {
const projectDir = await createTempProject();
const originalCwd = process.cwd();
await fs.mkdir(path.join(projectDir, "packages"));
try {
process.chdir(projectDir);
await expect(getSourceBasePath("packages/*/src/**/*.py")).resolves.toBe(
"packages",
);
} finally {
process.chdir(originalCwd);
}
});
it("does not treat brace expansion as glob syntax", async () => {
await expect(getSourceBasePath("src/{a,b}/*.py")).resolves.toBe(
"src/{a,b}",
);
});
});
-151
View File
@@ -1,151 +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,
getRuffVersionFromUvLockContent,
} = 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");
});
it("reads the version from uv.lock", () => {
const result = getRuffVersionFromFile("__tests__/fixtures/uv.lock");
expect(result).toBe("0.9.5");
});
});
describe("getRuffVersionFromUvLockContent", () => {
it("extracts the ruff version from uv.lock content", () => {
const result = getRuffVersionFromUvLockContent(
'[[package]]\nname = "other"\nversion = "1.0.0"\n\n[[package]]\nname = "ruff"\nversion = "0.9.5"\n',
);
expect(result).toBe("0.9.5");
});
it("returns undefined when ruff is not in uv.lock", () => {
const result = getRuffVersionFromUvLockContent(
'[[package]]\nname = "other"\nversion = "1.0.0"\n',
);
expect(result).toBeUndefined();
});
});
});
@@ -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");
});
});
+7 -13
View File
@@ -7,37 +7,31 @@ inputs:
required: false required: false
default: "check" default: "check"
src: src:
description: "Source path(s) or @actions/glob pattern(s) to run Ruff on. Defaults to the current workspace." description: "Source to run Ruff. Defaults to the current directory."
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, requirements.txt or uv.lock 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
download-from-astral-mirror:
description: "Download Ruff from the Astral mirror instead of directly from GitHub Releases."
required: false
default: "true"
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 -1
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": {
Generated Vendored
-32251
View File
File diff suppressed because one or more lines are too long
Generated Vendored
+39265
View File
File diff suppressed because one or more lines are too long
-30126
View File
File diff suppressed because one or more lines are too long
+37093
View File
File diff suppressed because one or more lines are too long
+12
View File
@@ -0,0 +1,12 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
collectCoverageFrom: ["src/**/*.ts", "!src/**/*.d.ts"],
moduleFileExtensions: ["ts", "js"],
preset: "ts-jest",
roots: ["<rootDir>/src"],
testEnvironment: "node",
testMatch: ["**/*.test.ts"],
transform: {
"^.+\\.ts$": "ts-jest",
},
};
-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"],
};
+782 -4869
View File
File diff suppressed because it is too large Load Diff
+20 -24
View File
@@ -2,18 +2,16 @@
"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", "test": "jest",
"test": "npm run build && npm run test:unit", "all": "npm ci --ignore-scripts && npm run build && npm run check && npm run test && npm run package"
"all": "npm run build && npm run check && npm run package && npm run test:unit"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -28,27 +26,25 @@
"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/glob": "^0.7.0", "@actions/tool-cache": "^2.0.2",
"@actions/tool-cache": "^4.0.0", "@octokit/core": "^7.0.3",
"@octokit/core": "^7.0.6",
"@octokit/plugin-paginate-rest": "^13.1.1", "@octokit/plugin-paginate-rest": "^13.1.1",
"@octokit/plugin-rest-endpoint-methods": "^17.0.0", "@octokit/plugin-rest-endpoint-methods": "^16.0.0",
"@renovatebot/pep440": "^4.2.1", "@renovatebot/pep440": "^4.2.1",
"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/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", "jest": "^29.7.0",
"js-yaml": "^4.1.1", "js-yaml": "^4.1.0",
"ts-jest": "^29.4.6", "ts-jest": "^29.2.5",
"typescript": "^5.9.3" "typescript": "^5.9.2"
} }
} }
-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",
});
}),
);
-922
View File
@@ -1,927 +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.16.1":
"a8df4e8e9f22e3b0ae0b9f165ddaafb7e34df692197a6c1a361e7426f90681d5",
"aarch64-pc-windows-msvc-0.16.1":
"9d591931196e46338183b2b0307f126f2071b0543ed90ad3e8d8187278ba954b",
"aarch64-unknown-linux-gnu-0.16.1":
"52b67c61ca7355a535dfab77f87a4a3ae3550191bead41c70aadded8b5dd33a4",
"aarch64-unknown-linux-musl-0.16.1":
"5929d9f37fc518a3825f33a76ad8092c0555ca045ca1dbf5e680038a402c840c",
"arm-unknown-linux-musleabihf-0.16.1":
"6159e52f130dde9332a21a73ce022fbf19331baf19c0b561834ef02821e2c39c",
"armv7-unknown-linux-gnueabihf-0.16.1":
"78d06e22c0eb1ee88d87f7f22868a5261c6301c6c1a6a6a685bcd2a0c94e66a0",
"armv7-unknown-linux-musleabihf-0.16.1":
"0b781ee2243716cab3e4523443fa0b73b90ee487ac6d61c7717bcacf39abf6e6",
"i686-pc-windows-msvc-0.16.1":
"dd912980ccb50c13ff727014125927e4720ec6f6e0402ee6f5aeb44ab8df078a",
"i686-unknown-linux-gnu-0.16.1":
"b78c5f4635d6037deea63e7c68152982e220f23e62eea09d81563fc082fd6ee1",
"i686-unknown-linux-musl-0.16.1":
"4ab0197bfb5b42ac01c8a5725317f8a73592f0e95a057ae50ba1602c5bb18bda",
"powerpc64le-unknown-linux-gnu-0.16.1":
"db72f14ff9a96b12064af6a5ae18cacfc19ce555e30b636fff3239d830b17829",
"riscv64gc-unknown-linux-gnu-0.16.1":
"2b2a71b3af2b0d7147e5dacb883a3a308d74437929162e897b30deb10a503b44",
"s390x-unknown-linux-gnu-0.16.1":
"dd9a0a1e6e88a38a1ef41f695c5dfd473e3c2e70721f44fd3cac50152c726875",
"x86_64-apple-darwin-0.16.1":
"00396fb9db4cb04e07ad277e6b10d845e6767f0a2aae67e1a57aa65fa01334f0",
"x86_64-pc-windows-msvc-0.16.1":
"f37898d133acba7df8cbd130abc3b58b98243d7f3ab751087193dde8887955c4",
"x86_64-unknown-linux-gnu-0.16.1":
"7e1cc9b3da4911bb2c98c076302fd8997d822fac74dd8f1e30371701e70a4c56",
"x86_64-unknown-linux-musl-0.16.1":
"23469683052cd2db1589f15032dd1751b2a3f212062e9fc901b0776d25fb36bc",
"aarch64-apple-darwin-0.16.0":
"ce6564491a2cc4b0659f45ee174dbef17e4dec24e03a9c03d313b5430bc21099",
"aarch64-pc-windows-msvc-0.16.0":
"5f7356bdd186a2af97e3dcd4422b481fd107ae0867cc04a2a8c150441ccd15b6",
"aarch64-unknown-linux-gnu-0.16.0":
"879d4f0ca1a7f21a4afc6ef9345118b8a75aa2bc4aae9e41e0474994d0ef0a4f",
"aarch64-unknown-linux-musl-0.16.0":
"7a6add3d38768dfa00c6d3853e9bd940b5526f3fbb76f02b1fe77ec0653f1e0e",
"arm-unknown-linux-musleabihf-0.16.0":
"a3edf70c0c265bee17b9173d332643ce5bab9ee575938555c0b2e035d3b6b6bc",
"armv7-unknown-linux-gnueabihf-0.16.0":
"6eb7ab8baf7ff9cb61e73c6c0615a637b15e8246652cd9c4d36182b5d912e641",
"armv7-unknown-linux-musleabihf-0.16.0":
"c74b5763fc29b788b06349f78752c929501106614571d52ea11434678e162744",
"i686-pc-windows-msvc-0.16.0":
"73bc7921afd23c28657a52f037760e2d12eb9aa285f56b94ce4034c100747b3a",
"i686-unknown-linux-gnu-0.16.0":
"2c22c34005beed261b7de1bd1c58e1aee5f72c66491a53bde9a456a288eb8cd8",
"i686-unknown-linux-musl-0.16.0":
"f2b38ddae2b54adf7b00237c190309f40368a64a69cb1e554a50643bb5b6703a",
"powerpc64le-unknown-linux-gnu-0.16.0":
"55eb2e715fd4b89661bd99163c8422a2829f1d790bb388fa431f7c410864b7e9",
"riscv64gc-unknown-linux-gnu-0.16.0":
"80eedc31c6f02c1ff9ea8828e7a99e7cae93bc67e8835d27c80590392d30089b",
"s390x-unknown-linux-gnu-0.16.0":
"520abb2a2baf1e639dd582b590895f055bbb7aaa0d9c574a25239ed83ffb6381",
"x86_64-apple-darwin-0.16.0":
"3d9ef6228c4eeb26d593c398b2dc5250e0f6d6425933db2993fcf30d49c78b69",
"x86_64-pc-windows-msvc-0.16.0":
"c5d1185c47261f86361d03b547da25be79120226a6f1721d623b2aba9d27668b",
"x86_64-unknown-linux-gnu-0.16.0":
"98001c995a134d95f9bc83106a7f94b552971b583f1c0ab75fb656a881e13865",
"x86_64-unknown-linux-musl-0.16.0":
"2138b7bc58ff877f5bba09aea4cc984ad5699433b6a3f811003527b8cff8e9ad",
"aarch64-apple-darwin-0.15.22":
"a2881af26fd1d19f4932c4ddf1e70b4e0efcf48513c5dae082564e03f0b467a3",
"aarch64-pc-windows-msvc-0.15.22":
"76a57fe257602c386499437071a16abb1ee51fff68c8b4c28b0bf8a0d9f7aa34",
"aarch64-unknown-linux-gnu-0.15.22":
"54ec426d839d7cea1096e9ea1c5486fd2f3df62ee6cfd71dc090b18f99bebd90",
"aarch64-unknown-linux-musl-0.15.22":
"88feb2dc2fafb92482185201f4d483134fbaab10370c79512d3dd372f1d3f28b",
"arm-unknown-linux-musleabihf-0.15.22":
"dfc790d7d1bb5c3ea2162ebfec45262e88c8f78903709ca54e15b1d21ac1c7b6",
"armv7-unknown-linux-gnueabihf-0.15.22":
"b31c1f0e3b2c24372a58d09a45889fd350a02f4b7cbf39ce239b0f3fa6635479",
"armv7-unknown-linux-musleabihf-0.15.22":
"c948d4cec399959773d6ac04fc92794d44dbf6a23d3a4e0411ef7d51afe7f494",
"i686-pc-windows-msvc-0.15.22":
"a30a9a9aa89f582d12b14a10235de48a08656539fe1c5949ab915130718073b6",
"i686-unknown-linux-gnu-0.15.22":
"e9af79223f3cdbd64dce693bd8ed1d52dec7408eb43c510f19e280b13679bf62",
"i686-unknown-linux-musl-0.15.22":
"fbfc808162931f6bc30c641eaf6f568880fa230f915f3e7bd89912e6b275be65",
"powerpc64le-unknown-linux-gnu-0.15.22":
"3760b7a4d2f5141ad4186c54545b77d93e7b05ecbcce8fd633b564ec215a8595",
"riscv64gc-unknown-linux-gnu-0.15.22":
"b411cea3bec3c2d6c01ceb6425a613cc91e3b1b6542d1661902d224c343dc550",
"s390x-unknown-linux-gnu-0.15.22":
"5493483cecb481eece55b2565d91395a24c51af62d43c380246fde1cb9d3b56a",
"x86_64-apple-darwin-0.15.22":
"687a9ceb88ab85dab061026d5017218225a481121b1a40862cc8f92b56f18090",
"x86_64-pc-windows-msvc-0.15.22":
"6e5419593984941405e9add902e89c6ea4af87d97919ac5ef82e1bc4e43bbd8d",
"x86_64-unknown-linux-gnu-0.15.22":
"d535a4be6504146e757eff67b992f11a293a7a108be22e2a5898b32c32565996",
"x86_64-unknown-linux-musl-0.15.22":
"853462df618fb9f156a9f04d9d899d9e5a9e1a43fbffaec72df4bb159348e71b",
"aarch64-apple-darwin-0.15.21":
"0452f9d5da6e8051d332cf21ae82a608d8e2cfeec5a71a46ffa9e50adbb2381d",
"aarch64-pc-windows-msvc-0.15.21":
"52f05487f1e9e5c4d20b06e3659104507f23ef184e5b194c600fba945226e196",
"aarch64-unknown-linux-gnu-0.15.21":
"9846136be7fe5b70351d5bde22fd21d4b3ab55b07c9793fdf190040b296ee9a3",
"aarch64-unknown-linux-musl-0.15.21":
"2ec7c0077431f96f74c3c72aea6505e902bc2ff47127653c8de1389ee30a3cb3",
"arm-unknown-linux-musleabihf-0.15.21":
"7c7f7a917119513f6c98a27fd2539a6fc0443aec70866c07b46763f2ba4bb802",
"armv7-unknown-linux-gnueabihf-0.15.21":
"e491d6b6fd77e6e878b55589b67db3ec5f2769908fb7db5e6c67ad468499da44",
"armv7-unknown-linux-musleabihf-0.15.21":
"63816c926e66942ae9d3774be5cb07f895805a64afed915045d330c07523f270",
"i686-pc-windows-msvc-0.15.21":
"a5bd8669602b50559cea879c391eca5a265a2759e77dec082cdab998660bf998",
"i686-unknown-linux-gnu-0.15.21":
"5e9f9df0736c021a68315a1d4c2fe23e8f7035d78223004491ca3a780a1bb1bc",
"i686-unknown-linux-musl-0.15.21":
"239062dd642ab4a3d3daea9d1f9a87296318b415cef3e6bc3c48c4a47127d8c8",
"powerpc64le-unknown-linux-gnu-0.15.21":
"d450f2dbfdfe31ef63f433c9c62273fb5692f0f44d545569f9c66c7ceb2b19d1",
"riscv64gc-unknown-linux-gnu-0.15.21":
"28067c07327533bdc9e94e1ff9e8446cd60f3bcd8c90dd6f302a0e5061870c05",
"s390x-unknown-linux-gnu-0.15.21":
"5cd4bcc992155993ebda0363db0261f40b0119f1c781ad2e9502ba404df67bb5",
"x86_64-apple-darwin-0.15.21":
"7e6ff3bd585b5b7c47634c957ac84fb5806d3c7ab4ef0e5ec1c53ce272f489da",
"x86_64-pc-windows-msvc-0.15.21":
"035c59abfd7bd1102e0b656f5771e6ae7a712a45ef54b5ab575541c7ff7d1eb0",
"x86_64-unknown-linux-gnu-0.15.21":
"7ddba1886f39ba918587f9ca37de9651008726834811c19ee83991705bd3e56b",
"x86_64-unknown-linux-musl-0.15.21":
"7e157ff9a2e13676118c587e6db0ec02d040e415b21014346d230e64789c0e78",
"aarch64-apple-darwin-0.15.20":
"cb41c48690c113dc08470e64103ce65ca15249c6ce3495d3ac792b329a83e8c1",
"aarch64-pc-windows-msvc-0.15.20":
"6472417dfa49885bab36b92286cf933497bade1a30c816e6aa92e3adc684131b",
"aarch64-unknown-linux-gnu-0.15.20":
"f915de3ab6d31a49f4c57b1f97129f359f9348c162ea03acfa07011ba79e1197",
"aarch64-unknown-linux-musl-0.15.20":
"2e06ebae862dce74eb7b084081621f2d4183e18e60cb074e7906f862869fa1b6",
"arm-unknown-linux-musleabihf-0.15.20":
"3fab24c45421c992c1a32da52473319969ad66069fa7b8ef96632e9f6edb48cc",
"armv7-unknown-linux-gnueabihf-0.15.20":
"032288e4fc032c0c6e7d790bdc3272beac8e35870fc3153b94804e906b73b489",
"armv7-unknown-linux-musleabihf-0.15.20":
"40a085563d348fb7ce3536ac5d422a3c8efd901218138397e02515f5498d993e",
"i686-pc-windows-msvc-0.15.20":
"9f575c9b1d7c7304535ff6e271e15adb71e89c57381fbc14be91c741e70cabe7",
"i686-unknown-linux-gnu-0.15.20":
"918f5e2f8d5cc8881388ae2f8b1faaebc6a6d93f325cc203e245782374ae74da",
"i686-unknown-linux-musl-0.15.20":
"c06588ab67ed9b89bf8e9a7e8cfde5ed39afb2cc0050b89e4f05e8705be39ccd",
"powerpc64le-unknown-linux-gnu-0.15.20":
"6005f0dbcf9526737e998b0e9643b45705a2a14196bcdf57522a1eb4c1e92606",
"riscv64gc-unknown-linux-gnu-0.15.20":
"f050ed3b244aed95998865608e6c5c07ddc871a8cba7b4bd167212ba35d7b3a2",
"s390x-unknown-linux-gnu-0.15.20":
"e10200d1436f39bba9b041d8f4ed86762ce799344f0af3cf625bd398713f4d5b",
"x86_64-apple-darwin-0.15.20":
"36b91219b3aae00464e2a4fa361766abd47d0402ac88fbe6da5de44285738386",
"x86_64-pc-windows-msvc-0.15.20":
"835fdea5b8d4e13d2cc49439006aa1fbe9b67a89371f03871fc7f70a0cf10516",
"x86_64-unknown-linux-gnu-0.15.20":
"df8e74862d4cd4fdac11faf3048789896ff9898a0cacb98497df20d0a1cc7bb4",
"x86_64-unknown-linux-musl-0.15.20":
"0715560b2402024b529a4ffd6ea7d3814a090eb255a6216259fb192823be3ad8",
"aarch64-apple-darwin-0.15.19":
"c7253342f2ffbe2a0eaadaacd3424c3a24331b0307e57cc54031bc7ccd35d83a",
"aarch64-pc-windows-msvc-0.15.19":
"66e3014fe7ae4424a0e7c48b524ab03e8e39a51012ba5acf656c3771fed24c0a",
"aarch64-unknown-linux-gnu-0.15.19":
"b00f8c17424c0abea8f87ca52880b864d10ae8e5c5d09ee61730a80ee3874a97",
"aarch64-unknown-linux-musl-0.15.19":
"d6945815c5b2b32d01483e1d59ac7d2e744df0a20272680b2290c3e5e4ce5020",
"arm-unknown-linux-musleabihf-0.15.19":
"a533fbfe66f4ee6318d4c49b315c83b2dae9e474e1a66cb89148a12574654bd1",
"armv7-unknown-linux-gnueabihf-0.15.19":
"3acea3a3808e724412d9abf898bd9228c6417bd9641168394a5db98c06669302",
"armv7-unknown-linux-musleabihf-0.15.19":
"1fd42881a29964d884d822b4b3aacf5661aba43c95ce6b333defdab468ba57c5",
"i686-pc-windows-msvc-0.15.19":
"6f89edb30cae3ba71176bdeb2a94d2ec6f2ddf3a647da61c45adf9b28efabd67",
"i686-unknown-linux-gnu-0.15.19":
"82b0a2793f9bc1205f6ac2ecf8b2cfdd4fab450b7b63688d030c1276d477b0df",
"i686-unknown-linux-musl-0.15.19":
"75876128126ceccd1374059b3aea2415c48aa735c8a0e3c16e2c48bff589f16b",
"powerpc64le-unknown-linux-gnu-0.15.19":
"ce4548a179737c1e633cadd879124d762ebe6a850b6f6619467db2f822f42df1",
"riscv64gc-unknown-linux-gnu-0.15.19":
"0032c5458e4515b1f3aea15cef4870d43a49bee55345994ee598e0e7ebed166d",
"s390x-unknown-linux-gnu-0.15.19":
"86a9df0baa93b3e000283d0e019f52b7e684ae9a27cd07f3a985d159b8f1772a",
"x86_64-apple-darwin-0.15.19":
"aaf50afd2a76be2674b8dd24b305e42a33a99269c2c43e334f6a940b29ca291c",
"x86_64-pc-windows-msvc-0.15.19":
"e996f09c5aa5b00675318f0cdb59628633a202219d9c929736c5dcaaad2a6c4d",
"x86_64-unknown-linux-gnu-0.15.19":
"0bfabff71f317e0e3b8c363043b888f045eff092073d5d9bd0ac7f8ede465711",
"x86_64-unknown-linux-musl-0.15.19":
"682158b99b14ce60b62e7b4585100a11ebd5716355efe4048760690eda21fb99",
"aarch64-apple-darwin-0.15.18":
"f0cc66c2df37f2a11c99a85b59b8fde21b37f61df062404a0afaed387b56dad0",
"aarch64-pc-windows-msvc-0.15.18":
"344835d508df8bd5fd30a2941a7cee95b9b4f0f6342886b5d52b89571614c0d2",
"aarch64-unknown-linux-gnu-0.15.18":
"c9636879455e940fc92edb95dd6e475d683da833987d1f0c147f39e521ae98a2",
"aarch64-unknown-linux-musl-0.15.18":
"941eb7a7b8bc11f195a08393bb45def1656003031cff89af3805ca89cfd066b4",
"arm-unknown-linux-musleabihf-0.15.18":
"e70e827e863e0c5efea70661c61fafc068ec630ecac529ac8921d6c83ba5bae2",
"armv7-unknown-linux-gnueabihf-0.15.18":
"7cbdcb38059ad6c4cad1ea61e7411eb550510c8688a045aa4aee7aff3c743dc3",
"armv7-unknown-linux-musleabihf-0.15.18":
"24e9f8ae7ca03639871299a9a55439593564ed6d6572dbc3649aa0d3bf35da19",
"i686-pc-windows-msvc-0.15.18":
"d33ccad071478e3faa106506a53c0a62780abbb6d8c3c1ed29a36d4ec6a13168",
"i686-unknown-linux-gnu-0.15.18":
"8ee90592d7b22bbb81c7ebbd08adcc7ba66154046e8539d793bfcb9b4e681dd1",
"i686-unknown-linux-musl-0.15.18":
"9854c1fd59e876d229075f81dee88fb29f88bdc1aed9bca5e899810dd640db33",
"powerpc64le-unknown-linux-gnu-0.15.18":
"6efa78ce85071f72f37f77fcdc51b39fd75efd795baaf1fe1d85a3d7cb1a2c1a",
"riscv64gc-unknown-linux-gnu-0.15.18":
"d9b15d6fcd2931b0d88c8e56fd49f67ce7d1d362ad4b2623678849da5638f423",
"s390x-unknown-linux-gnu-0.15.18":
"5699eba448816315ce7fb9489512f7823d5dc872d997641dbbc984c07a8a4799",
"x86_64-apple-darwin-0.15.18":
"229d2c192718fde76794185ab9339515b75a55cc1337e4ca9d60309e2c506900",
"x86_64-pc-windows-msvc-0.15.18":
"2c38db5fef5984c93db7bdc395c20c5c3470fafdd8deb72150db4d2da7870d07",
"x86_64-unknown-linux-gnu-0.15.18":
"1ffb1d5e04e60347be17c514cbb19303d6d8c75f629060ed481a150337dbf060",
"x86_64-unknown-linux-musl-0.15.18":
"9ca54aec52cfdbe1e708cc975e41e3e736a2c1ebe76668575efe09e0701465c8",
"aarch64-apple-darwin-0.15.17":
"81f372886fb7a0056949356c615fa689a091cf79b0b54ed914c810cdbc6d85e9",
"aarch64-pc-windows-msvc-0.15.17":
"827124e08fe4633e76a3186fb9e881485faf2057d8c12dd98924474d8b7d4493",
"aarch64-unknown-linux-gnu-0.15.17":
"71593a6ca85cfede1743b9163aa4531a273f0eed6ae6c99d26ffe2af51bb5a3d",
"aarch64-unknown-linux-musl-0.15.17":
"211d610dd81ef472d3b1404b28eaa19a62cb41ab9374584847307b97821a4ab8",
"arm-unknown-linux-musleabihf-0.15.17":
"0ad5f2c15c85f334e846396c261dc18c850ed8288c8ca58fbf736783fb38ff48",
"armv7-unknown-linux-gnueabihf-0.15.17":
"6a6abb8e81ee6e9fc1d389e39986698dcd2c97c98f983e6d0b1466791f9ebd64",
"armv7-unknown-linux-musleabihf-0.15.17":
"97e60f2f92df99b4a80a12fe97316e2f403336515e7719d61d9fae6cca9c84fa",
"i686-pc-windows-msvc-0.15.17":
"50b5ba7faa811d38993b3cde5d038480fa76c5af6b4183bfc28c9ad863c47a6b",
"i686-unknown-linux-gnu-0.15.17":
"105dbbb8d898cb7ffb5f5464af3126d4be7dd1a1e58d802d482bb7a3b9712393",
"i686-unknown-linux-musl-0.15.17":
"8803e601e51bb4e0fb6747272339394014d8447f231effb414e7937b9ea12888",
"powerpc64le-unknown-linux-gnu-0.15.17":
"202fc52df8cc69d8ffdadc9bc4f68af987f9991bf153d676db3e116aa7f82e89",
"riscv64gc-unknown-linux-gnu-0.15.17":
"cdd72afde5bbfc119fee54f215696db870aeb4893ae3c134c8dc83af3a69d9c0",
"s390x-unknown-linux-gnu-0.15.17":
"f73297ab1ed5b1bb733a04ad4bc87a30b0a8c16843d7b0981b8b9e1a3495ea3f",
"x86_64-apple-darwin-0.15.17":
"5522e517bd67ee8c2cb0d6a8298388d8edc8621a0bc5dc773f65e06c953693f6",
"x86_64-pc-windows-msvc-0.15.17":
"08b6cb2bd20f3d19e3cf6e768d8d8105c8337de1c52e90c7dc1deea483d68c86",
"x86_64-unknown-linux-gnu-0.15.17":
"7bca4641db4b0256d20f3a8c38057725e0982965639f5f9a0fb2f9aece4b7c4f",
"x86_64-unknown-linux-musl-0.15.17":
"cbf85fc152b7fc25cb27f0233b325e9c72cb26965152050164e7608b72515189",
"aarch64-apple-darwin-0.15.16":
"9deb6c4c38c9324bfd98ea4efb40946aae99a987e7f9d1308a1291f14f6b46e2",
"aarch64-pc-windows-msvc-0.15.16":
"d2654d546fa03618e4ce39a568e78a0ccda71319928832266009b286f57354f8",
"aarch64-unknown-linux-gnu-0.15.16":
"a39b83af7288da13c013a789af1a4f875562ba09fc8011a2825a4883cb3a97a9",
"aarch64-unknown-linux-musl-0.15.16":
"3b39fc2608e12c554d42f6950a5ea131db8d8a0736c8949b3581c361f9f011d4",
"arm-unknown-linux-musleabihf-0.15.16":
"30d04d1f068a443fe6a919a9f0a77a3e59deab515b6b60c07a678d94332d444b",
"armv7-unknown-linux-gnueabihf-0.15.16":
"1f32fb51a347b70faa7d248ed524954736a79d3fd00887f79ee8c22004c8cca5",
"armv7-unknown-linux-musleabihf-0.15.16":
"5d14928f26c14bb40c7ec0b9db1f7e05e6e6b21ffd499dafcd77d0575fa796d2",
"i686-pc-windows-msvc-0.15.16":
"a68887cad08f7cf47e3753b1c84add837a64d9070102f43ec29d2a9fc8ed144a",
"i686-unknown-linux-gnu-0.15.16":
"3afb75f98244d7aec2532b5140da8ec9f2f9422ae27d7ba42bd6f8b20d76547e",
"i686-unknown-linux-musl-0.15.16":
"6fa9f045b0893456476836333410af37e52c32ecd511046d177802aeed42b2aa",
"powerpc64le-unknown-linux-gnu-0.15.16":
"adeee21392ed843e1e30e64a1b238d844c00385ac983fbc231b786cfe5d3bee7",
"riscv64gc-unknown-linux-gnu-0.15.16":
"e8e04a9d0664bd78e5164be9376091dbeefc455cdb684814a949dd7ebf5daa88",
"s390x-unknown-linux-gnu-0.15.16":
"2bd5e6a045f19a593b269d362c07b4bcc68b57b73d2ada1eecf3618d0cde0237",
"x86_64-apple-darwin-0.15.16":
"a8a7a60182f66b5995bd16e1831cf013e89558edf52135a1b6646f155f491f98",
"x86_64-pc-windows-msvc-0.15.16":
"b6c843fa84afb37af9d665a418324782ae18ea3dd1de775abfd7cf17f9431f85",
"x86_64-unknown-linux-gnu-0.15.16":
"b949a853b208b0f818a150fb06490487db585a15becaeda28483dd662939030a",
"x86_64-unknown-linux-musl-0.15.16":
"f52d90f8a6b1b3ad7d74301c3c796652e851d8f05b6ba26d139f05f4838cf412",
"aarch64-apple-darwin-0.15.15":
"2f7063e09d3c2af019b56f4fc6548199851e9387f359d696111af312adfe7619",
"aarch64-pc-windows-msvc-0.15.15":
"801067abc7c499bda61ff7308a271f7b5258c1a9f90abb081d9ac099cee20720",
"aarch64-unknown-linux-gnu-0.15.15":
"d334ae13383977fdaf754e6ea3d85c7b3ccbd510c713463eca73d0cd7151fe12",
"aarch64-unknown-linux-musl-0.15.15":
"46ea2750c0f21dab01f853d55a521a5f388f8573dab698735effdc6a94c9b293",
"arm-unknown-linux-musleabihf-0.15.15":
"daaf7d2b228c6b60ec30a3fefe37552d1b2ad50fc47d416dfbe04e80c78e4017",
"armv7-unknown-linux-gnueabihf-0.15.15":
"752ed1eef8c0b3550c21e9682a4eb873dfe1e08e0c23ad59a1ff57212e2665b2",
"armv7-unknown-linux-musleabihf-0.15.15":
"dda544c4890d46ec5d0b4a553087036ba9c50df6f566c54b38f8612185cd49c9",
"i686-pc-windows-msvc-0.15.15":
"57abdd72d19a6dc81e1583b40fcc5c88584cbee4406e257c8002e4ab4f825429",
"i686-unknown-linux-gnu-0.15.15":
"0c9ea8fa37a517cd833c8cdc26646ae2321d0f1f5f4359b00c3ae14b39c09f5b",
"i686-unknown-linux-musl-0.15.15":
"37a77abef48e92d43755c268ae04cc395538f630a9f295de9d2afd401965551d",
"powerpc64le-unknown-linux-gnu-0.15.15":
"ea7ba10a14ec3ef32275359cb8200775b6875f949352df3438f0c8de5b4ab514",
"riscv64gc-unknown-linux-gnu-0.15.15":
"486c47b6441134b0f8e31cd4b5e12cc8d4501e5c19ea1d68523c60b7300d4344",
"s390x-unknown-linux-gnu-0.15.15":
"d1834ddb58e5e73c6aa4c080bc3e1da4f4789150bb2d1d74824ff1bb52d5ddf9",
"x86_64-apple-darwin-0.15.15":
"e43d7c03701c7a436e18da455baf29e4be3379fb70887c4dc65091dfa73c1747",
"x86_64-pc-windows-msvc-0.15.15":
"c336db29a9e9234310212bc0d0bec1f722ec44f8a62c77b3f96a300282c6ff6d",
"x86_64-unknown-linux-gnu-0.15.15":
"2c50f95a0f553731c87f8dc413465aa06059a0dc21dfe16786682db95ffeb9dc",
"x86_64-unknown-linux-musl-0.15.15":
"c09b24fdec07d8ea73f7cbb32c38eb4f36f52298ac445dccf763171c898d8fc0",
"aarch64-apple-darwin-0.15.14":
"953243859467ed3f4f2bc6c11e241cd42a11ed42605b33d971dc2b174772da7d",
"aarch64-pc-windows-msvc-0.15.14":
"2f7a3fc221ca307c441306eb8f66740bfa129835a921862d124474080311933e",
"aarch64-unknown-linux-gnu-0.15.14":
"9740a687b333e5e9b3764a9d09001268bd841c93140a0766ddceef53fd1ccd87",
"aarch64-unknown-linux-musl-0.15.14":
"1693dfc1f402a81b32ff4fee5f8b494857a1a857a2b8d63cad88d9d39c201297",
"arm-unknown-linux-musleabihf-0.15.14":
"3107f1e38cd07aad9567b1eba6fcce3d7f7af2c011dc665c9f733c650e92f1c7",
"armv7-unknown-linux-gnueabihf-0.15.14":
"f198a9b46f860d1eba5fdd869fe12611f1828a564bd55b00a70cd8ade791717b",
"armv7-unknown-linux-musleabihf-0.15.14":
"2cb7aa2aa1b3f0762e69a76d0e636f39d9a87f554c24a705c2b9c05b0bb824e0",
"i686-pc-windows-msvc-0.15.14":
"c746d60bccd1a38e78893f683bb8b3043e1c6cd4ebe7fc4d7c11ead639808634",
"i686-unknown-linux-gnu-0.15.14":
"34f99c35d9efbb4491f252ba565edc5051a879371829ee05b9f8b96432dff466",
"i686-unknown-linux-musl-0.15.14":
"bb8b59953ccfb08a60c07666e07c1b830c4907ac8d0036bdef416ee614dd5fc7",
"powerpc64le-unknown-linux-gnu-0.15.14":
"a25f2979686c99d47c1c4fb4a7676e015ac492682fdff7b05c078e253bd22c70",
"riscv64gc-unknown-linux-gnu-0.15.14":
"757f852cfd037bae48186bbb78bf683197a21599d3c0f063c2f7d3be6f3a5d80",
"s390x-unknown-linux-gnu-0.15.14":
"d8dcf7282d04eb44bb0717892605c46d9a26d35c641819cdef77b3a6b9b6213a",
"x86_64-apple-darwin-0.15.14":
"537f569780fc71f63238f1f43dc04285b24fddbc6eea7219a9a18f408bdd869d",
"x86_64-pc-windows-msvc-0.15.14":
"c589a77b0e112c374e9a5e0c42a7fb18d8725c5322ae261e343f66d1c87f0a5e",
"x86_64-unknown-linux-gnu-0.15.14":
"e9fe1bb63fb8950cf8596d069f5c61b5a962bdd6c75edbdd2c8320871c40d6dd",
"x86_64-unknown-linux-musl-0.15.14":
"e380c9cf14764b7134434c49d445d13c6e3db732cfd18d72c4bc2316a6dbb82e",
"aarch64-apple-darwin-0.15.13":
"66ab3abeeba319266612e675dfbe0ca9c63f1e5f6ca3c6174482b0b088b8f6ff",
"aarch64-pc-windows-msvc-0.15.13":
"b16d436afac64f8032831d2f7874fe5fa6b1b238bfae97924d2233904372deed",
"aarch64-unknown-linux-gnu-0.15.13":
"8b8ee2a0f7e9ace1a734b2993ab2bfd68112d8a9c151ffa3025dbccaed4da52f",
"aarch64-unknown-linux-musl-0.15.13":
"1084d8038eeb51f55c7b7c9779bb37e5f53b28b987767df61333e9d0da7b2701",
"arm-unknown-linux-musleabihf-0.15.13":
"2b5b01e5e4ea6b72d75f20b7eac05305b29057e983c42839d62a7a5b9059867a",
"armv7-unknown-linux-gnueabihf-0.15.13":
"e579f3068d8011dc8fb845e3939407facb2fb129a5bc31c7db216a5a0e9e15d6",
"armv7-unknown-linux-musleabihf-0.15.13":
"d470b0fa4ba9cf94675dd8769f44bcdd16970fa6d8f48c34eb576299638a0742",
"i686-pc-windows-msvc-0.15.13":
"418907ac98fed6d3a4011d9c3f4b6bc525d4572aa926368f7b37311bb163b15c",
"i686-unknown-linux-gnu-0.15.13":
"8f7dc9bf2f7e1cbb6cbcb009d3759614085a32658271aff97eec6f51fa456eab",
"i686-unknown-linux-musl-0.15.13":
"0ea01a5614197f7058af27fd067ae8a178f6d2e3ce3114bf22f74921289cd38a",
"powerpc64le-unknown-linux-gnu-0.15.13":
"bd07a50f51069f130c8a66472260a6acc88a81735f92c40b5c735119f4155cc2",
"riscv64gc-unknown-linux-gnu-0.15.13":
"e689a47ef17dd49c75d957b11f95b0bc102d8240817e209c0fea2907ea638a71",
"s390x-unknown-linux-gnu-0.15.13":
"879a0888431b78fce4bd934d794921723d549c558021b8f7b3fb952be05abf53",
"x86_64-apple-darwin-0.15.13":
"3ff692a61d5231ea6c94e6dafc575f87582a089d1281fac6b31cef2b4f2e4b6b",
"x86_64-pc-windows-msvc-0.15.13":
"59892c6773df805effe7928e69796447585b42af54310cb6f1552536b746c39a",
"x86_64-unknown-linux-gnu-0.15.13":
"fd09d74c60963714b9b0fbda226c669e85624e5a8488398b3e0d0b5ee7684dd1",
"x86_64-unknown-linux-musl-0.15.13":
"66ac0b0582c8d24badc210b8e933e9aa0ad6707414e236409c9b9ad638972fb9",
"aarch64-apple-darwin-0.15.12":
"bd59107e125f9c9f64a75be13b6ff21bf7cbc61c4b9529bbb75c8184e3dd6c3c",
"aarch64-pc-windows-msvc-0.15.12":
"310a94d0c708691905d7614385732a2cbdede0979b93b81c01d55ab9de7ae754",
"aarch64-unknown-linux-gnu-0.15.12":
"4dcd4cd8beb525d04294eeab67d84d199cce2c44b66858978ca729b694fe29ff",
"aarch64-unknown-linux-musl-0.15.12":
"8996fe01814d00480c81da2b8ce9e8274f63eb07992318f082f7071772699635",
"arm-unknown-linux-musleabihf-0.15.12":
"aca8dadd6bd0e7c2192bbe5cbb39efdd5bbca47460c0ef73e6102a8c858890c6",
"armv7-unknown-linux-gnueabihf-0.15.12":
"3b8278e41d29ecac9f9ae07d95f8dc6707c45a28322503ea48d8c0cae53056b4",
"armv7-unknown-linux-musleabihf-0.15.12":
"d60776dd376f82f8bbbf346e1fbb81ef844a6445068b0594e36102c1121f57e9",
"i686-pc-windows-msvc-0.15.12":
"a466127b868dc22095dc717856cc94f283a01ccffa3a9814d35b5d42661e73be",
"i686-unknown-linux-gnu-0.15.12":
"64c4c076d6cc45f65b058bf5e2c3dd3150f80964a5a47f76c4245cdaa40ff7bb",
"i686-unknown-linux-musl-0.15.12":
"44462b0989328e7f7bbf4396e6d18152909ffeb43f49991e5b2f9fb28738af6d",
"powerpc64le-unknown-linux-gnu-0.15.12":
"36d42837ce7526663c707572c91615f4b1457c5d22f6ec4f96ea8921f83cc38d",
"riscv64gc-unknown-linux-gnu-0.15.12":
"69e5f9d477ea238f7fd00c2ac39ea78a3d7c7369124f62620a8c8a6b195d4f3a",
"s390x-unknown-linux-gnu-0.15.12":
"a612eaae54928a170403de322ab659d82fc233a3d1bb0337c8c39b48e9e5b41f",
"x86_64-apple-darwin-0.15.12":
"f88b6ad8338ad7acf83497e4bc2f2b5d3cac46c3e71363b7263c1bb9027c9b37",
"x86_64-pc-windows-msvc-0.15.12":
"d378033392042377491ef2541457af47cc4652c4e984639c95413b0c4876b0f1",
"x86_64-unknown-linux-gnu-0.15.12":
"5e26a7811f7db364864ace00cced53003556a37b63f3c987e340b18207776f1c",
"x86_64-unknown-linux-musl-0.15.12":
"e0b074f722f87efe2e09de3a77820a012d1417ebabbd308cccdf6b98ba8d4bb3",
"aarch64-apple-darwin-0.15.11":
"89cd4370b5c5d7ab01f436bd5d878293c9858e4e964a3da3013c49549198dc8b",
"aarch64-pc-windows-msvc-0.15.11":
"35ee4e8f0e784100aa1f3325705f2a764319d673fbeb8ba74ce169d4da13a675",
"aarch64-unknown-linux-gnu-0.15.11":
"6c7004eb732951ecce2059b3d5ef93010f8410ececaf96c24dbac41155780eee",
"aarch64-unknown-linux-musl-0.15.11":
"4d196496a51d4bbefc4a2a65049364c920b470afd320befa20516e59d1c065e2",
"arm-unknown-linux-musleabihf-0.15.11":
"caf1d8a5fbeb85389f9aa354bb9e5444f8d16ce7c6e5483681ef83ec6f9f7aa5",
"armv7-unknown-linux-gnueabihf-0.15.11":
"c8733ca5191f6503853a1f908e005ce9f2984ed58623f7544c56c28ce5d737db",
"armv7-unknown-linux-musleabihf-0.15.11":
"758d7fa7f7258dce3074a17432d6425bd6eec3be0a0558bf46729bbcf011cae7",
"i686-pc-windows-msvc-0.15.11":
"4af40e2bd1c6044ab1e0cb94ed836773237a4d88697a72c0babb16aede6e352f",
"i686-unknown-linux-gnu-0.15.11":
"db2c037d01bef8a5bc790f10fb7bfe82d97d287b5cda88acdd3a8d0bfcf20069",
"i686-unknown-linux-musl-0.15.11":
"c0c8456deb03520b4c8cea4f7dcd6e1fbaaa810a751b031fa79db09c41eae800",
"powerpc64le-unknown-linux-gnu-0.15.11":
"d68248684191d453897040fe5aab170a5bea7298fdaf0796f6eafa17d820e3cf",
"riscv64gc-unknown-linux-gnu-0.15.11":
"33beb06489d7b4b8295c0a696ef2125afe6f31e8e9355ae61bff6aa46b118702",
"s390x-unknown-linux-gnu-0.15.11":
"8b20efbb2a004fa27be6fda2c821a82d55cfaae5a0878ad210a12d976e3f0b60",
"x86_64-apple-darwin-0.15.11":
"c6f2c36fc5f3d14ffc2db1e205aa69756de485771f39848afe848c16b8a1a648",
"x86_64-pc-windows-msvc-0.15.11":
"d1f76eac07c279aafc594d9ba4c6f658f7cb433002aeeadbe2959229fc368881",
"x86_64-unknown-linux-gnu-0.15.11":
"8018f4c746c98e3a4128295c5da294d8b04c2bb14e1868e2da6fd02faf93ebe4",
"x86_64-unknown-linux-musl-0.15.11":
"ea286f17b66054a3aac2672158b9ac194030d89043ff108357edd9b20123219a",
"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": "aarch64-apple-darwin-0.14.11":
"c3fab6bcad9cc2f8a342829dd4ea011c54b9671d023b71baac1cbf2b3526cefd", "c3fab6bcad9cc2f8a342829dd4ea011c54b9671d023b71baac1cbf2b3526cefd",
"aarch64-pc-windows-msvc-0.14.11": "aarch64-pc-windows-msvc-0.14.11":
+143 -143
View File
@@ -2,16 +2,16 @@ 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 { 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,48 +32,31 @@ 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,
downloadFromAstralMirror = true,
): 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 (!artifact) { if (platform === "pc-windows-msvc") {
throw new Error( extension = ".zip";
getMissingArtifactMessage(version, arch, platform, manifestUrl),
);
} }
const downloadUrl = constructDownloadUrl(version, platform, arch);
core.debug(`Downloading ruff from "${downloadUrl}" ...`);
// For the default astral-sh/versions source, checksum validation relies on const downloadPath = await tc.downloadTool(
// user input or the built-in KNOWN_CHECKSUMS table, not manifest sha256 values. downloadUrl,
const resolvedChecksum = undefined,
manifestUrl === undefined githubToken,
? checksum
: resolveChecksum(checksum, artifact.checksum);
const downloadPath = await downloadArtifact(
artifact.downloadUrl,
platform,
arch,
version,
getDownloadToken(artifact.downloadUrl, githubToken),
downloadFromAstralMirror,
);
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(
@@ -82,67 +65,25 @@ export async function downloadVersion(
version, version,
arch, arch,
); );
return { cachedToolDir, version }; return { cachedToolDir, version: version };
} }
/** function constructDownloadUrl(
* Rewrite a GitHub Releases URL to the Astral mirror. version: string,
* Returns `undefined` if the URL does not match the expected GitHub prefix.
*/
export function rewriteToMirror(url: string): string | undefined {
if (!url.startsWith(GITHUB_RELEASES_PREFIX)) {
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 =
downloadFromAstralMirror: boolean, semver.lte(version, "v0.4.10") && semver.gte(version, "v0.1.8")
): Promise<string> { ? `-${version}`
const mirrorUrl = downloadFromAstralMirror : "";
? rewriteToMirror(downloadUrl) const artifact = `ruff${artifactVersionSuffix}-${arch}-${platform}`;
: undefined; let extension = ".tar.gz";
const resolvedDownloadUrl = mirrorUrl ?? downloadUrl; if (platform === "pc-windows-msvc") {
extension = ".zip";
try {
return await downloadFile(
resolvedDownloadUrl,
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(
@@ -157,7 +98,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")) {
@@ -170,57 +111,116 @@ async function extractDownloadedArtifact(
return ruffDir; return ruffDir;
} }
function getMissingArtifactMessage( export async function resolveVersion(
version: string, versionInput: string,
arch: Architecture,
platform: Platform,
manifestUrl?: string,
): string {
if (manifestUrl === undefined) {
return `Could not find artifact for version ${version}, arch ${arch}, platform ${platform} in ${VERSIONS_MANIFEST_URL} .`;
}
return `manifest-file does not contain version ${version}, arch ${arch}, platform ${platform}.`;
}
function resolveChecksum(
checksum: string | undefined,
manifestChecksum: string,
): string {
return checksum !== undefined && checksum !== ""
? checksum
: manifestChecksum;
}
function getDownloadToken(
downloadUrl: string,
githubToken: string, githubToken: string,
): string | undefined { ): Promise<string> {
return downloadUrl.startsWith(GITHUB_RELEASES_PREFIX) core.debug(`Resolving ${versionInput}...`);
? githubToken const version =
: undefined; versionInput === "latest"
? 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;
} }
function constructDownloadUrl( async function getAvailableVersions(githubToken: string): Promise<string[]> {
try {
const octokit = new PaginatingOctokit({
auth: githubToken,
});
return await getReleaseTagNames(octokit);
} catch (err) {
if ((err as 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);
}
throw err;
}
}
async function getReleaseTagNames(
octokit: InstanceType<typeof PaginatingOctokit>,
): Promise<string[]> {
const response = await octokit.paginate(octokit.rest.repos.listReleases, {
owner: OWNER,
repo: REPO,
});
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);
}
async function getLatestVersion(githubToken: string) {
const octokit = new PaginatingOctokit({
auth: githubToken,
});
let latestRelease: { tag_name: string } | undefined;
try {
latestRelease = await getLatestRelease(octokit);
} catch (err) {
if ((err as 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.",
);
throw err;
}
}
if (!latestRelease) {
throw new Error("Could not determine latest release.");
}
return latestRelease.tag_name;
}
async function getLatestRelease(
octokit: InstanceType<typeof PaginatingOctokit>,
) {
const { data: latestRelease } = await octokit.rest.repos.getLatestRelease({
owner: OWNER,
repo: REPO,
});
return latestRelease;
}
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 artifact = `ruff${artifactVersionSuffix}-${arch}-${platform}`;
const versionPrefix = semver.lte(version, "v0.4.10") ? "v" : "";
return `${GITHUB_RELEASES_PREFIX}${versionPrefix}${normalizedVersion}/${artifact}${getExtension(platform)}`;
} }
const maxPep440 = pep440.maxSatisfying(versions, version);
function stripVersionPrefix(version: string): string { if (maxPep440 !== null) {
return version.startsWith("v") ? version.slice(1) : version; core.debug(
`Found a version that satisfies the pep440 specifier: ${maxPep440}`,
);
return maxPep440;
} }
return undefined;
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(", ");
}
+47 -41
View File
@@ -1,17 +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,
downloadFromAstralMirror,
githubToken, githubToken,
manifestFile,
src, src,
version, version,
versionFile as versionFileInput, versionFile as versionFileInput,
@@ -22,12 +22,7 @@ import {
getPlatform, getPlatform,
type Platform, type Platform,
} from "./utils/platforms"; } from "./utils/platforms";
import { import { getRuffVersionFromRequirementsFile } from "./utils/pyproject";
expandSourceInput,
getVersionSourceDirectory,
splitInput,
} from "./utils/source-input";
import { resolveRuffVersion } from "./version/resolve";
async function run(): Promise<void> { async function run(): Promise<void> {
const platform = getPlatform(); const platform = getPlatform();
@@ -40,13 +35,7 @@ async function run(): Promise<void> {
if (arch === undefined) { if (arch === undefined) {
throw new Error(`Unsupported architecture: ${process.arch}`); throw new Error(`Unsupported architecture: ${process.arch}`);
} }
const setupResult = await setupRuff( const setupResult = await setupRuff(platform, arch, checkSum, githubToken);
platform,
arch,
checkSum,
githubToken,
downloadFromAstralMirror,
);
addRuffToPath(setupResult.ruffDir); addRuffToPath(setupResult.ruffDir);
setOutputFormat(); setOutputFormat();
@@ -54,7 +43,11 @@ async function run(): Promise<void> {
core.setOutput("ruff-version", setupResult.version); core.setOutput("ruff-version", setupResult.version);
core.info(`Successfully installed ruff version ${setupResult.version}`); core.info(`Successfully installed ruff version ${setupResult.version}`);
await runRuff(path.join(setupResult.ruffDir, "ruff"), args, src); await runRuff(
path.join(setupResult.ruffDir, "ruff"),
args.split(" "),
src.split(" "),
);
process.exit(0); process.exit(0);
} catch (err) { } catch (err) {
@@ -67,10 +60,8 @@ async function setupRuff(
arch: Architecture, arch: Architecture,
checkSum: string | undefined, checkSum: string | undefined,
githubToken: string, githubToken: string,
downloadFromAstralMirror: boolean,
): 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",
@@ -91,8 +82,6 @@ async function setupRuff(
resolvedVersion, resolvedVersion,
checkSum, checkSum,
githubToken, githubToken,
manifestUrl,
downloadFromAstralMirror,
); );
return { return {
@@ -102,17 +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: await getVersionSourceDirectory( }
src, if (version !== "") {
version, return await resolveVersion(version, githubToken);
versionFileInput, }
), if (versionFileInput !== "") {
version, const versionFromPyproject =
versionFile: versionFileInput, getRuffVersionFromRequirementsFile(versionFileInput);
workspaceRoot: process.env.GITHUB_WORKSPACE || ".", 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 {
@@ -126,23 +133,22 @@ 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[],
src: string, src: string[],
): Promise<void> { ): Promise<void> {
const execArgs = [...splitInput(args), ...(await expandSourceInput(src))]; const execArgs = [...args, ...src];
await exec.exec(ruffExecutablePath, execArgs); await exec.exec(ruffExecutablePath, execArgs);
} }
+2 -2
View File
@@ -10,9 +10,9 @@ 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 octokit = new PaginatingOctokit({ auth: github_token });
const response = await octokit.paginate(octokit.rest.repos.listReleases, { const response = await octokit.paginate(octokit.rest.repos.listReleases, {
owner: OWNER, owner: OWNER,
-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,
});
-12
View File
@@ -6,15 +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");
export const downloadFromAstralMirror = getBooleanInput(
"download-from-astral-mirror",
true,
);
function getBooleanInput(name: string, defaultValue: boolean): boolean {
if (core.getInput(name) === "") {
return defaultValue;
}
return core.getBooleanInput(name);
}
-74
View File
@@ -1,74 +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 source path
* and traversing upwards through parent directories until reaching
* the GitHub workspace root.
*
* If the source path points to a file, the search begins in that file's
* parent directory.
*
* @param startPath The source path to start the search from (file or directory)
* @param workspaceRoot The GitHub workspace directory (GITHUB_WORKSPACE)
* @returns The path to the found pyproject.toml, or undefined if not found
*/
export function findPyprojectToml(
startPath: string,
workspaceRoot: string,
): string | undefined {
let currentDir = resolveStartDirectory(startPath);
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;
}
}
function resolveStartDirectory(startPath: string): string {
const resolvedStartPath = path.resolve(startPath);
if (!fs.existsSync(resolvedStartPath)) {
return resolvedStartPath;
}
const stats = fs.statSync(resolvedStartPath);
return stats.isDirectory()
? resolvedStartPath
: path.dirname(resolvedStartPath);
}
/**
* 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);
}
+188
View File
@@ -0,0 +1,188 @@
import * as core from "@actions/core";
import { findRuffVersionInSpec } from "./pyproject";
jest.mock("@actions/core", () => ({
info: jest.fn(),
warning: jest.fn(),
}));
describe("findRuffVersionInSpec", () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe("ruff dependency strings", () => {
it("should extract version from 'ruff==0.9.3'", () => {
const result = findRuffVersionInSpec("ruff==0.9.3");
expect(result).toBe("0.9.3");
expect(core.info).toHaveBeenCalledWith(
"Found ruff version in requirements file: 0.9.3",
);
expect(core.warning).not.toHaveBeenCalled();
});
it("should extract version from 'ruff>=0.14'", () => {
const result = findRuffVersionInSpec("ruff>=0.14");
expect(result).toBe(">=0.14");
expect(core.warning).not.toHaveBeenCalled();
});
it("should extract version from 'ruff ~=1.0.0'", () => {
const result = findRuffVersionInSpec("ruff ~=1.0.0");
expect(result).toBe("~=1.0.0");
expect(core.warning).not.toHaveBeenCalled();
});
it("should extract version from 'ruff>=0.14,<1.0'", () => {
const result = findRuffVersionInSpec("ruff>=0.14,<1.0");
expect(result).toBe(">=0.14,<1.0");
expect(core.warning).not.toHaveBeenCalled();
});
it("should extract 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(core.warning).not.toHaveBeenCalled();
});
it("should return undefined for non-ruff dependency 'another-dep 0.1.6'", () => {
const result = findRuffVersionInSpec("another-dep 0.1.6");
expect(result).toBeUndefined();
expect(core.info).not.toHaveBeenCalled();
expect(core.warning).not.toHaveBeenCalled();
});
it("should return undefined for non-ruff dependency 'another-dep==0.1.6'", () => {
const result = findRuffVersionInSpec("another-dep==0.1.6");
expect(result).toBeUndefined();
expect(core.info).not.toHaveBeenCalled();
expect(core.warning).not.toHaveBeenCalled();
});
it("should strip trailing backslash", () => {
const result = findRuffVersionInSpec("ruff==0.9.3 \\");
expect(result).toBe("0.9.3");
expect(core.info).toHaveBeenCalledWith(
"Found ruff version in requirements file: 0.9.3",
);
expect(core.warning).not.toHaveBeenCalled();
});
it("should strip trailing backslash with whitespace", () => {
const result = findRuffVersionInSpec(" ruff==0.9.3 \\ ");
expect(result).toBe("0.9.3");
expect(core.warning).not.toHaveBeenCalled();
});
});
describe("environment markers", () => {
it("should strip python_version environment marker", () => {
const result = findRuffVersionInSpec(
'ruff>=0.14 ; python_version >= "3.11"',
);
expect(result).toBe(">=0.14");
expect(core.info).toHaveBeenCalledWith(
"Found ruff version in requirements file: >=0.14",
);
expect(core.warning).toHaveBeenCalledWith(
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
);
});
it("should strip sys_platform environment marker", () => {
const result = findRuffVersionInSpec(
"ruff==0.9.3 ; sys_platform == 'linux'",
);
expect(result).toBe("0.9.3");
expect(core.warning).toHaveBeenCalledWith(
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
);
});
it("should strip multiple environment markers", () => {
const result = findRuffVersionInSpec(
'ruff>=0.14 ; python_version >= "3.11" and sys_platform == "linux"',
);
expect(result).toBe(">=0.14");
expect(core.warning).toHaveBeenCalledWith(
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
);
});
it("should handle environment markers with multiple constraints", () => {
const result = findRuffVersionInSpec(
'ruff>=0.14,<1.0 ; python_version >= "3.11"',
);
expect(result).toBe(">=0.14,<1.0");
expect(core.warning).toHaveBeenCalledWith(
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
);
});
});
describe("edge cases", () => {
it("should handle whitespace", () => {
const result = findRuffVersionInSpec(" ruff >=0.14 ");
expect(result).toBe(">=0.14");
expect(core.warning).not.toHaveBeenCalled();
});
it("should handle whitespace with environment markers", () => {
const result = findRuffVersionInSpec(
" ruff >=0.14 ; python_version >= '3.11' ",
);
expect(result).toBe(">=0.14");
expect(core.warning).toHaveBeenCalled();
});
it("should return undefined for empty string", () => {
const result = findRuffVersionInSpec("");
expect(result).toBeUndefined();
expect(core.info).not.toHaveBeenCalled();
expect(core.warning).not.toHaveBeenCalled();
});
it("should return undefined for whitespace only", () => {
const result = findRuffVersionInSpec(" ");
expect(result).toBeUndefined();
expect(core.info).not.toHaveBeenCalled();
expect(core.warning).not.toHaveBeenCalled();
});
it("should return undefined for just semicolon", () => {
const result = findRuffVersionInSpec(";");
expect(result).toBeUndefined();
expect(core.info).not.toHaveBeenCalled();
expect(core.warning).not.toHaveBeenCalled();
});
it("should handle exact example from issue #256", () => {
const result = findRuffVersionInSpec(
'ruff>=0.14 ; python_version >= "3.11"',
);
expect(result).toBe(">=0.14");
expect(core.info).toHaveBeenCalledWith(
"Found ruff version in requirements file: >=0.14",
);
expect(core.warning).toHaveBeenCalledWith(
"Environment markers are ignored. ruff is a standalone tool that works independently of Python version.",
);
});
it("should handle single-quoted environment markers", () => {
const result = findRuffVersionInSpec(
"ruff>=0.14 ; python_version >= '3.11'",
);
expect(result).toBe(">=0.14");
expect(core.warning).toHaveBeenCalled();
});
it("should handle double-quoted environment markers", () => {
const result = findRuffVersionInSpec(
'ruff>=0.14 ; python_version >= "3.11"',
);
expect(result).toBe(">=0.14");
expect(core.warning).toHaveBeenCalled();
});
});
});
+130
View File
@@ -0,0 +1,130 @@
import * as fs from "node:fs";
import * as core from "@actions/core";
import * as toml from "smol-toml";
/**
* Find ruff version in a dependency specification.
* Only handles strings that start with "ruff" (e.g., "ruff==0.9.3").
* Returns undefined for non-ruff dependencies.
* Strips environment markers (everything after ';').
* Strips leading '==' from exact version specifiers (PEP 440) for downstream compatibility.
*
* @internal This is exported for testing purposes only.
*/
export function findRuffVersionInSpec(spec: string): string | undefined {
const trimmedSpec = spec.trim();
const fullDepMatch = trimmedSpec.match(/^ruff\s*(.+)$/);
let versionSpec: string;
if (fullDepMatch) {
versionSpec = fullDepMatch[1];
} else {
return undefined;
}
// Strip trailing backslash (line continuation)
versionSpec = versionSpec.replace(/\\$/, "").trim();
// Strip environment markers (everything after ';')
const match = versionSpec.match(/^([^;]+)(?:;.*)?$/);
if (match) {
let version = match[1].trim();
if (version) {
// Strip leading '==' from exact version specifiers for compatibility with semver
if (version.startsWith("==")) {
version = version.slice(2);
}
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;
}
function getRuffVersionFromAllDependencies(
allDependencies: string[],
): string | undefined {
return allDependencies
.map((dep) => findRuffVersionInSpec(dep))
.find((version) => version !== 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 (typeof spec === "string") {
return findRuffVersionInSpec(`${name} ${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;
}
}
-161
View File
@@ -1,161 +0,0 @@
import * as fs from "node:fs/promises";
import * as path from "node:path";
import * as glob from "@actions/glob";
const GLOB_PATTERN = /[*?[]/;
/**
* Split an action input into whitespace-delimited tokens.
*
* The action has historically documented both `args` and `src` as plain
* whitespace-delimited inputs. Keep that behavior instead of invoking a shell.
*/
export function splitInput(input: string): string[] {
return input.trim().split(/\s+/).filter(Boolean);
}
/**
* Expand glob patterns from the `src` input in a cross-platform way.
*
* Glob syntax is defined by @actions/glob. This action does not emulate shell
* expansion. Unmatched patterns are preserved so Ruff can report the missing
* path.
*/
export async function expandSourceInput(srcInput: string): Promise<string[]> {
const sources = splitInput(srcInput);
if (sources.length === 0) {
return [];
}
const expandedSources: string[] = [];
for (const source of sources) {
if (!hasGlobPattern(source)) {
expandedSources.push(source);
continue;
}
const matches = await globMatches(source);
expandedSources.push(...(matches.length > 0 ? matches : [source]));
}
return expandedSources;
}
/**
* Resolve the starting path for version discovery from the `src` input.
*
* Takes the first whitespace-delimited token. Literal paths are preserved. Glob
* patterns use their first sorted match when available, so pyproject.toml
* discovery starts from a path Ruff will actually check. Unmatched globs fall
* back to @actions/glob's search path.
*/
export async function getVersionSourceDirectory(
srcInput: string,
versionInput: string | undefined,
versionFileInput: string | undefined,
): Promise<string> {
if (hasInput(versionInput) || hasInput(versionFileInput)) {
return ".";
}
return await getSourceBasePath(srcInput);
}
export async function getSourceBasePath(srcInput: string): Promise<string> {
const firstSource = splitInput(srcInput)[0] ?? ".";
if (!hasGlobPattern(firstSource)) {
return stripTrailingSeparators(firstSource);
}
const globber = await createGlobber(firstSource);
const matches = (await globber.glob()).sort();
if (matches.length > 0) {
return (
(await preserveSourcePathStyle(firstSource, [matches[0] ?? "."]))[0] ??
"."
);
}
const searchPaths = globber.getSearchPaths().sort();
if (searchPaths.length > 0) {
return (
(
await preserveSourcePathStyle(firstSource, [searchPaths[0] ?? "."])
)[0] ?? "."
);
}
return ".";
}
function hasInput(input: string | undefined): boolean {
return input !== undefined && input.trim() !== "";
}
function hasGlobPattern(source: string): boolean {
return GLOB_PATTERN.test(source);
}
function stripTrailingSeparators(source: string): string {
const root = path.parse(source).root;
const strippedSource = source.replace(/[/\\]+$/, "");
if (root !== "" && strippedSource.length < root.length) {
return root;
}
return strippedSource || root || ".";
}
async function globMatches(pattern: string): Promise<string[]> {
const globber = await createGlobber(pattern);
const matches = await globber.glob();
return await preserveSourcePathStyle(pattern, matches.sort());
}
async function createGlobber(pattern: string): Promise<glob.Globber> {
return await glob.create(normalizeGlobPattern(pattern), {
excludeHiddenFiles: !explicitlyTargetsHiddenPath(pattern),
followSymbolicLinks: false,
implicitDescendants: false,
});
}
async function preserveSourcePathStyle(
pattern: string,
matches: string[],
): Promise<string[]> {
if (path.isAbsolute(pattern)) {
return matches;
}
const cwd = await fs.realpath(process.cwd());
return matches.map((match) => path.relative(cwd, match) || ".");
}
function explicitlyTargetsHiddenPath(pattern: string): boolean {
return splitSourcePath(pattern).some(isHiddenPathSegment);
}
function splitSourcePath(source: string): string[] {
const root = path.parse(source).root;
const relativePath = source.slice(root.length);
const separatorPattern = process.platform === "win32" ? /[/\\]+/ : /\/+/;
return relativePath.split(separatorPattern).filter(Boolean);
}
function isHiddenPathSegment(segment: string): boolean {
return segment.startsWith(".") && segment !== "." && segment !== "..";
}
function normalizeGlobPattern(pattern: string): string {
if (process.platform !== "win32") {
return pattern;
}
return pattern.replace(/\\/g, "/");
}
-209
View File
@@ -1,209 +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> }>;
};
};
}
interface UvLock {
package?: Array<{ name?: string; version?: string }>;
}
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: "uv.lock",
parse: (filePath) => {
const fileContent = fs.readFileSync(filePath, "utf-8");
return getRuffVersionFromUvLockContent(fileContent);
},
supports: (filePath) => filePath.endsWith("uv.lock"),
},
{
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;
}
export function getRuffVersionFromUvLockContent(
uvLockContent: string,
): string | undefined {
const uvLock = toml.parse(uvLockContent) as UvLock;
const ruffPackage = (uvLock.package || []).find((pkg) => pkg.name === "ruff");
if (ruffPackage?.version === undefined) {
return undefined;
}
core.info(`Found ruff version in uv.lock: ${ruffPackage.version}`);
return ruffPackage.version;
}
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" | "uv.lock";
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"]
} }