mirror of
https://github.com/astral-sh/ruff-action.git
synced 2026-08-08 02:07:02 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd60375bed |
@@ -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
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "nodenext",
|
||||
"moduleResolution": "nodenext",
|
||||
"target": "es2022",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["check-all-tests-passed-needs.ts"]
|
||||
}
|
||||
@@ -39,13 +39,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@54f647b7e1bb85c95cddabcd46b0c578ec92bc1a # v4.36.3
|
||||
uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
source-root: src
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
uses: github/codeql-action/autobuild@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
@@ -71,4 +71,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5
|
||||
|
||||
@@ -17,8 +17,6 @@ jobs:
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: 🚀 Run Release Drafter
|
||||
uses: release-drafter/release-drafter@c2e2804cc59f45f57076a99af580d0fedb697927 # v7.3.0
|
||||
with:
|
||||
commitish: ${{ github.sha }}
|
||||
uses: release-drafter/release-drafter@b1476f6e6eb133afa41ed8589daba6dc69b4d3f5 # v6.1.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -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
@@ -15,27 +15,22 @@ permissions: {}
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write # for zizmor
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Actionlint
|
||||
uses: eifinger/actionlint-action@1fc89649be682d16ec5cf65ea16e269eb88d3982 # v1.10.2
|
||||
- name: Run zizmor
|
||||
uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: eifinger/actionlint-action@447fbfe7533062b7a9ea55f790f2396fba6d052a # v1.10.0
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
node-version: "20"
|
||||
- run: |
|
||||
npm ci --ignore-scripts
|
||||
- run: |
|
||||
npm run all
|
||||
- name: Check all jobs are in all-tests-passed.needs
|
||||
run: |
|
||||
tsc -p tsconfig.json
|
||||
tsc check-all-tests-passed-needs.ts
|
||||
node check-all-tests-passed-needs.js
|
||||
working-directory: .github/scripts
|
||||
- name: Make sure no changes from linters are detected
|
||||
@@ -47,7 +42,7 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, macos-14, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- 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"]
|
||||
os: [ ubuntu-latest, ubuntu-24.04-arm, macos-latest, macos-14, windows-latest ]
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use version ${{ matrix.ruff-version }}
|
||||
@@ -73,7 +68,7 @@ jobs:
|
||||
test-unsupported-version:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Try install old version
|
||||
@@ -93,7 +88,7 @@ jobs:
|
||||
test-version-from-version-file-pyproject:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use version from pyproject.toml
|
||||
@@ -112,7 +107,7 @@ jobs:
|
||||
test-default-version-from-pyproject:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use default version from pyproject.toml
|
||||
@@ -122,7 +117,7 @@ jobs:
|
||||
src: __tests__/fixtures/python-project
|
||||
- name: Correct version gets installed
|
||||
run: |
|
||||
if [ "$RUFF_VERSION" != "0.6.2" ]; then
|
||||
if [ "$RUFF_VERSION" != "0.14.11" ]; then
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
@@ -130,7 +125,7 @@ jobs:
|
||||
test-default-version-from-pyproject-dev-group:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use default version from pyproject.toml dev group
|
||||
@@ -149,7 +144,7 @@ jobs:
|
||||
test-default-version-from-pyproject-dependency-groups:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use default version from pyproject.toml dependency groups
|
||||
@@ -168,7 +163,7 @@ jobs:
|
||||
test-default-version-from-pyproject-dependency-groups-with-env-marker:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- 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
|
||||
- name: Correct version gets installed
|
||||
run: |
|
||||
if [ "$RUFF_VERSION" != "0.13.3" ]; then
|
||||
echo "Expected version 0.13.3 but got $RUFF_VERSION"
|
||||
if [ "$RUFF_VERSION" != "0.14.11" ]; then
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
@@ -188,7 +182,7 @@ jobs:
|
||||
test-default-version-from-pyproject-poetry-groups:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use default version from pyproject.toml poetry group dependencies
|
||||
@@ -207,7 +201,7 @@ jobs:
|
||||
test-default-version-from-pyproject-poetry:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use default version from pyproject.toml poetry dependencies
|
||||
@@ -226,7 +220,7 @@ jobs:
|
||||
test-default-version-from-pyproject-optional-dependencies:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use default version from pyproject.toml optional dependencies
|
||||
@@ -245,7 +239,7 @@ jobs:
|
||||
test-default-version-from-requirements:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use default version from requirements.txt
|
||||
@@ -264,7 +258,7 @@ jobs:
|
||||
test-default-version-from-requirements-with-hash:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use default version from requirements-with-hash.txt
|
||||
@@ -280,29 +274,10 @@ jobs:
|
||||
fi
|
||||
env:
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use version 0.7
|
||||
@@ -321,7 +296,7 @@ jobs:
|
||||
test-pep440-version-specifier:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Install version 0.9.10
|
||||
@@ -347,7 +322,7 @@ jobs:
|
||||
- os: macos-latest
|
||||
checksum: "af9583bff12afbca5d5670334e0187dd60c4d91bc71317d1b2dde70cb1200ba9"
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Checksum matches expected
|
||||
@@ -359,7 +334,7 @@ jobs:
|
||||
test-with-explicit-token:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use default version
|
||||
@@ -373,7 +348,7 @@ jobs:
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use args
|
||||
@@ -384,7 +359,7 @@ jobs:
|
||||
test-failure:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Format should fail
|
||||
@@ -414,41 +389,13 @@ jobs:
|
||||
fi
|
||||
env:
|
||||
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:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Use args
|
||||
@@ -458,50 +405,6 @@ jobs:
|
||||
src: >-
|
||||
__tests__/fixtures/python-project/src/python_project/__init__.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:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -520,18 +423,13 @@ jobs:
|
||||
- test-default-version-from-pyproject-optional-dependencies
|
||||
- test-default-version-from-requirements
|
||||
- test-default-version-from-requirements-with-hash
|
||||
- test-default-version-from-uv-lock
|
||||
- test-semver-range
|
||||
- test-pep440-version-specifier
|
||||
- test-checksum
|
||||
- test-with-explicit-token
|
||||
- test-args
|
||||
- test-failure
|
||||
- test-glob-src
|
||||
- test-glob-single-star-src
|
||||
- test-multiple-src
|
||||
- test-parent-directory-pyproject
|
||||
- test-custom-manifest-file
|
||||
if: always()
|
||||
steps:
|
||||
- name: All tests passed
|
||||
|
||||
@@ -11,21 +11,20 @@ jobs:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
- uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version-file: .nvmrc
|
||||
cache: npm
|
||||
node-version: "20"
|
||||
- name: Update known checksums
|
||||
id: update-known-checksums
|
||||
run:
|
||||
node dist/update-known-checksums/index.cjs
|
||||
node dist/update-known-checksums/index.js
|
||||
src/download/checksum/known-checksums.ts ${{ secrets.GITHUB_TOKEN }}
|
||||
- run: npm ci --ignore-scripts && npm run all
|
||||
- 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:
|
||||
commit-message: "chore: update known checksums"
|
||||
title:
|
||||
|
||||
@@ -16,9 +16,9 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
persist-credentials: true
|
||||
persist-credentials: false
|
||||
- name: Update Major Minor Tags
|
||||
run: |
|
||||
set -x
|
||||
|
||||
@@ -20,84 +20,51 @@ anything `ruff` can (ex, fix).
|
||||
- [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 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)
|
||||
- [GitHub authentication token](#github-authentication-token)
|
||||
- [Outputs](#outputs)
|
||||
|
||||
## Usage
|
||||
|
||||
| Input | Description | Default |
|
||||
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------------|
|
||||
| `version` | The version of Ruff to install. See [Install specific versions](#install-specific-versions) | discovered from `pyproject.toml`, else `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 |
|
||||
| `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` |
|
||||
| `src` | Source path(s) to run `ruff` on. Supports glob patterns. | [github.workspace] |
|
||||
| `checksum` | The sha256 checksum of the downloaded artifact. | None |
|
||||
| `github-token` | The GitHub token to use when downloading Ruff release artifacts from GitHub. | `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).
|
||||
| Input | Description | Default |
|
||||
|----------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------------|
|
||||
| `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 |
|
||||
| `args` | The arguments to pass to the `ruff` command. See [Configuring Ruff] | `check` |
|
||||
| `src` | The directory or single files to run `ruff` on. | [github.workspace] |
|
||||
| `checksum` | The sha256 checksum of the downloaded executable. | None |
|
||||
| `github-token` | The GitHub token to use for authentication. | `GITHUB_TOKEN` |
|
||||
|
||||
### Basic
|
||||
|
||||
```yaml
|
||||
- uses: astral-sh/ruff-action@v4.0.0
|
||||
- uses: astral-sh/ruff-action@v3
|
||||
```
|
||||
|
||||
### Specify a different source directory
|
||||
|
||||
```yaml
|
||||
- uses: astral-sh/ruff-action@v4.0.0
|
||||
- uses: astral-sh/ruff-action@v3
|
||||
with:
|
||||
src: "./src"
|
||||
```
|
||||
|
||||
### Specify multiple files
|
||||
|
||||
Separate multiple `src` values with whitespace.
|
||||
|
||||
```yaml
|
||||
- uses: astral-sh/ruff-action@v4.0.0
|
||||
- uses: astral-sh/ruff-action@v3
|
||||
with:
|
||||
src: >-
|
||||
path/to/file1.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@v3
|
||||
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
|
||||
|
||||
This action adds ruff to the PATH, so you can use it in subsequent steps.
|
||||
|
||||
```yaml
|
||||
- uses: astral-sh/ruff-action@v4.0.0
|
||||
- uses: astral-sh/ruff-action@v3
|
||||
- run: ruff check --fix
|
||||
- run: ruff format
|
||||
```
|
||||
@@ -108,7 +75,7 @@ you can use the `args` input to overwrite the default value (`check`):
|
||||
|
||||
```yaml
|
||||
- name: Install ruff without running check or format
|
||||
uses: astral-sh/ruff-action@v4.0.0
|
||||
uses: astral-sh/ruff-action@v3
|
||||
with:
|
||||
args: "--version"
|
||||
```
|
||||
@@ -116,17 +83,17 @@ you can use the `args` input to overwrite the default value (`check`):
|
||||
### Use `ruff format`
|
||||
|
||||
```yaml
|
||||
- uses: astral-sh/ruff-action@v4.0.0
|
||||
- uses: astral-sh/ruff-action@v3
|
||||
with:
|
||||
args: "format --check --diff"
|
||||
```
|
||||
|
||||
### Install specific versions
|
||||
|
||||
By default this action searches upward from `src` until the workspace root to find the nearest
|
||||
`pyproject.toml` and determine the Ruff version to install. If no `pyproject.toml` file is found,
|
||||
or no Ruff version is defined in `project.dependencies`, `project.optional-dependencies`,
|
||||
`dependency-groups`, or supported Poetry dependency tables, the latest version is installed.
|
||||
By default this action looks for a pyproject.toml file in the root of the repository to determine
|
||||
the ruff version to install. If no pyproject.toml file is found, or no ruff version is defined in
|
||||
`project.dependencies`, `project.optional-dependencies`, or `dependency-groups`,
|
||||
the latest version is installed.
|
||||
|
||||
> [!NOTE]
|
||||
> 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
|
||||
- name: Install the latest version of ruff
|
||||
uses: astral-sh/ruff-action@v4.0.0
|
||||
uses: astral-sh/ruff-action@v3
|
||||
with:
|
||||
version: "latest"
|
||||
```
|
||||
@@ -144,7 +111,7 @@ or no Ruff version is defined in `project.dependencies`, `project.optional-depen
|
||||
|
||||
```yaml
|
||||
- name: Install a specific version of ruff
|
||||
uses: astral-sh/ruff-action@v4.0.0
|
||||
uses: astral-sh/ruff-action@v3
|
||||
with:
|
||||
version: "0.4.4"
|
||||
```
|
||||
@@ -157,21 +124,21 @@ to install the latest version that satisfies the range.
|
||||
|
||||
```yaml
|
||||
- name: Install a semver range of ruff
|
||||
uses: astral-sh/ruff-action@v4.0.0
|
||||
uses: astral-sh/ruff-action@v3
|
||||
with:
|
||||
version: ">=0.4.0"
|
||||
```
|
||||
|
||||
```yaml
|
||||
- name: Pinning a minor version of ruff
|
||||
uses: astral-sh/ruff-action@v4.0.0
|
||||
uses: astral-sh/ruff-action@v3
|
||||
with:
|
||||
version: "0.4.x"
|
||||
```
|
||||
|
||||
```yaml
|
||||
- name: Install a pep440-specifier-satisfying version of ruff
|
||||
uses: astral-sh/ruff-action@v4.0.0
|
||||
uses: astral-sh/ruff-action@v3
|
||||
with:
|
||||
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
|
||||
|
||||
You can specify a file to read the version from.
|
||||
Currently `pyproject.toml`, `requirements.txt` and `uv.lock` are supported. If the file cannot
|
||||
be parsed or does not contain a Ruff version, the action warns and falls back to `latest`.
|
||||
Currently `pyproject.toml` and `requirements.txt` are supported.
|
||||
|
||||
```yaml
|
||||
- name: Install a version from a specified version file
|
||||
uses: astral-sh/ruff-action@v4.0.0
|
||||
uses: astral-sh/ruff-action@v3
|
||||
with:
|
||||
version-file: "my-path/to/pyproject.toml-requirements.txt-or-uv.lock"
|
||||
```
|
||||
|
||||
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.0.0
|
||||
with:
|
||||
version: "latest"
|
||||
manifest-file: "https://example.com/ruff.ndjson"
|
||||
version-file: "my-path/to/pyproject.toml-or-requirements.txt"
|
||||
```
|
||||
|
||||
### Validate checksum
|
||||
@@ -217,7 +163,7 @@ are automatically verified by this action. The sha256 hashes can be found on the
|
||||
|
||||
```yaml
|
||||
- name: Install a specific version and validate the checksum
|
||||
uses: astral-sh/ruff-action@v4.0.0
|
||||
uses: astral-sh/ruff-action@v3
|
||||
with:
|
||||
version: "0.7.4"
|
||||
checksum: "0de731c669b9ece77e799ac3f4a160c30849752714d9775c94cc4cfaf326860c"
|
||||
@@ -225,13 +171,9 @@ are automatically verified by this action. The sha256 hashes can be found on the
|
||||
|
||||
### GitHub authentication token
|
||||
|
||||
By default, this action resolves available Ruff versions from
|
||||
[`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.
|
||||
|
||||
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.
|
||||
This action uses the GitHub API to fetch the ruff release artifacts. To avoid hitting the GitHub API
|
||||
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.
|
||||
|
||||
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)
|
||||
@@ -239,7 +181,7 @@ are not sufficient, you can provide a custom GitHub token with the necessary per
|
||||
|
||||
```yaml
|
||||
- name: Install the latest version of ruff with a custom GitHub token
|
||||
uses: astral-sh/ruff-action@v4.0.0
|
||||
uses: astral-sh/ruff-action@v3
|
||||
with:
|
||||
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"}]}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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]
|
||||
dev = [
|
||||
"ruff~=0.13 ; python_version >= '3.11'",
|
||||
"ruff>=0.14 ; python_version >= '3.11'",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
|
||||
Generated
-20
@@ -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 }
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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}",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -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
@@ -7,37 +7,31 @@ inputs:
|
||||
required: false
|
||||
default: "check"
|
||||
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
|
||||
default: ${{ github.workspace }}
|
||||
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
|
||||
default: ""
|
||||
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
|
||||
checksum:
|
||||
description: "The checksum of the ruff version to install"
|
||||
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:
|
||||
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
|
||||
default: ${{ github.token }}
|
||||
outputs:
|
||||
ruff-version:
|
||||
description: "The installed ruff version. Useful when using latest."
|
||||
runs:
|
||||
using: "node24"
|
||||
main: "dist/ruff-action/index.cjs"
|
||||
using: "node20"
|
||||
main: "dist/ruff-action/index.js"
|
||||
branding:
|
||||
icon: "code"
|
||||
color: "black"
|
||||
|
||||
+1
-1
@@ -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": {
|
||||
"actions": {
|
||||
"source": {
|
||||
|
||||
-32183
File diff suppressed because one or more lines are too long
+39265
File diff suppressed because one or more lines are too long
-30058
File diff suppressed because one or more lines are too long
+37093
File diff suppressed because one or more lines are too long
@@ -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",
|
||||
},
|
||||
};
|
||||
@@ -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"],
|
||||
};
|
||||
Generated
+782
-4869
File diff suppressed because it is too large
Load Diff
+20
-24
@@ -2,18 +2,16 @@
|
||||
"name": "ruff-action",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"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": {
|
||||
"build": "tsc --noEmit",
|
||||
"build": "tsc",
|
||||
"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)\"",
|
||||
"update-known-checksums": "RUNNER_TEMP=known_checksums node dist/update-known-checksums/index.cjs src/download/checksum/known-checksums.ts \"$(gh auth token)\"",
|
||||
"test:unit": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js",
|
||||
"test": "npm run build && npm run test:unit",
|
||||
"all": "npm run build && npm run check && npm run package && npm run test:unit"
|
||||
"update-known-checksums": "RUNNER_TEMP=known_checksums node dist/update-known-checksums/index.js src/download/checksum/known-checksums.ts \"$(gh auth token)\"",
|
||||
"test": "jest",
|
||||
"all": "npm ci --ignore-scripts && npm run build && npm run check && npm run test && npm run package"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -28,27 +26,25 @@
|
||||
"author": "@eifinger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^3.0.0",
|
||||
"@actions/exec": "^3.0.0",
|
||||
"@actions/glob": "^0.7.0",
|
||||
"@actions/tool-cache": "^4.0.0",
|
||||
"@octokit/core": "^7.0.6",
|
||||
"@actions/core": "^1.11.1",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/tool-cache": "^2.0.2",
|
||||
"@octokit/core": "^7.0.3",
|
||||
"@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",
|
||||
"smol-toml": "^1.6.0",
|
||||
"undici": "^6.24.1"
|
||||
"smol-toml": "^1.4.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.4.7",
|
||||
"@biomejs/biome": "2.1.4",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/semver": "^7.7.1",
|
||||
"esbuild": "^0.27.4",
|
||||
"jest": "^30.3.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"ts-jest": "^29.4.6",
|
||||
"typescript": "^5.9.3"
|
||||
"@types/node": "^24.2.1",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@vercel/ncc": "^0.38.3",
|
||||
"jest": "^29.7.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
}),
|
||||
);
|
||||
@@ -1,791 +1,5 @@
|
||||
// AUTOGENERATED_DO_NOT_EDIT
|
||||
export const KNOWN_CHECKSUMS: { [key: string]: string } = {
|
||||
"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":
|
||||
"c3fab6bcad9cc2f8a342829dd4ea011c54b9671d023b71baac1cbf2b3526cefd",
|
||||
"aarch64-pc-windows-msvc-0.14.11":
|
||||
|
||||
+142
-142
@@ -2,16 +2,16 @@ import { promises as fs } from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as core from "@actions/core";
|
||||
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 {
|
||||
ASTRAL_MIRROR_PREFIX,
|
||||
GITHUB_RELEASES_PREFIX,
|
||||
TOOL_CACHE_NAME,
|
||||
VERSIONS_MANIFEST_URL,
|
||||
} from "../utils/constants";
|
||||
import { OWNER, REPO, TOOL_CACHE_NAME } from "../utils/constants";
|
||||
import type { Architecture, Platform } from "../utils/platforms";
|
||||
import { validateChecksum } from "./checksum/checksum";
|
||||
import { getArtifact } from "./manifest";
|
||||
|
||||
const PaginatingOctokit = Octokit.plugin(paginateRest, restEndpointMethods);
|
||||
|
||||
export function tryGetFromToolCache(
|
||||
arch: Architecture,
|
||||
@@ -32,48 +32,31 @@ export async function downloadVersion(
|
||||
platform: Platform,
|
||||
arch: Architecture,
|
||||
version: string,
|
||||
checksum: string | undefined,
|
||||
checkSum: string | undefined,
|
||||
githubToken: string,
|
||||
manifestUrl?: string,
|
||||
downloadFromAstralMirror = true,
|
||||
): Promise<{ version: string; cachedToolDir: string }> {
|
||||
const artifact = await getArtifact(version, arch, platform, manifestUrl);
|
||||
|
||||
if (!artifact) {
|
||||
throw new Error(
|
||||
getMissingArtifactMessage(version, arch, platform, manifestUrl),
|
||||
);
|
||||
const artifact = `ruff-${arch}-${platform}`;
|
||||
let extension = ".tar.gz";
|
||||
if (platform === "pc-windows-msvc") {
|
||||
extension = ".zip";
|
||||
}
|
||||
const downloadUrl = constructDownloadUrl(version, platform, arch);
|
||||
core.debug(`Downloading ruff from "${downloadUrl}" ...`);
|
||||
|
||||
// For the default astral-sh/versions source, checksum validation relies on
|
||||
// user input or the built-in KNOWN_CHECKSUMS table, not manifest sha256 values.
|
||||
const resolvedChecksum =
|
||||
manifestUrl === undefined
|
||||
? 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,
|
||||
const downloadPath = await tc.downloadTool(
|
||||
downloadUrl,
|
||||
undefined,
|
||||
githubToken,
|
||||
);
|
||||
core.debug(`Downloaded ruff to "${downloadPath}"`);
|
||||
await validateChecksum(checkSum, downloadPath, arch, platform, version);
|
||||
|
||||
const extractedDir = await extractDownloadedArtifact(
|
||||
version,
|
||||
downloadPath,
|
||||
getExtension(platform),
|
||||
extension,
|
||||
platform,
|
||||
`ruff-${arch}-${platform}`,
|
||||
artifact,
|
||||
);
|
||||
|
||||
const cachedToolDir = await tc.cacheDir(
|
||||
@@ -82,67 +65,25 @@ export async function downloadVersion(
|
||||
version,
|
||||
arch,
|
||||
);
|
||||
return { cachedToolDir, version };
|
||||
return { cachedToolDir, version: version };
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite a GitHub Releases URL to the Astral mirror.
|
||||
* 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,
|
||||
function constructDownloadUrl(
|
||||
version: string,
|
||||
platform: Platform,
|
||||
arch: Architecture,
|
||||
version: string,
|
||||
githubToken: string | undefined,
|
||||
downloadFromAstralMirror: boolean,
|
||||
): Promise<string> {
|
||||
const mirrorUrl = downloadFromAstralMirror
|
||||
? rewriteToMirror(downloadUrl)
|
||||
: undefined;
|
||||
const resolvedDownloadUrl = mirrorUrl ?? downloadUrl;
|
||||
|
||||
try {
|
||||
return await downloadFile(
|
||||
resolvedDownloadUrl,
|
||||
mirrorUrl !== undefined ? undefined : githubToken,
|
||||
);
|
||||
} catch (err) {
|
||||
if (mirrorUrl === undefined) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
core.warning(
|
||||
`Failed to download from mirror, falling back to GitHub Releases: ${(err as Error).message}`,
|
||||
);
|
||||
|
||||
return await downloadFile(
|
||||
constructDownloadUrl(version, platform, arch),
|
||||
githubToken,
|
||||
);
|
||||
): string {
|
||||
const artifactVersionSuffix =
|
||||
semver.lte(version, "v0.4.10") && semver.gte(version, "v0.1.8")
|
||||
? `-${version}`
|
||||
: "";
|
||||
const artifact = `ruff${artifactVersionSuffix}-${arch}-${platform}`;
|
||||
let extension = ".tar.gz";
|
||||
if (platform === "pc-windows-msvc") {
|
||||
extension = ".zip";
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
const versionPrefix = semver.lte(version, "v0.4.10") ? "v" : "";
|
||||
return `https://github.com/${OWNER}/${REPO}/releases/download/${versionPrefix}${version}/${artifact}${extension}`;
|
||||
}
|
||||
|
||||
async function extractDownloadedArtifact(
|
||||
@@ -157,7 +98,7 @@ async function extractDownloadedArtifact(
|
||||
const fullPathWithExtension = `${downloadPath}${extension}`;
|
||||
await fs.copyFile(downloadPath, 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 {
|
||||
ruffDir = await tc.extractTar(downloadPath);
|
||||
if (semver.gte(version, "v0.5.0")) {
|
||||
@@ -170,57 +111,116 @@ async function extractDownloadedArtifact(
|
||||
return ruffDir;
|
||||
}
|
||||
|
||||
function getMissingArtifactMessage(
|
||||
version: 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} .`;
|
||||
export async function resolveVersion(
|
||||
versionInput: string,
|
||||
githubToken: string,
|
||||
): Promise<string> {
|
||||
core.debug(`Resolving ${versionInput}...`);
|
||||
const version =
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return `manifest-file does not contain version ${version}, arch ${arch}, platform ${platform}.`;
|
||||
if (!latestRelease) {
|
||||
throw new Error("Could not determine latest release.");
|
||||
}
|
||||
return latestRelease.tag_name;
|
||||
}
|
||||
|
||||
function resolveChecksum(
|
||||
checksum: string | undefined,
|
||||
manifestChecksum: string,
|
||||
): string {
|
||||
return checksum !== undefined && checksum !== ""
|
||||
? checksum
|
||||
: manifestChecksum;
|
||||
async function getLatestRelease(
|
||||
octokit: InstanceType<typeof PaginatingOctokit>,
|
||||
) {
|
||||
const { data: latestRelease } = await octokit.rest.repos.getLatestRelease({
|
||||
owner: OWNER,
|
||||
repo: REPO,
|
||||
});
|
||||
return latestRelease;
|
||||
}
|
||||
|
||||
function getDownloadToken(
|
||||
downloadUrl: string,
|
||||
githubToken: string,
|
||||
): string | undefined {
|
||||
return downloadUrl.startsWith(GITHUB_RELEASES_PREFIX)
|
||||
? githubToken
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function constructDownloadUrl(
|
||||
function maxSatisfying(
|
||||
versions: string[],
|
||||
version: string,
|
||||
platform: Platform,
|
||||
arch: Architecture,
|
||||
): string {
|
||||
const normalizedVersion = stripVersionPrefix(version);
|
||||
const artifactVersionSuffix =
|
||||
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)}`;
|
||||
}
|
||||
|
||||
function stripVersionPrefix(version: string): string {
|
||||
return version.startsWith("v") ? version.slice(1) : version;
|
||||
}
|
||||
|
||||
function getExtension(platform: Platform): string {
|
||||
return platform === "pc-windows-msvc" ? ".zip" : ".tar.gz";
|
||||
): 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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -1,17 +1,17 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import * as core from "@actions/core";
|
||||
import * as exec from "@actions/exec";
|
||||
import * as semver from "semver";
|
||||
import {
|
||||
downloadVersion,
|
||||
resolveVersion,
|
||||
tryGetFromToolCache,
|
||||
} from "./download/download-version";
|
||||
import {
|
||||
args,
|
||||
checkSum,
|
||||
downloadFromAstralMirror,
|
||||
githubToken,
|
||||
manifestFile,
|
||||
src,
|
||||
version,
|
||||
versionFile as versionFileInput,
|
||||
@@ -22,12 +22,7 @@ import {
|
||||
getPlatform,
|
||||
type Platform,
|
||||
} from "./utils/platforms";
|
||||
import {
|
||||
expandSourceInput,
|
||||
getVersionSourceDirectory,
|
||||
splitInput,
|
||||
} from "./utils/source-input";
|
||||
import { resolveRuffVersion } from "./version/resolve";
|
||||
import { getRuffVersionFromRequirementsFile } from "./utils/pyproject";
|
||||
|
||||
async function run(): Promise<void> {
|
||||
const platform = getPlatform();
|
||||
@@ -40,13 +35,7 @@ async function run(): Promise<void> {
|
||||
if (arch === undefined) {
|
||||
throw new Error(`Unsupported architecture: ${process.arch}`);
|
||||
}
|
||||
const setupResult = await setupRuff(
|
||||
platform,
|
||||
arch,
|
||||
checkSum,
|
||||
githubToken,
|
||||
downloadFromAstralMirror,
|
||||
);
|
||||
const setupResult = await setupRuff(platform, arch, checkSum, githubToken);
|
||||
|
||||
addRuffToPath(setupResult.ruffDir);
|
||||
setOutputFormat();
|
||||
@@ -54,7 +43,11 @@ async function run(): Promise<void> {
|
||||
core.setOutput("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);
|
||||
} catch (err) {
|
||||
@@ -67,10 +60,8 @@ async function setupRuff(
|
||||
arch: Architecture,
|
||||
checkSum: string | undefined,
|
||||
githubToken: string,
|
||||
downloadFromAstralMirror: boolean,
|
||||
): Promise<{ ruffDir: string; version: string }> {
|
||||
const resolvedVersion = await determineVersion();
|
||||
const manifestUrl = manifestFile || undefined;
|
||||
if (semver.lt(resolvedVersion, "v0.0.247")) {
|
||||
throw Error(
|
||||
"This action does not support ruff versions older than 0.0.247",
|
||||
@@ -91,8 +82,6 @@ async function setupRuff(
|
||||
resolvedVersion,
|
||||
checkSum,
|
||||
githubToken,
|
||||
manifestUrl,
|
||||
downloadFromAstralMirror,
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -102,17 +91,35 @@ async function setupRuff(
|
||||
}
|
||||
|
||||
async function determineVersion(): Promise<string> {
|
||||
return await resolveRuffVersion({
|
||||
manifestFile: manifestFile || undefined,
|
||||
sourceDirectory: await getVersionSourceDirectory(
|
||||
src,
|
||||
version,
|
||||
versionFileInput,
|
||||
),
|
||||
version,
|
||||
versionFile: versionFileInput,
|
||||
workspaceRoot: process.env.GITHUB_WORKSPACE || ".",
|
||||
});
|
||||
if (versionFileInput !== "" && version !== "") {
|
||||
throw Error("It is not allowed to specify both version and version-file");
|
||||
}
|
||||
if (version !== "") {
|
||||
return await resolveVersion(version, githubToken);
|
||||
}
|
||||
if (versionFileInput !== "") {
|
||||
const versionFromPyproject =
|
||||
getRuffVersionFromRequirementsFile(versionFileInput);
|
||||
if (versionFromPyproject === undefined) {
|
||||
core.warning(
|
||||
`Could not parse version from ${versionFileInput}. Using latest version.`,
|
||||
);
|
||||
}
|
||||
return await resolveVersion(versionFromPyproject || "latest", githubToken);
|
||||
}
|
||||
const pyProjectPath = path.join(src, "pyproject.toml");
|
||||
if (!fs.existsSync(pyProjectPath)) {
|
||||
core.info(`Could not find ${pyProjectPath}. Using latest version.`);
|
||||
return await resolveVersion("latest", githubToken);
|
||||
}
|
||||
const versionFromPyproject =
|
||||
getRuffVersionFromRequirementsFile(pyProjectPath);
|
||||
if (versionFromPyproject === undefined) {
|
||||
core.info(
|
||||
`Could not parse version from ${pyProjectPath}. Using latest version.`,
|
||||
);
|
||||
}
|
||||
return await resolveVersion(versionFromPyproject || "latest", githubToken);
|
||||
}
|
||||
|
||||
function addRuffToPath(cachedPath: string): void {
|
||||
@@ -126,23 +133,22 @@ function setOutputFormat() {
|
||||
}
|
||||
|
||||
function addMatchers(): void {
|
||||
const actionRoot = getActionRoot();
|
||||
const matchersPath = path.join(actionRoot, ".github", "matchers");
|
||||
const matchersPath = path.join(
|
||||
__dirname,
|
||||
`..${path.sep}..`,
|
||||
".github",
|
||||
"matchers",
|
||||
);
|
||||
core.info(`##[add-matcher]${path.join(matchersPath, "check.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(
|
||||
ruffExecutablePath: string,
|
||||
args: string,
|
||||
src: string,
|
||||
args: string[],
|
||||
src: string[],
|
||||
): Promise<void> {
|
||||
const execArgs = [...splitInput(args), ...(await expandSourceInput(src))];
|
||||
const execArgs = [...args, ...src];
|
||||
await exec.exec(ruffExecutablePath, execArgs);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,9 +10,9 @@ const PaginatingOctokit = Octokit.plugin(paginateRest, restEndpointMethods);
|
||||
|
||||
async function run(): Promise<void> {
|
||||
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, {
|
||||
owner: OWNER,
|
||||
|
||||
@@ -1,9 +1,3 @@
|
||||
export const REPO = "ruff";
|
||||
export const OWNER = "astral-sh";
|
||||
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/";
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
@@ -6,15 +6,3 @@ export const githubToken = core.getInput("github-token");
|
||||
export const args = core.getInput("args");
|
||||
export const src = core.getInput("src");
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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, "/");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"esModuleInterop": true,
|
||||
"isolatedModules": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"noImplicitAny": true,
|
||||
"strict": true,
|
||||
"target": "ES2022"
|
||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
|
||||
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
|
||||
"outDir": "./lib" /* Redirect output structure to the directory. */,
|
||||
"rootDir": "./src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
|
||||
"strict": true /* Enable all strict type-checking options. */,
|
||||
"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"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user